Transformations: Various enhancements & fixes (#1845)
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 https://github.com/openhab/openhab-core/pull/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>pull/1852/head
parent
af1b0a31cd
commit
b317e98a8d
|
@ -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'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -81,10 +84,12 @@
|
|||
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')
|
||||
|
@ -92,12 +97,11 @@ export default {
|
|||
props: ['transformationId', 'createMode'],
|
||||
data () {
|
||||
return {
|
||||
newTransformation: this.createMode,
|
||||
newTransformation: this.createMode || false,
|
||||
ready: false,
|
||||
loading: false,
|
||||
transformation: {},
|
||||
types: [],
|
||||
scriptLanguages: [],
|
||||
languages: [],
|
||||
language: '',
|
||||
detailsOpened: false,
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -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({
|
||||
|
@ -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 () {
|
||||
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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 />
|
||||
|
@ -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: {
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
@ -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)"
|
||||
|
@ -123,7 +123,7 @@ export default {
|
|||
loading: false,
|
||||
transformations: [],
|
||||
initSearchbar: false,
|
||||
selectedItems: [],
|
||||
selectedTransformations: [],
|
||||
groupBy: 'alphabetical',
|
||||
showCheckboxes: false
|
||||
}
|
||||
|
@ -142,8 +142,8 @@ 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] = []
|
||||
}
|
||||
|
@ -151,6 +151,10 @@ export default {
|
|||
|
||||
return prev
|
||||
}, {})
|
||||
return Object.keys(typeGroups).sort().reduce((objEntries, key) => {
|
||||
objEntries[key] = typeGroups[key]
|
||||
return objEntries
|
||||
}, {})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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()
|
||||
|
@ -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) => {
|
||||
|
@ -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?
|
||||
|
|
Loading…
Reference in New Issue