Migrating light.hue panel WIP

This commit is contained in:
Fabio Manganiello 2020-12-03 00:59:35 +01:00
parent 0cd120f492
commit fc718c907a
36 changed files with 810 additions and 81 deletions

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-24ff873d.c68a1871.css" rel="prefetch"><link href="/static/css/chunk-2dcde994.79277e60.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.c0cffcb7.css" rel="prefetch"><link href="/static/css/chunk-5710a9bc.b05a2ff9.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.698b2d60.css" rel="prefetch"><link href="/static/css/chunk-7c2209ed.a322204e.css" rel="prefetch"><link href="/static/css/chunk-e8078048.67cca65c.css" rel="prefetch"><link href="/static/js/chunk-24ff873d.0f916e0f.js" rel="prefetch"><link href="/static/js/chunk-2dcde994.c90cdf08.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.c1ba820e.js" rel="prefetch"><link href="/static/js/chunk-5710a9bc.5aba1b9a.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.cd0ca5eb.js" rel="prefetch"><link href="/static/js/chunk-7c2209ed.3981671f.js" rel="prefetch"><link href="/static/js/chunk-e8078048.bc52467d.js" rel="prefetch"><link href="/static/css/app.54d8460c.css" rel="preload" as="style"><link href="/static/js/app.aaafc657.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.d4962a4a.js" rel="preload" as="script"><link href="/static/css/app.54d8460c.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.d4962a4a.js"></script><script src="/static/js/app.aaafc657.js"></script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-0a24466a.09555bc4.css" rel="prefetch"><link href="/static/css/chunk-16a3f845.79277e60.css" rel="prefetch"><link href="/static/css/chunk-24ff873d.c68a1871.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.c0cffcb7.css" rel="prefetch"><link href="/static/css/chunk-5710a9bc.b05a2ff9.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.698b2d60.css" rel="prefetch"><link href="/static/css/chunk-ac6aae98.a322204e.css" rel="prefetch"><link href="/static/css/chunk-e8078048.67cca65c.css" rel="prefetch"><link href="/static/js/chunk-0a24466a.ebe2c04f.js" rel="prefetch"><link href="/static/js/chunk-16a3f845.3bdbbdb5.js" rel="prefetch"><link href="/static/js/chunk-24ff873d.0f916e0f.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.377ea7b0.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.c1ba820e.js" rel="prefetch"><link href="/static/js/chunk-5710a9bc.5aba1b9a.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.cd0ca5eb.js" rel="prefetch"><link href="/static/js/chunk-ac6aae98.dda16597.js" rel="prefetch"><link href="/static/js/chunk-e8078048.bc52467d.js" rel="prefetch"><link href="/static/css/app.ac911816.css" rel="preload" as="style"><link href="/static/js/app.b4cc8001.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.e8b1896c.js" rel="preload" as="script"><link href="/static/css/app.ac911816.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.e8b1896c.js"></script><script src="/static/js/app.b4cc8001.js"></script></body></html>

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

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

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

View File

@ -0,0 +1,2 @@
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d2091df"],{a84f:function(e,t,n){"use strict";n.r(t);var r=n("7a23");function i(e,t,n,i,u,s){var a=Object(r["z"])("Loading"),o=Object(r["z"])("LightPlugin");return Object(r["r"])(),Object(r["e"])(r["a"],null,[u.loading?(Object(r["r"])(),Object(r["e"])(a,{key:0})):Object(r["f"])("",!0),Object(r["h"])(o,{"plugin-name":"light.hue",config:n.config,lights:u.lights,groups:u.groups,scenes:u.scenes,animations:u.animations,"initial-group":s.initialGroup,"loading-groups":u.loadingGroups,onGroupToggle:s.toggleGroup},null,8,["config","lights","groups","scenes","animations","initial-group","loading-groups","onGroupToggle"])],64)}n("4de4"),n("13d5"),n("b0c0"),n("4fad"),n("b64b"),n("d3b7"),n("3ca3"),n("ddb0");var u=n("b85c"),s=(n("96cf"),n("1da1")),a=n("5530"),o=n("3835"),c=n("cf99"),g=n("3e54"),p=n("3a5e"),l={name:"LightHue",components:{Loading:p["a"],LightPlugin:c["default"]},mixins:[g["a"]],props:{config:{type:Object,default:function(){}}},data:function(){return{lights:{},groups:{},scenes:{},animations:{},loading:!1,loadingLights:{},loadingGroups:{}}},computed:{groupsByName:function(){return this.groups?Object.entries(this.groups).reduce((function(e,t){var n=Object(o["a"])(t,2),r=n[0],i=n[1];return e[i.name||r]=Object(a["a"])(Object(a["a"])({},i),{},{id:r}),e}),{}):{}},initialGroup:function(){if(!this.config.groups||!Object.keys(this.config.groups).length)return null;var e=this.config.groups[0];return e in this.groups?this.groups[e].id:e in this.groupsByName?this.groupsByName[e].id:null}},methods:{getLights:function(){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,e.request("light.hue.get_lights");case 2:return t.abrupt("return",t.sent);case 3:case"end":return t.stop()}}),t)})))()},getGroups:function(){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return t.t0=Object,t.next=3,e.request("light.hue.get_groups");case 3:return t.t1=t.sent,t.abrupt("return",t.t0.entries.call(t.t0,t.t1).filter((function(e){return!e[1].recycle&&"room"===e[1].type.toLowerCase()})).reduce((function(e,t){var n=Object(o["a"])(t,2),r=n[0],i=n[1];return e[r]=i,e}),{}));case 5:case"end":return t.stop()}}),t)})))()},getScenes:function(){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function t(){return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,e.request("light.hue.get_scenes");case 2:return t.abrupt("return",t.sent);case 3:case"end":return t.stop()}}),t)})))()},toggleGroup:function(e){var t=this;return Object(s["a"])(regeneratorRuntime.mark((function n(){var r;return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return r=[],null!=e&&r.push(e.name),t.setGroupsLoading(e),n.prev=3,n.next=6,t.request("light.hue.toggle",{groups:r});case 6:return n.next=8,t.refresh();case 8:return n.prev=8,t.unsetGroupsLoading(e),n.finish(8);case 11:case"end":return n.stop()}}),n,null,[[3,,8,11]])})))()},refresh:function(){var e=this;return Object(s["a"])(regeneratorRuntime.mark((function t(){var n,r;return regeneratorRuntime.wrap((function(t){while(1)switch(t.prev=t.next){case 0:return e.loading=!0,t.prev=1,t.next=4,Promise.all([e.getLights(),e.getGroups(),e.getScenes()]);case 4:n=t.sent,r=Object(o["a"])(n,3),e.lights=r[0],e.groups=r[1],e.scenes=r[2];case 9:return t.prev=9,e.loading=!1,t.finish(9);case 12:case"end":return t.stop()}}),t,null,[[1,,9,12]])})))()},setGroupsLoading:function(){for(var e={},t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];if(n.length&&n[0]){var i,s=Object(u["a"])(n);try{for(s.s();!(i=s.n()).done;){var o=i.value;e[o.id]=!0}}catch(c){s.e(c)}finally{s.f()}}else e=Object.keys(this.groups);this.loadingGroups=Object(a["a"])(Object(a["a"])({},this.loadingGroups),e)},unsetGroupsLoading:function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];for(var r=0,i=t;r<i.length;r++){var u=i[r];u.id in this.loadingGroups&&delete this.loadingGroups[u.id]}}},mounted:function(){this.refresh()}};l.render=i;t["default"]=l}}]);
//# sourceMappingURL=chunk-2d2091df.377ea7b0.js.map

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

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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,87 @@
<template>
<div class="light-group-container">
<MenuPanel>
<li class="header">
<button class="back-btn" title="Back" @click="close" v-if="group">
<i class="fas fa-chevron-left" />
</button>
</li>
<div class="no-lights" v-if="!lights || !Object.keys(lights).length">
No lights found
</div>
<li v-for="(light, id) in lightsSorted" :key="id" v-else>
<Light :light="light" />
</li>
</MenuPanel>
</div>
</template>
<script>
import Light from "@/components/Light/Light";
import MenuPanel from "@/components/MenuPanel";
export default {
name: "Group",
emits: ['close'],
components: {MenuPanel, Light},
props: {
lights: {
type: Object,
},
group: {
type: Object,
},
},
computed: {
lightsSorted() {
if (!this.lights)
return []
return Object.entries(this.lights)
.sort((a, b) => a[1].name.localeCompare(b[1].name))
.map(([id, light]) => {
return {
...light,
id: id,
}
})
},
},
methods: {
close(event) {
event.stopPropagation()
this.$emit('close')
},
},
}
</script>
<style lang="scss">
.light-group-container {
width: 100%;
height: 100%;
.header {
.back-btn {
border: 0;
&:hover {
border: 0;
color: $default-hover-fg;
}
}
}
li.header {
.back-btn {
background: none;
margin-left: -0.75em;
}
}
}
</style>

View File

@ -0,0 +1,72 @@
<template>
<MenuPanel>
<li class="header">
<i class="icon fas fa-home" />
<span class="name">Rooms</span>
</li>
<li class="row group" v-for="group in groupsSorted" :key="group.id" @click="$emit('select', group.id)">
<span class="name col-9">
{{ group.name || `[Group #${group.id}]` }}
</span>
<span class="controls col-3 pull-right">
<ToggleSwitch :value="group.state.any_on" :disabled="group.id in (loadingGroups || {})" @input="toggleGroup(group)" />
</span>
</li>
</MenuPanel>
</template>
<script>
import MenuPanel from "@/components/MenuPanel";
import ToggleSwitch from "@/components/elements/ToggleSwitch";
export default {
name: "Groups",
components: {ToggleSwitch, MenuPanel},
emits: ['select', 'toggle'],
props: {
groups: {
type: Object,
default: () => {},
},
loadingGroups: {
type: Object,
default: () => {},
}
},
computed: {
groupsSorted() {
return Object.entries(this.groups)
.sort((a, b) => a[1].name.localeCompare(b[1].name))
.map(([id, group]) => {
return {
...group,
id: id,
}
})
},
},
methods: {
toggleGroup(group) {
this.$emit('toggle', group)
}
}
}
</script>
<style lang="scss" scoped>
.header {
display: flex;
.icon {
margin-right: 1em;
}
}
.group {
display: flex;
align-items: center;
}
</style>

View File

@ -0,0 +1,21 @@
<template>
<div class="light">
{{ light.name || light.id }}
</div>
</template>
<script>
export default {
name: "Light",
props: {
light: {
type: Object,
default: () => {},
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,122 @@
<template>
<div class="menu-panel">
<ul :style="style">
<slot />
</ul>
</div>
</template>
<script>
export default {
name: "MenuPanel",
props: {
style: {
type: [String, Object, Array],
},
},
}
</script>
<style lang="scss">
.menu-panel {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
background: $menu-panel-bg;
padding-top: 2em;
ul {
background: $menu-panel-content-bg;
border-radius: 15px;
box-shadow: $plugin-panel-shadow;
border: 0;
li {
display: flex;
padding: 0.5em;
box-shadow: $plugin-panel-entry-shadow;
cursor: pointer;
border: 0;
letter-spacing: 0.05em;
&:hover {
background: $hover-bg;
}
&:first-child {
border-radius: 15px 15px 0 0;
box-shadow: $plugin-panel-first-entry-shadow;
}
&:last-child {
border-radius: 0 0 15px 15px;
box-shadow: $plugin-panel-last-entry-shadow;
}
&.header {
background: $menu-header-bg;
font-weight: bold;
box-shadow: $menu-header-shadow;
&:hover {
background: $menu-header-bg;
}
}
}
}
}
@media screen and (max-width: $tablet) {
.menu-panel {
padding-top: 0;
ul {
min-width: 100%;
border-radius: 0;
li {
&:first-child {
border-radius: 0;
}
&:last-child {
border-radius: 0;
}
}
}
}
}
@media screen and (min-width: $tablet) {
.menu-panel {
ul {
min-width: 65%;
}
}
}
@media screen and (min-width: $desktop) {
.menu-panel {
ul {
min-width: 40%;
}
}
}
@media screen and (min-width: $widescreen) {
.menu-panel {
ul {
min-width: 30%;
}
}
}
@media screen and (min-width: $fullhd) {
.menu-panel {
ul {
min-width: 25%;
}
}
}
</style>

View File

@ -12,7 +12,7 @@
<i :class="icons[name].class" v-if="icons[name]?.class" />
<i class="fas fa-puzzle-piece" v-else />
</span>
<span class="name" v-if="!collapsed">{{ name }}</span>
<span class="name" v-if="!collapsed">{{ displayName(name) }}</span>
</a>
</li>
</nav>
@ -39,6 +39,12 @@ export default {
},
},
methods: {
displayName(name) {
return name.split('.').map((token) => token[0].toUpperCase() + token.slice(1)).join(' ')
},
},
data() {
return {
collapsed: false,
@ -46,9 +52,6 @@ export default {
host: null,
}
},
async mounted() {
}
}
</script>
@ -65,18 +68,19 @@ nav {
box-shadow: $nav-box-shadow-main;
margin-right: 4px;
a {
color: $nav-fg;
&:hover {
color: $nav-fg;
}
}
li {
padding: 1em 0.25em;
box-shadow: $nav-box-shadow-entry;
cursor: pointer;
a {
display: block;
color: $nav-fg;
padding: 1em 0.25em;
&:hover {
color: $nav-fg;
}
}
&:hover {
background: $nav-entry-hover-bg;
}
@ -117,6 +121,7 @@ nav {
a {
color: $nav-collapsed-fg;
padding: 0.25em 0;
&:hover {
color: $nav-collapsed-fg;
}

View File

@ -0,0 +1,180 @@
<template>
<div class="power-switch" :class="{disabled: disabled}" @click="onInput">
<!--suppress HtmlFormInputWithoutLabel -->
<input type="checkbox" :checked="value">
<label>
<!--suppress HtmlUnknownTag -->
<div class="switch">
<div class="dot" />
</div>
<span class="label">
<slot />
</span>
</label>
</div>
</template>
<script>
export default {
name: "ToggleSwitch",
emits: ['input'],
props: {
value: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
},
methods: {
onInput(event) {
event.stopPropagation()
if (this.disabled)
return false
this.$emit('input', event)
},
},
}
</script>
<style lang="scss" scoped>
.power-switch {
position: relative;
transition: transform .3s;
transform: scale(var(--scale, 1)) translateZ(0);
&:active {
--scale: .96;
}
&.disabled {
opacity: 0.6;
}
input {
display: none;
& + label {
border-radius: 1em;
display: block;
cursor: pointer;
position: relative;
transition: box-shadow .4s;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
background: none;
opacity: var(--gradient, 0);
transition: opacity .4s;
}
.switch {
position: relative;
display: inline-block;
vertical-align: top;
height: 1.4em;
width: 2.5em;
border-radius: 1em;
background: $toggle-bg;
box-shadow: $toggle-shadow;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
background: $toggle-selected-bg;
opacity: var(--gradient, 0);
transition: opacity .4s;
}
.dot {
background: $toggle-dot-bg;
position: absolute;
width: 1.5em;
height: 1.5em;
border-radius: 50%;
box-shadow: $toggle-dot-shadow;
left: -0.25em;
top: -1px;
transform: translateX(var(--offset, 0));
transition: transform .4s, box-shadow .4s;
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: inherit;
background: $toggle-selected-dot-bg;
box-shadow: $toggle-dot-shadow;
opacity: var(--gradient, 0);
transition: opacity .4s;
}
}
}
span {
line-height: 2em;
font-size: 1.2em;
color: var(--text, #646B8C);
font-weight: 500;
display: inline-block;
vertical-align: top;
position: relative;
margin-left: 0.5em;
transition: color .4s;
}
& + span {
text-align: center;
display: block;
position: absolute;
left: 0;
right: 0;
top: 100%;
opacity: 0;
font-size: 1em;
font-weight: 500;
color: #A6ACCD;
transform: translateY(4px);
transition: opacity .4s, transform .4s;
}
}
&:not(:checked) {
& + label {
pointer-events: none;
& + span {
opacity: 1;
transform: translateY(12px);
}
}
}
&:checked {
& + label {
--offset: 1.5em;
--text: #406046;
--gradient: 1;
--shadow: rgba(0, 39, 6, .1);
}
}
}
}
</style>

View File

@ -1,78 +1,137 @@
<template>
<div class="light-plugin">
I'm in the content!
{{ pluginName }}
<div class="plugin lights-plugin">
<div class="panel" v-if="selectedGroup == null && groups && Object.keys(groups).length">
<Groups :groups="groups" :loading-groups="loadingGroups" @select="selectedGroup = $event" @toggle="toggleGroup" />
</div>
<div class="panel" v-else>
<Group :group="groups[selectedGroup]" :lights="displayedLights" @close="closeGroup" />
</div>
</div>
</template>
<script>
import Utils from "@/Utils";
import Panel from "@/components/panels/Panel";
import Groups from "@/components/Light/Groups";
import Group from "@/components/Light/Group";
/**
* Generic component for light plugins panels.
*/
export default {
name: "Light",
components: {Group, Groups},
mixins: [Utils, Panel],
emits: ['group-toggle'],
props: {
// Set to false if the light plugin doesn't support groups.
hasGroups: {
type: Boolean,
default: true,
lights: {
type: Object,
},
// Set to false if the light plugin doesn't support scenes.
hasScenes: {
type: Boolean,
default: true,
groups: {
type: Object,
},
// Set to false if the light plugin doesn't support animations.
hasAnimations: {
type: Boolean,
default: true,
scenes: {
type: Object,
},
animations: {
type: Object,
},
loadingLights: {
type: Object,
default: () => {},
},
loadingGroups: {
type: Object,
default: () => {},
},
pluginName: {
type: String,
},
initialGroup: {
type: [Number, String],
},
},
data() {
return {
lights: {},
groups: {},
scenes: {},
selectedGroup: null,
initialized: false,
}
},
methods: {
async getLights() {
throw "getLights should be implemented by a derived component"
},
computed: {
displayedLights() {
const selectedGroup = this.selectedGroup || this.initialGroup
if (selectedGroup == null)
return this.lights
async getGroups() {
if (!this.hasGroups)
return {}
throw "getGroups should be implemented by a derived component"
},
async getScenes() {
if (!this.hasScenes)
return {}
throw "getScenes should be implemented by a derived component"
return this.groups[selectedGroup].lights.reduce((lights, lightId) => {
lights[lightId] = this.lights[lightId]
return lights
}, {})
},
},
async mounted() {
[this.lights, this.groups, this.scenes] = await Promise.all([
this.getLights(),
this.getGroups(),
this.getScenes(),
])
methods: {
initSelectedGroup() {
const self = this
const unwatch = this.$watch(() => self.initialGroup, (newVal) => {
if (!self.initialized) {
self.initialized = true
unwatch()
if (self.selectedGroup == null && newVal != null) {
self.selectedGroup = self.initialGroup
}
}
})
},
closeGroup() {
this.selectedGroup = null
},
toggleGroup(group) {
this.$emit('group-toggle', group)
},
},
mounted() {
this.initSelectedGroup()
},
}
</script>
<style scoped>
<style lang="scss" scoped>
.plugin {
width: 100%;
height: 100%;
display: flex;
}
.panel {
width: 100%;
height: 100%;
box-shadow: none;
overflow: auto;
}
</style>
<style lang="scss">
.lights-plugin {
.menu-panel {
ul {
li:not(.header) {
padding: 1.5em 1em;
}
}
}
}
</style>

View File

@ -1,13 +1,136 @@
<template>
<Light plugin-name="light.hue" />
<Loading v-if="loading" />
<LightPlugin plugin-name="light.hue" :config="config" :lights="lights" :groups="groups" :scenes="scenes"
:animations="animations" :initial-group="initialGroup" :loading-groups="loadingGroups"
@group-toggle="toggleGroup" />
</template>
<script>
import Light from "@/components/panels/Light/Index";
import LightPlugin from "@/components/panels/Light/Index";
import Utils from "@/Utils";
import Loading from "@/components/Loading";
export default {
name: "LightHue",
mixins: [Light],
components: {Light},
components: {Loading, LightPlugin},
mixins: [Utils],
props: {
config: {
type: Object,
default: () => {},
},
},
data() {
return {
lights: {},
groups: {},
scenes: {},
animations: {},
loading: false,
loadingLights: {},
loadingGroups: {},
}
},
computed: {
groupsByName() {
if (!this.groups)
return {}
return Object.entries(this.groups).reduce((groups, [id, group]) => {
groups[group.name || id] = {
...group,
id: id,
}
return groups
}, {})
},
initialGroup() {
if (!this.config.groups || !Object.keys(this.config.groups).length)
return null
const group = this.config.groups[0]
if (group in this.groups)
return this.groups[group].id
else if (group in this.groupsByName)
return this.groupsByName[group].id
return null
},
},
methods: {
async getLights() {
return await this.request('light.hue.get_lights')
},
async getGroups() {
return Object.entries(await this.request('light.hue.get_groups'))
.filter((group) => !group[1].recycle && group[1].type.toLowerCase() === 'room')
.reduce((obj, [id, group]) => {
obj[id] = group
return obj
}, {})
},
async getScenes() {
return await this.request('light.hue.get_scenes')
},
async toggleGroup(group) {
const groups = []
if (group != null)
groups.push(group.name)
this.setGroupsLoading(group)
try {
await this.request('light.hue.toggle', {
groups: groups,
})
await this.refresh()
} finally {
this.unsetGroupsLoading(group)
}
},
async refresh() {
this.loading = true
try {
[this.lights, this.groups, this.scenes] = await Promise.all([
this.getLights(),
this.getGroups(),
this.getScenes(),
])
} finally {
this.loading = false
}
},
setGroupsLoading(...groups) {
let loadingGroups = {}
if (groups.length && groups[0]) {
for (const group of groups)
loadingGroups[group.id] = true
} else {
loadingGroups = Object.keys(this.groups)
}
this.loadingGroups = {...this.loadingGroups, ...loadingGroups}
},
unsetGroupsLoading(...groups) {
for (const group of groups) {
if (group.id in this.loadingGroups)
delete this.loadingGroups[group.id]
}
},
},
mounted() {
this.refresh()
},
}
</script>

View File

@ -54,7 +54,7 @@ $active-glow-bg-2: #9cdfb0 !default;
/// Hover
$default-hover-fg: #35b870 !default;
$default-hover-fg-2: #38cf80 !default;
$hover-bg: #def6ea !default;
$hover-bg: #bef6da !default;
/// Navigator
$nav-bg: #002626 !default;
@ -67,3 +67,21 @@ $nav-box-shadow-main: 1px 0 2px #002626;
$nav-box-shadow-entry: 0 0 1px 1px #103824 !default;
$nav-box-shadow-collapsed: 1px 0 2px 1px #bbb !default;
$nav-collapsed-fg: #5e5e5e;
/// Panel/menu components
$menu-panel-bg: #e0eae8;
$menu-panel-content-bg: white;
$plugin-panel-shadow: 0 0 2px 2px #ccc !default;
$plugin-panel-entry-shadow: 1px 0 1px 1px #ddd !default;
$plugin-panel-first-entry-shadow: 2px 0 1px -2px #ddd !default;
$plugin-panel-last-entry-shadow: -1px 0 1px 0 #ddd !default;
$menu-header-bg: #dde5e1 !default;
$menu-header-shadow: 0 0 1px 1px #c0c0c0 !default;
/// Toggle switch
$toggle-bg: #e0e8e0 !default;
$toggle-selected-bg: linear-gradient(90deg, #4fef97, #27ee5e) !default;
$toggle-dot-bg: #d4d8d6 !default;
$toggle-shadow: inset 0 0 2px 1px #c8c8c8 !default;
$toggle-dot-shadow: inset 0 0 2px 1px #d0d0d0 !default;
$toggle-selected-dot-bg: linear-gradient(160deg, #ecfff0, #e4fff8);

View File

@ -23,10 +23,10 @@ export default {
axios.post('/execute', request, opts)
.then((response) => {
response = response.data.response
if (!response.errors.length) {
if (!response.errors?.length) {
resolve(response.output);
} else {
const error = response.errors[0]
const error = response.errors?.[0] || response
this.notify({
text: error,
error: true,

View File

@ -1,7 +1,8 @@
<template>
<main>
<Loading v-if="loading" />
<Nav :panels="components" :selected-panel="selectedPanel" :hostname="hostname" @select="selectedPanel = $event" />
<Nav :panels="components" :selected-panel="selectedPanel" :hostname="hostname"
@select="selectedPanel = $event" v-else />
<div class="panel-container">
<div class="panel" v-for="(panel, name) in components" :key="name">
@ -35,6 +36,16 @@ export default {
},
methods: {
initSelectedPanel() {
const match = this.$route.hash.match('#?([a-zA-Z0-9.]+)[?]?(.*)')
if (!match)
return
const plugin = match[1]
if (plugin?.length)
this.selectedPanel = plugin
},
initPanels() {
const self = this
this.components = {}
@ -75,6 +86,7 @@ export default {
try {
await this.parseConfig()
this.initPanels()
this.initSelectedPanel()
} finally {
this.loading = false
}
@ -87,9 +99,18 @@ main {
height: 100%;
display: flex;
.panel-container {
display: flex;
flex-grow: 100;
}
.panel {
width: 100%;
height: 100%;
display: flex;
margin: 0 !important;
box-shadow: none !important;
overflow: auto;
}
}
</style>