[Google Blockly] Add support for Behavior blocks#53154
Conversation
This reverts commit 4e206dd.
was breaking unit tests
| }, | ||
|
|
||
| installBehaviorBlocks(behaviorEditor) { | ||
| const generator = Blockly.getGenerator(); |
There was a problem hiding this comment.
Can we extract this logic out into another file? It's a bunch of code and this file will be more readable if it doesn't contains super-specific logic.
There was a problem hiding this comment.
Which logic do you think it makes the most sense to move? The code for installBehaviorBlocks?
There was a problem hiding this comment.
yeah we could just pull all the code out (and pass in a generator?) or pull out functions/objects that get assigned to various fields on generator. Basically I want to avoid making this another mega-file if we can avoid it, but I don't have a super strong opinion on the specific refactoring here.
There was a problem hiding this comment.
Assuming you're okay with tracking this as a follow-up, I added it here: https://codedotorg.atlassian.net/browse/CT-13
* remove unused import * remove unnecessary whitespace diff * expand definition in function names and convert to arrow function * refactor to use partitionBlocksByType * add comment to behaviorDefMutator * remove unused function * fix comment that indicates returning xml * add missing argument * fix flyout category callbacks * remove console log * add id to call block and sort by name * remove console.logs * convert to arrow function * update comment
|
@maribethb My edits are in if you'd like to give this a once-over! I understand it's a large PR with lots of Code.org-specific logic, so your relevant parts are limited. |
molly-moen
left a comment
There was a problem hiding this comment.
A couple nits/questions for my information but overall this looks good!
|
|
||
| // In Lab2, the level properties are in Redux, not appOptions. To make this work in Lab2, | ||
| // we would need to send that property from the backend and save it in lab2Redux. | ||
| const useModalFunctionEditor = window.appOptions?.level?.useModalFunctionEditor; |
There was a problem hiding this comment.
we can use Blockly.useModalFunctionEditor once this gets merged (can be done as a follow up)
There was a problem hiding this comment.
|
|
||
| // In Lab2, the level properties are in Redux, not appOptions. To make this work in Lab2, | ||
| // we would need to send that property from the backend and save it in lab2Redux. | ||
| const useModalFunctionEditor = window.appOptions?.level?.useModalFunctionEditor; |
There was a problem hiding this comment.
should we also incorporate the experiment flag here?
There was a problem hiding this comment.
I'm not sure we need it here until the behavior editor exists. I think the implications are:
- If the modal function editor is enabled for the level, but the experiment isn't on, the names of the behaviors aren't editable anyway (doesn't seem like a big deal?). Option: We could require both the modal function editor to be enabled and the experiment to be on to keep the names non-editable.
{
type: useModalFunctionEditor ? 'field_label' : 'field_input',
name: 'NAME',
text: '',
spellcheck: false,
},
- If the modal function editor is enabled for a level, we don't render the SVG frame for the behavior definition blocks, even though those definition blocks get defined on the main workspace until the editor works. Option: We could render the SVG frame for the behavior definition blocks even when the modal function editor is enabled as long as the experiment is off.
if (!useModalFunctionEditor && !this.workspace.noFunctionBlockFrame) {
this.functionalSvg_ = new BlockSvgFrame(
this,
msg.behaviorEditorHeader(),
'blocklyFunctionalFrame'
);
this.setOnChange(function () {
if (!this.isInFlyout) {
this.functionalSvg_.render(this.svgGroup_, this.RTL);
}
});
}
- Because there's no alternate callback to open the behavior editor yet, the code below doesn't need to be modified.
} else if (useModalFunctionEditor) {
workspace.registerButtonCallback('createNewBehavior', createNewBehavior);
blockList.push(newBehaviorButton);
Thoughts?
| lowestBlockBottom = blockBottom; | ||
| } | ||
| }); | ||
| return lowestBlockBottom + 16; |
There was a problem hiding this comment.
why do we add 16 here? Can you add a comment explaining this?
There was a problem hiding this comment.
My understanding is this is an arbitrary offset for when we add a new definition block to the bottom of of the blockspace. I added a constant BLOCK_OFFSET at the top of the file since we use 16 both for x and y in createNewDefinitionBlock, so it's now:
const newDefinitionBlock = Blockly.serialization.blocks.append(
{...blockState, x: BLOCK_OFFSET, y: getLowestBlockBottomY() + BLOCK_OFFSET},
Blockly.getMainWorkspace()
);
It's worth noting that eventually, this'll get removed.
| block.getFieldValue('NAME'), | ||
| Blockly.Names.NameType.PROCEDURE | ||
| ); | ||
| let xfix1 = ''; |
There was a problem hiding this comment.
I'm not sure what xfix1 is. Can you add a comment and/or update the variable name?
There was a problem hiding this comment.
same for xfix2 and branch
| this.getFieldValue('NAME'), | ||
| 'PROCEDURE' | ||
| ); | ||
| return [`new Behavior(${name}, [])`, generator.ORDER_ATOMIC]; |
There was a problem hiding this comment.
(can be a follow up) should new Behavior be translated?
There was a problem hiding this comment.
There was a problem hiding this comment.
I don't know that we can just start translating this since it's the student's JavaScript code. If we translated new Behavior, the JS interpreter probably wouldn't know how to handle it.
| }; | ||
|
|
||
| const originalSetOutput = blocklyWrapper.Block.prototype.setOutput; | ||
| blocklyWrapper.Block.prototype.setOutput = function (isOutput, check) { |
There was a problem hiding this comment.
what is the purpose of this?
There was a problem hiding this comment.
Blockly has a setOutput method, in which null specifies any type could be returned: https://developers.google.com/blockly/reference/js/blockly.block_class.setoutput_1_method
We have a "None" type specified in our constants, which we seem to have used to mean the same thing (any/a variable type as output):
So, this code replaces the original setOutput method with a custom version that will handle the case when "None" is passed appropriately. That's my guess, anyway 😅 Thoughts?
There was a problem hiding this comment.
seems right! Maybe worth adding a comment?
| export const nameComparator = (a, b) => { | ||
| const nameA = a.name.toUpperCase(); // ignore upper and lowercase | ||
| const nameB = b.name.toUpperCase(); // ignore upper and lowercase | ||
| if (nameA < nameB) { |
There was a problem hiding this comment.
I didn't know you could do this with strings!
There was a problem hiding this comment.
Yeah! For reference: https://javascript.info/comparison#string-comparison
…s. labels) unless experiment is turned on
This builds on the work to implement Blockly's shareable procedures blocks plugin. This adds support for behavior blocks. Last week, we merged support for function blocks, which were a bit more straightforward:
Behavior blocks have some additional peculiarities.
Note: The current Blockly labs are Dance, Poetry, Flappy, and Bounce. Since behaviors are not used in any of these labs, most of the changes in this PR are not discoverable unless you use the ?blocklyVersion=google URL param to force a non-Blockly lab (e.g., Sprite Lab) to use Google Blockly.
Expand here for more information about how behavior blocks work as of this PR
The screenshots below compare the behavior in Sprite Lab on studio.code.org using CDO Blockly to Sprite Lab on localhost using Google Blockly. Note: The following screenshots assume that
useModalFunctionEditoris true (the default for Sprite Lab projects) but that themodalFunctionEditorexperiment is disabled. There are two control levels because Dance Party levels use functions, and some Dance Party levels are configured to have useModalFunctionEditor set to true. This means that, on the function side, we could expose the unfinished function editor on any level that accidentally has that flag set incorrectly. There were too many levels with this flag set to true to change this on the level-file side, so instead, Molly added an experiment flag so thatuseModalFunctionEditorhas to be true and the experiment has to be enabled for the editor to be rendered. The following tables describe the interactions of those two flags and their effect on functionality.For Functions
useModalFunctionEditorflagmodalFunctionEditorexperimentThis PR does not cover enabling the “behavior editor” (i.e., the function editor for behaviors). Therefore, the following behavior is expected:
For Behaviors
useModalFunctionEditorflagmodalFunctionEditorexperimentThe screenshots in the "Testing" section focus specifically on case 2 above.
Links
Jira ticket: https://codedotorg.atlassian.net/browse/SL-641
Testing story
Before
In the previous Blockly version, behavior definition blocks were invisible on the main workspace.
After
Mainline/Google Blockly no longer supports invisible blocks. For now, the definition blocks for default behaviors (wobbling, growing, etc.) will be displayed on the main workspace, where they can also be edited. A follow-up PR will add these to the hidden definition workspace. At that point, the behavior definition blocks will no longer be visible here.
Before
After
Some of the styling (ex. gray “Create a Behavior” button) still needs to be updated, but the current Behavior category toolbox has parity with the CDO toolbox. (Note: Even though the edit buttons do not currently appear on the call blocks while they are in the toolbox, they appear once dragged to the main workspace.)
Before
After
Clicking the “Create a Behavior” button creates a new, editable behavior definition block on the main workspace. The corresponding call block is added to the toolbox.
Testing was done in English, Spanish, and Arabic. Reach out to @ebeastlake for a spreadsheet that documents manual testing.
Follow-up work
See: https://codedotorg.atlassian.net/browse/CT-13
PR Checklist: