├── mocks └── cssStub.js ├── public ├── favicon.ico ├── og-img.jpg ├── icon-apple-touch-57x57.jpg ├── icon-apple-touch-72x72.jpg ├── icon-apple-touch-114x114.jpg └── icon-apple-touch-144x144.jpg ├── src ├── assets │ └── img │ │ ├── logo.png │ │ ├── basemap-osm.png │ │ ├── basemap-dark.png │ │ ├── basemap-light.png │ │ ├── news │ │ ├── escolas.png │ │ ├── favelas.png │ │ ├── geologia.png │ │ └── funcionalidade.png │ │ ├── basemap-terrain.png │ │ ├── basemap-satellite.png │ │ └── spritesheet.svg ├── components │ ├── App │ │ ├── mocks │ │ │ ├── 120_154_scala_photoshop.png │ │ │ ├── 23_30_redimen_photoshop.png │ │ │ └── baseMapsMock.js │ │ ├── app.scss │ │ ├── sass │ │ │ ├── generic.scss │ │ │ ├── tools.scss │ │ │ ├── animations.scss │ │ │ ├── components.scss │ │ │ └── settings.scss │ │ ├── polyfill.js │ │ ├── reducers │ │ │ ├── geoServerXmlStyleReducer.js │ │ │ └── geoServerXmlReducer.js │ │ └── App.js │ ├── PolygonData │ │ ├── polygonData.scss │ │ └── PolygonData.js │ ├── SidebarLeft │ │ ├── sidebarLeft.scss │ │ ├── SidebarLeftContainer.js │ │ └── SidebarLeft.js │ ├── Charts │ │ └── charts.scss │ ├── ExportList │ │ └── exportList.scss │ ├── GooglePlaces │ │ ├── googlePlaces.scss │ │ ├── GooglePlacesContainer.js │ │ └── GooglePlaces.js │ ├── Tooltip │ │ ├── TooltipContainer.js │ │ ├── Tooltip.js │ │ └── tooltip.scss │ ├── HeaderRight │ │ ├── header-right.scss │ │ ├── HeaderRightContainer.js │ │ └── HeaderRight.js │ ├── Api │ │ ├── SinalidAPI.js │ │ └── ScaAPI.js │ ├── BaseMapList │ │ ├── baseMapList.scss │ │ └── BaseMapList.js │ ├── Loading │ │ ├── LoadingContainer.js │ │ ├── loading.scss │ │ └── Loading.js │ ├── Help │ │ ├── HelpContainer.js │ │ └── Help.js │ ├── Header │ │ ├── HeaderContainer.js │ │ ├── Header.js │ │ └── application-header.scss │ ├── MenuHeader │ │ ├── MenuHeader.js │ │ └── menuHeader.scss │ ├── LayerStylesCarousel │ │ ├── LayerStyleItem.js │ │ ├── LayerStylesCarouselContainer.js │ │ ├── layerStylesCarousel.scss │ │ └── LayerStylesCarousel.js │ ├── DataTable │ │ └── data-table.scss │ ├── Modal │ │ ├── Contents │ │ │ ├── About.js │ │ │ ├── login.scss │ │ │ ├── LayerFilter.js │ │ │ ├── Login.js │ │ │ ├── News.js │ │ │ └── Table.js │ │ ├── Modal.js │ │ └── ModalContainer.js │ ├── SearchLayer │ │ ├── searchLayer.scss │ │ └── SearchLayer.js │ ├── Menu │ │ ├── menu-extends.scss │ │ ├── MenuContainer.js │ │ ├── menuReducer.js │ │ └── menu.scss │ ├── ToolbarMenu │ │ ├── toolbarMenu.scss │ │ └── ToolbarMenu.js │ ├── Toolbar │ │ ├── toolbar.scss │ │ ├── ToolbarContainer.js │ │ └── Toolbar.js │ ├── SidebarRight │ │ ├── sidebarRight.scss │ │ ├── SidebarRight.js │ │ └── SidebarRightContainer.js │ ├── GlobalFilter │ │ └── Place.js │ ├── LayerSubtitle │ │ └── LayerSubtitleSpace.js │ ├── Sinalid │ │ ├── sinalid.scss │ │ └── Sinalid.js │ ├── ShareUrl │ │ └── ShareUrl.js │ └── LeafletMap │ │ ├── leafletMap.scss │ │ └── LeafletMapContainer.js └── actions │ └── ToolbarActions.js ├── artifacts ├── icone-onibus.png ├── screens │ ├── 01 index.jpg │ ├── 02 menu.jpg │ ├── 04 submenu.jpg │ ├── 03 menu opened.jpg │ ├── 10 menu tooltip.jpg │ ├── 10 advanced search.jpg │ ├── 09 record table full.jpg │ ├── 07 sidebar right records.jpg │ ├── 08 sidebar right record detail.jpg │ ├── 05 sidebar right styles single layer.jpg │ └── 06 sidebar right styles multiple layers.jpg └── diagrams │ └── baseComponentsStructure.png ├── esdoc.json ├── tests ├── Header │ ├── Header.test.js │ └── __snapshots__ │ │ └── Header.test.js.snap ├── MenuHeader │ ├── MenuHeader.test.js │ └── __snapshots__ │ │ └── MenuHeader.test.js.snap ├── SearchLayer │ ├── SearchLayer.test.js │ └── __snapshots__ │ │ └── SearchLayer.test.js.snap ├── tests-boilerplate.json ├── SidebarLeft │ ├── SidebarLeft.test.js │ └── __snapshots__ │ │ └── SidebarLeft.test.js.snap ├── Tooltip │ ├── Tooltip.test.js │ └── __snapshots__ │ │ └── Tooltip.test.js.snap ├── LayerStyleItem │ └── __snapshots__ │ │ └── LayerStyleItem.test.js.snap ├── test_generator.js └── Menu │ ├── Menu.test.js │ └── __snapshots__ │ └── Menu.test.js.snap ├── .editorconfig ├── .snyk ├── jestsetup.js ├── .gitignore ├── .babelrc ├── .eslintrc ├── copy_static.js ├── copy_modules.js ├── LICENSE ├── README-DEPLOY-MPRJ.md ├── CONTRIBUTING.md ├── README-GEOSERVER.md ├── README.md └── package.json /mocks/cssStub.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/og-img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/og-img.jpg -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/logo.png -------------------------------------------------------------------------------- /artifacts/icone-onibus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/icone-onibus.png -------------------------------------------------------------------------------- /artifacts/screens/01 index.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/01 index.jpg -------------------------------------------------------------------------------- /artifacts/screens/02 menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/02 menu.jpg -------------------------------------------------------------------------------- /src/assets/img/basemap-osm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/basemap-osm.png -------------------------------------------------------------------------------- /artifacts/screens/04 submenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/04 submenu.jpg -------------------------------------------------------------------------------- /public/icon-apple-touch-57x57.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/icon-apple-touch-57x57.jpg -------------------------------------------------------------------------------- /public/icon-apple-touch-72x72.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/icon-apple-touch-72x72.jpg -------------------------------------------------------------------------------- /src/assets/img/basemap-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/basemap-dark.png -------------------------------------------------------------------------------- /src/assets/img/basemap-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/basemap-light.png -------------------------------------------------------------------------------- /src/assets/img/news/escolas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/news/escolas.png -------------------------------------------------------------------------------- /src/assets/img/news/favelas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/news/favelas.png -------------------------------------------------------------------------------- /src/assets/img/news/geologia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/news/geologia.png -------------------------------------------------------------------------------- /public/icon-apple-touch-114x114.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/icon-apple-touch-114x114.jpg -------------------------------------------------------------------------------- /public/icon-apple-touch-144x144.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/public/icon-apple-touch-144x144.jpg -------------------------------------------------------------------------------- /src/assets/img/basemap-terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/basemap-terrain.png -------------------------------------------------------------------------------- /artifacts/screens/03 menu opened.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/03 menu opened.jpg -------------------------------------------------------------------------------- /artifacts/screens/10 menu tooltip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/10 menu tooltip.jpg -------------------------------------------------------------------------------- /src/assets/img/basemap-satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/basemap-satellite.png -------------------------------------------------------------------------------- /src/assets/img/news/funcionalidade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/assets/img/news/funcionalidade.png -------------------------------------------------------------------------------- /artifacts/screens/10 advanced search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/10 advanced search.jpg -------------------------------------------------------------------------------- /artifacts/screens/09 record table full.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/09 record table full.jpg -------------------------------------------------------------------------------- /artifacts/diagrams/baseComponentsStructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/diagrams/baseComponentsStructure.png -------------------------------------------------------------------------------- /artifacts/screens/07 sidebar right records.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/07 sidebar right records.jpg -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "src", 3 | "destination": "public/esdoc", 4 | "experimentalProposal": { 5 | "objectRestSpread": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /artifacts/screens/08 sidebar right record detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/08 sidebar right record detail.jpg -------------------------------------------------------------------------------- /src/components/App/mocks/120_154_scala_photoshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/components/App/mocks/120_154_scala_photoshop.png -------------------------------------------------------------------------------- /src/components/App/mocks/23_30_redimen_photoshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/src/components/App/mocks/23_30_redimen_photoshop.png -------------------------------------------------------------------------------- /artifacts/screens/05 sidebar right styles single layer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/05 sidebar right styles single layer.jpg -------------------------------------------------------------------------------- /artifacts/screens/06 sidebar right styles multiple layers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinisterioPublicoRJ/inloco/HEAD/artifacts/screens/06 sidebar right styles multiple layers.jpg -------------------------------------------------------------------------------- /src/components/App/app.scss: -------------------------------------------------------------------------------- 1 | #app, .module-app { 2 | height: 100%; 3 | } 4 | 5 | @import "sass/settings.scss"; 6 | @import "sass/tools.scss"; 7 | @import "sass/generic.scss"; 8 | @import "sass/components.scss"; 9 | @import "sass/animations.scss"; 10 | -------------------------------------------------------------------------------- /tests/Header/Header.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Header from '../../src/components/Header/Header.js'; 3 | 4 | it('Header component renders correctly with no data', () => { 5 | const component = shallow(
); 6 | expect(component).toMatchSnapshot(); 7 | }); 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = null -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.8.0 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | 'npm:moment:20170905': 7 | - chart.js > moment: 8 | patched: '2017-11-30T22:49:32.914Z' 9 | -------------------------------------------------------------------------------- /src/components/PolygonData/polygonData.scss: -------------------------------------------------------------------------------- 1 | .module-polygon-data{ 2 | .data-table, .charts { 3 | width: 90%; 4 | margin: 15px 5%; 5 | 6 | td { 7 | padding: 2px 3px; 8 | border: 1px solid $layer-item-border-color; 9 | } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tests/MenuHeader/MenuHeader.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MenuHeader from '../../src/components/MenuHeader/MenuHeader.js'; 3 | 4 | it('MenuHeader component renders correctly with no data', () => { 5 | const component = shallow(); 6 | expect(component).toMatchSnapshot(); 7 | }); 8 | -------------------------------------------------------------------------------- /jestsetup.js: -------------------------------------------------------------------------------- 1 | // Make Enzyme functions available in all test files without importing 2 | import { shallow, render, mount } from 'enzyme'; 3 | global.shallow = shallow; 4 | global.render = render; 5 | global.mount = mount; 6 | 7 | // Fail tests on any warning 8 | console.error = message => { 9 | throw new Error(message); 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/SidebarLeft/sidebarLeft.scss: -------------------------------------------------------------------------------- 1 | .sidebar-left { 2 | background: $menu-bg-color; 3 | padding-bottom: 76px; 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: $sidebar-left-width; 8 | height: 100%; 9 | z-index: 1001; // over Leaflet controls 10 | box-shadow: 3px 2px 15px 0 $sidebar-box-shadow-color; 11 | } 12 | -------------------------------------------------------------------------------- /tests/SearchLayer/SearchLayer.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SearchLayer from '../../src/components/SearchLayer/SearchLayer.js'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('SearchLayer component renders correctly with no data', () => { 6 | const component = shallow(); 7 | expect(component).toMatchSnapshot(); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/Charts/charts.scss: -------------------------------------------------------------------------------- 1 | .charts { 2 | h3 { 3 | font-weight: normal; 4 | padding-bottom: 10px; 5 | } 6 | 7 | .chart { 8 | margin-bottom: 25px; 9 | padding-bottom: 15px; 10 | border-bottom: 1px solid #ccc; 11 | 12 | &:last-child { 13 | border-bottom: none; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/tests-boilerplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": [ 3 | { 4 | "what": "renderer", 5 | "where": "react-test-renderer" 6 | }, 7 | { 8 | "what": "React", 9 | "where": "react" 10 | } 11 | ], 12 | "tests": [ 13 | { 14 | "name": "snapshot" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/components/ExportList/exportList.scss: -------------------------------------------------------------------------------- 1 | .export-list { 2 | list-style: none; 3 | padding: 0 15px; 4 | 5 | &--link { 6 | color: $export-list-text-color; 7 | display: block; 8 | padding: 3px 0; 9 | text-align: right; 10 | text-decoration: none; 11 | 12 | &:hover { 13 | text-decoration: underline; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/GooglePlaces/googlePlaces.scss: -------------------------------------------------------------------------------- 1 | .google-places { 2 | &--label { 3 | color: #2f2f2f; 4 | font-size: 14px; 5 | } 6 | 7 | &--input { 8 | color: #2f2f2f; 9 | font-size: 12px; 10 | margin-top: 10px; 11 | padding: 2px 3px; 12 | } 13 | } 14 | 15 | .pac-container { 16 | margin: -250px 0 0 -17px; 17 | width: 190px !important; 18 | height: 174px; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Tooltip/TooltipContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Tooltip from './Tooltip' 3 | import { connect } from 'react-redux' 4 | 5 | const mapStateToProps = (state) => { 6 | return { 7 | tooltip: state.tooltip, 8 | scrollTop: state.scrollTop, 9 | } 10 | } 11 | 12 | const TooltipContainer = connect( 13 | mapStateToProps, 14 | null 15 | )(Tooltip) 16 | 17 | export default TooltipContainer 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | .vscode/ 4 | 5 | # Build files 6 | static/ 7 | inloco/ 8 | rjinloco/ 9 | inloco.zip 10 | rjinloco.zip 11 | 12 | # Automatic generated docs 13 | public/esdoc/ 14 | tests/auto/ 15 | 16 | # Logs 17 | npm-debug.log 18 | 19 | # Modules copied from node_modules 20 | src/components/App/sass/font-awesome 21 | src/components/App/sass/leaflet 22 | 23 | # Jest Reports 24 | coverage 25 | 26 | # Mac OS X crap 27 | .DS_Store 28 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-class-properties" 4 | ], 5 | "presets": [ 6 | ["es2015", { "modules": false }], 7 | "react", "stage-2" 8 | ], 9 | "env": { 10 | "development": { 11 | "plugins": ["transform-es2015-modules-commonjs"], 12 | "presets": [ 13 | "react-hmre" 14 | ] 15 | }, 16 | "test": { 17 | "plugins": ["transform-es2015-modules-commonjs"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/App/sass/generic.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Project Inloco - Ministério Público do Estado Rio de Janeiro 3 | // Here are the general settings intended for reset 4 | // 5 | 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | } 11 | 12 | html, body { 13 | height: 100%; 14 | } 15 | 16 | body { 17 | font-family: sans-serif; 18 | } 19 | 20 | table { 21 | border-collapse: collapse; 22 | text-align: left; 23 | width: 100%; 24 | } 25 | 26 | .hidden { 27 | visibility: hidden; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/HeaderRight/header-right.scss: -------------------------------------------------------------------------------- 1 | .header-right { 2 | right: 0px; 3 | position: absolute; 4 | top: 150px; 5 | z-index: 1001; 6 | 7 | &--menu-button { 8 | background-color: $sidebar-right-bg-color; 9 | color: $sidebar-right-text-color; 10 | cursor: pointer; 11 | display: block; 12 | font-size: 32px; 13 | height: 55px; 14 | margin-top: 25px; 15 | padding-top: 10px; 16 | text-align: center; 17 | text-decoration: none; 18 | width: 55px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/App/sass/tools.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Project Inloco - Ministério Público do Estado Rio de Janeiro 3 | // It is where you store yout mixings and functions needed to build your layouts 4 | // 5 | 6 | // Font Awesome 7 | $fa-font-path: "sass/font-awesome/fonts" !default; // fix fonts relative path 8 | @import "font-awesome/scss/font-awesome.scss"; 9 | 10 | %content-fa { 11 | font-family: FontAwesome; 12 | font-style: normal; 13 | font-weight: normal; 14 | text-decoration: inherit; 15 | } 16 | 17 | // Leaflet 18 | @import "sass/leaflet/leaflet.css"; 19 | -------------------------------------------------------------------------------- /tests/SidebarLeft/SidebarLeft.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SidebarLeft from '../../src/components/SidebarLeft/SidebarLeft.js'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('SidebarLeft component renders correctly with no data', () => { 6 | const component = shallow(); 7 | expect(component).toMatchSnapshot(); 8 | }); 9 | 10 | it('SidebarLeft component renders correctly with real data -> menu open', () => { 11 | const component = shallow(); 12 | expect(component).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /src/components/Api/SinalidAPI.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const SinalidAPI = { 4 | /** 5 | * Gets a list of Missing People from Sinalid API 6 | * @param {function} callback Callback function to execute 7 | * @param {number} dpId Delegacia de Polícia ID 8 | */ 9 | listMissingPeople(callback, dpId) { 10 | axios 11 | .get(`/sinalid/api/listadesaparecidos/${dpId}`) 12 | .then(response => callback(response)) 13 | .catch(error => callback(error.response)) 14 | } 15 | } 16 | 17 | export default SinalidAPI 18 | -------------------------------------------------------------------------------- /src/components/BaseMapList/baseMapList.scss: -------------------------------------------------------------------------------- 1 | .basemap-list { 2 | list-style: none; 3 | display: flex; 4 | 5 | &--item { 6 | display: inline-block; 7 | margin-right: 5px; 8 | margin-left: 5px; 9 | position: relative; 10 | 11 | &:hover { 12 | .tooltip { 13 | display: block !important; 14 | } 15 | } 16 | } 17 | 18 | &--image { 19 | height: 35px; 20 | width: 35px; 21 | 22 | &:hover { 23 | outline: 2px solid #182d4c; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Loading/LoadingContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import Loading from './Loading' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | layers: state.layers, 8 | showLoader: state.showLoader, 9 | downloadLoader: state.downloadLoader, 10 | } 11 | } 12 | 13 | const mapDispatchToProps = (dispatch) => { 14 | return {} 15 | } 16 | 17 | const LoadingContainer = connect( 18 | mapStateToProps, 19 | mapDispatchToProps 20 | )(Loading) 21 | 22 | export default LoadingContainer 23 | -------------------------------------------------------------------------------- /src/components/Loading/loading.scss: -------------------------------------------------------------------------------- 1 | .module-loading { 2 | &.hidden { 3 | display: none; 4 | } 5 | 6 | background: rgba(0,0,0,.3); 7 | color: white; 8 | 9 | display: table; 10 | height: 100%; 11 | width: 100%; 12 | position: absolute; 13 | 14 | z-index: 10000; 15 | 16 | .spinner { 17 | width: 200px; 18 | height: 100px; 19 | text-align: center; 20 | 21 | display: table-cell; 22 | vertical-align: middle; 23 | 24 | .fa { 25 | font-size: 3em; 26 | margin-bottom: 10px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Help/HelpContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Help from './Help' 3 | import { connect } from 'react-redux' 4 | import { hideHelp } from '../../actions/actions.js' 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | return { 8 | showHelp: state.showHelp, 9 | } 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | onIntrojsExit: () => { 15 | dispatch(hideHelp()) 16 | } 17 | } 18 | } 19 | 20 | const HelpContainer = connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(Help) 24 | 25 | export default HelpContainer 26 | -------------------------------------------------------------------------------- /tests/MenuHeader/__snapshots__/MenuHeader.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MenuHeader component renders correctly with no data 1`] = ` 4 | 27 | `; 28 | -------------------------------------------------------------------------------- /src/components/Header/HeaderContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from './Header' 3 | import { connect } from 'react-redux' 4 | import { showMenuLayer } from '../../actions/actions.js' 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | showTooltipMenu: state.showTooltipMenu, 9 | } 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | onHeaderClick: () => { 15 | dispatch(showMenuLayer()) 16 | } 17 | } 18 | } 19 | 20 | const HeaderContainer = connect( 21 | mapStateToProps, 22 | mapDispatchToProps, 23 | )(Header) 24 | 25 | export default HeaderContainer 26 | -------------------------------------------------------------------------------- /src/components/MenuHeader/MenuHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const MenuHeader = ({onClickMenuHeader}) => { 4 | return ( 5 |
6 | InLoco 7 | 8 |

9 | Camadas de Exibição 10 | Escolha uma categoria abaixo 11 |

12 |
13 | ) 14 | } 15 | 16 | export default MenuHeader 17 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | extends: 2 | eslint-config-airbnb 3 | 4 | parser: 5 | babel-eslint 6 | 7 | settings: 8 | ecmascript: 6 9 | 10 | ecmaFeatures: 11 | jsx: true 12 | modules: true 13 | destructuring: true 14 | classes: true 15 | forOf: true 16 | blockBindings: true 17 | arrowFunctions: true 18 | 19 | env: 20 | browser: true 21 | 22 | rules: 23 | indent: 2 24 | func-style: 0 25 | func-names: 0 26 | comma-dangle: 0 27 | no-console: 0 28 | no-param-reassign: 0 29 | linebreak-style: 0 30 | import/no-extraneous-dependencies: 0 31 | import/no-unresolved: 0 32 | import/extensions: 0 33 | react/jsx-filename-extension: 0 -------------------------------------------------------------------------------- /src/components/HeaderRight/HeaderRightContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import HeaderRight from './HeaderRight' 3 | import { connect } from 'react-redux' 4 | import { showSidebarRight } from '../../actions/actions.js' 5 | 6 | const mapStateToProps = (state) => { 7 | return { 8 | layers: state.layers, 9 | } 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | onHeaderClick: () => { 15 | dispatch(showSidebarRight()) 16 | } 17 | } 18 | } 19 | 20 | const HeaderRightContainer = connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(HeaderRight) 24 | 25 | export default HeaderRightContainer 26 | -------------------------------------------------------------------------------- /copy_static.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var cpx = require('cpx') 3 | 4 | console.log(chalk.blue('Copying static files...')) 5 | 6 | try { 7 | cpx.copySync('public/index.html', 'static/') 8 | cpx.copySync('public/favicon.ico', 'static/') 9 | cpx.copySync('public/icon-apple-touch-57x57.jpg', 'static/') 10 | cpx.copySync('public/icon-apple-touch-72x72.jpg', 'static/') 11 | cpx.copySync('public/icon-apple-touch-114x114.jpg', 'static/') 12 | cpx.copySync('public/icon-apple-touch-144x144.jpg', 'static/') 13 | cpx.copySync('public/og-img.jpg', 'static/') 14 | 15 | console.log(chalk.green('Job done, moving on')) 16 | } catch (e) { 17 | console.log(chalk.red('An error occured: \n' + e)) 18 | } 19 | -------------------------------------------------------------------------------- /src/components/LayerStylesCarousel/LayerStyleItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LayerStyleItem = ({layer, style, index, onStyleClick}) => { 4 | 5 | let itemClassName = 'layer-styles-carousel--list-item' 6 | if (layer && layer.selectedLayerStyleId === index) { 7 | itemClassName += ' selected' 8 | } 9 | 10 | function styleClick() { 11 | return onStyleClick(layer, index) 12 | } 13 | 14 | return ( 15 |
  • 16 | { style ? 17 | {style.title}/ 18 | : '' } 19 |
  • 20 | ) 21 | } 22 | 23 | export default LayerStyleItem 24 | -------------------------------------------------------------------------------- /src/components/App/sass/animations.scss: -------------------------------------------------------------------------------- 1 | // Animations used on the tools buttons around the application 2 | 3 | .allow-transition { 4 | transition: all .15s ease-in-out; 5 | overflow: hidden; 6 | } 7 | 8 | .hide-element { 9 | border-radius: 50%; 10 | transform: translate(-33%, -33%) scale(0); 11 | } 12 | 13 | // Animation used to show/hide the layers menu 14 | 15 | .hide-sidebar { 16 | @extend .hide-element; 17 | } 18 | 19 | // Animation used to show/hide sidebar right 20 | .allow-transition-sidebar-right { 21 | transition: all .15s ease-in-out; 22 | } 23 | 24 | .hide-sidebar-element { 25 | transform: translateX(400px); 26 | } 27 | 28 | .hide-sidebar-right { 29 | right: 0px; 30 | @extend .hide-sidebar-element; 31 | } 32 | -------------------------------------------------------------------------------- /tests/SearchLayer/__snapshots__/SearchLayer.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SearchLayer component renders correctly with no data 1`] = ` 4 |
    9 | 27 |
    28 | `; 29 | -------------------------------------------------------------------------------- /src/components/GooglePlaces/GooglePlacesContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import GooglePlaces from '../GooglePlaces/GooglePlaces' 4 | import { addGooglePlacesLatLong } from '../../actions/ToolbarActions' 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | return { 8 | mapProperties: state.mapProperties, 9 | } 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | onAddGooglePlacesLatLong: (latLong) => { 15 | dispatch(addGooglePlacesLatLong(latLong)) 16 | } 17 | } 18 | } 19 | 20 | const GooglePlacesContainer = connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(GooglePlaces) 24 | 25 | export default GooglePlacesContainer 26 | -------------------------------------------------------------------------------- /tests/Tooltip/Tooltip.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tooltip from '../../src/components/Tooltip/Tooltip.js'; 3 | import renderer from 'react-test-renderer'; 4 | 5 | it('Tooltip component renders correctly with no data', () => { 6 | const component = shallow(); 7 | expect(component).toMatchSnapshot(); 8 | }); 9 | 10 | it('Tooltip component renders correctly with real data', () => { 11 | let tooltip = { 12 | "text":"Índice de Desenvolvimento da Educação Básica (IDEB) da rede estadual de educação. Leitura da tabela: I (Anos Iniciais), F (Anos Finais), E (Estadual), M (Municipal), P (Pública). Fonte: INEP Ano: 2015","show":true,"sidebarLeftWidth":300,"parentHeight":33,"top":230 13 | } 14 | 15 | const component = shallow(); 16 | expect(component).toMatchSnapshot(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/Header/__snapshots__/Header.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Header component renders correctly with no data 1`] = ` 4 | 33 | `; 34 | -------------------------------------------------------------------------------- /copy_modules.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var mkdirp = require('mkdirp') 3 | var copydir = require('copy-dir') 4 | var copyFile = require('cpx') 5 | 6 | var folderFA = 'src/components/App/sass/font-awesome' 7 | var folderLeaflet = 'src/components/App/sass/leaflet' 8 | var folderLeafletImages = 'src/components/App/sass/leaflet/images' 9 | 10 | console.log(chalk.blue('Copying Modules...')) 11 | 12 | try { 13 | mkdirp(folderFA) 14 | copydir.sync('node_modules/font-awesome', folderFA) 15 | 16 | mkdirp(folderLeaflet) 17 | copyFile.copySync('node_modules/leaflet/dist/leaflet.css', folderLeaflet) 18 | 19 | mkdirp(folderLeafletImages) 20 | copydir.sync('node_modules/leaflet/dist/images', folderLeafletImages) 21 | 22 | console.log(chalk.green('Job done, moving on')) 23 | } catch (e) { 24 | console.log(chalk.red('An error occured: \n' + e)) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Loading = ({ layers, showLoader, downloadLoader }) => { 4 | 5 | let loadingClass = 'module-loading' 6 | {/*if (!showLoader && showLoader !== undefined) { 7 | loadingClass += ' hidden' 8 | } 9 | 10 | if (downloadLoader && downloadLoader === true) { 11 | loadingClass = 'module-loading' 12 | }*/} 13 | 14 | return ( 15 |
    16 |
    17 |
    18 | Este site está sendo descontinuado por favor acesse nosso no site aqui :{" "} 19 | HUB Gestão do Território 20 |
    21 |
    22 | ) 23 | } 24 | 25 | export default Loading 26 | -------------------------------------------------------------------------------- /src/components/DataTable/data-table.scss: -------------------------------------------------------------------------------- 1 | .data-table { 2 | 3 | %data-table--cell { 4 | padding: 10px 5px; 5 | } 6 | 7 | &-container { 8 | overflow: auto; 9 | max-height: 500px; 10 | } 11 | 12 | &--header { 13 | border-bottom: $data-table-header-border-size solid $data-table-header-border-color; 14 | border-top: $data-table-header-border-size solid $data-table-header-border-color; 15 | color: $data-table-header-text-color; 16 | font-size: 12px; 17 | font-weight: normal; 18 | @extend %data-table--cell; 19 | } 20 | 21 | &--body { 22 | color: $data-table-body-text-color; 23 | font-size: 10px; 24 | @extend %data-table--cell; 25 | } 26 | 27 | &--row:nth-child(even) .data-table--body{ 28 | background-color: $data-table-row-bacgkround-color; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const About = () => { 4 | 5 | return ( 6 |
    7 |

    O inLoco 2.0 é uma plataforma de mapas interativos criada pelo Ministério Público do Estado do Rio de Janeiro, permitindo ao usuário visualizar e sobrepor dados geográficos de diversos assuntos, realizar buscas e dispor de diversas informações.

    8 |

    Em caso de dúvidas na utilização do sistema, mande um email para mpemmapas.cadg@mprj.mp.br.

    9 |

    Este sistema é software livre e seu código está disponibilizado no GitHub. Contribuições são bem-vindas! :)

    10 |
    11 | ) 12 | } 13 | 14 | export default About 15 | -------------------------------------------------------------------------------- /tests/LayerStyleItem/__snapshots__/LayerStyleItem.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`LayerStyleItem component renders correctly with no data 1`] = ` 4 |
  • 8 | `; 9 | 10 | exports[`LayerStyleItem component renders correctly with real data 1`] = ` 11 |
  • 15 | IDEB 2015 (Anos Iniciais) - Rede Estadual 20 |
  • 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Header = ({onHeaderClick, showTooltipMenu}) => { 4 | let className = "menu-button--tootltip" 5 | if (!showTooltipMenu) { 6 | className += " hidden" 7 | } 8 | return ( 9 | 19 | ) 20 | } 21 | 22 | export default Header 23 | -------------------------------------------------------------------------------- /tests/SidebarLeft/__snapshots__/SidebarLeft.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SidebarLeft component renders correctly with no data 1`] = ` 4 | 18 | `; 19 | 20 | exports[`SidebarLeft component renders correctly with real data -> menu open 1`] = ` 21 | 36 | `; 37 | -------------------------------------------------------------------------------- /src/components/App/mocks/baseMapsMock.js: -------------------------------------------------------------------------------- 1 | const BASE_MAPS_MOCK = [ 2 | { 3 | name: 'esri-light', 4 | image: require('../../../assets/img/basemap-light.png'), 5 | subtitle: 'Fundo claro (ESRI Light Gray Canvas)', 6 | }, 7 | { 8 | name: 'esri-dark', 9 | image: require('../../../assets/img/basemap-dark.png'), 10 | subtitle: 'Fundo escuro (ESRI Dark Gray Canvas)', 11 | }, 12 | { 13 | name: 'osm', 14 | image: require('../../../assets/img/basemap-osm.png'), 15 | subtitle: 'OpenStreetMap', 16 | }, 17 | { 18 | name: 'esri-terrain', 19 | image: require('../../../assets/img/basemap-terrain.png'), 20 | subtitle: 'Terreno (ESRI World Terrain)' 21 | }, 22 | { 23 | name: 'esri-satellite', 24 | image: require('../../../assets/img/basemap-satellite.png'), 25 | subtitle: 'Imagens de Satélite (ESRI World Imagery)' 26 | }, 27 | ] 28 | 29 | export default BASE_MAPS_MOCK 30 | -------------------------------------------------------------------------------- /src/components/LayerStylesCarousel/LayerStylesCarouselContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LayerStylesCarousel from './LayerStylesCarousel' 3 | import { connect } from 'react-redux' 4 | import { slideLeftStyles, slideRightStyles, selectLayerStyle } from '../../actions/actions.js' 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | return { 8 | layer: ownProps.layer, 9 | } 10 | } 11 | 12 | const mapDispatchToProps = (dispatch) => { 13 | return { 14 | onArrowLeftClick: (item) => { 15 | dispatch(slideLeftStyles(item)) 16 | }, 17 | onArrowRightClick: (item) => { 18 | dispatch(slideRightStyles(item)) 19 | }, 20 | onStyleClick: (item, styleId) => { 21 | dispatch(selectLayerStyle(item, styleId)) 22 | }, 23 | } 24 | } 25 | 26 | const LayerStylesCarouselContainer = connect( 27 | mapStateToProps, 28 | mapDispatchToProps 29 | )(LayerStylesCarousel) 30 | 31 | export default LayerStylesCarouselContainer 32 | -------------------------------------------------------------------------------- /src/components/MenuHeader/menuHeader.scss: -------------------------------------------------------------------------------- 1 | .menu-header { 2 | position: relative; 3 | 4 | &--logo { 5 | height: 39px; 6 | margin: 20px 0 0 15px; 7 | width: 185px; 8 | } 9 | 10 | &--button { 11 | background-color: $menu-text-color; 12 | color: $menu-bg-color; 13 | cursor: pointer; 14 | padding: 8px; 15 | position: absolute; 16 | right: 0; 17 | top: 20px; 18 | transition: all .3s ease background-color; 19 | 20 | &:hover { 21 | transition-duration: .15s; 22 | background-color: $menu-header-hover-background-color; 23 | } 24 | } 25 | 26 | &--title { 27 | color: $sidebar-left-highlight-color; 28 | font-size: 16px; 29 | font-weight: normal; 30 | margin: 20px 0 15px 15px; 31 | text-transform: uppercase; 32 | } 33 | 34 | &--caption { 35 | color: $menu-text-color; 36 | display: block; 37 | font-size: 80%; 38 | text-transform: initial; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/HeaderRight/HeaderRight.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | /** 4 | * @param {Object[]} layers - this is array of layers. 5 | * 6 | * This function loops through all layers in the state 7 | * and returns true if at leas one layer is selected 8 | * 9 | * @return {boolean} - returns true if at least one layer 10 | * is selected, and false if none is selected. 11 | */ 12 | const aLayerIsSelected = (layers) => { 13 | if(Array.isArray(layers)){ 14 | for (var i = 0; i < layers.length; i++) { 15 | var layer = layers[i] 16 | if (layer.selected){ 17 | return true 18 | } 19 | } 20 | return false 21 | } 22 | } 23 | const HeaderRight = ({layers, onHeaderClick}) => { 24 | var cssClass = 'header-right' 25 | if(!aLayerIsSelected(layers)){ 26 | cssClass += ' hidden' 27 | } 28 | return ( 29 |
    30 | 31 |
    32 | ) 33 | } 34 | 35 | export default HeaderRight 36 | -------------------------------------------------------------------------------- /src/components/BaseMapList/BaseMapList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BaseMapList = ({baseMaps, onChangeActiveBaseMap}) => { 4 | 5 | function handleChangeActiveBaseMap(baseMap) { 6 | return onChangeActiveBaseMap(baseMap) 7 | } 8 | 9 | return ( 10 |
      11 | { 12 | // Checks if baseMaps already exists in state 13 | baseMaps 14 | ? baseMaps.map((baseMap, index) => { 15 | return ( 16 |
    • 17 | handleChangeActiveBaseMap(baseMap)}> 18 | {`${baseMap.name} 19 | 20 | {baseMap.subtitle} 21 |
    • 22 | ) 23 | }) 24 | : '' 25 | } 26 |
    27 | ) 28 | } 29 | 30 | export default BaseMapList 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/components/SearchLayer/searchLayer.scss: -------------------------------------------------------------------------------- 1 | .search-layer { 2 | background-color: $search-layer-bg-color; 3 | padding: 15px 18px 15px 15px; 4 | width: $sidebar-left-width; 5 | 6 | &--title { 7 | color: $menu-text-color; 8 | display: block; 9 | font-size: 14px; 10 | position: relative; 11 | } 12 | 13 | &--icon { 14 | background-color: $search-layer-bg-color; 15 | bottom: 7px; 16 | font-size: 16px; 17 | position: absolute; 18 | right: 0; 19 | z-index: 1; 20 | } 21 | 22 | &--icon.fa-close { 23 | cursor: pointer; 24 | } 25 | 26 | &--input { 27 | background: transparent; 28 | border: 0; 29 | border-bottom: 1px solid $menu-text-color; 30 | color: $search-layer-input-color; 31 | font-size: 12px; 32 | font-style: italic; 33 | margin-top: 10px; 34 | padding-bottom: 5px; 35 | width: 100%; 36 | 37 | &:focus { 38 | outline: 0; 39 | } 40 | 41 | &::placeholder { 42 | color: $search-layer-input-color; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/App/sass/components.scss: -------------------------------------------------------------------------------- 1 | @import "../../BaseMapList/baseMapList.scss"; 2 | @import "../../Charts/charts.scss"; 3 | @import "../../DataTable/data-table.scss"; 4 | @import "../../ExportList/exportList.scss"; 5 | @import "../../GlobalFilter/globalFilter.scss"; 6 | @import "../../GooglePlaces/googlePlaces.scss"; 7 | @import "../../Header/application-header.scss"; 8 | @import "../../HeaderRight/header-right.scss"; 9 | @import "../../LayerStylesCarousel/layerStylesCarousel.scss"; 10 | @import "../../LayerSubtitle/layerSubtitle.scss"; 11 | @import "../../LeafletMap/leafletMap.scss"; 12 | @import "../../Loading/loading.scss"; 13 | @import "../../Menu/menu.scss"; 14 | @import "../../MenuHeader/menuHeader.scss"; 15 | @import "../../Modal/modal.scss"; 16 | @import "../../PolygonData/polygonData.scss"; 17 | @import "../../SearchLayer/searchLayer.scss"; 18 | @import "../../SidebarLeft/sidebarLeft.scss"; 19 | @import "../../SidebarRight/sidebarRight.scss"; 20 | @import "../../Sinalid/sinalid.scss"; 21 | @import "../../Toolbar/toolbar.scss"; 22 | @import "../../ToolbarMenu/toolbarMenu.scss"; 23 | @import "../../Tooltip/tooltip.scss"; 24 | @import "../../Modal/Contents/login.scss"; 25 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/login.scss: -------------------------------------------------------------------------------- 1 | .login-container { 2 | 3 | form { 4 | width: 250px; 5 | height: 150px; 6 | } 7 | 8 | .login-button { 9 | background: $layer-item-data-icon-background-color; 10 | border-radius: 4px; 11 | color: #fff; 12 | cursor: pointer; 13 | display: block; 14 | font-size: 12px; 15 | font-weight: bold; 16 | padding: 10px 15px; 17 | text-align: center; 18 | text-transform: uppercase; 19 | width: 300px; 20 | margin: 0 auto; 21 | } 22 | span { 23 | display: block; 24 | margin: 0 auto; 25 | width: 300px; 26 | } 27 | fieldset { 28 | position: absolute; 29 | top: 0; 30 | bottom: 0; 31 | left:0; 32 | right:0; 33 | margin: auto; 34 | width:50%; 35 | height: 30%; 36 | border: 0; 37 | } 38 | label { 39 | display: block; 40 | width: auto; 41 | } 42 | input { 43 | width: 100%; 44 | margin: 10px 0 10px 0; 45 | } 46 | .error { 47 | margin-top: 10px; 48 | color: red; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/SidebarLeft/SidebarLeftContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SidebarLeft from './SidebarLeft' 3 | import { connect } from 'react-redux' 4 | import { closeToolbars, searchLayer, cleanSearch, hideMenuLayer, untoggleAll } from '../../actions/actions.js' 5 | 6 | const mapDispatchToProps = (dispatch) => { 7 | return { 8 | onSearchClick: () => { 9 | dispatch(closeToolbars()) 10 | }, 11 | onKeyUpSearch: (text) => { 12 | dispatch(untoggleAll()) 13 | dispatch(searchLayer(text)) 14 | }, 15 | onBtnCleanSearch: () => { 16 | dispatch(cleanSearch()) 17 | dispatch(searchLayer('')) 18 | dispatch(untoggleAll()) 19 | }, 20 | onClickMenuHeader: () => { 21 | dispatch(hideMenuLayer()) 22 | } 23 | } 24 | } 25 | 26 | const mapStateToProps = (state) => { 27 | return { 28 | showMenu: state.showMenu, 29 | searchString: state.searchString, 30 | } 31 | } 32 | 33 | const SidebarLeftContainer = connect( 34 | mapStateToProps, 35 | mapDispatchToProps 36 | )(SidebarLeft) 37 | 38 | export default SidebarLeftContainer 39 | -------------------------------------------------------------------------------- /src/components/SidebarLeft/SidebarLeft.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MenuHeader from '../MenuHeader/MenuHeader.js' 3 | import MenuContainer from '../Menu/MenuContainer.js' 4 | import SearchLayer from '../SearchLayer/SearchLayer.js' 5 | import { withContentRect } from 'react-measure' 6 | 7 | const SidebarLeft = withContentRect(['bounds', 'client'])(({ 8 | measureRef, 9 | measure, 10 | contentRect, 11 | onSearchClick, 12 | onKeyUpSearch, 13 | showMenu, 14 | onClickMenuHeader, 15 | onBtnCleanSearch, 16 | searchString, 17 | }) => { 18 | var cssClass = 'sidebar-left allow-transition' 19 | if (!showMenu) { 20 | cssClass += ' hide-sidebar' 21 | } 22 | return ( 23 |
    24 | 25 | 26 | 27 |
    28 | ) 29 | }) 30 | 31 | export default SidebarLeft 32 | -------------------------------------------------------------------------------- /README-DEPLOY-MPRJ.md: -------------------------------------------------------------------------------- 1 | # How to deploy 2 | 3 | This file describes the steps to deploy a new version of InLoco into MPRJ servers. 4 | 5 | 1. Run `npm run build` 6 | 1. Rename `static/js.bundle.js` to `static/js.bundle.[YYYYMMDD].js` 7 | 1. Rename `static/vendor.bundle.js` to `static/vendor.bundle.[YYYYMMDD].js` 8 | 1. Edit `static/index.html` and change the js paths to include `[YYYYMMDD]` 9 | 1. Move the contents of the `static` directory to a new folder. Name it `inloco`. 10 | 1. Zip that folder, so you'll have a file `inloco.zip`. 11 | 1. Copy that file to `X:\[Homologacao\Estatico|Produção]` 12 | 1. Open a GLPI (or ask someone to do it). 13 | 14 | ## GLPI 15 | 16 | 1. Open http://p-glpidti01.pgj.rj.gov.br and log in with your MPRJ account 17 | 1. Assistência > Chamados 18 | 1. Click on `+` 19 | 1. Fill the fields: 20 | - Categoria: *Deploy Homologação / Produção* (open one GLPI for each one) 21 | - Ator: *mpemmapas.cadg 22 | - Elementos associados: *MP em Mapas* (always) 23 | - Título: *Deploy Homologação / Produção Estático InLoco* 24 | - Descrição: The path X:\... 25 | - Prioridade: *Média* or higher if needed 26 | - *Don't* attach the zip file. 27 | 1. After the GLPI is done, open it and approve or refuse it. 28 | -------------------------------------------------------------------------------- /src/components/Api/ScaAPI.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const ScaAPI = { 4 | /** 5 | * Call sca api and log user on system 6 | * @param callback function to call when process is completed 7 | */ 8 | logInUser(callback, username, password) { 9 | axios 10 | .post(`/mpmapas/api/authentication?password=${password}&username=${username}`) 11 | .then((response) => { 12 | callback(response) 13 | }) 14 | .catch((error) => { 15 | callback(error.response) 16 | }) 17 | }, 18 | logOutUser(callback) { 19 | axios 20 | .get(`/mpmapas/api/logout`) 21 | .then((response) => { 22 | console.log('Logout response',response); 23 | }) 24 | .catch((error) => { 25 | console.log('Logout error',error); 26 | }) 27 | }, 28 | authenticate(callback) { 29 | axios 30 | .get(`/mpmapas/api/authenticate`) 31 | .then((response) => { 32 | callback(response) 33 | }) 34 | .catch( (error) => { 35 | callback(error.response) 36 | }) 37 | } 38 | } 39 | 40 | export default ScaAPI 41 | -------------------------------------------------------------------------------- /tests/Tooltip/__snapshots__/Tooltip.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Tooltip component renders correctly with no data 1`] = ` 4 | 18 | `; 19 | 20 | exports[`Tooltip component renders correctly with real data 1`] = ` 21 | 44 | `; 45 | -------------------------------------------------------------------------------- /src/components/Menu/menu-extends.scss: -------------------------------------------------------------------------------- 1 | %menu-item-icon { 2 | @extend %content-fa; 3 | font-size: 20px; 4 | margin-top: -5px; 5 | position: absolute; 6 | } 7 | 8 | %menu-item-arrow-right { 9 | @extend %menu-item-icon; 10 | content: $fa-var-angle-right; // > 11 | right: 21px; 12 | } 13 | 14 | %menu-item-arrow-left { 15 | @extend %menu-item-icon; 16 | content: $fa-var-angle-left; // < 17 | left: 21px; 18 | } 19 | 20 | %menu-item-checkbox-empty { 21 | @extend %menu-item-arrow-left; 22 | content: $fa-var-square-o; 23 | font-size: 14px; 24 | top: 50%; 25 | margin-top: -7px; 26 | } 27 | 28 | %menu-item-checkbox-checked { 29 | @extend %menu-item-checkbox-empty; 30 | content: $fa-var-check-square-o; 31 | color: $menu-selected-layer-color; 32 | } 33 | 34 | $menu-item-padding-vertical: 10px; 35 | $menu-item-padding-horizontal: 17px; 36 | $menu-item-padding-horizontal-with-arrow: 38px; 37 | 38 | %padding-icon-right { 39 | padding: $menu-item-padding-vertical $menu-item-padding-horizontal-with-arrow $menu-item-padding-vertical $menu-item-padding-horizontal; 40 | } 41 | %padding-icon-left { 42 | padding: $menu-item-padding-vertical $menu-item-padding-horizontal $menu-item-padding-vertical $menu-item-padding-horizontal-with-arrow; 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Tooltip/Tooltip.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withContentRect } from 'react-measure' 3 | 4 | const calculateDivStyle = (ownHeight, tooltip, scrollTop) => { 5 | var divStyle = {} 6 | if (tooltip && tooltip.show) { 7 | const headerHeight = 131 // TODO dynamic value 8 | const menuItemHeight = 33 // TODO variable 9 | 10 | let mouseYMinusHeader = tooltip.mouseY - headerHeight 11 | let index = mouseYMinusHeader / menuItemHeight 12 | let roundedMouseYMinusHeader = Math.floor(index) * menuItemHeight 13 | let roundedMouseYTop = roundedMouseYMinusHeader + headerHeight 14 | let roundedMouseY = roundedMouseYTop + (menuItemHeight / 2) 15 | let correctTooltipPosition = roundedMouseY - (ownHeight/2) 16 | 17 | divStyle = { 18 | left: tooltip.sidebarLeftWidth, 19 | top: correctTooltipPosition, 20 | } 21 | } 22 | return divStyle 23 | } 24 | 25 | const Tooltip = withContentRect('bounds')(({measureRef, measure, contentRect, tooltip, scrollTop}) => { 26 | var className = '' 27 | var text = '' 28 | var title = '' 29 | if (tooltip && tooltip.show) { 30 | className = 'tooltip left' 31 | text = tooltip.text === '' ? 'Não tem descrição' : tooltip.text 32 | } 33 | return ( 34 |
    35 | {text} 36 |
    37 | ) 38 | }) 39 | 40 | export default Tooltip 41 | -------------------------------------------------------------------------------- /src/components/ToolbarMenu/toolbarMenu.scss: -------------------------------------------------------------------------------- 1 | .toolbar-item { 2 | position: relative; 3 | } 4 | 5 | .toolbar-menu { 6 | margin-top: 15px; 7 | min-width: 190px; 8 | background-color: $toolbar-menu-background-color; 9 | border-radius: 5px; 10 | padding: 15px 0; 11 | position: absolute; 12 | right: 0; 13 | color: $toolbar-menu-color; 14 | font-family: sans-serif; 15 | box-shadow: 0 2px 7px 0 $toolbar-menu-shadow; 16 | opacity: 1; 17 | transition: opacity ease .15s; 18 | 19 | &.map { 20 | margin-top: -100px; 21 | padding: 15px 10px; 22 | } 23 | 24 | &.hidden { 25 | opacity: 0; 26 | transition: opacity ease .3s; 27 | } 28 | } 29 | 30 | .fa-close{ 31 | float:right; 32 | } 33 | 34 | %toolbar-dinlineb { 35 | display: inline-block; 36 | } 37 | 38 | .toolbar-inputshare { 39 | border: none; 40 | width: 290px; 41 | padding: 0 15px; 42 | 43 | button { 44 | @extend %toolbar-dinlineb; 45 | padding: 7px 12px; 46 | font-size: 14px; 47 | color: $toolbar-inputshare-button-color; 48 | background-color: $toolbar-inputshare-button-background-color; 49 | border-radius: 5px 0 0 5px; 50 | border: none; 51 | cursor: pointer; 52 | 53 | &:hover { 54 | background-color: $toolbar-inputshare-button-hover-background-color; 55 | } 56 | 57 | &:active { 58 | background-color: $toolbar-inputshare-button-active-background-color; 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/components/Toolbar/toolbar.scss: -------------------------------------------------------------------------------- 1 | .platform-toolbar { 2 | top: 15px; 3 | right: 15px; 4 | width: 350px; 5 | height: 53px; 6 | padding: 10px 15px; 7 | position: absolute; 8 | z-index: 1000; 9 | 10 | &.sidebar-left-opened { 11 | right: $layer-content-width + 15px; 12 | } 13 | } 14 | 15 | .map-toolbar { 16 | bottom: 30px; 17 | right: 50px; 18 | width: 150px; 19 | height: 53px; 20 | padding: 10px 15px; 21 | position: absolute; 22 | z-index: 1000; 23 | 24 | &.sidebar-left-opened { 25 | right: $layer-content-width + 50px; 26 | } 27 | } 28 | 29 | .toolbar-item { 30 | cursor: pointer; 31 | background-color: $toolbar-item-background-color; 32 | color: white; 33 | width: 30px; 34 | height: 30px; 35 | border-radius: 50%; 36 | margin-left: 10px; 37 | padding-top: 6px; 38 | text-align: center; 39 | float: right; 40 | border: solid 1px #002e56; 41 | box-shadow: 0px 3px 5px 0px rgba(0, 0, 0, 0.72); 42 | transition: background-color ease .15s; 43 | 44 | .tooltip { 45 | display: none; 46 | } 47 | 48 | &:hover { 49 | background-color: $toolbar-item-hover-background-color; 50 | 51 | .tooltip { 52 | display: block; 53 | } 54 | } 55 | 56 | &.active { 57 | background-color: $toolbar-item-active-background-color; 58 | z-index: 1; 59 | 60 | &:hover { 61 | .tooltip { 62 | display: none; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/LayerFilter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DataTable from '../../DataTable/DataTable.js' 3 | 4 | const LayerFilter = ({isFilterEmptyResult, isLoadingFilter, layers, modalLayerFilterName, onLayerFilterSearch}) => { 5 | const handleFilter = e => { 6 | e.preventDefault() 7 | onLayerFilterSearch(modalLayerFilterName, e.target.elements.parameterKey.value, e.target.elements.parameterValue.value.toLocaleLowerCase()) 8 | } 9 | 10 | const filteredLayers = layers.filter(l => l.name === modalLayerFilterName) 11 | if (filteredLayers.length === 0) { 12 | return

    Erro ao carregar parâmetros.

    13 | } 14 | const filteredLayer = filteredLayers[0] 15 | 16 | return ( 17 |
    18 |

    Selecione um parâmetro, digite um valor e clique em Filtrar:

    19 |
    20 | 23 | 24 | 25 |
    26 | {isLoadingFilter ?

    Aguarde, carregando dados...

    : null} 27 | {isFilterEmptyResult ?

    Não foram encontrados dados para o valor buscado.

    : null} 28 | {filteredLayer.filteredData ? 29 | 30 | : null} 31 |
    32 | ) 33 | } 34 | 35 | export default LayerFilter 36 | -------------------------------------------------------------------------------- /src/components/SearchLayer/SearchLayer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SearchLayer = ({onSearchClick, onKeyUpSearch, onBtnCleanSearch, searchString}) => { 4 | let input 5 | var searchIconClass = 'search-layer--icon fa fa-' 6 | var onIconClick = null 7 | if (searchString !== undefined && searchString.length === 0) { 8 | searchIconClass += 'search' 9 | } else { 10 | searchIconClass += 'close' 11 | onIconClick = () => { 12 | input.value = '' 13 | onBtnCleanSearch() 14 | } 15 | } 16 | 17 | const preventSubmit = (e) => { 18 | e.preventDefault() 19 | } 20 | 21 | const inputField = node => { 22 | input = node 23 | if (node) { 24 | node.focus() 25 | } 26 | } 27 | 28 | const inputOnKeyUp = () => { 29 | onKeyUpSearch(input.value) 30 | } 31 | 32 | const inputOnClick = () => { 33 | onSearchClick() 34 | } 35 | 36 | return ( 37 |
    38 | 50 |
    51 | ) 52 | } 53 | 54 | export default SearchLayer 55 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Login = ({ loginError, loginStatus, onLoginClick }) => { 4 | let userNameInput; 5 | let passwordInput; 6 | 7 | const handleKeyPress = (target) => { 8 | if(target.charCode === 13){ 9 | console.log('Enter clicked!!!') 10 | let data = { 11 | "username": userNameInput.value, 12 | "password": btoa(passwordInput.value), 13 | } 14 | onLoginClick(data) 15 | } 16 | } 17 | const handleLoginClick = () => { 18 | let data = { 19 | "username": userNameInput.value, 20 | "password": btoa(passwordInput.value), 21 | } 22 | onLoginClick(data) 23 | } 24 | 25 | return ( 26 |
    27 |
    28 |
    29 | 30 | 31 | { userNameInput = input; }} /> 32 | 33 | 34 | 35 | { passwordInput = input; }}/> 36 | 37 | handleLoginClick()}>Entrar 38 | {loginError ? Credenciais erradas. Tente novamente. : null} 39 |
    40 |
    41 |
    42 | ) 43 | } 44 | 45 | export default Login 46 | -------------------------------------------------------------------------------- /src/components/Tooltip/tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | padding: 10px 15px; 3 | background: $tooltip-bg-color; 4 | border-radius: 5px; 5 | box-shadow: 0 1px 12px 2px $tooltip-box-shadow-color; 6 | color: $tooltip-text-color; 7 | font-size: 12px; 8 | margin-left: 30px; 9 | position: absolute; 10 | z-index: 10000; 11 | 12 | &:before { 13 | width: 0; 14 | height: 0; 15 | content: ''; 16 | position: absolute; 17 | z-index: 10001; 18 | } 19 | 20 | &.left { 21 | max-width: 250px; 22 | 23 | &:before { 24 | border-top: 8px solid transparent; 25 | border-bottom: 8px solid transparent; 26 | border-right: 12px solid $tooltip-bg-color; 27 | margin-top: -8px; 28 | left: -10px; 29 | top: 50%; 30 | } 31 | } 32 | 33 | &.bottom, &.top { 34 | font-family: sans-serif; 35 | font-size: 14px; 36 | right: -5px; 37 | white-space: nowrap; 38 | margin-left: 0; 39 | 40 | &:before { 41 | right: 10px; 42 | } 43 | } 44 | 45 | &.bottom { 46 | bottom: -50px; 47 | 48 | &:before { 49 | border-right: 8px solid transparent; 50 | border-left: 8px solid transparent; 51 | border-bottom: 12px solid $tooltip-bg-color; 52 | top: -10px; 53 | } 54 | } 55 | 56 | &.top { 57 | top: -50px; 58 | 59 | &:before { 60 | border-right: 8px solid transparent; 61 | border-left: 8px solid transparent; 62 | border-top: 12px solid $tooltip-bg-color; 63 | bottom: -10px; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/App/polyfill.js: -------------------------------------------------------------------------------- 1 | // Polyfill to ChildNode.remove() 2 | (function (arr) { 3 | arr.forEach(function (item) { 4 | if (item.hasOwnProperty('remove')) { 5 | return; 6 | } 7 | Object.defineProperty(item, 'remove', { 8 | configurable: true, 9 | enumerable: true, 10 | writable: true, 11 | value: function remove() { 12 | if (this.parentNode !== null) 13 | this.parentNode.removeChild(this); 14 | } 15 | }); 16 | }); 17 | })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); 18 | 19 | // Polyfill to Array.prototype.map() 20 | if (!Array.prototype.map) { 21 | 22 | Array.prototype.map = function(callback, thisArg) { 23 | 24 | var T, A, k; 25 | 26 | if (this == null) { 27 | throw new TypeError(' this is null or not defined'); 28 | } 29 | var O = Object(this); 30 | var len = O.length >>> 0; 31 | 32 | if (typeof callback !== 'function') { 33 | throw new TypeError(callback + ' is not a function'); 34 | } 35 | 36 | if (arguments.length > 1) { 37 | T = thisArg; 38 | } 39 | A = new Array(len); 40 | k = 0; 41 | 42 | while (k < len) { 43 | 44 | var kValue, mappedValue; 45 | 46 | if (k in O) { 47 | kValue = O[k]; 48 | mappedValue = callback.call(T, kValue, k, O); 49 | A[k] = mappedValue; 50 | } 51 | k++; 52 | } 53 | 54 | return A; 55 | }; 56 | } 57 | 58 | // Polyfill to Number.isInteger() 59 | Number.isInteger = Number.isInteger || function(value) { 60 | return typeof value === 'number' && 61 | isFinite(value) && 62 | Math.floor(value) === value; 63 | }; 64 | -------------------------------------------------------------------------------- /src/components/SidebarRight/sidebarRight.scss: -------------------------------------------------------------------------------- 1 | .sidebar-right { 2 | background: $layer-content-bg-color; 3 | position: fixed; 4 | top: 0; 5 | right: 0; 6 | width: $layer-content-width; 7 | height: 100%; 8 | overflow-y: auto; 9 | z-index: 1001; // over Leaflet controls 10 | box-shadow: 0 0 15px 0 $sidebar-box-shadow-color; 11 | } 12 | 13 | .layer-list { 14 | position: relative; 15 | 16 | &--title { 17 | border-bottom: 1px solid $layer-list-title-border-color; 18 | color: $layer-list-area-title-color; 19 | font-size: 22px; 20 | font-weight: normal; 21 | padding: 15px; 22 | } 23 | 24 | &--remove-all-layers-button { 25 | background: $sidebar-right-remove-all-layers-button-bg-color; 26 | color: $sidebar-right-remove-all-layers-button-text-color; 27 | width: $sidebar-right-remove-all-layers-button-width; 28 | height: $sidebar-right-remove-all-layers-button-height; 29 | 30 | border: none; 31 | border-radius: 3px; 32 | cursor: pointer; 33 | position: absolute; 34 | top: 15px; 35 | right: 53px; 36 | 37 | &:hover { 38 | background-color: $layer-list-button-remove-all-layers-background; 39 | } 40 | } 41 | 42 | &--close-button { 43 | position: absolute; 44 | right: 20px; 45 | top: 16px; 46 | width: 25px; 47 | height: 25px; 48 | padding: 1px 0px 0 0; 49 | font-size: 15px; 50 | font-weight: normal; 51 | text-align: center; 52 | color: $layer-list-icon-color; 53 | cursor: pointer; 54 | border: 3px solid; 55 | border-radius: 50%; 56 | 57 | &:hover{ 58 | color: $layer-list-button-close-background; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assets/img/spritesheet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/GlobalFilter/Place.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Place = ({place, onPlaceClick}) => { 4 | const handleItemClick = (e) => { 5 | onPlaceClick({ text: e.target.textContent, id: e.target.dataset.id }) 6 | e.stopPropagation(); 7 | } 8 | 9 | let className = 'place' 10 | let hasOpenChild = false 11 | let hasChild = false 12 | let isBeingSearched = false 13 | 14 | if (place.nodes && place.nodes.length > 0) { 15 | hasChild = true 16 | // test if at least one element has show property true 17 | for (let i=0, l=place.nodes.length; i handleItemClick(e)}> 51 | {place.title} 52 | {place.nodes && place.nodes.map( p => 53 | 54 | )} 55 | 56 | ) 57 | } else { 58 | return null 59 | } 60 | } 61 | 62 | export default Place 63 | -------------------------------------------------------------------------------- /src/components/Header/application-header.scss: -------------------------------------------------------------------------------- 1 | .application-header { 2 | left: 15px; 3 | position: absolute; 4 | top: 15px; 5 | z-index: 1001; 6 | 7 | &--logo-placeholder { 8 | background-color: $logo-placeholder-background-color; 9 | border-radius: 4px; 10 | height: 58px; 11 | width: 230px; 12 | box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.72); 13 | } 14 | 15 | &--logo { 16 | height: 39px; 17 | margin: 8px 22px; 18 | width: 185px; 19 | } 20 | 21 | &--menu-button { 22 | position: relative; 23 | background-color: $menu-bg-color; 24 | border-radius: 50%; 25 | color: $menu-text-color; 26 | cursor: pointer; 27 | display: block; 28 | font-size: 32px; 29 | height: 55px; 30 | margin-top: 25px; 31 | padding-top: 10px; 32 | text-align: center; 33 | text-decoration: none; 34 | width: 55px; 35 | box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.72); 36 | transition: background-color ease .15s; 37 | 38 | &:hover{ 39 | background-color: $menu-bg-color-hover; 40 | } 41 | 42 | &:before { 43 | line-height: 38px; 44 | } 45 | 46 | } 47 | } 48 | .menu-button { 49 | 50 | &--tootltip { 51 | position: absolute; 52 | padding: 6px; 53 | width: 130px; 54 | font: 14px sans-serif; 55 | border-radius: 4px; 56 | background-color: $menu-button-tooltip-bgcolor; 57 | transform: translateY(6px) translateX(25px); 58 | 59 | &:before { 60 | content: '\f0d9'; 61 | position: absolute; 62 | top: 2px; 63 | left: -9px; 64 | font: 26px 'FontAwesome'; 65 | color: $menu-button-tooltip-bgcolor; 66 | } 67 | 68 | } 69 | } 70 | 71 | @keyframes attetionMenu { 72 | from { 73 | transform: translateY(6px) translateX(25px); 74 | } 75 | to { 76 | transform: translateY(6px) translateX(35px); 77 | } 78 | } 79 | 80 | .focus-menu { 81 | .menu-button--tootltip{ 82 | animation: attetionMenu ease-in-out infinite 0.4s alternate; 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/components/LayerSubtitle/LayerSubtitleSpace.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LayerSubtitle from '../LayerSubtitle/LayerSubtitle.js' 3 | import { DropTarget } from 'react-dnd' 4 | 5 | const subtitleTarget = { 6 | drop(props, monitor) { 7 | return {target: props.layer} 8 | } 9 | } 10 | 11 | function collect(connect, monitor) { 12 | return { 13 | connectDropTarget: connect.dropTarget(), 14 | isOver: monitor.isOver() 15 | } 16 | } 17 | 18 | const LayerSubtitleSpace = ({ 19 | connectDropTarget, 20 | id, 21 | isOver, 22 | lastClickData, 23 | layer, 24 | onClearLayerFilter, 25 | onIconMouseOut, 26 | onIconMouseOver, 27 | onLayerClick, 28 | onLayerDown, 29 | onLayerDrop, 30 | onLayerFilterSearch, 31 | onLayerRemove, 32 | onLayerUp, 33 | onLoadParams, 34 | onOpenLayerFilterModal, 35 | onOpenModal, 36 | }) => { 37 | function over() { 38 | if (isOver){ 39 | return { 40 | zIndex: 1, 41 | opacity: 0.5, 42 | backgroundColor: 'yellow', 43 | } 44 | } 45 | return {} 46 | } 47 | 48 | return connectDropTarget( 49 |
    54 | 71 |
    72 | ) 73 | } 74 | 75 | export default DropTarget("layerSubtitle", subtitleTarget, collect)(LayerSubtitleSpace) 76 | -------------------------------------------------------------------------------- /src/actions/ToolbarActions.js: -------------------------------------------------------------------------------- 1 | export const addGooglePlacesLatLong = latLong => { 2 | return { 3 | type: 'ADD_GOOGLE_PLACES_LAT_LONG', 4 | latLong, 5 | } 6 | } 7 | 8 | export const addPlaceLayer = item => { 9 | return { 10 | type: 'ADD_PLACE_LAYER', 11 | item, 12 | } 13 | } 14 | 15 | export const addTutelaLayer = item => { 16 | return { 17 | type: 'ADD_TUTELA_LAYER', 18 | item, 19 | } 20 | } 21 | 22 | export const changeActiveBaseMap = (baseMap) => { 23 | return { 24 | type: 'CHANGE_ACTIVE_BASE_MAP', 25 | baseMap, 26 | } 27 | } 28 | 29 | export const changeActiveToolbar = item => { 30 | return { 31 | type: 'CHANGE_ACTIVE_TOOLBAR', 32 | item, 33 | } 34 | } 35 | 36 | export const changeContour = item => { 37 | return { 38 | type: 'CHANGE_CONTOUR', 39 | item, 40 | } 41 | } 42 | 43 | export const changeGlobalFilterType = filterName => { 44 | return { 45 | type: 'CHANGE_GLOBAL_FILTER_TYPE', 46 | filterName, 47 | } 48 | } 49 | 50 | export const changeOpacity = item => { 51 | return { 52 | type: 'CHANGE_OPACITY', 53 | item, 54 | } 55 | } 56 | 57 | export const clearPlaceTutelaLayer = () => { 58 | return { 59 | type: 'CLEAR_PLACE_TUTELA_LAYER', 60 | } 61 | } 62 | 63 | export const searchPlaces = item => { 64 | return { 65 | type: 'SEARCH_PLACES', 66 | item, 67 | } 68 | } 69 | 70 | export const searchTutela = item => { 71 | return { 72 | type: 'SEARCH_TUTELA', 73 | item, 74 | } 75 | } 76 | 77 | export const togglePlace = item => { 78 | return { 79 | type: 'TOGGLE_PLACE', 80 | item, 81 | } 82 | } 83 | 84 | export const toggleTutela = item => { 85 | return { 86 | type: 'TOGGLE_TUTELA', 87 | item, 88 | } 89 | } 90 | 91 | export const activateDownloadLoader = () => { 92 | return { 93 | type: 'ACTIVATE_DOWNLOAD_LOADER', 94 | } 95 | } 96 | 97 | export const deactivateDownloadLoader = () => { 98 | return { 99 | type: 'DEACTIVATE_DOWNLOAD_LOADER', 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/News.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const News = ({onCloseModal}) => { 4 | 5 | return ( 6 |
    7 | Bem-vindo a plataforma de mapas interativos do inLoco 2.0! A nova plataforma é moderna e intuitiva e permite ao usuário visualizar e sobrepor dados geográficos de diversos assuntos, realizar buscas e dispor de diversas informações. Além das consultas de sempre, essa ferramenta traz de forma acessível novas funcionalidades e está disponível para uso no seu dia a dia: 8 |
      9 |
    1. 10 | Desenho livre – É uma ferramenta rápida e fácil que permite que você desenhe ou demarque um ponto de interesse ou áreas relevantes no mapa. 11 |
    2. 12 |
    3. 13 | Busca por polígono ou área – Facilita a busca de uma área fechada em determinada região e a descreve com varias informações num raio de distância definido no mapa. 14 |
    4. 15 | 16 |
    5. 17 | Busca de endereço – É um serviço que permite buscar um endereço ou um ponto de interesse, através de uma busca textual. 18 |
    6. 19 | 20 |
    7. 21 | Mudar a camada de fundo – É possível alterar o estilo de plano de fundo do mapa oferecendo imagens por satélite, terrenos ou logradouros do Estado do Rio de Janeiro. 22 |
    8. 23 | 24 |
    9. 25 | Google Street View – A nova ferramenta agora está integrada ao Street View, que é um recurso do Google. Ela disponibiliza vistas panorâmicas de 360° e permite que os usuários vejam partes de algumas regiões ao nível do chão/solo. 26 |
    10. 27 |
    28 | {/* 29 | * Date.now of last update 30 | */} 31 |
    32 | 35 |
    36 |
    37 | ) 38 | } 39 | 40 | export default News 41 | -------------------------------------------------------------------------------- /src/components/Sinalid/sinalid.scss: -------------------------------------------------------------------------------- 1 | .module-sinalid { 2 | h4 { 3 | font-weight: normal; 4 | margin-bottom: 10px; 5 | } 6 | 7 | .msg-sembase { 8 | font: 30px 'robotomedium'; 9 | color: #606062; 10 | text-align: center; 11 | } 12 | 13 | .sinalid-wrap { 14 | padding-top: 20px; 15 | padding-bottom: 20px; 16 | margin-left: auto; 17 | margin-right: auto; 18 | } 19 | 20 | .sinalid-loader { 21 | display: block; 22 | margin-top: 150px; 23 | margin-right: auto; 24 | margin-left: auto; 25 | width: 64px; 26 | height: 64px; 27 | } 28 | 29 | .sinalid-wrap-header { 30 | margin-bottom: 20px; 31 | } 32 | 33 | .sinalid-infodp { 34 | margin-bottom: 30px; 35 | } 36 | 37 | .info-policestation-name { 38 | font-size: 22px; 39 | color: #757575; 40 | } 41 | 42 | .info-policestation-address { 43 | font-size: 16px; 44 | color: #757575; 45 | } 46 | 47 | .info-policestation-address span { margin-right: 5px; } 48 | 49 | 50 | 51 | .sinalid-logo { margin-bottom: 30px;} 52 | .sinalid-card { 53 | position: relative; 54 | margin-bottom: 25px; 55 | min-height: 140px; 56 | overflow: hidden; 57 | background-color: #ffffff; 58 | box-shadow: 0px 0px 9px 3px rgba(000,000,000, 0.20); 59 | } 60 | 61 | /* Conteudo card */ 62 | 63 | .sinalid-card--photo { 64 | position: absolute; 65 | top: -2px; 66 | width: 112px; 67 | height: 145px; 68 | } 69 | 70 | .sinalid-card--content { 71 | display: inline-block; 72 | padding: 12px; 73 | margin-left: 112px; 74 | } 75 | 76 | .card-content-header { 77 | margin-bottom: 10px; 78 | padding-bottom: 8px; 79 | border-bottom: solid 1px #d2d1d1; 80 | } 81 | 82 | .content-title { 83 | font: 18px 'robotomedium', 'Arial', 'sans-serif'; 84 | color: #757575; 85 | } 86 | 87 | .content-title .icon-sinalid { font-size: 24px; } 88 | .content-date { font: 12px 'robotolight', 'Arial', 'sans-serif'; } 89 | 90 | .content-date span { 91 | font-family: 'robotomedium', 'Arial', 'sans-serif'; 92 | color: #1d9c8f; 93 | } 94 | 95 | .card-content-text { font-size: 12px;} 96 | } 97 | -------------------------------------------------------------------------------- /src/components/GooglePlaces/GooglePlaces.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | export default class GooglePlaces extends Component { 4 | constructor(props) { 5 | super(props) 6 | } 7 | 8 | preventSubmit(e) { 9 | e.preventDefault() 10 | } 11 | 12 | onPlacesChanged(searchBox) { 13 | var places = searchBox.getPlaces() 14 | var viewport = places[0].geometry.viewport 15 | 16 | var nelat = viewport.getNorthEast().lat() 17 | var nelng = viewport.getNorthEast().lng() 18 | var swlat = viewport.getSouthWest().lat() 19 | var swlng = viewport.getSouthWest().lng() 20 | 21 | return [ 22 | [ 23 | nelat, nelng 24 | ], 25 | [ 26 | swlat, swlng 27 | ] 28 | ] 29 | } 30 | 31 | componentDidMount() { 32 | const { onAddGooglePlacesLatLong } = this.props 33 | const onPlacesChanged = this.onPlacesChanged 34 | 35 | const defaultBounds = new google.maps.LatLngBounds( 36 | new google.maps.LatLng(-23.3926, -44.8935), 37 | new google.maps.LatLng(-20.7916, -40.7384) 38 | ) 39 | 40 | let input = document.getElementById("GooglePlacesSearch") 41 | let searchBox = new google.maps.places.SearchBox(input, { 42 | bounds: defaultBounds 43 | }) 44 | 45 | // Listen for the event fired when the user selects a prediction and retrieve 46 | // more details for that place. 47 | searchBox.addListener('places_changed', function() { 48 | onAddGooglePlacesLatLong(onPlacesChanged(this)) 49 | }) 50 | } 51 | 52 | render() { 53 | return ( 54 |
    59 | 65 | 71 |
    72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/PolygonData/PolygonData.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Charts from '../Charts/Charts.js' 3 | 4 | const PolygonData = ({ polygonData }) => { 5 | 6 | let populacao = polygonData.filter(l => l.category === "População")[0] 7 | 8 | let columns = [] 9 | let keys = Object.keys(populacao.piramide_total) 10 | 11 | for (let i = 0; i < keys.length; i++) { 12 | let key = keys[i]; 13 | let splittedKey = key.split('_') 14 | columns.push( 15 | [splittedKey[1]+'-'+splittedKey[2], key] 16 | ) 17 | 18 | } 19 | 20 | let chartObject = { 21 | "charts": [ 22 | { 23 | columns, 24 | "entity": "População", 25 | "title": "Pirâmide Etária", 26 | "type": "piramide" 27 | } 28 | ], 29 | "features": [ 30 | { 31 | "properties": Object.assign({}, populacao.piramide_total) 32 | } 33 | ], 34 | } 35 | 36 | return ( 37 |
    38 |
    39 |

    40 | Dados do polígono 41 |

    42 |
    43 |
    44 | 45 | 46 | { 47 | polygonData.map( (l, index) => { 48 | let title = l.category 49 | let value = l.items.length 50 | if(l.category === "População"){ 51 | value = l.populacao_total 52 | } 53 | return ( 54 | 55 | 56 | 57 | 58 | ) 59 | }) 60 | } 61 | 62 |
    {title}{value}
    63 | 64 |
    65 |
    66 | ) 67 | } 68 | 69 | export default PolygonData 70 | -------------------------------------------------------------------------------- /src/components/Menu/MenuContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Menu from './Menu' 3 | import { connect } from 'react-redux' 4 | import { toggleLayer, toggleMenu, untoggleAll, showDescription, hideDescription, updateScrollTop } from '../../actions/actions.js' 5 | 6 | const getVisibleMenuElements = (menuItems) => { 7 | let hasOneVisibleMenuItem = false 8 | for (let menuItem of menuItems) { 9 | if (menuItem.selected) { 10 | hasOneVisibleMenuItem = true 11 | } 12 | } 13 | 14 | if (!hasOneVisibleMenuItem) { 15 | return menuItems 16 | } 17 | 18 | return menuItems.filter(menuItem => menuItem.selected) 19 | } 20 | 21 | const getVisibleLayers = (layers) => { 22 | let hasOneVisibleLayer = false 23 | for (let layer of layers) { 24 | if (layer.match) { 25 | hasOneVisibleLayer = true 26 | } 27 | } 28 | 29 | if (!hasOneVisibleLayer) { 30 | return layers 31 | } 32 | 33 | return layers.filter(layer => layer.match) 34 | } 35 | 36 | const mapStateToProps = (state, ownProps) => { 37 | return { 38 | menuItems: Array.isArray(state.menuItems) ? state.menuItems : [], 39 | layers: Array.isArray(state.layers) ? state.layers : [], 40 | currentLevel: state.currentLevel, 41 | sidebarLeftWidth: ownProps.sidebarLeftWidth, 42 | sidebarLeftHeight: ownProps.sidebarLeftHeight, 43 | } 44 | } 45 | 46 | const mapDispatchToProps = (dispatch) => { 47 | return { 48 | onLayerClick: (item) => { 49 | dispatch(toggleLayer(item)) 50 | }, 51 | onMenuItemClick: (item) => { 52 | dispatch(toggleMenu(item)) 53 | }, 54 | onUntoggleAllClick: () => { 55 | dispatch(untoggleAll()) 56 | }, 57 | onMouseOver: (e, 58 | layer, 59 | sidebarLeftWidth, 60 | parentHeight, 61 | top, 62 | bottom, 63 | ) => { 64 | if (layer) { 65 | dispatch(showDescription( 66 | layer, 67 | sidebarLeftWidth, 68 | e.clientY, 69 | )) 70 | } 71 | }, 72 | onMouseOut: (layer) => { 73 | if (layer) { 74 | dispatch(hideDescription(layer)) 75 | } 76 | }, 77 | onScroll: (scrollTop) => { 78 | dispatch(updateScrollTop(scrollTop)) 79 | }, 80 | }; 81 | }; 82 | 83 | const MenuContainer = connect( 84 | mapStateToProps, 85 | mapDispatchToProps 86 | )(Menu) 87 | 88 | export default MenuContainer 89 | -------------------------------------------------------------------------------- /src/components/Modal/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import About from './Contents/About.js' 3 | import LayerFilter from './Contents/LayerFilter.js' 4 | import Login from './Contents/Login.js' 5 | import News from './Contents/News.js' 6 | import Table from './Contents/Table.js' 7 | 8 | const Modal = ({ 9 | isFilterEmptyResult, 10 | isLoadingFilter, 11 | lastClickData, 12 | layers, 13 | loginError, 14 | loginStatus, 15 | modalLayerFilterName, 16 | newsModal, 17 | showAbout, 18 | showLayerFilterModal, 19 | showLogin, 20 | showModal, 21 | onChangeActiveTab, 22 | onCloseModal, 23 | onGetModalData, 24 | onLayerFilterSearch, 25 | onLoginClick, 26 | onPaginate, 27 | }) => { 28 | 29 | function handleCloseModal() { 30 | return onCloseModal() 31 | } 32 | 33 | let ContentComponent = 34 | 41 | let sectionClassName = "modal" 42 | let modalTitle = "Tabela de Registros" 43 | 44 | if (!showModal) { 45 | return null 46 | } 47 | 48 | if (newsModal) { 49 | newsModal = false 50 | sectionClassName = "news-modal" 51 | modalTitle = "Últimas atualizações e novidades" 52 | 53 | ContentComponent = 54 | } 55 | 56 | if (showLogin) { 57 | sectionClassName = "login-modal" 58 | modalTitle = "Login" 59 | 60 | ContentComponent = 61 | } 62 | 63 | if (showAbout) { 64 | sectionClassName = "about-modal" 65 | modalTitle = "Sobre" 66 | ContentComponent = 67 | } 68 | 69 | if (showLayerFilterModal) { 70 | sectionClassName = "filter-modal" 71 | modalTitle = "Filtro de camada" 72 | ContentComponent = 79 | } 80 | 81 | return ( 82 |
    83 |
    84 |

    85 | {modalTitle} 86 | 87 |

    88 | {ContentComponent} 89 |
    90 |
    91 | ) 92 | } 93 | 94 | export default Modal 95 | -------------------------------------------------------------------------------- /src/components/LayerStylesCarousel/layerStylesCarousel.scss: -------------------------------------------------------------------------------- 1 | .layer-styles-carousel { 2 | position: relative; 3 | margin-bottom: 10px; 4 | 5 | &::selection { 6 | background: none; 7 | } 8 | 9 | &--arrow { 10 | z-index: 2; 11 | position: absolute; 12 | top: 33%; 13 | padding: 7px; 14 | width: 25px; 15 | height: 25px; 16 | font-size: 12px; 17 | text-align: center; 18 | color: $layer-button-carousel-text-color; 19 | border-radius: 50%; 20 | background-color: $layer-button-carousel-bg-color; 21 | cursor: pointer; 22 | transition: ease background-color .3s, color .3s; 23 | 24 | &.remove { 25 | display: none; 26 | } 27 | 28 | &:hover, 29 | &:focus { 30 | transition-duration: .15s, .15s; 31 | color: $layer-button-carousel-text-color-hover; 32 | background-color: $layer-button-carousel-bg-color-hover; 33 | } 34 | 35 | &:active { 36 | opacity: .5; 37 | } 38 | 39 | &.fa-chevron-left:before { margin-left: -3px; } 40 | &.fa-chevron-right:before { margin-right: -2px; } 41 | } 42 | .layer-styles-carousel--arrow:last-child{ right: 16px; } 43 | 44 | &--list-container { 45 | display: inline-block; 46 | margin-top: 10px; 47 | margin-left: 2px; 48 | overflow: hidden; 49 | width: 328px; 50 | } 51 | 52 | &--list { 53 | height: 39px; 54 | position: relative; 55 | transition-duration: .2s; 56 | } 57 | 58 | &--list-item { 59 | display: inline-block; 60 | height: 39px; 61 | margin-left: 2px; 62 | border: 1px solid rgba(0,0,0,0); // every item has a 1px transparent border 63 | transition: 64 | background-color ease .3s, 65 | opacity ease .3s; 66 | 67 | &:first-child { 68 | margin-left: 0; 69 | } 70 | 71 | &:hover, 72 | &:active, 73 | &.selected { 74 | background-color: $layer-styles-list-item-selected-background; 75 | transition: 76 | background-color ease .15s, 77 | opacity ease .15s; 78 | 79 | .layer-styles-carousel--image { 80 | opacity: .5; 81 | } 82 | 83 | } 84 | &:active { 85 | background-color: $layer-styles-list-item-active-background-color 86 | } 87 | 88 | } 89 | 90 | &--image { 91 | border: 1px solid $layer-list-secondary-text-color; 92 | cursor: pointer; 93 | height: 37px; 94 | width: 60px; 95 | 96 | &:hover { 97 | border: 1px solid $layer-list-area-title-color; 98 | } 99 | 100 | &::selection { 101 | background: none; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/components/Menu/menuReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates menu array 3 | * @param {Object[]} layers Array of layers 4 | * @returns {Object[]} Menu array with categories data, including layers IDs 5 | */ 6 | const menuReducer = (layers) => { 7 | const menu = [] 8 | 9 | layers.forEach(layer => { 10 | if (layer.menu2) { 11 | // creates menu item if it doesn't exists 12 | const menuAlreadyExists = menu.some(menuItem => (layer.menu2[0] && menuItem.id === layer.menu2[0])) 13 | const subMenuAlreadyExists = menu.some(menuItem => (layer.menu2[1] && menuItem.id === layer.menu2[1])) 14 | 15 | // creates menu item if it doesn't exists 16 | if (!menuAlreadyExists && layer.menu2[0].trim()) { 17 | // create menu item 18 | menu.push({ 19 | display: true, 20 | id: layer.menu2[0], 21 | title: layer.menu2[0], 22 | layers: [], 23 | idMenu: menu.length, 24 | isSubMenu: false, 25 | submenus: [], 26 | }); 27 | } 28 | 29 | // new submenu 30 | if (!subMenuAlreadyExists && layer.menu2[1] && layer.menu2[1].trim()) { 31 | // create submenu 32 | const thisSubmenuId = menu.length 33 | 34 | menu.push({ 35 | display: true, 36 | id: layer.menu2[1], 37 | title: layer.menu2[1], 38 | layers: [], 39 | idMenu: thisSubmenuId, 40 | isSubMenu: true, 41 | submenus: [], 42 | }) 43 | 44 | // include my submenu id to father menu submenus array 45 | menu.forEach((menuItem) => { 46 | // find my father 47 | if (menuItem.id === layer.menu2[0]) { 48 | // add to submenus array 49 | menuItem.submenus.push(thisSubmenuId) 50 | } 51 | }) 52 | } 53 | 54 | // then add the layer ID to an array of its menu item 55 | menu.forEach((menuItem) => { 56 | // make sure it has no submenu 57 | if (layer.menu2.length === 1 && menuItem.id === layer.menu2[0]) { 58 | // add layer to this menu layers array 59 | menuItem.layers.push(layer.key) 60 | } 61 | 62 | // then add the layer ID to an array of its menu item 63 | if (layer.menu2.length === 2 && menuItem.id === layer.menu2[1]) { 64 | // add layer to this submenu layers array 65 | menuItem.layers.push(layer.key) 66 | } 67 | 68 | }) 69 | } 70 | }) 71 | 72 | // finally, sort menu categories in A-Z 73 | menu.sort((a, b) => { 74 | if (a.title < b.title) { 75 | return -1 76 | } 77 | if (a.title > b.title) { 78 | return 1 79 | } 80 | return 0 81 | }) 82 | 83 | return menu 84 | } 85 | 86 | export default menuReducer 87 | -------------------------------------------------------------------------------- /src/components/LayerStylesCarousel/LayerStylesCarousel.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LayerStyleItem from './LayerStyleItem' 3 | 4 | const LayerStylesCarousel = ({ layer, onArrowLeftClick, onArrowRightClick, onStyleClick }) => { 5 | 6 | const STYLE_WIDTH = 66 7 | 8 | let selectedLayerId 9 | 10 | let leftArrowClassName = 'layer-styles-carousel--arrow fa fa-chevron-left' 11 | let rightArrowClassName = 'layer-styles-carousel--arrow fa fa-chevron-right' 12 | 13 | let layerStylesLength = 0 14 | let layerStylesPositionCounter = 0 15 | 16 | if (layer) { 17 | if (layer.styles && layer.styles.length < 6) { 18 | leftArrowClassName += ' remove' 19 | rightArrowClassName += ' remove' 20 | } else { 21 | if (!layer.stylesPositionCounter) { 22 | leftArrowClassName += ' hidden' 23 | } else if (layer.stylesPositionCounter === layer.styles.length - 5) { 24 | rightArrowClassName += ' hidden' 25 | } 26 | } 27 | 28 | if (layer.styles) { 29 | layerStylesLength = layer.styles.length * STYLE_WIDTH 30 | } 31 | if (layer.stylesPositionCounter) { 32 | layerStylesPositionCounter = layer.stylesPositionCounter 33 | } 34 | } 35 | 36 | let carouselListStyle = { 37 | width: layerStylesLength + 'px', 38 | marginLeft: -(layerStylesPositionCounter * STYLE_WIDTH) + 'px', 39 | } 40 | 41 | function arrowLeftClick() { 42 | return onArrowLeftClick(layer) 43 | } 44 | 45 | function arrowRightClick() { 46 | return onArrowRightClick(layer) 47 | } 48 | 49 | if (layer.stylesOrdered) { 50 | layer.styles.sort((a, b) => { 51 | if (a.title > b.title) { 52 | return 1 53 | } 54 | if (a.title < b.title) { 55 | return -1 56 | } 57 | return 0 58 | }).reverse() 59 | } 60 | 61 | return ( 62 |
    63 | 64 |
    65 |
      66 | { 67 | layer && layer.styles ? 68 | layer.styles.map((style, indexStyle) => { 69 | return ( 70 | 77 | ) 78 | }) 79 | : '' 80 | } 81 |
    82 |
    83 | 84 |
    85 | ) 86 | } 87 | 88 | export default LayerStylesCarousel 89 | -------------------------------------------------------------------------------- /src/components/Modal/ModalContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Modal from './Modal' 3 | import { connect } from 'react-redux' 4 | import { 5 | changeActiveTab, 6 | closeModal, 7 | getModalData, 8 | layerFilterLoaded, 9 | layerFilterLoading, 10 | loginUser, 11 | paginate, 12 | populateApp, 13 | } from '../../actions/actions.js' 14 | import GeoAPI from '../Api/GeoAPI.js' 15 | import ScaAPI from '../Api/ScaAPI.js' 16 | 17 | const mapStateToProps = state => { 18 | return { 19 | isFilterEmptyResult: state.isFilterEmptyResult, 20 | isLoadingFilter: state.isLoadingFilter, 21 | lastClickData: state.lastClickData, 22 | layers: state.layers, 23 | loginStatus: state.loginStatus, 24 | loginError: state.loginError, 25 | modalLayerFilterName: state.modalLayerFilterName, 26 | newsModal: state.newsModal, 27 | showAbout: state.showAbout, 28 | showLayerFilterModal: state.showLayerFilterModal, 29 | showLogin: state.showLogin, 30 | showModal: state.showModal, 31 | } 32 | } 33 | 34 | const mapDispatchToProps = dispatch => { 35 | const onAjaxDataFetched = layerData => { 36 | dispatch(getModalData(layerData)) 37 | } 38 | 39 | const onGetModalData = (layer, lastClickData) => { 40 | const MAX_ITEMS_TO_LOAD = 9999 41 | 42 | let url = GeoAPI.createUrl({ 43 | layer: layer, 44 | clickData: lastClickData, 45 | featureCount: MAX_ITEMS_TO_LOAD 46 | }) 47 | GeoAPI.getLayerData(onAjaxDataFetched, url) 48 | } 49 | 50 | const populateCallback = xmlData => { 51 | dispatch(populateApp(xmlData, location.hash)) 52 | } 53 | 54 | const loginCallback = data => { 55 | dispatch(loginUser(data)) 56 | GeoAPI.getContent(populateCallback) 57 | } 58 | 59 | const authenticate = ({username, password}) => { 60 | ScaAPI.logInUser(loginCallback, username, password) 61 | } 62 | 63 | const authenticateLogout = () => { 64 | ScaAPI.logOutUser(loginCallback) 65 | } 66 | 67 | const onLayerFilterSearchLoaded = data => { 68 | dispatch(layerFilterLoaded(data)) 69 | } 70 | 71 | return { 72 | /** 73 | * Fetch data from server to get content 74 | * of the selected tab 75 | */ 76 | onChangeActiveTab: (layer, lastClickData) => { 77 | dispatch(changeActiveTab(layer)) 78 | var selectedLayer = layer 79 | // Call AJAX 80 | onGetModalData(selectedLayer, lastClickData) 81 | }, 82 | onCloseModal: () => { 83 | dispatch(closeModal()) 84 | }, 85 | onLayerFilterSearch: (layerName, parameterKey, parameterValue) => { 86 | dispatch(layerFilterLoading(layerName, parameterKey, parameterValue)) 87 | GeoAPI.getLayerFilteredData(layerName, parameterKey, parameterValue, onLayerFilterSearchLoaded) 88 | }, 89 | onLoginClick: data => { 90 | authenticate(data) 91 | }, 92 | onPaginate: (layer, page) => { 93 | dispatch(paginate(layer, page)) 94 | }, 95 | } 96 | } 97 | 98 | const ModalContainer = connect( 99 | mapStateToProps, 100 | mapDispatchToProps 101 | )(Modal) 102 | 103 | export default ModalContainer 104 | -------------------------------------------------------------------------------- /src/components/Toolbar/ToolbarContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Toolbar from './Toolbar' 3 | import { connect } from 'react-redux' 4 | import { 5 | addPlaceLayer, 6 | addTutelaLayer, 7 | changeActiveBaseMap, 8 | changeActiveToolbar, 9 | changeContour, 10 | changeGlobalFilterType, 11 | changeOpacity, 12 | clearPlaceTutelaLayer, 13 | searchPlaces, 14 | searchTutela, 15 | togglePlace, 16 | toggleTutela, 17 | activateDownloadLoader, 18 | deactivateDownloadLoader, 19 | } from '../../actions/ToolbarActions' 20 | import { 21 | populateApp, 22 | } from '../../actions/actions.js' 23 | import ScaAPI from '../Api/ScaAPI.js' 24 | import GeoAPI from '../Api/GeoAPI.js' 25 | 26 | const mapStateToProps = (state, ownProps) => { 27 | return { 28 | baseMaps: state.baseMaps, 29 | globalFilterType: state.globalFilterType, 30 | layers: state.layers, 31 | loginStatus: state.loginStatus, 32 | mapProperties: state.mapProperties, 33 | places: state.places, 34 | showSidebarRight: state.showSidebarRight, 35 | toolbarActive: state.toolbarActive, 36 | tutela: state.tutela, 37 | ownProps, 38 | } 39 | } 40 | 41 | const mapDispatchToProps = dispatch => { 42 | const populateCallback = xmlData => { 43 | dispatch(populateApp(xmlData, location.hash)) 44 | } 45 | const logoutCallback = data => { 46 | ScaAPI.logOutUser() 47 | GeoAPI.getContent(populateCallback) 48 | } 49 | return { 50 | onChangeActiveBaseMap: baseMap => { 51 | dispatch(changeActiveBaseMap(baseMap)) 52 | }, 53 | onClearPlaceTutelaLayer: () => { 54 | dispatch(clearPlaceTutelaLayer()) 55 | }, 56 | onContourChange: item => { 57 | dispatch(changeContour(item)) 58 | }, 59 | onGlobalFilterTypeChange: item => { 60 | dispatch(changeGlobalFilterType(item)) 61 | }, 62 | onKeyUpSearchPlaces: item => { 63 | dispatch(searchPlaces(item)) 64 | }, 65 | onKeyUpSearchTutela: item => { 66 | dispatch(searchTutela(item)) 67 | }, 68 | onOpacityChange: item => { 69 | dispatch(changeOpacity(item)) 70 | }, 71 | onPlaceClick: item => { 72 | dispatch(togglePlace(item)) 73 | dispatch(addPlaceLayer(item)) 74 | }, 75 | onToolbarItemClick: item => { 76 | // wait a little bit for search component open 77 | window.setTimeout(() => { 78 | document.getElementById('searchField').focus() 79 | }, 100) 80 | dispatch(changeActiveToolbar(item)) 81 | if (item === 'logout') { 82 | logoutCallback() 83 | } 84 | }, 85 | onTutelaClick: item => { 86 | dispatch(toggleTutela(item)) 87 | dispatch(addTutelaLayer(item)) 88 | }, 89 | onDownloadClick: () => { 90 | dispatch(activateDownloadLoader()) 91 | }, 92 | onDownloadEnd: () => { 93 | dispatch(deactivateDownloadLoader()) 94 | } 95 | } 96 | } 97 | 98 | const ToolbarContainer = connect( 99 | mapStateToProps, 100 | mapDispatchToProps 101 | )(Toolbar) 102 | 103 | export default ToolbarContainer 104 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Our development objectives and open issues are all public on [GitHub issues](https://github.com/MinisterioPublicoRJ/inloco/issues). Pull requests are welcome! 4 | 5 | Optionally, you can install [ZenHub](https://github.com/marketplace/zenhub) plugin (available on Chrome and Firefox) to see development plan board, see issues already in progress etc. 6 | 7 | ## Running 8 | The development build (`npm start`) will also start docs build (esdoc). This docs build will compile all components docs and show a webpage. To access it just: 9 | 1. `$ npm start` 10 | 1. Access url `http://localhost:3000/` 11 | 12 | Docs 13 | 1. Access url `http://localhost:3000/esdoc/` 14 | 15 | Other options are: 16 | 17 | ### Production mode 18 | 19 | ``` 20 | $ npm run prod 21 | ``` 22 | 23 | ### Production build (without running server) 24 | 25 | ``` 26 | $ npm run build 27 | ``` 28 | 29 | ### Run tests 30 | 31 | ``` 32 | $ npm install -g jest 33 | $ npm run test 34 | ``` 35 | If you get error with jest, try running `jest --no-cache` 36 | 37 | ### Running on mock mode 38 | This mode does not call the geoserver, instead it simulates geoserver calls. 39 | 40 | ``` 41 | $ npm run mock 42 | ``` 43 | 44 | ## Testing 45 | The tests environment was made using [jest](https://facebook.github.io/jest/) and [enzyme](https://github.com/airbnb/enzyme). At first, we're doing the following test types: 46 | 1. Component rendering with [Snapshot test](https://facebook.github.io/jest/docs/snapshot-testing.html). Example: 47 | 48 | ```javascript 49 | import React from 'react'; 50 | import App from '../../src/components/App/App.js'; 51 | import renderer from 'react-test-renderer'; 52 | 53 | it('component renders correctly', () => { 54 | const app = shallow(); 55 | expect(app).toMatchSnapshot(); 56 | }); 57 | ``` 58 | 59 | 2. Property tests (props) 60 | 3. Events tests 61 | 62 | ### How to run all tests 63 | `$ npm run test` : run all tests 64 | 65 | `$ npm run test:watch` : run all tests right now and also when some component change 66 | 67 | `$ npm run test:coverage` : run all tests and show coverage information 68 | 69 | 70 | 71 | ## Documenting 72 | We use JSDoc to document components. For each component we write: 73 | 1. Basic description of what the component is, soon after component class declaration, write the description with a comment: 74 | ```javascript 75 | /** 76 | * App component that represents the application 77 | */ 78 | ``` 79 | 1. What every component method does and what it returns, right after module declaration 80 | ```javascript 81 | /** 82 | * renders the element 83 | * @return html markup of the element 84 | */ 85 | ``` 86 | 87 | Just like on following example, with App class. 88 | ```javascript 89 | import React from 'react'; 90 | import Input from '../input/Input.js'; 91 | 92 | require('./app.scss'); 93 | 94 | /** 95 | * App component that represents the application 96 | */ 97 | 98 | export default class App extends React.Component { 99 | /** 100 | * renders the element 101 | * @return html markup of the element 102 | */ 103 | render() { 104 | return ( 105 |
    106 |

    Hello World

    107 | 108 |
    109 |
    110 | ); 111 | } 112 | } 113 | ``` 114 | -------------------------------------------------------------------------------- /src/components/App/reducers/geoServerXmlStyleReducer.js: -------------------------------------------------------------------------------- 1 | // styles constants 2 | const WORKSPACE = __WORKSPACE__ 3 | const ENDPOINT = __API__ 4 | const ICON_SIZE = { x: 27, y: 20 } 5 | const THUMB_SIZE = { x: 225, y: 150 } 6 | const TOOLTIP_SIZE = 750 7 | const PROJECTION = 'EPSG:3857' 8 | const ICON_THUMB_URL = './resources/img/plataforma/icones/' 9 | 10 | /** 11 | * Returns bounding box for a given layer 12 | * @param xmlNode XML node with a GeoServer layer 13 | * @return {String} bounding box string: minX,minY,maxX,maxY 14 | */ 15 | export const parseBoundingBox = (xmlNode) => { 16 | let boundingBox = xmlNode.getElementsByTagName('BoundingBox')[0] 17 | 18 | let minX = boundingBox.attributes.minx.value 19 | let minY = boundingBox.attributes.miny.value 20 | let maxX = boundingBox.attributes.maxx.value 21 | let maxY = boundingBox.attributes.maxy.value 22 | 23 | return `${minX},${minY},${maxX},${maxY}` 24 | } 25 | 26 | /** 27 | * Returns styles array 28 | * @param xmlNode XML node with a GeoServer layer 29 | * @param layer object with parsed layer data 30 | * @return {Object[]} Styles array 31 | */ 32 | export const parseStyle = (xmlNode, layer) => { 33 | let styles = [] 34 | const styleCollection = xmlNode.getElementsByTagName('Style') 35 | 36 | for (let i=0, l=styleCollection.length; i${layer.title}
    ${styleTitle}` 58 | 59 | let styleObj = { 60 | 'id' : i, 61 | 'title' : styleTitle, 62 | 'name' : styleName, 63 | 'description' : styleAbstract, 64 | 'icon' : iconURL, // icon generated by GeoServer dynamically 65 | 'icon_2' : iconURL_2, // cached icon 66 | 'thumb' : thumbURL, // thumb generated by GeoServer dynamically 67 | 'thumb_2' : thumbURL_2, // cached thumb 68 | 'tooltip' : tooltip, 69 | } 70 | 71 | styles.push(styleObj) 72 | } 73 | 74 | return styles 75 | } 76 | -------------------------------------------------------------------------------- /tests/test_generator.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var tests = []; 4 | 5 | var test = { 6 | className: "", 7 | imports: "" 8 | } 9 | 10 | var dataConfig; 11 | 12 | var dir = "/Users/rafael.tavares/projects-mp/github/inLoco-2.0/src/components"; 13 | 14 | function readConfigFile(){ 15 | fs.readFile('/Users/rafael.tavares/projects-mp/github/inLoco-2.0/tests/tests-boilerplate.json', 'utf8', function (err,data) { 16 | if (err) { 17 | return console.log(err); 18 | } 19 | dataConfig = JSON.parse(data); 20 | dive(dir, dataConfig); 21 | }); 22 | } 23 | 24 | 25 | 26 | var dive = function (dir, dataConfig) { 27 | fs.readdir(dir, function (err, list) { 28 | // Return the error if something went wrong 29 | if (err) 30 | return err; 31 | 32 | // For every file in the list 33 | list.forEach(function (file) { 34 | // Full path of that file 35 | var path = dir + "/" + file; 36 | var extension = file.split(".")[1]; 37 | if(extension === "js"){ 38 | read(path, dataConfig); 39 | } 40 | // Get the file's stats 41 | fs.stat(path, function (err, stat) { 42 | // If the file is a directory 43 | if (stat && stat.isDirectory()) 44 | // Dive into the directory 45 | dive(path, dataConfig); 46 | // else 47 | // Call the action 48 | // action(null, path); 49 | }); 50 | }); 51 | }); 52 | }; 53 | 54 | function read(pathToFile, dataConfig){ 55 | fs.readFile(pathToFile, 'utf8', function (err,data) { 56 | if (err) { 57 | return console.log(err); 58 | } 59 | data = data.split(" "); 60 | for (var index = 0; index < data.length; index++) { 61 | var element = data[index]; 62 | if (element === "class"){ 63 | var className = data[index+1]; 64 | test = { 65 | "className": "", 66 | "imports": `import ${className} from "../../src/components/${className}/${className}.js"\n` 67 | }; 68 | test.className = className; 69 | tests.push(test); 70 | } 71 | } 72 | //console.log(test); 73 | writeTest(test, dataConfig); 74 | }); 75 | } 76 | readConfigFile(); 77 | 78 | function writeTest(test, dataConfig){ 79 | for (var index = 0; index < dataConfig.imports.length; index++) { 80 | var what = dataConfig.imports[index].what; 81 | var where = dataConfig.imports[index].where; 82 | test.imports += "import "+ what + " from " + where + "\n"; 83 | } 84 | for (var index = 0; index < dataConfig.tests.length; index++) { 85 | var element = dataConfig.tests[index]; 86 | if (element.name === "snapshot"){ 87 | var varName = test.className.charAt(0).toLowerCase() + test.className.slice(1); 88 | element.testString = 89 | `it('component renders correctly', () => { 90 | const ${varName} = shallow(<${test.className}/>); 91 | expect(${varName}).toMatchSnapshot(); 92 | })\n`; 93 | test.testString = element.testString; 94 | } 95 | } 96 | console.log(test); 97 | //writeTest(test); 98 | fs.writeFile(`/Users/rafael.tavares/projects-mp/github/inLoco-2.0/tests/auto/${test.className}.test.js`, test.imports + test.testString, { flag: "wx" }, function(err) { 99 | if(err) { 100 | return console.log(err); 101 | } 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /src/components/ShareUrl/ShareUrl.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ClipboardButton from 'react-clipboard.js' 3 | 4 | /** 5 | * Filters layers using the `selected` property 6 | * @param {Object[]} layers - layers array 7 | * @returns {Object[]} - returns all layers that are selected 8 | */ 9 | const selectedLayers = (layers) => { 10 | if (!Array.isArray(layers)) { 11 | return [] 12 | } 13 | return layers.filter(layer => layer.selected) 14 | } 15 | 16 | /** 17 | * Truncate coordinate values to given precision, without rounding. 18 | * For instance, -23.1234567890 => -23.123456 when decimals = 6. 19 | * Note that it wont create zeroes on the end, so -23.45 => -23.45 when decimals = 6. 20 | * @param {number} number a number to be truncated 21 | * @param {number} decimals desired decimals on truncated number 22 | * @return {string} the truncated number as string. 23 | */ 24 | const truncateValue = (number, decimals) => { 25 | let re = new RegExp('^-?\\d+(?:\.\\d{0,' + (decimals || -1) + '})?') 26 | return number.toString().match(re)[0] 27 | } 28 | 29 | /** 30 | * Creates a share URL with current map view and active layers 31 | * @param {Object} mapProperties object with map properties 32 | * @param {Object} mapProperties.currentCoordinates object with the current view lat/lon and zoom level 33 | * @param {number} mapProperties.currentCoordinates.lat current view latitude 34 | * @param {number} mapProperties.currentCoordinates.lng current view longitude 35 | * @param {number} mapProperties.currentCoordinates.zoom current view zoom 36 | * @param {Object[]} activeLayers array with active layers 37 | * @return {string} JSX string 38 | */ 39 | const ShareUrl = ({mapProperties, layers, orderByLayerOrder, onToolbarItemClick}) => { 40 | if (!mapProperties || !mapProperties.currentCoordinates) { 41 | return null 42 | } 43 | let lat = truncateValue(mapProperties.currentCoordinates.lat, 6) 44 | let lng = truncateValue(mapProperties.currentCoordinates.lng, 6) 45 | let zoom = mapProperties.currentCoordinates.zoom 46 | let basemap = mapProperties.currentMap.name 47 | 48 | // drop current value if needed 49 | let baseUrl = location.href.split('#')[0] 50 | 51 | let url = `${baseUrl}#lat=${lat}&lng=${lng}&zoom=${zoom}&basemap=${basemap}` 52 | 53 | let activeLayers = orderByLayerOrder(selectedLayers(layers)).map(l => { 54 | // ex: plataforma_educ_escolas:educ_escolas_publicas 55 | let layerString = `${l.id}:${l.styles[l.selectedLayerStyleId].name.replace('plataforma:', '')}` 56 | if (l.filterKey && l.filterValue) { 57 | // ex: plataforma_educ_escolas:educ_escolas_publicas(Escola|maria) 58 | layerString += `(${l.filterKey}|${l.filterValue})` 59 | } 60 | return layerString 61 | }).join(',') 62 | 63 | if (activeLayers) { 64 | url += `&layers=${activeLayers}` 65 | } 66 | 67 | if (mapProperties.placeToCenter) { 68 | if (mapProperties.placeToCenter.cd_craai) { 69 | url += `&craai=${mapProperties.placeToCenter.cd_craai}` 70 | } 71 | if (mapProperties.placeToCenter.cd_municipio) { 72 | url += `&municipio=${mapProperties.placeToCenter.cd_municipio}` 73 | } 74 | if (mapProperties.placeToCenter.cd_bairro) { 75 | url += `&bairro=${mapProperties.placeToCenter.cd_bairro}` 76 | } 77 | if (mapProperties.placeToCenter.tipo === 'ORGAO') { 78 | url += `&orgao=${mapProperties.placeToCenter.id}` 79 | } 80 | } 81 | 82 | return ( 83 |
    84 | onToolbarItemClick('share')}>COPIAR 85 |
    86 | ) 87 | } 88 | 89 | export default ShareUrl 90 | -------------------------------------------------------------------------------- /src/components/SidebarRight/SidebarRight.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LayerSubtitleSpace from '../LayerSubtitle/LayerSubtitleSpace.js' 3 | import PolygonData from '../PolygonData/PolygonData.js' 4 | import HTML5Backend from 'react-dnd-html5-backend' 5 | import { DragDropContext } from 'react-dnd' 6 | 7 | const SidebarRight = ({ 8 | layers, 9 | showSidebarRight, 10 | onLayerClick, 11 | orderByLayerOrder, 12 | lastClickData, 13 | polygonData, 14 | onClearLayerFilter, 15 | onIconMouseOut, 16 | onIconMouseOver, 17 | onLayerDown, 18 | onLayerDrop, 19 | onLayerFilterSearch, 20 | onLayerRemove, 21 | onLayerUp, 22 | onLoadParams, 23 | onOpenLayerFilterModal, 24 | onOpenModal, 25 | onRemoveAllLayers, 26 | onSidebarRightHideClick, 27 | }) => { 28 | if (!orderByLayerOrder) { 29 | orderByLayerOrder = () => { return layers } 30 | } 31 | 32 | // define base class of element 33 | var cssClass = 'sidebar-right allow-transition-sidebar-right' 34 | // if showSidebarRight is false, add class to hide the element 35 | if (!showSidebarRight) { 36 | cssClass += ' hide-sidebar-right' 37 | } 38 | return ( 39 |
    40 |
    41 |

    Camadas em exibição

    42 | 49 | 55 |
    56 | { 57 | polygonData 58 | ? 59 | 60 | : 61 | null 62 | } 63 | {layers ? 64 | orderByLayerOrder(layers).reverse().map((layer, index) => { 65 | return ( 66 | 83 | ) 84 | }) 85 | : ''} 86 |
    87 |
    88 |
    89 | ) 90 | } 91 | // need to wrap the top most component of your application 92 | // to make children draggable with DragDropContext (set up React DnD). 93 | export default DragDropContext(HTML5Backend)(SidebarRight) 94 | -------------------------------------------------------------------------------- /src/components/ToolbarMenu/ToolbarMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import BaseMapList from '../BaseMapList/BaseMapList' 3 | import ExportList from '../ExportList/ExportList' 4 | import GlobalFilter from '../GlobalFilter/GlobalFilter' 5 | import GooglePlacesContainer from '../GooglePlaces/GooglePlacesContainer' 6 | import ShareUrl from '../ShareUrl/ShareUrl' 7 | 8 | /** 9 | * Creates a toolbar menu (one for each toolbar item) 10 | * @param {object} p An object with parameters 11 | * @return {string} JSX string 12 | */ 13 | const ToolbarMenu = ({ 14 | active, 15 | baseMaps, 16 | globalFilterType, 17 | item, 18 | mapProperties, 19 | layers, 20 | places, 21 | tutela, 22 | type, 23 | onChangeActiveBaseMap, 24 | onClearPlaceTutelaLayer, 25 | onContourChange, 26 | onGlobalFilterTypeChange, 27 | onKeyUpSearchPlaces, 28 | onKeyUpSearchTutela, 29 | onOpacityChange, 30 | onPlaceClick, 31 | onToolbarItemClick, 32 | onTutelaClick, 33 | onDownloadClick, 34 | onDownloadEnd, 35 | orderByLayerOrder, 36 | }) => { 37 | let className = 'toolbar-menu' 38 | 39 | if (type === 'map') { 40 | className += ' map' 41 | } 42 | 43 | if ( 44 | (!active || active !== item.name) || 45 | item.name === 'draw' || 46 | item.name === 'polygonRequest' || 47 | item.name === 'help' || 48 | item.name === 'about' 49 | ) { 50 | className += ' hidden' 51 | } 52 | 53 | return ( 54 |
    55 | { 56 | item.name === 'download' 57 | ? : null 63 | } 64 | { 65 | item.name === 'share' 66 | ? : null 72 | } 73 | { 74 | item.name === 'basemaps' 75 | ? : null 79 | } 80 | { 81 | item.name === 'search' 82 | ? 96 | : '' 97 | } 98 | { 99 | item.name === 'streetView' ? 100 | clique no mapa para exibir o street view. 101 | : null 102 | } 103 | { 104 | item.name === 'searchStreet' ? 105 | 106 | : null 107 | } 108 |
    109 | ) 110 | } 111 | 112 | export default ToolbarMenu 113 | -------------------------------------------------------------------------------- /tests/Menu/Menu.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Menu from '../../src/components/Menu/Menu.js'; 3 | 4 | it('Menu component renders correctly with no data', () => { 5 | const component = shallow(); 6 | expect(component).toMatchSnapshot(); 7 | }); 8 | 9 | it('Menu component renders correctly with real data', () => { 10 | let mock = { 11 | "sidebarLeftWidth":37.859375, 12 | "sidebarLeftHeight":85.046875, 13 | "menuItems":[ 14 | { 15 | "display":true, 16 | "id":"Assistência Social", 17 | "title":"Assistência Social", 18 | "layers":[1], 19 | "idMenu":1, 20 | "isSubMenu":false, 21 | "submenus":[], 22 | "selected":false, 23 | "match":true 24 | } 25 | ], 26 | "layers":[ 27 | { 28 | "id":"plataforma_acterj_conselho_tutelar", 29 | "name":"acterj_conselho_tutelar", 30 | "title":"Conselho Tutelar", 31 | "workspace":"plataforma", 32 | "display":true, 33 | "restricted":false, 34 | "layerName":"plataforma:acterj_conselho_tutelar", 35 | "description":"Fontes: Associação dos Conselheiros Tutelares (ACTERJ). Contato: acterj@gmail.com/ http://www.acterj.org.br/ Ano:2016.", 36 | "bbox":"-44.71388499999997,-23.223640999999986,-41.045743999999964,-20.96493900000002", 37 | "caops":["infancia","cidadania","educacao"], 38 | "menu":"assistencia", 39 | "menu2":["Assistência Social"], 40 | "key":0, 41 | "styles":[ 42 | { 43 | "id":0, 44 | "title":"Conselho Tutelar", 45 | "name":"plataforma:conselho_tutelar", 46 | "description":"Conselhos Tutelares registrados na ACTERJ e CEDCA", 47 | "icon":"/geoserver/plataforma/wms?service=WMS&version=1.1.0&request=GetMap&bbox=-44.71388499999997,-23.223640999999986,-41.045743999999964,-20.96493900000002&width=27&height=20&ssrs=EPSG:3857&format=image%2Fpng&layers=plataforma:acterj_conselho_tutelar&singleTile=true&styles=plataforma:conselho_tutelar", 48 | "icon_2":"./resources/img/plataforma/icones/plataforma_acterj_conselho_tutelar-plataforma_conselho_tutelar_27_20.png","thumb":"/geoserver/plataforma/wms?service=WMS&version=1.1.0&request=GetMap&bbox=-44.71388499999997,-23.223640999999986,-41.045743999999964,-20.96493900000002&width=225&height=150&ssrs=EPSG:3857&format=image%2Fpng&layers=plataforma:acterj_conselho_tutelar&singleTile=true&styles=plataforma:conselho_tutelar", 49 | "thumb_2":"./resources/img/plataforma/icones/plataforma_acterj_conselho_tutelar-plataforma_conselho_tutelar_225_150.png","tooltip":"Conselho Tutelar
    Conselho Tutelar
    " 50 | } 51 | ], 52 | "selected":false, 53 | "match":true, 54 | "showDescription":false, 55 | "selectedLayerStyleId":0 56 | } 57 | ], 58 | "currentLevel":0 59 | } 60 | 61 | const component = shallow(); 62 | expect(component).toMatchSnapshot(); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/Menu/__snapshots__/Menu.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Menu component renders correctly with no data 1`] = ` 4 | 18 | `; 19 | 20 | exports[`Menu component renders correctly with real data 1`] = ` 21 | Conselho Tutelar
    Conselho Tutelar", 68 | }, 69 | ], 70 | "title": "Conselho Tutelar", 71 | "workspace": "plataforma", 72 | }, 73 | ] 74 | } 75 | measure={[Function]} 76 | measureRef={[Function]} 77 | menuItems={ 78 | Array [ 79 | Object { 80 | "display": true, 81 | "id": "Assistência Social", 82 | "idMenu": 1, 83 | "isSubMenu": false, 84 | "layers": Array [ 85 | 1, 86 | ], 87 | "match": true, 88 | "selected": false, 89 | "submenus": Array [], 90 | "title": "Assistência Social", 91 | }, 92 | ] 93 | } 94 | sidebarLeftHeight={85.046875} 95 | sidebarLeftWidth={37.859375} 96 | /> 97 | `; 98 | -------------------------------------------------------------------------------- /src/components/LeafletMap/leafletMap.scss: -------------------------------------------------------------------------------- 1 | .module-leafletMap { 2 | height: 100%; 3 | position: absolute; 4 | width: 100%; 5 | 6 | .leaflet-container { 7 | height: 100%; 8 | width: 100%; 9 | margin: 0 auto; 10 | } 11 | .leaflet-draw .leaflet-bar a { 12 | float: left; 13 | border-bottom: 0; 14 | margin-right: 5px; 15 | margin-top: 5px; 16 | } 17 | 18 | .leaflet-draw { 19 | margin-top: 60px; 20 | margin-right: 35px; 21 | } 22 | 23 | .leaflet-draw:nth-child(2){ 24 | margin-top: 0px; 25 | } 26 | 27 | .leaflet-bar { 28 | border: 0; 29 | } 30 | 31 | &.sidebar-left-opened { 32 | .leaflet-control-scale { 33 | margin-left: $sidebar-left-width + 5px; 34 | } 35 | } 36 | 37 | &.sidebar-right-opened { 38 | .leaflet-control-zoom, .leaflet-control-attribution { 39 | margin-right: $layer-content-width + 5px; 40 | } 41 | .leaflet-draw { 42 | margin-right: $layer-content-width + 35px; 43 | } 44 | } 45 | 46 | // style copied from leaflet's original style 47 | .leaflet-draw-toolbar a { 48 | background-image: linear-gradient(transparent, transparent), url('../../assets/img/spritesheet.svg'); 49 | background-repeat: no-repeat; 50 | background-size: 300px 30px; 51 | background-clip: padding-box; 52 | } 53 | 54 | .leaflet-draw-draw-polyline { 55 | background-position: -15px 0px; 56 | } 57 | 58 | .leaflet-draw-draw-polygon { 59 | background-position: -45px -1px; 60 | } 61 | 62 | .leaflet-draw-draw-rectangle { 63 | background-position: -75px -1px; 64 | } 65 | 66 | .leaflet-draw-draw-circle { 67 | background-position: -105px -1px; 68 | } 69 | 70 | .leaflet-draw-draw-marker { 71 | background-position: -135px -1px; 72 | } 73 | 74 | .leaflet-draw-draw-circlemarker { 75 | background-position: -286px -1px; 76 | } 77 | 78 | .leaflet-draw-edit-edit { 79 | background-position: -225px -1px; 80 | } 81 | 82 | .leaflet-draw-edit-remove { 83 | background-position: -255px -1px; 84 | } 85 | 86 | .leaflet-draw-tooltip { 87 | background: $leaflet-draw-tooltip-background-color; 88 | background: $leaflet-draw-tooltip-background-color-alpha; 89 | border: 1px solid transparent; 90 | -webkit-border-radius: 4px; 91 | border-radius: 4px; 92 | color: $leaflet-draw-tooltip-color; 93 | font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif; 94 | margin-left: 20px; 95 | margin-top: -21px; 96 | padding: 4px 8px; 97 | position: absolute; 98 | visibility: hidden; 99 | white-space: nowrap; 100 | z-index: 6; 101 | } 102 | 103 | .leaflet-draw-actions { 104 | top: 80px !important; // had to use important to override plugin's inline style 105 | left: 0; 106 | display: block; 107 | list-style: none; 108 | margin: 0; 109 | padding: 0; 110 | position: absolute; 111 | white-space: nowrap; 112 | } 113 | 114 | .leaflet-draw-actions li { 115 | display: inline-block; 116 | } 117 | 118 | .leaflet-draw-actions a { 119 | display: block; 120 | text-align: center; 121 | font-size: 12px; 122 | padding-top: 5px; 123 | height: 30px; 124 | background-color: $leaflet-draw-actions-background-color; 125 | border-left: 1px solid $leaflet-draw-actions-border-color; 126 | color: $leaflet-draw-actions-color; 127 | font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; 128 | text-decoration: none; 129 | padding-left: 10px; 130 | padding-right: 10px; 131 | } 132 | 133 | .leaflet-draw-actions a:hover { 134 | background-color: $leaflet-draw-actions-hover-background-color; 135 | } 136 | } 137 | 138 | // Hides default Leaflet Layer Control 139 | .leaflet-control-layers.leaflet-control { 140 | display: none; 141 | } 142 | -------------------------------------------------------------------------------- /src/components/Help/Help.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Steps } from 'intro.js-react' 3 | 4 | import 'intro.js/introjs.css'; 5 | 6 | let introjsOptions = { 7 | initialStep: 0, 8 | steps: [ 9 | { 10 | element: '.toolbar-item.fa-question', 11 | intro: 'Bem-vindo ao inLoco 2.0! Este passo a passo irá mostrar rapidamente as funcionalidades da plataforma.', 12 | }, 13 | { 14 | element: '.application-header--menu-button', 15 | intro: 'Menu: Aqui você pode escolher múltiplas camadas para exibição no mapa, através de categorias ou busca textual.', 16 | }, 17 | { 18 | element: '.leaflet-control-zoom', 19 | intro: 'Para ajustar a área exibida, você pode arrastar, e dar duplo clique ou usar a rodinha do mouse (botão do meio) para alterar o zoom. Você também pode arrastar segurando shift para dar zoom em uma região específica.', 20 | }, 21 | { 22 | element: '.platform-toolbar', 23 | intro: 'Aqui você encontra diferentes ferramentas: zoom/destaque em uma área específica, busca por polígono, desenho livre/destaque no mapa, compartilhar e download.', 24 | }, 25 | { 26 | element: '.toolbar-item.login-logout', 27 | intro: 'Caso você seja membro ou servidor do MPRJ, poderá entrar com seu usuário e senha e ver camadas restritas ao público.', 28 | }, 29 | { 30 | element: '.toolbar-item.search', 31 | intro: 'Busca detalhada: aqui você pode buscar por municípios ou bairros, destacando-os no mapa.', 32 | }, 33 | { 34 | element: '.toolbar-item.polygonRequest', 35 | intro: 'Busca por área: aqui você pode obter informações sobre um recorte desenhado no mapa: pirâmide etária, quantidade de escolas etc.', 36 | }, 37 | { 38 | element: '.toolbar-item.draw', 39 | intro: 'Desenho livre: aqui você pode inserir marcadores e polígonos no mapa, afim de destacar um determinado local.', 40 | }, 41 | { 42 | element: '.toolbar-item.share', 43 | intro: 'Compartilhar: Clique aqui para copiar uma URL para esta visão do mapa.', 44 | }, 45 | { 46 | element: '.toolbar-item.download', 47 | intro: 'Baixar: Clique aqui para baixar o mapa visualizado em tela em diferentes formatos.', 48 | }, 49 | { 50 | element: '.toolbar-item.about', 51 | intro: 'Sobre: Clique aqui para ver informações gerais sobre a plataforma.', 52 | }, 53 | { 54 | element: '.map-toolbar', 55 | intro: 'Aqui você encontra mais ferramentas: Google Street View™, busca por endereços ou pontos de referência, e mudança de mapa base.', 56 | }, 57 | { 58 | element: '.toolbar-item.streetView', 59 | intro: 'Street View™: Clique aqui para ter uma visão panorâmica em 360° a nível do chão das ruas registradas nesta ferramenta do Google.', 60 | }, 61 | { 62 | element: '.toolbar-item.searchStreet', 63 | intro: 'Busca de ruas: Clique aqui para buscar endereços ou pontos de interesse, para levar o mapa diretamente para o local desejado.', 64 | }, 65 | { 66 | element: '.toolbar-item.basemaps', 67 | intro: 'Mapas de fundo: Clique aqui para alterar a camada de fundo, trocando entre imagens de satélite, terreno e mapas do Google Maps™ e OpenStreetMap.', 68 | }, 69 | ], 70 | options: { 71 | hidePrev: true, 72 | hideNext: true, 73 | showBullets: false, 74 | showProgress: true, 75 | nextLabel: 'Próximo →', 76 | prevLabel: '← Anterior', 77 | skipLabel: 'Sair', 78 | doneLabel: 'Pronto!', 79 | } 80 | } 81 | 82 | const Help = ({ showHelp, onIntrojsExit }) => { 83 | 84 | const handleIntrojsExit = () => { 85 | onIntrojsExit() 86 | } 87 | 88 | return ( 89 | 96 | ) 97 | } 98 | 99 | export default Help 100 | -------------------------------------------------------------------------------- /README-GEOSERVER.md: -------------------------------------------------------------------------------- 1 | # GeoServer 2 | 3 | Our GeoServer admin panel runs on MPRJ network and is not exposed to the Internet. The frontend makes calls to an nginx proxy, which only forwards WMS/WFS requests. 4 | 5 | This document describes the tags used on the layers to add different caracteristics on the inLoco frontend. 6 | 7 | ## Layers tags 8 | 9 | The frontend expects that the layers have the following tags. Some are required, some are not. Layers that don't have required tags will be ignored. 10 | 11 | ### Menu **(required)** 12 | 13 | In order to appear on the menu, layers _should_ have both `menu` and `menu2` tags. `menu` was used in [old inLoco 1.0](apps.mprj.mp.br/sistema/mpmapas/inloco.html) and was kept for retrocompatibility. 14 | 15 | - `menu:xxxx` is a hardcoded string to a menu category on the old system (for instance `menu:educacao`) 16 | 17 | - `menu2:xxxx[:xxxx]` is the tag used to create the element on the menu. It can either be a single level (for instance, `menu2:Educação`) or dual level (for instance, `menu2:Educação:IDEB por município`). The system will read the tags and create the menu dynamically. 18 | 19 | Important notes: 20 | 21 | - Both `menu` and `menu2` are required, if a tag has `menu2` but not `menu` it will not appear on the menu 22 | 23 | - `menu2` tags are case sensitive and have spaces, therefore if you create a layer with the tag `menu2:Educação` and another with `menu2: Educação` (notice the space) you will end up with duplicate menu items on the app. 24 | 25 | ### Styles ordering **(not required)** 26 | 27 | When a layer has the tag `ordenar`, the styles are organized alphabetically in descending order (Z-A). Thus, the last style name (tipically "(...) 2017") is display first, then the one for last year and so on. This tag is not required. 28 | 29 | ### Table ordering **(not required)** 30 | 31 | When opening a layer and clicking on the map, the right sidebar opens with details of the given point(s)/area(s). This details includes a small table with up to 3 items. This table will show the first three columns on the database, except if specified by this tag, which follows this standard: 32 | 33 | `tabela:["column1","column2","column3"]` 34 | 35 | For instance, the layer `Educação > Escolas` have the following tag: `tabela:["Escola","Rede","Gestão"]`. 36 | 37 | This tag is not required, and if not present the table will present the first 3 items on the database. 38 | 39 | ### Charts **(not required)** 40 | 41 | This non-required tag specify that a chart should appear when displaying information about one or more features. It uses the following specification: 42 | 43 | `grafico:type|title|entity|column1-display-name/column1-name[,...]` 44 | 45 | The app supports one of the following chart types: `linha`, `barra`, `barra-horizontal`, `pizza`, `piramide`. Respectivelly, they can be translated to: line chart, bar chart, horizontal bar chart, pie chart and pyramid chart. 46 | 47 | It's easier to understand with an example (layer `Segurança > Instituto de Segurança Pública > ISP: Armamentos`): 48 | 49 | `grafico:linha|Artefato|dp_nome|2016T1/artefato_t1_2016,2016T2/artefato_t2_2016,2016T3/artefato_t3_2016,2016T4/artefato_t4_2016,2017T1/artefato_t1_2017,2017T2/artefato_t2_2017` 50 | 51 | So the chart type is `linha`, the chart title is `Artefato`, the `entity` is `dp_nome` (so it will appear on the chart legend), and then there is a number of columns, separated by `,`, each one having their display name (`2016T1`, for instance) and the respective column (`artefato_t1_2016`, for instance), separated by `/`. 52 | 53 | `piramide` charts are used to display, in most cases, population number spread by age, and are automatically split in half (for men and women). The columns are specified manually like in the following example: 54 | 55 | `grafico:piramide|Pirâmide Etária|Código_Setor_Censitário|0-3/h_0_3,4-7/h_4_7,8-11/h_8_11,12-15/h_12_15,16-19/h_16_19,20-23/h_20_23,24-27/h_24_27,28-31/h_28_31,32-35/h_32_35,36-39/h_36_39,40-43/h_40_43,44-47/h_44_47,48-51/h_48_51,52-55/h_52_55,56-59/h_56_59,60-63/h_60_63,64-67/h_64_67,68-71/h_68_71,72-75/h_72_75,76-79/h_76_79,80-83/h_80_83,84-87/h_84_87,88-91/h_88_91,92-95/h_92_95,96-99/h_96_99,0-3/m_0_3,4-7/m_4_7,8-11/m_8_11,12-15/m_12_15,16-19/m_16_19,20-23/m_20_23,24-27/m_24_27,28-31/m_28_31,32-35/m_32_35,36-39/m_36_39,40-43/m_40_43,44-47/m_44_47,48-51/m_48_51,52-55/m_52_55,56-59/m_56_59,60-63/m_60_63,64-67/m_64_67,68-71/m_68_71,72-75/m_72_75,76-79/m_76_79,80-83/m_80_83,84-87/m_84_87,88-91/m_88_91,92-95/m_92_95,96-99/m_96_99` 56 | 57 | `grafico` tags are not required, and a single layer can have multiple tags. In this case the system will display them in the order they were inserted on the GeoServer database. 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # In Loco 2 | _In the place; in the proper or natural place._ 3 | 4 | A Geographic Information System (GIS) used by [Ministério Público do Estado do Rio de Janeiro](http://www.mprj.mp.br/) to show social, institutional and administrative data, made with [React](https://facebook.github.io/react/) and [Leaflet](http://leafletjs.com/), interacting with a public [GeoServer](http://geoserver.org/) backend. 5 | 6 | For more details about our GeoServer instance, please check [README-GEOSERVER.md](https://github.com/MinisterioPublicoRJ/inloco/blob/develop/README-GEOSERVER.md). 7 | 8 | ## See it live: http://inloco.mprj.mp.br/ 9 | 10 | [![Screen capture](https://user-images.githubusercontent.com/397851/30930156-25dbd42e-a397-11e7-8e7f-80c83cdddccd.png)](http://inloco.mprj.mp.br/) 11 | 12 | ## Contributing 13 | 14 | Our development objectives and open issues are all public on [GitHub issues](https://github.com/MinisterioPublicoRJ/inloco/issues). Pull requests are welcome! 15 | 16 | Optionally, you can install [ZenHub](https://github.com/marketplace/zenhub) plugin (available on Chrome and Firefox) to see development plan board, see issues already in progress etc. 17 | 18 | For more details, please read [CONTRIBUTING.md](https://github.com/MinisterioPublicoRJ/inloco/blob/develop/CONTRIBUTING.md). 19 | 20 | ## Running 21 | 22 | ### Installing 23 | 1. Clone the project or [download it](https://github.com/MinisterioPublicoRJ/inloco/archive/develop.zip) to your computer. 24 | 1. Make sure you have [Node.js and npm](https://nodejs.org/en/download/) installed. We suggest using node version 8 (you could use nvm to switch to that version, i.e. `brew install nvm`, `nvm install 8` and `nvm use 8`). 25 | 1. If on Windows, install [.NET Framework 2.0 SDK](https://www.microsoft.com/en-us/download/confirmation.aspx?id=15354) (Sass build dependency) 26 | 1. If needed be, configure npm proxy 27 | 1. `webpack` and `webpack-dev-server` must be installed globally (`npm i -g webpack webpack-dev-server`) 28 | 1. Run `npm install`. If you have any trouble with Node SASS, you could try `npm rebuild node-sass` 29 | 1. This project uses [EditorConfig](http://editorconfig.org/) to configure its code standards, please use the appropriate plugin on your IDE or text editor. 30 | 31 | ### Running 32 | 33 | ``` 34 | $ npm start 35 | ``` 36 | 37 | For more details (running tests etc.) please check [CONTRIBUTING.md](https://github.com/MinisterioPublicoRJ/inloco/blob/develop/CONTRIBUTING.md). 38 | 39 | # Team 40 | 41 | ## Development 42 | 43 | - [Daniel Belchior](https://www.linkedin.com/in/danielbelchior/) ([@danielbelchior](https://github.com/danielbelchior)) 44 | 45 | #### Formerly 46 | 47 | - [Arlindo Pereira](https://www.linkedin.com/in/arlindosaraivapereira/) ([@nighto](https://github.com/nighto)) 48 | - [Gabriel Barbier](https://www.linkedin.com/in/gabrielbarbier/) ([@barbier](https://github.com/barbier)) 49 | - [Luciano Baraúna](https://www.linkedin.com/in/lucianobarauna/) ([@lucianobarauna](https://github.com/lucianobarauna)) 50 | - [Pedro Marins](https://www.linkedin.com/in/pedromarins/) ([@pedromarins](https://github.com/pedromarins)) 51 | - [Rafael Lage Tavares](https://www.linkedin.com/in/rltrafael/) ([@rlage](https://github.com/rlage)) 52 | 53 | #### And those external contributors, with accepted PRs: 54 | 55 | - [Fernando Júnior](https://github.com/MinisterioPublicoRJ/inLoco-2.0/pull/359) ([@junioweb](https://github.com/junioweb)) 56 | - [Gabriela D'Ávila Ferrara](https://github.com/MinisterioPublicoRJ/inLoco-2.0/pull/364) ([@gabidavila](https://github.com/gabidavila)) 57 | - [Joe Cha](https://github.com/MinisterioPublicoRJ/inLoco-2.0/pull/373) ([@bother7](https://github.com/bother7)) 58 | - [Martín Castre](https://github.com/MinisterioPublicoRJ/inLoco-2.0/pull/357) ([@mcastre](https://github.com/mcastre)) 59 | - [Sebastian](https://github.com/MinisterioPublicoRJ/inLoco-2.0/pull/341) ([@HerrVoennchen](https://github.com/HerrVoennchen)) 60 | 61 | ## Geography 62 | 63 | - [Fellipe Figueiredo Silva](https://www.linkedin.com/in/fellipe-figueiredo-silva-9a8981106/) 64 | - [Frederico José Basilio do Nascimento](https://www.linkedin.com/in/frederico-nascimento-b214262b/) 65 | - [Pedro Henrique de Magalhães Casimiro](https://www.linkedin.com/in/pedro-henrique-de-magalh%C3%A3es-casimiro-7b7b4512a/) 66 | - [Rodrigo Sá de Araujo](https://www.linkedin.com/in/rodrigo-araujo-61338a141/) 67 | 68 | #### Formerly 69 | 70 | - Maria Pandolfi Guerreiro 71 | - [Renato de Lima Hingel](https://www.linkedin.com/in/renato-hingel-51651a35/) 72 | 73 | ## Statistics 74 | 75 | - [Cristiane Ramos Justen](https://www.linkedin.com/in/cristiane-ramos-justen-145451122/) 76 | 77 | #### Formerly 78 | 79 | - [Nicole Peçanha do Rêgo Barros](http://lattes.cnpq.br/0330661247598507) 80 | -------------------------------------------------------------------------------- /src/components/LeafletMap/LeafletMapContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LeafletMap from './LeafletMap' 3 | import { connect } from 'react-redux' 4 | import GeoAPI from '../Api/GeoAPI.js' 5 | import SinalidAPI from '../Api/SinalidAPI.js' 6 | import { 7 | populateStateWithLayerData, 8 | updateLastClickData, 9 | updateBasemapLoadingStatus, 10 | lastMapPosition, 11 | populateStateWithPolygonData, 12 | removePolygonData, 13 | startPolygonDataRequest, 14 | sinalidData, 15 | } from '../../actions/actions.js' 16 | 17 | const MAX_ITEMS_TO_LOAD = 3 18 | 19 | const selectedLayers = (layers) => { 20 | if (!Array.isArray(layers)) { 21 | return [] 22 | } 23 | return layers.filter(layer => layer.selected) 24 | } 25 | 26 | const mapStateToProps = (state, ownProps) => { 27 | return { 28 | mapProperties: state.mapProperties, 29 | showMenu: state.showMenu, 30 | showSidebarRight: state.showSidebarRight, 31 | layers: selectedLayers(state.layers), 32 | showDrawControls: state.showDrawControls, 33 | showSearchPolygon: state.showSearchPolygon, 34 | showPolygonDraw: state.showPolygonDraw, 35 | orderByLayerOrder: ownProps.orderByLayerOrder, 36 | places: state.places, 37 | toolbarActive: state.toolbarActive, 38 | streetViewCoordinates: state.streetViewCoordinates, 39 | } 40 | } 41 | 42 | const mapDispatchToProps = dispatch => { 43 | const onUpdateWithSelectedLayerData = layerData => { 44 | dispatch(populateStateWithLayerData(layerData)) 45 | 46 | // Sinalid 47 | if (layerData[0]) { 48 | layerData[0].features.map(feature => { 49 | if (feature.properties.DP) { 50 | SinalidAPI.listMissingPeople(onUpdateSinalidData, feature.properties.id) 51 | } 52 | }) 53 | } 54 | } 55 | const onUpdateSinalidData = data => { 56 | dispatch(sinalidData(data)) 57 | } 58 | const onDrawUpdateWithPolygonData = data => { 59 | dispatch(populateStateWithPolygonData(data)) 60 | } 61 | return { 62 | /** 63 | * Create URL to get layers data and populate data table 64 | * @param e - Event bubbled on map click 65 | * @param layers - Active Layers array 66 | */ 67 | handleMapClick: (e, layers, toolbarActive, googleApiToken) => { 68 | // update last click data 69 | const map = e.target 70 | const clickData = { 71 | BBOX: map.getBounds().toBBoxString(), 72 | WIDTH: map.getSize().x, 73 | HEIGHT: map.getSize().y, 74 | X: ~~map.layerPointToContainerPoint(e.layerPoint).x, 75 | Y: ~~map.layerPointToContainerPoint(e.layerPoint).y 76 | } 77 | dispatch(updateLastClickData(clickData)) 78 | 79 | // fetch layer data for clicked point if needed 80 | let urls = layers.map(l => { 81 | return GeoAPI.createUrl({ 82 | layer: l, 83 | clickData, 84 | featureCount: MAX_ITEMS_TO_LOAD, 85 | }) 86 | }) 87 | GeoAPI.getLayersData(onUpdateWithSelectedLayerData, urls) 88 | 89 | // open street view data if needed 90 | if (toolbarActive === 'streetView') { 91 | window.open(`https://www.google.com/maps/embed/v1/streetview?key=${googleApiToken}&location=${e.latlng.lat},${e.latlng.lng}`, '_blank') 92 | } 93 | }, 94 | onUpdateBasemapLoadingStatus: () => { 95 | dispatch(updateBasemapLoadingStatus()) 96 | }, 97 | handleMapMove: e => { 98 | const map = e.target 99 | const mapBounds = map.getBounds() 100 | const mapCenter = map.getCenter() 101 | const mapZoom = map.getZoom() 102 | const mapData = { 103 | bounds: mapBounds, 104 | lat: mapCenter.lat, 105 | lng: mapCenter.lng, 106 | zoom: mapZoom, 107 | } 108 | dispatch(lastMapPosition(mapData)) 109 | }, 110 | onDraw: (e, coordinates, activeLayers) => { 111 | const map = e.target 112 | dispatch(startPolygonDataRequest()) 113 | GeoAPI.getPolygonData(onDrawUpdateWithPolygonData, coordinates, activeLayers) 114 | }, 115 | onPolygonDelete: () => { 116 | dispatch(removePolygonData()) 117 | }, 118 | } 119 | } 120 | 121 | const LeafletMapContainer = connect( 122 | mapStateToProps, 123 | mapDispatchToProps 124 | )(LeafletMap) 125 | 126 | export default LeafletMapContainer 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inloco-2.0", 3 | "version": "1.0.0", 4 | "description": "Plataforma digital que reúne em um banco de dados informações sociais, institucionais e administrativas, relacionadas ao MP-RJ.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "axios": "^0.16.1", 8 | "babel-preset-stage-2": "^6.24.1", 9 | "chalk": "^2.2.0", 10 | "chart.js": "^2.6.0", 11 | "copy-dir": "^0.3.0", 12 | "cpx": "^1.5.0", 13 | "font-awesome": "^4.7.0", 14 | "html2canvas": "^0.5.0-beta4", 15 | "intro.js": "^2.8.0-alpha.1", 16 | "intro.js-react": "^0.1.5", 17 | "jspdf": "^1.3.3", 18 | "leaflet": "1.3.1", 19 | "leaflet-draw": "^1.0.2", 20 | "mkdirp": "^0.5.1", 21 | "proj4": "^2.4.4", 22 | "react": "15.4.2", 23 | "react-chartjs-2": "^2.6.1", 24 | "react-clipboard.js": "^1.1.2", 25 | "react-dnd": "^2.4.0", 26 | "react-dnd-html5-backend": "^2.4.1", 27 | "react-dom": "15.4.2", 28 | "react-ga": "^2.3.4", 29 | "react-leaflet": "^1.9.1", 30 | "react-leaflet-draw": "^0.16.0", 31 | "react-measure": "^2.0.2", 32 | "react-redux": "^5.0.3", 33 | "redux": "^3.6.0", 34 | "redux-thunk": "^2.2.0" 35 | }, 36 | "devDependencies": { 37 | "babel-core": "^6.0.20", 38 | "babel-eslint": "^7.1.1", 39 | "babel-jest": "^19.0.0", 40 | "babel-loader": "^6.0.1", 41 | "babel-plugin-transform-class-properties": "^6.23.0", 42 | "babel-plugin-transform-runtime": "^6.23.0", 43 | "babel-preset-es2015": "^6.24.0", 44 | "babel-preset-react": "^6.23.0", 45 | "babel-preset-react-hmre": "^1.1.1", 46 | "babel-preset-stage-0": "^6.0.15", 47 | "cross-env": "^3.1.4", 48 | "css-loader": "^0.14.5", 49 | "enzyme": "^2.8.0", 50 | "enzyme-to-json": "^1.5.0", 51 | "esdoc": "^0.5.2", 52 | "eslint": "^3.16.1", 53 | "eslint-config-airbnb": "^14.1.0", 54 | "eslint-plugin-import": "^2.2.0", 55 | "eslint-plugin-jsx-a11y": "^4.0.0", 56 | "eslint-plugin-react": "^6.10.0", 57 | "extract-text-webpack-plugin": "^2.1.0", 58 | "file-loader": "^0.10.1", 59 | "hoek": "^5.0.3", 60 | "http-proxy-middleware": "^0.17.4", 61 | "jest": "^19.0.2", 62 | "node-sass": "^4.9.2", 63 | "react-addons-test-utils": "15.4.2", 64 | "react-dom": "15.4.2", 65 | "react-test-renderer": "15.4.2", 66 | "redux-logger": "^3.0.6", 67 | "sass-loader": "^6.0.3", 68 | "style-loader": "^0.13.2", 69 | "uglify-js": "^3.1.3", 70 | "uglify-loader": "^2.0.0", 71 | "uglifyjs-webpack-plugin": "^0.4.6", 72 | "url-loader": "^0.5.8", 73 | "webpack": "^3.6.0", 74 | "webpack-dev-middleware": "^1.2.0", 75 | "webpack-dev-server": "^2.4.5", 76 | "webpack-hot-middleware": "^2.0.0" 77 | }, 78 | "scripts": { 79 | "copyfiles": "node copy_modules.js && node copy_static.js", 80 | "start": "npm run copyfiles && webpack-dev-server --host 0.0.0.0", 81 | "full": "npm run copyfiles && jest && esdoc -c esdoc.json && webpack-dev-server", 82 | "outside": "npm run copyfiles && webpack-dev-server --port 3000 --host 10.32.4.107", 83 | "logrequests": "npm run copyfiles && cross-env DEBUG='express:*' webpack-dev-server", 84 | "mprj": "npm run copyfiles && cross-env NODE_ENV=mprj webpack-dev-server", 85 | "prod": "npm run copyfiles && cross-env NODE_ENV=production webpack-dev-server --env.prod=true", 86 | "prebuild": "rm -rf inloco inloco.zip 2> /dev/null", 87 | "build": "npm run copyfiles && cross-env NODE_ENV=production webpack --env.prod=true", 88 | "postbuild": "mv static inloco && zip -r inloco.zip inloco", 89 | "lint": "eslint ./client ./webpack.config.js -f table || true", 90 | "doc": "esdoc -c esdoc.json", 91 | "test": "jest", 92 | "test:watch": "jest --watch", 93 | "test:coverage": "jest --coverage", 94 | "tests-boilerplate": "node tests/test_generator.js" 95 | }, 96 | "repository": { 97 | "type": "git", 98 | "url": "https://github.com/MinisterioPublicoRJ/inLoco-2.0.git" 99 | }, 100 | "jest": { 101 | "verbose": true, 102 | "testEnvironment": "node", 103 | "setupFiles": [ 104 | "./jestsetup.js" 105 | ], 106 | "snapshotSerializers": [ 107 | "/node_modules/enzyme-to-json/serializer" 108 | ], 109 | "transform": { 110 | "^.+\\.js$": "babel-jest" 111 | }, 112 | "moduleFileExtensions": [ 113 | "js" 114 | ], 115 | "moduleDirectories": [ 116 | "node_modules", 117 | "src" 118 | ], 119 | "moduleNameMapper": { 120 | "\\.(css|less|scss|jpg|png)$": "/mocks/cssStub.js" 121 | }, 122 | "roots": [ 123 | "/tests/" 124 | ] 125 | }, 126 | "author": "Ministério Público do Rio de Janeiro", 127 | "license": "ISC", 128 | "snyk": true 129 | } 130 | -------------------------------------------------------------------------------- /src/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import ReactGA from 'react-ga' 4 | import logger from 'redux-logger' 5 | import { applyMiddleware, createStore } from 'redux' 6 | import { Provider } from 'react-redux' 7 | import { populateApp } from '../../actions/actions.js' 8 | import { checkUserLoggedIn } from '../../actions/actions.js' 9 | import appReducer from './appReducer.js' 10 | import GeoAPI from '../Api/GeoAPI.js' 11 | import ScaAPI from '../Api/ScaAPI.js' 12 | import HeaderContainer from '../Header/HeaderContainer.js' 13 | import HeaderRightContainer from '../HeaderRight/HeaderRightContainer.js' 14 | import HelpContainer from '../Help/HelpContainer.js' 15 | import LeafletMapContainer from '../LeafletMap/LeafletMapContainer.js' 16 | import LoadingContainer from '../Loading/LoadingContainer.js' 17 | import ModalContainer from '../Modal/ModalContainer.js' 18 | import SidebarLeftContainer from '../SidebarLeft/SidebarLeftContainer.js' 19 | import SidebarRightContainer from '../SidebarRight/SidebarRightContainer.js' 20 | import ToolbarContainer from '../Toolbar/ToolbarContainer.js' 21 | import TooltipContainer from '../Tooltip/TooltipContainer.js' 22 | 23 | require('./app.scss') 24 | require('./polyfill.js') 25 | 26 | ReactGA.initialize('UA-80844385-5'); 27 | 28 | function logPageView() { 29 | ReactGA.set({ page: window.location.pathname + window.location.search }); 30 | ReactGA.pageview(window.location.pathname + window.location.search); 31 | } 32 | 33 | // start by removing pre-loading 34 | document.getElementById('pre-loading').remove() 35 | 36 | const store = createStore(appReducer, applyMiddleware(logger)) 37 | 38 | // check if user is logged in 39 | 40 | const authenticateCallback = (data) => { 41 | store.dispatch(checkUserLoggedIn(data)) 42 | } 43 | 44 | ScaAPI.authenticate(authenticateCallback) 45 | 46 | const ajaxCallback = (xmlData) => { 47 | store.dispatch(populateApp(xmlData, location.hash)) 48 | } 49 | GeoAPI.getContent(ajaxCallback) 50 | 51 | const placesCallback = (xmlData) => { 52 | store.dispatch(populatePlaces(xmlData)) 53 | } 54 | 55 | const orderByLayerOrder = (layers) => { 56 | return layers.sort(function(a, b) { 57 | return a.order - b.order 58 | }) 59 | } 60 | 61 | // Toolbars (order is RTL) 62 | const platformItems = [ 63 | { 64 | name: 'about', 65 | tooltip: 'Sobre', 66 | className: 'fa fa-info about', 67 | }, 68 | { 69 | name: 'download', 70 | tooltip: 'Baixar', 71 | className: 'fa fa-download download', 72 | }, 73 | { 74 | name: 'share', 75 | tooltip: 'Compartilhar', 76 | className: 'fa fa-share-alt share', 77 | }, 78 | { 79 | name: 'draw', 80 | tooltip: 'Desenhar', 81 | className: 'fa fa-pencil draw', 82 | }, 83 | { 84 | name: 'polygonRequest', 85 | tooltip: 'Busca por desenho', 86 | className: 'fa fa-pencil-square-o polygonRequest', 87 | }, 88 | { 89 | name: 'search', 90 | tooltip: 'Filtro por área ou órgão', 91 | className: 'fa fa-search search', 92 | }, 93 | { 94 | name: 'login', 95 | tooltip: 'Login', 96 | className: 'fa fa-sign-in login login-logout', 97 | }, 98 | { 99 | name: 'help', 100 | tooltip: 'Ajuda', 101 | className: 'fa fa-question help', 102 | }, 103 | ] 104 | const mapItems = [ 105 | { 106 | name: 'basemaps', 107 | tooltip: 'Camadas de fundo', 108 | className: 'fa fa-map basemaps', 109 | }, 110 | { 111 | name: 'searchStreet', 112 | tooltip: 'Busca de ruas/pontos de interesse', 113 | className: 'fa fa-binoculars searchStreet', 114 | }, 115 | { 116 | name: 'streetView', 117 | tooltip: 'Street View™', 118 | className: 'fa fa-street-view streetView', 119 | }, 120 | ] 121 | 122 | const App = () => { 123 | return ( 124 | 125 |
    126 | 127 | {/* 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | */} 136 | 137 |
    138 |
    139 | ) 140 | } 141 | 142 | ReactDOM.render( 143 | , document.getElementById('app') 144 | ) 145 | 146 | logPageView() 147 | -------------------------------------------------------------------------------- /src/components/SidebarRight/SidebarRightContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import SidebarRight from './SidebarRight' 4 | import GeoAPI from '../Api/GeoAPI.js' 5 | import { 6 | clearLayerFilter, 7 | dropLayer, 8 | getModalData, 9 | hideSidebarRight, 10 | layerFilterLoading, 11 | layerFilterLoaded, 12 | onIconMouseOut, 13 | onIconMouseOver, 14 | onLoadingParams, 15 | onLoadParams, 16 | openLayerFilterModal, 17 | openModal, 18 | removeAllLayers, 19 | slideLayerDown, 20 | slideLayerUp, 21 | toggleLayer, 22 | toggleLayerInformation 23 | } from '../../actions/actions.js' 24 | 25 | /** 26 | * This function filters layers using the selected property 27 | * @param {Object[]} layers - this is array of layers. 28 | * @returns {Object[]} - returns all layers that are selected 29 | */ 30 | const selectedLayers = (layers) => { 31 | if (!Array.isArray(layers)) { 32 | return [] 33 | } 34 | return layers.filter(layer => layer.selected) 35 | } 36 | 37 | const mapStateToProps = (state, ownProps) => { 38 | return { 39 | layers: selectedLayers(state.layers), 40 | showSidebarRight: state.showSidebarRight, 41 | orderByLayerOrder: ownProps.orderByLayerOrder, 42 | lastClickData: state.lastClickData, 43 | polygonData: state.polygonData, 44 | } 45 | } 46 | 47 | const mapDispatchToProps = (dispatch) => { 48 | const onAjaxDataFetched = (layerData) => { 49 | dispatch(getModalData(layerData)) 50 | } 51 | /** 52 | * Fetch data from server to get content 53 | * of the clicked layer 54 | */ 55 | const onGetModalData = (layer, lastClickData) => { 56 | const MAX_ITEMS_TO_LOAD = 9999 57 | 58 | let url = GeoAPI.createUrl({ 59 | layer: layer, 60 | clickData: lastClickData, 61 | featureCount: MAX_ITEMS_TO_LOAD 62 | }) 63 | GeoAPI.getLayerData(onAjaxDataFetched, url) 64 | } 65 | 66 | const onLayerFilterSearchLoaded = data => { 67 | dispatch(layerFilterLoaded(data)) 68 | } 69 | 70 | return { 71 | onClearLayerFilter: item => { 72 | dispatch(clearLayerFilter(item)) 73 | }, 74 | onIconMouseOut: layer => { 75 | dispatch(onIconMouseOut(layer)) 76 | }, 77 | onIconMouseOver: (e, layer) => { 78 | dispatch(onIconMouseOver(layer)) 79 | }, 80 | onLayerClick: item => { 81 | dispatch(toggleLayerInformation(item)) 82 | }, 83 | onLayerDown: item => { 84 | dispatch(slideLayerDown(item)) 85 | }, 86 | onLayerDrop: (dragged, target) => { 87 | dispatch(dropLayer(dragged, target)) 88 | }, 89 | onLayerFilterSearch: (layerName, parameterKey, parameterValue) => { 90 | dispatch(layerFilterLoading(layerName, parameterKey, parameterValue)) 91 | GeoAPI.getLayerFilteredData(layerName, parameterKey, parameterValue, onLayerFilterSearchLoaded) 92 | }, 93 | onLayerRemove: item => { 94 | dispatch(toggleLayer(item)) 95 | }, 96 | onLayerUp: item => { 97 | dispatch(slideLayerUp(item)) 98 | }, 99 | onLoadParams: layer => { 100 | dispatch(onLoadingParams(layer)) 101 | GeoAPI.getLayerParams(layer, data => { 102 | let xmlDoc 103 | if (window.DOMParser) { 104 | let parser = new DOMParser() 105 | xmlDoc = parser.parseFromString(data, 'text/xml') 106 | } else { 107 | // Internet Explorer 108 | xmlDoc = new ActiveXObject('Microsoft.XMLDOM') 109 | xmlDoc.async = 'false' 110 | xmlDoc.loadXML(data) 111 | } 112 | let params = Array.from( 113 | xmlDoc.getElementsByTagName('xsd:element') 114 | ).map( 115 | el => el.getAttribute('name') 116 | ) 117 | params.pop() // remove last one (xsd element of the layer itself) 118 | dispatch(onLoadParams(layer, params)) 119 | }) 120 | }, 121 | onOpenLayerFilterModal: item => { 122 | dispatch(openLayerFilterModal(item)) 123 | }, 124 | onOpenModal: (item, lastClickData) => { 125 | dispatch(openModal(item)) 126 | var selectedLayer = item 127 | onGetModalData(selectedLayer, lastClickData) 128 | }, 129 | onRemoveAllLayers: item => { 130 | dispatch(removeAllLayers()) 131 | }, 132 | onSidebarRightHideClick: () => { 133 | dispatch(hideSidebarRight()) 134 | }, 135 | } 136 | } 137 | 138 | const SidebarRightContainer = connect( 139 | mapStateToProps, 140 | mapDispatchToProps 141 | )(SidebarRight) 142 | 143 | export default SidebarRightContainer 144 | -------------------------------------------------------------------------------- /src/components/Toolbar/Toolbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ToolbarMenu from '../ToolbarMenu/ToolbarMenu' 3 | 4 | const Toolbar = ({ 5 | baseMaps, 6 | globalFilterType, 7 | layers, 8 | loginStatus, 9 | mapProperties, 10 | places, 11 | showSidebarRight, 12 | toolbarActive, 13 | tutela, 14 | onChangeActiveBaseMap, 15 | onClearPlaceTutelaLayer, 16 | onContourChange, 17 | onGlobalFilterTypeChange, 18 | onKeyUpSearchPlaces, 19 | onKeyUpSearchTutela, 20 | onOpacityChange, 21 | onPlaceClick, 22 | onToolbarItemClick, 23 | onTutelaClick, 24 | onDownloadClick, 25 | onDownloadEnd, 26 | ownProps, 27 | }) => { 28 | let className 29 | let tooltipClassName = 'tooltip' 30 | let active 31 | let { type, items } = ownProps 32 | 33 | if (type === 'platform') { 34 | className = 'platform-toolbar' 35 | tooltipClassName += ' bottom' 36 | } 37 | if (type === 'map') { 38 | className = 'map-toolbar' 39 | tooltipClassName += ' top' 40 | } 41 | if (!items) { 42 | return null 43 | } 44 | if (showSidebarRight) { 45 | className += ' sidebar-left-opened' 46 | } 47 | 48 | if (toolbarActive) { 49 | active = toolbarActive 50 | } 51 | 52 | if (loginStatus) { 53 | items = items.map((item) => { 54 | if (item.name === 'login') { 55 | item.className = 'fa fa-sign-out login-logout login' 56 | item.tooltip = 'Logout' 57 | item.name = 'logout' 58 | } 59 | return item 60 | }) 61 | } else { 62 | items = items.map(item => { 63 | if (item.name === 'logout') { 64 | item.className = 'fa fa-sign-in login-logout login' 65 | item.tooltip = 'Login' 66 | item.name = 'login' 67 | } 68 | return item 69 | }) 70 | } 71 | 72 | function handleClick(e){ 73 | if (e.target.classList.contains('toolbar-item')) { 74 | onToolbarItemClick(e.target.dataset.id) 75 | } 76 | } 77 | return ( 78 | // the data-html2canvas-ignore attribute tells html2canvas to ignore rendering this element on image capture 79 |
    80 | { 81 | items.map( (item, index) => { 82 | var itemClassName = 'toolbar-item ' + item.className 83 | if (active === item.name) { 84 | itemClassName += ' active' 85 | } 86 | 87 | // focus on searchStreet box manually 88 | if (item.name === 'searchStreet' && active === 'searchStreet') { 89 | setTimeout(() => { 90 | document.getElementById('GooglePlacesSearch').focus() 91 | }, 200) 92 | } 93 | 94 | return ( 95 |
    handleClick(e)} 100 | > 101 | 125 | 126 | {item.tooltip} 127 |
    128 | ) 129 | }) 130 | } 131 |
    132 | ) 133 | } 134 | 135 | export default Toolbar 136 | -------------------------------------------------------------------------------- /src/components/Modal/Contents/Table.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DataTable from '../../DataTable/DataTable.js' 3 | 4 | const Table = ({ 5 | layers, 6 | onGetModalData, 7 | onChangeActiveTab, 8 | onPaginate, 9 | onCloseModal, 10 | }) => { 11 | 12 | function handleGetModalData(layer, lastClickData) { 13 | return onGetModalData(layer, lastClickData) 14 | } 15 | 16 | function handleChangeActiveTab(layer) { 17 | return onChangeActiveTab(layer, lastClickData) 18 | } 19 | 20 | function handlePaginate(layer, page) { 21 | return onPaginate(layer, page) 22 | } 23 | 24 | /** 25 | * @param {Object} data Active layer object 26 | * This function create and download a custom CSV file with 27 | * the data of the active layer shown on modal window 28 | */ 29 | function createCsv(data) { 30 | let csvContent = "data:text/csv;charset=utf-8,\uFEFF" 31 | let titleArray = [] 32 | let contentArray = [] 33 | let titleData = "" 34 | let csvData = "" 35 | let link 36 | let encodeUri 37 | 38 | // Get the title line from the first entry of the object 39 | titleArray = Object.keys(data.modal.pages[0][0].properties) 40 | for(let i = 0; i < titleArray.length; i++) { 41 | titleData += `${titleArray[i]};` 42 | } 43 | titleData += "\n" 44 | 45 | // Get the content lines from the objects 46 | data.modal.pages.forEach(page => { 47 | page.forEach(p => { 48 | contentArray = Object.values(p.properties) 49 | for(let i = 0; i < contentArray.length; i++) { 50 | csvData += `${contentArray[i]};` 51 | } 52 | csvData += "\n" 53 | }) 54 | }) 55 | csvContent += titleData 56 | csvContent += csvData 57 | encodeUri = encodeURI(csvContent) 58 | 59 | // Fake a anchor tag and link to create a custom name for the file and delete it after use 60 | link = document.createElement('a'); 61 | link.setAttribute('href', encodeUri); 62 | link.setAttribute('download', "dados_mapa.csv"); 63 | link.click(); 64 | } 65 | 66 | const selectedLayers = layers.filter(l => l.selected) 67 | let selectedLayer 68 | 69 | return ( 70 |
    71 |
      72 | { 73 | selectedLayers.map((layer, index) => { 74 | let className = "modal-layer-list--link" 75 | if (layer && layer.modal && layer.modal.activeLayer) { 76 | className += ' active' 77 | selectedLayer = layer 78 | } 79 | 80 | return ( 81 |
    • 82 | handleChangeActiveTab(layer)}> 83 | {layer.title} 84 | 85 |
    • 86 | ) 87 | }) 88 | } 89 |
    90 | {selectedLayer && selectedLayer.modal && selectedLayer.modal.pages ? 91 | 92 | : '' 93 | } 94 |
      95 |
    • 96 | 100 | 118 |
    • 119 |
    • 120 | 123 |
    • 124 |
    125 |
    126 | ) 127 | } 128 | 129 | export default Table 130 | -------------------------------------------------------------------------------- /src/components/Menu/menu.scss: -------------------------------------------------------------------------------- 1 | @import "menu-extends.scss"; 2 | 3 | .menu-item-container { 4 | position: relative; 5 | transition: left .2s ease-in-out; 6 | left: 0; 7 | height: auto; 8 | visibility: visible; 9 | } 10 | 11 | .hidden.menu-item-container { 12 | left: -$sidebar-left-width; 13 | height: 0; 14 | } 15 | 16 | // ul 17 | .menu { 18 | color: $menu-text-color; 19 | font-family: sans-serif; 20 | font-size: 11px; 21 | overflow-y: auto; 22 | height: calc(100% - 132px); 23 | width: 100%; 24 | 25 | li { 26 | transition: 27 | all .15s ease background-color, 28 | all .15s ease color; 29 | &:hover { 30 | transition-duration: .3s; 31 | } 32 | } 33 | 34 | // li 35 | &-item { 36 | &-with-children, &-all-layers, &-layer { 37 | border-bottom: 1px solid $menu-secondary-color; 38 | cursor: pointer; 39 | list-style-type: none; 40 | } 41 | 42 | &-all-layers { 43 | position: relative; 44 | } 45 | 46 | &-all-layers:hover { 47 | background-color: $menu-all-layers-bgcolor-hover; 48 | } 49 | 50 | &-with-children, &-layer { 51 | @extend %padding-icon-right; 52 | 53 | &:hover { 54 | background-color: $menu-bg-color-hover; 55 | } 56 | } 57 | 58 | &-with-children { 59 | padding-left: 20px; 60 | 61 | &.selected { 62 | padding-left: 39px; 63 | &:hover { 64 | background-color: $menu-selected-item-children-selected-hover; 65 | } 66 | } 67 | } 68 | 69 | &-with-children, &-all-layers { 70 | text-transform: uppercase; 71 | 72 | // arrow > 73 | &:after { 74 | @extend %menu-item-arrow-right; 75 | } 76 | } 77 | 78 | // submenu children menus are hidden 79 | &-container .menu { 80 | height: 0; 81 | visibility: hidden; 82 | left: $sidebar-left-width; 83 | position: relative; 84 | transition: all .2s ease-in-out; 85 | 86 | li { 87 | display: none; 88 | } 89 | 90 | // when they are selected, they're visible 91 | &.selected { 92 | height: auto; 93 | visibility: visible; 94 | left: 0; 95 | 96 | li { 97 | display: block; 98 | } 99 | 100 | // but submenus that have submenus inside of it opened, are hidden 101 | &.has-submenu-opened { 102 | .menu-item-layer { 103 | display: none; 104 | } 105 | 106 | ul .menu-item-layer { 107 | display: block; 108 | } 109 | } 110 | 111 | // selected submenu 112 | .menu-item-container .menu-item-with-children { 113 | padding-left: 32px; 114 | color: $menu-selected-item-children-color; 115 | background-color: $menu-selected-item-children-bgcolor; 116 | 117 | &.active, 118 | &.active:hover { 119 | background: $sidebar-left-active-background-color; 120 | color: $menu-breadcrumb-text-color; 121 | } 122 | 123 | &.selected{ 124 | padding-left: 20px; 125 | } 126 | 127 | &:hover { 128 | color: $menu-selected-item-children-color-hover; 129 | background-color: $menu-selected-item-children-bgcolor-hover; 130 | } 131 | 132 | } 133 | } 134 | } 135 | 136 | // layer checkboxes 137 | &-layer { 138 | max-height: 33px; 139 | 140 | @extend %padding-icon-left; 141 | 142 | &:after { 143 | @extend %menu-item-checkbox-empty; 144 | } 145 | 146 | &.layer-selected { 147 | color: $menu-selected-layer-color; 148 | background: $menu-selected-layer-bgcolor; 149 | 150 | &:after { 151 | @extend %menu-item-checkbox-checked; 152 | } 153 | } 154 | } 155 | } 156 | 157 | // the menu category that is opened, and the "all layers" menu item 158 | &-item-all-layers, li.selected { 159 | background: $sidebar-left-highlight-color; 160 | color: $menu-breadcrumb-text-color; 161 | 162 | @extend %padding-icon-left; 163 | 164 | // arrow < 165 | &:after { 166 | @extend %menu-item-arrow-left; 167 | } 168 | 169 | &.on-search { 170 | &:after { 171 | content: ""; 172 | } 173 | } 174 | 175 | &.active { 176 | background: $sidebar-left-active-background-color; 177 | padding-left: 20px; 178 | cursor: default; 179 | 180 | &:after { 181 | content: ""; 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/components/App/sass/settings.scss: -------------------------------------------------------------------------------- 1 | 2 | $logo-placeholder-background-color: #0362b3; 3 | $sidebar-left-width: 300px; 4 | $sidebar-left-highlight-color: #f3ac1c; 5 | $sidebar-left-active-background-color: white; 6 | $sidebar-box-shadow-color: rgba(0, 0, 0, 0.5); 7 | 8 | $leaflet-draw-tooltip-color: #ffffff; 9 | $leaflet-draw-tooltip-background-color: rgb(54, 54, 54); 10 | $leaflet-draw-tooltip-background-color-alpha: rgba(0, 0, 0, 0.5); 11 | $leaflet-draw-actions-color: #FFF; 12 | $leaflet-draw-actions-border-color: #AAA; 13 | $leaflet-draw-actions-background-color: #919187; 14 | $leaflet-draw-actions-hover-background-color: #A0A098; 15 | 16 | $menu-header-hover-background-color: #ffc651; 17 | $menu-button-tooltip-bgcolor: #172d4c; 18 | 19 | $menu-bg-color: #182d4c; 20 | $menu-bg-color-hover: #f3ac1c; 21 | $menu-secondary-color: #12243c; 22 | $menu-text-color: white; 23 | $menu-breadcrumb-text-color: #152e4d; 24 | $menu-selected-layer-color: #182d4c; 25 | $menu-selected-layer-bgcolor: white; 26 | $menu-selected-item-children-bgcolor: #0a1e3a; 27 | $menu-selected-item-children-color: #ffffff; 28 | $menu-selected-item-children-color-hover: #152e4d; 29 | $menu-selected-item-children-bgcolor-hover: #f3ac1c; 30 | $menu-selected-item-children-selected-hover: #ffc651; 31 | $menu-all-layers-bgcolor-hover: #ffc651; 32 | 33 | $sidebar-right-text-color: #182d4c; 34 | $sidebar-right-bg-color: #ffffff; 35 | 36 | $search-layer-bg-color: #0b1b32; 37 | $search-layer-input-color: #5779a7; 38 | 39 | $tooltip-bg-color: white; 40 | $tooltip-text-color: #091e3b; 41 | $tooltip-box-shadow-color: rgba(0, 0, 0, .5); 42 | 43 | $toolbar-menu-color: #000000; 44 | $toolbar-menu-shadow: rgba(0, 0, 0, .3); 45 | $toolbar-menu-background-color: #ffffff; 46 | $toolbar-item-background-color: #0362b4; 47 | $toolbar-item-hover-background-color: #083d6b; 48 | $toolbar-item-active-background-color: #f3ac1c; 49 | 50 | $toolbar-inputshare-button-color: #ffffff; 51 | $toolbar-inputshare-button-background-color: #0362b3; 52 | $toolbar-inputshare-button-hover-background-color: #043b6a; 53 | $toolbar-inputshare-button-active-background-color: #f3ac1c; 54 | $toolbar-inputshare-button-active-background-color: #f3ac1c; 55 | $toolbar-inputshare-color: #a4a4a4; 56 | $toolbar-inputshare-border-color: #c2c2c2; 57 | 58 | $layer-content-bg-color: white; 59 | $layer-content-width: 400px; 60 | 61 | $sidebar-right-remove-all-layers-button-width: 28px; 62 | $sidebar-right-remove-all-layers-button-height: 25px; 63 | $sidebar-right-remove-all-layers-button-text-color: white; 64 | $sidebar-right-remove-all-layers-button-bg-color: #bd0401; 65 | 66 | $layer-list-area-title-color: #0b1b32; 67 | $layer-list-icon-color: #162d4d; 68 | $layer-list-primary-text-color: #fdb316; 69 | $layer-list-secondary-text-color: #2a2a2a; 70 | 71 | $layer-list-icon-bg-color-deactivated: white; 72 | $layer-list-icon-text-color-deactivated: #162d4d; 73 | $layer-list-icon-bg-color: #162d4d; 74 | $layer-list-icon-text-color: white; 75 | $layer-list-icon-color-hover: #f3ac1c; 76 | $layer-list-icon-color-danger-hover: #bd0401; 77 | $layer-list-icon-width: 28px; 78 | $layer-list-icon-height: 32px; 79 | $layer-button-carousel-bg-color:#172d4c; 80 | $layer-button-carousel-text-color: #ffffff; 81 | $layer-button-carousel-bg-color-hover: #fdb316; 82 | $layer-button-carousel-text-color-hover:#172d4c; 83 | 84 | $layer-list-header-background: #162d4d; 85 | $layer-list-header-text-color: #ffffff; 86 | $layer-list-header-background-contrast: #fdb316; 87 | $layer-list-header-caption-text-color: #ffffff; 88 | $layer-list-button-remove-all-layers-background: #840806; 89 | $layer-list-button-close-background: #fdb316; 90 | $layer-list-title-border-color: #ced1d6; 91 | 92 | $layer-styles-list-item-selected-background: #172d4c; 93 | $layer-styles-list-item-selected-icon-border-color: #091e3b; 94 | $layer-styles-list-item-active-background-color: #ffc651; 95 | $layer-item-border-color: #ced1d6; 96 | $layer-item-control-background-color: lightgray; 97 | $layer-item-icon-border-color: #ffffff; 98 | $layer-item-selected-background-color-hover: #fbc405; 99 | $layer-item-data-icon-background-color: #0362b4; 100 | $layer-item-data-icon-color: #fff; 101 | 102 | $layer-list-icon-color-contrast: #162d4d; 103 | $layer-list-primary-text-color-contrast: #162d4d; 104 | $layer-list-secondary-text-color-contrast: #162d4d; 105 | 106 | $data-table-header-border-color: #182d4c; 107 | $data-table-header-border-size: 2px; 108 | $data-table-header-text-color: #53616c; 109 | $data-table-body-text-color: #373737; 110 | $data-table-row-bacgkround-color: #f9f9f9; 111 | 112 | $modal-background-color: white; 113 | $modal-box-shadow-color: rgba(40, 40, 40, .7); 114 | $modal-title-color: #182d4c; 115 | $modal-layer-list-border-top-color: #ced1d6; 116 | $modal-layer-list-link-background-color: white; 117 | $modal-layer-list-link-text-color: #182d4c; 118 | $modal-layer-list-link-active-background-color: #182d4c; 119 | $modal-layer-list-link-active-text-color: white; 120 | 121 | $modal-export-list-border-color: #d5d5d5; 122 | 123 | $modal-pagination-primary-color: #182d4c; 124 | $modal-pagination-secondary-color: white; 125 | 126 | $modal-options-export-button-color: #03a104; 127 | $modal-options-back-button-color: #0362b4; 128 | $modal-options-button-text-color: white; 129 | 130 | $export-list-background-color: white; 131 | $export-list-text-color: #373737; 132 | 133 | $modal-street-view-background-color: rgba(0, 0, 0, .5); 134 | $modal-street-view-container-background-color: #ffffff; 135 | $modal-street-view-container-shadow-color: 0 0 10px 1px rgba(0, 0, 0, .5); 136 | 137 | $modal-street-view-button-background-color: #0362b4; 138 | $modal-street-view-button-color: #ffffff; 139 | $modal-street-view-button-shadow-color: 1px 3px 8px 0 rgba(0, 0, 0, .5); 140 | $modal-street-view-button-hover-color: #00427b; 141 | $modal-street-view-button-active-color: #f3ac1c; 142 | 143 | $modal-list-border-color: rgba(0, 0, 0, 0.18); 144 | $modal-list-img-border-color: rgba(128, 128, 128, 0.35); 145 | 146 | $global-filter-color: #b7b6b6; 147 | $global-filter-form-inputsearch-color: #828282; 148 | $global-filter-form-title: #373737; 149 | $global-filter-form-inputsearch-border: #cccccc; 150 | $global-filter-form-inputsearch-bgcolor: #ffffff; 151 | $global-filter-places-title-color: #172d4c; 152 | $input-checkopacity-after-color: #0362b3; 153 | $input-checkopacity-after-bgcolor: #ffffff; 154 | $input-checkopacity-cheked-color: #0362b3; 155 | $opacity-selector-thumb-bgcolor: #0362b3; 156 | $opacity-selector-track-bgcolor: #fdb316; 157 | $opacity-selector-track-border: #cccccc; 158 | $opacity-selector-color: #172d4c; 159 | 160 | $listcrais-bgcolor-hover-active: #172d4c; 161 | $listcrais-color-hover-active: #fdb316; 162 | $listcrais-place-bgcolor: #ffffff; 163 | $listcrais-place-color: #172d4c; 164 | $listcrais-item-border-color: #cccccc; 165 | 166 | -------------------------------------------------------------------------------- /src/components/Sinalid/Sinalid.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Pie, Bar} from 'react-chartjs-2' 3 | 4 | const Sinalid = ({layer}) => { 5 | console.log('Sinalid layer', layer) 6 | 7 | // Empty stats 8 | let statInitialized = false 9 | let stats = { 10 | gender: { 11 | 'MASCULINO': 0, 12 | 'FEMININO': 0, 13 | 'NÃO INFORMADO': 0, 14 | }, 15 | ageRange: { 16 | '12 A 17 ANOS': 0, 17 | '18 A 24 ANOS': 0, 18 | '25 A 29 ANOS': 0, 19 | '30 A 34 ANOS': 0, 20 | '35 A 64 ANOS': 0, 21 | '65 ANOS OU MAIS': 0, 22 | 'NÃO INFORMADA': 0, 23 | } 24 | } 25 | 26 | // Fill stats 27 | layer.features.map(feature => { 28 | feature.properties.sinalid ? feature.properties.sinalid.map(m => { 29 | statInitialized = true 30 | stats.gender[m.sexo]++ 31 | stats.ageRange[m.faixa_IDADE]++ 32 | }) : null 33 | }) 34 | 35 | // Fill charts 36 | let genderChart = { 37 | labels: [ 38 | 'Masculino', 39 | 'Feminino', 40 | '?', 41 | ], 42 | datasets: [ 43 | { 44 | data: [ 45 | stats.gender['MASCULINO'], 46 | stats.gender['FEMININO'], 47 | stats.gender['NÃO INFORMADO'], 48 | ], 49 | backgroundColor: [ 50 | 'rgba(0,0,255,.5)', 51 | 'rgba(255,0,0,.5)', 52 | 'rgba(0,0,0,.5)', 53 | ], 54 | hoverBackgroundColor: [ 55 | 'rgba(0,0,255,1)', 56 | 'rgba(255,0,0,1)', 57 | 'rgba(0,0,0,1)', 58 | ], 59 | } 60 | ], 61 | } 62 | let ageRangeChart = { 63 | labels: [ 64 | '12 a 17', 65 | '18 a 24', 66 | '25 a 29', 67 | '30 a 34', 68 | '35 a 64', 69 | '65+', 70 | '?', 71 | ], 72 | datasets: [ 73 | { 74 | data: [ 75 | stats.ageRange['12 A 17 ANOS'], 76 | stats.ageRange['18 A 24 ANOS'], 77 | stats.ageRange['25 A 29 ANOS'], 78 | stats.ageRange['30 A 34 ANOS'], 79 | stats.ageRange['35 A 64 ANOS'], 80 | stats.ageRange['65 ANOS OU MAIS'], 81 | stats.ageRange['NÃO INFORMADA'], 82 | ], 83 | backgroundColor: [ 84 | 'rgba(255,0,0,.5)', 85 | 'rgba(255,255,0,.5)', 86 | 'rgba(0,255,0,.5)', 87 | 'rgba(0,255,255,.5)', 88 | 'rgba(0,0,255,.5)', 89 | 'rgba(255,0,255,.5)', 90 | 'rgba(0,0,0,.5)', 91 | ], 92 | hoverBackgroundColor: [ 93 | 'rgba(255,0,0,1)', 94 | 'rgba(255,255,0,1)', 95 | 'rgba(0,255,0,1)', 96 | 'rgba(0,255,255,1)', 97 | 'rgba(0,0,255,1)', 98 | 'rgba(255,0,255,1)', 99 | 'rgba(0,0,0,1)', 100 | ], 101 | }, 102 | ], 103 | } 104 | 105 | // Remove empty values 106 | const removeEmptyValues = (obj) => { 107 | if (obj) { 108 | for (let i = obj.datasets[0].data.length - 1; i >= 0; i--) { 109 | if (obj.datasets[0].data[i] === 0) { 110 | obj.labels.splice(i, 1) 111 | obj.datasets[0].data.splice(i, 1) 112 | obj.datasets[0].backgroundColor.splice(i, 1) 113 | obj.datasets[0].hoverBackgroundColor.splice(i, 1) 114 | } 115 | } 116 | } 117 | } 118 | removeEmptyValues(genderChart) 119 | removeEmptyValues(ageRangeChart) 120 | 121 | let ageRangeChartOptions = { 122 | legend: { 123 | display: false 124 | }, 125 | scales: { 126 | yAxes: [ 127 | { 128 | ticks: { 129 | beginAtZero: true 130 | } 131 | } 132 | ] 133 | }, 134 | } 135 | 136 | console.log(statInitialized, stats) 137 | 138 | return ( 139 |
    140 |

    Sinalid - Desaparecidos

    141 | {layer.features.map((feature, indexFeature) => { 142 | return (feature.properties.DP ? 143 |
    144 |

    {feature.properties.DP}ª DP: {feature.properties.sinalid ? feature.properties.sinalid.length : 'Não há'} desaparecidos

    145 |

    Gênero:

    146 | 147 |

    Idade:

    148 | 149 |

    Lista de pessoas:

    150 | {feature.properties.sinalid ? feature.properties.sinalid.map((missingPerson, indexMissingPerson) => { 151 | return ( 152 |
    153 | {'Foto 154 |
    155 |
    156 |

    157 | 158 | {missingPerson.nome} 159 |

    160 |

    161 | Data de Desaparecimento: 162 | 163 | {missingPerson.desaparecimento} 164 |

    165 |
    166 |

    {missingPerson.circunstancias}

    167 |
    168 |
    169 | ) 170 | }) :

    Não há desaparecidos para esta delegacia.

    } 171 |
    172 | : null) 173 | })} 174 |
    175 | ) 176 | } 177 | 178 | export default Sinalid 179 | -------------------------------------------------------------------------------- /src/components/App/reducers/geoServerXmlReducer.js: -------------------------------------------------------------------------------- 1 | import { parseBoundingBox, parseStyle } from './geoServerXmlStyleReducer' 2 | 3 | const WORKSPACE = __WORKSPACE__ 4 | const ENDPOINT = __API__ 5 | 6 | /** 7 | * Parses XML response from GeoServer, creating a layers array 8 | * @param response response from GeoServer API (as XML) 9 | */ 10 | const geoServerXmlReducer = (response) => { 11 | let layers = [] 12 | 13 | // adds iterators to XML nodes, so we can run forEach on them 14 | NodeList.prototype.forEach = Array.prototype.forEach 15 | HTMLCollection.prototype.forEach = Array.prototype.forEach 16 | 17 | // get root layers node, and parse layer data for each layer 18 | const parser = new DOMParser() 19 | const xmlDoc = parser.parseFromString(response.data, 'text/xml') 20 | xmlDoc.firstChild.childNodes.forEach((rootChildrenNode) => { 21 | if (rootChildrenNode.nodeName === 'Capability') { 22 | rootChildrenNode.childNodes.forEach( (capabilityChildrenNode) => { 23 | if (capabilityChildrenNode.nodeName === 'Layer') { 24 | capabilityChildrenNode.childNodes.forEach((rootLayerChildrenNode) => { 25 | if (rootLayerChildrenNode.nodeName === 'Layer') { 26 | layers = parseLayerNode(rootLayerChildrenNode, layers) 27 | } 28 | }) 29 | } 30 | }) 31 | } 32 | }) 33 | 34 | return layers 35 | } 36 | 37 | /** 38 | * Parses XML node for a single layer, populating GeoAPI.layers array. 39 | * @param xmlNode XML node with a GeoServer layer 40 | * @param layers layers array we're building 41 | */ 42 | const parseLayerNode = (xmlNode, layers) => { 43 | if (isValidLayer(xmlNode)) { 44 | const charts = [] 45 | const caops = [] 46 | let menu = '' 47 | let menu2 = '' 48 | let name 49 | let title 50 | let abstract 51 | let table 52 | let stylesOrdered = false 53 | let RESTRICTED = false 54 | 55 | // gets name, title, abstract, and keywords for caops and menu 56 | xmlNode.childNodes.forEach(layerChildrenNode => { 57 | const nodeName = [] 58 | nodeName.Name = () => { name = layerChildrenNode.textContent } 59 | nodeName.Title = () => { title = layerChildrenNode.textContent } 60 | nodeName.Abstract = () => { abstract = layerChildrenNode.textContent } 61 | nodeName.KeywordList = () => { 62 | layerChildrenNode.childNodes.forEach(keywordNode => { 63 | if (keywordNode.nodeName === 'Keyword') { 64 | const keywordsArray = keywordNode.textContent.split(':') 65 | if (keywordsArray[0] === 'cao') { 66 | caops.push(keywordsArray[1]) 67 | } 68 | if (keywordsArray[0] === 'tabela') { 69 | try { 70 | table = JSON.parse(keywordsArray[1]) 71 | } 72 | catch (e) { 73 | // declarações para manipular quaisquer exceções 74 | console.log(e) 75 | table = [] 76 | } 77 | } 78 | if (keywordsArray[0] === 'menu') { 79 | menu = keywordsArray[1] 80 | } 81 | if (keywordsArray[0] === 'menu2') { 82 | const menu2Array = [ 83 | keywordsArray[1] 84 | ] 85 | if (keywordsArray.length > 2) { 86 | // copy all submenus 87 | for (let i = 2, l = keywordsArray.length; i < l; i++) { 88 | menu2Array.push(keywordsArray[i]) 89 | } 90 | } 91 | menu2 = menu2Array 92 | } 93 | if (keywordsArray[0] === 'grafico') { 94 | const chartData = keywordsArray[1].split('|') 95 | const chartColumns = chartData[3].split(',') 96 | for (let c = 0, lc = chartColumns.length; c < lc; c++) { 97 | chartColumns[c] = chartColumns[c].split('/') 98 | } 99 | const chartObject = { 100 | type: chartData[0], 101 | title: chartData[1], 102 | entity: chartData[2], 103 | columns: chartColumns 104 | } 105 | charts.push(chartObject) 106 | } 107 | if (keywordsArray[0] === 'ordenar') { 108 | stylesOrdered = true 109 | } 110 | if (keywordsArray[0] === 'restrito') { 111 | RESTRICTED = true 112 | } 113 | } 114 | }) 115 | } 116 | 117 | if (nodeName[layerChildrenNode.nodeName]) { 118 | nodeName[layerChildrenNode.nodeName]() 119 | } 120 | }) 121 | 122 | // create layer object 123 | var layer = { 124 | name, 125 | title, 126 | caops, 127 | menu, 128 | menu2, 129 | table, 130 | charts, 131 | stylesOrdered, 132 | id: `${WORKSPACE}_${name}`, 133 | workspace: WORKSPACE, 134 | display: true, 135 | restricted: RESTRICTED, 136 | layerName: `${WORKSPACE}:${name}`, 137 | description: abstract, 138 | bbox: parseBoundingBox(xmlNode), 139 | key: layers.length, 140 | } 141 | 142 | // get layer styles 143 | layer.styles = parseStyle(xmlNode, layer) 144 | 145 | // add to layers array if they have a valid menu key (i.e. is published) 146 | if (menu) { 147 | layers.push(layer) 148 | } 149 | } 150 | 151 | return layers 152 | } 153 | 154 | /** 155 | * Check if the given XML node represents a valid layer. 156 | * The layers we use must have a KeywordList node, with at least one element inside. 157 | * @param xmlNode XML node with a GeoServer layer 158 | * @returns {Boolean} 159 | */ 160 | const isValidLayer = (xmlNode) => { 161 | let ret = false 162 | 163 | xmlNode.childNodes.forEach((layerChildrenNode) => { 164 | if (layerChildrenNode.nodeName === 'KeywordList' && layerChildrenNode.childNodes.length > 0) { 165 | if (ret === false) { 166 | ret = true 167 | } 168 | } 169 | }) 170 | 171 | return ret 172 | } 173 | 174 | export default geoServerXmlReducer 175 | --------------------------------------------------------------------------------