Skip to content

Commit

Permalink
Transformations: Various enhancements & fixes (#1845)
Browse files Browse the repository at this point in the history
Closes #1844.

* Allows to copy the UID.
* Sorts the transformation types in the "by types" list alphabetically
and makes the segment labels uppercase to match the transformation
service names.
* Adjusts to recent core change
openhab/openhab-core#3487: removes the script
language selection and updates the editor mode and code snippet
injection.
* Extends language support and refactors the translate mode logic in
`script-editor.vue`.
* Fixes the clear label button not displayed in create mode and
displayed for non-editable transformations.
* Shows a tip on how to use the transformation for Item states.
* Fixes failing and not required API request after transformation
creation.
* Removes possibility to select and attempt to delete not-editable
transformations, which is not possible and failed.

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
  • Loading branch information
florian-h05 authored Apr 16, 2023
1 parent af1b0a3 commit b317e98
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
export default {
SNIPPETS: {
SCRIPT: {
'application/vnd.openhab.dsl.rule': 'var returnValue = "String has " + input.length + " characters"\n\nreturnValue',
'application/javascript': '(function(data) {\n var returnValue = "String has " + data.length + " characters"\n return returnValue\n})(input)',
'application/javascript;version=ECMAScript-5.1': '(function(data) {\n var returnValue = "String has " + data.length + " characters"\n return returnValue\n})(input)',
'application/x-ruby': '"String has #{input.length} characters"'
}
DSL: 'var returnValue = "String has " + input.length + " characters"\n\nreturnValue',
JINJA: '{# Available values:\nvalue - The incoming value.\nvalue_json - The incoming value parsed as JSON. #}\n{{value_json[\'AM2301\'].Temperature}}',
JS: '(function(data) {\n var returnValue = "String has " + data.length + " characters"\n return returnValue\n})(input)',
NASHORNJS: '(function(data) {\n var returnValue = "String has " + data.length + " characters"\n return returnValue\n})(input)',
MAP: 'key=value\n=default',
RB: '"String has #{input.length} characters"',
XSLT: '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n' +
'<xsl:stylesheet version=\'2.0\' xmlns:xsl=\'http://www.w3.org/1999/XSL/Transform\'>\n' +
' <xsl:output method=\'xml\' indent=\'no\'/>\n' +
' <xsl:template match=\'/\'>\n' +
' <reRoot><reNode><xsl:value-of select=\'/root/node/@val\' /> world</reNode></reRoot>\n' +
' </xsl:template>\n' +
'</xsl:stylesheet>'
},
EDITOR_MODES: {
DSL: 'application/vnd.openhab.dsl.rule',
EXEC: 'application/x-sh',
MAP: 'text/x-properties',
SCALE: 'text/x-properties'
NASHORNJS: 'application/javascript;version=ECMAScript-5.1',
SCALE: 'text/x-properties',
XSLT: 'application/xml'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ import _CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// language js
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/clike/clike.js'
import 'codemirror/mode/groovy/groovy.js'
import 'codemirror/mode/jinja2/jinja2.js'
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/properties/properties.js'
import 'codemirror/mode/python/python.js'
import 'codemirror/mode/ruby/ruby.js'
import 'codemirror/mode/shell/shell.js'
import 'codemirror/mode/xml/xml.js'
import 'codemirror/mode/yaml/yaml.js'
import 'codemirror/mode/properties/properties.js'
// theme css
import 'codemirror/theme/gruvbox-dark.css'
Expand Down Expand Up @@ -168,15 +171,16 @@ export default {
},
methods: {
translateMode (mode) {
if (this.mode && this.mode.indexOf('yaml') >= 0) return 'text/x-yaml'
if (this.mode && this.mode.indexOf('application/javascript') === 0) return 'text/javascript'
if (this.mode && this.mode === 'application/vnd.openhab.dsl.rule') return 'text/x-java'
if (this.mode && this.mode.indexOf('python') >= 0) return 'text/x-python'
if (this.mode && this.mode.indexOf('ruby') >= 0) return 'text/x-ruby'
if (this.mode && this.mode.indexOf('groovy') >= 0) return 'text/x-groovy'
if (this.mode && this.mode === 'rb') return 'text/x-ruby'
if (this.mode && this.mode === 'properties') return 'text/x-properties'
return this.mode
// Translations required for some special modes used in MainUI
// See https://codemirror.net/5/mode/index.html for supported language names & MIME types
if (!mode) return mode
if (mode.indexOf('yaml') >= 0) return 'text/x-yaml'
if (mode.indexOf('application/javascript') === 0 || mode === 'js') return 'text/javascript'
if (mode === 'application/vnd.openhab.dsl.rule') return 'text/x-java'
if (mode === 'py') return 'text/x-python'
if (mode === 'rb') return 'text/x-ruby'
if (mode.indexOf('jinja') >= 0) return 'text/jinja2'
return mode
},
ternComplete (file, query) {
let pos = tern.resolvePos(file, query.end)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</f7-nav-right>
</f7-navbar>
<!-- Create Transformation -->
<transformation-general-settings v-if="newTransformation && ready" :createMode="true" :transformation="transformation" :types="types" :languages="languages" :language="language" :scriptLanguages="scriptLanguages" @newLanguage="language = $event" @newType="transformation.type = $event" @newScriptMimeType="transformation.configuration.mode = $event" />
<transformation-general-settings v-if="newTransformation && ready" :createMode="true" :transformation="transformation" :types="types" :languages="languages" :language="language" @newType="transformation.type = $event" @newLanguage="language = $event" />
<div v-if="ready && newTransformation" class="if-aurora display-flex justify-content-center margin padding">
<div class="flex-shrink-0">
<f7-button class="padding-left padding-right" style="width: 150px" color="blue" large raised fill @click="createTransformation">
Expand Down Expand Up @@ -56,12 +56,15 @@
</f7-toolbar>
<f7-block class="block-narrow">
<transformation-general-settings :createMode="newTransformation" :transformation="transformation" />
<f7-col v-if="isEditable">
<f7-list>
<f7-col>
<f7-list v-if="isEditable">
<f7-list-button color="red" @click="deleteTransformation">
Remove Transformation
</f7-list-button>
</f7-list>
<p v-if="ready" class="text-align-center">
Tip: Use <code>{{ itemStateTransformationCode }}</code> <clipboard-icon :value="itemStateTransformationCode" tooltip="Copy transformation" /> for Item state transformations.
</p>
</f7-col>
</f7-block>
</f7-page>
Expand All @@ -81,23 +84,24 @@
import DirtyMixin from '../dirty-mixin'
import TransformationGeneralSettings from '@/pages/settings/transformations/transformation-general-settings'
import TransformationDefinitions from '@/assets/definitions/transformations.js'
import ClipboardIcon from '@/components/util/clipboard-icon.vue'
export default {
mixins: [DirtyMixin],
components: {
ClipboardIcon,
TransformationGeneralSettings,
'editor': () => import(/* webpackChunkName: "script-editor" */ '@/components/config/controls/script-editor.vue'),
'blockly-editor': () => import(/* webpackChunkName: "blockly-editor" */ '@/components/config/controls/blockly-editor.vue')
},
props: ['transformationId', 'createMode'],
data () {
return {
newTransformation: this.createMode,
newTransformation: this.createMode || false,
ready: false,
loading: false,
transformation: {},
types: [],
scriptLanguages: [],
languages: [],
language: '',
detailsOpened: false,
Expand All @@ -113,6 +117,9 @@ export default {
// TODO: Enable Blockly after blocks have been adjusted
// return this.transformation.configuration && this.transformation.configuration.blockSource
return false
},
itemStateTransformationCode () {
return `${this.transformation.type.toUpperCase()}(${this.transformationId})`
}
},
methods: {
Expand Down Expand Up @@ -143,20 +150,10 @@ export default {
},
editable: true
}
Promise.all([this.$oh.api.get('/rest/transformations/services'), this.$oh.api.get('/rest/module-types/script.ScriptAction'), this.$oh.api.get('/rest/config-descriptions/system:i18n')]).then((data) => {
Promise.all([this.$oh.api.get('/rest/transformations/services'), this.$oh.api.get('/rest/config-descriptions/system:i18n')]).then((data) => {
this.$set(this, 'types', data[0])
this.$set(this, 'scriptLanguages', data[1].configDescriptions
.find((c) => c.name === 'type').options
.map((l) => {
return {
contentType: l.value,
name: l.label.split(' (')[0],
version: l.label.split(' (')[1].replace(')', '')
}
}))
this.$set(this, 'languages', data[2].parameters.find(p => p.name === 'language').options)
this.$set(this, 'languages', data[1].parameters.find(p => p.name === 'language').options)
})
this.language = ''
this.ready = true
Expand All @@ -182,10 +179,8 @@ export default {
this.transformation.uid += ':' + this.language
}
// Insert code examples for SCRIPT
if (this.transformation.type === 'SCRIPT') {
this.transformation.configuration.function = TransformationDefinitions.SNIPPETS.SCRIPT[this.transformation.configuration.mode]
}
// Insert code example if available
if (TransformationDefinitions.SNIPPETS[this.transformation.type.toUpperCase()]) this.transformation.configuration.function = TransformationDefinitions.SNIPPETS[this.transformation.type.toUpperCase()]
this.$oh.api.put('/rest/transformations/' + this.transformation.uid, this.transformation).then(() => {
this.$f7.toast.create({
Expand All @@ -194,12 +189,6 @@ export default {
closeTimeout: 2000
}).open()
this.$f7router.navigate(this.$f7route.url.replace('/add', '/' + this.transformation.uid), { reloadCurrent: true })
this.newTransformation = false
this.ready = false
if (window) {
window.addEventListener('keydown', this.keyDown)
}
this.load()
})
},
load () {
Expand All @@ -208,7 +197,7 @@ export default {
this.$oh.api.get('/rest/transformations/' + this.transformationId).then((data) => {
this.$set(this, 'transformation', data)
this.editorMode = (this.transformation.configuration.mode) ? this.transformation.configuration.mode : TransformationDefinitions.EDITOR_MODES[this.transformation.type.toUpperCase()]
this.editorMode = TransformationDefinitions.EDITOR_MODES[this.transformation.type.toUpperCase()] || this.transformation.type
this.loading = false
this.ready = true
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
<f7-block class="block-narrow">
<f7-col>
<f7-list class="no-margin" inline-labels no-hairlines-md>
<f7-list-input label="Unique ID" type="text" placeholder="Required" :value="transformation.uid"
required :validate="createMode" pattern="[A-Za-z0-9_]+" error-message="Required. A-Z,a-z,0-9,_ only"
:disabled="!createMode" :info="(createMode) ? 'Note: cannot be changed after the creation' : ''"
@input="transformation.uid = $event.target.value" :clear-button="createMode" />
<f7-list-input label="Label" type="text" placeholder="Required" :value="transformation.label" required validate
:disabled="!transformation.editable" @input="transformation.label = $event.target.value" :clear-button="!transformation.editable" />
<f7-list-input v-if="createMode" label="Unique ID" type="text" placeholder="Required" :value="transformation.uid"
required validate pattern="[A-Za-z0-9_]+" error-message="Required. A-Z,a-z,0-9,_ only"
info="Note: cannot be changed after the creation"
@input="transformation.uid = $event.target.value" clear-button />
<f7-list-item v-if="!createMode" media-item class="channel-item" title="Unique ID">
<div slot="subtitle">
{{ transformation.uid }}
<clipboard-icon :value="transformation.uid" tooltip="Copy UID" />
</div>
</f7-list-item>
<f7-list-input label="Label" type="text" placeholder="Required" :value="transformation.label" required validate :disabled="!transformation.editable" @input="transformation.label = $event.target.value" :clear-button="createMode || transformation.editable" />
<f7-list-item v-if="createMode && languages" title="Language" smart-select :smart-select-params="smartSelectParams">
<select name="language" @change="$emit('newLanguage', $event.target.value)">
<option value="" selected />
Expand All @@ -27,21 +32,16 @@
:title="type" />
</f7-list>
</f7-col>
<f7-col v-if="createMode && scriptLanguages && transformation.type === 'SCRIPT'">
<f7-block-title>Scripting Language</f7-block-title>
<f7-list media-list>
<f7-list-item media-item radio radio-icon="start"
:value="transformation.configuration.mode" :checked="transformation.configuration.mode === lang.contentType" @change="$emit('newScriptMimeType', lang.contentType)"
v-for="lang in scriptLanguages" :key="lang.contentType"
:title="lang.name" :after="lang.version" :footer="lang.contentType" />
</f7-list>
</f7-col>
</f7-block>
</template>

<script>
import ClipboardIcon from '@/components/util/clipboard-icon.vue'
export default {
props: ['transformation', 'createMode', 'types', 'languages', 'language', 'scriptLanguages'],
components: { ClipboardIcon },
props: ['transformation', 'createMode', 'types', 'languages', 'language'],
emits: ['newType', 'newLanguage'],
data () {
return {
smartSelectParams: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
</f7-subnavbar>
</f7-navbar>
<f7-toolbar class="contextual-toolbar" :class="{ 'navbar': $theme.md }" v-if="showCheckboxes" bottom-ios bottom-aurora>
<f7-link color="red" v-show="selectedItems.length" v-if="!$theme.md" class="delete" icon-ios="f7:trash" icon-aurora="f7:trash" @click="removeSelected">
Remove {{ selectedItems.length }}
<f7-link color="red" v-show="selectedTransformations.length" v-if="!$theme.md" class="delete" icon-ios="f7:trash" icon-aurora="f7:trash" @click="removeSelected">
Remove {{ selectedTransformations.length }}
</f7-link>
<f7-link v-if="$theme.md" icon-md="material:close" icon-color="white" @click="showCheckboxes = false" />
<div class="title" v-if="$theme.md">
{{ selectedItems.length }} selected
{{ selectedTransformations.length }} selected
</div>
<div class="right" v-if="$theme.md">
<f7-link v-show="selectedItems.length" icon-md="material:delete" icon-color="white" @click="removeSelected" />
<f7-link v-show="selectedTransformations.length" icon-md="material:delete" icon-color="white" @click="removeSelected" />
</div>
</f7-toolbar>

Expand Down Expand Up @@ -87,7 +87,7 @@
:key="transformation.uid"
media-item
class="transformationlist-item"
:checkbox="showCheckboxes"
:checkbox="showCheckboxes && transformation.editable"
:checked="isChecked(transformation.uid)"
@click.ctrl="(e) => ctrlClick(e, transformation)"
@click.meta="(e) => ctrlClick(e, transformation)"
Expand Down Expand Up @@ -123,7 +123,7 @@ export default {
loading: false,
transformations: [],
initSearchbar: false,
selectedItems: [],
selectedTransformations: [],
groupBy: 'alphabetical',
showCheckboxes: false
}
Expand All @@ -142,15 +142,19 @@ export default {
return prev
}, {})
} else {
return this.transformations.reduce((prev, transformation, i, transformations) => {
const type = transformation.type
const typeGroups = this.transformations.reduce((prev, transformation, i, transformations) => {
const type = transformation.type.toUpperCase()
if (!prev[type]) {
prev[type] = []
}
prev[type].push(transformation)
return prev
}, {})
return Object.keys(typeGroups).sort().reduce((objEntries, key) => {
objEntries[key] = typeGroups[key]
return objEntries
}, {})
}
}
},
Expand Down Expand Up @@ -191,33 +195,34 @@ export default {
toggleCheck () {
this.showCheckboxes = !this.showCheckboxes
},
isChecked (item) {
return this.selectedItems.indexOf(item) >= 0
isChecked (transformation) {
return this.selectedTransformations.indexOf(transformation) >= 0
},
click (event, item) {
click (event, transformation) {
if (this.showCheckboxes) {
this.toggleItemCheck(event, item.uid, item)
this.toggleTransformationCheck(event, transformation.uid, transformation)
} else {
this.$f7router.navigate(item.uid)
this.$f7router.navigate(transformation.uid)
}
},
ctrlClick (event, item) {
this.toggleItemCheck(event, item.uid, item)
if (!this.selectedItems.length) this.showCheckboxes = false
ctrlClick (event, transformation) {
this.toggleTransformationCheck(event, transformation.uid, transformation)
if (!this.selectedTransformations.length) this.showCheckboxes = false
},
toggleItemCheck (event, itemName, item) {
toggleTransformationCheck (event, transformationUid, transformation) {
if (!transformation.editable) return
if (!this.showCheckboxes) this.showCheckboxes = true
if (this.isChecked(itemName)) {
this.selectedItems.splice(this.selectedItems.indexOf(itemName), 1)
if (this.isChecked(transformationUid)) {
this.selectedTransformations.splice(this.selectedTransformations.indexOf(transformationUid), 1)
} else {
this.selectedItems.push(itemName)
this.selectedTransformations.push(transformationUid)
}
},
removeSelected () {
const vm = this
this.$f7.dialog.confirm(
`Remove ${this.selectedItems.length} selected transformations?`,
`Remove ${this.selectedTransformations.length} selected transformations?`,
'Remove Transformations',
() => {
vm.doRemoveSelected()
Expand All @@ -227,7 +232,7 @@ export default {
doRemoveSelected () {
let dialog = this.$f7.dialog.progress('Deleting Transformations...')
const promises = this.selectedItems.map((p) => {
const promises = this.selectedTransformations.map((p) => {
return this.$oh.api.delete('/rest/transformations/' + p)
})
Promise.all(promises).then((data) => {
Expand All @@ -236,7 +241,7 @@ export default {
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.selectedItems = []
this.selectedTransformations = []
dialog.close()
this.load()
this.$f7.emit('sidebarRefresh', null) // for what?
Expand Down

0 comments on commit b317e98

Please sign in to comment.