new weather status used in dashboard

Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
This commit is contained in:
Julien Veyssier 2020-08-06 10:43:26 +02:00 committed by Jan C. Borchardt
parent d8bdb439a4
commit 70d1d1997a
No known key found for this signature in database
GPG Key ID: CBD846FC845CBE17
42 changed files with 1442 additions and 0 deletions

1
.gitignore vendored
View File

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

View File

@ -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>

View File

@ -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'],
],
];

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1009 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 666 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 235 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 242 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 806 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 815 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 642 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 863 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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.33M95.35 263.77a7.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.25M157.53 263.77a7.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.25M219.72 263.77a7.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><g fill="#61c9e7"><path d="M194.41 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.26M132.7 289.39a7.41 7.41 0 007.1-9.6l-8.95-29.18a7.43 7.43 0 00-14.2 4.35l8.94 29.17a7.43 7.43 0 007.1 5.26M280.17 261.6a7.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.25M254.86 288.41a7.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.25"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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>

After

Width:  |  Height:  |  Size: 703 B

View File

@ -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>

After

Width:  |  Height:  |  Size: 748 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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 {
}
}

View File

@ -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,
],
];
}
}

View File

@ -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);
}
}
}

View File

@ -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()];
}
}
}

View File

@ -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>

View File

@ -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,
}

View File

@ -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)
})
})

View File

@ -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',
},
],
},
}

View File

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

View File

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

View File

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