Site settings and fires fromNow Reactive
This commit is contained in:
parent
e4b0135953
commit
379e72a9e7
15 changed files with 482 additions and 64 deletions
122
imports/ui/components/Chronos/Chronos.js
Normal file
122
imports/ui/components/Chronos/Chronos.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Adapted from: https://github.com/remcoder/chronos (MIT License)
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import moment from 'moment';
|
||||
|
||||
const _timers = {};
|
||||
|
||||
function Timer(interval) {
|
||||
this.interval = interval || 1000;
|
||||
this.time = new ReactiveVar(0);
|
||||
}
|
||||
|
||||
Timer.prototype.start = function () {
|
||||
if (this._timer) throw new Error('Trying to start Chronos.Timer but it is already running.');
|
||||
this.time.set(new Date());
|
||||
|
||||
|
||||
this._timer = setInterval(Meteor.bindEnvironment(() => {
|
||||
// console.log('tick', this._timer);
|
||||
this.time.set(new Date());
|
||||
}), this.interval);
|
||||
};
|
||||
|
||||
Timer.prototype.stop = function () {
|
||||
// console.log('stopping timer');
|
||||
clearInterval(this._timer);
|
||||
this._timer = null;
|
||||
};
|
||||
|
||||
function _update(interval) {
|
||||
// get current reactive context
|
||||
const comp = Tracker.currentComputation;
|
||||
if (!comp) { return; } // no nothing when used outside a reactive context
|
||||
|
||||
// only create one timer per reactive context to prevent stacking of timers
|
||||
const cid = comp && comp._id;
|
||||
if (!_timers[cid]) {
|
||||
const timer = new Timer(interval);
|
||||
_timers[cid] = timer;
|
||||
|
||||
// add destroy method that stops the timer and removes itself from the list
|
||||
timer.destroy = function () {
|
||||
timer.stop();
|
||||
delete _timers[cid];
|
||||
};
|
||||
|
||||
timer.start();
|
||||
}
|
||||
|
||||
// make sure to stop and delete the attached timer when the computation is stopped
|
||||
comp.onInvalidate(() => {
|
||||
// console.log('onInvalidated',comp);
|
||||
if (comp.stopped && _timers[cid]) {
|
||||
// console.log('computation stopped');
|
||||
_timers[cid].destroy();
|
||||
}
|
||||
});
|
||||
|
||||
_timers[cid].time.dep.depend(comp); // make dependent on time
|
||||
|
||||
// console.log(_timers);
|
||||
return _timers[cid];
|
||||
}
|
||||
|
||||
// reactive version of moment()
|
||||
// please install moment separately
|
||||
// example usage: Chronos.moment(someTimestamp).format('ss');
|
||||
function _moment(/* arguments */) {
|
||||
if (!moment) throw new Error('moment not found. Please install it first');
|
||||
|
||||
_update();
|
||||
return moment(...arguments);
|
||||
}
|
||||
|
||||
// reactive version of new Date() get the current date/time
|
||||
function _date(interval) {
|
||||
_update(interval);
|
||||
return new Date();
|
||||
}
|
||||
|
||||
// reactive version of Date.now(). get the current # of milliseconds since the start of the epoch
|
||||
function _now(interval) {
|
||||
_update(interval);
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
// export global
|
||||
const Chronos = {
|
||||
|
||||
// a simple reactive timer
|
||||
// usage: var timer = new Timer();
|
||||
// get current time: timer.time.get();
|
||||
Timer,
|
||||
|
||||
// handy util func for making reactive contexts live updating in time
|
||||
// usage: simply call Chronos.update() in your helper to make it execute
|
||||
// every interval
|
||||
update: _update,
|
||||
|
||||
// reactive version of new Date() get the current date/time
|
||||
date: _date,
|
||||
|
||||
// reactive version of Date.now(). get the current # of milliseconds since the start of the epoch
|
||||
now: _now,
|
||||
|
||||
// reactive version of moment()
|
||||
// please install moment separately
|
||||
// example usage: Chronos.moment(someTimestamp).format('ss');
|
||||
moment: _moment,
|
||||
|
||||
// for debugging and testing
|
||||
_timers,
|
||||
|
||||
// deprecated. but kept for backwards compatibility
|
||||
liveUpdate: _update,
|
||||
currentTime: _date,
|
||||
liveMoment: _moment
|
||||
};
|
||||
|
||||
export default Chronos;
|
||||
52
imports/ui/components/FromNow/FromNow.js
Normal file
52
imports/ui/components/FromNow/FromNow.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Chronos from '/imports/ui/components/Chronos/Chronos';
|
||||
|
||||
class FromNow extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
when: props.when
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.when !== nextProps.when) {
|
||||
// console.log(`Next when ${nextProps.when}`);
|
||||
this.setState({
|
||||
when: nextProps.when
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(nextState.when === this.state.when);
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(`Render from now ${this.state.when}`);
|
||||
return (
|
||||
<span>{this.state.when}</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FromNow.propTypes = {
|
||||
when: PropTypes.string
|
||||
};
|
||||
|
||||
FromNow.defaultProps = {
|
||||
};
|
||||
|
||||
const FromNowContainer = withTracker((props) => {
|
||||
const whenDate = props.when;
|
||||
return {
|
||||
when: whenDate ? Chronos.moment(whenDate).fromNow() : null
|
||||
};
|
||||
})(FromNow);
|
||||
|
||||
export default FromNowContainer;
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
/* global Chronos */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable react/jsx-indent */
|
||||
|
|
@ -6,13 +5,14 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { translate } from 'react-i18next';
|
||||
import { translate, Trans } from 'react-i18next';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Map, Circle } from 'react-leaflet';
|
||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
import NotFound from '/imports/ui/pages/NotFound/NotFound';
|
||||
import FiresCollection from '/imports/api/Fires/Fires';
|
||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||
import '/imports/startup/client/comments';
|
||||
import './Fires.scss';
|
||||
|
|
@ -40,10 +40,9 @@ class Fire extends React.Component {
|
|||
|
||||
render() {
|
||||
const { loading, fire, t } = this.props;
|
||||
console.log(`Render ${this.state.when}`);
|
||||
if (fire && fire.when) {
|
||||
this.dateLongFormat = dateLongFormat(fire.when);
|
||||
this.dateFromNow = this.state.when;
|
||||
// this.dateFromNow = () => (<FromNow {...this.props} />);
|
||||
}
|
||||
return (fire ?
|
||||
(<div className="ViewFire">
|
||||
|
|
@ -55,50 +54,50 @@ class Fire extends React.Component {
|
|||
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat })}
|
||||
</h4>
|
||||
|
||||
<Map
|
||||
ref={(map) => {
|
||||
this.fireMap = map;
|
||||
}}
|
||||
animate
|
||||
sleep={false}
|
||||
center={[fire.lat, fire.lon]}
|
||||
className="fire-leaflet-container"
|
||||
zoom={12}
|
||||
>
|
||||
<Fragment>
|
||||
<Circle
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
fillColor="red"
|
||||
fillOpacity={0.1}
|
||||
radius={fire.scan ? fire.scan * 1000 : 300}
|
||||
/>
|
||||
</Fragment>
|
||||
<DefMapLayers />
|
||||
</Map>
|
||||
<p>{t('Coordenadas:')} {fire.lat}, {fire.lon}</p>
|
||||
{(fire.type === 'modis' || fire.type === 'viirs') &&
|
||||
<p>{t('Fuego detectado por satélites de la NASA {{when}}', { when: this.dateFromNow })}</p>
|
||||
<Map
|
||||
ref={(map) => {
|
||||
this.fireMap = map;
|
||||
}}
|
||||
animate
|
||||
sleep={false}
|
||||
center={[fire.lat, fire.lon]}
|
||||
className="fire-leaflet-container"
|
||||
zoom={12}
|
||||
>
|
||||
<Fragment>
|
||||
<Circle
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
fillColor="red"
|
||||
fillOpacity={0.1}
|
||||
radius={fire.scan ? fire.scan * 1000 : 300}
|
||||
/>
|
||||
</Fragment>
|
||||
<DefMapLayers />
|
||||
</Map>
|
||||
<p>{t('Coordenadas:')} {fire.lat}, {fire.lon}</p>
|
||||
{(fire.type === 'modis' || fire.type === 'viirs') &&
|
||||
<p><Trans>Fuego detectado por satélites de la NASA <FromNow {...this.props} /></Trans></p>
|
||||
}
|
||||
{(fire.type === 'vecinal') &&
|
||||
<p><Trans>Fuego notificado por uno de nuestros usuarios/as <FromNow {...this.props} /></Trans></p>
|
||||
}
|
||||
{(fire.type === 'vecinal') &&
|
||||
<p>{t('Fuego notificado por uno de nuestros usuarios/as {{when}}', { when: this.dateFromNow })}</p>
|
||||
}
|
||||
{/* TODO: marcar tipo de fuego, industria, etc */}
|
||||
<h4>{t('Comentarios')}</h4>
|
||||
<div className="comments-info">
|
||||
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
||||
{' '}
|
||||
{t('Por ejemplo:')}
|
||||
<ul>
|
||||
<li>{t('si conoces esta zona y cómo acceder al fuego (esto puede de ser de ayuda para apagarlo si sigue activo o para investigarlo en un futuro)')}</li>
|
||||
<li>{t('si conoces el motivo por el que comenzó el fuego')}</li>
|
||||
<li>{t('si quieres denunciar algún tipo de ilegalidad, incluso anónimamente')}</li>
|
||||
<li>{t('o cualquier otra información')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="comments-section">
|
||||
<Blaze template="commentsBox" id={`fire-${fire._id}`} />
|
||||
</div>
|
||||
{/* TODO: marcar tipo de fuego, industria, etc */}
|
||||
<h4>{t('Comentarios')}</h4>
|
||||
<div className="comments-info">
|
||||
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
||||
{' '}
|
||||
{t('Por ejemplo:')}
|
||||
<ul>
|
||||
<li>{t('si conoces esta zona y cómo acceder al fuego (esto puede de ser de ayuda para apagarlo si sigue activo o para investigarlo en un futuro)')}</li>
|
||||
<li>{t('si conoces el motivo por el que comenzó el fuego')}</li>
|
||||
<li>{t('si quieres denunciar algún tipo de ilegalidad, incluso anónimamente')}</li>
|
||||
<li>{t('o cualquier otra información')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="comments-section">
|
||||
<Blaze template="commentsBox" id={`fire-${fire._id}`} />
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -109,7 +108,7 @@ class Fire extends React.Component {
|
|||
Fire.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
when: PropTypes.string,
|
||||
when: PropTypes.instanceOf(Date),
|
||||
fire: PropTypes.object // .isRequired
|
||||
};
|
||||
|
||||
|
|
@ -118,16 +117,14 @@ Fire.defaultProps = {
|
|||
|
||||
// export default translate([], { wait: true })(withTracker((props) => {
|
||||
|
||||
const FireContainer = withTracker(({ match }) => {
|
||||
const FireContainer = withTracker(({ match, i18n }) => {
|
||||
const fireEncrypt = match.params.id;
|
||||
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
||||
|
||||
// });
|
||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||
return {
|
||||
loading: !subscription.ready(),
|
||||
fire: FiresCollection.findOne(),
|
||||
when: subscription.ready() ? Chronos.moment(FiresCollection.findOne().when).fromNow() : null
|
||||
when: subscription.ready() ? FiresCollection.findOne().when : null
|
||||
};
|
||||
})(Fire);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/Center
|
|||
import FireList from '/imports/ui/components/Maps/FireList';
|
||||
import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion';
|
||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||
import Loading from '/imports/ui/components/Loading/Loading';
|
||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
||||
import { isNotHomeAndMobile, isChrome } from '/imports/ui/components/Utils/isMobile';
|
||||
|
||||
|
|
@ -140,8 +142,8 @@ class FiresMap extends React.Component {
|
|||
<Col xs={12} sm={6} md={6} lg={6} >
|
||||
<p className="firesmap-legend">
|
||||
{ this.props.activefires.length === 0 ?
|
||||
<Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo.</Trans> :
|
||||
<Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length }}</strong> fuegos activos en el mapa. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo por la NASA.</Trans>
|
||||
<Fragment><Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo.</Trans> <Trans>Datos actualizados <FromNow when={this.props.lastCheck} /></Trans></Fragment> :
|
||||
<Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length }}</strong> fuegos activos en el mapa. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo por la NASA.</Trans> <Trans>Datos actualizados <FromNow when={this.props.lastCheck} /></Trans></Fragment>
|
||||
}
|
||||
</p>
|
||||
{isNotHomeAndMobile &&
|
||||
|
|
@ -228,6 +230,7 @@ FiresMap.propTypes = {
|
|||
userSubs: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
lastCheck: PropTypes.instanceOf(Date),
|
||||
activefirestotal: PropTypes.number.isRequired,
|
||||
center: PropTypes.arrayOf(PropTypes.number),
|
||||
zoom: PropTypes.number,
|
||||
|
|
@ -258,12 +261,14 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
Meteor.subscribe('activefirestotal');
|
||||
// Right now to all neighborhood alerts
|
||||
Meteor.subscribe('fireAlerts');
|
||||
Meteor.subscribe('settings');
|
||||
const userSubs = Meteor.subscribe('userSubsToFires');
|
||||
// const subscription = Meteor.subscribe('activefiresmyloc', zoom.get());
|
||||
// Warning with the performance of this log:
|
||||
// console.log(`Active fires ${ActiveFiresCollection.find().count()} of ${Counter.get('countActiveFires')}`);
|
||||
// console.log(`Active neighborhood fires ${FireAlertsCollection.find().fetch().length} and users subscribed ${UserSubsToFiresCollection.find().fetch().length}`);
|
||||
// console.log(UserSubsToFiresCollection.find().fetch());
|
||||
const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' });
|
||||
return {
|
||||
loading: !subscription.ready(),
|
||||
userSubs: UserSubsToFiresCollection.find().fetch(),
|
||||
|
|
@ -273,6 +278,7 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
// activefires: ActiveFiresCollection.find({}).fetch(),
|
||||
activefirestotal: Counter.get('countActiveFires'),
|
||||
firealerts: FireAlertsCollection.find().fetch(),
|
||||
lastCheck: lastCheck ? lastCheck.value : null,
|
||||
center: geolocation.get(),
|
||||
zoom: zoom.get()
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue