new weather status used in dashboard
Signed-off-by: Julien Veyssier <eneiluj@posteo.net>
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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'],
|
||||||
|
],
|
||||||
|
];
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 {
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
|
@ -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,
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
|
@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
|
@ -349,6 +349,7 @@ Feature: provisioning
|
||||||
| user_status |
|
| user_status |
|
||||||
| viewer |
|
| viewer |
|
||||||
| workflowengine |
|
| workflowengine |
|
||||||
|
| weather_status |
|
||||||
| files_external |
|
| files_external |
|
||||||
| oauth2 |
|
| oauth2 |
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
"user_ldap",
|
"user_ldap",
|
||||||
"user_status",
|
"user_status",
|
||||||
"viewer",
|
"viewer",
|
||||||
|
"weather_status",
|
||||||
"workflowengine"
|
"workflowengine"
|
||||||
],
|
],
|
||||||
"alwaysEnabled": [
|
"alwaysEnabled": [
|
||||||
|
|
|
@ -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
|
||||||
|
|