@@ -25,20 +25,20 @@ import { Order } from 'blockly/python';
2525import { MRC_STYLE_STEPS } from '../themes/styles' ;
2626import { ExtendedPythonGenerator } from '../editor/extended_python_generator' ;
2727import { createStepFieldFlydown } from '../fields/field_flydown' ;
28- import { BLOCK_NAME as MRC_JUMP_TO_STEP } from './mrc_jump_to_step' ;
28+ import { renameSteps as updateJumpToStepBlocks } from './mrc_jump_to_step' ;
2929import * as stepContainer from './mrc_step_container'
30- import * as value from './utils/value' ;
30+ import { createBooleanShadowValue } from './utils/value' ;
3131import * as toolboxItems from '../toolbox/items' ;
3232
3333export const BLOCK_NAME = 'mrc_steps' ;
3434
3535const INPUT_CONDITION_PREFIX = 'CONDITION_' ;
36- const INPUT_STEP_PREFIX = 'STEP_ ' ;
36+ const INPUT_STATEMENT_PREFIX = 'STATEMENT_ ' ;
3737
3838/** Extra state for serialising mrc_steps blocks. */
3939type StepsExtraState = {
4040 /**
41- * The steps
41+ * The step names.
4242 */
4343 stepNames : string [ ] ,
4444} ;
@@ -60,7 +60,6 @@ const STEPS = {
6060 this . setInputsInline ( false ) ;
6161 this . setStyle ( MRC_STYLE_STEPS ) ;
6262 this . setMutator ( stepContainer . getMutatorIcon ( this ) ) ;
63- this . updateShape_ ( ) ;
6463 } ,
6564 saveExtraState : function ( this : StepsBlock ) : StepsExtraState {
6665 return {
@@ -71,65 +70,88 @@ const STEPS = {
7170 this . mrcStepNames = state . stepNames ;
7271 this . updateShape_ ( ) ;
7372 } ,
74- compose : function ( this : StepsBlock , containerBlock : Blockly . Block ) {
75- if ( containerBlock . type !== stepContainer . STEP_CONTAINER_BLOCK_NAME ) {
76- throw new Error ( 'compose: containerBlock.type should be ' + stepContainer . STEP_CONTAINER_BLOCK_NAME ) ;
73+ /**
74+ * Populate the mutator's dialog with this block's components.
75+ */
76+ decompose : function ( this : StepsBlock , workspace : Blockly . Workspace ) : stepContainer . StepContainerBlock {
77+ const stepNames : string [ ] = [ ] ;
78+ this . mrcStepNames . forEach ( step => {
79+ stepNames . push ( step ) ;
80+ } ) ;
81+ return stepContainer . createMutatorBlocks ( workspace , stepNames ) ;
82+ } ,
83+ /**
84+ * Store condition and statement connections on the StepItemBlocks
85+ */
86+ saveConnections : function ( this : StepsBlock , containerBlock : stepContainer . StepContainerBlock ) {
87+ const stepItemBlocks : stepContainer . StepItemBlock [ ] = containerBlock . getStepItemBlocks ( ) ;
88+ for ( let i = 0 ; i < stepItemBlocks . length ; i ++ ) {
89+ const stepItemBlock = stepItemBlocks [ i ] ;
90+ const conditionInput = this . getInput ( INPUT_CONDITION_PREFIX + i ) ;
91+ stepItemBlock . conditionShadowState =
92+ conditionInput && conditionInput . connection ! . getShadowState ( true ) ;
93+ stepItemBlock . conditionTargetConnection =
94+ conditionInput && conditionInput . connection ! . targetConnection ;
95+ const statementInput = this . getInput ( INPUT_STATEMENT_PREFIX + i ) ;
96+ stepItemBlock . statementTargetConnection =
97+ statementInput && statementInput . connection ! . targetConnection ;
7798 }
78- const stepContainerBlock = containerBlock as stepContainer . StepContainerBlock ;
79- const stepItemBlocks : stepContainer . StepItemBlock [ ] = stepContainerBlock . getStepItemBlocks ( ) ;
80-
99+ } ,
100+ /**
101+ * Reconfigure this block based on the mutator dialog's components.
102+ */
103+ compose : function ( this : StepsBlock , containerBlock : stepContainer . StepContainerBlock ) {
104+ const mapOldStepNameToNewStepName : { [ newStepName : string ] : string } = { } ;
105+ const conditionShadowStates : Array < any > = [ ] ;
106+ const conditionTargetConnections : Array < Blockly . Connection | null > = [ ] ;
107+ const statementTargetConnections : Array < Blockly . Connection | null > = [ ] ;
108+
109+ const stepItemBlocks : stepContainer . StepItemBlock [ ] = containerBlock . getStepItemBlocks ( ) ;
110+
111+ // Iterate through the step item blocks to:
112+ // - Update this.mrcStepNames
113+ // - Keep track of steps that were renamed
114+ // - Collect the condition and statement connections that were saved on the StepItemBlocks.
81115 this . mrcStepNames = [ ] ;
82116 stepItemBlocks . forEach ( ( stepItemBlock ) => {
83- this . mrcStepNames . push ( stepItemBlock . getName ( ) ) ;
84- } ) ;
85-
86- // Update jump blocks for any renamed steps
87- const workspace = this . workspace ;
88- const jumpBlocks = workspace . getBlocksByType ( MRC_JUMP_TO_STEP , false ) ;
89- stepItemBlocks . forEach ( ( stepItemBlock ) => {
90- const oldName = stepItemBlock . getOriginalName ( ) ;
91- const newName = stepItemBlock . getName ( ) ;
92- if ( oldName && oldName !== newName ) {
93- jumpBlocks . forEach ( ( jumpBlock ) => {
94- if ( jumpBlock . getFieldValue ( 'STEP_NAME' ) === oldName ) {
95- jumpBlock . setFieldValue ( newName , 'STEP_NAME' ) ;
96- }
97- } ) ;
117+ const oldStepName = stepItemBlock . getOriginalName ( ) ;
118+ const newStepName = stepItemBlock . getName ( ) ;
119+ stepItemBlock . setOriginalName ( newStepName ) ;
120+ this . mrcStepNames . push ( newStepName ) ;
121+ if ( oldStepName !== newStepName ) {
122+ mapOldStepNameToNewStepName [ oldStepName ] = newStepName ;
98123 }
124+ conditionShadowStates . push ( stepItemBlock . conditionShadowState ) ;
125+ conditionTargetConnections . push ( stepItemBlock . conditionTargetConnection as Blockly . Connection | null ) ;
126+ statementTargetConnections . push ( stepItemBlock . statementTargetConnection as Blockly . Connection | null ) ;
99127 } ) ;
100128
101129 this . updateShape_ ( ) ;
102130
103- // Add a shadow True block to each empty condition input.
104- for ( var i = 0 ; i < this . mrcStepNames . length ; i ++ ) {
131+ // Reconnect blocks.
132+ for ( let i = 0 ; i < this . mrcStepNames . length ; i ++ ) {
133+ // Reconnect the condition.
134+ conditionTargetConnections [ i ] ?. reconnect ( this , INPUT_CONDITION_PREFIX + i ) ;
135+ // Add the boolean shadow block to the condition input. This must be done after the condition
136+ // has been reconnected. If it is done before the condition is reconnected, the shadow will
137+ // become disconnected.
138+ const conditionShadowState = conditionShadowStates [ i ] || createBooleanShadowValue ( true ) . shadow ;
105139 const conditionInput = this . getInput ( INPUT_CONDITION_PREFIX + i ) ;
106- if ( conditionInput && ! conditionInput . connection ?. targetConnection ) {
107- const shadowBlock = this . workspace . newBlock ( 'logic_boolean' ) as Blockly . BlockSvg ;
108- shadowBlock . setShadow ( true ) ;
109- shadowBlock . setFieldValue ( 'TRUE' , 'BOOL' ) ;
110- if ( this . workspace . rendered ) {
111- shadowBlock . initSvg ( ) ;
112- shadowBlock . render ( ) ;
113- }
114- conditionInput . connection ?. connect ( shadowBlock . outputConnection ! ) ;
115- }
140+ conditionInput ?. connection ?. setShadowState ( conditionShadowState as any ) ;
141+ // Reconnect the statement.
142+ statementTargetConnections [ i ] ?. reconnect ( this , INPUT_STATEMENT_PREFIX + i ) ;
143+ }
144+
145+ if ( Object . keys ( mapOldStepNameToNewStepName ) . length ) {
146+ // Update jump blocks for any renamed steps.
147+ updateJumpToStepBlocks ( this . workspace , mapOldStepNameToNewStepName ) ;
116148 }
117- } ,
118- decompose : function ( this : StepsBlock , workspace : Blockly . Workspace ) {
119- const stepNames : string [ ] = [ ] ;
120- this . mrcStepNames . forEach ( step => {
121- stepNames . push ( step ) ;
122- } ) ;
123- return stepContainer . createMutatorBlocks ( workspace , stepNames ) ;
124149 } ,
125150 /**
126- * mrcOnMutatorOpen is called when the mutator on an EventBlock is opened.
151+ * mrcOnMutatorOpen is called when the mutator on an StepsBlock is opened.
127152 */
128153 mrcOnMutatorOpen : function ( this : StepsBlock ) : void {
129154 stepContainer . onMutatorOpen ( this ) ;
130- } ,
131- mrcOnChange : function ( this : StepsBlock ) : void {
132-
133155 } ,
134156 mrcUpdateStepName : function ( this : StepsBlock , step : number , newName : string ) : string {
135157 const oldName = this . mrcStepNames [ step ] ;
@@ -152,99 +174,31 @@ const STEPS = {
152174 }
153175 this . mrcStepNames [ step ] = currentName ;
154176
155- // Update all mrc_jump_to_step blocks that reference the old name
156177 if ( oldName !== currentName ) {
157- const workspace = this . workspace ;
158- const jumpBlocks = workspace . getBlocksByType ( MRC_JUMP_TO_STEP , false ) ;
159- jumpBlocks . forEach ( ( jumpBlock ) => {
160- if ( jumpBlock . getFieldValue ( 'STEP_NAME' ) === oldName ) {
161- jumpBlock . setFieldValue ( currentName , 'STEP_NAME' ) ;
162- }
163- } ) ;
178+ // Update all mrc_jump_to_step blocks that reference the old name
179+ const mapOldStepNameToNewStepName : { [ newStepName : string ] : string } = { } ;
180+ mapOldStepNameToNewStepName [ oldName ] = currentName ;
181+ updateJumpToStepBlocks ( this . workspace , mapOldStepNameToNewStepName ) ;
164182 }
165183
166184 return currentName ;
167185 } ,
168186 updateShape_ : function ( this : StepsBlock ) : void {
169- // Build a map of step names to their current input indices
170- const currentStepMap : { [ stepName : string ] : number } = { } ;
171- let i = 0 ;
172- while ( this . getInput ( INPUT_CONDITION_PREFIX + i ) ) {
173- const conditionInput = this . getInput ( INPUT_CONDITION_PREFIX + i ) ;
174- const field = conditionInput ?. fieldRow [ 0 ] ;
175- if ( field ) {
176- currentStepMap [ field . getValue ( ) ] = i ;
177- }
178- i ++ ;
179- }
180-
181- // For each new step position, find where it currently is (if it exists)
182- for ( let j = 0 ; j < this . mrcStepNames . length ; j ++ ) {
183- const stepName = this . mrcStepNames [ j ] ;
184- const currentIndex = currentStepMap [ stepName ] ;
185-
186- if ( currentIndex !== undefined && currentIndex !== j ) {
187- // Step exists but is at wrong position - move it
188- const conditionConnection = this . getInput ( INPUT_CONDITION_PREFIX + currentIndex ) ?. connection ?. targetConnection ;
189- const stepConnection = this . getInput ( INPUT_STEP_PREFIX + currentIndex ) ?. connection ?. targetConnection ;
190-
191- // Temporarily disconnect
192- if ( conditionConnection ) {
193- conditionConnection . disconnect ( ) ;
194- }
195- if ( stepConnection ) {
196- stepConnection . disconnect ( ) ;
197- }
198-
199- // Remove old inputs
200- this . removeInput ( INPUT_CONDITION_PREFIX + currentIndex , false ) ;
201- this . removeInput ( INPUT_STEP_PREFIX + currentIndex , false ) ;
202-
203- // Create new inputs at correct position
204- const fieldFlydown = createStepFieldFlydown ( stepName , true ) ;
205- fieldFlydown . setValidator ( this . mrcUpdateStepName . bind ( this , j ) ) ;
206-
207- this . appendValueInput ( INPUT_CONDITION_PREFIX + j )
208- . appendField ( fieldFlydown )
209- . setCheck ( 'Boolean' )
210- . appendField ( Blockly . Msg . REPEAT_UNTIL ) ;
211- this . appendStatementInput ( INPUT_STEP_PREFIX + j ) ;
212-
213- // Reconnect
214- if ( conditionConnection ) {
215- this . getInput ( INPUT_CONDITION_PREFIX + j ) ?. connection ?. connect ( conditionConnection ) ;
216- }
217- if ( stepConnection ) {
218- this . getInput ( INPUT_STEP_PREFIX + j ) ?. connection ?. connect ( stepConnection ) ;
219- }
220-
221- delete currentStepMap [ stepName ] ;
222- } else if ( currentIndex !== undefined ) {
223- // Step is at correct position - just update the field
224- const conditionInput = this . getInput ( INPUT_CONDITION_PREFIX + j ) ;
225- const field = conditionInput ?. fieldRow [ 0 ] ;
226- if ( field && field . getValue ( ) !== stepName ) {
227- field . setValue ( stepName ) ;
228- }
229- delete currentStepMap [ stepName ] ;
230- } else {
231- // Step doesn't exist - create it
232- const fieldFlydown = createStepFieldFlydown ( stepName , true ) ;
233- fieldFlydown . setValidator ( this . mrcUpdateStepName . bind ( this , j ) ) ;
234-
235- this . appendValueInput ( INPUT_CONDITION_PREFIX + j )
236- . appendField ( fieldFlydown )
237- . setCheck ( 'Boolean' )
238- . appendField ( Blockly . Msg . REPEAT_UNTIL ) ;
239- this . appendStatementInput ( INPUT_STEP_PREFIX + j ) ;
240- }
187+ // Remove all inputs.
188+ for ( let i = 0 ; this . getInput ( INPUT_CONDITION_PREFIX + i ) ; i ++ ) {
189+ this . removeInput ( INPUT_CONDITION_PREFIX + i ) ;
190+ this . removeInput ( INPUT_STATEMENT_PREFIX + i ) ;
241191 }
242-
243- // Remove any leftover inputs (steps that were deleted)
244- for ( const stepName in currentStepMap ) {
245- const index = currentStepMap [ stepName ] ;
246- this . removeInput ( INPUT_CONDITION_PREFIX + index , false ) ;
247- this . removeInput ( INPUT_STEP_PREFIX + index , false ) ;
192+ // Add inputs for each step.
193+ for ( let i = 0 ; i < this . mrcStepNames . length ; i ++ ) {
194+ const stepName = this . mrcStepNames [ i ] ;
195+ const fieldFlydown = createStepFieldFlydown ( stepName , true ) ;
196+ fieldFlydown . setValidator ( this . mrcUpdateStepName . bind ( this , i ) ) ;
197+ this . appendValueInput ( INPUT_CONDITION_PREFIX + i )
198+ . appendField ( fieldFlydown )
199+ . setCheck ( 'Boolean' )
200+ . appendField ( Blockly . Msg . REPEAT_UNTIL ) ;
201+ this . appendStatementInput ( INPUT_STATEMENT_PREFIX + i ) ;
248202 }
249203 } ,
250204 mrcGetStepNames : function ( this : StepsBlock ) : string [ ] {
@@ -276,11 +230,11 @@ export const pythonFromBlock = function (
276230 code += generator . INDENT + 'match self._current_step:\n' ;
277231 block . mrcStepNames . forEach ( ( stepName , index ) => {
278232 code += generator . INDENT . repeat ( 2 ) + `case "${ stepName } ":\n` ;
279- let stepCode = generator . statementToCode ( block , INPUT_STEP_PREFIX + index ) ;
233+ const stepCode = generator . statementToCode ( block , INPUT_STATEMENT_PREFIX + index ) ;
280234 if ( stepCode !== '' ) {
281235 code += generator . prefixLines ( stepCode , generator . INDENT . repeat ( 2 ) ) ;
282236 }
283- let conditionCode = generator . valueToCode ( block , INPUT_CONDITION_PREFIX + index , Order . NONE ) || 'False' ;
237+ const conditionCode = generator . valueToCode ( block , INPUT_CONDITION_PREFIX + index , Order . NONE ) || 'False' ;
284238 code += generator . INDENT . repeat ( 3 ) + 'if ' + conditionCode + ':\n' ;
285239 if ( index === block . mrcStepNames . length - 1 ) {
286240 code += generator . INDENT . repeat ( 4 ) + 'self._current_step = None\n' ;
@@ -300,6 +254,6 @@ export function createStepsBlock(): toolboxItems.Block {
300254 } ;
301255 const fields : { [ key : string ] : any } = { } ;
302256 const inputs : { [ key : string ] : any } = { } ;
303- inputs [ INPUT_CONDITION_PREFIX + 0 ] = value . createBooleanShadowValue ( true ) ;
257+ inputs [ INPUT_CONDITION_PREFIX + 0 ] = createBooleanShadowValue ( true ) ;
304258 return new toolboxItems . Block ( BLOCK_NAME , extraState , fields , inputs ) ;
305259}
0 commit comments