Browse Source

new weather status used in dashboard

Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
Julien Veyssier 3 years ago
parent
commit
70d1d1997a
42 changed files with 1438 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 21 0
      apps/weather_status/appinfo/info.xml
  3. 34 0
      apps/weather_status/appinfo/routes.php
  4. 1 0
      apps/weather_status/img/app-dark.svg
  5. 1 0
      apps/weather_status/img/app.svg
  6. 1 0
      apps/weather_status/img/cloud-cloud.svg
  7. 1 0
      apps/weather_status/img/cloud-dots.svg
  8. 1 0
      apps/weather_status/img/cross.svg
  9. 1 0
      apps/weather_status/img/drop.svg
  10. 1 0
      apps/weather_status/img/fog.svg
  11. 1 0
      apps/weather_status/img/half-sun.svg
  12. 1 0
      apps/weather_status/img/heavy-rain.svg
  13. 1 0
      apps/weather_status/img/light-rain.svg
  14. 1 0
      apps/weather_status/img/moon-cloud-heavy-rain.svg
  15. 1 0
      apps/weather_status/img/moon-cloud-light-rain.svg
  16. 1 0
      apps/weather_status/img/moon-cloud-rain.svg
  17. 1 0
      apps/weather_status/img/moon-cloud.svg
  18. 1 0
      apps/weather_status/img/moon-small-cloud.svg
  19. 1 0
      apps/weather_status/img/moon.svg
  20. 1 0
      apps/weather_status/img/rain.svg
  21. 1 0
      apps/weather_status/img/snow.svg
  22. 0 0
      apps/weather_status/img/sun-cloud-heavy-rain.svg
  23. 1 0
      apps/weather_status/img/sun-cloud-light-rain.svg
  24. 1 0
      apps/weather_status/img/sun-cloud-rain.svg
  25. 1 0
      apps/weather_status/img/sun-cloud.svg
  26. 1 0
      apps/weather_status/img/sun-small-cloud.svg
  27. 1 0
      apps/weather_status/img/sun.svg
  28. 1 0
      apps/weather_status/img/thunder.svg
  29. 1 0
      apps/weather_status/img/umbrella.svg
  30. 0 0
      apps/weather_status/js/weather-status.js
  31. 0 0
      apps/weather_status/js/weather-status.js.map
  32. 72 0
      apps/weather_status/lib/AppInfo/Application.php
  33. 55 0
      apps/weather_status/lib/Capabilities.php
  34. 124 0
      apps/weather_status/lib/Controller/WeatherStatusController.php
  35. 429 0
      apps/weather_status/lib/Service/WeatherStatusService.php
  36. 503 0
      apps/weather_status/src/App.vue
  37. 116 0
      apps/weather_status/src/services/weatherStatusService.js
  38. 28 0
      apps/weather_status/src/weather-status.js
  39. 26 0
      apps/weather_status/webpack.js
  40. 1 0
      build/integration/features/provisioning-v1.feature
  41. 1 0
      core/shipped.json
  42. 2 0
      webpack.common.js

+ 1 - 0
.gitignore

@@ -40,6 +40,7 @@
 !/apps/theming
 !/apps/twofactor_backupcodes
 !/apps/user_status
+!/apps/weather_status
 !/apps/workflowengine
 /apps/files_external/3rdparty/irodsphp/PHPUnitTest
 /apps/files_external/3rdparty/irodsphp/web

+ 21 - 0
apps/weather_status/appinfo/info.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+      xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
+    <id>weather_status</id>
+    <name>Weather status</name>
+    <summary>Weather status in your dashboard</summary>
+    <description><![CDATA[Weather status integrated in the dashboard app.
+    User's position can be automatically determined or manually defined. A 6 hours forecast is then displayed.
+    This status can also be integrated in other places like the Calendar app.]]></description>
+    <version>1.0.0</version>
+    <licence>agpl</licence>
+    <author mail="eneiluj@posteo.net">Julien Veyssier</author>
+    <namespace>WeatherStatus</namespace>
+    <default_enable/>
+    <category>integration</category>
+    <category>dashboard</category>
+    <bugs>https://github.com/nextcloud/server</bugs>
+    <dependencies>
+        <nextcloud min-version="20" max-version="20"/>
+    </dependencies>
+</info>

+ 34 - 0
apps/weather_status/appinfo/routes.php

@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+return [
+	'ocs' => [
+		['name' => 'WeatherStatus#setMode', 'url' => '/api/v1/mode', 'verb' => 'PUT'],
+		['name' => 'WeatherStatus#usePersonalAddress', 'url' => '/api/v1/use-personal', 'verb' => 'PUT'],
+		['name' => 'WeatherStatus#getLocation', 'url' => '/api/v1/location', 'verb' => 'GET'],
+		['name' => 'WeatherStatus#setLocation', 'url' => '/api/v1/location', 'verb' => 'PUT'],
+		['name' => 'WeatherStatus#getForecast', 'url' => '/api/v1/forecast', 'verb' => 'GET'],
+	],
+];

+ 1 - 0
apps/weather_status/img/app-dark.svg

@@ -0,0 +1 @@
+<svg width="280.5" height="280.5" xmlns="http://www.w3.org/2000/svg"><path d="M140.22 210.04c38.48 0 69.78-31.3 69.78-69.78s-31.3-69.78-69.78-69.78c-38.47 0-69.78 31.3-69.78 69.78s31.3 69.78 69.78 69.78M132.8 38.9a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 00-14.85 0V38.9M132.8 241.63v31.46a7.43 7.43 0 0014.85 0v-31.46a7.43 7.43 0 00-14.85 0M89.54 59.91a7.43 7.43 0 006.43-11.14L80.24 21.53a7.43 7.43 0 00-12.86 7.43L83.1 56.2a7.42 7.42 0 006.43 3.71M187.2 221.62a7.43 7.43 0 00-2.72 10.14L200.2 259a7.42 7.42 0 1012.86-7.42l-15.73-27.25a7.43 7.43 0 00-10.15-2.71M18.76 70.14a7.43 7.43 0 002.72 10.15L48.72 96a7.42 7.42 0 107.43-12.86L28.9 67.42a7.43 7.43 0 00-10.14 2.72M258.97 200.24l-27.25-15.73a7.43 7.43 0 00-7.42 12.87l27.24 15.73a7.4 7.4 0 0010.14-2.72 7.43 7.43 0 00-2.71-10.15M46.28 140.27c0-4.1-3.33-7.42-7.43-7.42H7.4a7.43 7.43 0 000 14.85h31.46c4.1 0 7.43-3.33 7.43-7.43M273.05 132.85h-31.46a7.43 7.43 0 000 14.85h31.46a7.43 7.43 0 000-14.85M48.73 184.51L21.5 200.24a7.43 7.43 0 107.42 12.86l27.25-15.73a7.43 7.43 0 00-7.43-12.86M251.54 67.42L224.3 83.15A7.43 7.43 0 00231.72 96l27.24-15.73a7.43 7.43 0 00-7.42-12.86M83.1 224.34l-15.73 27.24a7.43 7.43 0 0012.87 7.43l15.73-27.25a7.43 7.43 0 00-12.87-7.42M187.2 58.91a7.4 7.4 0 0010.14-2.71l15.73-27.25a7.43 7.43 0 10-12.86-7.42l-15.73 27.24a7.43 7.43 0 002.71 10.14"/></svg>

+ 1 - 0
apps/weather_status/img/app.svg

@@ -0,0 +1 @@
+<svg width="280.5" height="280.5" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path d="M140.22 210.04c38.48 0 69.78-31.3 69.78-69.78s-31.3-69.78-69.78-69.78c-38.47 0-69.78 31.3-69.78 69.78s31.3 69.78 69.78 69.78M132.8 38.9a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 00-14.85 0V38.9M132.8 241.63v31.46a7.43 7.43 0 0014.85 0v-31.46a7.43 7.43 0 00-14.85 0M89.54 59.91a7.43 7.43 0 006.43-11.14L80.24 21.53a7.43 7.43 0 00-12.86 7.43L83.1 56.2a7.42 7.42 0 006.43 3.71M187.2 221.62a7.43 7.43 0 00-2.72 10.14L200.2 259a7.42 7.42 0 1012.86-7.42l-15.73-27.25a7.43 7.43 0 00-10.15-2.71M18.76 70.14a7.43 7.43 0 002.72 10.15L48.72 96a7.42 7.42 0 107.43-12.86L28.9 67.42a7.43 7.43 0 00-10.14 2.72M258.97 200.24l-27.25-15.73a7.43 7.43 0 00-7.42 12.87l27.24 15.73a7.4 7.4 0 0010.14-2.72 7.43 7.43 0 00-2.71-10.15M46.28 140.27c0-4.1-3.33-7.42-7.43-7.42H7.4a7.43 7.43 0 000 14.85h31.46c4.1 0 7.43-3.33 7.43-7.43M273.05 132.85h-31.46a7.43 7.43 0 000 14.85h31.46a7.43 7.43 0 000-14.85M48.73 184.51L21.5 200.24a7.43 7.43 0 107.42 12.86l27.25-15.73a7.43 7.43 0 00-7.43-12.86M251.54 67.42L224.3 83.15A7.43 7.43 0 00231.72 96l27.24-15.73a7.43 7.43 0 00-7.42-12.86M83.1 224.34l-15.73 27.24a7.43 7.43 0 0012.87 7.43l15.73-27.25a7.43 7.43 0 00-12.87-7.42M187.2 58.91a7.4 7.4 0 0010.14-2.71l15.73-27.25a7.43 7.43 0 10-12.86-7.42l-15.73 27.24a7.43 7.43 0 002.71 10.14"/></g></svg>

+ 1 - 0
apps/weather_status/img/cloud-cloud.svg

@@ -0,0 +1 @@
+<svg width="294.71" height="189.16" xmlns="http://www.w3.org/2000/svg"><path d="M109.03 119.2c0-29.19 26.4-52.94 58.86-52.94 7.02 0 13.81 1.08 20.29 3.22a54.36 54.36 0 0125.04-14.36A51.95 51.95 0 00166.9 26.3c-4.21 0-8.41.52-12.48 1.52-3 .74-6.15-.44-7.9-2.99a57.13 57.13 0 00-85.58-9.84 56.95 56.95 0 00-18.33 36.65 7.42 7.42 0 01-4.2 6c-.69.34-1.38.68-2.05 1.06-.62.34-1.29.6-1.98.76A44.3 44.3 0 009.8 74.66a43.2 43.2 0 00-9.8 27.58c0 24.19 19.78 43.86 44.1 43.86h52.96a48.04 48.04 0 0112.03-24.63c-.03-.76-.05-1.51-.05-2.27" fill="#61c9e7"/><path d="M269.25 120.4a7.43 7.43 0 01-4.46-9.5 28.85 28.85 0 001.72-9.82c0-14.1-10.27-26.16-24.7-30.9a43.63 43.63 0 00-21.08-1.55c-9.9 1.68-18.74 6.7-24.5 14.12a7.43 7.43 0 01-8.8 2.26 49.12 49.12 0 00-19.53-3.96c-24.27 0-44.02 17.1-44.02 38.1 0 1.34.09 2.73.26 4.1a7.39 7.39 0 01-2.23 6.26 34.3 34.3 0 00-9.71 16.53 31.03 31.03 0 00.02 14.85c4 16.13 20.62 28.27 40.46 28.27h100.63c22.82 0 41.38-16.04 41.38-35.76 0-14.45-9.99-27.41-25.44-33" fill="#4492a8"/></svg>

+ 1 - 0
apps/weather_status/img/cloud-dots.svg

@@ -0,0 +1 @@
+<svg width="251.88" height="220.4" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M47.2 155.9h157.35c26.07 0 47.27-21.1 47.27-47a46.59 46.59 0 00-18.56-37.34 7.5 7.5 0 01-2.64-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.35 1.62-3 .74-6.14-.45-7.9-3a61.1 61.1 0 00-91.5-10.5 60.87 60.87 0 00-19.6 39.19 7.43 7.43 0 01-4.2 6c-.74.35-1.48.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3A46.3 46.3 0 00-.05 108.9c0 25.92 21.2 47 47.26 47M47.94 186.84a16.77 16.77 0 100 33.54 16.77 16.77 0 000-33.54M141.7 203.6a16.78 16.78 0 10-33.56.02 16.78 16.78 0 0033.56-.01M214.36 203.6a16.77 16.77 0 10-33.54 0 16.77 16.77 0 0033.54 0"/></g></svg>

+ 1 - 0
apps/weather_status/img/cross.svg

@@ -0,0 +1 @@
+<svg width="24" height="24" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M22 12h-4M6 12H2M12 6V2M12 22v-4"/></svg>

+ 1 - 0
apps/weather_status/img/drop.svg

@@ -0,0 +1 @@
+<svg width="128.67" height="188.52" xmlns="http://www.w3.org/2000/svg"><path d="M-.02 126.27c0 34.33 28.86 62.26 64.33 62.26 35.48 0 64.34-27.93 64.34-62.26 0-26.94-42-93.7-64.34-126.26C41.97 32.57-.02 99.33-.02 126.27" fill="#61c9e7"/></svg>

+ 1 - 0
apps/weather_status/img/fog.svg

@@ -0,0 +1 @@
+<svg width="254.44" height="256.38" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M233.3 71.58a7.42 7.42 0 01-2.63-3.84 55.5 55.5 0 00-53.08-39.87c-4.5 0-9 .55-13.35 1.63-3 .74-6.15-.45-7.9-3A61.1 61.1 0 00106 .01 60.89 60.89 0 0064.83 16a60.87 60.87 0 00-19.6 39.2 7.42 7.42 0 01-4.2 6c-.73.34-1.47.72-2.2 1.12-.61.35-1.28.6-1.97.75A47.48 47.48 0 0010.5 79.37 46.3 46.3 0 000 108.92c0 25.91 21.2 47 47.26 47H204.6c26.06 0 47.27-21.09 47.27-47a46.6 46.6 0 00-18.56-37.34M231.32 187.91c0-4.1-3.33-7.43-7.43-7.43H28.87a7.42 7.42 0 100 14.86h195.02c4.1 0 7.43-3.34 7.43-7.43M213.14 241.54H18.12a7.43 7.43 0 000 14.85h195.02a7.42 7.42 0 100-14.85M62.28 211.43a7.42 7.42 0 100 14.85h71.47a7.43 7.43 0 000-14.85H62.28M247 211.43h-71.48a7.42 7.42 0 100 14.85H247a7.43 7.43 0 000-14.85"/></g></svg>

+ 1 - 0
apps/weather_status/img/half-sun.svg

@@ -0,0 +1 @@
+<svg width="318.87" height="166.86" xmlns="http://www.w3.org/2000/svg"><g fill="#dec60f"><path d="M159.33 78.52c-42.13 0-76.84 32.35-80.6 73.5h161.19c-3.76-41.15-38.47-73.5-80.59-73.5M152 7.44v36a7.42 7.42 0 1014.85 0v-36a7.43 7.43 0 00-14.85 0M79.67 21.37a7.43 7.43 0 00-2.72 10.15l18 31.18a7.42 7.42 0 1012.86-7.43l-18-31.18a7.43 7.43 0 00-10.14-2.72M62.63 95.01l-31.18-18a7.43 7.43 0 00-7.42 12.86l31.17 18a7.4 7.4 0 0010.15-2.71A7.43 7.43 0 0062.63 95M50.8 159.45c0-4.1-3.32-7.43-7.42-7.43h-36a7.43 7.43 0 000 14.86h36c4.1 0 7.42-3.33 7.42-7.43M311.33 152.02h-36a7.43 7.43 0 000 14.86h36a7.42 7.42 0 100-14.86M263.6 107.88l31.17-18A7.42 7.42 0 10287.34 77l-31.17 18a7.43 7.43 0 007.43 12.87M213.73 65.43a7.43 7.43 0 0010.15-2.72l18-31.18a7.42 7.42 0 10-12.86-7.43l-18 31.18a7.42 7.42 0 002.71 10.15"/></g></svg>

+ 1 - 0
apps/weather_status/img/heavy-rain.svg

@@ -0,0 +1 @@
+<svg width="251.88" height="258.64" xmlns="http://www.w3.org/2000/svg"><path d="M251.85 108.9a46.6 46.6 0 00-18.56-37.34 7.43 7.43 0 01-2.63-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.35 1.62-3 .74-6.15-.45-7.9-3A61.1 61.1 0 00106 0a60.89 60.89 0 00-41.18 15.97 60.87 60.87 0 00-19.6 39.2 7.43 7.43 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.61.34-1.28.6-1.97.75a47.48 47.48 0 00-26.35 16.3A46.3 46.3 0 00-.01 108.9c0 25.92 21.2 47 47.26 47H204.6c26.07 0 47.27-21.08 47.27-47" fill="#4492a8"/><g fill="#61c9e7"><path d="M66.66 223.44a7.42 7.42 0 007.1-9.6l-8.94-29.18a7.42 7.42 0 10-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M107.4 258.62a7.43 7.43 0 007.1-9.6l-8.93-29.18a7.42 7.42 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M159.22 213.06a7.43 7.43 0 00-4.93 9.28l8.94 29.17a7.43 7.43 0 1014.2-4.35L168.5 218a7.43 7.43 0 00-9.27-4.93M128.84 223.44a7.41 7.41 0 007.1-9.6l-8.93-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M191.03 223.44a7.41 7.41 0 007.1-9.6l-8.94-29.18A7.42 7.42 0 10175 189l8.94 29.17a7.43 7.43 0 007.1 5.26"/></g></svg>

+ 1 - 0
apps/weather_status/img/light-rain.svg

@@ -0,0 +1 @@
+<svg width="294.71" height="258.57" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M109.04 119.18c0-29.2 26.4-52.95 58.87-52.95 7 0 13.8 1.08 20.28 3.22a54.32 54.32 0 0125.04-14.35 51.93 51.93 0 00-46.32-28.83c-4.21 0-8.41.51-12.47 1.52-3 .75-6.16-.45-7.91-3A57.15 57.15 0 0099.46.02c-14.3 0-27.97 5.31-38.51 14.95a56.93 56.93 0 00-18.33 36.65 7.43 7.43 0 01-4.2 6c-.68.32-1.37.68-2.05 1.05-.62.35-1.29.6-1.98.75A44.3 44.3 0 009.8 74.64 43.2 43.2 0 000 102.2c0 24.18 19.78 43.86 44.1 43.86h52.96a48.04 48.04 0 0112.03-24.63c-.03-.76-.05-1.51-.05-2.26M64.99 258.59a7.44 7.44 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M127.17 258.59a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M189.36 258.59a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26"/></g><path d="M269.26 120.42a7.43 7.43 0 01-4.46-9.5 28.86 28.86 0 001.73-9.82c0-14.1-10.28-26.15-24.7-30.91a43.57 43.57 0 00-21.09-1.55c-9.9 1.69-18.74 6.7-24.49 14.12a7.43 7.43 0 01-8.8 2.28 49.12 49.12 0 00-19.54-3.96c-24.27 0-44.02 17.09-44.02 38.09 0 1.35.09 2.73.26 4.1a7.43 7.43 0 01-2.23 6.26 34.21 34.21 0 00-9.71 16.53 31.03 31.03 0 00.03 14.85c4 16.13 20.61 28.27 40.45 28.27h100.63c22.82 0 41.38-16.05 41.38-35.76 0-14.46-9.98-27.41-25.44-33" fill="#4492a8"/></svg>

+ 1 - 0
apps/weather_status/img/moon-cloud-heavy-rain.svg

@@ -0,0 +1 @@
+<svg width="318.94" height="289.62" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M285.04 127.6a7.42 7.42 0 01-2.64-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.34 1.62-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-50.33-26.48 60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3 46.29 46.29 0 00-10.51 29.55c0 25.92 21.2 47 47.27 47h157.34c26.06 0 47.26-21.08 47.26-47a46.6 46.6 0 00-18.56-37.33" paint-order="stroke fill markers"/><path d="M91.98 287.1a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M154.16 287.1a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M216.35 287.1a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M116.58 259.97a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M178.76 259.97a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M278.33 287.09a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M240.73 259.95a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25"/></g><path d="M79.82-.05c-3.37 1.02-6.69 2.2-9.96 3.54-57.55 23.57-85.2 89.56-61.63 147.12 8.19 20 21.61 36.84 38.8 49.07a61.6 61.6 0 01-10.8-34.83c0-14.3 4.93-28.13 13.96-39.22a62.66 62.66 0 015.99-6.36 125.82 125.82 0 01-1.69-70.09A128.43 128.43 0 0179.82-.05z" fill="#e1c014"/></svg>

+ 1 - 0
apps/weather_status/img/moon-cloud-light-rain.svg

@@ -0,0 +1 @@
+<svg width="318.94" height="289.62" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M285.04 127.6a7.42 7.42 0 01-2.64-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.34 1.62-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-50.33-26.48 60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3 46.29 46.29 0 00-10.51 29.55c0 25.92 21.2 47 47.27 47h157.34c26.06 0 47.26-21.08 47.26-47a46.6 46.6 0 00-18.56-37.33" paint-order="stroke fill markers"/><path d="M119.95 289.63a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M182.13 289.63a7.43 7.43 0 007.1-9.6l-8.93-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M244.32 289.63a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25"/></g><path d="M80.66-.9C77.29.14 73.97 1.32 70.7 2.66 13.15 26.22-14.5 92.21 9.07 149.77c8.19 20 21.61 36.84 38.8 49.07A61.6 61.6 0 0137.07 164c0-14.3 4.93-28.13 13.96-39.22a62.66 62.66 0 015.99-6.36 125.82 125.82 0 01-1.69-70.09A128.43 128.43 0 0180.66-.89z" fill="#e1c014"/></svg>

+ 1 - 0
apps/weather_status/img/moon-cloud-rain.svg

@@ -0,0 +1 @@
+<svg width="318.94" height="289.62" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M285.04 127.6a7.42 7.42 0 01-2.64-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.34 1.62-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-50.33-26.48 60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3 46.29 46.29 0 00-10.51 29.55c0 25.92 21.2 47 47.27 47h157.34c26.06 0 47.26-21.08 47.26-47a46.6 46.6 0 00-18.56-37.33" paint-order="stroke fill markers"/><path d="M125.03 287.94a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M187.21 287.94a7.43 7.43 0 007.1-9.6l-8.93-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M249.4 287.94a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M149.63 260.8a7.43 7.43 0 007.1-9.6l-8.94-29.17a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M211.81 260.8a7.43 7.43 0 007.1-9.6l-8.94-29.17a7.43 7.43 0 10-14.2 4.35l8.95 29.17a7.43 7.43 0 007.1 5.26"/></g><path d="M79.82-.05c-3.37 1.02-6.69 2.2-9.96 3.54-57.55 23.57-85.2 89.56-61.63 147.12 8.19 20 21.61 36.84 38.8 49.07a61.6 61.6 0 01-10.8-34.83c0-14.3 4.93-28.13 13.96-39.22a62.66 62.66 0 015.99-6.36 125.82 125.82 0 01-1.69-70.09A128.43 128.43 0 0179.82-.05z" fill="#e1c014"/></svg>

+ 1 - 0
apps/weather_status/img/moon-cloud.svg

@@ -0,0 +1 @@
+<svg width="238.3" height="228.57" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M91.64 66.54L86.46 85.9l18.52-7.66 16.8 10.91-1.55-19.97 15.57-12.62-19.48-4.69-7.19-18.7-10.48 17.07-20.01 1.04 13 15.26M176.79 45.46l-7.67-10.3-3.51 12.35-12.17 4.1 10.66 7.16.14 12.84 10.1-7.92 12.27 3.83-4.42-12.06 7.43-10.48-12.83.48"/><path d="M212.17 155.64a5.25 5.25 0 01-1.87-2.73 39.52 39.52 0 00-37.79-28.38c-3.2 0-6.4.39-9.5 1.16a5.29 5.29 0 01-5.63-2.14 43.5 43.5 0 00-65.14-7.48 43.35 43.35 0 00-13.95 27.9 5.3 5.3 0 01-3 4.28c-.52.24-1.04.51-1.56.8-.44.24-.91.42-1.4.53a33.84 33.84 0 00-18.77 11.6 32.99 32.99 0 00-7.48 21.05c0 18.44 15.1 33.45 33.65 33.45h112c18.56 0 33.66-15 33.66-33.45a33.2 33.2 0 00-13.22-26.59" paint-order="stroke fill markers"/></g><path d="M82.9-.33a117.62 117.62 0 00-10.3 3.67C13.06 27.73-15.55 96 8.84 155.54a116.43 116.43 0 0026.36 39.24 45.62 45.62 0 01-1.87-12.9A46.3 46.3 0 0143.7 152.7l.01-.01a46.59 46.59 0 0120.52-14.47A130.4 130.4 0 0156.7 50.6 132.88 132.88 0 0182.9-.32z" fill="#e1c014"/></svg>

+ 1 - 0
apps/weather_status/img/moon-small-cloud.svg

@@ -0,0 +1 @@
+<svg width="238.3" height="228.57" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M98.43 102.12l-5.19 19.36 18.53-7.65 16.8 10.9-1.56-19.97 15.58-12.61-19.49-4.7-7.18-18.7-10.48 17.08-20.02 1.04 13.01 15.25M176.79 45.46l-7.67-10.3-3.51 12.35-12.17 4.1 10.66 7.16.14 12.84 10.1-7.92 12.27 3.83-4.42-12.06 7.43-10.48-12.83.48"/><path d="M220.76 179.59a3.53 3.53 0 01-1.26-1.84 26.54 26.54 0 00-31.76-18.28 3.55 3.55 0 01-3.78-1.44 29.21 29.21 0 00-53.12 13.72 3.56 3.56 0 01-2 2.87c-.36.17-.71.35-1.06.54-.3.16-.61.28-.95.35a22.73 22.73 0 00-12.6 7.8 22.15 22.15 0 00-5.02 14.13 22.56 22.56 0 0022.6 22.47h75.23a22.56 22.56 0 0022.6-22.47 22.3 22.3 0 00-8.88-17.85" paint-order="stroke fill markers"/></g><path d="M82.47-.02a117.62 117.62 0 00-10.3 3.67C12.63 28.03-15.97 96.3 8.41 155.85a115.91 115.91 0 0062.9 63.4 116.18 116.18 0 0054.39 8.73c-14.4-2.74-25.45-15.44-25.45-30.57 0-3.46.59-6.86 1.7-10.09a130.42 130.42 0 01-40.12-53.35 130.4 130.4 0 01-5.56-83.06A132.88 132.88 0 0182.47-.02z" fill="#e1c014"/></svg>

+ 1 - 0
apps/weather_status/img/moon.svg

@@ -0,0 +1 @@
+<svg width="189.63" height="228.4" xmlns="http://www.w3.org/2000/svg"><path d="M62.11 134a130.4 130.4 0 01-5.55-83.05A132.89 132.89 0 0182.76 0a117.64 117.64 0 00-10.3 3.67C12.92 28.07-15.7 96.34 8.7 155.88a115.91 115.91 0 0062.89 63.4 115.93 115.93 0 0089.3.36c3.39-1.39 6.7-2.92 9.92-4.62a132.66 132.66 0 01-54.4-17.92 130.45 130.45 0 01-54.3-63.1" fill="#e1c014"/><path d="M122.15 123.3l-5.19 19.37 18.52-7.65 16.81 10.9-1.56-19.97 15.58-12.61-19.49-4.7-7.18-18.7-10.48 17.08-20.02 1.04 13.01 15.25M176.79 45.46l-7.67-10.3-3.51 12.35-12.17 4.1 10.66 7.16.14 12.84 10.1-7.92 12.27 3.83-4.42-12.06 7.43-10.48-12.83.48" fill="#61c9e7"/></svg>

+ 1 - 0
apps/weather_status/img/rain.svg

@@ -0,0 +1 @@
+<svg width="251.87" height="223.45" xmlns="http://www.w3.org/2000/svg"><path d="M47.26 155.9H204.6c26.06 0 47.27-21.1 47.27-47a46.6 46.6 0 00-18.56-37.34 7.43 7.43 0 01-2.64-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.34 1.62-3 .74-6.15-.45-7.9-3A61.1 61.1 0 00106.01 0a60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.74.35-1.48.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.47 47.47 0 00-26.35 16.3A46.3 46.3 0 000 108.9c0 25.92 21.2 47 47.26 47" fill="#4492a8"/><g fill="#61c9e7"><path d="M55.55 179.74a7.42 7.42 0 00-4.92 9.28l8.94 29.17a7.43 7.43 0 1014.2-4.35l-8.94-29.17a7.42 7.42 0 00-9.28-4.93M117.74 179.74a7.43 7.43 0 00-4.93 9.28l8.94 29.17a7.43 7.43 0 0014.2-4.35l-8.94-29.17a7.43 7.43 0 00-9.27-4.93M179.92 179.74a7.43 7.43 0 00-4.92 9.28l8.94 29.17a7.43 7.43 0 0014.2-4.35l-8.94-29.17a7.43 7.43 0 00-9.28-4.93"/></g></svg>

+ 1 - 0
apps/weather_status/img/snow.svg

@@ -0,0 +1 @@
+<svg width="251.87" height="236.67" xmlns="http://www.w3.org/2000/svg"><g fill="#61c9e7"><path d="M233.28 71.56a7.43 7.43 0 01-2.63-3.85 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .55-13.35 1.63-3 .74-6.15-.45-7.9-3A61.1 61.1 0 00105.99-.01 60.88 60.88 0 0064.8 15.97a60.87 60.87 0 00-19.6 39.2 7.43 7.43 0 01-4.2 6c-.73.34-1.47.72-2.2 1.12-.62.34-1.28.6-1.97.75a47.48 47.48 0 00-26.36 16.3A46.3 46.3 0 00-.02 108.9c0 25.91 21.2 47 47.26 47h157.34c26.06 0 47.27-21.09 47.27-47a46.6 46.6 0 00-18.56-37.34M42.25 186.84a6.63 6.63 0 00-6.63 6.63v10.21l-10.86-2.94a6.63 6.63 0 10-3.47 12.8l10.45 2.83-6.32 9.73a6.63 6.63 0 1011.12 7.21l5.8-8.93 6.28 9.35a6.62 6.62 0 1011-7.4l-6.71-10 10.3-2.8a6.63 6.63 0 10-3.48-12.8l-10.85 2.95v-10.21a6.63 6.63 0 00-6.63-6.63M125.91 186.84a6.63 6.63 0 00-6.63 6.63v10.21l-10.86-2.94a6.63 6.63 0 10-3.47 12.8l10.45 2.83-6.31 9.73a6.63 6.63 0 1011.12 7.21l5.8-8.93 6.28 9.35a6.62 6.62 0 1011-7.4l-6.72-10 10.3-2.8a6.63 6.63 0 00-3.47-12.8l-10.86 2.95v-10.21a6.63 6.63 0 00-6.63-6.63M209.58 186.84a6.63 6.63 0 00-6.63 6.63v10.21l-10.86-2.94a6.63 6.63 0 10-3.47 12.8l10.45 2.83-6.32 9.73a6.63 6.63 0 1011.12 7.21l5.8-8.93 6.28 9.35a6.62 6.62 0 1011-7.4l-6.71-10 10.3-2.8a6.63 6.63 0 10-3.48-12.8l-10.85 2.95v-10.21a6.63 6.63 0 00-6.63-6.63"/></g></svg>

File diff suppressed because it is too large
+ 0 - 0
apps/weather_status/img/sun-cloud-heavy-rain.svg


+ 1 - 0
apps/weather_status/img/sun-cloud-light-rain.svg

@@ -0,0 +1 @@
+<svg width="307.19" height="291.33" xmlns="http://www.w3.org/2000/svg"><path d="M55.57 92.54c0 9.27 3.43 18.02 9.54 24.76a62.36 62.36 0 0121.22-10.38 75.72 75.72 0 0125.61-45.8 36.97 36.97 0 00-56.37 31.4" fill="#dec60f"/><g fill="#61c9e7"><path d="M288.6 129.3a7.42 7.42 0 01-2.63-3.85A55.5 55.5 0 00232.9 85.6c-4.5 0-9 .54-13.34 1.62-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-50.33-26.48 60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3 46.29 46.29 0 00-10.51 29.55c0 25.92 21.2 47 47.27 47H259.9c26.06 0 47.26-21.08 47.26-47a46.6 46.6 0 00-18.56-37.33M123.52 291.33a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M185.7 291.33a7.43 7.43 0 007.1-9.6l-8.93-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M247.89 291.33a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25"/></g><g fill="#dec60f"><path d="M85.08 7.45v20.89a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 10-14.85 0M60.41 44.36a7.43 7.43 0 006.42-11.14L56.4 15.12a7.43 7.43 0 00-12.86 7.44l10.45 18.09a7.42 7.42 0 006.43 3.71M40.61 54L22.52 43.56a7.43 7.43 0 10-7.43 12.86l18.1 10.45A7.43 7.43 0 0040.62 54M35.73 92.54c0-4.1-3.32-7.43-7.42-7.43H7.4a7.43 7.43 0 000 14.85h20.9c4.1 0 7.42-3.32 7.42-7.42M40.61 131.07a7.43 7.43 0 10-7.42-12.86l-18.1 10.44a7.43 7.43 0 107.43 12.87l18.1-10.45M120.9 43.36a7.42 7.42 0 0010.15-2.72l10.43-18.09a7.43 7.43 0 00-12.86-7.42l-10.45 18.1a7.43 7.43 0 002.72 10.13"/></g></svg>

+ 1 - 0
apps/weather_status/img/sun-cloud-rain.svg

@@ -0,0 +1 @@
+<svg width="307.19" height="291.33" xmlns="http://www.w3.org/2000/svg"><path d="M55.57 92.54c0 9.27 3.43 18.02 9.54 24.76a62.36 62.36 0 0121.22-10.38 75.72 75.72 0 0125.61-45.8 36.97 36.97 0 00-56.37 31.4" fill="#dec60f"/><g fill="#61c9e7"><path d="M288.6 129.3a7.42 7.42 0 01-2.63-3.85A55.5 55.5 0 00232.9 85.6c-4.5 0-9 .54-13.34 1.62-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-50.33-26.48 60.89 60.89 0 00-41.18 15.98 60.88 60.88 0 00-19.6 39.19 7.42 7.42 0 01-4.2 6c-.73.35-1.47.73-2.2 1.13-.62.34-1.29.6-1.98.75a47.48 47.48 0 00-26.35 16.3 46.29 46.29 0 00-10.51 29.55c0 25.92 21.2 47 47.27 47H259.9c26.06 0 47.26-21.08 47.26-47a46.6 46.6 0 00-18.56-37.33M116.92 264.97a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M179.1 264.97a7.43 7.43 0 007.1-9.6l-8.93-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25M241.29 264.97a7.43 7.43 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 10-14.2 4.35l8.94 29.18a7.43 7.43 0 007.1 5.25"/></g><g fill="#dec60f"><path d="M85.08 7.45v20.89a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 10-14.85 0M60.41 44.36a7.43 7.43 0 006.42-11.14L56.4 15.12a7.43 7.43 0 00-12.86 7.44l10.45 18.09a7.42 7.42 0 006.43 3.71M40.61 54L22.52 43.56a7.43 7.43 0 10-7.43 12.86l18.1 10.45A7.43 7.43 0 0040.62 54M35.73 92.54c0-4.1-3.32-7.43-7.42-7.43H7.4a7.43 7.43 0 000 14.85h20.9c4.1 0 7.42-3.32 7.42-7.42M40.61 131.07a7.43 7.43 0 10-7.42-12.86l-18.1 10.44a7.43 7.43 0 107.43 12.87l18.1-10.45M120.9 43.36a7.42 7.42 0 0010.15-2.72l10.43-18.09a7.43 7.43 0 00-12.86-7.42l-10.45 18.1a7.43 7.43 0 002.72 10.13"/></g><path d="M215.98 291.79a7.41 7.41 0 007.1-9.6L214.15 253a7.43 7.43 0 00-14.2 4.35l8.95 29.17a7.43 7.43 0 007.1 5.26M154.27 290.59a7.41 7.41 0 007.1-9.6l-8.94-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26" fill="#61c9e7"/></svg>

+ 1 - 0
apps/weather_status/img/sun-cloud.svg

@@ -0,0 +1 @@
+<svg width="307.19" height="213.61" xmlns="http://www.w3.org/2000/svg"><path d="M288.6 129.37a7.38 7.38 0 01-2.63-3.84 55.51 55.51 0 00-53.08-39.87c-4.5 0-9 .55-13.34 1.63-3 .75-6.15-.45-7.9-3a61.1 61.1 0 00-91.51-10.5 60.89 60.89 0 00-19.6 39.2 7.44 7.44 0 01-4.2 6c-.73.34-1.47.71-2.2 1.11-.62.35-1.29.6-1.98.75a47.54 47.54 0 00-26.35 16.3 46.33 46.33 0 00-10.51 29.56c0 25.92 21.2 47 47.27 47H259.9c26.06 0 47.26-21.08 47.26-47a46.63 46.63 0 00-18.56-37.34" fill="#61c9e7"/><g fill="#dec60f"><path d="M55.57 92.57a36.7 36.7 0 009.54 24.76 62.36 62.36 0 0121.22-10.39 75.68 75.68 0 0125.61-45.78 36.97 36.97 0 00-56.37 31.41M92.5 35.77c4.1 0 7.43-3.33 7.43-7.43V7.45a7.42 7.42 0 10-14.85 0v20.9c0 4.09 3.33 7.42 7.43 7.42M53.97 40.7a7.42 7.42 0 1012.87-7.42l-10.45-18.1a7.43 7.43 0 00-12.86 7.43l10.44 18.1M15.1 56.44l18.09 10.45a7.47 7.47 0 0010.14-2.72 7.43 7.43 0 00-2.71-10.15l-18.1-10.45a7.43 7.43 0 00-7.43 12.87M7.42 100.04H28.3a7.43 7.43 0 000-14.86H7.41a7.42 7.42 0 100 14.86M43.33 120.97a7.42 7.42 0 00-10.14-2.72l-18.1 10.44a7.43 7.43 0 007.43 12.86l18.1-10.44a7.43 7.43 0 002.71-10.14M120.9 43.37a7.42 7.42 0 0010.15-2.72l10.43-18.1a7.43 7.43 0 00-12.86-7.42l-10.45 18.1a7.43 7.43 0 002.72 10.14"/></g></svg>

+ 1 - 0
apps/weather_status/img/sun-small-cloud.svg

@@ -0,0 +1 @@
+<svg width="280.5" height="280.5" xmlns="http://www.w3.org/2000/svg"><path d="M257 219.74a4.3 4.3 0 01-1.53-2.24 32.38 32.38 0 00-30.96-23.26c-2.63 0-5.25.32-7.78.95a4.33 4.33 0 01-4.61-1.75 35.63 35.63 0 00-53.38-6.13 35.52 35.52 0 00-11.43 22.87 4.34 4.34 0 01-2.45 3.5c-.42.2-.86.42-1.28.65-.36.2-.75.35-1.15.44a27.73 27.73 0 00-15.37 9.5 27.03 27.03 0 00-6.13 17.25 27.52 27.52 0 0027.56 27.4h91.77c15.2 0 27.57-12.29 27.57-27.4a27.2 27.2 0 00-10.82-21.78" fill="#61c9e7" paint-order="stroke fill markers"/><g fill="#dec60f"><path d="M140.2 70.73A69.86 69.86 0 0070.4 140.5c0 33.33 23.5 61.27 54.8 68.14a40.6 40.6 0 0110.03-5.1 48.61 48.61 0 0114.7-25.58h.01a48.59 48.59 0 0153.5-8.05 69.35 69.35 0 006.52-29.41 69.86 69.86 0 00-69.78-69.78zm-.9 131.44l-.1.04-.04.03.13-.07zM132.8 38.9a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 00-14.85 0V38.9M89.54 59.91a7.43 7.43 0 006.43-11.14L80.24 21.53a7.43 7.43 0 00-12.86 7.43L83.1 56.2a7.42 7.42 0 006.43 3.71M18.76 70.14a7.43 7.43 0 002.72 10.15L48.72 96a7.42 7.42 0 107.43-12.86L28.9 67.42a7.43 7.43 0 00-10.14 2.72M46.28 140.27c0-4.1-3.33-7.42-7.43-7.42H7.4a7.43 7.43 0 000 14.85h31.46c4.1 0 7.43-3.33 7.43-7.43M273.05 132.85h-31.46a7.43 7.43 0 000 14.85h31.46a7.43 7.43 0 000-14.85M48.73 184.51L21.5 200.24a7.43 7.43 0 107.42 12.86l27.25-15.73a7.43 7.43 0 00-7.43-12.86M251.54 67.42L224.3 83.15A7.43 7.43 0 00231.72 96l27.24-15.73a7.43 7.43 0 00-7.42-12.86M83.1 224.34l-15.73 27.24a7.43 7.43 0 0012.87 7.43l15.73-27.25a7.43 7.43 0 00-12.87-7.42M187.2 58.91a7.4 7.4 0 0010.14-2.71l15.73-27.25a7.43 7.43 0 10-12.86-7.42l-15.73 27.24a7.43 7.43 0 002.71 10.14"/></g></svg>

+ 1 - 0
apps/weather_status/img/sun.svg

@@ -0,0 +1 @@
+<svg width="280.5" height="280.5" version="1.1" xmlns="http://www.w3.org/2000/svg"><g fill="#dec60f"><path d="M140.22 210.04c38.48 0 69.78-31.3 69.78-69.78s-31.3-69.78-69.78-69.78c-38.47 0-69.78 31.3-69.78 69.78s31.3 69.78 69.78 69.78M132.8 38.9a7.43 7.43 0 0014.85 0V7.44a7.43 7.43 0 00-14.85 0V38.9M132.8 241.63v31.46a7.43 7.43 0 0014.85 0v-31.46a7.43 7.43 0 00-14.85 0M89.54 59.91a7.43 7.43 0 006.43-11.14L80.24 21.53a7.43 7.43 0 00-12.86 7.43L83.1 56.2a7.42 7.42 0 006.43 3.71M187.2 221.62a7.43 7.43 0 00-2.72 10.14L200.2 259a7.42 7.42 0 1012.86-7.42l-15.73-27.25a7.43 7.43 0 00-10.15-2.71M18.76 70.14a7.43 7.43 0 002.72 10.15L48.72 96a7.42 7.42 0 107.43-12.86L28.9 67.42a7.43 7.43 0 00-10.14 2.72M258.97 200.24l-27.25-15.73a7.43 7.43 0 00-7.42 12.87l27.24 15.73a7.4 7.4 0 0010.14-2.72 7.43 7.43 0 00-2.71-10.15M46.28 140.27c0-4.1-3.33-7.42-7.43-7.42H7.4a7.43 7.43 0 000 14.85h31.46c4.1 0 7.43-3.33 7.43-7.43M273.05 132.85h-31.46a7.43 7.43 0 000 14.85h31.46a7.43 7.43 0 000-14.85M48.73 184.51L21.5 200.24a7.43 7.43 0 107.42 12.86l27.25-15.73a7.43 7.43 0 00-7.43-12.86M251.54 67.42L224.3 83.15A7.43 7.43 0 00231.72 96l27.24-15.73a7.43 7.43 0 00-7.42-12.86M83.1 224.34l-15.73 27.24a7.43 7.43 0 0012.87 7.43l15.73-27.25a7.43 7.43 0 00-12.87-7.42M187.2 58.91a7.4 7.4 0 0010.14-2.71l15.73-27.25a7.43 7.43 0 10-12.86-7.42l-15.73 27.24a7.43 7.43 0 002.71 10.14" fill="#dec60f"/></g></svg>

+ 1 - 0
apps/weather_status/img/thunder.svg

@@ -0,0 +1 @@
+<svg width="251.88" height="229.04" xmlns="http://www.w3.org/2000/svg"><path d="M75.45 140.62l33.04-97.27h41.74l-24.34 65.69h50.42l-30.73 46.87h58.97c26.07 0 47.27-21.08 47.27-47a46.61 46.61 0 00-18.56-37.34 7.46 7.46 0 01-2.64-3.84 55.5 55.5 0 00-53.08-39.86c-4.5 0-9 .54-13.35 1.62-3 .75-6.14-.45-7.9-3A61.08 61.08 0 0064.79 16a60.88 60.88 0 00-19.6 39.2 7.43 7.43 0 01-4.2 6c-.73.34-1.47.72-2.2 1.12-.62.34-1.29.6-1.98.76a47.44 47.44 0 00-26.35 16.29A46.33 46.33 0 00-.05 108.92c0 25.9 21.2 47 47.26 47h68.74l3.84-15.3H75.45" fill="#61c9e7"/><path d="M125.83 108.97l24.35-65.7h-41.75L75.4 140.56h44.35l-3.84 15.3-3.72 14.84-14.61 58.29 38.2-58.29 9.76-14.85 30.73-46.87h-50.43" fill="#dec60f"/></svg>

+ 1 - 0
apps/weather_status/img/umbrella.svg

@@ -0,0 +1 @@
+<svg width="245.8" height="298.37" xmlns="http://www.w3.org/2000/svg"><g fill="#4492a8"><path d="M-.06 132.86c8.9-5.53 21.39-11.12 35.4-11.12 23.22 0 34.73 7.95 44.28 16.87 9.1-8.16 21.08-16.87 43.97-16.87 22.8 0 36.28 9.08 44.27 16.46 9.12-8.71 21.61-16.46 43.96-16.46 13.6 0 25.4 5.26 33.91 10.63-8.93-53.35-60.62-94.4-122.85-94.4-62.43 0-114.24 41.3-122.94 94.9"/><path d="M115.5 137.06v113.96c0 12.32 3.83 23.97 10.79 32.83 7.36 9.36 17.32 14.52 28.04 14.52 10.7 0 20.66-5.16 28.03-14.52 6.96-8.86 10.8-20.51 10.8-32.83a7.42 7.42 0 10-14.86 0c0 17.92-10.76 32.5-23.97 32.5-13.23 0-23.98-14.58-23.98-32.5V136.93a66.87 66.87 0 00-6.72-.33c-2.97 0-5.65.16-8.13.46M130.3 23.3V7.44a7.43 7.43 0 00-14.85 0v15.88a155.99 155.99 0 0114.85 0"/></g></svg>

File diff suppressed because it is too large
+ 0 - 0
apps/weather_status/js/weather-status.js


File diff suppressed because it is too large
+ 0 - 0
apps/weather_status/js/weather-status.js.map


+ 72 - 0
apps/weather_status/lib/AppInfo/Application.php

@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\WeatherStatus\AppInfo;
+
+use OCA\WeatherStatus\Capabilities;
+use OCP\AppFramework\App;
+use OCP\AppFramework\Bootstrap\IBootContext;
+use OCP\AppFramework\Bootstrap\IBootstrap;
+use OCP\AppFramework\Bootstrap\IRegistrationContext;
+use OCP\Dashboard\RegisterWidgetEvent;
+use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\Event;
+use OCP\Util;
+
+/**
+ * Class Application
+ *
+ * @package OCA\WeatherStatus\AppInfo
+ */
+class Application extends App implements IBootstrap {
+
+	/** @var string */
+	public const APP_ID = 'weather_status';
+
+	/**
+	 * Application constructor.
+	 *
+	 * @param array $urlParams
+	 */
+	public function __construct(array $urlParams = []) {
+		parent::__construct(self::APP_ID, $urlParams);
+
+		$dispatcher = $this->getContainer()->query(IEventDispatcher::class);
+		$dispatcher->addListener(RegisterWidgetEvent::class, function (Event $e) {
+			Util::addScript(self::APP_ID, 'weather-status');
+		});
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function register(IRegistrationContext $context): void {
+		// Register OCS Capabilities
+		$context->registerCapability(Capabilities::class);
+	}
+
+	public function boot(IBootContext $context): void {
+	}
+}

+ 55 - 0
apps/weather_status/lib/Capabilities.php

@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\WeatherStatus;
+
+use OCP\Capabilities\ICapability;
+
+use OCA\WeatherStatus\AppInfo\Application;
+
+/**
+ * Class Capabilities
+ *
+ * @package OCA\UserStatus
+ */
+class Capabilities implements ICapability {
+
+	/**
+	 * Capabilities constructor.
+	 *
+	 */
+	public function __construct() {
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function getCapabilities() {
+		return [
+			Application::APP_ID => [
+				'enabled' => true,
+			],
+		];
+	}
+}

+ 124 - 0
apps/weather_status/lib/Controller/WeatherStatusController.php

@@ -0,0 +1,124 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OCA\WeatherStatus\Controller;
+
+use OCA\WeatherStatus\Service\WeatherStatusService;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\ILogger;
+use OCP\IRequest;
+
+class WeatherStatusController extends OCSController {
+
+	/** @var string */
+	private $userId;
+
+	/** @var ILogger */
+	private $logger;
+
+	/** @var WeatherStatusService */
+	private $service;
+
+	public function __construct(string $appName,
+								IRequest $request,
+								ILogger $logger,
+								WeatherStatusService $service,
+								string $userId) {
+		parent::__construct($appName, $request);
+		$this->userId = $userId;
+		$this->logger = $logger;
+		$this->service = $service;
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * Try to use the address set in user personal settings as weather location
+	 *
+	 * @return DataResponse with success state and address information
+	 */
+	public function usePersonalAddress(): DataResponse {
+		return new DataResponse($this->service->usePersonalAddress());
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * Change the weather status mode. There are currently 2 modes:
+	 * - ask the browser
+	 * - use the user defined address
+	 *
+	 * @param int $mode New mode
+	 * @return DataResponse success state
+	 */
+	public function setMode(int $mode): DataResponse {
+		return new DataResponse($this->service->setMode($mode));
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * Set address and resolve it to get coordinates
+	 * or directly set coordinates and get address with reverse geocoding
+	 *
+	 * @param string|null $address Any approximative or exact address
+	 * @param float|null $lat Latitude in decimal degree format
+	 * @param float|null $lon Longitude in decimal degree format
+	 * @return DataResponse with success state and address information
+	 */
+	public function setLocation(?string $address, ?float $lat, ?float $lon): DataResponse {
+		$currentWeather = $this->service->setLocation($address, $lat, $lon);
+		return new DataResponse($currentWeather);
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * Get stored user location
+	 *
+	 * @return DataResponse which contains coordinates, formatted address and current weather status mode
+	 */
+	public function getLocation(): DataResponse {
+		$location = $this->service->getLocation();
+		return new DataResponse($location);
+	}
+
+	/**
+	 * @NoAdminRequired
+	 *
+	 * Get forecast for current location
+	 *
+	 * @return DataResponse which contains success state and filtered forecast data
+	 */
+	public function getForecast(): DataResponse {
+		$forecast = $this->service->getForecast();
+		if (isset($forecast['success']) && $forecast['success'] === false) {
+			return new DataResponse($forecast, Http::STATUS_NOT_FOUND);
+		} else {
+			return new DataResponse($forecast);
+		}
+	}
+}

+ 429 - 0
apps/weather_status/lib/Service/WeatherStatusService.php

@@ -0,0 +1,429 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\WeatherStatus\Service;
+
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\App\IAppManager;
+use OCP\Accounts\IAccountManager;
+use OCP\Accounts\PropertyDoesNotExistException;
+use OCP\IUserManager;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IClient;
+use OCP\ICacheFactory;
+use OCP\ICache;
+use OCP\ILogger;
+
+use OCA\WeatherStatus\AppInfo\Application;
+
+/**
+ * Class WeatherStatusService
+ *
+ * @package OCA\WeatherStatus\Service
+ */
+class WeatherStatusService {
+	public const MODE_BROWSER_LOCATION = 1;
+	public const MODE_MANUAL_LOCATION = 2;
+
+	/** @var IClientService */
+	private $clientService;
+
+	/** @var IClient */
+	private $client;
+
+	/** @var IConfig */
+	private $config;
+
+	/** @var IL10N */
+	private $l10n;
+
+	/** @var ILogger */
+	private $logger;
+
+	/** @var IAccountManager */
+	private $accountManager;
+
+	/** @var IUserManager */
+	private $userManager;
+
+	/** @var IAppManager */
+	private $appManager;
+
+	/** @var ICacheFactory */
+	private $cacheFactory;
+
+	/** @var ICache */
+	private $cache;
+
+	/** @var string */
+	private $userId;
+
+	/** @var string */
+	private $version;
+
+	/**
+	 * WeatherStatusService constructor
+	 *
+	 * @param IClientService $clientService
+	 * @param IConfig $config
+	 * @param IL10N $l10n
+	 * @param ILogger $logger
+	 * @param IAccountManager $accountManager
+	 * @param IUserManager $userManager
+	 * @param IAppManager $appManager
+	 * @param ICacheFactory $cacheFactory
+	 * @param string $userId
+	 */
+	public function __construct(IClientService $clientService,
+								IConfig $config,
+								IL10N $l10n,
+								ILogger $logger,
+								IAccountManager $accountManager,
+								IUserManager $userManager,
+								IAppManager $appManager,
+								ICacheFactory $cacheFactory,
+								string $userId) {
+		$this->config = $config;
+		$this->userId = $userId;
+		$this->l10n = $l10n;
+		$this->logger = $logger;
+		$this->accountManager = $accountManager;
+		$this->userManager = $userManager;
+		$this->appManager = $appManager;
+		$this->version = $appManager->getAppVersion(Application::APP_ID);
+		$this->clientService = $clientService;
+		$this->client = $clientService->newClient();
+		if ($cacheFactory->isAvailable()) {
+			$this->cache = $cacheFactory->createDistributed();
+		}
+	}
+
+	/**
+	 * Change the weather status mode. There are currently 2 modes:
+	 * - ask the browser
+	 * - use the user defined address
+	 * @param int $mode New mode
+	 * @return array success state
+	 */
+	public function setMode(int $mode): array {
+		$this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval($mode));
+		return ['success' => true];
+	}
+
+	/**
+	 * Try to use the address set in user personal settings as weather location
+	 *
+	 * @return array with success state and address information
+	 */
+	public function usePersonalAddress(): array {
+		$account = $this->accountManager->getAccount($this->userManager->get($this->userId));
+		try {
+			$address = $account->getProperty('address')->getValue();
+		} catch (PropertyDoesNotExistException $e) {
+			return ['success' => false];
+		}
+		if ($address === '') {
+			return ['success' => false];
+		}
+		return $this->setAddress($address);
+	}
+
+	/**
+	 * Set address and resolve it to get coordinates
+	 * or directly set coordinates and get address with reverse geocoding
+	 *
+	 * @param string|null $address Any approximative or exact address
+	 * @param float|null $lat Latitude in decimal degree format
+	 * @param float|null $lon Longitude in decimal degree format
+	 * @return array with success state and address information
+	 */
+	public function setLocation(?string $address, ?float $lat, ?float $lon): array {
+		if (!is_null($lat) && !is_null($lon)) {
+			// store coordinates
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($lat));
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($lon));
+			// resolve and store formatted address
+			$address = $this->resolveLocation($lat, $lon);
+			$address = $address ? $address : $this->l10n->t('Unknown address');
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'address', $address);
+			// get and store altitude
+			$altitude = $this->getAltitude($lat, $lon);
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
+			return [
+				'address' => $address,
+				'success' => true,
+			];
+		} elseif ($address) {
+			return $this->setAddress($address);
+		} else {
+			return ['success' => false];
+		}
+	}
+
+	/**
+	 * Provide address information from coordinates
+	 *
+	 * @param float $lat Latitude in decimal degree format
+	 * @param float $lon Longitude in decimal degree format
+	 */
+	private function resolveLocation(float $lat, float $lon): ?string {
+		$params = [
+			'lat' => number_format($lat, 2),
+			'lon' => number_format($lon, 2),
+			'addressdetails' => 1,
+			'format' => 'json',
+		];
+		$url = 'https://nominatim.openstreetmap.org/reverse';
+		$result = $this->requestJSON($url, $params);
+		return $this->formatOsmAddress($result);
+	}
+
+	/**
+	 * Get altitude from coordinates
+	 *
+	 * @param float $lat Latitude in decimal degree format
+	 * @param float $lon Longitude in decimal degree format
+	 * @return float altitude in meter
+	 */
+	private function getAltitude(float $lat, float $lon): float {
+		$params = [
+			'locations' => $lat . ',' . $lon,
+		];
+		$url = 'https://api.opentopodata.org/v1/srtm30m';
+		$result = $this->requestJSON($url, $params);
+		$altitude = 0;
+		if (isset($result['results']) && is_array($result['results']) && count($result['results']) > 0
+			&& is_array($result['results'][0]) && isset($result['results'][0]['elevation'])) {
+			$altitude = floatval($result['results'][0]['elevation']);
+		}
+		return $altitude;
+	}
+
+	/**
+	 * @return string Formatted address from JSON nominatim result
+	 */
+	private function formatOsmAddress(array $json): ?string {
+		if (isset($json['address']) && isset($json['display_name'])) {
+			$jsonAddr = $json['address'];
+			$cityAddress = '';
+			// priority : city, town, village, municipality
+			if (isset($jsonAddr['city'])) {
+				$cityAddress .= $jsonAddr['city'];
+			} elseif (isset($jsonAddr['town'])) {
+				$cityAddress .= $jsonAddr['town'];
+			} elseif (isset($jsonAddr['village'])) {
+				$cityAddress .= $jsonAddr['village'];
+			} elseif (isset($jsonAddr['municipality'])) {
+				$cityAddress .= $jsonAddr['municipality'];
+			} else {
+				return $json['display_name'];
+			}
+			// post code
+			if (isset($jsonAddr['postcode'])) {
+				$cityAddress .= ', ' . $jsonAddr['postcode'];
+			}
+			// country
+			if (isset($jsonAddr['country'])) {
+				$cityAddress .= ', ' . $jsonAddr['country'];
+				return $cityAddress;
+			} else {
+				return $json['display_name'];
+			}
+		} elseif (isset($json['display_name'])) {
+			return $json['display_name'];
+		}
+		return null;
+	}
+
+	/**
+	 * Set address and resolve it to get coordinates
+	 *
+	 * @param string $address Any approximative or exact address
+	 * @return array with success state and address information (coordinates and formatted address)
+	 */
+	public function setAddress(string $address): array {
+		$addressInfo = $this->searchForAddress($address);
+		if (isset($addressInfo['display_name']) && isset($addressInfo['lat']) && isset($addressInfo['lon'])) {
+			$formattedAddress = $this->formatOsmAddress($addressInfo);
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'address', $formattedAddress);
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($addressInfo['lat']));
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($addressInfo['lon']));
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval(self::MODE_MANUAL_LOCATION));
+			// get and store altitude
+			$altitude = $this->getAltitude(floatval($addressInfo['lat']), floatval($addressInfo['lon']));
+			$this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
+			return [
+				'lat' => $addressInfo['lat'],
+				'lon' => $addressInfo['lon'],
+				'address' => $formattedAddress,
+				'success' => true,
+			];
+		} else {
+			return ['success' => false];
+		}
+	}
+
+	/**
+	 * Ask nominatim information about an unformatted address
+	 *
+	 * @param string Unformatted address
+	 * @return array Full Nominatim result for the given address
+	 */
+	private function searchForAddress(string $address): array {
+		$params = [
+			'format' => 'json',
+			'addressdetails' => '1',
+			'extratags' => '1',
+			'namedetails' => '1',
+			'limit' => '1',
+		];
+		$url = 'https://nominatim.openstreetmap.org/search/' . $address;
+		$results = $this->requestJSON($url, $params);
+		if (count($results) > 0) {
+			return $results[0];
+		}
+		return ['error' => $this->l10n->t('No result.')];
+	}
+
+	/**
+	 * Get stored user location
+	 *
+	 * @return array which contains coordinates, formatted address and current weather status mode
+	 */
+	public function getLocation(): array {
+		$lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
+		$lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
+		$address = $this->config->getUserValue($this->userId, Application::APP_ID, 'address', '');
+		$mode = $this->config->getUserValue($this->userId, Application::APP_ID, 'mode', self::MODE_MANUAL_LOCATION);
+		return [
+			'lat' => $lat,
+			'lon' => $lon,
+			'address' => $address,
+			'mode' => intval($mode),
+		];
+	}
+
+	/**
+	 * Get forecast for current location
+	 *
+	 * @return array which contains success state and filtered forecast data
+	 */
+	public function getForecast(): array {
+		$lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
+		$lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
+		$alt = $this->config->getUserValue($this->userId, Application::APP_ID, 'altitude', '');
+		if (!is_numeric($alt)) {
+			$alt = 0;
+		}
+		if (is_numeric($lat) && is_numeric($lon)) {
+			return $this->forecastRequest(floatval($lat), floatval($lon), floatval($alt));
+		} else {
+			return ['success' => false];
+		}
+	}
+
+	/**
+	 * Actually make the request to the forecast service
+	 *
+	 * @param float $lat Latitude of requested forecast, in decimal degree format
+	 * @param float $lon Longitude of requested forecast, in decimal degree format
+	 * @param float $altitude Altitude of requested forecast, in meter
+	 * @param int $nbValues Number of forecast values (hours)
+	 * @return array Filtered forecast data
+	 */
+	private function forecastRequest(float $lat, float $lon, float $altitude, int $nbValues = 10): array {
+		$params = [
+			'lat' => number_format($lat, 2),
+			'lon' => number_format($lon, 2),
+			'altitude' => $altitude,
+		];
+		$url = 'https://api.met.no/weatherapi/locationforecast/2.0/compact';
+		$weather = $this->requestJSON($url, $params);
+		if (isset($weather['properties']) && isset($weather['properties']['timeseries']) && is_array($weather['properties']['timeseries'])) {
+			return array_slice($weather['properties']['timeseries'], 0, $nbValues);
+		}
+		return ['error' => $this->l10n->t('Malformed JSON data.')];
+	}
+
+	/**
+	 * Make a HTTP GET request and parse JSON result.
+	 * Request results are cached until the 'Expires' response header says so
+	 *
+	 * @param string $url Base URL to query
+	 * @param array $params GET parameters
+	 * @return array which contains the error message or the parsed JSON result
+	 */
+	private function requestJSON(string $url, array $params = []): array {
+		if (isset($this->cache)) {
+			$cacheKey = $url . '|' . implode(',', $params) . '|' . implode(',', array_keys($params));
+			if ($this->cache->hasKey($cacheKey)) {
+				return $this->cache->get($cacheKey);
+			}
+		}
+		try {
+			$options = [
+				'headers' => [
+					'User-Agent' => 'NextcloudWeatherStatus/' . $this->version . ' nextcloud.com'
+				],
+			];
+
+			$reqUrl = $url;
+			if (count($params) > 0) {
+				$paramsContent = http_build_query($params);
+				$reqUrl = $url . '?' . $paramsContent;
+			}
+
+			$response = $this->client->get($reqUrl, $options);
+			$body = $response->getBody();
+			$headers = $response->getHeaders();
+			$respCode = $response->getStatusCode();
+
+			if ($respCode >= 400) {
+				return ['error' => $this->l10n->t('Error')];
+			} else {
+				$json = json_decode($body, true);
+				if (isset($this->cache)) {
+					// default cache duration is one hour
+					$cacheDuration = 60 * 60;
+					if (isset($headers['Expires']) && count($headers['Expires']) > 0) {
+						// if the Expires response header is set, use it to define cache duration
+						$expireTs = (new \Datetime($headers['Expires'][0]))->getTimestamp();
+						$nowTs = (new \Datetime())->getTimestamp();
+						$duration = $expireTs - $nowTs;
+						if ($duration > $cacheDuration) {
+							$cacheDuration = $duration;
+						}
+					}
+					$this->cache->set($cacheKey, $json, $cacheDuration);
+				}
+				return $json;
+			}
+		} catch (\Exception $e) {
+			$this->logger->warning($url . 'API error : ' . $e, ['app' => Application::APP_ID]);
+			return ['error' => $e->getMessage()];
+		}
+	}
+}

+ 503 - 0
apps/weather_status/src/App.vue

@@ -0,0 +1,503 @@
+<!--
+  - @copyright Copyright (c) 2020 Julien Veyssier <eneiluj@posteo.net>
+  - @author Julien Veyssier <eneiluj@posteo.net>
+  -
+  - @license GNU AGPL version 3 or any later version
+  -
+  - This program is free software: you can redistribute it and/or modify
+  - it under the terms of the GNU Affero General Public License as
+  - published by the Free Software Foundation, either version 3 of the
+  - License, or (at your option) any later version.
+  -
+  - This program is distributed in the hope that it will be useful,
+  - but WITHOUT ANY WARRANTY; without even the implied warranty of
+  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  - GNU Affero General Public License for more details.
+  -
+  - You should have received a copy of the GNU Affero General Public License
+  - along with this program. If not, see <http://www.gnu.org/licenses/>.
+  -
+  -->
+
+<template>
+	<li :class="{ inline }">
+		<div id="weather-status-menu-item">
+			<Actions
+				class="weather-status-menu-item__subheader"
+				:default-icon="weatherIcon"
+				:menu-title="visibleMessage">
+				<ActionLink v-if="address && !errorMessage"
+					icon="icon-address"
+					target="_blank"
+					:href="weatherLinkTarget"
+					:close-after-click="true">
+					{{ locationText }}
+				</ActionLink>
+				<ActionSeparator v-if="address && !errorMessage" />
+				<ActionButton
+					icon="icon-crosshair"
+					:close-after-click="true"
+					@click="onBrowserLocationClick">
+					{{ t('weather_status', 'Detect location') }}
+				</ActionButton>
+				<ActionInput
+					ref="addressInput"
+					:disabled="false"
+					icon="icon-rename"
+					type="text"
+					value=""
+					@submit="onAddressSubmit">
+					{{ t('weather_status', 'Set custom address') }}
+				</ActionInput>
+			</Actions>
+		</div>
+	</li>
+</template>
+
+<script>
+import { showError } from '@nextcloud/dialogs'
+import moment from '@nextcloud/moment'
+import { getLocale } from '@nextcloud/l10n'
+import Actions from '@nextcloud/vue/dist/Components/Actions'
+import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import ActionInput from '@nextcloud/vue/dist/Components/ActionInput'
+import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
+import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
+import * as network from './services/weatherStatusService'
+
+const MODE_BROWSER_LOCATION = 1
+const MODE_MANUAL_LOCATION = 2
+const weatherOptions = {
+	clearsky_day: {
+		icon: 'icon-clearsky-day',
+		text: t('weather_status', 'Clear sky'),
+	},
+	clearsky_night: {
+		icon: 'icon-clearsky-night',
+		text: t('weather_status', 'Clear sky'),
+	},
+	cloudy: {
+		icon: 'icon-cloudy',
+		text: t('weather_status', 'Cloudy'),
+	},
+	fair_day: {
+		icon: 'icon-fair-day',
+		text: t('weather_status', 'Fair day'),
+	},
+	fair_night: {
+		icon: 'icon-fair-night',
+		text: t('weather_status', 'Fair night'),
+	},
+	partlycloudy_day: {
+		icon: 'icon-partlycloudy-day',
+		text: t('weather_status', 'Partly cloudy'),
+	},
+	partlycloudy_night: {
+		icon: 'icon-partlycloudy-night',
+		text: t('weather_status', 'Partly cloudy'),
+	},
+	fog: {
+		icon: 'icon-fog',
+		text: t('weather_status', 'Foggy'),
+	},
+	lightrain: {
+		icon: 'icon-lightrain',
+		text: t('weather_status', 'Light rain'),
+	},
+	rain: {
+		icon: 'icon-rain',
+		text: t('weather_status', 'Rain'),
+	},
+	heavyrain: {
+		icon: 'icon-heavyrain',
+		text: t('weather_status', 'Heavy rain'),
+	},
+	rainshowers_day: {
+		icon: 'icon-rainshowers-day',
+		text: t('weather_status', 'Rain showers'),
+	},
+	rainshowers_night: {
+		icon: 'icon-rainshowers-night',
+		text: t('weather_status', 'Rain showers'),
+	},
+	lightrainshowers_day: {
+		icon: 'icon-light-rainshowers-day',
+		text: t('weather_status', 'Light rain showers'),
+	},
+	lightrainshowers_night: {
+		icon: 'icon-light-rainshowers-night',
+		text: t('weather_status', 'Light rain showers'),
+	},
+	heavyrainshowers_day: {
+		icon: 'icon-heavy-rainshowers-day',
+		text: t('weather_status', 'Heavy rain showers'),
+	},
+	heavyrainshowers_night: {
+		icon: 'icon-heavy-rainshowers-night',
+		text: t('weather_status', 'Heavy rain showers'),
+	},
+}
+
+export default {
+	name: 'App',
+	components: {
+		Actions, ActionButton, ActionInput, ActionLink, ActionSeparator,
+	},
+	props: {
+		inline: {
+			type: Boolean,
+			default: false,
+		},
+	},
+	data() {
+		return {
+			locale: getLocale(),
+			loading: true,
+			errorMessage: '',
+			mode: MODE_BROWSER_LOCATION,
+			address: null,
+			lat: null,
+			lon: null,
+			forecasts: [],
+			loop: null,
+		}
+	},
+	computed: {
+		useFahrenheitLocale() {
+			return ['en_US', 'en_MH', 'en_FM', 'en_PW', 'en_KY', 'en_LR'].includes(this.locale)
+		},
+		strUnit() {
+			return this.useFahrenheitLocale ? '°F' : '°C'
+		},
+		locationText() {
+			return t('weather_status', 'More weather for {adr}', { adr: this.address })
+		},
+		sixHoursTempForecast() {
+			return this.forecasts.length > 5 ? this.forecasts[5].data.instant.details.air_temperature : ''
+		},
+		sixHoursWeatherForecast() {
+			return this.forecasts.length > 5 ? this.forecasts[5].data.next_1_hours.summary.symbol_code : ''
+		},
+		sixHoursFormattedTime() {
+			if (this.forecasts.length > 5) {
+				const date = moment(this.forecasts[5].time)
+				return t('weather_status', 'at {time}', { time: date.format('LT') })
+			}
+			return ''
+		},
+		weatherIcon() {
+			if (this.loading) {
+				return 'icon-loading-small'
+			} else {
+				return this.sixHoursWeatherForecast && this.sixHoursWeatherForecast in weatherOptions
+					? weatherOptions[this.sixHoursWeatherForecast].icon
+					: 'icon-fair-day'
+			}
+		},
+		weatherText() {
+			return this.sixHoursWeatherForecast && this.sixHoursWeatherForecast in weatherOptions
+				? weatherOptions[this.sixHoursWeatherForecast].text + ' ' + this.sixHoursFormattedTime
+				: '???'
+		},
+		/**
+		 * The message displayed in the top right corner
+		 *
+		 * @returns {String}
+		 */
+		visibleMessage() {
+			if (this.loading) {
+				return t('weather_status', 'Loading weather')
+			} else if (this.errorMessage) {
+				return this.errorMessage
+			} else {
+				return this.sixHoursWeatherForecast
+					? t('weather_status', '{temperature} {unit} {weatherDescription}', {
+						temperature: this.getLocalizedTemperature(this.sixHoursTempForecast),
+						unit: this.strUnit,
+						weatherDescription: this.weatherText,
+					})
+					: t('weather_status', 'Set location for weather')
+			}
+		},
+		weatherLinkTarget() {
+			return 'https://www.windy.com/-Rain-thunder-rain?rain,' + this.lat + ',' + this.lon + ',11'
+		},
+	},
+	mounted() {
+		this.initWeatherStatus()
+	},
+	methods: {
+		async initWeatherStatus() {
+			try {
+				const loc = await network.getLocation()
+				this.lat = loc.lat
+				this.lon = loc.lon
+				this.address = loc.address
+				this.mode = loc.mode
+
+				if (this.mode === MODE_BROWSER_LOCATION) {
+					this.askBrowserLocation()
+				} else if (this.mode === MODE_MANUAL_LOCATION) {
+					this.startLoop()
+				}
+			} catch (err) {
+				showError(t('weather_status', 'There was an error getting the weather status information.'))
+				console.debug(err)
+			}
+		},
+		startLoop() {
+			clearInterval(this.loop)
+			if (this.lat && this.lon) {
+				this.loop = setInterval(() => this.getForecast(), 60 * 1000 * 60)
+				this.getForecast()
+			} else {
+				this.loading = false
+			}
+		},
+		askBrowserLocation() {
+			this.loading = true
+			this.errorMessage = ''
+			if (navigator.geolocation && window.isSecureContext) {
+				navigator.geolocation.getCurrentPosition((position) => {
+					console.debug('browser location success')
+					this.lat = position.coords.latitude
+					this.lon = position.coords.longitude
+					this.saveMode(MODE_BROWSER_LOCATION)
+					this.mode = MODE_BROWSER_LOCATION
+					this.saveLocation(this.lat, this.lon)
+				},
+				(error) => {
+					console.debug('location permission refused')
+					console.debug(error)
+					this.saveMode(MODE_MANUAL_LOCATION)
+					this.mode = MODE_MANUAL_LOCATION
+					// fallback on what we have if possible
+					if (this.lat && this.lon) {
+						this.startLoop()
+					} else {
+						this.usePersonalAddress()
+					}
+				})
+			} else {
+				console.debug('no secure context!')
+				this.saveMode(MODE_MANUAL_LOCATION)
+				this.mode = MODE_MANUAL_LOCATION
+				this.startLoop()
+			}
+		},
+		async getForecast() {
+			try {
+				this.forecasts = await network.fetchForecast()
+			} catch (err) {
+				this.errorMessage = t('weather_status', 'No weather information found')
+				console.debug(err)
+			}
+			this.loading = false
+		},
+		async setAddress(address) {
+			this.loading = true
+			this.errorMessage = ''
+			try {
+				const loc = await network.setAddress(address)
+				if (loc.success) {
+					this.lat = loc.lat
+					this.lon = loc.lon
+					this.address = loc.address
+					this.mode = MODE_MANUAL_LOCATION
+					this.startLoop()
+				} else {
+					this.errorMessage = t('weather_status', 'Location not found')
+					this.loading = false
+				}
+			} catch (err) {
+				showError(t('weather_status', 'There was an error setting the location address.'))
+				console.debug(err)
+				this.loading = false
+			}
+		},
+		async saveLocation(lat, lon) {
+			try {
+				const loc = await network.setLocation(lat, lon)
+				this.address = loc.address
+				this.startLoop()
+			} catch (err) {
+				showError(t('weather_status', 'There was an error setting the location.'))
+				console.debug(err)
+			}
+		},
+		async saveMode(mode) {
+			try {
+				await network.setMode(mode)
+			} catch (err) {
+				showError(t('weather_status', 'There was an error saving the mode.'))
+				console.debug(err)
+			}
+		},
+		onBrowserLocationClick() {
+			this.askBrowserLocation()
+		},
+		async usePersonalAddress() {
+			this.loading = true
+			try {
+				const loc = await network.usePersonalAddress()
+				this.lat = loc.lat
+				this.lon = loc.lon
+				this.address = loc.address
+				this.mode = MODE_MANUAL_LOCATION
+				this.startLoop()
+			} catch (err) {
+				showError(t('weather_status', 'There was an error using personal address.'))
+				console.debug(err)
+				this.loading = false
+			}
+		},
+		onAddressSubmit() {
+			const newAddress = this.$refs.addressInput.$el.querySelector('input[type="text"]').value
+			this.setAddress(newAddress)
+		},
+		getLocalizedTemperature(celcius) {
+			return this.useFahrenheitLocale
+				? ((celcius * (9 / 5)) + 32).toFixed(1)
+				: celcius
+		},
+	},
+}
+</script>
+
+<style lang="scss">
+.icon-clearsky-day {
+	background-image: url('./../img/sun.svg');
+}
+.icon-clearsky-night {
+	background-image: url('./../img/moon.svg');
+}
+.icon-cloudy {
+	background-image: url('./../img/cloud-cloud.svg');
+}
+.icon-fair-day {
+	background-image: url('./../img/sun-small-cloud.svg');
+}
+.icon-fair-night {
+	background-image: url('./../img/moon-small-cloud.svg');
+}
+.icon-partlycloudy-day {
+	background-image: url('./../img/sun-cloud.svg');
+}
+.icon-partlycloudy-night {
+	background-image: url('./../img/moon-cloud.svg');
+}
+.icon-fog {
+	background-image: url('./../img/fog.svg');
+}
+.icon-lightrain {
+	background-image: url('./../img/light-rain.svg');
+}
+.icon-rain {
+	background-image: url('./../img/rain.svg');
+}
+.icon-heavyrain {
+	background-image: url('./../img/heavy-rain.svg');
+}
+.icon-light-rainshowers-day {
+	background-image: url('./../img/sun-cloud-light-rain.svg');
+}
+.icon-light-rainshowers-night {
+	background-image: url('./../img/moon-cloud-light-rain.svg');
+}
+.icon-rainshowers-day {
+	background-image: url('./../img/sun-cloud-rain.svg');
+}
+.icon-rainshowers-night {
+	background-image: url('./../img/moon-cloud-rain.svg');
+}
+.icon-heavy-rainshowers-day {
+	background-image: url('./../img/sun-cloud-heavy-rain.svg');
+}
+.icon-heavy-rainshowers-night {
+	background-image: url('./../img/moon-cloud-heavy-rain.svg');
+}
+.icon-crosshair {
+    background-color: var(--color-main-text);
+    padding: 0 !important;
+    mask: url(./../img/cross.svg) no-repeat;
+    mask-size: 18px 18px;
+    mask-position: center;
+    -webkit-mask: url(./../img/cross.svg) no-repeat;
+    -webkit-mask-size: 18px 18px;
+    -webkit-mask-position: center;
+    min-width: 44px !important;
+    min-height: 44px !important;
+}
+
+li:not(.inline) .weather-status-menu-item {
+	&__header {
+		display: block;
+		align-items: center;
+		color: var(--color-main-text);
+		padding: 10px 12px 5px 12px;
+		box-sizing: border-box;
+		opacity: 1;
+		white-space: nowrap;
+		width: 100%;
+		text-align: center;
+		max-width: 250px;
+		text-overflow: ellipsis;
+		min-width: 175px;
+	}
+
+	&__subheader {
+		width: 100%;
+
+		> button {
+			background-color: var(--color-main-background);
+			background-size: 16px;
+			border: 0;
+			border-radius: 0;
+			font-weight: normal;
+			font-size: 0.875em;
+			padding-left: 40px;
+
+			&:hover,
+			&:focus {
+				box-shadow: inset 4px 0 var(--color-primary-element);
+			}
+		}
+	}
+}
+
+body .inline .weather-status-menu-item__subheader > button {
+	background-color: rgba(255, 255, 255, 0.8);
+}
+
+body.theme--dark .inline .weather-status-menu-item__subheader > button {
+	background-color: rgba(24, 24, 24, 0.8) !important;
+}
+
+.inline .weather-status-menu-item__subheader {
+	width: 100%;
+
+	> button {
+		background-size: 16px;
+		border: 0;
+		border-radius: var(--border-radius-pill);
+		font-weight: normal;
+		font-size: 0.875em;
+		padding-left: 40px;
+
+		&:hover,
+		&:focus {
+			background-color: var(--color-background-hover);
+		}
+
+		&.icon-loading-small {
+			&::after {
+				left: 21px;
+			}
+		}
+	}
+}
+
+	li {
+		list-style-type: none;
+	}
+</style>

+ 116 - 0
apps/weather_status/src/services/weatherStatusService.js

@@ -0,0 +1,116 @@
+/**
+ * @copyright Copyright (c) 2020, Julien Veyssier
+ *
+ * @author Julien Veyssier <eneiluj@posteo.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+import HttpClient from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+/**
+ *
+ *
+ * @param {String} lat the latitude
+ * @param {String} lon the longitude
+ * @returns {Promise<Object>}
+ */
+const setLocation = async(lat, lon) => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'location'
+	const response = await HttpClient.put(url, {
+		address: '',
+		lat,
+		lon,
+	})
+
+	return response.data.ocs.data
+}
+
+/**
+ *
+ * @param {String} address The location
+ * @returns {Promise<Object>}
+ */
+const setAddress = async(address) => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'location'
+	const response = await HttpClient.put(url, {
+		address,
+		lat: null,
+		lon: null,
+	})
+
+	return response.data.ocs.data
+}
+
+/**
+ *
+ * @param {String} mode can be 1 browser or 2 custom
+ * @returns {Promise<Object>}
+ */
+const setMode = async(mode) => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'mode'
+	const response = await HttpClient.put(url, {
+		mode,
+	})
+
+	return response.data.ocs.data
+}
+
+/**
+ *
+ * @returns {Promise<Object>}
+ */
+const usePersonalAddress = async() => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'use-personal'
+	const response = await HttpClient.put(url)
+
+	return response.data.ocs.data
+}
+
+/**
+ * Fetches the location information for current user
+ *
+ * @returns {Promise<Object>}
+ */
+const getLocation = async() => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'location'
+	const response = await HttpClient.get(url)
+
+	return response.data.ocs.data
+}
+
+/**
+ * Fetches the weather forecast
+ *
+ * @param {String} address The location
+ * @returns {Promise<Object>}
+ */
+const fetchForecast = async() => {
+	const url = generateOcsUrl('apps/weather_status/api/v1', 2) + 'forecast'
+	const response = await HttpClient.get(url)
+
+	return response.data.ocs.data
+}
+
+export {
+	usePersonalAddress,
+	setMode,
+	getLocation,
+	setLocation,
+	setAddress,
+	fetchForecast,
+}

+ 28 - 0
apps/weather_status/src/weather-status.js

@@ -0,0 +1,28 @@
+import Vue from 'vue'
+import { getRequestToken } from '@nextcloud/auth'
+import { generateUrl } from '@nextcloud/router'
+import App from './App'
+
+// eslint-disable-next-line camelcase
+__webpack_nonce__ = btoa(getRequestToken())
+
+// Correct the root of the app for chunk loading
+// eslint-disable-next-line
+__webpack_public_path__ = generateUrl('/apps/weather_status/js/')
+
+Vue.prototype.t = t
+
+document.addEventListener('DOMContentLoaded', function() {
+	if (!OCA.Dashboard) {
+		return
+	}
+
+	OCA.Dashboard.registerStatus('weather', (el) => {
+		const Dashboard = Vue.extend(App)
+		return new Dashboard({
+			propsData: {
+				inline: true,
+			},
+		}).$mount(el)
+	})
+})

+ 26 - 0
apps/weather_status/webpack.js

@@ -0,0 +1,26 @@
+const path = require('path')
+
+module.exports = {
+	entry: {
+		'weather-status': path.join(__dirname, 'src', 'weather-status')
+	},
+	output: {
+		path: path.resolve(__dirname, './js'),
+		publicPath: '/js/',
+		filename: '[name].js?v=[chunkhash]',
+		jsonpFunction: 'webpackJsonpWeatherStatus'
+	},
+	optimization: {
+		splitChunks: {
+			automaticNameDelimiter: '-',
+		}
+	},
+	module: {
+        rules: [
+            {
+                test: /\.(png|jpg|gif|svg|woff|woff2|eot|ttf)$/,
+                loader: 'url-loader',
+            },
+        ],
+	},
+}

+ 1 - 0
build/integration/features/provisioning-v1.feature

@@ -349,6 +349,7 @@ Feature: provisioning
 			| user_status |
 			| viewer |
 			| workflowengine |
+			| weather_status |
 			| files_external |
 			| oauth2 |
 

+ 1 - 0
core/shipped.json

@@ -43,6 +43,7 @@
     "user_ldap",
     "user_status",
     "viewer",
+    "weather_status",
     "workflowengine"
   ],
   "alwaysEnabled": [

+ 2 - 0
webpack.common.js

@@ -16,6 +16,7 @@ const oauth2 = require('./apps/oauth2/webpack')
 const settings = require('./apps/settings/webpack')
 const systemtags = require('./apps/systemtags/webpack')
 const user_status = require('./apps/user_status/webpack')
+const weather_status = require('./apps/weather_status/webpack')
 const twofactor_backupscodes = require('./apps/twofactor_backupcodes/webpack')
 const updatenotification = require('./apps/updatenotification/webpack')
 const workflowengine = require('./apps/workflowengine/webpack')
@@ -33,6 +34,7 @@ const modules = {
 	settings,
 	systemtags,
 	user_status,
+	weather_status,
 	twofactor_backupscodes,
 	updatenotification,
 	workflowengine

Some files were not shown because too many files changed in this diff