Migrated ImageCarousel widget

This commit is contained in:
Fabio Manganiello 2020-11-26 00:26:10 +01:00
parent 243e56b194
commit cc3e52c69d
10 changed files with 353 additions and 36 deletions

View file

@ -4,7 +4,7 @@ import re
from flask import Blueprint, abort, send_from_directory from flask import Blueprint, abort, send_from_directory
from platypush.config import Config from platypush.config import Config
from platypush.backend.http.app import template_folder, static_folder from platypush.backend.http.app import template_folder
img_folder = os.path.join(template_folder, 'img') img_folder = os.path.join(template_folder, 'img')
@ -24,7 +24,6 @@ __routes__ = [
def resources_path(path): def resources_path(path):
""" Custom static resources """ """ Custom static resources """
path_tokens = path.split('/') path_tokens = path.split('/')
filename = path_tokens.pop(-1)
http_conf = Config.get('backend.http') http_conf = Config.get('backend.http')
resource_dirs = http_conf.get('resource_dirs', {}) resource_dirs = http_conf.get('resource_dirs', {})
@ -61,6 +60,7 @@ def serve_favicon():
""" favicon.ico icon """ """ favicon.ico icon """
return send_from_directory(template_folder, 'favicon.ico') return send_from_directory(template_folder, 'favicon.ico')
@img.route('/img/<path:path>', methods=['GET']) @img.route('/img/<path:path>', methods=['GET'])
def imgpath(path): def imgpath(path):
""" Default static images """ """ Default static images """

View file

@ -68,7 +68,7 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.date-time { .date-time {
.date { .date {
font-size: 1.3em; font-size: 1.1em;
} }
.time { .time {

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="date-time-weather"> <div class="date-time-weather">
<div class="row date-time-container"> <div class="row date-time-container">
<DateTime :show-date="_showDate" :show-time="_showTime" :animate="animate" <DateTime :show-date="_showDate" :show-time="_showTime" :show-seconds="_showSeconds" :animate="animate"
v-if="_showDate || _showTime" /> v-if="_showDate || _showTime" />
</div> </div>
@ -30,8 +30,8 @@
<script> <script>
import Utils from "@/Utils"; import Utils from "@/Utils";
import DateTime from "@/widgets/DateTime/Index"; import DateTime from "@/components/widgets/DateTime/Index";
import Weather from "@/widgets/Weather/Index"; import Weather from "@/components/widgets/Weather/Index";
import Sensor from "@/components/Sensor"; import Sensor from "@/components/Sensor";
// Widget to show date, time, weather and temperature information // Widget to show date, time, weather and temperature information
@ -44,7 +44,7 @@ export default {
// Otherwise, it will be a static image. // Otherwise, it will be a static image.
animate: { animate: {
required: false, required: false,
default: false, default: true,
}, },
// Size of the weather icon in pixels. // Size of the weather icon in pixels.
@ -84,6 +84,12 @@ export default {
default: true, default: true,
}, },
// If false then don't display the seconds.
showSeconds: {
required: false,
default: true,
},
// Name of the attribute on a received SensorDataChangeEvent that // Name of the attribute on a received SensorDataChangeEvent that
// represents the temperature value to be rendered. // represents the temperature value to be rendered.
sensorTemperatureAttr: { sensorTemperatureAttr: {
@ -117,6 +123,10 @@ export default {
return this.parseBoolean(this.showTime) return this.parseBoolean(this.showTime)
}, },
_showSeconds() {
return this.parseBoolean(this.showSeconds)
},
_showWeather() { _showWeather() {
return this.parseBoolean(this.showWeather) return this.parseBoolean(this.showWeather)
}, },

View file

@ -0,0 +1,292 @@
<template>
<div class="image-carousel">
<Loading v-if="!images.length" />
<div ref="background" class="background" />
<img ref="img" :src="imgURL" alt="Your carousel images"
:style="{display: !images.length ? 'none' : 'block'}">
<div class="row info-container" v-if="_showDate || _showTime">
<div class="col-6 weather-container">
<span v-if="!_showWeather">&nbsp;</span>
<Weather :show-icon="_showWeatherIcon" :show-summary="_showWeatherSummary" :show-temperature="_showTemperature"
:icon-color="weatherIconColor" :icon-size="weatherIconSize" :animate="_animateWeatherIcon" v-else />
</div>
<div class="col-6 date-time-container">
<DateTime :show-date="_showDate" :show-time="_showTime" :show-seconds="_showSeconds"
v-if="_showTime || _showDate" />
</div>
</div>
</div>
</template>
<script>
import Utils from "@/Utils";
import Loading from "@/components/Loading";
import DateTime from "@/components/widgets/DateTime/Index";
import Weather from "@/components/widgets/Weather/Index";
export default {
name: "ImageCarousel",
components: {Weather, DateTime, Loading},
mixins: [Utils],
props: {
// Images directory
imgDir: {
type: String,
required: true,
},
// Refresh interval in seconds.
refreshSeconds: {
type: Number,
required: false,
default: 15,
},
// Show the current date on top of the images
showDate: {
type: Boolean,
required: false,
default: false,
},
// Show the current time on top of the images
showTime: {
type: Boolean,
required: false,
default: false,
},
// If false then don't display the seconds.
showSeconds: {
type: Boolean,
required: false,
default: false,
},
// If false then don't display weather info.
showWeather: {
type: Boolean,
required: false,
default: false,
},
// If false then temperature won't be displayed.
showTemperature: {
type: Boolean,
required: false,
default: true,
},
// If false then don't display the weather state icon.
showWeatherIcon: {
type: Boolean,
required: false,
default: true,
},
// If false then don't display the weather summary text.
showWeatherSummary: {
type: Boolean,
required: false,
default: true,
},
// Weather con color.
weatherIconColor: {
type: String,
required: false,
default: 'white',
},
// Size of the weather icon in pixels.
weatherIconSize: {
type: Number,
required: false,
default: 40,
},
// If false then the weather icon will be animated.
// Otherwise, it will be a static image.
animateWeatherIcon: {
required: false,
default: true,
},
},
data() {
return {
images: [],
currentImage: undefined,
loading: false,
}
},
computed: {
imgURL() {
let port = 8008
if ('backend.http' in this.$root.config && 'port' in this.$root.config['backend.http']) {
port = this.$root.config['backend.http'].port
}
return '//' + window.location.hostname + ':' + port + this.currentImage
},
_showDate() {
return this.parseBoolean(this.showDate)
},
_showTime() {
return this.parseBoolean(this.showTime)
},
_showSeconds() {
return this.parseBoolean(this.showSeconds)
},
_showTemperature() {
return this.parseBoolean(this.showTemperature)
},
_showWeather() {
return this.parseBoolean(this.showWeather)
},
_showWeatherIcon() {
return this.parseBoolean(this.showWeatherIcon)
},
_showWeatherSummary() {
return this.parseBoolean(this.showWeatherSummary)
},
_animateWeatherIcon() {
return this.parseBoolean(this.animateWeatherIcon)
}
},
methods: {
async refresh() {
if (!this.images.length) {
this.loading = true
try {
this.images = await this.request('utils.search_web_directory', {
directory: this.imgDir,
extensions: ['.jpg', '.jpeg', '.png'],
})
this.shuffleImages()
} finally {
this.loading = false
}
}
if (this.images.length) {
this.currentImage = this.images.pop()
}
},
onNewImage() {
if (!this.$refs.img)
return
this.$refs.background.style['background-image'] = 'url(' + this.imgURL + ')'
this.$refs.img.style.width = 'auto'
if (this.$refs.img.width > this.$refs.img.height) {
const ratio = this.$refs.img.width / this.$refs.img.height
if (4/3 <= ratio <= 16/9) {
this.$refs.img.style.width = '100%'
}
if (ratio <= 4/3) {
this.$refs.img.style.height = '100%'
}
}
},
shuffleImages() {
for (let i=this.images.length-1; i > 0; i--) {
let j = Math.floor(Math.random() * (i+1))
let x = this.images[i]
this.images[i] = this.images[j]
this.images[j] = x
}
},
},
mounted() {
this.$refs.img.addEventListener('load', this.onNewImage)
this.$refs.img.addEventListener('error', this.refresh)
this.refresh()
setInterval(this.refresh, Math.round(this.refreshSeconds * 1000))
},
}
</script>
<style lang="scss" scoped>
.image-carousel {
width: calc(100% + 1.5em);
height: calc(100% + 1.5em);
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: black;
margin: -0.75em 0.75em 0.75em -0.75em !important;
.background {
position: absolute;
top: 0;
width: 100%;
height: 100vh;
background-color: black;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
filter: blur(13px);
-webkit-filter: blur(13px);
}
img {
position: absolute;
max-height: 100%;
z-index: 2;
}
}
.info-container {
width: 100%;
position: absolute;
bottom: 0;
display: flex;
align-items: flex-end;
z-index: 10;
color: white;
text-shadow: 3px 3px 4px black;
font-size: 1.25em;
margin: 0.5em;
padding: 0 1em;
.date-time {
text-align: right;
}
}
</style>
<style lang="scss">
.info-container {
.weather-container {
margin-bottom: 0.5em;
h1 {
justify-content: left;
margin-bottom: -0.5em;
font-size: 0.8em;
}
}
}
</style>

View file

@ -31,8 +31,13 @@ export default {
} }
</script> </script>
<style scoped> <style lang="scss" scoped>
.row { .row {
width: 100%; width: 100%;
height: 49%;
&:not(:last-child) {
margin-bottom: 1%;
}
} }
</style> </style>

View file

@ -3,8 +3,9 @@
<Loading v-if="loading" /> <Loading v-if="loading" />
<h1 v-else> <h1 v-else>
<skycons :condition="weatherIcon" :paused="animate" :size="iconSize" v-if="weatherIcon" /> <skycons :condition="weatherIcon" :paused="!animate" :size="iconSize" :color="iconColor"
<span class="temperature" v-if="weather"> v-if="_showIcon && weatherIcon" />
<span class="temperature" v-if="_showTemperature && weather">
{{ Math.round(parseFloat(weather.temperature)) + '&deg;' }} {{ Math.round(parseFloat(weather.temperature)) + '&deg;' }}
</span> </span>
</h1> </h1>
@ -28,7 +29,7 @@ export default {
// Otherwise, it will be a static image. // Otherwise, it will be a static image.
animate: { animate: {
required: false, required: false,
default: false, default: true,
}, },
// Size of the weather icon in pixels. // Size of the weather icon in pixels.
@ -38,12 +39,30 @@ export default {
default: 50, default: 50,
}, },
// Icon color.
iconColor: {
type: String,
required: false,
},
// If false then the weather icon won't be displayed.
showIcon: {
required: false,
default: true,
},
// If false then the weather summary won't be displayed. // If false then the weather summary won't be displayed.
showSummary: { showSummary: {
required: false, required: false,
default: true, default: true,
}, },
// If false then the temperature won't be displayed.
showTemperature: {
required: false,
default: true,
},
// Refresh interval in seconds. // Refresh interval in seconds.
refreshSeconds: { refreshSeconds: {
type: Number, type: Number,
@ -64,6 +83,14 @@ export default {
_showSummary() { _showSummary() {
return this.parseBoolean(this.showSummary) return this.parseBoolean(this.showSummary)
}, },
_showIcon() {
return this.parseBoolean(this.showIcon)
},
_showTemperature() {
return this.parseBoolean(this.showTemperature)
},
}, },
methods: { methods: {
@ -102,8 +129,8 @@ export default {
h1 { h1 {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
align-items: center;
} }
.temperature { .temperature {

View file

@ -25,13 +25,7 @@ export default {
computed: { computed: {
classes() { classes() {
let classes = ['widget', 'column'] return (this.class && this.class.length ? this.class.split(' ') : ['col-3']).concat(['widget', 'column'])
if (this.class && this.class.length)
classes = classes.concat(this.class.split(' '))
else
classes = classes.concat('col-3')
return classes
}, },
}, },
} }
@ -39,9 +33,9 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.widget { .widget {
height: calc(100% - 1em);
background: $background-color; background: $background-color;
border-radius: 5px; border-radius: 5px;
margin-bottom: 1em;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-content: center; align-content: center;
@ -49,16 +43,4 @@ export default {
overflow: hidden; overflow: hidden;
box-shadow: 0 3px 3px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08); box-shadow: 0 3px 3px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
} }
</style>
@media screen and (max-width: $tablet){
.widget {
height: calc(100% - 1em);
}
}
@media screen and (min-width: $desktop){
.widget {
height: calc(50% - 1em);
}
}
</style>

View file

@ -16,8 +16,8 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import Utils from '@/Utils' import Utils from '@/Utils'
import Loading from "@/components/Loading"; import Loading from "@/components/Loading";
import Row from "@/widgets/Row"; import Row from "@/components/widgets/Row";
import Widget from "@/widgets/Widget"; import Widget from "@/components/widgets/Widget";
export default { export default {
name: 'Dashboard', name: 'Dashboard',
@ -60,7 +60,7 @@ export default {
class: row.attributes.class ? row.attributes.class.nodeValue : undefined, class: row.attributes.class ? row.attributes.class.nodeValue : undefined,
widgets: [...row.children].map((el) => { widgets: [...row.children].map((el) => {
const component = defineAsyncComponent( const component = defineAsyncComponent(
() => import(`@/widgets/${el.nodeName}/Index`) () => import(`@/components/widgets/${el.nodeName}/Index`)
) )
const style = el.attributes.style ? el.attributes.style.nodeValue : undefined const style = el.attributes.style ? el.attributes.style.nodeValue : undefined
@ -120,6 +120,7 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column;
margin: 0; margin: 0;
padding: 1em 1em 0 1em; padding: 1em 1em 0 1em;
background: $dashboard-bg; background: $dashboard-bg;