From da3a86f55d5136904cd1d1b90904258cc4395505 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Thu, 24 Jul 2025 18:48:34 -0400 Subject: [PATCH 01/74] support doc overrides for inputs & fields --- src/definitions/block_definition.js | 2 ++ src/docs/render_block_fields.js | 22 +++++++++++++++++++++- src/docs/render_block_inputs.js | 28 +++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/definitions/block_definition.js b/src/definitions/block_definition.js index 070deb6..5ddb662 100644 --- a/src/definitions/block_definition.js +++ b/src/definitions/block_definition.js @@ -18,6 +18,7 @@ class BlockDefinition { name = null description = '' + docOverrides = {} ioPlus = false colour = null @@ -178,6 +179,7 @@ BlockDefinition.parseRawDefinition = function(rawBlockDefinition, definitionPath blockDef.type = rawBlockDefinition.type blockDef.name = rawBlockDefinition.name blockDef.primaryCategory = rawBlockDefinition.primaryCategory + blockDef.docOverrides = rawBlockDefinition.docOverrides blockDef.description = rawBlockDefinition.description ? niceTemplate(rawBlockDefinition.description) : "" diff --git a/src/docs/render_block_fields.js b/src/docs/render_block_fields.js index 534f41c..aa92472 100644 --- a/src/docs/render_block_fields.js +++ b/src/docs/render_block_fields.js @@ -1,10 +1,14 @@ -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 @@ -15,6 +19,22 @@ const 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 = [] diff --git a/src/docs/render_block_inputs.js b/src/docs/render_block_inputs.js index cffe3bc..1127201 100644 --- a/src/docs/render_block_inputs.js +++ b/src/docs/render_block_inputs.js @@ -1,12 +1,38 @@ -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(definition.docOverrides?.inputs) { + return renderOverridenInputs(definition) + } + if(!keys(definition.inputs).length) { return "This block has no inputs" } + 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 } From 19dd399a1fb1b4459a708646380fdc79d7381914 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Thu, 24 Jul 2025 18:48:47 -0400 Subject: [PATCH 02/74] override conditional inputs and weather fields --- app/blocks/controls/if.js | 52 +++++++++++++++++++++++++-------- app/blocks/power_ups/weather.js | 39 +++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/app/blocks/controls/if.js b/app/blocks/controls/if.js index 2a1a370..e76286b 100644 --- a/app/blocks/controls/if.js +++ b/app/blocks/controls/if.js @@ -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,21 +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: { @@ -55,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 = { diff --git a/app/blocks/power_ups/weather.js b/app/blocks/power_ups/weather.js index 43d7a7a..de6e3e7 100644 --- a/app/blocks/power_ups/weather.js +++ b/app/blocks/power_ups/weather.js @@ -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", @@ -66,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" ], @@ -98,7 +102,6 @@ export default { }, WEATHER_PROPERTY: { - description: "Select which metric of the forecast to use.", label: "" }, @@ -107,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 From d181533dc7279dd6c063dd52041523bc19fab167 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 11:31:20 -0400 Subject: [PATCH 03/74] change site root --- docs/.vitepress/config.js | 2 +- src/exporters/block_processor/help.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 9e6b6b6..8a75dcf 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -13,7 +13,7 @@ export default defineConfig({ ['link', { rel: 'icon', href: "data:image/svg+xml,🧩" }] ], - base: "/io-actions/", + base: "/actions-docs/", lastUpdated: true, diff --git a/src/exporters/block_processor/help.js b/src/exporters/block_processor/help.js index 9ea4435..cf71241 100644 --- a/src/exporters/block_processor/help.js +++ b/src/exporters/block_processor/help.js @@ -1,6 +1,6 @@ const // TODO: rely on project configuration for docs site location - DOCS_BLOCKS_ROOT = "https://adafruit.github.io/io-actions", + DOCS_BLOCKS_ROOT = "https://io.adafruitcom/actions-docs", processHelp = definition => { if (!definition.definitionPath) { return {} } From 6728875106f8468522188929d61b3cf270db1a7c Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 11:33:14 -0400 Subject: [PATCH 04/74] oops missing . --- src/exporters/block_processor/help.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exporters/block_processor/help.js b/src/exporters/block_processor/help.js index cf71241..fcf578a 100644 --- a/src/exporters/block_processor/help.js +++ b/src/exporters/block_processor/help.js @@ -1,6 +1,6 @@ const // TODO: rely on project configuration for docs site location - DOCS_BLOCKS_ROOT = "https://io.adafruitcom/actions-docs", + DOCS_BLOCKS_ROOT = "https://io.adafruit.com/actions-docs", processHelp = definition => { if (!definition.definitionPath) { return {} } From 03822ca3d0d86e2131d7e09bdd18fa3c1f9fcd4c Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 13:57:45 -0400 Subject: [PATCH 05/74] bump the snapshots --- .../block_snapshots_test.js.snapshot | 197 +++++++++++------- 1 file changed, 122 insertions(+), 75 deletions(-) diff --git a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot index 8ad2f89..3dc09a4 100644 --- a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot +++ b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot @@ -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.adafruitcom/actions-docs/blocks/notifications/email" } `; @@ -46,10 +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" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/utility/log" } `; @@ -73,6 +76,7 @@ exports[`Block Snapshots > Blockly JSON > action_publish 1`] = ` { "type": "input_value", "name": "VALUE", + "check": "expression", "align": "RIGHT" } ], @@ -81,10 +85,11 @@ exports[`Block Snapshots > Blockly JSON > action_publish 1`] = ` { "type": "input_value", "name": "FEED", + "check": "expression", "align": "RIGHT" } ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/action/publish" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/publish" } `; @@ -134,7 +139,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.adafruitcom/actions-docs/blocks/uncategorized/root" } `; @@ -158,10 +163,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.adafruitcom/actions-docs/blocks/notifications/sms" } `; @@ -185,6 +191,7 @@ exports[`Block Snapshots > Blockly JSON > action_webhook 1`] = ` { "type": "input_value", "name": "URL", + "check": "expression", "align": "RIGHT" } ], @@ -205,10 +212,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.adafruitcom/actions-docs/blocks/notifications/webhook" } `; @@ -227,7 +235,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.adafruitcom/actions-docs/blocks/uncategorized/all_days" } `; @@ -246,7 +254,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.adafruitcom/actions-docs/blocks/uncategorized/all_hours" } `; @@ -265,7 +273,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.adafruitcom/actions-docs/blocks/uncategorized/all_minutes" } `; @@ -284,7 +292,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.adafruitcom/actions-docs/blocks/uncategorized/all_months" } `; @@ -303,7 +311,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.adafruitcom/actions-docs/blocks/uncategorized/day_settings" } `; @@ -364,7 +372,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.adafruitcom/actions-docs/blocks/uncategorized/days_of_week" } `; @@ -382,7 +390,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.adafruitcom/actions-docs/blocks/uncategorized/delay_days" } `; @@ -498,7 +506,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.adafruitcom/actions-docs/blocks/uncategorized/delay_hours" } `; @@ -758,7 +766,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.adafruitcom/actions-docs/blocks/uncategorized/delay_minutes" } `; @@ -776,7 +784,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.adafruitcom/actions-docs/blocks/uncategorized/delay_none" } `; @@ -1036,7 +1044,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.adafruitcom/actions-docs/blocks/uncategorized/delay_seconds" } `; @@ -1090,7 +1098,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.adafruitcom/actions-docs/blocks/uncategorized/delay_settings" } `; @@ -1403,7 +1411,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.adafruitcom/actions-docs/blocks/uncategorized/every_days_between" } `; @@ -1660,7 +1668,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.adafruitcom/actions-docs/blocks/uncategorized/every_hours_between" } `; @@ -2221,7 +2229,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.adafruitcom/actions-docs/blocks/uncategorized/every_minutes_between" } `; @@ -2374,7 +2382,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.adafruitcom/actions-docs/blocks/uncategorized/every_months_between" } `; @@ -2384,7 +2392,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,7 +2414,7 @@ exports[`Block Snapshots > Blockly JSON > feed_get_value 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/feed/get_value" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/feeds/get_value" } `; @@ -2416,7 +2424,7 @@ exports[`Block Snapshots > Blockly JSON > feed_selector 1`] = ` "type": "feed_selector", "colour": 300, "tooltip": "The last value of this feed or component, always a String", - "output": null, + "output": "expression", "message0": "Feed: %1 %2", "args0": [ { @@ -2438,7 +2446,7 @@ exports[`Block Snapshots > Blockly JSON > feed_selector 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/feed/selector" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/selector" } `; @@ -2465,6 +2473,7 @@ exports[`Block Snapshots > Blockly JSON > feed_set_value 1`] = ` { "type": "input_value", "name": "VALUE", + "check": "expression", "align": "RIGHT" } ], @@ -2472,7 +2481,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.adafruitcom/actions-docs/blocks/feeds/set_value" } `; @@ -2491,7 +2500,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.adafruitcom/actions-docs/blocks/uncategorized/hour_settings" } `; @@ -2508,6 +2517,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = ` { "type": "input_value", "name": "IF0", + "check": "expression", "align": "RIGHT" } ], @@ -2516,6 +2526,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = ` { "type": "input_statement", "name": "THEN0", + "check": "expression", "align": "RIGHT" } ], @@ -2536,7 +2547,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.adafruitcom/actions-docs/blocks/logic/if" } `; @@ -2546,7 +2557,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 +2582,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.adafruitcom/actions-docs/blocks/logic/boolean" } `; @@ -2578,12 +2592,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 +2637,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.adafruitcom/actions-docs/blocks/math/compare" } `; @@ -2635,16 +2651,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.adafruitcom/actions-docs/blocks/logic/negate" } `; @@ -2654,12 +2671,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 +2700,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.adafruitcom/actions-docs/blocks/logic/operation" } `; @@ -2695,12 +2714,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 +2755,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.adafruitcom/actions-docs/blocks/math/arithmetic" } `; @@ -2754,6 +2775,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_constrain 1`] = ` { "type": "input_value", "name": "VALUE", + "check": "expression", "align": "RIGHT" } ], @@ -2766,7 +2788,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.adafruitcom/actions-docs/blocks/math/constrain" } `; @@ -2776,7 +2798,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 +2817,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.adafruitcom/actions-docs/blocks/math/number" } `; @@ -2826,10 +2851,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.adafruitcom/actions-docs/blocks/math/round" } `; @@ -2839,7 +2865,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 +2881,7 @@ exports[`Block Snapshots > Blockly JSON > io_text 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/text/text" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/text/text" } `; @@ -2862,12 +2891,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 +2906,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.adafruitcom/actions-docs/blocks/text/join" } `; @@ -2889,8 +2920,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 +2936,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.adafruitcom/actions-docs/blocks/text/text_multiline" } `; @@ -2912,7 +2946,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 +2958,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.adafruitcom/actions-docs/blocks/variables/get" } `; @@ -2945,10 +2979,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" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/variables/set" } `; @@ -2978,10 +3013,11 @@ exports[`Block Snapshots > Blockly JSON > matcher_boolean_operation 1`] = ` { "type": "input_value", "name": "B", + "check": "expression", "align": "RIGHT" } ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/matchers/matcher_boolean_operation" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/matcher_boolean_operation" } `; @@ -3027,10 +3063,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.adafruitcom/actions-docs/blocks/triggers/matcher_compare" } `; @@ -3064,10 +3101,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.adafruitcom/actions-docs/blocks/triggers/matcher_text_compare" } `; @@ -3090,6 +3128,7 @@ exports[`Block Snapshots > Blockly JSON > math_map 1`] = ` { "type": "input_value", "name": "VALUE", + "check": "expression", "align": "RIGHT" } ], @@ -3111,7 +3150,7 @@ exports[`Block Snapshots > Blockly JSON > math_map 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/math/map" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/map" } `; @@ -3127,6 +3166,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = ` { "type": "input_value", "name": "FROM", + "check": "expression", "align": "RIGHT" } ], @@ -3135,6 +3175,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = ` { "type": "input_value", "name": "TO", + "check": "expression", "align": "RIGHT" } ], @@ -3145,7 +3186,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/math/range" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/range" } `; @@ -3164,7 +3205,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.adafruitcom/actions-docs/blocks/uncategorized/minute_settings" } `; @@ -3183,7 +3224,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.adafruitcom/actions-docs/blocks/uncategorized/month_settings" } `; @@ -3238,7 +3279,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.adafruitcom/actions-docs/blocks/triggers/on_schedule" } `; @@ -3387,7 +3428,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.adafruitcom/actions-docs/blocks/uncategorized/one_day" } `; @@ -3508,7 +3549,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.adafruitcom/actions-docs/blocks/uncategorized/one_hour" } `; @@ -3773,7 +3814,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.adafruitcom/actions-docs/blocks/uncategorized/one_minute" } `; @@ -3846,7 +3887,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.adafruitcom/actions-docs/blocks/uncategorized/one_month" } `; @@ -3939,7 +3980,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.adafruitcom/actions-docs/blocks/uncategorized/some_months" } `; @@ -3949,12 +3990,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 +4023,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.adafruitcom/actions-docs/blocks/logic/compare" } `; @@ -3994,16 +4037,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.adafruitcom/actions-docs/blocks/text/template" } `; @@ -4013,7 +4060,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 +4203,7 @@ exports[`Block Snapshots > Blockly JSON > weather 1`] = ` "weatherMixin", "prepareWeather" ], - "helpUrl": "https://adafruit.github.io/io-actions/blocks/power_ups/weather" + "helpUrl": "https://io.adafruitcom/actions-docs/blocks/weather/weather" } `; @@ -4189,7 +4236,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.adafruitcom/actions-docs/blocks/triggers/when_data" } `; @@ -4224,7 +4271,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.adafruitcom/actions-docs/blocks/triggers/when_data_matching" } `; @@ -4281,6 +4328,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.adafruitcom/actions-docs/blocks/triggers/when_data_matching_state" } `; From 49cf69b4f42ecf0918de88e48eeaa7efa673c344 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 14:05:30 -0400 Subject: [PATCH 06/74] remove empty and unimplemented sections --- src/docs/render_block.js | 44 ++++++++++++++++++++++++++------- src/docs/render_block_fields.js | 2 +- src/docs/render_block_inputs.js | 4 +-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/docs/render_block.js b/src/docs/render_block.js index d9d3cb1..dccbd98 100644 --- a/src/docs/render_block.js +++ b/src/docs/render_block.js @@ -1,4 +1,4 @@ -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' @@ -21,12 +21,42 @@ This Block requires an IO+ subscription to use. [Learn more about IO+](https://i renderIOPlusAlert = ({ ioPlus }) => ioPlus ? IO_PLUS_ALERT : "", + renderFieldsSection = definition => { + const fieldsMarkdown = renderFields(definition) + + 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 => @@ -44,15 +74,11 @@ ${ renderDescription(definition) } ${ renderIOPlusAlert(definition) } -## Fields -${ renderFields(definition) } +${ renderFieldsSection(definition) } -## Inputs -${ renderInputs(definition) } +${ renderInputsSection(definition) } -## Output ${ renderOutput(definition) } -## Examples ${ renderExamples(definition) } ` diff --git a/src/docs/render_block_fields.js b/src/docs/render_block_fields.js index aa92472..d112cdf 100644 --- a/src/docs/render_block_fields.js +++ b/src/docs/render_block_fields.js @@ -14,7 +14,7 @@ const return newField })) - if(!fields.length) { return "This block has no form fields." } + if(!fields.length) { return } return fields.map(renderField).join("\n\n") }, diff --git a/src/docs/render_block_inputs.js b/src/docs/render_block_inputs.js index 1127201..bd14240 100644 --- a/src/docs/render_block_inputs.js +++ b/src/docs/render_block_inputs.js @@ -9,9 +9,7 @@ const return renderOverridenInputs(definition) } - if(!keys(definition.inputs).length) { - return "This block has no inputs" - } + if(!keys(definition.inputs).length) { return } return renderEachInput(definition) }, From 65fd5959bc3c3e2e46eaabb4c4832d12684aa774 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 14:48:49 -0400 Subject: [PATCH 07/74] uncategorized blocks in sidebar --- src/exporters/block_index_exporter.js | 2 +- src/exporters/sidebar_exporter.js | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/exporters/block_index_exporter.js b/src/exporters/block_index_exporter.js index 0081892..b75ea86 100644 --- a/src/exporters/block_index_exporter.js +++ b/src/exporters/block_index_exporter.js @@ -1,5 +1,5 @@ import { writeFileSync } from 'fs' -import { forEach, isString, without } from 'lodash-es' +import { isString, without } from 'lodash-es' export default class BlockIndexExporter { diff --git a/src/exporters/sidebar_exporter.js b/src/exporters/sidebar_exporter.js index 7484cb6..851c1ee 100644 --- a/src/exporters/sidebar_exporter.js +++ b/src/exporters/sidebar_exporter.js @@ -25,17 +25,30 @@ export default class SidebarExporter { collapsed: true, items: [] })) + }, + uncategorizedCategory = { + text: "Uncategorized", + collapsed: true, + items: [] } + blockSidebar.items.push(uncategorizedCategory) forEach(this.definitionSet.blocks, blockDefinition => { - const sidebarEntry = { - text: blockDefinition.name, - link: blockDefinition.documentationPath() + const + sidebarEntry = { + text: blockDefinition.name, + 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 }) From 37600d82f1af2a91b14da90b854a8ee5f35db82f Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 15:32:20 -0400 Subject: [PATCH 08/74] standalone regenerators, block migrations --- app/blocks/action/publish.js | 64 ------------------- app/blocks/feed/selector.js | 53 --------------- app/regenerators/migrations.js | 38 +++++++++++ src/definitions/definition_set.js | 24 ++++++- .../script_templates/regenerators.template.js | 6 +- src/loaders/definition_loader.js | 19 ++++++ 6 files changed, 82 insertions(+), 122 deletions(-) delete mode 100644 app/blocks/action/publish.js delete mode 100644 app/blocks/feed/selector.js create mode 100644 app/regenerators/migrations.js diff --git a/app/blocks/action/publish.js b/app/blocks/action/publish.js deleted file mode 100644 index 4788c62..0000000 --- a/app/blocks/action/publish.js +++ /dev/null @@ -1,64 +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.", - check: "expression", - shadow: 'io_text' - }, - - FEED: { - description: "The Feed to write to.", - check: "expression", - 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' }), - } - } - } - } -} diff --git a/app/blocks/feed/selector.js b/app/blocks/feed/selector.js deleted file mode 100644 index 64f50e5..0000000 --- a/app/blocks/feed/selector.js +++ /dev/null @@ -1,53 +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'], - - connections: { - mode: "value", - output: "expression", - }, - - 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 - } - } - } - } -} diff --git a/app/regenerators/migrations.js b/app/regenerators/migrations.js new file mode 100644 index 0000000..a23659d --- /dev/null +++ b/app/regenerators/migrations.js @@ -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 + } + } + } + } +} diff --git a/src/definitions/definition_set.js b/src/definitions/definition_set.js index 87bcb89..9340443 100644 --- a/src/definitions/definition_set.js +++ b/src/definitions/definition_set.js @@ -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) diff --git a/src/exporters/script_templates/regenerators.template.js b/src/exporters/script_templates/regenerators.template.js index 605fc22..816d7e3 100644 --- a/src/exporters/script_templates/regenerators.template.js +++ b/src/exporters/script_templates/regenerators.template.js @@ -30,13 +30,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 => { diff --git a/src/loaders/definition_loader.js b/src/loaders/definition_loader.js index 9641da4..464520e 100644 --- a/src/loaders/definition_loader.js +++ b/src/loaders/definition_loader.js @@ -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), From 702749303ba617e8924796c432ebca6f407c71a5 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 15:46:26 -0400 Subject: [PATCH 09/74] remove matcher_boolean_operation (not used) --- .../matchers/matcher_boolean_operation.js | 60 ------------------- .../script_templates/regenerators.template.js | 1 - 2 files changed, 61 deletions(-) delete mode 100644 app/blocks/matchers/matcher_boolean_operation.js diff --git a/app/blocks/matchers/matcher_boolean_operation.js b/app/blocks/matchers/matcher_boolean_operation.js deleted file mode 100644 index 36688a5..0000000 --- a/app/blocks/matchers/matcher_boolean_operation.js +++ /dev/null @@ -1,60 +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: { - check: "expression", - 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 } - } - } -} diff --git a/src/exporters/script_templates/regenerators.template.js b/src/exporters/script_templates/regenerators.template.js index 816d7e3..7571acf 100644 --- a/src/exporters/script_templates/regenerators.template.js +++ b/src/exporters/script_templates/regenerators.template.js @@ -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', From 3272fbe05f7a0efdd5098851aff38b73f7f15966 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 25 Jul 2025 15:48:04 -0400 Subject: [PATCH 10/74] drop removed blocks, fix help urls --- .../block_snapshots_test.js.snapshot | 215 +++++------------- 1 file changed, 56 insertions(+), 159 deletions(-) diff --git a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot index 3dc09a4..ffaf3a0 100644 --- a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot +++ b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot @@ -31,7 +31,7 @@ exports[`Block Snapshots > Blockly JSON > action_email 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/notifications/email" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/email" } `; @@ -52,44 +52,7 @@ exports[`Block Snapshots > Blockly JSON > action_log 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/utility/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", - "check": "expression", - "align": "RIGHT" - } - ], - "message2": "...to: %1", - "args2": [ - { - "type": "input_value", - "name": "FEED", - "check": "expression", - "align": "RIGHT" - } - ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/publish" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/utility/log" } `; @@ -139,7 +102,7 @@ exports[`Block Snapshots > Blockly JSON > action_root 1`] = ` } ], "mutator": "action_root", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/root" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/root" } `; @@ -167,7 +130,7 @@ exports[`Block Snapshots > Blockly JSON > action_sms 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/notifications/sms" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/sms" } `; @@ -216,7 +179,7 @@ exports[`Block Snapshots > Blockly JSON > action_webhook 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/notifications/webhook" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/notifications/webhook" } `; @@ -235,7 +198,7 @@ exports[`Block Snapshots > Blockly JSON > all_days 1`] = ` } ], "mutator": "all_days", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/all_days" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_days" } `; @@ -254,7 +217,7 @@ exports[`Block Snapshots > Blockly JSON > all_hours 1`] = ` } ], "mutator": "all_hours", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/all_hours" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_hours" } `; @@ -273,7 +236,7 @@ exports[`Block Snapshots > Blockly JSON > all_minutes 1`] = ` } ], "mutator": "all_minutes", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/all_minutes" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_minutes" } `; @@ -292,7 +255,7 @@ exports[`Block Snapshots > Blockly JSON > all_months 1`] = ` } ], "mutator": "all_months", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/all_months" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/all_months" } `; @@ -311,7 +274,7 @@ exports[`Block Snapshots > Blockly JSON > day_settings 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/day_settings" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/day_settings" } `; @@ -372,7 +335,7 @@ exports[`Block Snapshots > Blockly JSON > days_of_week 1`] = ` } ], "mutator": "days_of_week", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/days_of_week" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/days_of_week" } `; @@ -390,7 +353,7 @@ exports[`Block Snapshots > Blockly JSON > delay_days 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_days" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_days" } `; @@ -506,7 +469,7 @@ exports[`Block Snapshots > Blockly JSON > delay_hours 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_hours" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_hours" } `; @@ -766,7 +729,7 @@ exports[`Block Snapshots > Blockly JSON > delay_minutes 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_minutes" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_minutes" } `; @@ -784,7 +747,7 @@ exports[`Block Snapshots > Blockly JSON > delay_none 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_none" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_none" } `; @@ -1044,7 +1007,7 @@ exports[`Block Snapshots > Blockly JSON > delay_seconds 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_seconds" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_seconds" } `; @@ -1098,7 +1061,7 @@ exports[`Block Snapshots > Blockly JSON > delay_settings 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/delay_settings" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/delay_settings" } `; @@ -1411,7 +1374,7 @@ exports[`Block Snapshots > Blockly JSON > every_days_between 1`] = ` } ], "mutator": "every_days_between", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/every_days_between" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_days_between" } `; @@ -1668,7 +1631,7 @@ exports[`Block Snapshots > Blockly JSON > every_hours_between 1`] = ` } ], "mutator": "every_hours_between", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/every_hours_between" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_hours_between" } `; @@ -2229,7 +2192,7 @@ exports[`Block Snapshots > Blockly JSON > every_minutes_between 1`] = ` } ], "mutator": "every_minutes_between", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/every_minutes_between" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_minutes_between" } `; @@ -2382,7 +2345,7 @@ exports[`Block Snapshots > Blockly JSON > every_months_between 1`] = ` } ], "mutator": "every_months_between", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/every_months_between" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/every_months_between" } `; @@ -2414,39 +2377,7 @@ exports[`Block Snapshots > Blockly JSON > feed_get_value 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/feeds/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": "expression", - "message0": "Feed: %1 %2", - "args0": [ - { - "name": "FEED_KEY", - "type": "field_dropdown", - "options": [ - [ - "Loading Feeds...", - "" - ] - ] - }, - { - "type": "input_dummy", - "align": "RIGHT" - } - ], - "extensions": [ - "replaceDropdownOptions", - "populateFeedDropdown" - ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/selector" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/feeds/get_value" } `; @@ -2481,7 +2412,7 @@ exports[`Block Snapshots > Blockly JSON > feed_set_value 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/feeds/set_value" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/feeds/set_value" } `; @@ -2500,7 +2431,7 @@ exports[`Block Snapshots > Blockly JSON > hour_settings 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/hour_settings" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/hour_settings" } `; @@ -2547,7 +2478,7 @@ exports[`Block Snapshots > Blockly JSON > io_controls_if 1`] = ` } ], "mutator": "io_controls_if", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/logic/if" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/if" } `; @@ -2582,7 +2513,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_boolean 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/logic/boolean" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/boolean" } `; @@ -2641,7 +2572,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_compare 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/compare" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/compare" } `; @@ -2661,7 +2592,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_negate 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/logic/negate" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/negate" } `; @@ -2704,7 +2635,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_operation 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/logic/operation" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/operation" } `; @@ -2759,7 +2690,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_arithmetic 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/arithmetic" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/arithmetic" } `; @@ -2788,7 +2719,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_constrain 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/constrain" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/constrain" } `; @@ -2817,7 +2748,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_number 1`] = ` "extensions": [ "validateNumbers" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/number" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/number" } `; @@ -2855,7 +2786,7 @@ exports[`Block Snapshots > Blockly JSON > io_math_round 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/round" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/round" } `; @@ -2881,7 +2812,7 @@ exports[`Block Snapshots > Blockly JSON > io_text 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/text/text" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/text" } `; @@ -2910,7 +2841,7 @@ exports[`Block Snapshots > Blockly JSON > io_text_join 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/text/join" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/join" } `; @@ -2936,7 +2867,7 @@ exports[`Block Snapshots > Blockly JSON > io_text_multiline 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/text/text_multiline" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/text_multiline" } `; @@ -2958,7 +2889,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_get 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/variables/get" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/variables/get" } `; @@ -2983,41 +2914,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_set 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/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", - "check": "expression", - "align": "RIGHT" - } - ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/matcher_boolean_operation" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/variables/set" } `; @@ -3067,7 +2964,7 @@ exports[`Block Snapshots > Blockly JSON > matcher_compare 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/matcher_compare" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/matcher_compare" } `; @@ -3105,7 +3002,7 @@ exports[`Block Snapshots > Blockly JSON > matcher_text_compare 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/matcher_text_compare" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/matcher_text_compare" } `; @@ -3150,7 +3047,7 @@ exports[`Block Snapshots > Blockly JSON > math_map 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/math/map" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/math/map" } `; @@ -3186,7 +3083,7 @@ exports[`Block Snapshots > Blockly JSON > math_range 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/range" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/range" } `; @@ -3205,7 +3102,7 @@ exports[`Block Snapshots > Blockly JSON > minute_settings 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/minute_settings" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/minute_settings" } `; @@ -3224,7 +3121,7 @@ exports[`Block Snapshots > Blockly JSON > month_settings 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/month_settings" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/month_settings" } `; @@ -3279,7 +3176,7 @@ exports[`Block Snapshots > Blockly JSON > on_schedule 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/on_schedule" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/on_schedule" } `; @@ -3428,7 +3325,7 @@ exports[`Block Snapshots > Blockly JSON > one_day 1`] = ` } ], "mutator": "one_day", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/one_day" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_day" } `; @@ -3549,7 +3446,7 @@ exports[`Block Snapshots > Blockly JSON > one_hour 1`] = ` } ], "mutator": "one_hour", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/one_hour" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_hour" } `; @@ -3814,7 +3711,7 @@ exports[`Block Snapshots > Blockly JSON > one_minute 1`] = ` } ], "mutator": "one_minute", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/one_minute" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_minute" } `; @@ -3887,7 +3784,7 @@ exports[`Block Snapshots > Blockly JSON > one_month 1`] = ` } ], "mutator": "one_month", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/one_month" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/one_month" } `; @@ -3980,7 +3877,7 @@ exports[`Block Snapshots > Blockly JSON > some_months 1`] = ` } ], "mutator": "some_months", - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/uncategorized/some_months" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/uncategorized/some_months" } `; @@ -4027,7 +3924,7 @@ exports[`Block Snapshots > Blockly JSON > text_compare 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/logic/compare" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/logic/compare" } `; @@ -4050,7 +3947,7 @@ exports[`Block Snapshots > Blockly JSON > text_template 1`] = ` "align": "RIGHT" } ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/text/template" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/text/template" } `; @@ -4203,7 +4100,7 @@ exports[`Block Snapshots > Blockly JSON > weather 1`] = ` "weatherMixin", "prepareWeather" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/weather/weather" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/weather/weather" } `; @@ -4236,7 +4133,7 @@ exports[`Block Snapshots > Blockly JSON > when_data 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/when_data" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data" } `; @@ -4271,7 +4168,7 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/when_data_matching" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data_matching" } `; @@ -4328,6 +4225,6 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching_state 1`] = ` "replaceDropdownOptions", "populateFeedDropdown" ], - "helpUrl": "https://io.adafruitcom/actions-docs/blocks/triggers/when_data_matching_state" + "helpUrl": "https://io.adafruit.com/actions-docs/blocks/triggers/when_data_matching_state" } `; From a3e5f70621b1c8e6be4d4f4fca1fe69bd92c3801 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 14:40:53 -0400 Subject: [PATCH 11/74] name block image files --- src/image_exporter.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/image_exporter.js b/src/image_exporter.js index 5f675b8..d37b8c5 100644 --- a/src/image_exporter.js +++ b/src/image_exporter.js @@ -10,7 +10,8 @@ export const imageExportRegistryItems = [ scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK, preconditionFn: scope => "enabled", callback: (scope, menuOpenEvent, menuSelectEvent, location) => { - downloadBlockAsSVG(scope.block.id) + const { id, type } = scope.block + downloadBlockAsSVG(id, type) } }, { id: "block-png", @@ -19,7 +20,8 @@ export const imageExportRegistryItems = [ scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK, preconditionFn: scope => "enabled", callback: (scope, menuOpenEvent, menuSelectEvent, location) => { - downloadBlockAsPNG(scope.block.id) + const { id, type } = scope.block + downloadBlockAsPNG(id, type) } } ] @@ -75,13 +77,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,7 +97,7 @@ 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 From 20026c433d41b1d947f56adeea40c5dc30a9c5d2 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 14:41:14 -0400 Subject: [PATCH 12/74] ignore more generated files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index af46c79..a3821c0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ .ds_store # Blockly exports +tmp export +docs/block_images # Logs logs From ae0baf6908d1d5977822fb704d71e33d691f488f Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 14:41:32 -0400 Subject: [PATCH 13/74] fix root block short description --- app/blocks/root/root.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/blocks/root/root.js b/app/blocks/root/root.js index 9dfae43..1a00684 100644 --- a/app/blocks/root/root.js +++ b/app/blocks/root/root.js @@ -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: {}, From e3dcab44e5b6fc1bd2d6236442a3fc8d1ba75087 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 14:56:10 -0400 Subject: [PATCH 14/74] separate CSS from image export code --- src/blockly_css.js | 495 +++++++++++++++++++++++++++++++++++++++++ src/image_exporter.js | 506 +----------------------------------------- 2 files changed, 501 insertions(+), 500 deletions(-) create mode 100644 src/blockly_css.js diff --git a/src/blockly_css.js b/src/blockly_css.js new file mode 100644 index 0000000..f1a666b --- /dev/null +++ b/src/blockly_css.js @@ -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; +} +` diff --git a/src/image_exporter.js b/src/image_exporter.js index d37b8c5..42b040b 100644 --- a/src/image_exporter.js +++ b/src/image_exporter.js @@ -1,5 +1,7 @@ import Blockly from 'blockly' +import BLOCKLY_CSS from "#src/blockly_css.js" + // right-click menu items export const imageExportRegistryItems = [ @@ -8,8 +10,8 @@ export const imageExportRegistryItems = [ displayText: 'Save Block as SVG...', weight: 100, scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK, - preconditionFn: scope => "enabled", - callback: (scope, menuOpenEvent, menuSelectEvent, location) => { + preconditionFn: () => "enabled", + callback: scope => { const { id, type } = scope.block downloadBlockAsSVG(id, type) } @@ -18,8 +20,8 @@ export const imageExportRegistryItems = [ displayText: 'Save Block as PNG...', weight: 100, scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK, - preconditionFn: scope => "enabled", - callback: (scope, menuOpenEvent, menuSelectEvent, location) => { + preconditionFn: () => "enabled", + callback: scope => { const { id, type } = scope.block downloadBlockAsPNG(id, type) } @@ -102,499 +104,3 @@ const 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; -} -` From 6949620141a3fc6e962bf237c65114564c9e73d9 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 14:56:43 -0400 Subject: [PATCH 15/74] export workspace containing all blocks --- src/exporters/index.js | 2 + .../workspace_all_blocks_exporter.js | 50 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/exporters/workspace_all_blocks_exporter.js diff --git a/src/exporters/index.js b/src/exporters/index.js index 435d502..86eb440 100644 --- a/src/exporters/index.js +++ b/src/exporters/index.js @@ -2,6 +2,7 @@ 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" @@ -24,6 +25,7 @@ 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, diff --git a/src/exporters/workspace_all_blocks_exporter.js b/src/exporters/workspace_all_blocks_exporter.js new file mode 100644 index 0000000..819da4a --- /dev/null +++ b/src/exporters/workspace_all_blocks_exporter.js @@ -0,0 +1,50 @@ +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, + 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() { + + } +} From 6ad85110eb414c36d67f41d6eb72d48ec16e5903 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 15:01:28 -0400 Subject: [PATCH 16/74] add image exporting to vue component --- docs/components/blockly_workspace.vue | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/components/blockly_workspace.vue b/docs/components/blockly_workspace.vue index e04957e..66c2f68 100644 --- a/docs/components/blockly_workspace.vue +++ b/docs/components/blockly_workspace.vue @@ -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: { From e9f3f8c5c7f7eba7781a8c5d3d81e60c2825ac39 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 15:22:02 -0400 Subject: [PATCH 17/74] reify shadows if no input block present --- src/definitions/block_definition.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/definitions/block_definition.js b/src/definitions/block_definition.js index 5ddb662..f0b5e0d 100644 --- a/src/definitions/block_definition.js +++ b/src/definitions/block_definition.js @@ -149,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 + } } }, From ad3d1d8cb231f29a8c7f34dc3270df832e89b793 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 15:22:31 -0400 Subject: [PATCH 18/74] render block image tags --- src/docs/render_block.js | 7 ++++++- src/exporters/block_index_exporter.js | 8 +++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/docs/render_block.js b/src/docs/render_block.js index dccbd98..348c386 100644 --- a/src/docs/render_block.js +++ b/src/docs/render_block.js @@ -4,7 +4,7 @@ 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,6 +17,9 @@ This Block requires an IO+ subscription to use. [Learn more about IO+](https://i return trim(`${name} ${ioPlusBadge}`) }, + // ![alt](url "title") + renderBlockImage = ({ name, type }) => `![the ${name} block](/block_images/${type}.png "${name}")`, + renderDescription = ({ description }) => description || "No docs for this block, yet.", renderIOPlusAlert = ({ ioPlus }) => ioPlus ? IO_PLUS_ALERT : "", @@ -70,6 +73,8 @@ definitionPath: ${ definition.definitionPath } Type: \`${definition.type}\` +${ renderBlockImage(definition) } + ${ renderDescription(definition) } ${ renderIOPlusAlert(definition) } diff --git a/src/exporters/block_index_exporter.js b/src/exporters/block_index_exporter.js index b75ea86..4f189d0 100644 --- a/src/exporters/block_index_exporter.js +++ b/src/exporters/block_index_exporter.js @@ -1,6 +1,8 @@ import { writeFileSync } from 'fs' import { isString, without } from 'lodash-es' +import { renderBlockImage } from '../docs/render_block.js' + export default class BlockIndexExporter { definitionSet = null @@ -78,11 +80,11 @@ const definitionToIndexLines = def => { // block name and link indexLines.push(`### [${ def.name }](/${ def.documentationPath() })`) - // block image // TODO - // indexLines.push(``) - // block short description indexLines.push(`_${def.tooltip}_`) + // block image + indexLines.push(renderBlockImage(def)) + return indexLines.join("\n") } From 552fffaaf6796c313039bce15294a48a486e0cf0 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:06:46 -0400 Subject: [PATCH 19/74] set up cypress for block images --- cypress/cypress.config.js | 5 + cypress/e2e/block_images.cy.js | 9 + cypress/support/e2e.js | 0 package-lock.json | 1952 +++++++++++++++++++++++++++++++- package.json | 1 + 5 files changed, 1962 insertions(+), 5 deletions(-) create mode 100644 cypress/cypress.config.js create mode 100644 cypress/e2e/block_images.cy.js create mode 100644 cypress/support/e2e.js diff --git a/cypress/cypress.config.js b/cypress/cypress.config.js new file mode 100644 index 0000000..a8cfec2 --- /dev/null +++ b/cypress/cypress.config.js @@ -0,0 +1,5 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: {}, +}); diff --git a/cypress/e2e/block_images.cy.js b/cypress/e2e/block_images.cy.js new file mode 100644 index 0000000..bb377af --- /dev/null +++ b/cypress/e2e/block_images.cy.js @@ -0,0 +1,9 @@ +describe("Block Images", () => { + it("download all block images", () => { + cy.visit("http://localhost:5173/") + cy.get("[data-id]").each(($el) => { + cy.wrap($el).rightclick({ force: true }) + cy.contains("Save Block as PNG...").click() + }) + }) +}) diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index f24e985..5f98dbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,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", @@ -335,6 +336,70 @@ "blockly": "^10.0.0" } }, + "node_modules/@cypress/request": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", + "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.14.0", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/@docsearch/css": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", @@ -1522,8 +1587,21 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", "dev": true, - "optional": true, - "peer": true + "optional": true + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "dev": true, + "license": "MIT" }, "node_modules/@types/unist": { "version": "3.0.3", @@ -1539,6 +1617,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -1847,6 +1936,20 @@ "node": ">= 6.0.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1888,6 +1991,45 @@ "node": ">= 14.0.0" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1912,12 +2054,53 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1927,17 +2110,92 @@ "node": ">=12" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/birpc": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.3.0.tgz", @@ -1948,6 +2206,13 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/blockly": { "version": "10.1.3", "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.1.3.tgz", @@ -1956,6 +2221,13 @@ "jsdom": "22.1.0" } }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1966,6 +2238,41 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1974,6 +2281,46 @@ "optional": true, "peer": true }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1983,6 +2330,13 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2057,6 +2411,132 @@ "node": ">= 16" } }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2075,6 +2555,24 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2097,6 +2595,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2119,6 +2637,13 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2151,6 +2676,95 @@ "dev": true, "license": "MIT" }, + "node_modules/cypress": { + "version": "14.5.3", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.3.tgz", + "integrity": "sha512-syLwKjDeMg77FRRx68bytLdlqHXDT4yBVh0/PPkcgesChYDjUZbwxLqMXuryYKzAyJsPsQHUDW1YU74/IYEUIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.9", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "ci-info": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-table3": "0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "hasha": "5.2.2", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.7.1", + "supports-color": "^8.1.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/data-urls": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", @@ -2164,6 +2778,13 @@ "node": ">=14" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -2255,12 +2876,37 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -2274,6 +2920,30 @@ "dev": true, "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -2285,6 +2955,51 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -2517,6 +3232,95 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2544,6 +3348,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", @@ -2559,6 +3373,32 @@ } } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2633,19 +3473,58 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2667,6 +3546,88 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "10.4.2", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", @@ -2726,6 +3687,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globals": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.2.0.tgz", @@ -2738,6 +3715,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2753,6 +3749,72 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-to-html": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", @@ -2833,6 +3895,21 @@ "node": ">= 6" } }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2845,6 +3922,16 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2856,6 +3943,27 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2890,6 +3998,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2907,6 +4025,16 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2937,6 +4065,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -2951,6 +4096,39 @@ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -2970,6 +4148,13 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, "node_modules/jackspeak": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", @@ -3000,6 +4185,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, "node_modules/jsdom": { "version": "22.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", @@ -3047,6 +4239,13 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3059,6 +4258,52 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -3068,6 +4313,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "> 0.8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3081,6 +4336,74 @@ "node": ">= 0.8.0" } }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3096,6 +4419,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash-es": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", @@ -3107,6 +4437,104 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/loupe": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", @@ -3139,6 +4567,15 @@ "dev": true, "license": "MIT" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-to-hast": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", @@ -3161,6 +4598,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", @@ -3274,6 +4718,16 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3286,6 +4740,16 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -3339,11 +4803,37 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3353,6 +4843,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/oniguruma-to-es": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", @@ -3382,6 +4888,13 @@ "node": ">= 0.8.0" } }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3412,6 +4925,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", @@ -3493,6 +5022,13 @@ "node": ">= 14.16" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -3500,6 +5036,13 @@ "dev": true, "license": "MIT" }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3520,6 +5063,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss": { "version": "8.5.5", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", @@ -3569,6 +5122,29 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -3580,11 +5156,29 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -3593,6 +5187,22 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -3645,6 +5255,16 @@ "dev": true, "license": "MIT" }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "throttleit": "^1.0.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -3659,6 +5279,27 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3780,6 +5421,37 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3804,6 +5476,19 @@ "license": "MIT", "peer": true }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3842,6 +5527,82 @@ "@types/hast": "^3.0.4" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3854,6 +5615,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3908,6 +5684,32 @@ "node": ">=0.10.0" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -4013,6 +5815,16 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4096,6 +5908,23 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -4113,6 +5942,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -4138,6 +5997,16 @@ "node": ">=14" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -4149,6 +6018,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4254,6 +6150,16 @@ "node": ">= 4.0.0" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4272,6 +6178,31 @@ "requires-port": "^1.0.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -5129,6 +7060,17 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 42052ac..fcf069e 100644 --- a/package.json +++ b/package.json @@ -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", From 5b5f9d9c4f72d6515b9a31778bdfc8599db17575 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:11:19 -0400 Subject: [PATCH 20/74] a bold block image export script --- export.js | 43 +++++++++++++++- export_util.js | 2 +- .../blockly_workspace.template.html | 49 +++++++++++++++++++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/exporters/document_templates/blockly_workspace.template.html diff --git a/export.js b/export.js index c2ee89a..d6c2606 100644 --- a/export.js +++ b/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,8 +35,15 @@ 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 => { @@ -41,6 +53,33 @@ const // 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`]) + + // kill the server + viteProcess.kill() + } }, exporterNames = Object.keys(exporters) diff --git a/export_util.js b/export_util.js index 987107b..55c97db 100644 --- a/export_util.js +++ b/export_util.js @@ -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`) }, diff --git a/src/exporters/document_templates/blockly_workspace.template.html b/src/exporters/document_templates/blockly_workspace.template.html new file mode 100644 index 0000000..51b07b4 --- /dev/null +++ b/src/exporters/document_templates/blockly_workspace.template.html @@ -0,0 +1,49 @@ + + + + + Blockly: All Blocks + + + + + + + +
+ + From 296bf65816f24fdd6054f9b556e5842aa73fca50 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:24:23 -0400 Subject: [PATCH 21/74] point to cypress config, check that vite closes --- export.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/export.js b/export.js index d6c2606..37e9b94 100644 --- a/export.js +++ b/export.js @@ -75,10 +75,18 @@ const // extract the screenshots console.log('Generating screenshots...') - spawnSync("npx", ["cypress", "run", "--config", `downloadsFolder=${destination}/images`]) + spawnSync("npx", ["cypress", "run", + "--config", `downloadsFolder=${destination}/images`, + "--config-file", `cypress/cypress.config.js`, + ]) + console.log('Generation complete.') // kill the server - viteProcess.kill() + if(!viteProcess.kill()) { + console.log("Vite failed to exit gracefully") + process.exit(1) + } + console.log('Server closed.') } }, exporterNames = Object.keys(exporters) From 02dd9b9c1c2c6d11a2b20572f209f7d296bfc0d0 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:32:09 -0400 Subject: [PATCH 22/74] explicit exit --- export.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/export.js b/export.js index 37e9b94..34b719c 100644 --- a/export.js +++ b/export.js @@ -106,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) From f463143199e578dbbcdcc2e3ef82ccee7ce9661b Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:46:46 -0400 Subject: [PATCH 23/74] coordinate screenshots via data-id --- cypress/e2e/block_images.cy.js | 4 +++- src/exporters/workspace_all_blocks_exporter.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/block_images.cy.js b/cypress/e2e/block_images.cy.js index bb377af..92f43c7 100644 --- a/cypress/e2e/block_images.cy.js +++ b/cypress/e2e/block_images.cy.js @@ -1,9 +1,11 @@ describe("Block Images", () => { it("download all block images", () => { cy.visit("http://localhost:5173/") - cy.get("[data-id]").each(($el) => { + 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.`) }) }) }) diff --git a/src/exporters/workspace_all_blocks_exporter.js b/src/exporters/workspace_all_blocks_exporter.js index 819da4a..de31e18 100644 --- a/src/exporters/workspace_all_blocks_exporter.js +++ b/src/exporters/workspace_all_blocks_exporter.js @@ -23,6 +23,7 @@ export default class WorkspaceExporter { languageVersion: 0, blocks: allBlocks.map((block, index) => ({ ...block, + id: `block-type-${block.type}`, x: 50, y: 50*index })) From f23734256328acd23727fca87cec34461f81d291 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 16:55:13 -0400 Subject: [PATCH 24/74] improve default feed names --- .../document_templates/blockly_workspace.template.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/exporters/document_templates/blockly_workspace.template.html b/src/exporters/document_templates/blockly_workspace.template.html index 51b07b4..a87d3ec 100644 --- a/src/exporters/document_templates/blockly_workspace.template.html +++ b/src/exporters/document_templates/blockly_workspace.template.html @@ -14,9 +14,10 @@ }, extensionData: { feedOptions: [ - ["Feeder 1", "abc123"], - ["A Feed Z", "qrstuv"], - ["Feedinsky &", "oneforyou-oneforme"], + ["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" ], From b959330af6ac772c2dc31fc7c812aed3cbb52359 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 29 Jul 2025 17:01:14 -0400 Subject: [PATCH 25/74] break blockly auto-factor functionality --- .../document_templates/blockly_workspace.template.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/exporters/document_templates/blockly_workspace.template.html b/src/exporters/document_templates/blockly_workspace.template.html index a87d3ec..0f256c9 100644 --- a/src/exporters/document_templates/blockly_workspace.template.html +++ b/src/exporters/document_templates/blockly_workspace.template.html @@ -14,10 +14,10 @@ }, 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"], + ["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" ], From 6626035439f6a8a1518c2efccc1aee1c1db9375f Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 30 Jul 2025 15:23:39 -0400 Subject: [PATCH 26/74] support raw booleans in block extensions --- src/renderers/object_renderer.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderers/object_renderer.js b/src/renderers/object_renderer.js index a47bb65..6b273ca 100644 --- a/src/renderers/object_renderer.js +++ b/src/renderers/object_renderer.js @@ -1,4 +1,4 @@ -import { compact, forOwn, keys, isString, isFunction, isArray, isNumber, isNull, isObject, isUndefined, map, isRegExp, sortBy } from 'lodash-es' +import { compact, forOwn, keys, isBoolean, isString, isFunction, isArray, isNumber, isNull, isObject, isUndefined, map, isRegExp, sortBy } from 'lodash-es' const @@ -13,7 +13,10 @@ const quotedKey = key => : key const renderValue = (value, tab=TAB) => { - if (isString(value)) { + if (isBoolean(value)) { + return renderBoolean(value) + + } else if (isString(value)) { return renderString(value) } else if (isRegExp(value) || isNull(value) || isNumber(value) || isUndefined(value) || value === false) { @@ -48,6 +51,10 @@ const renderString = stringValue => { return `"${stringValue}"` } +const renderBoolean = boolValue => { + return `${boolValue}` +} + const renderFunction = (func, indentation=TAB) => { const functionString = func.toString(), From 03ee183cc3a8c85ef2b2c7b706d4da6b229985d1 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 30 Jul 2025 15:23:55 -0400 Subject: [PATCH 27/74] make weather block behavior toggleable --- app/blocks/power_ups/weather_mixin.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/blocks/power_ups/weather_mixin.js b/app/blocks/power_ups/weather_mixin.js index 70eab74..2ff24fb 100644 --- a/app/blocks/power_ups/weather_mixin.js +++ b/app/blocks/power_ups/weather_mixin.js @@ -26,7 +26,12 @@ export default { } }, + autoDisable: true, + setEnabledByLocation: function() { + // bail if this behavior has been disabled + if(!this.autoDisable) { return } + // must have a location and a parent (copacetic with disableOrphans) if(this.getFieldValue("POWER_UP_ID") === "" || !this.getParent()) { this.disabled || this.setEnabled(false) From 85d2a3100612a5be2113c9aa07440d45f9f9bcdc Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 30 Jul 2025 15:24:15 -0400 Subject: [PATCH 28/74] force enable weather block before screenshots --- cypress/e2e/block_images.cy.js | 10 ++++++++++ .../document_templates/blockly_workspace.template.html | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/block_images.cy.js b/cypress/e2e/block_images.cy.js index 92f43c7..25a76f6 100644 --- a/cypress/e2e/block_images.cy.js +++ b/cypress/e2e/block_images.cy.js @@ -1,6 +1,16 @@ describe("Block Images", () => { it("download all block images", () => { cy.visit("http://localhost:5173/") + + // any extra setup to do before taking screenshots + cy.window().then(win => { + // disable the weather block's automatic disabling behavior + const weatherBlock = win.blocklyWorkspace.getBlockById('block-type-weather') + weatherBlock.autoDisable = false + // enable it + weatherBlock.setEnabled(true) + }) + cy.get("[data-id^='block-type-']").each(($el) => { cy.wrap($el).rightclick({ force: true }) cy.contains("Save Block as PNG...").click() diff --git a/src/exporters/document_templates/blockly_workspace.template.html b/src/exporters/document_templates/blockly_workspace.template.html index 0f256c9..b290c9e 100644 --- a/src/exporters/document_templates/blockly_workspace.template.html +++ b/src/exporters/document_templates/blockly_workspace.template.html @@ -8,7 +8,8 @@ import { inject } from './blockly_app.js' import { imageExportRegistryItems } from '#src/image_exporter.js' - inject("blocklyDiv", { + // expose a handle for Cypress to get ahold of the blockly workspace + window.blocklyWorkspace = inject("blocklyDiv", { contextMenu: { register: [ ...imageExportRegistryItems ] }, From 00e7074c386a6512866f6223e28974385c714fb0 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Thu, 31 Jul 2025 17:38:52 -0400 Subject: [PATCH 29/74] WIP at including jsdoc type info and hinting --- app/blocks/math/number.js | 5 ++- app/blocks/math/range.js | 1 + app/types.js | 75 +++++++++++++++++++++++++++++++++++++++ jsconfig.json | 1 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 app/types.js diff --git a/app/blocks/math/number.js b/app/blocks/math/number.js index f12f432..6892ca4 100644 --- a/app/blocks/math/number.js +++ b/app/blocks/math/number.js @@ -1,7 +1,8 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_math_number', name: "Number", - colour: 120, + color: 120, description: "A numeric value, whole or decimal.", @@ -14,6 +15,8 @@ export default { validateNumbers: ({ block }) => { const numField = block.getField("NUM") + if(!numField) { throw new Error("NUM field missing on io_math_number?") } + numField.setValidator(newValue => { const parsed = Number(newValue) diff --git a/app/blocks/math/range.js b/app/blocks/math/range.js index f1c57f8..3e14814 100644 --- a/app/blocks/math/range.js +++ b/app/blocks/math/range.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "math_range", bytecodeKey: "range", diff --git a/app/types.js b/app/types.js new file mode 100644 index 0000000..09a2679 --- /dev/null +++ b/app/types.js @@ -0,0 +1,75 @@ +/** + * @import Blockly from "blockly" + */ + +/** + * @typedef {object} BlockConnections + * @prop {string} mode "value", "statement" + * @prop {string|string[]} output what kinds of blocks can be inserted here? + */ + +/** + * @typedef {object} BlockExtensionFunctionInjectable + * @prop {Blockly.Block} block + */ + +/** + * @callback BlockExtensionFunction + * @param {BlockExtensionFunctionInjectable} injectables + */ + +/** + * @typedef {Object.} BlockExtensions + */ + +/** + * @callback BlockGenerator + * @param {Blockly.Block} block + * @param {Blockly.Generator} generator + */ + +/** + * @typedef {Object.} BlockGenerators + */ + +/** + * @typedef {object} BlockRegeneratorHelpers + * @property {function} expressionToBlock + */ + +/** + * @callback BlockRegenerator + * @param {Object.>} blockObject + * @param {BlockRegeneratorHelpers} helpers + */ + +/** + * @typedef {Object.} BlockRegenerators + */ + +/** + * A plain-old JavaScript object that provides shortcuts for specifying a + * Blockly Block. It can export: + * - Blockly's block JSON format + * - Blockly's block instance JSON format + * - documentation fragments + * + * @typedef {object} BlockDefinitionRaw + * @prop {string} type unique string to identify this kind of block internally + * @prop {string=} bytecodeKey the unique key this block gets serialized to + * @prop {string} name unique string we use when talking about a block + * @prop {boolean=} inputsInline Blockly pass-through property that determines + * how the block is rendered + * @prop {(number|string)=} color A number or string from 0-360 that specifies a + * color in Blockly's radial color space + * @prop {(number|string)=} colour Alias for "color" + * @prop {string} description Markdown documentation for this block. The first + * line will be automatically set as the Block's tooltip. + * @prop {BlockConnections} connections + * @prop {BlockExtensions=} extensions + * @prop {string} template + * @prop {object=} fields + * @prop {object=} inputs + * @prop {BlockGenerators=} generators + * @prop {BlockRegenerators=} regenerators + */ diff --git a/jsconfig.json b/jsconfig.json index b774d95..3ad153f 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,6 +5,7 @@ "target": "es2022", "paths": { "#app/*.js": ["./app/*.js"], + "#types": ["./app/types.js"], "#src/*.js": ["./src/*.js"], "#test/*.js": ["./test/*.js"] } From dce355387e0552545c8ed86e07713d19a7c61483 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 15:12:08 -0400 Subject: [PATCH 30/74] primaryCategory check, all math blocks checked --- app/blocks/math/arithmetic.js | 1 + app/blocks/math/compare.js | 1 + app/blocks/math/constrain.js | 1 + app/blocks/math/map.js | 1 + app/blocks/math/round.js | 1 + app/types.js | 3 +++ 6 files changed, 8 insertions(+) diff --git a/app/blocks/math/arithmetic.js b/app/blocks/math/arithmetic.js index bf3dcff..30cf12c 100644 --- a/app/blocks/math/arithmetic.js +++ b/app/blocks/math/arithmetic.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_math_arithmetic', bytecodeKey: "arithmetic", diff --git a/app/blocks/math/compare.js b/app/blocks/math/compare.js index acdcc42..4578ce7 100644 --- a/app/blocks/math/compare.js +++ b/app/blocks/math/compare.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_logic_compare', bytecodeKey: "", diff --git a/app/blocks/math/constrain.js b/app/blocks/math/constrain.js index ae69c1f..ef5bbad 100644 --- a/app/blocks/math/constrain.js +++ b/app/blocks/math/constrain.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "io_math_constrain", bytecodeKey: "constrain", diff --git a/app/blocks/math/map.js b/app/blocks/math/map.js index 3473524..8a3264c 100644 --- a/app/blocks/math/map.js +++ b/app/blocks/math/map.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "math_map", bytecodeKey: "mapValue", diff --git a/app/blocks/math/round.js b/app/blocks/math/round.js index 02a04d9..ea9e22f 100644 --- a/app/blocks/math/round.js +++ b/app/blocks/math/round.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "io_math_round", bytecodeKey: "round", diff --git a/app/types.js b/app/types.js index 09a2679..cae964e 100644 --- a/app/types.js +++ b/app/types.js @@ -63,6 +63,9 @@ * @prop {(number|string)=} color A number or string from 0-360 that specifies a * color in Blockly's radial color space * @prop {(number|string)=} colour Alias for "color" + * @prop {string=} primaryCategory For blocks appearing in multiple categories, + * this property determines which menu this block will appear under in the + * docs. * @prop {string} description Markdown documentation for this block. The first * line will be automatically set as the Block's tooltip. * @prop {BlockConnections} connections From 16578e4af7edea5529771ff72cb481c666d9388a Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 15:49:53 -0400 Subject: [PATCH 31/74] weather block, mixins, docOverrides, ioPlus --- app/blocks/power_ups/weather.js | 9 +++++---- app/types.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/blocks/power_ups/weather.js b/app/blocks/power_ups/weather.js index de6e3e7..49f26d2 100644 --- a/app/blocks/power_ups/weather.js +++ b/app/blocks/power_ups/weather.js @@ -1,12 +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("") +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "weather", bytecodeKey: "weather", @@ -36,6 +36,7 @@ export default { weatherLocationOptions.unshift([ "Select Location", "" ]) } + // @ts-ignore block.replaceDropdownOptions("POWER_UP_ID", weatherLocationOptions) // skip the rest if we're in the toolbox @@ -46,16 +47,16 @@ export default { // nope out for insertion markers if(block.isInsertionMarker()) { return } - // auto-disable block, if necessary + // @ts-ignore auto-disable block, if necessary block.setEnabledByLocation() // react to incoming forecast data const unobserve = observeData('currentWeatherByLocation', (newData = {}) => { // if this block is disposed, clean up this listener if (block.isDisposed()) { unobserve(); return } - // update the reference to the injected/updated extension data + // @ts-ignore update the reference to the injected/updated extension data block.currentWeatherByLocation = newData - // re-run the things that use the data + // @ts-ignore re-run the things that use the data block.refreshPropertyOptions({}) }) }, 1) diff --git a/app/types.js b/app/types.js index cae964e..2a29686 100644 --- a/app/types.js +++ b/app/types.js @@ -11,6 +11,8 @@ /** * @typedef {object} BlockExtensionFunctionInjectable * @prop {Blockly.Block} block + * @prop {function} observeData + * @prop {object} data */ /** @@ -22,6 +24,14 @@ * @typedef {Object.} BlockExtensions */ +/** + * @typedef {string|object} BlocklyMixin + */ + + /** + * @typedef {BlocklyMixin[]} BlockMixins + */ + /** * @callback BlockGenerator * @param {Blockly.Block} block @@ -66,13 +76,16 @@ * @prop {string=} primaryCategory For blocks appearing in multiple categories, * this property determines which menu this block will appear under in the * docs. + * @prop {boolean=} ioPlus Indicates this block requires an IO+ account. * @prop {string} description Markdown documentation for this block. The first * line will be automatically set as the Block's tooltip. * @prop {BlockConnections} connections * @prop {BlockExtensions=} extensions + * @prop {BlockMixins=} mixins * @prop {string} template * @prop {object=} fields * @prop {object=} inputs * @prop {BlockGenerators=} generators * @prop {BlockRegenerators=} regenerators + * @prop {object=} docOverrides */ From 22f64eae88ea63f35477da254666d2d80206ac4b Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 15:56:00 -0400 Subject: [PATCH 32/74] connections, mutator, root block + delays --- app/blocks/root/action_settings/delay_days.js | 5 ++--- app/blocks/root/action_settings/delay_hours.js | 5 ++--- app/blocks/root/action_settings/delay_minutes.js | 5 ++--- app/blocks/root/action_settings/delay_none.js | 5 ++--- app/blocks/root/action_settings/delay_seconds.js | 5 ++--- app/blocks/root/action_settings/delay_settings.js | 7 ++----- app/blocks/root/root.js | 3 +++ app/types.js | 6 ++++-- 8 files changed, 19 insertions(+), 22 deletions(-) diff --git a/app/blocks/root/action_settings/delay_days.js b/app/blocks/root/action_settings/delay_days.js index 3d9194d..d549cd3 100644 --- a/app/blocks/root/action_settings/delay_days.js +++ b/app/blocks/root/action_settings/delay_days.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_days", name: "Delay Days", @@ -12,8 +13,6 @@ export default { template: "1 day", generators: { - json: () => { - - } + json: () => {} } } diff --git a/app/blocks/root/action_settings/delay_hours.js b/app/blocks/root/action_settings/delay_hours.js index 73a93b2..999e762 100644 --- a/app/blocks/root/action_settings/delay_hours.js +++ b/app/blocks/root/action_settings/delay_hours.js @@ -1,6 +1,7 @@ import { makeOptions } from "#app/util/fields.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_hours", name: "Delay Hours", @@ -28,8 +29,6 @@ export default { }, generators: { - json: () => { - - } + json: () => {} } } diff --git a/app/blocks/root/action_settings/delay_minutes.js b/app/blocks/root/action_settings/delay_minutes.js index db9f7cd..4b1526d 100644 --- a/app/blocks/root/action_settings/delay_minutes.js +++ b/app/blocks/root/action_settings/delay_minutes.js @@ -1,6 +1,7 @@ import { makeOptions } from "#app/util/fields.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_minutes", name: "Delay Minutes", @@ -28,8 +29,6 @@ export default { }, generators: { - json: () => { - - } + json: () => {} } } diff --git a/app/blocks/root/action_settings/delay_none.js b/app/blocks/root/action_settings/delay_none.js index 30625e0..be573d0 100644 --- a/app/blocks/root/action_settings/delay_none.js +++ b/app/blocks/root/action_settings/delay_none.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_none", name: "No Delay", @@ -12,8 +13,6 @@ export default { template: "No Delay", generators: { - json: () => { - - } + json: () => {} } } diff --git a/app/blocks/root/action_settings/delay_seconds.js b/app/blocks/root/action_settings/delay_seconds.js index 652afae..160ccd5 100644 --- a/app/blocks/root/action_settings/delay_seconds.js +++ b/app/blocks/root/action_settings/delay_seconds.js @@ -1,6 +1,7 @@ import { makeOptions } from "#app/util/fields.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_seconds", name: "Delay Seconds", @@ -27,8 +28,6 @@ export default { }, generators: { - json: () => { - - } + json: () => {} } } diff --git a/app/blocks/root/action_settings/delay_settings.js b/app/blocks/root/action_settings/delay_settings.js index e224adf..ccaa017 100644 --- a/app/blocks/root/action_settings/delay_settings.js +++ b/app/blocks/root/action_settings/delay_settings.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "delay_settings", name: "Delay Settings", @@ -6,7 +7,6 @@ export default { connections: { }, - template: ` Delay Settings |CENTER Delay: %DELAY_PERIOD @@ -33,9 +33,6 @@ export default { }, generators: { - json: (block, generator) => { - - return [ {}, 0 ] - } + json: () => [ {}, 0 ] } } diff --git a/app/blocks/root/root.js b/app/blocks/root/root.js index 1a00684..04b22de 100644 --- a/app/blocks/root/root.js +++ b/app/blocks/root/root.js @@ -1,6 +1,7 @@ import mutator from './action_settings/mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_root", name: "Root", @@ -55,7 +56,9 @@ export default { } const + // @ts-ignore seconds = block.delaySeconds, + // @ts-ignore mode = block.delayMode, delay = (seconds > 0) ? { seconds, mode } diff --git a/app/types.js b/app/types.js index 2a29686..10cd5ad 100644 --- a/app/types.js +++ b/app/types.js @@ -4,8 +4,8 @@ /** * @typedef {object} BlockConnections - * @prop {string} mode "value", "statement" - * @prop {string|string[]} output what kinds of blocks can be inserted here? + * @prop {string=} mode "value", "statement" + * @prop {string|string[]=} output what kinds of blocks can be inserted here? */ /** @@ -45,6 +45,7 @@ /** * @typedef {object} BlockRegeneratorHelpers * @property {function} expressionToBlock + * @property {function} arrayToStatements */ /** @@ -81,6 +82,7 @@ * line will be automatically set as the Block's tooltip. * @prop {BlockConnections} connections * @prop {BlockExtensions=} extensions + * @prop {object=} mutator * @prop {BlockMixins=} mixins * @prop {string} template * @prop {object=} fields From 18d649313c47b56d67ab25eb57212e1eca29e0ea Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 15:58:43 -0400 Subject: [PATCH 33/74] connections optional, disabled. check text blocks --- app/blocks/text/compare.js | 1 + app/blocks/text/join.js | 1 + app/blocks/text/regex.js | 1 + app/blocks/text/template.js | 1 + app/blocks/text/text.js | 1 + app/blocks/text/text_multiline.js | 1 + app/types.js | 4 +++- 7 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/blocks/text/compare.js b/app/blocks/text/compare.js index 51c8980..d1f95a8 100644 --- a/app/blocks/text/compare.js +++ b/app/blocks/text/compare.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'text_compare', bytecodeKey: 'textCompare', diff --git a/app/blocks/text/join.js b/app/blocks/text/join.js index c936b5b..f60a36c 100644 --- a/app/blocks/text/join.js +++ b/app/blocks/text/join.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_text_join', bytecodeKey: "textJoin", diff --git a/app/blocks/text/regex.js b/app/blocks/text/regex.js index 9d42785..9924cd2 100644 --- a/app/blocks/text/regex.js +++ b/app/blocks/text/regex.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { disabled: true, diff --git a/app/blocks/text/template.js b/app/blocks/text/template.js index 544943f..0d957ac 100644 --- a/app/blocks/text/template.js +++ b/app/blocks/text/template.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'text_template', bytecodeKey: "textTemplate", diff --git a/app/blocks/text/text.js b/app/blocks/text/text.js index bfc90c4..59a95f7 100644 --- a/app/blocks/text/text.js +++ b/app/blocks/text/text.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "io_text", name: "Text", diff --git a/app/blocks/text/text_multiline.js b/app/blocks/text/text_multiline.js index 01aad16..061c245 100644 --- a/app/blocks/text/text_multiline.js +++ b/app/blocks/text/text_multiline.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_text_multiline', name: "Multiline Text", diff --git a/app/types.js b/app/types.js index 10cd5ad..445714e 100644 --- a/app/types.js +++ b/app/types.js @@ -78,9 +78,11 @@ * this property determines which menu this block will appear under in the * docs. * @prop {boolean=} ioPlus Indicates this block requires an IO+ account. + * @prop {boolean=} disabled Marks this block for exclusion from all app and + * docs builds. * @prop {string} description Markdown documentation for this block. The first * line will be automatically set as the Block's tooltip. - * @prop {BlockConnections} connections + * @prop {BlockConnections=} connections * @prop {BlockExtensions=} extensions * @prop {object=} mutator * @prop {BlockMixins=} mixins From 52c77e93dc6b896a61af2c02591ddf369f5d6955 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:13:40 -0400 Subject: [PATCH 34/74] connections, extensions. trigger blocks --- app/blocks/triggers/on_schedule.js | 5 +++-- app/blocks/triggers/schedule/day/all_days.js | 5 ++--- app/blocks/triggers/schedule/day/day_settings.js | 3 ++- app/blocks/triggers/schedule/day/days_of_week.js | 1 + app/blocks/triggers/schedule/day/every_days_between.js | 1 + app/blocks/triggers/schedule/day/one_day.js | 1 + app/blocks/triggers/schedule/hour/all_hours.js | 5 ++--- app/blocks/triggers/schedule/hour/every_hours_between.js | 1 + app/blocks/triggers/schedule/hour/hour_settings.js | 3 ++- app/blocks/triggers/schedule/hour/one_hour.js | 1 + app/blocks/triggers/schedule/minute/all_minutes.js | 5 ++--- .../triggers/schedule/minute/every_minutes_between.js | 1 + app/blocks/triggers/schedule/minute/minute_settings.js | 3 ++- app/blocks/triggers/schedule/minute/one_minute.js | 1 + app/blocks/triggers/schedule/month/all_months.js | 5 ++--- app/blocks/triggers/schedule/month/every_months_between.js | 1 + app/blocks/triggers/schedule/month/month_settings.js | 3 ++- app/blocks/triggers/schedule/month/one_month.js | 1 + app/blocks/triggers/schedule/month/some_months.js | 1 + app/blocks/triggers/when_data.js | 1 + app/blocks/triggers/when_data_matching.js | 1 + app/blocks/triggers/when_data_matching_state.js | 1 + app/types.js | 6 ++++-- 23 files changed, 36 insertions(+), 20 deletions(-) diff --git a/app/blocks/triggers/on_schedule.js b/app/blocks/triggers/on_schedule.js index 3c9667c..0d4ef52 100644 --- a/app/blocks/triggers/on_schedule.js +++ b/app/blocks/triggers/on_schedule.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "on_schedule", bytecodeKey: "onSchedule", @@ -68,13 +69,13 @@ export default { }, regenerators: { - json: (blockObject, helpers) => { + json: (blockObject) => { const EVERY_REGEX = /^(\d{1,2})(-(\d{1,2}))?\/(\d{1,2})$/m, isEveryBetween = cron => EVERY_REGEX.test(cron), everyBetweenToBlock = (everyBetweenCron, blockType) => { - const [ skip1, START, skip2, END, FREQUENCY ] = everyBetweenCron.match(EVERY_REGEX) + const [ , START, , END, FREQUENCY ] = everyBetweenCron.match(EVERY_REGEX) return { block: { type: blockType, diff --git a/app/blocks/triggers/schedule/day/all_days.js b/app/blocks/triggers/schedule/day/all_days.js index 2426e96..abb7ebe 100644 --- a/app/blocks/triggers/schedule/day/all_days.js +++ b/app/blocks/triggers/schedule/day/all_days.js @@ -1,6 +1,7 @@ import mutator from "./day_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "all_days", name: "All Days", @@ -17,8 +18,6 @@ export default { template: "Every day", generators: { - json: block => { - return [ '*', 0 ] - } + json: () => [ '*', 0 ] } } diff --git a/app/blocks/triggers/schedule/day/day_settings.js b/app/blocks/triggers/schedule/day/day_settings.js index 09650aa..a660dbb 100644 --- a/app/blocks/triggers/schedule/day/day_settings.js +++ b/app/blocks/triggers/schedule/day/day_settings.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "day_settings", name: "Day Settings", @@ -16,6 +17,6 @@ export default { }, generators: { - json: block => { } + json: () => { } } } diff --git a/app/blocks/triggers/schedule/day/days_of_week.js b/app/blocks/triggers/schedule/day/days_of_week.js index a200e6a..aae9dba 100644 --- a/app/blocks/triggers/schedule/day/days_of_week.js +++ b/app/blocks/triggers/schedule/day/days_of_week.js @@ -1,6 +1,7 @@ import mutator from "./day_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "days_of_week", name: "Days of the Week", diff --git a/app/blocks/triggers/schedule/day/every_days_between.js b/app/blocks/triggers/schedule/day/every_days_between.js index 1c5d22e..be9a759 100644 --- a/app/blocks/triggers/schedule/day/every_days_between.js +++ b/app/blocks/triggers/schedule/day/every_days_between.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from "./day_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "every_days_between", name: "Every X Days", diff --git a/app/blocks/triggers/schedule/day/one_day.js b/app/blocks/triggers/schedule/day/one_day.js index 5478eb3..529b437 100644 --- a/app/blocks/triggers/schedule/day/one_day.js +++ b/app/blocks/triggers/schedule/day/one_day.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from "./day_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "one_day", name: "One Day", diff --git a/app/blocks/triggers/schedule/hour/all_hours.js b/app/blocks/triggers/schedule/hour/all_hours.js index ee78497..a3230f3 100644 --- a/app/blocks/triggers/schedule/hour/all_hours.js +++ b/app/blocks/triggers/schedule/hour/all_hours.js @@ -1,6 +1,7 @@ import mutator from "./hour_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "all_hours", name: "All Hours", @@ -17,8 +18,6 @@ export default { template: "Every hour", generators: { - json: block => { - return [ '*', 0 ] - } + json: () => [ '*', 0 ] } } diff --git a/app/blocks/triggers/schedule/hour/every_hours_between.js b/app/blocks/triggers/schedule/hour/every_hours_between.js index a0b7ac0..c811958 100644 --- a/app/blocks/triggers/schedule/hour/every_hours_between.js +++ b/app/blocks/triggers/schedule/hour/every_hours_between.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from "./hour_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "every_hours_between", name: "Every X Hours", diff --git a/app/blocks/triggers/schedule/hour/hour_settings.js b/app/blocks/triggers/schedule/hour/hour_settings.js index f2ac79a..be08338 100644 --- a/app/blocks/triggers/schedule/hour/hour_settings.js +++ b/app/blocks/triggers/schedule/hour/hour_settings.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "hour_settings", name: "Hour Settings", @@ -16,6 +17,6 @@ export default { }, generators: { - json: block => { } + json: () => { } } } diff --git a/app/blocks/triggers/schedule/hour/one_hour.js b/app/blocks/triggers/schedule/hour/one_hour.js index e893f57..2f31cfd 100644 --- a/app/blocks/triggers/schedule/hour/one_hour.js +++ b/app/blocks/triggers/schedule/hour/one_hour.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from "./hour_mutator.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "one_hour", name: "One Hour", diff --git a/app/blocks/triggers/schedule/minute/all_minutes.js b/app/blocks/triggers/schedule/minute/all_minutes.js index 05b9396..e561f72 100644 --- a/app/blocks/triggers/schedule/minute/all_minutes.js +++ b/app/blocks/triggers/schedule/minute/all_minutes.js @@ -1,6 +1,7 @@ import mutator from './minute_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "all_minutes", name: "All Minutes", @@ -17,8 +18,6 @@ export default { template: "Every minute", generators: { - json: block => { - return [ '*', 0 ] - } + json: () => [ '*', 0 ] } } diff --git a/app/blocks/triggers/schedule/minute/every_minutes_between.js b/app/blocks/triggers/schedule/minute/every_minutes_between.js index a3a28dd..e413906 100644 --- a/app/blocks/triggers/schedule/minute/every_minutes_between.js +++ b/app/blocks/triggers/schedule/minute/every_minutes_between.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from './minute_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "every_minutes_between", name: "Every X Minutes", diff --git a/app/blocks/triggers/schedule/minute/minute_settings.js b/app/blocks/triggers/schedule/minute/minute_settings.js index 8b9a5d5..ad215e8 100644 --- a/app/blocks/triggers/schedule/minute/minute_settings.js +++ b/app/blocks/triggers/schedule/minute/minute_settings.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "minute_settings", name: "Minute Settings", @@ -16,6 +17,6 @@ export default { }, generators: { - json: block => { } + json: () => { } } } diff --git a/app/blocks/triggers/schedule/minute/one_minute.js b/app/blocks/triggers/schedule/minute/one_minute.js index 1cebfc7..d18baa9 100644 --- a/app/blocks/triggers/schedule/minute/one_minute.js +++ b/app/blocks/triggers/schedule/minute/one_minute.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from './minute_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "one_minute", name: "One Minute", diff --git a/app/blocks/triggers/schedule/month/all_months.js b/app/blocks/triggers/schedule/month/all_months.js index 58f4a22..2c337fb 100644 --- a/app/blocks/triggers/schedule/month/all_months.js +++ b/app/blocks/triggers/schedule/month/all_months.js @@ -1,6 +1,7 @@ import mutator from './month_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "all_months", name: "All Months", @@ -17,8 +18,6 @@ export default { template: "Every month", generators: { - json: block => { - return [ '*', 0 ] - } + json: () => [ '*', 0 ] } } diff --git a/app/blocks/triggers/schedule/month/every_months_between.js b/app/blocks/triggers/schedule/month/every_months_between.js index 622e736..e31b92b 100644 --- a/app/blocks/triggers/schedule/month/every_months_between.js +++ b/app/blocks/triggers/schedule/month/every_months_between.js @@ -2,6 +2,7 @@ import { makeOptions } from "#app/util/fields.js" import mutator from './month_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "every_months_between", name: "Every X Months", diff --git a/app/blocks/triggers/schedule/month/month_settings.js b/app/blocks/triggers/schedule/month/month_settings.js index a1de2ba..f664679 100644 --- a/app/blocks/triggers/schedule/month/month_settings.js +++ b/app/blocks/triggers/schedule/month/month_settings.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "month_settings", name: "Month Settings", @@ -16,6 +17,6 @@ export default { }, generators: { - json: block => { } + json: () => { } } } diff --git a/app/blocks/triggers/schedule/month/one_month.js b/app/blocks/triggers/schedule/month/one_month.js index 03c743d..55de850 100644 --- a/app/blocks/triggers/schedule/month/one_month.js +++ b/app/blocks/triggers/schedule/month/one_month.js @@ -1,6 +1,7 @@ import mutator from './month_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "one_month", name: "One Month", diff --git a/app/blocks/triggers/schedule/month/some_months.js b/app/blocks/triggers/schedule/month/some_months.js index 66ce391..fda05bf 100644 --- a/app/blocks/triggers/schedule/month/some_months.js +++ b/app/blocks/triggers/schedule/month/some_months.js @@ -1,6 +1,7 @@ import mutator from './month_mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'some_months', name: "Some Months", diff --git a/app/blocks/triggers/when_data.js b/app/blocks/triggers/when_data.js index d0b4241..a43b420 100644 --- a/app/blocks/triggers/when_data.js +++ b/app/blocks/triggers/when_data.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "when_data", bytecodeKey: "whenData", diff --git a/app/blocks/triggers/when_data_matching.js b/app/blocks/triggers/when_data_matching.js index f89f435..61139ba 100644 --- a/app/blocks/triggers/when_data_matching.js +++ b/app/blocks/triggers/when_data_matching.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "when_data_matching", bytecodeKey: "whenDataMatching", diff --git a/app/blocks/triggers/when_data_matching_state.js b/app/blocks/triggers/when_data_matching_state.js index 1f4eb73..c901ab3 100644 --- a/app/blocks/triggers/when_data_matching_state.js +++ b/app/blocks/triggers/when_data_matching_state.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "when_data_matching_state", bytecodeKey: "whenDataMatchStateChanged", diff --git a/app/types.js b/app/types.js index 445714e..836a0f0 100644 --- a/app/types.js +++ b/app/types.js @@ -4,7 +4,9 @@ /** * @typedef {object} BlockConnections - * @prop {string=} mode "value", "statement" + * @prop {("value"|"statement")=} mode Is this block a value or statement? + * @prop {string=} next Limits the blocks that can connect to the bottom of + * this statement block. * @prop {string|string[]=} output what kinds of blocks can be inserted here? */ @@ -21,7 +23,7 @@ */ /** - * @typedef {Object.} BlockExtensions + * @typedef {string[]|Object.} BlockExtensions */ /** From fdabcd552e31611d0ab8ebbc0083e902673a4b39 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:22:34 -0400 Subject: [PATCH 35/74] helpers.registerVariable, variable blocks --- app/blocks/variables/get.js | 1 + app/blocks/variables/set.js | 2 ++ app/types.js | 1 + 3 files changed, 4 insertions(+) diff --git a/app/blocks/variables/get.js b/app/blocks/variables/get.js index 177b81d..ffcafb8 100644 --- a/app/blocks/variables/get.js +++ b/app/blocks/variables/get.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_variables_get', bytecodeKey: "getVariable", diff --git a/app/blocks/variables/set.js b/app/blocks/variables/set.js index 6d5a840..18b354b 100644 --- a/app/blocks/variables/set.js +++ b/app/blocks/variables/set.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_variables_set', bytecodeKey: "setVariable", @@ -36,6 +37,7 @@ export default { const defaultedValue = value ? JSON.parse(value) + // TODO: is this !== 0 check superfluous? : (value !== 0 && value !== null) && null, blockPayload = JSON.stringify({ setVariable: { diff --git a/app/types.js b/app/types.js index 836a0f0..cb6ad40 100644 --- a/app/types.js +++ b/app/types.js @@ -48,6 +48,7 @@ * @typedef {object} BlockRegeneratorHelpers * @property {function} expressionToBlock * @property {function} arrayToStatements + * @property {function} registerVariable */ /** From c64cac5a7d57d79105ea4addf9f03784f0c24ec7 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:24:55 -0400 Subject: [PATCH 36/74] matchers and logic blocks --- app/blocks/logic/boolean.js | 1 + app/blocks/logic/negate.js | 1 + app/blocks/logic/operation.js | 1 + app/blocks/logic/ternary.js | 2 ++ app/blocks/matchers/matcher_compare.js | 1 + app/blocks/matchers/matcher_text_compare.js | 1 + 6 files changed, 7 insertions(+) diff --git a/app/blocks/logic/boolean.js b/app/blocks/logic/boolean.js index f5de017..b045357 100644 --- a/app/blocks/logic/boolean.js +++ b/app/blocks/logic/boolean.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_logic_boolean', name: "Boolean", diff --git a/app/blocks/logic/negate.js b/app/blocks/logic/negate.js index a39b714..81d50a7 100644 --- a/app/blocks/logic/negate.js +++ b/app/blocks/logic/negate.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_logic_negate', bytecodeKey: "negate", diff --git a/app/blocks/logic/operation.js b/app/blocks/logic/operation.js index a1217a3..1b2eca9 100644 --- a/app/blocks/logic/operation.js +++ b/app/blocks/logic/operation.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_logic_operation', bytecodeKey: "logic", diff --git a/app/blocks/logic/ternary.js b/app/blocks/logic/ternary.js index d79f207..ed77e4e 100644 --- a/app/blocks/logic/ternary.js +++ b/app/blocks/logic/ternary.js @@ -1,8 +1,10 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { disabled: true, type: 'io_logic_ternary', name: "Ternary", colour: 60, + description: "Simpler conditional, inline if/then/else", template: ` if %IF diff --git a/app/blocks/matchers/matcher_compare.js b/app/blocks/matchers/matcher_compare.js index 71551dc..3df3d7b 100644 --- a/app/blocks/matchers/matcher_compare.js +++ b/app/blocks/matchers/matcher_compare.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'matcher_compare', bytecodeKey: "matcherCompare", diff --git a/app/blocks/matchers/matcher_text_compare.js b/app/blocks/matchers/matcher_text_compare.js index 75498d2..5e4ad4c 100644 --- a/app/blocks/matchers/matcher_text_compare.js +++ b/app/blocks/matchers/matcher_text_compare.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'matcher_text_compare', bytecodeKey: "matcherTextCompare", From e363fc409016d4ac853b7a36b318d940efcd6179 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:26:53 -0400 Subject: [PATCH 37/74] all blocks checked --- app/blocks/action/email.js | 1 + app/blocks/action/log.js | 1 + app/blocks/action/sms.js | 1 + app/blocks/action/webhook.js | 1 + app/blocks/controls/if.js | 1 + app/blocks/feed/get_value.js | 1 + app/blocks/feed/set_value.js | 1 + 7 files changed, 7 insertions(+) diff --git a/app/blocks/action/email.js b/app/blocks/action/email.js index fa1b87c..9c56299 100644 --- a/app/blocks/action/email.js +++ b/app/blocks/action/email.js @@ -1,6 +1,7 @@ import { singleLineTemplate, multilineLineTemplate } from "#app/blocks/shadows.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_email", bytecodeKey: "emailAction", diff --git a/app/blocks/action/log.js b/app/blocks/action/log.js index 66fecc4..1761c8d 100644 --- a/app/blocks/action/log.js +++ b/app/blocks/action/log.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_log", bytecodeKey: "logAction", diff --git a/app/blocks/action/sms.js b/app/blocks/action/sms.js index 4aec5ed..c1f17b2 100644 --- a/app/blocks/action/sms.js +++ b/app/blocks/action/sms.js @@ -1,6 +1,7 @@ import { multilineLineTemplate } from "#app/blocks/shadows.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_sms", bytecodeKey: "smsAction", diff --git a/app/blocks/action/webhook.js b/app/blocks/action/webhook.js index bd4b1e6..b32df37 100644 --- a/app/blocks/action/webhook.js +++ b/app/blocks/action/webhook.js @@ -1,6 +1,7 @@ import { multilineLineTemplate } from "#app/blocks/shadows.js" +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_webhook", bytecodeKey: "webhookAction", diff --git a/app/blocks/controls/if.js b/app/blocks/controls/if.js index e76286b..b426634 100644 --- a/app/blocks/controls/if.js +++ b/app/blocks/controls/if.js @@ -1,6 +1,7 @@ import mutator from './if/mutator.js' +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_controls_if', bytecodeKey: "conditional", diff --git a/app/blocks/feed/get_value.js b/app/blocks/feed/get_value.js index a261e43..877b4bf 100644 --- a/app/blocks/feed/get_value.js +++ b/app/blocks/feed/get_value.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "feed_get_value", bytecodeKey: "getFeedValue", diff --git a/app/blocks/feed/set_value.js b/app/blocks/feed/set_value.js index dc514d4..1e02789 100644 --- a/app/blocks/feed/set_value.js +++ b/app/blocks/feed/set_value.js @@ -1,3 +1,4 @@ +/** @type {import('#types').BlockDefinitionRaw} */ export default { type: "feed_set_value", bytecodeKey: "setFeedValue", From 2723b4fbc13732734ec9f24e2f6d322e92b80185 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:33:51 -0400 Subject: [PATCH 38/74] add a todo --- app/blocks/variables/set.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/blocks/variables/set.js b/app/blocks/variables/set.js index 18b354b..1be402a 100644 --- a/app/blocks/variables/set.js +++ b/app/blocks/variables/set.js @@ -35,9 +35,10 @@ export default { value = generator.valueToCode(block, 'VALUE', 0) const + // TODO: this is suspect, try valueToCode() || null, above defaultedValue = value ? JSON.parse(value) - // TODO: is this !== 0 check superfluous? + // @ts-ignore : (value !== 0 && value !== null) && null, blockPayload = JSON.stringify({ setVariable: { From 6183cc6953b66996a75f12dba6143c7744b02b4e Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 1 Aug 2025 16:42:52 -0400 Subject: [PATCH 39/74] couple more jsdoc fixes --- app/regenerators/migrations.js | 2 ++ app/util/fields.js | 6 +----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/regenerators/migrations.js b/app/regenerators/migrations.js index a23659d..06dccd3 100644 --- a/app/regenerators/migrations.js +++ b/app/regenerators/migrations.js @@ -7,6 +7,7 @@ export default { // becomes feed_set_value action_publish: { + /** @type {import('#types').BlockRegenerator} */ json: (blockObject, helpers) => { const payload = blockObject.publishAction @@ -24,6 +25,7 @@ export default { // becomes feed_get_value feed_selector: { + /** @type {import('#types').BlockRegenerator} */ json: blockObject => { const payload = blockObject.feed diff --git a/app/util/fields.js b/app/util/fields.js index b4a5f5a..153e5c2 100644 --- a/app/util/fields.js +++ b/app/util/fields.js @@ -9,10 +9,6 @@ const makeUpTo = (from, target, step) => { return range(from, target, step) } -const stringifyOptions = (options) => { - -} - /** * * @param {Object} options @@ -22,7 +18,7 @@ const stringifyOptions = (options) => { * @param {number} [options.step] * @param {boolean} [options.reverse] * @param {Function} [options.valueFunc] -* @returns {Array} +* @returns {[string,string][]} */ export const makeOptions = (options = {}) => { let optionValues From 0685fc9bfc3298dc38774134a98df5a8b50b89e7 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Sat, 2 Aug 2025 00:31:26 -0400 Subject: [PATCH 40/74] fix tests --- test/app/blocks/snapshots/block_snapshots_test.js.snapshot | 2 +- test/src/integration/exporters_test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot index ffaf3a0..53a075f 100644 --- a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot +++ b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot @@ -61,7 +61,7 @@ exports[`Block Snapshots > Blockly JSON > action_root 1`] = ` "inputsInline": false, "type": "action_root", "colour": "0", - "tooltip": "Add Triggers to determine when this Action runs.", + "tooltip": "Add Triggers to determine when this Action runs. Add Actions to determine what this Action does.", "message0": "Triggers: %1", "args0": [ { diff --git a/test/src/integration/exporters_test.js b/test/src/integration/exporters_test.js index 6d76fa4..0d053e1 100644 --- a/test/src/integration/exporters_test.js +++ b/test/src/integration/exporters_test.js @@ -81,8 +81,8 @@ describe("Exporting Blockly Files", () => { assert.exists(trunkObject) assert.hasAllKeys(trunkObject, ["type", "inputs"]) assert.hasAllKeys(trunkObject.inputs, ["SUBJECT", "PREDICATE"]) - assert.hasAllKeys(trunkObject.inputs.SUBJECT, ["shadow"]) - assert.hasAllKeys(trunkObject.inputs.PREDICATE, ["shadow"]) + assert.hasAllKeys(trunkObject.inputs.SUBJECT, ["shadow", "block"]) + assert.hasAllKeys(trunkObject.inputs.PREDICATE, ["shadow", "block"]) }) it("export other block trees by name", { skip: true }, () => { From d1e17415394dc5714fdeae26e97a25db8c565d94 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Sat, 2 Aug 2025 00:32:45 -0400 Subject: [PATCH 41/74] bump blockly to last 10 version --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f98dbc..fefda30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@blockly/theme-modern": "^5.0.5", - "blockly": "^10.1.3", + "blockly": "^10.4.3", "lodash-es": "^4.17.21" }, "devDependencies": { @@ -2214,9 +2214,10 @@ "license": "Apache-2.0" }, "node_modules/blockly": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.1.3.tgz", - "integrity": "sha512-3sgeKobXiU8fpj6Kgn7rlVggus+T77bohYhY72sivoL7nzH/6G/NBFC6uuQJUlzCxjsWXDh5QVEN/rfnAaL0mw==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz", + "integrity": "sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug==", + "license": "Apache-2.0", "dependencies": { "jsdom": "22.1.0" } diff --git a/package.json b/package.json index fcf069e..e30cf75 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "@blockly/theme-modern": "^5.0.5", - "blockly": "^10.1.3", + "blockly": "^10.4.3", "lodash-es": "^4.17.21" } } From 7be119deb7ded50fdea6eebf56fd5ae8e3e77a6f Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 12:39:37 -0500 Subject: [PATCH 42/74] Update map.js --- app/blocks/math/map.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/blocks/math/map.js b/app/blocks/math/map.js index 8a3264c..e236eb3 100644 --- a/app/blocks/math/map.js +++ b/app/blocks/math/map.js @@ -5,27 +5,25 @@ export default { name: "Map", colour: 120, description: "Scale a value from one range of numbers to another", - connections: { mode: "value", output: "number", }, - template: ` Map Value: %VALUE From: %FROM_RANGE To: %TO_RANGE `, - inputs: { VALUE: { + description: "The number to scale from the original range to the target range.", check: "expression", bytecodeProperty: "value", shadow: 'io_math_number' }, - FROM_RANGE: { + description: "The original range that the input value comes from (e.g., sensor readings from 0 to 1023).", check: 'range', bytecodeProperty: "from", shadow: { @@ -42,8 +40,8 @@ export default { } } }, - TO_RANGE: { + description: "The target range to scale the value to (e.g., convert to 0.0-1.0 for percentages).", check: 'range', bytecodeProperty: "to", shadow: { @@ -61,7 +59,6 @@ export default { } }, }, - generators: { json: (block, generator) => { const @@ -69,11 +66,9 @@ export default { from = JSON.parse(generator.valueToCode(block, 'FROM_RANGE', 0)), to = JSON.parse(generator.valueToCode(block, 'TO_RANGE', 0)), payload = { mapValue: { value, from, to }} - return [ JSON.stringify(payload), 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -111,7 +106,6 @@ export default { } }), } - return { type: 'math_map', inputs } } } From 9b9cb4503e2c3747ba9cd25f28f59bb7da829587 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:20:08 -0500 Subject: [PATCH 43/74] Update constrain.js --- app/blocks/math/constrain.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/blocks/math/constrain.js b/app/blocks/math/constrain.js index ef5bbad..2715d6c 100644 --- a/app/blocks/math/constrain.js +++ b/app/blocks/math/constrain.js @@ -5,24 +5,22 @@ export default { name: "Constrain", colour: 120, description: "Constrain a given number to fall within a given range.", - connections: { mode: "value", output: "number", }, - template: ` Constrain %VALUE to %RANGE `, - inputs: { VALUE: { + description: "The number to constrain within the specified range limits.", check: "expression", shadow: "io_math_number" }, - RANGE: { + description: "The minimum and maximum bounds to limit the value within (values outside this range will be clamped to the nearest boundary).", check: 'range', shadow: { type: "math_range", @@ -39,18 +37,15 @@ export default { } } }, - generators: { json: (block, generator) => { const value = JSON.parse(generator.valueToCode(block, 'VALUE', 0)), range = JSON.parse(generator.valueToCode(block, 'RANGE', 0)), payload = { constrain: { value, range } } - return [ JSON.stringify(payload), 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -73,7 +68,6 @@ export default { } }), } - return { type: 'io_math_constrain', inputs } } } From 3c7b38d587b3fa69db5fecc3c1e40e4340dc226a Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:25:32 -0500 Subject: [PATCH 44/74] Update on_schedule.js --- app/blocks/triggers/on_schedule.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/blocks/triggers/on_schedule.js b/app/blocks/triggers/on_schedule.js index 0d4ef52..7505054 100644 --- a/app/blocks/triggers/on_schedule.js +++ b/app/blocks/triggers/on_schedule.js @@ -4,7 +4,7 @@ export default { bytecodeKey: "onSchedule", name: "Schedule", colour: 30, - description: "A schedule to run the action, from every minute to once a year.", + description: "Create a time-based trigger that runs actions automatically at specific times, dates, or intervals (e.g., 'Every weekday at 9 AM', 'Every 15 minutes', or 'On the 1st of every month').", connections: { mode: "statement", @@ -22,21 +22,25 @@ export default { inputs: { MONTH: { + description: "Which months to run the schedule (e.g., 'All months', 'January only', or 'Every 3 months starting in March').", check: "cron_month", block: "all_months", }, DAY: { + description: "Which days to run the schedule - either specific days of the month (1-31) or days of the week (Monday, Tuesday, etc.).", check: "cron_day", block: "all_days" }, HOUR: { + description: "Which hours to run the schedule (0-23 in 24-hour format, e.g., '14' for 2 PM or 'Every 4 hours').", check: "cron_hour", block: "all_hours" }, MINUTE: { + description: "Which minutes to run the schedule (0-59, e.g., 'At 15 minutes past the hour' or 'Every 30 minutes').", check: "cron_minute", block: { type: "one_minute", From 51def7dafcc155187102a7d8dc02a253c5d1ee62 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:34:13 -0500 Subject: [PATCH 45/74] Update root.js --- app/blocks/root/root.js | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/app/blocks/root/root.js b/app/blocks/root/root.js index 04b22de..7791581 100644 --- a/app/blocks/root/root.js +++ b/app/blocks/root/root.js @@ -1,46 +1,37 @@ import mutator from './action_settings/mutator.js' - - /** @type {import('#types').BlockDefinitionRaw} */ export default { type: "action_root", name: "Root", colour: "0", - description: "Add Triggers to determine when this Action runs. Add Actions to determine what this Action does.", - + description: "The foundation of every Adafruit IO Action. Connect Triggers (like 'when temperature > 80°F' or 'every morning at 8 AM') to define when your Action runs, then attach Action blocks (like 'send email', 'publish to feed', or 'if/then logic') to define what happens when triggered.", connections: {}, - mutator, - template: ` Triggers: |LEFT %TRIGGERS - Actions: |LEFT %EXPRESSIONS \u3164 `, - inputs: { TRIGGERS: { + description: "Connect trigger blocks here to define WHEN your Action should run. Choose from Reactive triggers (respond to feed updates), Scheduled triggers (run at specific times), or Timer triggers (delayed responses). Multiple triggers can be chained together.", type: 'statement', check: 'trigger' }, - EXPRESSIONS: { + description: "Connect action blocks here to define WHAT happens when your triggers activate. This can include sending emails, publishing values to feeds, conditional if/then logic, mathematical operations, or webhook calls. Actions execute in sequence from top to bottom.", type: 'statement', check: 'expression' } }, - generators: { json: (block, generator) => { const parseStatementToCodeAsJson = statementInputName => { let expressions = [] - try { let expressionsJson = generator.statementToCode(block, statementInputName) - try { expressions = JSON.parse(`[${expressionsJson}]`) } catch(e) { @@ -51,10 +42,8 @@ export default { console.error(`Error calling statementToCode on root input ${statementInputName}:`) console.error(e) } - return expressions } - const // @ts-ignore seconds = block.delaySeconds, @@ -63,7 +52,6 @@ export default { delay = (seconds > 0) ? { seconds, mode } : undefined - return JSON.stringify({ version: "1.0.0-beta.1", settings: { delay }, @@ -72,11 +60,9 @@ export default { }, null, 2) } }, - regenerators: { json: (blockObject, helpers) => { const { triggers, expressions, settings } = blockObject - return { type: "action_root", movable: false, From 63be361898036b80a4d3a598c17028ef2656a91e Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:40:06 -0500 Subject: [PATCH 46/74] Update set.js --- app/blocks/variables/set.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/blocks/variables/set.js b/app/blocks/variables/set.js index 1be402a..dc574c2 100644 --- a/app/blocks/variables/set.js +++ b/app/blocks/variables/set.js @@ -5,35 +5,31 @@ export default { name: "Set Variable", inputsInline: true, colour: 240, - description: "Set a variable to a value", - + description: "Store a value in a named variable for later use in your Action. Variables let you remember feed values, calculation results, or any data to use in subsequent action blocks.", connections: { mode: 'statement', output: "expression", next: "expression", }, - template: "Set variable %VAR = %VALUE", - inputs: { VALUE: { + description: "The value to store in the variable. This can be text, numbers, feed values, calculation results, or data from other blocks.", check: "expression", shadow: "io_text", } }, - fields: { VAR: { + description: "Choose or create a variable name to store the value. Use descriptive names like 'temperature_reading' or 'user_count' to make your Actions easier to understand.", type: 'field_variable' } }, - generators: { json: (block, generator) => { const variableName = block.getField('VAR').getText(), value = generator.valueToCode(block, 'VALUE', 0) - const // TODO: this is suspect, try valueToCode() || null, above defaultedValue = value @@ -46,17 +42,14 @@ export default { value: defaultedValue } }) - return blockPayload } }, - regenerators: { json: (blockObject, helpers) => { const { name, value } = blockObject.setVariable, id = helpers.registerVariable(name) - return { type: "io_variables_set", fields: { From b24bed02ed91bc61346a0afe5a4565eb266f95e2 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:42:24 -0500 Subject: [PATCH 47/74] Update get.js --- app/blocks/variables/get.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/blocks/variables/get.js b/app/blocks/variables/get.js index ffcafb8..44e59d4 100644 --- a/app/blocks/variables/get.js +++ b/app/blocks/variables/get.js @@ -4,21 +4,18 @@ export default { bytecodeKey: "getVariable", name: "Get Variable", colour: 240, - description: "Get the value previously assigned to a variable.", - + description: "Retrieve the value stored in a variable that was previously set using a Set Variable block. Use this to access stored feed values, calculation results, or any data saved earlier in your Action.", connections: { mode: 'value', output: "expression", }, - template: "Get variable %VAR", - fields: { VAR: { + description: "Select the variable name whose value you want to retrieve. The variable must have been created and assigned a value earlier in your Action using a Set Variable block.", type: 'field_variable' } }, - generators: { json: block => { const @@ -28,17 +25,14 @@ export default { name } }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const { name } = blockObject.getVariable, id = helpers.registerVariable(name) - return { type: "io_variables_get", fields: { From 022545048987d0abce640d0d7373e94b14e6ac70 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:50:58 -0500 Subject: [PATCH 48/74] Update when_data_matching_state.js --- .../triggers/when_data_matching_state.js | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/app/blocks/triggers/when_data_matching_state.js b/app/blocks/triggers/when_data_matching_state.js index c901ab3..0846475 100644 --- a/app/blocks/triggers/when_data_matching_state.js +++ b/app/blocks/triggers/when_data_matching_state.js @@ -5,22 +5,18 @@ export default { name: "Data Match Changing", colour: 30, inputsInline: true, - description: "Run this Action when the specified Feed receives a data point that compares to its previous data point in the specified way.", - + description: "Advanced trigger that watches for changes in how your feed data matches a condition over time. Unlike basic triggers that just check if data equals a value, this compares the current data point with the previous one to detect when conditions START being true, STOP being true, or CONTINUE being true. Perfect for detecting state changes like 'temperature just went above 80°' or 'door just closed after being open'.", connections: { mode: "statement", output: "trigger", next: "trigger" }, - mixins: [ 'replaceDropdownOptions' ], extensions: [ "populateFeedDropdown" ], - template: "When %FEED_KEY gets data that %MATCH_STATE matching %MATCHER", - inputs: { MATCHER: { - description: "Attach a Matcher block to apply to both data points to make the specified comparison", + description: "The condition to test against both the previous and current data points. For example: 'equals 1', 'greater than 80', or 'contains \"open\"'. This same condition is applied to both the old and new values to determine the state change.", check: 'matcher', shadow: { type: 'matcher_compare', @@ -30,27 +26,24 @@ export default { } } }, - fields: { FEED_KEY: { - description: "Select a Feed to watch for new data", + description: "Choose which feed to monitor for incoming data. Each new data point will be compared against the previous one using your matcher condition.", align: "LEFT", options: [ [ "Loading Feeds...", ""] ] }, - MATCH_STATE: { - description: "Select the kind of change to watch for:", + description: "Select what kind of change pattern you want to detect between the previous and current data points:", options: [ - ["starts", "starts", "the last data point DID NOT match, but this one DOES"], - ["stops", "stops", "the last data point DID match, but this one DOES NOT"], - ["keeps", "keeps", "both data points DO match"], - ["keeps not", "avoids", "both data points DO NOT match"], + ["starts", "starts", "Triggers when the condition becomes true for the first time (previous data didn't match, but new data does). Example: temperature was below 80°, now it's above 80°."], + ["stops", "stops", "Triggers when the condition stops being true (previous data matched, but new data doesn't). Example: door was open, now it's closed."], + ["keeps", "keeps", "Triggers when the condition remains true (both previous and current data match). Example: temperature stays above 80° for multiple readings."], + ["keeps not", "avoids", "Triggers when the condition remains false (both previous and current data don't match). Example: temperature stays below 80° for multiple readings."], ] } }, - generators: { json: (block, generator) => { const @@ -62,15 +55,12 @@ export default { feed, matcher, state } }) - return payload } }, - regenerators: { json: (blockObject, helpers) => { const { feed, matcher, state } = blockObject.whenDataMatchStateChanged - return { type: "when_data_matching_state", fields: { From 2a0849561677f977ce8f964857d4ef6be91cc04f Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 7 Aug 2025 15:53:31 -0500 Subject: [PATCH 49/74] Update when_data_matching.js --- app/blocks/triggers/when_data_matching.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/blocks/triggers/when_data_matching.js b/app/blocks/triggers/when_data_matching.js index 61139ba..36227ee 100644 --- a/app/blocks/triggers/when_data_matching.js +++ b/app/blocks/triggers/when_data_matching.js @@ -5,22 +5,18 @@ export default { name: "Data Matching", colour: 30, inputsInline: true, - description: "Run this Action when the specified Feed receives data that matches the specified condition.", - + description: "The most common trigger type - runs your Action immediately whenever new data arrives at a feed that meets your specified condition. Perfect for real-time responses like 'send alert when temperature exceeds 85°F', 'turn on lights when motion detected', or 'notify me when battery drops below 20%'. This trigger fires every single time the condition is met.", connections: { mode: "statement", output: "trigger", next: "trigger" }, - mixins: ['replaceDropdownOptions'], extensions: [ "populateFeedDropdown" ], - template: "When %FEED_KEY gets data matching: %MATCHER", - inputs: { MATCHER: { - description: "Attach a Matcher block to compare against new Feed values", + description: "Define the condition that incoming data must meet to trigger this Action. Examples: 'equals 1' (for button presses), 'greater than 80' (for temperature alerts), 'contains \"motion\"' (for text matching), or 'any value' (triggers on every data point regardless of value).", check: 'matcher', shadow: { type: 'matcher_compare', @@ -30,16 +26,14 @@ export default { } } }, - fields: { FEED_KEY: { - description: "Select the Feed to watch for new data.", + description: "Choose which feed to monitor for new incoming data. Every time fresh data arrives at this feed, it will be tested against your matcher condition.", options: [ [ "Loading Feeds...", ""] ] } }, - generators: { json: (block, generator) => { const @@ -50,15 +44,12 @@ export default { feed, matcher } }) - return payload } }, - regenerators: { json: (blockObject, helpers) => { const payload = blockObject.whenDataMatching - return { type: "when_data_matching", fields: { From ad0e464a7d51b09e99f4fd97a1f0d07d1b4fd756 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Fri, 8 Aug 2025 10:21:46 -0500 Subject: [PATCH 50/74] Update when_data.js --- app/blocks/triggers/when_data.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/app/blocks/triggers/when_data.js b/app/blocks/triggers/when_data.js index a43b420..0ff19e5 100644 --- a/app/blocks/triggers/when_data.js +++ b/app/blocks/triggers/when_data.js @@ -5,28 +5,23 @@ export default { name: "Any Data", colour: 30, inputsInline: true, - description: "Run this action when a Feed receives a new data point.", - + description: "The simplest trigger - runs your Action every single time ANY new data arrives at a feed, regardless of what the value is. Perfect for logging all activity ('record every sensor reading'), acknowledging data receipt ('send confirmation for every message'), or triggering workflows that need to process all incoming data. No conditions, no filtering - just pure data arrival detection.", connections: { mode: "statement", output: "trigger", next: "trigger" }, - mixins: ['replaceDropdownOptions'], extensions: ['populateFeedDropdown'], - template: "When %FEED_KEY gets any data |LEFT", - fields: { FEED_KEY: { - description: "the Feed to watch for new data points", + description: "Choose which feed to monitor for activity. Every single data point that arrives at this feed will trigger your Action - whether it's a number, text, true/false, or any other value type.", options: [ [ "Loading Feeds...", ""] ] } }, - generators: { json: block => { const @@ -34,15 +29,12 @@ export default { payload = JSON.stringify({ whenData: { feed } }) - return payload } }, - regenerators: { json: blockObject => { const payload = blockObject.whenData - return { type: "when_data", fields: { From 5112a1472fc66c6007feff96576d33a20819afd8 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Fri, 8 Aug 2025 10:25:47 -0500 Subject: [PATCH 51/74] Update on_schedule.js --- app/blocks/triggers/on_schedule.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/blocks/triggers/on_schedule.js b/app/blocks/triggers/on_schedule.js index 7505054..ed7248b 100644 --- a/app/blocks/triggers/on_schedule.js +++ b/app/blocks/triggers/on_schedule.js @@ -4,7 +4,7 @@ export default { bytecodeKey: "onSchedule", name: "Schedule", colour: 30, - description: "Create a time-based trigger that runs actions automatically at specific times, dates, or intervals (e.g., 'Every weekday at 9 AM', 'Every 15 minutes', or 'On the 1st of every month').", + description: "Create powerful time-based automation that runs your Actions on a schedule - from simple daily reminders to complex patterns like 'every 15 minutes during weekdays' or 'first Monday of each quarter'. Works like a smart alarm clock for your IoT devices, automatically triggering actions without any manual intervention. Perfect for turning lights on/off, sending regular reports, or controlling devices based on time patterns.", connections: { mode: "statement", @@ -22,25 +22,25 @@ export default { inputs: { MONTH: { - description: "Which months to run the schedule (e.g., 'All months', 'January only', or 'Every 3 months starting in March').", + description: "Choose which months to run the schedule. Options include 'All months' for year-round automation, specific months like 'January only' for seasonal control, or patterns like 'Every 3 months starting in March' for quarterly tasks.", check: "cron_month", block: "all_months", }, DAY: { - description: "Which days to run the schedule - either specific days of the month (1-31) or days of the week (Monday, Tuesday, etc.).", + description: "Select which days to trigger your schedule. You can choose specific calendar dates (1-31) for monthly events like 'every 15th', or days of the week (Monday-Sunday) for weekly patterns like 'every Tuesday and Thursday'. Cannot mix both date and weekday options.", check: "cron_day", block: "all_days" }, HOUR: { - description: "Which hours to run the schedule (0-23 in 24-hour format, e.g., '14' for 2 PM or 'Every 4 hours').", + description: "Set the hour(s) when your Action should run, using 24-hour format (0-23). Examples: '9' for 9 AM, '14' for 2 PM, '21' for 9 PM, or 'Every 4 hours' for repeated intervals throughout the day.", check: "cron_hour", block: "all_hours" }, MINUTE: { - description: "Which minutes to run the schedule (0-59, e.g., 'At 15 minutes past the hour' or 'Every 30 minutes').", + description: "Specify the exact minute(s) within the hour to run your Action (0-59). Examples: '0' for on-the-hour (like 9:00), '15' for quarter-past (like 9:15), '30' for half-past, or 'Every 15 minutes' for frequent automation.", check: "cron_minute", block: { type: "one_minute", From 728d275cc2c7931779b07f58d644b6bda1f6aff8 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Fri, 8 Aug 2025 10:30:05 -0500 Subject: [PATCH 52/74] Update matcher_text_compare.js --- app/blocks/matchers/matcher_text_compare.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/app/blocks/matchers/matcher_text_compare.js b/app/blocks/matchers/matcher_text_compare.js index 5e4ad4c..444ebf7 100644 --- a/app/blocks/matchers/matcher_text_compare.js +++ b/app/blocks/matchers/matcher_text_compare.js @@ -5,48 +5,40 @@ export default { name: "Compare Text Matcher", colour: 180, inputsInline: true, - description: "Compare the new feed value with text for equality, inequality, or inclusion.", - + description: "Compare text-based feed data using smart text matching. Perfect for triggers based on status messages ('door opened', 'motion detected'), device states ('online', 'offline'), or any text-based sensor data. Works with exact matches, exclusions, or partial text detection within longer messages.", connections: { mode: 'value', output: 'matcher' }, - template: "%OP %B", - inputs: { B: { - description: "The string to compare with the Feed value.", + description: "The text to compare against your feed data. Examples: 'open' to detect door status, 'motion' for PIR sensors, 'online' for device connectivity, or any specific word/phrase you're monitoring for.", check: "expression", shadow: 'io_text' } }, - fields: { OP: { - description: "Select what kind of comparison to do:", + description: "Choose how to compare the incoming feed data with your text:", options: [ - ['=', 'EQ', "Returns true if the Feed value and text are the same."], - ['\u2260', 'NEQ', "Returns true if the Feed value and text are not the same."], - ['includes', 'INC', "Returns true if the Feed value includes the text."], + ['=', 'EQ', "Exact match: Feed value must be exactly the same as your text (e.g., feed='online' matches text='online', but 'ONLINE' or 'device online' would not match)."], + ['\u2260', 'NEQ', "Not equal: Feed value must be different from your text (e.g., useful for 'not offline' conditions or excluding specific status messages)."], + ['includes', 'INC', "Contains: Feed value includes your text anywhere within it (e.g., feed='motion detected at 3pm' would match text='motion', perfect for parsing longer status messages)."], ] } }, - generators: { json: (block, generator) => { const comparator = block.getFieldValue('OP'), rightExp = generator.valueToCode(block, 'B', 0) || null, - blockPayload = JSON.stringify({ matcherTextCompare: { comparator: comparator?.toLowerCase() || null, right: JSON.parse(rightExp), }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -57,7 +49,6 @@ export default { inputs = { B: helpers.expressionToBlock(right, { shadow: "io_text" }), } - return { type: 'matcher_text_compare', fields, inputs } } } From 21eb6a27440264856827e5958fb2e6936ce45e7b Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 14 Aug 2025 14:38:11 -0500 Subject: [PATCH 53/74] Update matcher_compare.js --- app/blocks/matchers/matcher_compare.js | 27 +++++++++----------------- 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/app/blocks/matchers/matcher_compare.js b/app/blocks/matchers/matcher_compare.js index 3df3d7b..4b116fb 100644 --- a/app/blocks/matchers/matcher_compare.js +++ b/app/blocks/matchers/matcher_compare.js @@ -5,51 +5,43 @@ export default { name: "Compare Numbers Matcher", colour: 224, inputsInline: true, - description: "Numerically compare the new Feed value with another number.", - + description: "Create smart triggers based on numerical sensor data and thresholds. Perfect for temperature alerts ('notify when above 80°F'), battery monitoring ('warn when below 20%'), humidity control ('turn on fan when over 60%'), or any sensor-based automation that depends on numerical comparisons.", connections: { mode: 'value', output: 'matcher' }, - template: "%OP %B", - fields: { OP: { - description: "Select a comparison to perform", + description: "Choose how to compare your feed's numerical data:", options: [ - ['=', 'EQ', "True if the two numbers are equal"], - ['\u2260', 'NEQ', "True if the two numbers are not equal"], - ['\u200F<', 'LT', "True if the Feed value is less than number B"], - ['\u200F\u2264', 'LTE', "True if the Feed value is less than or equal to number B"], - ['\u200F>', 'GT', "True if the Feed value is greater than number B"], - ['\u200F\u2265', 'GTE', "True if the Feed value is greater than or equal to number B"], + ['=', 'EQ', "Exactly equal: Triggers when feed value equals your number (e.g., button state = 1, exact temperature reading = 72.5°F). Best for digital switches and precise measurements."], + ['\u2260', 'NEQ', "Not equal: Triggers when feed value is anything other than your number (e.g., not zero, sensor reading changed from baseline). Useful for detecting changes or non-specific states."], + ['\u200F<', 'LT', "Less than: Triggers when feed value drops below your threshold (e.g., temperature < 60°F for heating alerts, battery < 15% for low power warnings)."], + ['\u200F\u2264', 'LTE', "Less than or equal: Triggers when feed value is at or below threshold (e.g., humidity ≤ 30% for dry air alerts, speed ≤ 0 for stopped condition)."], + ['\u200F>', 'GT', "Greater than: Triggers when feed value exceeds your threshold (e.g., temperature > 85°F for cooling alerts, motion count > 10 for high activity)."], + ['\u200F\u2265', 'GTE', "Greater than or equal: Triggers when feed value meets or exceeds threshold (e.g., pressure ≥ 1000 hPa for weather tracking, light level ≥ 500 for daylight detection)."], ] } }, - inputs: { B: { - description: "The value to compare with the Feed value.", + description: "Set your comparison threshold or target value. Examples: 80 for temperature alerts, 20 for battery percentage warnings, 1000 for pressure readings, or any numerical value that's meaningful for your sensor data.", check: "expression", shadow: 'io_math_number' } }, - generators: { json: (block, generator) => { const comparator = block.getFieldValue('OP'), rightExp = generator.valueToCode(block, 'B', 0) || 'null', - blockPayload = JSON.stringify({ matcherCompare: { comparator: comparator?.toLowerCase() || null, right: JSON.parse(rightExp), }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -60,7 +52,6 @@ export default { inputs = { B: helpers.expressionToBlock(right, { shadow: 'io_math_number' }), } - return { type: 'matcher_compare', fields, inputs } } } From c5e2a904c6933b93fafe02f0d83e4b64ccd5fda2 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 14 Aug 2025 14:39:54 -0500 Subject: [PATCH 54/74] Update compare.js --- app/blocks/text/compare.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/app/blocks/text/compare.js b/app/blocks/text/compare.js index d1f95a8..6c5d8ed 100644 --- a/app/blocks/text/compare.js +++ b/app/blocks/text/compare.js @@ -6,47 +6,40 @@ export default { colour: 180, inputsInline: true, primaryCategory: "Logic", - description: "Compare two chunks of text for equality, inequality, or inclusion.", - + description: "Compare any two pieces of text or data to build conditional logic in your Actions. Perfect for creating if/then statements like 'if device status equals online', 'if user name is not guest', or 'if error message contains timeout'. Works with feed values, variables, user input, or any text-based data.", connections: { mode: "value", output: "expression", }, - template: `%A %OP %B`, - inputs: { A: { - description: "The left side of the comparison. Will be coerced to a string", + description: "The first value to compare (left side). Can be feed data, variable content, user input, or any text. Numbers and other data types will be automatically converted to text for comparison.", check: "expression", shadow: 'io_text' }, - B: { - description: "The right side of the comparison. Will be coerced to a string", + description: "The second value to compare (right side). Can be literal text like 'online', variable content, feed values, or any data you want to compare against the first input. Also automatically converted to text.", check: "expression", shadow: 'io_text' }, }, - fields: { OP: { - description: "Select what kind of comparison to do:", + description: "Choose how to compare the two text inputs:", options: [ - ['=', 'EQ', "Returns true if the the inputs are the same."], - ['\u2260', 'NEQ', "Returns true if the inputs not the same."], - ['includes', 'INC', "Returns true if input A includes input B."], + ['=', 'EQ', "Exact match: Returns true only if both inputs are identical (e.g., 'online' = 'online' is true, but 'Online' = 'online' is false due to case sensitivity)."], + ['\u2260', 'NEQ', "Not equal: Returns true if the inputs are different in any way (e.g., useful for 'if status is not offline' or 'if username is not empty' conditions)."], + ['includes', 'INC', "Contains: Returns true if the first input contains the second input anywhere within it (e.g., 'sensor error timeout' includes 'error' would be true)."], ] } }, - generators: { json: (block, generator) => { const comparator = block.getFieldValue('OP'), leftExp = generator.valueToCode(block, 'A', 0) || null, rightExp = generator.valueToCode(block, 'B', 0) || null, - blockPayload = JSON.stringify({ textCompare: { left: JSON.parse(leftExp), @@ -54,11 +47,9 @@ export default { right: JSON.parse(rightExp), }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -70,7 +61,6 @@ export default { A: helpers.expressionToBlock(left, { shadow: "io_text" }), B: helpers.expressionToBlock(right, { shadow: "io_text" }), } - return { type: 'text_compare', fields, inputs } } } From 1b44c72e043dfb1a0808473894c469b76d8e3d83 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 14 Aug 2025 14:42:52 -0500 Subject: [PATCH 55/74] Update compare.js --- app/blocks/math/compare.js | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/app/blocks/math/compare.js b/app/blocks/math/compare.js index 4578ce7..fde352c 100644 --- a/app/blocks/math/compare.js +++ b/app/blocks/math/compare.js @@ -6,50 +6,43 @@ export default { colour: 120, inputsInline: true, primaryCategory: "Math", - description: "Numerically compare two given values using the selected math operation.", - + description: "Build mathematical conditions by comparing any two numerical values in your Action logic. Perfect for creating if/then statements like 'if temperature is greater than target temp', 'if battery level equals low threshold', or 'if sensor reading is between two values'. Works with feed data, variables, calculations, or any numerical inputs.", connections: { mode: "value", output: "expression", }, - template: `%A %OP %B`, - inputs: { A: { - description: "The left side of the comparison. Will be coerced to a number", + description: "The first number to compare (left side). Can be sensor data, variable values, calculation results, or any numerical input. Text and other data types will be automatically converted to numbers where possible.", check: "expression", shadow: 'io_math_number' }, - B: { - description: "The right side of the comparison. Will be coerced to a number", + description: "The second number to compare (right side). Can be threshold values, target numbers, other sensor readings, or any numerical data you want to compare against the first input. Also automatically converted to numbers.", check: "expression", shadow: 'io_math_number' }, }, - fields: { OP: { - description: "The mathematical comparison to use.", + description: "Choose the mathematical relationship to test between your two numbers:", options: [ - ['=', 'EQ', "True if the two numbers are equal"], - ['\u2260', 'NEQ', "True if the two numbers are not equal"], - ['\u200F<', 'LT', "True if number A is less than number B"], - ['\u200F\u2264', 'LTE', "True if number A is less than or equal to number B"], - ['\u200F>', 'GT', "True if number A is greater than number B"], - ['\u200F\u2265', 'GTE', "True if number A is greater than or equal to number B"], + ['=', 'EQ', "Exactly equal: True when both numbers are precisely the same (e.g., temperature = 72.0, useful for exact target matching or digital sensor states like 0/1)."], + ['\u2260', 'NEQ', "Not equal: True when the numbers are different by any amount (e.g., sensor reading ≠ error value, useful for detecting changes or valid readings)."], + ['\u200F<', 'LT', "Less than: True when first number is smaller (e.g., temperature < comfort threshold, battery < critical level for alerts)."], + ['\u200F\u2264', 'LTE', "Less than or equal: True when first number is smaller or exactly equal (e.g., humidity ≤ 40% for dry conditions, speed ≤ 0 for stopped state)."], + ['\u200F>', 'GT', "Greater than: True when first number is larger (e.g., pressure > storm threshold, light level > daylight minimum for automation)."], + ['\u200F\u2265', 'GTE', "Greater than or equal: True when first number is larger or exactly equal (e.g., temperature ≥ 75°F for cooling, count ≥ 10 for bulk processing)."], ] } }, - generators: { json: (block, generator) => { const comparator = block.getFieldValue('OP'), leftExp = generator.valueToCode(block, 'A', 0) || 'null', rightExp = generator.valueToCode(block, 'B', 0) || 'null', - blockPayload = JSON.stringify({ compare: { left: JSON.parse(leftExp), @@ -57,11 +50,9 @@ export default { right: JSON.parse(rightExp), }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -73,7 +64,6 @@ export default { A: helpers.expressionToBlock(left, { shadow: 'io_math_number' }), B: helpers.expressionToBlock(right, { shadow: 'io_math_number' }), } - return { type: 'io_logic_compare', fields, inputs } } } From c6571763a08e2d1466c03be330849afcbaa82d04 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 14 Aug 2025 14:44:46 -0500 Subject: [PATCH 56/74] Update operation.js --- app/blocks/logic/operation.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/app/blocks/logic/operation.js b/app/blocks/logic/operation.js index 1b2eca9..580dbc0 100644 --- a/app/blocks/logic/operation.js +++ b/app/blocks/logic/operation.js @@ -5,46 +5,39 @@ export default { name: "Logic Operation", inputsInline: true, colour: 60, - description: "Perform the specifed boolean logic operation on two operands.", - + description: "Combine multiple conditions to create sophisticated decision logic in your Actions. Perfect for complex automation like 'if temperature is high AND humidity is low', 'if motion detected OR door opened', or any scenario where you need multiple criteria to work together. Essential for building smart, multi-factor IoT control systems.", connections: { mode: "value", output: "expression", }, - template: `%A %OP %B`, - inputs: { A: { - description: "A block diagram that will be resolved to a truthy/falsy value", + description: "The first condition to evaluate (left side). Connect comparison blocks, sensor checks, or any logic that results in true/false. Examples: 'temperature > 80', 'door equals open', or 'battery < 20%'.", check: "expression", shadow: 'io_logic_boolean' }, - B: { - description: "A block diagram that will be resolved to a truthy/falsy value", + description: "The second condition to evaluate (right side). Connect another comparison, sensor check, or boolean logic. Examples: 'humidity > 60%', 'motion detected', or 'time between 9 AM and 5 PM'.", check: "expression", shadow: 'io_logic_boolean' } }, - fields: { OP: { - description: "Select the logic operation to perform on the inputs:", + description: "Choose how to combine your two conditions:", options: [ - ['and', 'AND', "Resolve `true` if both operands are true, otherwise `false`."], - ['or', 'OR', "Resolve `true` if either or both operands are true, otherwise `false`."], + ['and', 'AND', "Both conditions must be true: Returns true only when BOTH inputs are true (e.g., 'temperature > 80 AND humidity < 30' for hot and dry conditions requiring both criteria simultaneously)."], + ['or', 'OR', "Either condition can be true: Returns true when AT LEAST ONE input is true (e.g., 'motion detected OR door opened' triggers on any activity, 'battery < 10% OR offline > 1 hour' for multiple alert conditions)."], ] } }, - generators: { json: (block, generator) => { const operator = block.getFieldValue('OP'), leftExp = generator.valueToCode(block, 'A', 0) || null, rightExp = generator.valueToCode(block, 'B', 0) || null, - blockPayload = JSON.stringify({ logic: { left: JSON.parse(leftExp), @@ -52,11 +45,9 @@ export default { right: JSON.parse(rightExp), }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -64,11 +55,10 @@ export default { fields = { OP: comparator?.toUpperCase() }, - inputs = { + inputs: { A: helpers.expressionToBlock(left, { shadow: 'io_logic_boolean' }), B: helpers.expressionToBlock(right, { shadow: 'io_logic_boolean' }), } - return { type: 'io_logic_operation', fields, inputs } } } From 53545ba248344be8d02423df30ad242ec6f71b33 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Thu, 14 Aug 2025 14:48:08 -0500 Subject: [PATCH 57/74] Update negate.js --- app/blocks/logic/negate.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/blocks/logic/negate.js b/app/blocks/logic/negate.js index 81d50a7..4d9e640 100644 --- a/app/blocks/logic/negate.js +++ b/app/blocks/logic/negate.js @@ -4,23 +4,19 @@ export default { bytecodeKey: "negate", name: "Negate", colour: 60, - description: "Swaps a truthy value to `false`, or a falsy value to `true`.", - + description: "Flip any condition to its opposite - turns true into false and false into true. Essential for creating inverse logic like 'if NOT raining', 'if door is NOT open', or 'if temperature is NOT above 75°F'. Perfect for building exception handling, safety conditions, and reverse automation logic in your IoT Actions.", connections: { mode: "value", output: "expression", }, - template: "not %EXPRESSION", - inputs: { EXPRESSION: { - description: "Block diagram that will be resolved, then have its truthiness flipped.", + description: "Connect any condition or comparison that you want to reverse. Examples: attach 'temperature > 80' to create 'NOT temperature > 80' (meaning temperature ≤ 80), or 'motion detected' to create 'NOT motion detected' (meaning no motion).", check: "expression", shadow: 'io_logic_boolean' } }, - generators: { json: (block, generator) => { const @@ -30,15 +26,12 @@ export default { target: JSON.parse(operand) } } - return [ JSON.stringify(payload), 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const payload = blockObject.negate - return { type: 'io_logic_negate', inputs: { From a03ca05cc8e0a64ca899d34909f33e326145689d Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 15 Aug 2025 12:20:56 -0400 Subject: [PATCH 58/74] code fix --- app/blocks/logic/operation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/blocks/logic/operation.js b/app/blocks/logic/operation.js index 580dbc0..6e8b3fd 100644 --- a/app/blocks/logic/operation.js +++ b/app/blocks/logic/operation.js @@ -55,7 +55,7 @@ export default { fields = { OP: comparator?.toUpperCase() }, - inputs: { + inputs = { A: helpers.expressionToBlock(left, { shadow: 'io_logic_boolean' }), B: helpers.expressionToBlock(right, { shadow: 'io_logic_boolean' }), } From c8911d545dd2326c6216cedbe595817a73605d1b Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 15 Aug 2025 12:38:52 -0400 Subject: [PATCH 59/74] update snapshots to new help text --- .../block_snapshots_test.js.snapshot | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot index 53a075f..21e5878 100644 --- a/test/app/blocks/snapshots/block_snapshots_test.js.snapshot +++ b/test/app/blocks/snapshots/block_snapshots_test.js.snapshot @@ -61,7 +61,7 @@ exports[`Block Snapshots > Blockly JSON > action_root 1`] = ` "inputsInline": false, "type": "action_root", "colour": "0", - "tooltip": "Add Triggers to determine when this Action runs. Add Actions to determine what this Action does.", + "tooltip": "The foundation of every Adafruit IO Action. Connect Triggers (like 'when temperature > 80°F' or 'every morning at 8 AM') to define when your Action runs, then attach Action blocks (like 'send email', 'publish to feed', or 'if/then logic') to define what happens when triggered.", "message0": "Triggers: %1", "args0": [ { @@ -2522,7 +2522,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_compare 1`] = ` "inputsInline": true, "type": "io_logic_compare", "colour": 120, - "tooltip": "Numerically compare two given values using the selected math operation.", + "tooltip": "Build mathematical conditions by comparing any two numerical values in your Action logic. Perfect for creating if/then statements like 'if temperature is greater than target temp', 'if battery level equals low threshold', or 'if sensor reading is between two values'. Works with feed data, variables, calculations, or any numerical inputs.", "output": "expression", "message0": "%1", "args0": [ @@ -2581,7 +2581,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_negate 1`] = ` "inputsInline": false, "type": "io_logic_negate", "colour": 60, - "tooltip": "Swaps a truthy value to \`false\`, or a falsy value to \`true\`.", + "tooltip": "Flip any condition to its opposite - turns true into false and false into true. Essential for creating inverse logic like 'if NOT raining', 'if door is NOT open', or 'if temperature is NOT above 75°F'. Perfect for building exception handling, safety conditions, and reverse automation logic in your IoT Actions.", "output": "expression", "message0": "not %1", "args0": [ @@ -2601,7 +2601,7 @@ exports[`Block Snapshots > Blockly JSON > io_logic_operation 1`] = ` "inputsInline": true, "type": "io_logic_operation", "colour": 60, - "tooltip": "Perform the specifed boolean logic operation on two operands.", + "tooltip": "Combine multiple conditions to create sophisticated decision logic in your Actions. Perfect for complex automation like 'if temperature is high AND humidity is low', 'if motion detected OR door opened', or any scenario where you need multiple criteria to work together. Essential for building smart, multi-factor IoT control systems.", "output": "expression", "message0": "%1", "args0": [ @@ -2876,7 +2876,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_get 1`] = ` "inputsInline": false, "type": "io_variables_get", "colour": 240, - "tooltip": "Get the value previously assigned to a variable.", + "tooltip": "Retrieve the value stored in a variable that was previously set using a Set Variable block. Use this to access stored feed values, calculation results, or any data saved earlier in your Action.", "output": "expression", "message0": "Get variable %1 %2", "args0": [ @@ -2898,7 +2898,7 @@ exports[`Block Snapshots > Blockly JSON > io_variables_set 1`] = ` "inputsInline": true, "type": "io_variables_set", "colour": 240, - "tooltip": "Set a variable to a value", + "tooltip": "Store a value in a named variable for later use in your Action. Variables let you remember feed values, calculation results, or any data to use in subsequent action blocks.", "nextStatement": "expression", "previousStatement": "expression", "message0": "Set variable %1 = %2", @@ -2923,7 +2923,7 @@ exports[`Block Snapshots > Blockly JSON > matcher_compare 1`] = ` "inputsInline": true, "type": "matcher_compare", "colour": 224, - "tooltip": "Numerically compare the new Feed value with another number.", + "tooltip": "Create smart triggers based on numerical sensor data and thresholds. Perfect for temperature alerts ('notify when above 80°F'), battery monitoring ('warn when below 20%'), humidity control ('turn on fan when over 60%'), or any sensor-based automation that depends on numerical comparisons.", "output": "matcher", "message0": "%1 %2", "args0": [ @@ -2973,7 +2973,7 @@ exports[`Block Snapshots > Blockly JSON > matcher_text_compare 1`] = ` "inputsInline": true, "type": "matcher_text_compare", "colour": 180, - "tooltip": "Compare the new feed value with text for equality, inequality, or inclusion.", + "tooltip": "Compare text-based feed data using smart text matching. Perfect for triggers based on status messages ('door opened', 'motion detected'), device states ('online', 'offline'), or any text-based sensor data. Works with exact matches, exclusions, or partial text detection within longer messages.", "output": "matcher", "message0": "%1 %2", "args0": [ @@ -3130,7 +3130,7 @@ exports[`Block Snapshots > Blockly JSON > on_schedule 1`] = ` "inputsInline": false, "type": "on_schedule", "colour": 30, - "tooltip": "A schedule to run the action, from every minute to once a year.", + "tooltip": "Create powerful time-based automation that runs your Actions on a schedule - from simple daily reminders to complex patterns like 'every 15 minutes during weekdays' or 'first Monday of each quarter'. Works like a smart alarm clock for your IoT devices, automatically triggering actions without any manual intervention. Perfect for turning lights on/off, sending regular reports, or controlling devices based on time patterns.", "nextStatement": "trigger", "previousStatement": "trigger", "message0": "Schedule %1", @@ -3886,7 +3886,7 @@ exports[`Block Snapshots > Blockly JSON > text_compare 1`] = ` "inputsInline": true, "type": "text_compare", "colour": 180, - "tooltip": "Compare two chunks of text for equality, inequality, or inclusion.", + "tooltip": "Compare any two pieces of text or data to build conditional logic in your Actions. Perfect for creating if/then statements like 'if device status equals online', 'if user name is not guest', or 'if error message contains timeout'. Works with feed values, variables, user input, or any text-based data.", "output": "expression", "message0": "%1", "args0": [ @@ -4109,7 +4109,7 @@ exports[`Block Snapshots > Blockly JSON > when_data 1`] = ` "inputsInline": true, "type": "when_data", "colour": 30, - "tooltip": "Run this action when a Feed receives a new data point.", + "tooltip": "The simplest trigger - runs your Action every single time ANY new data arrives at a feed, regardless of what the value is. Perfect for logging all activity ('record every sensor reading'), acknowledging data receipt ('send confirmation for every message'), or triggering workflows that need to process all incoming data. No conditions, no filtering - just pure data arrival detection.", "nextStatement": "trigger", "previousStatement": "trigger", "message0": "When %1 gets any data %2", @@ -4142,7 +4142,7 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching 1`] = ` "inputsInline": true, "type": "when_data_matching", "colour": 30, - "tooltip": "Run this Action when the specified Feed receives data that matches the specified condition.", + "tooltip": "The most common trigger type - runs your Action immediately whenever new data arrives at a feed that meets your specified condition. Perfect for real-time responses like 'send alert when temperature exceeds 85°F', 'turn on lights when motion detected', or 'notify me when battery drops below 20%'. This trigger fires every single time the condition is met.", "nextStatement": "trigger", "previousStatement": "trigger", "message0": "When %1 gets data matching: %2", @@ -4177,7 +4177,7 @@ exports[`Block Snapshots > Blockly JSON > when_data_matching_state 1`] = ` "inputsInline": true, "type": "when_data_matching_state", "colour": 30, - "tooltip": "Run this Action when the specified Feed receives a data point that compares to its previous data point in the specified way.", + "tooltip": "Advanced trigger that watches for changes in how your feed data matches a condition over time. Unlike basic triggers that just check if data equals a value, this compares the current data point with the previous one to detect when conditions START being true, STOP being true, or CONTINUE being true. Perfect for detecting state changes like 'temperature just went above 80°' or 'door just closed after being open'.", "nextStatement": "trigger", "previousStatement": "trigger", "message0": "When %1 gets data that %2 matching %3", From 55e3b094157d0b5a1130d2866e931bd5e01129db Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Fri, 15 Aug 2025 13:04:55 -0400 Subject: [PATCH 60/74] try different invisible space character --- app/blocks/root/root.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/blocks/root/root.js b/app/blocks/root/root.js index 7791581..2441dac 100644 --- a/app/blocks/root/root.js +++ b/app/blocks/root/root.js @@ -12,7 +12,7 @@ export default { %TRIGGERS Actions: |LEFT %EXPRESSIONS - \u3164 + \u00A0 `, inputs: { TRIGGERS: { From 86a3ac24ea1e20b93ead7b39d27182a775ad4ee1 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 09:36:44 -0500 Subject: [PATCH 61/74] Update boolean.js --- app/blocks/logic/boolean.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/blocks/logic/boolean.js b/app/blocks/logic/boolean.js index b045357..03905ed 100644 --- a/app/blocks/logic/boolean.js +++ b/app/blocks/logic/boolean.js @@ -3,28 +3,24 @@ export default { type: 'io_logic_boolean', name: "Boolean", colour: 60, - description: "A true or false value.", - + description: "A simple true or false value for building logic conditions and controlling digital outputs. Use 'true' to turn things on, enable conditions, or represent 'yes' states. Use 'false' to turn things off, disable conditions, or represent 'no' states. Essential for controlling relays, LEDs, alarms, and any on/off IoT devices.", connections: { mode: "value", output: [ "expression", "boolean" ], }, - template: "%BOOL", - fields: { BOOL: { + description: "Choose the boolean state you want to use:", options: [ - ['true', 'TRUE'], - ['false', 'FALSE'], + ['true', 'TRUE', "Represents 'on', 'yes', 'enabled', or 'active' state. Use for turning on devices, enabling features, or setting positive conditions."], + ['false', 'FALSE', "Represents 'off', 'no', 'disabled', or 'inactive' state. Use for turning off devices, disabling features, or setting negative conditions."], ] } }, - generators: { json: block => { const bool = block.getFieldValue('BOOL') === 'TRUE' - return [ JSON.stringify(bool), 0 ] } } From cb960642d702b1434c1f58f4d409c9b7c40c9cea Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 09:49:48 -0500 Subject: [PATCH 62/74] Update if.js --- app/blocks/controls/if.js | 61 +++++++++++++++------------------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/app/blocks/controls/if.js b/app/blocks/controls/if.js index b426634..1ade50b 100644 --- a/app/blocks/controls/if.js +++ b/app/blocks/controls/if.js @@ -1,35 +1,28 @@ import mutator from './if/mutator.js' - - /** @type {import('#types').BlockDefinitionRaw} */ export default { type: 'io_controls_if', bytecodeKey: "conditional", name: "Conditional", - description: "Execute different block diagrams based on the outcome of conditional checks.", + description: "Create smart decision-making logic for your IoT Actions using if/then/else statements. Perfect for building automation like 'if temperature > 80°F then turn on fan, else if temperature < 60°F then turn on heater, else turn off both'. Essential for creating intelligent responses based on sensor data, time conditions, or any combination of factors.", colour: 60, - connections: { mode: "statement", output: "expression", next: 'expression' }, - mutator, - template: ` if %IF0 do %THEN0 else if %ELSE_IF_LABEL else %ELSE_LABEL `, - inputs: { IF0: { check: "expression", shadow: 'io_logic_boolean' }, - THEN0: { check: "expression", type: 'statement', @@ -47,93 +40,85 @@ export default { } } }, - ELSE_IF_LABEL: { type: 'label', }, - ELSE_LABEL: { type: 'label', } }, - 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.) + This condition will always be checked first. Connect comparison blocks, sensor checks, or any logic that results in true/false. If it evaluates to \`true\`, the actions in the corresponding 'do' section will execute, and the rest of the conditional block will be skipped. If \`false\`, execution moves to the next "else if" (if present), or the final "else" (if present). + + **Examples:** + - \`temperature > 80\` - Check if it's hot + - \`motion detected AND time after sunset\` - Security logic + - \`battery level < 15%\` - Low power warning ### \`Do\` - The block diagram to execute when the preceding "if" or "else if" clause - resolves to \`true\`. + The actions to execute when the preceding "if" or "else if" condition is \`true\`. Connect any action blocks like sending emails, controlling devices, publishing to feeds, or logging data. These actions only run if their corresponding condition evaluates to true. + + **Examples:** + - Send alert email + turn on cooling fan + - Log warning message + publish backup data + - Turn on lights + send notification ### \`Else if\` - **Optional:** "else if" only appears after clicking "+ else if", and can be - removed by clicking the "-" next to it. + **Optional:** Add additional conditions to test if the main "if" was false. Click the gear icon and select "+ else if" to add more branches. Each "else if" is only checked if all previous conditions were false. Perfect for handling multiple scenarios like different temperature ranges, various alert levels, or time-based alternatives. - Another "if" to check, only if every prior if has executed and none - resolved to \`true\`. + **Examples:** + - \`else if temperature < 60\` → turn on heater + - \`else if battery < 50%\` → reduce power consumption + - \`else if motion detected\` → different security response ### \`Else\` - **Optional:** "else" only appears after clicking "+ else", and can be removed - by clicking "-" next to it. + **Optional:** The fallback section that executes when ALL "if" and "else if" conditions are false. Click the gear icon and select "+ else" to add this section. Perfect for default actions, error handling, or "normal operation" behavior. - This section will execute if all "if"s and "else-if"s have been executed and - all resolved to \`false\`. + **Examples:** + - Turn off all climate control (temperature is in normal range) + - Send "all systems normal" status update + - Resume regular power consumption mode ` }, - generators: { json: (block, generator) => { const payload = { conditional: {} } - let index = 0 while(block.getInput(`IF${index}`)) { const ifClause = generator.valueToCode(block, `IF${index}`, 0) || 'null', thenClause = generator.statementToCode(block, `THEN${index}`) || '' - payload.conditional[`if${index}`] = JSON.parse(ifClause) payload.conditional[`then${index}`] = JSON.parse(`[ ${thenClause} ]`) - index += 1 } - if(block.getInput('ELSE')) { const elseClause = generator.statementToCode(block, 'ELSE') || '' - payload.conditional.else = JSON.parse(`[${ elseClause }]`) } - return JSON.stringify(payload) } }, - regenerators: { json: (bytecode, helpers) => { const payload = bytecode.conditional - if(!payload) { throw new Error("No data for io_controls_if regenerator") } - const inputs = {} - let index = 0 while(payload[`if${index}`] || payload[`then${index}`]) { inputs[`IF${index}`] = helpers.expressionToBlock(payload[`if${index}`], { shadow: 'io_logic_boolean' }) inputs[`THEN${index}`] = helpers.arrayToStatements(payload[`then${index}`]) - index += 1 } - if(payload.else) { inputs.ELSE = helpers.arrayToStatements(payload.else) } - return { type: "io_controls_if", inputs, From 6e97f00d52be3866c5cb72d84f4985d1c173f073 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 09:51:52 -0500 Subject: [PATCH 63/74] Update round.js --- app/blocks/math/round.js | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/app/blocks/math/round.js b/app/blocks/math/round.js index ea9e22f..73f989f 100644 --- a/app/blocks/math/round.js +++ b/app/blocks/math/round.js @@ -4,47 +4,40 @@ export default { bytecodeKey: "round", name: "Round/Floor/Ceiling", color: 120, - description: "Round a value to the nearest whole number via round, floor, or ceiling functions", - + description: "Convert decimal numbers to whole numbers using different rounding strategies. Perfect for cleaning up sensor readings (23.7°F → 24°F), creating clean display values (3.14159 → 3), or preparing data for systems that only accept integers. Choose round for normal rounding, floor for always rounding down, or ceiling for always rounding up.", connections: { mode: "value", output: "expression", }, - inputs: { VALUE: { - description: "A value you'd like to round to a whole number. Will be coerced to a number.", + description: "The decimal number you want to convert to a whole number. Examples: sensor readings like 72.8°F, calculated values like 3.14159, or any numerical data that needs to be simplified. Non-numeric values will be automatically converted to numbers where possible.", bytecodeProperty: "value", check: "expression", shadow: "io_math_number" } }, - fields: { OPERATION: { - description: "Select which rounding operation to perform on the input:", + description: "Choose how to convert your decimal to a whole number:", options: [ - ["Round", "round", "if .5 or higher: round up; otherwise round down"], - ["Floor", "floor", "rounds down"], - ["Ceiling", "ceiling", "rounds up"], + ["Round", "round", "Standard rounding: 0.5 or higher rounds up, below 0.5 rounds down (e.g., 23.7 → 24, 23.4 → 23, 23.5 → 24). Best for general use and displaying clean values."], + ["Floor", "floor", "Always rounds down to the nearest whole number, regardless of decimal value (e.g., 23.9 → 23, 23.1 → 23). Perfect for counting items, time calculations, or when you need conservative estimates."], + ["Ceiling", "ceiling", "Always rounds up to the nearest whole number, regardless of decimal value (e.g., 23.1 → 24, 23.9 → 24). Great for capacity planning, ensuring minimum values, or safety margins."], ], bytecodeProperty: "operation", } }, - template: "%OPERATION %VALUE", - generators: { json: (block, generator) => { const value = JSON.parse(generator.valueToCode(block, 'VALUE', 0)), operation = block.getFieldValue('OPERATION'), payload = { round: { value, operation } } - return [ JSON.stringify(payload), 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -55,7 +48,6 @@ export default { fields = { OPERATION: operation, } - return { type: 'io_math_round', inputs, fields } } } From fd2912518d49164cf47de9e6b534688c44c40e2e Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 09:53:50 -0500 Subject: [PATCH 64/74] Update number.js --- app/blocks/math/number.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/app/blocks/math/number.js b/app/blocks/math/number.js index 6892ca4..364bf2e 100644 --- a/app/blocks/math/number.js +++ b/app/blocks/math/number.js @@ -3,39 +3,32 @@ export default { type: 'io_math_number', name: "Number", color: 120, - - description: "A numeric value, whole or decimal.", - + description: "Enter any numerical value for use in your IoT Actions - whole numbers, decimals, positive, or negative. Perfect for setting thresholds (like 75 for temperature alerts), target values (like 50% for humidity control), timer durations (like 300 seconds), or any numerical data your automation needs. The foundation for all mathematical operations and comparisons.", connections: { mode: "value", output: [ "expression", "number" ], }, - extensions: { validateNumbers: ({ block }) => { const numField = block.getField("NUM") - if(!numField) { throw new Error("NUM field missing on io_math_number?") } - numField.setValidator(newValue => { const parsed = Number(newValue) - if(!parsed && parsed !== 0) { return null // failed to parse, signal validation failure - } else { return parsed// parsed fine, use the result } }) } }, - template: " %NUM", - fields: { - NUM: { text: '0' } + NUM: { + description: "Enter your numerical value here. Examples: 75 (temperature threshold), 3.14159 (mathematical constant), -10 (negative temperature), 0.5 (percentage as decimal), or any number your automation requires. Validates input to ensure it's a proper number.", + text: '0' + } }, - generators: { json: block => { return [Number(block.getFieldValue('NUM')) || '0', 0] From 660938e1f25476cbf53d91f6db5b3ba5be5e8a41 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 11:46:11 -0500 Subject: [PATCH 65/74] Update map.js --- app/blocks/math/map.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/blocks/math/map.js b/app/blocks/math/map.js index e236eb3..b704040 100644 --- a/app/blocks/math/map.js +++ b/app/blocks/math/map.js @@ -4,7 +4,7 @@ export default { bytecodeKey: "mapValue", name: "Map", colour: 120, - description: "Scale a value from one range of numbers to another", + description: "Transform sensor readings and data values by scaling them from one number range to another. Essential for IoT projects that need to convert raw sensor data (like 0-1023 from Arduino analog pins) into meaningful units (like 0-100% humidity), or translate between different measurement systems. Perfect for normalizing data, creating percentage values, or adapting sensor outputs to match your specific needs.", connections: { mode: "value", output: "number", @@ -17,13 +17,13 @@ export default { `, inputs: { VALUE: { - description: "The number to scale from the original range to the target range.", + description: "The raw number you want to convert to a different scale. Examples: sensor reading of 512 (from 0-1023 analog range), temperature of 25°C (for Fahrenheit conversion), or any numerical data that needs scaling to a new range.", check: "expression", bytecodeProperty: "value", shadow: 'io_math_number' }, FROM_RANGE: { - description: "The original range that the input value comes from (e.g., sensor readings from 0 to 1023).", + description: "The original scale that your input value currently represents. Examples: (0,1023) for Arduino analog sensors, (0,255) for RGB color values, (-40,125) for temperature sensor ranges, or (0,100) for percentage data that needs different scaling.", check: 'range', bytecodeProperty: "from", shadow: { @@ -41,7 +41,7 @@ export default { } }, TO_RANGE: { - description: "The target range to scale the value to (e.g., convert to 0.0-1.0 for percentages).", + description: "The new scale you want to convert your value to. Examples: (0,100) for percentage displays, (0.0,1.0) for normalized values, (32,212) for Fahrenheit conversion, or any target range that matches your display needs or system requirements.", check: 'range', bytecodeProperty: "to", shadow: { From 957b2bf1e6d933cc8564efc5103eccde1578c79c Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 11:48:49 -0500 Subject: [PATCH 66/74] Update constrain.js --- app/blocks/math/constrain.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/blocks/math/constrain.js b/app/blocks/math/constrain.js index 2715d6c..1c6a308 100644 --- a/app/blocks/math/constrain.js +++ b/app/blocks/math/constrain.js @@ -4,7 +4,7 @@ export default { bytecodeKey: "constrain", name: "Constrain", colour: 120, - description: "Constrain a given number to fall within a given range.", + description: "Keep any number within specified minimum and maximum boundaries. If the input value is below the minimum, it becomes the minimum. If above the maximum, it becomes the maximum. Perfect for ensuring values stay within expected ranges, creating percentage bounds, or limiting user input to acceptable values.", connections: { mode: "value", output: "number", @@ -15,12 +15,12 @@ export default { `, inputs: { VALUE: { - description: "The number to constrain within the specified range limits.", + description: "The number to limit within your specified boundaries. Examples: user input values, calculation results, sensor readings, or any numerical data that might go outside your desired range.", check: "expression", shadow: "io_math_number" }, RANGE: { - description: "The minimum and maximum bounds to limit the value within (values outside this range will be clamped to the nearest boundary).", + description: "The minimum and maximum limits for your value. Examples: (0,100) for percentages, (1,10) for rating scales, (-50,150) for temperature ranges, or any boundaries that make sense for your data. Values outside these limits get automatically adjusted to the nearest boundary.", check: 'range', shadow: { type: "math_range", From b619b89bd2cfd3c28dbcd5d51b1affe938873504 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 11:53:48 -0500 Subject: [PATCH 67/74] Update arithmetic.js --- app/blocks/math/arithmetic.js | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/app/blocks/math/arithmetic.js b/app/blocks/math/arithmetic.js index 30cf12c..b91a011 100644 --- a/app/blocks/math/arithmetic.js +++ b/app/blocks/math/arithmetic.js @@ -5,42 +5,36 @@ export default { name: "Arithmetic", colour: 120, inputsInline: true, - description: "Perform the specified arithmetic operation on two specified operands.", - + description: "Perform mathematical calculations using sensor data, feed values, or any numbers in your Actions. Perfect for creating custom formulas like calculating averages, converting units (Celsius to Fahrenheit), computing percentages, or building complex calculations from multiple data sources.", connections: { mode: "value", output: "expression", }, - template: `%A %OP %B`, - inputs: { A: { - description: "The left side of the operation. Will be coerced to a number", + description: "The first number in your calculation (left side). Examples: temperature reading (72.5), feed value, sensor data, or results from other calculations. Non-numeric values will be automatically converted to numbers where possible.", check: "expression", shadow: 'io_math_number' }, - B: { - description: "The right side of the operation. Will be coerced to a number", + description: "The second number in your calculation (right side). Examples: conversion factors (1.8 for temp conversion), target values (75 for comparison), other sensor readings, or mathematical constants. Also automatically converted to numbers.", check: "expression", shadow: 'io_math_number' }, }, - fields: { OP: { - description: "The mathematical operation to perform.", + description: "Choose the mathematical operation to perform:", options: [ - ['+', 'ADD', "add two numbers"], - ['-', 'MINUS', "subtract number B from number A"], - ['x', 'MULTIPLY', "multiply two numbers"], - ['/', 'DIVIDE', "divide number A by number B"], - ['^', 'POWER', "raise number A to the power of number B"], + ['+', 'ADD', "Addition: Combine two numbers (e.g., 25 + 5 = 30). For totaling values, adding offsets, or combining multiple sensor readings into sums."], + ['-', 'MINUS', "Subtraction: Remove B from A (e.g., 30 - 5 = 25). For calculating differences, finding deltas between readings, or subtracting baseline values."], + ['x', 'MULTIPLY', "Multiplication: A times B (e.g., 6 x 4 = 24). For unit conversions, scaling values, calculating areas/volumes, or applying multiplication factors."], + ['/', 'DIVIDE', "Division: A divided by B (e.g., 20 ÷ 4 = 5). For calculating averages, ratios, percentages, or converting between different unit scales."], + ['^', 'POWER', "Exponentiation: A raised to the power of B (e.g., 2^3 = 8). For advanced calculations, exponential growth models, or complex mathematical formulas."], ] } }, - generators: { json: (block, generator) => { const @@ -54,7 +48,6 @@ export default { operator = block.getFieldValue('OP'), leftExp = generator.valueToCode(block, 'A', 0) || 'null', rightExp = generator.valueToCode(block, 'B', 0) || 'null', - blockPayload = JSON.stringify({ arithmetic: { left: JSON.parse(leftExp), @@ -64,11 +57,9 @@ export default { right: JSON.parse(rightExp) } }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -87,7 +78,6 @@ export default { A: helpers.expressionToBlock(payload.left, { shadow: 'io_math_number' }), B: helpers.expressionToBlock(payload.right, { shadow: 'io_math_number' }), } - return { type: 'io_math_arithmetic', fields, inputs } } } From 330e3e5ee146058b9194f9bdc186896e317286c0 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 11:58:34 -0500 Subject: [PATCH 68/74] Update text_multiline.js --- app/blocks/text/text_multiline.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/blocks/text/text_multiline.js b/app/blocks/text/text_multiline.js index 061c245..f789186 100644 --- a/app/blocks/text/text_multiline.js +++ b/app/blocks/text/text_multiline.js @@ -3,25 +3,21 @@ export default { type: 'io_text_multiline', name: "Multiline Text", colour: 180, - description: "A String of longer-form text with newlines.", - + description: "Create formatted text content with multiple lines, paragraphs, and line breaks. Perfect for composing detailed email messages, creating formatted reports with sensor data, writing multi-paragraph notifications, or building structured text content that needs proper formatting and readability across multiple lines.", connections: { mode: "value", output: [ "expression", "string" ], }, - template: "¶ %TEXT", - fields: { TEXT: { + description: "Enter your multi-line text content here. Use Enter/Return to create new lines and paragraphs. Perfect for email templates ('Dear User,\\n\\nYour temperature reading is...\\n\\nBest regards'), formatted reports, detailed notifications, or any text that needs structure and readability.", multiline_text: '' } }, - generators: { json: block => { const text = block.getFieldValue('TEXT') - return [ JSON.stringify(text), 0 ] } } From 2956877c5a794f6a2dd35e4128eec5c4f71356c1 Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 12:00:05 -0500 Subject: [PATCH 69/74] Update text.js --- app/blocks/text/text.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/blocks/text/text.js b/app/blocks/text/text.js index 59a95f7..fda7e3c 100644 --- a/app/blocks/text/text.js +++ b/app/blocks/text/text.js @@ -3,25 +3,21 @@ export default { type: "io_text", name: "Text", colour: 180, - description: "A String of text", - + description: "Enter any text content for use in your Actions - words, phrases, device commands, or messages. Perfect for setting device states ('on', 'off'), creating notification messages ('Alert: High temperature detected'), defining comparison values ('motion detected'), or sending commands to connected systems. The building block for all text-based automation and communication.", connections: { mode: "value", output: [ "expression", "string" ], }, - template: `"%TEXT`, - fields: { TEXT: { + description: "Type your text content here. Examples: 'on' for device commands, 'High temperature alert!' for notifications, 'motion' for sensor state matching, email subjects like 'Daily Report', or any words/phrases your automation needs. Single line text only - use Multiline Text block for paragraphs.", text: '' } }, - generators: { json: block => { const text = block.getFieldValue('TEXT') - return [ JSON.stringify(text), 0 ] } } From c728e4fac8e5eed990dd978ba866f1bab7830ca1 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Tue, 19 Aug 2025 14:05:08 -0400 Subject: [PATCH 70/74] add expression output to constrain and map --- app/blocks/math/constrain.js | 2 +- app/blocks/math/map.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/blocks/math/constrain.js b/app/blocks/math/constrain.js index 1c6a308..ae1e6f8 100644 --- a/app/blocks/math/constrain.js +++ b/app/blocks/math/constrain.js @@ -7,7 +7,7 @@ export default { description: "Keep any number within specified minimum and maximum boundaries. If the input value is below the minimum, it becomes the minimum. If above the maximum, it becomes the maximum. Perfect for ensuring values stay within expected ranges, creating percentage bounds, or limiting user input to acceptable values.", connections: { mode: "value", - output: "number", + output: [ "expression", "number" ], }, template: ` Constrain %VALUE diff --git a/app/blocks/math/map.js b/app/blocks/math/map.js index b704040..4255306 100644 --- a/app/blocks/math/map.js +++ b/app/blocks/math/map.js @@ -7,7 +7,7 @@ export default { description: "Transform sensor readings and data values by scaling them from one number range to another. Essential for IoT projects that need to convert raw sensor data (like 0-1023 from Arduino analog pins) into meaningful units (like 0-100% humidity), or translate between different measurement systems. Perfect for normalizing data, creating percentage values, or adapting sensor outputs to match your specific needs.", connections: { mode: "value", - output: "number", + output: [ "expression", "number" ], }, template: ` Map From 4ba443baec7055d41aabedc3efa22a4f94e32abc Mon Sep 17 00:00:00 2001 From: Tyler Cooper Date: Tue, 19 Aug 2025 17:20:03 -0500 Subject: [PATCH 71/74] Update template.js --- app/blocks/text/template.js | 202 +++++++++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 40 deletions(-) diff --git a/app/blocks/text/template.js b/app/blocks/text/template.js index 0d957ac..6781d86 100644 --- a/app/blocks/text/template.js +++ b/app/blocks/text/template.js @@ -6,71 +6,194 @@ export default { colour: 180, inputsInline: true, description: ` - Render a text template. - + Create dynamic, personalized messages by combining static text with live data from your IoT system. Perfect for intelligent notifications like "Hello {{ user.name }}, your temperature sensor read {{ feeds['sensors.temperature'].value }}°F" or automated reports that include current sensor values, user information, and real-time data. Uses the powerful Liquid templating language for advanced formatting and logic. + + ## What is a Text Template? + + Think of a text template like a form letter where you leave blanks to fill in with specific information. Instead of writing "Dear _____", you write "Dear {{ user.name }}" and the system automatically fills in the actual name when the message is sent. + + ## How It Works + + This block renders (processes) a text template, replacing special placeholders with actual values. ::: v-pre - Special forms surrounded by {{ curly_braces }} - will be replaced with their current value during the action run. - - These templates use the Liquid templating language, from Shopify, and many - helpful functions come built-in. See the - [Liquid Documentation](https://shopify.github.io/liquid/basics/introduction/) - to learn more. - - ### Template Variables: - - \`{{ variables.var_name }}\` - get the value of a variable you have defined - with name 'var_name' - - \`{{ vars.var_name }}\` - shorthand for same as above - - \`{{ variables['var name'] }}\` - get the value of a variable you have - defined with name 'var name' (allows spaces in variable names - - \`{{ vars['var name'] }}\` - shorthand for same as above - - \`{{ user.name }}\` - your user's name - - \`{{ user.username }}\` - your user's username - - \`{{ feeds['group_key.feed_key'].name }}\` - access a feed with key - 'group_key.feed_key' and get its name - - \`{{ feeds[...].key }}\` - ...get its key - - \`{{ feeds[...].value }}\` - ...get its last value + Anything surrounded by {{ double curly braces }} gets replaced with real data when your action runs. + + For example: + - Template: "Hello {{ user.name }}!" + - Becomes: "Hello John!" + + These templates use the Liquid templating language from Shopify, which includes many helpful built-in functions. + Learn more: [Liquid Documentation](https://shopify.github.io/liquid/basics/introduction/) + + ## Template Variables Reference + + ### User Information + - \`{{ user.name }}\` - Your full name (e.g., "John Smith") + - \`{{ user.username }}\` - Your username (e.g., "jsmith123") + + ### Custom Variables + - \`{{ variables.var_name }}\` - Get value of variable named 'var_name' + - \`{{ vars.var_name }}\` - Shorthand version (same as above) + - \`{{ variables['var name'] }}\` - Use brackets for names with spaces + - \`{{ vars['my special var'] }}\` - Shorthand with spaces + + ### Feed Data (Sensors & Devices) + - \`{{ feeds['group_key.feed_key'].value }}\` - Current value from a feed + - \`{{ feeds['group_key.feed_key'].name }}\` - Feed's display name + - \`{{ feeds['group_key.feed_key'].key }}\` - Feed's unique key + - \`{{ feeds['group_key.feed_key'].updated_at }}\` - Last update timestamp + + ## Real-World IoT Examples + + ### 🌡️ Temperature Alerts + **Basic Alert:** + \`"Temperature Alert: {{ feeds['sensors.temp'].value }}°F detected!"\` + Output: "Temperature Alert: 85°F detected!" + + **Detailed Alert with Location:** + \`"⚠️ HIGH TEMP WARNING + Location: {{ feeds['sensors.temp'].name }} + Current: {{ feeds['sensors.temp'].value }}°F + Time: {{ feeds['sensors.temp'].updated_at }} + Please check your {{ vars.device_location }} immediately!"\` + + ### 📊 Daily Status Reports + **Simple Report:** + \`"Daily Report for {{ user.name }}: + • Temperature: {{ feeds['home.temp'].value }}°F + • Humidity: {{ feeds['home.humidity'].value }}% + • Battery: {{ vars.battery_level }}%"\` + + **Comprehensive Home Report:** + \`"Good morning {{ user.name }}! Here's your home status: + + 🌡️ Climate Control: + - Living Room: {{ feeds['climate.living_temp'].value }}°F + - Bedroom: {{ feeds['climate.bedroom_temp'].value }}°F + - Humidity: {{ feeds['climate.humidity'].value }}% + + 🔋 Device Status: + - Door Sensor Battery: {{ feeds['security.door_battery'].value }}% + - Motion Detector: {{ vars.motion_status }} + - Last Activity: {{ feeds['security.last_motion'].updated_at }} + + Have a great day!"\` + + ### 🚪 Security Notifications + **Door Alert:** + \`"🚪 {{ feeds['security.front_door'].name }} is {{ feeds['security.front_door'].value }} + Time: {{ feeds['security.front_door'].updated_at }} + User: {{ user.name }}"\` + + ### 🌱 Garden Monitoring + **Watering Reminder:** + \`"Hey {{ user.name }}, your {{ vars.plant_name }} needs attention! + Soil Moisture: {{ feeds['garden.moisture'].value }}% + Last Watered: {{ vars.last_water_date }} + Recommendation: Water {{ vars.water_amount }}ml"\` + + ### 🔔 Smart Doorbell Messages + **Visitor Notification:** + \`"{{ user.name }}, someone is at your door! + Camera: {{ feeds['doorbell.camera'].name }} + Motion Level: {{ feeds['doorbell.motion'].value }} + Time: {{ feeds['doorbell.motion'].updated_at }}"\` + + ## Advanced Liquid Features + + ### Conditional Logic + Use if/else statements for smart messages: + \`"{% if feeds['sensors.temp'].value > 80 %} + 🔥 It's hot! {{ feeds['sensors.temp'].value }}°F + {% else %} + ❄️ Nice and cool: {{ feeds['sensors.temp'].value }}°F + {% endif %}"\` + + ### Formatting Numbers + Round numbers or add decimal places: + \`"Temperature: {{ feeds['sensors.temp'].value | round: 1 }}°F" + "Battery: {{ vars.battery | round }}%"\` + + ### Date Formatting + Format timestamps for readability: + \`"Last updated: {{ feeds['sensor.temp'].updated_at | date: '%B %d at %I:%M %p' }}"\` + Output: "Last updated: January 15 at 03:45 PM" + + ### Math Operations + Perform calculations in templates: + \`"Temperature in Celsius: {{ feeds['sensors.temp'].value | minus: 32 | times: 5 | divided_by: 9 | round: 1 }}°C"\` + + ## Common Patterns & Tips + + ### 1. Fallback Values + Provide defaults when data might be missing: + \`"Temperature: {{ feeds['sensors.temp'].value | default: 'No reading' }}"\` + + ### 2. Multiple Feed Access + Combine data from multiple sources: + \`"Indoor: {{ feeds['home.inside_temp'].value }}°F + Outdoor: {{ feeds['home.outside_temp'].value }}°F + Difference: {{ feeds['home.inside_temp'].value | minus: feeds['home.outside_temp'].value }}°F"\` + + ### 3. Status Indicators + Use emoji based on values (keep in mind emoji will not work with displays): + \`"Battery: {{ vars.battery_level }}% + {% if vars.battery_level < 20 %}🔴{% elsif vars.battery_level < 50 %}🟡{% else %}🟢{% endif %}"\` + + ### 4. Time-Based Greetings + \`"{% assign hour = 'now' | date: '%H' | plus: 0 %} + {% if hour < 12 %}Good morning{% elsif hour < 17 %}Good afternoon{% else %}Good evening{% endif %}, {{ user.name }}!"\` + + ## Troubleshooting Common Issues + + ### Problem: Variable shows as blank + **Solution:** Check that: + - Variable name is spelled correctly (case-sensitive!) + - Feed key matches exactly (including group.feed format) + - Variable has been set before this template runs + + ### Problem: Template shows raw {{ }} text + **Solution:** Make sure: + - You're using double curly braces {{ }} + - No extra spaces inside braces + - Feed keys use square brackets and quotes: feeds['key'] + + ### Problem: Timestamp looks weird + **Solution:** Format dates using Liquid filters: + \`{{ feeds['sensor'].updated_at | date: '%Y-%m-%d %H:%M' }}\` + + ## Best Practices + + 1. **Keep it readable**: Use line breaks and spacing for multi-line messages + 2. **Test with sample data**: Try your template with different values + 3. **Use meaningful variable names**: 'kitchen_temp' is better than 'temp1' + 4. **Add context**: Include units (°F, %, etc.) and location names ::: `, - connections: { mode: "value", output: [ "expression", "string" ], }, - template: "{{ %TEMPLATE", - inputs: { TEMPLATE: { + description: "Create your template text with static content and dynamic {{ variable }} placeholders. Examples: 'Alert: {{ feeds['temp.kitchen'].value }}°F detected' or 'Daily Report for {{ user.name }}: Battery at {{ vars.battery_level }}%'. Use {{ }} to insert live data into your message.", check: "expression", shadow: 'io_text_multiline' } }, - generators: { json: (block, generator) => { const template = generator.valueToCode(block, 'TEMPLATE', 0) || null, - blockPayload = JSON.stringify({ textTemplate: { template: JSON.parse(template) }, }) - return [ blockPayload, 0 ] } }, - regenerators: { json: (blockObject, helpers) => { const @@ -78,7 +201,6 @@ export default { inputs = { TEMPLATE: helpers.expressionToBlock(template, { shadow: "io_text" }) } - return { type: 'text_template', inputs } } } From 774c37327ac66554925eba4521f3015573e3b11e Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 20 Aug 2025 11:44:48 -0400 Subject: [PATCH 72/74] auto-cleanup if error during workspace injection --- src/exporters/script_templates/blockly_api.js | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/exporters/script_templates/blockly_api.js b/src/exporters/script_templates/blockly_api.js index 06afd7b..2407b07 100644 --- a/src/exporters/script_templates/blockly_api.js +++ b/src/exporters/script_templates/blockly_api.js @@ -49,45 +49,52 @@ export const // do normal Blockly injection here currentWorkspace = Blockly.inject(blocklyDivId, blocklyInjectOptions) - // shortcut to make the outside data available everywhere/global - // consider if this could be done other ways, less global - currentWorkspace.extensionData = options.extensionData + try { + // shortcut to make the outside data available everywhere/global + // consider if this could be done other ways, less global + currentWorkspace.extensionData = options.extensionData - registerToolboxCallbacks(currentWorkspace) + registerToolboxCallbacks(currentWorkspace) - if(options.disableOrphans) { - currentWorkspace.addChangeListener(Blockly.Events.disableOrphans) - } + if(options.disableOrphans) { + currentWorkspace.addChangeListener(Blockly.Events.disableOrphans) + } - if(options.workspaceData) { - const workspaceJson = jsonToWorkspace(options.workspaceData) - Blockly.serialization.workspaces.load(workspaceJson, currentWorkspace) + if(options.workspaceData) { + const workspaceJson = jsonToWorkspace(options.workspaceData) + Blockly.serialization.workspaces.load(workspaceJson, currentWorkspace) - } else if(options.workspaceJson) { - Blockly.serialization.workspaces.load(options.workspaceJson, currentWorkspace) + } else if(options.workspaceJson) { + Blockly.serialization.workspaces.load(options.workspaceJson, currentWorkspace) - } else { - Blockly.serialization.workspaces.load(initialWorkspace, currentWorkspace) - } + } else { + Blockly.serialization.workspaces.load(initialWorkspace, currentWorkspace) + } - if(options.onJsonUpdated || options.onJsonError) { - // auto-regenerate code - currentWorkspace.addChangeListener(e => { - if(e.isUiEvent || // no UI events - e.type == Blockly.Events.FINISHED_LOADING || // no on-load - currentWorkspace.isDragging()) // not while dragging - { return } + if(options.onJsonUpdated || options.onJsonError) { + // auto-regenerate code + currentWorkspace.addChangeListener(e => { + if(e.isUiEvent || // no UI events + e.type == Blockly.Events.FINISHED_LOADING || // no on-load + currentWorkspace.isDragging()) // not while dragging + { return } - // generate next cycle so orphans get disabled first - setTimeout(() => { - try { - const json = workspaceToJson(currentWorkspace) - options.onJsonUpdated?.(json) - } catch(error) { - options.onJsonError?.(error) - } + // generate next cycle so orphans get disabled first + setTimeout(() => { + try { + const json = workspaceToJson(currentWorkspace) + options.onJsonUpdated?.(json) + } catch(error) { + options.onJsonError?.(error) + } + }) }) - }) + } + } catch(error) { + // clean things up + dispose() + // rethrow exception + throw error } return currentWorkspace From 9a0b94450cbb9f117fd47a23611bea92dda65e86 Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 20 Aug 2025 13:28:51 -0400 Subject: [PATCH 73/74] smarter tooltip generation, better escaping --- app/blocks/text/template.js | 14 ++++++++++++-- src/definitions/block_definition.js | 5 ++++- src/docs/render_block_inputs.js | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/app/blocks/text/template.js b/app/blocks/text/template.js index 6781d86..ad79973 100644 --- a/app/blocks/text/template.js +++ b/app/blocks/text/template.js @@ -6,6 +6,7 @@ export default { colour: 180, inputsInline: true, description: ` + ::: v-pre Create dynamic, personalized messages by combining static text with live data from your IoT system. Perfect for intelligent notifications like "Hello {{ user.name }}, your temperature sensor read {{ feeds['sensors.temperature'].value }}°F" or automated reports that include current sensor values, user information, and real-time data. Uses the powerful Liquid templating language for advanced formatting and logic. ## What is a Text Template? @@ -15,7 +16,6 @@ export default { ## How It Works This block renders (processes) a text template, replacing special placeholders with actual values. - ::: v-pre Anything surrounded by {{ double curly braces }} gets replaced with real data when your action runs. For example: @@ -177,7 +177,17 @@ export default { template: "{{ %TEMPLATE", inputs: { TEMPLATE: { - description: "Create your template text with static content and dynamic {{ variable }} placeholders. Examples: 'Alert: {{ feeds['temp.kitchen'].value }}°F detected' or 'Daily Report for {{ user.name }}: Battery at {{ vars.battery_level }}%'. Use {{ }} to insert live data into your message.", + description: ` + ::: v-pre + Create your template text with static content and dynamic {{ variable }} placeholders. + + Examples: + - 'Alert: {{ feeds['temp.kitchen'].value }}°F detected' + - 'Daily Report for {{ user.name }}: Battery at {{ vars.battery_level }}%' + + Use {{ }} to insert live data into your message. + ::: + `, check: "expression", shadow: 'io_text_multiline' } diff --git a/src/definitions/block_definition.js b/src/definitions/block_definition.js index f0b5e0d..1901a5e 100644 --- a/src/definitions/block_definition.js +++ b/src/definitions/block_definition.js @@ -191,7 +191,10 @@ BlockDefinition.parseRawDefinition = function(rawBlockDefinition, definitionPath ? niceTemplate(rawBlockDefinition.description) : "" blockDef.ioPlus = rawBlockDefinition.ioPlus - blockDef.tooltip = blockDef.description.split("\n")[0] + // take the first line of the description + // blockDef.tooltip = blockDef.description.split("\n")[0] + // take the first sentence of the description + blockDef.tooltip = blockDef.description.split(/\.(\s|$)/)[0] + "." blockDef.disabled = !!rawBlockDefinition.disabled blockDef.connections = rawBlockDefinition.connections blockDef.template = rawBlockDefinition.template diff --git a/src/docs/render_block_inputs.js b/src/docs/render_block_inputs.js index bd14240..48925b9 100644 --- a/src/docs/render_block_inputs.js +++ b/src/docs/render_block_inputs.js @@ -36,7 +36,7 @@ const if(input.type === 'label') { return } lines.push(`### \`${ capitalize(inputName) }\``) - lines.push(input.description) + if(input.description) { lines.push(niceTemplate(input.description)) } }) return lines.join("\n\n") From 888fec3f9d0f61516deb1bbd25573c8460cf380f Mon Sep 17 00:00:00 2001 From: Loren Norman Date: Wed, 20 Aug 2025 13:55:43 -0400 Subject: [PATCH 74/74] fix template tooltip --- app/blocks/text/template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/blocks/text/template.js b/app/blocks/text/template.js index ad79973..c51a758 100644 --- a/app/blocks/text/template.js +++ b/app/blocks/text/template.js @@ -6,9 +6,9 @@ export default { colour: 180, inputsInline: true, description: ` - ::: v-pre - Create dynamic, personalized messages by combining static text with live data from your IoT system. Perfect for intelligent notifications like "Hello {{ user.name }}, your temperature sensor read {{ feeds['sensors.temperature'].value }}°F" or automated reports that include current sensor values, user information, and real-time data. Uses the powerful Liquid templating language for advanced formatting and logic. + Create dynamic, personalized messages by combining static text with live data from your IoT system. Perfect for intelligent notifications like "Hello {{ user.name }}, your temperature sensor read {{ feeds['sensors.temperature'].value }}°F" or automated reports that include current sensor values, user information, and real-time data. Uses the powerful Liquid templating language for advanced formatting and logic. + ::: v-pre ## What is a Text Template? Think of a text template like a form letter where you leave blanks to fill in with specific information. Instead of writing "Dear _____", you write "Dear {{ user.name }}" and the system automatically fills in the actual name when the message is sent.