diff --git a/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley b/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley index 792ea9ae2e..387e7e8aa2 100644 --- a/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley +++ b/bundles/org.openhab.ui/web/src/assets/sitemap-lexer.nearley @@ -9,18 +9,21 @@ item: 'item=', staticIcon: 'staticIcon=', icon: 'icon=', - widgetattr: ['url=', 'refresh=', 'service=', 'height=', 'minValue=', 'maxValue=', 'step=', 'encoding=', 'yAxisDecimalPattern=', 'inputHint=', 'columns='], + widgetattr: ['url=', 'refresh=', 'service=', 'height=', 'minValue=', 'maxValue=', 'step=', 'encoding=', 'yAxisDecimalPattern=', 'inputHint=', 'columns=', 'row=', 'column='], widgetboolattr: ['legend='], widgetfrcitmattr: 'forceasitem=', widgetmapattr: 'mappings=', - widgetbuttonattr: 'buttons=', + widgetbuttonsattr:'buttons=', widgetvisiattr: 'visibility=', widgetcolorattr: ['labelcolor=', 'valuecolor=', 'iconcolor='], widgetswitchattr: 'switchSupport', widgetronlyattr: 'releaseOnly', + widgetstatelattr: 'stateless', + widgetclickattr: 'click=', + widgetreleaseattr:'release=', widgetperiodattr: 'period=', - nlwidget: ['Switch ', 'Selection ', 'Slider ', 'Setpoint ', 'Input ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Buttongrid ', 'Default '], - lwidget: ['Text ', 'Group ', 'Image ', 'Frame '], + nlwidget: ['Switch ', 'Selection ', 'Slider ', 'Setpoint ', 'Input ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Button ', 'Default '], + lwidget: ['Text ', 'Group ', 'Image ', 'Frame ', 'Buttongrid '], lparen: '(', rparen: ')', lbrace: '{', @@ -46,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', 'Default'] + const requiresItem = ['Group', 'Chart', 'Switch', 'Mapview', 'Slider', 'Selection', 'Setpoint', 'Input ', 'Colorpicker', 'Button', 'Default'] function getSitemap(d) { return { @@ -75,7 +78,7 @@ } } - // if icon exists remove staticIcon, if not set icon to staticIcon and make saticIcon=true + // if icon exists remove staticIcon, if not set icon to staticIcon and make staticIcon=true if (widget.config.icon) { delete widget.config.staticIcon } @@ -83,12 +86,6 @@ widget.config.icon = widget.config.staticIcon widget.config.staticIcon = true } - - // reject widgets with missing parameters - if (requiresItem.includes(widget.component) && !widget.config.item) return reject - if ((widget.component === 'Video' || widget.component === 'Webview') && !widget.config.url) return reject - if (widget.component === 'Chart' && !widget.config.period) return reject - return widget } %} @@ -113,9 +110,12 @@ WidgetAttrs -> WidgetAttr | WidgetAttrs _ WidgetAttr {% (d) => d[0].concat([d[2]]) %} WidgetAttr -> %widgetswitchattr {% (d) => ['switchEnabled', true] %} | %widgetronlyattr {% (d) => ['releaseOnly', true] %} + | %widgetstatelattr {% (d) => ['stateless', true] %} | %widgetfrcitmattr _ WidgetBooleanAttrValue {% (d) => ['forceAsItem', d[2]] %} | %widgetboolattr _ WidgetBooleanAttrValue {% (d) => [d[0].value, d[2]] %} | %widgetperiodattr _ WidgetPeriodAttrValue {% (d) => ['period', d[2]] %} + | %widgetclickattr _ WidgetAttrValue {% (d) => ['cmd', d[2]] %} + | %widgetreleaseattr _ WidgetAttrValue {% (d) => ['releaseCmd', d[2]] %} | %icon _ WidgetIconRulesAttrValue {% (d) => ['iconrules', d[2]] %} | %icon _ WidgetIconAttrValue {% (d) => [d[0].value, d[2].join("")] %} | %staticIcon _ WidgetIconAttrValue {% (d) => [d[0].value, d[2].join("")] %} @@ -143,7 +143,7 @@ WidgetAttrValue -> %number | %string {% (d) => d[0].value %} WidgetMappingsAttrName -> %widgetmapattr WidgetMappingsAttrValue -> %lbracket _ Mappings _ %rbracket {% (d) => d[2] %} -WidgetButtonsAttrName -> %widgetbuttonattr +WidgetButtonsAttrName -> %widgetbuttonsattr WidgetButtonsAttrValue -> %lbracket _ Buttons _ %rbracket {% (d) => d[2] %} WidgetVisibilityAttrName -> %widgetvisiattr WidgetVisibilityAttrValue -> %lbracket _ Visibilities _ %rbracket {% (d) => d[2] %} @@ -157,9 +157,9 @@ Mapping -> Command _ %colon _ Command _ %equals _ Label | Command _ %colon _ Command _ %equals _ Label _ %equals _ WidgetIconAttrValue {% (d) => d[0] + ':' + d[4] + '=' + d[8] + '=' + d[12].join("") %} | Command _ %equals _ Label _ %equals _ WidgetIconAttrValue {% (d) => d[0] + '=' + d[4] + '=' + d[8].join("") %} -Buttons -> Button {% (d) => [d[0]] %} - | Buttons _ %comma _ Button {% (d) => d[0].concat([d[4]]) %} -Button -> %number _ %colon _ %number _ %colon _ ButtonValue {% (d) => { return { 'row': parseInt(d[0].value), 'column': parseInt(d[4].value), 'command': d[8] } } %} +Buttons -> ButtonDef {% (d) => [d[0]] %} + | Buttons _ %comma _ ButtonDef {% (d) => d[0].concat([d[4]]) %} +ButtonDef -> %number _ %colon _ %number _ %colon _ ButtonValue {% (d) => { return { 'row': parseInt(d[0].value), 'column': parseInt(d[4].value), 'command': d[8] } } %} ButtonValue -> Command _ %equals _ Label {% (d) => d[0] + '=' + d[4] %} | Command _ %equals _ Label _ %equals _ WidgetIconAttrValue {% (d) => d[0] + '=' + d[4] + '=' + d[8].join("") %} diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/dslUtil_jest.spec.js b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/dslUtil_jest.spec.js index 8560285f3c..4ea8fc80a2 100644 --- a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/dslUtil_jest.spec.js +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/dslUtil_jest.spec.js @@ -35,8 +35,6 @@ describe('dslUtil', () => { it('renders a single simple widget correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'TestItem', label: 'Test Switch' @@ -52,8 +50,6 @@ describe('dslUtil', () => { it('renders a widget with icon correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'TestItem', label: 'Test Switch', @@ -70,8 +66,6 @@ describe('dslUtil', () => { it('renders a widget with static icon correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'TestItem', label: 'Test Switch', @@ -106,8 +100,6 @@ describe('dslUtil', () => { it('renders a widget with mappings correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Selection', { item: 'Scene_General', mappings: [ @@ -125,8 +117,6 @@ describe('dslUtil', () => { it('renders a Buttongrid widget correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Buttongrid', { item: 'Scene_General', buttons: [ @@ -142,10 +132,37 @@ describe('dslUtil', () => { expect(sitemap[1]).toEqual(' Buttongrid item=Scene_General buttons=[1:1:1=Morning, 1:2:2=Evening, 1:3:10="Cinéma", 2:1:11=TV, 2:2:3="Bed time", 2:3:4=Night=moon]') }) + it('renders a Buttongrid with Buttons widget correctly', () => { + const component = createSitemapComponent('test', 'Test') + const widget = addWidget(component, 'Buttongrid', { + label: 'Scenes', + staticIcon: true, + icon: 'screen' + }) + addWidget(widget, 'Button', { + row: 1, + column: 1, + item: 'Scene_General', + label: 'Morning', + stateless: true, + cmd: 1 + }) + addWidget(widget, 'Button', { + row: 1, + column: 2, + item: 'Scene_General', + label: 'Cinéma', + cmd: '10', + releaseCmd: 'test 11' + }) + const sitemap = dslUtil.toDsl(component).split('\n') + expect(sitemap[1]).toEqual(' Buttongrid label="Scenes" staticIcon=screen {') + expect(sitemap[2]).toEqual(' Button row=1 column=1 item=Scene_General label="Morning" stateless click=1') + expect(sitemap[3]).toEqual(' Button row=1 column=2 item=Scene_General label="Cinéma" click="10" release="test 11"') + }) + it('renders a widget with mappings and string keys correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Selection', { item: 'Echos', mappings: [ @@ -160,8 +177,6 @@ describe('dslUtil', () => { it('renders a widget with mappings and release command correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'pressAndRelease', mappings: ['ON:OFF=ON'] @@ -172,8 +187,6 @@ describe('dslUtil', () => { it('renders a widget with mappings and release command and string commands correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'pressAndRelease', mappings: ['"ON command":"OFF command"="ON"'] @@ -184,8 +197,6 @@ describe('dslUtil', () => { it('renders a widget with 0 value parameter correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Slider', { item: 'Dimmer1', minValue: 0, @@ -198,8 +209,6 @@ describe('dslUtil', () => { it('renders widget with visibility correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Text', { item: 'Test', visibility: [ @@ -214,8 +223,6 @@ describe('dslUtil', () => { it('renders widget with visibility and text condition correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Switch', { item: 'Test', visibility: [ @@ -229,8 +236,6 @@ describe('dslUtil', () => { it('renders widget with valuecolor correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Text', { item: 'Temperature', valuecolor: [ @@ -247,8 +252,6 @@ describe('dslUtil', () => { it('renders widget with valuecolor and text condition correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Text', { item: 'Temperature', valuecolor: [ @@ -261,8 +264,6 @@ describe('dslUtil', () => { it('renders widget with valuecolor and AND condition correctly', () => { const component = createSitemapComponent('test', 'Test') - const widget = { - } addWidget(component, 'Text', { item: 'Temperature', valuecolor: [ diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/sitemap-code_jest.spec.js b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/sitemap-code_jest.spec.js index e15adeb7fc..a193d68fc2 100644 --- a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/sitemap-code_jest.spec.js +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/__tests__/sitemap-code_jest.spec.js @@ -229,6 +229,67 @@ describe('SitemapCode', () => { }) }) + it('parses a Buttongrid with Button components correctly', async () => { + expect(wrapper.vm.sitemapDsl).toBeDefined() + // simulate updating the sitemap in code + const sitemap = [ + 'sitemap test label="Test" {', + ' Buttongrid label="Scenes" staticIcon=screen {', + ' Button row=1 column=1 item=Scene_General label=Morning stateless click=1', + ' Button row=1 column=2 item=Scene_General label="Cinéma" click="10" release="11"', + ' }', + '}', + '' + ].join('\n') + wrapper.vm.updateSitemap(sitemap) + expect(wrapper.vm.sitemapDsl).toMatch(/^sitemap test label="Test"/) + expect(wrapper.vm.parsedSitemap.error).toBeFalsy() + + await wrapper.vm.$nextTick() + + // check whether an 'updated' event was emitted and its payload + // (should contain the parsing result for the new sitemap definition) + const events = wrapper.emitted().updated + expect(events).toBeTruthy() + expect(events.length).toBe(1) + const payload = events[0][0] + expect(payload.slots).toBeDefined() + expect(payload.slots.widgets).toBeDefined() + expect(payload.slots.widgets.length).toBe(1) + expect(payload.slots.widgets[0]).toEqual({ + component: 'Buttongrid', + config: { + label: 'Scenes', + staticIcon: true, + icon: 'screen' + }, + slots: { + widgets: [{ + component: 'Button', + config: { + row: 1, + column: 1, + item: 'Scene_General', + label: 'Morning', + stateless: true, + cmd: 1 + } + }, + { + component: 'Button', + config: { + row: 1, + column: 2, + item: 'Scene_General', + label: 'Cinéma', + cmd: '10', + releaseCmd: '11' + } + }] + } + }) + }) + it('parses a mapping code back to a mapping on a component', async () => { expect(wrapper.vm.sitemapDsl).toBeDefined() // simulate updating the sitemap in code diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js index dd5c057dc1..8a8922804f 100644 --- a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/dslUtil.js @@ -11,6 +11,8 @@ function writeWidget (widget, indent) { dsl += ' releaseOnly' } else if (key === 'forceAsItem') { dsl += ' forceasitem=' + widget.config[key] + } else if (key === 'stateless') { + dsl += ' stateless' } else if (key === 'icon') { if (widget.config.staticIcon) { dsl += ' staticIcon=' + widget.config[key] @@ -20,6 +22,10 @@ function writeWidget (widget, indent) { } else if (key !== 'staticIcon') { if (key === 'iconrules') { dsl += ' icon=' + } else if (key === 'cmd') { + dsl += ' click=' + } else if (key === 'releaseCmd') { + dsl += ' release=' } else { dsl += ` ${key}=` } diff --git a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue index 257d3dce21..999cc185cb 100644 --- a/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue +++ b/bundles/org.openhab.ui/web/src/components/pagedesigner/sitemap/treeview-item.vue @@ -1,5 +1,5 @@