Implementing custom dashboard components [WIP]
This commit is contained in:
parent
eb486df1ee
commit
88b788430d
25 changed files with 15558 additions and 28 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
platypush/backend/http/webapp/dist/static/css/chunk-9ab30700.c557c44b.css
vendored
Normal file
7
platypush/backend/http/webapp/dist/static/css/chunk-9ab30700.c557c44b.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/app.24b6b5a6.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/app.24b6b5a6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/app.24b6b5a6.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/app.24b6b5a6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-9f884670"],{"0279":function(e,t,a){"use strict";var c=a("7a23"),n=Object(c["K"])("data-v-8fae7678");Object(c["u"])("data-v-8fae7678");var s=Object(c["h"])("div",{class:"switch"},[Object(c["h"])("div",{class:"dot"})],-1),i={class:"label"};Object(c["s"])();var o=n((function(e,t,a,n,o,l){return Object(c["r"])(),Object(c["e"])("div",{class:["power-switch",{disabled:a.disabled}],onClick:t[1]||(t[1]=function(){return l.onInput.apply(l,arguments)})},[Object(c["h"])("input",{type:"checkbox",checked:a.value},null,8,["checked"]),Object(c["h"])("label",null,[s,Object(c["h"])("span",i,[Object(c["y"])(e.$slots,"default")])])],2)})),l={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput:function(e){if(e.stopPropagation(),this.disabled)return!1;this.$emit("input",e)}}};a("5b0a");l.render=o,l.__scopeId="data-v-8fae7678";t["a"]=l},"5b0a":function(e,t,a){"use strict";a("7ef9")},"7ef9":function(e,t,a){}}]);
|
||||
//# sourceMappingURL=chunk-9f884670.9830b044.js.map
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-06539e5d"],{"0279":function(e,t,a){"use strict";var c=a("7a23"),n=Object(c["K"])("data-v-8fae7678");Object(c["u"])("data-v-8fae7678");var s=Object(c["h"])("div",{class:"switch"},[Object(c["h"])("div",{class:"dot"})],-1),i={class:"label"};Object(c["s"])();var o=n((function(e,t,a,n,o,l){return Object(c["r"])(),Object(c["e"])("div",{class:["power-switch",{disabled:a.disabled}],onClick:t[1]||(t[1]=function(){return l.onInput.apply(l,arguments)})},[Object(c["h"])("input",{type:"checkbox",checked:a.value},null,8,["checked"]),Object(c["h"])("label",null,[s,Object(c["h"])("span",i,[Object(c["y"])(e.$slots,"default")])])],2)})),l={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput:function(e){if(e.stopPropagation(),this.disabled)return!1;this.$emit("input",e)}}};a("5b0a");l.render=o,l.__scopeId="data-v-8fae7678";t["a"]=l},"5b0a":function(e,t,a){"use strict";a("7ef9a")},"7ef9a":function(e,t,a){}}]);
|
||||
//# sourceMappingURL=chunk-06539e5d.1a0f4e72.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-31bc5041"],{6341:function(e,n,t){"use strict";t.r(n);t("b64b");var c=t("7a23"),r=Object(c["K"])("data-v-eac2ea44");Object(c["u"])("data-v-eac2ea44");var i={class:"switches-container"},s={class:"switch-plugins"},u={key:0,class:"no-content"},a={key:0,class:"refresh col-2"},o=Object(c["h"])("i",{class:"fa fa-sync"},null,-1),l={class:"refresh-button"},d=Object(c["h"])("i",{class:"fa fa-sync"},null,-1);Object(c["s"])();var b=r((function(e,n,t,r,b,h){var f=Object(c["z"])("Loading");return Object(c["r"])(),Object(c["e"])("div",i,[b.loading?(Object(c["r"])(),Object(c["e"])(f,{key:0})):Object(c["f"])("",!0),Object(c["h"])("div",s,[Object.keys(b.plugins).length?Object(c["f"])("",!0):(Object(c["r"])(),Object(c["e"])("div",u,"No switch plugins configured")),(Object(c["r"])(!0),Object(c["e"])(c["a"],null,Object(c["x"])(Object.keys(b.plugins),(function(e){return Object(c["r"])(),Object(c["e"])("div",{class:"switch-plugin",key:e,onClick:function(n){return b.selectedPlugin=b.selectedPlugin===e?null:e}},[Object(c["h"])("div",{class:["header",{selected:b.selectedPlugin===e}]},[Object(c["h"])("div",{class:"name col-10",textContent:Object(c["C"])(e)},null,8,["textContent"]),b.selectedPlugin===e?(Object(c["r"])(),Object(c["e"])("div",a,[Object(c["h"])("button",{onClick:Object(c["J"])((function(n){return b.bus.emit("refresh",e)}),["stop"]),title:"Refresh plugin",disabled:b.loading},[o],8,["onClick","disabled"])])):Object(c["f"])("",!0)],2),Object(c["h"])("div",{class:["body",{hidden:b.selectedPlugin!==e}]},[(Object(c["r"])(),Object(c["e"])(Object(c["A"])(b.components[e]),{config:b.plugins[e],"plugin-name":e,selected:b.selectedPlugin===e,bus:b.bus},null,8,["config","plugin-name","selected","bus"]))],2)],8,["onClick"])})),128))]),Object(c["h"])("div",l,[Object(c["h"])("button",{onClick:n[1]||(n[1]=function(){return h.refresh.apply(h,arguments)}),disabled:b.loading,title:"Refresh plugins"},[d],8,["disabled"])])])})),h=(t("4160"),t("a15b"),t("d81d"),t("fb6a"),t("d3b7"),t("ac1f"),t("1276"),t("159b"),t("96cf"),t("1da1")),f=t("3a5e"),p=t("3e54"),O=t("14b7"),j={name:"Switches",components:{Loading:f["a"]},mixins:[p["a"]],data:function(){return{loading:!1,plugins:{},components:{},selectedPlugin:null,bus:Object(O["a"])()}},methods:{initPanels:function(){var e=this;this.components={},Object.keys(this.plugins).forEach(function(){var n=Object(h["a"])(regeneratorRuntime.mark((function n(r){var i,s,u;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return i=r.split(".").map((function(e){return e[0].toUpperCase()+e.slice(1)})).join(""),s=null,n.prev=2,n.next=5,t("c1da")("./".concat(i,"/Index"));case 5:s=n.sent,n.next=11;break;case 8:return n.prev=8,n.t0=n["catch"](2),n.abrupt("return");case 11:u=Object(c["i"])(Object(h["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.abrupt("return",s);case 1:case"end":return e.stop()}}),e)})))),e.$options.components[r]=u,e.components[r]=u;case 14:case"end":return n.stop()}}),n,null,[[2,8]])})));return function(e){return n.apply(this,arguments)}}())},refresh:function(){var e=this;return Object(h["a"])(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return e.loading=!0,n.prev=1,n.next=4,e.request("utils.get_switch_plugins");case 4:e.plugins=n.sent,e.initPanels();case 6:return n.prev=6,e.loading=!1,n.finish(6);case 9:case"end":return n.stop()}}),n,null,[[1,,6,9]])})))()}},mounted:function(){this.refresh()}};t("84aa");j.render=b,j.__scopeId="data-v-eac2ea44";n["default"]=j},"7ac9":function(e,n,t){},"84aa":function(e,n,t){"use strict";t("7ac9")},c1da:function(e,n,t){var c={"./LightHue/Index":["0219","chunk-9f884670","chunk-5d632024","chunk-e017dc3e"],"./Smartthings/Index":["6e68","chunk-9f884670","chunk-5d632024","chunk-972487d6"],"./SwitchSwitchbot/Index":["5083","chunk-9f884670","chunk-5d632024","chunk-0021f7ee"],"./SwitchTplink/Index":["d11f","chunk-9f884670","chunk-5d632024","chunk-c4aee99e"],"./SwitchWemo/Index":["bedd","chunk-9f884670","chunk-5d632024","chunk-60dbbc82"],"./ZigbeeMqtt/Index":["65d6","chunk-9f884670","chunk-5d632024","chunk-07773226"],"./Zwave/Index":["e170","chunk-9f884670","chunk-5d632024","chunk-0827360a"]};function r(e){if(!t.o(c,e))return Promise.resolve().then((function(){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}));var n=c[e],r=n[0];return Promise.all(n.slice(1).map(t.e)).then((function(){return t(r)}))}r.keys=function(){return Object.keys(c)},r.id="c1da",e.exports=r}}]);
|
||||
//# sourceMappingURL=chunk-31bc5041.f4934e0d.js.map
|
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-31bc5041"],{6341:function(e,n,t){"use strict";t.r(n);t("b64b");var c=t("7a23"),r=Object(c["K"])("data-v-eac2ea44");Object(c["u"])("data-v-eac2ea44");var i={class:"switches-container"},s={class:"switch-plugins"},u={key:0,class:"no-content"},a={key:0,class:"refresh col-2"},o=Object(c["h"])("i",{class:"fa fa-sync"},null,-1),l={class:"refresh-button"},d=Object(c["h"])("i",{class:"fa fa-sync"},null,-1);Object(c["s"])();var b=r((function(e,n,t,r,b,h){var f=Object(c["z"])("Loading");return Object(c["r"])(),Object(c["e"])("div",i,[b.loading?(Object(c["r"])(),Object(c["e"])(f,{key:0})):Object(c["f"])("",!0),Object(c["h"])("div",s,[Object.keys(b.plugins).length?Object(c["f"])("",!0):(Object(c["r"])(),Object(c["e"])("div",u,"No switch plugins configured")),(Object(c["r"])(!0),Object(c["e"])(c["a"],null,Object(c["x"])(Object.keys(b.plugins),(function(e){return Object(c["r"])(),Object(c["e"])("div",{class:"switch-plugin",key:e,onClick:function(n){return b.selectedPlugin=b.selectedPlugin===e?null:e}},[Object(c["h"])("div",{class:["header",{selected:b.selectedPlugin===e}]},[Object(c["h"])("div",{class:"name col-10",textContent:Object(c["C"])(e)},null,8,["textContent"]),b.selectedPlugin===e?(Object(c["r"])(),Object(c["e"])("div",a,[Object(c["h"])("button",{onClick:Object(c["J"])((function(n){return b.bus.emit("refresh",e)}),["stop"]),title:"Refresh plugin",disabled:b.loading},[o],8,["onClick","disabled"])])):Object(c["f"])("",!0)],2),Object(c["h"])("div",{class:["body",{hidden:b.selectedPlugin!==e}]},[(Object(c["r"])(),Object(c["e"])(Object(c["A"])(b.components[e]),{config:b.plugins[e],"plugin-name":e,selected:b.selectedPlugin===e,bus:b.bus},null,8,["config","plugin-name","selected","bus"]))],2)],8,["onClick"])})),128))]),Object(c["h"])("div",l,[Object(c["h"])("button",{onClick:n[1]||(n[1]=function(){return h.refresh.apply(h,arguments)}),disabled:b.loading,title:"Refresh plugins"},[d],8,["disabled"])])])})),h=(t("4160"),t("a15b"),t("d81d"),t("fb6a"),t("d3b7"),t("ac1f"),t("1276"),t("159b"),t("96cf"),t("1da1")),f=t("3a5e"),p=t("3e54"),O=t("14b7"),j={name:"Switches",components:{Loading:f["a"]},mixins:[p["a"]],data:function(){return{loading:!1,plugins:{},components:{},selectedPlugin:null,bus:Object(O["a"])()}},methods:{initPanels:function(){var e=this;this.components={},Object.keys(this.plugins).forEach(function(){var n=Object(h["a"])(regeneratorRuntime.mark((function n(r){var i,s,u;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return i=r.split(".").map((function(e){return e[0].toUpperCase()+e.slice(1)})).join(""),s=null,n.prev=2,n.next=5,t("c1da")("./".concat(i,"/Index"));case 5:s=n.sent,n.next=11;break;case 8:return n.prev=8,n.t0=n["catch"](2),n.abrupt("return");case 11:u=Object(c["i"])(Object(h["a"])(regeneratorRuntime.mark((function e(){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.abrupt("return",s);case 1:case"end":return e.stop()}}),e)})))),e.$options.components[r]=u,e.components[r]=u;case 14:case"end":return n.stop()}}),n,null,[[2,8]])})));return function(e){return n.apply(this,arguments)}}())},refresh:function(){var e=this;return Object(h["a"])(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return e.loading=!0,n.prev=1,n.next=4,e.request("utils.get_switch_plugins");case 4:e.plugins=n.sent,e.initPanels();case 6:return n.prev=6,e.loading=!1,n.finish(6);case 9:case"end":return n.stop()}}),n,null,[[1,,6,9]])})))()}},mounted:function(){this.refresh()}};t("84aa");j.render=b,j.__scopeId="data-v-eac2ea44";n["default"]=j},"7ac9":function(e,n,t){},"84aa":function(e,n,t){"use strict";t("7ac9")},c1da:function(e,n,t){var c={"./LightHue/Index":["0219","chunk-06539e5d","chunk-5d632024","chunk-e017dc3e"],"./Smartthings/Index":["6e68","chunk-06539e5d","chunk-5d632024","chunk-972487d6"],"./SwitchSwitchbot/Index":["5083","chunk-06539e5d","chunk-5d632024","chunk-0021f7ee"],"./SwitchTplink/Index":["d11f","chunk-06539e5d","chunk-5d632024","chunk-c4aee99e"],"./SwitchWemo/Index":["bedd","chunk-06539e5d","chunk-5d632024","chunk-60dbbc82"],"./ZigbeeMqtt/Index":["65d6","chunk-06539e5d","chunk-5d632024","chunk-07773226"],"./Zwave/Index":["e170","chunk-06539e5d","chunk-5d632024","chunk-0827360a"]};function r(e){if(!t.o(c,e))return Promise.resolve().then((function(){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}));var n=c[e],r=n[0];return Promise.all(n.slice(1).map(t.e)).then((function(){return t(r)}))}r.keys=function(){return Object.keys(c)},r.id="c1da",e.exports=r}}]);
|
||||
//# sourceMappingURL=chunk-31bc5041.821f5281.js.map
|
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/chunk-9ab30700.2d0a4b4e.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/chunk-9ab30700.2d0a4b4e.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/chunk-9ab30700.2d0a4b4e.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/chunk-9ab30700.2d0a4b4e.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
14949
platypush/backend/http/webapp/package-lock.json
generated
14949
platypush/backend/http/webapp/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -67,6 +67,8 @@ export default {
|
|||
for (let handler of handlers) {
|
||||
if (handler instanceof Array)
|
||||
handler = handler[0]
|
||||
else if (handler instanceof Object)
|
||||
handler = Object.values(handler)[0]
|
||||
|
||||
handler(event.args)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<div class="component-widget">
|
||||
<Loading v-if="loading" />
|
||||
<div class="container" ref="container" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Utils from "@/Utils";
|
||||
import Loading from "@/components/Loading";
|
||||
import components from './index'
|
||||
import { createApp, h } from "vue";
|
||||
import mitt from 'mitt';
|
||||
|
||||
const bus = mitt();
|
||||
|
||||
export default {
|
||||
name: "Elements",
|
||||
components: {Loading},
|
||||
mixins: [Utils],
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
unwatch: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
_parseActions(element) {
|
||||
const actionsTags = [...element.children].filter((node) => node.tagName?.toLowerCase() === 'actions')
|
||||
const children = actionsTags?.length ? actionsTags[0].children : element.children
|
||||
const actionTags = [...children].filter((node) => node.tagName?.toLowerCase() === 'action')
|
||||
|
||||
if (!actionTags?.length)
|
||||
return
|
||||
|
||||
return [...actionTags]
|
||||
.map((actionTag) => {
|
||||
return {
|
||||
action: actionTag.attributes.name.value,
|
||||
args: [...actionTag.children].reduce((obj, arg) => {
|
||||
let value = undefined
|
||||
try {
|
||||
value = JSON.parse(arg.innerText)
|
||||
} catch (e) {
|
||||
if (arg.innerText?.length)
|
||||
value = arg.innerText
|
||||
}
|
||||
|
||||
obj[arg.tagName.toLowerCase()] = value
|
||||
return obj
|
||||
}, {}),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_parseVars(element) {
|
||||
const varsTags = [...element.children].filter((node) => node.tagName?.toLowerCase() === 'vars')
|
||||
if (!varsTags?.length)
|
||||
return
|
||||
|
||||
return [...varsTags[0].children].reduce((vars, varTag) => {
|
||||
let value = undefined
|
||||
try {
|
||||
value = JSON.parse(varTag.innerText)
|
||||
} catch (e) {
|
||||
if (varTag.innerText?.length)
|
||||
value = varTag.innerText
|
||||
}
|
||||
vars[varTag.tagName.toLowerCase()] = value
|
||||
return vars
|
||||
}, {})
|
||||
},
|
||||
|
||||
_parseHandlers(element) {
|
||||
const handlers = {}
|
||||
const parseHndlScript = (hndlText) => {
|
||||
return (app) => {
|
||||
return eval(`// noinspection JSUnusedLocalSymbols
|
||||
(async function (self) {
|
||||
${hndlText}
|
||||
})`)(app)
|
||||
}
|
||||
}
|
||||
|
||||
const parseEventHndl = (hndlText) => {
|
||||
return (app) => {
|
||||
return (event) => {
|
||||
return eval(`// noinspection JSUnusedLocalSymbols
|
||||
(async function (self, event) {
|
||||
${hndlText}
|
||||
})`)(app, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hndlTags = [...element.children].filter((node) => node.tagName?.toLowerCase() === 'handlers')
|
||||
if (hndlTags?.length) {
|
||||
const mounted = [...hndlTags[0].children].filter((node) => node.tagName?.toLowerCase() === 'mounted')
|
||||
if (mounted?.length)
|
||||
handlers.mounted = parseHndlScript(mounted[0].innerText)
|
||||
|
||||
const refresh = [...hndlTags[0].children].filter((node) => node.tagName?.toLowerCase() === 'refresh')
|
||||
if (refresh?.length) {
|
||||
handlers.refresh = {
|
||||
handler: parseHndlScript(refresh[0].innerText),
|
||||
interval: refresh[0].attributes.interval?.value || 10,
|
||||
}
|
||||
}
|
||||
|
||||
const events = [...hndlTags[0].children].filter((node) => node.tagName?.toLowerCase() === 'event')
|
||||
if (events?.length)
|
||||
handlers.events = events.reduce((events, hndlTag) => {
|
||||
events[hndlTag.attributes.type.value] = parseEventHndl(hndlTag.innerText)
|
||||
return events
|
||||
}, {})
|
||||
}
|
||||
|
||||
const actionsTags = [...element.children].filter((node) => node.tagName?.toLowerCase() === 'actions')
|
||||
if (actionsTags?.length) {
|
||||
const beforeActionsTags = [...actionsTags[0].children].filter((node) => node.tagName?.toLowerCase() === 'before')
|
||||
if (beforeActionsTags?.length)
|
||||
handlers.beforeActions = parseHndlScript(beforeActionsTags[0].innerText)
|
||||
|
||||
const afterActionsTags = [...actionsTags[0].children].filter((node) => node.tagName?.toLowerCase() === 'after')
|
||||
if (afterActionsTags?.length)
|
||||
handlers.afterActions = parseHndlScript(afterActionsTags[0].innerText)
|
||||
}
|
||||
|
||||
return handlers
|
||||
},
|
||||
|
||||
_parseProps(element) {
|
||||
return [...element.attributes].reduce((obj, attr) => {
|
||||
obj[attr.name] = attr.value
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
propagateEvent(event) {
|
||||
bus.emit('event', event)
|
||||
},
|
||||
|
||||
_addEventHandler() {
|
||||
this.unwatch = this.subscribe((event) => {
|
||||
bus.emit('event', event)
|
||||
})
|
||||
},
|
||||
|
||||
_removeEventHandler() {
|
||||
if (this.unwatch) {
|
||||
this.unwatch()
|
||||
this.unwatch = null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loading = true
|
||||
this._addEventHandler()
|
||||
|
||||
try {
|
||||
this.$refs.container.innerHTML = this.content
|
||||
|
||||
Object.entries(components).forEach(([name, component]) => {
|
||||
this.$options.components[name] = component;
|
||||
[...this.$refs.container.getElementsByTagName(name)].forEach((element) => {
|
||||
const props = this._parseProps(element)
|
||||
props.actions = this._parseActions(element)
|
||||
props.handlers = this._parseHandlers(element)
|
||||
props._vars = this._parseVars(element)
|
||||
|
||||
createApp({
|
||||
render() { return h(component, props) },
|
||||
data() {
|
||||
return { bus: bus }
|
||||
},
|
||||
}).mount(element)
|
||||
})
|
||||
})
|
||||
|
||||
for (const tagName of ['handlers', 'actions', 'vars'])
|
||||
this.$refs.container.getElementsByTagName(tagName).forEach((hndlTag) => {
|
||||
hndlTag.parentNode.removeChild(hndlTag)
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
this._removeEventHandler()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.component-widget {
|
||||
margin: -.75em 0 0 -.75em !important;
|
||||
padding: 0;
|
||||
width: calc(100% + 1.5em);
|
||||
height: calc(100% + 1.5em);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div class="run component-row" @click="run">
|
||||
<div :class="{'col-10': hasIcon, 'col-12': !hasIcon}" v-text="name" />
|
||||
<div class="col-2 icon-container" v-if="hasIcon">
|
||||
<img class="icon" :src="iconUrl" :alt="name" v-if="iconUrl?.length">
|
||||
<i class="icon" :class="iconClass" :style="iconStyle" v-else />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixins from './mixins';
|
||||
|
||||
/**
|
||||
* This component is used to run one or more actions.
|
||||
*/
|
||||
export default {
|
||||
name: "Run",
|
||||
mixins: [mixins],
|
||||
props: {
|
||||
/**
|
||||
* Component name
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: '[Unnamed action]',
|
||||
},
|
||||
|
||||
/**
|
||||
* Action (FontAwesome) icon class (default: `fa fa-play`)
|
||||
*/
|
||||
iconClass: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
/**
|
||||
* Action icon URL (default: `fa fa-play`)
|
||||
*/
|
||||
iconUrl: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
/**
|
||||
* Action icon color override, for FontAwesome icons
|
||||
*/
|
||||
iconColor: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
iconStyle() {
|
||||
if (!this.iconClass?.length && this.iconColor?.length)
|
||||
return
|
||||
|
||||
return {'color': this.iconColor}
|
||||
},
|
||||
|
||||
hasIcon() {
|
||||
return this.iconUrl?.length || this.iconClass?.length
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "mixins";
|
||||
|
||||
.run {
|
||||
.icon-container {
|
||||
position: relative;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div class="slider-root component-row">
|
||||
<div class="col-7" v-text="name" />
|
||||
<div class="col-5 slider-container">
|
||||
<div class="slider">
|
||||
<SliderElement :value="value" :range="[parseFloat(min), parseFloat(max)]" @mouseup="run" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixins from './mixins';
|
||||
import SliderElement from "@/components/elements/Slider";
|
||||
|
||||
/**
|
||||
* This component can be used to run action on the basis of a
|
||||
* numeric value included in a specified interval (i.e. a slider).
|
||||
*/
|
||||
export default {
|
||||
name: "Slider",
|
||||
components: {SliderElement},
|
||||
mixins: [mixins],
|
||||
props: {
|
||||
/**
|
||||
* Display name for this slider.
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: '[Unnamed slider]',
|
||||
},
|
||||
|
||||
/**
|
||||
* Minimum value for the slider (default: 0).
|
||||
*/
|
||||
min: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
|
||||
/**
|
||||
* Maximum value for the slider.
|
||||
*/
|
||||
max: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async run(event) {
|
||||
this.value = parseFloat(event.target.value)
|
||||
|
||||
if (this.handlers.beforeActions)
|
||||
await this.handlers.beforeActions(this)
|
||||
for (const action of this.actions)
|
||||
await this.request_(action)
|
||||
if (this.handlers.afterActions) {
|
||||
await this.handlers.afterActions(this)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: undefined,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "mixins";
|
||||
|
||||
.slider-root {
|
||||
.slider-container {
|
||||
position: relative;
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<div class="switch component-row" @click="run">
|
||||
<div class="col-10" v-text="name" />
|
||||
<div class="col-2 toggle-container">
|
||||
<div class="toggle">
|
||||
<ToggleSwitch :value="value" @input.stop="run" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixins from './mixins';
|
||||
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||
|
||||
/**
|
||||
* This component can be used to trigger toggle actions on
|
||||
* entities with a binary (ON/OFF) state.
|
||||
*/
|
||||
export default {
|
||||
name: "Switch",
|
||||
components: {ToggleSwitch},
|
||||
mixins: [mixins],
|
||||
props: {
|
||||
/**
|
||||
* Display name for this switch.
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: '[Unnamed switch]',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: undefined,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "mixins";
|
||||
|
||||
.switch {
|
||||
.toggle-container {
|
||||
position: relative;
|
||||
|
||||
.toggle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,132 @@
|
|||
import Utils from "@/Utils";
|
||||
|
||||
export default {
|
||||
mixins: [Utils],
|
||||
props: {
|
||||
// Actions to run upon interaction with the widget. Format:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "action": "light.hue.toggle",
|
||||
// "args": {
|
||||
// "lights": ["Bulb 1", "Bulb 2"]
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "action": "music.mpd.pause"
|
||||
// }
|
||||
// ]
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => { return [] },
|
||||
},
|
||||
|
||||
// Map of variables used by this component, in the form
|
||||
// of variable_name -> variable_value.
|
||||
_vars: {
|
||||
type: Object,
|
||||
default: () => { return {} },
|
||||
},
|
||||
|
||||
// Map of handlers, in the form of event_type -> functions.
|
||||
// Supported event handler types:
|
||||
//
|
||||
// - mounted: Function to execute when the component is mounted.
|
||||
// - beforeActions: Function to execute before the component action is run.
|
||||
// - afterActions: Function to execute after the component action is run.
|
||||
// - refresh: Function to be called at startup (if mounted is also specified
|
||||
// then refresh will be called after mounted when the component is
|
||||
// first mounted) and at regular intervals defined on the
|
||||
// interval property (default: 10 seconds).
|
||||
// - events: This is a mapping of functions that react to Platypush
|
||||
// platform events published on the websocket (e.g. lights or
|
||||
// switches toggles, media events etc.). The form is
|
||||
// platypush_event_type -> function.
|
||||
handlers: {
|
||||
type: Object,
|
||||
default: () => { return {} },
|
||||
},
|
||||
|
||||
// Event bus
|
||||
bus: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
vars: {...(this._vars || {})},
|
||||
_interval: undefined,
|
||||
refresh: null,
|
||||
refreshInterval: null,
|
||||
value: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async run() {
|
||||
if (this.handlers.input)
|
||||
return this.handlers.input(this)(this.value)
|
||||
|
||||
if (this.handlers.beforeActions)
|
||||
await this.handlers.beforeActions(this)
|
||||
for (const action of this.actions)
|
||||
await this.request_(action)
|
||||
if (this.handlers.afterActions) {
|
||||
await this.handlers.afterActions(this)
|
||||
}
|
||||
},
|
||||
|
||||
async request_(action) {
|
||||
const args = Object.entries(action.args).reduce((args, [key, value]) => {
|
||||
if (value.trim) {
|
||||
value = value.trim()
|
||||
const m = value.match(/^{{\s*(.*)\s*}}/)
|
||||
if (m) {
|
||||
value = eval(`// noinspection JSUnusedLocalSymbols
|
||||
(function (self) {
|
||||
return ${m[1]}
|
||||
})`)(this)
|
||||
}
|
||||
}
|
||||
|
||||
args[key] = value
|
||||
return args
|
||||
}, {})
|
||||
|
||||
await this.request(action.action, args)
|
||||
},
|
||||
|
||||
async processEvent(event) {
|
||||
const hndl = (this.handlers.events || {})[event.type]
|
||||
if (hndl)
|
||||
await hndl(this)(event)
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.$root.bus.on('event', this.processEvent)
|
||||
|
||||
if (this.handlers.mounted)
|
||||
await this.handlers.mounted(this)
|
||||
|
||||
if (this.handlers.refresh) {
|
||||
this.refreshInterval = (this.handlers.refresh?.interval || 0) * 1000
|
||||
this.refresh = () => {
|
||||
this.handlers.refresh.handler(this)
|
||||
}
|
||||
|
||||
await this.refresh()
|
||||
if (this.refreshInterval) {
|
||||
const self = this
|
||||
const wrapper = () => { return self.refresh() }
|
||||
this._interval = setInterval(wrapper, this.refreshInterval)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unmounted() {
|
||||
if (this._interval)
|
||||
clearInterval(this._interval)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
@mixin component-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: .75em .5em;
|
||||
border-bottom: $default-border;
|
||||
|
||||
&:hover {
|
||||
background: $hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.component-row {
|
||||
@include component-row;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import Run from './components/Run'
|
||||
import Switch from './components/Switch'
|
||||
import Slider from './components/Slider'
|
||||
|
||||
export default {
|
||||
Run,
|
||||
Switch,
|
||||
Slider,
|
||||
}
|
|
@ -40,7 +40,7 @@ export default {
|
|||
},
|
||||
|
||||
generateId() {
|
||||
return btoa([...Array(16).keys()].forEach(() => String.fromCharCode(Math.round(Math.random() * 255))))
|
||||
return btoa([...Array(11).keys()].map(() => String.fromCharCode(Math.round(Math.random() * 255))))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,27 +59,29 @@ export default {
|
|||
parseTemplate(name, tmpl) {
|
||||
const node = new DOMParser().parseFromString(tmpl, 'text/xml').childNodes[0]
|
||||
const self = this
|
||||
this.style = node.attributes.style ? node.attributes.style.nodeValue : undefined
|
||||
this.class = node.attributes.class ? node.attributes.class.nodeValue : undefined
|
||||
this.style = node.attributes.style?.nodeValue
|
||||
this.class = node.attributes.class?.nodeValue
|
||||
|
||||
this.rows = [...node.getElementsByTagName('Row')].map((row) => {
|
||||
return {
|
||||
style: row.attributes.style ? row.attributes.style.nodeValue : undefined,
|
||||
class: row.attributes.class ? row.attributes.class.nodeValue : undefined,
|
||||
style: row.attributes.style?.nodeValue,
|
||||
class: row.attributes.class?.nodeValue,
|
||||
widgets: [...row.children].map((el) => {
|
||||
const component = defineAsyncComponent(
|
||||
() => import(`@/components/widgets/${el.nodeName}/Index`)
|
||||
)
|
||||
|
||||
const style = el.attributes.style ? el.attributes.style.nodeValue : undefined
|
||||
const classes = el.attributes.class ? el.attributes.class.nodeValue : undefined
|
||||
const style = el.attributes.style?.nodeValue
|
||||
const classes = el.attributes.class?.nodeValue
|
||||
const attrs = [...el.attributes].reduce((obj, node) => {
|
||||
if (node.nodeName !== 'style') {
|
||||
obj[node.nodeName] = node.nodeValue
|
||||
}
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
}, {
|
||||
content: el.innerHTML,
|
||||
})
|
||||
|
||||
const widget = {
|
||||
component: component,
|
||||
|
|
Loading…
Reference in a new issue