Support for entities with children on the frontend

This commit is contained in:
Fabio Manganiello 2023-01-01 23:06:40 +01:00
parent 772ba6adb0
commit 80c2c77272
Signed by: blacklight
GPG key ID: D90FBA7F76362774
5 changed files with 171 additions and 31 deletions

View file

@ -0,0 +1,37 @@
<template>
<div class="entity device-container">
<div class="head">
<div class="col-1 icon">
<EntityIcon
:icon="value.meta?.icon || {}"
:loading="loading"
:error="error" />
</div>
<div class="col-12 label">
<div class="name" v-text="value.name" />
</div>
</div>
</div>
</template>
<script>
import EntityMixin from "./EntityMixin"
import EntityIcon from "./EntityIcon"
export default {
name: 'Device',
components: {EntityIcon},
mixins: [EntityMixin],
}
</script>
<style lang="scss" scoped>
@import "common";
.device-container {
.icon {
margin-right: 1em;
}
}
</style>

View file

@ -1,5 +1,9 @@
<template> <template>
<div class="row item entity-container" :class="{blink: justUpdated}"> <div class="entity-container-wrapper"
:class="{'with-children': hasChildren, collapsed: isCollapsed}">
<div class="row item entity-container"
:class="{blink: justUpdated, 'with-children': hasChildren, collapsed: isCollapsed}">
<div class="adjuster" :class="{'col-12': !hasChildren, 'col-11': hasChildren}">
<component :is="component" <component :is="component"
:value="value" :value="value"
:loading="loading" :loading="loading"
@ -8,6 +12,23 @@
@loading="$emit('loading', $event)" @loading="$emit('loading', $event)"
/> />
</div> </div>
<div class="col-1 collapse-toggler" @click.stop="collapsed = !collapsed" v-if="hasChildren">
<i class="fas"
:class="{'fa-chevron-down': isCollapsed, 'fa-chevron-up': !isCollapsed}" />
</div>
</div>
<div class="children" v-if="!isCollapsed">
<div class="child" v-for="entity in computedChildren" :key="entity.id">
<Entity
:value="entity"
:loading="loading"
:level="level + 1"
@input="$emit('input', entity)" />
</div>
</div>
</div>
</template> </template>
<script> <script>
@ -22,10 +43,28 @@ export default {
data() { data() {
return { return {
component: null, component: null,
collapsed: true,
justUpdated: false, justUpdated: false,
} }
}, },
computed: {
computedChildren() {
return Object.values(this.children || {}).filter((child) => child)
},
hasChildren() {
return !!this.computedChildren.length
},
isCollapsed() {
if (!this.hasChildren)
return true
return this.collapsed
},
},
methods: { methods: {
valuesEqual(a, b) { valuesEqual(a, b) {
a = {...a} a = {...a}
@ -68,10 +107,47 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "common"; @import "common";
.entity-container-wrapper {
&.with-children:not(.collapsed) {
box-shadow: 0 3px 4px 0 $default-shadow-color;
}
}
.entity-container { .entity-container {
width: 100%; width: 100%;
display: flex;
align-items: center;
position: relative; position: relative;
padding: 0 !important; padding: 0 !important;
border-bottom: $default-border-3;
&.with-children:not(.collapsed) {
background: $selected-bg;
font-weight: bold;
box-shadow: 0 0 3px 2px $default-shadow-color;
}
&:hover {
background: $hover-bg;
}
.collapse-toggler {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
min-height: 3em;
margin-left: 0;
cursor: pointer;
&:hover {
color: $default-hover-fg;
}
}
.adjuster {
cursor: pointer;
}
} }
.blink { .blink {

View file

@ -20,6 +20,16 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
children: {
type: Object,
default: () => {},
},
level: {
type: Number,
default: 0,
},
}, },
data() { data() {

View file

@ -49,10 +49,12 @@
v-for="entity in group.entities" :key="entity.id"> v-for="entity in group.entities" :key="entity.id">
<Entity <Entity
:value="entity" :value="entity"
@input="onEntityInput" :children="childrenByParentId(entity.id)"
@input="onEntityInput(entity)"
:error="!!errorEntities[entity.id]" :error="!!errorEntities[entity.id]"
:loading="!!loadingEntities[entity.id]" :loading="!!loadingEntities[entity.id]"
@loading="loadingEntities[entity.id] = $event" @loading="loadingEntities[entity.id] = $event"
v-if="!entity.parent_id"
/> />
</div> </div>
</div> </div>
@ -135,11 +137,14 @@ export default {
}, },
displayGroups() { displayGroups() {
return Object.entries(this.entityGroups[this.selector.grouping]).filter( return Object.entries(this.entityGroups[this.selector.grouping]).
filter(
(entry) => entry[1].filter( (entry) => entry[1].filter(
(e) => !!this.selector.selectedEntities[e.id] (e) =>
!!this.selector.selectedEntities[e.id] && e.parent_id == null
).length > 0 ).length > 0
).sort((a, b) => a[0].localeCompare(b[0])).map( ).
map(
([grouping, entities]) => { ([grouping, entities]) => {
return { return {
name: grouping, name: grouping,
@ -148,13 +153,16 @@ export default {
), ),
} }
} }
) ).
sort((a, b) => a.name.localeCompare(b.name))
}, },
}, },
methods: { methods: {
groupEntities(attr) { groupEntities(attr) {
return Object.values(this.entities).reduce((obj, entity) => { return Object.values(this.entities).
filter((entity) => entity.parent_id == null).
reduce((obj, entity) => {
const entities = obj[entity[attr]] || {} const entities = obj[entity[attr]] || {}
entities[entity.id] = entity entities[entity.id] = entity
@ -223,6 +231,15 @@ export default {
} }
}, },
childrenByParentId(parentId) {
return Object.values(this.entities).
filter((entity) => entity.parent_id === parentId).
reduce((obj, entity) => {
obj[entity.id] = entity
return obj
}, {})
},
clearEntityTimeouts(entityId) { clearEntityTimeouts(entityId) {
if (this.errorEntities[entityId]) if (this.errorEntities[entityId])
delete this.errorEntities[entityId] delete this.errorEntities[entityId]

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="entity sensor-container" :class="{hidden: value.value == null}"> <div class="entity sensor-container">
<div class="head"> <div class="head">
<div class="col-1 icon"> <div class="col-1 icon">
<EntityIcon <EntityIcon