Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'apps/weather_status/src/App.vue')
-rw-r--r--apps/weather_status/src/App.vue503
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>