Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
841 additions
and
227 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
bundles/org.openhab.ui/web/src/assets/definitions/blockly/libraries.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import Blockly from 'blockly' | ||
import { addOSGiService } from './utils' | ||
|
||
const generateCodeForBlock = (block) => { | ||
const blockTypeId = block.openhab.blockTypeId | ||
const definition = block.openhab.definition | ||
const library = block.openhab.library | ||
const codeComponent = (definition.slots && definition.slots.code && definition.slots.code[0]) ? definition.slots.code[0] : null | ||
|
||
const context = { | ||
fields: {}, | ||
inputs: {}, | ||
statements: {}, | ||
utilities: {}, | ||
uniqueIdentifiers: {} | ||
} | ||
|
||
const provideUtility = (utilityName) => { | ||
let utilityCode = [`function ${Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_}() { /* error! */ }`] | ||
if (library.slots.utilities) { | ||
const utilityComponent = library.slots.utilities.find(c => c.config && c.config.name === utilityName) | ||
if (!utilityComponent) { | ||
} else { | ||
switch (utilityComponent.component) { | ||
case 'UtilityFrameworkService': | ||
return addOSGiService(utilityName, utilityComponent.config.serviceClass) | ||
case 'UtilityJavaType': | ||
utilityCode = `var ${Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_} = Java.type('${utilityComponent.config.javaClass}');` | ||
break | ||
default: | ||
utilityCode = utilityComponent.config.code.replace('{{name}}', Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_) | ||
// process additional utilities if referenced in the function code | ||
while (/(\{\{[A-Za-z0-9_]+\}\})/gm.test(utilityCode)) { | ||
const match = /(\{\{[A-Za-z0-9_:]+\}\})/gm.exec(utilityCode) | ||
const referencedUtility = match[0].replace('{{', '').replace('}}', '') | ||
if (!context.utilities[referencedUtility]) { | ||
// BEWARE - risk of infinite recursion | ||
context.utilities[referencedUtility] = provideUtility(referencedUtility) | ||
} | ||
utilityCode = utilityCode.replace(match[0], context.utilities[referencedUtility]) | ||
} | ||
} | ||
} | ||
} | ||
|
||
return Blockly.JavaScript.provideFunction_(utilityName, utilityCode.split('\n')) | ||
} | ||
|
||
const processPlaceholder = (code, placeholder) => { | ||
const placeholderFields = placeholder.replace('{{', '').replace('}}', '').split(':').map((f) => f.trim()) | ||
if (placeholderFields.length >= 2) { | ||
const [placeholderType, placeholderName, placeholderOption] = placeholderFields | ||
switch (placeholderType) { | ||
case 'field': | ||
context.fields[placeholderName] = block.getFieldValue(placeholderName) | ||
return code.replace(placeholder, context.fields[placeholderName]) | ||
case 'input': | ||
const order = placeholderOption ? Blockly.JavaScript['ORDER_' + placeholderOption.replace('ORDER_', '')] : Blockly.JavaScript.ORDER_NONE | ||
context.inputs[placeholderName] = Blockly.JavaScript.valueToCode(block, placeholderName, order) | ||
return code.replace(placeholder, context.inputs[placeholderName]) | ||
case 'utility': | ||
if (!context.utilities[placeholderName]) { | ||
context.utilities[placeholderName] = provideUtility(placeholderName) | ||
} | ||
return code.replace(placeholder, context.utilities[placeholderName]) | ||
case 'temp_name': | ||
if (!context.uniqueIdentifiers[placeholderName]) { | ||
const realm = placeholderOption ? Blockly.Variables[placeholderOption] : Blockly.Variables.NAME_TYPE | ||
context.uniqueIdentifiers[placeholderName] = Blockly.JavaScript.variableDB_.getDistinctName(placeholderName, realm) | ||
} | ||
return code.replace(placeholder, context.uniqueIdentifiers[placeholderName]) | ||
case 'statements': | ||
if (!context.statements[placeholderName]) { | ||
context.statements[placeholderName] = Blockly.JavaScript.statementToCode(block, placeholderName) | ||
} | ||
return code.replace(placeholder, context.statements[placeholderName].replace(/^ {2}/, '').trim()) | ||
default: | ||
return code.replace(placeholder, `/* Invalid placeholder type ${placeholderType}! */`) | ||
} | ||
} | ||
} | ||
|
||
if (!codeComponent || !codeComponent.config || !codeComponent.config.template) { | ||
if (block.outputConnection) { | ||
return [`/* missing implementation for value block ${blockTypeId} */`, Blockly.JavaScript.ORDER_NONE] | ||
} else { | ||
return `/* missing implementation for statement block ${blockTypeId} */\n` | ||
} | ||
} | ||
|
||
const template = codeComponent.config.template | ||
let code = template | ||
|
||
while (/(\{\{[A-Za-z0-9_:]+\}\})/gm.test(code)) { | ||
const match = /(\{\{[A-Za-z0-9_:]+\}\})/gm.exec(code) | ||
code = processPlaceholder(code, match[0]) | ||
} | ||
|
||
if (block.outputConnection) { | ||
const order = codeComponent.config.order ? Blockly.JavaScript[codeComponent.config.order] : Blockly.JavaScript.ORDER_NONE | ||
return [code, order] | ||
} else { | ||
return code | ||
} | ||
} | ||
|
||
export const defineLibraryToolboxCategory = (library, f7) => (workspace) => { | ||
let category = [] | ||
if (library && library.slots && library.slots.blocks) { | ||
library.slots.blocks.forEach((blockTypeDefinition) => { | ||
const xml = `<block type="${library.uid}_${blockTypeDefinition.config.type}" />` | ||
const block = Blockly.Xml.textToDom(xml) | ||
category.push(block) | ||
}) | ||
} | ||
|
||
return category | ||
} | ||
|
||
export const defineLibraries = (libraryDefinitions) => { | ||
libraryDefinitions.forEach((library) => { | ||
if (library.slots && library.slots.blocks) { | ||
library.slots.blocks.forEach((block) => { | ||
const blockTypeId = library.uid + '_' + block.config.type | ||
Blockly.Blocks[blockTypeId] = { | ||
init: function () { | ||
this.jsonInit(block.config) | ||
}, | ||
openhab: { | ||
blockTypeId: blockTypeId, | ||
library: library, | ||
definition: block | ||
} | ||
} | ||
|
||
Blockly.JavaScript[blockTypeId] = generateCodeForBlock | ||
}) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
bundles/org.openhab.ui/web/src/pages/developer/blocks/block-preview.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<template> | ||
<div class="block-preview" ref="blockPreview"> | ||
<code v-if="definitionError" class="definition-error text-color-red" ref="blockPreview"> | ||
{{ definitionError }} | ||
</code> | ||
<f7-menu style="position: absolute; right: 20px; top: 20px"> | ||
<f7-menu-item style="margin-left: auto" :text="currentBlock" dropdown> | ||
<f7-menu-dropdown right> | ||
<f7-menu-dropdown-item | ||
v-for="block in blocksDefinition.slots.blocks" :key="block.config.type" | ||
@click="displayCurrentBlock(block)" href="#" :text="block.config.type" /> | ||
</f7-menu-dropdown> | ||
</f7-menu-item> | ||
</f7-menu> | ||
</div> | ||
</template> | ||
|
||
<style lang="stylus"> | ||
.block-preview | ||
position absolute | ||
top 0 | ||
left 0 | ||
right 0 | ||
bottom 0 | ||
.definition-error | ||
position absolute | ||
top calc(2rem + var(--f7-menu-item-height)) | ||
left 1rem | ||
right 1rem | ||
z-index 10000 | ||
.current-block-select | ||
position absolute | ||
top 20 | ||
right 20 | ||
z-index 10000 | ||
.blocklySvg | ||
background-color inherit !important | ||
.blocklyMainBackground | ||
stroke inherit | ||
.blocklyDropDownDiv | ||
z-index 9000 | ||
</style> | ||
|
||
<script> | ||
import Blockly from 'blockly' | ||
import Vue from 'vue' | ||
Vue.config.ignoredElements = ['field', 'block', 'category', 'xml', 'mutation', 'value', 'sep'] | ||
export default { | ||
props: ['blocksDefinition'], | ||
data () { | ||
return { | ||
workspace: null, | ||
definitionError: null, | ||
currentBlock: null | ||
} | ||
}, | ||
mounted () { | ||
this.initWorkspace() | ||
this.defineBlocks() | ||
}, | ||
watch: { | ||
blocksDefinition () { | ||
this.defineBlocks() | ||
} | ||
}, | ||
methods: { | ||
initWorkspace () { | ||
this.workspace = Blockly.inject(this.$refs.blockPreview, { | ||
theme: (this.$f7.data.themeOptions.dark === 'dark') ? 'dark' : undefined, | ||
trashcan: false, | ||
readOnly: false | ||
}) | ||
}, | ||
defineBlocks () { | ||
try { | ||
this.definitionError = null | ||
if (this.blocksDefinition && this.blocksDefinition.slots && this.blocksDefinition.slots.blocks) { | ||
this.blocksDefinition.slots.blocks.forEach((block) => { | ||
Blockly.Blocks[block.config.type] = { | ||
init: function () { | ||
// const blockJson = JSON.stringify(block.config) | ||
this.jsonInit(block.config) | ||
} | ||
} | ||
}) | ||
this.displayCurrentBlock() | ||
} | ||
} catch (e) { | ||
this.definitionError = e.toString() | ||
} | ||
}, | ||
displayCurrentBlock (block) { | ||
if (block) this.currentBlock = block.config.type | ||
if (!this.currentBlock) this.currentBlock = this.blocksDefinition.slots.blocks[0].config.type | ||
let xml = '<xml>' | ||
xml += `<block type="${this.currentBlock}" deletable="false"></block>` | ||
xml += '</xml>' | ||
this.workspace.clear() | ||
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), this.workspace) | ||
} | ||
} | ||
} | ||
</script> |
Oops, something went wrong.