iobroker.javascript
Version:
Rules Engine for ioBroker
1,168 lines (1,018 loc) • 42.8 kB
JavaScript
'use strict';
if (typeof goog !== 'undefined') {
goog.provide('Blockly.JavaScript.Trigger');
goog.require('Blockly.JavaScript');
}
Blockly.CustomBlocks = Blockly.CustomBlocks || [];
Blockly.CustomBlocks.push('Trigger');
Blockly.Trigger = {
HUE: 330,
blocks: {}
};
// --- ON Extended-----------------------------------------------------------
Blockly.Trigger.blocks['on_ext'] =
'<block type="on_ext">'
+ ' <mutation items="1"></mutation>'
+ ' <value name="CONDITION">'
+ ' </value>'
+ ' <value name="ACK_CONDITION">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['on_ext_oid_container'] = {
/**
* Mutator block for container.
* @this Blockly.Block
*/
init: function() {
this.setColour(Blockly.Trigger.HUE);
this.appendDummyInput()
.appendField(Blockly.Translate('on_ext_on'));
this.appendStatementInput('STACK');
this.setTooltip(Blockly.Translate('on_ext_on_tooltip'));
this.contextMenu = false;
}
};
Blockly.Blocks['on_ext_oid'] = {
/**
* Mutator block for add items.
* @this Blockly.Block
*/
init: function() {
this.setColour(Blockly.Trigger.HUE);
this.appendDummyInput('OID')
.appendField(Blockly.Translate('on_ext_oid'));
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip(Blockly.Translate('on_ext_oid_tooltip'));
this.contextMenu = false;
}
};
Blockly.Blocks['on_ext'] = {
init: function() {
this.itemCount_ = 1;
this.setMutator(new Blockly.Mutator(['on_ext_oid']));
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('on_ext_tooltip'));
this.setHelpUrl(getHelp('on_help'));
},
/**
* Create XML to represent number of text inputs.
* @return {!Element} XML storage element.
* @this Blockly.Block
*/
mutationToDom: function () {
const container = document.createElement('mutation');
container.setAttribute('items', this.itemCount_);
return container;
},
/**
* Parse XML to restore the text inputs.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
*/
domToMutation: function (xmlElement) {
this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
this.updateShape_();
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
* @return {!Blockly.Block} Root block in mutator.
* @this Blockly.Block
*/
decompose: function (workspace) {
const containerBlock = workspace.newBlock('on_ext_oid_container');
containerBlock.initSvg();
let connection = containerBlock.getInput('STACK').connection;
for (let i = 0; i < this.itemCount_; i++) {
const itemBlock = workspace.newBlock('on_ext_oid');
itemBlock.initSvg();
connection.connect(itemBlock.previousConnection);
connection = itemBlock.nextConnection;
}
return containerBlock;
},
/**
* Reconfigure this block based on the mutator dialog's components.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
compose: function (containerBlock) {
let itemBlock = containerBlock.getInputTargetBlock('STACK');
// Count number of inputs.
const connections = [];
while (itemBlock) {
connections.push(itemBlock.valueConnection_);
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
// Disconnect any children that don't belong.
for (let k = 0; k < this.itemCount_; k++) {
const connection = this.getInput('OID' + k).connection.targetConnection;
if (connection && connections.indexOf(connection) === -1) {
connection.disconnect();
}
}
this.itemCount_ = connections.length;
if (this.itemCount_ < 1) {
this.itemCount_ = 1;
}
this.updateShape_();
// Reconnect any child blocks.
for (let i = 0; i < this.itemCount_; i++) {
Blockly.Mutator.reconnect(connections[i], this, 'OID' + i);
}
},
/**
* Store pointers to any connected child blocks.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
saveConnections: function(containerBlock) {
let itemBlock = containerBlock.getInputTargetBlock('STACK');
let i = 0;
while (itemBlock) {
const input = this.getInput('OID' + i);
itemBlock.valueConnection_ = input && input.connection.targetConnection;
i++;
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
},
/**
* Modify this block to have the correct number of inputs.
* @private
* @this Blockly.Block
*/
updateShape_: function() {
if (this.getInput('CONDITION')) {
this.removeInput('CONDITION');
}
if (this.getInput('ACK_CONDITION')) {
this.removeInput('ACK_CONDITION');
}
let input;
for (let j = 0; input = this.inputList[j]; j++) {
if (input.name === 'STATEMENT') {
this.inputList.splice(j, 1);
break;
}
}
// Add new inputs.
const wp = this.workspace;
let i;
for (i = 0; i < this.itemCount_; i++) {
let _input = this.getInput('OID' + i);
if (!_input) {
_input = this.appendValueInput('OID' + i);
if (i === 0) {
_input.appendField(Blockly.Translate('on_ext'));
}
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const shadow = wp.newBlock('field_oid');
shadow.setShadow(true);
shadow.outputConnection.connect(_input.connection);
shadow.initSvg();
shadow.render();
}
}, 100, _input);
} else {
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const shadow = wp.newBlock('field_oid');
shadow.setShadow(true);
shadow.outputConnection.connect(_input.connection);
shadow.initSvg();
shadow.render();
}
}, 100, _input);
}
}
// Remove deleted inputs.
while (this.getInput('OID' + i)) {
this.removeInput('OID' + i);
i++;
}
this.appendDummyInput('CONDITION')
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_onchange'), 'ne'],
[Blockly.Translate('on_any'), 'any'],
[Blockly.Translate('on_gt'), 'gt'],
[Blockly.Translate('on_ge'), 'ge'],
[Blockly.Translate('on_lt'), 'lt'],
[Blockly.Translate('on_le'), 'le'],
[Blockly.Translate('on_true'), 'true'],
[Blockly.Translate('on_false'), 'false']
]), 'CONDITION');
this.appendDummyInput('ACK_CONDITION')
.appendField(Blockly.Translate('on_ack'))
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_ack_any'), ''],
[Blockly.Translate('on_ack_true'), 'true'],
[Blockly.Translate('on_ack_false'), 'false']
]), 'ACK_CONDITION');
if (input) {
this.inputList.push(input);
} else {
this.appendStatementInput('STATEMENT')
.setCheck(null)
}
}
};
Blockly.JavaScript['on_ext'] = function(block) {
const dropdown_condition = block.getFieldValue('CONDITION');
const ack_condition = block.getFieldValue('ACK_CONDITION');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
let val;
if (dropdown_condition === 'true' || dropdown_condition === 'false') {
val = `val: ${dropdown_condition}`;
} else {
val = `change: '${dropdown_condition}'`;
}
const oids = [];
for (let n = 0; n < block.itemCount_; n++) {
let id = Blockly.JavaScript.valueToCode(block, 'OID' + n, Blockly.JavaScript.ORDER_COMMA);
if (id) {
id = id.toString();
if (id.startsWith('\'') && id.endsWith('\'')) {
id = '[' + id + ']';
}
if (oids.indexOf(id) === -1) {
oids.push(id);
}
}
}
const oid = '[].concat(' + oids.join(').concat(') + ')';
return `on({ id: ${oid}, ${val}${ack_condition ? `, ack: ${ack_condition}` : ''} }, async (obj) => {\n` +
(oids.length === 1 ? Blockly.JavaScript.prefixLines('let value = obj.state.val;\nlet oldValue = obj.oldState.val;', Blockly.JavaScript.INDENT) + '\n' : '') +
statement +
'});\n';
};
// --- ON -----------------------------------------------------------
Blockly.Trigger.blocks['on'] =
'<block type="on">'
+ ' <value name="OID">'
+ ' </value>'
+ ' <value name="CONDITION">'
+ ' </value>'
+ ' <value name="ACK_CONDITION">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['on'] = {
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('on'));
this.appendDummyInput('OID')
.appendField(new Blockly.FieldOID('Object ID'), 'OID');
this.appendDummyInput('CONDITION')
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_onchange'), 'ne'],
[Blockly.Translate('on_any'), 'any'],
[Blockly.Translate('on_gt'), 'gt'],
[Blockly.Translate('on_ge'), 'ge'],
[Blockly.Translate('on_lt'), 'lt'],
[Blockly.Translate('on_le'), 'le'],
[Blockly.Translate('on_true'), 'true'],
[Blockly.Translate('on_false'), 'false']
]), 'CONDITION');
this.appendDummyInput('ACK_CONDITION')
.appendField(Blockly.Translate('on_ack'))
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_ack_any'), ''],
[Blockly.Translate('on_ack_true'), 'true'],
[Blockly.Translate('on_ack_false'), 'false']
]), 'ACK_CONDITION');
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('on_tooltip'));
this.setHelpUrl(getHelp('on_help'));
}
};
Blockly.JavaScript['on'] = function(block) {
const value_objectid = block.getFieldValue('OID');
const dropdown_condition = block.getFieldValue('CONDITION');
const ack_condition = block.getFieldValue('ACK_CONDITION');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
let objectName = main.objects[value_objectid] && main.objects[value_objectid].common && main.objects[value_objectid].common.name ? main.objects[value_objectid].common.name : '';
if (typeof objectName === 'object') {
objectName = objectName[systemLang] || objectName.en;
}
Blockly.Msg.VARIABLES_DEFAULT_NAME = 'value';
let val;
if (dropdown_condition === 'true' || dropdown_condition === 'false') {
val = 'val: ' + dropdown_condition;
} else {
val = `change: '${dropdown_condition}'`;
}
return `on({ id: '${value_objectid}'${objectName ? ` /* ${objectName} */` : ''}, ${val}${ack_condition ? `, ack: ${ack_condition}` : ''} }, async (obj) => {\n` +
Blockly.JavaScript.prefixLines('let value = obj.state.val;', Blockly.JavaScript.INDENT) + '\n' +
Blockly.JavaScript.prefixLines('let oldValue = obj.oldState.val;', Blockly.JavaScript.INDENT) + '\n' +
statement +
'});\n';
};
// --- get info about event -----------------------------------------------------------
Blockly.Trigger.blocks['on_source'] =
'<block type="on_source">'
+ ' <value name="ATTR">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['on_source'] = {
/**
* Block for conditionally returning a value from a procedure.
* @this Blockly.Block
*/
init: function() {
this.appendDummyInput()
.appendField('↪');
this.appendDummyInput('ATTR')
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_source_state_val'), 'state.val'],
[Blockly.Translate('on_source_state_ts'), 'state.ts'],
[Blockly.Translate('on_source_state_q'), 'state.q'],
[Blockly.Translate('on_source_state_from'), 'state.from'],
[Blockly.Translate('on_source_state_ack'), 'state.ack'],
[Blockly.Translate('on_source_state_lc'), 'state.lc'],
[Blockly.Translate('on_source_state_c'), 'state.c'],
[Blockly.Translate('on_source_state_user'), 'state.user'],
[Blockly.Translate('on_source_id'), 'id'],
[Blockly.Translate('on_source_name'), 'common.name'],
[Blockly.Translate('on_source_desc'), 'common.desc'],
[Blockly.Translate('on_source_channel_id'), 'channelId'],
[Blockly.Translate('on_source_channel_name'), 'channelName'],
[Blockly.Translate('on_source_device_id'), 'deviceId'],
[Blockly.Translate('on_source_device_name'), 'deviceName'],
[Blockly.Translate('on_source_oldstate_val'), 'oldState.val'],
[Blockly.Translate('on_source_oldstate_ts'), 'oldState.ts'],
[Blockly.Translate('on_source_oldstate_q'), 'oldState.q'],
[Blockly.Translate('on_source_oldstate_from'), 'oldState.from'],
[Blockly.Translate('on_source_oldstate_ack'), 'oldState.ack'],
[Blockly.Translate('on_source_oldstate_lc'), 'oldState.lc'],
[Blockly.Translate('on_source_oldstate_c'), 'oldState.c'],
[Blockly.Translate('on_source_oldstate_user'), 'oldState.user']
]), 'ATTR');
this.setInputsInline(true);
this.setOutput(true);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('on_source_tooltip'));
this.setHelpUrl(getHelp('on_help'));
},
/**
* Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop.
* @param {!Blockly.Events.Abstract} e Change event.
* @this Blockly.Block
*/
onchange: function(e) {
let legal = false;
// Is the block nested in a trigger?
let block = this;
do {
if (this.FUNCTION_TYPES.includes(block.type)) {
legal = true;
break;
}
block = block.getSurroundParent();
} while (block);
if (legal) {
this.setWarningText(null, this.id);
} else {
this.setWarningText(Blockly.Translate('on_source_warning'), this.id);
}
},
/**
* List of block types that are functions and thus do not need warnings.
* To add a new function type add this to your code:
* Blockly.Blocks['procedures_ifreturn'].FUNCTION_TYPES.push('custom_func');
*/
FUNCTION_TYPES: ['on', 'on_ext']
};
Blockly.JavaScript['on_source'] = function(block) {
let attr = block.getFieldValue('ATTR');
const parts = attr.split('.');
if (parts.length > 1) {
attr = `(obj.${parts[0]} ? obj.${attr} : '')`;
} else {
attr = `obj.${attr}`;
}
return [attr, Blockly.JavaScript.ORDER_ATOMIC];
};
// --- SCHEDULE -----------------------------------------------------------
Blockly.Trigger.blocks['schedule'] =
'<block type="schedule">'
+ ' <value name="SCHEDULE">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['schedule'] = {
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('schedule'));
this.appendDummyInput('SCHEDULE')
.appendField(new Blockly.FieldCRON('* * * * *'), 'SCHEDULE');
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('schedule_tooltip'));
this.setHelpUrl(getHelp('schedule_help'));
}
};
Blockly.JavaScript['schedule'] = function(block) {
let schedule = block.getFieldValue('SCHEDULE');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
if (schedule[0] === '{') {
schedule = "'" + schedule + "'";
} else {
schedule = '"' + schedule + '"';
}
return `schedule(${schedule}, async () => {\n` +
statement +
'});\n';
};
// --- SCHEDULE BY ID -----------------------------------------------------
Blockly.Trigger.blocks['schedule_by_id'] =
'<block type="schedule_by_id">'
+ ' <value name="OID">'
+ ' </value>'
+ ' <value name="ACK_CONDITION">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['schedule_by_id'] = {
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('schedule_by_id'));
this.appendDummyInput('OID')
.appendField(new Blockly.FieldOID('Object ID'), 'OID');
this.appendDummyInput('ACK_CONDITION')
.appendField(Blockly.Translate('on_ack'))
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('on_ack_any'), ''],
[Blockly.Translate('on_ack_true'), 'true'],
[Blockly.Translate('on_ack_false'), 'false']
]), 'ACK_CONDITION');
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('schedule_by_id_tooltip'));
this.setHelpUrl(getHelp('schedule_by_id_help'));
}
};
Blockly.JavaScript['schedule_by_id'] = function(block) {
const value_objectid = block.getFieldValue('OID');
const ack_condition = block.getFieldValue('ACK_CONDITION');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
return `scheduleById('${value_objectid}'${ack_condition ? `, ${ack_condition}` : ''}, async () => {\n` +
statement +
'});\n';
};
// --- ASTRO -----------------------------------------------------------
Blockly.Trigger.blocks['astro'] =
'<block type="astro">'
+ ' <value name="TYPE">'
//+ ' <shadow type="text">'
//+ ' <field name="TEXT">test</field>'
//+ ' </shadow>'
+ ' </value>'
+ ' <value name="OFFSET">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['astro'] = {
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('astro'));
this.appendDummyInput("TYPE")
.appendField(new Blockly.FieldDropdown([
[Blockly.Translate('astro_sunriseText'), "sunrise"],
[Blockly.Translate('astro_sunriseEndText'), "sunriseEnd"],
[Blockly.Translate('astro_goldenHourEndText'), "goldenHourEnd"],
[Blockly.Translate('astro_solarNoonText'), "solarNoon"],
[Blockly.Translate('astro_goldenHourText'), "goldenHour"],
[Blockly.Translate('astro_sunsetStartText'), "sunsetStart"],
[Blockly.Translate('astro_sunsetText'), "sunset"],
[Blockly.Translate('astro_duskText'), "dusk"],
[Blockly.Translate('astro_nauticalDuskText'), "nauticalDusk"],
[Blockly.Translate('astro_nightText'), "night"],
[Blockly.Translate('astro_nightEndText'), "nightEnd"],
[Blockly.Translate('astro_nauticalDawnText'), "nauticalDawn"],
[Blockly.Translate('astro_dawnText'), "dawn"],
[Blockly.Translate('astro_nadirText'), "nadir"]
]), 'TYPE');
this.appendDummyInput()
.appendField(Blockly.Translate('astro_offset'));
this.appendDummyInput("OFFSET")
.appendField(new Blockly.FieldTextInput("0"), "OFFSET");
this.appendDummyInput()
.appendField(Blockly.Translate('astro_minutes'));
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('astro_tooltip'));
this.setHelpUrl(getHelp('astro_help'));
}
};
Blockly.JavaScript['astro'] = function(block) {
const astrotype = block.getFieldValue('TYPE');
const offset = parseInt(block.getFieldValue('OFFSET'), 10);
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
return `schedule({ astro: '${astrotype}', shift: ${offset} }, async () => {\n` +
statement +
'});\n';
};
// --- set named schedule -----------------------------------------------------------
Blockly.Trigger.blocks['schedule_create'] =
'<block type="schedule_create">'
+ ' <value name="NAME">'
+ ' </value>'
+ ' <value name="SCHEDULE">'
+ ' <shadow type="field_cron">'
+ ' <field name="CRON">* * * * *</field>'
+ ' </shadow>'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
/**
* Ensure two identically-named procedures don't exist.
* @param {string} name Proposed procedure name.
* @param {!Blockly.Block} block Block to disambiguate.
* @return {string} Non-colliding name.
*/
Blockly.Trigger.findLegalName = function(name, block) {
if (block.isInFlyout) {
// Flyouts can have multiple procedures called 'do something'.
return name;
}
while (!Blockly.Trigger.isLegalName_(name, block.workspace, block)) {
// Collision with another procedure.
const r = name.match(/^(.*?)(\d+)$/);
if (!r) {
name += '1';
} else {
name = r[1] + (parseInt(r[2], 10) + 1);
}
}
return name;
};
/**
* Does this procedure have a legal name? Illegal names include names of
* procedures already defined.
* @param {string} name The questionable name.
* @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
* @param {Blockly.Block=} opt_exclude Optional block to exclude from
* comparisons (one doesn't want to collide with oneself).
* @return {boolean} True if the name is legal.
* @private
*/
Blockly.Trigger.isLegalName_ = function(name, workspace, opt_exclude) {
if (name === 'schedule') {
return false;
}
const blocks = workspace.getAllBlocks();
// Iterate through every block and check the name.
for (let i = 0; i < blocks.length; i++) {
if (blocks[i] == opt_exclude) {
continue;
}
if (blocks[i].isSchedule_) {
const blockName = blocks[i].getFieldValue('NAME');
if (Blockly.Names.equals(blockName, name)) {
return false;
}
}
}
return true;
};
/**
* Rename a procedure. Called by the editable field.
* @param {string} name The proposed new name.
* @return {string} The accepted name.
* @this {!Blockly.Field}
*/
Blockly.Trigger.rename = function (name) {
// Strip leading and trailing whitespace. Beyond this, all names are legal.
name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
return Blockly.Trigger.findLegalName(name, this.sourceBlock_);
};
Blockly.Blocks['schedule_create'] = {
init: function() {
const nameField = new Blockly.FieldTextInput(
Blockly.Trigger.findLegalName('schedule', this),
Blockly.Trigger.rename);
nameField.setSpellcheck(false);
this.appendDummyInput('NAME')
.appendField(Blockly.Translate('schedule_create'))
.appendField(nameField, 'NAME');
this.appendValueInput('SCHEDULE')
.appendField(Blockly.Translate('schedule_text'));
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('schedule_create_tooltip'));
this.setHelpUrl(getHelp('schedule_create_help'));
},
isSchedule_: true,
getVars: function () {
return [this.getFieldValue('NAME')];
},
getVarModels: function () {
const name = this.getFieldValue('NAME');
return [{ getId: () => { return name; }, name: name, type: 'cron' }];
}
};
Blockly.JavaScript['schedule_create'] = function (block) {
const name = Blockly.JavaScript.variableDB_.safeName_(block.getFieldValue('NAME'));
const schedule = Blockly.JavaScript.valueToCode(block, 'SCHEDULE', Blockly.JavaScript.ORDER_ATOMIC);
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
return name + ' = schedule(' + schedule + ', async () => {\n' +
statement +
'});\n';
};
// --- clearSchedule -----------------------------------------------------------
Blockly.Trigger.getAllSchedules = function (workspace) {
const blocks = workspace.getAllBlocks();
const result = [];
// Iterate through every block and check the name.
for (let i = 0; i < blocks.length; i++) {
if (blocks[i].isSchedule_) {
result.push([blocks[i].getFieldValue('NAME'), blocks[i].getFieldValue('NAME')]);
}
}
// BF(2020.05.16): for back compatibility. Remove it after 5 years
if (window.scripts.loading) {
const variables = workspace.getVariablesOfType('');
variables.forEach(v => !result.find(it => it[0] === v.name) && result.push([v.name, v.name]));
}
const variables1 = workspace.getVariablesOfType('cron');
variables1.forEach(v => !result.find(it => it[0] === v.name) && result.push([v.name, v.name]));
!result.length && result.push(['', '']);
return result;
};
Blockly.Trigger.blocks['schedule_clear'] =
'<block type="schedule_clear">'
+ ' <value name="NAME">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['schedule_clear'] = {
init: function() {
this.appendDummyInput('NAME')
.appendField(Blockly.Translate('schedule_clear'))
.appendField(new Blockly.FieldDropdown(function () {
return scripts.blocklyWorkspace ? Blockly.Trigger.getAllSchedules(scripts.blocklyWorkspace) : [];
}), 'NAME');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(true);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('schedule_clear_tooltip'));
this.setHelpUrl(getHelp('schedule_clear_help'));
}
};
Blockly.JavaScript['schedule_clear'] = function(block) {
const name = Blockly.JavaScript.variableDB_.safeName_(block.getFieldValue('NAME'));
return `(() => { if (${name}) { clearSchedule(${name}); ${name} = null; }})();\n`;
};
// --- CRON dialog --------------------------------------------------
Blockly.Trigger.blocks['field_cron'] =
'<block type="field_cron">'
+ ' <value name="CRON">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['field_cron'] = {
// Checkbox.
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('field_cron_CRON'));
this.appendDummyInput()
.appendField(new Blockly.FieldCRON('* * * * *'), 'CRON');
this.setInputsInline(true);
this.setColour(Blockly.Trigger.HUE);
this.setOutput(true, 'String');
this.setTooltip(Blockly.Translate('field_cron_tooltip'));
}
};
Blockly.JavaScript['field_cron'] = function(block) {
const cron = block.getFieldValue('CRON');
return [`'${cron}'`, Blockly.JavaScript.ORDER_ATOMIC]
};
// --- CRON builder --------------------------------------------------
Blockly.Trigger.blocks['cron_builder'] =
'<block type="cron_builder">'
+ ' <value name="LINE">'
+ ' </value>'
+ ' <value name="MINUTES">'
+ ' </value>'
+ ' <value name="HOURS">'
+ ' </value>'
+ ' <value name="DAYS">'
+ ' </value>'
+ ' <value name="MONTHS">'
+ ' </value>'
+ ' <value name="WEEKDAYS">'
+ ' </value>'
+ ' <value name="WITH_SECONDS">'
+ ' </value>'
+ ' <mutation seconds="false"></mutation>'
+ '</block>';
Blockly.Blocks['cron_builder'] = {
// Checkbox.
init: function() {
this.appendDummyInput()
.appendField(Blockly.Translate('cron_builder_CRON'));
this.appendDummyInput('LINE')
.appendField(Blockly.Translate('cron_builder_line'))
.appendField(new Blockly.FieldCheckbox('FALSE', function (option) {
this.sourceBlock_.setInputsInline(option === true || option === 'true' || option === 'TRUE');
}), 'LINE');
let _input = this.appendValueInput('DOW')
.appendField(Blockly.Translate('cron_builder_dow'));
const wp = this.workspace;
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
_input = this.appendValueInput('MONTHS')
.appendField(Blockly.Translate('cron_builder_month'));
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
_input = this.appendValueInput('DAYS')
.appendField(Blockly.Translate('cron_builder_day'));
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
_input = this.appendValueInput('HOURS')
.appendField(Blockly.Translate('cron_builder_hour'));
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
_input = this.appendValueInput('MINUTES')
.appendField(Blockly.Translate('cron_builder_minutes'));
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
this.appendDummyInput('WITH_SECONDS')
.appendField(Blockly.Translate('cron_builder_with_seconds'))
.appendField(new Blockly.FieldCheckbox('FALSE', function (option) {
const withSeconds = option === true || option === 'true' || option === 'TRUE';
this.sourceBlock_.updateShape_(withSeconds);
}), 'WITH_SECONDS');
this.seconds_ = false;
this.as_line_ = false;
this.setInputsInline(this.as_line_);
this.setColour(Blockly.Trigger.HUE);
this.setOutput(true, 'String');
this.setTooltip(Blockly.Translate('field_cron_tooltip'));
},
/**
* Create XML to represent number of text inputs.
* @return {!Element} XML storage element.
* @this Blockly.Block
*/
mutationToDom: function () {
const container = document.createElement('mutation');
container.setAttribute('seconds', this.seconds_);
container.setAttribute('as_line', this.as_line_);
return container;
},
/**
* Parse XML to restore the text inputs.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
*/
domToMutation: function (xmlElement) {
this.seconds_ = xmlElement.getAttribute('seconds') === 'true';
this.as_line_ = xmlElement.getAttribute('as_line') === 'true';
this.setInputsInline(this.as_line_);
this.updateShape_(this.seconds_);
},
updateShape_: function(withSeconds) {
this.seconds_ = withSeconds;
// Add or remove a statement Input.
const inputExists = this.getInput('SECONDS');
if (withSeconds) {
if (!inputExists) {
const _input = this.appendValueInput('SECONDS');
_input.appendField(Blockly.Translate('cron_builder_seconds'));
const wp = this.workspace;
setTimeout(function (_input) {
if (!_input.connection.isConnected()) {
const _shadow = wp.newBlock('text');
_shadow.setShadow(true);
_shadow.setFieldValue('*', 'TEXT');
_shadow.initSvg();
_shadow.render();
_shadow.outputConnection.connect(_input.connection);
}
}, 100, _input);
}
} else if (inputExists) {
this.removeInput('SECONDS');
}
}
};
Blockly.JavaScript['cron_builder'] = function(block) {
const dow = Blockly.JavaScript.valueToCode(block, 'DOW', Blockly.JavaScript.ORDER_ATOMIC);
const months = Blockly.JavaScript.valueToCode(block, 'MONTHS', Blockly.JavaScript.ORDER_ATOMIC);
const days = Blockly.JavaScript.valueToCode(block, 'DAYS', Blockly.JavaScript.ORDER_ATOMIC);
const hours = Blockly.JavaScript.valueToCode(block, 'HOURS', Blockly.JavaScript.ORDER_ATOMIC);
const minutes = Blockly.JavaScript.valueToCode(block, 'MINUTES', Blockly.JavaScript.ORDER_ATOMIC);
const seconds = Blockly.JavaScript.valueToCode(block, 'SECONDS', Blockly.JavaScript.ORDER_ATOMIC);
const withSeconds = block.getFieldValue('WITH_SECONDS');
const code =
(withSeconds === 'TRUE' || withSeconds === 'true' || withSeconds === true ?
seconds + '.toString().trim() + \' \' + ' : '') +
minutes + '.toString().trim() + \' \' + ' +
hours + '.toString().trim() + \' \' + ' +
days + '.toString().trim() + \' \' + ' +
months + '.toString().trim() + \' \' + ' +
dow + '.toString().trim()';
return [code, Blockly.JavaScript.ORDER_ATOMIC]
};
// --- onMessage -----------------------------------------------------------
Blockly.Trigger.blocks['onMessage'] =
'<block type="onMessage">'
+ ' <value name="NAME">'
+ ' </value>'
+ ' <value name="MESSAGE">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['onMessage'] = {
init: function() {
this.appendDummyInput('NAME')
.appendField(Blockly.Translate('onMessage'));
this.appendDummyInput('MESSAGE')
.appendField(Blockly.Translate('onMessage_message'))
.appendField(new Blockly.FieldTextInput('customMessage'), 'MESSAGE');
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('onMessage_tooltip'));
this.setHelpUrl(getHelp('onMessage_help'));
}
};
Blockly.JavaScript['onMessage'] = function (block) {
const message = block.getFieldValue('MESSAGE');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
return `onMessage('${message}', async (data, callback) => {\n` +
statement +
Blockly.JavaScript.prefixLines(`callback({ result: true });`, Blockly.JavaScript.INDENT) + '\n' +
'});\n';
};
// --- onFile -----------------------------------------------------------
Blockly.Trigger.blocks['onFile'] =
'<block type="onFile">'
+ ' <value name="OID">'
+ ' <shadow type="field_oid_meta">'
+ ' <field name="oid">0_userdata.0</field>'
+ ' </shadow>'
+ ' </value>'
+ ' <value name="FILE">'
+ ' <shadow type="text">'
+ ' <field name="FILE_NAME">*</field>'
+ ' </shadow>'
+ ' </value>'
+ ' <value name="WITH_FILE">'
+ ' </value>'
+ ' <value name="STATEMENT">'
+ ' </value>'
+ '</block>';
Blockly.Blocks['onFile'] = {
init: function() {
this.appendValueInput('OID')
.appendField(Blockly.Translate('onFile'))
.setCheck(null);
this.appendValueInput('FILE')
.appendField(Blockly.Translate('onFile_file'))
.setCheck(null);
this.appendDummyInput('WITH_FILE_INPUT')
.appendField(Blockly.Translate('onFile_withFile'))
.appendField(new Blockly.FieldCheckbox('FALSE'), 'WITH_FILE');
this.appendStatementInput('STATEMENT')
.setCheck(null);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('onFile_tooltip'));
this.setHelpUrl(getHelp('onFile_help'));
}
};
Blockly.JavaScript['onFile'] = function (block) {
const value_objectid = Blockly.JavaScript.valueToCode(block, 'OID', Blockly.JavaScript.ORDER_ATOMIC);
const file = Blockly.JavaScript.valueToCode(block, 'FILE', Blockly.JavaScript.ORDER_ATOMIC);
const withFile = block.getFieldValue('WITH_FILE');
const statement = Blockly.JavaScript.statementToCode(block, 'STATEMENT');
let objectName = '';
try {
const objId = eval(value_objectid); // Code to string
objectName = main.objects[objId] && main.objects[objId].common && main.objects[objId].common.name ? main.objects[objId].common.name : '';
if (typeof objectName === 'object') {
objectName = objectName[systemLang] || objectName.en;
}
} catch (error) {
}
return `onFile(${value_objectid}${objectName ? ` /* ${objectName} */` : ''}, ${file}, ${withFile === 'TRUE' ? 'true' : 'false'}, ` +
'async (id, fileName, size, data, mimeType) => {\n' +
statement +
'});\n';
};
// --- onFile -----------------------------------------------------------
Blockly.Trigger.blocks['offFile'] =
'<block type="offFile">'
+ ' <value name="OID">'
+ ' <shadow type="field_oid_meta">'
+ ' <field name="oid">0_userdata.0</field>'
+ ' </shadow>'
+ ' </value>'
+ ' <value name="FILE">'
+ ' <shadow type="text">'
+ ' <field name="FILE_NAME">*</field>'
+ ' </shadow>'
+ ' </value>'
+ '</block>';
Blockly.Blocks['offFile'] = {
init: function() {
this.appendValueInput('OID')
.appendField(Blockly.Translate('offFile'))
.setCheck(null);
this.appendValueInput('FILE')
.appendField(Blockly.Translate('onFile_file'))
.setCheck(null);
this.setPreviousStatement(true, null);
this.setInputsInline(false);
this.setColour(Blockly.Trigger.HUE);
this.setTooltip(Blockly.Translate('offFile_tooltip'));
this.setHelpUrl(getHelp('offFile_help'));
}
};
Blockly.JavaScript['offFile'] = function (block) {
const value_objectid = Blockly.JavaScript.valueToCode(block, 'OID', Blockly.JavaScript.ORDER_ATOMIC);
const file = Blockly.JavaScript.valueToCode(block, 'FILE', Blockly.JavaScript.ORDER_ATOMIC);
let objectName = '';
try {
const objId = eval(value_objectid); // Code to string
objectName = main.objects[objId] && main.objects[objId].common && main.objects[objId].common.name ? main.objects[objId].common.name : '';
if (typeof objectName === 'object') {
objectName = objectName[systemLang] || objectName.en;
}
} catch (error) {
}
return `offFile(${value_objectid}${objectName ? ` /* ${objectName} */` : ''}, ${file});\n`;
};