Compare commits

...

20 commits

Author SHA1 Message Date
39415808bf Fase 1a: flag de cutover notifDisabledChannels en processNotif
Guard reversible al inicio de processNotif: los canales listados en
settings.private.notifDisabledChannels (p.ej. ['mobile','web']) los gestiona el
microservicio tcef-notifications, y el observer/cron viejos los ignoran.
Exclusion mutua por canal sin desplegar codigo (solo editar settings).
ES5-compatible (Meteor 1.6 / Node 8).
2026-07-13 00:10:39 +02:00
9d92ec4f88 deps: regenera package-lock.json y actualiza puntero de meteor-comments-ui 2026-07-12 23:26:17 +02:00
2a31435135 Refactor server startup: elimina segfaults, añade leaflet-workaround
Retira import './segfaults' (workaround de crash obsoleto) y añade
leaflet-workaround.js (shims global window/document/navigator para
render de Leaflet en servidor). Ajustes menores en publications de
FalsePositives y subsUnion.
2026-07-12 23:26:16 +02:00
8281fa3427 chore: ignorar settings-production-*.json y settings-tests.json (secretos) 2026-07-12 23:26:16 +02:00
vjrj
ee7af168d6 Update meteor dev user/pass 2022-08-14 21:53:41 +02:00
vjrj
4cbbe65fa3 Fix wrong grey tiles 2022-08-13 19:37:56 +02:00
vjrj
b9457fe141 Minor changes in fires union 2018-12-23 13:31:41 +01:00
vjrj
c4cb52abaf Improved chimp command in README 2018-11-18 18:01:38 +01:00
vjrj
423dbc5ed9 Added optional chimp configuration 2018-11-18 18:01:17 +01:00
vjrj
1fdf86dd47 Union of subs 'null' when app is initialized 2018-11-18 16:24:49 +01:00
vjrj
5f9a84dfd7 Unify don't calc boundaries of empty union 2018-11-18 15:45:14 +01:00
vjrj
564b9e8ead Trying to fix race condition in i18n load 2018-11-18 15:43:48 +01:00
vjrj
df51368d50 Better log in error 2018-11-18 15:05:04 +01:00
vjrj
0f6dd21541 Removed watch to test 2018-11-18 15:04:46 +01:00
vjrj
8903c765e3 Minor refactor 2018-11-18 14:27:51 +01:00
vjrj
266707052c Better error message 2018-11-18 14:27:34 +01:00
vjrj
3f8bc3666b Fixed index DOM warning. Google Play btn height 2018-11-18 13:08:36 +01:00
vjrj
6abe668740 Added fires union (wip) 2018-11-18 11:48:03 +01:00
vjrj
7b5f17fbe8 Added activeFiresUnion (wip) 2018-10-28 20:27:38 +01:00
vjrj
8b598fab7c Disabled fires render 2018-10-15 06:02:05 +02:00
34 changed files with 941 additions and 734 deletions

5
.gitignore vendored
View file

@ -12,3 +12,8 @@ public/locales/undefined/
.screenshots .screenshots
.vscode .vscode
output.json output.json
# fase-0 higiene: variantes de settings con secretos (no versionar)
settings-production-*.json
settings-tests.json
*.json~

View file

@ -33,11 +33,11 @@ TEST_PORT=3000 TEST_WATCH=1 TEST_CLIENT=0 MONGO_URL=mongodb://localhost:27017/fu
# and # and
chimp --watch --ddp=http://localhost:3000 --path=cucumber node_modules/.bin/chimp --watch --ddp=http://localhost:3000 --path=cucumber
# and # and
chimp --ddp=http://localhost:3000 --path=cucumber node_modules/.bin/chimp --ddp=http://localhost:3000 --path=cucumber
``` ```

29
cucumber/chimp-config.js Normal file
View file

@ -0,0 +1,29 @@
module.exports = {
// - - - - CHIMP - - - -
/* watch: false,
* watchTags: '@watch',
* offline: false,
*/
// - - - - CUCUMBER - - - -
/* path: './features',
* jsonOutput: 'output.json', */
// '- - - - DEBUGGING - - - -
log: 'info',
debug: false,
seleniumDebug: false,
webdriverLogLevel: false,
// debugBrkCucumber: 5858,
// - - - - WEBDRIVER-IO - - - -
webdriverio: {
waitforTimeout: 10000,
waitforInterval: 250, // KEEP SMALL (!!!) this is the INTERVAL in which waitFor* is looped fo
desiredCapabilities: {
chromeOptions: {
// args: ["headless", "disable-gpu"]
args: ['--disable-gpu', '--no-sandbox', '--headless']
},
isHeadless: true
}
}
};

View file

@ -40,7 +40,6 @@ Feature: Test all secundary pages
| fire/Fe26.2**1a0361ed0384f741403682e26b4bbc3850bee24e775da13c9782b22365ea895f*r_3gmVad5vzkeyqPpo6UcA*1s5fFz3iDGKTYP2RCvXMshof00QCHf4ErDl9K2dxoX0u-J-t6scyOWG8pGp3ehg_FfEtyR_kYcEKU3rE0jaSlZbD09TIvhiIJeS3C6Uc8YD-rit0XBrgsVKfYSxKzTRoOYiYFJ8JYd298hMtfiASePjS05Z58hhicyCcJYYRlarqDScG3LiVY3lL5y2nfcdIMNuSjCiKOJWuMkxwd9nR1UHMudLl0hEoy56mPdnHpDYtP9IYUlIOk1LlWBxcmHKifbXeqHu94p8j13Kk20dh2R49Hw3KsSoE9UbWmGQA9wAZXT82301i3rGF5GPAKjlTlRYcWisQurnPwHSVmx3DhUdiYwKGxt4KeaM5QVI4BE9octvE41OOprB_-Il105diQEh2Y9vdvX51ZVWIRfCboICPM6rJb0Oin7U7F1iM-oD_5s3DGnelfM5LGBcKwiB5paMo5M5vdBMaO-zR216cW9yGVXw9IZqHx8xDQWnoHAZjt8NLHeiGF2QOmIGtEUH7qnwhGpkcvszajmAZzR8saZgoH1qfBfvpVA41YfV14gU**4d030f05e23ad75409cebc609107467fc60be5077d52cf041087cd024fc4dc45*GY97aGFc1MyAsoO3Qxqtgwk9j-MbAPdEGBmEHq6r8VU | Additional information | false | | fire/Fe26.2**1a0361ed0384f741403682e26b4bbc3850bee24e775da13c9782b22365ea895f*r_3gmVad5vzkeyqPpo6UcA*1s5fFz3iDGKTYP2RCvXMshof00QCHf4ErDl9K2dxoX0u-J-t6scyOWG8pGp3ehg_FfEtyR_kYcEKU3rE0jaSlZbD09TIvhiIJeS3C6Uc8YD-rit0XBrgsVKfYSxKzTRoOYiYFJ8JYd298hMtfiASePjS05Z58hhicyCcJYYRlarqDScG3LiVY3lL5y2nfcdIMNuSjCiKOJWuMkxwd9nR1UHMudLl0hEoy56mPdnHpDYtP9IYUlIOk1LlWBxcmHKifbXeqHu94p8j13Kk20dh2R49Hw3KsSoE9UbWmGQA9wAZXT82301i3rGF5GPAKjlTlRYcWisQurnPwHSVmx3DhUdiYwKGxt4KeaM5QVI4BE9octvE41OOprB_-Il105diQEh2Y9vdvX51ZVWIRfCboICPM6rJb0Oin7U7F1iM-oD_5s3DGnelfM5LGBcKwiB5paMo5M5vdBMaO-zR216cW9yGVXw9IZqHx8xDQWnoHAZjt8NLHeiGF2QOmIGtEUH7qnwhGpkcvszajmAZzR8saZgoH1qfBfvpVA41YfV14gU**4d030f05e23ad75409cebc609107467fc60be5077d52cf041087cd024fc4dc45*GY97aGFc1MyAsoO3Qxqtgwk9j-MbAPdEGBmEHq6r8VU | Additional information | false |
Then I check that all page urls works properly Then I check that all page urls works properly
@watch
Scenario: Check that other non visible pages work well Scenario: Check that other non visible pages work well
Given I logout if logged Given I logout if logged
And a list of non visible pages ids and contents And a list of non visible pages ids and contents

View file

@ -1,5 +1,6 @@
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import firesCommonSchema from '../Common/FiresSchema'; import firesCommonSchema from '../Common/FiresSchema';
@ -18,8 +19,11 @@ ActiveFires.deny({
remove: () => true remove: () => true
}); });
const activeFiresSchema = firesCommonSchema;
ActiveFires.schema = new SimpleSchema(firesCommonSchema); activeFiresSchema.fireUnion = { type: Meteor.Collection.ObjectID, optional: true, blackbox: true };
ActiveFires.schema = new SimpleSchema(activeFiresSchema);
ActiveFires.attachSchema(ActiveFires.schema); ActiveFires.attachSchema(ActiveFires.schema);

View file

@ -0,0 +1,71 @@
/* eslint-disable consistent-return */
/* eslint-disable import/no-absolute-path */
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
const ActiveFiresUnion = new Mongo.Collection('activefiresunion', { idGeneration: 'MONGO' });
/* Sample:
*
* TODO
*
* */
// We have to store polygons and their centroids
// {"type":"Polygon","coordinates":[[[-5.8645,43.5524],[-5.8621,43.5524],[-5.8621,43.5546],[-5.8645,43.5546],[-5.8645,43.5524]]]}
ActiveFiresUnion.allow({
insert: () => false,
update: () => false,
remove: () => false
});
ActiveFiresUnion.deny({
insert: () => true,
update: () => true,
remove: () => true
});
// https://www.npmjs.com/package/simpl-schema
const GeoJsonSchema = new SimpleSchema({
type: {
type: String,
allowedValues: ['Polygon']
},
coordinates: {
type: Array
},
'coordinates.$': {
type: Array
},
'coordinates.$.$': {
type: Array
},
'coordinates.$.$.$': {
type: Number
}
});
const firesUnionSchema = {
centerid: LocationSchema,
// https://docs.mongodb.com/manual/reference/geojson/
// <field>: { type: <GeoJSON type> , coordinates: <coordinates> }
shape: GeoJsonSchema,
history: { type: Array, optional: true },
'history.$': GeoJsonSchema,
when: Date, // First date
createdAt: defaultCreatedAt,
updatedAt: defaultUpdateAt
};
ActiveFiresUnion.schema = new SimpleSchema(firesUnionSchema);
ActiveFiresUnion.attachSchema(ActiveFiresUnion.schema);
export default ActiveFiresUnion;

View file

@ -0,0 +1,80 @@
/* global Counter */
/* eslint-disable import/no-absolute-path */
/* eslint-disable prefer-arrow-callback */
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { NumberBetween } from '/imports/modules/server/other-checks';
// import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
// import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
// import Industries from '/imports/api/Industries/Industries';
import ActiveFiresUnion from '../ActiveFiresUnion';
const counter = new Counter('countActiveFiresUnion', ActiveFiresUnion.find({}));
Meteor.publish('activefiresuniontotal', function total() {
return counter;
});
const activeFiresUnion = (b1, b2, c1, c2, withMarks) => {
// a --- b
// c --- d
const geometry = {
$geometry: {
type: 'Polygon',
coordinates: [[
[c1, c2],
[c1, b2],
[b1, b2],
[b1, c2],
[c1, c2]
]]
}
};
// console.log(JSON.stringify(geometry));
const fires = ActiveFiresUnion.find({
shape: {
$geoIntersects: geometry
}
}, {
fields: {
shape: 1,
centerid: 1,
when: 1,
createdAt: 1,
updatedAt: 1
}
});
if (Meteor.isDevelopment) console.log(`Active fires union total: ${fires.count()}`);
/*
if (withMarks && fires.fetch().length > 0) {
const union = firesUnion(fires);
const falsePos = whichAreFalsePositives(FalsePositives, union);
const industries = whichAreFalsePositives(Industries, union);
return [fires, falsePos, industries];
} */
return fires;
};
Meteor.publish('activefiresunionmyloc', function activeInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat, withMarks) {
// latitude -90 and 90 and the longitude between -180 and 180
check(northEastLng, NumberBetween(-180, 180));
check(southWestLat, NumberBetween(-90, 90));
check(southWestLng, NumberBetween(-180, 180));
check(northEastLat, NumberBetween(-90, 90));
check(withMarks, Boolean);
// I use this for fire stats
// if (!Meteor.isDevelopment) return this.ready(); // empty
return activeFiresUnion(northEastLng, northEastLat, southWestLng, southWestLat, withMarks);
});
// Warning: this increase always by one the fire stats
Meteor.publish('lastFireUnionDetected', function lastFireDetected() {
// I use this for fire stats
// if (!Meteor.isDevelopment) return this.ready(); // empty
return ActiveFiresUnion.find({}, { limit: 1, sort: { when: -1 } });
});

View file

@ -5,7 +5,8 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import { NumberBetween } from '/imports/modules/server/other-checks'; import { NumberBetween } from '/imports/modules/server/other-checks';
import L from 'leaflet-headless'; import '/imports/startup/server/leaflet-workaround.js';
import L from 'leaflet';
import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify'; import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify';
import Industries from '/imports/api/Industries/Industries'; import Industries from '/imports/api/Industries/Industries';
import FalsePositives from '../FalsePositives'; import FalsePositives from '../FalsePositives';

View file

@ -147,7 +147,7 @@ export function fireFromHash(fireEnc, params) {
// const unsealed = Promise.await(urlEnc.decrypt(fireEnc)); // const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
const unsealed = Promise.await(unsealW(fireEnc)); const unsealed = Promise.await(unsealW(fireEnc));
if (unsealed === undefined) { if (unsealed === undefined) {
throw Error('Undefined unsealed'); throw Error(`Fail to unseal '${fireEnc}'`);
} }
const w = unsealed.when; const w = unsealed.when;
unsealed.when = new Date(w); unsealed.when = new Date(w);

View file

@ -43,7 +43,18 @@ function imgUrl(lat, lng) {
return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`; return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
} */ } */
// Cutover flag (fase 1a): channels listed in
// Meteor.settings.private.notifDisabledChannels (e.g. ['mobile','web']) are
// handled by the tcef-notifications microservice, so the old observer/cron must
// NOT send them. Mutual exclusion per channel — reversible by editing settings,
// no code deploy needed. ES5-compatible on purpose (Meteor 1.6 / Node 8).
const notifDisabledChannels = (Meteor.settings.private && Meteor.settings.private.notifDisabledChannels) || [];
const processNotif = (notif) => { const processNotif = (notif) => {
if (notifDisabledChannels.indexOf(notif.type) !== -1) {
// This channel was migrated to tcef-notifications; do nothing here.
return;
}
if (isMailServerMaster && validFcmSender && notif.type === 'mobile' && notif.notified !== true) { if (isMailServerMaster && validFcmSender && notif.type === 'mobile' && notif.notified !== true) {
const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken); const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken);
const user = Meteor.users.findOne({ _id: notif.userId }); const user = Meteor.users.findOne({ _id: notif.userId });

View file

@ -9,6 +9,7 @@ import '../../api/Users/server/publications';
import '../../api/Utility/server/methods'; import '../../api/Utility/server/methods';
import '../../api/ActiveFires/server/publications'; import '../../api/ActiveFires/server/publications';
import '../../api/ActiveFiresUnion/server/publications';
import '../../api/FireAlerts/server/publications'; import '../../api/FireAlerts/server/publications';
import '../../api/Subscriptions/methods'; import '../../api/Subscriptions/methods';

View file

@ -17,5 +17,4 @@ import './prerender';
import './feedback'; import './feedback';
import './cron'; import './cron';
import './isMaster'; import './isMaster';
import './segfaults';
import './rest'; import './rest';

View file

@ -0,0 +1,19 @@
// imports/startup/server/leaflet-workaround.js
global.window = {
screen: {
deviceXDPI: 1,
logicalXDPI: 1
}
};
global.document = {
documentElement: {
style: {}
},
createElement: () => {
return {};
}
};
global.navigator = {
userAgent: 'foo',
platform: 'bar'
};

View file

@ -10,6 +10,7 @@ import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
import Industries from '/imports/api/Industries/Industries'; import Industries from '/imports/api/Industries/Industries';
import IndustryRegistries from '/imports/api/Industries/IndustryRegistries'; import IndustryRegistries from '/imports/api/Industries/IndustryRegistries';
import Notifications from '/imports/api/Notifications/Notifications'; import Notifications from '/imports/api/Notifications/Notifications';
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
Meteor.startup(() => { Meteor.startup(() => {
@ -240,6 +241,18 @@ Meteor.startup(() => {
} }
}); });
Migrations.add({
version: 18,
up: () => {
const raw = ActiveFiresUnion.rawCollection();
raw.createIndex({ centerid: '2dsphere' });
raw.createIndex({ shape: '2dsphere' });
raw.createIndex({ when: 1 });
raw.createIndex({ createdAt: 1 });
raw.createIndex({ updatedAt: 1 });
}
});
// Set createdAt in users & subs // Set createdAt in users & subs
Migrations.migrateTo('latest'); Migrations.migrateTo('latest');

View file

@ -1,25 +0,0 @@
import SegfaultHandler from 'segfault-handler';
import moment from 'moment';
// import ravenLogger from './ravenLogger';
// https://github.com/ddopson/node-segfault-handler
const dateFileFormat = moment().format('YYYYMMDD_HH:mm:ss');
SegfaultHandler.registerHandler(`/var/tmp/tcef-stacktrace-${dateFileFormat}.log`);
// This callaback does not work:
// https://github.com/ddopson/node-segfault-handler/issues/49
/* SegfaultHandler.registerHandler('/var/tmp/tcef-crash.log', (signal, address, stack) => {
* // Do what you want with the signal, address, or stack (array)
* // This callback will execute before the signal is forwarded on.
* ravenLogger.log(stack, signal);
* }); */
// Only uncomment for segv tests
// SegfaultHandler.causeSegfault();
// Or this for normal error (should go to raven in configured)
// throw new Error('This is just a test of error in server');

View file

@ -4,6 +4,8 @@
import Fires from '/imports/api/Fires/Fires'; import Fires from '/imports/api/Fires/Fires';
// import Comments from '/imports/api/Comments/Comments'; // import Comments from '/imports/api/Comments/Comments';
const firesMapEnabled = false;
sitemaps.add('/sitemap.xml', () => { sitemaps.add('/sitemap.xml', () => {
const today = new Date(); const today = new Date();
@ -20,21 +22,23 @@ sitemaps.add('/sitemap.xml', () => {
// When user has public page // When user has public page
/* const users = Meteor.users.find().fetch(); /* const users = Meteor.users.find().fetch();
_.each(users, function(user) { _.each(users, function(user) {
out.push({ out.push({
page: '/persona/' + user.username, page: '/persona/' + user.username,
lastmod: user.updatedAt lastmod: user.updatedAt
}); });
}); */ }); */
Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => { if (firesMapEnabled) {
// Search the last comment of tha fire Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => {
const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } }); // Search the last comment of tha fire
out.push({ const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } });
page: `/fire/archive/${fire._id._str}`, out.push({
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt page: `/fire/archive/${fire._id._str}`,
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt
});
}); });
}); }
return out; return out;
}); });

View file

@ -4,7 +4,8 @@ import { Meteor } from 'meteor/meteor';
import Subscriptions from '/imports/api/Subscriptions/Subscriptions'; import Subscriptions from '/imports/api/Subscriptions/Subscriptions';
import SiteSettings from '/imports/api/SiteSettings/SiteSettings'; import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
import Perlin from 'loms.perlin'; import Perlin from 'loms.perlin';
import L from 'leaflet-headless'; import './leaflet-workaround';
import L from 'leaflet';
import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify'; import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify';
import { isMailServerMaster } from '/imports/startup/server/email'; import { isMailServerMaster } from '/imports/startup/server/email';

View file

@ -35,7 +35,8 @@ class DefMapLayers extends Component {
<TileLayer <TileLayer
opacity={defOpacity} opacity={defOpacity}
attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors" attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
url="https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png" // url="https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
/> />
</BaseLayer>); </BaseLayer>);
const osmlayer = ( const osmlayer = (

View file

@ -1,10 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Circle } from 'react-leaflet'; import { GeoJSON } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { rectangleAround } from 'map-common-utils';
import { onMarkClick } from './MarkListeners'; import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup'; import FirePopup from './FirePopup';
class FireCircleMark extends Component { class FireCircleMark extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -22,22 +22,26 @@ class FireCircleMark extends Component {
lat, lat,
lon, lon,
scan, scan,
track,
nasa, nasa,
id, id,
history, history,
when, when,
t t
} = this.props; } = this.props;
const rect = rectangleAround({ lat, lon }, track, track);
return ( return (
<Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}> <GeoJSON data={rect} color="red" stroke width="1" opacity=".4" fillOpacity="1">
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
</Circle> </GeoJSON>
); );
/* <Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}> */
} }
} }
FireCircleMark.propTypes = { FireCircleMark.propTypes = {
scan: PropTypes.number.isRequired, scan: PropTypes.number.isRequired,
track: PropTypes.number.isRequired,
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
nasa: PropTypes.bool.isRequired, nasa: PropTypes.bool.isRequired,

View file

@ -3,8 +3,8 @@ import React, { Component, Fragment } from 'react';
import { CircleMarker, Marker, Tooltip } from 'react-leaflet'; import { CircleMarker, Marker, Tooltip } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons'; import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons';
import { onMarkClick } from './MarkListeners';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup'; import FirePopup from './FirePopup';
class FireIconMark extends Component { class FireIconMark extends Component {
@ -30,17 +30,7 @@ class FireIconMark extends Component {
render() { render() {
const { const {
lat, lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t
lon,
scan,
nasa,
id,
history,
falsePositives,
industries,
neighbour,
when,
t
} = this.props; } = this.props;
return ( return (
<div> <div>
@ -79,6 +69,7 @@ FireIconMark.propTypes = {
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
scan: PropTypes.number, scan: PropTypes.number,
track: PropTypes.number,
nasa: PropTypes.bool, nasa: PropTypes.bool,
falsePositives: PropTypes.bool.isRequired, falsePositives: PropTypes.bool.isRequired,
industries: PropTypes.bool.isRequired, industries: PropTypes.bool.isRequired,

View file

@ -0,0 +1,22 @@
/* eslint-disable import/no-absolute-path */
/* eslint-disable react/jsx-indent-props */
/* eslint-disable react/jsx-indent */
import React from 'react';
import PropTypes from 'prop-types';
import FirePolygonMark from '/imports/ui/components/Maps/FirePolygonMark';
export default function FireListUnion(props) {
const {
firesUnion, nasa, t, history
} = props;
/* console.log(`Using marks: ${useMarks}, using pixels: ${usePixel}`); */
const items = firesUnion.map(({ _id, ...otherProps }) => (<FirePolygonMark t={t} history={history} id={_id} key={_id} nasa={nasa} {...otherProps} />));
return (<div style={{ display: 'none' }}>{items}</div>);
}
FireListUnion.propTypes = {
firesUnion: PropTypes.array.isRequired,
nasa: PropTypes.bool.isRequired,
history: PropTypes.object.isRequired,
t: PropTypes.func.isRequired
};

View file

@ -3,31 +3,32 @@
import React from 'react'; import React from 'react';
import { CircleMarker } from 'react-leaflet'; import { CircleMarker } from 'react-leaflet';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import FirePopup from './FirePopup';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import FirePopup from './FirePopup';
/* Less acurate (1 pixel per fire) but faster */ /* Less acurate (1 pixel per fire) but faster */
const FirePixel = ({ const FirePixel = ({
lat, lon, nasa, id, when, t, history lat, lon, nasa, id, when, t, history, track
}) => ( }) => (
<CircleMarker <CircleMarker
center={[lat, lon]} center={[lat, lon]}
color={nasa ? 'red' : '#D35400'} color={nasa ? 'red' : '#D35400'}
stroke={false} stroke={false}
fillOpacity="1" fillOpacity="1"
fill fill
radius={nasa ? 1 : 2} radius={nasa ? 1 : 2}
> >
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
</CircleMarker> </CircleMarker>
); );
FirePixel.propTypes = { FirePixel.propTypes = {
lat: PropTypes.number.isRequired, lat: PropTypes.number.isRequired,
lon: PropTypes.number.isRequired, lon: PropTypes.number.isRequired,
id: PropTypes.object.isRequired, id: PropTypes.object.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
scan: PropTypes.number,
track: PropTypes.number,
when: PropTypes.instanceOf(Date), when: PropTypes.instanceOf(Date),
nasa: PropTypes.bool.isRequired, nasa: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired t: PropTypes.func.isRequired

View file

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import { GeoJSON } from 'react-leaflet';
import PropTypes from 'prop-types';
import { onMarkClick } from './MarkListeners';
import FirePopup from './FirePopup';
class FirePolygonMark extends Component {
constructor(props) {
super(props);
this.t = props.t;
this.onClick = this.onClick.bind(this);
}
onClick() {
const { history, nasa, id } = this.props;
onMarkClick(history, nasa, id);
}
render() {
const {
nasa,
id,
shape,
centerid,
history,
when,
t
} = this.props;
const lon = centerid.coordinates[0];
const lat = centerid.coordinates[1];
return (
<GeoJSON data={shape} color="orange" stroke width="1" opacity=".2" fillOpacity=".0" />
);
/* <FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} /> */
}
}
FirePolygonMark.propTypes = {
id: PropTypes.object.isRequired,
nasa: PropTypes.bool.isRequired,
shape: PropTypes.object.isRequired,
centerid: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
when: PropTypes.instanceOf(Date).isRequired,
t: PropTypes.func.isRequired
};
export default FirePolygonMark;

View file

@ -2,7 +2,8 @@ import { calcUnion as calcUnionWoBounds } from 'map-common-utils';
const calcUnion = (L, subs, decorated, typeCircle) => { const calcUnion = (L, subs, decorated, typeCircle) => {
const unionJson = calcUnionWoBounds(subs, decorated, typeCircle); const unionJson = calcUnionWoBounds(subs, decorated, typeCircle);
return [unionJson, L.geoJSON(unionJson).getBounds()]; // return [unionJson, L.geoJSON(unionJson).getBounds()];
return [unionJson, unionJson === null ? null : L.geoJSON(unionJson).getBounds()];
}; };
export default calcUnion; export default calcUnion;

View file

@ -98,66 +98,66 @@ const App = props => (
/* https://react.i18next.com/components/i18nextprovider.html */ /* https://react.i18next.com/components/i18nextprovider.html */
<div> <div>
{props.i18nReady.get() && {props.i18nReady.get() &&
<ErrorBoundary> <ErrorBoundary>
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<ErrorBoundary appName={i18n.t('AppNameFull')} title={i18n.t('general-error-title')} subTitle={i18n.t('general-error-description')}> <ErrorBoundary appName={i18n.t('AppNameFull')} title={i18n.t('general-error-title')} subTitle={i18n.t('general-error-description')}>
<Router history={history}> <Router history={history}>
<LocationListener> <LocationListener>
{ !props.loading && { !props.loading &&
<div className="App"> <div className="App">
<Helmet> <Helmet>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<title>{i18n.t('AppName')}</title> <title>{i18n.t('AppName')}</title>
<meta name="description" content={`${i18n.t('AppDescrip')}: ${i18n.t('AppDescripLong')}`} /> <meta name="description" content={`${i18n.t('AppDescrip')}: ${i18n.t('AppDescripLong')}`} />
<link rel="alternate" href={`https://fires.comunes.org${history.location.pathname}`} hrefLang="en" /> <link rel="alternate" href={`https://fires.comunes.org${history.location.pathname}`} hrefLang="en" />
<link rel="alternate" href={`https://fuegos.comunes.org${history.location.pathname}`} hrefLang="es" /> <link rel="alternate" href={`https://fuegos.comunes.org${history.location.pathname}`} hrefLang="es" />
</Helmet> </Helmet>
<Navigation {...props} /> <Navigation {...props} />
<ReSendEmail {...props} /> <ReSendEmail {...props} />
<Grid> <Grid>
<Switch> <Switch>
<Route exact name="index" path="/" component={Index} /> <Route exact name="index" path="/" component={Index} />
<Authenticated exact path="/subscriptions" component={Subscriptions} {...props} /> <Authenticated exact path="/subscriptions" component={Subscriptions} {...props} />
<Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} /> <Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} />
<Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} /> <Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} />
<Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} /> <Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} />
<Authenticated exact path="/profile" component={Profile} {...props} /> <Authenticated exact path="/profile" component={Profile} {...props} />
<Authenticated exact path="/status" component={Status} {...props} /> <Authenticated exact path="/status" component={Status} {...props} />
<Route path="/fires" component={FiresMap} industries={false} {...props} /> <Route path="/fires" component={FiresMap} industries={false} {...props} />
<Route path="/zones" component={ZonesMap} {...props} /> <Route path="/zones" component={ZonesMap} {...props} />
<Route path="/fire/:type(active|archive|alert)/:id" component={Fires} {...props} /> <Route path="/fire/:type(active|archive|alert)/:id" component={Fires} {...props} />
<Route path="/fire/:id" component={Fires} {...props} /> <Route path="/fire/:id" component={Fires} {...props} />
<Public path="/auth/:token" component={Auth} {...props} /> <Public path="/auth/:token" component={Auth} {...props} />
<Public path="/signup" component={Signup} {...props} /> <Public path="/signup" component={Signup} {...props} />
<Public path="/login" component={Login} {...props} /> <Public path="/login" component={Login} {...props} />
<Route path="/logout" component={Logout} {...props} /> <Route path="/logout" component={Logout} {...props} />
<Route path="/sandbox" component={Sandbox} {...props} /> <Route path="/sandbox" component={Sandbox} {...props} />
<Route path="/error" component={TestError} {...props} /> <Route path="/error" component={TestError} {...props} />
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} /> <Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
<Route name="recover-password" path="/recover-password" component={RecoverPassword} /> <Route name="recover-password" path="/recover-password" component={RecoverPassword} />
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} /> <Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
<Route name="terms" path="/terms" component={Terms} /> <Route name="terms" path="/terms" component={Terms} />
<Route name="privacy" path="/privacy" component={Privacy} /> <Route name="privacy" path="/privacy" component={Privacy} />
<Route name="license" path="/license" component={License} /> <Route name="license" path="/license" component={License} />
<Route name="credits" path="/credits" component={Credits} /> <Route name="credits" path="/credits" component={Credits} />
<Route name="about" path="/about" component={About} /> <Route name="about" path="/about" component={About} />
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
</Grid> </Grid>
<Footer /> <Footer />
<Reconnect {...props} /> <Reconnect {...props} />
<Feedback {...props} /> <Feedback {...props} />
{props.i18nReady.get() && <Blaze template="cookieConsent" /> } {props.i18nReady.get() && <Blaze template="cookieConsent" /> }
</div>} </div>}
</LocationListener> </LocationListener>
</Router> </Router>
</ErrorBoundary> </ErrorBoundary>
</I18nextProvider> </I18nextProvider>
</ErrorBoundary> } </ErrorBoundary> }
</div> </div>
); );
@ -186,7 +186,9 @@ export default withTracker(() => {
const loading = !Roles.subscription.ready() || !i18nReady.get(); const loading = !Roles.subscription.ready() || !i18nReady.get();
const name = user && user.profile && user.profile.name && getUserName(user.profile.name); const name = user && user.profile && user.profile.name && getUserName(user.profile.name);
const emailAddress = user && user.emails && user.emails[0].address; const emailAddress = user && user.emails && user.emails[0].address;
// console.log(`i18n ready?: ${i18nReady.get()}`); Meteor.autorun(() => {
console.log(`i18n ready?: ${i18nReady.get()}`);
});
return { return {
loading, loading,
loggingIn, loggingIn,

View file

@ -10,7 +10,8 @@ import { Row, Col, Alert, FormGroup } from 'react-bootstrap';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Bert } from 'meteor/themeteorchef:bert'; import { Bert } from 'meteor/themeteorchef:bert';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { Map, Circle } from 'react-leaflet'; import { Map, GeoJSON } from 'react-leaflet';
import { rectangleAround } from 'map-common-utils';
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';
@ -115,8 +116,9 @@ class Fire extends React.Component {
t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) : t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) :
t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat }); t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat });
} }
const ready = fire && !loading;
return (fire && !loading ? ( const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null;
return (ready ? (
<div className="ViewFire"> <div className="ViewFire">
<Helmet> <Helmet>
<title>{t('AppName')}: {t('Información adicional sobre fuego')}</title> <title>{t('AppName')}: {t('Información adicional sobre fuego')}</title>
@ -137,14 +139,13 @@ class Fire extends React.Component {
zoom={13} zoom={13}
> >
<Fragment> <Fragment>
<Circle <GeoJSON
center={[fire.lat, fire.lon]}
color="red"
fillColor="red"
fillOpacity={0.0}
interactive={false}
radius={fire.scan ? fire.scan * 500 : 300}
ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }} ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }}
data={rect}
color="red"
stroke
width="1"
fillOpacity="0.0"
/> />
</Fragment> </Fragment>
<FireList <FireList

View file

@ -23,9 +23,11 @@ import 'leaflet-sleep/Leaflet.Sleep.js';
import geolocation from '/imports/startup/client/geolocation'; import geolocation from '/imports/startup/client/geolocation';
import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition'; import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition';
import FireList from '/imports/ui/components/Maps/FireList'; import FireList from '/imports/ui/components/Maps/FireList';
import FireListUnion from '/imports/ui/components/Maps/FireListUnion';
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 ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires'; import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries'; import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries';
import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives'; import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives';
@ -82,19 +84,6 @@ class FiresMap extends React.Component {
}); });
} }
/* shouldComponentUpdate(nextProps, nextState) {
* const notMoving = !nextState.moving;
* const markersChanged = this.state.useMarkers !== nextState.useMarkers;
* const unionChanged = this.state.showSubsUnion !== nextState.showSubsUnion;
* const otherViewport = this.state.viewport !== nextState.viewport;
* // const init = nextState.viewport.center === [0, 0];
* // console.log(notMoving ? 'Not moving map' : 'Moving map');
* // console.log(otherViewport ? 'Other viewport' : 'Not other viewport');
* console.log(`${otherViewport ? 'OTHER' : 'Not other'} viewport ${nextState.viewport.center} zoom: ${nextState.viewport.zoom}`);
* return this.state.init || (notMoving && otherViewport && this.state.moved) || unionChanged || markersChanged;
* }
*/
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const notMoving = !nextState.moving; const notMoving = !nextState.moving;
return notMoving; return notMoving;
@ -202,6 +191,7 @@ class FiresMap extends React.Component {
if (Meteor.isDevelopment && this.props.activefires.length === 1) console.log(`Active fire: ${JSON.stringify(this.props.activefires[0])}`); if (Meteor.isDevelopment && this.props.activefires.length === 1) console.log(`Active fire: ${JSON.stringify(this.props.activefires[0])}`);
if (Meteor.isDevelopment) console.log(`Active fires total: ${this.props.activefires.length}`); if (Meteor.isDevelopment) console.log(`Active fires total: ${this.props.activefires.length}`);
if (Meteor.isDevelopment) console.log(`Active fires union total: ${this.props.activefiresunion.length}`);
if (Meteor.isDevelopment) console.log(`False positives total: ${this.props.falsePositives.length}`); if (Meteor.isDevelopment) console.log(`False positives total: ${this.props.falsePositives.length}`);
if (Meteor.isDevelopment) console.log(`Fire alerts total: ${this.props.firealerts.length}`); if (Meteor.isDevelopment) console.log(`Fire alerts total: ${this.props.firealerts.length}`);
if (Meteor.isDevelopment) console.log(`Industries total: ${this.props.industries.length}`); if (Meteor.isDevelopment) console.log(`Industries total: ${this.props.industries.length}`);
@ -246,7 +236,7 @@ class FiresMap extends React.Component {
<p className="firesmap-legend"> <p className="firesmap-legend">
{ (this.props.activefires.length + this.props.firealerts.length) === 0 ? { (this.props.activefires.length + this.props.firealerts.length) === 0 ?
<Fragment><Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment> : <Fragment><Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment> :
<Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length + this.props.firealerts.length }}</strong> fuegos activos. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment> <Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefiresunion.length + this.props.firealerts.length }}</strong> fuegos activos. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment>
} }
</p> </p>
{isNotHomeAndMobile() && this.props.firealerts.length > 0 && {isNotHomeAndMobile() && this.props.firealerts.length > 0 &&
@ -307,6 +297,14 @@ class FiresMap extends React.Component {
neighbour={false} neighbour={false}
industries={false} industries={false}
/> />
{ Meteor.isDevelopment &&
<FireListUnion
t={t}
history={this.props.history}
firesUnion={this.props.activefiresunion}
nasa
/>
}
<FireList <FireList
t={t} t={t}
history={this.props.history} history={this.props.history}
@ -369,12 +367,14 @@ FiresMap.propTypes = {
userSubs: PropTypes.string, userSubs: PropTypes.string,
userSubsBounds: PropTypes.string, userSubsBounds: PropTypes.string,
activefires: PropTypes.arrayOf(PropTypes.object).isRequired, activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
activefiresunion: PropTypes.arrayOf(PropTypes.object).isRequired,
lastFireDetected: PropTypes.object, lastFireDetected: PropTypes.object,
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired, firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired, falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired,
industries: PropTypes.arrayOf(PropTypes.object).isRequired, industries: PropTypes.arrayOf(PropTypes.object).isRequired,
lastCheck: PropTypes.instanceOf(Date), lastCheck: PropTypes.instanceOf(Date),
activefirestotal: PropTypes.number.isRequired, activefirestotal: PropTypes.number.isRequired,
activefiresuniontotal: PropTypes.number.isRequired,
center: PropTypes.arrayOf(PropTypes.number), center: PropTypes.arrayOf(PropTypes.number),
zoom: PropTypes.number, zoom: PropTypes.number,
marks: PropTypes.bool.isRequired, marks: PropTypes.bool.isRequired,
@ -391,6 +391,7 @@ export default translate([], { wait: true })(withTracker(() => {
// console.log(`With industries: ${withIndustries}`); // console.log(`With industries: ${withIndustries}`);
let subscription; let subscription;
let subscriptionUnion;
let alertSubscription; let alertSubscription;
const centerStored = store.get('firesmap_center'); const centerStored = store.get('firesmap_center');
@ -422,6 +423,14 @@ export default translate([], { wait: true })(withTracker(() => {
mapSize.get()[1].lat, mapSize.get()[1].lat,
marks.get() && zoom.get() >= MAXZOOM marks.get() && zoom.get() >= MAXZOOM
); );
subscriptionUnion = Meteor.subscribe(
'activefiresunionmyloc',
mapSize.get()[0].lng,
mapSize.get()[0].lat,
mapSize.get()[1].lng,
mapSize.get()[1].lat,
false
);
alertSubscription = Meteor.subscribe( alertSubscription = Meteor.subscribe(
'fireAlerts', 'fireAlerts',
mapSize.get()[0].lng, mapSize.get()[0].lng,
@ -442,6 +451,7 @@ export default translate([], { wait: true })(withTracker(() => {
}); });
Meteor.subscribe('activefirestotal'); Meteor.subscribe('activefirestotal');
Meteor.subscribe('activefiresuniontotal');
const settingsSubs = Meteor.subscribe('settings'); const settingsSubs = Meteor.subscribe('settings');
const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' }); const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' });
@ -453,13 +463,15 @@ export default translate([], { wait: true })(withTracker(() => {
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } }); const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
return { return {
loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready()), loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready() && subscriptionUnion.ready()),
userSubs: userSubs ? userSubs.value : null, userSubs: userSubs ? userSubs.value : null,
userSubsBounds: userSubs ? userSubsBounds.value : null, userSubsBounds: userSubs ? userSubsBounds.value : null,
subsready: settingsSubs.ready(), subsready: settingsSubs.ready(),
// Not reactive query depending on zoom level // Not reactive query depending on zoom level
activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
activefiresunion: ActiveFiresUnion.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
activefirestotal: Counter.get('countActiveFires') + fireAlerts.length, activefirestotal: Counter.get('countActiveFires') + fireAlerts.length,
activefiresuniontotal: Counter.get('countActiveFiresUnion') + fireAlerts.length,
firealerts: fireAlerts, firealerts: fireAlerts,
falsePositives, falsePositives,
industries, industries,

View file

@ -182,9 +182,9 @@ class Index extends Component {
</div> </div>
<p className="moto"><Trans>Siempre alerta a los fuegos en nuestro vecindario</Trans></p> <p className="moto"><Trans>Siempre alerta a los fuegos en nuestro vecindario</Trans></p>
{/* <Link className="participe-btn btn btn-lg btn-warning" role="button" to="/#platforms">{this.props.t('Participa')}</Link> */} {/* <Link className="participe-btn btn btn-lg btn-warning" role="button" to="/#platforms">{this.props.t('Participa')}</Link> */}
<p> <div className="android-btn">
<MobileStoreButton store="android" url={androidUrl} /> <MobileStoreButton store="android" url={androidUrl} />
</p> </div>
</div> </div>
</section> </section>
</section> </section>

View file

@ -77,3 +77,7 @@
} }
} }
} }
.android-btn > div {
height: 50px !important;
}

View file

@ -63,8 +63,8 @@ class SubscriptionsMap extends React.Component {
handleLeafletLoad(map) { handleLeafletLoad(map) {
// console.log('Map loading'); // console.log('Map loading');
// console.log(map); if (map && this.props.userSubs !== 'null') {
if (map) { // console.log(`Union of ${this.props.userSubs}`);
this.state.union = subsUnion(this.state.union, { this.state.union = subsUnion(this.state.union, {
map, map,
subs: this.props.userSubs, subs: this.props.userSubs,

1023
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
"name": "tcef", "name": "tcef",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://localhost:27017/fuegos meteor --settings settings-development.json", "start": "QT_QPA_PLATFORM='' MONGO_URL=mongodb://fuegos:fuegos@localhost:27017/fuegos meteor --settings settings-development.json",
"test": "jest" "test": "jest"
}, },
"dependencies": { "dependencies": {
@ -39,11 +39,10 @@
"juice": "^4.2.2", "juice": "^4.2.2",
"leaflet": "^1.3.1", "leaflet": "^1.3.1",
"leaflet-graphicscale": "0.0.2", "leaflet-graphicscale": "0.0.2",
"leaflet-headless": "^0.2.6",
"leaflet-sleep": "^0.5.1", "leaflet-sleep": "^0.5.1",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"loms.perlin": "^1.0.1", "loms.perlin": "^1.0.1",
"map-common-utils": "^0.4.0", "map-common-utils": "^0.5.0",
"maxmind": "^2.3.0", "maxmind": "^2.3.0",
"meteor-accounts-t9n": "^2.0.3", "meteor-accounts-t9n": "^2.0.3",
"meteor-node-stubs": "^0.3.2", "meteor-node-stubs": "^0.3.2",
@ -83,7 +82,6 @@
"react-router-hash-link": "^1.1.1", "react-router-hash-link": "^1.1.1",
"react-share": "^2.0.0", "react-share": "^2.0.0",
"reactstrap": "^5.0.0-alpha.3", "reactstrap": "^5.0.0-alpha.3",
"segfault-handler": "^1.0.0",
"simpl-schema": "^1.5.0", "simpl-schema": "^1.5.0",
"simple-line-icons": "^2.4.1", "simple-line-icons": "^2.4.1",
"store": "^2.0.12", "store": "^2.0.12",

@ -1 +1 @@
Subproject commit 3346b6db70c153b1dd66e6a16a0ad28f10ebf740 Subproject commit 4761da921f467838a2c01b34e84a7703ee49b12c

View file

@ -0,0 +1,29 @@
/* eslint-env mocha */
/* eslint-disable func-names, prefer-arrow-callback */
/* eslint-disable import/no-absolute-path */
import { chai } from 'meteor/practicalmeteor:chai';
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
const centerid = { type: 'Point', coordinates: [-5.905956029891968, 43.55727622097011] };
const shape = { type: 'Polygon', coordinates: [[[-5.9084, 43.5558], [-5.906, 43.5558], [-5.906, 43.5566], [-5.9036, 43.5566], [-5.9036, 43.5583], [-5.9061, 43.5583], [-5.9061, 43.558], [-5.9084, 43.558], [-5.9084, 43.5558]]] };
const fireUnion = { centerid, shape, when: Date('2018-05-02T16:11:04.617Z') };
describe('activeFireUnion insert', () => {
it('should insert fire union', () => {
// Remove previous test register
const alreadyInserted = ActiveFiresUnion.findOne({ centerid });
if (alreadyInserted) {
ActiveFiresUnion.remove(alreadyInserted._id);
}
const insertedId = ActiveFiresUnion.insert(fireUnion);
ActiveFiresUnion.schema.validate(fireUnion);
const inserted = ActiveFiresUnion.findOne(insertedId);
delete inserted._id;
chai.expect(fireUnion).to.deep.equal(inserted);
ActiveFiresUnion.remove(insertedId);
chai.expect(ActiveFiresUnion.find({ _id: inserted }).count()).equal(0);
});
});