Add missing sitemap attributes (#1487)

Closes #324.

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
pull/1512/head
Mark Herwege 2022-10-08 18:36:47 +02:00 committed by GitHub
parent 09adec507b
commit 72fe51ea1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 181 additions and 100 deletions

View File

@ -2,32 +2,40 @@
const moo = require('moo')
let lexer = moo.compile({
WS: /[ \t]+/,
comment: /\/\/.*?$/,
number: /\-?[0-9]+(?:\.[0-9]*)?/,
string: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: x => x.slice(1, -1) },
sitemap: 'sitemap ',
name: 'name=',
label: 'label=',
item: 'item=',
icon: 'icon=',
widgetattr: ['url=', 'refresh=', 'service=', 'refresh=', 'period=', 'legend=', 'height=', 'frequency=', 'sendFrequency=',
'switchEnabled=', 'mappings=', 'minValue=', 'maxValue=', 'step=', 'separator=', 'encoding='],
nlwidget: ['Switch ', 'Selection ', 'Slider ', 'List ', 'Setpoint ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Default '],
lwidget: ['Text', 'Group', 'Image', 'Frame'],
identifier: /[A-Za-z0-9_]+/,
lparen: '(',
rparen: ')',
colon: ':',
lbrace: '{',
rbrace: '}',
lbracket: '[',
rbracket: ']',
lt: '<',
gt: '>',
equals: '=',
comma: ',',
NL: { match: /\n/, lineBreaks: true },
WS: /[ \t]+/,
comment: /\/\/.*?$/,
number: /\-?[0-9]+(?:\.[0-9]*)?/,
string: { match: /"(?:\\["\\]|[^\n"\\])*"/, value: x => x.slice(1, -1) },
sitemap: 'sitemap ',
name: 'name=',
label: 'label=',
item: 'item=',
icon: 'icon=',
widgetattr: ['url=', 'refresh=', 'service=', 'period=', 'legend=', 'height=', 'mappings=', 'minValue=', 'maxValue=', 'step=', 'separator=', 'encoding=', 'yAxisDecimalPattern='],
widgetfreqattr: 'sendFrequency=',
widgetfrcitmattr: 'forceasitem=',
widgetvisiattr: 'visibility=',
widgetcolorattr: ['labelcolor=', 'valuecolor=', 'iconcolor='],
widgetswitchattr: 'switchSupport',
nlwidget: ['Switch ', 'Selection ', 'Slider ', 'List ', 'Setpoint ', 'Video ', 'Chart ', 'Webview ', 'Colorpicker ', 'Mapview ', 'Default '],
lwidget: ['Text ', 'Group ', 'Image ', 'Frame '],
identifier: /[A-Za-z][A-Za-z0-9_]*/,
lparen: '(',
rparen: ')',
colon: ':',
lbrace: '{',
rbrace: '}',
lbracket: '[',
rbracket: ']',
eq: '==',
noteq: '!=',
lteq: '<=',
gteq: '>=',
lt: '<',
gt: '>',
equals: '=',
comma: ',',
NL: { match: /\n/, lineBreaks: true },
})
function getSitemap(d) {
@ -64,7 +72,6 @@
@lexer lexer
Main -> _ Sitemap _ {% (d) => d[1] %}
Sitemap -> %sitemap _ SitemapName __ SitemapLabel __ %lbrace _ Widgets _ %rbrace {% getSitemap %}
@ -77,23 +84,49 @@ Widgets -> Widget
Widget -> %nlwidget _ WidgetAttrs:* {% getWidget %}
| %lwidget _ WidgetAttrs:* {% getWidget %}
| %lwidget _ WidgetAttrs:* __ %lbrace __ Widgets __ %rbrace {% getWidget %}
| %lwidget _ WidgetAttrs:* _ %lbrace _ Widgets _ %rbrace {% getWidget %}
WidgetAttrs -> WidgetAttr {% (d) => [d[0]] %}
| WidgetAttrs _ WidgetAttr {% (d) => d[0].concat([d[2]]) %}
WidgetAttr -> WidgetAttrName WidgetAttrValue {% (d) => [d[0][0].value, d[1]] %}
WidgetAttr -> %widgetswitchattr {% (d) => ['switchEnabled', true] %}
| %widgetfreqattr {% (d) => ['frequency', d[1]] %}
| %widgetfrcitmattr {% (d) => ['forceAsItem', d[1]] %}
| WidgetAttrName WidgetAttrValue {% (d) => [d[0][0].value, d[1]] %}
| WidgetVisibilityAttrName WidgetVisibilityAttrValue {% (d) => [d[0][0].value, d[1]] %}
| WidgetColorAttrName WidgetColorAttrValue {% (d) => [d[0][0].value, d[1]] %}
WidgetAttrName -> %item | %label | %icon | %widgetattr
WidgetAttrValue -> %string {% (d) => d[0].value %}
| %identifier {% (d) => d[0].value %}
| %number {% (d) => { return parseFloat(d[0].value) } %}
| %lbracket _ Mappings _ %rbracket {% (d) => d[2] %}
WidgetVisibilityAttrName -> %widgetvisiattr
WidgetVisibilityAttrValue -> %lbracket _ Visibilities _ %rbracket {% (d) => d[2] %}
WidgetColorAttrName -> %widgetcolorattr
WidgetColorAttrValue -> %lbracket _ Colors _ %rbracket {% (d) => d[2] %}
Mappings -> Mapping {% (d) => [d[0]] %}
| Mappings _ %comma _ Mapping {% (d) => d[0].concat([d[4]]) %}
Mapping -> MappingCommand %equals MappingLabel {% (d) => d[0][0].value.toString() + '=' + d[2][0].value.toString() %}
Mapping -> MappingCommand _ %equals _ MappingLabel {% (d) => d[0][0].value.toString() + '=' + d[4][0].value.toString() %}
MappingCommand -> %number | %identifier | %string
MappingLabel -> %number | %identifier | %string
Visibilities -> Visibility {% (d) => [d[0]] %}
| Visibilities _ %comma _ Visibility {% (d) => d[0].concat([d[4]]) %}
Visibility -> VisibilityCommand _ VisibilityComparator _ VisibilityValue {% (d) => d[0][0].value.toString() + d[2][0].value.toString() + d[4][0].value.toString() %}
VisibilityCommand -> %identifier | %string
VisibilityComparator -> %eq | %noteq | %lteq | %gteq | %lt | %gt
VisibilityValue -> %number | %identifier | %string
Colors -> Color {% (d) => [d[0]] %}
| Colors _ %comma _ Color {% (d) => d[0].concat([d[4]]) %}
Color -> ColorCommand _ ColorComparator _ ColorValue _ %equals _ ColorName {% (d) => d[0][0].value.toString() + d[2][0].value.toString() + d[4][0].value.toString() + '=' + d[8][0].value.toString() %}
| ColorComparator _ ColorValue _ %equals _ ColorName {% (d) => d[0][0].value.toString() + d[2][0].value.toString() + '=' + d[6][0].value.toString() %}
| ColorValue _ %equals _ ColorName {% (d) => "==" + d[0][0].value.toString() + '=' + d[4][0].value.toString() %}
| ColorName {% (d) => d[0][0].value.toString() %}
ColorCommand -> %identifier | %string
ColorComparator -> %eq | %noteq | %lteq | %gteq | %lt | %gt
ColorValue -> %number | %identifier | %string
ColorName -> %identifier | %string
_ -> null {% () => null %}
| _ %WS {% () => null %}

View File

@ -0,0 +1,51 @@
<template>
<f7-card v-if="widget">
<f7-card-content v-if="attributes.length">
<f7-list inline-labels sortable sortable-opposite sortable-enabled @sortable:sort="onSort">
<f7-list-input v-for="(attr, idx) in attributes" :key="attr.key"
type="text" :placeholder="placeholder" :value="attr.value" @change="updateAttribute(idx, $event)" clear-button />
</f7-list>
</f7-card-content>
<f7-card-footer key="item-card-buttons-edit-mode" v-if="widget.component !== 'Sitemap'">
<f7-button color="blue" @click="addAttribute">
Add
</f7-button>
</f7-card-footer>
</f7-card>
</template>
<script>
export default {
props: ['widget', 'attribute', 'placeholder'],
computed: {
attributes () {
if (this.widget && this.widget.config && this.widget.config[this.attribute]) {
return this.widget.config[this.attribute].map((attr, idx) => ({ key: idx + ': ' + attr, value: attr }))
}
return []
}
},
methods: {
updateAttribute (idx, $event) {
const value = $event.target.value
if (!value) {
this.widget.config[this.attribute].splice(idx, 1)
} else {
this.$set(this.widget.config[this.attribute], idx, value)
}
},
addAttribute () {
if (this.widget && this.widget.config && this.widget.config[this.attribute]) {
this.widget.config[this.attribute].push('')
} else {
this.$set(this.widget.config, this.attribute, [''])
}
},
onSort (ev) {
const element = this.widget.config[this.attribute][ev.from]
this.widget.config[this.attribute].splice(ev.from, 1)
this.widget.config[this.attribute].splice(ev.to, 0, element)
}
}
}
</script>

View File

@ -4,18 +4,31 @@ function writeWidget (widget, indent) {
if (widget.config) {
for (let key in widget.config) {
if (!widget.config[key]) continue
dsl += ` ${key}=`
if (key === 'item' || Number.isFinite(widget.config[key])) {
dsl += widget.config[key]
} else if (key === 'mappings') {
dsl += '['
const mappingsDsl = widget.config.mappings.map((m) =>
`${m.split('=')[0]}="${m.substring(m.indexOf('=') + 1)}"`
)
dsl += mappingsDsl.join(',')
dsl += ']'
if ((Array.isArray(widget.config[key]) && widget.config[key].filter(Boolean).length <= 0)) continue
if (key === 'switchEnabled') {
dsl += ' switchSupport'
} else if (key === 'frequency') {
dsl += ' sendFrequency=' + widget.config[key]
} else if (key === 'forceAsItem') {
dsl += ' forceasitem=' + widget.config[key]
} else {
dsl += '"' + widget.config[key] + '"'
dsl += ` ${key}=`
if (key === 'item' || Number.isFinite(widget.config[key])) {
dsl += widget.config[key]
} else if (['mappings', 'visibility', 'valuecolor', 'labelcolor', 'iconcolor'].includes(key)) {
dsl += '['
const arrayDsl = widget.config[key].map((v) => {
// Anything after the first comparator that is a string should be in quotes.
// Also quote string if no comparator (i.e. fixed labelcolor or valuecolor).
let value = v.substring(0, v.search(/[=<>]/))
value += v.substring(v.search(/[=<>]/)).replace(/"/g, '').replace(/[A-Za-z][A-Za-z0-9 _-]*/g, function (x) { return '"' + x.trim() + '"' })
return value.trim()
})
dsl += arrayDsl.filter(Boolean).join(',')
dsl += ']'
} else {
dsl += '"' + widget.config[key] + '"'
}
}
}
}

View File

@ -1,51 +0,0 @@
<template>
<f7-card v-if="widget">
<f7-card-content v-if="mappings.length">
<f7-list inline-labels sortable @sortable:sort="onSort">
<f7-list-input v-for="(mapping, idx) in mappings" :key="idx"
:label="`#${idx+1}`" type="text" placeholder="command=Label" :value="mapping" @input="updateMapping(idx, $event)" clear-button />
</f7-list>
</f7-card-content>
<f7-card-footer key="item-card-buttons-edit-mode" v-if="widget.component !== 'Sitemap'">
<f7-button color="blue" @click="addMapping">
Add
</f7-button>
</f7-card-footer>
</f7-card>
</template>
<script>
export default {
props: ['widget'],
computed: {
mappings () {
if (this.widget && this.widget.config && this.widget.config.mappings) {
return this.widget.config.mappings
}
return []
}
},
methods: {
updateMapping (idx, $event) {
const value = $event.target.value
if (!value) {
this.widget.config.mappings.splice(idx, 1)
} else {
this.$set(this.widget.config.mappings, idx, value)
}
},
addMapping () {
if (this.widget && this.widget.config && this.widget.config.mappings) {
this.widget.config.mappings.push('')
} else {
this.$set(this.widget.config, 'mappings', [''])
}
},
onSort (ev) {
const element = this.widget.config.mappings[ev.from]
this.widget.config.mappings.splice(ev.from, 1)
this.widget.config.mappings.splice(ev.to, 0, element)
}
}
}
</script>

View File

@ -5,7 +5,7 @@
<f7-list-input v-if="widget.component === 'Sitemap'" label="ID" type="text" placeholder="ID" :value="widget.uid" @input="widget.uid = $event.target.value"
required validate pattern="[A-Za-z0-9_]+" error-message="Required. Alphanumeric &amp; underscores only" :disabled="!createMode" />
<f7-list-input label="Label" type="text" placeholder="Label" :value="widget.config.label" @input="updateParameter('label', $event)" clear-button />
<item-picker v-if="widget.component !== 'Sitemap'" title="Item" :value="widget.config.item" @input="(value) => widget.config.item = value" />
<item-picker v-if="widget.component !== 'Sitemap' && widget.component !== 'Frame'" title="Item" :value="widget.config.item" @input="(value) => widget.config.item = value" />
<ul v-if="widget.component !== 'Sitemap'">
<f7-list-input ref="icon" label="Icon" autocomplete="off" type="text" placeholder="temperature, firstfloor..." :value="widget.config.icon"
@input="updateParameter('icon', $event)" clear-button>
@ -23,14 +23,17 @@
<f7-list-input v-if="supports('period')" label="Period" type="text" :value="widget.config.period" @input="updateParameter('period', $event)" clear-button />
<f7-list-input v-if="supports('height')" label="Height" type="number" :value="widget.config.height" @input="updateParameter('height', $event)" clear-button />
<f7-list-input v-if="supports('sendFrequency')" label="Frequency" type="text" :value="widget.config.sendFrequency" @input="updateParameter('sendFrequency', $event)" clear-button />
<f7-list-input v-if="supports('frequency')" label="Frequency" type="text" :value="widget.config.frequency" @input="updateParameter('frequency', $event)" clear-button />
<f7-list-input v-if="supports('minValue')" label="Minimum" type="number" :value="widget.config.minValue" @input="updateParameter('minValue', $event)" clear-button />
<f7-list-input v-if="supports('maxValue')" label="Maximum" type="number" :value="widget.config.maxValue" @input="updateParameter('maxValue', $event)" clear-button />
<f7-list-input v-if="supports('step')" label="Step" type="number" :value="widget.config.step" @input="updateParameter('step', $event)" clear-button />
<f7-list-input v-if="supports('separator')" label="Separator" type="text" :value="widget.config.separator" @input="updateParameter('separator', $event)" clear-button />
<f7-list-input v-if="supports('yAxisDecimalPattern')" label="Y-axis decimal pattern" type="text" :value="widget.config.separator" @input="updateParameter('yAxisDecimalPattern', $event)" clear-button />
<f7-list-item v-if="supports('switchEnabled')" title="Switch enabled">
<f7-toggle slot="after" :checked="widget.config.switchEnabled" @toggle:change="widget.config.switchEnabled = $event" />
</f7-list-item>
<f7-list-item v-if="supports('forceAsItem')" title="Force as item">
<f7-toggle slot="after" :checked="widget.config.forceAsItem" @toggle:change="widget.config.forceAsItem = $event" />
</f7-list-item>
</ul>
</f7-list>
</f7-card-content>
@ -67,13 +70,13 @@ export default {
additionalControls: {
Image: ['url', 'refresh'],
Video: ['url', 'encoding'],
Chart: ['service', 'period', 'refresh', 'legend'],
Chart: ['service', 'period', 'refresh', 'legend', 'forceAsItem', 'yAxisDecimalPattern'],
Webview: ['url', 'height'],
Mapview: ['height'],
Slider: ['sendFrequency', 'switchEnabled', 'minValue', 'maxValue', 'step'],
List: ['separator'],
Setpoint: ['minValue', 'maxValue', 'step'],
Colorpicker: ['frequency'],
Colorpicker: ['sendFrequency'],
Default: ['height']
}
}

View File

@ -46,9 +46,25 @@
Nothing selected
</div>
</f7-block>
<f7-block v-if="selectedWidget && selectedWidget.component !== 'Sitemap'">
<div><f7-block-title>Visibility</f7-block-title></div>
<attribute-details :widget="selectedWidget" attribute="visibility" placeholder="item_name operator value" />
</f7-block>
<f7-block v-if="selectedWidget && selectedWidget.component !== 'Sitemap'">
<div><f7-block-title>Label Color</f7-block-title></div>
<attribute-details :widget="selectedWidget" attribute="labelcolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block v-if="selectedWidget && selectedWidget.component !== 'Sitemap'">
<div><f7-block-title>Value Color</f7-block-title></div>
<attribute-details :widget="selectedWidget" attribute="valuecolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block v-if="selectedWidget && selectedWidget.component === 'Image'">
<div><f7-block-title>Icon Color</f7-block-title></div>
<attribute-details :widget="selectedWidget" attribute="iconcolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block v-if="selectedWidget && ['Switch', 'Selection'].indexOf(selectedWidget.component) >= 0">
<div><f7-block-title>Mappings</f7-block-title></div>
<mapping-details :widget="selectedWidget" />
<attribute-details :widget="selectedWidget" attribute="mappings" placeholder="command = label" />
</f7-block>
<f7-block v-if="selectedWidget && canAddChildren">
<div><f7-block-title>Add Child Widget</f7-block-title></div>
@ -101,8 +117,20 @@
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'widget'">
<widget-details :widget="selectedWidget" :createMode="createMode" @remove="removeWidget" @movedown="moveWidgetDown" @moveup="moveWidgetUp" />
</f7-block>
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'visibility' && selectedWidget.component !== 'Sitemap'">
<attribute-details :widget="selectedWidget" attribute="visibility" placeholder="item_name operator value" />
</f7-block>
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'labelcolor' && selectedWidget.component !== 'Sitemap'">
<attribute-details :widget="selectedWidget" attribute="labelcolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'valuecolor' && selectedWidget.component !== 'Sitemap'">
<attribute-details :widget="selectedWidget" attribute="valuecolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'iconcolor' && selectedWidget.component === 'Image'">
<attribute-details :widget="selectedWidget" attribute="iconcolor" placeholder="item_name operator value = color" />
</f7-block>
<f7-block style="margin-bottom: 6rem" v-if="selectedWidget && detailsTab === 'mappings' && ['Switch', 'Selection'].indexOf(selectedWidget.component) >= 0">
<mapping-details :widget="selectedWidget" />
<attribute-details :widget="selectedWidget" attribute="mappings" placeholder="command = label" />
</f7-block>
</f7-page>
</f7-sheet>
@ -176,7 +204,7 @@
<script>
import SitemapCode from '@/components/pagedesigner/sitemap/sitemap-code.vue'
import WidgetDetails from '@/components/pagedesigner/sitemap/widget-details.vue'
import MappingDetails from '@/components/pagedesigner/sitemap/mapping-details.vue'
import AttributeDetails from '@/components/pagedesigner/sitemap/attribute-details.vue'
import DirtyMixin from '../../dirty-mixin'
export default {
@ -184,7 +212,7 @@ export default {
components: {
SitemapCode,
WidgetDetails,
MappingDetails
AttributeDetails
},
props: ['createMode', 'uid'],
data () {
@ -285,6 +313,7 @@ export default {
}
},
save (stay) {
this.cleanConfig(this.sitemap)
if (!this.sitemap.uid) {
this.$f7.dialog.alert('Please give an ID to the sitemap')
return
@ -331,6 +360,9 @@ export default {
cleanConfig (widget) {
if (widget.config) {
for (let key in widget.config) {
if (widget.config[key] && Array.isArray(widget.config[key])) {
widget.config[key] = widget.config[key].filter(Boolean)
}
if (!widget.config[key]) {
delete widget.config[key]
}