forked from platypush/platypush
Migrated ImageCarousel widget
This commit is contained in:
parent
243e56b194
commit
cc3e52c69d
10 changed files with 353 additions and 36 deletions
|
@ -4,7 +4,7 @@ import re
|
|||
from flask import Blueprint, abort, send_from_directory
|
||||
|
||||
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')
|
||||
|
@ -24,7 +24,6 @@ __routes__ = [
|
|||
def resources_path(path):
|
||||
""" Custom static resources """
|
||||
path_tokens = path.split('/')
|
||||
filename = path_tokens.pop(-1)
|
||||
http_conf = Config.get('backend.http')
|
||||
resource_dirs = http_conf.get('resource_dirs', {})
|
||||
|
||||
|
@ -61,6 +60,7 @@ def serve_favicon():
|
|||
""" favicon.ico icon """
|
||||
return send_from_directory(template_folder, 'favicon.ico')
|
||||
|
||||
|
||||
@img.route('/img/<path:path>', methods=['GET'])
|
||||
def imgpath(path):
|
||||
""" Default static images """
|
||||
|
|
|
@ -68,7 +68,7 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
.date-time {
|
||||
.date {
|
||||
font-size: 1.3em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.time {
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="date-time-weather">
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
|
@ -30,8 +30,8 @@
|
|||
|
||||
<script>
|
||||
import Utils from "@/Utils";
|
||||
import DateTime from "@/widgets/DateTime/Index";
|
||||
import Weather from "@/widgets/Weather/Index";
|
||||
import DateTime from "@/components/widgets/DateTime/Index";
|
||||
import Weather from "@/components/widgets/Weather/Index";
|
||||
import Sensor from "@/components/Sensor";
|
||||
|
||||
// Widget to show date, time, weather and temperature information
|
||||
|
@ -44,7 +44,7 @@ export default {
|
|||
// Otherwise, it will be a static image.
|
||||
animate: {
|
||||
required: false,
|
||||
default: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// Size of the weather icon in pixels.
|
||||
|
@ -84,6 +84,12 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
|
||||
// If false then don't display the seconds.
|
||||
showSeconds: {
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// Name of the attribute on a received SensorDataChangeEvent that
|
||||
// represents the temperature value to be rendered.
|
||||
sensorTemperatureAttr: {
|
||||
|
@ -117,6 +123,10 @@ export default {
|
|||
return this.parseBoolean(this.showTime)
|
||||
},
|
||||
|
||||
_showSeconds() {
|
||||
return this.parseBoolean(this.showSeconds)
|
||||
},
|
||||
|
||||
_showWeather() {
|
||||
return this.parseBoolean(this.showWeather)
|
||||
},
|
|
@ -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"> </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>
|
|
@ -31,8 +31,13 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.row {
|
||||
width: 100%;
|
||||
height: 49%;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -3,8 +3,9 @@
|
|||
<Loading v-if="loading" />
|
||||
|
||||
<h1 v-else>
|
||||
<skycons :condition="weatherIcon" :paused="animate" :size="iconSize" v-if="weatherIcon" />
|
||||
<span class="temperature" v-if="weather">
|
||||
<skycons :condition="weatherIcon" :paused="!animate" :size="iconSize" :color="iconColor"
|
||||
v-if="_showIcon && weatherIcon" />
|
||||
<span class="temperature" v-if="_showTemperature && weather">
|
||||
{{ Math.round(parseFloat(weather.temperature)) + '°' }}
|
||||
</span>
|
||||
</h1>
|
||||
|
@ -28,7 +29,7 @@ export default {
|
|||
// Otherwise, it will be a static image.
|
||||
animate: {
|
||||
required: false,
|
||||
default: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// Size of the weather icon in pixels.
|
||||
|
@ -38,12 +39,30 @@ export default {
|
|||
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.
|
||||
showSummary: {
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// If false then the temperature won't be displayed.
|
||||
showTemperature: {
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// Refresh interval in seconds.
|
||||
refreshSeconds: {
|
||||
type: Number,
|
||||
|
@ -64,6 +83,14 @@ export default {
|
|||
_showSummary() {
|
||||
return this.parseBoolean(this.showSummary)
|
||||
},
|
||||
|
||||
_showIcon() {
|
||||
return this.parseBoolean(this.showIcon)
|
||||
},
|
||||
|
||||
_showTemperature() {
|
||||
return this.parseBoolean(this.showTemperature)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -102,8 +129,8 @@ export default {
|
|||
|
||||
h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.temperature {
|
|
@ -25,13 +25,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
classes() {
|
||||
let classes = ['widget', 'column']
|
||||
if (this.class && this.class.length)
|
||||
classes = classes.concat(this.class.split(' '))
|
||||
else
|
||||
classes = classes.concat('col-3')
|
||||
|
||||
return classes
|
||||
return (this.class && this.class.length ? this.class.split(' ') : ['col-3']).concat(['widget', 'column'])
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -39,9 +33,9 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.widget {
|
||||
height: calc(100% - 1em);
|
||||
background: $background-color;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
@ -49,16 +43,4 @@ export default {
|
|||
overflow: hidden;
|
||||
box-shadow: 0 3px 3px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $tablet){
|
||||
.widget {
|
||||
height: calc(100% - 1em);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $desktop){
|
||||
.widget {
|
||||
height: calc(50% - 1em);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -16,8 +16,8 @@
|
|||
import { defineAsyncComponent } from 'vue'
|
||||
import Utils from '@/Utils'
|
||||
import Loading from "@/components/Loading";
|
||||
import Row from "@/widgets/Row";
|
||||
import Widget from "@/widgets/Widget";
|
||||
import Row from "@/components/widgets/Row";
|
||||
import Widget from "@/components/widgets/Widget";
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
|
@ -60,7 +60,7 @@ export default {
|
|||
class: row.attributes.class ? row.attributes.class.nodeValue : undefined,
|
||||
widgets: [...row.children].map((el) => {
|
||||
const component = defineAsyncComponent(
|
||||
() => import(`@/widgets/${el.nodeName}/Index`)
|
||||
() => import(`@/components/widgets/${el.nodeName}/Index`)
|
||||
)
|
||||
|
||||
const style = el.attributes.style ? el.attributes.style.nodeValue : undefined
|
||||
|
@ -120,6 +120,7 @@ export default {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 1em 1em 0 1em;
|
||||
background: $dashboard-bg;
|
||||
|
|
Loading…
Reference in a new issue