[light.hue] Support bridges with ungrouped lights.

This commit is contained in:
Fabio Manganiello 2024-12-20 22:28:03 +01:00
parent f3aa245c0e
commit 14f979049b
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
4 changed files with 221 additions and 145 deletions

View file

@ -3,7 +3,7 @@
<MenuPanel> <MenuPanel>
<div class="panel-row header"> <div class="panel-row header">
<div class="col-3" v-if="group"> <div class="col-3" v-if="group">
<button class="back-btn" title="Back" @click="close"> <button class="back-btn" title="Back" @click="close" v-if="withBackButton">
<i class="fas fa-chevron-left" /> <i class="fas fa-chevron-left" />
</button> </button>
</div> </div>
@ -12,7 +12,7 @@
v-text="groupName" @click="selectedView = selectedView === 'group' ? null : 'group'" /> v-text="groupName" @click="selectedView = selectedView === 'group' ? null : 'group'" />
<div class="col-3 pull-right" v-if="group"> <div class="col-3 pull-right" v-if="group">
<ToggleSwitch :value="group.state.any_on" @input="$emit('group-toggle', group)" /> <ToggleSwitch :value="anyLightsOn" @input="$emit('group-toggle', group)" />
</div> </div>
</div> </div>
@ -22,7 +22,7 @@
<div class="lights-view" v-else> <div class="lights-view" v-else>
<div class="row view-selector"> <div class="row view-selector">
<button :class="{selected: selectedView === 'lights'}" title="Lights" @click="selectedView = 'lights'"> <button :class="{selected: selectedView === 'lights'}" :title="title" @click="selectedView = 'lights'">
<i class="icon fas fa-lightbulb" /> <i class="icon fas fa-lightbulb" />
</button> </button>
<button :class="{selected: selectedView === 'scenes'}" title="Scenes" @click="selectedView = 'scenes'"> <button :class="{selected: selectedView === 'scenes'}" title="Scenes" @click="selectedView = 'scenes'">
@ -98,6 +98,11 @@ export default {
type: Object, type: Object,
}, },
title: {
type: String,
default: 'Lights',
},
animations: { animations: {
type: Object, type: Object,
default: () => {}, default: () => {},
@ -107,6 +112,11 @@ export default {
type: Object, type: Object,
default: () => new ColorConverter(), default: () => new ColorConverter(),
}, },
withBackButton: {
type: Boolean,
default: true,
},
}, },
data() { data() {
@ -118,6 +128,13 @@ export default {
}, },
computed: { computed: {
anyLightsOn() {
if (this.group?.state?.any_on != null)
return this.group.state.any_on
return Object.values(this.lights).some(light => light.state.on)
},
lightsSorted() { lightsSorted() {
if (!this.lights) if (!this.lights)
return [] return []
@ -151,7 +168,7 @@ export default {
return this.group.name return this.group.name
if (this.group?.id != null) if (this.group?.id != null)
return `[Group ${this.group.id}]` return `[Group ${this.group.id}]`
return 'Lights' return this.title
}, },
}, },
@ -169,90 +186,6 @@ export default {
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.light-group-container { @import "./groups.scss";
width: 100%;
min-height: 100%;
.row.panel-row {
flex-direction: column;
&.expanded,
&.selected {
background: $selected-bg;
}
}
.header {
padding: 0.5em !important;
display: flex;
align-items: center;
.back-btn {
border: 0;
background: none;
&:hover {
border: 0;
color: $default-hover-fg;
}
}
.name {
text-align: center;
&.selected {
color: $selected-fg;
}
&:hover {
color: $default-hover-fg;
}
}
}
.view-selector {
width: 100%;
border-radius: 0;
button {
width: 33.3%;
padding: 1.5em;
text-align: left;
opacity: 0.8;
box-shadow: $plugin-panel-entry-shadow;
border-right: 0;
&.selected {
background: $selected-bg;
}
&:hover {
background: $hover-bg;
}
}
.icon {
width: 100%;
text-align: center;
font-size: 1.2em;
}
}
}
</style>
<style lang="scss">
.light-group-container {
.group-controls {
margin: 0;
padding: 1em;
background-color: $default-bg-6;
border-radius: 0 0 1em 1em;
.controls {
margin: 0;
padding: 1em;
}
}
}
</style> </style>

View file

@ -1,27 +1,29 @@
<template> <template>
<MenuPanel> <div class="light-groups-container">
<div class="panel-row header"> <MenuPanel>
<div class="col-3"> <div class="panel-row header">
<i class="icon fas fa-home" /> <div class="col-3">
<i class="icon fas fa-home" />
</div>
<div class="col-6 name">
Rooms
</div>
<div class="col-3 pull-right">
<ToggleSwitch :value="anyLightsOn" @input="$emit('toggle')" />
</div>
</div> </div>
<div class="col-6 name">
Rooms
</div>
<div class="col-3 pull-right">
<ToggleSwitch :value="anyLightsOn" @input="$emit('toggle')" />
</div>
</div>
<div class="panel-row row group" v-for="group in groupsSorted" :key="group.id" @click="$emit('select', group.id)"> <div class="panel-row row group" v-for="group in groupsSorted" :key="group.id" @click="$emit('select', group.id)">
<span class="name col-9"> <span class="name col-9">
{{ group.name || `[Group ${group.id}]` }} {{ group.name || `[Group ${group.id}]` }}
</span> </span>
<span class="controls col-3 pull-right"> <span class="controls col-3 pull-right">
<ToggleSwitch :value="group.state.any_on" :disabled="group.id in (loadingGroups || {})" <ToggleSwitch :value="group.state.any_on" :disabled="group.id in (loadingGroups || {})"
@input="$emit('toggle', group)" /> @input="$emit('toggle', group)" />
</span> </span>
</div> </div>
</MenuPanel> </MenuPanel>
</div>
</template> </template>
<script> <script>
@ -74,23 +76,5 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.header { @import "./groups.scss";
display: flex;
align-items: center;
padding-top: 0.75em !important;
padding-bottom: 0.75em !important;
.icon {
margin-left: 0.5em;
}
.name {
text-align: center;
}
}
.group {
display: flex;
align-items: center;
}
</style> </style>

View file

@ -0,0 +1,105 @@
.light-group-container {
width: 100%;
min-height: 100%;
.row.panel-row {
flex-direction: column;
&.expanded,
&.selected {
background: $selected-bg;
}
}
.header {
padding: 0.5em !important;
display: flex;
align-items: center;
.back-btn {
border: 0;
background: none;
&:hover {
border: 0;
color: $default-hover-fg;
}
}
.name {
text-align: center;
&.selected {
color: $selected-fg;
}
&:hover {
color: $default-hover-fg;
}
}
}
.view-selector {
width: 100%;
border-radius: 0;
button {
width: 33.3%;
padding: 1.5em;
text-align: left;
opacity: 0.8;
box-shadow: $plugin-panel-entry-shadow;
border-right: 0;
&.selected {
background: $selected-bg;
}
&:hover {
background: $hover-bg;
}
}
.icon {
width: 100%;
text-align: center;
font-size: 1.2em;
}
}
}
:deep(.light-group-container) {
.group-controls {
margin: 0;
padding: 1em;
background-color: $default-bg-6;
border-radius: 0 0 1em 1em;
.controls {
margin: 0;
padding: 1em;
}
}
}
.light-groups-container {
.header {
display: flex;
align-items: center;
padding-top: 0.75em !important;
padding-bottom: 0.75em !important;
.icon {
margin-left: 0.5em;
}
.name {
text-align: center;
}
}
.group {
display: flex;
align-items: center;
}
}

View file

@ -1,17 +1,46 @@
<template> <template>
<div class="plugin lights-plugin"> <div class="plugin lights-plugin">
<div class="panel" v-if="selectedGroup == null && groups && Object.keys(groups).length"> <div class="panel">
<Groups :groups="groups" :loading-groups="loadingGroups" :color-converter="colorConverter" <div class="groups lights-container" v-if="selectedGroup == null && Object.keys(groups || {}).length">
@select="selectedGroup = $event" @toggle="$emit('group-toggle', $event)" /> <Groups :groups="groups"
</div> :loading-groups="loadingGroups"
<div class="panel" v-else> :color-converter="colorConverter"
<Group :group="groups[selectedGroup]" :lights="displayedLights" :scenes="scenesByGroup[selectedGroup]" @select="selectedGroup = $event"
:color-converter="colorConverter" :animations="animationsByGroup[selectedGroup]" @close="selectedGroup = null" @toggle="$emit('group-toggle', $event)" />
@light-toggle="$emit('light-toggle', $event)" @group-toggle="$emit('group-toggle', $event)" </div>
@set-light="$emit('set-light', $event)"
@set-group="$emit('set-group', {groupId: selectedGroup, value: $event})" <div class="lights-container ungrouped-lights"
@select-scene="$emit('select-scene', {groupId: selectedGroup, sceneId: $event})" v-if="Object.keys(ungroupedLights || {}).length && selectedGroup == null">
@start-animation="$emit('start-animation', $event)" @stop-animation="$emit('stop-animation', $event)" /> <Group :group="ungroupedLights"
:lights="ungroupedLights"
:scenes="scenesByGroup[selectedGroup]"
:color-converter="colorConverter"
:animations="animationsByGroup[selectedGroup]"
:with-back-button="false"
title="Ungrouped Lights"
@close="selectedGroup = null"
@light-toggle="$emit('light-toggle', $event)"
@select-scene="$emit('select-scene', {groupId: selectedGroup, sceneId: $event})"
@set-light="$emit('set-light', $event)"
@start-animation="$emit('start-animation', $event)"
@stop-animation="$emit('stop-animation', $event)" />
</div>
<div class="group" v-if="groups?.[selectedGroup]">
<Group :group="groups[selectedGroup]"
:lights="displayedLights"
:scenes="scenesByGroup[selectedGroup]"
:color-converter="colorConverter"
:animations="animationsByGroup[selectedGroup]"
@close="selectedGroup = null"
@group-toggle="$emit('group-toggle', $event)"
@light-toggle="$emit('light-toggle', $event)"
@select-scene="$emit('select-scene', {groupId: selectedGroup, sceneId: $event})"
@set-group="$emit('set-group', {groupId: selectedGroup, value: $event})"
@set-light="$emit('set-light', $event)"
@start-animation="$emit('start-animation', $event)"
@stop-animation="$emit('stop-animation', $event)" />
</div>
</div> </div>
</div> </div>
</template> </template>
@ -27,9 +56,12 @@ import {ColorConverter} from "@/components/panels/Light/color";
* Generic component for light plugins panels. * Generic component for light plugins panels.
*/ */
export default { export default {
name: "Light",
components: {Group, Groups},
mixins: [Utils, Panel], mixins: [Utils, Panel],
components: {
Group,
Groups,
},
emits: [ emits: [
'group-toggle', 'group-toggle',
'light-changed', 'light-changed',
@ -113,6 +145,15 @@ export default {
}, {}) }, {})
}, },
ungroupedLights() {
return Object.keys(this.lights || {})
.filter((lightId) => !this.groupsByLight[lightId])
.reduce((obj, lightId) => {
obj[lightId] = this.lights[lightId]
return obj
}, {})
},
scenesByGroup() { scenesByGroup() {
if (!this.scenes) if (!this.scenes)
return {} return {}
@ -120,10 +161,16 @@ export default {
const self = this const self = this
return Object.entries(this.scenes).reduce((obj, [sceneId, scene]) => { return Object.entries(this.scenes).reduce((obj, [sceneId, scene]) => {
scene.lights.forEach((lightId) => { scene.lights.forEach((lightId) => {
if (!self.groupsByLight[lightId]) {
if (!obj[-1])
obj[-1] = {}
obj[-1][sceneId] = scene
return
}
Object.keys(self.groupsByLight[lightId]).forEach((groupId) => { Object.keys(self.groupsByLight[lightId]).forEach((groupId) => {
if (!obj[groupId]) if (!obj[groupId])
obj[groupId] = {} obj[groupId] = {}
obj[groupId][sceneId] = scene obj[groupId][sceneId] = scene
}) })
}) })
@ -152,6 +199,10 @@ export default {
obj[group.id] = {} obj[group.id] = {}
obj[group.id][lightId] = animation obj[group.id][lightId] = animation
} }
} else {
if (!obj[-1])
obj[-1] = {}
obj[-1][lightId] = animation
} }
return obj return obj
@ -229,6 +280,7 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column;
} }
.panel { .panel {
@ -240,6 +292,8 @@ export default {
</style> </style>
<style lang="scss"> <style lang="scss">
@import "@/components/Light/groups.scss";
.lights-plugin { .lights-plugin {
.menu-panel { .menu-panel {
ul { ul {