From e3eddd6b85eeea6a6b5ae1561420a3b4538d570a Mon Sep 17 00:00:00 2001 From: vjrj Date: Thu, 21 Dec 2017 10:57:00 +0100 Subject: [PATCH] Many improvements in SelectionMap --- imports/api/Subscriptions/Subscriptions.js | 32 ++++++++++ imports/api/Subscriptions/methods.js | 23 ++++++- .../api/Subscriptions/server/publications.js | 6 +- .../DistanceSlider/DistanceSlider.js | 7 ++- imports/ui/components/Prompt/Prompt.js | 60 +++---------------- .../components/SelectionMap/SelectionMap.js | 44 ++++++++++---- .../SubscriptionEditor/SubscriptionEditor.js | 26 ++++---- .../FireSubscription/FireSubscription.js | 16 +---- imports/ui/pages/Index/Index.js | 5 +- imports/ui/pages/Login/Login.js | 2 +- imports/ui/pages/Signup/Signup.js | 56 ++++++++--------- .../ui/pages/Subscriptions/Subscriptions.js | 24 +++++--- .../ui/stylesheets/bootstrap-overrides.scss | 4 ++ 13 files changed, 173 insertions(+), 132 deletions(-) diff --git a/imports/api/Subscriptions/Subscriptions.js b/imports/api/Subscriptions/Subscriptions.js index 373c015..b039ba1 100644 --- a/imports/api/Subscriptions/Subscriptions.js +++ b/imports/api/Subscriptions/Subscriptions.js @@ -37,10 +37,42 @@ Subscriptions.deny({ * } * */ +// https://stackoverflow.com/questions/24492333/meteor-simple-schema-for-mongo-geo-location-data +// https://github.com/aldeed/meteor-simple-schema/issues/606 +const LocationSchema = new SimpleSchema({ + type: { + type: String, + allowedValues: ['Point'] + }, + coordinates: { + type: Array, + minCount: 2, + maxCount: 2, + custom: function custom() { + if (!(this.value[0] >= -90 && this.value[0] <= 90)) { + return 'lngOutOfRange'; + } + if (!(this.value[1] >= -180 && this.value[1] <= 180)) { + return 'latOutOfRange'; + } + return true; + } + }, + 'coordinates.$': { + type: Number + } +}); + +LocationSchema.messageBox.messages({ + lonOutOfRange: '[label] longitude should be between -90 and 90', + latOutOfRange: '[label] latitude should be between -180 and 180' +}); + Subscriptions.schema = new SimpleSchema({ location: Object, 'location.lat': Number, 'location.lon': Number, + geo: LocationSchema, distance: Number, owner: String, type: String diff --git a/imports/api/Subscriptions/methods.js b/imports/api/Subscriptions/methods.js index d5e7c62..e7e3bef 100644 --- a/imports/api/Subscriptions/methods.js +++ b/imports/api/Subscriptions/methods.js @@ -3,6 +3,13 @@ import { check, Match } from 'meteor/check'; import Subscriptions from './Subscriptions'; import rateLimit from '../../modules/rate-limit'; +function geo(doc) { + return { + type: 'Point', + coordinates: [doc.location.lon, doc.location.lat] + }; +} + Meteor.methods({ 'subscriptions.insert': function subscriptionsInsert(doc) { check(doc, { @@ -10,10 +17,17 @@ Meteor.methods({ distance: Number }); const type = 'web'; - try { - return Subscriptions.insert({ owner: this.userId, type, ...doc }); + const newDoc = { + owner: this.userId, + type, + geo: geo(doc), + ...doc + }; + // console.log(newDoc); + return Subscriptions.insert(newDoc); } catch (exception) { + console.error(exception); throw new Meteor.Error('500', exception); } }, @@ -25,8 +39,10 @@ Meteor.methods({ }); try { + const dup = doc; const subscriptionId = doc._id; - Subscriptions.update(subscriptionId, { $set: doc }); + dup.geo = geo(doc); + Subscriptions.update(subscriptionId, { $set: dup }); return subscriptionId; // Return _id so we can redirect to subscription after update. } catch (exception) { throw new Meteor.Error('500', exception); @@ -38,6 +54,7 @@ Meteor.methods({ try { return Subscriptions.remove(subscriptionId); } catch (exception) { + console.error(exception); throw new Meteor.Error('500', exception); } } diff --git a/imports/api/Subscriptions/server/publications.js b/imports/api/Subscriptions/server/publications.js index d3d9ed8..e711c0d 100644 --- a/imports/api/Subscriptions/server/publications.js +++ b/imports/api/Subscriptions/server/publications.js @@ -35,13 +35,13 @@ Meteor.publishTransformed('userSubsToFires', function transform() { } // console.log(`with noise: [${doc.lat}, ${doc.lon}]`); delete doc.chatId; - delete doc.geo; + // delete doc.geo; return doc; }); }); Meteor.publish('mysubscriptions', function subscriptions() { - return Subscriptions.find({ owner: this.userId }); + return Subscriptions.find({ owner: this.userId, type: 'web' }); }); // Note: subscriptions.view is also used when editing an existing subscription. @@ -49,5 +49,5 @@ Meteor.publish('subscriptions.view', function subscriptionsView(subscriptionId) check(subscriptionId, String); const id = new Mongo.ObjectID(subscriptionId); check(id, Meteor.Collection.ObjectID); - return Subscriptions.find({ _id: id, owner: this.userId }); + return Subscriptions.find({ _id: id, owner: this.userId, type: 'web' }); }); diff --git a/imports/ui/components/DistanceSlider/DistanceSlider.js b/imports/ui/components/DistanceSlider/DistanceSlider.js index 417ae1e..3647c2e 100644 --- a/imports/ui/components/DistanceSlider/DistanceSlider.js +++ b/imports/ui/components/DistanceSlider/DistanceSlider.js @@ -56,7 +56,7 @@ class DistanceSlider extends React.Component {

¿A que distancia a la redonda quieres recibir notificaciones?

diff --git a/imports/ui/components/Prompt/Prompt.js b/imports/ui/components/Prompt/Prompt.js index 1b39ebb..d5db620 100644 --- a/imports/ui/components/Prompt/Prompt.js +++ b/imports/ui/components/Prompt/Prompt.js @@ -7,56 +7,8 @@ import PropTypes from 'prop-types'; import { Modal, Button } from 'react-bootstrap'; import { confirmable } from 'react-confirm'; +// https://github.com/haradakunihiko/react-confirm/blob/master/example/react-bootstrap/src/components/Confirmation.js class Prompt extends Component { - // constructor(props) { - /* super(props); - * this.state = { - * open: true - * }; - } - - componentDidMount() { - * console.log(this.prompt); - * this.setState({ open: true }); - } - */ - - render2() { - const { - show, - proceed, - dismiss, - cancel, - confirmation, - okBtn, - cancelBtn - } = this.props; - return ( -
-
-
-
-
Modal title
- -
-
-

Modal body text goes here.

-
-
- - -
-
-
-
- ); - } render() { const { show, @@ -66,6 +18,7 @@ role="dialog" confirmation, okBtn, cancelBtn, + title, enableEscape = true } = this.props; return ( @@ -78,9 +31,11 @@ role="dialog" backdrop={enableEscape ? true : 'static'} keyboard={enableEscape} > - - - + {title && + + {title} + + }

{confirmation}

@@ -100,6 +55,7 @@ Prompt.propTypes = { cancel: PropTypes.func, // from confirmable. call to close the dialog with promise rejected. dismiss: PropTypes.func, // from confirmable. call to only close the dialog. confirmation: PropTypes.string, // arguments of your confirm function + title: PropTypes.string, okBtn: PropTypes.string.isRequired, cancelBtn: PropTypes.string.isRequired, enableEscape: PropTypes.bool diff --git a/imports/ui/components/SelectionMap/SelectionMap.js b/imports/ui/components/SelectionMap/SelectionMap.js index 940bd83..5be19d6 100644 --- a/imports/ui/components/SelectionMap/SelectionMap.js +++ b/imports/ui/components/SelectionMap/SelectionMap.js @@ -6,7 +6,8 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { Map, Marker, CircleMarker, Circle } from 'react-leaflet'; +import { Meteor } from 'meteor/meteor'; +import { Map, Marker, CircleMarker, Circle, Tooltip } from 'react-leaflet'; import Leaflet from 'leaflet'; import { translate } from 'react-i18next'; import { withTracker } from 'meteor/react-meteor-data'; @@ -20,6 +21,7 @@ import 'leaflet-sleep/Leaflet.Sleep.js'; import Control from 'react-leaflet-control'; import { Button, ButtonGroup } from 'react-bootstrap'; import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion'; +import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions'; import './SelectionMap.scss'; export const action = { @@ -114,7 +116,12 @@ class SelectionMap extends Component { if (this.props.currentSubs.length > 0 && this.state.subsFit) { // has autofit, do nothing } else if (this.selectionMap && this.distanceCircle) { - this.getMap().fitBounds(this.distanceCircle.leafletElement.getBounds(), [70, 70]); + if (!this.getMap().getBounds().contains(this.distanceCircle.leafletElement.getBounds())) { + // console.log('New area circle not visible'); + this.getMap().fitBounds(this.distanceCircle.leafletElement.getBounds()); // padding , [70, 70]); + } else { + // console.log('New area circle visible'); + } } } @@ -132,7 +139,7 @@ class SelectionMap extends Component { } isValidState() { - return this.state.center && this.state.center[0]; + return !this.props.loadingSubs && this.state.center && this.state.center[0]; } handleLeafletLoad(map) { @@ -175,7 +182,7 @@ class SelectionMap extends Component { > {this.props.action === action.edit && - this.props.currentSubs.map(subs => ( + this.props.currentSubs.map((subs, index) => ( { onRemove(subs._id); }} - /> + > + {index === 0 && + + {t('Pulsa aquí para borrar la zona')} + + } + )) } {this.props.action === action.add && @@ -257,14 +275,20 @@ SelectionMap.propTypes = { onSndBtn: PropTypes.func, onRemove: PropTypes.func, action: PropTypes.number.isRequired, - loadingSubs: PropTypes.bool, + loadingSubs: PropTypes.bool.isRequired, currentSubs: PropTypes.arrayOf(PropTypes.shape({ location: PropTypes.shape({ latitude: PropTypes.number, longitude: PropTypes.number }).isRequired, distance: PropTypes.number.isRequired })) }; -export default translate([], { wait: true })(withTracker(props => ({ - center: props.center[0] !== null ? props.center : geolocation.get(), - distance: props.distance -}))(SelectionMap)); +export default translate([], { wait: true })(withTracker((props) => { + const subscription = Meteor.subscribe('mysubscriptions'); + // console.log(props.loadingSubs); + return { + center: props.center[0] !== null ? props.center : geolocation.get(), + distance: props.distance, + loadingSubs: !subscription.ready(), + currentSubs: UserSubsToFiresCollection.find({ owner: Meteor.userId(), type: 'web' }).fetch() + }; +})(SelectionMap)); diff --git a/imports/ui/components/SubscriptionEditor/SubscriptionEditor.js b/imports/ui/components/SubscriptionEditor/SubscriptionEditor.js index 2a52314..f391402 100644 --- a/imports/ui/components/SubscriptionEditor/SubscriptionEditor.js +++ b/imports/ui/components/SubscriptionEditor/SubscriptionEditor.js @@ -30,16 +30,22 @@ class SubscriptionEditor extends React.Component { if (existingSubscription) doc._id = existingSubscription; - Meteor.call(methodToCall, doc, (error, subscriptionId) => { - if (error) { - Bert.alert(error.reason, 'danger'); - } else { - const confirmation = existingSubscription ? t('Zona actualizada') : t('Zona añadida'); - Bert.alert(confirmation, 'success'); - // history.push(`/subscriptions/${subscriptionId}`); - history.push('/subscriptions'); - } - }); + const authenticated = !!Meteor.userId(); + + if (authenticated) { + Meteor.call(methodToCall, doc, (error, subscriptionId) => { + if (error) { + Bert.alert(error.reason, 'danger'); + } else { + const confirmation = existingSubscription ? t('Zona actualizada') : t('Zona añadida'); + Bert.alert(confirmation, 'success'); + // history.push(`/subscriptions/${subscriptionId}`); + history.push('/subscriptions'); + } + }); + } else { + this.props.history.push('/signup', doc); + } } render() { diff --git a/imports/ui/pages/FireSubscription/FireSubscription.js b/imports/ui/pages/FireSubscription/FireSubscription.js index 050be09..00f5d96 100644 --- a/imports/ui/pages/FireSubscription/FireSubscription.js +++ b/imports/ui/pages/FireSubscription/FireSubscription.js @@ -2,7 +2,6 @@ /* eslint-disable react/jsx-indent-props */ import React from 'react'; import PropTypes from 'prop-types'; -import { Meteor } from 'meteor/meteor'; import { withTracker } from 'meteor/react-meteor-data'; import { Row, Col } from 'react-bootstrap'; import { translate } from 'react-i18next'; @@ -10,7 +9,6 @@ import DistanceSlider from '/imports/ui/components/DistanceSlider/DistanceSlider import SelectionMap, { action } from '/imports/ui/components/SelectionMap/SelectionMap'; import Gkeys from '/imports/startup/client/Gkeys'; import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition.js'; -import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions'; import SubsAutocomplete from './SubsAutocomplete'; class FireSubscription extends React.Component { @@ -96,8 +94,6 @@ class FireSubscription extends React.Component { onFstBtn={state => this.onSubs(state)} onSelection={state => this.onSelection(state)} action={action.add} - loadingSubs={this.props.loading} - currentSubs={this.props.subscriptions} /> @@ -106,9 +102,6 @@ class FireSubscription extends React.Component { } FireSubscription.propTypes = { - t: PropTypes.func.isRequired, - loading: PropTypes.bool.isRequired, - subscriptions: PropTypes.arrayOf(PropTypes.object).isRequired, center: PropTypes.arrayOf(PropTypes.number), zoom: PropTypes.number, distance: PropTypes.number, @@ -117,11 +110,4 @@ FireSubscription.propTypes = { onSubs: PropTypes.func.isRequired }; -export default translate([], { wait: true })(withTracker(() => { - const subscription = Meteor.subscribe('mysubscriptions'); - // console.log(UserSubsToFiresCollection.find().fetch()); - return { - loading: !subscription.ready(), - subscriptions: UserSubsToFiresCollection.find({ owner: Meteor.userId() }).fetch() - }; -})(FireSubscription)); +export default translate([], { wait: true })(FireSubscription); diff --git a/imports/ui/pages/Index/Index.js b/imports/ui/pages/Index/Index.js index b94886e..6a775d6 100644 --- a/imports/ui/pages/Index/Index.js +++ b/imports/ui/pages/Index/Index.js @@ -219,7 +219,10 @@ class Index extends Component {

Suscríbete a alertas de fuegos

- +
diff --git a/imports/ui/pages/Login/Login.js b/imports/ui/pages/Login/Login.js index 4d446c7..8c004fb 100644 --- a/imports/ui/pages/Login/Login.js +++ b/imports/ui/pages/Login/Login.js @@ -16,7 +16,7 @@ class Login extends React.Component { super(props); this.t = props.t; this.handleSubmit = this.handleSubmit.bind(this); - console.log(this.props.location.state); + // console.log(this.props.location.state); } componentDidMount() { diff --git a/imports/ui/pages/Signup/Signup.js b/imports/ui/pages/Signup/Signup.js index 33a8e54..bb34baa 100644 --- a/imports/ui/pages/Signup/Signup.js +++ b/imports/ui/pages/Signup/Signup.js @@ -17,8 +17,10 @@ import { T9n } from 'meteor-accounts-t9n'; class Signup extends React.Component { constructor(props) { super(props); - this.t = this.props.t; + this.t = props.t; this.handleSubmit = this.handleSubmit.bind(this); + // console.log(props.location.state); + this.state = props.location.state; } componentDidMount() { @@ -27,37 +29,37 @@ class Signup extends React.Component { validate(component.form, { rules: { firstName: { - required: true, + required: true }, lastName: { - required: true, + required: true }, emailAddress: { required: true, - email: true, + email: true }, password: { required: true, - minlength: 6, - }, + minlength: 6 + } }, messages: { firstName: { - required: this.t("¿Cuál es tu nombre?"), + required: this.t('¿Cuál es tu nombre?') }, lastName: { - required: this.t("¿Cuál es tu apellido?"), + required: this.t('¿Cuál es tu apellido?') }, emailAddress: { - required: this.t("Necesitamos una contraseña aquí."), - email: this.t("¿Es correcto este correo?"), + required: this.t('Necesitamos una contraseña aquí.'), + email: this.t('¿Es correcto este correo?') }, password: { - required: this.t("Necesitamos una contraseña aquí."), - minlength: this.t("Usa al menos seis caracteres."), - }, + required: this.t('Necesitamos una contraseña aquí.'), + minlength: this.t('Usa al menos seis caracteres.') + } }, - submitHandler() { component.handleSubmit(); }, + submitHandler() { component.handleSubmit(); } }); } @@ -70,16 +72,16 @@ class Signup extends React.Component { profile: { name: { first: this.firstName.value, - last: this.lastName.value, - }, - }, + last: this.lastName.value + } + } }, (error) => { if (error) { Bert.alert(T9n.get(`error.accounts.${error.reason}`), 'danger'); } else { Meteor.call('users.sendVerificationEmail'); Bert.alert('Welcome!', 'success'); - history.push('/documents'); + history.push('/subscriptions'); } }); } @@ -88,7 +90,7 @@ class Signup extends React.Component { return (
-

{this.t("Registrarse")}

+

{this.t('Registrarse')}

{/* + -

{this.t("¿Ya tienes un cuenta?")} {this.t("Iniciar sesión")}.

+

{this.t('¿Ya tienes un cuenta?')} {this.t('Iniciar sesión')}.

diff --git a/imports/ui/pages/Subscriptions/Subscriptions.js b/imports/ui/pages/Subscriptions/Subscriptions.js index 704d611..bfed714 100644 --- a/imports/ui/pages/Subscriptions/Subscriptions.js +++ b/imports/ui/pages/Subscriptions/Subscriptions.js @@ -29,7 +29,11 @@ class Subscriptions extends Component { onFstBtn() { // console.log(this.state); - this.props.history.push(`${this.props.match.url}/new`, { center: this.state.center, zoom: this.state.zoom }); + if (this.state.action === action.view) { + this.props.history.push(`${this.props.match.url}/new`, { center: this.state.center, zoom: this.state.zoom }); + } else if (this.state.action === action.edit) { + this.setState({ action: action.view }); + } } onSndBtn() { @@ -42,15 +46,19 @@ class Subscriptions extends Component { } handleRemove(subscriptionId) { - const { t } = this.props; + const { t, subscriptions } = this.props; confirm(t('Dejarás de recibir notificaciones de fuegos en esa área ¿Estás seguro/a? '), { okBtn: t('Sí'), cancelBtn: t('No') }).then( () => { // `proceed` callback + const num = subscriptions.length; Meteor.call('subscriptions.remove', subscriptionId, (error) => { if (error) { Bert.alert(error.reason, 'danger'); } else { Bert.alert('Subscription deleted!', 'success'); + if (num === 1) { // it was 1, now deleted + this.setState({ action: action.view }); + } } }); }, @@ -66,23 +74,25 @@ class Subscriptions extends Component { t, subscriptions } = this.props; + const firstBtnTitle = ['Añadir zona', '', 'Terminar']; // view, add, edit return (!loading ? (
-

Suscripciones a fuegos en zonas de mi interés

+

Suscripciones a alertas de fuegos en zonas de mi interés


- { subscriptions.length === 0 && - No estás suscrito a fuegos en ninguna zona + { subscriptions.length === 0 ? + No estás suscrito a fuegos en ninguna zona : + En verde, áreas de las que recibirás alertas de fuegos }
this.onFstBtn(state)} - sndBtn={this.props.subscriptions.length >= 1 ? t('Editar') : null} + sndBtn={this.state.action === action.view && this.props.subscriptions.length >= 1 ? t('Editar') : null} onSndBtn={() => this.onSndBtn()} onViewportChanged={viewport => this.onViewportChanged(viewport)} loadingSubs={this.props.loading} diff --git a/imports/ui/stylesheets/bootstrap-overrides.scss b/imports/ui/stylesheets/bootstrap-overrides.scss index 3506bc4..7016a34 100644 --- a/imports/ui/stylesheets/bootstrap-overrides.scss +++ b/imports/ui/stylesheets/bootstrap-overrides.scss @@ -19,3 +19,7 @@ body { .navbar-nav .nav-link { white-space: nowrap; } + +.modal-dialog { + margin-top: 70px; +}