Compare commits
47 commits
2.9.12
...
tcef-maste
| Author | SHA1 | Date | |
|---|---|---|---|
| 39415808bf | |||
| 9d92ec4f88 | |||
| 2a31435135 | |||
| 8281fa3427 | |||
|
|
ee7af168d6 | ||
|
|
4cbbe65fa3 | ||
|
|
b9457fe141 | ||
|
|
c4cb52abaf | ||
|
|
423dbc5ed9 | ||
|
|
1fdf86dd47 | ||
|
|
5f9a84dfd7 | ||
|
|
564b9e8ead | ||
|
|
df51368d50 | ||
|
|
0f6dd21541 | ||
|
|
8903c765e3 | ||
|
|
266707052c | ||
|
|
3f8bc3666b | ||
|
|
6abe668740 | ||
|
|
7b5f17fbe8 | ||
|
|
8b598fab7c | ||
|
|
cb7aeda4b8 | ||
|
|
181f488263 | ||
|
|
e96e6ee8f7 | ||
|
|
df47bef794 | ||
|
|
3ea653552e | ||
|
|
6063437df0 | ||
|
|
cdc3bbdd5b | ||
|
|
33c68001c2 | ||
|
|
53a7c3be21 | ||
|
|
3d059287ae | ||
|
|
a208eb7ea1 | ||
|
|
c4b2f8cd73 | ||
|
|
f9febb4415 | ||
|
|
f5665a56a8 | ||
|
|
4d7938220b | ||
|
|
829fae4f07 | ||
|
|
e223ddb643 | ||
|
|
dc5e38f8d5 | ||
|
|
ed08563cad | ||
|
|
9a31ab2457 | ||
|
|
f608fc4d28 | ||
|
|
6f683ebaa2 | ||
|
|
8e729a6939 | ||
|
|
ffef42d19a | ||
|
|
3defbb01a2 | ||
|
|
bbdc53bda1 | ||
|
|
8a741dc729 |
45 changed files with 1595 additions and 986 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -12,3 +12,8 @@ public/locales/undefined/
|
|||
.screenshots
|
||||
.vscode
|
||||
output.json
|
||||
|
||||
# fase-0 higiene: variantes de settings con secretos (no versionar)
|
||||
settings-production-*.json
|
||||
settings-tests.json
|
||||
*.json~
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -33,14 +33,19 @@ TEST_PORT=3000 TEST_WATCH=1 TEST_CLIENT=0 MONGO_URL=mongodb://localhost:27017/fu
|
|||
|
||||
# and
|
||||
|
||||
chimp --watch --ddp=http://localhost:3000 --path=cucumber
|
||||
node_modules/.bin/chimp --watch --ddp=http://localhost:3000 --path=cucumber
|
||||
|
||||
# and
|
||||
|
||||
chimp --ddp=http://localhost:3000 --path=cucumber
|
||||
node_modules/.bin/chimp --ddp=http://localhost:3000 --path=cucumber
|
||||
|
||||
```
|
||||
|
||||
### FAQ & Troubleshooting
|
||||
|
||||
**Q** - I get something like `(...) /node_modules/fibers/future.js:280 (...) Error: Could not locate the bindings file.` What can I do?
|
||||
**A** - Try `meteor npm rebuild`
|
||||
|
||||
## Data source acknowledgements
|
||||
|
||||
*We acknowledge the use of data and imagery from LANCE FIRMS operated by the NASA/GSFC/Earth Science Data and Information System (ESDIS) with funding provided by NASA/HQ*.
|
||||
|
|
@ -51,4 +56,5 @@ GNU APLv3. See our [LICENSE](https://github.com/comunes/todos-contra-el-fuego-we
|
|||
|
||||
## Thanks & other acknowlegments
|
||||
|
||||
(...)
|
||||
Thanks indeed to:
|
||||
- Lupe Bao Reixa for Galician translation
|
||||
|
|
|
|||
29
cucumber/chimp-config.js
Normal file
29
cucumber/chimp-config.js
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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 |
|
||||
Then I check that all page urls works properly
|
||||
|
||||
@watch
|
||||
Scenario: Check that other non visible pages work well
|
||||
Given I logout if logged
|
||||
And a list of non visible pages ids and contents
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import firesCommonSchema from '../Common/FiresSchema';
|
||||
|
|
@ -18,8 +19,11 @@ ActiveFires.deny({
|
|||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ const findFiresInRegion = (zone) => {
|
|||
export const countRealFires = (firesCursor) => {
|
||||
const realFires = [];
|
||||
firesCursor.forEach((fire) => {
|
||||
if (debug) console.log(`${JSON.stringify(fire)} -----`);
|
||||
const union = firesUnion([fire]);
|
||||
if (debug) console.log(`${JSON.stringify(union)} -----`);
|
||||
const falsePos = whichAreFalsePositives(FalsePositives, union);
|
||||
const industries = whichAreFalsePositives(Industries, union);
|
||||
if (falsePos.count() === 0 && industries.count() === 0) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, wit
|
|||
lat: 1,
|
||||
lon: 1,
|
||||
when: 1,
|
||||
scan: 1
|
||||
scan: 1,
|
||||
track: 1
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
71
imports/api/ActiveFiresUnion/ActiveFiresUnion.js
Normal file
71
imports/api/ActiveFiresUnion/ActiveFiresUnion.js
Normal 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;
|
||||
80
imports/api/ActiveFiresUnion/server/publications.js
Normal file
80
imports/api/ActiveFiresUnion/server/publications.js
Normal 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 } });
|
||||
});
|
||||
|
|
@ -5,7 +5,8 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
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 Industries from '/imports/api/Industries/Industries';
|
||||
import FalsePositives from '../FalsePositives';
|
||||
|
|
@ -17,20 +18,26 @@ Meteor.publish('falsePositivesTotal', function total() {
|
|||
});
|
||||
|
||||
export const firesUnion = (fires) => {
|
||||
const group = new L.FeatureGroup();
|
||||
const firesArray = Array.isArray(fires) ? fires : fires.fetch(); // if not is a cursor
|
||||
const remap = firesArray.map(function remap(doc) {
|
||||
// default scan: 1 for neightbor alerts
|
||||
return { location: { lat: doc.lat, lon: doc.lon }, distance: doc.scan || 1 };
|
||||
const isNASA = doc.type === 'modis' || doc.type === 'viirs';
|
||||
const pixelSize = doc.type === 'viirs' ? 0.375 : 1; // viirs has 375m pixel size, modis 1000m
|
||||
// default 1 km for neightbor alerts
|
||||
const map = {
|
||||
location: { lat: doc.lat, lon: doc.lon },
|
||||
distance: isNASA ? doc.scan * pixelSize : 1,
|
||||
distanceY: isNASA ? doc.track * pixelSize : 1
|
||||
};
|
||||
// console.log(map);
|
||||
return map;
|
||||
});
|
||||
const union = calcUnion(remap, group, sub => sub);
|
||||
const union = calcUnion(L, remap, sub => sub, false);
|
||||
return union;
|
||||
};
|
||||
|
||||
export const zoneToUnion = (lat, lon, distance) => {
|
||||
const group = new L.FeatureGroup();
|
||||
const remap = [{ location: { lat, lon }, distance }];
|
||||
const union = calcUnion(remap, group, sub => sub);
|
||||
const union = calcUnion(L, remap, sub => sub, true);
|
||||
return union;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ Meteor.publish('fireAlerts', function fireAlerts(northEastLng, northEastLat, sou
|
|||
fields: {
|
||||
lat: 1,
|
||||
lon: 1,
|
||||
scan: 1
|
||||
scan: 1,
|
||||
track: 1
|
||||
}
|
||||
});
|
||||
if (Meteor.isDevelopment) console.log(`Neighbour alerts total: ${fires.count()}`);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export function fireFromHash(fireEnc, params) {
|
|||
// const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
||||
const unsealed = Promise.await(unsealW(fireEnc));
|
||||
if (unsealed === undefined) {
|
||||
throw Error('Undefined unsealed');
|
||||
throw Error(`Fail to unseal '${fireEnc}'`);
|
||||
}
|
||||
const w = unsealed.when;
|
||||
unsealed.when = new Date(w);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { Random } from 'meteor/random';
|
|||
import { subscriptionsInsert } from '/imports/api/Subscriptions/methods.js';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { upsertFalsePositive } from '/imports/api/FalsePositives/methods.js';
|
||||
|
||||
const debug = false;
|
||||
|
||||
const uptime = new Date();
|
||||
|
|
@ -156,11 +157,14 @@ if (!Meteor.settings.private.internalApiToken) {
|
|||
lat: 1,
|
||||
lon: 1,
|
||||
when: 1,
|
||||
scan: 1
|
||||
type: 1, // modis, viirs, vecinal
|
||||
scan: 1,
|
||||
track: 1
|
||||
}
|
||||
});
|
||||
|
||||
const result = { total: fires.count(), real: countRealFires(fires) };
|
||||
if (debug) console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius ${result}`);
|
||||
|
||||
if (!full) {
|
||||
return result;
|
||||
|
|
@ -439,11 +443,15 @@ if (!Meteor.settings.private.internalApiToken) {
|
|||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
// TODO
|
||||
const fire = fireFromHash(sealed, {});
|
||||
console.log(`Marking fire as false positive: ${fire}`);
|
||||
const result = upsertFalsePositive(type, user._id, fire);
|
||||
return jsend.success({ upsert: result });
|
||||
console.log(`Marking hash fire as '${type}' false positive: ${sealed}`);
|
||||
const fireC = fireFromHash(sealed, { id: sealed });
|
||||
if (fireC && typeof fireC === 'object') {
|
||||
const fire = fireC.fetch()[0];
|
||||
console.log(`Marking fire as false positive: ${fire._id}`);
|
||||
const result = upsertFalsePositive(type, user._id, fire);
|
||||
return jsend.success({ upsert: result });
|
||||
}
|
||||
return failMsg('Cannot mark fire as false positive');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
153
imports/modules/server/notificationsProcess.js
Normal file
153
imports/modules/server/notificationsProcess.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
import i18n from 'i18next';
|
||||
import moment from 'moment';
|
||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||
// import sendMail from '/imports/startup/server/email';
|
||||
import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email';
|
||||
import { isMailServerMaster } from '/imports/startup/server/email';
|
||||
// import { hr } from '/imports/startup/server/email';
|
||||
import getEmailOf from '/imports/modules/get-email-of-user';
|
||||
import image from 'google-maps-image-api-url';
|
||||
import gcm from 'node-gcm';
|
||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
||||
import ravenLogger from '/imports/startup/server/ravenLogger';
|
||||
|
||||
let validFcmSender = true;
|
||||
|
||||
if (!(Meteor.settings.private.fcmApiToken && Meteor.settings.private.fcmApiToken.length > 0)) {
|
||||
console.warn('Missing settings.private.fcmApiToken key, mobile notifications will not work');
|
||||
validFcmSender = false;
|
||||
}
|
||||
|
||||
// https://www.npmjs.com/package/google-maps-image-api-url
|
||||
// https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email
|
||||
// https://developers.google.com/maps/documentation/static-maps/intro
|
||||
function imgUrl(lat, lng) {
|
||||
return image({
|
||||
key: Meteor.settings.gmaps.key,
|
||||
type: 'staticmap',
|
||||
center: `${lat},${lng}`,
|
||||
size: '640x480',
|
||||
zoom: 16,
|
||||
maptype: 'hybrid',
|
||||
language: 'es',
|
||||
markers: `icon: ${Meteor.settings.private.fireIconUrl}|${lat},${lng}`
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
function imgEl(lat, lng) {
|
||||
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) => {
|
||||
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) {
|
||||
const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken);
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
moment.locale(user.lang);
|
||||
// duplicate code below
|
||||
const body = `${trim(notif.content)}`;
|
||||
|
||||
// https://firebase.google.com/docs/cloud-messaging/concept-options
|
||||
const msg = new gcm.Message();
|
||||
|
||||
msg.addNotification({
|
||||
title: i18n.t('Alerta de fuego'),
|
||||
body,
|
||||
click_action: 'FLUTTER_NOTIFICATION_CLICK',
|
||||
tag: notif._id, // prevent duplication of fire notifications
|
||||
sound: 'default', // Indicates sound to be played. Supports only default currently.
|
||||
icon: 'launch_image' // 'ic_launcher'
|
||||
});
|
||||
|
||||
msg.addData('id', notif._id._str);
|
||||
msg.addData('description', body);
|
||||
msg.addData('lat', notif.geo.coordinates[1]);
|
||||
msg.addData('lon', notif.geo.coordinates[0]);
|
||||
msg.addData('when', notif.when);
|
||||
msg.addData('subsId', notif.subsId._str);
|
||||
msg.addData('sealed', notif.sealed);
|
||||
|
||||
const registrationTokens = [];
|
||||
if (!user.fireBaseToken) {
|
||||
console.warn('This mobile user doesn\'t have a firebase registration token');
|
||||
} else {
|
||||
registrationTokens.push(user.fireBaseToken);
|
||||
// FIXME: better join users
|
||||
if (validFcmSender) {
|
||||
fcmSender.send(msg, { registrationTokens }, Meteor.bindEnvironment(function processResult(err, response) {
|
||||
if (err) {
|
||||
console.error(`FCM error: ${err}`);
|
||||
// FIXME send to sentry
|
||||
ravenLogger.log(err);
|
||||
} else {
|
||||
// console.log(`FCM response: ${response}`);
|
||||
Notifications.update(notif._id, { $set: { notified: true, notifiedAt: new Date() } });
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isMailServerMaster && notif.type === 'web' && notif.emailNotified !== true) {
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
const { firstName, emailAddress } = getEmailOf(user);
|
||||
|
||||
if (emailAddress) {
|
||||
const img = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
// const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
const fireUrl = `${Meteor.absoluteUrl('fire/')}${notif.sealed}`;
|
||||
// const fireHtmlUrl = `<a href="${fireUrl}">${i18n.t('Más información sobre este fuego')}</a>`;
|
||||
// TODO get _id of fire
|
||||
// const fireTextUrl = `${i18n.t('Más información sobre este fuego')}:\n${fireUrl}`;
|
||||
// FIXME use our map as url and static map as img
|
||||
moment.locale(user.lang);
|
||||
// moment user tz ?
|
||||
const message = `${trim(notif.content)} (${i18n.t('fireDetectedAt', { when: dateLongFormat(notif.when) })}).`;
|
||||
|
||||
// TODO Comunes Address
|
||||
|
||||
const emailOpts = {
|
||||
to: emailAddress,
|
||||
// userName: firstName,
|
||||
// sendAt: new Date(),
|
||||
subject: subjectTruncate.apply(message),
|
||||
// text: `${message}\n\n${fireTextUrl}\n\n`,
|
||||
// template: '<body><h2>{{appName}}</h2>{{{html}}}</body>',
|
||||
lang: user.lang,
|
||||
template: 'new-fire',
|
||||
templateVars: {
|
||||
applicationName: i18n.t('AppName'),
|
||||
firstName,
|
||||
message,
|
||||
fireUrl,
|
||||
img,
|
||||
subsUrl: Meteor.absoluteUrl('subscriptions')
|
||||
}
|
||||
};
|
||||
sendEmail(emailOpts).catch((error) => {
|
||||
throw new Meteor.Error('500', `${error}`);
|
||||
});
|
||||
// sendMail(emailOpts, true);
|
||||
Notifications.update(notif._id, { $set: { emailNotified: true, emailNotifiedAt: new Date() } });
|
||||
} else {
|
||||
// Not email or not verified -> remove notif so we don't retry to send it
|
||||
Notifications.remove({ _id: notif._id });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default processNotif;
|
||||
|
|
@ -17,7 +17,7 @@ import i18nOpts from '../common/i18n';
|
|||
|
||||
const detectorOptions = {
|
||||
// order and from where user language should be detected
|
||||
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
|
||||
order: ['querystring', 'navigator', 'cookie', 'localStorage', 'htmlTag'],
|
||||
|
||||
// keys or params to lookup language from
|
||||
lookupQuerystring: 'lng',
|
||||
|
|
@ -61,6 +61,14 @@ if (sendMissing && Meteor.isDevelopment) {
|
|||
};
|
||||
}
|
||||
|
||||
function setT9(lang) {
|
||||
if (lang === 'gl') {
|
||||
T9n.setLanguage('es');
|
||||
} else {
|
||||
T9n.setLanguage(lang);
|
||||
}
|
||||
}
|
||||
|
||||
i18n.use(backend)
|
||||
.use(LngDetector)
|
||||
.use(Cache)
|
||||
|
|
@ -75,7 +83,7 @@ i18n.use(backend)
|
|||
// document.title = t('AppName');
|
||||
// Accounts translation
|
||||
// https://github.com/softwarerero/meteor-accounts-t9n
|
||||
T9n.setLanguage(i18n.language);
|
||||
setT9(i18n.language);
|
||||
// console.log(T9n.get('error.accounts.User not found'));
|
||||
moment.locale(i18n.language);
|
||||
|
||||
|
|
@ -104,7 +112,8 @@ Meteor.subscribe('userData'); // lang is there
|
|||
|
||||
i18n.on('languageChanged', (lng) => {
|
||||
moment.locale(lng);
|
||||
T9n.setLanguage(lng);
|
||||
// TODO fix this when gl is translated
|
||||
setT9(lng);
|
||||
Tracker.autorun((computation) => {
|
||||
if (Meteor.userId()) {
|
||||
// logged
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import moment from 'moment';
|
|||
// Load the js langs
|
||||
import es from 'meteor-accounts-t9n/build/es';
|
||||
import en from 'meteor-accounts-t9n/build/en';
|
||||
// TODO ask for translation of this
|
||||
// import gl from 'meteor-accounts-t9n/build/gl';
|
||||
|
||||
const backOpts = {
|
||||
// path where resources get loaded from
|
||||
|
|
@ -21,7 +23,10 @@ const shouldDebug = (forceDebug && !Meteor.isProduction);
|
|||
const i18nOpts = {
|
||||
backend: backOpts,
|
||||
// lng: 'es',
|
||||
fallbackLng: ['es', 'en'],
|
||||
fallbackLng: {
|
||||
gl: ['es'],
|
||||
default: ['en']
|
||||
},
|
||||
sendMissingTo: 'fallback',
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react!!
|
||||
|
|
@ -35,7 +40,7 @@ const i18nOpts = {
|
|||
return value;
|
||||
}
|
||||
},
|
||||
whitelist: ['es', 'en'], // allowed languages
|
||||
whitelist: ['es', 'en', 'gl'], // allowed languages
|
||||
load: 'languageOnly', // 'es' o 'en', previously: 'all', // es-ES -> es, en-US -> en
|
||||
debug: shouldDebug,
|
||||
ns: 'common',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import '../../api/Users/server/publications';
|
|||
import '../../api/Utility/server/methods';
|
||||
|
||||
import '../../api/ActiveFires/server/publications';
|
||||
import '../../api/ActiveFiresUnion/server/publications';
|
||||
import '../../api/FireAlerts/server/publications';
|
||||
|
||||
import '../../api/Subscriptions/methods';
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { tweetIberiaFires, tweetEuropeFires } from '/imports/api/ActiveFires/server/tweetFiresInZone';
|
||||
import { isMailServerMaster, sendEmailsFromQueue } from '/imports/startup/server/email';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
import processNotif from '/imports/modules/server/notificationsProcess.js';
|
||||
|
||||
// https://github.com/thesaucecode/meteor-synced-cron/
|
||||
|
||||
|
|
@ -44,7 +46,7 @@ Meteor.startup(() => {
|
|||
timezone: 'Europe/Madrid',
|
||||
schedule: (parser) => {
|
||||
// http://bunkat.github.io/later/
|
||||
const sched = parser.text('every 1 min');
|
||||
const sched = parser.text('every 10 min');
|
||||
if (sched.error !== -1) {
|
||||
console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`);
|
||||
}
|
||||
|
|
@ -52,6 +54,30 @@ Meteor.startup(() => {
|
|||
},
|
||||
job: () => sendEmailsFromQueue()
|
||||
});
|
||||
|
||||
SyncedCron.add({
|
||||
name: 'Process pending notif',
|
||||
timezone: 'Europe/Madrid',
|
||||
schedule: (parser) => {
|
||||
// http://bunkat.github.io/later/
|
||||
const sched = parser.text('every 15 min');
|
||||
if (sched.error !== -1) {
|
||||
console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`);
|
||||
}
|
||||
return sched;
|
||||
},
|
||||
job: () => {
|
||||
const mobileNotif = Notifications.find({ nofitied: null, type: 'mobile' });
|
||||
mobileNotif.forEach((notif) => {
|
||||
processNotif(notif);
|
||||
});
|
||||
const emailNotif = Notifications.find({ emailNofitied: null, type: 'web' });
|
||||
emailNotif.forEach((notif) => {
|
||||
processNotif(notif);
|
||||
});
|
||||
return { mobile: mobileNotif.count(), web: emailNotif.count() };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const esEn = Meteor.settings.private.twitter.es.enabled;
|
||||
|
|
|
|||
|
|
@ -17,5 +17,4 @@ import './prerender';
|
|||
import './feedback';
|
||||
import './cron';
|
||||
import './isMaster';
|
||||
import './segfaults';
|
||||
import './rest';
|
||||
|
|
|
|||
19
imports/startup/server/leaflet-workaround.js
Normal file
19
imports/startup/server/leaflet-workaround.js
Normal 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'
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
|||
import Industries from '/imports/api/Industries/Industries';
|
||||
import IndustryRegistries from '/imports/api/Industries/IndustryRegistries';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
|
||||
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
|
||||
Migrations.migrateTo('latest');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,145 +1,19 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import moment from 'moment';
|
||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||
import Notifications from '/imports/api/Notifications/Notifications';
|
||||
// import sendMail from '/imports/startup/server/email';
|
||||
import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email';
|
||||
import processNotif from '/imports/modules/server/notificationsProcess.js';
|
||||
import { isMailServerMaster } from '/imports/startup/server/email';
|
||||
// import { hr } from '/imports/startup/server/email';
|
||||
import getEmailOf from '/imports/modules/get-email-of-user';
|
||||
import image from 'google-maps-image-api-url';
|
||||
import gcm from 'node-gcm';
|
||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
||||
|
||||
Meteor.startup(() => {
|
||||
let validFcmSender = true;
|
||||
|
||||
if (!(Meteor.settings.private.fcmApiToken && Meteor.settings.private.fcmApiToken.length > 0)) {
|
||||
console.warn('Missing settings.private.fcmApiToken key, mobile notifications will not work');
|
||||
validFcmSender = false;
|
||||
}
|
||||
|
||||
const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken);
|
||||
|
||||
// https://www.npmjs.com/package/google-maps-image-api-url
|
||||
// https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email
|
||||
// https://developers.google.com/maps/documentation/static-maps/intro
|
||||
function imgUrl(lat, lng) {
|
||||
return image({
|
||||
key: Meteor.settings.gmaps.key,
|
||||
type: 'staticmap',
|
||||
center: `${lat},${lng}`,
|
||||
size: '640x480',
|
||||
zoom: 16,
|
||||
maptype: 'hybrid',
|
||||
language: 'es',
|
||||
markers: `icon: ${Meteor.settings.private.fireIconUrl}|${lat},${lng}`
|
||||
if (isMailServerMaster) {
|
||||
Notifications.find().observe({
|
||||
added: function notifAdded(notif) {
|
||||
processNotif(notif);
|
||||
},
|
||||
changed: function notifChanged(updatedNotif) { // , oldNotif) {
|
||||
processNotif(updatedNotif);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
function imgEl(lat, lng) {
|
||||
return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
|
||||
} */
|
||||
|
||||
function process(notif) {
|
||||
if (isMailServerMaster && notif.type === 'mobile' && !notif.notified) {
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
moment.locale(user.lang);
|
||||
// duplicate code below
|
||||
const body = `${trim(notif.content)}`;
|
||||
|
||||
// https://firebase.google.com/docs/cloud-messaging/concept-options
|
||||
const msg = new gcm.Message();
|
||||
|
||||
msg.addNotification({
|
||||
title: i18n.t('Alerta de fuego'),
|
||||
body,
|
||||
click_action: 'FLUTTER_NOTIFICATION_CLICK',
|
||||
tag: notif._id, // prevent duplication of fire notifications
|
||||
sound: 'default', // Indicates sound to be played. Supports only default currently.
|
||||
icon: 'launch_image' // 'ic_launcher'
|
||||
});
|
||||
|
||||
msg.addData('id', notif._id._str);
|
||||
msg.addData('description', body);
|
||||
msg.addData('lat', notif.geo.coordinates[1]);
|
||||
msg.addData('lon', notif.geo.coordinates[0]);
|
||||
msg.addData('when', notif.when);
|
||||
msg.addData('subsId', notif.subsId._str);
|
||||
msg.addData('sealed', notif.sealed);
|
||||
|
||||
const registrationTokens = [];
|
||||
if (!user.fireBaseToken) {
|
||||
console.warn('This mobile user doesn\'t have a firebase registration token');
|
||||
} else {
|
||||
registrationTokens.push(user.fireBaseToken);
|
||||
// FIXME: better join users
|
||||
if (validFcmSender) {
|
||||
fcmSender.send(msg, { registrationTokens }, Meteor.bindEnvironment(function processResult(err, response) {
|
||||
if (err) {
|
||||
console.error(`FCM error: ${err}`);
|
||||
} else {
|
||||
// console.log(`FCM response: ${response}`);
|
||||
Notifications.update(notif._id, { $set: { notified: true, notifiedAt: new Date() } });
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else if (notif.type === 'web' && !notif.emailNotified) {
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
const { firstName, emailAddress } = getEmailOf(user);
|
||||
|
||||
if (emailAddress) {
|
||||
const img = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
// const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]);
|
||||
const fireUrl = `${Meteor.absoluteUrl('fire/')}${notif.sealed}`;
|
||||
// const fireHtmlUrl = `<a href="${fireUrl}">${i18n.t('Más información sobre este fuego')}</a>`;
|
||||
// TODO get _id of fire
|
||||
// const fireTextUrl = `${i18n.t('Más información sobre este fuego')}:\n${fireUrl}`;
|
||||
// FIXME use our map as url and static map as img
|
||||
moment.locale(user.lang);
|
||||
// moment user tz ?
|
||||
const message = `${trim(notif.content)} (${i18n.t('fireDetectedAt', { when: dateLongFormat(notif.when) })}).`;
|
||||
|
||||
// TODO Comunes Address
|
||||
|
||||
const emailOpts = {
|
||||
to: emailAddress,
|
||||
// userName: firstName,
|
||||
// sendAt: new Date(),
|
||||
subject: subjectTruncate.apply(message),
|
||||
// text: `${message}\n\n${fireTextUrl}\n\n`,
|
||||
// template: '<body><h2>{{appName}}</h2>{{{html}}}</body>',
|
||||
lang: user.lang,
|
||||
template: 'new-fire',
|
||||
templateVars: {
|
||||
applicationName: i18n.t('AppName'),
|
||||
firstName,
|
||||
message,
|
||||
fireUrl,
|
||||
img,
|
||||
subsUrl: Meteor.absoluteUrl('subscriptions')
|
||||
}
|
||||
};
|
||||
sendEmail(emailOpts).catch((error) => {
|
||||
throw new Meteor.Error('500', `${error}`);
|
||||
});
|
||||
// sendMail(emailOpts, true);
|
||||
Notifications.update(notif._id, { $set: { emailNotified: true, emailNotifiedAt: new Date() } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Notifications.find().observe({
|
||||
added: function notifAdded(notif) {
|
||||
process(notif);
|
||||
},
|
||||
changed: function notifChanged(updatedNotif, oldNotif) {
|
||||
process(updatedNotif);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
import Fires from '/imports/api/Fires/Fires';
|
||||
// import Comments from '/imports/api/Comments/Comments';
|
||||
|
||||
const firesMapEnabled = false;
|
||||
|
||||
sitemaps.add('/sitemap.xml', () => {
|
||||
const today = new Date();
|
||||
|
||||
|
|
@ -20,21 +22,23 @@ sitemaps.add('/sitemap.xml', () => {
|
|||
|
||||
// When user has public page
|
||||
/* const users = Meteor.users.find().fetch();
|
||||
_.each(users, function(user) {
|
||||
out.push({
|
||||
page: '/persona/' + user.username,
|
||||
lastmod: user.updatedAt
|
||||
});
|
||||
_.each(users, function(user) {
|
||||
out.push({
|
||||
page: '/persona/' + user.username,
|
||||
lastmod: user.updatedAt
|
||||
});
|
||||
}); */
|
||||
|
||||
Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => {
|
||||
// Search the last comment of tha fire
|
||||
const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } });
|
||||
out.push({
|
||||
page: `/fire/archive/${fire._id._str}`,
|
||||
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt
|
||||
if (firesMapEnabled) {
|
||||
Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => {
|
||||
// Search the last comment of tha fire
|
||||
const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } });
|
||||
out.push({
|
||||
page: `/fire/archive/${fire._id._str}`,
|
||||
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { Meteor } from 'meteor/meteor';
|
|||
import Subscriptions from '/imports/api/Subscriptions/Subscriptions';
|
||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||
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 { isMailServerMaster } from '/imports/startup/server/email';
|
||||
|
||||
|
|
@ -35,9 +36,8 @@ Meteor.startup(() => {
|
|||
const noNoisy = sub => sub;
|
||||
|
||||
const process = (isPublic) => {
|
||||
const group = new L.FeatureGroup();
|
||||
const subscribers = Subscriptions.find().fetch();
|
||||
const result = calcUnion(subscribers, group, isPublic ? addNoisy : noNoisy);
|
||||
const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true);
|
||||
const union = result[0];
|
||||
const bounds = result[1];
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class DefMapLayers extends Component {
|
|||
<TileLayer
|
||||
opacity={defOpacity}
|
||||
attribution="© <a href="http://osm.org/copyright">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>);
|
||||
const osmlayer = (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Circle } from 'react-leaflet';
|
||||
import { GeoJSON } from 'react-leaflet';
|
||||
import PropTypes from 'prop-types';
|
||||
import { rectangleAround } from 'map-common-utils';
|
||||
import { onMarkClick } from './MarkListeners';
|
||||
import FirePopup from './FirePopup';
|
||||
|
||||
|
||||
class FireCircleMark extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -22,22 +22,26 @@ class FireCircleMark extends Component {
|
|||
lat,
|
||||
lon,
|
||||
scan,
|
||||
track,
|
||||
nasa,
|
||||
id,
|
||||
history,
|
||||
when,
|
||||
t
|
||||
} = this.props;
|
||||
const rect = rectangleAround({ lat, lon }, track, track);
|
||||
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} />
|
||||
</Circle>
|
||||
</GeoJSON>
|
||||
);
|
||||
/* <Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}> */
|
||||
}
|
||||
}
|
||||
|
||||
FireCircleMark.propTypes = {
|
||||
scan: PropTypes.number.isRequired,
|
||||
track: PropTypes.number.isRequired,
|
||||
lat: PropTypes.number.isRequired,
|
||||
lon: PropTypes.number.isRequired,
|
||||
nasa: PropTypes.bool.isRequired,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import React, { Component, Fragment } from 'react';
|
|||
import { CircleMarker, Marker, Tooltip } from 'react-leaflet';
|
||||
import PropTypes from 'prop-types';
|
||||
import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons';
|
||||
import { onMarkClick } from './MarkListeners';
|
||||
import { translate } from 'react-i18next';
|
||||
import { onMarkClick } from './MarkListeners';
|
||||
import FirePopup from './FirePopup';
|
||||
|
||||
class FireIconMark extends Component {
|
||||
|
|
@ -30,17 +30,7 @@ class FireIconMark extends Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
lat,
|
||||
lon,
|
||||
scan,
|
||||
nasa,
|
||||
id,
|
||||
history,
|
||||
falsePositives,
|
||||
industries,
|
||||
neighbour,
|
||||
when,
|
||||
t
|
||||
lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -79,6 +69,7 @@ FireIconMark.propTypes = {
|
|||
lat: PropTypes.number.isRequired,
|
||||
lon: PropTypes.number.isRequired,
|
||||
scan: PropTypes.number,
|
||||
track: PropTypes.number,
|
||||
nasa: PropTypes.bool,
|
||||
falsePositives: PropTypes.bool.isRequired,
|
||||
industries: PropTypes.bool.isRequired,
|
||||
|
|
|
|||
22
imports/ui/components/Maps/FireListUnion.js
Normal file
22
imports/ui/components/Maps/FireListUnion.js
Normal 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
|
||||
};
|
||||
|
|
@ -3,31 +3,32 @@
|
|||
import React from 'react';
|
||||
import { CircleMarker } from 'react-leaflet';
|
||||
import PropTypes from 'prop-types';
|
||||
import FirePopup from './FirePopup';
|
||||
import { translate } from 'react-i18next';
|
||||
import FirePopup from './FirePopup';
|
||||
|
||||
/* Less acurate (1 pixel per fire) but faster */
|
||||
const FirePixel = ({
|
||||
lat, lon, nasa, id, when, t, history
|
||||
lat, lon, nasa, id, when, t, history, track
|
||||
}) => (
|
||||
<CircleMarker
|
||||
center={[lat, lon]}
|
||||
color={nasa ? 'red' : '#D35400'}
|
||||
stroke={false}
|
||||
fillOpacity="1"
|
||||
fill
|
||||
radius={nasa ? 1 : 2}
|
||||
center={[lat, lon]}
|
||||
color={nasa ? 'red' : '#D35400'}
|
||||
stroke={false}
|
||||
fillOpacity="1"
|
||||
fill
|
||||
radius={nasa ? 1 : 2}
|
||||
>
|
||||
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
|
||||
</CircleMarker>
|
||||
);
|
||||
|
||||
|
||||
FirePixel.propTypes = {
|
||||
lat: PropTypes.number.isRequired,
|
||||
lon: PropTypes.number.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
scan: PropTypes.number,
|
||||
track: PropTypes.number,
|
||||
when: PropTypes.instanceOf(Date),
|
||||
nasa: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
|
|
|
|||
48
imports/ui/components/Maps/FirePolygonMark.js
Normal file
48
imports/ui/components/Maps/FirePolygonMark.js
Normal 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;
|
||||
|
|
@ -33,8 +33,7 @@ const subsUnion = (union, options) => {
|
|||
options.map.leafletElement.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest));
|
||||
}
|
||||
} else if (options.subs.length > 0) {
|
||||
const unionGroup = new L.FeatureGroup();
|
||||
const result = calcUnion(options.subs, unionGroup, sub => sub);
|
||||
const result = calcUnion(L, options.subs, sub => sub, true);
|
||||
const unionJson = result[0];
|
||||
const bounds = result[1];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +1,9 @@
|
|||
import { check } from 'meteor/check';
|
||||
import LGeo from 'leaflet-geodesy';
|
||||
import tunion from '@turf/union';
|
||||
import ttrunc from '@turf/truncate';
|
||||
import { calcUnion as calcUnionWoBounds } from 'map-common-utils';
|
||||
|
||||
// https://stackoverflow.com/questions/35394577/leaflet-js-union-merge-circles
|
||||
const truncOptions = { precision: 6, coordinates: 2 };
|
||||
|
||||
const unify = (polyList) => {
|
||||
let unionTemp;
|
||||
for (let i = 0; i < polyList.length; i += 1) {
|
||||
const pol = polyList[i].toGeoJSON();
|
||||
const cleanPol = ttrunc(pol, truncOptions);
|
||||
if (i === 0) {
|
||||
unionTemp = cleanPol;
|
||||
} else {
|
||||
unionTemp = ttrunc(tunion(unionTemp, cleanPol), truncOptions);
|
||||
}
|
||||
}
|
||||
return unionTemp;
|
||||
};
|
||||
|
||||
const calcUnion = (subs, group, decorated) => {
|
||||
const unionGroup = group;
|
||||
const copts = {
|
||||
parts: 144
|
||||
};
|
||||
subs.forEach((osub) => {
|
||||
try {
|
||||
if (osub.location && osub.location.lat && osub.location.lon && osub.distance) {
|
||||
check(osub.location.lon, Number);
|
||||
check(osub.location.lat, Number);
|
||||
check(osub.distance, Number);
|
||||
const dsub = decorated(osub);
|
||||
const circle = LGeo.circle([dsub.location.lat, dsub.location.lon], dsub.distance * 1000, copts);
|
||||
circle.addTo(unionGroup);
|
||||
} else {
|
||||
console.info(`Wrong subscription ${JSON.stringify(osub)}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e, `Wrong subscription trying to make union ${JSON.stringify(osub)}`);
|
||||
}
|
||||
});
|
||||
const unionJson = unify(unionGroup.getLayers());
|
||||
return [unionJson, unionGroup.getBounds()];
|
||||
const calcUnion = (L, subs, decorated, typeCircle) => {
|
||||
const unionJson = calcUnionWoBounds(subs, decorated, typeCircle);
|
||||
// return [unionJson, L.geoJSON(unionJson).getBounds()];
|
||||
return [unionJson, unionJson === null ? null : L.geoJSON(unionJson).getBounds()];
|
||||
};
|
||||
|
||||
export default calcUnion;
|
||||
|
|
|
|||
|
|
@ -98,66 +98,66 @@ const App = props => (
|
|||
/* https://react.i18next.com/components/i18nextprovider.html */
|
||||
<div>
|
||||
{props.i18nReady.get() &&
|
||||
<ErrorBoundary>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ErrorBoundary appName={i18n.t('AppNameFull')} title={i18n.t('general-error-title')} subTitle={i18n.t('general-error-description')}>
|
||||
<Router history={history}>
|
||||
<LocationListener>
|
||||
{ !props.loading &&
|
||||
<div className="App">
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<title>{i18n.t('AppName')}</title>
|
||||
<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://fuegos.comunes.org${history.location.pathname}`} hrefLang="es" />
|
||||
</Helmet>
|
||||
<Navigation {...props} />
|
||||
<ReSendEmail {...props} />
|
||||
<ErrorBoundary>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ErrorBoundary appName={i18n.t('AppNameFull')} title={i18n.t('general-error-title')} subTitle={i18n.t('general-error-description')}>
|
||||
<Router history={history}>
|
||||
<LocationListener>
|
||||
{ !props.loading &&
|
||||
<div className="App">
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<title>{i18n.t('AppName')}</title>
|
||||
<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://fuegos.comunes.org${history.location.pathname}`} hrefLang="es" />
|
||||
</Helmet>
|
||||
<Navigation {...props} />
|
||||
<ReSendEmail {...props} />
|
||||
|
||||
<Grid>
|
||||
<Switch>
|
||||
<Route exact name="index" path="/" component={Index} />
|
||||
<Authenticated exact path="/subscriptions" component={Subscriptions} {...props} />
|
||||
<Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} />
|
||||
<Authenticated exact path="/profile" component={Profile} {...props} />
|
||||
<Authenticated exact path="/status" component={Status} {...props} />
|
||||
<Route path="/fires" component={FiresMap} industries={false} {...props} />
|
||||
<Route path="/zones" component={ZonesMap} {...props} />
|
||||
<Grid>
|
||||
<Switch>
|
||||
<Route exact name="index" path="/" component={Index} />
|
||||
<Authenticated exact path="/subscriptions" component={Subscriptions} {...props} />
|
||||
<Authenticated exact path="/subscriptions/new" component={NewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id" component={ViewSubscription} {...props} />
|
||||
<Authenticated exact path="/subscriptions/:_id/edit" component={EditSubscription} {...props} />
|
||||
<Authenticated exact path="/profile" component={Profile} {...props} />
|
||||
<Authenticated exact path="/status" component={Status} {...props} />
|
||||
<Route path="/fires" component={FiresMap} industries={false} {...props} />
|
||||
<Route path="/zones" component={ZonesMap} {...props} />
|
||||
|
||||
<Route path="/fire/:type(active|archive|alert)/:id" component={Fires} {...props} />
|
||||
<Route path="/fire/:id" component={Fires} {...props} />
|
||||
<Public path="/auth/:token" component={Auth} {...props} />
|
||||
<Public path="/signup" component={Signup} {...props} />
|
||||
<Public path="/login" component={Login} {...props} />
|
||||
<Route path="/logout" component={Logout} {...props} />
|
||||
<Route path="/sandbox" component={Sandbox} {...props} />
|
||||
<Route path="/error" component={TestError} {...props} />
|
||||
<Route path="/fire/:type(active|archive|alert)/:id" component={Fires} {...props} />
|
||||
<Route path="/fire/:id" component={Fires} {...props} />
|
||||
<Public path="/auth/:token" component={Auth} {...props} />
|
||||
<Public path="/signup" component={Signup} {...props} />
|
||||
<Public path="/login" component={Login} {...props} />
|
||||
<Route path="/logout" component={Logout} {...props} />
|
||||
<Route path="/sandbox" component={Sandbox} {...props} />
|
||||
<Route path="/error" component={TestError} {...props} />
|
||||
|
||||
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
|
||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
|
||||
<Route name="terms" path="/terms" component={Terms} />
|
||||
<Route name="privacy" path="/privacy" component={Privacy} />
|
||||
<Route name="license" path="/license" component={License} />
|
||||
<Route name="credits" path="/credits" component={Credits} />
|
||||
<Route name="about" path="/about" component={About} />
|
||||
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
|
||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
|
||||
<Route name="terms" path="/terms" component={Terms} />
|
||||
<Route name="privacy" path="/privacy" component={Privacy} />
|
||||
<Route name="license" path="/license" component={License} />
|
||||
<Route name="credits" path="/credits" component={Credits} />
|
||||
<Route name="about" path="/about" component={About} />
|
||||
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Grid>
|
||||
<Footer />
|
||||
<Reconnect {...props} />
|
||||
<Feedback {...props} />
|
||||
{props.i18nReady.get() && <Blaze template="cookieConsent" /> }
|
||||
</div>}
|
||||
</LocationListener>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
</I18nextProvider>
|
||||
</ErrorBoundary> }
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Grid>
|
||||
<Footer />
|
||||
<Reconnect {...props} />
|
||||
<Feedback {...props} />
|
||||
{props.i18nReady.get() && <Blaze template="cookieConsent" /> }
|
||||
</div>}
|
||||
</LocationListener>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
</I18nextProvider>
|
||||
</ErrorBoundary> }
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -186,7 +186,9 @@ export default withTracker(() => {
|
|||
const loading = !Roles.subscription.ready() || !i18nReady.get();
|
||||
const name = user && user.profile && user.profile.name && getUserName(user.profile.name);
|
||||
const emailAddress = user && user.emails && user.emails[0].address;
|
||||
// console.log(`i18n ready?: ${i18nReady.get()}`);
|
||||
Meteor.autorun(() => {
|
||||
console.log(`i18n ready?: ${i18nReady.get()}`);
|
||||
});
|
||||
return {
|
||||
loading,
|
||||
loggingIn,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import { Row, Col, Alert, FormGroup } from 'react-bootstrap';
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
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 DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
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 el {{when}}', { when: this.dateLongFormat });
|
||||
}
|
||||
|
||||
return (fire && !loading ? (
|
||||
const ready = fire && !loading;
|
||||
const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null;
|
||||
return (ready ? (
|
||||
<div className="ViewFire">
|
||||
<Helmet>
|
||||
<title>{t('AppName')}: {t('Información adicional sobre fuego')}</title>
|
||||
|
|
@ -137,14 +139,13 @@ class Fire extends React.Component {
|
|||
zoom={13}
|
||||
>
|
||||
<Fragment>
|
||||
<Circle
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
fillColor="red"
|
||||
fillOpacity={0.0}
|
||||
interactive={false}
|
||||
radius={fire.scan ? fire.scan * 500 : 300}
|
||||
<GeoJSON
|
||||
ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }}
|
||||
data={rect}
|
||||
color="red"
|
||||
stroke
|
||||
width="1"
|
||||
fillOpacity="0.0"
|
||||
/>
|
||||
</Fragment>
|
||||
<FireList
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ import 'leaflet-sleep/Leaflet.Sleep.js';
|
|||
import geolocation from '/imports/startup/client/geolocation';
|
||||
import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition';
|
||||
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 DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||
import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion';
|
||||
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
||||
import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries';
|
||||
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) {
|
||||
const notMoving = !nextState.moving;
|
||||
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) 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(`Fire alerts total: ${this.props.firealerts.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">
|
||||
{ (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="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>
|
||||
{isNotHomeAndMobile() && this.props.firealerts.length > 0 &&
|
||||
|
|
@ -307,6 +297,14 @@ class FiresMap extends React.Component {
|
|||
neighbour={false}
|
||||
industries={false}
|
||||
/>
|
||||
{ Meteor.isDevelopment &&
|
||||
<FireListUnion
|
||||
t={t}
|
||||
history={this.props.history}
|
||||
firesUnion={this.props.activefiresunion}
|
||||
nasa
|
||||
/>
|
||||
}
|
||||
<FireList
|
||||
t={t}
|
||||
history={this.props.history}
|
||||
|
|
@ -369,12 +367,14 @@ FiresMap.propTypes = {
|
|||
userSubs: PropTypes.string,
|
||||
userSubsBounds: PropTypes.string,
|
||||
activefires: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
activefiresunion: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
lastFireDetected: PropTypes.object,
|
||||
firealerts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
falsePositives: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
industries: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
lastCheck: PropTypes.instanceOf(Date),
|
||||
activefirestotal: PropTypes.number.isRequired,
|
||||
activefiresuniontotal: PropTypes.number.isRequired,
|
||||
center: PropTypes.arrayOf(PropTypes.number),
|
||||
zoom: PropTypes.number,
|
||||
marks: PropTypes.bool.isRequired,
|
||||
|
|
@ -391,6 +391,7 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
// console.log(`With industries: ${withIndustries}`);
|
||||
|
||||
let subscription;
|
||||
let subscriptionUnion;
|
||||
let alertSubscription;
|
||||
|
||||
const centerStored = store.get('firesmap_center');
|
||||
|
|
@ -422,6 +423,14 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
mapSize.get()[1].lat,
|
||||
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(
|
||||
'fireAlerts',
|
||||
mapSize.get()[0].lng,
|
||||
|
|
@ -442,6 +451,7 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
});
|
||||
|
||||
Meteor.subscribe('activefirestotal');
|
||||
Meteor.subscribe('activefiresuniontotal');
|
||||
|
||||
const settingsSubs = Meteor.subscribe('settings');
|
||||
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 } });
|
||||
|
||||
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,
|
||||
userSubsBounds: userSubs ? userSubsBounds.value : null,
|
||||
subsready: settingsSubs.ready(),
|
||||
// Not reactive query depending on zoom level
|
||||
activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
|
||||
activefiresunion: ActiveFiresUnion.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(),
|
||||
activefirestotal: Counter.get('countActiveFires') + fireAlerts.length,
|
||||
activefiresuniontotal: Counter.get('countActiveFiresUnion') + fireAlerts.length,
|
||||
firealerts: fireAlerts,
|
||||
falsePositives,
|
||||
industries,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
|
|||
// import { Link } from 'react-router-dom';
|
||||
// https://www.npmjs.com/package/react-resize-detector
|
||||
import ReactResizeDetector from 'react-resize-detector';
|
||||
import MobileStoreButton from 'react-mobile-store-button';
|
||||
import _ from 'lodash';
|
||||
import 'html5-device-mockups/dist/device-mockups.min.css';
|
||||
import 'bootstrap-carousel-swipe/carousel-swipe';
|
||||
|
|
@ -20,7 +21,6 @@ import SubscriptionsMap from '/imports/ui/pages/Subscriptions/SubscriptionsMap';
|
|||
import ShareIt from '/imports/ui/components/ShareIt/ShareIt';
|
||||
import getFallbackLang from '/imports/modules/lang-fallback';
|
||||
import FiresMap from '../FiresMap/FiresMap';
|
||||
|
||||
import './Index.scss';
|
||||
import './Index-custom.scss';
|
||||
|
||||
|
|
@ -102,6 +102,7 @@ class Index extends Component {
|
|||
render() {
|
||||
const { t } = this.props;
|
||||
const title = `${t('AppName')}: ${t('Inicio')}`;
|
||||
const androidUrl = 'https://play.google.com/store/apps/details?id=org.comunes.fires';
|
||||
return (
|
||||
<div className="IndexDisabled full-width">
|
||||
<Helmet>
|
||||
|
|
@ -181,6 +182,9 @@ class Index extends Component {
|
|||
</div>
|
||||
<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> */}
|
||||
<div className="android-btn">
|
||||
<MobileStoreButton store="android" url={androidUrl} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -77,3 +77,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.android-btn > div {
|
||||
height: 50px !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,15 +9,17 @@ import { Meteor } from 'meteor/meteor';
|
|||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
import { testId } from '/imports/ui/components/Utils/TestUtils';
|
||||
import Col from '../../components/Col/Col';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import InputHint from '../../components/InputHint/InputHint';
|
||||
import validate from '../../../modules/validate';
|
||||
import { translate } from 'react-i18next';
|
||||
import { T9n } from 'meteor-accounts-t9n';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Col from '../../components/Col/Col';
|
||||
import InputHint from '../../components/InputHint/InputHint';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Profile.scss';
|
||||
|
||||
// List of languages: https://github.com/i18next/i18next/issues/1068
|
||||
|
||||
class Profile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -155,6 +157,7 @@ class Profile extends React.Component {
|
|||
|
||||
renderPasswordUser(loading, user) {
|
||||
const { t, i18n } = this.props;
|
||||
const enabledLangs = ['en', 'es', 'gl'];
|
||||
const langName = {
|
||||
en: 'English', es: 'Español', gl: 'Galego', ast: 'Asturianu', ca: 'Català'
|
||||
};
|
||||
|
|
@ -202,7 +205,7 @@ class Profile extends React.Component {
|
|||
{langName[i18n.language]}
|
||||
</button>
|
||||
<div className="dropdown-menu">
|
||||
{i18n.languages.map(lang => (
|
||||
{enabledLangs.map(lang => (
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => this.onLangSelect(lang)}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ class SubscriptionsMap extends React.Component {
|
|||
|
||||
handleLeafletLoad(map) {
|
||||
// console.log('Map loading');
|
||||
// console.log(map);
|
||||
if (map) {
|
||||
if (map && this.props.userSubs !== 'null') {
|
||||
// console.log(`Union of ${this.props.userSubs}`);
|
||||
this.state.union = subsUnion(this.state.union, {
|
||||
map,
|
||||
subs: this.props.userSubs,
|
||||
|
|
|
|||
1240
package-lock.json
generated
1240
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
|
@ -2,7 +2,7 @@
|
|||
"name": "tcef",
|
||||
"private": true,
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -10,8 +10,6 @@
|
|||
"@cleverbeagle/dates": "^0.5.1",
|
||||
"@cleverbeagle/seeder": "^1.3.1",
|
||||
"@turf/buffer": "^5.1.5",
|
||||
"@turf/truncate": "^5.1.5",
|
||||
"@turf/union": "^5.1.5",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"bcrypt": "^1.0.3",
|
||||
"bootstrap-carousel-swipe": "0.0.6",
|
||||
|
|
@ -40,12 +38,11 @@
|
|||
"jsend": "^1.0.2",
|
||||
"juice": "^4.2.2",
|
||||
"leaflet": "^1.3.1",
|
||||
"leaflet-geodesy": "^0.2.1",
|
||||
"leaflet-graphicscale": "0.0.2",
|
||||
"leaflet-headless": "^0.2.6",
|
||||
"leaflet-sleep": "^0.5.1",
|
||||
"lodash": "^4.17.4",
|
||||
"loms.perlin": "^1.0.1",
|
||||
"map-common-utils": "^0.5.0",
|
||||
"maxmind": "^2.3.0",
|
||||
"meteor-accounts-t9n": "^2.0.3",
|
||||
"meteor-node-stubs": "^0.3.2",
|
||||
|
|
@ -76,6 +73,7 @@
|
|||
"react-leaflet-fullscreen": "0.0.6",
|
||||
"react-leaflet-google": "^3.2.1",
|
||||
"react-leaflet-sidebarv2": "^0.5.1",
|
||||
"react-mobile-store-button": "0.0.3",
|
||||
"react-places-autocomplete": "^5.4.3",
|
||||
"react-progress-bar.js": "^0.2.3",
|
||||
"react-resize-detector": "^1.1.0",
|
||||
|
|
@ -84,7 +82,6 @@
|
|||
"react-router-hash-link": "^1.1.1",
|
||||
"react-share": "^2.0.0",
|
||||
"reactstrap": "^5.0.0-alpha.3",
|
||||
"segfault-handler": "^1.0.0",
|
||||
"simpl-schema": "^1.5.0",
|
||||
"simple-line-icons": "^2.4.1",
|
||||
"store": "^2.0.12",
|
||||
|
|
@ -161,7 +158,8 @@
|
|||
"_ensureIndex",
|
||||
"_verifyEmailToken",
|
||||
"_resetPasswordToken",
|
||||
"_name"
|
||||
"_name",
|
||||
"_str"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 3346b6db70c153b1dd66e6a16a0ad28f10ebf740
|
||||
Subproject commit 4761da921f467838a2c01b34e84a7703ee49b12c
|
||||
|
|
@ -1 +1,223 @@
|
|||
{}
|
||||
{
|
||||
"fireDetected": "lume detectado {{when}}",
|
||||
"fireDetectedAt": "lume detectado o {{when}}",
|
||||
"Sí": "Si",
|
||||
"No": "Non",
|
||||
"AppName": "Tod@s contra o Lume",
|
||||
"AppNameFull": "Tod@s contra o Lume!",
|
||||
"AppDescrip": "Colaboración masiva contra os incendios",
|
||||
"AppDescripLong": "Empregamos diferentes fontes de datos para notificarche os lumes activos nas túas zonas de interese",
|
||||
"OrgName": "Comúns",
|
||||
"OrgNameFull": "Asociación Comúns",
|
||||
"Privacidad": "Privacidade",
|
||||
"Términos": "Termos",
|
||||
"Términos de Servicio": "Termos do Servizo",
|
||||
"Inicio": "Inicio",
|
||||
"Licencia": "Licencia",
|
||||
"Cerrar sesión": "Pechar sesión",
|
||||
"Registrarse": "Rexistrarse",
|
||||
"Regístrate": "Rexístrate",
|
||||
"Iniciar sesión": "Iniciar sesión",
|
||||
"o regístrate con un correo": "ou rexístrate cun correo",
|
||||
"o con un correo": "ou cun correo",
|
||||
"Nombre": "Nome",
|
||||
"Apellidos": "Apelidos",
|
||||
"Correo electrónico": "Correo electrónico",
|
||||
"Contraseña": "Contrasinal",
|
||||
"¿Ya tienes un cuenta?": "Xa tes unha conta?",
|
||||
"Usa al menos seis caracteres.": "Emprega polo menos seis caracteres.",
|
||||
"Iniciar sesión con Google": "Iniciar sesión con Google",
|
||||
"¿Olvidaste tu contraseña?": "Esqueciches o teu contrasinal?",
|
||||
"¿No tienes una cuenta?": "Non tes unha conta?",
|
||||
"Introduce tu correo abajo para recibir un enlace para resetear tu contraseña.": "Introduce o teu correo abaixo para recibir un enlace para resetear o teu contrasinal.",
|
||||
"Recupera tu contraseña": "Recupera o teu contrasinal",
|
||||
"¿Recuerdas tu contraseña?": "Lembras o teu contrasinal?",
|
||||
"Necesitamos un correo aquí.": "Necesitamos un correo aquí.",
|
||||
"¿Es este correo correcto?": "É iste correo correcto?",
|
||||
"Necesitamos una contraseña aquí.": "Necesitamos un contrasinal aquí.",
|
||||
"Bienvenid@ de nuevo": "Benvid@ de novo",
|
||||
"Guardar perfíl": "Gardar perfil",
|
||||
"Contraseña actual": "Contrasinal actual",
|
||||
"Nueva contraseña": "Novo contrasinal",
|
||||
"Editar perfíl": "Editar perfil",
|
||||
"Editar perfíl en": "Editar perfil en",
|
||||
"¡Perfíl actualizado!": "Perfil actualizado!",
|
||||
"¿Cuál es tu nombre?": "Cal é o teu nome?",
|
||||
"¿Cuál es tu apellido?": "Cal é o teu apelido?",
|
||||
"Necesito tu contraseña si la quieres cambiar.": "Necesito o teu contrasinal se o queres cambiar.",
|
||||
"Necesito tu nueva contraseña si la quieres cambiar.": "Necesito o teu novo contrasinal se o queres cambiar.",
|
||||
"¿Es correcto este correo?": "É correcto iste correo?",
|
||||
"verifyEmail": "Ei! pódesnos verificar o teu correo electrónico ({{email}})?",
|
||||
"Reenviar email de verificación": "Reenviar email de verificación",
|
||||
"checkVerificationEmail": "Comproba no teu correo {{email}} o enlace de verificación!",
|
||||
"checkResetEmail": "Comproba no teu correo {{email}} o enlace de reseteo!",
|
||||
"¡Listo, gracias!": "Listo, grazas!",
|
||||
"Por favor, inténtalo otra vez.": "Por favor, téntao de novo.",
|
||||
"Verificando...": "Verificando...",
|
||||
"Introduce una nueva contraseña, por favor.": "Introduce un novo contrasinal, por favor.",
|
||||
"Repite tu nueva contraseña, por favor.": "Repite o teu novo contrasinal, por favor.",
|
||||
"Mmmm, tus contraseñas no coinciden. Inténtalo otra vez": "Mmmm, os teus contrasinais non coinciden. Téntao de novo",
|
||||
"Para resetear tu contraseña, introduce una nueva debajo. Iniciarás la sesión con la nueva contraseña.": "Para resetear o teu contrasinal, introduce un novo debaixo. Iniciarás a sesión co novo contrasinal.",
|
||||
"Resetea tu contraseña": "Resetea o teu contrasinal",
|
||||
"Repite la nueva contraseña": "Repite o novo contrasinal",
|
||||
"Resetea la contraseña y entra": "Resetea o contrasinal e entra",
|
||||
"Uso de Cookies": "Emprego de Cookies",
|
||||
"Utilizamos cookies para asegurar un mejor uso de nuestra web. Si continúas navegando, consideramos que aceptas su uso": "Empregamos cookies para asegurar un mellor uso da nosa web. Se continúas a navegar, consideramos que aceptas o seu uso",
|
||||
"Participa": "Participa",
|
||||
"activeFires": "Lumes activos",
|
||||
"Fuegos activos": "Lumes activos",
|
||||
"noActiveFireInMapCount": "Non hai lumes activos nesta zona do mapa. <1><0>{{countTotal}}</0></1> lumes activos no mundo.",
|
||||
"activeFireInMapCount": "En vermello, <1><0>{{count,number}}</0></1> lumes activos. <3><0>{{countTotal,number}}</0></3> lumes activos no mundo.",
|
||||
"activeNeigFireInMapCount": "En laranxa, os lumes notificados polas/os nosas/os usuarias/os recentemente.",
|
||||
"Centrar en tu ubicación": "Centrar na túa ubicación",
|
||||
"Resaltar en verde el área vigilada por nuestros usuarios/as": "Resaltar en verde a área vixiada por as/os nosas/os usuarias/os",
|
||||
"Resaltar los fuegos con un marcador": "Resaltar os lumes cun marcador",
|
||||
"mapPrivacy": "<0>Para preservar a privacidade das/os nosas/os usuarias/os, os datos reflectidos están aleatoriamente alterados e son só orientativos.</0>",
|
||||
"Nuevas notificaciones de {{app}}": "Novas notificacións de {{app}}",
|
||||
"Imágenes capturadas por los satélites de la NASA muestran el humo de grandes incendios que se extienden sobre el Océano Pacífico. La actividad del fuego está delineada en rojo.": "Imaxes capturadas polos satélites da NASA amosan o fume de grandes incendios que se estenden sobre o Océano Pacífico. A actividade do lume esta deliñada en vermello.",
|
||||
"Todavía sin subscriptiones": "Aínda sen subscricións",
|
||||
"Suscripción añadida": "Suscrición engadida",
|
||||
"Suscripción actualizada": "Suscrición actualizada",
|
||||
"Desuscrito": "Desuscrita",
|
||||
"Mis zonas": "As miñas zonas",
|
||||
"Nueva zona": "Nova zona",
|
||||
"Suscribirme a fuegos en este radio": "Suscribirme a lumes neste radio",
|
||||
"Suscribirme a este radio": "Suscribirme a iste radio",
|
||||
"Créditos": "Créditos",
|
||||
"Escribe aquí un lugar": "Escribe aquí un lugar",
|
||||
"Indícanos la posición de la zona a vigilar (por ej. tu pueblo, una calle, etc):": "Indícanos a posición da zona a vixiar (por ex. a túa aldea, unha rúa, etc):",
|
||||
"También puedes seleccionar la zona en el mapa arrastrando el puntero naranja.": "Tamén podes seleccionar a zona no mapa arrastrando o punteiro laranxa.",
|
||||
"¿A que distancia a la redonda quieres recibir notificaciones de fuegos?": "A que distancia darredor queres recibir notificacións de lumes?",
|
||||
"Pulsa para activar": "Preme para activar",
|
||||
"Arrastrar para seleccionar otro punto": "Arrastrar para seleccionar outro punto",
|
||||
"Mapa gris de OpenStreetMap": "Mapa gris de OpenStreetMap",
|
||||
"Mapa color de OpenStreetMap": "Mapa cor de OpenStreetMap",
|
||||
"Mapa de carreteras de Google": "Mapa de estradas de Google",
|
||||
"Mapa de terreno de Google": "Mapa de terreo de Google",
|
||||
"Mapa de satélite de Google": "Mapa de satélite de Google",
|
||||
"Zona añadida": "Zona engadida",
|
||||
"Añadir zona": "Engadir zona",
|
||||
"Editar": "Editar",
|
||||
"Suscripciones a alertas de fuegos en zonas de mi interés": "Suscricións a alertas de lumes en zonas do meu interese",
|
||||
"En verde, áreas de las que recibirás alertas de fuegos": "En verde, áreas das que recibirás alertas de lumes",
|
||||
"Terminar": "Rematar",
|
||||
"Pulsa para borrar": "Preme para borrar",
|
||||
"Pulsa aquí para borrar la zona": "Preme aquí para borrar a zona",
|
||||
"Los fuegos activos se actualizan en tiempo cuasi real.": "Os lumes activos actualízanse en tempo cuasi real.",
|
||||
"Somos muchos ojos": "Somos moitos ollos",
|
||||
"Usa nuestro bot de Telegram para estar al tanto de los fuegos en tus área": "Emprega o noso bot do Telegram para estar ó tanto dos lumes na túa área",
|
||||
"Otros dispositivos": "Outros dispositivos",
|
||||
"support-us-home": "Estamos a desenvolver as nosas ferramentas para outros dispositivos. Podes <1>contribuír a facelo posible.</1>",
|
||||
"Software Libre": "Software Libre",
|
||||
"Suscríbete a alertas de fuegos": "Suscríbete a alertas de lumes",
|
||||
"Gracias por Participar": "Grazas por Participar",
|
||||
"También puedes seguirnos en la web": "Tamén nos podes seguir na web",
|
||||
"Participar": "Participar",
|
||||
"Dejarás de recibir notificaciones de fuegos en esa área ¿Estás seguro/a? ": "Deixarás de recibir notificacións de lumes nesa área. Estás segura/o? ",
|
||||
"Sobre los datos y imágenes usados": "Sobre os datos e imaxes empregados",
|
||||
"dev-with-us-home": "Todo o noso traballo é <1>sofware libre</1>. <3>Traductoræs</3> e <5>desenvolvedoræs sempre benvid@s</5>.",
|
||||
"Política de Privacidad": "Política de Privacidade",
|
||||
"No estás suscrito a fuegos en ninguna zona": "Non tes suscrición a lumes en ningunha zona",
|
||||
"Iniciar sesión con Telegram": "Iniciar sesión con Telegram",
|
||||
"Idioma": "Idioma",
|
||||
"Comentarios": "Comentarios",
|
||||
"Guardar": "Gardar",
|
||||
"Responder": "Responder",
|
||||
"Borrar": "Borrar",
|
||||
"Añadir un comentario": "Engadir un comentario",
|
||||
"Añadir una respuesta": "Engadir unha resposta",
|
||||
"Añadir comentario": "Engadir comentario",
|
||||
"Necesitas iniciar sesión para": "Precisas iniciar a sesión para",
|
||||
"añadir comentarios": "engadir comentarios",
|
||||
"puntuar comentarios": "puntuar comentarios",
|
||||
"responder": "responder",
|
||||
"Más comentarios": "Máis comentarios",
|
||||
"Elige un lugar": "Escolle un lugar",
|
||||
"Elige un radio de vigilancia": "Escolle un radio de vixiancia",
|
||||
"Recibe alertas de fuegos en esa zona": "Recibe alertas de lumes nesa zona",
|
||||
"Alerta cuando hay un fuego": "Alerta cando hai un lume",
|
||||
"Anterior": "Anterior",
|
||||
"Siguiente": "Seguinte",
|
||||
"Siempre alerta a los fuegos en nuestro vecindario": "Alerta sempre dos lumes na nosa veciñanza",
|
||||
"Información adicional sobre fuego detectado en {{where}} el {{when}}": "Información adicional sobre lume detectado en {{where}} o {{when}}",
|
||||
"Información adicional sobre fuego detectado el {{when}}": "Información adicional sobre lume detectado o {{when}}",
|
||||
"Coordenadas:": "Coordenadas:",
|
||||
"Fuego detectado por satélites de la NASA <1></1>": "Lume detectado por satélites da NASA <1></1>",
|
||||
"Puedes añadir un comentario si tienes información adicional sobre este fuego.": "Podes engadir un comentario se tes información adicional sobre este lume",
|
||||
"Por ejemplo:": "Por exemplo:",
|
||||
"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)": "se coñeces esta zona e como acceder ó lume (isto pode ser de moita axuda para apagalo se segue activo ou para investigalo nun futuro)",
|
||||
"si conoces el motivo por el que comenzó el fuego": "se coñeces o motivo polo que empezou o lume",
|
||||
"si quieres denunciar algún tipo de ilegalidad, incluso anónimamente": "se queres denunciar algún tipo de ilegalidade, incluso anonimamente",
|
||||
"o cualquier otra información": "ou calquera outra información",
|
||||
"Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.": "Faga zoom nunha zona do seu interese se quere que os lumes se actualicen en tempo real.",
|
||||
"Notificaciones": "Notificacións",
|
||||
"Recibe nuestras notificaciones de fuegos por correo o en tu navegador": "Recibe as nosas notificacións de lumes por correo ou no teu navegador",
|
||||
"Fuego notificado por uno de nuestros usuarios/as <1></1>": "Lume notificado por un/ha das/os nosas/os usuarias/os",
|
||||
"No recibirás notificaciones de fuegos en este equipo, solo por correo": "Non recibirás notificacións de lumes neste equipo, só por correo",
|
||||
"not-found": "Upppps: Esta páxina non existe",
|
||||
"general-error-title": "Upppps: Algo saiu mal",
|
||||
"general-error-description": "Estamos a investigar o problema, volve tentalo nun cachiño",
|
||||
"Más información sobre este fuego": "Máis información sobre este lume",
|
||||
"Última actualización, {{when}}": "Última actualización, {{when}}",
|
||||
"termsAccept": "Acepto as <1>condicións de servizo</1> deste sitio",
|
||||
"Bienvenid@!": "Benvid@!",
|
||||
"welcome": "Benvid@",
|
||||
"Has iniciado sesión con {{service}} usando la dirección de correo {{email}}.": "Iniciaches a sesión con {{service}} usando a dirección de correo {{email}}.",
|
||||
"Verifica tu dirección de correo": "Verifica a túa dirección de correo",
|
||||
"Zonas vigiladas": "Zonas vixiladas",
|
||||
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "En verde, as zonas vixiladas polas/os nosas/os usuarias/os actualmente",
|
||||
"Actualizado <1></1>, último fuego detectado <3></3>.": "Actualizado <1></1>, último lume detectado <3></3>.",
|
||||
"Tomamos nota, ¡gracias por colaborar!": "Tomamos nota, grazas por colaborar!",
|
||||
"Indícanos de que tipo de fuego se trata y ayúdanos así a mejorar nuestras notificaciones:": "Indícanos de que tipo de lume se trata e axúdanos así a mellorar as nosas notificacións:",
|
||||
"¿No es un fuego forestal?": "Non é un lume forestal?",
|
||||
"Regístrate o inicia sesión para aportar información sobre este fuego": "Rexístrate ou inicia sesión para aportar información sobre este lume",
|
||||
"Fuego no encontrado": "Lume non atopado",
|
||||
"Es una industria": "É unha industria",
|
||||
"Es una quema controlada": "É unha queima controlada",
|
||||
"Parece una falsa alarma": "Parece unha falsa alarma",
|
||||
"Fuegos activos en el mundo actualizados en tiempo real": "Lumes activos no mundo actualizados en tempo real",
|
||||
"Información adicional sobre fuego": "Información adicional sobre o lume",
|
||||
"CO2emisions": "Sabías que os lumes <1>producen tanto CO² como os coches</1> e <3>aproximadamente ⅕ de tódalas nosas emisións </3>?",
|
||||
"Ayúdanos a combatir el cambio climático y a proteger el medioambiente": "Axúdanos a combatir o cambio climático e a protexer o medioambiente",
|
||||
"Fuego en La Tuna, Los Ángeles, Estados Unidos, septiembre de 2017": "Lume en La Tuna, Los Ángeles, Estados Unidos, setembro do 2017",
|
||||
"Polución en la ciudad de Almaty, Kazakhstan, enero de 2014": "Polución na cidade de Almaty, Kazakhstan, xaneiro do 2014",
|
||||
"Fuente": "Fonte",
|
||||
"NASA": "NASA",
|
||||
"nuestros usuarios/as": "as nosas/os usuarias/os",
|
||||
"Pulsa para más información": "Preme para máis información",
|
||||
"Detectado": "Detectado",
|
||||
"Zonas vigiladas por nuestros usuari@s actualmente": "Zonas vixiadas pol@s nos@s usuari@s actualmente",
|
||||
"Iniciar sesión con Facebook": "Iniciar sesión con Facebook",
|
||||
"Elige un tipo": "Escolle un tipo",
|
||||
"Puedes participar en las traducciones": "Podes participar nas traducións",
|
||||
"Aceptar": "Aceptar",
|
||||
"Por favor, escribe aquí tu feedback...": "Por favor, escribe aquí o teu feedback...",
|
||||
"Feedback": "Feedback",
|
||||
"Enviar": "Enviar",
|
||||
"Tu correo": "O teu correo",
|
||||
"Desconectado del servidor, reconectando en %delay% segundos.": "Desconectando di servidor, reconectando en %delay% segundos.",
|
||||
"Desconectado del servidor, reconectando...": "Desconectado do servidor, reconectando...",
|
||||
"Reintentar ahora": "Reintentar agora",
|
||||
"Feedback recibido, gracias...": "Feedback recibido, grazas...",
|
||||
"Parece que este fuego no es un fuego forestal.": "Parece que este lume non é un lume forestal.",
|
||||
"Sobre nosotr@s": "Sobre nós",
|
||||
"Nosotr@s": "Nós",
|
||||
"Sobre '{{appName}}'": "Sobre '{{appName}}'",
|
||||
"¿Vecindarios vigilando y combatiendo fuegos? ¿de qué va todo esto?": "Veciñanzas vixiando e combatendo lumes? de vai todo isto?",
|
||||
"noActiveNeigFireInMap": "Non hai lumes notificados recentemente polas/os nosas/os usuarias/os nesta zona.",
|
||||
"Es una industria (fuente: registro oficial)": "É unha industria (fonte: rexistro oficial)",
|
||||
"Es una industria (fuente: nuestros usuarios/as)": "É unha industria (fonte as/os nosas/os usuarias/os):",
|
||||
"Pantalla completa": "Pantalla completa",
|
||||
"Salir de pantalla completa": "Saír da pantalla completa",
|
||||
"Escribe un lugar para centrar el mapa": "Escribe un lugar para centrar o mapa",
|
||||
"geo-not-perms-error": "Upppps: parece que non temos permiso para coñecer a túa ubicación",
|
||||
"geo-not-avail-error": "Upppps: parece que a túa ubicación non está dispoñible",
|
||||
"geo-not-timeout-error": "Upppps: o teu dispositivo está a tardar demasiado en coñecer a túa ubicación",
|
||||
"Estás subscrito a una zona muy grande": "Suscribícheste a unha zona grande de máis",
|
||||
"Lugar no encontrado": "Lugar non atopado",
|
||||
"on-already-subscribed": "Upppps: Xa te suscribiras a esta zona antes",
|
||||
"Cancelar": "Cancelar",
|
||||
"Nota: Las nubes pueden entorpecer la detección de fuegos activos.": "Nota: As nubes poden entorpecer a detección de lumes activos.",
|
||||
"Hay más información sobre un fuego": "Hai máis información sobre un lume",
|
||||
"esDecirTalDia": "É dicir o {{date}}"
|
||||
}
|
||||
|
|
|
|||
29
test/activeFiresUnion.test.js
Normal file
29
test/activeFiresUnion.test.js
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue