Item metadata: add expire editor, change default widget logic (#540)

Disallow standalone widgets to be selected for list items (close #536).

Allow slight alterations to the default widgets suggested by the system
without reconfiguring it completely: if the metadata value is blank,
assume the default system-suggested widget is to be used and merge the
config found in the metadata.
Closes #534 (since now only the iconUseState option has to be set to
enable dynamic icons for Number items and similar).

Change the metadata namespace selection menu to only display the metadata
which is set in the metadata menu, and use an action sheet to add more.
This also paves the way for user-defined namespaces.

Move the metadata order out of the item edition card in the Model page,
into its own section.

Add metadata editor for the expire namespace
(https://github.com/openhab/openhab-core/issues/1620).

Reorder the sections of the item details page in a more logical manner
(semantic classification below the tags that define it).

Signed-off-by: Yannick Schaus <github@schaus.net>
pull/544/head
Yannick Schaus 2020-11-19 21:02:06 +01:00 committed by GitHub
parent cd66d064a7
commit c3b7f47569
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 333 additions and 78 deletions

View File

@ -6,6 +6,7 @@ export default [
{ name: 'listWidget', label: 'Default List Item Widget' },
{ name: 'cellWidget', label: 'Default Cell Widget' },
{ name: 'autoupdate', label: 'Auto-update' },
{ name: 'expire', label: 'Expiration timer' },
{ name: 'alexa', label: 'Amazon Alexa' },
{ name: 'homekit', label: 'Apple HomeKit' },
{ name: 'ga', label: 'Google Assistant' }

View File

@ -0,0 +1,166 @@
<template>
<div>
<f7-block-title medium>Do</f7-block-title>
<f7-list>
<f7-list-item radio :checked="parsedAction.action === 'state'" name="action" title="update state" @click="updateAction('state')" />
<f7-list-item radio :checked="parsedAction.action === 'command'" name="action" title="send command" @click="updateAction('command')" />
<f7-list-input
:label="parsedAction.action === 'command' ? 'Command' : 'State'"
name="value"
ref="value"
type="text"
placeholder="UNDEF if unset"
:value="parsedAction.value"
@blur="(evt) => updateActionValue(evt.target.value)"
/>
</f7-list>
<f7-block-footer class="param-description padding-left">
<small>After a different command or state update is received, perform the chosen action when the duration specified below has passed. The timer is reset if another state update or command is received before it expires.</small>
</f7-block-footer>
<f7-block-title medium>After</f7-block-title>
<f7-list>
<f7-list-input
:floating-label="$theme.md"
label="Expiration Delay"
name="timer"
ref="duration"
type="text"
:value="sanitizedDuration"
@blur="(evt) => updateDuration(evt.target.value)"
pattern="(\d+h)*(\d+m)*(\d+s)*" validate validate-on-blur>
</f7-list-input>
<f7-list-item class="display-flex justify-content-center">
<div ref="picker" />
</f7-list-item>
</f7-list>
<f7-block-footer class="param-description padding-left">
<small>Delay to wait before the timer expires and the action specified above is performed.</small>
</f7-block-footer>
</div>
</template>
<script>
export default {
props: ['itemName', 'metadata', 'namespace'],
data () {
return {
}
},
computed: {
sanitizedDuration () {
return this.sanitizeDuration(this.metadata.value)
},
sanitizedAction () {
if (!this.metadata.value) return ''
let action = this.metadata.value.split(',')[1]
if (!action) return ''
return action.trim().replace(/\s/g, '')
},
parsedTimerParts () {
if (!this.sanitizedDuration) return ['0', '0', '0']
let match = this.sanitizedDuration.match(/(\d+h)*(\d+m)*(\d+s)*/)
let hours = (match[1]) ? match[1].replace('h', '') : '0'
let minutes = (match[2]) ? match[2].replace('m', '') : '0'
let seconds = (match[3]) ? match[3].replace('s', '') : '0'
return [hours, minutes, seconds]
},
parsedAction () {
if (!this.sanitizedAction) return { action: 'state', value: '' }
const action = this.sanitizedAction.indexOf('command=') === 0 ? 'command' : 'state'
const value = this.sanitizedAction.replace('state=', '').replace('command=', '')
return { action, value }
}
},
mounted () {
const self = this
const inputControl = this.$refs.duration
const containerControl = this.$refs.picker
if (!inputControl || !inputControl.$el || !containerControl) return
const inputElement = this.$$(inputControl.$el).find('input')
this.picker = this.$f7.picker.create({
containerEl: containerControl,
inputEl: inputElement,
toolbar: false,
inputReadOnly: false,
rotateEffect: true,
value: this.parsedTimerParts,
formatValue: function (values, displayValues) {
return displayValues[0] + 'h' + displayValues[1] + 'm' + displayValues[2] + 's'
},
cols: [
// Hours
{
values: (function () {
var arr = []
for (var i = 0; i <= 99; i++) { arr.push(i.toString()) }
return arr
})()
},
// Divider
{
divider: true,
content: 'h'
},
// Minutes
{
values: (function () {
var arr = []
for (var i = 0; i <= 59; i++) { arr.push(i.toString()) }
return arr
})()
},
// Divider
{
divider: true,
content: 'm'
},
// Seconds
{
values: (function () {
var arr = []
for (var i = 0; i <= 59; i++) { arr.push(i.toString()) }
return arr
})()
},
// Divider
{
divider: true,
content: 's'
}
],
on: {
change: function (picker, values, displayValues) {
self.updateDuration(displayValues[0] + 'h' + displayValues[1] + 'm' + displayValues[2] + 's')
}
}
})
},
watch: {
parsedTimerParts (val) {
this.picker.setValue(val)
}
},
methods: {
sanitizeDuration (value) {
if (!value) return ''
return value.split(',')[0].trim().replace(/\s/g, '')
},
updateDuration (value) {
if (!value) return
this.metadata.value = this.sanitizeDuration(value) + ((this.sanitizedAction) ? ',' + this.sanitizedAction : '')
},
updateAction (value) {
if (!value) return
const action = ((value === 'command') ? 'command=' : '') + this.parsedAction.value
this.metadata.value = this.sanitizedDuration + ((action) ? ',' + action : '')
},
updateActionValue (value) {
if (!value) return
const action = ((this.parsedAction.action === 'command') ? 'command=' : '') + value.trim()
this.metadata.value = this.sanitizedDuration + ((action) ? ',' + action : '')
}
}
}
</script>

View File

@ -1,13 +1,21 @@
<template>
<f7-list>
<f7-list-item
v-for="namespace in metadataNamespaces" :key="namespace.name"
:link="'/settings/items/' + item.name + '/metadata/' + namespace.name"
:title="namespace.label"
:after="(item.metadata && item.metadata[namespace.name]) ? item.metadata[namespace.name].value : 'Not Set'"
/>
<f7-list-button color="blue" @click="editCustomMetadata">Edit Custom Metadata</f7-list-button>
</f7-list>
<f7-card>
<f7-card-content v-if="item.metadata && Object.keys(item.metadata).filter((n) => n !== 'semantics').length > 0">
<f7-list>
<ul>
<f7-list-item
v-for="namespace in metadataNamespaces.filter((n) => item.metadata[n.name])" :key="namespace.name"
:link="'/settings/items/' + item.name + '/metadata/' + namespace.name"
:title="namespace.label"
:after="(item.metadata[namespace.name]) ? item.metadata[namespace.name].value : 'Not Set'"
/>
</ul>
</f7-list>
</f7-card-content>
<f7-card-footer>
<f7-button color="blue" @click="addMetadata">Add Metadata</f7-button>
</f7-card-footer>
</f7-card>
</template>
<script>
@ -27,6 +35,31 @@ export default {
(namespace) => {
if (namespace) this.$f7.views.main.router.navigate('/settings/items/' + this.item.name + '/metadata/' + namespace)
})
},
addMetadata () {
this.$f7.actions.create({
buttons: [
[
{ label: true, text: 'Well-known namespaces' },
...MetadataNamespaces.map((n) => {
return {
text: n.label,
color: 'blue',
onClick: () => {
this.$f7router.navigate('/settings/items/' + this.item.name + '/metadata/' + n.name)
}
}
})
],
[
{ label: true, text: 'Custom namespaces' },
{ color: 'blue', text: 'Enter Custom Namespace...', onClick: this.editCustomMetadata }
],
[
{ color: 'red', text: 'Cancel', close: true }
]
]
}).open()
}
}
}

View File

@ -6,23 +6,23 @@
</f7-col>
</f7-block>
<f7-list v-if="viewMode === 'design'">
<f7-list v-if="viewMode === 'design' && defaultComponent.component">
<f7-list-item :key="componentSelectKey"
:title="'Widget'" smart-select :smart-select-params="{ openIn: 'popup', searchbar: true, closeOnSelect: true }" ref="widgets">
<select name="widgets" @change="updateComponent">
<option value=""></option>
<optgroup v-if="$store.getters.widgets.length" label="Personal Widgets">
<option v-for="widget in $store.getters.widgets" :value="'widget:' + widget.uid" :key="widget.uid" :selected="metadata.value.replace('widget:', '') === widget.uid">{{widget.uid}}</option>
</optgroup>
<option value="">Default ({{defaultComponent.component}})</option>
<optgroup label="Standard Library (List)" v-if="namespace === 'listWidget'">
<option v-for="widget in standardListWidgets" :key="widget.name" :value="widget.name" :selected="metadata.value === widget.name">{{widget.label}}</option>
</optgroup>
<optgroup label="Standard Library (Cell)" v-if="namespace === 'cellWidget'">
<optgroup label="Standard Library (Cell)" v-else-if="namespace === 'cellWidget'">
<option v-for="widget in standardCellWidgets" :key="widget.name" :value="widget.name" :selected="metadata.value === widget.name">{{widget.label}}</option>
</optgroup>
<optgroup label="Standard Library" v-else>
<option v-for="widget in standardWidgets" :key="widget.name" :value="widget.name" :selected="metadata.value === widget.name">{{widget.label}}</option>
</optgroup>
<optgroup v-if="$store.getters.widgets.length" label="Personal Widgets">
<option v-for="widget in $store.getters.widgets" :value="'widget:' + widget.uid" :key="widget.uid" :selected="metadata.value.replace('widget:', '') === widget.uid">{{widget.uid}}</option>
</optgroup>
<!-- <optgroup label="System Widgets">
<option v-for="widget in systemWidgets" :key="widget.name" :value="widget.name">{{widget.label}}</option>
</optgroup> -->
@ -31,8 +31,9 @@
</f7-list>
<div v-if="viewMode === 'design' && configDescriptions.parameters" class="widget-metadata-config-sheet">
<f7-block-title>Configuration</f7-block-title>
<f7-block-footer class="padding-horizontal">Note: a parameter named 'item' will be set automatically with the name of this item - no need to specify it here.</f7-block-footer>
<config-sheet :parameterGroups="configDescriptions.parameterGroups || []" :parameters="configDescriptions.parameters || []" :configuration="metadata.config" />
<f7-block-footer class="padding-horizontal margin-bottom">Note: the parameter named 'item' will be set automatically with the name of the item ({{this.item.name}}) unless it's set explicitely.</f7-block-footer>
<f7-block-footer v-if="currentComponent.component && currentComponent.component.indexOf('widget:') === 0" class="padding-horizontal margin-bottom">Make sure the personal widget is of the expected type (cell, list item or standalone).</f7-block-footer>
<config-sheet :parameterGroups="configDescriptions.parameterGroups" :parameters="configDescriptions.parameters" :configuration="metadata.config" />
</div>
<div v-if="viewMode === 'preview'">
<generic-widget-component v-if="previewContext.component" :context="previewContext" />
@ -68,7 +69,7 @@ export default {
data () {
return {
viewMode: 'design',
previewOpened: false,
defaultComponent: {},
componentSelectKey: this.$f7.utils.id(),
standardWidgets: Object.values(StandardWidgets).filter((c) => c.widget).map((c) => c.widget()),
standardListWidgets: Object.values(StandardListWidgets).filter((c) => c.widget && typeof c.widget === 'function').map((c) => c.widget()),
@ -79,39 +80,70 @@ export default {
},
computed: {
configDescriptions () {
if (!this.metadata.value) return {}
const widget = this.$store.getters.widgets.find((w) => w.uid === this.metadata.value.replace('widget:', ''))
if (widget && widget.props) return widget.props
let ret = {}
if (!this.currentComponent || !this.currentComponent.component) return ret
const widget = this.$store.getters.widgets.find((w) => w.uid === this.currentComponent.component.replace('widget:', ''))
if (widget && widget.props) ret = Object.assign({}, widget.props)
if (this.namespace === 'listWidget') {
const standardListItemWidget = this.standardListWidgets.find((w) => w.name === this.metadata.value)
if (standardListItemWidget && standardListItemWidget.props) return standardListItemWidget.props
const standardListItemWidget = this.standardListWidgets.find((w) => w.name === this.currentComponent.component)
if (standardListItemWidget && standardListItemWidget.props) ret = Object.assign({}, standardListItemWidget.props)
} else if (this.namespace === 'cellWidget') {
const standardCellWidget = this.standardCellWidgets.find((w) => w.name === this.metadata.value)
if (standardCellWidget && standardCellWidget.props) return standardCellWidget.props
const standardCellWidget = this.standardCellWidgets.find((w) => w.name === this.currentComponent.component)
if (standardCellWidget && standardCellWidget.props) ret = Object.assign({}, standardCellWidget.props)
} else {
const standardWidget = this.standardWidgets.find((w) => w.name === this.metadata.value)
if (standardWidget && standardWidget.props) return standardWidget.props
const standardWidget = this.standardWidgets.find((w) => w.name === this.currentComponent.component)
if (standardWidget && standardWidget.props) ret = Object.assign({}, standardWidget.props)
}
return {}
if (!ret.parameters) ret.parameters = []
if (!ret.parameterGroups) ret.parameterGroups = []
if (ret.parameters.length && (!this.metadata.value || this.metadata.value === ' ')) {
// for the default system-suggested widget, take the default config and put it as default value
for (const key in this.defaultComponent.config) {
const parameter = ret.parameters.find((p) => p.name === key)
if (parameter) parameter.defaultValue = this.defaultComponent.config[key]
}
} else {
// for user-specified widgets, set a default value for the 'item' parameter only
const itemParameter = ret.parameters.find((p) => p.name === 'item')
if (itemParameter) itemParameter.defaultValue = this.item.name
}
return ret
},
currentComponent () {
let component
if (!this.metadata.value || this.metadata.value === ' ') {
component = Object.assign({}, this.defaultComponent)
if (typeof this.metadata.config === 'object') {
component.config = Object.assign({}, component.config, this.metadata.config)
}
return component
} else {
component = {
component: this.metadata.value,
config: Object.assign({}, this.metadata.config || {})
}
if (!component.config.item) component.config.item = this.item.name
}
if (!component.config.item) component.config.item = this.item.name
return component
},
previewContext () {
const componentFromMetadata = (this.metadata.value !== '') ? {
component: this.metadata.value,
config: Object.assign({ item: this.item.name }, this.metadata.config || {})
} : null
if (this.namespace === 'listWidget') {
return {
store: this.$store.getters.trackedItems,
vars: this.widgetVars,
component: {
component: 'oh-list-card',
config: {},
config: {
mediaList: true
},
slots: {
default: [componentFromMetadata || itemDefaultListComponent(this.item)]
default: [this.currentComponent]
}
}
}
@ -123,7 +155,7 @@ export default {
component: 'oh-grid-cells',
config: {},
slots: {
default: [componentFromMetadata || itemDefaultCellComponent(this.item)]
default: [this.currentComponent]
}
}
}
@ -131,18 +163,25 @@ export default {
return {
store: this.$store.getters.trackedItems,
vars: this.widgetVars,
component: componentFromMetadata || itemDefaultStandaloneComponent(this.item)
component: this.currentComponent
}
}
}
},
mounted () {
this.$store.dispatch('startTrackingStates')
// copy the item & remove the metadata to get the default widget
const defaultItem = Object.assign({}, this.item)
if (defaultItem.metadata) {
delete defaultItem.metadata[this.namespace]
}
this.defaultComponent =
(this.namespace === 'cellWidget') ? itemDefaultCellComponent(defaultItem)
: (this.namespace === 'listWidget') ? itemDefaultListComponent(defaultItem)
: itemDefaultStandaloneComponent(defaultItem)
},
beforeDestroy () {
this.previewOpened = false
this.$refs.previewSheet.f7Sheet.close()
this.$refs.previewSheet.f7Sheet.destroy()
this.$store.dispatch('stopTrackingStates')
},
methods: {

View File

@ -4,6 +4,8 @@
<f7-block-title>Item</f7-block-title>
<item-details :model="model" :links="links" @item-updated="$emit('item-updated')" @item-created="$emit('item-created')" @item-removed="$emit('item-removed')" @cancel-create="$emit('cancel-create')" />
<f7-block-title v-if="model.item.created !== false">Metadata</f7-block-title>
<metadata-menu v-if="model.item.created !== false" :item="model.item" />
<f7-block-title v-if="model.item.type !== 'Group' && model.item.created !== false">Channel Links</f7-block-title>
<link-details :item="model.item" :links="links" />
</div>
@ -12,6 +14,7 @@
<script>
import ItemStatePreview from '@/components/item/item-state-preview.vue'
import ItemDetails from '@/components/model/item-details.vue'
import MetadataMenu from '@/components/item/metadata/item-metadata-menu.vue'
import LinkDetails from '@/components/model/link-details.vue'
export default {
@ -19,6 +22,7 @@ export default {
components: {
ItemStatePreview,
ItemDetails,
MetadataMenu,
LinkDetails
},
methods: {

View File

@ -10,13 +10,6 @@
<div class="padding-top" v-if="editMode">
<item-form :item="model.item" :hide-type="true" :force-semantics="forceSemantics"></item-form>
<f7-list accordion-list v-if="editMode" inset class="padding-top">
<f7-list-item accordion-item title="Metadata">
<f7-accordion-content>
<metadata-menu :item="model.item" />
</f7-accordion-content>
</f7-list-item>
</f7-list>
</div>
<div class="padding-top" v-else-if="createMode">
<item-form :item="model.item" :enable-name="true" :force-semantics="forceSemantics"></item-form>

View File

@ -5,12 +5,13 @@
export default function itemDefaultCellComponent (item, itemNameAsFooter) {
const stateDescription = item.stateDescription || {}
const metadata = (item.metadata && item.metadata.cellWidget) ? item.metadata.cellWidget : {}
let component = null
if (item.metadata && item.metadata.cellWidget) {
if (metadata.value && metadata.value !== ' ') {
component = {
component: item.metadata.cellWidget.value,
config: item.metadata.cellWidget.config
component: metadata.value,
config: Object.assign({}, metadata.config)
}
} else {
if (item.type === 'Switch' && !stateDescription.readOnly) {
@ -84,7 +85,7 @@ export default function itemDefaultCellComponent (item, itemNameAsFooter) {
component: 'oh-label-cell'
}
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.options || stateDescription.readOnly)) {
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.commandOptions || stateDescription.readOnly)) {
component.config = {
trendItem: item.name,
action: 'analyze',
@ -105,7 +106,10 @@ export default function itemDefaultCellComponent (item, itemNameAsFooter) {
}
if (!component.config) component.config = {}
component.config.item = item.name
if ((!metadata.value || metadata.value === ' ') && typeof metadata.config === 'object') {
component.config = Object.assign({}, component.config, metadata.config)
}
if (!component.config.item) component.config.item = item.name
if (!component.config.title) component.config.title = item.label || item.name
if (item.label && itemNameAsFooter && !component.config.footer) component.config.footer = item.name
component.config.stateAsHeader = true

View File

@ -37,7 +37,7 @@ export default {
widget: OhLabelCellDefinition,
computed: {
label () {
return this.config.label || this.context.store[this.config.item].displayState || this.context.store[this.config.item].state
return this.config.label || this.context.store[this.config.item].displayState || this.context.store[this.config.item].state
}
}
}

View File

@ -5,12 +5,13 @@
export default function itemDefaultStandaloneComponent (item) {
const stateDescription = item.stateDescription || {}
const metadata = (item.metadata && item.metadata.widget) ? item.metadata.widget : {}
let component = null
if (item.metadata && item.metadata.widget) {
if (metadata.value && metadata.value !== ' ') {
component = {
component: item.metadata.widget.value,
config: item.metadata.widget.config
component: metadata.value,
config: Object.assign({}, metadata.config)
}
} else {
if (item.type === 'Switch' && !stateDescription.readOnly) {
@ -79,7 +80,7 @@ export default function itemDefaultStandaloneComponent (item) {
component: 'oh-label-card'
}
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.options || stateDescription.readOnly)) {
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.commandOptions || stateDescription.readOnly)) {
component.config = {
trendItem: item.name,
action: 'analyze',
@ -100,7 +101,10 @@ export default function itemDefaultStandaloneComponent (item) {
}
if (!component.config) component.config = {}
component.config.item = item.name
if ((!metadata.value || metadata.value === ' ') && typeof metadata.config === 'object') {
component.config = Object.assign({}, component.config, metadata.config)
}
if (!component.config.item) component.config.item = item.name
return component
}

View File

@ -5,12 +5,13 @@
export default function itemDefaultListComponent (item, itemNameAsFooter) {
const stateDescription = item.stateDescription || {}
const metadata = (item.metadata && item.metadata.listWidget) ? item.metadata.listWidget : {}
let component = null
if (item.metadata && item.metadata.listWidget) {
if (metadata.value && metadata.value !== ' ') {
component = {
component: item.metadata.listWidget.value,
config: item.metadata.listWidget.config
component: metadata.value,
config: Object.assign({}, metadata.config)
}
} else {
if (item.type === 'Switch' && !stateDescription.readOnly) {
@ -70,7 +71,7 @@ export default function itemDefaultListComponent (item, itemNameAsFooter) {
component: 'oh-label-item'
}
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.options || stateDescription.readOnly)) {
if (item.type.indexOf('Number') === 0 && (!item.commandDescription || !item.commandDescription.commandOptions || stateDescription.readOnly)) {
component.config = {
action: 'analyze',
actionAnalyzerItems: [item.name]
@ -90,7 +91,10 @@ export default function itemDefaultListComponent (item, itemNameAsFooter) {
}
if (!component.config) component.config = {}
component.config.item = item.name
if ((!metadata.value || metadata.value === ' ') && typeof metadata.config === 'object') {
component.config = Object.assign({}, component.config, metadata.config)
}
if (!component.config.item) component.config.item = item.name
if (!component.config.title) component.config.title = item.label || item.name
if (item.category && !component.config.icon) component.config.icon = 'oh:' + item.category
if (item.category && ['Switch', 'Rollershutter', 'Contact', 'Dimmer', 'Group'].indexOf(item.type) >= 0) component.config.iconUseState = true

View File

@ -32,6 +32,20 @@
</f7-block>
</f7-col>
</f7-row>
<f7-row v-if="item && item.metadata && item.metadata.semantics">
<f7-col>
<f7-block-title>Semantic Classification</f7-block-title>
<f7-list>
<f7-list-item title="class" :after="item.metadata.semantics.value"></f7-list-item>
<f7-list-item
v-for="(value, key) in item.metadata.semantics.config"
:key="key"
:title="key"
:after="value"
></f7-list-item>
</f7-list>
</f7-col>
</f7-row>
<f7-row v-if="item && item.groupNames && item.groupNames.length > 0">
<f7-col>
<f7-block-title>Direct Parent Groups</f7-block-title>
@ -53,20 +67,6 @@
<group-members :group-item="item" :context="context" @updated="load" />
</f7-col>
</f7-row>
<f7-row v-if="item && item.metadata && item.metadata.semantics">
<f7-col>
<f7-block-title>Semantic Classification</f7-block-title>
<f7-list>
<f7-list-item title="class" :after="item.metadata.semantics.value"></f7-list-item>
<f7-list-item
v-for="(value, key) in item.metadata.semantics.config"
:key="key"
:title="key"
:after="value"
></f7-list-item>
</f7-list>
</f7-col>
</f7-row>
<f7-row v-if="item.name">
<f7-col>
<f7-block-title>Metadata</f7-block-title>

View File

@ -57,6 +57,7 @@ import ItemMetadataItemDescription from '@/components/item/metadata/item-metadat
import ItemMetadataSynonyms from '@/components/item/metadata/item-metadata-synonyms.vue'
import ItemMetadataWidget from '@/components/item/metadata/item-metadata-widget.vue'
import ItemMetadataAutoUpdate from '@/components/item/metadata/item-metadata-autoupdate.vue'
import ItemMetadataExpire from '@/components/item/metadata/item-metadata-expire.vue'
import ItemMetadataAlexa from '@/components/item/metadata/item-metadata-alexa.vue'
import ItemMetadataHomeKit from '@/components/item/metadata/item-metadata-homekit.vue'
import ItemMetadataGa from '@/components/item/metadata/item-metadata-ga.vue'
@ -91,6 +92,8 @@ export default {
return ItemMetadataWidget
case 'autoupdate':
return ItemMetadataAutoUpdate
case 'expire':
return ItemMetadataExpire
case 'alexa':
return ItemMetadataAlexa
case 'homekit':

View File

@ -82,6 +82,7 @@
<f7-toolbar tabbar bottom>
<f7-link class="padding-left padding-right" :tab-link-active="detailsTab === 'state'" @click="detailsTab = 'state'">State</f7-link>
<f7-link class="padding-left padding-right" :tab-link-active="detailsTab === 'item'" @click="detailsTab = 'item'">Item</f7-link>
<f7-link class="padding-left padding-right" :tab-link-active="detailsTab === 'meta'" @click="detailsTab = 'meta'">Meta</f7-link>
<f7-link class="padding-left padding-right" :tab-link-active="detailsTab === 'links'" @click="detailsTab = 'links'">Links</f7-link>
<div class="right">
<f7-link sheet-close class="padding-right"><f7-icon f7="chevron_down"></f7-icon></f7-link>
@ -90,6 +91,7 @@
<f7-block style="margin-bottom: 6rem" v-if="selectedItem">
<item-state-preview v-if="detailsTab === 'state' && !newItem" :item="selectedItem.item" :context="context" />
<item-details v-if="detailsTab === 'item'" :model="selectedItem" :links="links" @item-updated="update" @item-created="update" @item-removed="selectItem(null)" @cancel-create="selectItem(null)"/>
<metadata-menu v-if="detailsTab === 'meta'" :item="selectedItem.item" />
<link-details v-if="detailsTab === 'links'" :item="selectedItem.item" :links="links" />
</f7-block>
<f7-block v-else>
@ -123,7 +125,7 @@
--f7-theme-color-rgb var(--f7-color-blue-rgb)
z-index 10900
.md .model-details-sheet .toolbar .link
width 28%
width 17%
@media (min-width: 768px)
.semantic-tree-wrapper
@ -162,6 +164,7 @@ import AddFromThing from './add-from-thing.vue'
import ItemStatePreview from '@/components/item/item-state-preview.vue'
import ItemDetails from '@/components/model/item-details.vue'
import MetadataMenu from '@/components/item/metadata/item-metadata-menu.vue'
import LinkDetails from '@/components/model/link-details.vue'
import MetadataNamespaces from '@/assets/definitions/metadata/namespaces.js'
@ -171,6 +174,7 @@ export default {
ModelDetailsPane,
ItemStatePreview,
ItemDetails,
MetadataMenu,
LinkDetails
},
data () {