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