Compare commits
41 commits
76e5324d14
...
b959330af6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b959330af6 | ||
|
|
f237342563 | ||
|
|
f463143199 | ||
|
|
02dd9b9c1c | ||
|
|
296bf65816 | ||
|
|
5b5f9d9c4f | ||
|
|
552fffaaf6 | ||
|
|
ad3d1d8cb2 | ||
|
|
e9f3f8c5c7 | ||
|
|
6ad85110eb | ||
|
|
6949620141 | ||
|
|
e3dcab44e5 | ||
|
|
ae0baf6908 | ||
|
|
20026c433d | ||
|
|
a3e5f70621 | ||
|
|
3272fbe05f | ||
|
|
702749303b | ||
|
|
37600d82f1 | ||
|
|
65fd5959bc | ||
|
|
49cf69b4f4 | ||
|
|
03822ca3d0 | ||
|
|
6728875106 | ||
|
|
d181533dc7 | ||
|
|
19dd399a1f | ||
|
|
da3a86f55d | ||
|
|
31cbbfb3a4 | ||
|
|
dd34206376 | ||
|
|
8adf43ed7b | ||
|
|
25ec0db569 | ||
|
|
38a850cdd7 | ||
|
|
eaaaf03b4f | ||
|
|
42bca1b579 | ||
|
|
bd002ba253 | ||
|
|
848d9d5fda | ||
|
|
46aec1da15 | ||
|
|
9dec8c9b73 | ||
|
|
25f061b833 | ||
|
|
fbb759bb59 | ||
|
|
f936b63d83 | ||
|
|
f2a533bf9c | ||
|
|
3d0dbe114f |
63 changed files with 3371 additions and 974 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,7 +2,9 @@
|
|||
.ds_store
|
||||
|
||||
# Blockly exports
|
||||
tmp
|
||||
export
|
||||
docs/block_images
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ export default {
|
|||
inputs: {
|
||||
SUBJECT: {
|
||||
description: "a template for generating the email subject",
|
||||
check: "expression",
|
||||
bytecodeProperty: "subjectTemplate",
|
||||
shadow: singleLineTemplate,
|
||||
},
|
||||
|
||||
BODY: {
|
||||
description: "a multi-line template for generating the email body",
|
||||
check: "expression",
|
||||
bytecodeProperty: "bodyTemplate",
|
||||
shadow: multilineLineTemplate,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export default {
|
|||
inputs: {
|
||||
EXPRESSION: {
|
||||
description: "A Block diagram you'd like to see the resolved value and type of.",
|
||||
check: "expression",
|
||||
shadow: 'io_text'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
// deprecated: use feeds/set_value
|
||||
export default {
|
||||
type: "action_publish",
|
||||
bytecodeKey: "publishAction",
|
||||
name: "Publish to Feed",
|
||||
colour: "0",
|
||||
description: "Sends the given value to the specified Feed.",
|
||||
|
||||
connections: {
|
||||
mode: "statement",
|
||||
output: "expression",
|
||||
next: 'expression'
|
||||
},
|
||||
|
||||
template: `
|
||||
📈 Publish |CENTER
|
||||
...value: %VALUE
|
||||
...to: %FEED
|
||||
`,
|
||||
|
||||
inputs: {
|
||||
VALUE: {
|
||||
description: "The value to write to the Feed.",
|
||||
shadow: 'io_text'
|
||||
},
|
||||
|
||||
FEED: {
|
||||
description: "The Feed to write to.",
|
||||
shadow: 'feed_selector'
|
||||
},
|
||||
},
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const payload = {
|
||||
publishAction: {
|
||||
feed: JSON.parse(generator.valueToCode(block, 'FEED', 0) || null),
|
||||
value: JSON.parse(generator.valueToCode(block, 'VALUE', 0) || null)
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(payload)
|
||||
}
|
||||
},
|
||||
|
||||
regenerators: {
|
||||
json: (blockObject, helpers) => {
|
||||
const payload = blockObject.publishAction
|
||||
|
||||
// migrating to a new block
|
||||
return {
|
||||
type: "feed_set_value",
|
||||
fields: {
|
||||
FEED_KEY: payload.feed.feed.key,
|
||||
},
|
||||
inputs: {
|
||||
VALUE: helpers.expressionToBlock(payload.value, { shadow: 'io_text' }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ export default {
|
|||
inputs: {
|
||||
BODY: {
|
||||
description: "A template for generating the SMS body",
|
||||
check: "expression",
|
||||
shadow: multilineLineTemplate
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export default {
|
|||
inputs: {
|
||||
URL: {
|
||||
description: "A valid web location to send a POST request to.",
|
||||
check: "expression",
|
||||
shadow: {
|
||||
type: 'io_text',
|
||||
fields: { TEXT: 'https://...' }
|
||||
|
|
@ -33,6 +34,7 @@ export default {
|
|||
|
||||
BODY: {
|
||||
description: "A JSON template to render and POST",
|
||||
check: "expression",
|
||||
shadow: multilineLineTemplate
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,12 +8,6 @@ export default {
|
|||
description: "Execute different block diagrams based on the outcome of conditional checks.",
|
||||
colour: 60,
|
||||
|
||||
// "if - the first value to check for truthiness",
|
||||
// "do - the commands to execute if the first check was true",
|
||||
// "else if - (optional, repeating) an extra value to check for truthiness",
|
||||
// "do - the commands to execute if the previous check was true",
|
||||
// "else - (optional) the commands to execute if no checks were true",
|
||||
|
||||
connections: {
|
||||
mode: "statement",
|
||||
output: "expression",
|
||||
|
|
@ -29,19 +23,28 @@ export default {
|
|||
else %ELSE_LABEL
|
||||
`,
|
||||
|
||||
// TODO: open a way to send raw documentation for the inputs section
|
||||
// the conditional block has a lot of dynamic behavior that is harder to
|
||||
// document plainly alongside the data definitions
|
||||
|
||||
inputs: {
|
||||
IF0: {
|
||||
description: "Runs the given block tree and checks whether it resolve true or false. If true, executes the 'do' branch, otherwise moves onto the next if (if present), or the final else (if present.)",
|
||||
check: "expression",
|
||||
shadow: 'io_logic_boolean'
|
||||
},
|
||||
|
||||
THEN0: {
|
||||
description: "The block diagram to execute when the preceding 'if' clause resolves to true",
|
||||
check: "expression",
|
||||
type: 'statement',
|
||||
shadow: {
|
||||
type:'action_log',
|
||||
inputs: {
|
||||
EXPRESSION: {
|
||||
shadow: {
|
||||
type: 'io_text',
|
||||
fields: {
|
||||
TEXT: 'conditional was true!'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ELSE_IF_LABEL: {
|
||||
|
|
@ -53,6 +56,33 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
docOverrides: {
|
||||
inputs: `
|
||||
### \`If\`
|
||||
This block tree will always be run. If it resolve to \`true\`, the blocks
|
||||
under the next 'do' section will be executed. Otherwise, execution moves
|
||||
to the next "else if" (if present), or the final "else" (if present.)
|
||||
|
||||
### \`Do\`
|
||||
The block diagram to execute when the preceding "if" or "else if" clause
|
||||
resolves to \`true\`.
|
||||
|
||||
### \`Else if\`
|
||||
**Optional:** "else if" only appears after clicking "+ else if", and can be
|
||||
removed by clicking the "-" next to it.
|
||||
|
||||
Another "if" to check, only if every prior if has executed and none
|
||||
resolved to \`true\`.
|
||||
|
||||
### \`Else\`
|
||||
**Optional:** "else" only appears after clicking "+ else", and can be removed
|
||||
by clicking "-" next to it.
|
||||
|
||||
This section will execute if all "if"s and "else-if"s have been executed and
|
||||
all resolved to \`false\`.
|
||||
`
|
||||
},
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const payload = {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ export default {
|
|||
mixins: ['replaceDropdownOptions'],
|
||||
extensions: ['populateFeedDropdown'],
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: `Get %FEED_KEY`,
|
||||
|
||||
fields: {
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
// deprecated: use feeds/get_value
|
||||
export default {
|
||||
type: "feed_selector",
|
||||
bytecodeKey: "feed",
|
||||
name: "Feed",
|
||||
colour: 300,
|
||||
description: "The last value of this feed or component, always a String",
|
||||
|
||||
mixins: ['replaceDropdownOptions'],
|
||||
extensions: ['populateFeedDropdown'],
|
||||
|
||||
template: "Feed: %FEED_KEY",
|
||||
|
||||
fields: {
|
||||
FEED_KEY: {
|
||||
description: "A listing of the User's Feeds to select from.",
|
||||
options: [
|
||||
[ "Loading Feeds...", "" ],
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
generators: {
|
||||
json: block => {
|
||||
const
|
||||
key = block.getFieldValue('FEED_KEY'),
|
||||
payload = JSON.stringify({
|
||||
feed: { key }
|
||||
})
|
||||
|
||||
return [ payload, 0 ]
|
||||
}
|
||||
},
|
||||
|
||||
regenerators: {
|
||||
json: blockObject => {
|
||||
const payload = blockObject.feed
|
||||
|
||||
// migrating to a new block
|
||||
return {
|
||||
type: "feed_get_value",
|
||||
fields: {
|
||||
FEED_KEY: payload.key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ export default {
|
|||
inputs: {
|
||||
VALUE: {
|
||||
description: "The value to publish to the Feed.",
|
||||
check: "expression",
|
||||
shadow: 'io_text'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default {
|
|||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "boolean",
|
||||
output: [ "expression", "boolean" ],
|
||||
},
|
||||
|
||||
template: "%BOOL",
|
||||
|
|
|
|||
|
|
@ -5,11 +5,17 @@ export default {
|
|||
colour: 60,
|
||||
description: "Swaps a truthy value to `false`, or a falsy value to `true`.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: "not %EXPRESSION",
|
||||
|
||||
inputs: {
|
||||
EXPRESSION: {
|
||||
description: "Block diagram that will be resolved, then have its truthiness flipped.",
|
||||
check: "expression",
|
||||
shadow: 'io_logic_boolean'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,16 +6,23 @@ export default {
|
|||
colour: 60,
|
||||
description: "Perform the specifed boolean logic operation on two operands.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: `%A %OP %B`,
|
||||
|
||||
inputs: {
|
||||
A: {
|
||||
description: "A block diagram that will be resolved to a truthy/falsy value",
|
||||
check: "expression",
|
||||
shadow: 'io_logic_boolean'
|
||||
},
|
||||
|
||||
B: {
|
||||
description: "A block diagram that will be resolved to a truthy/falsy value",
|
||||
check: "expression",
|
||||
shadow: 'io_logic_boolean'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
export default {
|
||||
type: 'matcher_boolean_operation',
|
||||
bytecodeKey: "matcherBooleanOperation",
|
||||
name: "Compare Matcher",
|
||||
colour: 60,
|
||||
inputsInline: true,
|
||||
description: "Perform a logic operation between the triggering Feed value and a block diagram.",
|
||||
|
||||
connections: { mode: 'value', output: 'matcher' },
|
||||
|
||||
template: `is true %OP %B`,
|
||||
|
||||
fields: {
|
||||
OP: {
|
||||
options: [
|
||||
['and', 'AND'],
|
||||
['or', 'OR'],
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
inputs: {
|
||||
B: {
|
||||
shadow: 'io_logic_boolean'
|
||||
}
|
||||
},
|
||||
|
||||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
operator = block.getFieldValue('OP'),
|
||||
rightExp = generator.valueToCode(block, 'B', 0) || null,
|
||||
|
||||
blockPayload = JSON.stringify({
|
||||
matcherBooleanOperation: {
|
||||
comparator: operator?.toLowerCase() || null,
|
||||
right: JSON.parse(rightExp),
|
||||
},
|
||||
})
|
||||
|
||||
return [ blockPayload, 0 ]
|
||||
}
|
||||
},
|
||||
|
||||
regenerators: {
|
||||
json: (blockObject, helpers) => {
|
||||
const
|
||||
{ comparator, right } = blockObject.matcherBooleanOperation,
|
||||
fields = {
|
||||
OP: comparator?.toUpperCase()
|
||||
},
|
||||
inputs = {
|
||||
B: helpers.expressionToBlock(right, { shadow: 'io_logic_boolean' }),
|
||||
}
|
||||
|
||||
return { type: 'matcher_boolean_operation', fields, inputs }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ export default {
|
|||
inputs: {
|
||||
B: {
|
||||
description: "The value to compare with the Feed value.",
|
||||
check: "expression",
|
||||
shadow: 'io_math_number'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export default {
|
|||
inputs: {
|
||||
B: {
|
||||
description: "The string to compare with the Feed value.",
|
||||
check: "expression",
|
||||
shadow: 'io_text'
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,16 +6,23 @@ export default {
|
|||
inputsInline: true,
|
||||
description: "Perform the specified arithmetic operation on two specified operands.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: `%A %OP %B`,
|
||||
|
||||
inputs: {
|
||||
A: {
|
||||
description: "The left side of the operation. Will be coerced to a number",
|
||||
check: "expression",
|
||||
shadow: 'io_math_number'
|
||||
},
|
||||
|
||||
B: {
|
||||
description: "The right side of the operation. Will be coerced to a number",
|
||||
check: "expression",
|
||||
shadow: 'io_math_number'
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,18 +4,26 @@ export default {
|
|||
name: "Compare Numbers",
|
||||
colour: 120,
|
||||
inputsInline: true,
|
||||
primaryCategory: "Math",
|
||||
description: "Numerically compare two given values using the selected math operation.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: `%A %OP %B`,
|
||||
|
||||
inputs: {
|
||||
A: {
|
||||
description: "The left side of the comparison. Will be coerced to a number",
|
||||
check: "expression",
|
||||
shadow: 'io_math_number'
|
||||
},
|
||||
|
||||
B: {
|
||||
description: "The right side of the comparison. Will be coerced to a number",
|
||||
check: "expression",
|
||||
shadow: 'io_math_number'
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export default {
|
|||
|
||||
inputs: {
|
||||
VALUE: {
|
||||
check: "expression",
|
||||
shadow: "io_math_number"
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export default {
|
|||
|
||||
inputs: {
|
||||
VALUE: {
|
||||
check: "expression",
|
||||
bytecodeProperty: "value",
|
||||
shadow: 'io_math_number'
|
||||
},
|
||||
|
|
@ -63,7 +64,7 @@ export default {
|
|||
generators: {
|
||||
json: (block, generator) => {
|
||||
const
|
||||
value = JSON.parse(generator.valueToCode(block, 'VALUE', 0)),
|
||||
value = JSON.parse(generator.valueToCode(block, 'VALUE', 0) || null),
|
||||
from = JSON.parse(generator.valueToCode(block, 'FROM_RANGE', 0)),
|
||||
to = JSON.parse(generator.valueToCode(block, 'TO_RANGE', 0)),
|
||||
payload = { mapValue: { value, from, to }}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default {
|
|||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "number",
|
||||
output: [ "expression", "number" ],
|
||||
},
|
||||
|
||||
extensions: {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ export default {
|
|||
inputs: {
|
||||
FROM: {
|
||||
description: "The lower bound of the range.",
|
||||
check: "expression",
|
||||
bytecodeProperty: "from",
|
||||
shadow: "io_math_number"
|
||||
},
|
||||
|
||||
TO: {
|
||||
description: "The upper bound of the range.",
|
||||
check: "expression",
|
||||
bytecodeProperty: "to",
|
||||
shadow: "io_math_number"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,16 +5,6 @@ export default {
|
|||
color: 120,
|
||||
description: "Round a value to the nearest whole number via round, floor, or ceiling functions",
|
||||
|
||||
docBlocks: [
|
||||
{
|
||||
type: 'io_math_number',
|
||||
fields: { NUM: 1.45 }
|
||||
}, {
|
||||
type: 'io_text',
|
||||
fields: { TEXT: "1.55" }
|
||||
}
|
||||
],
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
|
|
@ -24,6 +14,7 @@ export default {
|
|||
VALUE: {
|
||||
description: "A value you'd like to round to a whole number. Will be coerced to a number.",
|
||||
bytecodeProperty: "value",
|
||||
check: "expression",
|
||||
shadow: "io_math_number"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import weatherMixin from "./weather_mixin.js"
|
||||
|
||||
|
||||
|
||||
const
|
||||
{ keyToLabel, HELP_TEXT_BY_PROP: propText } = weatherMixin,
|
||||
propLines = (prefix, props) =>
|
||||
props.map(prop => `${prefix}- \`${keyToLabel(prop)}\`: ${propText[prop].description}`).join("")
|
||||
|
||||
export default {
|
||||
type: "weather",
|
||||
bytecodeKey: "weather",
|
||||
|
|
@ -9,6 +15,11 @@ export default {
|
|||
ioPlus: true,
|
||||
description: "Fetch the current or forecast weather conditions at the specified location.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
mixins: [
|
||||
'replaceDropdownOptions',
|
||||
{ weatherMixin }
|
||||
|
|
@ -61,14 +72,12 @@ export default {
|
|||
|
||||
fields: {
|
||||
POWER_UP_ID: {
|
||||
description: "Select a location from those defined by the Weather Power-Up",
|
||||
options: [
|
||||
[ "Loading locations...", "" ],
|
||||
]
|
||||
},
|
||||
|
||||
WEATHER_TIME: {
|
||||
description: "Select which kind of forecast to query",
|
||||
options: [
|
||||
[ "Now", "current" ],
|
||||
[ "In 5 minutes", "forecast_minutes_5" ],
|
||||
|
|
@ -93,7 +102,6 @@ export default {
|
|||
},
|
||||
|
||||
WEATHER_PROPERTY: {
|
||||
description: "Select which metric of the forecast to use.",
|
||||
label: ""
|
||||
},
|
||||
|
||||
|
|
@ -102,6 +110,36 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
docOverrides: {
|
||||
fields: `
|
||||
### \`Location\`
|
||||
The list of weather locations defined in the Weather Power-Up. Select
|
||||
the location you would like weather information for.
|
||||
|
||||
### \`Forecast\` and \`Metric\`
|
||||
A list a weather forecasts to choose from. The weather metrics available
|
||||
are different based on the chosen forecast.
|
||||
|
||||
:::details \`Now\` Metrics` + propLines(`
|
||||
`, weatherMixin.CURRENT_PROPS) + `
|
||||
:::
|
||||
|
||||
:::details \`In X minutes\` Metrics` + propLines(`
|
||||
`, weatherMixin.MINUTE_PROPS) + `
|
||||
:::
|
||||
|
||||
:::details \`In X hours\` Metrics` + propLines(`
|
||||
`, weatherMixin.HOUR_PROPS) + `
|
||||
:::
|
||||
|
||||
:::details \`In X days\` Metrics
|
||||
**Some daily metrics can be narrowed to just the daytime or overnight portions.**
|
||||
` + propLines(`
|
||||
`, weatherMixin.DAY_PROPS) + `
|
||||
:::
|
||||
`
|
||||
},
|
||||
|
||||
generators: {
|
||||
json: block => {
|
||||
const
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default {
|
|||
type: "action_root",
|
||||
name: "Root",
|
||||
colour: "0",
|
||||
description: "Add Triggers to determine when this Action runs.\nAdd Actions to determine what this Action does.",
|
||||
description: "Add Triggers to determine when this Action runs. Add Actions to determine what this Action does.",
|
||||
|
||||
connections: {},
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,26 @@ export default {
|
|||
name: "Compare Text",
|
||||
colour: 180,
|
||||
inputsInline: true,
|
||||
primaryCategory: "Logic",
|
||||
description: "Compare two chunks of text for equality, inequality, or inclusion.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: `%A %OP %B`,
|
||||
|
||||
inputs: {
|
||||
A: {
|
||||
description: "The left side of the comparison. Will be coerced to a string",
|
||||
check: "expression",
|
||||
shadow: 'io_text'
|
||||
},
|
||||
|
||||
B: {
|
||||
description: "The right side of the comparison. Will be coerced to a string",
|
||||
check: "expression",
|
||||
shadow: 'io_text'
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,16 +6,23 @@ export default {
|
|||
inputsInline: true,
|
||||
description: "Join two pieces of text into one.",
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: "%A + %B",
|
||||
|
||||
inputs: {
|
||||
A: {
|
||||
description: "The first string of text",
|
||||
check: "expression",
|
||||
shadow: "io_text"
|
||||
},
|
||||
|
||||
B: {
|
||||
description: "The last string of text",
|
||||
check: "expression",
|
||||
shadow: "io_text"
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,10 +41,18 @@ export default {
|
|||
:::
|
||||
`,
|
||||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: [ "expression", "string" ],
|
||||
},
|
||||
|
||||
template: "{{ %TEMPLATE",
|
||||
|
||||
inputs: {
|
||||
TEMPLATE: { shadow: 'io_text_multiline' }
|
||||
TEMPLATE: {
|
||||
check: "expression",
|
||||
shadow: 'io_text_multiline'
|
||||
}
|
||||
},
|
||||
|
||||
generators: {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default {
|
|||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "String",
|
||||
output: [ "expression", "string" ],
|
||||
},
|
||||
|
||||
template: `"%TEXT`,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ export default {
|
|||
|
||||
connections: {
|
||||
mode: "value",
|
||||
output: "String",
|
||||
output: [ "expression", "string" ],
|
||||
},
|
||||
|
||||
template: "P %TEXT",
|
||||
template: "¶ %TEXT",
|
||||
|
||||
fields: {
|
||||
TEXT: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default {
|
|||
|
||||
connections: {
|
||||
mode: 'value',
|
||||
output: "String",
|
||||
output: "expression",
|
||||
},
|
||||
|
||||
template: "Get variable %VAR",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export default {
|
|||
|
||||
inputs: {
|
||||
VALUE: {
|
||||
check: "expression",
|
||||
shadow: "io_text",
|
||||
}
|
||||
},
|
||||
|
|
|
|||
38
app/regenerators/migrations.js
Normal file
38
app/regenerators/migrations.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// These are for blocks that have been replaced with new blocks, but still
|
||||
// exist in the database. The block definition gets removed, but a new
|
||||
// regenerator is written here to catch those legacy blocks and "port" them
|
||||
// to the new block when they are loaded.
|
||||
|
||||
|
||||
export default {
|
||||
// becomes feed_set_value
|
||||
action_publish: {
|
||||
json: (blockObject, helpers) => {
|
||||
const payload = blockObject.publishAction
|
||||
|
||||
return {
|
||||
type: "feed_set_value",
|
||||
fields: {
|
||||
FEED_KEY: payload.feed.feed.key,
|
||||
},
|
||||
inputs: {
|
||||
VALUE: helpers.expressionToBlock(payload.value, { shadow: 'io_text' }),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// becomes feed_get_value
|
||||
feed_selector: {
|
||||
json: blockObject => {
|
||||
const payload = blockObject.feed
|
||||
|
||||
return {
|
||||
type: "feed_get_value",
|
||||
fields: {
|
||||
FEED_KEY: payload.key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
cypress/cypress.config.js
Normal file
5
cypress/cypress.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {},
|
||||
});
|
||||
11
cypress/e2e/block_images.cy.js
Normal file
11
cypress/e2e/block_images.cy.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
describe("Block Images", () => {
|
||||
it("download all block images", () => {
|
||||
cy.visit("http://localhost:5173/")
|
||||
cy.get("[data-id^='block-type-']").each(($el) => {
|
||||
cy.wrap($el).rightclick({ force: true })
|
||||
cy.contains("Save Block as PNG...").click()
|
||||
}).then(blockElements => {
|
||||
cy.log(`Saved ${blockElements.length} block images.`)
|
||||
})
|
||||
})
|
||||
})
|
||||
0
cypress/support/e2e.js
Normal file
0
cypress/support/e2e.js
Normal file
|
|
@ -13,7 +13,7 @@ export default defineConfig({
|
|||
['link', { rel: 'icon', href: "data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🧩</text></svg>" }]
|
||||
],
|
||||
|
||||
base: "/io-actions/",
|
||||
base: "/actions-docs/",
|
||||
|
||||
lastUpdated: true,
|
||||
|
||||
|
|
@ -24,14 +24,13 @@ export default defineConfig({
|
|||
},
|
||||
|
||||
editLink: {
|
||||
text: "Suggest an edit to this page",
|
||||
// runs on the frontend, must be a pure function!
|
||||
pattern: ({ filePath }) => {
|
||||
pattern: ({ filePath, frontmatter }) => {
|
||||
// special handling for block pages
|
||||
if(filePath.match(/^blocks\//)) {
|
||||
// docs come from the js, md is not the true source
|
||||
const jsPath = filePath.replace(/.md$/, '.js')
|
||||
// and we want to link to the main branch, not docs
|
||||
return `https://github.com/adafruit/io-actions/edit/main/app/${jsPath}`
|
||||
// block pages have their source paths in their frontmatter
|
||||
return `https://github.com/adafruit/io-actions/edit/main/app/blocks/${frontmatter.definitionPath}`
|
||||
}
|
||||
|
||||
return `https://github.com/adafruit/io-actions/edit/main/docs/${filePath}`
|
||||
|
|
@ -42,13 +41,14 @@ export default defineConfig({
|
|||
{ text: '🧩', link: '/sandbox' },
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Getting Started', link: '/getting-started' },
|
||||
{ text: 'The Blocks', link: '/block-index' },
|
||||
{ text: 'Examples', link: '/automation-examples' }
|
||||
{ text: 'Block List', link: '/blocks/index' },
|
||||
{ text: 'Contributing', link: '/contributing' },
|
||||
// { text: 'Examples', link: '/automation-examples' }
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
// covers /blocks/* and /block-index
|
||||
"/block": [ blocksSidebar ],
|
||||
// block index and pages
|
||||
"/blocks/": [ blocksSidebar ],
|
||||
|
||||
// devtools for the sandbox
|
||||
"/sandbox": [
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
title: The Blocks
|
||||
---
|
||||
|
||||
# The Blocks
|
||||
|
||||
Coming soon...
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { dispose, inject } from "../blockly/blockly_app.js"
|
||||
import initialWorkspace from "../blockly/workspace.json"
|
||||
import { imageExportRegistryItems } from '#src/image_exporter.js'
|
||||
|
||||
|
||||
const
|
||||
{ block, blocks=[], width="100%", height="200px", toolbox=true } = defineProps(
|
||||
|
|
@ -10,6 +12,30 @@
|
|||
injectOptions = {},
|
||||
options = {
|
||||
injectOptions,
|
||||
contextMenu: {
|
||||
register: [ ...imageExportRegistryItems ]
|
||||
},
|
||||
// TODO: specify dummy extension data with the block defs
|
||||
extensionData: {
|
||||
feedOptions: [
|
||||
["Group A Feed 1", "group-a.feed-1"],
|
||||
["Group A Feed 2", "group-a.feed-2"],
|
||||
["Group B Feed 1", "group-b.feed-1"],
|
||||
["Group C Feed 1", "group-C.feed-1"],
|
||||
],
|
||||
weatherLocationOptions: [
|
||||
[ "Industry City", "1" ],
|
||||
[ "Varick", "2" ],
|
||||
[ "Shenzhen", "3" ],
|
||||
],
|
||||
currentWeatherByLocation: {
|
||||
1: {
|
||||
current: {
|
||||
cloudCover: "5.4321",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
workspaceJson: block
|
||||
? {
|
||||
blocks: {
|
||||
|
|
@ -21,8 +47,8 @@
|
|||
x: 20,
|
||||
y: 20
|
||||
},
|
||||
...blocks.map((docBlock, idx) => ({
|
||||
...docBlock,
|
||||
...blocks.map((otherBlock, idx) => ({
|
||||
...otherBlock,
|
||||
x: 180,
|
||||
y: idx*30 + 20
|
||||
}))
|
||||
|
|
|
|||
41
docs/contributing.md
Normal file
41
docs/contributing.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
title: Contributing
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
## Documentation Wrong or Missing?
|
||||
|
||||
_You can help!_
|
||||
|
||||
### Have a GitHub Account?
|
||||
|
||||
Awesome! You can suggest changes to any page in a few simple steps:
|
||||
1. Scroll to the bottom of the page in question.
|
||||
2. Click the _"Edit this page"_ link.
|
||||
3. You will be asked to create a Fork, do so.
|
||||
4. You should now see the page source, opened and editable.
|
||||
5. Search for the text you'd like to change.
|
||||
6. Make the desired changes.
|
||||
7. Click the green _"Commit Changes..."_ button. (top-right)
|
||||
8. Add a brief explanation about the purpose of your changes.
|
||||
9. Click the green _"Propose Changes"_ button.
|
||||
10. Congrats! You've just gotten involved improving things for everyone.
|
||||
|
||||
### No GitHub Account?
|
||||
|
||||
If you don't already have a GitHub account, but you're willing to create one,
|
||||
simply head over to the [GitHub signup page](https://github.com/signup) and
|
||||
do so. Once created, return to the instructions above.
|
||||
|
||||
If you'd rather not create a GitHub account, no worries! You can suggest any
|
||||
changes via [the Adafruit IO Forums](https://forums.adafruit.com/viewforum.php?f=56)
|
||||
or [support channels](https://io.adafruit.com/support).
|
||||
|
||||
## Want to Suggest a New Block?
|
||||
|
||||
We'd love to hear from you!
|
||||
|
||||
You can suggest new or updated blocks via
|
||||
[the Adafruit IO Forums](https://forums.adafruit.com/viewforum.php?f=56) or
|
||||
[support channels](https://io.adafruit.com/support).
|
||||
|
|
@ -4,4 +4,16 @@ title: Getting Started
|
|||
|
||||
# Getting Started
|
||||
|
||||
Coming soon...
|
||||
_Welcome to the wonderful world of IoT automation using blocks, by Adafruit IO!_
|
||||
|
||||
::: tip New here?
|
||||
Get started with our handy guide, [How to Use Blockly for Actions](https://learn.adafruit.com/how-to-use-blockly-for-actions-on-adafruit-io)
|
||||
:::
|
||||
|
||||
Here's some project Guides that use Actions:
|
||||
- [No-Code Counters and Email Reports](https://learn.adafruit.com/no-code-counters-and-email-reports-with-adafruit-io-actions)
|
||||
- [IoT Door Detector](https://learn.adafruit.com/using-adafruit-io-actions-to-make-an-iot-door-detector)
|
||||
- [Rain-sensing Umbrella Stand](https://learn.adafruit.com/no-code-rain-sensing-smart-desktop-umbrella-stand)
|
||||
- [No-Code Barometer](https://learn.adafruit.com/dps310-analog-barometer)
|
||||
|
||||
We'll continue to update this section as new guides are written, check back soon!
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ layout: home
|
|||
hero:
|
||||
name: "IO Actions:"
|
||||
text: "Block Reference"
|
||||
tagline: Automation that clicks!
|
||||
tagline: Automation that clicks, for <a href="https://io.adafruit.com">Adafruit IO</a>
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Getting Started
|
||||
link: /getting-started.md
|
||||
- theme: alt
|
||||
text: The Blocks
|
||||
link: /block-index.md
|
||||
- theme: alt
|
||||
text: Automation Examples
|
||||
link: /automation-examples.md
|
||||
text: Block List
|
||||
link: /blocks/index.md
|
||||
|
||||
features:
|
||||
- icon: 📚
|
||||
|
|
|
|||
57
export.js
57
export.js
|
|
@ -1,9 +1,14 @@
|
|||
import { spawn, spawnSync } from 'node:child_process'
|
||||
import { copyFileSync, cpSync } from 'node:fs'
|
||||
|
||||
import { cleanDir, write, totalBytesWritten } from "./export_util.js"
|
||||
import DefinitionSet from '#src/definitions/definition_set.js'
|
||||
import { exportTo } from '#src/exporters/index.js'
|
||||
|
||||
|
||||
const toExport = process.argv[2]
|
||||
const
|
||||
toExport = process.argv[2],
|
||||
taskArgs = process.argv.slice(3)
|
||||
|
||||
if(!toExport) {
|
||||
console.error(`Export Error: Missing export name!\nUsage: node export.js [export name]`)
|
||||
|
|
@ -30,16 +35,59 @@ const
|
|||
},
|
||||
|
||||
"docs": async () => {
|
||||
await exporters.app("docs/blockly")
|
||||
// allow option to skip image generation
|
||||
const skipImages = taskArgs.includes("skipImages")
|
||||
if(!skipImages) {
|
||||
await exporters.blockImages()
|
||||
cleanDir("docs/block_images")
|
||||
cpSync("tmp/block_images/images", "docs/block_images", { recursive: true })
|
||||
}
|
||||
|
||||
await exporters.app("docs/blockly")
|
||||
cleanDir("docs/blocks")
|
||||
|
||||
await exportTo("docs", definitions, exportItem => {
|
||||
exportItem.sidebar("blocks/_blocks_sidebar.json")
|
||||
exportItem.blockPages(block => `blocks/${block.definitionPath.replace(/.js$/, '.md')}`)
|
||||
exportItem.blockIndex("blocks/index.md")
|
||||
exportItem.blockPages()
|
||||
// exportItem.blockExamples(block => "blocks/${block.definitionPath}/examples.json")
|
||||
})
|
||||
},
|
||||
|
||||
"blockImages": async () => {
|
||||
const destination = "tmp/block_images"
|
||||
cleanDir(destination)
|
||||
cleanDir(`${destination}/images`)
|
||||
|
||||
// export a special app with no toolbox, all blocks on workspace
|
||||
await exportTo(destination, definitions, exportItem => {
|
||||
exportItem.workspaceAllBlocks("workspace.json")
|
||||
write(`${destination}/toolbox.json`, "null")
|
||||
exportItem.blocks("blocks.json")
|
||||
exportItem.script("blockly_app.js")
|
||||
// TODO: make a DocumentExporter for generating html wrappers
|
||||
copyFileSync("src/exporters/document_templates/blockly_workspace.template.html", `${destination}/index.html`)
|
||||
})
|
||||
|
||||
// serve it
|
||||
console.log('Serving workspace for screenshots...')
|
||||
const viteProcess = spawn("npx", ["vite", "serve", destination])
|
||||
|
||||
// extract the screenshots
|
||||
console.log('Generating screenshots...')
|
||||
spawnSync("npx", ["cypress", "run",
|
||||
"--config", `downloadsFolder=${destination}/images`,
|
||||
"--config-file", `cypress/cypress.config.js`,
|
||||
])
|
||||
console.log('Generation complete.')
|
||||
|
||||
// kill the server
|
||||
if(!viteProcess.kill()) {
|
||||
console.log("Vite failed to exit gracefully")
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Server closed.')
|
||||
}
|
||||
},
|
||||
exporterNames = Object.keys(exporters)
|
||||
|
||||
|
|
@ -58,3 +106,6 @@ await exporter()
|
|||
const elapsed = Date.now() - startTime
|
||||
console.log("=======================")
|
||||
console.log(`🏁 Done. Wrote ${totalBytesWritten.toFixed(3)}k in ${elapsed}ms 🏁`)
|
||||
|
||||
|
||||
process.exit(0)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const
|
|||
if(fs.existsSync(dirName)) {
|
||||
fs.rmSync(dirName, { recursive: true, force: true })
|
||||
}
|
||||
fs.mkdirSync(dirName)
|
||||
fs.mkdirSync(dirName, { recursive: true, force: true })
|
||||
console.log(`/${dirName}: clean`)
|
||||
},
|
||||
|
||||
|
|
|
|||
1952
package-lock.json
generated
1952
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -35,6 +35,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.2.0",
|
||||
"chai": "^5.1.2",
|
||||
"cypress": "^14.5.3",
|
||||
"eslint": "^8.57.0",
|
||||
"esm-reload": "^1.0.1",
|
||||
"glob": "^10.4.2",
|
||||
|
|
|
|||
495
src/blockly_css.js
Normal file
495
src/blockly_css.js
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
export default `
|
||||
.blocklySvg {
|
||||
background-color: #fff;
|
||||
outline: none;
|
||||
overflow: hidden; /* IE overflows by default. */
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 99999; /* big value for bootstrap3 compatibility */
|
||||
}
|
||||
|
||||
.injectionDiv {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden; /* So blocks in drag surface disappear at edges */
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.blocklyNonSelectable {
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.blocklyBlockCanvas.blocklyCanvasTransitioning,
|
||||
.blocklyBubbleCanvas.blocklyCanvasTransitioning {
|
||||
transition: transform .5s;
|
||||
}
|
||||
|
||||
.blocklyTooltipDiv {
|
||||
background-color: #ffffc7;
|
||||
border: 1px solid #ddc;
|
||||
box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);
|
||||
color: #000;
|
||||
display: none;
|
||||
font: 9pt sans-serif;
|
||||
opacity: .9;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
z-index: 100000; /* big value for bootstrap3 compatibility */
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
border: 1px solid;
|
||||
border-color: #dadce0;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 4px;
|
||||
box-shadow: 0 0 3px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv.blocklyFocused {
|
||||
box-shadow: 0 0 6px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownContent {
|
||||
max-height: 300px; /* @todo: spec for maximum height. */
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blocklyDropDownArrow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
z-index: -1;
|
||||
background-color: inherit;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyDropDownButton {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
border: 1px solid;
|
||||
transition: box-shadow .1s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blocklyArrowTop {
|
||||
border-top: 1px solid;
|
||||
border-left: 1px solid;
|
||||
border-top-left-radius: 4px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyArrowBottom {
|
||||
border-bottom: 1px solid;
|
||||
border-right: 1px solid;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyResizeSE {
|
||||
cursor: se-resize;
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyResizeSW {
|
||||
cursor: sw-resize;
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyResizeLine {
|
||||
stroke: #515A5A;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklyHighlightedConnectionPath {
|
||||
fill: none;
|
||||
stroke: #fc3;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.blocklyPathLight {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklySelected>.blocklyPathLight {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyDraggable {
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.blocklyDragging {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
/* Changes cursor on mouse down. Not effective in Firefox because of
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */
|
||||
.blocklyDraggable:active {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.blocklyDragging.blocklyDraggingDelete {
|
||||
cursor: url("./handdelete.cur"), auto;
|
||||
}
|
||||
|
||||
.blocklyDragging>.blocklyPath,
|
||||
.blocklyDragging>.blocklyPathLight {
|
||||
fill-opacity: .8;
|
||||
stroke-opacity: .8;
|
||||
}
|
||||
|
||||
.blocklyDragging>.blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyDisabled>.blocklyPath {
|
||||
fill-opacity: .5;
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
.blocklyDisabled>.blocklyPathLight,
|
||||
.blocklyDisabled>.blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyInsertionMarker>.blocklyPath,
|
||||
.blocklyInsertionMarker>.blocklyPathLight,
|
||||
.blocklyInsertionMarker>.blocklyPathDark {
|
||||
fill-opacity: .2;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.blocklyMultilineText {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.blocklyNonEditableText>text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyFlyout {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.blocklyText text {
|
||||
cursor: default;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
*/
|
||||
.blocklySvg text {
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.blocklyHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyFieldDropdown:not(.blocklyHidden) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.blocklyIconGroup {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.blocklyIconGroup:not(:hover),
|
||||
.blocklyIconGroupReadonly {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.blocklyIconShape {
|
||||
fill: #00f;
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.blocklyIconSymbol {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.blocklyMinimalBody {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.blocklyHtmlInput {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Remove the increase and decrease arrows on the field number editor */
|
||||
input.blocklyHtmlInput[type=number]::-webkit-inner-spin-button,
|
||||
input.blocklyHtmlInput[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.blocklyMainBackground {
|
||||
stroke-width: 1;
|
||||
stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */
|
||||
}
|
||||
|
||||
.blocklyMutatorBackground {
|
||||
fill: #fff;
|
||||
stroke: #ddd;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklyFlyoutBackground {
|
||||
fill: #ddd;
|
||||
fill-opacity: .8;
|
||||
}
|
||||
|
||||
.blocklyMainWorkspaceScrollbar {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.blocklyFlyoutScrollbar {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.blocklyScrollbarHorizontal,
|
||||
.blocklyScrollbarVertical {
|
||||
position: absolute;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.blocklyScrollbarBackground {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.blocklyScrollbarHandle {
|
||||
fill: #ccc;
|
||||
}
|
||||
|
||||
.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,
|
||||
.blocklyScrollbarHandle:hover {
|
||||
fill: #bbb;
|
||||
}
|
||||
|
||||
/* Darken flyout scrollbars due to being on a grey background. */
|
||||
/* By contrast, workspace scrollbars are on a white background. */
|
||||
.blocklyFlyout .blocklyScrollbarHandle {
|
||||
fill: #bbb;
|
||||
}
|
||||
|
||||
.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,
|
||||
.blocklyFlyout .blocklyScrollbarHandle:hover {
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyInvalidInput {
|
||||
background: #faa;
|
||||
}
|
||||
|
||||
.blocklyVerticalMarker {
|
||||
stroke-width: 3px;
|
||||
fill: rgba(255,255,255,.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyComputeCanvas {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.blocklyNoPointerEvents {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyContextMenu {
|
||||
border-radius: 4px;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.blocklyDropdownMenu {
|
||||
border-radius: 2px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.blocklyDropdownMenu .blocklyMenuItem {
|
||||
/* 28px on the left for icon or checkbox. */
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
/* BiDi override for the resting state. */
|
||||
.blocklyDropdownMenu .blocklyMenuItemRtl {
|
||||
/* Flip left/right padding for BiDi. */
|
||||
padding-left: 5px;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv .blocklyMenu {
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 3px 1px rgba(0,0,0,.3);
|
||||
font: normal 13px Arial, sans-serif;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 100%;
|
||||
z-index: 20000; /* Arbitrary, but some apps depend on it... */
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv .blocklyMenu.blocklyFocused {
|
||||
box-shadow: 0 0 6px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv .blocklyMenu {
|
||||
background: inherit; /* Compatibility with gapi, reset from goog-menu */
|
||||
border: inherit; /* Compatibility with gapi, reset from goog-menu */
|
||||
font: normal 13px "Helvetica Neue", Helvetica, sans-serif;
|
||||
outline: none;
|
||||
position: relative; /* Compatibility with gapi, reset from goog-menu */
|
||||
z-index: 20000; /* Arbitrary, but some apps depend on it... */
|
||||
}
|
||||
|
||||
/* State: resting. */
|
||||
.blocklyMenuItem {
|
||||
border: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
/* 7em on the right for shortcut. */
|
||||
min-width: 7em;
|
||||
padding: 6px 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* State: disabled. */
|
||||
.blocklyMenuItemDisabled {
|
||||
color: #ccc;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
/* State: hover. */
|
||||
.blocklyMenuItemHighlight {
|
||||
background-color: rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
/* State: selected/checked. */
|
||||
.blocklyMenuItemCheckbox {
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.blocklyMenuItemSelected .blocklyMenuItemCheckbox {
|
||||
background: url(./sprites.png) no-repeat -48px -16px;
|
||||
float: left;
|
||||
margin-left: -24px;
|
||||
position: static; /* Scroll with the menu. */
|
||||
}
|
||||
|
||||
.blocklyMenuItemRtl .blocklyMenuItemCheckbox {
|
||||
float: right;
|
||||
margin-right: -24px;
|
||||
}
|
||||
|
||||
/* Begin .geras-renderer.classic-theme (stripped) */
|
||||
.blocklyText,
|
||||
.blocklyFlyoutLabelText {
|
||||
font: normal 11pt sans-serif;
|
||||
}
|
||||
.blocklyText {
|
||||
fill: #fff;
|
||||
}
|
||||
.blocklyNonEditableText>rect,
|
||||
.blocklyEditableText>rect {
|
||||
fill: #fff;
|
||||
fill-opacity: .6;
|
||||
stroke: none;
|
||||
}
|
||||
.blocklyNonEditableText>text,
|
||||
.blocklyEditableText>text {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyFlyoutLabelText {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyText.blocklyBubbleText {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyEditableText:not(.editing):hover>rect {
|
||||
stroke: #fff;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.blocklyHtmlInput {
|
||||
font-family: sans-serif;
|
||||
font-weight: normal;
|
||||
}
|
||||
.blocklySelected>.blocklyPath {
|
||||
stroke: #fc3;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.blocklyHighlightedConnectionPath {
|
||||
stroke: #fc3;
|
||||
}
|
||||
.blocklyReplaceable .blocklyPath {
|
||||
fill-opacity: .5;
|
||||
}
|
||||
.blocklyReplaceable .blocklyPathLight,
|
||||
.blocklyReplaceable .blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
.blocklyInsertionMarker>.blocklyPath {
|
||||
fill-opacity: 0.2;
|
||||
stroke: none;
|
||||
}
|
||||
.blocklyInsertionMarker>.blocklyPathLight,
|
||||
.blocklyInsertionMarker>.blocklyPathDark {
|
||||
fill-opacity: 0.2;
|
||||
stroke: none;
|
||||
}
|
||||
`
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
import { capitalize, filter, isString, map, isEmpty, keyBy, mapValues, reduce, forEach, pickBy, identity } from 'lodash-es'
|
||||
import { capitalize, filter, isString, isEmpty, mapValues, forEach, pickBy, identity } from 'lodash-es'
|
||||
|
||||
import BlockExporter from "#src/exporters/block_exporter.js"
|
||||
import { niceTemplate } from '#src/util.js'
|
||||
|
||||
|
||||
const UNCATEGORIZED_PATH = "uncategorized"
|
||||
|
||||
class BlockDefinition {
|
||||
definitionSet = null
|
||||
|
||||
|
|
@ -16,8 +18,8 @@ class BlockDefinition {
|
|||
name = null
|
||||
|
||||
description = ''
|
||||
docOverrides = {}
|
||||
ioPlus = false
|
||||
docBlocks = null
|
||||
|
||||
colour = null
|
||||
color = null
|
||||
|
|
@ -41,7 +43,7 @@ class BlockDefinition {
|
|||
|
||||
disabled = false
|
||||
|
||||
categories = []
|
||||
primaryCategory = null
|
||||
|
||||
|
||||
getCategories() {
|
||||
|
|
@ -52,6 +54,42 @@ class BlockDefinition {
|
|||
: [])
|
||||
}
|
||||
|
||||
getPrimaryCategory() {
|
||||
const categories = this.getCategories()
|
||||
|
||||
// doesn't appear in a category
|
||||
if(!categories.length) {
|
||||
// why specify a primary? warn
|
||||
if(this.primaryCategory) {
|
||||
console.warn(`Warning [${this.type}]: No category found, but did have "primaryCategory" key: "${this.primaryCategory}"`)
|
||||
}
|
||||
|
||||
return UNCATEGORIZED_PATH
|
||||
}
|
||||
|
||||
const firstCategoryName = categories[0].name
|
||||
|
||||
// appears in multiple categories
|
||||
if(categories.length > 1) {
|
||||
// doesn't specify a primary! this is bad, unsure what menu and URL it will fall under, warn
|
||||
if(!this.primaryCategory) {
|
||||
console.warn(`Warning [${this.type}]: Multiple categories but no "primaryCategory" declaration, using "${firstCategoryName}"`)
|
||||
} else {
|
||||
return this.primaryCategory
|
||||
}
|
||||
}
|
||||
|
||||
return firstCategoryName
|
||||
}
|
||||
|
||||
documentationPath() {
|
||||
const
|
||||
blockMdFilename = this.definitionPath.split("/").at(-1).replace(/.js$/, '.md'),
|
||||
primaryCategory = this.getPrimaryCategory()
|
||||
|
||||
return `blocks/${primaryCategory}/${blockMdFilename}`.toLowerCase()
|
||||
}
|
||||
|
||||
toBlocklyJSON() {
|
||||
return BlockExporter.export(this)
|
||||
}
|
||||
|
|
@ -86,9 +124,17 @@ class BlockDefinition {
|
|||
}
|
||||
|
||||
const
|
||||
definitionPropsToInputs = ({ inputValue, block, shadow }) => {
|
||||
definitionPropsToInputs = input => {
|
||||
const { block, shadow, type } = input
|
||||
|
||||
// bail unless this is a statement or value input
|
||||
// (undefined implies value, for now)
|
||||
if(![undefined, 'statement', 'value'].includes(type)) {
|
||||
return
|
||||
}
|
||||
|
||||
if(!block && !shadow) {
|
||||
console.warn("Warning: no block or shadow specified for", inputValue)
|
||||
console.warn("Warning: no block or shadow specified for input:", input)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +149,14 @@ const
|
|||
}
|
||||
|
||||
} else if(shadow) {
|
||||
return shadowToInput(shadow)
|
||||
const shadowJson = shadowToInput(shadow)
|
||||
|
||||
return {
|
||||
// also copy the shadow into a real block
|
||||
// TODO: nested shadow blocks
|
||||
block: shadowJson.shadow,
|
||||
...shadowJson
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -132,10 +185,11 @@ BlockDefinition.parseRawDefinition = function(rawBlockDefinition, definitionPath
|
|||
blockDef.definitionJS = rawBlockDefinition
|
||||
blockDef.type = rawBlockDefinition.type
|
||||
blockDef.name = rawBlockDefinition.name
|
||||
blockDef.primaryCategory = rawBlockDefinition.primaryCategory
|
||||
blockDef.docOverrides = rawBlockDefinition.docOverrides
|
||||
blockDef.description = rawBlockDefinition.description
|
||||
? niceTemplate(rawBlockDefinition.description)
|
||||
: ""
|
||||
blockDef.docBlocks = rawBlockDefinition.docBlocks
|
||||
blockDef.ioPlus = rawBlockDefinition.ioPlus
|
||||
blockDef.tooltip = blockDef.description.split("\n")[0]
|
||||
blockDef.disabled = !!rawBlockDefinition.disabled
|
||||
|
|
@ -155,10 +209,19 @@ BlockDefinition.parseRawDefinition = function(rawBlockDefinition, definitionPath
|
|||
// warnings on any data that's missing, ugly, etc
|
||||
if(!blockDef.name) {
|
||||
// if no name given, humanize the type property as a default
|
||||
// TODO: make a setting for this and re-enable
|
||||
// console.warn(`No "name" property provided for block: "${rawBlockDefinition.type}" (${definitionPath})`)
|
||||
blockDef.name = rawBlockDefinition.type.split(/[\W_]+/).map(capitalize).join(" ").replace(/^io /i, "")
|
||||
console.warn(`Warning: [${blockDef.type}] No "name" property provided, defaulted to: "${blockDef.name}"`)
|
||||
}
|
||||
|
||||
if(!blockDef.connections) {
|
||||
console.warn(`Warning: [${blockDef.type}] No "connections" property provided, no defaults.`)
|
||||
}
|
||||
|
||||
forEach(blockDef.inputs, (input, inputName) => {
|
||||
if(!input.check && input.type !== 'label') {
|
||||
console.warn(`Warning: [${blockDef.type}] Input is unchecked: ${inputName} ${input.type}`)
|
||||
}
|
||||
})
|
||||
|
||||
return blockDef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,14 +60,23 @@ DefinitionSet.load = async function(appLocation) {
|
|||
enabledBlocks = reject(rawDefinitions.blocks, "definition.disabled"),
|
||||
definitionSet = new DefinitionSet()
|
||||
|
||||
// TODO: fields
|
||||
// TODO: shadows
|
||||
// TODO: inputs
|
||||
// TODO: process fields
|
||||
// TODO: process shadows
|
||||
// TODO: process inputs
|
||||
|
||||
// process mixins
|
||||
definitionSet.mixins = rawDefinitions.mixins
|
||||
// process extensions
|
||||
definitionSet.extensions = rawDefinitions.extensions
|
||||
// process mutators
|
||||
definitionSet.mutators = rawDefinitions.mutators
|
||||
|
||||
// process standalone regenerators
|
||||
forEach(rawDefinitions.regenerators, (regenerators, blockType) => {
|
||||
definitionSet.regenerators[blockType] = regenerators
|
||||
})
|
||||
|
||||
// process blocks
|
||||
forEach(enabledBlocks, ({ definition, path }) => {
|
||||
const blockDef = BlockDefinition.parseRawDefinition(definition, path, definitionSet)
|
||||
definitionSet.blocks.push(blockDef)
|
||||
|
|
@ -110,15 +119,24 @@ DefinitionSet.load = async function(appLocation) {
|
|||
definitionSet.mutators[blockDef.type] = mutator
|
||||
}
|
||||
|
||||
if(definitionSet.generators[blockDef.type]) {
|
||||
throw new Error(`Generator already present for block: ${blockDef.type}`)
|
||||
}
|
||||
definitionSet.generators[blockDef.type] = blockDef.generators
|
||||
|
||||
if(definitionSet.regenerators[blockDef.type]) {
|
||||
throw new Error(`Regenerator already present for block: ${blockDef.type}`)
|
||||
}
|
||||
definitionSet.regenerators[blockDef.type] = blockDef.regenerators
|
||||
})
|
||||
|
||||
// process toolbox
|
||||
forEach(rawDefinitions.toolboxes, rawToolboxDef => {
|
||||
const toolboxDef = ToolboxDefinition.parseRawDefinition(rawToolboxDef, definitionSet)
|
||||
definitionSet.toolboxes.push(toolboxDef)
|
||||
})
|
||||
|
||||
// process workspace
|
||||
forEach(rawDefinitions.workspaces, rawWorkspaceDef => {
|
||||
const workspaceDef = WorkspaceDefinition.parseRawDefinition(rawWorkspaceDef, definitionSet)
|
||||
definitionSet.workspaces.push(workspaceDef)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { capitalize, trim } from 'lodash-es'
|
||||
import { trim } from 'lodash-es'
|
||||
|
||||
import renderFields from './render_block_fields.js'
|
||||
import renderInputs from './render_block_inputs.js'
|
||||
|
||||
|
||||
const
|
||||
export const
|
||||
IO_PLUS_ALERT = `
|
||||
::: tip :warning: IO+ Required
|
||||
This Block requires an IO+ subscription to use. [Learn more about IO+](https://io.adafruit.com/plus)
|
||||
|
|
@ -17,54 +17,73 @@ This Block requires an IO+ subscription to use. [Learn more about IO+](https://i
|
|||
return trim(`${name} ${ioPlusBadge}`)
|
||||
},
|
||||
|
||||
// 
|
||||
renderBlockImage = ({ name, type }) => ``,
|
||||
|
||||
renderDescription = ({ description }) => description || "No docs for this block, yet.",
|
||||
|
||||
renderIOPlusAlert = ({ ioPlus }) => ioPlus ? IO_PLUS_ALERT : "",
|
||||
|
||||
renderWorkspace = definition => {
|
||||
const workspaceProps = JSON.stringify({
|
||||
toolbox: false,
|
||||
block: definition.toBlocklyInstanceJSON(),
|
||||
blocks: definition.docBlocks || []
|
||||
})
|
||||
renderFieldsSection = definition => {
|
||||
const fieldsMarkdown = renderFields(definition)
|
||||
|
||||
return `<BlocklyWorkspace v-bind='${ workspaceProps }' />`
|
||||
return fieldsMarkdown
|
||||
? `## Fields\n\n${ fieldsMarkdown }`
|
||||
: ""
|
||||
},
|
||||
|
||||
renderInputsSection = definition => {
|
||||
const inputsMarkdown = renderInputs(definition)
|
||||
|
||||
return inputsMarkdown
|
||||
? `## Inputs\n\n${ inputsMarkdown }`
|
||||
: ""
|
||||
},
|
||||
|
||||
renderOutput = definition => {
|
||||
return capitalize(definition.connections?.output || "Unspecified")
|
||||
return ''
|
||||
|
||||
// TODO: re-enable when we have something meanginful to show the user
|
||||
// const defaultedOutput = capitalize(definition.connections?.output || "Unspecified")
|
||||
|
||||
// return `
|
||||
// ## Output
|
||||
// ${ defaultedOutput }
|
||||
// `
|
||||
},
|
||||
|
||||
renderExamples = definition => {
|
||||
return "Coming soon..."
|
||||
return ""
|
||||
|
||||
// TODO: re-enable conditionally when we have examples
|
||||
// return `
|
||||
// ## Examples
|
||||
// Coming soon...
|
||||
// `
|
||||
}
|
||||
|
||||
export default definition =>
|
||||
`---
|
||||
title: "Block: ${definition.name}"
|
||||
editLink: true
|
||||
definitionPath: ${ definition.definitionPath }
|
||||
---
|
||||
|
||||
# Block: ${ renderBlockTitle(definition) }
|
||||
|
||||
Type: \`${definition.type}\`
|
||||
|
||||
${ renderBlockImage(definition) }
|
||||
|
||||
${ renderDescription(definition) }
|
||||
|
||||
${ renderIOPlusAlert(definition) }
|
||||
|
||||
## Workspace
|
||||
${ renderWorkspace(definition) }
|
||||
${ renderFieldsSection(definition) }
|
||||
|
||||
## Fields
|
||||
${ renderFields(definition) }
|
||||
${ renderInputsSection(definition) }
|
||||
|
||||
## Inputs
|
||||
${ renderInputs(definition) }
|
||||
|
||||
## Output
|
||||
${ renderOutput(definition) }
|
||||
|
||||
## Examples
|
||||
${ renderExamples(definition) }
|
||||
`
|
||||
|
|
|
|||
|
|
@ -1,20 +1,40 @@
|
|||
import { capitalize, map, mapValues, values } from 'lodash-es'
|
||||
import { capitalize, keys, map, mapValues, pickBy, values } from 'lodash-es'
|
||||
|
||||
import { niceTemplate } from '#src/util.js'
|
||||
|
||||
|
||||
const
|
||||
renderFields = definition => {
|
||||
if(definition.docOverrides?.fields) {
|
||||
return renderOverridenFields(definition)
|
||||
}
|
||||
|
||||
const fields = values(mapValues(definition.fields, (newField, name) => {
|
||||
newField.field = name
|
||||
return newField
|
||||
}))
|
||||
|
||||
if(!fields.length) { return "This block has no form fields." }
|
||||
if(!fields.length) { return }
|
||||
|
||||
return fields.map(renderField).join("\n\n")
|
||||
},
|
||||
|
||||
renderOverridenFields = definition => {
|
||||
// warn if any inputs have descriptions that won't be rendered
|
||||
const
|
||||
{ fields } = definition.docOverrides,
|
||||
missedFields = keys(pickBy(definition.fields, "description")).join(", ")
|
||||
|
||||
if(missedFields) {
|
||||
console.warn(`Warning [${definition.type}]: Inputs doc is overriden, input descriptions will not be seen for: ${missedFields}`)
|
||||
}
|
||||
|
||||
// determine if the override is a function to call
|
||||
return niceTemplate(typeof fields === 'string'
|
||||
? fields
|
||||
: fields(definition))
|
||||
},
|
||||
|
||||
renderField = field => {
|
||||
const lines = []
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,36 @@
|
|||
import { capitalize, forEach, keys } from 'lodash-es'
|
||||
import { capitalize, pickBy, forEach, keys } from 'lodash-es'
|
||||
|
||||
import { niceTemplate } from '#src/util.js'
|
||||
|
||||
|
||||
const
|
||||
renderInputs = definition => {
|
||||
if(!keys(definition.inputs).length) {
|
||||
return "This block has no inputs"
|
||||
if(definition.docOverrides?.inputs) {
|
||||
return renderOverridenInputs(definition)
|
||||
}
|
||||
|
||||
if(!keys(definition.inputs).length) { return }
|
||||
|
||||
return renderEachInput(definition)
|
||||
},
|
||||
|
||||
renderOverridenInputs = definition => {
|
||||
// warn if any inputs have descriptions that won't be rendered
|
||||
const
|
||||
{ inputs } = definition.docOverrides,
|
||||
missedInputs = keys(pickBy(definition.inputs, "description")).join(", ")
|
||||
|
||||
if(missedInputs) {
|
||||
console.warn(`Warning [${definition.type}]: Inputs doc is overriden, input descriptions will not be seen for: ${missedInputs}`)
|
||||
}
|
||||
|
||||
// determine if the override is a function to call
|
||||
return niceTemplate(typeof inputs === 'string'
|
||||
? inputs
|
||||
: inputs(definition))
|
||||
},
|
||||
|
||||
renderEachInput = definition => {
|
||||
const lines = []
|
||||
forEach(definition.inputs, (input, inputName) => {
|
||||
if(input.type === 'label') { return }
|
||||
|
|
|
|||
90
src/exporters/block_index_exporter.js
Normal file
90
src/exporters/block_index_exporter.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { writeFileSync } from 'fs'
|
||||
import { isString, without } from 'lodash-es'
|
||||
|
||||
import { renderBlockImage } from '../docs/render_block.js'
|
||||
|
||||
|
||||
export default class BlockIndexExporter {
|
||||
definitionSet = null
|
||||
destination = null
|
||||
|
||||
constructor(definitionSet, destination) {
|
||||
this.definitionSet = definitionSet
|
||||
this.destination = destination
|
||||
}
|
||||
|
||||
export(givenOptions = {}) {
|
||||
const
|
||||
options = {
|
||||
toFile: false,
|
||||
filenameFunc: blockDef => blockDef.documentationPath(),
|
||||
...givenOptions
|
||||
},
|
||||
categories = this.definitionSet.getCategories(),
|
||||
categorized = [],
|
||||
index = []
|
||||
|
||||
index.push(`---
|
||||
title: "Block List"
|
||||
editLink: false
|
||||
---
|
||||
|
||||
# Block List`)
|
||||
index.push(`${this.definitionSet.blocks.length} blocks and counting`)
|
||||
|
||||
categories.forEach(category => {
|
||||
index.push(`## ${category.name}`)
|
||||
|
||||
index.push(
|
||||
this.definitionSet.blocks.reduce((acc, def) => {
|
||||
if(category.contents?.includes(def) || category.usesBlocks?.includes(def.type)) {
|
||||
acc.push(definitionToIndexLines(def))
|
||||
categorized.push(def)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, []).join("\n")
|
||||
)
|
||||
})
|
||||
|
||||
// Special "Uncategorized" category
|
||||
const uncategorized = without(this.definitionSet.blocks, ...categorized)
|
||||
index.push(`## Uncategorized`)
|
||||
index.push("These blocks do not appear in the toolbox directly. They may appear in gear menus, as sub-blocks, etc.")
|
||||
|
||||
index.push(
|
||||
uncategorized.map(def => definitionToIndexLines(def)).join("\n")
|
||||
)
|
||||
|
||||
const finalMarkdown = index.join("\n\n")
|
||||
|
||||
if(!options.toFile) {
|
||||
return finalMarkdown
|
||||
}
|
||||
|
||||
const filename = isString(options.toFile)
|
||||
? options.toFile
|
||||
: `index.md`
|
||||
|
||||
writeFileSync(`${this.destination}/${filename}`, finalMarkdown)
|
||||
}
|
||||
|
||||
exportToFile = (toFile=true) => {
|
||||
this.export({ toFile })
|
||||
}
|
||||
}
|
||||
|
||||
const definitionToIndexLines = def => {
|
||||
const indexLines = []
|
||||
|
||||
// block name and link
|
||||
indexLines.push(`### [${ def.name }](/${ def.documentationPath() })`)
|
||||
|
||||
// block short description
|
||||
indexLines.push(`_${def.tooltip}_`)
|
||||
|
||||
// block image
|
||||
indexLines.push(renderBlockImage(def))
|
||||
|
||||
return indexLines.join("\n")
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { mkdirSync, writeFileSync } from 'fs'
|
||||
import { dirname } from 'path'
|
||||
import { forEach } from 'lodash-es'
|
||||
import { forEach, identity, pickBy } from 'lodash-es'
|
||||
|
||||
import toBlockMarkdown from "#src/docs/render_block.js"
|
||||
|
||||
|
|
@ -18,14 +18,13 @@ export default class BlockPageExporter {
|
|||
const
|
||||
options = {
|
||||
toFile: false,
|
||||
filenameFunc: null,
|
||||
filenameFunc: blockDef => blockDef.documentationPath(),
|
||||
...givenOptions
|
||||
}
|
||||
|
||||
forEach(this.definitionSet.blocks, blockDefinition => {
|
||||
const
|
||||
docPath = options.filenameFunc?.(blockDefinition)
|
||||
|| blockDefinition.definitionPath.replace(/.js$/, '.md'),
|
||||
docPath = options.filenameFunc(blockDefinition),
|
||||
fullPath = `${this.destination}/${docPath}`
|
||||
|
||||
mkdirSync(dirname(fullPath), { recursive: true })
|
||||
|
|
@ -34,6 +33,11 @@ export default class BlockPageExporter {
|
|||
}
|
||||
|
||||
exportToFile = (filenameFunc, toFile=true) => {
|
||||
this.export({ toFile, filenameFunc })
|
||||
const exportOptions = {
|
||||
toFile,
|
||||
// no filenameFunc if absent/falsy
|
||||
...pickBy({ filenameFunc }, identity)
|
||||
}
|
||||
this.export(exportOptions)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
const
|
||||
// TODO: rely on project configuration for docs site location
|
||||
DOCS_BLOCKS_ROOT = "https://adafruit.github.io/io-actions/blocks",
|
||||
DOCS_BLOCKS_ROOT = "https://io.adafruit.com/actions-docs",
|
||||
|
||||
processHelp = definition => {
|
||||
if (!definition.definitionPath) { return {} }
|
||||
|
||||
const thisBlockPredicate = definition.definitionPath.slice(0, -3)
|
||||
const
|
||||
// location of the markdown, without the .md extension
|
||||
thisBlockPredicate = definition.documentationPath().slice(0, -3),
|
||||
// build the full URL
|
||||
helpUrl = `${DOCS_BLOCKS_ROOT}/${thisBlockPredicate}`
|
||||
|
||||
return {
|
||||
helpUrl: `${DOCS_BLOCKS_ROOT}/${thisBlockPredicate}`
|
||||
}
|
||||
return { helpUrl }
|
||||
}
|
||||
|
||||
export default processHelp
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Blockly: All Blocks</title>
|
||||
|
||||
<script type="module">
|
||||
import { inject } from './blockly_app.js'
|
||||
import { imageExportRegistryItems } from '#src/image_exporter.js'
|
||||
|
||||
inject("blocklyDiv", {
|
||||
contextMenu: {
|
||||
register: [ ...imageExportRegistryItems ]
|
||||
},
|
||||
extensionData: {
|
||||
feedOptions: [
|
||||
["Group\u00A0A Feed 1", "group-a.feed-1"],
|
||||
["Group\u00A0A Feed 2", "group-a.feed-2"],
|
||||
["Group\u00A0B Feed 1", "group-b.feed-1"],
|
||||
["Group\u00A0C Feed 1", "group-C.feed-1"],
|
||||
],
|
||||
weatherLocationOptions: [
|
||||
[ "Industry City", "1" ],
|
||||
[ "Varick", "2" ],
|
||||
[ "Shenzhen", "3" ],
|
||||
],
|
||||
currentWeatherByLocation: {
|
||||
1: {
|
||||
current: {
|
||||
cloudCover: "5.4321",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#blocklyDiv {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="blocklyDiv"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -2,7 +2,9 @@ import BlockExporter from "./block_exporter.js"
|
|||
import ToolboxExporter from "./toolbox_exporter.js"
|
||||
import WorkspaceExporter from "./workspace_exporter.js"
|
||||
import ScriptExporter from "./script_exporter.js"
|
||||
import WorkspaceAllBlocksExporter from "./workspace_all_blocks_exporter.js"
|
||||
import SidebarExporter from "./sidebar_exporter.js"
|
||||
import BlockIndexExporter from "./block_index_exporter.js"
|
||||
import BlockPageExporter from "./block_page_exporter.js"
|
||||
|
||||
|
||||
|
|
@ -23,7 +25,9 @@ export const exportTo = async (destination, definitions, exportFunc) => {
|
|||
blocks: new BlockExporter(definitions, destination).exportToFile,
|
||||
script: new ScriptExporter(definitions, destination).exportToFile,
|
||||
// docs exporters
|
||||
workspaceAllBlocks: new WorkspaceAllBlocksExporter(definitions, destination).exportToFile,
|
||||
sidebar: new SidebarExporter(definitions, destination).exportToFile,
|
||||
blockIndex: new BlockIndexExporter(definitions, destination).exportToFile,
|
||||
blockPages: new BlockPageExporter(definitions, destination).exportToFile,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ const BYTECODE_BLOCK_TYPE_MAP = {
|
|||
whenDataMatchStateChanged: 'when_data_matching_state',
|
||||
matcherCompare: 'matcher_compare',
|
||||
matcherTextCompare: 'matcher_text_compare',
|
||||
matcherBooleanOperation: 'matcher_boolean_operation',
|
||||
logAction: 'action_log',
|
||||
conditional: 'io_controls_if',
|
||||
compare: 'io_logic_compare',
|
||||
|
|
@ -30,13 +29,15 @@ const BYTECODE_BLOCK_TYPE_MAP = {
|
|||
negate: 'io_logic_negate',
|
||||
setVariable: 'io_variables_set',
|
||||
getVariable: 'io_variables_get',
|
||||
feed: 'feed_selector',
|
||||
getFeedValue: 'feed_get_value',
|
||||
setFeedValue: 'feed_set_value',
|
||||
publishAction: 'action_publish',
|
||||
webhookAction: 'action_webhook',
|
||||
emailAction: 'action_email',
|
||||
smsAction: 'action_sms',
|
||||
// removed blocks that have migration regenerators
|
||||
// see: app/regenerators/migrations.js
|
||||
feed: 'feed_selector',
|
||||
publishAction: 'action_publish',
|
||||
}
|
||||
|
||||
const lookupRegenerator = expressionName => {
|
||||
|
|
|
|||
|
|
@ -25,24 +25,30 @@ export default class SidebarExporter {
|
|||
collapsed: true,
|
||||
items: []
|
||||
}))
|
||||
},
|
||||
uncategorizedCategory = {
|
||||
text: "Uncategorized",
|
||||
collapsed: true,
|
||||
items: []
|
||||
}
|
||||
|
||||
blockSidebar.items.push(uncategorizedCategory)
|
||||
|
||||
forEach(this.definitionSet.blocks, blockDefinition => {
|
||||
// skip disabled blocks
|
||||
if(blockDefinition.disabled) { return }
|
||||
|
||||
const docPath = blockDefinition.definitionPath.replace(/.js$/, '.md')
|
||||
|
||||
const
|
||||
blockSidebarPath = `/blocks/${docPath.slice(0, -3)}`,
|
||||
sidebarEntry = {
|
||||
text: blockDefinition.name,
|
||||
link: blockSidebarPath
|
||||
}
|
||||
link: blockDefinition.documentationPath()
|
||||
},
|
||||
blockCategories = blockDefinition.getCategories()
|
||||
|
||||
// put into Uncategorized if no category
|
||||
if(!blockCategories.length) {
|
||||
uncategorizedCategory.items.push(sidebarEntry)
|
||||
}
|
||||
|
||||
// add links to each sidebar category we're a part of
|
||||
forEach(blockDefinition.getCategories(), category => {
|
||||
forEach(blockCategories, category => {
|
||||
// if category contains this block, add to its sidebar
|
||||
const sidebarCategory = find(blockSidebar.items, { text: category.name })
|
||||
|
||||
|
|
|
|||
51
src/exporters/workspace_all_blocks_exporter.js
Normal file
51
src/exporters/workspace_all_blocks_exporter.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { writeFileSync } from 'fs'
|
||||
import { isString, invokeMap } from 'lodash-es'
|
||||
|
||||
|
||||
export default class WorkspaceExporter {
|
||||
definitionSet = null
|
||||
destination = null
|
||||
|
||||
constructor(definitionSet, destination) {
|
||||
this.definitionSet = definitionSet
|
||||
this.destination = destination
|
||||
}
|
||||
|
||||
export(givenOptions = {}) {
|
||||
const
|
||||
options = {
|
||||
toFile: false,
|
||||
...givenOptions
|
||||
},
|
||||
allBlocks = invokeMap(this.definitionSet.blocks, "toBlocklyInstanceJSON"),
|
||||
workspaceObject = {
|
||||
blocks: {
|
||||
languageVersion: 0,
|
||||
blocks: allBlocks.map((block, index) => ({
|
||||
...block,
|
||||
id: `block-type-${block.type}`,
|
||||
x: 50,
|
||||
y: 50*index
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if(!options.toFile) {
|
||||
return workspaceObject
|
||||
}
|
||||
|
||||
const filename = isString(options.toFile)
|
||||
? options.toFile
|
||||
: `workspace_all_blocks.json`
|
||||
|
||||
writeFileSync(`${this.destination}/${filename}`, JSON.stringify(workspaceObject, null, 2))
|
||||
}
|
||||
|
||||
exportToFile = (toFile=true) => {
|
||||
this.export({ toFile })
|
||||
}
|
||||
|
||||
static export() {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import Blockly from 'blockly'
|
||||
|
||||
import BLOCKLY_CSS from "#src/blockly_css.js"
|
||||
|
||||
|
||||
// right-click menu items
|
||||
export const imageExportRegistryItems = [
|
||||
|
|
@ -8,18 +10,20 @@ export const imageExportRegistryItems = [
|
|||
displayText: 'Save Block as SVG...',
|
||||
weight: 100,
|
||||
scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
|
||||
preconditionFn: scope => "enabled",
|
||||
callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
|
||||
downloadBlockAsSVG(scope.block.id)
|
||||
preconditionFn: () => "enabled",
|
||||
callback: scope => {
|
||||
const { id, type } = scope.block
|
||||
downloadBlockAsSVG(id, type)
|
||||
}
|
||||
}, {
|
||||
id: "block-png",
|
||||
displayText: 'Save Block as PNG...',
|
||||
weight: 100,
|
||||
scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
|
||||
preconditionFn: scope => "enabled",
|
||||
callback: (scope, menuOpenEvent, menuSelectEvent, location) => {
|
||||
downloadBlockAsPNG(scope.block.id)
|
||||
preconditionFn: () => "enabled",
|
||||
callback: scope => {
|
||||
const { id, type } = scope.block
|
||||
downloadBlockAsPNG(id, type)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -75,13 +79,13 @@ const
|
|||
URL.revokeObjectURL(element.href)
|
||||
},
|
||||
|
||||
downloadBlockAsSVG = blockId => {
|
||||
downloadBlockAsSVG = (blockId, blockType) => {
|
||||
const svgObjectURL = blockToSVGBlob(blockId)
|
||||
|
||||
download(svgObjectURL, 'block.svg')
|
||||
download(svgObjectURL, `${blockType}.svg`)
|
||||
},
|
||||
|
||||
downloadBlockAsPNG = blockId => {
|
||||
downloadBlockAsPNG = (blockId, blockType) => {
|
||||
const
|
||||
svgObjectURL = blockToSVGBlob(blockId),
|
||||
img = new Image()
|
||||
|
|
@ -95,504 +99,8 @@ const
|
|||
|
||||
// extract a png object url from the canvas and download it
|
||||
const pngObjectURL = canvas.toDataURL("image/png")
|
||||
download(pngObjectURL, 'block.png')
|
||||
download(pngObjectURL, `${blockType}.png`)
|
||||
}
|
||||
|
||||
img.src = svgObjectURL
|
||||
}
|
||||
|
||||
// hardcode the CSS we actually use for now (includes geras renderer CSS)
|
||||
const BLOCKLY_CSS = `
|
||||
.blocklySvg {
|
||||
background-color: #fff;
|
||||
outline: none;
|
||||
overflow: hidden; /* IE overflows by default. */
|
||||
position: absolute;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 99999; /* big value for bootstrap3 compatibility */
|
||||
}
|
||||
|
||||
.injectionDiv {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden; /* So blocks in drag surface disappear at edges */
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.blocklyNonSelectable {
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.blocklyBlockCanvas.blocklyCanvasTransitioning,
|
||||
.blocklyBubbleCanvas.blocklyCanvasTransitioning {
|
||||
transition: transform .5s;
|
||||
}
|
||||
|
||||
.blocklyTooltipDiv {
|
||||
background-color: #ffffc7;
|
||||
border: 1px solid #ddc;
|
||||
box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);
|
||||
color: #000;
|
||||
display: none;
|
||||
font: 9pt sans-serif;
|
||||
opacity: .9;
|
||||
padding: 2px;
|
||||
position: absolute;
|
||||
z-index: 100000; /* big value for bootstrap3 compatibility */
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
border: 1px solid;
|
||||
border-color: #dadce0;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 4px;
|
||||
box-shadow: 0 0 3px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv.blocklyFocused {
|
||||
box-shadow: 0 0 6px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownContent {
|
||||
max-height: 300px; /* @todo: spec for maximum height. */
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blocklyDropDownArrow {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
z-index: -1;
|
||||
background-color: inherit;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyDropDownButton {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
padding: 0;
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
border: 1px solid;
|
||||
transition: box-shadow .1s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blocklyArrowTop {
|
||||
border-top: 1px solid;
|
||||
border-left: 1px solid;
|
||||
border-top-left-radius: 4px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyArrowBottom {
|
||||
border-bottom: 1px solid;
|
||||
border-right: 1px solid;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
.blocklyResizeSE {
|
||||
cursor: se-resize;
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyResizeSW {
|
||||
cursor: sw-resize;
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyResizeLine {
|
||||
stroke: #515A5A;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklyHighlightedConnectionPath {
|
||||
fill: none;
|
||||
stroke: #fc3;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.blocklyPathLight {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklySelected>.blocklyPathLight {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyDraggable {
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.blocklyDragging {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
/* Changes cursor on mouse down. Not effective in Firefox because of
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */
|
||||
.blocklyDraggable:active {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.blocklyDragging.blocklyDraggingDelete {
|
||||
cursor: url("./handdelete.cur"), auto;
|
||||
}
|
||||
|
||||
.blocklyDragging>.blocklyPath,
|
||||
.blocklyDragging>.blocklyPathLight {
|
||||
fill-opacity: .8;
|
||||
stroke-opacity: .8;
|
||||
}
|
||||
|
||||
.blocklyDragging>.blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyDisabled>.blocklyPath {
|
||||
fill-opacity: .5;
|
||||
stroke-opacity: .5;
|
||||
}
|
||||
|
||||
.blocklyDisabled>.blocklyPathLight,
|
||||
.blocklyDisabled>.blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyInsertionMarker>.blocklyPath,
|
||||
.blocklyInsertionMarker>.blocklyPathLight,
|
||||
.blocklyInsertionMarker>.blocklyPathDark {
|
||||
fill-opacity: .2;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.blocklyMultilineText {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.blocklyNonEditableText>text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyFlyout {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.blocklyText text {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
*/
|
||||
.blocklySvg text {
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.blocklyHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blocklyFieldDropdown:not(.blocklyHidden) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.blocklyIconGroup {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.blocklyIconGroup:not(:hover),
|
||||
.blocklyIconGroupReadonly {
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.blocklyIconShape {
|
||||
fill: #00f;
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.blocklyIconSymbol {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.blocklyMinimalBody {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.blocklyHtmlInput {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Remove the increase and decrease arrows on the field number editor */
|
||||
input.blocklyHtmlInput[type=number]::-webkit-inner-spin-button,
|
||||
input.blocklyHtmlInput[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.blocklyMainBackground {
|
||||
stroke-width: 1;
|
||||
stroke: #c6c6c6; /* Equates to #ddd due to border being off-pixel. */
|
||||
}
|
||||
|
||||
.blocklyMutatorBackground {
|
||||
fill: #fff;
|
||||
stroke: #ddd;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.blocklyFlyoutBackground {
|
||||
fill: #ddd;
|
||||
fill-opacity: .8;
|
||||
}
|
||||
|
||||
.blocklyMainWorkspaceScrollbar {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.blocklyFlyoutScrollbar {
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.blocklyScrollbarHorizontal,
|
||||
.blocklyScrollbarVertical {
|
||||
position: absolute;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.blocklyScrollbarBackground {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.blocklyScrollbarHandle {
|
||||
fill: #ccc;
|
||||
}
|
||||
|
||||
.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,
|
||||
.blocklyScrollbarHandle:hover {
|
||||
fill: #bbb;
|
||||
}
|
||||
|
||||
/* Darken flyout scrollbars due to being on a grey background. */
|
||||
/* By contrast, workspace scrollbars are on a white background. */
|
||||
.blocklyFlyout .blocklyScrollbarHandle {
|
||||
fill: #bbb;
|
||||
}
|
||||
|
||||
.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,
|
||||
.blocklyFlyout .blocklyScrollbarHandle:hover {
|
||||
fill: #aaa;
|
||||
}
|
||||
|
||||
.blocklyInvalidInput {
|
||||
background: #faa;
|
||||
}
|
||||
|
||||
.blocklyVerticalMarker {
|
||||
stroke-width: 3px;
|
||||
fill: rgba(255,255,255,.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyComputeCanvas {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.blocklyNoPointerEvents {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blocklyContextMenu {
|
||||
border-radius: 4px;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.blocklyDropdownMenu {
|
||||
border-radius: 2px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.blocklyDropdownMenu .blocklyMenuItem {
|
||||
/* 28px on the left for icon or checkbox. */
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
/* BiDi override for the resting state. */
|
||||
.blocklyDropdownMenu .blocklyMenuItemRtl {
|
||||
/* Flip left/right padding for BiDi. */
|
||||
padding-left: 5px;
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv .blocklyMenu {
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 3px 1px rgba(0,0,0,.3);
|
||||
font: normal 13px Arial, sans-serif;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 100%;
|
||||
z-index: 20000; /* Arbitrary, but some apps depend on it... */
|
||||
}
|
||||
|
||||
.blocklyWidgetDiv .blocklyMenu.blocklyFocused {
|
||||
box-shadow: 0 0 6px 1px rgba(0,0,0,.3);
|
||||
}
|
||||
|
||||
.blocklyDropDownDiv .blocklyMenu {
|
||||
background: inherit; /* Compatibility with gapi, reset from goog-menu */
|
||||
border: inherit; /* Compatibility with gapi, reset from goog-menu */
|
||||
font: normal 13px "Helvetica Neue", Helvetica, sans-serif;
|
||||
outline: none;
|
||||
position: relative; /* Compatibility with gapi, reset from goog-menu */
|
||||
z-index: 20000; /* Arbitrary, but some apps depend on it... */
|
||||
}
|
||||
|
||||
/* State: resting. */
|
||||
.blocklyMenuItem {
|
||||
border: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
/* 7em on the right for shortcut. */
|
||||
min-width: 7em;
|
||||
padding: 6px 15px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* State: disabled. */
|
||||
.blocklyMenuItemDisabled {
|
||||
color: #ccc;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
/* State: hover. */
|
||||
.blocklyMenuItemHighlight {
|
||||
background-color: rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
/* State: selected/checked. */
|
||||
.blocklyMenuItemCheckbox {
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.blocklyMenuItemSelected .blocklyMenuItemCheckbox {
|
||||
background: url(./sprites.png) no-repeat -48px -16px;
|
||||
float: left;
|
||||
margin-left: -24px;
|
||||
position: static; /* Scroll with the menu. */
|
||||
}
|
||||
|
||||
.blocklyMenuItemRtl .blocklyMenuItemCheckbox {
|
||||
float: right;
|
||||
margin-right: -24px;
|
||||
}
|
||||
|
||||
/* Begin .geras-renderer.classic-theme (stripped) */
|
||||
.blocklyText,
|
||||
.blocklyFlyoutLabelText {
|
||||
font: normal 11pt sans-serif;
|
||||
}
|
||||
.blocklyText {
|
||||
fill: #fff;
|
||||
}
|
||||
.blocklyNonEditableText>rect,
|
||||
.blocklyEditableText>rect {
|
||||
fill: #fff;
|
||||
fill-opacity: .6;
|
||||
stroke: none;
|
||||
}
|
||||
.blocklyNonEditableText>text,
|
||||
.blocklyEditableText>text {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyFlyoutLabelText {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyText.blocklyBubbleText {
|
||||
fill: #000;
|
||||
}
|
||||
.blocklyEditableText:not(.editing):hover>rect {
|
||||
stroke: #fff;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.blocklyHtmlInput {
|
||||
font-family: sans-serif;
|
||||
font-weight: normal;
|
||||
}
|
||||
.blocklySelected>.blocklyPath {
|
||||
stroke: #fc3;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.blocklyHighlightedConnectionPath {
|
||||
stroke: #fc3;
|
||||
}
|
||||
.blocklyReplaceable .blocklyPath {
|
||||
fill-opacity: .5;
|
||||
}
|
||||
.blocklyReplaceable .blocklyPathLight,
|
||||
.blocklyReplaceable .blocklyPathDark {
|
||||
display: none;
|
||||
}
|
||||
.blocklyInsertionMarker>.blocklyPath {
|
||||
fill-opacity: 0.2;
|
||||
stroke: none;
|
||||
}
|
||||
.blocklyInsertionMarker>.blocklyPathLight,
|
||||
.blocklyInsertionMarker>.blocklyPathDark {
|
||||
fill-opacity: 0.2;
|
||||
stroke: none;
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const
|
|||
EXTENSION_LOCATION = `extensions/`,
|
||||
MIXIN_LOCATION = `mixins/`,
|
||||
MUTATOR_LOCATION = `mutators/`,
|
||||
REGENERATOR_LOCATION = `regenerators/`,
|
||||
TOOLBOX_LOCATION = `toolbox/`,
|
||||
WORKSPACE_LOCATION = `workspace/`,
|
||||
|
||||
|
|
@ -62,6 +63,23 @@ export const DefinitionLoader = {
|
|||
))
|
||||
},
|
||||
|
||||
loadRegenerators: async (appLocation=APP_LOCATION) => {
|
||||
const jsfiles = await glob(`./${appLocation}/${REGENERATOR_LOCATION}**/*.js`, { ignore: EXAMPLE_FILES })
|
||||
|
||||
// loads app/regenerators/*.js into object like:
|
||||
// { blockType: { json: Function }}
|
||||
let regenerators = {}
|
||||
await Promise.all(jsfiles.map( async filePath => {
|
||||
const regeneratorsFromFile = (await import(`${PROJECT_ROOT}/${filePath}`)).default
|
||||
regenerators = {
|
||||
...regenerators,
|
||||
...regeneratorsFromFile
|
||||
}
|
||||
}))
|
||||
|
||||
return regenerators
|
||||
},
|
||||
|
||||
loadBlocks: async (appLocation=APP_LOCATION) => {
|
||||
// get the file listing
|
||||
const
|
||||
|
|
@ -101,6 +119,7 @@ export const DefinitionLoader = {
|
|||
mutators: await DefinitionLoader.loadMutators(options.source),
|
||||
mixins: await DefinitionLoader.loadMixins(options.source),
|
||||
extensions: await DefinitionLoader.loadExtensions(options.source),
|
||||
regenerators: await DefinitionLoader.loadRegenerators(options.source),
|
||||
blocks: await DefinitionLoader.loadBlocks(options.source),
|
||||
toolboxes: await DefinitionLoader.loadToolboxes(options.source),
|
||||
workspaces: await DefinitionLoader.loadWorkspaces(options.source),
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ exports[`Block Snapshots > Blockly JSON > action_email 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "SUBJECT",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -26,10 +27,11 @@ exports[`Block Snapshots > Blockly JSON > action_email 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "BODY",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/action/email"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/email"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -46,45 +48,11 @@ exports[`Block Snapshots > Blockly JSON > action_log 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "EXPRESSION",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/action/log"
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Block Snapshots > Blockly JSON > action_publish 1`] = `
|
||||
{
|
||||
"inputsInline": false,
|
||||
"type": "action_publish",
|
||||
"colour": "0",
|
||||
"tooltip": "Sends the given value to the specified Feed.",
|
||||
"nextStatement": "expression",
|
||||
"previousStatement": "expression",
|
||||
"message0": "📈 Publish %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "CENTRE"
|
||||
}
|
||||
],
|
||||
"message1": "...value: %1",
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"message2": "...to: %1",
|
||||
"args2": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FEED",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/action/publish"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/utility/log"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -134,7 +102,7 @@ exports[`Block Snapshots > Blockly JSON > action_root 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "action_root",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/root"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/root"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -158,10 +126,11 @@ exports[`Block Snapshots > Blockly JSON > action_sms 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "BODY",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/action/sms"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/sms"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -185,6 +154,7 @@ exports[`Block Snapshots > Blockly JSON > action_webhook 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "URL",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -205,10 +175,11 @@ exports[`Block Snapshots > Blockly JSON > action_webhook 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "BODY",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/action/webhook"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/webhook"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -227,7 +198,7 @@ exports[`Block Snapshots > Blockly JSON > all_days 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "all_days",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/day/all_days"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_days"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -246,7 +217,7 @@ exports[`Block Snapshots > Blockly JSON > all_hours 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "all_hours",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/hour/all_hours"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_hours"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -265,7 +236,7 @@ exports[`Block Snapshots > Blockly JSON > all_minutes 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "all_minutes",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/minute/all_minutes"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_minutes"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -284,7 +255,7 @@ exports[`Block Snapshots > Blockly JSON > all_months 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "all_months",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/month/all_months"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_months"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -303,7 +274,7 @@ exports[`Block Snapshots > Blockly JSON > day_settings 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/day/day_settings"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/day_settings"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -364,7 +335,7 @@ exports[`Block Snapshots > Blockly JSON > days_of_week 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "days_of_week",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/day/days_of_week"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/days_of_week"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -382,7 +353,7 @@ exports[`Block Snapshots > Blockly JSON > delay_days 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_days"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_days"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -498,7 +469,7 @@ exports[`Block Snapshots > Blockly JSON > delay_hours 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_hours"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_hours"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -758,7 +729,7 @@ exports[`Block Snapshots > Blockly JSON > delay_minutes 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_minutes"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_minutes"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -776,7 +747,7 @@ exports[`Block Snapshots > Blockly JSON > delay_none 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_none"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_none"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -1036,7 +1007,7 @@ exports[`Block Snapshots > Blockly JSON > delay_seconds 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_seconds"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_seconds"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -1090,7 +1061,7 @@ exports[`Block Snapshots > Blockly JSON > delay_settings 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/root/action_settings/delay_settings"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_settings"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -1403,7 +1374,7 @@ exports[`Block Snapshots > Blockly JSON > every_days_between 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "every_days_between",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/day/every_days_between"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_days_between"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -1660,7 +1631,7 @@ exports[`Block Snapshots > Blockly JSON > every_hours_between 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "every_hours_between",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/hour/every_hours_between"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_hours_between"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2221,7 +2192,7 @@ exports[`Block Snapshots > Blockly JSON > every_minutes_between 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "every_minutes_between",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/minute/every_minutes_between"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_minutes_between"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2374,7 +2345,7 @@ exports[`Block Snapshots > Blockly JSON > every_months_between 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "every_months_between",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/month/every_months_between"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_months_between"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2384,7 +2355,7 @@ exports[`Block Snapshots > Blockly JSON > feed_get_value 1`] = `
|
|||
"type": "feed_get_value",
|
||||
"colour": 300,
|
||||
"tooltip": "Resolves to the last value of this feed or component, always a String",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "Get %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -2406,39 +2377,7 @@ exports[`Block Snapshots > Blockly JSON > feed_get_value 1`] = `
|
|||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/feed/get_value"
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Block Snapshots > Blockly JSON > feed_selector 1`] = `
|
||||
{
|
||||
"inputsInline": false,
|
||||
"type": "feed_selector",
|
||||
"colour": 300,
|
||||
"tooltip": "The last value of this feed or component, always a String",
|
||||
"output": null,
|
||||
"message0": "Feed: %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"name": "FEED_KEY",
|
||||
"type": "field_dropdown",
|
||||
"options": [
|
||||
[
|
||||
"Loading Feeds...",
|
||||
""
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_dummy",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"extensions": [
|
||||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/feed/selector"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/feeds/get_value"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2465,6 +2404,7 @@ exports[`Block Snapshots > Blockly JSON > feed_set_value 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2472,7 +2412,7 @@ exports[`Block Snapshots > Blockly JSON > feed_set_value 1`] = `
|
|||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/feed/set_value"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/feeds/set_value"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2491,7 +2431,7 @@ exports[`Block Snapshots > Blockly JSON > hour_settings 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/hour/hour_settings"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/hour_settings"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2508,6 +2448,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "IF0",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2516,6 +2457,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = `
|
|||
{
|
||||
"type": "input_statement",
|
||||
"name": "THEN0",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2536,7 +2478,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "io_controls_if",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/controls/if"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/if"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2546,7 +2488,10 @@ exports[`Block Snapshots > Blockly JSON > io_logic_boolean 1`] = `
|
|||
"type": "io_logic_boolean",
|
||||
"colour": 60,
|
||||
"tooltip": "A true or false value.",
|
||||
"output": "boolean",
|
||||
"output": [
|
||||
"expression",
|
||||
"boolean"
|
||||
],
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -2568,7 +2513,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_boolean 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/logic/boolean"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/boolean"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2578,12 +2523,13 @@ exports[`Block Snapshots > Blockly JSON > io_logic_compare 1`] = `
|
|||
"type": "io_logic_compare",
|
||||
"colour": 120,
|
||||
"tooltip": "Numerically compare two given values using the selected math operation.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "A",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2622,10 +2568,11 @@ exports[`Block Snapshots > Blockly JSON > io_logic_compare 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/compare"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/compare"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2635,16 +2582,17 @@ exports[`Block Snapshots > Blockly JSON > io_logic_negate 1`] = `
|
|||
"type": "io_logic_negate",
|
||||
"colour": 60,
|
||||
"tooltip": "Swaps a truthy value to \`false\`, or a falsy value to \`true\`.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "not %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "EXPRESSION",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/logic/negate"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/negate"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2654,12 +2602,13 @@ exports[`Block Snapshots > Blockly JSON > io_logic_operation 1`] = `
|
|||
"type": "io_logic_operation",
|
||||
"colour": 60,
|
||||
"tooltip": "Perform the specifed boolean logic operation on two operands.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "A",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2682,10 +2631,11 @@ exports[`Block Snapshots > Blockly JSON > io_logic_operation 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/logic/operation"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/operation"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2695,12 +2645,13 @@ exports[`Block Snapshots > Blockly JSON > io_math_arithmetic 1`] = `
|
|||
"type": "io_math_arithmetic",
|
||||
"colour": 120,
|
||||
"tooltip": "Perform the specified arithmetic operation on two specified operands.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "A",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2735,10 +2686,11 @@ exports[`Block Snapshots > Blockly JSON > io_math_arithmetic 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/arithmetic"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/arithmetic"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2754,6 +2706,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_constrain 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2766,7 +2719,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_constrain 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/constrain"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/constrain"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2776,7 +2729,10 @@ exports[`Block Snapshots > Blockly JSON > io_math_number 1`] = `
|
|||
"type": "io_math_number",
|
||||
"colour": 120,
|
||||
"tooltip": "A numeric value, whole or decimal.",
|
||||
"output": "number",
|
||||
"output": [
|
||||
"expression",
|
||||
"number"
|
||||
],
|
||||
"message0": " %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -2792,7 +2748,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_number 1`] = `
|
|||
"extensions": [
|
||||
"validateNumbers"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/number"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/number"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2826,10 +2782,11 @@ exports[`Block Snapshots > Blockly JSON > io_math_round 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/round"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/round"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2839,7 +2796,10 @@ exports[`Block Snapshots > Blockly JSON > io_text 1`] = `
|
|||
"type": "io_text",
|
||||
"colour": 180,
|
||||
"tooltip": "A String of text",
|
||||
"output": "String",
|
||||
"output": [
|
||||
"expression",
|
||||
"string"
|
||||
],
|
||||
"message0": "\\"%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -2852,7 +2812,7 @@ exports[`Block Snapshots > Blockly JSON > io_text 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/text/text"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/text"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2862,12 +2822,13 @@ exports[`Block Snapshots > Blockly JSON > io_text_join 1`] = `
|
|||
"type": "io_text_join",
|
||||
"colour": 180,
|
||||
"tooltip": "Join two pieces of text into one.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "A",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -2876,10 +2837,11 @@ exports[`Block Snapshots > Blockly JSON > io_text_join 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/text/join"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/join"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2889,8 +2851,11 @@ exports[`Block Snapshots > Blockly JSON > io_text_multiline 1`] = `
|
|||
"type": "io_text_multiline",
|
||||
"colour": 180,
|
||||
"tooltip": "A String of longer-form text with newlines.",
|
||||
"output": "String",
|
||||
"message0": "P %1 %2",
|
||||
"output": [
|
||||
"expression",
|
||||
"string"
|
||||
],
|
||||
"message0": "¶ %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"name": "TEXT",
|
||||
|
|
@ -2902,7 +2867,7 @@ exports[`Block Snapshots > Blockly JSON > io_text_multiline 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/text/text_multiline"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/text_multiline"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2912,7 +2877,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_get 1`] = `
|
|||
"type": "io_variables_get",
|
||||
"colour": 240,
|
||||
"tooltip": "Get the value previously assigned to a variable.",
|
||||
"output": "String",
|
||||
"output": "expression",
|
||||
"message0": "Get variable %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -2924,7 +2889,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_get 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/variables/get"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/variables/get"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -2945,43 +2910,11 @@ exports[`Block Snapshots > Blockly JSON > io_variables_set 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/variables/set"
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Block Snapshots > Blockly JSON > matcher_boolean_operation 1`] = `
|
||||
{
|
||||
"inputsInline": true,
|
||||
"type": "matcher_boolean_operation",
|
||||
"colour": 60,
|
||||
"tooltip": "Perform a logic operation between the triggering Feed value and a block diagram.",
|
||||
"output": "matcher",
|
||||
"message0": "is true %1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"name": "OP",
|
||||
"type": "field_dropdown",
|
||||
"options": [
|
||||
[
|
||||
"and",
|
||||
"AND"
|
||||
],
|
||||
[
|
||||
"or",
|
||||
"OR"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/matchers/matcher_boolean_operation"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/variables/set"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3027,10 +2960,11 @@ exports[`Block Snapshots > Blockly JSON > matcher_compare 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/matchers/matcher_compare"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/matcher_compare"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3064,10 +2998,11 @@ exports[`Block Snapshots > Blockly JSON > matcher_text_compare 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/matchers/matcher_text_compare"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/matcher_text_compare"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3090,6 +3025,7 @@ exports[`Block Snapshots > Blockly JSON > math_map 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -3111,7 +3047,7 @@ exports[`Block Snapshots > Blockly JSON > math_map 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/map"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/map"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3127,6 +3063,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "FROM",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -3135,6 +3072,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "TO",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -3145,7 +3083,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/math/range"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/range"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3164,7 +3102,7 @@ exports[`Block Snapshots > Blockly JSON > minute_settings 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/minute/minute_settings"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/minute_settings"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3183,7 +3121,7 @@ exports[`Block Snapshots > Blockly JSON > month_settings 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/month/month_settings"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/month_settings"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3238,7 +3176,7 @@ exports[`Block Snapshots > Blockly JSON > on_schedule 1`] = `
|
|||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/on_schedule"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/on_schedule"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3387,7 +3325,7 @@ exports[`Block Snapshots > Blockly JSON > one_day 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "one_day",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/day/one_day"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_day"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3508,7 +3446,7 @@ exports[`Block Snapshots > Blockly JSON > one_hour 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "one_hour",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/hour/one_hour"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_hour"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3773,7 +3711,7 @@ exports[`Block Snapshots > Blockly JSON > one_minute 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "one_minute",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/minute/one_minute"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_minute"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3846,7 +3784,7 @@ exports[`Block Snapshots > Blockly JSON > one_month 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "one_month",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/month/one_month"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_month"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3939,7 +3877,7 @@ exports[`Block Snapshots > Blockly JSON > some_months 1`] = `
|
|||
}
|
||||
],
|
||||
"mutator": "some_months",
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/schedule/month/some_months"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/some_months"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3949,12 +3887,13 @@ exports[`Block Snapshots > Blockly JSON > text_compare 1`] = `
|
|||
"type": "text_compare",
|
||||
"colour": 180,
|
||||
"tooltip": "Compare two chunks of text for equality, inequality, or inclusion.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "A",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
|
|
@ -3981,10 +3920,11 @@ exports[`Block Snapshots > Blockly JSON > text_compare 1`] = `
|
|||
{
|
||||
"type": "input_value",
|
||||
"name": "B",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/text/compare"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/compare"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -3994,16 +3934,20 @@ exports[`Block Snapshots > Blockly JSON > text_template 1`] = `
|
|||
"type": "text_template",
|
||||
"colour": 180,
|
||||
"tooltip": "Render a text template.",
|
||||
"output": null,
|
||||
"output": [
|
||||
"expression",
|
||||
"string"
|
||||
],
|
||||
"message0": "{{ %1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TEMPLATE",
|
||||
"check": "expression",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/text/template"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/template"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -4013,7 +3957,7 @@ exports[`Block Snapshots > Blockly JSON > weather 1`] = `
|
|||
"type": "weather",
|
||||
"colour": 360,
|
||||
"tooltip": "Fetch the current or forecast weather conditions at the specified location.",
|
||||
"output": null,
|
||||
"output": "expression",
|
||||
"message0": "Weather %1",
|
||||
"args0": [
|
||||
{
|
||||
|
|
@ -4156,7 +4100,7 @@ exports[`Block Snapshots > Blockly JSON > weather 1`] = `
|
|||
"weatherMixin",
|
||||
"prepareWeather"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/power_ups/weather"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/weather/weather"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -4189,7 +4133,7 @@ exports[`Block Snapshots > Blockly JSON > when_data 1`] = `
|
|||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/when_data"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -4224,7 +4168,7 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching 1`] = `
|
|||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/when_data_matching"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data_matching"
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -4281,6 +4225,6 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching_state 1`] = `
|
|||
"replaceDropdownOptions",
|
||||
"populateFeedDropdown"
|
||||
],
|
||||
"helpUrl": "https://adafruit.github.io/io-actions/blocks/triggers/when_data_matching_state"
|
||||
"helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data_matching_state"
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue