blockly 7.20211209.4 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +852 -844
  3. package/blockly_compressed.js +669 -664
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +41 -27
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +146 -141
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +19 -9
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/dart_compressed.js +40 -43
  208. package/dart_compressed.js.map +1 -1
  209. package/generators/dart/colour.js +56 -64
  210. package/generators/dart/lists.js +61 -50
  211. package/generators/dart/math.js +160 -148
  212. package/generators/dart/text.js +83 -61
  213. package/generators/javascript/colour.js +37 -34
  214. package/generators/javascript/lists.js +50 -43
  215. package/generators/javascript/math.js +123 -139
  216. package/generators/javascript/text.js +67 -81
  217. package/generators/lua/colour.js +25 -23
  218. package/generators/lua/lists.js +97 -69
  219. package/generators/lua/logic.js +1 -2
  220. package/generators/lua/math.js +182 -144
  221. package/generators/lua/text.js +116 -99
  222. package/generators/php/colour.js +38 -32
  223. package/generators/php/lists.js +109 -89
  224. package/generators/php/math.js +90 -81
  225. package/generators/php/text.js +63 -61
  226. package/generators/python/colour.js +18 -18
  227. package/generators/python/lists.js +38 -30
  228. package/generators/python/loops.js +12 -8
  229. package/generators/python/math.js +104 -106
  230. package/generators/python/text.js +34 -30
  231. package/javascript_compressed.js +37 -39
  232. package/javascript_compressed.js.map +1 -1
  233. package/lua_compressed.js +39 -42
  234. package/lua_compressed.js.map +1 -1
  235. package/msg/az.js +2 -2
  236. package/msg/be.js +4 -4
  237. package/msg/cs.js +15 -15
  238. package/msg/de.js +1 -1
  239. package/msg/diq.js +1 -1
  240. package/msg/eo.js +1 -1
  241. package/msg/es.js +1 -1
  242. package/msg/fa.js +1 -1
  243. package/msg/fr.js +4 -4
  244. package/msg/he.js +1 -1
  245. package/msg/hr.js +2 -2
  246. package/msg/hy.js +2 -2
  247. package/msg/id.js +12 -12
  248. package/msg/inh.js +14 -14
  249. package/msg/ja.js +7 -7
  250. package/msg/lv.js +29 -29
  251. package/msg/pa.js +3 -3
  252. package/msg/smn.js +436 -0
  253. package/msg/te.js +1 -1
  254. package/msg/yue.js +1 -1
  255. package/msg/zh-hans.js +3 -3
  256. package/msg/zh-hant.js +3 -3
  257. package/package.json +7 -6
  258. package/php_compressed.js +38 -42
  259. package/php_compressed.js.map +1 -1
  260. package/python_compressed.js +26 -25
  261. package/python_compressed.js.map +1 -1
  262. package/blocks/all.js +0 -23
package/core/block_svg.js CHANGED
@@ -25,13 +25,15 @@ const constants = goog.require('Blockly.constants');
25
25
  const dom = goog.require('Blockly.utils.dom');
26
26
  const eventUtils = goog.require('Blockly.Events.utils');
27
27
  const internalConstants = goog.require('Blockly.internalConstants');
28
- const object = goog.require('Blockly.utils.object');
29
28
  const svgMath = goog.require('Blockly.utils.svgMath');
30
29
  const userAgent = goog.require('Blockly.utils.userAgent');
31
30
  const {ASTNode} = goog.require('Blockly.ASTNode');
32
31
  const {Block} = goog.require('Blockly.Block');
33
32
  /* eslint-disable-next-line no-unused-vars */
33
+ const {BlockMove} = goog.requireType('Blockly.Events.BlockMove');
34
+ /* eslint-disable-next-line no-unused-vars */
34
35
  const {Comment} = goog.requireType('Blockly.Comment');
36
+ const {config} = goog.require('Blockly.config');
35
37
  const {ConnectionType} = goog.require('Blockly.ConnectionType');
36
38
  /* eslint-disable-next-line no-unused-vars */
37
39
  const {Connection} = goog.requireType('Blockly.Connection');
@@ -81,1700 +83,1812 @@ goog.require('Blockly.Touch');
81
83
  /**
82
84
  * Class for a block's SVG representation.
83
85
  * Not normally called directly, workspace.newBlock() is preferred.
84
- * @param {!WorkspaceSvg} workspace The block's workspace.
85
- * @param {?string} prototypeName Name of the language object containing
86
- * type-specific functions for this block.
87
- * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
88
- * create a new ID.
89
86
  * @extends {Block}
90
87
  * @implements {IASTNodeLocationSvg}
91
88
  * @implements {IBoundedElement}
92
89
  * @implements {ICopyable}
93
90
  * @implements {IDraggable}
94
- * @constructor
95
91
  * @alias Blockly.BlockSvg
96
92
  */
97
- const BlockSvg = function(workspace, prototypeName, opt_id) {
98
- // Create core elements for the block.
93
+ class BlockSvg extends Block {
99
94
  /**
100
- * @type {!SVGGElement}
101
- * @private
95
+ * @param {!WorkspaceSvg} workspace The block's workspace.
96
+ * @param {string} prototypeName Name of the language object containing
97
+ * type-specific functions for this block.
98
+ * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
99
+ * create a new ID.
102
100
  */
103
- this.svgGroup_ = dom.createSvgElement(Svg.G, {}, null);
104
- this.svgGroup_.translate_ = '';
101
+ constructor(workspace, prototypeName, opt_id) {
102
+ super(workspace, prototypeName, opt_id);
103
+
104
+ /**
105
+ * An optional method called when a mutator dialog is first opened.
106
+ * This function must create and initialize a top-level block for the
107
+ * mutator dialog, and return it. This function should also populate this
108
+ * top-level block with any sub-blocks which are appropriate. This method
109
+ * must also be coupled with defining a `compose` method for the default
110
+ * mutation dialog button and UI to appear.
111
+ * @type {undefined|?function(WorkspaceSvg):!BlockSvg}
112
+ */
113
+ this.decompose = this.decompose;
114
+
115
+ /**
116
+ * An optional method called when a mutator dialog saves its content.
117
+ * This function is called to modify the original block according to new
118
+ * settings. This method must also be coupled with defining a `decompose`
119
+ * method for the default mutation dialog button and UI to appear.
120
+ * @type {undefined|?function(!BlockSvg)}
121
+ */
122
+ this.compose = this.compose;
123
+
124
+ /**
125
+ * An optional method called by the default mutator UI which gives the block
126
+ * a chance to save information about what child blocks are connected to
127
+ * what mutated connections.
128
+ * @type {undefined|?function(!BlockSvg)}
129
+ */
130
+ this.saveConnections = this.saveConnections;
131
+
132
+ /**
133
+ * An optional method for defining custom block context menu items.
134
+ * @type {undefined|?function(!Array<!Object>)}
135
+ */
136
+ this.customContextMenu = this.customContextMenu;
137
+
138
+ /**
139
+ * An property used internally to reference the block's rendering debugger.
140
+ * @type {?BlockRenderingDebug}
141
+ * @package
142
+ */
143
+ this.renderingDebugger = null;
144
+
145
+ /**
146
+ * Height of this block, not including any statement blocks above or below.
147
+ * Height is in workspace units.
148
+ * @type {number}
149
+ */
150
+ this.height = 0;
151
+
152
+ /**
153
+ * Width of this block, including any connected value blocks.
154
+ * Width is in workspace units.
155
+ * @type {number}
156
+ */
157
+ this.width = 0;
158
+
159
+ /**
160
+ * Map from IDs for warnings text to PIDs of functions to apply them.
161
+ * Used to be able to maintain multiple warnings.
162
+ * @type {Object<string, number>}
163
+ * @private
164
+ */
165
+ this.warningTextDb_ = null;
166
+
167
+ /**
168
+ * Block's mutator icon (if any).
169
+ * @type {?Mutator}
170
+ */
171
+ this.mutator = null;
172
+
173
+ /**
174
+ * Block's comment icon (if any).
175
+ * @type {?Comment}
176
+ * @deprecated August 2019. Use getCommentIcon instead.
177
+ */
178
+ this.comment = null;
179
+
180
+ /**
181
+ * Block's comment icon (if any).
182
+ * @type {?Comment}
183
+ * @private
184
+ */
185
+ this.commentIcon_ = null;
186
+
187
+ /**
188
+ * Block's warning icon (if any).
189
+ * @type {?Warning}
190
+ */
191
+ this.warning = null;
192
+
193
+ // Create core elements for the block.
194
+ /**
195
+ * @type {!SVGGElement}
196
+ * @private
197
+ */
198
+ this.svgGroup_ = dom.createSvgElement(Svg.G, {}, null);
199
+ this.svgGroup_.translate_ = '';
200
+
201
+ /**
202
+ * A block style object.
203
+ * @type {!Theme.BlockStyle}
204
+ */
205
+ this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
206
+
207
+ /**
208
+ * The renderer's path object.
209
+ * @type {IPathObject}
210
+ * @package
211
+ */
212
+ this.pathObject =
213
+ workspace.getRenderer().makePathObject(this.svgGroup_, this.style);
214
+
215
+ /** @type {boolean} */
216
+ this.rendered = false;
217
+ /**
218
+ * Is this block currently rendering? Used to stop recursive render calls
219
+ * from actually triggering a re-render.
220
+ * @type {boolean}
221
+ * @private
222
+ */
223
+ this.renderIsInProgress_ = false;
224
+
225
+ /**
226
+ * Whether mousedown events have been bound yet.
227
+ * @type {boolean}
228
+ * @private
229
+ */
230
+ this.eventsInit_ = false;
231
+
232
+ /** @type {!WorkspaceSvg} */
233
+ this.workspace;
234
+ /** @type {RenderedConnection} */
235
+ this.outputConnection;
236
+ /** @type {RenderedConnection} */
237
+ this.nextConnection;
238
+ /** @type {RenderedConnection} */
239
+ this.previousConnection;
240
+
241
+ /**
242
+ * Whether to move the block to the drag surface when it is dragged.
243
+ * True if it should move, false if it should be translated directly.
244
+ * @type {boolean}
245
+ * @private
246
+ */
247
+ this.useDragSurface_ =
248
+ svgMath.is3dSupported() && !!workspace.getBlockDragSurface();
249
+
250
+ const svgPath = this.pathObject.svgPath;
251
+ svgPath.tooltip = this;
252
+ Tooltip.bindMouseEvents(svgPath);
253
+
254
+ // Expose this block's ID on its top-level SVG group.
255
+ if (this.svgGroup_.dataset) {
256
+ this.svgGroup_.dataset['id'] = this.id;
257
+ } else if (userAgent.IE) {
258
+ // SVGElement.dataset is not available on IE11, but data-* properties
259
+ // can be set with setAttribute().
260
+ this.svgGroup_.setAttribute('data-id', this.id);
261
+ }
262
+
263
+ this.doInit_();
264
+ }
105
265
 
106
266
  /**
107
- * A block style object.
108
- * @type {!Theme.BlockStyle}
267
+ * Create and initialize the SVG representation of the block.
268
+ * May be called more than once.
109
269
  */
110
- this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
270
+ initSvg() {
271
+ if (!this.workspace.rendered) {
272
+ throw TypeError('Workspace is headless.');
273
+ }
274
+ for (let i = 0, input; (input = this.inputList[i]); i++) {
275
+ input.init();
276
+ }
277
+ const icons = this.getIcons();
278
+ for (let i = 0; i < icons.length; i++) {
279
+ icons[i].createIcon();
280
+ }
281
+ this.applyColour();
282
+ this.pathObject.updateMovable(this.isMovable());
283
+ const svg = this.getSvgRoot();
284
+ if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
285
+ browserEvents.conditionalBind(svg, 'mousedown', this, this.onMouseDown_);
286
+ }
287
+ this.eventsInit_ = true;
288
+
289
+ if (!svg.parentNode) {
290
+ this.workspace.getCanvas().appendChild(svg);
291
+ }
292
+ }
111
293
 
112
294
  /**
113
- * The renderer's path object.
114
- * @type {IPathObject}
115
- * @package
295
+ * Get the secondary colour of a block.
296
+ * @return {?string} #RRGGBB string.
116
297
  */
117
- this.pathObject =
118
- workspace.getRenderer().makePathObject(this.svgGroup_, this.style);
298
+ getColourSecondary() {
299
+ return this.style.colourSecondary;
300
+ }
119
301
 
120
- /** @type {boolean} */
121
- this.rendered = false;
122
302
  /**
123
- * Is this block currently rendering? Used to stop recursive render calls
124
- * from actually triggering a re-render.
125
- * @type {boolean}
126
- * @private
303
+ * Get the tertiary colour of a block.
304
+ * @return {?string} #RRGGBB string.
127
305
  */
128
- this.renderIsInProgress_ = false;
129
-
130
-
131
- /** @type {!WorkspaceSvg} */
132
- this.workspace = workspace;
133
-
134
- /** @type {RenderedConnection} */
135
- this.outputConnection = null;
136
- /** @type {RenderedConnection} */
137
- this.nextConnection = null;
138
- /** @type {RenderedConnection} */
139
- this.previousConnection = null;
306
+ getColourTertiary() {
307
+ return this.style.colourTertiary;
308
+ }
140
309
 
141
310
  /**
142
- * Whether to move the block to the drag surface when it is dragged.
143
- * True if it should move, false if it should be translated directly.
144
- * @type {boolean}
145
- * @private
311
+ * Selects this block. Highlights the block visually and fires a select event
312
+ * if the block is not already selected.
146
313
  */
147
- this.useDragSurface_ =
148
- svgMath.is3dSupported() && !!workspace.getBlockDragSurface();
149
-
150
- const svgPath = this.pathObject.svgPath;
151
- svgPath.tooltip = this;
152
- Tooltip.bindMouseEvents(svgPath);
153
- BlockSvg.superClass_.constructor.call(this, workspace, prototypeName, opt_id);
154
-
155
- // Expose this block's ID on its top-level SVG group.
156
- if (this.svgGroup_.dataset) {
157
- this.svgGroup_.dataset['id'] = this.id;
158
- } else if (userAgent.IE) {
159
- // SVGElement.dataset is not available on IE11, but data-* properties
160
- // can be set with setAttribute().
161
- this.svgGroup_.setAttribute('data-id', this.id);
314
+ select() {
315
+ if (this.isShadow() && this.getParent()) {
316
+ // Shadow blocks should not be selected.
317
+ this.getParent().select();
318
+ return;
319
+ }
320
+ if (common.getSelected() === this) {
321
+ return;
322
+ }
323
+ let oldId = null;
324
+ if (common.getSelected()) {
325
+ oldId = common.getSelected().id;
326
+ // Unselect any previously selected block.
327
+ eventUtils.disable();
328
+ try {
329
+ common.getSelected().unselect();
330
+ } finally {
331
+ eventUtils.enable();
332
+ }
333
+ }
334
+ const event = new (eventUtils.get(eventUtils.SELECTED))(
335
+ oldId, this.id, this.workspace.id);
336
+ eventUtils.fire(event);
337
+ common.setSelected(this);
338
+ this.addSelect();
162
339
  }
163
- };
164
- object.inherits(BlockSvg, Block);
165
-
166
- /**
167
- * Height of this block, not including any statement blocks above or below.
168
- * Height is in workspace units.
169
- */
170
- BlockSvg.prototype.height = 0;
171
-
172
- /**
173
- * Width of this block, including any connected value blocks.
174
- * Width is in workspace units.
175
- */
176
- BlockSvg.prototype.width = 0;
177
340
 
178
- /**
179
- * Map from IDs for warnings text to PIDs of functions to apply them.
180
- * Used to be able to maintain multiple warnings.
181
- * @type {Object<string, number>}
182
- * @private
183
- */
184
- BlockSvg.prototype.warningTextDb_ = null;
341
+ /**
342
+ * Unselects this block. Unhighlights the block and fires a select (false)
343
+ * event if the block is currently selected.
344
+ */
345
+ unselect() {
346
+ if (common.getSelected() !== this) {
347
+ return;
348
+ }
349
+ const event = new (eventUtils.get(eventUtils.SELECTED))(
350
+ this.id, null, this.workspace.id);
351
+ event.workspaceId = this.workspace.id;
352
+ eventUtils.fire(event);
353
+ common.setSelected(null);
354
+ this.removeSelect();
355
+ }
185
356
 
186
- /**
187
- * Constant for identifying rows that are to be rendered inline.
188
- * Don't collide with Blockly.inputTypes.
189
- * @const
190
- */
191
- BlockSvg.INLINE = -1;
357
+ /**
358
+ * Returns a list of mutator, comment, and warning icons.
359
+ * @return {!Array<!Icon>} List of icons.
360
+ */
361
+ getIcons() {
362
+ const icons = [];
363
+ if (this.mutator) {
364
+ icons.push(this.mutator);
365
+ }
366
+ if (this.commentIcon_) {
367
+ icons.push(this.commentIcon_);
368
+ }
369
+ if (this.warning) {
370
+ icons.push(this.warning);
371
+ }
372
+ return icons;
373
+ }
192
374
 
193
- /**
194
- * ID to give the "collapsed warnings" warning. Allows us to remove the
195
- * "collapsed warnings" warning without removing any warnings that belong to
196
- * the block.
197
- * @type {string}
198
- * @const
199
- */
200
- BlockSvg.COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';
375
+ /**
376
+ * Sets the parent of this block to be a new block or null.
377
+ * @param {?Block} newParent New parent block.
378
+ * @package
379
+ * @override
380
+ */
381
+ setParent(newParent) {
382
+ const oldParent = this.parentBlock_;
383
+ if (newParent === oldParent) {
384
+ return;
385
+ }
201
386
 
202
- /**
203
- * An optional method called when a mutator dialog is first opened.
204
- * This function must create and initialize a top-level block for the mutator
205
- * dialog, and return it. This function should also populate this top-level
206
- * block with any sub-blocks which are appropriate. This method must also be
207
- * coupled with defining a `compose` method for the default mutation dialog
208
- * button and UI to appear.
209
- * @type {?function(WorkspaceSvg):!BlockSvg}
210
- */
211
- BlockSvg.prototype.decompose;
387
+ dom.startTextWidthCache();
388
+ super.setParent(newParent);
389
+ dom.stopTextWidthCache();
212
390
 
213
- /**
214
- * An optional method called when a mutator dialog saves its content.
215
- * This function is called to modify the original block according to new
216
- * settings. This method must also be coupled with defining a `decompose`
217
- * method for the default mutation dialog button and UI to appear.
218
- * @type {?function(!BlockSvg)}
219
- */
220
- BlockSvg.prototype.compose;
391
+ const svgRoot = this.getSvgRoot();
221
392
 
222
- /**
223
- * An optional method for defining custom block context menu items.
224
- * @type {?function(!Array<!Object>)}
225
- */
226
- BlockSvg.prototype.customContextMenu;
393
+ // Bail early if workspace is clearing, or we aren't rendered.
394
+ // We won't need to reattach ourselves anywhere.
395
+ if (this.workspace.isClearing || !svgRoot) {
396
+ return;
397
+ }
227
398
 
228
- /**
229
- * An property used internally to reference the block's rendering debugger.
230
- * @type {?BlockRenderingDebug}
231
- * @package
232
- */
233
- BlockSvg.prototype.renderingDebugger;
399
+ const oldXY = this.getRelativeToSurfaceXY();
400
+ if (newParent) {
401
+ (/** @type {!BlockSvg} */ (newParent)).getSvgRoot().appendChild(svgRoot);
402
+ const newXY = this.getRelativeToSurfaceXY();
403
+ // Move the connections to match the child's new position.
404
+ this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
405
+ } else if (oldParent) {
406
+ // If we are losing a parent, we want to move our DOM element to the
407
+ // root of the workspace.
408
+ this.workspace.getCanvas().appendChild(svgRoot);
409
+ this.translate(oldXY.x, oldXY.y);
410
+ }
234
411
 
235
- /**
236
- * Create and initialize the SVG representation of the block.
237
- * May be called more than once.
238
- */
239
- BlockSvg.prototype.initSvg = function() {
240
- if (!this.workspace.rendered) {
241
- throw TypeError('Workspace is headless.');
242
- }
243
- for (let i = 0, input; (input = this.inputList[i]); i++) {
244
- input.init();
245
- }
246
- const icons = this.getIcons();
247
- for (let i = 0; i < icons.length; i++) {
248
- icons[i].createIcon();
249
- }
250
- this.applyColour();
251
- this.pathObject.updateMovable(this.isMovable());
252
- const svg = this.getSvgRoot();
253
- if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
254
- browserEvents.conditionalBind(svg, 'mousedown', this, this.onMouseDown_);
412
+ this.applyColour();
255
413
  }
256
- this.eventsInit_ = true;
257
414
 
258
- if (!svg.parentNode) {
259
- this.workspace.getCanvas().appendChild(svg);
415
+ /**
416
+ * Return the coordinates of the top-left corner of this block relative to the
417
+ * drawing surface's origin (0,0), in workspace units.
418
+ * If the block is on the workspace, (0, 0) is the origin of the workspace
419
+ * coordinate system.
420
+ * This does not change with workspace scale.
421
+ * @return {!Coordinate} Object with .x and .y properties in
422
+ * workspace coordinates.
423
+ */
424
+ getRelativeToSurfaceXY() {
425
+ let x = 0;
426
+ let y = 0;
427
+
428
+ const dragSurfaceGroup = this.useDragSurface_ ?
429
+ this.workspace.getBlockDragSurface().getGroup() :
430
+ null;
431
+
432
+ let element = this.getSvgRoot();
433
+ if (element) {
434
+ do {
435
+ // Loop through this block and every parent.
436
+ const xy = svgMath.getRelativeXY(element);
437
+ x += xy.x;
438
+ y += xy.y;
439
+ // If this element is the current element on the drag surface, include
440
+ // the translation of the drag surface itself.
441
+ if (this.useDragSurface_ &&
442
+ this.workspace.getBlockDragSurface().getCurrentBlock() ===
443
+ element) {
444
+ const surfaceTranslation =
445
+ this.workspace.getBlockDragSurface().getSurfaceTranslation();
446
+ x += surfaceTranslation.x;
447
+ y += surfaceTranslation.y;
448
+ }
449
+ element = /** @type {!SVGElement} */ (element.parentNode);
450
+ } while (element && element !== this.workspace.getCanvas() &&
451
+ element !== dragSurfaceGroup);
452
+ }
453
+ return new Coordinate(x, y);
260
454
  }
261
- };
262
-
263
- /**
264
- * Get the secondary colour of a block.
265
- * @return {?string} #RRGGBB string.
266
- */
267
- BlockSvg.prototype.getColourSecondary = function() {
268
- return this.style.colourSecondary;
269
- };
270
455
 
271
- /**
272
- * Get the tertiary colour of a block.
273
- * @return {?string} #RRGGBB string.
274
- */
275
- BlockSvg.prototype.getColourTertiary = function() {
276
- return this.style.colourTertiary;
277
- };
278
-
279
- /**
280
- * Selects this block. Highlights the block visually and fires a select event
281
- * if the block is not already selected.
282
- */
283
- BlockSvg.prototype.select = function() {
284
- if (this.isShadow() && this.getParent()) {
285
- // Shadow blocks should not be selected.
286
- this.getParent().select();
287
- return;
288
- }
289
- if (common.getSelected() === this) {
290
- return;
291
- }
292
- let oldId = null;
293
- if (common.getSelected()) {
294
- oldId = common.getSelected().id;
295
- // Unselect any previously selected block.
296
- eventUtils.disable();
297
- try {
298
- common.getSelected().unselect();
299
- } finally {
300
- eventUtils.enable();
456
+ /**
457
+ * Move a block by a relative offset.
458
+ * @param {number} dx Horizontal offset in workspace units.
459
+ * @param {number} dy Vertical offset in workspace units.
460
+ */
461
+ moveBy(dx, dy) {
462
+ if (this.parentBlock_) {
463
+ throw Error('Block has parent.');
464
+ }
465
+ const eventsEnabled = eventUtils.isEnabled();
466
+ let event;
467
+ if (eventsEnabled) {
468
+ event = /** @type {!BlockMove} */
469
+ (new (eventUtils.get(eventUtils.BLOCK_MOVE))(this));
301
470
  }
471
+ const xy = this.getRelativeToSurfaceXY();
472
+ this.translate(xy.x + dx, xy.y + dy);
473
+ this.moveConnections(dx, dy);
474
+ if (eventsEnabled) {
475
+ event.recordNew();
476
+ eventUtils.fire(event);
477
+ }
478
+ this.workspace.resizeContents();
302
479
  }
303
- const event = new (eventUtils.get(eventUtils.SELECTED))(
304
- oldId, this.id, this.workspace.id);
305
- eventUtils.fire(event);
306
- common.setSelected(this);
307
- this.addSelect();
308
- };
309
-
310
- /**
311
- * Unselects this block. Unhighlights the block and fires a select (false) event
312
- * if the block is currently selected.
313
- */
314
- BlockSvg.prototype.unselect = function() {
315
- if (common.getSelected() !== this) {
316
- return;
317
- }
318
- const event = new (eventUtils.get(eventUtils.SELECTED))(
319
- this.id, null, this.workspace.id);
320
- event.workspaceId = this.workspace.id;
321
- eventUtils.fire(event);
322
- common.setSelected(null);
323
- this.removeSelect();
324
- };
325
-
326
- /**
327
- * Block's mutator icon (if any).
328
- * @type {?Mutator}
329
- */
330
- BlockSvg.prototype.mutator = null;
331
-
332
- /**
333
- * Block's comment icon (if any).
334
- * @type {?Comment}
335
- * @deprecated August 2019. Use getCommentIcon instead.
336
- */
337
- BlockSvg.prototype.comment = null;
338
-
339
- /**
340
- * Block's comment icon (if any).
341
- * @type {?Comment}
342
- * @private
343
- */
344
- BlockSvg.prototype.commentIcon_ = null;
345
-
346
- /**
347
- * Block's warning icon (if any).
348
- * @type {?Warning}
349
- */
350
- BlockSvg.prototype.warning = null;
351
480
 
352
- /**
353
- * Returns a list of mutator, comment, and warning icons.
354
- * @return {!Array<!Icon>} List of icons.
355
- */
356
- BlockSvg.prototype.getIcons = function() {
357
- const icons = [];
358
- if (this.mutator) {
359
- icons.push(this.mutator);
360
- }
361
- if (this.commentIcon_) {
362
- icons.push(this.commentIcon_);
363
- }
364
- if (this.warning) {
365
- icons.push(this.warning);
481
+ /**
482
+ * Transforms a block by setting the translation on the transform attribute
483
+ * of the block's SVG.
484
+ * @param {number} x The x coordinate of the translation in workspace units.
485
+ * @param {number} y The y coordinate of the translation in workspace units.
486
+ */
487
+ translate(x, y) {
488
+ this.getSvgRoot().setAttribute(
489
+ 'transform', 'translate(' + x + ',' + y + ')');
366
490
  }
367
- return icons;
368
- };
369
491
 
370
- /**
371
- * Sets the parent of this block to be a new block or null.
372
- * @param {?Block} newParent New parent block.
373
- * @package
374
- * @override
375
- */
376
- BlockSvg.prototype.setParent = function(newParent) {
377
- const oldParent = this.parentBlock_;
378
- if (newParent === oldParent) {
379
- return;
492
+ /**
493
+ * Move this block to its workspace's drag surface, accounting for
494
+ * positioning. Generally should be called at the same time as
495
+ * setDragging_(true). Does nothing if useDragSurface_ is false.
496
+ * @package
497
+ */
498
+ moveToDragSurface() {
499
+ if (!this.useDragSurface_) {
500
+ return;
501
+ }
502
+ // The translation for drag surface blocks,
503
+ // is equal to the current relative-to-surface position,
504
+ // to keep the position in sync as it move on/off the surface.
505
+ // This is in workspace coordinates.
506
+ const xy = this.getRelativeToSurfaceXY();
507
+ this.clearTransformAttributes_();
508
+ this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y);
509
+ // Execute the move on the top-level SVG component
510
+ const svg = this.getSvgRoot();
511
+ if (svg) {
512
+ this.workspace.getBlockDragSurface().setBlocksAndShow(svg);
513
+ }
380
514
  }
381
515
 
382
- dom.startTextWidthCache();
383
- BlockSvg.superClass_.setParent.call(this, newParent);
384
- dom.stopTextWidthCache();
385
-
386
- const svgRoot = this.getSvgRoot();
387
-
388
- // Bail early if workspace is clearing, or we aren't rendered.
389
- // We won't need to reattach ourselves anywhere.
390
- if (this.workspace.isClearing || !svgRoot) {
391
- return;
516
+ /**
517
+ * Move a block to a position.
518
+ * @param {Coordinate} xy The position to move to in workspace units.
519
+ */
520
+ moveTo(xy) {
521
+ const curXY = this.getRelativeToSurfaceXY();
522
+ this.moveBy(xy.x - curXY.x, xy.y - curXY.y);
392
523
  }
393
524
 
394
- const oldXY = this.getRelativeToSurfaceXY();
395
- if (newParent) {
396
- newParent.getSvgRoot().appendChild(svgRoot);
397
- const newXY = this.getRelativeToSurfaceXY();
398
- // Move the connections to match the child's new position.
399
- this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
400
- } else if (oldParent) {
401
- // If we are losing a parent, we want to move our DOM element to the
402
- // root of the workspace.
403
- this.workspace.getCanvas().appendChild(svgRoot);
404
- this.translate(oldXY.x, oldXY.y);
525
+ /**
526
+ * Move this block back to the workspace block canvas.
527
+ * Generally should be called at the same time as setDragging_(false).
528
+ * Does nothing if useDragSurface_ is false.
529
+ * @param {!Coordinate} newXY The position the block should take on
530
+ * on the workspace canvas, in workspace coordinates.
531
+ * @package
532
+ */
533
+ moveOffDragSurface(newXY) {
534
+ if (!this.useDragSurface_) {
535
+ return;
536
+ }
537
+ // Translate to current position, turning off 3d.
538
+ this.translate(newXY.x, newXY.y);
539
+ this.workspace.getBlockDragSurface().clearAndHide(
540
+ this.workspace.getCanvas());
405
541
  }
406
542
 
407
- this.applyColour();
408
- };
409
-
410
- /**
411
- * Return the coordinates of the top-left corner of this block relative to the
412
- * drawing surface's origin (0,0), in workspace units.
413
- * If the block is on the workspace, (0, 0) is the origin of the workspace
414
- * coordinate system.
415
- * This does not change with workspace scale.
416
- * @return {!Coordinate} Object with .x and .y properties in
417
- * workspace coordinates.
418
- */
419
- BlockSvg.prototype.getRelativeToSurfaceXY = function() {
420
- let x = 0;
421
- let y = 0;
422
-
423
- const dragSurfaceGroup = this.useDragSurface_ ?
424
- this.workspace.getBlockDragSurface().getGroup() :
425
- null;
426
-
427
- let element = this.getSvgRoot();
428
- if (element) {
429
- do {
430
- // Loop through this block and every parent.
431
- const xy = svgMath.getRelativeXY(element);
432
- x += xy.x;
433
- y += xy.y;
434
- // If this element is the current element on the drag surface, include
435
- // the translation of the drag surface itself.
436
- if (this.useDragSurface_ &&
437
- this.workspace.getBlockDragSurface().getCurrentBlock() === element) {
438
- const surfaceTranslation =
439
- this.workspace.getBlockDragSurface().getSurfaceTranslation();
440
- x += surfaceTranslation.x;
441
- y += surfaceTranslation.y;
442
- }
443
- element = /** @type {!SVGElement} */ (element.parentNode);
444
- } while (element && element !== this.workspace.getCanvas() &&
445
- element !== dragSurfaceGroup);
543
+ /**
544
+ * Move this block during a drag, taking into account whether we are using a
545
+ * drag surface to translate blocks.
546
+ * This block must be a top-level block.
547
+ * @param {!Coordinate} newLoc The location to translate to, in
548
+ * workspace coordinates.
549
+ * @package
550
+ */
551
+ moveDuringDrag(newLoc) {
552
+ if (this.useDragSurface_) {
553
+ this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y);
554
+ } else {
555
+ this.svgGroup_.translate_ =
556
+ 'translate(' + newLoc.x + ',' + newLoc.y + ')';
557
+ this.svgGroup_.setAttribute(
558
+ 'transform', this.svgGroup_.translate_ + this.svgGroup_.skew_);
559
+ }
446
560
  }
447
- return new Coordinate(x, y);
448
- };
449
561
 
450
- /**
451
- * Move a block by a relative offset.
452
- * @param {number} dx Horizontal offset in workspace units.
453
- * @param {number} dy Vertical offset in workspace units.
454
- */
455
- BlockSvg.prototype.moveBy = function(dx, dy) {
456
- if (this.parentBlock_) {
457
- throw Error('Block has parent.');
458
- }
459
- const eventsEnabled = eventUtils.isEnabled();
460
- let event;
461
- if (eventsEnabled) {
462
- event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(this);
463
- }
464
- const xy = this.getRelativeToSurfaceXY();
465
- this.translate(xy.x + dx, xy.y + dy);
466
- this.moveConnections(dx, dy);
467
- if (eventsEnabled) {
468
- event.recordNew();
469
- eventUtils.fire(event);
562
+ /**
563
+ * Clear the block of transform="..." attributes.
564
+ * Used when the block is switching from 3d to 2d transform or vice versa.
565
+ * @private
566
+ */
567
+ clearTransformAttributes_() {
568
+ this.getSvgRoot().removeAttribute('transform');
470
569
  }
471
- this.workspace.resizeContents();
472
- };
473
570
 
474
- /**
475
- * Transforms a block by setting the translation on the transform attribute
476
- * of the block's SVG.
477
- * @param {number} x The x coordinate of the translation in workspace units.
478
- * @param {number} y The y coordinate of the translation in workspace units.
479
- */
480
- BlockSvg.prototype.translate = function(x, y) {
481
- this.getSvgRoot().setAttribute('transform', 'translate(' + x + ',' + y + ')');
482
- };
483
-
484
- /**
485
- * Move this block to its workspace's drag surface, accounting for positioning.
486
- * Generally should be called at the same time as setDragging_(true).
487
- * Does nothing if useDragSurface_ is false.
488
- * @package
489
- */
490
- BlockSvg.prototype.moveToDragSurface = function() {
491
- if (!this.useDragSurface_) {
492
- return;
493
- }
494
- // The translation for drag surface blocks,
495
- // is equal to the current relative-to-surface position,
496
- // to keep the position in sync as it move on/off the surface.
497
- // This is in workspace coordinates.
498
- const xy = this.getRelativeToSurfaceXY();
499
- this.clearTransformAttributes_();
500
- this.workspace.getBlockDragSurface().translateSurface(xy.x, xy.y);
501
- // Execute the move on the top-level SVG component
502
- const svg = this.getSvgRoot();
503
- if (svg) {
504
- this.workspace.getBlockDragSurface().setBlocksAndShow(svg);
505
- }
506
- };
571
+ /**
572
+ * Snap this block to the nearest grid point.
573
+ */
574
+ snapToGrid() {
575
+ if (!this.workspace) {
576
+ return; // Deleted block.
577
+ }
578
+ if (this.workspace.isDragging()) {
579
+ return; // Don't bump blocks during a drag.
580
+ }
581
+ if (this.getParent()) {
582
+ return; // Only snap top-level blocks.
583
+ }
584
+ if (this.isInFlyout) {
585
+ return; // Don't move blocks around in a flyout.
586
+ }
587
+ const grid = this.workspace.getGrid();
588
+ if (!grid || !grid.shouldSnap()) {
589
+ return; // Config says no snapping.
590
+ }
591
+ const spacing = grid.getSpacing();
592
+ const half = spacing / 2;
593
+ const xy = this.getRelativeToSurfaceXY();
594
+ const dx =
595
+ Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x);
596
+ const dy =
597
+ Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y);
598
+ if (dx || dy) {
599
+ this.moveBy(dx, dy);
600
+ }
601
+ }
507
602
 
508
- /**
509
- * Move a block to a position.
510
- * @param {Coordinate} xy The position to move to in workspace units.
511
- */
512
- BlockSvg.prototype.moveTo = function(xy) {
513
- const curXY = this.getRelativeToSurfaceXY();
514
- this.moveBy(xy.x - curXY.x, xy.y - curXY.y);
515
- };
603
+ /**
604
+ * Returns the coordinates of a bounding box describing the dimensions of this
605
+ * block and any blocks stacked below it.
606
+ * Coordinate system: workspace coordinates.
607
+ * @return {!Rect} Object with coordinates of the bounding box.
608
+ */
609
+ getBoundingRectangle() {
610
+ const blockXY = this.getRelativeToSurfaceXY();
611
+ const blockBounds = this.getHeightWidth();
612
+ let left;
613
+ let right;
614
+ if (this.RTL) {
615
+ left = blockXY.x - blockBounds.width;
616
+ right = blockXY.x;
617
+ } else {
618
+ left = blockXY.x;
619
+ right = blockXY.x + blockBounds.width;
620
+ }
621
+ return new Rect(blockXY.y, blockXY.y + blockBounds.height, left, right);
622
+ }
516
623
 
517
- /**
518
- * Move this block back to the workspace block canvas.
519
- * Generally should be called at the same time as setDragging_(false).
520
- * Does nothing if useDragSurface_ is false.
521
- * @param {!Coordinate} newXY The position the block should take on
522
- * on the workspace canvas, in workspace coordinates.
523
- * @package
524
- */
525
- BlockSvg.prototype.moveOffDragSurface = function(newXY) {
526
- if (!this.useDragSurface_) {
527
- return;
624
+ /**
625
+ * Notify every input on this block to mark its fields as dirty.
626
+ * A dirty field is a field that needs to be re-rendered.
627
+ */
628
+ markDirty() {
629
+ this.pathObject.constants = (/** @type {!WorkspaceSvg} */ (this.workspace))
630
+ .getRenderer()
631
+ .getConstants();
632
+ for (let i = 0, input; (input = this.inputList[i]); i++) {
633
+ input.markDirty();
634
+ }
528
635
  }
529
- // Translate to current position, turning off 3d.
530
- this.translate(newXY.x, newXY.y);
531
- this.workspace.getBlockDragSurface().clearAndHide(this.workspace.getCanvas());
532
- };
533
636
 
534
- /**
535
- * Move this block during a drag, taking into account whether we are using a
536
- * drag surface to translate blocks.
537
- * This block must be a top-level block.
538
- * @param {!Coordinate} newLoc The location to translate to, in
539
- * workspace coordinates.
540
- * @package
541
- */
542
- BlockSvg.prototype.moveDuringDrag = function(newLoc) {
543
- if (this.useDragSurface_) {
544
- this.workspace.getBlockDragSurface().translateSurface(newLoc.x, newLoc.y);
545
- } else {
546
- this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
547
- this.svgGroup_.setAttribute(
548
- 'transform', this.svgGroup_.translate_ + this.svgGroup_.skew_);
637
+ /**
638
+ * Set whether the block is collapsed or not.
639
+ * @param {boolean} collapsed True if collapsed.
640
+ */
641
+ setCollapsed(collapsed) {
642
+ if (this.collapsed_ === collapsed) {
643
+ return;
644
+ }
645
+ super.setCollapsed(collapsed);
646
+ if (!collapsed) {
647
+ this.updateCollapsed_();
648
+ } else if (this.rendered) {
649
+ this.render();
650
+ // Don't bump neighbours. Users like to store collapsed functions together
651
+ // and bumping makes them go out of alignment.
652
+ }
549
653
  }
550
- };
551
654
 
552
- /**
553
- * Clear the block of transform="..." attributes.
554
- * Used when the block is switching from 3d to 2d transform or vice versa.
555
- * @private
556
- */
557
- BlockSvg.prototype.clearTransformAttributes_ = function() {
558
- this.getSvgRoot().removeAttribute('transform');
559
- };
655
+ /**
656
+ * Makes sure that when the block is collapsed, it is rendered correctly
657
+ * for that state.
658
+ * @private
659
+ */
660
+ updateCollapsed_() {
661
+ const collapsed = this.isCollapsed();
662
+ const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
663
+ const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
664
+
665
+ for (let i = 0, input; (input = this.inputList[i]); i++) {
666
+ if (input.name !== collapsedInputName) {
667
+ input.setVisible(!collapsed);
668
+ }
669
+ }
560
670
 
561
- /**
562
- * Snap this block to the nearest grid point.
563
- */
564
- BlockSvg.prototype.snapToGrid = function() {
565
- if (!this.workspace) {
566
- return; // Deleted block.
567
- }
568
- if (this.workspace.isDragging()) {
569
- return; // Don't bump blocks during a drag.
570
- }
571
- if (this.getParent()) {
572
- return; // Only snap top-level blocks.
573
- }
574
- if (this.isInFlyout) {
575
- return; // Don't move blocks around in a flyout.
576
- }
577
- const grid = this.workspace.getGrid();
578
- if (!grid || !grid.shouldSnap()) {
579
- return; // Config says no snapping.
580
- }
581
- const spacing = grid.getSpacing();
582
- const half = spacing / 2;
583
- const xy = this.getRelativeToSurfaceXY();
584
- const dx =
585
- Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x);
586
- const dy =
587
- Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y);
588
- if (dx || dy) {
589
- this.moveBy(dx, dy);
590
- }
591
- };
671
+ if (!collapsed) {
672
+ this.updateDisabled();
673
+ this.removeInput(collapsedInputName);
674
+ return;
675
+ }
592
676
 
593
- /**
594
- * Returns the coordinates of a bounding box describing the dimensions of this
595
- * block and any blocks stacked below it.
596
- * Coordinate system: workspace coordinates.
597
- * @return {!Rect} Object with coordinates of the bounding box.
598
- */
599
- BlockSvg.prototype.getBoundingRectangle = function() {
600
- const blockXY = this.getRelativeToSurfaceXY();
601
- const blockBounds = this.getHeightWidth();
602
- let left;
603
- let right;
604
- if (this.RTL) {
605
- left = blockXY.x - blockBounds.width;
606
- right = blockXY.x;
607
- } else {
608
- left = blockXY.x;
609
- right = blockXY.x + blockBounds.width;
610
- }
611
- return new Rect(blockXY.y, blockXY.y + blockBounds.height, left, right);
612
- };
677
+ const icons = this.getIcons();
678
+ for (let i = 0, icon; (icon = icons[i]); i++) {
679
+ icon.setVisible(false);
680
+ }
613
681
 
614
- /**
615
- * Notify every input on this block to mark its fields as dirty.
616
- * A dirty field is a field that needs to be re-rendered.
617
- */
618
- BlockSvg.prototype.markDirty = function() {
619
- this.pathObject.constants = (/** @type {!WorkspaceSvg} */ (this.workspace))
620
- .getRenderer()
621
- .getConstants();
622
- for (let i = 0, input; (input = this.inputList[i]); i++) {
623
- input.markDirty();
682
+ const text = this.toString(internalConstants.COLLAPSE_CHARS);
683
+ const field = this.getField(collapsedFieldName);
684
+ if (field) {
685
+ field.setValue(text);
686
+ return;
687
+ }
688
+ const input = this.getInput(collapsedInputName) ||
689
+ this.appendDummyInput(collapsedInputName);
690
+ input.appendField(new FieldLabel(text), collapsedFieldName);
624
691
  }
625
- };
626
692
 
627
- /**
628
- * Set whether the block is collapsed or not.
629
- * @param {boolean} collapsed True if collapsed.
630
- */
631
- BlockSvg.prototype.setCollapsed = function(collapsed) {
632
- if (this.collapsed_ === collapsed) {
633
- return;
634
- }
635
- BlockSvg.superClass_.setCollapsed.call(this, collapsed);
636
- if (!collapsed) {
637
- this.updateCollapsed_();
638
- } else if (this.rendered) {
639
- this.render();
640
- // Don't bump neighbours. Users like to store collapsed functions together
641
- // and bumping makes them go out of alignment.
642
- }
643
- };
693
+ /**
694
+ * Open the next (or previous) FieldTextInput.
695
+ * @param {!Field} start Current field.
696
+ * @param {boolean} forward If true go forward, otherwise backward.
697
+ */
698
+ tab(start, forward) {
699
+ const tabCursor = new TabNavigateCursor();
700
+ tabCursor.setCurNode(ASTNode.createFieldNode(start));
701
+ const currentNode = tabCursor.getCurNode();
702
+
703
+ if (forward) {
704
+ tabCursor.next();
705
+ } else {
706
+ tabCursor.prev();
707
+ }
644
708
 
645
- /**
646
- * Makes sure that when the block is collapsed, it is rendered correctly
647
- * for that state.
648
- * @private
649
- */
650
- BlockSvg.prototype.updateCollapsed_ = function() {
651
- const collapsed = this.isCollapsed();
652
- const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
653
- const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
709
+ const nextNode = tabCursor.getCurNode();
710
+ if (nextNode && nextNode !== currentNode) {
711
+ const nextField = /** @type {!Field} */ (nextNode.getLocation());
712
+ nextField.showEditor();
654
713
 
655
- for (let i = 0, input; (input = this.inputList[i]); i++) {
656
- if (input.name !== collapsedInputName) {
657
- input.setVisible(!collapsed);
714
+ // Also move the cursor if we're in keyboard nav mode.
715
+ if (this.workspace.keyboardAccessibilityMode) {
716
+ this.workspace.getCursor().setCurNode(nextNode);
717
+ }
658
718
  }
659
719
  }
660
720
 
661
- if (!collapsed) {
662
- this.updateDisabled();
663
- this.removeInput(collapsedInputName);
664
- return;
721
+ /**
722
+ * Handle a mouse-down on an SVG block.
723
+ * @param {!Event} e Mouse down event or touch start event.
724
+ * @private
725
+ */
726
+ onMouseDown_(e) {
727
+ const gesture = this.workspace && this.workspace.getGesture(e);
728
+ if (gesture) {
729
+ gesture.handleBlockStart(e, this);
730
+ }
665
731
  }
666
732
 
667
- const icons = this.getIcons();
668
- for (let i = 0, icon; (icon = icons[i]); i++) {
669
- icon.setVisible(false);
733
+ /**
734
+ * Load the block's help page in a new window.
735
+ * @package
736
+ */
737
+ showHelp() {
738
+ const url =
739
+ (typeof this.helpUrl === 'function') ? this.helpUrl() : this.helpUrl;
740
+ if (url) {
741
+ window.open(url);
742
+ }
670
743
  }
671
744
 
672
- const text = this.toString(internalConstants.COLLAPSE_CHARS);
673
- const field = this.getField(collapsedFieldName);
674
- if (field) {
675
- field.setValue(text);
676
- return;
677
- }
678
- const input = this.getInput(collapsedInputName) ||
679
- this.appendDummyInput(collapsedInputName);
680
- input.appendField(new FieldLabel(text), collapsedFieldName);
681
- };
745
+ /**
746
+ * Generate the context menu for this block.
747
+ * @return {?Array<!Object>} Context menu options or null if no menu.
748
+ * @protected
749
+ */
750
+ generateContextMenu() {
751
+ if (this.workspace.options.readOnly || !this.contextMenu) {
752
+ return null;
753
+ }
754
+ const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
755
+ ContextMenuRegistry.ScopeType.BLOCK, {block: this});
682
756
 
683
- /**
684
- * Open the next (or previous) FieldTextInput.
685
- * @param {!Field} start Current field.
686
- * @param {boolean} forward If true go forward, otherwise backward.
687
- */
688
- BlockSvg.prototype.tab = function(start, forward) {
689
- const tabCursor = new TabNavigateCursor();
690
- tabCursor.setCurNode(ASTNode.createFieldNode(start));
691
- const currentNode = tabCursor.getCurNode();
757
+ // Allow the block to add or modify menuOptions.
758
+ if (this.customContextMenu) {
759
+ this.customContextMenu(menuOptions);
760
+ }
692
761
 
693
- if (forward) {
694
- tabCursor.next();
695
- } else {
696
- tabCursor.prev();
762
+ return menuOptions;
697
763
  }
698
764
 
699
- const nextNode = tabCursor.getCurNode();
700
- if (nextNode && nextNode !== currentNode) {
701
- const nextField = /** @type {!Field} */ (nextNode.getLocation());
702
- nextField.showEditor();
765
+ /**
766
+ * Show the context menu for this block.
767
+ * @param {!Event} e Mouse event.
768
+ * @package
769
+ */
770
+ showContextMenu(e) {
771
+ const menuOptions = this.generateContextMenu();
703
772
 
704
- // Also move the cursor if we're in keyboard nav mode.
705
- if (this.workspace.keyboardAccessibilityMode) {
706
- this.workspace.getCursor().setCurNode(nextNode);
773
+ if (menuOptions && menuOptions.length) {
774
+ ContextMenu.show(e, menuOptions, this.RTL);
775
+ ContextMenu.setCurrentBlock(this);
707
776
  }
708
777
  }
709
- };
710
778
 
711
- /**
712
- * Handle a mouse-down on an SVG block.
713
- * @param {!Event} e Mouse down event or touch start event.
714
- * @private
715
- */
716
- BlockSvg.prototype.onMouseDown_ = function(e) {
717
- const gesture = this.workspace && this.workspace.getGesture(e);
718
- if (gesture) {
719
- gesture.handleBlockStart(e, this);
720
- }
721
- };
779
+ /**
780
+ * Move the connections for this block and all blocks attached under it.
781
+ * Also update any attached bubbles.
782
+ * @param {number} dx Horizontal offset from current location, in workspace
783
+ * units.
784
+ * @param {number} dy Vertical offset from current location, in workspace
785
+ * units.
786
+ * @package
787
+ */
788
+ moveConnections(dx, dy) {
789
+ if (!this.rendered) {
790
+ // Rendering is required to lay out the blocks.
791
+ // This is probably an invisible block attached to a collapsed block.
792
+ return;
793
+ }
794
+ const myConnections = this.getConnections_(false);
795
+ for (let i = 0; i < myConnections.length; i++) {
796
+ myConnections[i].moveBy(dx, dy);
797
+ }
798
+ const icons = this.getIcons();
799
+ for (let i = 0; i < icons.length; i++) {
800
+ icons[i].computeIconLocation();
801
+ }
722
802
 
723
- /**
724
- * Load the block's help page in a new window.
725
- * @package
726
- */
727
- BlockSvg.prototype.showHelp = function() {
728
- const url =
729
- (typeof this.helpUrl === 'function') ? this.helpUrl() : this.helpUrl;
730
- if (url) {
731
- window.open(url);
803
+ // Recurse through all blocks attached under this one.
804
+ for (let i = 0; i < this.childBlocks_.length; i++) {
805
+ (/** @type {!BlockSvg} */ (this.childBlocks_[i])).moveConnections(dx, dy);
806
+ }
732
807
  }
733
- };
734
808
 
735
- /**
736
- * Generate the context menu for this block.
737
- * @return {?Array<!Object>} Context menu options or null if no menu.
738
- * @protected
739
- */
740
- BlockSvg.prototype.generateContextMenu = function() {
741
- if (this.workspace.options.readOnly || !this.contextMenu) {
742
- return null;
809
+ /**
810
+ * Recursively adds or removes the dragging class to this node and its
811
+ * children.
812
+ * @param {boolean} adding True if adding, false if removing.
813
+ * @package
814
+ */
815
+ setDragging(adding) {
816
+ if (adding) {
817
+ const group = this.getSvgRoot();
818
+ group.translate_ = '';
819
+ group.skew_ = '';
820
+ common.draggingConnections.push(...this.getConnections_(true));
821
+ dom.addClass(
822
+ /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
823
+ } else {
824
+ common.draggingConnections.length = 0;
825
+ dom.removeClass(
826
+ /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
827
+ }
828
+ // Recurse through all blocks attached under this one.
829
+ for (let i = 0; i < this.childBlocks_.length; i++) {
830
+ (/** @type {!BlockSvg} */ (this.childBlocks_[i])).setDragging(adding);
831
+ }
743
832
  }
744
- const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
745
- ContextMenuRegistry.ScopeType.BLOCK, {block: this});
746
833
 
747
- // Allow the block to add or modify menuOptions.
748
- if (this.customContextMenu) {
749
- this.customContextMenu(menuOptions);
834
+ /**
835
+ * Set whether this block is movable or not.
836
+ * @param {boolean} movable True if movable.
837
+ */
838
+ setMovable(movable) {
839
+ super.setMovable(movable);
840
+ this.pathObject.updateMovable(movable);
750
841
  }
751
842
 
752
- return menuOptions;
753
- };
754
-
755
- /**
756
- * Show the context menu for this block.
757
- * @param {!Event} e Mouse event.
758
- * @package
759
- */
760
- BlockSvg.prototype.showContextMenu = function(e) {
761
- const menuOptions = this.generateContextMenu();
762
-
763
- if (menuOptions && menuOptions.length) {
764
- ContextMenu.show(e, menuOptions, this.RTL);
765
- ContextMenu.setCurrentBlock(this);
843
+ /**
844
+ * Set whether this block is editable or not.
845
+ * @param {boolean} editable True if editable.
846
+ */
847
+ setEditable(editable) {
848
+ super.setEditable(editable);
849
+ const icons = this.getIcons();
850
+ for (let i = 0; i < icons.length; i++) {
851
+ icons[i].updateEditable();
852
+ }
766
853
  }
767
- };
768
854
 
769
- /**
770
- * Move the connections for this block and all blocks attached under it.
771
- * Also update any attached bubbles.
772
- * @param {number} dx Horizontal offset from current location, in workspace
773
- * units.
774
- * @param {number} dy Vertical offset from current location, in workspace
775
- * units.
776
- * @package
777
- */
778
- BlockSvg.prototype.moveConnections = function(dx, dy) {
779
- if (!this.rendered) {
780
- // Rendering is required to lay out the blocks.
781
- // This is probably an invisible block attached to a collapsed block.
782
- return;
783
- }
784
- const myConnections = this.getConnections_(false);
785
- for (let i = 0; i < myConnections.length; i++) {
786
- myConnections[i].moveBy(dx, dy);
855
+ /**
856
+ * Sets whether this block is a shadow block or not.
857
+ * @param {boolean} shadow True if a shadow.
858
+ * @package
859
+ */
860
+ setShadow(shadow) {
861
+ super.setShadow(shadow);
862
+ this.applyColour();
787
863
  }
788
- const icons = this.getIcons();
789
- for (let i = 0; i < icons.length; i++) {
790
- icons[i].computeIconLocation();
864
+
865
+ /**
866
+ * Set whether this block is an insertion marker block or not.
867
+ * Once set this cannot be unset.
868
+ * @param {boolean} insertionMarker True if an insertion marker.
869
+ * @package
870
+ */
871
+ setInsertionMarker(insertionMarker) {
872
+ if (this.isInsertionMarker_ === insertionMarker) {
873
+ return; // No change.
874
+ }
875
+ this.isInsertionMarker_ = insertionMarker;
876
+ if (this.isInsertionMarker_) {
877
+ this.setColour(
878
+ this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR);
879
+ this.pathObject.updateInsertionMarker(true);
880
+ }
791
881
  }
792
882
 
793
- // Recurse through all blocks attached under this one.
794
- for (let i = 0; i < this.childBlocks_.length; i++) {
795
- this.childBlocks_[i].moveConnections(dx, dy);
883
+ /**
884
+ * Return the root node of the SVG or null if none exists.
885
+ * @return {!SVGGElement} The root SVG node (probably a group).
886
+ */
887
+ getSvgRoot() {
888
+ return this.svgGroup_;
796
889
  }
797
- };
798
890
 
799
- /**
800
- * Recursively adds or removes the dragging class to this node and its children.
801
- * @param {boolean} adding True if adding, false if removing.
802
- * @package
803
- */
804
- BlockSvg.prototype.setDragging = function(adding) {
805
- if (adding) {
806
- const group = this.getSvgRoot();
807
- group.translate_ = '';
808
- group.skew_ = '';
809
- common.draggingConnections.push(...this.getConnections_(true));
810
- dom.addClass(
811
- /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
812
- } else {
813
- common.draggingConnections.length = 0;
814
- dom.removeClass(
815
- /** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
816
- }
817
- // Recurse through all blocks attached under this one.
818
- for (let i = 0; i < this.childBlocks_.length; i++) {
819
- this.childBlocks_[i].setDragging(adding);
820
- }
821
- };
891
+ /**
892
+ * Dispose of this block.
893
+ * @param {boolean=} healStack If true, then try to heal any gap by connecting
894
+ * the next statement with the previous statement. Otherwise, dispose of
895
+ * all children of this block.
896
+ * @param {boolean=} animate If true, show a disposal animation and sound.
897
+ * @suppress {checkTypes}
898
+ */
899
+ dispose(healStack, animate) {
900
+ if (!this.workspace) {
901
+ // The block has already been deleted.
902
+ return;
903
+ }
904
+ Tooltip.dispose();
905
+ Tooltip.unbindMouseEvents(this.pathObject.svgPath);
906
+ dom.startTextWidthCache();
907
+ // Save the block's workspace temporarily so we can resize the
908
+ // contents once the block is disposed.
909
+ const blockWorkspace = this.workspace;
910
+ // If this block is being dragged, unlink the mouse events.
911
+ if (common.getSelected() === this) {
912
+ this.unselect();
913
+ this.workspace.cancelCurrentGesture();
914
+ }
915
+ // If this block has a context menu open, close it.
916
+ if (ContextMenu.getCurrentBlock() === this) {
917
+ ContextMenu.hide();
918
+ }
822
919
 
823
- /**
824
- * Set whether this block is movable or not.
825
- * @param {boolean} movable True if movable.
826
- */
827
- BlockSvg.prototype.setMovable = function(movable) {
828
- BlockSvg.superClass_.setMovable.call(this, movable);
829
- this.pathObject.updateMovable(movable);
830
- };
920
+ if (animate && this.rendered) {
921
+ this.unplug(healStack);
922
+ blockAnimations.disposeUiEffect(this);
923
+ }
924
+ // Stop rerendering.
925
+ this.rendered = false;
831
926
 
832
- /**
833
- * Set whether this block is editable or not.
834
- * @param {boolean} editable True if editable.
835
- */
836
- BlockSvg.prototype.setEditable = function(editable) {
837
- BlockSvg.superClass_.setEditable.call(this, editable);
838
- const icons = this.getIcons();
839
- for (let i = 0; i < icons.length; i++) {
840
- icons[i].updateEditable();
841
- }
842
- };
927
+ // Clear pending warnings.
928
+ if (this.warningTextDb_) {
929
+ for (const n in this.warningTextDb_) {
930
+ clearTimeout(this.warningTextDb_[n]);
931
+ }
932
+ this.warningTextDb_ = null;
933
+ }
843
934
 
844
- /**
845
- * Sets whether this block is a shadow block or not.
846
- * @param {boolean} shadow True if a shadow.
847
- * @package
848
- */
849
- BlockSvg.prototype.setShadow = function(shadow) {
850
- BlockSvg.superClass_.setShadow.call(this, shadow);
851
- this.applyColour();
852
- };
935
+ const icons = this.getIcons();
936
+ for (let i = 0; i < icons.length; i++) {
937
+ icons[i].dispose();
938
+ }
939
+ super.dispose(!!healStack);
853
940
 
854
- /**
855
- * Set whether this block is an insertion marker block or not.
856
- * Once set this cannot be unset.
857
- * @param {boolean} insertionMarker True if an insertion marker.
858
- * @package
859
- */
860
- BlockSvg.prototype.setInsertionMarker = function(insertionMarker) {
861
- if (this.isInsertionMarker_ === insertionMarker) {
862
- return; // No change.
863
- }
864
- this.isInsertionMarker_ = insertionMarker;
865
- if (this.isInsertionMarker_) {
866
- this.setColour(
867
- this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR);
868
- this.pathObject.updateInsertionMarker(true);
941
+ dom.removeNode(this.svgGroup_);
942
+ blockWorkspace.resizeContents();
943
+ // Sever JavaScript to DOM connections.
944
+ this.svgGroup_ = null;
945
+ dom.stopTextWidthCache();
869
946
  }
870
- };
871
947
 
872
- /**
873
- * Return the root node of the SVG or null if none exists.
874
- * @return {!SVGGElement} The root SVG node (probably a group).
875
- */
876
- BlockSvg.prototype.getSvgRoot = function() {
877
- return this.svgGroup_;
878
- };
879
-
880
- /**
881
- * Dispose of this block.
882
- * @param {boolean=} healStack If true, then try to heal any gap by connecting
883
- * the next statement with the previous statement. Otherwise, dispose of
884
- * all children of this block.
885
- * @param {boolean=} animate If true, show a disposal animation and sound.
886
- * @suppress {checkTypes}
887
- */
888
- BlockSvg.prototype.dispose = function(healStack, animate) {
889
- if (!this.workspace) {
890
- // The block has already been deleted.
891
- return;
892
- }
893
- Tooltip.dispose();
894
- Tooltip.unbindMouseEvents(this.pathObject.svgPath);
895
- dom.startTextWidthCache();
896
- // Save the block's workspace temporarily so we can resize the
897
- // contents once the block is disposed.
898
- const blockWorkspace = this.workspace;
899
- // If this block is being dragged, unlink the mouse events.
900
- if (common.getSelected() === this) {
901
- this.unselect();
902
- this.workspace.cancelCurrentGesture();
903
- }
904
- // If this block has a context menu open, close it.
905
- if (ContextMenu.getCurrentBlock() === this) {
906
- ContextMenu.hide();
907
- }
908
-
909
- if (animate && this.rendered) {
910
- this.unplug(healStack);
911
- blockAnimations.disposeUiEffect(this);
912
- }
913
- // Stop rerendering.
914
- this.rendered = false;
915
-
916
- // Clear pending warnings.
917
- if (this.warningTextDb_) {
918
- for (const n in this.warningTextDb_) {
919
- clearTimeout(this.warningTextDb_[n]);
948
+ /**
949
+ * Delete a block and hide chaff when doing so. The block will not be deleted
950
+ * if it's in a flyout. This is called from the context menu and keyboard
951
+ * shortcuts as the full delete action. If you are disposing of a block from
952
+ * the workspace and don't need to perform flyout checks, handle event
953
+ * grouping, or hide chaff, then use `block.dispose()` directly.
954
+ */
955
+ checkAndDelete() {
956
+ if (this.workspace.isFlyout) {
957
+ return;
920
958
  }
921
- this.warningTextDb_ = null;
959
+ eventUtils.setGroup(true);
960
+ this.workspace.hideChaff();
961
+ if (this.outputConnection) {
962
+ // Do not attempt to heal rows
963
+ // (https://github.com/google/blockly/issues/4832)
964
+ this.dispose(false, true);
965
+ } else {
966
+ this.dispose(/* heal */ true, true);
967
+ }
968
+ eventUtils.setGroup(false);
922
969
  }
923
970
 
924
- const icons = this.getIcons();
925
- for (let i = 0; i < icons.length; i++) {
926
- icons[i].dispose();
971
+ /**
972
+ * Encode a block for copying.
973
+ * @return {?ICopyable.CopyData} Copy metadata, or null if the block is
974
+ * an insertion marker.
975
+ * @package
976
+ */
977
+ toCopyData() {
978
+ if (this.isInsertionMarker_) {
979
+ return null;
980
+ }
981
+ return {
982
+ saveInfo: /** @type {!blocks.State} */ (
983
+ blocks.save(this, {addCoordinates: true, addNextBlocks: false})),
984
+ source: this.workspace,
985
+ typeCounts: common.getBlockTypeCounts(this, true),
986
+ };
927
987
  }
928
- BlockSvg.superClass_.dispose.call(this, !!healStack);
929
988
 
930
- dom.removeNode(this.svgGroup_);
931
- blockWorkspace.resizeContents();
932
- // Sever JavaScript to DOM connections.
933
- this.svgGroup_ = null;
934
- dom.stopTextWidthCache();
935
- };
936
-
937
- /**
938
- * Delete a block and hide chaff when doing so. The block will not be deleted if
939
- * it's in a flyout. This is called from the context menu and keyboard shortcuts
940
- * as the full delete action. If you are disposing of a block from the workspace
941
- * and don't need to perform flyout checks, handle event grouping, or hide
942
- * chaff, then use `block.dispose()` directly.
943
- */
944
- BlockSvg.prototype.checkAndDelete = function() {
945
- if (this.workspace.isFlyout) {
946
- return;
947
- }
948
- eventUtils.setGroup(true);
949
- this.workspace.hideChaff();
950
- if (this.outputConnection) {
951
- // Do not attempt to heal rows
952
- // (https://github.com/google/blockly/issues/4832)
953
- this.dispose(false, true);
954
- } else {
955
- this.dispose(/* heal */ true, true);
956
- }
957
- eventUtils.setGroup(false);
958
- };
959
-
960
- /**
961
- * Encode a block for copying.
962
- * @return {?ICopyable.CopyData} Copy metadata, or null if the block is
963
- * an insertion marker.
964
- * @package
965
- */
966
- BlockSvg.prototype.toCopyData = function() {
967
- if (this.isInsertionMarker_) {
968
- return null;
969
- }
970
- return {
971
- saveInfo: /** @type {!blocks.State} */ (
972
- blocks.save(this, {addCoordinates: true, addNextBlocks: false})),
973
- source: this.workspace,
974
- typeCounts: common.getBlockTypeCounts(this, true),
975
- };
976
- };
989
+ /**
990
+ * Updates the colour of the block to match the block's state.
991
+ * @package
992
+ */
993
+ applyColour() {
994
+ this.pathObject.applyColour(this);
977
995
 
978
- /**
979
- * Updates the colour of the block to match the block's state.
980
- * @package
981
- */
982
- BlockSvg.prototype.applyColour = function() {
983
- this.pathObject.applyColour(this);
996
+ const icons = this.getIcons();
997
+ for (let i = 0; i < icons.length; i++) {
998
+ icons[i].applyColour();
999
+ }
984
1000
 
985
- const icons = this.getIcons();
986
- for (let i = 0; i < icons.length; i++) {
987
- icons[i].applyColour();
1001
+ for (let x = 0, input; (input = this.inputList[x]); x++) {
1002
+ for (let y = 0, field; (field = input.fieldRow[y]); y++) {
1003
+ field.applyColour();
1004
+ }
1005
+ }
988
1006
  }
989
1007
 
990
- for (let x = 0, input; (input = this.inputList[x]); x++) {
991
- for (let y = 0, field; (field = input.fieldRow[y]); y++) {
992
- field.applyColour();
1008
+ /**
1009
+ * Updates the color of the block (and children) to match the current disabled
1010
+ * state.
1011
+ * @package
1012
+ */
1013
+ updateDisabled() {
1014
+ const children =
1015
+ /** @type {!Array<!BlockSvg>} */ (this.getChildren(false));
1016
+ this.applyColour();
1017
+ if (this.isCollapsed()) {
1018
+ return;
1019
+ }
1020
+ for (let i = 0, child; (child = children[i]); i++) {
1021
+ if (child.rendered) {
1022
+ child.updateDisabled();
1023
+ }
993
1024
  }
994
1025
  }
995
- };
996
1026
 
997
- /**
998
- * Updates the color of the block (and children) to match the current disabled
999
- * state.
1000
- * @package
1001
- */
1002
- BlockSvg.prototype.updateDisabled = function() {
1003
- const children = this.getChildren(false);
1004
- this.applyColour();
1005
- if (this.isCollapsed()) {
1006
- return;
1027
+ /**
1028
+ * Get the comment icon attached to this block, or null if the block has no
1029
+ * comment.
1030
+ * @return {?Comment} The comment icon attached to this block, or null.
1031
+ */
1032
+ getCommentIcon() {
1033
+ return this.commentIcon_;
1007
1034
  }
1008
- for (let i = 0, child; (child = children[i]); i++) {
1009
- if (child.rendered) {
1010
- child.updateDisabled();
1035
+
1036
+ /**
1037
+ * Set this block's comment text.
1038
+ * @param {?string} text The text, or null to delete.
1039
+ */
1040
+ setCommentText(text) {
1041
+ const {Comment} = goog.module.get('Blockly.Comment');
1042
+ if (!Comment) {
1043
+ throw Error('Missing require for Blockly.Comment');
1044
+ }
1045
+ if (this.commentModel.text === text) {
1046
+ return;
1047
+ }
1048
+ super.setCommentText(text);
1049
+
1050
+ const shouldHaveComment = text !== null;
1051
+ if (!!this.commentIcon_ === shouldHaveComment) {
1052
+ // If the comment's state of existence is correct, but the text is new
1053
+ // that means we're just updating a comment.
1054
+ this.commentIcon_.updateText();
1055
+ return;
1056
+ }
1057
+ if (shouldHaveComment) {
1058
+ this.commentIcon_ = new Comment(this);
1059
+ this.comment = this.commentIcon_; // For backwards compatibility.
1060
+ } else {
1061
+ this.commentIcon_.dispose();
1062
+ this.commentIcon_ = null;
1063
+ this.comment = null; // For backwards compatibility.
1064
+ }
1065
+ if (this.rendered) {
1066
+ this.render();
1067
+ // Adding or removing a comment icon will cause the block to change shape.
1068
+ this.bumpNeighbours();
1011
1069
  }
1012
1070
  }
1013
- };
1014
-
1015
- /**
1016
- * Get the comment icon attached to this block, or null if the block has no
1017
- * comment.
1018
- * @return {?Comment} The comment icon attached to this block, or null.
1019
- */
1020
- BlockSvg.prototype.getCommentIcon = function() {
1021
- return this.commentIcon_;
1022
- };
1023
1071
 
1024
- /**
1025
- * Set this block's comment text.
1026
- * @param {?string} text The text, or null to delete.
1027
- */
1028
- BlockSvg.prototype.setCommentText = function(text) {
1029
- const {Comment} = goog.module.get('Blockly.Comment');
1030
- if (!Comment) {
1031
- throw Error('Missing require for Blockly.Comment');
1032
- }
1033
- if (this.commentModel.text === text) {
1034
- return;
1035
- }
1036
- BlockSvg.superClass_.setCommentText.call(this, text);
1037
-
1038
- const shouldHaveComment = text !== null;
1039
- if (!!this.commentIcon_ === shouldHaveComment) {
1040
- // If the comment's state of existence is correct, but the text is new
1041
- // that means we're just updating a comment.
1042
- this.commentIcon_.updateText();
1043
- return;
1044
- }
1045
- if (shouldHaveComment) {
1046
- this.commentIcon_ = new Comment(this);
1047
- this.comment = this.commentIcon_; // For backwards compatibility.
1048
- } else {
1049
- this.commentIcon_.dispose();
1050
- this.commentIcon_ = null;
1051
- this.comment = null; // For backwards compatibility.
1052
- }
1053
- if (this.rendered) {
1054
- this.render();
1055
- // Adding or removing a comment icon will cause the block to change shape.
1056
- this.bumpNeighbours();
1057
- }
1058
- };
1072
+ /**
1073
+ * Set this block's warning text.
1074
+ * @param {?string} text The text, or null to delete.
1075
+ * @param {string=} opt_id An optional ID for the warning text to be able to
1076
+ * maintain multiple warnings.
1077
+ */
1078
+ setWarningText(text, opt_id) {
1079
+ const {Warning} = goog.module.get('Blockly.Warning');
1080
+ if (!Warning) {
1081
+ throw Error('Missing require for Blockly.Warning');
1082
+ }
1083
+ if (!this.warningTextDb_) {
1084
+ // Create a database of warning PIDs.
1085
+ // Only runs once per block (and only those with warnings).
1086
+ this.warningTextDb_ = Object.create(null);
1087
+ }
1088
+ const id = opt_id || '';
1089
+ if (!id) {
1090
+ // Kill all previous pending processes, this edit supersedes them all.
1091
+ for (const n of Object.keys(this.warningTextDb_)) {
1092
+ clearTimeout(this.warningTextDb_[n]);
1093
+ delete this.warningTextDb_[n];
1094
+ }
1095
+ } else if (this.warningTextDb_[id]) {
1096
+ // Only queue up the latest change. Kill any earlier pending process.
1097
+ clearTimeout(this.warningTextDb_[id]);
1098
+ delete this.warningTextDb_[id];
1099
+ }
1100
+ if (this.workspace.isDragging()) {
1101
+ // Don't change the warning text during a drag.
1102
+ // Wait until the drag finishes.
1103
+ const thisBlock = this;
1104
+ this.warningTextDb_[id] = setTimeout(function() {
1105
+ if (thisBlock.workspace) { // Check block wasn't deleted.
1106
+ delete thisBlock.warningTextDb_[id];
1107
+ thisBlock.setWarningText(text, id);
1108
+ }
1109
+ }, 100);
1110
+ return;
1111
+ }
1112
+ if (this.isInFlyout) {
1113
+ text = null;
1114
+ }
1059
1115
 
1060
- /**
1061
- * Set this block's warning text.
1062
- * @param {?string} text The text, or null to delete.
1063
- * @param {string=} opt_id An optional ID for the warning text to be able to
1064
- * maintain multiple warnings.
1065
- */
1066
- BlockSvg.prototype.setWarningText = function(text, opt_id) {
1067
- const {Warning} = goog.module.get('Blockly.Warning');
1068
- if (!Warning) {
1069
- throw Error('Missing require for Blockly.Warning');
1070
- }
1071
- if (!this.warningTextDb_) {
1072
- // Create a database of warning PIDs.
1073
- // Only runs once per block (and only those with warnings).
1074
- this.warningTextDb_ = Object.create(null);
1075
- }
1076
- const id = opt_id || '';
1077
- if (!id) {
1078
- // Kill all previous pending processes, this edit supersedes them all.
1079
- for (const n of Object.keys(this.warningTextDb_)) {
1080
- clearTimeout(this.warningTextDb_[n]);
1081
- delete this.warningTextDb_[n];
1082
- }
1083
- } else if (this.warningTextDb_[id]) {
1084
- // Only queue up the latest change. Kill any earlier pending process.
1085
- clearTimeout(this.warningTextDb_[id]);
1086
- delete this.warningTextDb_[id];
1087
- }
1088
- if (this.workspace.isDragging()) {
1089
- // Don't change the warning text during a drag.
1090
- // Wait until the drag finishes.
1091
- const thisBlock = this;
1092
- this.warningTextDb_[id] = setTimeout(function() {
1093
- if (thisBlock.workspace) { // Check block wasn't deleted.
1094
- delete thisBlock.warningTextDb_[id];
1095
- thisBlock.setWarningText(text, id);
1116
+ let changedState = false;
1117
+ if (typeof text === 'string') {
1118
+ // Bubble up to add a warning on top-most collapsed block.
1119
+ let parent = this.getSurroundParent();
1120
+ let collapsedParent = null;
1121
+ while (parent) {
1122
+ if (parent.isCollapsed()) {
1123
+ collapsedParent = parent;
1124
+ }
1125
+ parent = parent.getSurroundParent();
1126
+ }
1127
+ if (collapsedParent) {
1128
+ collapsedParent.setWarningText(
1129
+ Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID);
1096
1130
  }
1097
- }, 100);
1098
- return;
1099
- }
1100
- if (this.isInFlyout) {
1101
- text = null;
1102
- }
1103
1131
 
1104
- let changedState = false;
1105
- if (typeof text === 'string') {
1106
- // Bubble up to add a warning on top-most collapsed block.
1107
- let parent = this.getSurroundParent();
1108
- let collapsedParent = null;
1109
- while (parent) {
1110
- if (parent.isCollapsed()) {
1111
- collapsedParent = parent;
1132
+ if (!this.warning) {
1133
+ this.warning = new Warning(this);
1134
+ changedState = true;
1112
1135
  }
1113
- parent = parent.getSurroundParent();
1114
- }
1115
- if (collapsedParent) {
1116
- collapsedParent.setWarningText(
1117
- Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID);
1118
- }
1119
-
1120
- if (!this.warning) {
1121
- this.warning = new Warning(this);
1122
- changedState = true;
1123
- }
1124
- this.warning.setText(/** @type {string} */ (text), id);
1125
- } else {
1126
- // Dispose all warnings if no ID is given.
1127
- if (this.warning && !id) {
1128
- this.warning.dispose();
1129
- changedState = true;
1130
- } else if (this.warning) {
1131
- const oldText = this.warning.getText();
1132
- this.warning.setText('', id);
1133
- const newText = this.warning.getText();
1134
- if (!newText) {
1136
+ this.warning.setText(/** @type {string} */ (text), id);
1137
+ } else {
1138
+ // Dispose all warnings if no ID is given.
1139
+ if (this.warning && !id) {
1135
1140
  this.warning.dispose();
1141
+ changedState = true;
1142
+ } else if (this.warning) {
1143
+ const oldText = this.warning.getText();
1144
+ this.warning.setText('', id);
1145
+ const newText = this.warning.getText();
1146
+ if (!newText) {
1147
+ this.warning.dispose();
1148
+ }
1149
+ changedState = oldText !== newText;
1136
1150
  }
1137
- changedState = oldText !== newText;
1151
+ }
1152
+ if (changedState && this.rendered) {
1153
+ this.render();
1154
+ // Adding or removing a warning icon will cause the block to change shape.
1155
+ this.bumpNeighbours();
1138
1156
  }
1139
1157
  }
1140
- if (changedState && this.rendered) {
1141
- this.render();
1142
- // Adding or removing a warning icon will cause the block to change shape.
1143
- this.bumpNeighbours();
1144
- }
1145
- };
1146
1158
 
1147
- /**
1148
- * Give this block a mutator dialog.
1149
- * @param {?Mutator} mutator A mutator dialog instance or null to remove.
1150
- */
1151
- BlockSvg.prototype.setMutator = function(mutator) {
1152
- if (this.mutator && this.mutator !== mutator) {
1153
- this.mutator.dispose();
1154
- }
1155
- if (mutator) {
1156
- mutator.setBlock(this);
1157
- this.mutator = mutator;
1158
- mutator.createIcon();
1159
- }
1160
- if (this.rendered) {
1161
- this.render();
1162
- // Adding or removing a mutator icon will cause the block to change shape.
1163
- this.bumpNeighbours();
1159
+ /**
1160
+ * Give this block a mutator dialog.
1161
+ * @param {?Mutator} mutator A mutator dialog instance or null to remove.
1162
+ */
1163
+ setMutator(mutator) {
1164
+ if (this.mutator && this.mutator !== mutator) {
1165
+ this.mutator.dispose();
1166
+ }
1167
+ if (mutator) {
1168
+ mutator.setBlock(this);
1169
+ this.mutator = mutator;
1170
+ mutator.createIcon();
1171
+ }
1172
+ if (this.rendered) {
1173
+ this.render();
1174
+ // Adding or removing a mutator icon will cause the block to change shape.
1175
+ this.bumpNeighbours();
1176
+ }
1164
1177
  }
1165
- };
1166
1178
 
1167
- /**
1168
- * Set whether the block is enabled or not.
1169
- * @param {boolean} enabled True if enabled.
1170
- */
1171
- BlockSvg.prototype.setEnabled = function(enabled) {
1172
- if (this.isEnabled() !== enabled) {
1173
- BlockSvg.superClass_.setEnabled.call(this, enabled);
1174
- if (this.rendered && !this.getInheritedDisabled()) {
1175
- this.updateDisabled();
1179
+ /**
1180
+ * Set whether the block is enabled or not.
1181
+ * @param {boolean} enabled True if enabled.
1182
+ */
1183
+ setEnabled(enabled) {
1184
+ if (this.isEnabled() !== enabled) {
1185
+ super.setEnabled(enabled);
1186
+ if (this.rendered && !this.getInheritedDisabled()) {
1187
+ this.updateDisabled();
1188
+ }
1176
1189
  }
1177
1190
  }
1178
- };
1179
1191
 
1180
- /**
1181
- * Set whether the block is highlighted or not. Block highlighting is
1182
- * often used to visually mark blocks currently being executed.
1183
- * @param {boolean} highlighted True if highlighted.
1184
- */
1185
- BlockSvg.prototype.setHighlighted = function(highlighted) {
1186
- if (!this.rendered) {
1187
- return;
1192
+ /**
1193
+ * Set whether the block is highlighted or not. Block highlighting is
1194
+ * often used to visually mark blocks currently being executed.
1195
+ * @param {boolean} highlighted True if highlighted.
1196
+ */
1197
+ setHighlighted(highlighted) {
1198
+ if (!this.rendered) {
1199
+ return;
1200
+ }
1201
+ this.pathObject.updateHighlighted(highlighted);
1188
1202
  }
1189
- this.pathObject.updateHighlighted(highlighted);
1190
- };
1191
-
1192
- /**
1193
- * Adds the visual "select" effect to the block, but does not actually select
1194
- * it or fire an event.
1195
- * @see BlockSvg#select
1196
- */
1197
- BlockSvg.prototype.addSelect = function() {
1198
- this.pathObject.updateSelected(true);
1199
- };
1200
1203
 
1201
- /**
1202
- * Removes the visual "select" effect from the block, but does not actually
1203
- * unselect it or fire an event.
1204
- * @see BlockSvg#unselect
1205
- */
1206
- BlockSvg.prototype.removeSelect = function() {
1207
- this.pathObject.updateSelected(false);
1208
- };
1204
+ /**
1205
+ * Adds the visual "select" effect to the block, but does not actually select
1206
+ * it or fire an event.
1207
+ * @see BlockSvg#select
1208
+ */
1209
+ addSelect() {
1210
+ this.pathObject.updateSelected(true);
1211
+ }
1209
1212
 
1210
- /**
1211
- * Update the cursor over this block by adding or removing a class.
1212
- * @param {boolean} enable True if the delete cursor should be shown, false
1213
- * otherwise.
1214
- * @package
1215
- */
1216
- BlockSvg.prototype.setDeleteStyle = function(enable) {
1217
- this.pathObject.updateDraggingDelete(enable);
1218
- };
1213
+ /**
1214
+ * Removes the visual "select" effect from the block, but does not actually
1215
+ * unselect it or fire an event.
1216
+ * @see BlockSvg#unselect
1217
+ */
1218
+ removeSelect() {
1219
+ this.pathObject.updateSelected(false);
1220
+ }
1219
1221
 
1222
+ /**
1223
+ * Update the cursor over this block by adding or removing a class.
1224
+ * @param {boolean} enable True if the delete cursor should be shown, false
1225
+ * otherwise.
1226
+ * @package
1227
+ */
1228
+ setDeleteStyle(enable) {
1229
+ this.pathObject.updateDraggingDelete(enable);
1230
+ }
1220
1231
 
1221
- // Overrides of functions on Blockly.Block that take into account whether the
1222
- // block has been rendered.
1223
- /**
1224
- * Get the colour of a block.
1225
- * @return {string} #RRGGBB string.
1226
- */
1227
- BlockSvg.prototype.getColour = function() {
1228
- return this.style.colourPrimary;
1229
- };
1232
+ // Overrides of functions on Blockly.Block that take into account whether the
1230
1233
 
1231
- /**
1232
- * Change the colour of a block.
1233
- * @param {number|string} colour HSV hue value, or #RRGGBB string.
1234
- */
1235
- BlockSvg.prototype.setColour = function(colour) {
1236
- BlockSvg.superClass_.setColour.call(this, colour);
1237
- const styleObj =
1238
- this.workspace.getRenderer().getConstants().getBlockStyleForColour(
1239
- this.colour_);
1234
+ // block has been rendered.
1240
1235
 
1241
- this.pathObject.setStyle(styleObj.style);
1242
- this.style = styleObj.style;
1243
- this.styleName_ = styleObj.name;
1236
+ /**
1237
+ * Get the colour of a block.
1238
+ * @return {string} #RRGGBB string.
1239
+ */
1240
+ getColour() {
1241
+ return this.style.colourPrimary;
1242
+ }
1244
1243
 
1245
- this.applyColour();
1246
- };
1244
+ /**
1245
+ * Change the colour of a block.
1246
+ * @param {number|string} colour HSV hue value, or #RRGGBB string.
1247
+ */
1248
+ setColour(colour) {
1249
+ super.setColour(colour);
1250
+ const styleObj =
1251
+ this.workspace.getRenderer().getConstants().getBlockStyleForColour(
1252
+ this.colour_);
1247
1253
 
1248
- /**
1249
- * Set the style and colour values of a block.
1250
- * @param {string} blockStyleName Name of the block style.
1251
- * @throws {Error} if the block style does not exist.
1252
- */
1253
- BlockSvg.prototype.setStyle = function(blockStyleName) {
1254
- const blockStyle =
1255
- this.workspace.getRenderer().getConstants().getBlockStyle(blockStyleName);
1256
- this.styleName_ = blockStyleName;
1257
-
1258
- if (blockStyle) {
1259
- this.hat = blockStyle.hat;
1260
- this.pathObject.setStyle(blockStyle);
1261
- // Set colour to match Block.
1262
- this.colour_ = blockStyle.colourPrimary;
1263
- this.style = blockStyle;
1254
+ this.pathObject.setStyle(styleObj.style);
1255
+ this.style = styleObj.style;
1256
+ this.styleName_ = styleObj.name;
1264
1257
 
1265
1258
  this.applyColour();
1266
- } else {
1267
- throw Error('Invalid style name: ' + blockStyleName);
1268
1259
  }
1269
- };
1270
-
1271
- /**
1272
- * Move this block to the front of the visible workspace.
1273
- * <g> tags do not respect z-index so SVG renders them in the
1274
- * order that they are in the DOM. By placing this block first within the
1275
- * block group's <g>, it will render on top of any other blocks.
1276
- * @package
1277
- */
1278
- BlockSvg.prototype.bringToFront = function() {
1279
- let block = this;
1280
- do {
1281
- const root = block.getSvgRoot();
1282
- const parent = root.parentNode;
1283
- const childNodes = parent.childNodes;
1284
- // Avoid moving the block if it's already at the bottom.
1285
- if (childNodes[childNodes.length - 1] !== root) {
1286
- parent.appendChild(root);
1287
- }
1288
- block = block.getParent();
1289
- } while (block);
1290
- };
1291
1260
 
1292
- /**
1293
- * Set whether this block can chain onto the bottom of another block.
1294
- * @param {boolean} newBoolean True if there can be a previous statement.
1295
- * @param {(string|Array<string>|null)=} opt_check Statement type or
1296
- * list of statement types. Null/undefined if any type could be connected.
1297
- */
1298
- BlockSvg.prototype.setPreviousStatement = function(newBoolean, opt_check) {
1299
- BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean, opt_check);
1261
+ /**
1262
+ * Set the style and colour values of a block.
1263
+ * @param {string} blockStyleName Name of the block style.
1264
+ * @throws {Error} if the block style does not exist.
1265
+ */
1266
+ setStyle(blockStyleName) {
1267
+ const blockStyle =
1268
+ this.workspace.getRenderer().getConstants().getBlockStyle(
1269
+ blockStyleName);
1270
+ this.styleName_ = blockStyleName;
1271
+
1272
+ if (blockStyle) {
1273
+ this.hat = blockStyle.hat;
1274
+ this.pathObject.setStyle(blockStyle);
1275
+ // Set colour to match Block.
1276
+ this.colour_ = blockStyle.colourPrimary;
1277
+ this.style = blockStyle;
1278
+
1279
+ this.applyColour();
1280
+ } else {
1281
+ throw Error('Invalid style name: ' + blockStyleName);
1282
+ }
1283
+ }
1300
1284
 
1301
- if (this.rendered) {
1302
- this.render();
1303
- this.bumpNeighbours();
1285
+ /**
1286
+ * Move this block to the front of the visible workspace.
1287
+ * <g> tags do not respect z-index so SVG renders them in the
1288
+ * order that they are in the DOM. By placing this block first within the
1289
+ * block group's <g>, it will render on top of any other blocks.
1290
+ * @package
1291
+ */
1292
+ bringToFront() {
1293
+ let block = this;
1294
+ do {
1295
+ const root = block.getSvgRoot();
1296
+ const parent = root.parentNode;
1297
+ const childNodes = parent.childNodes;
1298
+ // Avoid moving the block if it's already at the bottom.
1299
+ if (childNodes[childNodes.length - 1] !== root) {
1300
+ parent.appendChild(root);
1301
+ }
1302
+ block = block.getParent();
1303
+ } while (block);
1304
1304
  }
1305
- };
1306
1305
 
1307
- /**
1308
- * Set whether another block can chain onto the bottom of this block.
1309
- * @param {boolean} newBoolean True if there can be a next statement.
1310
- * @param {(string|Array<string>|null)=} opt_check Statement type or
1311
- * list of statement types. Null/undefined if any type could be connected.
1312
- */
1313
- BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) {
1314
- BlockSvg.superClass_.setNextStatement.call(this, newBoolean, opt_check);
1306
+ /**
1307
+ * Set whether this block can chain onto the bottom of another block.
1308
+ * @param {boolean} newBoolean True if there can be a previous statement.
1309
+ * @param {(string|Array<string>|null)=} opt_check Statement type or
1310
+ * list of statement types. Null/undefined if any type could be
1311
+ * connected.
1312
+ */
1313
+ setPreviousStatement(newBoolean, opt_check) {
1314
+ super.setPreviousStatement(newBoolean, opt_check);
1315
1315
 
1316
- if (this.rendered) {
1317
- this.render();
1318
- this.bumpNeighbours();
1316
+ if (this.rendered) {
1317
+ this.render();
1318
+ this.bumpNeighbours();
1319
+ }
1319
1320
  }
1320
- };
1321
1321
 
1322
- /**
1323
- * Set whether this block returns a value.
1324
- * @param {boolean} newBoolean True if there is an output.
1325
- * @param {(string|Array<string>|null)=} opt_check Returned type or list
1326
- * of returned types. Null or undefined if any type could be returned
1327
- * (e.g. variable get).
1328
- */
1329
- BlockSvg.prototype.setOutput = function(newBoolean, opt_check) {
1330
- BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check);
1322
+ /**
1323
+ * Set whether another block can chain onto the bottom of this block.
1324
+ * @param {boolean} newBoolean True if there can be a next statement.
1325
+ * @param {(string|Array<string>|null)=} opt_check Statement type or
1326
+ * list of statement types. Null/undefined if any type could be
1327
+ * connected.
1328
+ */
1329
+ setNextStatement(newBoolean, opt_check) {
1330
+ super.setNextStatement(newBoolean, opt_check);
1331
1331
 
1332
- if (this.rendered) {
1333
- this.render();
1334
- this.bumpNeighbours();
1332
+ if (this.rendered) {
1333
+ this.render();
1334
+ this.bumpNeighbours();
1335
+ }
1335
1336
  }
1336
- };
1337
1337
 
1338
- /**
1339
- * Set whether value inputs are arranged horizontally or vertically.
1340
- * @param {boolean} newBoolean True if inputs are horizontal.
1341
- */
1342
- BlockSvg.prototype.setInputsInline = function(newBoolean) {
1343
- BlockSvg.superClass_.setInputsInline.call(this, newBoolean);
1338
+ /**
1339
+ * Set whether this block returns a value.
1340
+ * @param {boolean} newBoolean True if there is an output.
1341
+ * @param {(string|Array<string>|null)=} opt_check Returned type or list
1342
+ * of returned types. Null or undefined if any type could be returned
1343
+ * (e.g. variable get).
1344
+ */
1345
+ setOutput(newBoolean, opt_check) {
1346
+ super.setOutput(newBoolean, opt_check);
1344
1347
 
1345
- if (this.rendered) {
1346
- this.render();
1347
- this.bumpNeighbours();
1348
+ if (this.rendered) {
1349
+ this.render();
1350
+ this.bumpNeighbours();
1351
+ }
1348
1352
  }
1349
- };
1350
1353
 
1351
- /**
1352
- * Remove an input from this block.
1353
- * @param {string} name The name of the input.
1354
- * @param {boolean=} opt_quiet True to prevent error if input is not present.
1355
- * @return {boolean} True if operation succeeds, false if input is not present
1356
- * and opt_quiet is true
1357
- * @throws {Error} if the input is not present and opt_quiet is not true.
1358
- */
1359
- BlockSvg.prototype.removeInput = function(name, opt_quiet) {
1360
- const removed = BlockSvg.superClass_.removeInput.call(this, name, opt_quiet);
1354
+ /**
1355
+ * Set whether value inputs are arranged horizontally or vertically.
1356
+ * @param {boolean} newBoolean True if inputs are horizontal.
1357
+ */
1358
+ setInputsInline(newBoolean) {
1359
+ super.setInputsInline(newBoolean);
1361
1360
 
1362
- if (this.rendered) {
1363
- this.render();
1364
- // Removing an input will cause the block to change shape.
1365
- this.bumpNeighbours();
1361
+ if (this.rendered) {
1362
+ this.render();
1363
+ this.bumpNeighbours();
1364
+ }
1366
1365
  }
1367
1366
 
1368
- return removed;
1369
- };
1367
+ /**
1368
+ * Remove an input from this block.
1369
+ * @param {string} name The name of the input.
1370
+ * @param {boolean=} opt_quiet True to prevent error if input is not present.
1371
+ * @return {boolean} True if operation succeeds, false if input is not present
1372
+ * and opt_quiet is true
1373
+ * @throws {Error} if the input is not present and opt_quiet is not true.
1374
+ */
1375
+ removeInput(name, opt_quiet) {
1376
+ const removed = super.removeInput(name, opt_quiet);
1370
1377
 
1371
- /**
1372
- * Move a numbered input to a different location on this block.
1373
- * @param {number} inputIndex Index of the input to move.
1374
- * @param {number} refIndex Index of input that should be after the moved input.
1375
- */
1376
- BlockSvg.prototype.moveNumberedInputBefore = function(inputIndex, refIndex) {
1377
- BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex, refIndex);
1378
+ if (this.rendered) {
1379
+ this.render();
1380
+ // Removing an input will cause the block to change shape.
1381
+ this.bumpNeighbours();
1382
+ }
1378
1383
 
1379
- if (this.rendered) {
1380
- this.render();
1381
- // Moving an input will cause the block to change shape.
1382
- this.bumpNeighbours();
1384
+ return removed;
1383
1385
  }
1384
- };
1385
1386
 
1386
- /**
1387
- * Add a value input, statement input or local variable to this block.
1388
- * @param {number} type One of Blockly.inputTypes.
1389
- * @param {string} name Language-neutral identifier which may used to find this
1390
- * input again. Should be unique to this block.
1391
- * @return {!Input} The input object created.
1392
- * @protected
1393
- * @override
1394
- */
1395
- BlockSvg.prototype.appendInput_ = function(type, name) {
1396
- const input = BlockSvg.superClass_.appendInput_.call(this, type, name);
1387
+ /**
1388
+ * Move a numbered input to a different location on this block.
1389
+ * @param {number} inputIndex Index of the input to move.
1390
+ * @param {number} refIndex Index of input that should be after the moved
1391
+ * input.
1392
+ */
1393
+ moveNumberedInputBefore(inputIndex, refIndex) {
1394
+ super.moveNumberedInputBefore(inputIndex, refIndex);
1397
1395
 
1398
- if (this.rendered) {
1399
- this.render();
1400
- // Adding an input will cause the block to change shape.
1401
- this.bumpNeighbours();
1396
+ if (this.rendered) {
1397
+ this.render();
1398
+ // Moving an input will cause the block to change shape.
1399
+ this.bumpNeighbours();
1400
+ }
1402
1401
  }
1403
- return input;
1404
- };
1405
1402
 
1406
- /**
1407
- * Sets whether this block's connections are tracked in the database or not.
1408
- *
1409
- * Used by the deserializer to be more efficient. Setting a connection's
1410
- * tracked_ value to false keeps it from adding itself to the db when it
1411
- * gets its first moveTo call, saving expensive ops for later.
1412
- * @param {boolean} track If true, start tracking. If false, stop tracking.
1413
- * @package
1414
- */
1415
- BlockSvg.prototype.setConnectionTracking = function(track) {
1416
- if (this.previousConnection) {
1417
- this.previousConnection.setTracking(track);
1403
+ /**
1404
+ * Add a value input, statement input or local variable to this block.
1405
+ * @param {number} type One of Blockly.inputTypes.
1406
+ * @param {string} name Language-neutral identifier which may used to find
1407
+ * this input again. Should be unique to this block.
1408
+ * @return {!Input} The input object created.
1409
+ * @protected
1410
+ * @override
1411
+ */
1412
+ appendInput_(type, name) {
1413
+ const input = super.appendInput_(type, name);
1414
+
1415
+ if (this.rendered) {
1416
+ this.render();
1417
+ // Adding an input will cause the block to change shape.
1418
+ this.bumpNeighbours();
1419
+ }
1420
+ return input;
1418
1421
  }
1419
- if (this.outputConnection) {
1420
- this.outputConnection.setTracking(track);
1422
+
1423
+ /**
1424
+ * Sets whether this block's connections are tracked in the database or not.
1425
+ *
1426
+ * Used by the deserializer to be more efficient. Setting a connection's
1427
+ * tracked_ value to false keeps it from adding itself to the db when it
1428
+ * gets its first moveTo call, saving expensive ops for later.
1429
+ * @param {boolean} track If true, start tracking. If false, stop tracking.
1430
+ * @package
1431
+ */
1432
+ setConnectionTracking(track) {
1433
+ if (this.previousConnection) {
1434
+ /** @type {!RenderedConnection} */ (this.previousConnection)
1435
+ .setTracking(track);
1436
+ }
1437
+ if (this.outputConnection) {
1438
+ /** @type {!RenderedConnection} */ (this.outputConnection)
1439
+ .setTracking(track);
1440
+ }
1441
+ if (this.nextConnection) {
1442
+ /** @type {!RenderedConnection} */ (this.nextConnection)
1443
+ .setTracking(track);
1444
+ const child =
1445
+ /** @type {!RenderedConnection} */ (this.nextConnection)
1446
+ .targetBlock();
1447
+ if (child) {
1448
+ child.setConnectionTracking(track);
1449
+ }
1450
+ }
1451
+
1452
+ if (this.collapsed_) {
1453
+ // When track is true, we don't want to start tracking collapsed
1454
+ // connections. When track is false, we're already not tracking
1455
+ // collapsed connections, so no need to update.
1456
+ return;
1457
+ }
1458
+
1459
+ for (let i = 0; i < this.inputList.length; i++) {
1460
+ const conn =
1461
+ /** @type {!RenderedConnection} */ (this.inputList[i].connection);
1462
+ if (conn) {
1463
+ conn.setTracking(track);
1464
+
1465
+ // Pass tracking on down the chain.
1466
+ const block = conn.targetBlock();
1467
+ if (block) {
1468
+ block.setConnectionTracking(track);
1469
+ }
1470
+ }
1471
+ }
1421
1472
  }
1422
- if (this.nextConnection) {
1423
- this.nextConnection.setTracking(track);
1424
- const child = this.nextConnection.targetBlock();
1425
- if (child) {
1426
- child.setConnectionTracking(track);
1473
+
1474
+ /**
1475
+ * Returns connections originating from this block.
1476
+ * @param {boolean} all If true, return all connections even hidden ones.
1477
+ * Otherwise, for a non-rendered block return an empty list, and for a
1478
+ * collapsed block don't return inputs connections.
1479
+ * @return {!Array<!RenderedConnection>} Array of connections.
1480
+ * @package
1481
+ */
1482
+ getConnections_(all) {
1483
+ const myConnections = [];
1484
+ if (all || this.rendered) {
1485
+ if (this.outputConnection) {
1486
+ myConnections.push(this.outputConnection);
1487
+ }
1488
+ if (this.previousConnection) {
1489
+ myConnections.push(this.previousConnection);
1490
+ }
1491
+ if (this.nextConnection) {
1492
+ myConnections.push(this.nextConnection);
1493
+ }
1494
+ if (all || !this.collapsed_) {
1495
+ for (let i = 0, input; (input = this.inputList[i]); i++) {
1496
+ if (input.connection) {
1497
+ myConnections.push(input.connection);
1498
+ }
1499
+ }
1500
+ }
1427
1501
  }
1502
+ return myConnections;
1428
1503
  }
1429
1504
 
1430
- if (this.collapsed_) {
1431
- // When track is true, we don't want to start tracking collapsed
1432
- // connections. When track is false, we're already not tracking
1433
- // collapsed connections, so no need to update.
1434
- return;
1505
+ /**
1506
+ * Walks down a stack of blocks and finds the last next connection on the
1507
+ * stack.
1508
+ * @param {boolean} ignoreShadows If true,the last connection on a non-shadow
1509
+ * block will be returned. If false, this will follow shadows to find the
1510
+ * last connection.
1511
+ * @return {?RenderedConnection} The last next connection on the stack,
1512
+ * or null.
1513
+ * @package
1514
+ * @override
1515
+ */
1516
+ lastConnectionInStack(ignoreShadows) {
1517
+ return /** @type {RenderedConnection} */ (
1518
+ super.lastConnectionInStack(ignoreShadows));
1435
1519
  }
1436
1520
 
1437
- for (let i = 0; i < this.inputList.length; i++) {
1438
- const conn = this.inputList[i].connection;
1439
- if (conn) {
1440
- conn.setTracking(track);
1521
+ /**
1522
+ * Find the connection on this block that corresponds to the given connection
1523
+ * on the other block.
1524
+ * Used to match connections between a block and its insertion marker.
1525
+ * @param {!Block} otherBlock The other block to match against.
1526
+ * @param {!Connection} conn The other connection to match.
1527
+ * @return {?RenderedConnection} The matching connection on this block,
1528
+ * or null.
1529
+ * @package
1530
+ * @override
1531
+ */
1532
+ getMatchingConnection(otherBlock, conn) {
1533
+ return /** @type {RenderedConnection} */ (
1534
+ super.getMatchingConnection(otherBlock, conn));
1535
+ }
1441
1536
 
1442
- // Pass tracking on down the chain.
1443
- const block = conn.targetBlock();
1444
- if (block) {
1445
- block.setConnectionTracking(track);
1446
- }
1447
- }
1537
+ /**
1538
+ * Create a connection of the specified type.
1539
+ * @param {number} type The type of the connection to create.
1540
+ * @return {!RenderedConnection} A new connection of the specified type.
1541
+ * @protected
1542
+ */
1543
+ makeConnection_(type) {
1544
+ return new RenderedConnection(this, type);
1448
1545
  }
1449
- };
1450
1546
 
1451
- /**
1452
- * Returns connections originating from this block.
1453
- * @param {boolean} all If true, return all connections even hidden ones.
1454
- * Otherwise, for a non-rendered block return an empty list, and for a
1455
- * collapsed block don't return inputs connections.
1456
- * @return {!Array<!RenderedConnection>} Array of connections.
1457
- * @package
1458
- */
1459
- BlockSvg.prototype.getConnections_ = function(all) {
1460
- const myConnections = [];
1461
- if (all || this.rendered) {
1462
- if (this.outputConnection) {
1463
- myConnections.push(this.outputConnection);
1547
+ /**
1548
+ * Bump unconnected blocks out of alignment. Two blocks which aren't actually
1549
+ * connected should not coincidentally line up on screen.
1550
+ */
1551
+ bumpNeighbours() {
1552
+ if (!this.workspace) {
1553
+ return; // Deleted block.
1464
1554
  }
1465
- if (this.previousConnection) {
1466
- myConnections.push(this.previousConnection);
1555
+ if (this.workspace.isDragging()) {
1556
+ return; // Don't bump blocks during a drag.
1467
1557
  }
1468
- if (this.nextConnection) {
1469
- myConnections.push(this.nextConnection);
1558
+ const rootBlock = this.getRootBlock();
1559
+ if (rootBlock.isInFlyout) {
1560
+ return; // Don't move blocks around in a flyout.
1470
1561
  }
1471
- if (all || !this.collapsed_) {
1472
- for (let i = 0, input; (input = this.inputList[i]); i++) {
1473
- if (input.connection) {
1474
- myConnections.push(input.connection);
1562
+ // Loop through every connection on this block.
1563
+ const myConnections = this.getConnections_(false);
1564
+ for (let i = 0, connection; (connection = myConnections[i]); i++) {
1565
+ const renderedConn = /** @type {!RenderedConnection} */ (connection);
1566
+ // Spider down from this block bumping all sub-blocks.
1567
+ if (renderedConn.isConnected() && renderedConn.isSuperior()) {
1568
+ renderedConn.targetBlock().bumpNeighbours();
1569
+ }
1570
+
1571
+ const neighbours = connection.neighbours(config.snapRadius);
1572
+ for (let j = 0, otherConnection; (otherConnection = neighbours[j]); j++) {
1573
+ const renderedOther =
1574
+ /** @type {!RenderedConnection} */ (otherConnection);
1575
+ // If both connections are connected, that's probably fine. But if
1576
+ // either one of them is unconnected, then there could be confusion.
1577
+ if (!renderedConn.isConnected() || !renderedOther.isConnected()) {
1578
+ // Only bump blocks if they are from different tree structures.
1579
+ if (renderedOther.getSourceBlock().getRootBlock() !== rootBlock) {
1580
+ // Always bump the inferior block.
1581
+ if (renderedConn.isSuperior()) {
1582
+ renderedOther.bumpAwayFrom(renderedConn);
1583
+ } else {
1584
+ renderedConn.bumpAwayFrom(renderedOther);
1585
+ }
1586
+ }
1475
1587
  }
1476
1588
  }
1477
1589
  }
1478
1590
  }
1479
- return myConnections;
1480
- };
1481
1591
 
1482
- /**
1483
- * Walks down a stack of blocks and finds the last next connection on the stack.
1484
- * @param {boolean} ignoreShadows If true,the last connection on a non-shadow
1485
- * block will be returned. If false, this will follow shadows to find the
1486
- * last connection.
1487
- * @return {?RenderedConnection} The last next connection on the stack,
1488
- * or null.
1489
- * @package
1490
- * @override
1491
- */
1492
- BlockSvg.prototype.lastConnectionInStack = function(ignoreShadows) {
1493
- return /** @type {RenderedConnection} */ (
1494
- BlockSvg.superClass_.lastConnectionInStack.call(this, ignoreShadows));
1495
- };
1592
+ /**
1593
+ * Schedule snapping to grid and bumping neighbours to occur after a brief
1594
+ * delay.
1595
+ * @package
1596
+ */
1597
+ scheduleSnapAndBump() {
1598
+ const block = this;
1599
+ // Ensure that any snap and bump are part of this move's event group.
1600
+ const group = eventUtils.getGroup();
1496
1601
 
1497
- /**
1498
- * Find the connection on this block that corresponds to the given connection
1499
- * on the other block.
1500
- * Used to match connections between a block and its insertion marker.
1501
- * @param {!Block} otherBlock The other block to match against.
1502
- * @param {!Connection} conn The other connection to match.
1503
- * @return {?RenderedConnection} The matching connection on this block,
1504
- * or null.
1505
- * @package
1506
- * @override
1507
- */
1508
- BlockSvg.prototype.getMatchingConnection = function(otherBlock, conn) {
1509
- return /** @type {RenderedConnection} */ (
1510
- BlockSvg.superClass_.getMatchingConnection.call(this, otherBlock, conn));
1511
- };
1602
+ setTimeout(function() {
1603
+ eventUtils.setGroup(group);
1604
+ block.snapToGrid();
1605
+ eventUtils.setGroup(false);
1606
+ }, config.bumpDelay / 2);
1512
1607
 
1513
- /**
1514
- * Create a connection of the specified type.
1515
- * @param {number} type The type of the connection to create.
1516
- * @return {!RenderedConnection} A new connection of the specified type.
1517
- * @protected
1518
- */
1519
- BlockSvg.prototype.makeConnection_ = function(type) {
1520
- return new RenderedConnection(this, type);
1521
- };
1608
+ setTimeout(function() {
1609
+ eventUtils.setGroup(group);
1610
+ block.bumpNeighbours();
1611
+ eventUtils.setGroup(false);
1612
+ }, config.bumpDelay);
1613
+ }
1522
1614
 
1523
- /**
1524
- * Bump unconnected blocks out of alignment. Two blocks which aren't actually
1525
- * connected should not coincidentally line up on screen.
1526
- */
1527
- BlockSvg.prototype.bumpNeighbours = function() {
1528
- if (!this.workspace) {
1529
- return; // Deleted block.
1530
- }
1531
- if (this.workspace.isDragging()) {
1532
- return; // Don't bump blocks during a drag.
1533
- }
1534
- const rootBlock = this.getRootBlock();
1535
- if (rootBlock.isInFlyout) {
1536
- return; // Don't move blocks around in a flyout.
1537
- }
1538
- // Loop through every connection on this block.
1539
- const myConnections = this.getConnections_(false);
1540
- for (let i = 0, connection; (connection = myConnections[i]); i++) {
1541
- // Spider down from this block bumping all sub-blocks.
1542
- if (connection.isConnected() && connection.isSuperior()) {
1543
- connection.targetBlock().bumpNeighbours();
1544
- }
1545
-
1546
- const neighbours = connection.neighbours(internalConstants.SNAP_RADIUS);
1547
- for (let j = 0, otherConnection; (otherConnection = neighbours[j]); j++) {
1548
- // If both connections are connected, that's probably fine. But if
1549
- // either one of them is unconnected, then there could be confusion.
1550
- if (!connection.isConnected() || !otherConnection.isConnected()) {
1551
- // Only bump blocks if they are from different tree structures.
1552
- if (otherConnection.getSourceBlock().getRootBlock() !== rootBlock) {
1553
- // Always bump the inferior block.
1554
- if (connection.isSuperior()) {
1555
- otherConnection.bumpAwayFrom(connection);
1556
- } else {
1557
- connection.bumpAwayFrom(otherConnection);
1558
- }
1559
- }
1560
- }
1615
+ /**
1616
+ * Position a block so that it doesn't move the target block when connected.
1617
+ * The block to position is usually either the first block in a dragged stack
1618
+ * or an insertion marker.
1619
+ * @param {!RenderedConnection} sourceConnection The connection on the
1620
+ * moving block's stack.
1621
+ * @param {!RenderedConnection} targetConnection The connection that
1622
+ * should stay stationary as this block is positioned.
1623
+ * @package
1624
+ */
1625
+ positionNearConnection(sourceConnection, targetConnection) {
1626
+ // We only need to position the new block if it's before the existing one,
1627
+ // otherwise its position is set by the previous block.
1628
+ if (sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
1629
+ sourceConnection.type === ConnectionType.INPUT_VALUE) {
1630
+ const dx = targetConnection.x - sourceConnection.x;
1631
+ const dy = targetConnection.y - sourceConnection.y;
1632
+
1633
+ this.moveBy(dx, dy);
1561
1634
  }
1562
1635
  }
1563
- };
1564
1636
 
1565
- /**
1566
- * Schedule snapping to grid and bumping neighbours to occur after a brief
1567
- * delay.
1568
- * @package
1569
- */
1570
- BlockSvg.prototype.scheduleSnapAndBump = function() {
1571
- const block = this;
1572
- // Ensure that any snap and bump are part of this move's event group.
1573
- const group = eventUtils.getGroup();
1574
-
1575
- setTimeout(function() {
1576
- eventUtils.setGroup(group);
1577
- block.snapToGrid();
1578
- eventUtils.setGroup(false);
1579
- }, internalConstants.BUMP_DELAY / 2);
1637
+ /**
1638
+ * Return the parent block or null if this block is at the top level.
1639
+ * @return {?BlockSvg} The block (if any) that holds the current block.
1640
+ * @override
1641
+ */
1642
+ getParent() {
1643
+ return /** @type {?BlockSvg} */ (super.getParent());
1644
+ }
1580
1645
 
1581
- setTimeout(function() {
1582
- eventUtils.setGroup(group);
1583
- block.bumpNeighbours();
1584
- eventUtils.setGroup(false);
1585
- }, internalConstants.BUMP_DELAY);
1586
- };
1646
+ /**
1647
+ * @return {?BlockSvg} The block (if any) that surrounds the current block.
1648
+ * @override
1649
+ */
1650
+ getSurroundParent() {
1651
+ return /** @type {?BlockSvg} */ (super.getSurroundParent());
1652
+ }
1587
1653
 
1588
- /**
1589
- * Position a block so that it doesn't move the target block when connected.
1590
- * The block to position is usually either the first block in a dragged stack or
1591
- * an insertion marker.
1592
- * @param {!RenderedConnection} sourceConnection The connection on the
1593
- * moving block's stack.
1594
- * @param {!RenderedConnection} targetConnection The connection that
1595
- * should stay stationary as this block is positioned.
1596
- * @package
1597
- */
1598
- BlockSvg.prototype.positionNearConnection = function(
1599
- sourceConnection, targetConnection) {
1600
- // We only need to position the new block if it's before the existing one,
1601
- // otherwise its position is set by the previous block.
1602
- if (sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
1603
- sourceConnection.type === ConnectionType.INPUT_VALUE) {
1604
- const dx = targetConnection.x - sourceConnection.x;
1605
- const dy = targetConnection.y - sourceConnection.y;
1654
+ /**
1655
+ * @return {?BlockSvg} The next statement block or null.
1656
+ * @override
1657
+ */
1658
+ getNextBlock() {
1659
+ return /** @type {?BlockSvg} */ (super.getNextBlock());
1660
+ }
1606
1661
 
1607
- this.moveBy(dx, dy);
1662
+ /**
1663
+ * @return {?BlockSvg} The previou statement block or null.
1664
+ * @override
1665
+ */
1666
+ getPreviousBlock() {
1667
+ return /** @type {?BlockSvg} */ (super.getPreviousBlock());
1608
1668
  }
1609
- };
1610
1669
 
1611
- /**
1612
- * Return the parent block or null if this block is at the top level.
1613
- * @return {?BlockSvg} The block (if any) that holds the current block.
1614
- * @override
1615
- */
1616
- BlockSvg.prototype.getParent = function() {
1617
- return /** @type {!BlockSvg} */ (BlockSvg.superClass_.getParent.call(this));
1618
- };
1670
+ /**
1671
+ * @return {?RenderedConnection} The first statement connection or null.
1672
+ * @package
1673
+ * @override
1674
+ */
1675
+ getFirstStatementConnection() {
1676
+ return /** @type {?RenderedConnection} */ (
1677
+ super.getFirstStatementConnection());
1678
+ }
1619
1679
 
1620
- /**
1621
- * Return the top-most block in this block's tree.
1622
- * This will return itself if this block is at the top level.
1623
- * @return {!BlockSvg} The root block.
1624
- * @override
1625
- */
1626
- BlockSvg.prototype.getRootBlock = function() {
1627
- return /** @type {!BlockSvg} */ (
1628
- BlockSvg.superClass_.getRootBlock.call(this));
1629
- };
1680
+ /**
1681
+ * @return {!BlockSvg} The top block in a stack.
1682
+ * @override
1683
+ */
1684
+ getTopStackBlock() {
1685
+ return /** @type {!BlockSvg} */ (super.getTopStackBlock());
1686
+ }
1630
1687
 
1631
- /**
1632
- * Lays out and reflows a block based on its contents and settings.
1633
- * @param {boolean=} opt_bubble If false, just render this block.
1634
- * If true, also render block's parent, grandparent, etc. Defaults to true.
1635
- */
1636
- BlockSvg.prototype.render = function(opt_bubble) {
1637
- if (this.renderIsInProgress_) {
1638
- return; // Don't allow recursive renders.
1688
+ /**
1689
+ * @param {boolean} ordered Sort the list if true.
1690
+ * @return {!Array<!BlockSvg>} Children of this block.
1691
+ * @override
1692
+ */
1693
+ getChildren(ordered) {
1694
+ return /** @type {!Array<!BlockSvg>} */ (super.getChildren(ordered));
1639
1695
  }
1640
- this.renderIsInProgress_ = true;
1641
- try {
1642
- this.rendered = true;
1643
- dom.startTextWidthCache();
1644
1696
 
1645
- if (this.isCollapsed()) {
1646
- this.updateCollapsed_();
1697
+ /**
1698
+ * @param {boolean} ordered Sort the list if true.
1699
+ * @return {!Array<!BlockSvg>} Descendants of this block.
1700
+ * @override
1701
+ */
1702
+ getDescendants(ordered) {
1703
+ return /** @type {!Array<!BlockSvg>} */ (super.getDescendants(ordered));
1704
+ }
1705
+
1706
+ /**
1707
+ * @param {string} name The name of the input.
1708
+ * @return {?BlockSvg} The attached value block, or null if the input is
1709
+ * either disconnected or if the input does not exist.
1710
+ * @override
1711
+ */
1712
+ getInputTargetBlock(name) {
1713
+ return /** @type {?BlockSvg} */ (super.getInputTargetBlock(name));
1714
+ }
1715
+
1716
+ /**
1717
+ * Return the top-most block in this block's tree.
1718
+ * This will return itself if this block is at the top level.
1719
+ * @return {!BlockSvg} The root block.
1720
+ * @override
1721
+ */
1722
+ getRootBlock() {
1723
+ return /** @type {!BlockSvg} */ (super.getRootBlock());
1724
+ }
1725
+
1726
+ /**
1727
+ * Lays out and reflows a block based on its contents and settings.
1728
+ * @param {boolean=} opt_bubble If false, just render this block.
1729
+ * If true, also render block's parent, grandparent, etc. Defaults to true.
1730
+ */
1731
+ render(opt_bubble) {
1732
+ if (this.renderIsInProgress_) {
1733
+ return; // Don't allow recursive renders.
1647
1734
  }
1648
- this.workspace.getRenderer().render(this);
1649
- this.updateConnectionLocations_();
1735
+ this.renderIsInProgress_ = true;
1736
+ try {
1737
+ this.rendered = true;
1738
+ dom.startTextWidthCache();
1650
1739
 
1651
- if (opt_bubble !== false) {
1652
- const parentBlock = this.getParent();
1653
- if (parentBlock) {
1654
- parentBlock.render(true);
1655
- } else {
1656
- // Top-most block. Fire an event to allow scrollbars to resize.
1657
- this.workspace.resizeContents();
1740
+ if (this.isCollapsed()) {
1741
+ this.updateCollapsed_();
1742
+ }
1743
+ this.workspace.getRenderer().render(this);
1744
+ this.updateConnectionLocations_();
1745
+
1746
+ if (opt_bubble !== false) {
1747
+ const parentBlock = this.getParent();
1748
+ if (parentBlock) {
1749
+ parentBlock.render(true);
1750
+ } else {
1751
+ // Top-most block. Fire an event to allow scrollbars to resize.
1752
+ this.workspace.resizeContents();
1753
+ }
1658
1754
  }
1659
- }
1660
1755
 
1661
- dom.stopTextWidthCache();
1662
- this.updateMarkers_();
1663
- } finally {
1664
- this.renderIsInProgress_ = false;
1756
+ dom.stopTextWidthCache();
1757
+ this.updateMarkers_();
1758
+ } finally {
1759
+ this.renderIsInProgress_ = false;
1760
+ }
1665
1761
  }
1666
- };
1667
1762
 
1668
- /**
1669
- * Redraw any attached marker or cursor svgs if needed.
1670
- * @protected
1671
- */
1672
- BlockSvg.prototype.updateMarkers_ = function() {
1673
- if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) {
1674
- this.workspace.getCursor().draw();
1675
- }
1676
- if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) {
1677
- // TODO(#4592): Update all markers on the block.
1678
- this.workspace.getMarker(MarkerManager.LOCAL_MARKER).draw();
1763
+ /**
1764
+ * Redraw any attached marker or cursor svgs if needed.
1765
+ * @protected
1766
+ */
1767
+ updateMarkers_() {
1768
+ if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) {
1769
+ this.workspace.getCursor().draw();
1770
+ }
1771
+ if (this.workspace.keyboardAccessibilityMode && this.pathObject.markerSvg) {
1772
+ // TODO(#4592): Update all markers on the block.
1773
+ this.workspace.getMarker(MarkerManager.LOCAL_MARKER).draw();
1774
+ }
1679
1775
  }
1680
- };
1681
1776
 
1682
- /**
1683
- * Update all of the connections on this block with the new locations calculated
1684
- * during rendering. Also move all of the connected blocks based on the new
1685
- * connection locations.
1686
- * @private
1687
- */
1688
- BlockSvg.prototype.updateConnectionLocations_ = function() {
1689
- const blockTL = this.getRelativeToSurfaceXY();
1690
- // Don't tighten previous or output connections because they are inferior
1691
- // connections.
1692
- if (this.previousConnection) {
1693
- this.previousConnection.moveToOffset(blockTL);
1694
- }
1695
- if (this.outputConnection) {
1696
- this.outputConnection.moveToOffset(blockTL);
1697
- }
1698
-
1699
- for (let i = 0; i < this.inputList.length; i++) {
1700
- const conn = this.inputList[i].connection;
1701
- if (conn) {
1702
- conn.moveToOffset(blockTL);
1703
- if (conn.isConnected()) {
1704
- conn.tighten();
1777
+ /**
1778
+ * Update all of the connections on this block with the new locations
1779
+ * calculated during rendering. Also move all of the connected blocks based
1780
+ * on the new connection locations.
1781
+ * @private
1782
+ */
1783
+ updateConnectionLocations_() {
1784
+ const blockTL = this.getRelativeToSurfaceXY();
1785
+ // Don't tighten previous or output connections because they are inferior
1786
+ // connections.
1787
+ if (this.previousConnection) {
1788
+ this.previousConnection.moveToOffset(blockTL);
1789
+ }
1790
+ if (this.outputConnection) {
1791
+ this.outputConnection.moveToOffset(blockTL);
1792
+ }
1793
+
1794
+ for (let i = 0; i < this.inputList.length; i++) {
1795
+ const conn =
1796
+ /** @type {!RenderedConnection} */ (this.inputList[i].connection);
1797
+ if (conn) {
1798
+ conn.moveToOffset(blockTL);
1799
+ if (conn.isConnected()) {
1800
+ conn.tighten();
1801
+ }
1705
1802
  }
1706
1803
  }
1707
- }
1708
1804
 
1709
- if (this.nextConnection) {
1710
- this.nextConnection.moveToOffset(blockTL);
1711
- if (this.nextConnection.isConnected()) {
1712
- this.nextConnection.tighten();
1805
+ if (this.nextConnection) {
1806
+ this.nextConnection.moveToOffset(blockTL);
1807
+ if (this.nextConnection.isConnected()) {
1808
+ this.nextConnection.tighten();
1809
+ }
1713
1810
  }
1714
1811
  }
1715
- };
1716
1812
 
1717
- /**
1718
- * Add the cursor SVG to this block's SVG group.
1719
- * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the
1720
- * block SVG group.
1721
- * @package
1722
- */
1723
- BlockSvg.prototype.setCursorSvg = function(cursorSvg) {
1724
- this.pathObject.setCursorSvg(cursorSvg);
1725
- };
1813
+ /**
1814
+ * Add the cursor SVG to this block's SVG group.
1815
+ * @param {SVGElement} cursorSvg The SVG root of the cursor to be added to the
1816
+ * block SVG group.
1817
+ * @package
1818
+ */
1819
+ setCursorSvg(cursorSvg) {
1820
+ this.pathObject.setCursorSvg(cursorSvg);
1821
+ }
1726
1822
 
1727
- /**
1728
- * Add the marker SVG to this block's SVG group.
1729
- * @param {SVGElement} markerSvg The SVG root of the marker to be added to the
1730
- * block SVG group.
1731
- * @package
1732
- */
1733
- BlockSvg.prototype.setMarkerSvg = function(markerSvg) {
1734
- this.pathObject.setMarkerSvg(markerSvg);
1735
- };
1823
+ /**
1824
+ * Add the marker SVG to this block's SVG group.
1825
+ * @param {SVGElement} markerSvg The SVG root of the marker to be added to the
1826
+ * block SVG group.
1827
+ * @package
1828
+ */
1829
+ setMarkerSvg(markerSvg) {
1830
+ this.pathObject.setMarkerSvg(markerSvg);
1831
+ }
1736
1832
 
1737
- /**
1738
- * Returns a bounding box describing the dimensions of this block
1739
- * and any blocks stacked below it.
1740
- * @return {!{height: number, width: number}} Object with height and width
1741
- * properties in workspace units.
1742
- * @package
1743
- */
1744
- BlockSvg.prototype.getHeightWidth = function() {
1745
- let height = this.height;
1746
- let width = this.width;
1747
- // Recursively add size of subsequent blocks.
1748
- const nextBlock = this.getNextBlock();
1749
- if (nextBlock) {
1750
- const nextHeightWidth = nextBlock.getHeightWidth();
1751
- const workspace = /** @type {!WorkspaceSvg} */ (this.workspace);
1752
- const tabHeight = workspace.getRenderer().getConstants().NOTCH_HEIGHT;
1753
- height += nextHeightWidth.height - tabHeight;
1754
- width = Math.max(width, nextHeightWidth.width);
1755
- }
1756
- return {height: height, width: width};
1757
- };
1833
+ /**
1834
+ * Returns a bounding box describing the dimensions of this block
1835
+ * and any blocks stacked below it.
1836
+ * @return {!{height: number, width: number}} Object with height and width
1837
+ * properties in workspace units.
1838
+ * @package
1839
+ */
1840
+ getHeightWidth() {
1841
+ let height = this.height;
1842
+ let width = this.width;
1843
+ // Recursively add size of subsequent blocks.
1844
+ const nextBlock = this.getNextBlock();
1845
+ if (nextBlock) {
1846
+ const nextHeightWidth = nextBlock.getHeightWidth();
1847
+ const workspace = /** @type {!WorkspaceSvg} */ (this.workspace);
1848
+ const tabHeight = workspace.getRenderer().getConstants().NOTCH_HEIGHT;
1849
+ height += nextHeightWidth.height - tabHeight;
1850
+ width = Math.max(width, nextHeightWidth.width);
1851
+ }
1852
+ return {height: height, width: width};
1853
+ }
1854
+
1855
+ /**
1856
+ * Visual effect to show that if the dragging block is dropped, this block
1857
+ * will be replaced. If a shadow block, it will disappear. Otherwise it will
1858
+ * bump.
1859
+ * @param {boolean} add True if highlighting should be added.
1860
+ * @package
1861
+ */
1862
+ fadeForReplacement(add) {
1863
+ this.pathObject.updateReplacementFade(add);
1864
+ }
1865
+
1866
+ /**
1867
+ * Visual effect to show that if the dragging block is dropped it will connect
1868
+ * to this input.
1869
+ * @param {Connection} conn The connection on the input to highlight.
1870
+ * @param {boolean} add True if highlighting should be added.
1871
+ * @package
1872
+ */
1873
+ highlightShapeForInput(conn, add) {
1874
+ this.pathObject.updateShapeForInputHighlight(conn, add);
1875
+ }
1876
+ }
1758
1877
 
1759
1878
  /**
1760
- * Visual effect to show that if the dragging block is dropped, this block will
1761
- * be replaced. If a shadow block, it will disappear. Otherwise it will bump.
1762
- * @param {boolean} add True if highlighting should be added.
1763
- * @package
1879
+ * Constant for identifying rows that are to be rendered inline.
1880
+ * Don't collide with Blockly.inputTypes.
1881
+ * @const
1764
1882
  */
1765
- BlockSvg.prototype.fadeForReplacement = function(add) {
1766
- this.pathObject.updateReplacementFade(add);
1767
- };
1883
+ BlockSvg.INLINE = -1;
1768
1884
 
1769
1885
  /**
1770
- * Visual effect to show that if the dragging block is dropped it will connect
1771
- * to this input.
1772
- * @param {Connection} conn The connection on the input to highlight.
1773
- * @param {boolean} add True if highlighting should be added.
1774
- * @package
1886
+ * ID to give the "collapsed warnings" warning. Allows us to remove the
1887
+ * "collapsed warnings" warning without removing any warnings that belong to
1888
+ * the block.
1889
+ * @type {string}
1890
+ * @const
1775
1891
  */
1776
- BlockSvg.prototype.highlightShapeForInput = function(conn, add) {
1777
- this.pathObject.updateShapeForInputHighlight(conn, add);
1778
- };
1892
+ BlockSvg.COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';
1779
1893
 
1780
1894
  exports.BlockSvg = BlockSvg;