diff --git a/designs/icons-tcef.svg b/designs/icons-tcef.svg index 0b965d0..f490152 100644 --- a/designs/icons-tcef.svg +++ b/designs/icons-tcef.svg @@ -246,7 +246,7 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.24748737" - inkscape:cx="680.72767" + inkscape:cx="-594.08486" inkscape:cy="654.87036" inkscape:document-units="mm" inkscape:current-layer="layer1" @@ -272,64 +272,67 @@ inkscape:groupmode="layer" id="layer1"> + style="fill:#d45500;stroke-width:2.49893761" + transform="matrix(0.15677218,0,0,0.1664724,230.41495,-36.603316)" + id="g3415" + inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/your-position.png" + inkscape:export-xdpi="24.116745" + inkscape:export-ydpi="24.116745"> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + style="fill:#d45500;stroke-width:2.49893761" /> + + + + + + + + + + + + + + + + diff --git a/imports/startup/client/geolocation.js b/imports/startup/client/geolocation.js new file mode 100644 index 0000000..5d3a97a --- /dev/null +++ b/imports/startup/client/geolocation.js @@ -0,0 +1,18 @@ +import { Meteor } from 'meteor/meteor'; +const geolocation = new ReactiveVar(); + +Meteor.startup(function() { + Meteor.call("geo", function (error, response) { + if (error) { + console.warn(error); + } else { + var pos = [ + response.location.latitude, + response.location.longitude + ]; + geolocation.set(pos); + } + }); +}); + +export default geolocation; diff --git a/imports/startup/client/gkeys.js b/imports/startup/client/gkeys.js new file mode 100644 index 0000000..ca38c40 --- /dev/null +++ b/imports/startup/client/gkeys.js @@ -0,0 +1,34 @@ +import { Meteor } from 'meteor/meteor'; +import i18n from 'i18next'; + +const gmapkey = new ReactiveVar(); + +Meteor.startup(function() { + Meteor.call('getMapKey', function (error, key) { + if (typeof key !== 'undefined') { + // console.log(key); + gmapkey.set(key); + } else { + callback(error, null) + } + }) +}); + +var getGKeys = function (callback) { + Meteor.autorun(function() { + var key = gmapkey.get(); + if (typeof key !== 'undefined') { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = function () { + // console.log(key); + callback(null, key); + }; + // https://stackoverflow.com/questions/28130114/google-maps-places-autocomplete-language-output + script.src = `https://maps.googleapis.com/maps/api/js?key=${key}&libraries=places&language=${i18n.language}` + document.body.appendChild(script); + } + }); +} + +export default getGKeys; diff --git a/imports/startup/client/i18n.js b/imports/startup/client/i18n.js index ecf5b83..2de5331 100644 --- a/imports/startup/client/i18n.js +++ b/imports/startup/client/i18n.js @@ -75,7 +75,7 @@ i18n.use(backend) whitelist: false, // whitelist: ['es', 'en'], // allowed languages load: 'all', // es-ES -> es, en-US -> en - debug: true, + debug: false, ns: 'common', defaultNS: 'common', saveMissing: true, // if true seems it's fails to getResourceBundle diff --git a/imports/startup/server/geolocation.js b/imports/startup/server/geolocation.js index 2039b2f..dac9eb6 100644 --- a/imports/startup/server/geolocation.js +++ b/imports/startup/server/geolocation.js @@ -40,4 +40,11 @@ Meteor.methods({ }) return promise.await(); }, + getMapKey: function () { + // http://meteorpedia.com/read/Environment_Variables + // https://developers.google.com/maps/documentation/javascript/get-api-key + // https://console.developers.google.com/ + // export GMAPS_KEY=SomeGMapsKey + return process.env.GMAPS_KEY || Meteor.settings.gmaps.key;; + }, }); diff --git a/imports/ui/components/DistanceSlider/DistanceSlider.js b/imports/ui/components/DistanceSlider/DistanceSlider.js new file mode 100644 index 0000000..f1b2e6f --- /dev/null +++ b/imports/ui/components/DistanceSlider/DistanceSlider.js @@ -0,0 +1,96 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Trans, translate } from 'react-i18next'; +import Slider, { Range } from 'rc-slider'; +import Tooltip from 'rc-tooltip'; +// We can just import Slider or Range to reduce bundle size +// import Slider from 'rc-slider/lib/Slider'; +// import Range from 'rc-slider/lib/Range'; +import 'rc-slider/assets/index.css'; + +// https://www.npmjs.com/package/rc-slider +const createSliderWithTooltip = Slider.createSliderWithTooltip; +const Handle = Slider.Handle; + +const handle = (props) => { + const { value, dragging, index, ...restProps } = props; + return ( + + + + ); +}; + +const wrapperStyle = { width: 400, margin: 50 }; + +// https://github.com/react-component/slider/tree/master/examples + +class DistanceSlider extends React.Component { + constructor(props) { + super(props); + this.state = { + value: 10, + }; + } + + onSliderChange = (value) => { + // console.log(value); + this.setState({ + value, + }); + this.props.onChange(value); + } + + onAfterChange = (value) => { + // console.log(`After change: ${value}`); //eslint-disable-line + this.props.onChange(value); + } + + render() { + return ( + +
+

¿A que distancia a la redonda quieres recibir notificaciones?

+ +
+ ) + } +} + +export default translate([], { wait: true })(DistanceSlider); diff --git a/imports/ui/components/SelectionMap/SelectionMap.js b/imports/ui/components/SelectionMap/SelectionMap.js new file mode 100644 index 0000000..5f8ddf0 --- /dev/null +++ b/imports/ui/components/SelectionMap/SelectionMap.js @@ -0,0 +1,119 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Map, TileLayer, Marker, Popup, CircleMarker, Circle} from 'react-leaflet'; +import Leaflet from 'leaflet'; +import { withTracker } from 'meteor/react-meteor-data'; +import { translate } from 'react-i18next'; +import geolocation from '/imports/startup/client/geolocation'; +import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.css'; +import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.js'; + +const positionIcon = new Leaflet.Icon({ + iconUrl: "/your-position.png", + /* shadowUrl: require('../public/marker-shadow.png'), */ + iconSize: [50, 77], // size of the icon + /* shadowSize: [50, 64], // size of the shadow */ + iconAnchor: [25, 82], // point of the icon which will correspond to marker's location + /* shadowAnchor: [4, 62], // the same for the shadow + * popupAnchor: [-3, -76]// point from which the popup should open relative to the iconAnchor*/ +}) + +class SelectionMap extends Component { + constructor(props) { + super(props); + this.state = { + center: geolocation.get(), + marker: geolocation.get(), + zoom: 11, + draggable: true, + modified: false + }; + // console.log(this.state); + } + + componentDidUpdate = () => { + this.fit(); + } + + getMap = () => { + return this.refs.selectionMap.leafletElement; + } + + toggleDraggable = () => { + this.setState({ draggable: !this.state.draggable }) + } + + updatePosition = () => { + const { lat, lng } = this.refs.marker.leafletElement.getLatLng() + this.setState({ + marker: [ lat, lng ], + modified: true + }); + } + + fit() { + // console.log("fit!"); + this.getMap().fitBounds(this.refs.distanceCircle.leafletElement.getBounds(), [70, 70]); + } + + componentDidMount() { + this.addScale(); + } + + addScale = () => { + // https://www.npmjs.com/package/leaflet-graphicscale + const map = this.getMap(); + var options = { + fill: 'fill', + showSubunits: true, + } + var graphicScale = L.control.graphicScale([options]).addTo(map); + } + + render() { + this.state.center = !this.state.modified && this.props.lat? + [this.props.lat, this.props.lng]: this.state.center; + this.state.marker = !this.state.modified && this.props.lat? + [this.props.lat, this.props.lng]: this.state.marker; + this.state.distance = this.props.distance; + this.state.modified = false; + return ( +
+ { this.state && this.state.center && + + + + + + + } +
+ ) + } +} + +SelectionMap.propTypes = { + lat: PropTypes.number, + lng: PropTypes.number, + distance: PropTypes.number +}; + +export default translate([], { wait: true })(SelectionMap); diff --git a/imports/ui/layouts/App/App.js b/imports/ui/layouts/App/App.js index 00d7bd1..7382994 100644 --- a/imports/ui/layouts/App/App.js +++ b/imports/ui/layouts/App/App.js @@ -35,6 +35,7 @@ import { I18nextProvider } from 'react-i18next'; import i18n from '/imports/startup/client/i18n'; import '/imports/startup/client/piwik-start.js'; import ravenLogger from '/imports/startup/client/ravenLogger'; +import geolocation from '/imports/startup/client/geolocation'; // https://github.com/gadicc/meteor-blaze-react-component/ import Blaze from 'meteor/gadicc:blaze-react-component'; import createHistory from 'history/createBrowserHistory'; diff --git a/imports/ui/pages/FiresMap/FiresMap.js b/imports/ui/pages/FiresMap/FiresMap.js index ef92256..18962c2 100644 --- a/imports/ui/pages/FiresMap/FiresMap.js +++ b/imports/ui/pages/FiresMap/FiresMap.js @@ -16,6 +16,7 @@ import LGeo from 'leaflet-geodesy'; import union from 'turf-union'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.css'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.js'; +import _ from 'lodash'; // https://stackoverflow.com/questions/35394577/leaflet-js-union-merge-circles function unify(polyList) { @@ -163,17 +164,22 @@ class FiresMap extends React.Component { height.set(this.divElement.clientHeight); width.set(this.divElement.clientWidth); this.addScale(); + const self = this; + this.handleViewportChangeDebounced = _.debounce(function (viewport) { + console.log(`Viewport changed: ${JSON.stringify(this.state.viewport)}`); + zoom.set(viewport.zoom); + lat.set(viewport.center[0]); + lng.set(viewport.center[1]); + self.state.viewport = viewport; + self.state.modified = true; + self.showSubsUnion(self.state.showSubsUnion); + }, 2000); } - onViewportChanged = viewport => { - // console.log(`Viewport changed: ${JSON.stringify(this.state.viewport)}`); - zoom.set(viewport.zoom); - lat.set(viewport.center[0]); - lng.set(viewport.center[1]); - this.state.viewport = viewport; - this.state.modified = true; - this.showSubsUnion(this.state.showSubsUnion); - } + // https://stackoverflow.com/questions/23123138/perform-debounce-in-react-js + onViewportChanged = (viewport) => { + this.handleViewportChangeDebounced(viewport); + }; onClickReset = () => { // console.log("onclick"); @@ -297,7 +303,7 @@ class FiresMap extends React.Component { - (*) Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos. + (*) Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos. ); diff --git a/imports/ui/pages/Sandbox/Sandbox.js b/imports/ui/pages/Sandbox/Sandbox.js index 3196cff..85d2931 100644 --- a/imports/ui/pages/Sandbox/Sandbox.js +++ b/imports/ui/pages/Sandbox/Sandbox.js @@ -1,58 +1,145 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Button } from 'react-bootstrap'; +import { FormGroup, ControlLabel, Button, Row, Col, HelpBlock } from 'react-bootstrap'; import { Trans, translate } from 'react-i18next'; -import Slider, { Range } from 'rc-slider'; -import Tooltip from 'rc-tooltip'; -// We can just import Slider or Range to reduce bundle size -// import Slider from 'rc-slider/lib/Slider'; -// import Range from 'rc-slider/lib/Range'; -import 'rc-slider/assets/index.css'; +import DistanceSlider from '/imports/ui/components/DistanceSlider/DistanceSlider'; +import SelectionMap from '/imports/ui/components/SelectionMap/SelectionMap'; +import getGKeys from '/imports/startup/client/gkeys'; +import PlacesAutocomplete, { geocodeByAddress, geocodeByPlaceId, getLatLng } from 'react-places-autocomplete' +import update from 'immutability-helper'; +import { withTracker } from 'meteor/react-meteor-data'; -// https://www.npmjs.com/package/rc-slider -const createSliderWithTooltip = Slider.createSliderWithTooltip; -const Handle = Slider.Handle; +class Sandbox extends React.Component { + constructor(props) { + super(props); + this.state = { + adddress: '' + }; + self = this; + // this.handleSelect = this.handleSelect.bind(this) + // this.handleChange = this.handleChange.bind(this) + } -const handle = (props) => { - const { value, dragging, index, ...restProps } = props; - return ( - - - - ); -}; -const wrapperStyle = { width: 400, margin: 50 }; + onChange = (address) => { this.setState({ address }) } -const Sandbox = props => ( -
- -
-); + handleSelect = (address) => { + geocodeByAddress(address) + .then((results) => getLatLng(results[0])) + .then(({ lat, lng }) => { + // console.log('Success Yay', { lat, lng }) + const newState = update(this.state, {$merge: {lat: lat, lng: lng}}); + // console.log(newState); + this.setState(newState); + // console.log(this.state); + }) + .catch(error => { + console.log(error); + if (error === 'ZERO_RESULTS') { + console.log("No results"); + } + }); + } -Sandbox.defaultProps = { -}; + onSliderChange = (value) => { + this.setState(update(this.state, {$merge: {distance: value}})); + } + + render() { + // https://www.npmjs.com/package/react-places-autocomplete + + // https://github.com/kenny-hibino/react-places-autocomplete/issues/103 + const myStyles = { + autocompleteContainer: { + paddingBottom: '20px', + backgroundSize: 'auto 12px', + backgroundPosition: 'bottom left 10px', + backgroundRepeat: 'no-repeat', + backgroundImage: "url('https://maps.gstatic.com/mapfiles/api-3/images/powered-by-google-on-white3_hdpi.png')", + }, + } + + const AutocompleteItem = ({ formattedSuggestion }) => ( +
+ {' '} + {formattedSuggestion.mainText}{' '} + {formattedSuggestion.secondaryText} +
) + + // https://developers.google.com/places/web-service/search + // https://github.com/kenny-hibino/react-places-autocomplete/blob/master/demo/Demo.js + return ( +
+ + + { typeof this.props.gkey === 'string' && +
+

Suscríbete a alertas de fuegos

+
+ + + Indícanos la posición a vigilar (por ej. tu pueblo, una calle, etc): + + { console.log('Blur event!'); }, + onFocus:() => { console.log('Focused!'); }, + autoFocus:true + }} /> + También puedes seleccionar el lugar en el mapa arrastrando el puntero naranja. + +
+
+ } + + + + + +
+ + + +
+ ) + } +} Sandbox.propTypes = { -}; + gkey: PropTypes.string +} -export default translate([], { wait: true })(Sandbox); +const gkey = new ReactiveVar(); + +getGKeys(function(err, key) { + if (err) { + console.log(err); + } else { + gkey.set(key); + } +}); + +export default translate([], { wait: true }) (withTracker(() => { + return { + gkey: gkey.get() + }; +})(Sandbox)); diff --git a/package.json b/package.json index 7197fbe..e423bf3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "i18next-browser-languagedetector": "^2.1.0", "i18next-localstorage-cache": "^1.1.1", "i18next-xhr-backend": "^1.5.0", + "immutability-helper": "^2.5.1", "indexof": "0.0.1", "jquery": "^2.2.4", "jquery-validation": "^1.17.0", @@ -42,6 +43,7 @@ "react-dom": "^15.6.1", "react-i18next": "^6.1.0", "react-leaflet": "^1.7.7", + "react-places-autocomplete": "^5.4.3", "react-resize-detector": "^1.1.0", "react-router-bootstrap": "^0.24.3", "react-router-dom": "^4.2.2", diff --git a/public/favicon.png b/public/favicon.png index 2b26d3d..77a5179 100644 Binary files a/public/favicon.png and b/public/favicon.png differ diff --git a/public/locales/es/common.json b/public/locales/es/common.json index fa7ec65..88bbf46 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -119,5 +119,12 @@ "activeNeigFireInMapCount": "En naranja, los fuegos notificados por nuestros usuarios/as recientemente.", "Centrar el mapa en tu ubicación": - "Centrar el mapa en tu ubicación" + "Centrar el mapa en tu ubicación", + "Resaltar en verde el área vigilada por nuestros usuarios/as": + "Resaltar en verde el área vigilada por nuestros usuarios/as", + "Resaltar los fuegos con un marcador": + "Resaltar los fuegos con un marcador", + "mapPrivacy": + "<0>Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos. <0>Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos." + } diff --git a/public/your-position.png b/public/your-position.png new file mode 100644 index 0000000..c831c40 Binary files /dev/null and b/public/your-position.png differ