@@ -30,6 +30,7 @@ import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './ut
30
30
import * as toolboxItems from '../toolbox/items' ;
31
31
import * as storageModule from '../storage/module' ;
32
32
import * as storageModuleContent from '../storage/module_content' ;
33
+ import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder' ;
33
34
import { createPort } from './mrc_port' ;
34
35
import { ClassData , FunctionData } from './utils/python_json_types' ;
35
36
import { renameMethodCallers } from './mrc_call_python_function'
@@ -41,6 +42,8 @@ export const OUTPUT_NAME = 'mrc_component';
41
42
export const FIELD_NAME = 'NAME' ;
42
43
export const FIELD_TYPE = 'TYPE' ;
43
44
45
+ const WARNING_ID_NOT_IN_HOLDER = 'not in holder' ;
46
+
44
47
type ConstructorArg = {
45
48
name : string ,
46
49
type : string ,
@@ -60,6 +63,15 @@ interface ComponentMixin extends ComponentMixinType {
60
63
mrcArgs : ConstructorArg [ ] ,
61
64
mrcImportModule : string ,
62
65
mrcStaticFunctionName : string ,
66
+
67
+ /**
68
+ * mrcHasNotInHolderWarning is set to true if we set the NOT_IN_HOLDER warning text on the block.
69
+ * It is checked to avoid adding a warning if there already is one. Otherwise, if we get two move
70
+ * events (one for drag and one for snap), and we call setWarningText for both events, we get a
71
+ * detached warning balloon.
72
+ * See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
73
+ */
74
+ mrcHasNotInHolderWarning : boolean ,
63
75
}
64
76
type ComponentMixinType = typeof COMPONENT ;
65
77
@@ -68,6 +80,7 @@ const COMPONENT = {
68
80
* Block initialization.
69
81
*/
70
82
init : function ( this : ComponentBlock ) : void {
83
+ this . mrcHasNotInHolderWarning = false ;
71
84
this . setStyle ( MRC_STYLE_COMPONENTS ) ;
72
85
const nameField = new Blockly . FieldTextInput ( '' )
73
86
nameField . setValidator ( this . mrcNameFieldValidator . bind ( this , nameField ) ) ;
@@ -90,8 +103,8 @@ const COMPONENT = {
90
103
if ( this . mrcArgs ) {
91
104
this . mrcArgs . forEach ( ( arg ) => {
92
105
extraState . params ! . push ( {
93
- ' name' : arg . name ,
94
- ' type' : arg . type ,
106
+ name : arg . name ,
107
+ type : arg . type ,
95
108
} ) ;
96
109
} ) ;
97
110
}
@@ -115,12 +128,11 @@ const COMPONENT = {
115
128
if ( extraState . params ) {
116
129
extraState . params . forEach ( ( arg ) => {
117
130
this . mrcArgs . push ( {
118
- ' name' : arg . name ,
119
- ' type' : arg . type ,
131
+ name : arg . name ,
132
+ type : arg . type ,
120
133
} ) ;
121
134
} ) ;
122
135
}
123
- this . mrcArgs = extraState . params ? extraState . params : [ ] ;
124
136
this . updateBlock_ ( ) ;
125
137
} ,
126
138
/**
@@ -167,15 +179,45 @@ const COMPONENT = {
167
179
getArgName : function ( this : ComponentBlock , _ : number ) : string {
168
180
return this . getFieldValue ( FIELD_NAME ) + '__' + 'port' ;
169
181
} ,
170
-
171
-
172
182
getComponentPorts : function ( this : ComponentBlock , ports : { [ argName : string ] : string } ) : void {
173
183
// Collect the ports for this component block.
174
184
for ( let i = 0 ; i < this . mrcArgs . length ; i ++ ) {
175
185
const argName = this . getArgName ( i ) ;
176
186
ports [ argName ] = this . mrcArgs [ i ] . name ;
177
187
}
178
188
} ,
189
+ /**
190
+ * mrcOnLoad is called for each ComponentBlock when the blocks are loaded in the blockly workspace.
191
+ */
192
+ mrcOnLoad : function ( this : ComponentBlock ) : void {
193
+ this . checkBlockIsInHolder ( ) ;
194
+ } ,
195
+ /**
196
+ * mrcOnMove is called when a ComponentBlock is moved.
197
+ */
198
+ mrcOnMove : function ( this : ComponentBlock ) : void {
199
+ this . checkBlockIsInHolder ( ) ;
200
+ } ,
201
+ checkBlockIsInHolder : function ( this : ComponentBlock ) : void {
202
+ const rootBlock : Blockly . Block | null = this . getRootBlock ( ) ;
203
+ if ( rootBlock && rootBlock . type === MRC_MECHANISM_COMPONENT_HOLDER ) {
204
+ // If the root block is the mechanism_component_holder, the component block is allowed to stay.
205
+ // Remove any previous warning.
206
+ this . setWarningText ( null , WARNING_ID_NOT_IN_HOLDER ) ;
207
+ this . mrcHasNotInHolderWarning = false ;
208
+ } else {
209
+ // Otherwise, add a warning to the block.
210
+ this . unplug ( true ) ;
211
+ if ( ! this . mrcHasNotInHolderWarning ) {
212
+ this . setWarningText ( Blockly . Msg . WARNING_COMPONENT_NOT_IN_HOLDER , WARNING_ID_NOT_IN_HOLDER ) ;
213
+ const icon = this . getIcon ( Blockly . icons . IconType . WARNING ) ;
214
+ if ( icon ) {
215
+ icon . setBubbleVisible ( true ) ;
216
+ }
217
+ this . mrcHasNotInHolderWarning = true ;
218
+ }
219
+ }
220
+ } ,
179
221
/**
180
222
* mrcChangeIds is called when a module is copied so that the copy has different ids than the original.
181
223
*/
@@ -253,8 +295,8 @@ function createComponentBlock(
253
295
254
296
if ( constructorData . expectedPortType ) {
255
297
extraState . params ! . push ( {
256
- ' name' : constructorData . expectedPortType ,
257
- ' type' : 'Port' ,
298
+ name : constructorData . expectedPortType ,
299
+ type : 'Port' ,
258
300
} ) ;
259
301
if ( moduleType == storageModule . ModuleType . ROBOT ) {
260
302
inputs [ 'ARG0' ] = createPort ( constructorData . expectedPortType ) ;
0 commit comments