Site settings and fires fromNow Reactive
This commit is contained in:
parent
e4b0135953
commit
379e72a9e7
15 changed files with 482 additions and 64 deletions
|
|
@ -60,4 +60,3 @@ mongo-livedata
|
||||||
reywood:publish-composite
|
reywood:publish-composite
|
||||||
barbatus:stars-rating
|
barbatus:stars-rating
|
||||||
arkham:comments-ui
|
arkham:comments-ui
|
||||||
remcoder:chronos
|
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ react-meteor-data@0.2.15
|
||||||
reactive-dict@1.2.0
|
reactive-dict@1.2.0
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
reload@1.1.11
|
reload@1.1.11
|
||||||
remcoder:chronos@0.5.0
|
|
||||||
retry@1.0.9
|
retry@1.0.9
|
||||||
reywood:publish-composite@1.5.2
|
reywood:publish-composite@1.5.2
|
||||||
routepolicy@1.0.12
|
routepolicy@1.0.12
|
||||||
|
|
@ -138,4 +137,3 @@ url@1.1.0
|
||||||
vjrj:piwik@0.3.1
|
vjrj:piwik@0.3.1
|
||||||
webapp@1.4.0
|
webapp@1.4.0
|
||||||
webapp-hashing@1.0.9
|
webapp-hashing@1.0.9
|
||||||
zodiase:function-bind@0.0.1
|
|
||||||
|
|
|
||||||
49
imports/api/SiteSettings/SiteSettings.js
Normal file
49
imports/api/SiteSettings/SiteSettings.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* eslint-disable consistent-return */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||||
|
import SiteSettingsTypes from './SiteSettingsTypes';
|
||||||
|
|
||||||
|
const SiteSettings = new Mongo.Collection('siteSettings', { idGeneration: 'MONGO' });
|
||||||
|
|
||||||
|
SiteSettings.allow({
|
||||||
|
insert: () => false,
|
||||||
|
update: () => false,
|
||||||
|
remove: () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
SiteSettings.deny({
|
||||||
|
insert: () => true,
|
||||||
|
update: () => true,
|
||||||
|
remove: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
SiteSettings.get = (name) => {
|
||||||
|
const setting = SiteSettings.findOne({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
return typeof setting === 'object' ? setting.value : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
SiteSettings.observe = (name, callback) => {
|
||||||
|
SiteSettings.find({ name }).observe({
|
||||||
|
added: (document) => {
|
||||||
|
callback(document.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SiteSettings.getSchema = type => new SimpleSchema({
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
description: String,
|
||||||
|
value: SiteSettingsTypes[type].value,
|
||||||
|
createdAt: defaultCreatedAt,
|
||||||
|
updatedAt: defaultUpdateAt
|
||||||
|
});
|
||||||
|
|
||||||
|
// SiteSettings.attachSchema(SiteSettings.schema);
|
||||||
|
|
||||||
|
export default SiteSettings;
|
||||||
66
imports/api/SiteSettings/SiteSettingsTypes.js
Normal file
66
imports/api/SiteSettings/SiteSettingsTypes.js
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Adapted from: https://github.com/yogiben/meteor-admin-settings
|
||||||
|
|
||||||
|
const SiteSettingsTypes = {
|
||||||
|
string: {
|
||||||
|
value: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textarea: {
|
||||||
|
value: {
|
||||||
|
type: String
|
||||||
|
/* autoform: {
|
||||||
|
* afFieldInput: {
|
||||||
|
* rows: 5
|
||||||
|
* }
|
||||||
|
* } */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
value: {
|
||||||
|
type: Number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
boolean: {
|
||||||
|
value: {
|
||||||
|
type: Boolean
|
||||||
|
/* autoform: {
|
||||||
|
* afFieldInput: {
|
||||||
|
* type: 'boolean-select'
|
||||||
|
* }
|
||||||
|
* } */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
value: {
|
||||||
|
type: Date
|
||||||
|
/* autoform: {
|
||||||
|
* afFieldInput: {
|
||||||
|
* type: 'date'
|
||||||
|
* }
|
||||||
|
* } */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
value: {
|
||||||
|
type: String
|
||||||
|
/* autoform: {
|
||||||
|
* afFieldInput: {
|
||||||
|
* type: 'color'
|
||||||
|
* }
|
||||||
|
* } */
|
||||||
|
}
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
value: {
|
||||||
|
type: String
|
||||||
|
/* autoform: {
|
||||||
|
* afFieldInput: {
|
||||||
|
* type: 'password'
|
||||||
|
* }
|
||||||
|
* } */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteSettingsTypes;
|
||||||
60
imports/api/SiteSettings/methods.js
Normal file
60
imports/api/SiteSettings/methods.js
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import SiteSettings from './SiteSettings';
|
||||||
|
import SiteSettingsTypes from './SiteSettingsTypes';
|
||||||
|
import rateLimit from '../../modules/rate-limit';
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
'siteSettings.insert': function siteSettingsInsert(setting) {
|
||||||
|
check(setting, {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
description: String,
|
||||||
|
value: SiteSettingsTypes[self.type].value
|
||||||
|
});
|
||||||
|
|
||||||
|
SiteSettings.getSchema.validate(setting);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SiteSettings.insert({ owner: this.userId, ...setting });
|
||||||
|
} catch (exception) {
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'siteSettings.update': function siteSettingsUpdate(setting) {
|
||||||
|
check(setting, {
|
||||||
|
_id: String,
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
description: String,
|
||||||
|
value: SiteSettingsTypes[self.type].value
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const siteSettingId = setting._id;
|
||||||
|
SiteSettings.update(siteSettingId, { $set: setting });
|
||||||
|
return siteSettingId; // Return _id so we can redirect to siteSetting after update.
|
||||||
|
} catch (exception) {
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'siteSettings.remove': function siteSettingsRemove(siteSettingId) {
|
||||||
|
check(siteSettingId, String);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return SiteSettings.remove(siteSettingId);
|
||||||
|
} catch (exception) {
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimit({
|
||||||
|
methods: [
|
||||||
|
'siteSettings.insert',
|
||||||
|
'siteSettings.update',
|
||||||
|
'siteSettings.remove'
|
||||||
|
],
|
||||||
|
limit: 5,
|
||||||
|
timeRange: 1000
|
||||||
|
});
|
||||||
6
imports/api/SiteSettings/server/publications.js
Normal file
6
imports/api/SiteSettings/server/publications.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable prefer-arrow-callback */
|
||||||
|
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import SiteSettings from '../SiteSettings';
|
||||||
|
|
||||||
|
Meteor.publish('settings', () => SiteSettings.find());
|
||||||
|
|
@ -21,3 +21,6 @@ import '../../api/Notifications/server/publications';
|
||||||
|
|
||||||
import '../../api/Fires/methods';
|
import '../../api/Fires/methods';
|
||||||
import '../../api/Fires/server/publications';
|
import '../../api/Fires/server/publications';
|
||||||
|
|
||||||
|
import '../../api/SiteSettings/methods';
|
||||||
|
import '../../api/SiteSettings/server/publications';
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Accounts } from 'meteor/accounts-base';
|
||||||
import randomHex from 'crypto-random-hex';
|
import randomHex from 'crypto-random-hex';
|
||||||
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
||||||
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
||||||
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
// https://github.com/percolatestudio/meteor-migrations
|
// https://github.com/percolatestudio/meteor-migrations
|
||||||
|
|
@ -83,8 +84,16 @@ Meteor.startup(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Migrations.add({
|
||||||
|
version: 5,
|
||||||
|
up: function siteSettingsIndex() {
|
||||||
|
// other way:
|
||||||
|
SiteSettings._ensureIndex({ name: 1 }, { unique: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set createdAt in users & subs
|
// Set createdAt in users & subs
|
||||||
Migrations.migrateTo('latest');
|
Migrations.migrateTo('latest');
|
||||||
|
|
||||||
// Migrations.migrateTo('2,rerun');
|
// Migrations.migrateTo('5,rerun');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
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 import/no-absolute-path */
|
||||||
/* eslint-disable react/jsx-indent-props */
|
/* eslint-disable react/jsx-indent-props */
|
||||||
/* eslint-disable react/jsx-indent */
|
/* eslint-disable react/jsx-indent */
|
||||||
|
|
@ -6,13 +5,14 @@
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTracker } from 'meteor/react-meteor-data';
|
import { withTracker } from 'meteor/react-meteor-data';
|
||||||
import { translate } from 'react-i18next';
|
import { translate, Trans } from 'react-i18next';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Map, Circle } from 'react-leaflet';
|
import { Map, Circle } from 'react-leaflet';
|
||||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||||
import NotFound from '/imports/ui/pages/NotFound/NotFound';
|
import NotFound from '/imports/ui/pages/NotFound/NotFound';
|
||||||
import FiresCollection from '/imports/api/Fires/Fires';
|
import FiresCollection from '/imports/api/Fires/Fires';
|
||||||
|
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||||
import '/imports/startup/client/comments';
|
import '/imports/startup/client/comments';
|
||||||
import './Fires.scss';
|
import './Fires.scss';
|
||||||
|
|
@ -40,10 +40,9 @@ class Fire extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, fire, t } = this.props;
|
const { loading, fire, t } = this.props;
|
||||||
console.log(`Render ${this.state.when}`);
|
|
||||||
if (fire && fire.when) {
|
if (fire && fire.when) {
|
||||||
this.dateLongFormat = dateLongFormat(fire.when);
|
this.dateLongFormat = dateLongFormat(fire.when);
|
||||||
this.dateFromNow = this.state.when;
|
// this.dateFromNow = () => (<FromNow {...this.props} />);
|
||||||
}
|
}
|
||||||
return (fire ?
|
return (fire ?
|
||||||
(<div className="ViewFire">
|
(<div className="ViewFire">
|
||||||
|
|
@ -55,50 +54,50 @@ class Fire extends React.Component {
|
||||||
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat })}
|
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat })}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<Map
|
<Map
|
||||||
ref={(map) => {
|
ref={(map) => {
|
||||||
this.fireMap = map;
|
this.fireMap = map;
|
||||||
}}
|
}}
|
||||||
animate
|
animate
|
||||||
sleep={false}
|
sleep={false}
|
||||||
center={[fire.lat, fire.lon]}
|
center={[fire.lat, fire.lon]}
|
||||||
className="fire-leaflet-container"
|
className="fire-leaflet-container"
|
||||||
zoom={12}
|
zoom={12}
|
||||||
>
|
>
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Circle
|
<Circle
|
||||||
center={[fire.lat, fire.lon]}
|
center={[fire.lat, fire.lon]}
|
||||||
color="red"
|
color="red"
|
||||||
fillColor="red"
|
fillColor="red"
|
||||||
fillOpacity={0.1}
|
fillOpacity={0.1}
|
||||||
radius={fire.scan ? fire.scan * 1000 : 300}
|
radius={fire.scan ? fire.scan * 1000 : 300}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
<DefMapLayers />
|
<DefMapLayers />
|
||||||
</Map>
|
</Map>
|
||||||
<p>{t('Coordenadas:')} {fire.lat}, {fire.lon}</p>
|
<p>{t('Coordenadas:')} {fire.lat}, {fire.lon}</p>
|
||||||
{(fire.type === 'modis' || fire.type === 'viirs') &&
|
{(fire.type === 'modis' || fire.type === 'viirs') &&
|
||||||
<p>{t('Fuego detectado por satélites de la NASA {{when}}', { when: this.dateFromNow })}</p>
|
<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') &&
|
{/* TODO: marcar tipo de fuego, industria, etc */}
|
||||||
<p>{t('Fuego notificado por uno de nuestros usuarios/as {{when}}', { when: this.dateFromNow })}</p>
|
<h4>{t('Comentarios')}</h4>
|
||||||
}
|
<div className="comments-info">
|
||||||
{/* TODO: marcar tipo de fuego, industria, etc */}
|
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
||||||
<h4>{t('Comentarios')}</h4>
|
{' '}
|
||||||
<div className="comments-info">
|
{t('Por ejemplo:')}
|
||||||
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
<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>
|
||||||
{t('Por ejemplo:')}
|
<li>{t('si conoces el motivo por el que comenzó el fuego')}</li>
|
||||||
<ul>
|
<li>{t('si quieres denunciar algún tipo de ilegalidad, incluso anónimamente')}</li>
|
||||||
<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('o cualquier otra información')}</li>
|
||||||
<li>{t('si conoces el motivo por el que comenzó el fuego')}</li>
|
</ul>
|
||||||
<li>{t('si quieres denunciar algún tipo de ilegalidad, incluso anónimamente')}</li>
|
</div>
|
||||||
<li>{t('o cualquier otra información')}</li>
|
<div className="comments-section">
|
||||||
</ul>
|
<Blaze template="commentsBox" id={`fire-${fire._id}`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="comments-section">
|
|
||||||
<Blaze template="commentsBox" id={`fire-${fire._id}`} />
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,7 +108,7 @@ class Fire extends React.Component {
|
||||||
Fire.propTypes = {
|
Fire.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
when: PropTypes.string,
|
when: PropTypes.instanceOf(Date),
|
||||||
fire: PropTypes.object // .isRequired
|
fire: PropTypes.object // .isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -118,16 +117,14 @@ Fire.defaultProps = {
|
||||||
|
|
||||||
// export default translate([], { wait: true })(withTracker((props) => {
|
// export default translate([], { wait: true })(withTracker((props) => {
|
||||||
|
|
||||||
const FireContainer = withTracker(({ match }) => {
|
const FireContainer = withTracker(({ match, i18n }) => {
|
||||||
const fireEncrypt = match.params.id;
|
const fireEncrypt = match.params.id;
|
||||||
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
||||||
|
|
||||||
// });
|
|
||||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||||
return {
|
return {
|
||||||
loading: !subscription.ready(),
|
loading: !subscription.ready(),
|
||||||
fire: FiresCollection.findOne(),
|
fire: FiresCollection.findOne(),
|
||||||
when: subscription.ready() ? Chronos.moment(FiresCollection.findOne().when).fromNow() : null
|
when: subscription.ready() ? FiresCollection.findOne().when : null
|
||||||
};
|
};
|
||||||
})(Fire);
|
})(Fire);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,11 @@ import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/Center
|
||||||
import FireList from '/imports/ui/components/Maps/FireList';
|
import FireList from '/imports/ui/components/Maps/FireList';
|
||||||
import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion';
|
import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion';
|
||||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
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 Loading from '/imports/ui/components/Loading/Loading';
|
||||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||||
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
||||||
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
||||||
import { isNotHomeAndMobile, isChrome } from '/imports/ui/components/Utils/isMobile';
|
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} >
|
<Col xs={12} sm={6} md={6} lg={6} >
|
||||||
<p className="firesmap-legend">
|
<p className="firesmap-legend">
|
||||||
{ this.props.activefires.length === 0 ?
|
{ 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> :
|
<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> :
|
||||||
<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="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>
|
</p>
|
||||||
{isNotHomeAndMobile &&
|
{isNotHomeAndMobile &&
|
||||||
|
|
@ -228,6 +230,7 @@ FiresMap.propTypes = {
|
||||||
userSubs: PropTypes.arrayOf(PropTypes.object).isRequired,
|
userSubs: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
|
activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
lastCheck: PropTypes.instanceOf(Date),
|
||||||
activefirestotal: PropTypes.number.isRequired,
|
activefirestotal: PropTypes.number.isRequired,
|
||||||
center: PropTypes.arrayOf(PropTypes.number),
|
center: PropTypes.arrayOf(PropTypes.number),
|
||||||
zoom: PropTypes.number,
|
zoom: PropTypes.number,
|
||||||
|
|
@ -258,12 +261,14 @@ export default translate([], { wait: true })(withTracker(() => {
|
||||||
Meteor.subscribe('activefirestotal');
|
Meteor.subscribe('activefirestotal');
|
||||||
// Right now to all neighborhood alerts
|
// Right now to all neighborhood alerts
|
||||||
Meteor.subscribe('fireAlerts');
|
Meteor.subscribe('fireAlerts');
|
||||||
|
Meteor.subscribe('settings');
|
||||||
const userSubs = Meteor.subscribe('userSubsToFires');
|
const userSubs = Meteor.subscribe('userSubsToFires');
|
||||||
// const subscription = Meteor.subscribe('activefiresmyloc', zoom.get());
|
// const subscription = Meteor.subscribe('activefiresmyloc', zoom.get());
|
||||||
// Warning with the performance of this log:
|
// Warning with the performance of this log:
|
||||||
// console.log(`Active fires ${ActiveFiresCollection.find().count()} of ${Counter.get('countActiveFires')}`);
|
// 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(`Active neighborhood fires ${FireAlertsCollection.find().fetch().length} and users subscribed ${UserSubsToFiresCollection.find().fetch().length}`);
|
||||||
// console.log(UserSubsToFiresCollection.find().fetch());
|
// console.log(UserSubsToFiresCollection.find().fetch());
|
||||||
|
const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' });
|
||||||
return {
|
return {
|
||||||
loading: !subscription.ready(),
|
loading: !subscription.ready(),
|
||||||
userSubs: UserSubsToFiresCollection.find().fetch(),
|
userSubs: UserSubsToFiresCollection.find().fetch(),
|
||||||
|
|
@ -273,6 +278,7 @@ export default translate([], { wait: true })(withTracker(() => {
|
||||||
// activefires: ActiveFiresCollection.find({}).fetch(),
|
// activefires: ActiveFiresCollection.find({}).fetch(),
|
||||||
activefirestotal: Counter.get('countActiveFires'),
|
activefirestotal: Counter.get('countActiveFires'),
|
||||||
firealerts: FireAlertsCollection.find().fetch(),
|
firealerts: FireAlertsCollection.find().fetch(),
|
||||||
|
lastCheck: lastCheck ? lastCheck.value : null,
|
||||||
center: geolocation.get(),
|
center: geolocation.get(),
|
||||||
zoom: zoom.get()
|
zoom: zoom.get()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@
|
||||||
"Información adicional sobre fuego detectado en {{where}} el {{when}}": "Additional information about fire detected in {{where}} on {{when}}",
|
"Información adicional sobre fuego detectado en {{where}} el {{when}}": "Additional information about fire detected in {{where}} on {{when}}",
|
||||||
"Información adicional sobre fuego detectado el {{when}}": "Additional information about fire detected on {{when}}",
|
"Información adicional sobre fuego detectado el {{when}}": "Additional information about fire detected on {{when}}",
|
||||||
"Coordenadas:": "Coordinates:",
|
"Coordenadas:": "Coordinates:",
|
||||||
"Fuego detectado por satélites de la NASA {{when}}": "Fire detected by NASA satellites {{when}}",
|
"Fuego detectado por satélites de la NASA <1></1>": "Fire detected by NASA satellites <1></1>",
|
||||||
"Puedes añadir un comentario si tienes información adicional sobre este fuego.": "You can add a comment if you have additional information about this fire.",
|
"Puedes añadir un comentario si tienes información adicional sobre este fuego.": "You can add a comment if you have additional information about this fire.",
|
||||||
"Por ejemplo:": "For example:",
|
"Por ejemplo:": "For example:",
|
||||||
"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)": "If you know the area and how to access the fire (this can be helpful to turn off if still active or to investigate in the future)",
|
"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)": "If you know the area and how to access the fire (this can be helpful to turn off if still active or to investigate in the future)",
|
||||||
|
|
@ -152,7 +152,7 @@
|
||||||
"Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.": "Zoom into an area of your interest if you want that fires to be updated in real time.",
|
"Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.": "Zoom into an area of your interest if you want that fires to be updated in real time.",
|
||||||
"Notificaciones": "Notifications",
|
"Notificaciones": "Notifications",
|
||||||
"Recibe nuestras notificaciones de fuegos por correo o en tu navegador": "Receive our fire notifications by mail or in your browser",
|
"Recibe nuestras notificaciones de fuegos por correo o en tu navegador": "Receive our fire notifications by mail or in your browser",
|
||||||
"Fuego notificado por uno de nuestros usuarios/as {{when}}": "Fire reported by one of our users {{when}}",
|
"Fuego notificado por uno de nuestros usuarios/as <1></1>": "Fire reported by one of our users <1></1>",
|
||||||
"No recibirás notificaciones de fuegos en este equipo, solo por correo": "You will not receive notifications of fires in this device, only by email",
|
"No recibirás notificaciones de fuegos en este equipo, solo por correo": "You will not receive notifications of fires in this device, only by email",
|
||||||
"not-found": "Oops: This page doesn't exist",
|
"not-found": "Oops: This page doesn't exist",
|
||||||
"Más información sobre este fuego": "More information about this fire",
|
"Más información sobre este fuego": "More information about this fire",
|
||||||
|
|
@ -160,5 +160,6 @@
|
||||||
"Bienvenid@!": "Welcome!",
|
"Bienvenid@!": "Welcome!",
|
||||||
"Verifica tu dirección de correo": "Verify Your Email Address",
|
"Verifica tu dirección de correo": "Verify Your Email Address",
|
||||||
"Zonas vigiladas": "Monitored areas",
|
"Zonas vigiladas": "Monitored areas",
|
||||||
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "In green, the areas monitored by our users currently"
|
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "In green, the areas monitored by our users currently",
|
||||||
|
"Datos actualizados <1></1>": "Data updated <1></1>"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@
|
||||||
"Información adicional sobre fuego detectado el {{when}}": "Información adicional sobre fuego detectado el {{when}}",
|
"Información adicional sobre fuego detectado el {{when}}": "Información adicional sobre fuego detectado el {{when}}",
|
||||||
|
|
||||||
"Coordenadas:": "Coordenadas:",
|
"Coordenadas:": "Coordenadas:",
|
||||||
"Fuego detectado por satélites de la NASA {{when}}": "Fuego detectado por satélites de la NASA {{when}}",
|
"Fuego detectado por satélites de la NASA <1></1>": "Fuego detectado por satélites de la NASA <1></1>",
|
||||||
"Puedes añadir un comentario si tienes información adicional sobre este fuego.": "Puedes añadir un comentario si tienes información adicional sobre este fuego.",
|
"Puedes añadir un comentario si tienes información adicional sobre este fuego.": "Puedes añadir un comentario si tienes información adicional sobre este fuego.",
|
||||||
"Por ejemplo:": "Por ejemplo:",
|
"Por ejemplo:": "Por ejemplo:",
|
||||||
"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)": "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)",
|
"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)": "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)",
|
||||||
|
|
@ -220,7 +220,7 @@
|
||||||
"Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.": "Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.",
|
"Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.": "Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.",
|
||||||
"Notificaciones": "Notificaciones",
|
"Notificaciones": "Notificaciones",
|
||||||
"Recibe nuestras notificaciones de fuegos por correo o en tu navegador": "Recibe nuestras notificaciones de fuegos por correo o en tu navegador",
|
"Recibe nuestras notificaciones de fuegos por correo o en tu navegador": "Recibe nuestras notificaciones de fuegos por correo o en tu navegador",
|
||||||
"Fuego notificado por uno de nuestros usuarios/as {{when}}": "Fuego notificado por uno de nuestros usuarios/as {{when}}",
|
"Fuego notificado por uno de nuestros usuarios/as <1></1>": "Fuego notificado por uno de nuestros usuarios/as <1></1>",
|
||||||
"No recibirás notificaciones de fuegos en este equipo, solo por correo": "No recibirás notificaciones de fuegos en este equipo, solo por correo",
|
"No recibirás notificaciones de fuegos en este equipo, solo por correo": "No recibirás notificaciones de fuegos en este equipo, solo por correo",
|
||||||
"not-found": "Upppps: Esta página no existe",
|
"not-found": "Upppps: Esta página no existe",
|
||||||
"Más información sobre este fuego": "Más información sobre este fuego",
|
"Más información sobre este fuego": "Más información sobre este fuego",
|
||||||
|
|
@ -231,5 +231,6 @@
|
||||||
"Verifica tu dirección de correo":
|
"Verifica tu dirección de correo":
|
||||||
"Verifica tu dirección de correo",
|
"Verifica tu dirección de correo",
|
||||||
"Zonas vigiladas": "Zonas vigiladas",
|
"Zonas vigiladas": "Zonas vigiladas",
|
||||||
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "En verde, las zonas vigiladas por nuestros usuari@s actualmente"
|
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "En verde, las zonas vigiladas por nuestros usuari@s actualmente",
|
||||||
|
"Datos actualizados <1></1>": "Datos actualizados <1></1>"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
test/siteSettings.test.js
Normal file
49
test/siteSettings.test.js
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { chai } from 'meteor/practicalmeteor:chai';
|
||||||
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
|
import SiteSettingsTypes from '/imports/api/SiteSettings/SiteSettingsTypes';
|
||||||
|
|
||||||
|
const setting = {
|
||||||
|
name: 'site-test',
|
||||||
|
value: 'Some value',
|
||||||
|
description: 'Some description',
|
||||||
|
type: 'string'
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('site settings store', () => {
|
||||||
|
it('should get settingstypes', () => {
|
||||||
|
chai.expect(SiteSettingsTypes.string.value.type).equal(String);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert settings', () => {
|
||||||
|
const id = SiteSettings.insert(setting);
|
||||||
|
SiteSettings.getSchema(setting.type).validate(setting);
|
||||||
|
|
||||||
|
const inserted = SiteSettings.findOne(id);
|
||||||
|
delete inserted._id;
|
||||||
|
chai.expect(setting).to.deep.equal(inserted);
|
||||||
|
SiteSettings.remove(id);
|
||||||
|
chai.expect(SiteSettings.find({ _id: inserted }).count()).equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not insert twice', () => {
|
||||||
|
const id = SiteSettings.insert(setting);
|
||||||
|
// If this fails manual check that the setting it's not already in the db.
|
||||||
|
chai.expect(() => {
|
||||||
|
SiteSettings.insert(setting);
|
||||||
|
}).to.throw('E11000 duplicate key error collection: fuegos.siteSettings index: name_1 dup key: { : "site-test" }');
|
||||||
|
const inserted = SiteSettings.find(id).count();
|
||||||
|
chai.expect(inserted).equal(1);
|
||||||
|
SiteSettings.remove(id);
|
||||||
|
chai.expect(SiteSettings.find({ _id: inserted }).count()).equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail validation', () => {
|
||||||
|
chai.expect(() => {
|
||||||
|
SiteSettings.getSchema('boolean').validate(setting);
|
||||||
|
}).to.throw('Value must be of type Boolean');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue