Skip to content

Commit

Permalink
Sitemap editor: Add support for color temperature picker (#2880)
Browse files Browse the repository at this point in the history
Closes #2852.

Refs openhab/openhab-core#4420.
Related to openhab/openhab-core#3891.

This implements configuring a color temperature picker in the sitemap builder UI.

It also does some visualisation improvements of names and labels (by
defaults shows item label in treeview, analogous to model treeview).

---------

Also-by: Florian Hotze <[email protected]>
Signed-off-by: Mark Herwege <[email protected]>
  • Loading branch information
mherwege authored Nov 30, 2024
1 parent 1e0b7de commit 09b153c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 115 deletions.
4 changes: 2 additions & 2 deletions bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
widgetclickattr: 'click=',
widgetreleaseattr:'release=',
widgetperiodattr: 'period=',
nlwidget: ['Switch ', 'Selection ', 'Slider ', 'Setpoint ', 'Input ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Button ', 'Default '],
nlwidget: ['Switch ', 'Selection ', 'Slider ', 'Setpoint ', 'Input ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Colortemperaturepicker', 'Mapview ', 'Button ', 'Default '],
lwidget: ['Text ', 'Group ', 'Image ', 'Frame ', 'Buttongrid '],
lparen: '(',
rparen: ')',
Expand All @@ -49,7 +49,7 @@
number: /[+-]?[0-9]+(?:\.[0-9]*)?/,
string: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: x => x.slice(1, -1) }
})
const requiresItem = ['Group', 'Chart', 'Switch', 'Mapview', 'Slider', 'Selection', 'Setpoint', 'Input ', 'Colorpicker', 'Button', 'Default']
const requiresItem = ['Group', 'Chart', 'Switch', 'Mapview', 'Slider', 'Selection', 'Setpoint', 'Input ', 'Colorpicker', 'Colortemperaturepicker', 'Button', 'Default']

function getSitemap(d) {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export default {
data () {
return {
items: [],
itemsReady: false
}
},
created () {
this.WIDGET_TYPES = [
{ type: 'Sitemap', icon: 'slider_horizontal_below_rectangle' },
{ type: 'Text', icon: 'textformat' },
{ type: 'Switch', icon: 'power' },
{ type: 'Selection', icon: 'text_justify' },
{ type: 'Slider', icon: 'slider_horizontal_3' },
{ type: 'Frame', icon: 'macwindow' },
{ type: 'Setpoint', icon: 'plus_slash_minus' },
{ type: 'Input', icon: 'text_cursor' },
{ type: 'Buttongrid', label: 'Button Grid', icon: 'square_grid_3x2' },
{ type: 'Button', icon: 'square_fill_line_vertical_square' },
{ type: 'Default', icon: 'rectangle' },
{ type: 'Group', icon: 'square_stack_3d_down_right' },
{ type: 'Chart', icon: 'chart_bar_square' },
{ type: 'Webview', label: 'Web View', icon: 'globe' },
{ type: 'Colorpicker', label: 'Color Picker', icon: 'drop' },
{ type: 'Colortemperaturepicker', label: 'Color Temperature Picker', icon: 'thermometer' },
{ type: 'Mapview', label: 'Map View', icon: 'map' },
{ type: 'Image', icon: 'photo' },
{ type: 'Video', icon: 'videocam' }
]
this.LINKABLE_WIDGET_TYPES = ['Sitemap', 'Text', 'Frame', 'Group', 'Image', 'Buttongrid']
this.WIDGET_TYPES_REQUIRING_ITEM = ['Group', 'Chart', 'Switch', 'Mapview', 'Slider', 'Selection', 'Setpoint', 'Input', 'Colorpicker', 'Colortemperaturepicker', 'Default']
this.WIDGET_TYPES_SHOWING_VALUE = ['Text', 'Switch', 'Selection', 'Slider', 'Setpoint', 'Input', 'Default', 'Group']

this.REGEX_PERIOD = /^((P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?|\d*[YMWDh])-)?-?(P(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?|\d*[YMWDh])$/
this.REGEX_DECIMAL_PATTERN = /^(?:'[0#.,;E]?'|[^0#.,;E'])*((#[,#]*|0)[,0]*)(\.(0+#*|#+))?(?:E0+)?(?:';'|[^;])*(?:;(?:'[0#.,;E]?'|[^0#.,;E'])*((#[,#]*|0)[,0]*)(\.(0+#*|#+))?(?:E0+)?.*)?$/
this.REGEX_MAPPING = /^\s*("[^\n"]*"|\w+)\s*=\s*("[^\n"]*"|\w+)\s*(=\s*("[^\n"]*"|\w+))?$/u
this.REGEX_MAPPING_SWITCH = /^\s*("[^\n"]*"|\w+)\s*(:\s*("[^\n"]*"|\w+)\s*)?=\s*("[^\n"]*"|\w+)\s*(=\s*("[^\n"]*"|\w+))?$/u
this.REGEX_RULE_VISIBILITY = /^(\s*((\w+\s*)?(==|>=|<=|!=|>|<)\s*)?("[^\n"]*"|\w+)\s*AND)*\s*((\w+\s*)?(==|>=|<=|!=|>|<)\s*)?("[^\n"]*"|\w+)\s*$/u
this.REGEX_RULE = /^(((\s*((\w+\s*)?(==|>=|<=|!=|>|<)\s*)?("[^\n"]*"|\w+)\s*AND)*\s*((\w+\s*)?(==|>=|<=|!=|>|<)\s*)?("[^\n"]*"|\w+)\s*)?\s*=)?\s*("#?(\w|:|-)+"|#?(\w|:|-)+)$/u

this.ADDITIONAL_CONTROLS = {
Image: ['url', 'refresh'],
Video: ['url', 'encoding'],
Chart: ['service', 'period', 'refresh', 'legend', 'forceAsItem', 'yAxisDecimalPattern'],
Webview: ['url', 'height'],
Mapview: ['height'],
Slider: ['switchEnabled', 'releaseOnly', 'minValue', 'maxValue', 'step'],
Setpoint: ['minValue', 'maxValue', 'step'],
Colortemperaturepicker: ['minValue', 'maxValue'],
Input: ['inputHint'],
Button: ['row', 'column', 'stateless', 'cmd', 'releaseCmd'],
Default: ['height']
}
this.ENCODING_DEFS = [
{ key: 'mjpeg', value: 'MJPEG Video' },
{ key: 'HLS', value: 'HTTP Live Streaming' }
]
this.INPUT_HINT_DEFS = [
{ key: 'text', value: 'Text' },
{ key: 'number', value: 'Number' },
{ key: 'date', value: 'Date' },
{ key: 'time', value: 'Time' },
{ key: 'datetime', value: 'Date and Time' }
]

if (!this.itemsList) {
this.$oh.api.get('/rest/items?staticDataOnly=true').then((items) => {
this.items = items
this.itemsReady = true
})
} else {
this.items = this.itemsList ?? []
this.itemsReady = true
}
},
methods: {
widgetTypeDef (component) {
const componentType = component ?? this.widget.component
return this.WIDGET_TYPES.find(w => w.type === componentType)
},
widgetTypeIcon (component) {
return this.widgetTypeDef(component).icon
},
widgetTypeLabel (component) {
return this.widgetTypeDef(component).label ?? this.widgetTypeDef(component).type
},
widgetConfigLabel () {
return this.widget.config.label ?? ((this.widget.component === 'Button') ? this.widget.config.cmd : '')
},
widgetItemLabel (includeItemName) {
const item = this.items.find(i => i.name === this.widget.config.item)
return (item?.label || this.widget.config.item) + (includeItemName && item ? ` (${item.name})` : '')
},
widgetConfigDescription (includeItemName) {
const buttonPosition = this.widget.component === 'Button' ? ` (${this.widget.config?.row ?? '-'},${this.widget.config?.column ?? '-'})` : ''
return (this.widget.config?.item ? ': ' + this.widgetItemLabel(includeItemName) : '') + buttonPosition
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<template>
<f7-treeview-item selectable :label="widget.config.label ? widget.config.label : ((widget.component === 'Button') ? widget.config.cmd : '')"
:icon-ios="icon('ios')" :icon-aurora="icon('aurora')" :icon-md="icon('md')"
<f7-treeview-item v-if="itemsReady" selectable :label="widgetConfigLabel()"
:icon-f7="widgetTypeIcon()"
:textColor="iconColor" :color="'blue'"
:selected="selected && selected === widget"
:opened="!widget.closed"
@click="select">
<sitemap-treeview-item v-for="(childwidget, idx) in children"
<sitemap-treeview-item class="sitemap-treeview-item" v-for="(childwidget, idx) in children"
:key="idx"
:includeItemName="includeItemName"
:widget="childwidget" :parent-widget="widget"
:itemsList="items"
@selected="(event) => $emit('selected', event)"
:selected="selected" />
<div slot="label" class="subtitle">
Expand All @@ -16,53 +18,25 @@
</f7-treeview-item>
</template>

<style lang="stylus">
.sitemap-tree
.treeview
.treeview-item-content
width calc(100% - (var(--f7-treeview-toggle-size) + 5px))
.subtitle
overflow hidden
text-overflow ellipsis
</style>

<script>
import SitemapMixin from '@/components/pagedesigner/sitemap/sitemap-mixin'
export default {
props: ['widget', 'parentWidget', 'selected'],
mixins: [SitemapMixin],
props: ['includeItemName', 'widget', 'parentWidget', 'itemsList', 'selected'],
methods: {
icon (theme) {
switch (this.widget.component) {
case 'Switch':
return 'f7:power'
case 'Selection':
return 'f7:text_justify'
case 'Slider':
return 'f7:slider_horizontal_3'
case 'Setpoint':
return 'f7:plus_slash_minus'
case 'Input':
return 'f7:text_cursor'
case 'Video':
return 'f7:videocam'
case 'Chart':
return 'f7:chart_bar_square'
case 'Webview':
return 'f7:globe'
case 'Colorpicker':
return 'f7:drop'
case 'Mapview':
return 'f7:map'
case 'Buttongrid':
return 'f7:square_grid_3x2'
case 'Button':
return 'f7:square_fill_line_vertical_square'
case 'Default':
return 'f7:rectangle'
case 'Text':
return 'f7:textformat'
case 'Group':
return 'f7:square_stack_3d_down_right'
case 'Image':
return 'f7:photo'
case 'Frame':
return 'f7:macwindow'
default:
return 'f7:slider_horizontal_below_rectangle'
}
},
subtitle () {
const buttonPosition = this.widget.component === 'Button' ? ' (' + (this.widget.config?.row ?? '-') + ',' + (this.widget.config?.column ?? '-') + ')' : ''
return this.widget.component + ((this.widget.config && this.widget.config.item) ? ': ' + this.widget.config.item : '') + buttonPosition
return this.widgetTypeLabel() + this.widgetConfigDescription(this.includeItemName)
},
select (event) {
let self = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@
import { Categories } from '@/assets/categories.js'
import ItemPicker from '@/components/config/controls/item-picker.vue'
import PersistencePicker from '@/components/config/controls/persistence-picker.vue'
import SitemapMixin from '@/components/pagedesigner/sitemap/sitemap-mixin'
export default {
mixins: [SitemapMixin],
components: {
ItemPicker,
PersistencePicker
Expand All @@ -110,31 +112,6 @@ export default {
iconAutocomplete: null
}
},
created () {
this.ADDITIONAL_CONTROLS = {
Image: ['url', 'refresh'],
Video: ['url', 'encoding'],
Chart: ['service', 'period', 'refresh', 'legend', 'forceAsItem', 'yAxisDecimalPattern'],
Webview: ['url', 'height'],
Mapview: ['height'],
Slider: ['switchEnabled', 'releaseOnly', 'minValue', 'maxValue', 'step'],
Setpoint: ['minValue', 'maxValue', 'step'],
Input: ['inputHint'],
Button: ['row', 'column', 'stateless', 'cmd', 'releaseCmd'],
Default: ['height']
}
this.ENCODING_DEFS = [
{ key: 'mjpeg', value: 'MJPEG Video' },
{ key: 'HLS', value: 'HTTP Live Streaming' }
]
this.INPUT_HINT_DEFS = [
{ key: 'text', value: 'Text' },
{ key: 'number', value: 'Number' },
{ key: 'date', value: 'Date' },
{ key: 'time', value: 'Time' },
{ key: 'datetime', value: 'Date and Time' }
]
},
methods: {
initializeAutocomplete (inputElement) {
this.iconAutocomplete = this.$f7.autocomplete.create({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import PagesList from '../../pages-list.vue'
import SitemapEdit from '../sitemap-edit.vue'
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Framework7 from 'framework7'
Expand All @@ -24,7 +23,8 @@ describe('SitemapEdit', () => {
open: () => { }
}
}
}
},
data: { sitemap: {} }
}
}
})
Expand All @@ -34,7 +34,8 @@ describe('SitemapEdit', () => {
localVue,
propsData: {
createMode: true,
uid: 'test'
uid: 'test',
itemsList: []
}
})
})
Expand Down
Loading

0 comments on commit 09b153c

Please sign in to comment.