Merge pull request #1 from lorennorman/lines-format
new shorthand format
This commit is contained in:
commit
8ed2ac0ca4
28 changed files with 480 additions and 580 deletions
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'controls_if',
|
||||
|
||||
toolbox: {
|
||||
category: 'Logic',
|
||||
},
|
||||
|
||||
commonType: 'controls_if',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
// If/elseif/else condition.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'logic_boolean',
|
||||
|
||||
toolbox: {
|
||||
category: 'Values',
|
||||
},
|
||||
|
||||
commonType: 'logic_boolean',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const bool = block.getFieldValue('BOOL') === 'TRUE'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'logic_compare',
|
||||
|
||||
toolbox: {
|
||||
category: 'Logic',
|
||||
},
|
||||
|
||||
commonType: 'logic_compare',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'logic_negate',
|
||||
|
||||
toolbox: {
|
||||
category: 'Logic',
|
||||
},
|
||||
|
||||
commonType: 'logic_negate',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'logic_null',
|
||||
|
||||
toolbox: {
|
||||
category: 'Values',
|
||||
},
|
||||
|
||||
commonType: 'logic_null',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
return [ null, 0]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'logic_operation',
|
||||
|
||||
toolbox: {
|
||||
category: 'Logic',
|
||||
},
|
||||
|
||||
commonType: 'logic_operation',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'math_arithmetic',
|
||||
|
||||
toolbox: {
|
||||
category: 'Math',
|
||||
},
|
||||
|
||||
commonType: 'math_arithmetic',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
toolbox: { },
|
||||
type: 'math_change',
|
||||
|
||||
commonType: 'math_change',
|
||||
toolbox: { },
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'math_number',
|
||||
|
||||
toolbox: {
|
||||
category: 'Values',
|
||||
},
|
||||
|
||||
commonType: 'math_number',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
return [Number(block.getFieldValue('NUM')) || '0', 0]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'text',
|
||||
|
||||
toolbox: {
|
||||
category: 'Values',
|
||||
},
|
||||
|
||||
commonType: 'text',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const text = block.getFieldValue('TEXT')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export default {
|
||||
type: 'text_multiline',
|
||||
|
||||
toolbox: {
|
||||
category: 'Values',
|
||||
},
|
||||
|
||||
commonType: 'text_multiline',
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const text = block.getFieldValue('TEXT')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
toolbox: { },
|
||||
type: 'variables_get',
|
||||
|
||||
commonType: 'variables_get',
|
||||
toolbox: { },
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export default {
|
||||
toolbox: { },
|
||||
type: 'variables_set',
|
||||
|
||||
commonType: 'variables_set',
|
||||
toolbox: { },
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,74 +1,40 @@
|
|||
export default {
|
||||
type: "action_post_webhook",
|
||||
|
||||
toolbox: {
|
||||
category: 'Actions',
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "action_post_webhook",
|
||||
"message0": "🔗 POST a Webhook %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message1": "Web URL: %1",
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "URL",
|
||||
"check": "String",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message2": "Select a Feed: %1",
|
||||
"args2": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED",
|
||||
"check": "feed",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message3": "Body: %1",
|
||||
"args3": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BODY",
|
||||
"check": "String",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message4": "Form Encode? %1 %2",
|
||||
"args4": [
|
||||
{
|
||||
"type": "field_checkbox",
|
||||
"name": "FORM_ENCODE",
|
||||
"checked": false
|
||||
}, {
|
||||
"type": "input_dummy",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"output": "action",
|
||||
"colour": 345,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
visualization: {
|
||||
colour: 345,
|
||||
},
|
||||
|
||||
inputs: {
|
||||
URL: {
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "action",
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "🔗 POST a Webhook", "CENTER" ],
|
||||
|
||||
[ "Web URL:", {
|
||||
inputValue: "URL",
|
||||
check: "String",
|
||||
shadow: {
|
||||
type: 'text',
|
||||
fields: { TEXT: 'https://...' }
|
||||
}
|
||||
},
|
||||
FEED: {
|
||||
shadow: {
|
||||
type: 'selector_feed'
|
||||
}
|
||||
},
|
||||
BODY: {
|
||||
}],
|
||||
|
||||
[ "Select a Feed:", {
|
||||
inputValue: "FEED",
|
||||
check: "feed",
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Body:", {
|
||||
inputValue: "BODY",
|
||||
check: "String",
|
||||
shadow: {
|
||||
type: 'text_multiline',
|
||||
fields: {
|
||||
|
|
@ -92,8 +58,13 @@ export default {
|
|||
]`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}],
|
||||
|
||||
[ "Form Encode?", {
|
||||
field: "FORM_ENCODE",
|
||||
checked: false
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,52 +1,35 @@
|
|||
export default {
|
||||
type: "action_publish_to_feed",
|
||||
|
||||
toolbox: {
|
||||
category: 'Actions',
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "action_publish_to_feed",
|
||||
"message0": "Publish to Feed %1 Feed: %2 Value: %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED",
|
||||
"check": "feed",
|
||||
"align": "RIGHT"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": [
|
||||
"Boolean",
|
||||
"String",
|
||||
"Number"
|
||||
],
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"output": "action",
|
||||
"colour": 345,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
visualization: {
|
||||
colour: 345,
|
||||
},
|
||||
|
||||
inputs: {
|
||||
FEED: {
|
||||
shadow: {
|
||||
type: 'selector_feed'
|
||||
}
|
||||
},
|
||||
VALUE: {
|
||||
shadow: {
|
||||
type: 'text'
|
||||
}
|
||||
},
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "action",
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "Publish to Feed", "CENTER" ],
|
||||
|
||||
[ "Feed:", {
|
||||
inputValue: "FEED",
|
||||
check: "feed",
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Value:", {
|
||||
inputValue: "VALUE",
|
||||
check: [ "Boolean", "String", "Number" ],
|
||||
shadow: 'text'
|
||||
}]
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -14,34 +14,32 @@ export default {
|
|||
output: "action",
|
||||
},
|
||||
|
||||
data: {
|
||||
inputValues: {
|
||||
FEED: {
|
||||
check: 'feed',
|
||||
shadow: 'selector_feed',
|
||||
},
|
||||
SUBJECT: {
|
||||
check: 'String',
|
||||
shadow: {
|
||||
type: 'text',
|
||||
fields: { TEXT: '{{feed_name}} feed has a new value: {{value}}' }
|
||||
}
|
||||
},
|
||||
BODY: {
|
||||
check: 'String',
|
||||
shadow: {
|
||||
type: 'text_multiline',
|
||||
fields: { TEXT: 'The {{feed_name}} feed has a new value: {{value}} at {{created_at}}' }
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
lines: [
|
||||
{ center: "📧 Send an Email" },
|
||||
"Select a Feed: %FEED",
|
||||
"Subject: %SUBJECT",
|
||||
"Body: %BODY",
|
||||
[ "📧 Send an Email", 'CENTER'],
|
||||
|
||||
[ "Select a Feed:", {
|
||||
inputValue: "FEED",
|
||||
check: 'feed',
|
||||
shadow: 'selector_feed',
|
||||
}],
|
||||
|
||||
[ "Subject:", {
|
||||
inputValue: "SUBJECT",
|
||||
check: 'String',
|
||||
shadow: {
|
||||
type: 'text',
|
||||
fields: { TEXT: '{{feed_name}} feed has a new value: {{value}}' }
|
||||
}
|
||||
}],
|
||||
|
||||
[ "Body:", {
|
||||
inputValue: "BODY",
|
||||
check: 'String',
|
||||
shadow: {
|
||||
type: 'text_multiline',
|
||||
fields: { TEXT: 'The {{feed_name}} feed has a new value: {{value}} at {{created_at}}' }
|
||||
}
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
|
|
|
|||
|
|
@ -1,56 +1,39 @@
|
|||
export default {
|
||||
type: "action_send_sms",
|
||||
|
||||
toolbox: {
|
||||
category: 'Actions',
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "action_send_sms",
|
||||
"message0": "📲 Send an SMS %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message1": "Select a Feed: %1",
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED",
|
||||
"check": "feed",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message2": "Body: %1",
|
||||
"args2": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BODY",
|
||||
"check": "String",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"output": "action",
|
||||
"colour": 345,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
visualization: {
|
||||
colour: 345
|
||||
},
|
||||
|
||||
inputs: {
|
||||
FEED: {
|
||||
shadow: {
|
||||
type: 'selector_feed'
|
||||
}
|
||||
},
|
||||
BODY: {
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "action",
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "📲 Send an SMS", "CENTER" ],
|
||||
|
||||
[ "Select a Feed:", {
|
||||
inputValue: "FEED",
|
||||
check: "feed",
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Body:", {
|
||||
inputValue: "BODY",
|
||||
check: "String",
|
||||
shadow: {
|
||||
type: 'text_multiline',
|
||||
fields: {
|
||||
TEXT: 'The {{feed_name}} feed has a new value: {{value}} at {{created_at}}'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default {
|
|||
colour: 230
|
||||
},
|
||||
|
||||
// TODO: consider something simpler, maybe leftOutput, topOutput, and bottomCheck?
|
||||
// Block connections
|
||||
connections: {
|
||||
// mode options:
|
||||
|
|
@ -34,49 +35,42 @@ export default {
|
|||
output: "trigger",
|
||||
},
|
||||
|
||||
// specify all data this block contains
|
||||
data: {
|
||||
// inline form elements
|
||||
fields: {
|
||||
// 'options' key makes a dropdown
|
||||
FIELD_A: { options: [
|
||||
[ 'user sees', 'internal id' ], // n times
|
||||
]},
|
||||
// 'checked' key makes a checkbox
|
||||
FIELD_B: { checked: true }
|
||||
},
|
||||
|
||||
// single block attachments
|
||||
inputValues: {
|
||||
INPUT_A: {
|
||||
check: 'other_block_output', // validate connected block is this type
|
||||
shadow: 'other_block_type' // generate this kind of shadow from toolbox
|
||||
},
|
||||
FEED_B: {
|
||||
check: [ "other", "block", "outputs" ], // validate connected block is in this collection of types
|
||||
shadow: 'other_block_type'
|
||||
},
|
||||
},
|
||||
|
||||
// list of block attachments (not implemented yet)
|
||||
inputStatements: {}
|
||||
},
|
||||
|
||||
// describes each line of the block, from top to bottom
|
||||
lines: [
|
||||
"line contents", // bare string: simple line text
|
||||
{ // bare object:
|
||||
// - text is line text
|
||||
// - input refers to any input collection key
|
||||
text: 'line contents with input appended',
|
||||
input: 'INPUT_A',
|
||||
},
|
||||
// alignment key: creates an input dummy for alignment
|
||||
{ center: "centered text" }, // bare string value: becomes line text
|
||||
{ right: { // object value: text with input options
|
||||
text: 'right-aligned text with an input appended',
|
||||
input: 'INPUT_B',
|
||||
}}
|
||||
// STRING LINES
|
||||
"line contents", // simple text line, default alignment,
|
||||
|
||||
// ARRAY LINES
|
||||
// [ string, string ]: [ text, alignment ]
|
||||
[ "line contents", "alignment" ],
|
||||
|
||||
// [ string, object ]: [ text, { inputValue, inputStatement, field, fields } ]
|
||||
[ "line contents", {
|
||||
// for a single block input
|
||||
inputValue: 'INPUT_VALUE_NAME',
|
||||
check: 'input_block_output',
|
||||
shadow: 'block_type_to_shadow', // -> { shadow: { type: 'block_type_to_shadow' }}
|
||||
shadow: {
|
||||
type: 'block_type_to_shadow',
|
||||
inputs: {}, // fill in the inputs on the shadowed block
|
||||
fields: {}, // fill in the fields on the shadowed block
|
||||
},
|
||||
|
||||
// TODO: for multiple block inputs
|
||||
// inputStatement: 'INPUT_STATEMENT_NAME', ...
|
||||
|
||||
// for a single field input
|
||||
field: 'FIELD_NAME',
|
||||
text: 'whatever', // makes a text field
|
||||
spellcheck: true, // text field option
|
||||
checked: true, // makes a checkbox field
|
||||
options: [ // makes a dropdown field
|
||||
['user text', 'computer id'],
|
||||
// ...
|
||||
],
|
||||
|
||||
// TODO: for multiple field inputs: {}
|
||||
}],
|
||||
],
|
||||
|
||||
// generators for this block type
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
|
||||
export default {
|
||||
type: "root_block",
|
||||
|
||||
toolbox: { },
|
||||
|
||||
json: {
|
||||
"type": "root_block",
|
||||
"message0": "Action Root %1 Trigger: %2 Action: %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TRIGGER",
|
||||
"check": "trigger",
|
||||
"align": "RIGHT"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ACTION",
|
||||
"check": "action",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"colour": 120,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
visualization: {
|
||||
colour: 120,
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "Action Root", "CENTER" ],
|
||||
|
||||
[ "Trigger:", {
|
||||
inputValue: "TRIGGER",
|
||||
check: "trigger",
|
||||
}],
|
||||
|
||||
[ "Action:", {
|
||||
inputValue: "ACTION",
|
||||
check: "action",
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -1,33 +1,35 @@
|
|||
export default {
|
||||
type: "selector_comparison",
|
||||
|
||||
toolbox: {
|
||||
category: 'Comparisons',
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "selector_comparison",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "OPERATOR",
|
||||
"options": [
|
||||
[ "any", "any" ],
|
||||
[ ">", "gt" ],
|
||||
[ ">=", "gte" ],
|
||||
[ "<", "lt" ],
|
||||
[ "<=", "lte" ],
|
||||
[ "=", "equal_to" ],
|
||||
[ "≠", "not_equal_to" ],
|
||||
[ "includes", "inc" ]
|
||||
]
|
||||
}
|
||||
],
|
||||
"output": "comparison_operator",
|
||||
visualization: {
|
||||
"colour": 230,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
},
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "comparison_operator"
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "", {
|
||||
field: "OPERATOR",
|
||||
options: [
|
||||
[ "any", "any" ],
|
||||
[ ">", "gt" ],
|
||||
[ ">=", "gte" ],
|
||||
[ "<", "lt" ],
|
||||
[ "<=", "lte" ],
|
||||
[ "=", "equal_to" ],
|
||||
[ "≠", "not_equal_to" ],
|
||||
[ "includes", "inc" ]
|
||||
]
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: block => [ block.getFieldValue('OPERATOR'), 0 ]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
export default {
|
||||
type: "selector_feed",
|
||||
|
||||
toolbox: {
|
||||
category: 'Feeds',
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "selector_feed",
|
||||
"message0": "Feed: %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "FEED_ID",
|
||||
"options": [
|
||||
[ "Feed_A", "1001" ],
|
||||
[ "Feed_B", "1002" ],
|
||||
[ "Feed_C", "1003" ],
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": [ "populate_feeds_dropdown" ],
|
||||
"output": "feed",
|
||||
"colour": 30,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
visualization: {
|
||||
colour: 30,
|
||||
// TODO: proper extension handling
|
||||
extensions: [ "populate_feeds_dropdown" ],
|
||||
},
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "feed"
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "Feed:", {
|
||||
field: "FEED_ID",
|
||||
options: [
|
||||
[ "Feed_A", "1001" ],
|
||||
[ "Feed_B", "1002" ],
|
||||
[ "Feed_C", "1003" ],
|
||||
]
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: block => [ block.getFieldValue('FEED_ID'), 0 ]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,61 +1,45 @@
|
|||
|
||||
export default {
|
||||
type: "trigger_reactive",
|
||||
|
||||
toolbox: {
|
||||
category: 'Triggers'
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "trigger_reactive",
|
||||
"message0": "📥 Reactive %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message1": "Compare Feeds %1",
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message2": "Feed: %1",
|
||||
"args2": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED_A",
|
||||
"check": "feed",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message3": "Operator: %1",
|
||||
"args3": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "COMPARATOR",
|
||||
"check": "comparison_operator",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message4": "Feed or Value: %1",
|
||||
"args4": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED_B",
|
||||
"check": [
|
||||
"feed",
|
||||
"Number",
|
||||
"String"
|
||||
],
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message5": "Limit Every: %1 %2",
|
||||
"args5": [ {
|
||||
"type": "field_dropdown",
|
||||
"name": "NOTIFY_LIMIT",
|
||||
"options": [
|
||||
visualization: {
|
||||
"colour": 230,
|
||||
},
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "trigger",
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "📥 Reactive", "CENTER"],
|
||||
[ "Compare Feeds", "CENTER" ],
|
||||
|
||||
[ "Feed:", {
|
||||
inputValue: "FEED_A",
|
||||
check: "feed",
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Operator:", {
|
||||
inputValue: "COMPARATOR",
|
||||
check: "comparison_operator",
|
||||
shadow: 'selector_comparison'
|
||||
}],
|
||||
|
||||
[ "Feed or Value:", {
|
||||
inputValue: "FEED_B",
|
||||
check: [ "feed", "Number", "String" ],
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Limit Every:", {
|
||||
field: "NOTIFY_LIMIT",
|
||||
options: [
|
||||
[ '10 sec', '0' ],
|
||||
[ '1 min', '1' ],
|
||||
[ '15 min', '15' ],
|
||||
|
|
@ -64,45 +48,13 @@ export default {
|
|||
[ '6 hrs', '360' ],
|
||||
[ '1 day', '1440' ]
|
||||
]
|
||||
}, {
|
||||
"type": "input_dummy",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message6": "Notify on Reset? %1 %2",
|
||||
"args6": [
|
||||
{
|
||||
"type": "field_checkbox",
|
||||
"name": "NOTIFY_ON_RESET",
|
||||
"checked": true
|
||||
}, {
|
||||
"type": "input_dummy",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"output": "trigger",
|
||||
"colour": 230,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
},
|
||||
}],
|
||||
|
||||
inputs: {
|
||||
FEED_A: {
|
||||
shadow: {
|
||||
type: 'selector_feed'
|
||||
}
|
||||
},
|
||||
COMPARATOR: {
|
||||
shadow: {
|
||||
type: 'selector_comparison'
|
||||
}
|
||||
},
|
||||
FEED_B: {
|
||||
shadow: {
|
||||
type: 'selector_feed'
|
||||
}
|
||||
},
|
||||
},
|
||||
[ "Notify on Reset?", {
|
||||
field: "NOTIFY_ON_RESET",
|
||||
checked: true
|
||||
}],
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,37 +1,28 @@
|
|||
export default {
|
||||
type: "trigger_schedule",
|
||||
|
||||
toolbox: {
|
||||
category: 'Triggers'
|
||||
},
|
||||
|
||||
json: {
|
||||
"type": "trigger_schedule",
|
||||
"message0": "📅 Scheduled %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message1": "Schedule %1 %2",
|
||||
"args1": [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "CRONTAB",
|
||||
"text": "* * * * *",
|
||||
"spellcheck": false
|
||||
},
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"output": "trigger",
|
||||
visualization: {
|
||||
"colour": 230,
|
||||
"tooltip": "",
|
||||
"helpUrl": ""
|
||||
},
|
||||
|
||||
inputs: { },
|
||||
connections: {
|
||||
mode: 'value',
|
||||
output: 'trigger'
|
||||
},
|
||||
|
||||
lines: [
|
||||
[ "📅 Scheduled", 'CENTER' ],
|
||||
[ "Schedule", {
|
||||
field: 'CRONTAB',
|
||||
text: "* * * * *",
|
||||
spellcheck: false,
|
||||
align: 'CENTER'
|
||||
}]
|
||||
],
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export default {
|
||||
type: "trigger_timer",
|
||||
|
||||
// where and how this block appears in the toolbox
|
||||
toolbox: {
|
||||
category: 'Triggers'
|
||||
},
|
||||
|
|
@ -15,9 +14,33 @@ export default {
|
|||
output: "trigger",
|
||||
},
|
||||
|
||||
data: {
|
||||
fields: {
|
||||
RUN_AFTER: { options: [
|
||||
lines: [
|
||||
[ "⏲️ Timer", "center" ], // alignment shorthand
|
||||
[ "Compare Feeds", "center" ],
|
||||
|
||||
[ 'Feed:', {
|
||||
inputValue: "FEED_A", // input value
|
||||
check: 'feed',
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Compare Feeds:", {
|
||||
inputValue: "COMPARATOR",
|
||||
check: 'comparison_operator',
|
||||
shadow: {
|
||||
type: 'selector_comparison'
|
||||
}
|
||||
}],
|
||||
|
||||
[ "Feed or Value:", {
|
||||
inputValue: "FEED_B",
|
||||
check: 'feed',
|
||||
shadow: 'selector_feed'
|
||||
}],
|
||||
|
||||
[ "Run After:", {
|
||||
field: "RUN_AFTER", // field
|
||||
options: [
|
||||
[ '10 sec', '10' ],
|
||||
[ '1 min', '60' ],
|
||||
[ '15 min', '900' ],
|
||||
|
|
@ -25,34 +48,13 @@ export default {
|
|||
[ '1 hr', '3600' ],
|
||||
[ '6 hrs', '21600' ],
|
||||
[ '1 day', '86400' ]
|
||||
]},
|
||||
EXTEND_TIMER: { checked: true }
|
||||
},
|
||||
]
|
||||
}],
|
||||
|
||||
inputValues: {
|
||||
FEED_A: {
|
||||
check: 'feed',
|
||||
shadow: 'selector_feed'
|
||||
},
|
||||
COMPARATOR: {
|
||||
check: 'comparison_operator',
|
||||
shadow: 'selector_comparison'
|
||||
},
|
||||
FEED_B: {
|
||||
check: [ "feed", "Number", "String" ],
|
||||
shadow: 'selector_feed'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
lines: [
|
||||
{ center: "⏲️ Timer" },
|
||||
{ center: "Compare % Feeds" },
|
||||
'Feed: %FEED_A',
|
||||
"Compare Feeds: %COMPARATOR",
|
||||
"Feed or Value: %FEED_B",
|
||||
"Run After: %RUN_AFTER",
|
||||
"Extend Timer? %EXTEND_TIMER"
|
||||
[ "Extend Timer?", {
|
||||
field: "EXTEND_TIMER",
|
||||
checked: true
|
||||
}]
|
||||
],
|
||||
|
||||
generators: {
|
||||
|
|
@ -65,6 +67,8 @@ export default {
|
|||
comparisonTargetValue = generator.valueToCode(block, 'FEED_B', 0) || null,
|
||||
comparisonPayload = { [comparisonTargetKey]: comparisonTargetValue },
|
||||
|
||||
// pair with api -> blockly diagram code in io-rails
|
||||
// imagine an expressive, generative middle layer...
|
||||
payload = {
|
||||
trigger_type: 'timer',
|
||||
feed_id: generator.valueToCode(block, 'FEED_A', 0) || null,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { map } from 'lodash-es'
|
||||
import Blockly from 'blockly'
|
||||
import { keys, map, without } from 'lodash-es'
|
||||
import Blockly, { common } from 'blockly'
|
||||
|
||||
import blockDefaults from './defaults.js'
|
||||
import ALL_BLOCKS from './all.js'
|
||||
import { toBlockJSON } from '../tools/tools.js'
|
||||
import { getBlockType } from '../tools/util.js'
|
||||
|
||||
|
||||
export const
|
||||
|
|
@ -12,30 +11,52 @@ export const
|
|||
customBlocksJson = [],
|
||||
allBlocksByCategory = {}
|
||||
|
||||
const processCommonBlock = commonBlock => {
|
||||
const { commonType } = commonBlock
|
||||
const
|
||||
COMMON_KEYS = [
|
||||
"type",
|
||||
"toolbox",
|
||||
"generators"
|
||||
],
|
||||
|
||||
if(!Blockly.Blocks[commonType]) {
|
||||
throw new Error(`Common Block not found for type: ${commonType}`)
|
||||
}
|
||||
}
|
||||
CUSTOM_KEYS = COMMON_KEYS.concat([
|
||||
"toolbox",
|
||||
"help",
|
||||
"visualization",
|
||||
"connections",
|
||||
"lines"
|
||||
]),
|
||||
|
||||
const processCustomBlock = customBlock => {
|
||||
customBlocksJson.push({
|
||||
isCommonBlockType = type => !!Blockly.Blocks[type],
|
||||
|
||||
processCommonBlock = commonBlock => {
|
||||
// ensure only supported keys are present
|
||||
const extraKeys = without(keys(commonBlock), ...COMMON_KEYS)
|
||||
|
||||
if(extraKeys.length) {
|
||||
throw new Error(`Common Block definition has unrecognized keys: "${extraKeys.join('", "')}"\nBlock: ${JSON.stringify(commonBlock, null, 2)}`)
|
||||
}
|
||||
},
|
||||
|
||||
processCustomBlock = customBlock => {
|
||||
// ensure only supported keys are present
|
||||
const extraKeys = without(keys(customBlock), ...CUSTOM_KEYS)
|
||||
|
||||
if(extraKeys.length) {
|
||||
throw new Error(`Common Block definition has unrecognized keys: "${extraKeys.join('", "')}"\nBlock: ${JSON.stringify(customBlock, null, 2)}`)
|
||||
}
|
||||
|
||||
customBlocksJson.push({
|
||||
...blockDefaults,
|
||||
...customBlock.type
|
||||
? toBlockJSON(customBlock)
|
||||
: customBlock.json
|
||||
...toBlockJSON(customBlock)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// walk the blocks and process them into the exported collections
|
||||
map(ALL_BLOCKS, (block, key) => {
|
||||
const hasType = getBlockType(block)
|
||||
if(!block.type) { throw new Error(`No "type" declared for block: ${key}`) }
|
||||
|
||||
if(!hasType) { throw new Error(`No "type" declared for block: ${key}`) }
|
||||
|
||||
block.commonType
|
||||
isCommonBlockType(block.type)
|
||||
? processCommonBlock(block) // built-in blocks
|
||||
: processCustomBlock(block) // our blocks
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// build toolbox from a config and blocks that reference it
|
||||
import { includes, isString, map, filter, mapValues } from 'lodash-es'
|
||||
import { chain, includes, isString, map, filter } from 'lodash-es'
|
||||
|
||||
import { allBlockDefinitions } from '../blocks/index.js'
|
||||
import { getBlockType } from '../tools/util.js'
|
||||
|
||||
|
||||
const
|
||||
|
|
@ -16,34 +15,6 @@ const
|
|||
{ name: 'Comparisons', colour: 208 },
|
||||
],
|
||||
|
||||
blockToInputs = block =>
|
||||
block.data?.inputValues
|
||||
// shadow processing
|
||||
? mapValues(block.data?.inputValues, ({ shadow }) =>
|
||||
isString(shadow) // shorthand
|
||||
? { shadow: { type: shadow }}
|
||||
: shadow
|
||||
)
|
||||
: block.inputs,
|
||||
|
||||
blockToFields = block => {
|
||||
return block.fields
|
||||
},
|
||||
|
||||
selectBlocksByCategoryName = name =>
|
||||
filter(allBlockDefinitions, def =>
|
||||
def.toolbox.category === name || includes(def.toolbox.categories, name)
|
||||
),
|
||||
|
||||
generateCategoryContents = ({ name }) =>
|
||||
map(selectBlocksByCategoryName(name), block => ({
|
||||
kind: 'block',
|
||||
type: getBlockType(block),
|
||||
inputs: blockToInputs(block),
|
||||
fields: blockToFields(block)
|
||||
})
|
||||
),
|
||||
|
||||
generateToolboxContents = () => map(TOOLBOX_CONFIG, category =>
|
||||
// inject other kinds of toolbox objects here
|
||||
category === SEP
|
||||
|
|
@ -55,7 +26,33 @@ const
|
|||
...category.extras,
|
||||
contents: generateCategoryContents(category)
|
||||
}
|
||||
)
|
||||
),
|
||||
|
||||
generateCategoryContents = ({ name }) =>
|
||||
map(selectBlocksByCategoryName(name), block => ({
|
||||
kind: 'block',
|
||||
type: block.type,
|
||||
inputs: blockToInputs(block),
|
||||
// fields: {}
|
||||
})),
|
||||
|
||||
selectBlocksByCategoryName = name =>
|
||||
filter(allBlockDefinitions, def =>
|
||||
def.toolbox.category === name || includes(def.toolbox.categories, name)
|
||||
),
|
||||
|
||||
blockToInputs = ({ lines }) =>
|
||||
chain(lines)
|
||||
.map('[1]')
|
||||
.filter("inputValue")
|
||||
.keyBy("inputValue")
|
||||
.mapValues(shadowPropertyToInput)
|
||||
.value(),
|
||||
|
||||
shadowPropertyToInput = ({ shadow }) =>
|
||||
isString(shadow) // is shorthand?
|
||||
? { shadow: { type: shadow }} // expand to full object
|
||||
: { shadow } // set as shadow value
|
||||
|
||||
export const toolbox = {
|
||||
kind: 'categoryToolbox',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { flatMap, forEach, includes, isObject, isString, keys, reduce, values, without } from 'lodash-es'
|
||||
import { flatMap, forEach, includes, isArray, isObject, isString, keys, reduce, values, without } from 'lodash-es'
|
||||
|
||||
import { defaultAlignment } from '../blocks/defaults.js'
|
||||
|
||||
|
|
@ -82,22 +82,24 @@ const processConnections = block => {
|
|||
const processLines = block => {
|
||||
// grab block settings
|
||||
// process each line
|
||||
const { lines, data } = block
|
||||
const { lines } = block
|
||||
return reduce(lines, (allLines, line, lineNumber) => {
|
||||
const { message, args } = processLine(line, data)
|
||||
const { message, args } = processLine(line)
|
||||
allLines[`message${lineNumber}`] = message
|
||||
allLines[`args${lineNumber}`] = args
|
||||
return allLines
|
||||
}, {})
|
||||
}
|
||||
|
||||
const processLine = (line, data={}) => {
|
||||
const processLine = (line) => {
|
||||
if(!isString(line) && !isObject(line)) {
|
||||
throw new Error(`Given non-object, non-string line: ${line}`)
|
||||
}
|
||||
|
||||
const { alignment, lineValue } = splitAlignment(line)
|
||||
const { alignment, lineValue, lineData={} } = parseLine(line)
|
||||
|
||||
// always expect alignment
|
||||
if(!alignment) { throw new Error(`Alignment not detected for line: ${JSON.stringify(line, null, 2)}`) }
|
||||
// expect 'text' key
|
||||
if(!isString(lineValue.text)) { throw new Error(`No text given for line: ${JSON.stringify(line, null, 2)}`)}
|
||||
// expect optional 'input' key
|
||||
|
|
@ -105,98 +107,104 @@ const processLine = (line, data={}) => {
|
|||
// expect no other keys
|
||||
if(otherKeys.length) { throw new Error(`Expected no keys other than "text" and "input", got:\n${otherKeys}\nfor line: ${JSON.stringify(line, null, 2)}`)}
|
||||
|
||||
const
|
||||
args = [],
|
||||
allDataKeys = flatMap([ keys(data.fields), keys(data.inputValues), keys(data.inputStatements)])
|
||||
// build up the args
|
||||
const args = []
|
||||
|
||||
let { message, inputKey } = lineToMessageAndInput(lineValue, allDataKeys)
|
||||
// append inputValues to args
|
||||
if(lineData.inputValue) {
|
||||
args.push({
|
||||
type: "input_value",
|
||||
name: lineData.inputValue,
|
||||
check: lineData.check
|
||||
})
|
||||
|
||||
// process alignment and input into args
|
||||
if(inputKey) {
|
||||
// lookup in values, fields, statements
|
||||
// TODO: ensure only one found
|
||||
const fieldInput = data.fields?.[inputKey]
|
||||
if(fieldInput) {
|
||||
args.push({
|
||||
type: (fieldInput.options && "field_dropdown")
|
||||
|| (includes(keys(fieldInput), "checked") && "field_checkbox"),
|
||||
name: inputKey,
|
||||
check: fieldInput.check,
|
||||
options: fieldInput.options
|
||||
})
|
||||
}
|
||||
const valueInput = data.inputValues?.[inputKey]
|
||||
if(valueInput) {
|
||||
args.push({
|
||||
type: "input_value",
|
||||
name: inputKey,
|
||||
check: valueInput.check
|
||||
})
|
||||
}
|
||||
const statementInput = data.inputStatements?.[inputKey]
|
||||
if(statementInput){
|
||||
throw new Error(`Not implemented: statement inputs`)
|
||||
}
|
||||
// append fields to args
|
||||
} else if(lineData.field) {
|
||||
args.push({
|
||||
type: (lineData.options && "field_dropdown")
|
||||
|| (includes(keys(lineData), "checked") && "field_checkbox")
|
||||
|| (includes(keys(lineData), "text") && "field_input"),
|
||||
name: lineData.field,
|
||||
checked: lineData.checked,
|
||||
options: lineData.options,
|
||||
text: lineData.text,
|
||||
spellcheck: lineData.spellcheck,
|
||||
})
|
||||
}
|
||||
|
||||
if(alignment) {
|
||||
// append alignment to an existing input
|
||||
if(args[0]?.type === "input_value") {
|
||||
args[0].align = alignment.toUpperCase()
|
||||
// if an input exists, append alignment to it
|
||||
if(args[0]?.type === "input_value") {
|
||||
args[0].align = alignment
|
||||
|
||||
// make an input just for alignment
|
||||
} else {
|
||||
args.push({
|
||||
"type": "input_dummy",
|
||||
"align": alignment.toUpperCase()
|
||||
})
|
||||
}
|
||||
// otherwise make an input for alignment
|
||||
} else {
|
||||
args.push({
|
||||
"type": "input_dummy",
|
||||
"align": alignment
|
||||
})
|
||||
}
|
||||
|
||||
// quick sanity check on args length
|
||||
if(args.length > 2) {
|
||||
throw new Error(`args array longer than 2: ${JSON.stringify(args, null, 2)}`)
|
||||
}
|
||||
|
||||
// process text into message
|
||||
if(args.length === 1) {
|
||||
message = message.concat(" %1")
|
||||
} else if(args.length === 2) {
|
||||
message = message.concat(" %1 %2")
|
||||
}
|
||||
const
|
||||
argsIndices = (args.length === 1) ? " %1"
|
||||
: (args.length === 2) ? " %1 %2"
|
||||
: '',
|
||||
message = lineValue.text.concat(argsIndices)
|
||||
|
||||
return { args, message }
|
||||
}
|
||||
|
||||
const validAlignments = ['center', 'centre', 'right', 'left']
|
||||
const splitAlignment = line => {
|
||||
if(isString(line)) {
|
||||
return { alignment: defaultAlignment, lineValue: { text: line } }
|
||||
}
|
||||
const parseLine = line => {
|
||||
if(isString(line)) { return parseStringLine(line) }
|
||||
|
||||
// look for alignment keys
|
||||
const lineKeys = keys(line)
|
||||
let alignment
|
||||
forEach(lineKeys, key => {
|
||||
const lowerKey = key?.toLowerCase()
|
||||
if(includes(validAlignments, lowerKey)) {
|
||||
alignment = (lowerKey === 'center') ? 'centre' : lowerKey
|
||||
if(isArray(line)) { return parseArrayLine(line) }
|
||||
|
||||
if(lineKeys.length > 1) {
|
||||
const lineKeysMessage = without(lineKeys, key).join('", "')
|
||||
throw new Error(`Alignment key ("${key}") should not have sibling keys, but has:\n"${lineKeysMessage}"`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const lineValue = alignment
|
||||
? values(line)[0]
|
||||
: line
|
||||
|
||||
// if found, alignment must be only key
|
||||
return {
|
||||
alignment: alignment || defaultAlignment,
|
||||
lineValue: isString(lineValue)
|
||||
? { text: lineValue }
|
||||
: lineValue
|
||||
}
|
||||
throw new Error(`Line not valid: ${JSON.stringify(line, null, 2)}`)
|
||||
}
|
||||
|
||||
const parseStringLine = text =>
|
||||
({ alignment: defaultAlignment, lineValue: { text } })
|
||||
|
||||
const parseArrayLine = line => {
|
||||
const [ text, second ] = line
|
||||
|
||||
if(isString(second)) {
|
||||
return { alignment: second, lineValue: { text } }
|
||||
}
|
||||
|
||||
if(isObject(second)) {
|
||||
const alignment = parseAlignment(second.align)
|
||||
return { alignment, lineValue: { text }, lineData: second }
|
||||
}
|
||||
|
||||
throw new Error(`second index invalid for line: ${JSON.stringify(line, null, 2)}`)
|
||||
}
|
||||
|
||||
const validAlignments = [ 'CENTER', 'CENTRE', 'RIGHT', 'LEFT' ]
|
||||
const parseAlignment = alignmentString => {
|
||||
// if the input is falsy, return default alignment
|
||||
if( !alignmentString ) { return defaultAlignment }
|
||||
|
||||
alignmentString = alignmentString.toUpperCase()
|
||||
// throw if the downcased string is not in the valid list
|
||||
if( !includes(validAlignments, alignmentString) ) {
|
||||
throw new Error(`Alignment not valid: ${alignmentString}`)
|
||||
}
|
||||
|
||||
// anglocize and return
|
||||
return alignmentString === 'CENTER'
|
||||
? 'CENTRE'
|
||||
: alignmentString
|
||||
}
|
||||
|
||||
|
||||
// TODO: re-use this when we want to do inline fields like:
|
||||
// "Field 1: %FIELD_NAME_1 more text then %FIELD_NAME_2 fields not at end"
|
||||
const DATA_REGEX = / %\w+/ // a space, then %, then one or more word characters
|
||||
const lineToMessageAndInput = ({ text, input }, dataKeys) => {
|
||||
// check if this text contains a data string
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export const getBlockType = block => block.type || block.commonType || block.json.type
|
||||
Loading…
Reference in a new issue