Compare commits
No commits in common. "tcef-master" and "2.3.9" have entirely different histories.
tcef-maste
...
2.3.9
164 changed files with 2460 additions and 14488 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -9,11 +9,3 @@ npm-debug.log
|
|||
.deploy/
|
||||
public/locales/undefined/
|
||||
.deploy-staging
|
||||
.screenshots
|
||||
.vscode
|
||||
output.json
|
||||
|
||||
# fase-0 higiene: variantes de settings con secretos (no versionar)
|
||||
settings-production-*.json
|
||||
settings-tests.json
|
||||
*.json~
|
||||
|
|
|
|||
|
|
@ -4,33 +4,33 @@
|
|||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base@1.3.0 # Packages every Meteor app needs to have
|
||||
meteor-base@1.2.0 # Packages every Meteor app needs to have
|
||||
mobile-experience@1.0.5 # Packages for a great mobile UX
|
||||
mongo@1.4.7 # The database Meteor supports right now
|
||||
mongo@1.3.0 # The database Meteor supports right now
|
||||
reactive-var@1.0.11 # Reactive variable for tracker
|
||||
tracker@1.1.3 # Meteor's client-side reactive programming library
|
||||
|
||||
standard-minifier-css@1.4.0 # CSS minifier run for production mode
|
||||
standard-minifier-js@2.3.1 # JS minifier run for production mode
|
||||
es5-shim@4.7.0 # ECMAScript 5 compatibility for older browsers.
|
||||
ecmascript@0.10.6 # Enable ECMAScript2015+ syntax in app code
|
||||
shell-server@0.3.1 # Server-side component of the `meteor shell` command
|
||||
standard-minifier-css@1.3.5 # CSS minifier run for production mode
|
||||
standard-minifier-js@2.2.0 # JS minifier run for production mode
|
||||
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
|
||||
ecmascript@0.9.0 # Enable ECMAScript2015+ syntax in app code
|
||||
shell-server@0.3.0 # Server-side component of the `meteor shell` command
|
||||
|
||||
react-meteor-data
|
||||
alanning:roles
|
||||
fourseven:scss
|
||||
accounts-base@1.4.2
|
||||
accounts-base@1.4.0
|
||||
accounts-password@1.5.0
|
||||
service-configuration@1.0.11
|
||||
accounts-facebook@1.3.1
|
||||
accounts-github@1.4.1
|
||||
accounts-google@1.3.1
|
||||
accounts-facebook@1.3.0
|
||||
accounts-github@1.4.0
|
||||
accounts-google@1.3.0
|
||||
themeteorchef:bert
|
||||
fortawesome:fontawesome
|
||||
aldeed:collection2-core@2.0.1
|
||||
audit-argument-checks@1.0.7
|
||||
ddp-rate-limiter@1.0.7
|
||||
dynamic-import@0.3.0
|
||||
dynamic-import@0.2.0
|
||||
static-html
|
||||
alexwine:bootstrap-4
|
||||
|
||||
|
|
@ -45,6 +45,7 @@ natestrauser:publish-performant-counts
|
|||
maximum:server-transform
|
||||
mys:fonts # fonst in npm
|
||||
ostrio:mailer # mailer
|
||||
mizzao:user-status # user status (online, etc)
|
||||
# babrahams:constellation # dev utility
|
||||
percolate:migrations # db migrations
|
||||
ostrio:meteor-root
|
||||
|
|
@ -53,17 +54,11 @@ practicalmeteor:chai
|
|||
aldeed:schema-deny
|
||||
aldeed:template-extension
|
||||
dburles:collection-helpers
|
||||
less@2.7.11
|
||||
markdown@1.0.12
|
||||
mongo-livedata@1.0.12
|
||||
less
|
||||
markdown
|
||||
mongo-livedata
|
||||
reywood:publish-composite
|
||||
barbatus:stars-rating
|
||||
arkham:comments-ui
|
||||
facts@1.0.9
|
||||
facts
|
||||
gadicohen:sitemaps
|
||||
nspangler:autoreconnect
|
||||
saucecode:timezoned-synced-cron
|
||||
# lmachens:kadira
|
||||
meteorhacks:zones
|
||||
nimble:restivus
|
||||
xolvio:cleaner
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
METEOR@1.6.1.1
|
||||
METEOR@1.6
|
||||
|
|
|
|||
133
.meteor/versions
133
.meteor/versions
|
|
@ -1,56 +1,52 @@
|
|||
255kb:meteor-status@1.5.0
|
||||
accounts-base@1.4.2
|
||||
accounts-facebook@1.3.1
|
||||
accounts-github@1.4.1
|
||||
accounts-google@1.3.1
|
||||
accounts-base@1.4.0
|
||||
accounts-facebook@1.3.0
|
||||
accounts-github@1.4.0
|
||||
accounts-google@1.3.0
|
||||
accounts-oauth@1.1.15
|
||||
accounts-password@1.5.1
|
||||
accounts-password@1.5.0
|
||||
alanning:roles@1.2.16
|
||||
aldeed:collection2-core@2.1.2
|
||||
aldeed:collection2-core@2.0.1
|
||||
aldeed:schema-deny@2.0.1
|
||||
aldeed:template-extension@4.1.0
|
||||
alexwine:bootstrap-4@4.1.0
|
||||
alexwine:bootstrap-4@4.0.0-beta.2
|
||||
allow-deny@1.1.0
|
||||
arkham:comments-ui@1.4.4
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.4.0
|
||||
babel-compiler@7.0.7
|
||||
babel-runtime@1.2.2
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.24.7
|
||||
babel-runtime@1.1.1
|
||||
barbatus:stars-rating@1.1.1
|
||||
base64@1.0.11
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
blaze@2.3.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.4.0
|
||||
browser-policy@1.1.0
|
||||
browser-policy-common@1.0.11
|
||||
browser-policy-content@1.1.0
|
||||
browser-policy-framing@1.1.0
|
||||
caching-compiler@1.1.11
|
||||
boilerplate-generator@1.3.0
|
||||
caching-compiler@1.1.9
|
||||
caching-html-compiler@1.1.2
|
||||
callback-hook@1.1.0
|
||||
check@1.3.1
|
||||
callback-hook@1.0.10
|
||||
check@1.2.5
|
||||
coffeescript@1.0.17
|
||||
dburles:collection-helpers@1.1.0
|
||||
dburles:mongo-collection-instances@0.3.5
|
||||
ddp@1.4.0
|
||||
ddp-client@2.3.2
|
||||
ddp-common@1.4.0
|
||||
ddp-client@2.2.0
|
||||
ddp-common@1.3.0
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.1.2
|
||||
ddp-server@2.1.0
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.0
|
||||
dynamic-import@0.3.0
|
||||
ecmascript@0.10.7
|
||||
diff-sequence@1.0.7
|
||||
dynamic-import@0.2.0
|
||||
ecmascript@0.9.0
|
||||
ecmascript-runtime@0.5.0
|
||||
ecmascript-runtime-client@0.6.2
|
||||
ecmascript-runtime-client@0.5.0
|
||||
ecmascript-runtime-server@0.5.0
|
||||
ejson@1.1.0
|
||||
email@1.2.3
|
||||
es5-shim@4.7.3
|
||||
es5-shim@4.6.15
|
||||
facebook-oauth@1.4.0
|
||||
facts@1.0.9
|
||||
flowkey:raven@1.1.0
|
||||
flowkey:raven@1.0.0
|
||||
fortawesome:fontawesome@4.7.0
|
||||
fourseven:scss@4.5.4
|
||||
gadicc:blaze-react-component@1.4.0
|
||||
|
|
@ -58,53 +54,51 @@ gadicohen:robots-txt@0.0.10
|
|||
gadicohen:sitemaps@0.0.26
|
||||
geojson-utils@1.0.10
|
||||
github-oauth@1.2.0
|
||||
google-oauth@1.2.5
|
||||
google-oauth@1.2.4
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.4.0
|
||||
id-map@1.1.0
|
||||
jquery@1.11.11
|
||||
http@1.3.0
|
||||
id-map@1.0.9
|
||||
jquery@1.11.10
|
||||
lai:collection-extensions@0.2.1_1
|
||||
launch-screen@1.1.1
|
||||
less@2.7.12
|
||||
less@2.7.11
|
||||
livedata@1.0.18
|
||||
localstorage@1.2.0
|
||||
logging@1.1.20
|
||||
logging@1.1.19
|
||||
markdown@1.0.12
|
||||
maximum:package-base@1.1.2
|
||||
maximum:server-transform@0.5.0
|
||||
mdg:geolocation@1.3.0
|
||||
meteor@1.8.6
|
||||
meteor-base@1.3.0
|
||||
meteorhacks:inject-initial@1.0.4
|
||||
meteorhacks:zones@1.6.0
|
||||
meteortesting:browser-tests@0.2.0
|
||||
meteortesting:mocha@0.5.1
|
||||
minifier-css@1.3.1
|
||||
minifier-js@2.3.4
|
||||
minimongo@1.4.4
|
||||
meteor@1.8.0
|
||||
meteor-base@1.2.0
|
||||
meteortesting:browser-tests@0.1.2
|
||||
meteortesting:mocha@0.5.0
|
||||
minifier-css@1.2.16
|
||||
minifier-js@2.2.1
|
||||
minimongo@1.4.0
|
||||
mizzao:timesync@0.3.4
|
||||
mizzao:user-status@0.6.7
|
||||
mobile-experience@1.0.5
|
||||
mobile-status-bar@1.0.14
|
||||
modules@0.11.6
|
||||
modules-runtime@0.9.2
|
||||
mongo@1.4.7
|
||||
modules@0.11.0
|
||||
modules-runtime@0.9.0
|
||||
mongo@1.3.0
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
mongo-id@1.0.6
|
||||
mongo-livedata@1.0.12
|
||||
mys:fonts@0.0.2
|
||||
natestrauser:publish-performant-counts@0.1.2
|
||||
nimble:restivus@0.8.12
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@2.2.34
|
||||
nspangler:autoreconnect@0.0.1
|
||||
oauth@1.2.3
|
||||
oauth@1.2.0
|
||||
oauth2@1.2.0
|
||||
observe-sequence@1.0.16
|
||||
ordered-dict@1.1.0
|
||||
ordered-dict@1.0.9
|
||||
ostrio:cookies@2.1.3
|
||||
ostrio:mailer@2.1.0
|
||||
ostrio:meteor-root@1.0.7
|
||||
ostrio:meteor-root@1.0.6
|
||||
peerlibrary:assert@0.2.5
|
||||
peerlibrary:fiber-utils@0.6.0
|
||||
peerlibrary:reactive-mongo@0.1.1
|
||||
|
|
@ -112,44 +106,37 @@ peerlibrary:server-autorun@0.5.2
|
|||
percolate:migrations@1.0.2
|
||||
practicalmeteor:chai@2.1.0_1
|
||||
practicalmeteor:mocha-core@1.0.1
|
||||
promise@0.10.2
|
||||
promise@0.10.0
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.1.0
|
||||
rate-limit@1.0.9
|
||||
react-meteor-data@0.2.16
|
||||
random@1.0.10
|
||||
rate-limit@1.0.8
|
||||
react-meteor-data@0.2.15
|
||||
reactive-dict@1.2.0
|
||||
reactive-var@1.0.11
|
||||
reload@1.2.0
|
||||
retry@1.1.0
|
||||
reywood:publish-composite@1.6.0
|
||||
routepolicy@1.0.13
|
||||
saucecode:timezoned-synced-cron@1.2.11
|
||||
reload@1.1.11
|
||||
retry@1.0.9
|
||||
reywood:publish-composite@1.5.2
|
||||
routepolicy@1.0.12
|
||||
selaias:cookie-consent@0.4.0
|
||||
server-render@0.3.0
|
||||
service-configuration@1.0.11
|
||||
session@1.1.7
|
||||
sha@1.0.9
|
||||
shell-server@0.3.1
|
||||
shim-common@0.1.0
|
||||
simple:json-routes@2.1.0
|
||||
socket-stream-client@0.1.0
|
||||
shell-server@0.3.0
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
srp@1.0.10
|
||||
standard-minifier-css@1.4.1
|
||||
standard-minifier-js@2.3.3
|
||||
standard-minifier-css@1.3.5
|
||||
standard-minifier-js@2.2.1
|
||||
static-html@1.2.2
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
themeteorchef:bert@2.1.3
|
||||
tmeasday:check-npm-versions@0.3.2
|
||||
tmeasday:check-npm-versions@0.3.1
|
||||
tracker@1.1.3
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.2.0
|
||||
url@1.1.0
|
||||
vjrj:piwik@0.3.1
|
||||
webapp@1.5.0
|
||||
webapp@1.4.0
|
||||
webapp-hashing@1.0.9
|
||||
xolvio:cleaner@0.3.3
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# Don't reload meteor server when we modify or do test
|
||||
cucumber
|
||||
output.json
|
||||
7
API.md
7
API.md
|
|
@ -1,7 +0,0 @@
|
|||
Basic API:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:3000/api/v1/status/last-fire-detected
|
||||
curl -X GET http://localhost:3000/api/v1/status/last-fire-check
|
||||
curl -X GET http://localhost:3000/api/v1/status/active-fires-count
|
||||
```
|
||||
25
README.md
25
README.md
|
|
@ -1,19 +1,10 @@
|
|||
# Tod@s contra el Fuego (All Against Fire)
|
||||
|
||||
[](http://ci.comunes.org/job/todos-contra-el-fuego-web/)
|
||||
|
||||
This is web service that notifies about fires detected in an area of your interest. It helps the early detection of fires and facilitates local mobilization, while the professional extinction services arrive.
|
||||
|
||||
## Install
|
||||
|
||||
Prerrequisites: a running meteor (we share the `fuegos` database with the telegram bot).
|
||||
|
||||
Install `meteor` and run `meteor npm install` and `npm start`
|
||||
|
||||
Some other development deps (in debian & ubuntu):
|
||||
```
|
||||
apt-get install libcairo2-dev libjpeg-dev libgif-dev pkg-config
|
||||
```
|
||||
Install `meteor` and run `npm start`
|
||||
|
||||
### GeoIP
|
||||
|
||||
|
|
@ -29,23 +20,18 @@ More platforms and services in the future...
|
|||
|
||||
We do tests via:
|
||||
```
|
||||
TEST_PORT=3000 TEST_WATCH=1 TEST_CLIENT=0 MONGO_URL=mongodb://localhost:27017/fuegostest meteor --settings settings-development.json test --driver-package meteortesting:mocha --port 3010
|
||||
TEST_WATCH=1 MONGO_URL=mongodb://localhost:27017/fuegos meteor --settings settings-development.json test --driver-package meteortesting:mocha --port 3010
|
||||
|
||||
# and
|
||||
|
||||
node_modules/.bin/chimp --watch --ddp=http://localhost:3000 --path=cucumber
|
||||
chimp --watch --ddp=http://localhost:3000 --path=cucumber
|
||||
|
||||
# and
|
||||
|
||||
node_modules/.bin/chimp --ddp=http://localhost:3000 --path=cucumber
|
||||
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*.
|
||||
|
|
@ -56,5 +42,4 @@ 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
|
||||
(...)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="fragment" content="!">
|
||||
<title>All Against the Fire | Tod@s contra el Fuego</title>
|
||||
<title>All against Fire | Tod@s contra el Fuego</title>
|
||||
<meta name="description" content="Crowdsourcing against wildfires | Colaboración masiva contra los incendios">
|
||||
<meta name="viewport" content="initial-scale=1, minimal-ui, maximum-scale=1, minimum-scale=1" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1 @@
|
|||
import '../imports/startup/client';
|
||||
|
||||
// https://github.com/GoogleChrome/rendertron#rendering-budget-timeout
|
||||
window.renderComplete = false;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
@watch
|
||||
Feature: Allow users to give feedback
|
||||
|
||||
As a user (authenticated or not)
|
||||
|
|
@ -9,7 +10,6 @@ Feature: Allow users to give feedback
|
|||
# Because is not verified
|
||||
Then I can send feedback with email
|
||||
When I sign out
|
||||
Given I'm not logged
|
||||
Then I can send feedback with email
|
||||
|
||||
Scenario: A anon user can give feedback
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
Feature: Test all secundary pages
|
||||
|
||||
Background:
|
||||
Given I am on the home page
|
||||
|
||||
Scenario: Check that all secondary pages work well
|
||||
Given a list of page ids and contents
|
||||
| license | License |
|
||||
|
|
@ -13,9 +10,8 @@ Feature: Test all secundary pages
|
|||
| about | About |
|
||||
Then I check that all pages works properly
|
||||
|
||||
Scenario: Check that all secondary pages work well when not logged
|
||||
Given I logout if logged
|
||||
And a list of page urls and contents
|
||||
Scenario: Check that all secondary pages work well
|
||||
Given a list of page urls and contents
|
||||
| login | Login | true |
|
||||
| signup | Sign Up | true |
|
||||
| fires | Fires | true |
|
||||
|
|
@ -25,27 +21,10 @@ Feature: Test all secundary pages
|
|||
| fire/inexistent | This page doesn't exist | 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
|
||||
|
||||
# And they are spiderable
|
||||
|
||||
@watch
|
||||
Scenario: Check that all secondary pages work well when logged
|
||||
Given I have an account and I logged in
|
||||
And a list of page urls and contents
|
||||
| status | Status | false |
|
||||
| fires | Fires | true |
|
||||
| zones | Monitored | true |
|
||||
| verify-email/something | Verify | true |
|
||||
| fire/inexistent | This page doesn't exist | 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
|
||||
|
||||
Scenario: Check that other non visible pages work well
|
||||
Given I logout if logged
|
||||
And a list of non visible pages ids and contents
|
||||
| error | Upppps | false |
|
||||
# FIXME: find a way to monitor json pages
|
||||
# | api/v1/status/last-fire-detected | updateAt |
|
||||
# | api/v1/status/last-fire-check | description |
|
||||
# | api/v1/status/active-fires-count | total |
|
||||
Given a list of non visible pages ids and contents
|
||||
| status | Status |
|
||||
| error | Upps |
|
||||
Then I check that all non visible pages works properly
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ module.exports = function doSteps() {
|
|||
client.waitForVisible('#feedback-tab', 10000);
|
||||
client.click('#feedback-tab');
|
||||
client.waitForVisible('#feedback-form', 10000);
|
||||
client.waitForEnabled('input[name="email"]');
|
||||
client.setValue('input[name="email"]', randomEmail());
|
||||
client.setValue('#feedbackTextarea', randomText(500));
|
||||
client.click('#sendFeedbackBtn');
|
||||
|
|
|
|||
|
|
@ -2,50 +2,17 @@
|
|||
|
||||
(function () {
|
||||
module.exports = function () {
|
||||
const printLog = (type) => {
|
||||
client.log(type).value.forEach((logItem) => {
|
||||
const { source, message } = logItem;
|
||||
if (type === 'browser' && source === 'console-api') {
|
||||
// Clean messages of type:
|
||||
// http://localhost:3000/app/app.js?hash=34da4258bd2caa4e14c7c0c20ba6396f309e7236 13325:18 "GMaps script just loaded"
|
||||
// to get the last part
|
||||
const cleanMessage = message.replace(/[^ ]{1,} [^ ]{1,} "(.*)"$/g, '$1');
|
||||
console.log(`${logItem.level}: ${cleanMessage}`);
|
||||
} else {
|
||||
// Right now we don't need logs like:
|
||||
// %cDownload the React DevTools for a better development experience: https://fb.me/react-devtools
|
||||
// console.log(`${logItem.level}: ${message}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.StepResult((stepResult) => {
|
||||
if (stepResult.getStatus() === 'failed') {
|
||||
// http://webdriver.io/api/protocol/log.html
|
||||
console.log(`Step result: ${stepResult.getStatus()}`);
|
||||
printLog('browser');
|
||||
}
|
||||
});
|
||||
|
||||
this.Before(() => {
|
||||
// global.expect = require('@xolvio/jasmine-expect').expect;
|
||||
|
||||
// https://github.com/webdriverio/webdriverio/issues/1145
|
||||
/* Not used
|
||||
if (!this.initMyCmds) {
|
||||
client.addCommand('waitForClickable', function elementClickable(selector, timeout) {
|
||||
this.waitForVisible(selector, timeout);
|
||||
this.waitForEnabled(selector, timeout);
|
||||
});
|
||||
|
||||
client.addCommand('waitUntilText', function waitUntilText(selector, text, ms) {
|
||||
ms = ms || 5000;
|
||||
this.waitForVisible(selector);
|
||||
const self = this;
|
||||
this.waitUntil(() => self.getText(selector).includes(text), ms, `expected text in ${selector} be different after ${ms / 1000}s. Obtained: '${self.getText(selector)}' expected: '${text}'`);
|
||||
});
|
||||
|
||||
this.initMyCmds = true;
|
||||
}
|
||||
} */
|
||||
|
||||
client.windowHandleSize({ width: 1500, height: 1000 });
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ let pass;
|
|||
let email;
|
||||
|
||||
const setUserValues = (isEdit) => {
|
||||
client.waitForVisible('input[name="firstName"]', 10000);
|
||||
client.waitForVisible('input[name="firstName"]', 5000);
|
||||
firstName = randomUsername();
|
||||
lastName = randomUsername();
|
||||
email = randomEmail();
|
||||
|
|
@ -32,54 +32,31 @@ module.exports = function doSteps(notos) {
|
|||
goHome(client);
|
||||
});
|
||||
|
||||
function waitNoBert() {
|
||||
client.pause(2000);
|
||||
if (client.isVisible('.bert-container')) {
|
||||
// console.log('Removing bert alert');
|
||||
client.click('.bert-container');
|
||||
client.waitForVisible('.bert-container', 10000, true);
|
||||
} else {
|
||||
// console.log('No bert alert');
|
||||
}
|
||||
}
|
||||
|
||||
function logoutIfLogged() {
|
||||
function register() {
|
||||
if (client.isVisible('#logout')) {
|
||||
client.click('#logout');
|
||||
}
|
||||
client.waitForVisible('#signup', 10000);
|
||||
}
|
||||
|
||||
function register() {
|
||||
logoutIfLogged();
|
||||
client.waitForVisible('#signup', 10000);
|
||||
client.waitForVisible('#signup', 5000);
|
||||
client.click('#signup');
|
||||
setUserValues();
|
||||
if (!notos) {
|
||||
client.click('input[name="tos"]');
|
||||
}
|
||||
client.click('#signUpSubmit');
|
||||
client.waitForVisible('#logout', 10000);
|
||||
}
|
||||
|
||||
this.Given(/^I logout if logged$/, () => {
|
||||
waitNoBert();
|
||||
logoutIfLogged();
|
||||
});
|
||||
|
||||
this.Given(/^I have an account and I logged in$/, () => {
|
||||
register();
|
||||
});
|
||||
|
||||
function checkName() {
|
||||
client.waitForVisible('#profile', 5000);
|
||||
client.waitUntilText('#profile', firstName);
|
||||
client.waitUntilText('#profile', lastName);
|
||||
client.waitForText('#profile', firstName);
|
||||
client.waitForText('#profile', lastName);
|
||||
}
|
||||
|
||||
this.Then(/^I should be logged in$/, () => {
|
||||
client.waitUntil(() => client.isVisible('#logout') === true, 10000);
|
||||
client.waitUntil(() => client.isVisible('#profile') === true, 10000);
|
||||
});
|
||||
|
||||
/* this.Given(/^I am signed out$/, () => {
|
||||
|
|
@ -87,24 +64,29 @@ module.exports = function doSteps(notos) {
|
|||
* });
|
||||
*/
|
||||
|
||||
function closeAlert() {
|
||||
// client.waitForVisible('.bert-alert', 5000);
|
||||
// client.click('.bert-content');
|
||||
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
|
||||
}
|
||||
|
||||
this.When(/^I sign out$/, () => {
|
||||
waitNoBert();
|
||||
client.waitForClickable('#logout', 10000);
|
||||
// client.waitForVisible('#logout', 10000);
|
||||
if (client.isVisible('.bert-alert')) {
|
||||
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
|
||||
}
|
||||
client.waitForVisible('#logout', 5000);
|
||||
client.click('#logout');
|
||||
client.waitForVisible('#login', 10000);
|
||||
});
|
||||
|
||||
function login(badpass) {
|
||||
waitNoBert();
|
||||
client.waitForVisible('#login', 10000);
|
||||
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
|
||||
client.click('#login');
|
||||
client.waitForVisible('#loginSubmit', 5000);
|
||||
client.setValue('input[name="emailAddress"]', email);
|
||||
client.setValue('input[name="password"]', badpass ? randomPassword() : pass);
|
||||
client.click('#loginSubmit');
|
||||
waitNoBert();
|
||||
closeAlert();
|
||||
}
|
||||
|
||||
this.When(/^I enter my email and password$/, () => {
|
||||
|
|
@ -112,13 +94,11 @@ module.exports = function doSteps(notos) {
|
|||
});
|
||||
|
||||
this.Then(/^I can edit my profile$/, () => {
|
||||
waitNoBert();
|
||||
client.waitForClickable('#profile', 10000);
|
||||
client.waitForVisible('#profile', 5000);
|
||||
client.click('#profile');
|
||||
client.pause(2000);
|
||||
setUserValues(true);
|
||||
client.click('#profileSubmit');
|
||||
waitNoBert();
|
||||
closeAlert();
|
||||
checkName();
|
||||
});
|
||||
|
||||
|
|
@ -136,7 +116,7 @@ module.exports = function doSteps(notos) {
|
|||
|
||||
this.Then(/^I should be registered$/, () => {
|
||||
client.waitForVisible('.bert-alert', 10000, true);
|
||||
waitNoBert();
|
||||
closeAlert();
|
||||
});
|
||||
|
||||
this.Given(/^I register with some name, password and email but without accept conditions$/, () => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ module.exports = function () {
|
|||
const content = pages[i][1];
|
||||
client.waitForVisible(id, 10000);
|
||||
client.click(id);
|
||||
client.waitUntilText('#react-root', content);
|
||||
client.waitForText('#react-root', content);
|
||||
// https://jasmine.github.io/2.3/introduction.html#section-Expectations
|
||||
expect(client.getTitle()).toContain(pages[i][1]);
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ module.exports = function () {
|
|||
const content = pages[i][1];
|
||||
client.url(`${process.env.ROOT_URL}/${url}`);
|
||||
client.waitForVisible('#react-root', 10000);
|
||||
client.waitUntilText('#react-root', content);
|
||||
client.waitForText('#react-root', content);
|
||||
const checkTitle = pages[i][2] === 'true';
|
||||
// https://jasmine.github.io/2.3/introduction.html#section-Expectations
|
||||
if (checkTitle) {
|
||||
|
|
@ -41,7 +41,10 @@ module.exports = function () {
|
|||
|
||||
this.Then(/^I check that all non visible pages works properly$/, (callback) => {
|
||||
for (let i = 0; i < pages.length; i += 1) {
|
||||
processPageUrl(i);
|
||||
client.url(`${process.env.ROOT_URL}/${pages[i][0]}`);
|
||||
const content = pages[i][1];
|
||||
client.waitForVisible('#react-root', 10000);
|
||||
client.waitForText('#react-root', content);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
|
@ -74,7 +77,7 @@ module.exports = function () {
|
|||
// Write code here that turns the phrase above into concrete actions
|
||||
for (let i = 0; i < pages.length; i += 1) {
|
||||
client.url(`${process.env.ROOT_URL}/${pages[i][0]}?_escaped_fragment_=`);
|
||||
client.waitUntilText('#react-root', pages[i][1]);
|
||||
client.waitForText('#react-root', pages[i][1]);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
|
|
@ -246,9 +245,9 @@
|
|||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.23559314"
|
||||
inkscape:cx="-2890.4884"
|
||||
inkscape:cy="-486.23766"
|
||||
inkscape:zoom="0.25"
|
||||
inkscape:cx="-324.9908"
|
||||
inkscape:cy="997.3585"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
|
|
@ -449,18 +448,11 @@
|
|||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<rect
|
||||
style="opacity:1;vector-effect:none;fill:#c8b7b7;fill-opacity:1;stroke:#c8b7b7;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="rect8537"
|
||||
width="368.82184"
|
||||
height="657.66956"
|
||||
x="-971.73767"
|
||||
y="74.954208" />
|
||||
<g
|
||||
id="g8543"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-l.png"
|
||||
inkscape:export-xdpi="4"
|
||||
inkscape:export-ydpi="4">
|
||||
id="g17717"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/favicon-200.png"
|
||||
inkscape:export-xdpi="28.790001"
|
||||
inkscape:export-ydpi="28.790001">
|
||||
<rect
|
||||
y="108.84225"
|
||||
x="-419.07861"
|
||||
|
|
@ -507,66 +499,61 @@
|
|||
style="font-size:192px;fill:#ff0000" /></flowRegion><flowPara
|
||||
id="flowPara8488"></flowPara></flowRoot> <g
|
||||
id="g22554-45"
|
||||
transform="matrix(-0.04534342,-0.17182024,0.17182024,-0.04534342,-93.960693,-204.49561)"
|
||||
transform="rotate(-104.78334,-231.82318,51.6182)"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
|
||||
inkscape:export-xdpi="4.0856881"
|
||||
inkscape:export-ydpi="4.0856881"
|
||||
style="stroke-width:5.62737894">
|
||||
inkscape:export-ydpi="4.0856881">
|
||||
<g
|
||||
id="g8559"
|
||||
style="stroke-width:5.62737894">
|
||||
id="g8559">
|
||||
<g
|
||||
inkscape:export-ydpi="4.0856881"
|
||||
inkscape:export-xdpi="4.0856881"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
|
||||
transform="rotate(-144.74008,-66.772616,119.34544)"
|
||||
id="g22554-2"
|
||||
style="stroke-width:5.62737894">
|
||||
id="g22554-2">
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczasc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5329-8"
|
||||
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczcsc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5331-30"
|
||||
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:export-ydpi="4.0856881"
|
||||
inkscape:export-xdpi="4.0856881"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
|
||||
transform="rotate(96.689458,-75.070103,121.00438)"
|
||||
id="g22554-5"
|
||||
style="stroke-width:5.62737894">
|
||||
id="g22554-5">
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczasc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5329-4"
|
||||
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczcsc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5331-9"
|
||||
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
transform="rotate(-22.690691,-35.704234,120.074)"
|
||||
id="g8542"
|
||||
style="stroke-width:5.62737894">
|
||||
id="g8542">
|
||||
<path
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
|
||||
id="path5329-9"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cszzcczasc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
|
||||
id="path5331-35"
|
||||
inkscape:connector-curvature="0"
|
||||
|
|
@ -574,86 +561,5 @@
|
|||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g8551"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-s.png"
|
||||
inkscape:export-xdpi="4"
|
||||
inkscape:export-ydpi="4">
|
||||
<use
|
||||
transform="translate(0,404.8127)"
|
||||
height="100%"
|
||||
width="100%"
|
||||
id="use8529"
|
||||
xlink:href="#rect17701"
|
||||
y="0"
|
||||
x="0" />
|
||||
<use
|
||||
style="stroke-width:2"
|
||||
transform="matrix(0.5,0,0,0.5,-165.41924,545.68726)"
|
||||
height="100%"
|
||||
width="100%"
|
||||
id="use8531"
|
||||
xlink:href="#g22554-4"
|
||||
y="0"
|
||||
x="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g8547"
|
||||
transform="translate(0,-14.019767)"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-m.png"
|
||||
inkscape:export-xdpi="4"
|
||||
inkscape:export-ydpi="4">
|
||||
<use
|
||||
transform="translate(0,190.50006)"
|
||||
height="100%"
|
||||
width="100%"
|
||||
id="use8533"
|
||||
xlink:href="#rect17701"
|
||||
y="0"
|
||||
x="0" />
|
||||
<use
|
||||
style="stroke-width:1.33333337"
|
||||
transform="matrix(0.75,0,0,0.75,-82.709618,260.93734)"
|
||||
height="100%"
|
||||
width="100%"
|
||||
id="use8535"
|
||||
xlink:href="#g22554-4"
|
||||
y="0"
|
||||
x="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="translate(176.48029,228.33241)"
|
||||
id="g8543-0"
|
||||
inkscape:export-filename="/home/vjrj/proyectos/dev/fires_flutter/images/logo-1024.png"
|
||||
inkscape:export-xdpi="147.38"
|
||||
inkscape:export-ydpi="147.38">
|
||||
<rect
|
||||
y="108.84225"
|
||||
x="-419.07861"
|
||||
height="176.48029"
|
||||
width="176.48029"
|
||||
id="rect17701-2"
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
|
||||
<g
|
||||
style="stroke-width:0.88457036"
|
||||
inkscape:export-ydpi="4.0856881"
|
||||
inkscape:export-xdpi="4.0856881"
|
||||
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
|
||||
transform="matrix(1.1304923,0,0,1.1304923,-313.01185,70.713452)"
|
||||
id="g22554-4-3">
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczasc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5329-5-7"
|
||||
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
|
||||
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:9.36170292;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cszzcczcsc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5331-3-5"
|
||||
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:9.36170292;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 46 KiB |
|
|
@ -1,6 +1,5 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import firesCommonSchema from '../Common/FiresSchema';
|
||||
|
|
@ -19,11 +18,8 @@ ActiveFires.deny({
|
|||
remove: () => true
|
||||
});
|
||||
|
||||
const activeFiresSchema = firesCommonSchema;
|
||||
|
||||
activeFiresSchema.fireUnion = { type: Meteor.Collection.ObjectID, optional: true, blackbox: true };
|
||||
|
||||
ActiveFires.schema = new SimpleSchema(activeFiresSchema);
|
||||
ActiveFires.schema = new SimpleSchema(firesCommonSchema);
|
||||
|
||||
ActiveFires.attachSchema(ActiveFires.schema);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
|
||||
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
||||
import Industries from '/imports/api/Industries/Industries';
|
||||
import ravenLogger from '/imports/startup/server/ravenLogger';
|
||||
import ActiveFires from '../ActiveFires';
|
||||
|
||||
const debug = 0;
|
||||
|
||||
const cleanProv = (prov, stringsToRemove) => {
|
||||
let lprov = prov;
|
||||
stringsToRemove.forEach((st) => {
|
||||
lprov = lprov.replace(st[0], st[1]);
|
||||
});
|
||||
return lprov;
|
||||
};
|
||||
|
||||
const findFiresInRegion = (zone) => {
|
||||
const result = ActiveFires.find({
|
||||
ourid: {
|
||||
$geoWithin: {
|
||||
$geometry: zone.geometry
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* console.log(`False positive total: ${falsePos.count()}`);
|
||||
console.log(`False positives: ${JSON.stringify(falsePos.fetch())}`); */
|
||||
return result;
|
||||
};
|
||||
|
||||
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) {
|
||||
realFires.push(fire);
|
||||
}
|
||||
});
|
||||
// group fires
|
||||
const realUnion = firesUnion(realFires);
|
||||
const unionCount = realUnion[0] &&
|
||||
realUnion[0].geometry &&
|
||||
realUnion[0].geometry.coordinates ? realUnion[0].geometry.coordinates.length : 0;
|
||||
return unionCount;
|
||||
};
|
||||
|
||||
const countFiresInRegions = (regions, stringsToRemove) => {
|
||||
let total = 0;
|
||||
const fireStats = {};
|
||||
|
||||
regions.features.forEach((region) => {
|
||||
const regionName = cleanProv(region.properties.name, stringsToRemove);
|
||||
const regionCode = region.properties.iso_a2;
|
||||
if (debug) console.log(`${regionName} -----`);
|
||||
try {
|
||||
const fires = findFiresInRegion(region);
|
||||
// TODO Also check neighbour fires (when better implementation of that part)
|
||||
const initialCount = fires.count();
|
||||
if (initialCount > 0) {
|
||||
const unionCount = countRealFires(fires);
|
||||
if (debug) console.log(`${regionName} initial: ${initialCount}, union calc: ${unionCount}`);
|
||||
if (unionCount > 0) {
|
||||
total += unionCount;
|
||||
fireStats[regionName] = { count: unionCount, code: regionCode };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ravenLogger.log(e);
|
||||
}
|
||||
});
|
||||
return { total, fires: fireStats };
|
||||
};
|
||||
|
||||
export default countFiresInRegions;
|
||||
|
|
@ -31,12 +31,12 @@ const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, wit
|
|||
lat: 1,
|
||||
lon: 1,
|
||||
when: 1,
|
||||
scan: 1,
|
||||
track: 1
|
||||
scan: 1
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isDevelopment) console.log(`Active fires total: ${fires.count()}`);
|
||||
// console.log(JSON.stringify(result));
|
||||
// console.log(`Fires total: ${fires.count()}`);
|
||||
|
||||
if (withMarks && fires.fetch().length > 0) {
|
||||
const union = firesUnion(fires);
|
||||
|
|
@ -58,8 +58,3 @@ Meteor.publish('activefiresmyloc', function activeInMyLoc(northEastLng, northEas
|
|||
|
||||
return activefires(northEastLng, northEastLat, southWestLng, southWestLat, withMarks);
|
||||
});
|
||||
|
||||
// Warning: this increase always by one the fire stats
|
||||
Meteor.publish('lastFireDetected', function lastFireDetected() {
|
||||
return ActiveFires.find({}, { limit: 1, sort: { when: -1 } });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,155 +0,0 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
import getPrivateFile from '/imports/modules/server/get-private-file';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import tbuffer from '@turf/buffer';
|
||||
import emojiFlags from 'emoji-flags';
|
||||
import tweet from '/imports/modules/server/twitter';
|
||||
import countFiresInRegions from './countFires';
|
||||
|
||||
const debug = false;
|
||||
|
||||
const autonomies = tbuffer(JSON.parse(getPrivateFile('data/es-atlas-autonomies.json')), 0);
|
||||
const portugal = tbuffer(JSON.parse(getPrivateFile('data/pt-atlas.json')), 0);
|
||||
|
||||
const europe = tbuffer(JSON.parse(getPrivateFile('data/europe-atlas.json')), 0);
|
||||
|
||||
const stringsToRemoveIberia = [
|
||||
['Ciudad Autónoma de', ''],
|
||||
['País Vasco/', ''],
|
||||
['Pais Vasco', 'Euskadi'],
|
||||
['Comunidad Foral de', ''],
|
||||
['Región de', ''],
|
||||
['Valencia', 'CValenciana'],
|
||||
['Comunidad de Madrid', 'CMadrid'],
|
||||
['León', 'CyLeón'],
|
||||
['Leon', 'CyLeón'],
|
||||
['Castilla-', ''],
|
||||
['Cataluña/', ''],
|
||||
['Cataluña', 'Catalunya'],
|
||||
['La Mancha', 'CLMancha'],
|
||||
['Baleares', 'IllesBalears'],
|
||||
['Galicia', 'Galiza'],
|
||||
['Principado de', ''],
|
||||
[' ', '']
|
||||
];
|
||||
|
||||
const emoji = (key, name, fires) => {
|
||||
const emo = emojiFlags.countryCode(key.code);
|
||||
if (!emo) {
|
||||
if (Meteor.isDevelopment) console.log(`Cannot find flag for country ${name}: ${JSON.stringify(key)}`);
|
||||
return '';
|
||||
}
|
||||
return `${emo.emoji} ${fires}`;
|
||||
};
|
||||
|
||||
const composeEuropeTweet = (num, stats, abrev) => {
|
||||
if (num <= 0) {
|
||||
return '';
|
||||
}
|
||||
const keys = Object.keys(stats);
|
||||
const numZones = keys.length;
|
||||
let text = '';
|
||||
keys.map((key, index) => { // , index
|
||||
const zoneFires = stats[key].count;
|
||||
const zoneFiresText = zoneFires > 1 ? index === 0 ? zoneFires : `${zoneFires}` : index === 0 ? 'One' : '1';
|
||||
const what = index > 0 ? ' ' : zoneFires > 1 ? ' active #wildfires ' : ' active #wildfire ';
|
||||
const subText = abrev ? emoji(stats[key], key, zoneFires) :
|
||||
`${zoneFiresText}${what}in #{key}`;
|
||||
let delimiter;
|
||||
if (!abrev) {
|
||||
delimiter = index === numZones - 1 ? ' and' : ',';
|
||||
} else {
|
||||
delimiter = ',';
|
||||
}
|
||||
|
||||
const subTexts = index === 0 ? subText : `${delimiter} ${subText}`;
|
||||
text += subTexts;
|
||||
return '';
|
||||
});
|
||||
return `${text}`;
|
||||
};
|
||||
|
||||
const composeIberiaTweet = (num, stats) => {
|
||||
if (num <= 0) {
|
||||
return '';
|
||||
}
|
||||
const keys = Object.keys(stats);
|
||||
const numZones = keys.length;
|
||||
let text = '';
|
||||
keys.map((key, index) => { // , index
|
||||
const zoneFires = stats[key].count;
|
||||
const zoneFiresText = zoneFires > 1 ? index === 0 ? zoneFires : `otros ${zoneFires}` : index === 0 ? 'Un' : 'otro';
|
||||
const what = index > 0 ? ' ' : zoneFires > 1 ? ' #fuegos activos ' : ' #fuego activo ';
|
||||
const subText = `${zoneFiresText}${what}en #${key}`;
|
||||
const delimiter = index === numZones - 1 ? ' y' : ',';
|
||||
const subTexts = index === 0 ? subText : `${delimiter} ${subText}`;
|
||||
text += subTexts;
|
||||
return '';
|
||||
});
|
||||
return `${text}`;
|
||||
};
|
||||
|
||||
const tweetHeaders = ['🔥'];
|
||||
const tweetFooters = ['Más info en: https://fuegos.comunes.org/fires'];
|
||||
|
||||
|
||||
const correctTweetSize = (tweetText) => {
|
||||
const length = tweetText.length;
|
||||
if (length > 280) {
|
||||
if (Meteor.isDevelopment) console.log(`Tweet is too big (${length}): ${tweetText}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const tweetEuropeFires = () => {
|
||||
const resultEu = countFiresInRegions(europe, [[' ', '']]);
|
||||
|
||||
if (resultEu.total > 0) {
|
||||
let tweetText = `${tweetHeaders[0].trim()} ${composeEuropeTweet(resultEu.total, resultEu.fires, false)}. More info: https://fires.comunes.org/fires`;
|
||||
if (!correctTweetSize(tweetText)) {
|
||||
tweetText = `${tweetHeaders[0].trim()}Active #wildfires in #Europe:\n${composeEuropeTweet(resultEu.total, resultEu.fires, true)}. More info: https://fires.comunes.org/fires`;
|
||||
}
|
||||
if (!correctTweetSize(tweetText)) {
|
||||
tweetText = `${tweetHeaders[0].trim()}Active #wildfires in #Europe:\n${composeEuropeTweet(resultEu.total, resultEu.fires, true)}`;
|
||||
}
|
||||
if (!correctTweetSize(tweetText)) {
|
||||
tweetText = `${tweetHeaders[0].trim()} There are ${resultEu.total} active #wildfires in #Europe. More info: https://fires.comunes.org/fires`;
|
||||
}
|
||||
if (Meteor.isDevelopment) {
|
||||
console.log(tweetText);
|
||||
} else {
|
||||
tweet(tweetText, 'en');
|
||||
}
|
||||
return tweetText;
|
||||
}
|
||||
};
|
||||
|
||||
const tweetIberiaFires = () => {
|
||||
const resultEs = countFiresInRegions(autonomies, stringsToRemoveIberia);
|
||||
const resultPt = countFiresInRegions(portugal, stringsToRemoveIberia);
|
||||
|
||||
const fires = Object.assign(resultEs.fires, resultPt.fires);
|
||||
const total = resultEs.total + resultPt.total;
|
||||
|
||||
// console.log(`Total fires ${total}, ${JSON.stringify(fires)}`);
|
||||
if (total > 0) {
|
||||
const tweetText = `${tweetHeaders[0].trim()} ${composeIberiaTweet(total, fires)}. ${tweetFooters[0]}`;
|
||||
if (correctTweetSize(tweetText)) {
|
||||
if (Meteor.isDevelopment) {
|
||||
console.log(tweetText);
|
||||
} else {
|
||||
tweet(tweetText, 'es');
|
||||
}
|
||||
}
|
||||
return tweetText;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
if (Meteor.isDevelopment && debug) {
|
||||
tweetIberiaFires();
|
||||
tweetEuropeFires();
|
||||
}
|
||||
|
||||
export { composeIberiaTweet, tweetHeaders, tweetFooters, tweetIberiaFires, tweetEuropeFires };
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/* 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;
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/* 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 } });
|
||||
});
|
||||
|
|
@ -3,5 +3,4 @@ import moment from 'moment-timezone';
|
|||
export const momentTz = date => moment.tz(date, moment.tz.guess());
|
||||
export const dateLongFormat = date => momentTz(date).format('LLLL (z)');
|
||||
export const dateParseShortFormat = date => moment(date, 'YYYY-MM-DD').format('LL');
|
||||
export const dateYYYYMMDD = date => moment(date).format('YYYY-MM-DD');
|
||||
export const dateFromNow = date => momentTz(date).fromNow();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
const types = {
|
||||
industry: 'Es una industria',
|
||||
// TODO volcano: 'Es un volcán',
|
||||
controled: 'Es una quema controlada',
|
||||
falsealarm: 'Parece una falsa alarma'
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,29 +6,6 @@ import FalsePositiveTypes from './FalsePositiveTypes';
|
|||
import Fires from '../Fires/Fires';
|
||||
import rateLimit from '../../modules/rate-limit';
|
||||
|
||||
export function upsertFalsePositive(keyType, owner, fire) {
|
||||
const fireType = fire.type;
|
||||
if (fireType === 'vecinal') {
|
||||
throw new Meteor.Error('500', 'No se puede marcar este tipo de fuego');
|
||||
}
|
||||
const date = new Date();
|
||||
const formated = moment(date).format('YYYYMMDD');
|
||||
try {
|
||||
const set = {
|
||||
owner,
|
||||
type: keyType,
|
||||
when: date,
|
||||
whendateformat: formated,
|
||||
fireId: fire._id,
|
||||
geo: fire.ourid
|
||||
};
|
||||
return FalsePositives.upsert({ geo: fire.ourid, owner }, { $set: set }, { multi: false, upsert: true });
|
||||
} catch (exception) {
|
||||
console.log(exception);
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
'falsePositives.insert': function falsePositivesInsert(fireId, keyType) {
|
||||
check(fireId, Meteor.Collection.ObjectID);
|
||||
|
|
@ -40,9 +17,27 @@ Meteor.methods({
|
|||
}
|
||||
check(this.userId, String);
|
||||
const fire = Fires.findOne(fireId);
|
||||
const owner = this.userId;
|
||||
if (fire) {
|
||||
upsertFalsePositive(keyType, owner, fire);
|
||||
const fireType = fire.type;
|
||||
if (fireType === 'vecinal') {
|
||||
throw new Meteor.Error('500', 'No se puede marcar este tipo de fuego');
|
||||
}
|
||||
const date = new Date();
|
||||
const formated = moment(date).format('YYYYMMDD');
|
||||
try {
|
||||
const set = {
|
||||
owner: this.userId,
|
||||
type: keyType,
|
||||
when: date,
|
||||
whendateformat: formated,
|
||||
fireId,
|
||||
geo: fire.ourid
|
||||
};
|
||||
return FalsePositives.upsert({ geo: fire.ourid, owner: this.userId }, { $set: set }, { multi: false, upsert: true });
|
||||
} catch (exception) {
|
||||
console.log(exception);
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
} else {
|
||||
throw new Meteor.Error('500', 'Fuego no encontrado');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { NumberBetween } from '/imports/modules/server/other-checks';
|
||||
import '/imports/startup/server/leaflet-workaround.js';
|
||||
import L from 'leaflet';
|
||||
import L from 'leaflet-headless';
|
||||
import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify';
|
||||
import Industries from '/imports/api/Industries/Industries';
|
||||
import FalsePositives from '../FalsePositives';
|
||||
|
|
@ -18,26 +17,12 @@ Meteor.publish('falsePositivesTotal', function total() {
|
|||
});
|
||||
|
||||
export const firesUnion = (fires) => {
|
||||
const firesArray = Array.isArray(fires) ? fires : fires.fetch(); // if not is a cursor
|
||||
const remap = firesArray.map(function remap(doc) {
|
||||
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 group = new L.FeatureGroup();
|
||||
const remap = fires.fetch().map(function remap(doc) {
|
||||
// default scan: 1 for neightbor alerts
|
||||
return { location: { lat: doc.lat, lon: doc.lon }, distance: doc.scan || 1 };
|
||||
});
|
||||
const union = calcUnion(L, remap, sub => sub, false);
|
||||
return union;
|
||||
};
|
||||
|
||||
export const zoneToUnion = (lat, lon, distance) => {
|
||||
const remap = [{ location: { lat, lon }, distance }];
|
||||
const union = calcUnion(L, remap, sub => sub, true);
|
||||
const union = calcUnion(remap, group, sub => sub);
|
||||
return union;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,9 @@ Meteor.publish('fireAlerts', function fireAlerts(northEastLng, northEastLat, sou
|
|||
fields: {
|
||||
lat: 1,
|
||||
lon: 1,
|
||||
scan: 1,
|
||||
track: 1
|
||||
scan: 1
|
||||
}
|
||||
});
|
||||
if (Meteor.isDevelopment) console.log(`Neighbour alerts total: ${fires.count()}`);
|
||||
// console.log(`Fires total: ${fires.count()}`);
|
||||
return fires;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
|||
import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
|
||||
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
||||
import Industries from '/imports/api/Industries/Industries';
|
||||
import ravenLogger from '/imports/startup/server/ravenLogger';
|
||||
|
||||
import FiresCollection from '../Fires';
|
||||
|
||||
|
|
@ -133,21 +132,16 @@ Meteor.publish('fireFromId', function fireFromId(_id) {
|
|||
}
|
||||
});
|
||||
|
||||
function logUrl(fireEnc, params) {
|
||||
const message = `Wrong fire: ${fireEnc}. Params received in url: ${JSON.stringify(params)}`;
|
||||
console.log(message);
|
||||
ravenLogger.log(message);
|
||||
}
|
||||
|
||||
export function fireFromHash(fireEnc, params) {
|
||||
Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
|
||||
check(fireEnc, String);
|
||||
check(params, Object);
|
||||
|
||||
try {
|
||||
// console.log(fireEnc);
|
||||
// const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
||||
const unsealed = Promise.await(unsealW(fireEnc));
|
||||
if (unsealed === undefined) {
|
||||
throw Error(`Fail to unseal '${fireEnc}'`);
|
||||
console.info(`Wrong fire: ${fireEnc}`);
|
||||
// https://guide.meteor.com/data-loading.html
|
||||
return this.ready();
|
||||
}
|
||||
const w = unsealed.when;
|
||||
unsealed.when = new Date(w);
|
||||
|
|
@ -162,17 +156,8 @@ export function fireFromHash(fireEnc, params) {
|
|||
return findOrCreateFire(unsealedFix);
|
||||
/* console.log(`fires: ${fire.count()}`);
|
||||
* return fire; */
|
||||
}
|
||||
|
||||
Meteor.publish('fireFromHash', function fireFromHashFunc(fireEnc, params) {
|
||||
check(fireEnc, String);
|
||||
check(params, Object);
|
||||
try {
|
||||
return fireFromHash(fireEnc, params);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
logUrl(fireEnc, params);
|
||||
// https://guide.meteor.com/data-loading.html
|
||||
return this.ready();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
|
||||
|
|
@ -23,12 +22,11 @@ Notifications.deny({
|
|||
|
||||
Notifications.schema = new SimpleSchema({
|
||||
userId: String,
|
||||
subsId: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true },
|
||||
content: String,
|
||||
geo: LocationSchema,
|
||||
type: String,
|
||||
notified: { type: Boolean, optional: true },
|
||||
notifiedAt: { type: Date, optional: true },
|
||||
webNotified: { type: Boolean, optional: true },
|
||||
webNotifiedAt: { type: Date, optional: true },
|
||||
emailNotified: { type: Boolean, optional: true },
|
||||
emailNotifiedAt: { type: Date, optional: true },
|
||||
when: Date,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Meteor.methods({
|
|||
check(notifId, Meteor.Collection.ObjectID);
|
||||
|
||||
try {
|
||||
Notifications.update(notifId, { $set: { notified: true, notifiedAt: new Date() } });
|
||||
Notifications.update(notifId, { $set: { webNotified: true, webNotifiedAt: new Date() } });
|
||||
return notifId;
|
||||
} catch (exception) {
|
||||
throw new Meteor.Error('500', exception);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor';
|
|||
import Notifications from '../Notifications';
|
||||
|
||||
Meteor.publish('mynotifications', function notifications() {
|
||||
const notif = Notifications.find({ userId: this.userId, type: 'web', notified: null });
|
||||
const notif = Notifications.find({ userId: this.userId, type: 'web', webNotified: null });
|
||||
// console.log(`Notifications for user ${this.userId}: ${notif.count()}`);
|
||||
return notif;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,457 +0,0 @@
|
|||
/* global Restivus */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { NumberBetween } from '/imports/modules/server/other-checks';
|
||||
import Fires from '/imports/api/Fires/Fires';
|
||||
import { check } from 'meteor/check';
|
||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||
import { countRealFires } from '/imports/api/ActiveFires/server/countFires';
|
||||
import { whichAreFalsePositives, firesUnion, zoneToUnion } from '/imports/api/FalsePositives/server/publications';
|
||||
import { fireFromHash } from '/imports/api/Fires/server/publications.js';
|
||||
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
||||
import Industries from '/imports/api/Industries/Industries';
|
||||
import Subscriptions from '/imports/api/Subscriptions/Subscriptions';
|
||||
import jsend from 'jsend';
|
||||
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();
|
||||
|
||||
const restivusError = (code, message) => ({ status: 'error', statusCode: code, body: message });
|
||||
|
||||
|
||||
function failMsg(msg) {
|
||||
return restivusError(500, `Unexpected error in REST call: ${msg}`);
|
||||
}
|
||||
|
||||
function fail(e) {
|
||||
return restivusError(500, `Unexpected error in REST call: ${e}`);
|
||||
}
|
||||
|
||||
function defaultFailParams(e) {
|
||||
return restivusError(400, `Wrong REST params: ${e}`);
|
||||
}
|
||||
|
||||
function checkAuthToken(token) {
|
||||
if (!Meteor.settings.private.internalApiToken || token !== Meteor.settings.private.internalApiToken) {
|
||||
const message = 'Unauthorized auth token in REST API';
|
||||
console.warn(message);
|
||||
return restivusError(401, message);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function checkLatLonDist(km, lat, lng) {
|
||||
check(lng, NumberBetween(-180, 180));
|
||||
check(lat, NumberBetween(-90, 90));
|
||||
check(km, NumberBetween(0, Meteor.isDevelopment ? 1000 : 100));
|
||||
}
|
||||
|
||||
if (!Meteor.settings.private.internalApiToken) {
|
||||
console.warn('Meteor.settings.private.internalApiToken is not configured so we don\'t enable our REST API');
|
||||
} else {
|
||||
// export
|
||||
const apiV1 = new Restivus({
|
||||
useDefaultAuth: true,
|
||||
apiPath: 'api',
|
||||
version: 'v1',
|
||||
prettyJson: true
|
||||
});
|
||||
|
||||
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
|
||||
// Meteor.users collection
|
||||
/* apiV1.addCollection(Meteor.users, {
|
||||
* excludedEndpoints: ['getAll', 'put', 'delete', 'patch', 'get'],
|
||||
* routeOptions: {
|
||||
* authRequired: true
|
||||
* },
|
||||
* endpoints: {
|
||||
* post: {
|
||||
* authRequired: false
|
||||
* },
|
||||
* delete: {
|
||||
* roleRequired: 'admin'
|
||||
* }
|
||||
* }
|
||||
* }); */
|
||||
|
||||
apiV1.addCollection(Fires, {
|
||||
excludedEndpoints: ['put', 'post', 'patch', 'delete'],
|
||||
// excludedEndpoints: ['getAll', 'put', 'post', 'patch', 'delete'],
|
||||
routeOptions: {
|
||||
authRequired: false
|
||||
},
|
||||
endpoints: {
|
||||
get: {
|
||||
action: function getFire() {
|
||||
return Fires.findOne(new Meteor.Collection.ObjectID(this.urlParams.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Maps to: /api/v1/status/last-fire-check
|
||||
apiV1.addRoute('status/last-fire-check', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return SiteSettings.findOne({ name: 'last-fire-check' });
|
||||
}
|
||||
});
|
||||
|
||||
// Maps to: /api/v1/status/last-fire-detected
|
||||
apiV1.addRoute('status/last-fire-detected', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
||||
}
|
||||
});
|
||||
|
||||
// Maps to: /api/v1/status/active-fires-count
|
||||
apiV1.addRoute('status/active-fires-count', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return { total: ActiveFiresCollection.find({}).count() };
|
||||
}
|
||||
});
|
||||
|
||||
// Maps to: /api/v1/status/uptime
|
||||
apiV1.addRoute('status/uptime', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return { ms: new Date() - uptime };
|
||||
}
|
||||
});
|
||||
|
||||
function getFires(route, full) {
|
||||
const lat = Number(route.urlParams.lat);
|
||||
const lng = Number(route.urlParams.lng);
|
||||
const km = Number(route.urlParams.km);
|
||||
const { token } = route.urlParams;
|
||||
try {
|
||||
checkLatLonDist(km, lat, lng);
|
||||
check(token, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
if (debug) console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius`);
|
||||
|
||||
const fires = ActiveFiresCollection.find({
|
||||
ourid: {
|
||||
$near: {
|
||||
$geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [lng, lat]
|
||||
},
|
||||
$maxDistance: km * 1000,
|
||||
$minDistance: 0
|
||||
}
|
||||
}
|
||||
}, {
|
||||
fields: {
|
||||
lat: 1,
|
||||
lon: 1,
|
||||
when: 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;
|
||||
}
|
||||
|
||||
let union;
|
||||
|
||||
// TODO only get real
|
||||
const firesA = fires.fetch();
|
||||
|
||||
if (firesA.length > 0) {
|
||||
union = firesUnion(fires);
|
||||
} else {
|
||||
union = zoneToUnion(lat, lng, km);
|
||||
}
|
||||
const falsePos = whichAreFalsePositives(FalsePositives, union);
|
||||
const industries = whichAreFalsePositives(Industries, union);
|
||||
result.fires = firesA;
|
||||
result.industries = industries.fetch();
|
||||
result.falsePos = falsePos.fetch();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Maps to: /api/v1/fires-in/:lat/:lng/:km
|
||||
// 100 km max
|
||||
// Ex: http://127.0.0.1:3000/api/v1/fires-in/token/38.736946/-9.142685/100
|
||||
apiV1.addRoute('fires-in/:token/:lat/:lng/:km', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return getFires(this, false);
|
||||
}
|
||||
});
|
||||
|
||||
apiV1.addRoute('fires-in-full/:token/:lat/:lng/:km', { authRequired: false }, {
|
||||
get: function get() {
|
||||
return getFires(this, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Add mobile user:
|
||||
// curl -X POST http://localhost:3000/api/v1/users/mobile -d "token: thisAppAutToken" -d "mobileToken: user-mobile-firebase-token"
|
||||
// Response:
|
||||
//
|
||||
// https://docs.meteor.com/api/passwords.html#Accounts-createUser
|
||||
apiV1.addRoute('mobile/users', { authRequired: false }, {
|
||||
post: function post() {
|
||||
const { token, mobileToken, lang } = this.bodyParams;
|
||||
try {
|
||||
check(token, String);
|
||||
check(lang, String);
|
||||
check(mobileToken, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
let username;
|
||||
|
||||
const already = Meteor.users.find({ fireBaseToken: mobileToken });
|
||||
|
||||
if (already.count() > 1) {
|
||||
return restivusError(500, 'Unexpected error in REST call: several users with that mobile token?');
|
||||
} else if (already.count() === 1) {
|
||||
username = already.fetch()[0].username;
|
||||
} else {
|
||||
do {
|
||||
username = Random.id(15);
|
||||
} while (Meteor.users.find({ username }).count() !== 0);
|
||||
}
|
||||
|
||||
// FIXME check valid lang
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const result = Meteor.users.upsert({
|
||||
fireBaseToken: mobileToken
|
||||
}, {
|
||||
$set: {
|
||||
username,
|
||||
fireBaseToken: mobileToken,
|
||||
lang,
|
||||
profile: { },
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (debug) {
|
||||
console.log(this.bodyParams);
|
||||
console.log(this.urlParams);
|
||||
console.log(this.queryParams);
|
||||
}
|
||||
|
||||
const upsertUser = Meteor.users.findOne({ username });
|
||||
|
||||
if (debug) console.log(upsertUser);
|
||||
|
||||
return jsend.success({
|
||||
upsertResult: result,
|
||||
username,
|
||||
userId: upsertUser._id,
|
||||
lang: upsertUser.lang,
|
||||
mobileToken: upsertUser.fireBaseToken
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// max sitance: 100km
|
||||
apiV1.addRoute('mobile/subscriptions', { authRequired: false }, {
|
||||
post: function post() {
|
||||
const {
|
||||
token,
|
||||
mobileToken,
|
||||
id,
|
||||
lat,
|
||||
lon,
|
||||
distance
|
||||
} = this.bodyParams;
|
||||
let oid;
|
||||
try {
|
||||
check(token, String);
|
||||
check(mobileToken, String);
|
||||
check(id, String);
|
||||
oid = new Mongo.ObjectID(id);
|
||||
check(oid, Meteor.Collection.ObjectID);
|
||||
check(lat, Number);
|
||||
check(lon, Number);
|
||||
check(distance, Number);
|
||||
checkLatLonDist(distance, lat, lon);
|
||||
} catch (e) {
|
||||
if (debug) console.log(e);
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
const newSubs = {};
|
||||
newSubs._id = new Meteor.Collection.ObjectID(id);
|
||||
newSubs.location = {};
|
||||
newSubs.location.lat = lat;
|
||||
newSubs.location.lon = lon;
|
||||
newSubs.distance = distance;
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = subscriptionsInsert(newSubs, user._id, 'mobile');
|
||||
} catch (e) {
|
||||
return fail(e);
|
||||
}
|
||||
return jsend.success({ subsId: result._str });
|
||||
}
|
||||
});
|
||||
|
||||
apiV1.addRoute('mobile/subscriptions/:token/:mobileToken/:subsId', { authRequired: false }, {
|
||||
delete: function del() {
|
||||
const {
|
||||
token,
|
||||
mobileToken,
|
||||
subsId
|
||||
} = this.urlParams;
|
||||
try {
|
||||
check(token, String);
|
||||
check(mobileToken, String);
|
||||
check(subsId, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
try {
|
||||
Subscriptions.remove({ owner: user._id, _id: new Meteor.Collection.ObjectID(subsId) });
|
||||
} catch (e) {
|
||||
return fail(e);
|
||||
}
|
||||
|
||||
return jsend.success({});
|
||||
}
|
||||
});
|
||||
|
||||
apiV1.addRoute('mobile/subscriptions/all/:token/:mobileToken', { authRequired: false }, {
|
||||
get: function get() {
|
||||
const { token, mobileToken } = this.urlParams;
|
||||
try {
|
||||
check(token, String);
|
||||
check(mobileToken, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
const result = Subscriptions.find({ owner: user._id });
|
||||
|
||||
return jsend.success({ subscriptions: result.fetch(), count: result.count() });
|
||||
},
|
||||
delete: function delAll() {
|
||||
const { token, mobileToken } = this.urlParams;
|
||||
try {
|
||||
check(token, String);
|
||||
check(mobileToken, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed('Auth api check failed');
|
||||
|
||||
if (Meteor.users.find({ fireBaseToken: mobileToken }).count() !== 1) return fail;
|
||||
|
||||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
const toRemove = Subscriptions.find({ owner: user._id }).count();
|
||||
Subscriptions.remove({ owner: user._id });
|
||||
|
||||
return jsend.success({ count: toRemove });
|
||||
}
|
||||
});
|
||||
|
||||
apiV1.addRoute('status/subs-public-union/:token', { authRequired: false }, {
|
||||
get: function get() {
|
||||
const { token } = this.urlParams;
|
||||
try {
|
||||
check(token, String);
|
||||
} catch (e) {
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
const currentUnion = SiteSettings.findOne({ name: 'subs-public-union' });
|
||||
const userSubsBounds = SiteSettings.findOne({ name: 'subs-public-union-bounds' });
|
||||
|
||||
return jsend.success({ union: currentUnion, bounds: userSubsBounds });
|
||||
}
|
||||
});
|
||||
|
||||
apiV1.addRoute('mobile/falsepositive', { authRequired: false }, {
|
||||
post: function post() {
|
||||
const {
|
||||
token,
|
||||
mobileToken,
|
||||
sealed,
|
||||
type
|
||||
} = this.bodyParams;
|
||||
try {
|
||||
check(token, String);
|
||||
check(mobileToken, String);
|
||||
check(sealed, String);
|
||||
check(type, String);
|
||||
} catch (e) {
|
||||
if (debug) console.log(e);
|
||||
return defaultFailParams(e);
|
||||
}
|
||||
|
||||
const failed = checkAuthToken(token);
|
||||
if (failed) return failed;
|
||||
|
||||
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||
if (!user) return failMsg('User not found');
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||
|
|
@ -43,14 +42,12 @@ Subscriptions.deny({
|
|||
|
||||
|
||||
Subscriptions.schema = new SimpleSchema({
|
||||
_id: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true },
|
||||
location: Object,
|
||||
'location.lat': Number,
|
||||
'location.lon': Number,
|
||||
geo: LocationSchema,
|
||||
distance: Number,
|
||||
chatId: { type: Number, optional: true }, // only in 'telegram' type
|
||||
telegramBot: { type: String, optional: true }, // only in 'telegram' type
|
||||
owner: String,
|
||||
type: String,
|
||||
createdAt: defaultCreatedAt,
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@ function geo(doc) {
|
|||
};
|
||||
}
|
||||
|
||||
export function subscriptionsInsert(doc, userId, type) {
|
||||
Meteor.methods({
|
||||
'subscriptions.insert': function subscriptionsInsert(doc) {
|
||||
check(doc, {
|
||||
_id: Match.Maybe(Meteor.Collection.ObjectID),
|
||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||
distance: Number
|
||||
});
|
||||
const type = 'web';
|
||||
const newDoc = {
|
||||
owner: userId,
|
||||
owner: this.userId,
|
||||
type,
|
||||
geo: geo(doc),
|
||||
...doc
|
||||
|
|
@ -33,26 +34,6 @@ export function subscriptionsInsert(doc, userId, type) {
|
|||
// console.error(exception);
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
|
||||
export function subscriptionsRemove(subscriptionId) {
|
||||
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||
|
||||
try {
|
||||
return Subscriptions.remove(subscriptionId);
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
|
||||
Meteor.methods({
|
||||
'subscriptions.insert': function subscriptionInsertMethod(doc) {
|
||||
check(doc, {
|
||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||
distance: Number
|
||||
});
|
||||
return subscriptionsInsert(doc, this.userId, 'web');
|
||||
},
|
||||
'subscriptions.update': function subscriptionsUpdate(doc) {
|
||||
check(doc, {
|
||||
|
|
@ -71,9 +52,15 @@ Meteor.methods({
|
|||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
},
|
||||
'subscriptions.remove': function subscriptionsRemoveMethod(subscriptionId) {
|
||||
'subscriptions.remove': function subscriptionsRemove(subscriptionId) {
|
||||
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||
return subscriptionsRemove(subscriptionId);
|
||||
|
||||
try {
|
||||
return Subscriptions.remove(subscriptionId);
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
throw new Meteor.Error('500', exception);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -78,8 +78,11 @@ const schemaUser = new SimpleSchema({
|
|||
// Option 2: [String] type
|
||||
// If you are sure you will never need to use role groups, then
|
||||
// you can specify [String] as the type
|
||||
roles: { type: Array, optional: true },
|
||||
'roles.$': { type: String, optional: true },
|
||||
roles: {
|
||||
type: Array,
|
||||
// autoform: { type: 'hidden' },
|
||||
optional: true
|
||||
},
|
||||
// https://github.com/todda00/meteor-friendly-slugs/issues/1
|
||||
friendlySlugs: {
|
||||
type: Object,
|
||||
|
|
@ -107,7 +110,6 @@ const schemaUser = new SimpleSchema({
|
|||
telegramUsername: { type: String, optional: true },
|
||||
telegramFirstName: { type: String, optional: true },
|
||||
telegramLanguageCode: { type: String, optional: true },
|
||||
fireBaseToken: { type: String, optional: true },
|
||||
createdAt: defaultCreatedAt,
|
||||
updatedAt: defaultUpdateAt
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ const LocationSchema = new SimpleSchema({
|
|||
minCount: 2,
|
||||
maxCount: 2,
|
||||
custom: function custom() {
|
||||
if (!(this.value[0] >= -180 && this.value[0] <= 180)) {
|
||||
if (!(this.value[0] >= -90 && this.value[0] <= 90)) {
|
||||
return 'lngOutOfRange';
|
||||
}
|
||||
if (!(this.value[1] >= -90 && this.value[1] <= 90)) {
|
||||
if (!(this.value[1] >= -180 && this.value[1] <= 180)) {
|
||||
return 'latOutOfRange';
|
||||
}
|
||||
return true;
|
||||
|
|
@ -27,8 +27,8 @@ const LocationSchema = new SimpleSchema({
|
|||
});
|
||||
|
||||
LocationSchema.messageBox.messages({
|
||||
latOutOfRange: '[label] latitude should be between -90 and 90',
|
||||
lngOutOfRange: '[label] longitude should be between -180 and 180'
|
||||
lonOutOfRange: '[label] longitude should be between -90 and 90',
|
||||
latOutOfRange: '[label] latitude should be between -180 and 180'
|
||||
});
|
||||
|
||||
export default LocationSchema;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import fs from 'fs';
|
||||
import getFallbackLang from '/imports/modules/lang-fallback';
|
||||
import getFallbackLang from '/imports/startup/server/accounts/lang-fallback';
|
||||
|
||||
const as = f => `assets/app/${f}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import getOAuthProfile from '/imports/modules/get-oauth-profile';
|
||||
|
||||
const getEmailOf = (user) => {
|
||||
const hasPassword = user.services && user.services.password && user.services.password.bcrypt;
|
||||
|
||||
// console.log(`Has password: ${hasPassword}`);
|
||||
const OAuthProfile = getOAuthProfile({
|
||||
password: hasPassword,
|
||||
profile: user.profile
|
||||
}, user);
|
||||
const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first;
|
||||
const emailAddress = OAuthProfile ? OAuthProfile.email :
|
||||
user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null;
|
||||
return { emailAddress, firstName };
|
||||
};
|
||||
|
||||
export default getEmailOf;
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
/* 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;
|
||||
|
|
@ -1,21 +1,11 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import i18n from 'i18next';
|
||||
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
|
||||
import sendMail from '/imports/startup/server/email';
|
||||
// import { Email } from 'meteor/email';
|
||||
import getPrivateFile from './get-private-file';
|
||||
import templateToText from './handlebars-email-to-text';
|
||||
import templateToHTML from './handlebars-email-to-html';
|
||||
|
||||
export function subjectTruncate(n = 70, useWordBoundary = true) {
|
||||
// https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript
|
||||
if (this.length <= n) {
|
||||
return `${this}`;
|
||||
}
|
||||
const subString = this.substr(0, n - 1);
|
||||
return `${useWordBoundary ? subString.substr(0, subString.lastIndexOf(' ')) : subString}...`;
|
||||
}
|
||||
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
|
||||
import sendMail from '/imports/startup/server/email';
|
||||
import i18n from 'i18next';
|
||||
|
||||
const sendEmail = (options, { resolve, reject }) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import Twit from 'twit';
|
||||
|
||||
// https://www.npmjs.com/package/twit
|
||||
const confTwitter = (lang) => {
|
||||
const tConf = Meteor.settings.private.twitter[lang];
|
||||
|
||||
const T = new Twit({
|
||||
consumer_key: tConf.consumer_key,
|
||||
consumer_secret: tConf.consumer_secret,
|
||||
access_token: tConf.access_token,
|
||||
access_token_secret: tConf.access_token_secret
|
||||
// timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests.
|
||||
});
|
||||
|
||||
return T;
|
||||
};
|
||||
|
||||
const tweet = (message, lang) => {
|
||||
const T = confTwitter(lang);
|
||||
T.post('statuses/update', { status: message }, (err, data, response) => {
|
||||
// console.log(data);
|
||||
// console.log(response);
|
||||
});
|
||||
};
|
||||
|
||||
export default tweet;
|
||||
|
|
@ -17,7 +17,7 @@ import i18nOpts from '../common/i18n';
|
|||
|
||||
const detectorOptions = {
|
||||
// order and from where user language should be detected
|
||||
order: ['querystring', 'navigator', 'cookie', 'localStorage', 'htmlTag'],
|
||||
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
|
||||
|
||||
// keys or params to lookup language from
|
||||
lookupQuerystring: 'lng',
|
||||
|
|
@ -61,14 +61,6 @@ if (sendMissing && Meteor.isDevelopment) {
|
|||
};
|
||||
}
|
||||
|
||||
function setT9(lang) {
|
||||
if (lang === 'gl') {
|
||||
T9n.setLanguage('es');
|
||||
} else {
|
||||
T9n.setLanguage(lang);
|
||||
}
|
||||
}
|
||||
|
||||
i18n.use(backend)
|
||||
.use(LngDetector)
|
||||
.use(Cache)
|
||||
|
|
@ -78,16 +70,15 @@ i18n.use(backend)
|
|||
console.error(err);
|
||||
return;
|
||||
}
|
||||
console.log(`Language initialized: ${i18n.language}`);
|
||||
|
||||
i18nReady.set(true);
|
||||
// document.title = t('AppName');
|
||||
// Accounts translation
|
||||
// https://github.com/softwarerero/meteor-accounts-t9n
|
||||
setT9(i18n.language);
|
||||
// console.log("Language: " + i18n.language);
|
||||
T9n.setLanguage(i18n.language);
|
||||
// console.log(T9n.get('error.accounts.User not found'));
|
||||
moment.locale(i18n.language);
|
||||
|
||||
i18nReady.set(true);
|
||||
moment.locale(i18n.language);
|
||||
|
||||
// cookies eu consent
|
||||
const cookiesOpt = {
|
||||
|
|
@ -112,8 +103,7 @@ Meteor.subscribe('userData'); // lang is there
|
|||
|
||||
i18n.on('languageChanged', (lng) => {
|
||||
moment.locale(lng);
|
||||
// TODO fix this when gl is translated
|
||||
setT9(lng);
|
||||
T9n.setLanguage(lng);
|
||||
Tracker.autorun((computation) => {
|
||||
if (Meteor.userId()) {
|
||||
// logged
|
||||
|
|
|
|||
|
|
@ -1,19 +1,8 @@
|
|||
/* global */
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import '/imports/startup/client/modernizr';
|
||||
import App from '../../ui/layouts/App/App';
|
||||
|
||||
import '../../ui/stylesheets/app.scss';
|
||||
|
||||
// https://stackoverflow.com/questions/19562207/jquery-detect-browser-ie9-and-below-and-throw-up-a-modal-to-upgrade
|
||||
const isIE9OrBelow = () => /MSIE\s/.test(navigator.userAgent) && parseFloat(navigator.appVersion.split('MSIE')[1]) < 10;
|
||||
|
||||
if (isIE9OrBelow()) window.alert('You are using an outdated browser. Please use Chrome or Firefox to display this site.');
|
||||
else {
|
||||
Modernizr.on('webp', (result) => {
|
||||
if (Meteor.isDevelopment) console.log(`webp ${result ? '' : 'not '}supported`);
|
||||
});
|
||||
Meteor.startup(() => render(<App />, document.getElementById('react-root')));
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,8 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
|
||||
Meteor.startup(() => Tracker.autorun(() => {
|
||||
const userId = Meteor.userId();
|
||||
Meteor.startup(function() {
|
||||
return Tracker.autorun(function() {
|
||||
var userId;
|
||||
userId = Meteor.userId();
|
||||
// console.log(userId);
|
||||
Meteor.Piwik.setUserInfo(userId);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@ import RavenLogger from 'meteor/flowkey:raven';
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const ravenOptions = {};
|
||||
const publicDSN = Meteor.settings.public.sentryPublicDSN;
|
||||
const enabled = !!publicDSN;
|
||||
|
||||
const ravenLogger = enabled ? new RavenLogger({
|
||||
publicDSN, // will be used on the client
|
||||
const ravenLogger = new RavenLogger({
|
||||
publicDSN: Meteor.settings.public.sentryPublicDSN, // will be used on the client
|
||||
shouldCatchConsoleError: true, // default
|
||||
trackUser: true // default
|
||||
}, ravenOptions) : { log: (error) => { console.log(error); } };
|
||||
|
||||
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
|
||||
}, ravenOptions);
|
||||
|
||||
export default ravenLogger;
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
/* global reconnecToServer */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// Reconnect Function
|
||||
reconnectToServer(5000, true);
|
||||
});
|
||||
|
|
@ -3,8 +3,7 @@
|
|||
// Client and Server
|
||||
Comments.config({
|
||||
rating: 'likes-and-dislikes',
|
||||
allowAnonymous: () => false,
|
||||
allowReplies: () => false, // disabled right now: I don't know hot to get referenceId of replies
|
||||
allowAnonymous: () => true,
|
||||
anonymousSalt: 'klasddl3lala0l3lasdlas0ol3lasdlao3lasdoaslaldal3lasdclasdlal3lasdladlaq',
|
||||
publishUserFields: {
|
||||
profile: 1
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ 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
|
||||
|
|
@ -23,10 +21,7 @@ const shouldDebug = (forceDebug && !Meteor.isProduction);
|
|||
const i18nOpts = {
|
||||
backend: backOpts,
|
||||
// lng: 'es',
|
||||
fallbackLng: {
|
||||
gl: ['es'],
|
||||
default: ['en']
|
||||
},
|
||||
fallbackLng: ['es', 'en'],
|
||||
sendMissingTo: 'fallback',
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react!!
|
||||
|
|
@ -40,7 +35,7 @@ const i18nOpts = {
|
|||
return value;
|
||||
}
|
||||
},
|
||||
whitelist: ['es', 'en', 'gl'], // allowed languages
|
||||
whitelist: ['es', 'en'], // allowed languages
|
||||
load: 'languageOnly', // 'es' o 'en', previously: 'all', // es-ES -> es, en-US -> en
|
||||
debug: shouldDebug,
|
||||
ns: 'common',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const getFallbackLang = (lang) => {
|
||||
if (lang && (lang === 'ast' || lang === 'gl' || lang === 'eu' ||
|
||||
lang === 'es' || lang === 'ca' || lang.match(/^es-/))) {
|
||||
if (lang && (lang === 'ast' || lang === 'gl' || lang === 'eu' || lang === 'ca' || lang.match(/^es-/))) {
|
||||
return 'es';
|
||||
}
|
||||
return 'en';
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import getFallbackLang from '/imports/modules/lang-fallback';
|
||||
import sendWelcomeEmail from '/imports/api/Users/server/send-welcome-email';
|
||||
import getOAuthProfile from '/imports/modules/get-oauth-profile';
|
||||
import sendWelcomeEmail from '../../../api/Users/server/send-welcome-email';
|
||||
import getOAuthProfile from '../../../modules/get-oauth-profile';
|
||||
import getFallbackLang from './lang-fallback';
|
||||
|
||||
Accounts.onCreateUser((options, user) => {
|
||||
const userToCreate = user;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ 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';
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import ravenLogger from '/imports/startup/server/ravenLogger';
|
||||
|
||||
Meteor.startup(() => {
|
||||
const bound = Meteor.bindEnvironment((callback) => { callback(); });
|
||||
process.on('uncaughtException', (err) => {
|
||||
bound(() => {
|
||||
console.error(err);
|
||||
|
||||
// https://github.com/flowkey/meteor-raven
|
||||
ravenLogger.log(err);
|
||||
|
||||
// https://github.com/nodejs/node-v0.x-archive/blob/master/doc/api/process.markdown#exit-codes
|
||||
process.exit(7);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/* global Comments */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import i18n from 'i18next';
|
||||
import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email';
|
||||
import getEmailOf from '/imports/modules/get-email-of-user';
|
||||
import '../common/comments';
|
||||
|
||||
/* import i18n from 'i18next';
|
||||
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 { hr } from '/imports/startup/server/email';
|
||||
import getOAuthProfile from '/imports/modules/get-oauth-profile';
|
||||
import image from 'google-maps-image-api-url';
|
||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; */
|
||||
|
||||
Meteor.startup(() => {
|
||||
// On Server or Client (preferably on Server)
|
||||
Comments.config({
|
||||
onEvent: (name, action, payload) => {
|
||||
// e.g send a mail
|
||||
// console.log(`name: ${name}, action: ${action}, payload: ${JSON.stringify(payload)}`);
|
||||
|
||||
// Samples:
|
||||
|
||||
// name: comment, action: add, payload: {"referenceId":"fire-56a9cc5a5586879eb9cc6dd7","content":"asdfasdf asd fasd fas","userId":"tdK4e43ikjDXct7Gj","isAnonymous":false,"createdAt":"2018-03-04T14:53:40.319Z","likes":[],"dislikes":[],"replies":[],"media":{},"status":"approved","starRatings":[],"ratingScore":0,"_id":"mfAe3HL9qNofhiAnh"}
|
||||
// name: reply, action: add, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"}
|
||||
// name: reply, action: like, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":["tdK4e43ikjDXct7Gj"],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":1,"_id":"DyHt28rF6rTDnC5fP","ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"}
|
||||
// name: comment, action: like, payload: {"_id":"mfAe3HL9qNofhiAnh","ratedUserId":"tdK4e43ikjDXct7Gj"}
|
||||
// name: reply, action: edit, payload: {"_id":"DyHt28rF6rTDnC5fP","replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf dasd fadsfa","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"starRatings":[],"ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"}
|
||||
// name: reply, action: remove, payload: {"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"}
|
||||
|
||||
// name: (comment|reply)
|
||||
// action: (add|like|edit|remove)
|
||||
|
||||
if (name === 'comment' && action.match(/add|like|edit/)) {
|
||||
if (action === 'add') {
|
||||
// Check for other users that did comments and send an email (uniq users, and not this user)
|
||||
// console.log(payload.referenceId);
|
||||
const query = payload.isAnonymous ?
|
||||
{ referenceId: payload.referenceId } :
|
||||
{ referenceId: payload.referenceId, userId: { $ne: payload.userId } };
|
||||
Comments.getCollection().rawCollection().distinct('userId', query).then((users) => {
|
||||
// console.log(users);
|
||||
const path = payload.referenceId.replace(/fire-/, 'fire/archive/');
|
||||
const fireUrl = Meteor.absoluteUrl(path);
|
||||
// console.log(fireUrl);
|
||||
Meteor.users.find({ _id: { $in: users } }).forEach((user) => {
|
||||
const { firstName, emailAddress } = getEmailOf(user);
|
||||
// console.log(JSON.stringify(user));
|
||||
if (emailAddress) {
|
||||
const emailOpts = {
|
||||
to: emailAddress,
|
||||
subject: subjectTruncate.apply(i18n.t('Hay más información sobre un fuego')),
|
||||
lang: user.lang,
|
||||
template: 'new-fire-comment',
|
||||
templateVars: {
|
||||
applicationName: i18n.t('AppName'),
|
||||
firstName,
|
||||
fireUrl
|
||||
}
|
||||
};
|
||||
sendEmail(emailOpts).catch((error) => {
|
||||
throw new Meteor.Error('500', `${error}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
if (name === 'reply' && action.match(/add|like|edit/)) {
|
||||
// Check for other previous users to this thread and send an email
|
||||
// console.log('Reply op');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
/* global SyncedCron */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
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/
|
||||
|
||||
Meteor.startup(() => {
|
||||
SyncedCron.config({
|
||||
// Log job run details to console
|
||||
log: Meteor.settings.private.cron.debug || false,
|
||||
|
||||
// Use a custom logger function (defaults to Meteor's logging package)
|
||||
// workaround to disable logging succesfully
|
||||
logger: Meteor.settings.private.cron.debug ? null : () => {}
|
||||
|
||||
// Name of collection to use for synchronisation and logging
|
||||
// collectionName: 'cronHistory',
|
||||
|
||||
// Default to localTime
|
||||
// Options: 'utc', 'localtime', or specific timezones 'America/New_York'
|
||||
// Will be applied to jobs with no timezone defined
|
||||
// timezone: 'utc',
|
||||
|
||||
/*
|
||||
TTL in seconds for history records in collection to expire
|
||||
NOTE: Unset to remove expiry but ensure you remove the index from
|
||||
mongo by hand
|
||||
|
||||
ALSO: SyncedCron can't use the `_ensureIndex` command to modify
|
||||
the TTL index. The best way to modify the default value of
|
||||
`collectionTTL` is to remove the index by hand (in the mongo shell
|
||||
run `db.cronHistory.dropIndex({startedAt: 1})`) and re-run your
|
||||
project. SyncedCron will recreate the index with the updated TTL.
|
||||
*/
|
||||
// collectionTTL: 172800
|
||||
});
|
||||
|
||||
if (isMailServerMaster) {
|
||||
SyncedCron.add({
|
||||
name: 'Send emails',
|
||||
timezone: 'Europe/Madrid',
|
||||
schedule: (parser) => {
|
||||
// http://bunkat.github.io/later/
|
||||
const sched = parser.text('every 10 min');
|
||||
if (sched.error !== -1) {
|
||||
console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`);
|
||||
}
|
||||
return sched;
|
||||
},
|
||||
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;
|
||||
const enEn = Meteor.settings.private.twitter.en.enabled;
|
||||
|
||||
if (esEn) {
|
||||
SyncedCron.add({
|
||||
name: 'Check for fires stats in Spain and tweet about',
|
||||
timezone: 'Europe/Madrid',
|
||||
// Optionally set a positive offset if you wish to 'snooze' a schedule
|
||||
// offset: 30 * 60 * 100,
|
||||
// context: {
|
||||
// userID: 'xyz'
|
||||
// },
|
||||
schedule: (parser) => {
|
||||
// this.magic = true; // Context is accesible here as this context.
|
||||
// parser is a later.parse object
|
||||
// return parser.text('every 2 minutes');
|
||||
// http://bunkat.github.io/later/
|
||||
const sched = parser.text(Meteor.settings.private.twitter.es.when);
|
||||
if (sched.error !== -1) {
|
||||
console.error(`Twitter cron 'when' field parsed with errors: ${sched.error}`);
|
||||
}
|
||||
return sched;
|
||||
},
|
||||
job: () => tweetIberiaFires()
|
||||
/* console.log('cron is working');
|
||||
* console.log(this.userID) // Context Object becomes this argument
|
||||
* console.log(this.magic) /
|
||||
* var numbersCrunched = CrushSomeNumbers();
|
||||
* return numbersCrunched;
|
||||
return tweetFires(); */
|
||||
});
|
||||
}
|
||||
|
||||
if (enEn) {
|
||||
SyncedCron.add({
|
||||
name: 'Check for fires stats in Europe and tweet about',
|
||||
timezone: 'Europe/Madrid',
|
||||
schedule: (parser) => {
|
||||
const sched = parser.text(Meteor.settings.private.twitter.en.when);
|
||||
if (sched.error !== -1) {
|
||||
console.error(`Twitter cron 'when' field parsed with errors: ${sched.error}`);
|
||||
}
|
||||
return sched;
|
||||
},
|
||||
job: () => tweetEuropeFires()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (isMailServerMaster || esEn || enEn) {
|
||||
SyncedCron.start();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { MailTime } from 'meteor/ostrio:mailer';
|
||||
import i18n from 'i18next';
|
||||
import isMaster from './isMaster';
|
||||
|
||||
const transports = [];
|
||||
|
||||
|
|
@ -20,16 +20,9 @@ export const hr = `<table cellspacing="0" cellpadding="0" border="0" width="100%
|
|||
</tr>
|
||||
</table>`;
|
||||
|
||||
let MailQueue;
|
||||
|
||||
export const isMailServerMaster = Meteor.settings.private.isMailServer && isMaster();
|
||||
|
||||
if (isMailServerMaster) {
|
||||
console.log('I\'m the mail server');
|
||||
MailQueue = new MailTime({
|
||||
const MailQueue = new MailTime({
|
||||
db,
|
||||
type: 'client', // instead of 'server' for now to avoid use the JosK task manager at 250ms, we use our cron
|
||||
// see https://github.com/VeliovGroup/Mail-Time/issues/5
|
||||
type: 'server',
|
||||
strategy: 'balancer', // Transports will be used in round robin chain
|
||||
transports,
|
||||
from(transport) {
|
||||
|
|
@ -50,16 +43,15 @@ if (isMailServerMaster) {
|
|||
// concatThrottling: 30,
|
||||
template: MailTime.Template // Use default template
|
||||
});
|
||||
} else {
|
||||
console.log('I\'m a mail client');
|
||||
MailQueue = new MailTime({
|
||||
db,
|
||||
type: 'client',
|
||||
debug: Meteor.settings.private.debugMailer,
|
||||
strategy: 'balancer', // Transports will be used in round robin chain
|
||||
concatEmails: true // Concatenate emails to the same address
|
||||
});
|
||||
}
|
||||
|
||||
// A Client (not used yet)
|
||||
// const MailQueueClient = new MailTime({
|
||||
// db,
|
||||
// type: 'client',
|
||||
// debug: true,
|
||||
// strategy: 'balancer', // Transports will be used in round robin chain
|
||||
// concatEmails: true // Concatenate emails to the same address
|
||||
// });
|
||||
|
||||
export default function sendMail(opts, debug) {
|
||||
if (debug) {
|
||||
|
|
@ -86,6 +78,27 @@ if (Meteor.settings.private.testMailer) {
|
|||
sendMail(emailOpts, true);
|
||||
}
|
||||
|
||||
export const sendEmailsFromQueue = () => {
|
||||
MailQueue.___send(() => { });
|
||||
|
||||
// Set interval to greater than 256
|
||||
// https://github.com/VeliovGroup/Mail-Time/issues/5
|
||||
const MailJobs = new Mongo.Collection('__JobTasks__mailTimeQueue', { idGeneration: 'MONGO' });
|
||||
|
||||
const updateJob = (mailJob) => {
|
||||
MailJobs.update(mailJob._id, { $set: { delay: 30000 } });
|
||||
// console.log(MailJobs.findOne());
|
||||
};
|
||||
|
||||
const mailJob = MailJobs.findOne();
|
||||
|
||||
if (mailJob) {
|
||||
updateJob(mailJob);
|
||||
}
|
||||
|
||||
MailJobs.find().observe({
|
||||
added: function notifAdded(item) {
|
||||
updateJob(item);
|
||||
},
|
||||
changed: function notifChanged(item) {
|
||||
updateJob(item);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
// Workaround for: https://github.com/meteor/meteor/issues/9796
|
||||
// https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
|
||||
// https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
|
||||
|
||||
import Fiber from 'fibers';
|
||||
|
||||
Fiber.poolSize = 1e9;
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
import './ravenLogger';
|
||||
import './catchExceptions';
|
||||
import './fibers';
|
||||
import './i18n';
|
||||
import './accounts';
|
||||
import './api';
|
||||
|
|
@ -10,11 +8,8 @@ import './IPGeocoder';
|
|||
import './migrations';
|
||||
import './notificationsObserver';
|
||||
import './facts';
|
||||
import './comments';
|
||||
import '../common/comments';
|
||||
import './sitemaps';
|
||||
import './subsUnion';
|
||||
import './prerender';
|
||||
import './feedback';
|
||||
import './cron';
|
||||
import './isMaster';
|
||||
import './rest';
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import pm2Master from 'pm2-master';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (pm2Master.isMaster()) {
|
||||
console.log('This is the master process');
|
||||
} else {
|
||||
console.log('This is a worker process');
|
||||
}
|
||||
});
|
||||
|
||||
const isMaster = () => pm2Master.isMaster();
|
||||
|
||||
export default isMaster;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// 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'
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/* global Migrations, Comments */
|
||||
/* global Migrations */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
|
@ -9,9 +9,6 @@ import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
|||
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(() => {
|
||||
// https://github.com/percolatestudio/meteor-migrations
|
||||
|
|
@ -160,101 +157,9 @@ Meteor.startup(() => {
|
|||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 11,
|
||||
up: function noAnonComments() {
|
||||
Comments.getCollection().remove({ isAnonymous: true });
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 12,
|
||||
up: function setTelegramUsersBotId() {
|
||||
Meteor.users.update({ telegramChatId: { $ne: null }, telegramBot: null }, {
|
||||
$set: {
|
||||
telegramBot: 'es'
|
||||
}
|
||||
}, { multi: true });
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 13,
|
||||
up: function removeTelegramBotFromUsersId() {
|
||||
Meteor.users.update({}, {
|
||||
$unset: {
|
||||
telegramBot: ''
|
||||
}
|
||||
}, { multi: true });
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 14,
|
||||
up: function setTelegramUsersBotId() {
|
||||
UserSubsToFiresCollection.update({ chatId: { $ne: null }, telegramBot: null }, {
|
||||
$set: {
|
||||
telegramBot: 'es'
|
||||
}
|
||||
}, { multi: true });
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 15,
|
||||
up: function moveToFalsePositivesUppercase() {
|
||||
/* const falsepositiveslower = new Mongo.Collection('falsepositives', { idGeneration: 'MONGO' });
|
||||
* falsepositiveslower.find({}).forEach((falseDoc) => {
|
||||
* FalsePositives.insert(falseDoc);
|
||||
* });*/
|
||||
// TODO remove falsepositives lowercase collection
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 16,
|
||||
up: function moveToFalsePositivesUppercaseWithUser() {
|
||||
const falsepositiveslower = new Mongo.Collection('falsepositives', { idGeneration: 'MONGO' });
|
||||
const now = new Date();
|
||||
falsepositiveslower.find({}).forEach((falseDoc) => {
|
||||
const user = Meteor.users.findOne({ telegramChatId: falseDoc.chatId });
|
||||
if (user) {
|
||||
falseDoc.owner= user._id;
|
||||
}
|
||||
falseDoc.type = 'industry';
|
||||
falseDoc.createdAt = now;
|
||||
falseDoc.updatedAt = now;
|
||||
FalsePositives.insert(falseDoc);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Migrations.add({
|
||||
version: 17,
|
||||
up: function renameWebNotifiedField() {
|
||||
Notifications.update({ webNotified: { $exists: true } }, {
|
||||
$rename: { webNotifiedAt: 'notifiedAt' }
|
||||
}, { upsert: false, multi: true });
|
||||
Notifications.update({ webNotified: { $exists: true } }, {
|
||||
$rename: { webNotified: 'notified' }
|
||||
}, { upsert: false, multi: true });
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
// Migrations.migrateTo('14,rerun');
|
||||
// Migrations.migrateTo('7,rerun');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,111 @@
|
|||
/* 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 processNotif from '/imports/modules/server/notificationsProcess.js';
|
||||
import { isMailServerMaster } from '/imports/startup/server/email';
|
||||
// import sendMail from '/imports/startup/server/email';
|
||||
import sendEmail from '/imports/modules/server/send-email';
|
||||
// import { hr } from '/imports/startup/server/email';
|
||||
import getOAuthProfile from '/imports/modules/get-oauth-profile';
|
||||
import image from 'google-maps-image-api-url';
|
||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (isMailServerMaster) {
|
||||
// https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript
|
||||
function truncate(n, useWordBoundary) {
|
||||
if (this.length <= n) {
|
||||
return this;
|
||||
}
|
||||
const subString = this.substr(0, n - 1);
|
||||
return `${useWordBoundary ?
|
||||
subString.substr(0, subString.lastIndexOf(' ')) :
|
||||
subString}...`;
|
||||
}
|
||||
|
||||
// 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"/>`;
|
||||
} */
|
||||
|
||||
function process(notif) {
|
||||
if (notif.type === 'web' && !notif.emailNotified) {
|
||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||
const hasPassword = user.services && user.services.password && user.services.password.bcrypt;
|
||||
|
||||
// console.log(`Has password: ${hasPassword}`);
|
||||
const OAuthProfile = getOAuthProfile({
|
||||
password: hasPassword,
|
||||
profile: user.profile
|
||||
}, user);
|
||||
const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first;
|
||||
const emailAddress = OAuthProfile ? OAuthProfile.email :
|
||||
user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null;
|
||||
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: truncate.apply(message, [50, true]),
|
||||
// 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) {
|
||||
processNotif(notif);
|
||||
process(notif);
|
||||
},
|
||||
changed: function notifChanged(updatedNotif) { // , oldNotif) {
|
||||
processNotif(updatedNotif);
|
||||
changed: function notifChanged(updatedNotif, oldNotif) {
|
||||
process(updatedNotif);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,17 +2,12 @@ import RavenLogger from 'meteor/flowkey:raven';
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const ravenOptions = {};
|
||||
const publicDSN = Meteor.settings.public.sentryPublicDSN;
|
||||
const privateDSN = Meteor.settings.sentryPrivateDSN;
|
||||
const enabled = publicDSN && privateDSN;
|
||||
|
||||
const ravenLogger = enabled ? new RavenLogger({
|
||||
publicDSN,
|
||||
privateDSN,
|
||||
shouldCatchConsoleError: true, // default true
|
||||
trackUser: true // default false
|
||||
}, ravenOptions) : { log: (error) => { console.log(error); } };
|
||||
|
||||
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
|
||||
const ravenLogger = new RavenLogger({
|
||||
publicDSN: Meteor.settings.public.sentryPublicDSN,
|
||||
privateDSN: Meteor.settings.sentryPrivateDSN,
|
||||
shouldCatchConsoleError: true, // default
|
||||
trackUser: true // default
|
||||
}, ravenOptions);
|
||||
|
||||
export default ravenLogger;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
import '../../api/Rest/Rest.js';
|
||||
|
|
@ -4,8 +4,6 @@
|
|||
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();
|
||||
|
||||
|
|
@ -29,7 +27,6 @@ sitemaps.add('/sitemap.xml', () => {
|
|||
});
|
||||
}); */
|
||||
|
||||
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 } });
|
||||
|
|
@ -38,7 +35,6 @@ sitemaps.add('/sitemap.xml', () => {
|
|||
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,19 +4,12 @@ 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 './leaflet-workaround';
|
||||
import L from 'leaflet';
|
||||
import L from 'leaflet-headless';
|
||||
import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify';
|
||||
import { isMailServerMaster } from '/imports/startup/server/email';
|
||||
|
||||
// sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev
|
||||
|
||||
Meteor.startup(() => {
|
||||
if (!isMailServerMaster) {
|
||||
console.log('We only process subsUnion in master');
|
||||
return;
|
||||
}
|
||||
const debug = true; // Meteor.isDevelopment;
|
||||
Perlin.seed(Math.random());
|
||||
|
||||
const addNoisy = (osub) => {
|
||||
|
|
@ -33,91 +26,59 @@ Meteor.startup(() => {
|
|||
return sub;
|
||||
};
|
||||
|
||||
const noNoisy = sub => sub;
|
||||
|
||||
const process = (isPublic) => {
|
||||
const subscribers = Subscriptions.find().fetch();
|
||||
const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true);
|
||||
const process = () => {
|
||||
const group = new L.FeatureGroup();
|
||||
const result = calcUnion(Subscriptions.find().fetch(), group, addNoisy);
|
||||
const union = result[0];
|
||||
const bounds = result[1];
|
||||
|
||||
const publicl = isPublic ? 'public' : 'private';
|
||||
const Publicl = publicl.replace(/\b\w/g, l => l.toUpperCase());
|
||||
|
||||
if (typeof union === 'object') {
|
||||
const unionSet = {
|
||||
$set: {
|
||||
name: `subs-${publicl}-union`,
|
||||
name: 'subs-public-union',
|
||||
value: JSON.stringify(union),
|
||||
isPublic,
|
||||
description: `${Publicl} subscriptions union`,
|
||||
isPublic: true,
|
||||
description: 'Public subscriptions union',
|
||||
type: 'string'
|
||||
}
|
||||
};
|
||||
const boundsSet = {
|
||||
$set: {
|
||||
name: `subs-${publicl}-union-bounds`,
|
||||
name: 'subs-public-union-bounds',
|
||||
value: JSON.stringify(bounds),
|
||||
isPublic,
|
||||
description: `${Publicl} subscriptions union bounds`,
|
||||
isPublic: true,
|
||||
description: 'Public subscriptions union bounds',
|
||||
type: 'string'
|
||||
}
|
||||
};
|
||||
const sizeSet = {
|
||||
$set: {
|
||||
name: 'subs-union-count',
|
||||
value: subscribers.length,
|
||||
isPublic: false,
|
||||
description: 'Subscriptions count',
|
||||
type: 'number'
|
||||
}
|
||||
};
|
||||
// FIXME, take care of object size:
|
||||
// https://stackoverflow.com/questions/10827812/what-is-the-length-maximum-for-a-string-data-type-in-mongodb-used-with-ruby
|
||||
SiteSettings.upsert({ name: `subs-${publicl}-union` }, unionSet, { multi: false });
|
||||
SiteSettings.upsert({ name: `subs-${publicl}-union-bounds` }, boundsSet, { multi: false });
|
||||
SiteSettings.upsert({ name: 'subs-union-count' }, sizeSet, { multi: false });
|
||||
if (debug) console.log(`${Publicl} subscription union calculated`);
|
||||
SiteSettings.upsert({ name: 'subs-public-union' }, unionSet, { multi: false });
|
||||
SiteSettings.upsert({ name: 'subs-public-union-bounds' }, boundsSet, { multi: false });
|
||||
if (Meteor.isDevelopment) console.log('Subscription union calculated');
|
||||
} else {
|
||||
console.log('Subscription union failed!');
|
||||
}
|
||||
};
|
||||
|
||||
// At startup, we check if it's necessary to calc subscriptions union again
|
||||
const currentUnion = SiteSettings.findOne({ name: 'subs-public-union' });
|
||||
const lastSubs = Subscriptions.findOne({}, { sort: { updatedAt: -1 } });
|
||||
const countUnionSubs = SiteSettings.findOne({ name: 'subs-union-count' });
|
||||
const countSubs = Subscriptions.find({}).count();
|
||||
if (currentUnion && lastSubs) {
|
||||
const lastUnionUpdated = currentUnion.updatedAt;
|
||||
const lastSubsUpdated = lastSubs.updatedAt;
|
||||
if (lastUnionUpdated > lastSubsUpdated || !countUnionSubs || countSubs !== countUnionSubs.value) {
|
||||
console.log('Subs union outdated');
|
||||
process(true);
|
||||
process(false);
|
||||
} else {
|
||||
console.log('Subs union up-to-date');
|
||||
}
|
||||
}
|
||||
// At startup
|
||||
process();
|
||||
|
||||
Subscriptions.find({ createdAt: { $gt: new Date() } }).observe({
|
||||
added: function newSubAdded() { // doc) {
|
||||
if (debug) console.log('Subs added so recreate union');
|
||||
process(true);
|
||||
process(false);
|
||||
if (Meteor.isDevelopment) console.log('Subs added so recreate union');
|
||||
process();
|
||||
}
|
||||
});
|
||||
|
||||
Subscriptions.find().observe({
|
||||
changed: function subsChanged() { // updatedDoc, oldDoc) {
|
||||
if (debug) console.log('Subs changed so recreate union');
|
||||
process(true);
|
||||
process(false);
|
||||
if (Meteor.isDevelopment) console.log('Subs changed so recreate union');
|
||||
process();
|
||||
},
|
||||
removed: function subsRemoved() { // oldDoc) {
|
||||
if (debug) console.log('Subs removed so recreate union');
|
||||
process(true);
|
||||
process(false);
|
||||
if (Meteor.isDevelopment) console.log('Subs removed so recreate union');
|
||||
process();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import ravenLogger from '/imports/startup/client/ravenLogger';
|
||||
import './ErrorBoundary.scss';
|
||||
|
||||
|
|
@ -12,6 +14,7 @@ import './ErrorBoundary.scss';
|
|||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.t = props.t;
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
|
|
@ -26,41 +29,33 @@ class ErrorBoundary extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
appName, title, subTitle, children
|
||||
} = this.props;
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return (
|
||||
<div className="error-boundary">
|
||||
<Row className="align-items-center justify-content-center">
|
||||
<Col xs={12} sm={6} md={5} lg={4}>
|
||||
<h1 className="page-header">{appName}</h1>
|
||||
<h4 className="page-header">{title}</h4>
|
||||
<p>{subTitle}</p>
|
||||
<h1 className="page-header">{this.t('AppNameFull')}</h1>
|
||||
<h4 className="page-header">{this.t('general-error-title')}</h4>
|
||||
<p>
|
||||
{this.t('general-error-description')}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
appName: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
subTitle: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
]).isRequired
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
ErrorBoundary.defaultProps = {
|
||||
appName: 'All Against the Fire!',
|
||||
title: 'Upppps: Something has gone wrong',
|
||||
subTitle: 'We are investigating the problem, try again in a while'
|
||||
};
|
||||
|
||||
export default ErrorBoundary;
|
||||
export default translate([], { wait: true })(withTracker(props => ({
|
||||
// props.something
|
||||
}))(ErrorBoundary));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { translate } from 'react-i18next';
|
||||
|
|
@ -18,13 +18,13 @@ import './Feedback.scss';
|
|||
class Feedback extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.t = props.t;
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.onTabClick = this.onTabClick.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const component = this;
|
||||
const { t } = this.props;
|
||||
|
||||
validate(component.form, {
|
||||
rules: {
|
||||
|
|
@ -38,11 +38,11 @@ class Feedback extends Component {
|
|||
},
|
||||
messages: {
|
||||
feedbackText: {
|
||||
required: t('Por favor, escribe aquí tu feedback...')
|
||||
required: this.t('Por favor, escribe aquí tu feedback...')
|
||||
},
|
||||
email: {
|
||||
required: t('Tu correo'),
|
||||
email: t('¿Es correcto este correo?')
|
||||
required: this.t('Tu correo'),
|
||||
email: this.t('¿Es correcto este correo?')
|
||||
}
|
||||
},
|
||||
submitHandler() { component.handleSubmit(); }
|
||||
|
|
@ -71,9 +71,8 @@ class Feedback extends Component {
|
|||
render() {
|
||||
// console.log(`Render Feedback because isHome ${this.props.isHome}, email: '${this.props.emailAddress}'`);
|
||||
const disabled = this.props.emailVerified && this.props.emailAddress;
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Fragment>
|
||||
{ !this.props.isHome &&
|
||||
<div id="feedback">
|
||||
<div id="feedback-form" ref={formdiv => (this.formdiv = formdiv)} style={{ display: 'none' }} className="card">
|
||||
|
|
@ -89,7 +88,7 @@ class Feedback extends Component {
|
|||
name="email"
|
||||
className="form-control"
|
||||
ref={email => (this.email = email)}
|
||||
placeholder={t('Tu correo')}
|
||||
placeholder={this.t('Tu correo')}
|
||||
key={disabled ? 'disabledEmail' : 'enabledEmail'}
|
||||
disabled={disabled}
|
||||
defaultValue={disabled ? this.props.emailAddress : ''}
|
||||
|
|
@ -104,13 +103,13 @@ class Feedback extends Component {
|
|||
className="form-control"
|
||||
name="feedbackText"
|
||||
ref={feedbackText => (this.feedbackText = feedbackText)}
|
||||
placeholder={t('Por favor, escribe aquí tu feedback...')}
|
||||
placeholder={this.t('Por favor, escribe aquí tu feedback...')}
|
||||
rows="6"
|
||||
/>
|
||||
<FormControl.Feedback />
|
||||
</FormGroup>
|
||||
<Button type="submit" bsStyle="success" className="float-right" id={testId('sendFeedbackBtn')} >
|
||||
{t('Enviar')}
|
||||
{this.t('Enviar')}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -118,11 +117,11 @@ class Feedback extends Component {
|
|||
id="feedback-tab"
|
||||
onClick={(event) => { this.onTabClick(event); }}
|
||||
>
|
||||
{t('Feedback')}
|
||||
{this.t('Feedback')}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +133,7 @@ Feedback.propTypes = {
|
|||
isHome: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default translate()(withTracker(props => ({
|
||||
export default translate([], { wait: true })(withTracker(props => ({
|
||||
emailAddress: props.emailAddress,
|
||||
emailVerified: props.emailVerified,
|
||||
isHome: isHome()
|
||||
|
|
|
|||
|
|
@ -5,10 +5,6 @@ import React, { Component } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import Chronos from '/imports/ui/components/Chronos/Chronos';
|
||||
import { translate } from 'react-i18next';
|
||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||
|
||||
import './FromNow.scss';
|
||||
|
||||
class FromNow extends Component {
|
||||
constructor(props) {
|
||||
|
|
@ -31,39 +27,26 @@ class FromNow extends Component {
|
|||
return !(nextState.when === this.state.when);
|
||||
}
|
||||
|
||||
thatIs() {
|
||||
return this.props.t('esDecirTalDia', { date: dateLongFormat(this.props.whenDate) });
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(`Render from now ${this.state.when}`);
|
||||
return (
|
||||
<span data-toggle="tooltip" className="from-now" title={this.thatIs()}>{this.state.when}</span>
|
||||
<span>{this.state.when}</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FromNow.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
when: PropTypes.string,
|
||||
whenDate: PropTypes.instanceOf(Date)
|
||||
when: PropTypes.string
|
||||
};
|
||||
|
||||
FromNow.defaultProps = {
|
||||
};
|
||||
|
||||
/*
|
||||
* const FromNowContainer = withTracker((props) => {
|
||||
*
|
||||
* })(FromNow);
|
||||
*
|
||||
* export default FromNowContainer;
|
||||
* */
|
||||
|
||||
export default translate([], { wait: true })(withTracker((props) => {
|
||||
const FromNowContainer = withTracker((props) => {
|
||||
const whenDate = props.when;
|
||||
return {
|
||||
when: whenDate ? Chronos.moment(whenDate).fromNow() : null,
|
||||
whenDate
|
||||
when: whenDate ? Chronos.moment(whenDate).fromNow() : null
|
||||
};
|
||||
})(FromNow));
|
||||
})(FromNow);
|
||||
|
||||
export default FromNowContainer;
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
.from-now {
|
||||
border-bottom: 1px gray dashed;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Line } from 'react-progress-bar.js';
|
||||
import { Line } from 'react-progressbar.js';
|
||||
|
||||
import './LoadingBar.scss';
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ import './LoadingBar.scss';
|
|||
const LoadingBar = ({ progress }) => (
|
||||
<div className="loading-bar">
|
||||
<Line
|
||||
progress={Meteor.status().status !== 'connected' ? Meteor.status().retryCount / 10 : progress}
|
||||
progress={progress}
|
||||
options={{ strokeWidth: 2, color: '#5A7636' }}
|
||||
initialAnimate
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import { TileLayer, LayersControl } from 'react-leaflet';
|
|||
|
||||
const { BaseLayer } = LayersControl;
|
||||
|
||||
const defOpacity = 0.7;
|
||||
|
||||
class DefMapLayers extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -33,16 +31,13 @@ class DefMapLayers extends Component {
|
|||
const osmgraylayer = (
|
||||
<BaseLayer checked={this.props.gray} name={t('Mapa gris de OpenStreetMap')}>
|
||||
<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://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
|
||||
url="https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png"
|
||||
/>
|
||||
</BaseLayer>);
|
||||
const osmlayer = (
|
||||
<BaseLayer checked={this.props.osmcolor} name={t('Mapa color de OpenStreetMap')}>
|
||||
<TileLayer
|
||||
opacity={defOpacity}
|
||||
attribution="&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
|
|
@ -54,15 +49,15 @@ class DefMapLayers extends Component {
|
|||
{/* React.Fragment does not work here */}
|
||||
{ this.state.gkey &&
|
||||
<BaseLayer name={t('Mapa de carreteras de Google')}>
|
||||
<GoogleLayer opacity={defOpacity} googlekey={this.state.gkey} maptype="ROADMAP" />
|
||||
<GoogleLayer googlekey={this.state.gkey} maptype="ROADMAP" />
|
||||
</BaseLayer> }
|
||||
{ this.state.gkey &&
|
||||
<BaseLayer name={t('Mapa de terreno de Google')} checked={this.props.terrain}>
|
||||
<GoogleLayer opacity={defOpacity} googlekey={this.state.gkey} maptype="TERRAIN" />
|
||||
<BaseLayer name={t('Mapa de terreno de Google')}>
|
||||
<GoogleLayer googlekey={this.state.gkey} maptype="TERRAIN" />
|
||||
</BaseLayer> }
|
||||
{ this.state.gkey &&
|
||||
<BaseLayer name={t('Mapa de satélite de Google')} checked={this.props.satellite}>
|
||||
<GoogleLayer opacity={defOpacity} googlekey={this.state.gkey} maptype="SATELLITE" />
|
||||
<GoogleLayer googlekey={this.state.gkey} maptype="SATELLITE" />
|
||||
</BaseLayer> }
|
||||
</LayersControl>
|
||||
);
|
||||
|
|
@ -73,7 +68,6 @@ DefMapLayers.propTypes = {
|
|||
t: PropTypes.func.isRequired,
|
||||
gray: PropTypes.bool,
|
||||
osmcolor: PropTypes.bool,
|
||||
terrain: PropTypes.bool,
|
||||
satellite: PropTypes.bool
|
||||
|
||||
};
|
||||
|
|
@ -81,7 +75,6 @@ DefMapLayers.propTypes = {
|
|||
DefMapLayers.defaultProps = {
|
||||
gray: false,
|
||||
osmcolor: false,
|
||||
terrain: false,
|
||||
satellite: false
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import { GeoJSON } from 'react-leaflet';
|
||||
import { Circle } 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,26 +22,22 @@ class FireCircleMark extends Component {
|
|||
lat,
|
||||
lon,
|
||||
scan,
|
||||
track,
|
||||
nasa,
|
||||
id,
|
||||
history,
|
||||
when,
|
||||
t
|
||||
} = this.props;
|
||||
const rect = rectangleAround({ lat, lon }, track, track);
|
||||
return (
|
||||
<GeoJSON data={rect} color="red" stroke width="1" opacity=".4" fillOpacity="1">
|
||||
<Circle center={[lat, lon]} color="red" stroke={false} fillOpacity="1" fill radius={scan * 500} onClick={this.onClick}>
|
||||
<FirePopup t={t} history={history} id={id} nasa={nasa} lat={lat} lon={lon} when={when} />
|
||||
</GeoJSON>
|
||||
</Circle>
|
||||
);
|
||||
/* <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 { translate } from 'react-i18next';
|
||||
import { onMarkClick } from './MarkListeners';
|
||||
import { translate } from 'react-i18next';
|
||||
import FirePopup from './FirePopup';
|
||||
|
||||
class FireIconMark extends Component {
|
||||
|
|
@ -30,7 +30,16 @@ class FireIconMark extends Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t
|
||||
lat,
|
||||
lon,
|
||||
scan,
|
||||
nasa,
|
||||
id,
|
||||
history,
|
||||
falsePositives,
|
||||
industries,
|
||||
when,
|
||||
t
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -69,11 +78,9 @@ 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,
|
||||
neighbour: PropTypes.bool.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
when: PropTypes.instanceOf(Date),
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import FirePixel from '/imports/ui/components/Maps/FirePixel';
|
|||
|
||||
export default function FireList(props) {
|
||||
const {
|
||||
fires, scale, useMarkers, nasa, t, history, falsePositives, industries, neighbour
|
||||
fires, scale, useMarkers, nasa, t, history, falsePositives, industries
|
||||
} = props;
|
||||
const useMarks = useMarkers && scale;
|
||||
const usePixel = !nasa || !scale;
|
||||
/* console.log(`Using marks: ${useMarks}, using pixels: ${usePixel}`); */
|
||||
let items;
|
||||
if (useMarks) {
|
||||
items = fires.map(({ _id, ...otherProps }) => (<FireIconMark t={t} history={history} id={_id} key={_id} nasa={nasa} neighbour={neighbour} falsePositives={falsePositives} industries={industries} {...otherProps} />));
|
||||
items = fires.map(({ _id, ...otherProps }) => (<FireIconMark t={t} history={history} id={_id} key={_id} nasa={nasa} falsePositives={falsePositives} industries={industries} {...otherProps} />));
|
||||
} else if (usePixel && !falsePositives && !industries) {
|
||||
items = fires.map(({ _id, ...otherProps }) => (<FirePixel t={t} key={_id} id={_id} history={history} nasa={nasa} {...otherProps} />));
|
||||
} else if (!falsePositives && !industries) {
|
||||
|
|
@ -32,7 +32,6 @@ FireList.propTypes = {
|
|||
nasa: PropTypes.bool.isRequired,
|
||||
falsePositives: PropTypes.bool.isRequired,
|
||||
industries: PropTypes.bool.isRequired,
|
||||
neighbour: PropTypes.bool.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
/* 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,12 +3,12 @@
|
|||
import React from 'react';
|
||||
import { CircleMarker } from 'react-leaflet';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import FirePopup from './FirePopup';
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
/* Less acurate (1 pixel per fire) but faster */
|
||||
const FirePixel = ({
|
||||
lat, lon, nasa, id, when, t, history, track
|
||||
lat, lon, nasa, id, when, t, history
|
||||
}) => (
|
||||
<CircleMarker
|
||||
center={[lat, lon]}
|
||||
|
|
@ -22,13 +22,12 @@ const FirePixel = ({
|
|||
</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
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
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;
|
||||
|
|
@ -13,21 +13,21 @@ export const fireIcon = new Leaflet.Icon({
|
|||
});
|
||||
|
||||
export const fireIconL = new Leaflet.Icon({
|
||||
iconUrl: '/fire-marker-l.png?v2',
|
||||
iconSize: [28, 28],
|
||||
iconAnchor: [14, 30]
|
||||
iconUrl: '/fire-marker-l.png',
|
||||
iconSize: [16, 24],
|
||||
iconAnchor: [8, 26]
|
||||
});
|
||||
|
||||
export const fireIconM = new Leaflet.Icon({
|
||||
iconUrl: '/fire-marker-m.png?v2',
|
||||
iconSize: [28, 28],
|
||||
iconAnchor: [14, 30]
|
||||
iconUrl: '/fire-marker-m.png',
|
||||
iconSize: [12, 17],
|
||||
iconAnchor: [6, 19]
|
||||
});
|
||||
|
||||
export const fireIconS = new Leaflet.Icon({
|
||||
iconUrl: '/fire-marker-s.png?v2',
|
||||
iconSize: [28, 28],
|
||||
iconAnchor: [14, 30]
|
||||
iconUrl: '/fire-marker-s.png',
|
||||
iconSize: [8, 12],
|
||||
iconAnchor: [4, 14]
|
||||
});
|
||||
|
||||
export const industryIcon = new Leaflet.Icon({
|
||||
|
|
|
|||
|
|
@ -33,7 +33,8 @@ const subsUnion = (union, options) => {
|
|||
options.map.leafletElement.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest));
|
||||
}
|
||||
} else if (options.subs.length > 0) {
|
||||
const result = calcUnion(L, options.subs, sub => sub, true);
|
||||
const unionGroup = new L.FeatureGroup();
|
||||
const result = calcUnion(options.subs, unionGroup, sub => sub);
|
||||
const unionJson = result[0];
|
||||
const bounds = result[1];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,48 @@
|
|||
import { calcUnion as calcUnionWoBounds } from 'map-common-utils';
|
||||
import { check } from 'meteor/check';
|
||||
import LGeo from 'leaflet-geodesy';
|
||||
import tunion from '@turf/union';
|
||||
import ttrunc from '@turf/truncate';
|
||||
|
||||
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()];
|
||||
// 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()];
|
||||
};
|
||||
|
||||
export default calcUnion;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Link } from 'react-router-dom';
|
|||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import { Trans, translate } from 'react-i18next';
|
||||
import { testId } from '/imports/ui/components/Utils/TestUtils';
|
||||
/* import BetaRibbon from '../../components/BetaRibbon/BetaRibbon'; */
|
||||
import BetaRibbon from '../../components/BetaRibbon/BetaRibbon';
|
||||
import PublicNavigation from '../PublicNavigation/PublicNavigation';
|
||||
import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNavigation';
|
||||
import NavItem from '../NavItem/NavItem';
|
||||
|
|
@ -19,7 +19,7 @@ import './Navigation.scss';
|
|||
const Navigation = props => (
|
||||
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div style={{ overflow: 'hidden' } /* for ribbon */} className="container">
|
||||
{/* <BetaRibbon /> */}
|
||||
<BetaRibbon />
|
||||
{/* <Navbar bsClass="navbar navbar-dark bg-dark"> */}
|
||||
{/* https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Navbar.js */}
|
||||
<Navbar.Header>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { trim } from './util.js';
|
|||
function process(notif) {
|
||||
// No already notified
|
||||
if (Push.Permission.has()) {
|
||||
if (!notif.notified && notif.type === 'web') {
|
||||
if (!notif.webNotified) {
|
||||
Push.create(i18n.t('AppName'), {
|
||||
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: dateFromNow(notif.when) })})`,
|
||||
icon: '/n-fire-marker.png',
|
||||
|
|
@ -47,7 +47,7 @@ Meteor.startup(() => {
|
|||
if (Meteor.userId()) {
|
||||
Meteor.subscribe('mynotifications');
|
||||
// Check for notifications not processed at startup
|
||||
Notifications.find({ notified: null, type: 'web' }).forEach((notif) => {
|
||||
Notifications.find({ webNotified: null }).forEach((notif) => {
|
||||
process(notif);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,30 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React from 'react';
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
/* import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker'; */
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||
|
||||
const Reconnect = ({ t, authenticated }) => (
|
||||
<div>
|
||||
{ true && /* !authenticated && */
|
||||
class Reconnect extends Component {
|
||||
render() {
|
||||
const { t, authenticated } = this.props;
|
||||
// console.log(`Reconnect ${!authenticated ? 'enabled' : 'disabled'}`);
|
||||
return (
|
||||
<Fragment>
|
||||
{ !authenticated &&
|
||||
<Blaze
|
||||
template="meteorStatus"
|
||||
textDisconnect={t('Desconectado del servidor, reconectando en %delay% segundos.')}
|
||||
textConnecting={t('Desconectado del servidor, reconectando...')}
|
||||
linkText={t('Reintentar ahora')}
|
||||
/> }
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reconnect.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
|
|
@ -30,12 +36,11 @@ Reconnect.defaultProps = {
|
|||
|
||||
export default translate([], { wait: true })(Reconnect);
|
||||
|
||||
/*
|
||||
if (!Meteor.isProduction) {
|
||||
// We clear the console on disconnect during development
|
||||
Tracker.autorun(() => {
|
||||
if (Meteor.status().status === 'waiting') {
|
||||
console.clear();
|
||||
// console.clear();
|
||||
}
|
||||
});
|
||||
} */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,7 +242,6 @@ class SelectionMap extends Component {
|
|||
{ this.props.sndBtn && this.props.onSndBtn &&
|
||||
<Button
|
||||
bsStyle="warning"
|
||||
title={this.props.sndBtnTitle}
|
||||
onClick={event => this.onSndBtn(event)}
|
||||
>
|
||||
{this.props.sndBtn.match(/^fa-/) ? <i className={`fa ${this.props.sndBtn}`} /> : this.props.sndBtn }
|
||||
|
|
@ -267,8 +266,7 @@ class SelectionMap extends Component {
|
|||
}
|
||||
|
||||
SelectionMap.defaultProps = {
|
||||
zoom: 11,
|
||||
sndBtnTitle: ''
|
||||
zoom: 11
|
||||
};
|
||||
|
||||
SelectionMap.propTypes = {
|
||||
|
|
@ -282,7 +280,6 @@ SelectionMap.propTypes = {
|
|||
onFstBtn: PropTypes.func.isRequired,
|
||||
sndBtn: PropTypes.string,
|
||||
onSndBtn: PropTypes.func,
|
||||
sndBtnTitle: PropTypes.string,
|
||||
onRemove: PropTypes.func,
|
||||
action: PropTypes.number.isRequired,
|
||||
loadingSubs: PropTypes.bool.isRequired,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import '/imports/startup/client/meta';
|
|||
import '/imports/startup/client/ravenLogger';
|
||||
import '/imports/startup/client/geolocation';
|
||||
import '/imports/startup/client/piwik-start.js';
|
||||
import '/imports/startup/client/reconnect.js';
|
||||
import Reconnect from '../../components/Reconnect/Reconnect';
|
||||
import Navigation from '../../components/Navigation/Navigation';
|
||||
import Authenticated from '../../components/Authenticated/Authenticated';
|
||||
|
|
@ -38,16 +37,16 @@ import VerifyEmail from '../../pages/VerifyEmail/VerifyEmail';
|
|||
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
||||
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
||||
import Profile from '../../pages/Profile/Profile';
|
||||
import NotFound from '../../pages/NotFound/NotFound';
|
||||
import FiresMap from '../../pages/FiresMap/FiresMap';
|
||||
import Fires from '../../pages/Fires/Fires';
|
||||
import Sandbox from '../../pages/Sandbox/Sandbox';
|
||||
import ZonesMap from '../../pages/ZonesMap/ZonesMap';
|
||||
import FiresMap from '../../pages/FiresMap/FiresMap';
|
||||
import NotFound from '../../pages/NotFound/NotFound';
|
||||
import Terms from '../../pages/Terms/Terms';
|
||||
import About from '../../pages/About/About';
|
||||
import Privacy from '../../pages/Privacy/Privacy';
|
||||
import License from '../../pages/License/License';
|
||||
import Credits from '../../pages/Credits/Credits';
|
||||
import ZonesMap from '../../pages/ZonesMap/ZonesMap';
|
||||
import Footer from '../../components/Footer/Footer';
|
||||
import Feedback from '../../components/Feedback/Feedback';
|
||||
import ReSendEmail from '../../components/ReSendEmail/ReSendEmail';
|
||||
|
|
@ -58,10 +57,11 @@ import './App.scss';
|
|||
|
||||
class LocationListener extends Component {
|
||||
// https://stackoverflow.com/questions/43512450/react-router-v4-route-onchange-event
|
||||
static contextTypes = {
|
||||
router: PropTypes.object
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// https://github.com/GoogleChrome/rendertron#rendering-budget-timeout
|
||||
window.renderComplete = true;
|
||||
this.handleLocationChange(this.context.router.history.location);
|
||||
this.unlisten =
|
||||
this.context.router.history.listen(this.handleLocationChange);
|
||||
|
|
@ -75,42 +75,28 @@ class LocationListener extends Component {
|
|||
// your staff here
|
||||
console.log(`----- location: '${location.pathname}'`);
|
||||
Meteor.Piwik.trackPage(location.pathname);
|
||||
// Meteor.isReadyForSpiderable = true;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/39133797/react-only-return-props-children
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
LocationListener.contextTypes = {
|
||||
router: PropTypes.object
|
||||
};
|
||||
|
||||
LocationListener.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node
|
||||
]).isRequired
|
||||
};
|
||||
|
||||
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')}>
|
||||
<ErrorBoundary>
|
||||
<Router history={history}>
|
||||
<LocationListener>
|
||||
{ !props.loading &&
|
||||
{ !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" />
|
||||
<link rel="alternate" href="https://fires.comunes.org/" hrefLang="en" />
|
||||
<link rel="alternate" href="https://fuegos.comunes.org/" hrefLang="es" />
|
||||
</Helmet>
|
||||
<Navigation {...props} />
|
||||
<ReSendEmail {...props} />
|
||||
|
|
@ -118,15 +104,20 @@ const App = props => (
|
|||
<Grid>
|
||||
<Switch>
|
||||
<Route exact name="index" path="/" component={Index} />
|
||||
{/* <Authenticated exact path="/documents" component={Documents} {...props} />
|
||||
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
|
||||
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
|
||||
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
|
||||
*/}
|
||||
<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/:type(with-industries)" component={FiresMap} industries {...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} />
|
||||
|
|
@ -135,6 +126,7 @@ const App = props => (
|
|||
<Route path="/logout" component={Logout} {...props} />
|
||||
<Route path="/sandbox" component={Sandbox} {...props} />
|
||||
<Route path="/error" component={TestError} {...props} />
|
||||
{/* <Route path="/subscriptions" render={props => <FireSubscription focusInput {...props} />} /> */}
|
||||
|
||||
<Route name="verify-email" path="/verify-email/:token" component={VerifyEmail} />
|
||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||
|
|
@ -150,15 +142,15 @@ const App = props => (
|
|||
</Grid>
|
||||
<Footer />
|
||||
<Reconnect {...props} />
|
||||
<Feedback {...props} />
|
||||
{props.i18nReady.get() && <Blaze template="cookieConsent" /> }
|
||||
</div>}
|
||||
<Feedback history={history} {...props} />
|
||||
{props.i18nReady.get() &&
|
||||
<Blaze template="cookieConsent" />
|
||||
}
|
||||
</div> : ''}
|
||||
</LocationListener>
|
||||
</Router>
|
||||
</ErrorBoundary>
|
||||
</I18nextProvider>
|
||||
</ErrorBoundary> }
|
||||
</div>
|
||||
);
|
||||
|
||||
App.defaultProps = {
|
||||
|
|
@ -186,9 +178,7 @@ 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;
|
||||
Meteor.autorun(() => {
|
||||
console.log(`i18n ready?: ${i18nReady.get()}`);
|
||||
});
|
||||
// console.log(`i18n ready?: ${i18nReady.get()}`);
|
||||
return {
|
||||
loading,
|
||||
loggingIn,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Gkeys from '/imports/startup/client/Gkeys';
|
|||
import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition';
|
||||
import LocationAutocomplete from '/imports/ui/components/LocationAutocomplete/LocationAutocomplete';
|
||||
import { isNotHomeAndMobile } from '/imports/ui/components/Utils/isMobile';
|
||||
import { isHome } from '/imports/ui/components/Utils/location';
|
||||
|
||||
class FireSubscription extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -105,9 +104,8 @@ class FireSubscription extends React.Component {
|
|||
distance={this.state.distance}
|
||||
fstBtn={this.props.subsBtn}
|
||||
onFstBtn={state => this.onSubs(state)}
|
||||
sndBtn={isHome() ? null : 'fa-times'}
|
||||
sndBtn="fa-times"
|
||||
onSndBtn={this.onCancel}
|
||||
sndBtnTitle={this.props.t('Cancelar')}
|
||||
onSelection={state => this.onSelection(state)}
|
||||
action={action.add}
|
||||
disableFstBtn={this.props.disableFstBtn}
|
||||
|
|
@ -118,7 +116,6 @@ class FireSubscription extends React.Component {
|
|||
}
|
||||
|
||||
FireSubscription.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
center: PropTypes.arrayOf(PropTypes.number),
|
||||
zoom: PropTypes.number,
|
||||
distance: PropTypes.number,
|
||||
|
|
|
|||
|
|
@ -10,15 +10,14 @@ 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, GeoJSON } from 'react-leaflet';
|
||||
import { rectangleAround } from 'map-common-utils';
|
||||
import { Map, Circle } from 'react-leaflet';
|
||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||
import NotFound from '/imports/ui/pages/NotFound/NotFound';
|
||||
import FiresCollection from '/imports/api/Fires/Fires';
|
||||
import FireList from '/imports/ui/components/Maps/FireList';
|
||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||
import { dateLongFormat, dateYYYYMMDD } from '/imports/api/Common/dates';
|
||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||
import '/imports/startup/client/comments';
|
||||
import FalsePositiveTypes from '/imports/api/FalsePositives/FalsePositiveTypes';
|
||||
import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives';
|
||||
|
|
@ -53,7 +52,7 @@ class Fire extends React.Component {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(nextState.when === this.state.when && nextState.loading === this.state.loading && this.state.notfound === nextState.notfound && nextState.nasaLink === this.state.nasaLink);
|
||||
return !(nextState.when === this.state.when && nextState.loading === this.state.loading && this.state.notfound === nextState.notfound);
|
||||
}
|
||||
|
||||
onTypeSelect(key) {
|
||||
|
|
@ -68,34 +67,7 @@ class Fire extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
handleLeafletMapLoad(map) {
|
||||
if (map && map.leafletElement) {
|
||||
const lmap = map.leafletElement;
|
||||
const bounds = lmap.getBounds();
|
||||
const ne = bounds.getNorthEast();
|
||||
const sw = bounds.getSouthWest();
|
||||
// console.log(`${sw},${ne}`);
|
||||
// const nasaLinkBase = 'https://worldview.earthdata.nasa.gov/?p=geographic&l=VIIRS_SNPP_CorrectedReflectance_TrueColor(hidden),MODIS_Aqua_CorrectedReflectance_TrueColor(hidden),MODIS_Terra_CorrectedReflectance_TrueColor,MODIS_Fires_All,MODIS_Fires_Aqua,VIIRS_SNPP_Fires_375m_Night,VIIRS_SNPP_Fires_375m_Day,MODIS_Fires_Terra,Reference_Labels(hidden),Reference_Features(hidden),Coastlines&t='
|
||||
const nasaLinkBase = 'https://worldview.earthdata.nasa.gov/?p=geographic&l=' +
|
||||
'VIIRS_SNPP_CorrectedReflectance_TrueColor,' +
|
||||
'MODIS_Aqua_CorrectedReflectance_TrueColor,' +
|
||||
'MODIS_Terra_CorrectedReflectance_TrueColor,' +
|
||||
'MODIS_Terra_Data_No_Data(hidden),' +
|
||||
'MODIS_Fires_All,' +
|
||||
'MODIS_Fires_Aqua,' +
|
||||
'VIIRS_SNPP_Fires_375m_Night,' +
|
||||
'VIIRS_SNPP_Fires_375m_Day,' +
|
||||
'MODIS_Fires_Terra,' +
|
||||
'Reference_Labels,' +
|
||||
'Reference_Features,' +
|
||||
'Coastlines' +
|
||||
'&t=';
|
||||
const nasaZoom = 5;
|
||||
this.setState({ nasaLink: `${nasaLinkBase}${this.dateYYYYMMDD}&z=3&v=${sw.lng},${sw.lat},${ne.lng},${ne.lat}&ab=off&as=${this.dateYYYYMMDD}&ae=${this.dateYYYYMMDD}&av=${nasaZoom}&al=true` });
|
||||
}
|
||||
}
|
||||
|
||||
handleLeafletCircleLoad(circle) {
|
||||
handleLeafletLoad(circle) {
|
||||
if (this.fireMap && this.fireMap.leafletElement && circle && circle.leafletElement) {
|
||||
this.fireMap.leafletElement.fitBounds(circle.leafletElement.getBounds());
|
||||
}
|
||||
|
|
@ -111,14 +83,12 @@ class Fire extends React.Component {
|
|||
* console.log(`Not found fire: ${notfound}`); */
|
||||
if (fire && fire.when) {
|
||||
this.dateLongFormat = dateLongFormat(fire.when);
|
||||
this.dateYYYYMMDD = dateYYYYMMDD(fire.when);
|
||||
this.title = fire.address ?
|
||||
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 });
|
||||
}
|
||||
const ready = fire && !loading;
|
||||
const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null;
|
||||
return (ready ? (
|
||||
|
||||
return (fire && !loading ? (
|
||||
<div className="ViewFire">
|
||||
<Helmet>
|
||||
<title>{t('AppName')}: {t('Información adicional sobre fuego')}</title>
|
||||
|
|
@ -128,10 +98,7 @@ class Fire extends React.Component {
|
|||
<Fragment>
|
||||
<h4 className="page-header">{this.title}</h4>
|
||||
<Map
|
||||
ref={(map) => {
|
||||
this.fireMap = map;
|
||||
this.handleLeafletMapLoad(map);
|
||||
}}
|
||||
ref={(map) => { this.fireMap = map; }}
|
||||
animate
|
||||
sleep={false}
|
||||
center={[fire.lat, fire.lon]}
|
||||
|
|
@ -139,13 +106,14 @@ class Fire extends React.Component {
|
|||
zoom={13}
|
||||
>
|
||||
<Fragment>
|
||||
<GeoJSON
|
||||
ref={(circle) => { this.circle = circle; this.handleLeafletCircleLoad(circle); }}
|
||||
data={rect}
|
||||
<Circle
|
||||
center={[fire.lat, fire.lon]}
|
||||
color="red"
|
||||
stroke
|
||||
width="1"
|
||||
fillOpacity="0.0"
|
||||
fillColor="red"
|
||||
fillOpacity={0.0}
|
||||
interactive={false}
|
||||
radius={fire.scan ? fire.scan * 500 : 300}
|
||||
ref={(circle) => { this.circle = circle; this.handleLeafletLoad(circle); }}
|
||||
/>
|
||||
</Fragment>
|
||||
<FireList
|
||||
|
|
@ -156,7 +124,6 @@ class Fire extends React.Component {
|
|||
useMarkers
|
||||
nasa={false}
|
||||
falsePositives
|
||||
neighbour={false}
|
||||
industries={false}
|
||||
/>
|
||||
<FireList
|
||||
|
|
@ -167,7 +134,6 @@ class Fire extends React.Component {
|
|||
useMarkers
|
||||
nasa={false}
|
||||
falsePositives={false}
|
||||
neighbour={false}
|
||||
industries
|
||||
/>
|
||||
<DefMapLayers satellite />
|
||||
|
|
@ -175,10 +141,7 @@ class Fire extends React.Component {
|
|||
</Map>
|
||||
<p>{t('Coordenadas:')} {fire.lat}, {fire.lon}</p>
|
||||
{(fire.type === 'modis' || fire.type === 'viirs') &&
|
||||
<Fragment>
|
||||
<p><a target="_blank" href={`${this.state.nasaLink}`}><Trans>Fuego detectado por satélites de la NASA <FromNow {...this.props} /></Trans></a>
|
||||
</p>
|
||||
</Fragment>
|
||||
<p><Trans>Fuego detectado por satélites de la NASA <FromNow {...this.props} /></Trans></p>
|
||||
}
|
||||
{(fire.type === 'vecinal') &&
|
||||
<p><Trans>Fuego notificado por uno de nuestros usuarios/as <FromNow {...this.props} /></Trans></p>
|
||||
|
|
@ -279,7 +242,7 @@ const FireContainer = withTracker(({ match }) => {
|
|||
} else {
|
||||
console.log('Seems a fire from enc hash');
|
||||
fromHash = true;
|
||||
subscription = Meteor.subscribe('fireFromHash', id, match.params);
|
||||
subscription = Meteor.subscribe('fireFromHash', id);
|
||||
}
|
||||
|
||||
// console.log(`Type of '${fireType}' fire, active: ${active}, archive: ${archive}, fromHash: ${fromHash}`);
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate, Trans } from 'react-i18next';
|
||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||
|
||||
class FireStats extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
lastFireDetected: props.lastFireDetected,
|
||||
lastCheck: props.lastCheck,
|
||||
loadingAll: props.loadingAll
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.lastCheck !== nextProps.lastCheck ||
|
||||
this.props.lastFireDetected !== nextProps.lastFireDetected ||
|
||||
this.props.loadingAll !== nextProps.loadingAll
|
||||
) {
|
||||
this.setState({
|
||||
lastFireDetected: nextProps.lastFireDetected,
|
||||
lastCheck: nextProps.lastCheck,
|
||||
loadingAll: nextProps.loadingAll
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !(nextState.lastCheck === this.state.lastCheck &&
|
||||
nextState.lastFireDetected === this.state.lastFireDetected &&
|
||||
nextState.loadingAll === this.state.loadingAll);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.loadingAll) return (<Fragment><br /><em><Trans>Actualizando...</Trans></em></Fragment>);
|
||||
if (this.state.lastCheck && !this.state.lastFireDetected) {
|
||||
return (<Fragment><br /><Trans>Actualizado <FromNow when={this.state.lastCheck} />.</Trans></Fragment>);
|
||||
}
|
||||
if (this.state.lastCheck && this.state.lastFireDetected) {
|
||||
return (<Fragment><br /><Trans>Actualizado <FromNow when={this.state.lastCheck} />, último fuego detectado <FromNow when={this.state.lastFireDetected.when} />.</Trans></Fragment>);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
FireStats.propTypes = {
|
||||
lastFireDetected: PropTypes.object,
|
||||
loadingAll: PropTypes.bool.isRequired,
|
||||
lastCheck: PropTypes.instanceOf(Date)
|
||||
};
|
||||
|
||||
FireStats.defaultProps = {
|
||||
};
|
||||
|
||||
export default translate([], { wait: true })(FireStats);
|
||||
|
|
@ -23,32 +23,30 @@ 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 FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||
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';
|
||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||
import { isNotHomeAndMobile, isChrome, isAnyMobile } from '/imports/ui/components/Utils/isMobile';
|
||||
import { isNotHomeAndMobile, isChrome } from '/imports/ui/components/Utils/isMobile';
|
||||
import { isHome } from '/imports/ui/components/Utils/location';
|
||||
import ShareIt from '/imports/ui/components/ShareIt/ShareIt';
|
||||
import FullScreenMap from '/imports/ui/components/Maps/FullScreenMap';
|
||||
import LocationAutocomplete from '/imports/ui/components/LocationAutocomplete/LocationAutocomplete';
|
||||
import Gkeys from '/imports/startup/client/Gkeys';
|
||||
import FireStats from './FireStats';
|
||||
|
||||
import './FiresMap.scss';
|
||||
|
||||
const MAXZOOM = 6;
|
||||
const MAXZOOMREACTIVE = 6;
|
||||
const DEFZOOM = 8;
|
||||
const zoom = new ReactiveVar(DEFZOOM);
|
||||
// https://en.wikipedia.org/wiki/Geographical_midpoint_of_Europe
|
||||
const center = new ReactiveVar([53.5775, 3.106111]);
|
||||
const center = new ReactiveVar([0, 0]);
|
||||
const mapSize = new ReactiveVar();
|
||||
const marks = new ReactiveVar(true);
|
||||
const marks = new ReactiveVar(false);
|
||||
const showUnion = new ReactiveVar(true);
|
||||
|
||||
// Remove map in subscription
|
||||
|
|
@ -84,6 +82,19 @@ 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;
|
||||
|
|
@ -141,7 +152,7 @@ class FiresMap extends React.Component {
|
|||
}
|
||||
|
||||
centerOnUserLocation(viewport) {
|
||||
this.setState({ viewport: { center: viewport.center, zoom: 10 } });
|
||||
this.setState({ viewport });
|
||||
}
|
||||
|
||||
useMarkers(use) {
|
||||
|
|
@ -185,15 +196,11 @@ class FiresMap extends React.Component {
|
|||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const loading = this.props.loading || !this.props.subsready;
|
||||
console.log(`Rendering ${loading ? 'loading' : 'LOADED'}, zoom ${this.state.viewport.zoom}, map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. False positives: ${this.props.falsePositives.length}. Reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`);
|
||||
console.log(`Rendering ${this.props.loading ? 'loading' : 'LOADED'}, zoom ${this.state.viewport.zoom}, map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. False positives: ${this.props.falsePositives.length}. Reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`);
|
||||
const title = `${t('AppName')}: ${t('Fuegos activos')}`;
|
||||
|
||||
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}`);
|
||||
|
||||
if (!this.state.init) {
|
||||
|
|
@ -213,6 +220,18 @@ class FiresMap extends React.Component {
|
|||
</Helmet> }
|
||||
<h4 className="page-header"><Trans parent="span">Fuegos activos</Trans></h4>
|
||||
<Row>
|
||||
<Col xs={12} sm={6} md={6} lg={6} >
|
||||
<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. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo.</Trans> <Trans>Datos actualizados <FromNow when={this.props.lastCheck} />.</Trans></Fragment> :
|
||||
<Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length + this.props.firealerts.length }}</strong> fuegos activos en el mapa. Hay un total de <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos detectados en todo el mundo por la NASA.</Trans> <Trans>Datos actualizados <FromNow when={this.props.lastCheck} />.</Trans></Fragment>
|
||||
}
|
||||
</p>
|
||||
{isNotHomeAndMobile() && this.props.firealerts.length > 0 &&
|
||||
<p className="firesmap-legend"><Trans parent="span" i18nKey="activeNeigFireInMapCount">En naranja, los fuegos notificados por nuestros usuarios/as recientemente.</Trans></p> }
|
||||
{isNotHomeAndMobile() && this.props.firealerts.length === 0 &&
|
||||
<p className="firesmap-legend"><Trans parent="span" i18nKey="noActiveNeigFireInMap">No hay fuegos notificados recientemente por nuestros usuarios/as en esta zona.</Trans></p> }
|
||||
</Col>
|
||||
<Col xs={12} sm={6} md={6} lg={6}>
|
||||
{isNotHomeAndMobile() &&
|
||||
<Fragment>
|
||||
|
|
@ -231,29 +250,16 @@ class FiresMap extends React.Component {
|
|||
<Trans className="mark-checkbox" parent="span">Resaltar los fuegos con un marcador</Trans>
|
||||
</Checkbox>}
|
||||
</Fragment>}
|
||||
</Col>
|
||||
<Col xs={12} sm={6} md={6} lg={6} >
|
||||
<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.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 &&
|
||||
<p className="firesmap-legend"><Trans parent="span" i18nKey="activeNeigFireInMapCount">En naranja, los fuegos notificados por nuestros usuarios/as recientemente.</Trans></p> }
|
||||
{ /* disabled */ false && isNotHomeAndMobile() && this.props.firealerts.length === 0 && !isAnyMobile &&
|
||||
<p className="firesmap-legend"><Trans parent="span" i18nKey="noActiveNeigFireInMap">No hay fuegos notificados recientemente por nuestros usuarios/as en esta zona.</Trans></p> }
|
||||
|
||||
<p className="firesmap-note">
|
||||
<em>{ this.state.viewport.zoom >= MAXZOOMREACTIVE ?
|
||||
<Trans>Los fuegos activos se actualizan en tiempo cuasi real.</Trans> :
|
||||
<Trans>Los fuegos activos se actualizan en tiempo real.</Trans> :
|
||||
<Trans>Haga zoom en una zona de su interés si quiere que los fuegos se actualicen en tiempo real.</Trans>
|
||||
}
|
||||
</em>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
{loading ?
|
||||
{this.props.loading || !this.props.subsready ?
|
||||
<LoadingBar progress={this.props.loading ? 0.9 : 1} />
|
||||
: ''}
|
||||
{/* https://github.com/CliffCloud/Leaflet.Sleep */}
|
||||
|
|
@ -294,17 +300,8 @@ class FiresMap extends React.Component {
|
|||
useMarkers={this.state.useMarkers}
|
||||
nasa={false}
|
||||
falsePositives
|
||||
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}
|
||||
|
|
@ -312,7 +309,6 @@ class FiresMap extends React.Component {
|
|||
scale={this.state.viewport.zoom >= MAXZOOM}
|
||||
useMarkers={this.state.useMarkers}
|
||||
nasa={false}
|
||||
neighbour={false}
|
||||
falsePositives={false}
|
||||
industries
|
||||
/>
|
||||
|
|
@ -323,7 +319,6 @@ class FiresMap extends React.Component {
|
|||
scale={this.state.viewport.zoom >= MAXZOOM}
|
||||
useMarkers={this.state.useMarkers}
|
||||
nasa
|
||||
neighbour={false}
|
||||
falsePositives={false}
|
||||
industries={false}
|
||||
/>
|
||||
|
|
@ -331,10 +326,9 @@ class FiresMap extends React.Component {
|
|||
t={t}
|
||||
history={this.props.history}
|
||||
fires={this.props.firealerts}
|
||||
scale={this.state.viewport.zoom >= MAXZOOM}
|
||||
scale={false}
|
||||
useMarkers={this.state.useMarkers}
|
||||
nasa={false}
|
||||
neighbour
|
||||
falsePositives={false}
|
||||
industries={false}
|
||||
/>
|
||||
|
|
@ -350,7 +344,6 @@ class FiresMap extends React.Component {
|
|||
<Row>
|
||||
<Col xs={12} sm={12} md={12} lg={12}>
|
||||
<p className="firesmap-footnote"><span style={{ paddingRight: '5px' }}>(*)</span><Trans i18nKey="mapPrivacy" parent="span"><em>Para preservar la privacidad de nuestros usuarios/as, los datos reflejados están aleatoriamente alterados y son solo orientativos.</em></Trans></p>
|
||||
<p className="firesmap-footnote"><em><Trans>Nota: Las nubes pueden entorpecer la detección de fuegos activos.</Trans></em></p>
|
||||
</Col>
|
||||
</Row>
|
||||
{ !isHome() &&
|
||||
|
|
@ -367,14 +360,11 @@ 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,7 +381,6 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
// console.log(`With industries: ${withIndustries}`);
|
||||
|
||||
let subscription;
|
||||
let subscriptionUnion;
|
||||
let alertSubscription;
|
||||
|
||||
const centerStored = store.get('firesmap_center');
|
||||
|
|
@ -406,8 +395,6 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
if (typeof showUnionStored === 'boolean') {
|
||||
showUnion.set(showUnionStored);
|
||||
}
|
||||
// Disable, because this increase the number of fires by one
|
||||
// Meteor.subscribe('lastFireDetected');
|
||||
Meteor.autorun(() => {
|
||||
if ((centerStored !== [0, 0] || geolocation.get()) && geoInit) {
|
||||
center.set(centerStored || geolocation.get());
|
||||
|
|
@ -423,14 +410,6 @@ 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,
|
||||
|
|
@ -451,7 +430,6 @@ 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' });
|
||||
|
|
@ -460,23 +438,19 @@ export default translate([], { wait: true })(withTracker(() => {
|
|||
const fireAlerts = FireAlertsCollection.find().fetch();
|
||||
const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap);
|
||||
const industries = IndustriesCollection.find().fetch().map(industriesRemap);
|
||||
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
||||
|
||||
return {
|
||||
loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready() && subscriptionUnion.ready()),
|
||||
loading: !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.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,
|
||||
lastCheck: lastCheck ? lastCheck.value : null,
|
||||
lastFireDetected,
|
||||
center: center.get(),
|
||||
marks: marks.get(),
|
||||
showUnion: showUnion.get(),
|
||||
|
|
|
|||
|
|
@ -54,13 +54,6 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
|
||||
@include breakpoint(mobile) {
|
||||
p.firesmap-note,
|
||||
p.firesmap-legend {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.firesmap-note,
|
||||
.firesmap-footnote {
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -187,72 +187,28 @@
|
|||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.no-webp .carousel-item-1 {
|
||||
background-image: url('/images/home-slide1.jpg')
|
||||
.carousel-item-1 {
|
||||
background-image: url('/home-slide1.jpg')
|
||||
}
|
||||
|
||||
.no-webp .carousel-item-2.lazy {
|
||||
background-image: url('/images/home-slide2.jpg')
|
||||
.carousel-item-2 {
|
||||
background-image: url('/home-slide2.jpg')
|
||||
}
|
||||
|
||||
.no-webp .carousel-item-3.lazy {
|
||||
background-image: url('/images/home-slide3.jpg')
|
||||
.carousel-item-3 {
|
||||
background-image: url('/home-slide3.jpg')
|
||||
}
|
||||
|
||||
.no-webp .carousel-item-4.lazy {
|
||||
background-image: url('/images/home-slide4.jpg')
|
||||
.carousel-item-4 {
|
||||
background-image: url('/home-slide4.jpg')
|
||||
}
|
||||
|
||||
.no-webp .carousel-snd-item-1 {
|
||||
background-image: url('/images/home-slide-snd-1.jpg')
|
||||
.carousel-snd-item-1 {
|
||||
background-image: url('/home-slide-snd-1.jpg')
|
||||
}
|
||||
|
||||
.no-webp .carousel-snd-item-2.lazy {
|
||||
background-image: url('/images/home-slide-snd-2.jpg')
|
||||
}
|
||||
|
||||
.webp .carousel-item-1 {
|
||||
background-image: url('/images/home-slide1.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-item-2.lazy {
|
||||
background-image: url('/images/home-slide2.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-item-3.lazy {
|
||||
background-image: url('/images/home-slide3.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-item-4.lazy {
|
||||
background-image: url('/images/home-slide4.webp')
|
||||
}
|
||||
|
||||
/* .webp .carousel-item-1 {
|
||||
background-image: url('/images/home-slide1-blur.webp')
|
||||
} */
|
||||
|
||||
.webp .carousel-item-2 {
|
||||
background-image: url('/images/home-slide2-blur.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-item-3 {
|
||||
background-image: url('/images/home-slide3-blur.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-item-4 {
|
||||
background-image: url('/images/home-slide4-blur.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-snd-item-1 {
|
||||
background-image: url('/images/home-slide-snd-1.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-snd-item-2 {
|
||||
background-image: url('/images/home-slide-snd-2-blur.webp')
|
||||
}
|
||||
|
||||
.webp .carousel-snd-item-2.lazy {
|
||||
background-image: url('/images/home-slide-snd-2.webp')
|
||||
.carousel-snd-item-2 {
|
||||
background-image: url('/home-slide-snd-2.jpg')
|
||||
}
|
||||
|
||||
.participe-btn {
|
||||
|
|
@ -269,52 +225,28 @@
|
|||
-moz-background-size: cover;
|
||||
background-size: cover;
|
||||
-o-background-size: cover;
|
||||
}
|
||||
|
||||
.no-webp .bg-image-full {
|
||||
background-image: url('/images/home-fires.jpg');
|
||||
}
|
||||
|
||||
.webp .bg-image-full {
|
||||
background-image: url('/images/home-fires.webp');
|
||||
background-image: url('/home-fires.jpg');
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 767px) {
|
||||
.no-webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-800.jpg');
|
||||
}
|
||||
|
||||
.webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-800.webp');
|
||||
.bg-image-full {
|
||||
background-image: url('/home-fires-800.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 400px) {
|
||||
.no-webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-400.jpg');
|
||||
}
|
||||
|
||||
.webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-400.webp');
|
||||
.bg-image-full {
|
||||
background-image: url('/home-fires-400.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1000px) {
|
||||
.no-webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-1000.jpg');
|
||||
.bg-image-full {
|
||||
background-image: url('/home-fires-1000.jpg'); }
|
||||
}
|
||||
|
||||
.webp .bg-image-full {
|
||||
background-image: url('/images/home-fires-1000.webp');
|
||||
}
|
||||
}
|
||||
|
||||
.nowebp .device[data-device=iPhone6][data-orientation=portrait][data-color=white] {
|
||||
background-image: url(/images/mobile.png);
|
||||
}
|
||||
|
||||
.webp .device[data-device=iPhone6][data-orientation=portrait][data-color=white] {
|
||||
background-image: url(/images/mobile.webp);
|
||||
.device[data-device=iPhone6][data-orientation=portrait][data-color=white] {
|
||||
background-image: url(/mobile.png);
|
||||
}
|
||||
|
||||
.full-width .page-header { // section titles
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable key-spacing */
|
||||
/* eslint-env jquery */
|
||||
import 'simple-line-icons/css/simple-line-icons.css';
|
||||
import React, { Component } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { translate, Trans } from 'react-i18next';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import PropTypes from 'prop-types';
|
||||
|
|
@ -11,7 +10,6 @@ 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';
|
||||
|
|
@ -19,8 +17,9 @@ import { isAnyMobile } from '/imports/ui/components/Utils/isMobile';
|
|||
import SubscriptionEditor from '/imports/ui/components/SubscriptionEditor/SubscriptionEditor';
|
||||
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 'simple-line-icons/css/simple-line-icons.css';
|
||||
import './Index.scss';
|
||||
import './Index-custom.scss';
|
||||
|
||||
|
|
@ -35,22 +34,11 @@ class Index extends Component {
|
|||
self.scaleHeader();
|
||||
}, 250);
|
||||
this.onResize = this.onResize.bind(this);
|
||||
this.getBotUrl = this.getBotUrl.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const car1 = $('#carouselMainIndicators');
|
||||
const car2 = $('#carouselSndIndicators');
|
||||
car1.carousel();
|
||||
car2.carousel();
|
||||
/* _.defer(() => {
|
||||
$('#firsthomeslide').addClass('lazy');
|
||||
}); */
|
||||
const loadLazy = (ev) => {
|
||||
ev.relatedTarget.classList.add('lazy');
|
||||
};
|
||||
car1.on('slide.bs.carousel', loadLazy);
|
||||
car2.on('slide.bs.carousel', loadLazy);
|
||||
$('#carouselMainIndicators').carousel();
|
||||
$('#carouselSndIndicators').carousel();
|
||||
}
|
||||
|
||||
onResize() {
|
||||
|
|
@ -66,15 +54,7 @@ class Index extends Component {
|
|||
</div>
|
||||
</section>);
|
||||
}
|
||||
return (<div />);
|
||||
}
|
||||
|
||||
getBotUrl() {
|
||||
const fallbackLng = getFallbackLang(this.props.i18n.language);
|
||||
console.log(`language: ${this.props.i18n.language}, fallback: ${fallbackLng}`);
|
||||
return fallbackLng === 'es' ?
|
||||
'https://t.me/TodosContraElFuego_bot' :
|
||||
'https://t.me/AllAgainstTheFire_bot';
|
||||
return (<Fragment />);
|
||||
}
|
||||
|
||||
scaleHeader() {
|
||||
|
|
@ -89,7 +69,7 @@ class Index extends Component {
|
|||
}
|
||||
|
||||
handleBtnClick() {
|
||||
window.open(this.getBotUrl(), '_blank');
|
||||
window.open('https://t.me/TodosContraElFuego_bot', '_blank');
|
||||
}
|
||||
|
||||
gotoParticipe() {
|
||||
|
|
@ -102,14 +82,13 @@ 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>
|
||||
<title>{title}</title>
|
||||
</Helmet>
|
||||
{/* https://v4-alpha.getbootstrap.com/components/carousel/ */}
|
||||
<div>
|
||||
<Fragment>
|
||||
<section className="sect1">
|
||||
<header>
|
||||
<ReactResizeDetector handleWidth handleHeight onResize={this.onResize} />
|
||||
|
|
@ -120,14 +99,14 @@ class Index extends Component {
|
|||
>
|
||||
{/* for dev ^^^: data-interval=false */}
|
||||
<ol className="carousel-indicators">
|
||||
<li key="sl1-1" data-target="#carouselMainIndicators" data-slide-to="0" className="active" />
|
||||
<li key="sl1-2" data-target="#carouselMainIndicators" data-slide-to="1" />
|
||||
<li key="sl1-3" data-target="#carouselMainIndicators" data-slide-to="2" />
|
||||
<li key="sl1-4" data-target="#carouselMainIndicators" data-slide-to="3" />
|
||||
<li data-target="#carouselMainIndicators" data-slide-to="0" className="active" />
|
||||
<li data-target="#carouselMainIndicators" data-slide-to="1" />
|
||||
<li data-target="#carouselMainIndicators" data-slide-to="2" />
|
||||
<li data-target="#carouselMainIndicators" data-slide-to="3" />
|
||||
</ol>
|
||||
<div className="carousel-inner" role="listbox">
|
||||
|
||||
<div id="firsthomeslide" className="carousel-item carousel-item-1 active">
|
||||
<div className="carousel-item carousel-item-1 active">
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
|
|
@ -182,9 +161,6 @@ 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>
|
||||
|
|
@ -216,8 +192,8 @@ class Index extends Component {
|
|||
>
|
||||
{/* for dev ^^^: data-interval=false */}
|
||||
<ol className="carousel-indicators">
|
||||
<li key="sl2-1" data-target="#carouselSndIndicators" data-slide-to="0" className="active" />
|
||||
<li key="sl2-2" data-target="#carouselSndIndicators" data-slide-to="1" />
|
||||
<li data-target="#carouselSndIndicators" data-slide-to="0" className="active" />
|
||||
<li data-target="#carouselSndIndicators" data-slide-to="1" />
|
||||
</ol>
|
||||
<div className="carousel-inner" role="listbox">
|
||||
|
||||
|
|
@ -274,7 +250,7 @@ class Index extends Component {
|
|||
<div className="device" data-device="iPhone6" data-orientation="portrait" data-color="white">
|
||||
<div className="screen">
|
||||
{/* Demo image for screen mockup, you can put an image here, some HTML, an animation, video, or anything else! */}
|
||||
<img src="/images/telegram-screen.png" className="img-fluid" alt="" />
|
||||
<img src="/telegram-screen.png" className="img-fluid" alt="" />
|
||||
</div>
|
||||
<div className="button" tabIndex="-1" onKeyDown={() => {}} role="button" onClick={() => this.handleBtnClick()} />
|
||||
</div>
|
||||
|
|
@ -284,7 +260,7 @@ class Index extends Component {
|
|||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<a className="feature-link" rel="noopener noreferrer" target="_blank" href={this.getBotUrl()}>
|
||||
<a className="feature-link" rel="noopener noreferrer" target="_blank" href="https://t.me/TodosContraElFuego_bot">
|
||||
<div className="feature-item">
|
||||
<i className="fa fa-telegram text-primary" />
|
||||
<h3>Telegram</h3>
|
||||
|
|
@ -324,7 +300,7 @@ class Index extends Component {
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -332,7 +308,6 @@ class Index extends Component {
|
|||
|
||||
Index.propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
i18n: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.android-btn > div {
|
||||
height: 50px !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,15 @@ 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 { translate } from 'react-i18next';
|
||||
import { T9n } from 'meteor-accounts-t9n';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
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 './Profile.scss';
|
||||
|
||||
// List of languages: https://github.com/i18next/i18next/issues/1068
|
||||
|
||||
class Profile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -157,7 +155,6 @@ 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à'
|
||||
};
|
||||
|
|
@ -205,7 +202,7 @@ class Profile extends React.Component {
|
|||
{langName[i18n.language]}
|
||||
</button>
|
||||
<div className="dropdown-menu">
|
||||
{enabledLangs.map(lang => (
|
||||
{i18n.languages.map(lang => (
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={() => this.onLangSelect(lang)}
|
||||
|
|
@ -272,7 +269,7 @@ Profile.propTypes = {
|
|||
i18n: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default translate()(withTracker(() => {
|
||||
export default translate([], { wait: true })(withTracker(() => {
|
||||
const subscription = Meteor.subscribe('users.editProfile');
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable react/jsx-indent */
|
||||
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, ButtonGroup, Row, Col } from 'react-bootstrap';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
|
@ -63,8 +63,8 @@ class SubscriptionsMap extends React.Component {
|
|||
|
||||
handleLeafletLoad(map) {
|
||||
// console.log('Map loading');
|
||||
if (map && this.props.userSubs !== 'null') {
|
||||
// console.log(`Union of ${this.props.userSubs}`);
|
||||
// console.log(map);
|
||||
if (map) {
|
||||
this.state.union = subsUnion(this.state.union, {
|
||||
map,
|
||||
subs: this.props.userSubs,
|
||||
|
|
@ -95,7 +95,7 @@ class SubscriptionsMap extends React.Component {
|
|||
const title = `${t('AppName')}: ${t('Zonas vigiladas')}`;
|
||||
console.log(`Rendering Subs users ready ${this.props.subsready} viewport: ${JSON.stringify(this.state.viewport)}`);
|
||||
return (
|
||||
<div>
|
||||
<Fragment>
|
||||
{ !isHome() &&
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
|
|
@ -152,7 +152,7 @@ class SubscriptionsMap extends React.Component {
|
|||
{ !isHome() &&
|
||||
<ShareIt title={title} />
|
||||
}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,20 @@
|
|||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
|
||||
class TestError extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.t = props.t;
|
||||
this.state = {
|
||||
};
|
||||
throw new Error('Just testing');
|
||||
}
|
||||
|
||||
render() {
|
||||
throw new Error('Just testing');
|
||||
return (
|
||||
<div />
|
||||
);
|
||||
|
|
@ -20,9 +24,13 @@ class TestError extends Component {
|
|||
}
|
||||
|
||||
TestError.propTypes = {
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
TestError.defaultProps = {
|
||||
};
|
||||
|
||||
export default TestError;
|
||||
// export default translate([], { wait: true })(TestError);
|
||||
export default translate([], { wait: true })(withTracker(props => ({
|
||||
// props.something
|
||||
}))(TestError));
|
||||
|
|
|
|||
|
|
@ -1,28 +1,26 @@
|
|||
/* eslint-disable react/jsx-indent-props */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
/* eslint-disable import/no-absolute-path */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { translate } from 'react-i18next';
|
||||
import SubscriptionsMap from '/imports/ui/pages/Subscriptions/SubscriptionsMap';
|
||||
|
||||
export default class ZonesMap extends Component {
|
||||
class ZonesMap extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
this.t = props.t;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Fragment>
|
||||
<SubscriptionsMap {...this.props} />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ZonesMap.propTypes = {
|
||||
history: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
ZonesMap.defaultProps = {
|
||||
};
|
||||
export default translate([], { wait: true })(ZonesMap);
|
||||
|
|
|
|||
|
|
@ -11,13 +11,11 @@ a, a:hover {
|
|||
|
||||
h4.page-header {
|
||||
line-height: 2em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
@include breakpoint(mobile) {
|
||||
h4.page-header {
|
||||
line-height: 1.3em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -25,9 +23,19 @@ h4.page-header {
|
|||
/* margin-top: 30px; */
|
||||
}
|
||||
|
||||
h4.page-header {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.page-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#react-root > div > div.container > div > div.alert {
|
||||
margin-top: 20px;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue