diff --git a/bundles/org.openhab.ui/web/src/assets/i18n/common/en.json b/bundles/org.openhab.ui/web/src/assets/i18n/common/en.json index f76f4d9c9..694c4b260 100644 --- a/bundles/org.openhab.ui/web/src/assets/i18n/common/en.json +++ b/bundles/org.openhab.ui/web/src/assets/i18n/common/en.json @@ -6,6 +6,7 @@ "dialogs.save": "Save", "dialogs.close": "Close", "dialogs.copy": "Copy", + "dialogs.create": "Create", "dialogs.delete": "Delete", "dialogs.reload": "Reload", "dialogs.retry": "Try Again", diff --git a/bundles/org.openhab.ui/web/src/assets/i18n/rule-status/en.json b/bundles/org.openhab.ui/web/src/assets/i18n/rule-status/en.json index af482b281..7cf09ed03 100644 --- a/bundles/org.openhab.ui/web/src/assets/i18n/rule-status/en.json +++ b/bundles/org.openhab.ui/web/src/assets/i18n/rule-status/en.json @@ -3,6 +3,7 @@ "HANDLER_INITIALIZING_ERROR": "ERROR: HANDLER", "HANDLER_MISSING_ERROR": "ERROR: HANDLER", "TEMPLATE_MISSING_ERROR": "ERROR: TEMPLATE", + "TEMPLATE_PENDING": "TEMPLATE PENDING", "INVALID_RULE": "INVALID", "DISABLED": "DISABLED" } diff --git a/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue b/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue index db497d3f9..7a263511d 100644 --- a/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue +++ b/bundles/org.openhab.ui/web/src/components/config/controls/script-editor.vue @@ -178,6 +178,7 @@ export default { // 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 === 'application/json' || mode === 'json') return 'application/json' if (mode.startsWith('application/javascript') || mode === 'js') return 'text/javascript' if (mode === 'application/vnd.openhab.dsl.rule') return 'text/x-java' if (mode === 'application/x-groovy' || mode === 'groovy') return 'text/x-groovy' @@ -234,7 +235,7 @@ export default { onCmReady (cm) { const self = this let extraKeys = {} - if (this.mode.indexOf('application/javascript') === 0) { + if (this.mode && this.mode.indexOf('application/javascript') === 0) { window.tern = tern if (this.ternAutocompletionHook) { tern.registerPlugin('openhab-tern-hook', (server, options) => { @@ -281,7 +282,7 @@ export default { closeOnUnfocus: false, completeSingle: self.mode && self.mode.indexOf('yaml') > 0, hint (cm, option) { - if (self.mode.indexOf('application/vnd.openhab.uicomponent') === 0) { + if (self.mode && self.mode.indexOf('application/vnd.openhab.uicomponent') === 0) { return componentsHint(cm, option, self.mode) } else if (self.mode === 'application/vnd.openhab.item+yaml') { return itemsHint(cm, option, self.mode) diff --git a/bundles/org.openhab.ui/web/src/components/rule/action-module-wizard.vue b/bundles/org.openhab.ui/web/src/components/rule/action-module-wizard.vue index dd3cebc1b..304265ddd 100644 --- a/bundles/org.openhab.ui/web/src/components/rule/action-module-wizard.vue +++ b/bundles/org.openhab.ui/web/src/components/rule/action-module-wizard.vue @@ -151,7 +151,7 @@ import ConfigSheet from '@/components/config/config-sheet.vue' export default { mixins: [ModuleWizard], - props: ['currentModule', 'currentModuleType'], + props: ['currentModule', 'currentModuleType', 'moduleTypes'], components: { ItemPicker, ConfigSheet @@ -196,18 +196,16 @@ export default { }, chooseScriptCategory () { this.category = 'script' - this.$emit('typeSelect', 'script.ScriptAction') - this.$nextTick(() => { - this.$set(this, 'languages', this.currentModuleType.configDescriptions - .find((c) => c.name === 'type').options - .map((l) => { - return { - contentType: l.value, - name: l.label.split(' (')[0], - version: l.label.split(' (')[1].replace(')', '') - } - })) - }) + let moduleType = this.moduleTypes.find((t) => t.uid === 'script.ScriptAction') + if (moduleType) { + this.$set(this, 'languages', moduleType.configDescriptions.find((c) => c.name === 'type').options.map((l) => { + return { + contentType: l.value, + name: l.label.split(' (')[0], + version: l.label.split(' (')[1].replace(')', '') + } + })) + } }, chooseRulesCategory () { this.category = 'rules' @@ -272,7 +270,10 @@ export default { // this.$set(this.currentModule.configuration, 'command', hsb.join(',')) }, scriptLanguagePicked (value) { - this.$emit('startScript', value) + this.$emit('typeSelect', 'script.ScriptAction') + this.$nextTick(() => { + this.$emit('startScript', value) + }) }, itemPicked (value) { this.category = 'item' diff --git a/bundles/org.openhab.ui/web/src/components/rule/condition-module-wizard.vue b/bundles/org.openhab.ui/web/src/components/rule/condition-module-wizard.vue index 8b11bcb3b..0006c312c 100644 --- a/bundles/org.openhab.ui/web/src/components/rule/condition-module-wizard.vue +++ b/bundles/org.openhab.ui/web/src/components/rule/condition-module-wizard.vue @@ -129,7 +129,7 @@ import ConfigSheet from '@/components/config/config-sheet.vue' export default { mixins: [ModuleWizard], - props: ['currentModule', 'currentModuleType'], + props: ['currentModule', 'currentModuleType', 'moduleTypes'], components: { ItemPicker, ConfigSheet @@ -158,18 +158,16 @@ export default { }, chooseScriptCategory () { this.category = 'script' - this.$emit('typeSelect', 'script.ScriptCondition') - this.$nextTick(() => { - this.$set(this, 'languages', this.currentModuleType.configDescriptions - .find((c) => c.name === 'type').options - .map((l) => { - return { - contentType: l.value, - name: l.label.split(' (')[0], - version: l.label.split(' (')[1].replace(')', '') - } - })) - }) + let moduleType = this.moduleTypes.find((t) => t.uid === 'script.ScriptCondition') + if (moduleType) { + this.$set(this, 'languages', moduleType.configDescriptions.find((c) => c.name === 'type').options.map((l) => { + return { + contentType: l.value, + name: l.label.split(' (')[0], + version: l.label.split(' (')[1].replace(')', '') + } + })) + } }, chooseTimeCategory () { this.category = 'time' @@ -228,7 +226,10 @@ export default { } }, scriptLanguagePicked (value) { - this.$emit('startScript', value) + this.$emit('typeSelect', 'script.ScriptCondition') + this.$nextTick(() => { + this.$emit('startScript', value) + }) }, itemPicked (value) { this.category = 'item' diff --git a/bundles/org.openhab.ui/web/src/components/rule/rule-general-settings.vue b/bundles/org.openhab.ui/web/src/components/rule/rule-general-settings.vue index b9057dc3c..d2506e8f1 100644 --- a/bundles/org.openhab.ui/web/src/components/rule/rule-general-settings.vue +++ b/bundles/org.openhab.ui/web/src/components/rule/rule-general-settings.vue @@ -3,19 +3,20 @@ - + - + @@ -24,7 +25,7 @@ - - + @@ -44,7 +45,7 @@ import TagInput from '@/components/tags/tag-input.vue' export default { - props: ['rule', 'ready', 'createMode', 'hasRuleTemplate', 'inScriptEditor', 'inSceneEditor'], + props: ['rule', 'ready', 'createMode', 'stubMode', 'templateName', 'inScriptEditor', 'inSceneEditor'], components: { TagInput }, diff --git a/bundles/org.openhab.ui/web/src/components/rule/rule-status-mixin.js b/bundles/org.openhab.ui/web/src/components/rule/rule-status-mixin.js index aa2797b17..76da0780f 100644 --- a/bundles/org.openhab.ui/web/src/components/rule/rule-status-mixin.js +++ b/bundles/org.openhab.ui/web/src/components/rule/rule-status-mixin.js @@ -3,14 +3,15 @@ import RuleStatusLabels from '@/assets/i18n/rule-status/en' export default { methods: { ruleStatusBadgeColor (statusInfo) { - if (statusInfo.status === 'IDLE') return 'green' - if (statusInfo.statusDetail === 'DISABLED') return 'gray' - if (statusInfo.status === 'UNINITIALIZED') return 'red' - if (statusInfo.status === 'INITIALIZING') return 'yellow' - if (statusInfo.status === 'RUNNING') return 'orange' + if (statusInfo?.status === 'IDLE') return 'green' + if (statusInfo?.statusDetail === 'DISABLED') return 'gray' + if (statusInfo?.status === 'UNINITIALIZED') return statusInfo.statusDetail === 'TEMPLATE_PENDING' ? 'orange' : 'red' + if (statusInfo?.status === 'INITIALIZING') return 'yellow' + if (statusInfo?.status === 'RUNNING') return 'orange' return 'green' }, ruleStatusBadgeText (statusInfo) { + if (!statusInfo?.status) return '' if (statusInfo.status === 'IDLE') return 'IDLE' if (statusInfo.statusDetail !== 'NONE') return RuleStatusLabels[statusInfo.statusDetail] return statusInfo.status diff --git a/bundles/org.openhab.ui/web/src/js/routes.js b/bundles/org.openhab.ui/web/src/js/routes.js index c58c84fe0..2900da78f 100644 --- a/bundles/org.openhab.ui/web/src/js/routes.js +++ b/bundles/org.openhab.ui/web/src/js/routes.js @@ -341,6 +341,12 @@ export default [ beforeLeave: [checkDirtyBeforeLeave], async: loadAsync(RuleEditPage, { createMode: true }) }, + { + path: 'stub', + beforeEnter: [enforceAdminForRoute], + beforeLeave: [checkDirtyBeforeLeave], + async: loadAsync(RuleEditPage, { createMode: false, stubMode: true }) + }, { path: ':ruleId', beforeEnter: [enforceAdminForRoute], diff --git a/bundles/org.openhab.ui/web/src/pages/settings/dirty-mixin.js b/bundles/org.openhab.ui/web/src/pages/settings/dirty-mixin.js index 636299e5c..37b2676d5 100644 --- a/bundles/org.openhab.ui/web/src/pages/settings/dirty-mixin.js +++ b/bundles/org.openhab.ui/web/src/pages/settings/dirty-mixin.js @@ -40,7 +40,9 @@ export default { switchTab (tab, onSuccessCallback) { if (this.currentTab !== tab) { this.currentTab = tab - onSuccessCallback() + if (onSuccessCallback) { + onSuccessCallback() + } } } } diff --git a/bundles/org.openhab.ui/web/src/pages/settings/rules/module-description-suggestions.js b/bundles/org.openhab.ui/web/src/pages/settings/rules/module-description-suggestions.js index fb16dcc11..2f1ab60a9 100644 --- a/bundles/org.openhab.ui/web/src/pages/settings/rules/module-description-suggestions.js +++ b/bundles/org.openhab.ui/web/src/pages/settings/rules/module-description-suggestions.js @@ -2,9 +2,30 @@ import cronstrue from 'cronstrue' export default { methods: { + findModuleType (mod, section) { + if (!mod || !this.moduleTypes) { + return undefined + } + let result + if (section) { + return this.moduleTypes[section]?.find((m) => m.uid === mod.type) + } else { + if (this.moduleTypes.actions) { + result = this.moduleTypes.actions.find((m) => m.uid === mod.type) + } + if (!result && this.moduleTypes.triggers) { + result = this.moduleTypes.triggers.find((m) => m.uid === mod.type) + } + if (!result && this.moduleTypes.conditions) { + result = this.moduleTypes.conditions.find((m) => m.uid === mod.type) + } + return result + } + }, suggestedModuleTitle (mod, moduleType, section) { if (!moduleType) { - moduleType = this.moduleTypes[section].find((m) => m.uid === mod.type) + if (!this.moduleTypes) return 'Name' + moduleType = this.findModuleType(mod, section) if (!moduleType) return 'Name' } const config = mod.configuration @@ -93,7 +114,8 @@ export default { }, suggestedModuleDescription (mod, moduleType, section) { if (!moduleType) { - moduleType = this.moduleTypes[section].find((m) => m.uid === mod.type) + if (!this.moduleTypes) return 'Description' + moduleType = this.findModuleType(mod, section) if (!moduleType) return 'Description' } const config = mod.configuration diff --git a/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit-mixin.js b/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit-mixin.js index 469d34b80..f0f756bf9 100644 --- a/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit-mixin.js +++ b/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit-mixin.js @@ -100,6 +100,12 @@ export default { case 'state': this.$set(this.rule, 'status', JSON.parse(event.payload)) // e.g. {"status":"RUNNING","statusDetail":"NONE"} break + case 'added': + case 'updated': + if (!this.dirty) { + this.load() + } + break } }) }, diff --git a/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit.vue b/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit.vue index 4c8424d70..9662e6ee7 100644 --- a/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit.vue +++ b/bundles/org.openhab.ui/web/src/pages/settings/rules/rule-edit.vue @@ -1,14 +1,15 @@ @@ -178,7 +250,36 @@ position absolute top 80% white-space pre-wrap - +#source + .block-narrow + position relative + height var(--f7-toolbar-height) + color var(--f7-block-strong-text-color) + background-color var(--f7-block-strong-bg-color) + .col + display flex + position relative + top 50% + transform translate(0, -50%) + flex-wrap nowrap + justify-content space-between + align-items center + .left + margin-right auto + .middle + position: absolute; + left: 50%; + transform: translate(-50%, 0); + .right + margin-left auto + .rule-source-viewer.vue-codemirror + display block + top calc(var(--f7-navbar-height) + var(--f7-tabbar-height)) + height calc(100% - 2*var(--f7-navbar-height) - var(--f7-toolbar-height) - var(--f7-block-margin-vertical) - 1rem) + width 100% + .source-type-text + font-size var(--f7-navbar-subtitle-font-size) + color var(--f7-block-footer-text-color)