Skip to content

Commit

Permalink
Set unit metadata & state description pattern when creating UoM Item (o…
Browse files Browse the repository at this point in the history
…penhab#2126)

Related to openhab/openhab-core#3481.
Closes openhab#2108.

- Ensures that the internal unit is set when an Item is created, so that
in case the system unit changes (i.e. measurement system changes)
persisted data does not get corrupted.
By default, the unit metadata is set to the system default unit, however
the user can easily change that on Item creation.
  
The unit can also be changed later as it is just normal metadata,
however this can corrupt persisted data.
- Adds the ability to set state description pattern when creating a UoM
Item.
- Shows the group type for groups, e.g. `Group (Number:Temperature)`
instead of just the Item type.

This applies to both the Items and the model page.

---------

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
  • Loading branch information
florian-h05 authored Dec 5, 2023
1 parent 18e93f5 commit 617f70b
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 30 deletions.
28 changes: 21 additions & 7 deletions bundles/org.openhab.ui/web/src/components/item/group-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
<f7-list inline-labels no-hairlines-md>
<f7-list-item v-if="item.type === 'Group'" title="Members Base Type" smart-select :smart-select-params="{openIn: 'popup', closeOnSelect: true}">
<select name="select-basetype" @change="setGroupType($event.target.value)">
<option v-for="type in types.GroupTypes" :key="type" :value="type" :selected="type === item.groupType.split(':')[0]">
<option v-for="type in types.GroupTypes" :key="type" :value="type" :selected="item.groupType ? type === item.groupType.split(':')[0] : false">
{{ type }}
</option>
</select>
</f7-list-item>
<f7-list-item v-if="dimensions.length && item.groupType && item.groupType.startsWith('Number')" title="Dimension" type="text" smart-select :smart-select-params="{searchbar: true, openIn: 'popup', closeOnSelect: true}">
<select name="select-dimension" @change="setGroupType($event.target.value)">
<option key="Number" value="Number" :selected="item.type === 'Number'">
&nbsp;
</option>
<option v-for="d in dimensions" :key="d.name" :value="'Number:' + d.name" :selected="'Number:' + d.name === item.groupType">
<select name="select-dimension" @change="setDimension($event.target.value)">
<option key="Number" value="Number" :selected="item.type === 'Number'" />
<option v-for="(d, i) in dimensions" :key="d.name" :value="i" :selected="'Number:' + d.name === item.groupType">
{{ d.label }}
</option>
</select>
</f7-list-item>
<f7-list-input v-if="item.groupType && item.groupType.startsWith('Number:') && createMode" label="Unit" type="text" :value="item.unit"
info="Used internally, for persistence and external systems. It is independent from the state visualization in the UI, which is defined through the state description."
@input="item.unit = $event.target.value" clear-button />
<f7-list-input v-if="item.type && item.type.startsWith('Number:') && createMode" label="State Description Pattern" type="text" :value="item.stateDescriptionPattern"
info="Pattern or transformation applied to the state for display purposes."
@input="item.stateDescriptionPattern = $event.target.value" clear-button />
<f7-list-item key="function-picker-arithmetic" v-if="item.type === 'Group' && item.groupType && (['Dimmer', 'Rollershutter'].indexOf(item.groupType) >= 0 || item.groupType.indexOf('Number') === 0)" title="Aggregation Function" smart-select :smart-select-params="{openIn: 'popover', closeOnSelect: true}">
<select name="select-function" @change="setFunction($event.target.value)">
<option v-for="type in types.ArithmeticFunctions" :key="type.name" :value="type.name" :selected="type.name === item.functionKey">
Expand Down Expand Up @@ -64,7 +68,7 @@ import uomMixin from '@/components/item/uom-mixin'
export default {
mixins: [uomMixin],
props: ['item'],
props: ['item', 'createMode'],
data () {
return {
types
Expand All @@ -88,6 +92,16 @@ export default {
if (type !== 'None') this.$set(this.item, 'groupType', type)
})
},
setDimension (index) {
if (index === 'Number') {
this.setGroupType('Number')
return
}
const dimension = this.dimensions[index]
this.setGroupType('Number:' + dimension.name)
this.$set(this.item, 'unit', dimension.systemUnit)
this.$set(this.item, 'stateDescriptionPattern', `%.0f ${dimension.systemUnit}`)
},
setFunction (key) {
if (!key) {
delete this.item.function
Expand Down
33 changes: 28 additions & 5 deletions bundles/org.openhab.ui/web/src/components/item/item-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
</select>
</f7-list-item>
<f7-list-item v-if="dimensions.length && item.type && !hideType && item.type.startsWith('Number')" title="Dimension" type="text" smart-select :smart-select-params="{searchbar: true, openIn: 'popup', closeOnSelect: true}">
<select name="select-dimension" @change="item.type = $event.target.value">
<option key="Number" value="Number" :selected="item.type === 'Number'">
&nbsp;
</option>
<option v-for="d in dimensions" :key="d.name" :value="'Number:' + d.name" :selected="'Number:' + d.name === item.type">
<select name="select-dimension" @change="setDimension($event.target.value)">
<option key="Number" value="Number" :selected="item.type === 'Number'" />
<option v-for="(d, i) in dimensions" :key="d.name" :value="i" :selected="'Number:' + d.name === item.type">
{{ d.label }}
</option>
</select>
</f7-list-item>
<!-- Use v-show instead of v-if, because otherwise the autocomplete for category would take over the unit -->
<f7-list-input v-show="!hideType && item.type && item.type.startsWith('Number:') && createMode" label="Unit" type="text" :value="item.unit"
info="Used internally, for persistence and external systems. It is independent from the state visualization in the UI, which is defined through the state description."
@input="item.unit = $event.target.value" clear-button />
<f7-list-input v-show="!hideType && item.type && item.type.startsWith('Number:') && createMode" label="State Description Pattern" type="text" :value="item.stateDescriptionPattern"
info="Pattern or transformation applied to the state for display purposes."
@input="item.stateDescriptionPattern = $event.target.value" clear-button />
<f7-list-input v-if="!hideCategory" ref="category" label="Category" autocomplete="off" type="text" placeholder="temperature, firstfloor..." :value="item.category"
@input="item.category = $event.target.value" clear-button>
<div slot="root-end" style="margin-left: calc(35% + 8px)">
Expand Down Expand Up @@ -79,7 +84,25 @@ export default {
return this.getNonSemanticTags(this.item).length
}
},
watch: {
// Required for pre-filling unit and state description pattern fields in "Add Items from Thing" functionality
dimensions () {
if (this.createMode && this.item.type && this.item.type.startsWith('Number:')) {
this.setDimension(this.dimensions.findIndex((d) => d.name === this.item.type.split(':')[1]))
}
}
},
methods: {
setDimension (index) {
if (index === 'Number') {
this.$set(this.item, 'type', 'Number')
return
}
const dimension = this.dimensions[index]
this.$set(this.item, 'type', 'Number:' + dimension.name)
this.$set(this.item, 'unit', dimension.systemUnit)
this.$set(this.item, 'stateDescriptionPattern', `%.0f ${dimension.systemUnit}`)
},
initializeAutocomplete (inputElement) {
this.categoryAutocomplete = this.$f7.autocomplete.create({
inputEl: inputElement,
Expand Down
45 changes: 45 additions & 0 deletions bundles/org.openhab.ui/web/src/components/item/item-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export default {
methods: {
getItemTypeAndMetaLabel (item) {
let ret = item.type
if (item.type === 'Group') {
ret += ` (${item.groupType})`
}
if (item.metadata && item.metadata.semantics) {
ret += ' · '
const classParts = item.metadata.semantics.value.split('_')
Expand All @@ -24,6 +27,48 @@ export default {
getNonSemanticTags (item) {
if (!item.tags) return []
return item.tags.filter((t) => !this.isSemanticTag(t))
},
doSave (item) {
console.log(item)

if (item.groupType === 'None') delete item.groupType
if (item.function === 'None') delete item.groupType

const unit = item.unit
delete item.unit
const stateDescriptionPattern = item.stateDescriptionPattern
delete item.stateDescriptionPattern

// TODO: Add support for saving metadata
return this.$oh.api.put('/rest/items/' + item.name, item).then(() => {
let unitPromise = Promise.resolve()
if (this.createMode && (item.type.startsWith('Number:') || item.groupType?.startsWith('Number:')) && unit) {
const metadata = {
value: unit,
config: {}
}
unitPromise = this.$oh.api.put('/rest/items/' + item.name + '/metadata/unit', metadata)
}
return unitPromise
}).then(() => {
let stateDescriptionPromise = Promise.resolve()
if (this.createMode && (item.type.startsWith('Number:') || item.groupType?.startsWith('Number:')) && stateDescriptionPattern) {
if (stateDescriptionPattern !== `%.0f ${unit}`) {
const metadata = {
value: ' ',
config: {
pattern: stateDescriptionPattern
}
}
stateDescriptionPromise = this.$oh.api.put('/rest/items/' + item.name + '/metadata/stateDescription', metadata)
}
}
return stateDescriptionPromise
}).then(() => {
return Promise.resolve()
}).catch((err) => {
return Promise.reject(err)
})
}
}
}
3 changes: 2 additions & 1 deletion bundles/org.openhab.ui/web/src/components/item/uom-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export default {
data.uomInfo.dimensions.forEach((d) => {
this.dimensions.push({
name: d.dimension,
label: d.dimension + ' (' + d.systemUnit + ')'
label: d.dimension + ' (' + d.systemUnit + ')',
systemUnit: d.systemUnit
})
})
})
Expand Down
25 changes: 20 additions & 5 deletions bundles/org.openhab.ui/web/src/components/model/item-details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<div class="padding-top" v-else-if="createMode">
<item-form :item="editedItem" :items="items" :createMode="true" :force-semantics="forceSemantics" />
</div>
<div v-if="(createMode || editMode) && editedItem && editedItem.type === 'Group'">
<f7-block-title>Group Settings</f7-block-title>
<group-form :item="editedItem" :createMode="createMode" />
</div>
</f7-card-content>
<f7-card-footer v-if="createMode || editMode" key="item-card-buttons">
<f7-button v-if="createMode" color="blue" fill raised @click="create">
Expand All @@ -40,10 +44,15 @@
<script>
import Item from '@/components/item/item.vue'
import ItemForm from '@/components/item/item-form.vue'
import GroupForm from '@/components/item/group-form.vue'
import ItemMixin from '@/components/item/item-mixin'
export default {
mixins: [ItemMixin],
props: ['model', 'links', 'items', 'context'],
components: {
GroupForm,
Item,
ItemForm
},
Expand Down Expand Up @@ -91,35 +100,41 @@ export default {
},
save () {
this.editMode = false
this.$oh.api.put('/rest/items/' + this.editedItem.name, this.editedItem).then((data) => {
this.doSave(this.editedItem).then(() => {
this.$f7.toast.create({
text: 'Item updated',
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.$emit('item-updated', this.editedItem)
}).catch((err) => {
this.$f7.toast.create({
text: 'Item not saved: ' + err,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
this.$emit('item-updated', this.editedItem)
},
create () {
this.editMode = false
// TODO properly validate item
if (!this.editedItem.name) return
this.$oh.api.put('/rest/items/' + this.editedItem.name, this.editedItem).then((data) => {
this.doSave(this.editedItem).then(() => {
this.$f7.toast.create({
text: 'Item created',
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.$set(this.model, 'item', JSON.parse(data))
this.$set(this.model, 'item', this.editedItem)
this.model.item.created = true
this.model.item.editable = true
this.$emit('item-created', this.model.item)
this.onModelChange()
}).catch((err) => {
this.$f7.toast.create({
text: 'Item not created: ' + err,
text: 'Item not saved: ' + err,
destroyOnClose: true,
closeTimeout: 2000
}).open()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<h2>{{ item.label }}</h2>
<!-- <h4 v-show="item.label">{{item.name}}</h4> -->
<h5 v-show="item.type">
<small>{{ item.type }}</small>
<small>{{ item.type === 'Group' ? `${item.type} (${item.groupType})` : item.type }}</small>
</h5>
</f7-subnavbar>
</f7-navbar>
Expand Down
25 changes: 14 additions & 11 deletions bundles/org.openhab.ui/web/src/pages/settings/items/item-edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</f7-col>
<f7-col v-if="item && item.type === 'Group'">
<f7-block-title>Group Settings</f7-block-title>
<group-form :item="item" />
<group-form :item="item" :createMode="createMode" />
</f7-col>
</f7-block>
</f7-tab>
Expand Down Expand Up @@ -76,10 +76,12 @@ import YAML from 'yaml'
import ItemForm from '@/components/item/item-form.vue'
import GroupForm from '@/components/item/group-form.vue'
import ItemPicker from '@/components/config/controls/item-picker.vue'
import DirtyMixin from '../dirty-mixin'
import ItemMixin from '@/components/item/item-mixin'
export default {
mixins: [DirtyMixin],
mixins: [DirtyMixin, ItemMixin],
props: ['itemName', 'createMode'],
components: {
ItemPicker,
Expand Down Expand Up @@ -132,7 +134,6 @@ export default {
} else {
const loadItem = this.$oh.api.get('/rest/items/' + this.itemName + '?metadata=.*')
loadItem.then((data) => {
if (!data.groupType) data.groupType = 'None'
this.item = data
this.$nextTick(() => {
this.ready = true
Expand All @@ -159,12 +160,10 @@ export default {
if (this.currentTab === 'code') {
if (!this.fromYaml()) return Promise.reject()
}
if (!this.item.name) return // user cannot change name
if (!this.item.name) return this.$f7.dialog.alert('Please give Item a valid name').open() // user cannot change name
if (!this.item.type || !this.types.ItemTypes.includes(this.item.type.split(':')[0])) return this.$f7.dialog.alert('Please give Item a valid type').open()
if (this.item.groupType === 'None') delete this.item.groupType
// TODO: Add support for saving metadata
this.$oh.api.put('/rest/items/' + this.item.name, this.item).then((data) => {
this.doSave(this.item).then(() => {
if (this.createMode) {
this.$f7.toast.create({
text: 'Item created',
Expand All @@ -180,6 +179,7 @@ export default {
closeTimeout: 2000
}).open()
}
this.dirty = false
this.$f7router.back()
}).catch((err) => {
Expand All @@ -195,16 +195,19 @@ export default {
this.dirty = true
},
toYaml () {
this.itemYaml = YAML.stringify({
const yamlObj = {
label: this.item.label,
type: this.item.type,
category: this.item.category,
groupNames: this.item.groupNames,
groupType: this.item.groupType,
function: this.item.function,
tags: this.item.tags
// metadata: this.item.metadata
})
}
if (this.item.type === 'Group') {
yamlObj.groupType = this.item.groupType || 'None'
yamlObj.function = this.item.function || 'None'
}
this.itemYaml = YAML.stringify(yamlObj)
},
fromYaml () {
if (!this.item.editable) return false
Expand Down

0 comments on commit 617f70b

Please sign in to comment.