@blockdia/scratch-blocks 0.1.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 (243) hide show
  1. package/.nvmrc +1 -0
  2. package/LICENSE +674 -0
  3. package/README.md +57 -0
  4. package/TRADEMARK +1 -0
  5. package/blockly_compressed_horizontal.js +2268 -0
  6. package/blockly_compressed_vertical.js +2296 -0
  7. package/blockly_uncompressed_horizontal.js +1212 -0
  8. package/blockly_uncompressed_vertical.js +1212 -0
  9. package/blocks_common/colour.js +61 -0
  10. package/blocks_common/math.js +159 -0
  11. package/blocks_common/matrix.js +54 -0
  12. package/blocks_common/note.js +58 -0
  13. package/blocks_common/text.js +57 -0
  14. package/blocks_compressed.js +37 -0
  15. package/blocks_compressed_horizontal.js +68 -0
  16. package/blocks_compressed_vertical.js +216 -0
  17. package/blocks_horizontal/control.js +212 -0
  18. package/blocks_horizontal/default_toolbox.js +139 -0
  19. package/blocks_horizontal/event.js +190 -0
  20. package/blocks_horizontal/wedo.js +325 -0
  21. package/blocks_vertical/control.js +532 -0
  22. package/blocks_vertical/data.js +666 -0
  23. package/blocks_vertical/default_toolbox.js +571 -0
  24. package/blocks_vertical/event.js +329 -0
  25. package/blocks_vertical/extensions.js +294 -0
  26. package/blocks_vertical/looks.js +591 -0
  27. package/blocks_vertical/motion.js +587 -0
  28. package/blocks_vertical/operators.js +470 -0
  29. package/blocks_vertical/procedures.js +1051 -0
  30. package/blocks_vertical/sensing.js +555 -0
  31. package/blocks_vertical/sound.js +246 -0
  32. package/blocks_vertical/vertical_extensions.js +289 -0
  33. package/build/gen_blocks.js +1 -0
  34. package/build/test_expect.js +1 -0
  35. package/build/test_input.js +1 -0
  36. package/build.py +647 -0
  37. package/cleanup.sh +101 -0
  38. package/core/block.js +1837 -0
  39. package/core/block_animations.js +107 -0
  40. package/core/block_drag_surface.js +299 -0
  41. package/core/block_dragger.js +424 -0
  42. package/core/block_events.js +531 -0
  43. package/core/block_render_svg_horizontal.js +892 -0
  44. package/core/block_render_svg_vertical.js +1734 -0
  45. package/core/block_svg.js +1355 -0
  46. package/core/blockly.js +620 -0
  47. package/core/blocks.js +37 -0
  48. package/core/bubble.js +664 -0
  49. package/core/bubble_dragger.js +285 -0
  50. package/core/colours.js +177 -0
  51. package/core/comment.js +293 -0
  52. package/core/comment_events.js +539 -0
  53. package/core/connection.js +776 -0
  54. package/core/connection_db.js +300 -0
  55. package/core/constants.js +408 -0
  56. package/core/contextmenu.js +534 -0
  57. package/core/css.js +1354 -0
  58. package/core/data_category.js +490 -0
  59. package/core/dragged_connection_manager.js +260 -0
  60. package/core/dropdowndiv.js +408 -0
  61. package/core/events.js +429 -0
  62. package/core/events_abstract.js +113 -0
  63. package/core/extensions.js +450 -0
  64. package/core/field.js +810 -0
  65. package/core/field_angle.js +410 -0
  66. package/core/field_checkbox.js +133 -0
  67. package/core/field_colour.js +253 -0
  68. package/core/field_colour_slider.js +387 -0
  69. package/core/field_date.js +353 -0
  70. package/core/field_dropdown.js +447 -0
  71. package/core/field_iconmenu.js +309 -0
  72. package/core/field_image.js +200 -0
  73. package/core/field_label.js +136 -0
  74. package/core/field_label_serializable.js +125 -0
  75. package/core/field_matrix.js +566 -0
  76. package/core/field_note.js +850 -0
  77. package/core/field_number.js +366 -0
  78. package/core/field_numberdropdown.js +77 -0
  79. package/core/field_textdropdown.js +164 -0
  80. package/core/field_textinput.js +675 -0
  81. package/core/field_textinput_removable.js +105 -0
  82. package/core/field_variable.js +385 -0
  83. package/core/field_variable_getter.js +185 -0
  84. package/core/field_vertical_separator.js +161 -0
  85. package/core/flyout_base.js +935 -0
  86. package/core/flyout_button.js +324 -0
  87. package/core/flyout_dragger.js +83 -0
  88. package/core/flyout_extension_category_header.js +159 -0
  89. package/core/flyout_horizontal.js +475 -0
  90. package/core/flyout_vertical.js +770 -0
  91. package/core/generator.js +426 -0
  92. package/core/gesture.js +1010 -0
  93. package/core/grid.js +227 -0
  94. package/core/icon.js +205 -0
  95. package/core/inject.js +496 -0
  96. package/core/input.js +285 -0
  97. package/core/insertion_marker_manager.js +678 -0
  98. package/core/intersection_observer.js +102 -0
  99. package/core/msg.js +62 -0
  100. package/core/mutator.js +426 -0
  101. package/core/names.js +198 -0
  102. package/core/options.js +244 -0
  103. package/core/procedures.js +739 -0
  104. package/core/rendered_connection.js +417 -0
  105. package/core/scratch_block_comment.js +646 -0
  106. package/core/scratch_blocks_utils.js +246 -0
  107. package/core/scratch_bubble.js +699 -0
  108. package/core/scratch_events.js +131 -0
  109. package/core/scratch_msgs.js +85 -0
  110. package/core/scrollbar.js +875 -0
  111. package/core/toolbox.js +803 -0
  112. package/core/tooltip.js +337 -0
  113. package/core/touch.js +226 -0
  114. package/core/trashcan.js +343 -0
  115. package/core/ui_events.js +91 -0
  116. package/core/ui_menu_utils.js +68 -0
  117. package/core/utils.js +825 -0
  118. package/core/variable_events.js +259 -0
  119. package/core/variable_map.js +415 -0
  120. package/core/variable_model.js +116 -0
  121. package/core/variables.js +674 -0
  122. package/core/warning.js +199 -0
  123. package/core/widgetdiv.js +344 -0
  124. package/core/workspace.js +673 -0
  125. package/core/workspace_audio.js +170 -0
  126. package/core/workspace_comment.js +426 -0
  127. package/core/workspace_comment_render_svg.js +706 -0
  128. package/core/workspace_comment_svg.js +609 -0
  129. package/core/workspace_drag_surface_svg.js +195 -0
  130. package/core/workspace_dragger.js +132 -0
  131. package/core/workspace_svg.js +2382 -0
  132. package/core/xml.js +930 -0
  133. package/core/zoom_controls.js +301 -0
  134. package/dist/horizontal.js +222 -0
  135. package/dist/vertical.js +222 -0
  136. package/dist/web/horizontal.js +232 -0
  137. package/dist/web/vertical.js +232 -0
  138. package/i18n/common.py +234 -0
  139. package/i18n/create_messages.py +162 -0
  140. package/i18n/create_scratch_msgs.js +69 -0
  141. package/i18n/dedup_json.py +73 -0
  142. package/i18n/js_to_json.js +46 -0
  143. package/i18n/js_to_json.py +136 -0
  144. package/i18n/json_to_js.js +52 -0
  145. package/i18n/json_to_js.py +185 -0
  146. package/i18n/sync_tx_translations.js +111 -0
  147. package/i18n/test_scratch_msgs.js +87 -0
  148. package/i18n/tests.py +47 -0
  149. package/i18n/xliff_to_json.py +232 -0
  150. package/local_build.sh +70 -0
  151. package/media/click.mp3 +0 -0
  152. package/media/click.ogg +0 -0
  153. package/media/click.wav +0 -0
  154. package/media/comment-arrow-down.svg +10 -0
  155. package/media/comment-arrow-up.svg +10 -0
  156. package/media/delete-x.svg +10 -0
  157. package/media/delete.mp3 +0 -0
  158. package/media/delete.ogg +0 -0
  159. package/media/delete.wav +0 -0
  160. package/media/dropdown-arrow-dark.svg +1 -0
  161. package/media/dropdown-arrow.svg +1 -0
  162. package/media/extensions/microbit-block-icon.svg +130 -0
  163. package/media/extensions/music-block-icon.svg +17 -0
  164. package/media/extensions/pen-block-icon.svg +19 -0
  165. package/media/extensions/wedo2-block-icon.svg +36 -0
  166. package/media/eyedropper.svg +22 -0
  167. package/media/green-flag.svg +17 -0
  168. package/media/handclosed.cur +0 -0
  169. package/media/handdelete.cur +0 -0
  170. package/media/handopen.cur +0 -0
  171. package/media/icons/arrow.svg +12 -0
  172. package/media/icons/arrow_button.svg +1 -0
  173. package/media/icons/control_forever.svg +1 -0
  174. package/media/icons/control_repeat.svg +1 -0
  175. package/media/icons/control_stop.svg +1 -0
  176. package/media/icons/control_wait.svg +1 -0
  177. package/media/icons/event_broadcast_blue.svg +1 -0
  178. package/media/icons/event_broadcast_coral.svg +1 -0
  179. package/media/icons/event_broadcast_green.svg +1 -0
  180. package/media/icons/event_broadcast_magenta.svg +1 -0
  181. package/media/icons/event_broadcast_orange.svg +1 -0
  182. package/media/icons/event_broadcast_purple.svg +1 -0
  183. package/media/icons/event_when-broadcast-received_blue.svg +1 -0
  184. package/media/icons/event_when-broadcast-received_coral.svg +1 -0
  185. package/media/icons/event_when-broadcast-received_green.svg +1 -0
  186. package/media/icons/event_when-broadcast-received_magenta.svg +1 -0
  187. package/media/icons/event_when-broadcast-received_orange.svg +1 -0
  188. package/media/icons/event_when-broadcast-received_purple.svg +1 -0
  189. package/media/icons/event_whenflagclicked.svg +1 -0
  190. package/media/icons/remove.svg +19 -0
  191. package/media/icons/set-led_blue.svg +1 -0
  192. package/media/icons/set-led_coral.svg +1 -0
  193. package/media/icons/set-led_green.svg +1 -0
  194. package/media/icons/set-led_magenta.svg +1 -0
  195. package/media/icons/set-led_mystery.svg +1 -0
  196. package/media/icons/set-led_orange.svg +1 -0
  197. package/media/icons/set-led_purple.svg +1 -0
  198. package/media/icons/set-led_white.svg +1 -0
  199. package/media/icons/set-led_yellow.svg +1 -0
  200. package/media/icons/wedo_motor-clockwise.svg +1 -0
  201. package/media/icons/wedo_motor-counterclockwise.svg +1 -0
  202. package/media/icons/wedo_motor-speed_fast.svg +1 -0
  203. package/media/icons/wedo_motor-speed_med.svg +1 -0
  204. package/media/icons/wedo_motor-speed_slow.svg +1 -0
  205. package/media/icons/wedo_when-distance_close.svg +1 -0
  206. package/media/icons/wedo_when-tilt-backward.svg +1 -0
  207. package/media/icons/wedo_when-tilt-forward.svg +1 -0
  208. package/media/icons/wedo_when-tilt-left.svg +1 -0
  209. package/media/icons/wedo_when-tilt-right.svg +1 -0
  210. package/media/icons/wedo_when-tilt.svg +1 -0
  211. package/media/repeat.svg +18 -0
  212. package/media/rotate-left.svg +1 -0
  213. package/media/rotate-right.svg +1 -0
  214. package/media/sprites.png +0 -0
  215. package/media/status-not-ready.svg +13 -0
  216. package/media/status-ready.svg +13 -0
  217. package/media/zoom-in.svg +1 -0
  218. package/media/zoom-out.svg +1 -0
  219. package/media/zoom-reset.svg +1 -0
  220. package/msg/js/en.js +290 -0
  221. package/msg/json/en.json +285 -0
  222. package/msg/messages.js +359 -0
  223. package/msg/scratch_msgs.js +22969 -0
  224. package/package.json +60 -0
  225. package/pull_from_blockly.sh +151 -0
  226. package/renovate.json5 +15 -0
  227. package/shim/blockly_compressed_horizontal-blocks_compressed.js +1 -0
  228. package/shim/blockly_compressed_horizontal.Blockly.js +1 -0
  229. package/shim/blockly_compressed_horizontal.goog.js +1 -0
  230. package/shim/blockly_compressed_horizontal.js +1 -0
  231. package/shim/blockly_compressed_vertical-blocks_compressed.js +1 -0
  232. package/shim/blockly_compressed_vertical.Blockly.js +1 -0
  233. package/shim/blockly_compressed_vertical.goog.js +1 -0
  234. package/shim/blockly_compressed_vertical.js +1 -0
  235. package/shim/blocks_compressed_horizontal-blockly_compressed_horizontal-messages.js +1 -0
  236. package/shim/blocks_compressed_horizontal.js +1 -0
  237. package/shim/blocks_compressed_vertical-blockly_compressed_vertical-messages.js +1 -0
  238. package/shim/blocks_compressed_vertical.js +1 -0
  239. package/shim/gh-pages.js +1 -0
  240. package/shim/horizontal.js +1 -0
  241. package/shim/index.js +17 -0
  242. package/shim/vertical.js +1 -0
  243. package/universal-python.js +82 -0
@@ -0,0 +1,2382 @@
1
+ /**
2
+ * @license
3
+ * Visual Blocks Editor
4
+ *
5
+ * Copyright 2014 Google Inc.
6
+ * https://developers.google.com/blockly/
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ /**
22
+ * @fileoverview Object representing a workspace rendered as SVG.
23
+ * @author fraser@google.com (Neil Fraser)
24
+ */
25
+ 'use strict';
26
+
27
+ goog.provide('Blockly.WorkspaceSvg');
28
+
29
+ // TODO(scr): Fix circular dependencies
30
+ //goog.require('Blockly.BlockSvg');
31
+ goog.require('Blockly.Colours');
32
+ goog.require('Blockly.ConnectionDB');
33
+ goog.require('Blockly.constants');
34
+ goog.require('Blockly.DataCategory');
35
+ goog.require('Blockly.DropDownDiv');
36
+ goog.require('Blockly.Events.BlockCreate');
37
+ goog.require('Blockly.Gesture');
38
+ goog.require('Blockly.Grid');
39
+ goog.require('Blockly.Options');
40
+ goog.require('Blockly.scratchBlocksUtils');
41
+ goog.require('Blockly.ScrollbarPair');
42
+ goog.require('Blockly.Touch');
43
+ goog.require('Blockly.Trashcan');
44
+ //goog.require('Blockly.VerticalFlyout');
45
+ goog.require('Blockly.Workspace');
46
+ goog.require('Blockly.WorkspaceAudio');
47
+ goog.require('Blockly.WorkspaceComment');
48
+ goog.require('Blockly.WorkspaceCommentSvg');
49
+ goog.require('Blockly.WorkspaceCommentSvg.render');
50
+ goog.require('Blockly.WorkspaceDragSurfaceSvg');
51
+ goog.require('Blockly.Xml');
52
+ goog.require('Blockly.ZoomControls');
53
+ goog.require('Blockly.IntersectionObserver');
54
+
55
+ goog.require('goog.array');
56
+ goog.require('goog.dom');
57
+ goog.require('goog.math.Coordinate');
58
+ goog.require('goog.userAgent');
59
+ goog.require('goog.math.Rect');
60
+
61
+ /**
62
+ * Class for a workspace. This is an onscreen area with optional trashcan,
63
+ * scrollbars, bubbles, and dragging.
64
+ * @param {!Blockly.Options} options Dictionary of options.
65
+ * @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for
66
+ * blocks.
67
+ * @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
68
+ * the workspace.
69
+ * @extends {Blockly.Workspace}
70
+ * @constructor
71
+ */
72
+ Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface) {
73
+ Blockly.WorkspaceSvg.superClass_.constructor.call(this, options);
74
+ this.getMetrics =
75
+ options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_;
76
+ this.setMetrics =
77
+ options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_;
78
+
79
+ Blockly.ConnectionDB.init(this);
80
+
81
+ if (opt_blockDragSurface) {
82
+ this.blockDragSurface_ = opt_blockDragSurface;
83
+ }
84
+
85
+ if (opt_wsDragSurface) {
86
+ this.workspaceDragSurface_ = opt_wsDragSurface;
87
+ }
88
+
89
+ this.useWorkspaceDragSurface_ =
90
+ this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
91
+
92
+ /**
93
+ * List of currently highlighted blocks. Block highlighting is often used to
94
+ * visually mark blocks currently being executed.
95
+ * @type !Array.<!Blockly.BlockSvg>
96
+ * @private
97
+ */
98
+ this.highlightedBlocks_ = [];
99
+
100
+ /**
101
+ * Object in charge of loading, storing, and playing audio for a workspace.
102
+ * @type {Blockly.WorkspaceAudio}
103
+ * @private
104
+ */
105
+ this.audioManager_ = new Blockly.WorkspaceAudio(options.parentWorkspace);
106
+
107
+ /**
108
+ * This workspace's grid object or null.
109
+ * @type {Blockly.Grid}
110
+ * @private
111
+ */
112
+ this.grid_ = this.options.gridPattern ?
113
+ new Blockly.Grid(options.gridPattern, options.gridOptions) : null;
114
+
115
+ this.registerToolboxCategoryCallback(Blockly.VARIABLE_CATEGORY_NAME,
116
+ Blockly.DataCategory);
117
+ this.registerToolboxCategoryCallback(Blockly.PROCEDURE_CATEGORY_NAME,
118
+ Blockly.Procedures.flyoutCategory);
119
+
120
+ this.procedureReturnsEnabled = Blockly.Procedures.DEFAULT_ENABLE_RETURNS;
121
+ this.initialProcedureReturnTypes_ = null;
122
+ this.procedureReturnChangeTimeout_ = null;
123
+ this.checkProcedureReturnAfterGesture_ = false;
124
+ };
125
+ goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace);
126
+
127
+ /**
128
+ * A wrapper function called when a resize event occurs.
129
+ * You can pass the result to `unbindEvent_`.
130
+ * @type {Array.<!Array>}
131
+ */
132
+ Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null;
133
+
134
+ /**
135
+ * The render status of an SVG workspace.
136
+ * Returns `false` for headless workspaces and true for instances of
137
+ * `Blockly.WorkspaceSvg`.
138
+ * @type {boolean}
139
+ */
140
+ Blockly.WorkspaceSvg.prototype.rendered = true;
141
+
142
+ /**
143
+ * Whether the workspace is visible. False if the workspace has been hidden
144
+ * by calling `setVisible(false)`.
145
+ * @type {boolean}
146
+ * @private
147
+ */
148
+ Blockly.WorkspaceSvg.prototype.isVisible_ = true;
149
+
150
+ /**
151
+ * Is this workspace the surface for a flyout?
152
+ * @type {boolean}
153
+ */
154
+ Blockly.WorkspaceSvg.prototype.isFlyout = false;
155
+
156
+ /**
157
+ * Is this workspace the surface for a mutator?
158
+ * @type {boolean}
159
+ * @package
160
+ */
161
+ Blockly.WorkspaceSvg.prototype.isMutator = false;
162
+
163
+ /**
164
+ * Whether this workspace has resizes enabled.
165
+ * Disable during batch operations for a performance improvement.
166
+ * @type {boolean}
167
+ * @private
168
+ */
169
+ Blockly.WorkspaceSvg.prototype.resizesEnabled_ = true;
170
+
171
+ /**
172
+ * Whether this workspace has toolbox/flyout refreshes enabled.
173
+ * Disable during batch operations for a performance improvement.
174
+ * @type {boolean}
175
+ * @private
176
+ */
177
+ Blockly.WorkspaceSvg.prototype.toolboxRefreshEnabled_ = true;
178
+
179
+ /**
180
+ * Current horizontal scrolling offset in pixel units.
181
+ * @type {number}
182
+ */
183
+ Blockly.WorkspaceSvg.prototype.scrollX = 0;
184
+
185
+ /**
186
+ * Current vertical scrolling offset in pixel units.
187
+ * @type {number}
188
+ */
189
+ Blockly.WorkspaceSvg.prototype.scrollY = 0;
190
+
191
+ /**
192
+ * Horizontal scroll value when scrolling started in pixel units.
193
+ * @type {number}
194
+ */
195
+ Blockly.WorkspaceSvg.prototype.startScrollX = 0;
196
+
197
+ /**
198
+ * Vertical scroll value when scrolling started in pixel units.
199
+ * @type {number}
200
+ */
201
+ Blockly.WorkspaceSvg.prototype.startScrollY = 0;
202
+
203
+ /**
204
+ * Distance from mouse to object being dragged.
205
+ * @type {goog.math.Coordinate}
206
+ * @private
207
+ */
208
+ Blockly.WorkspaceSvg.prototype.dragDeltaXY_ = null;
209
+
210
+ /**
211
+ * Current scale.
212
+ * @type {number}
213
+ */
214
+ Blockly.WorkspaceSvg.prototype.scale = 1;
215
+
216
+ /**
217
+ * The workspace's trashcan (if any).
218
+ * @type {Blockly.Trashcan}
219
+ */
220
+ Blockly.WorkspaceSvg.prototype.trashcan = null;
221
+
222
+ /**
223
+ * This workspace's scrollbars, if they exist.
224
+ * @type {Blockly.ScrollbarPair}
225
+ */
226
+ Blockly.WorkspaceSvg.prototype.scrollbar = null;
227
+
228
+ /**
229
+ * The current gesture in progress on this workspace, if any.
230
+ * @type {Blockly.Gesture}
231
+ * @private
232
+ */
233
+ Blockly.WorkspaceSvg.prototype.currentGesture_ = null;
234
+
235
+ /**
236
+ * This workspace's surface for dragging blocks, if it exists.
237
+ * @type {Blockly.BlockDragSurfaceSvg}
238
+ * @private
239
+ */
240
+ Blockly.WorkspaceSvg.prototype.blockDragSurface_ = null;
241
+
242
+ /**
243
+ * This workspace's drag surface, if it exists.
244
+ * @type {Blockly.WorkspaceDragSurfaceSvg}
245
+ * @private
246
+ */
247
+ Blockly.WorkspaceSvg.prototype.workspaceDragSurface_ = null;
248
+
249
+ /**
250
+ * Whether to move workspace to the drag surface when it is dragged.
251
+ * True if it should move, false if it should be translated directly.
252
+ * @type {boolean}
253
+ * @private
254
+ */
255
+ Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false;
256
+
257
+ /**
258
+ * Whether the drag surface is actively in use. When true, calls to
259
+ * translate will translate the drag surface instead of the translating the
260
+ * workspace directly.
261
+ * This is set to true in setupDragSurface and to false in resetDragSurface.
262
+ * @type {boolean}
263
+ * @private
264
+ */
265
+ Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false;
266
+
267
+ /**
268
+ * The first parent div with 'injectionDiv' in the name, or null if not set.
269
+ * Access this with getInjectionDiv.
270
+ * @type {!Element}
271
+ * @private
272
+ */
273
+ Blockly.WorkspaceSvg.prototype.injectionDiv_ = null;
274
+
275
+ /**
276
+ * Last known position of the page scroll.
277
+ * This is used to determine whether we have recalculated screen coordinate
278
+ * stuff since the page scrolled.
279
+ * @type {!goog.math.Coordinate}
280
+ * @private
281
+ */
282
+ Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null;
283
+
284
+ /**
285
+ * Map from function names to callbacks, for deciding what to do when a button
286
+ * is clicked.
287
+ * @type {!Object.<string, function(!Blockly.FlyoutButton)>}
288
+ * @private
289
+ */
290
+ Blockly.WorkspaceSvg.prototype.flyoutButtonCallbacks_ = {};
291
+
292
+ /**
293
+ * Map from function names to callbacks, for deciding what to do when a custom
294
+ * toolbox category is opened.
295
+ * @type {!Object.<string, function(!Blockly.Workspace):!Array.<!Element>>}
296
+ * @private
297
+ */
298
+ Blockly.WorkspaceSvg.prototype.toolboxCategoryCallbacks_ = {};
299
+
300
+ /**
301
+ * Inverted screen CTM, for use in mouseToSvg.
302
+ * @type {SVGMatrix}
303
+ * @private
304
+ */
305
+ Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null;
306
+
307
+ /**
308
+ * Inverted screen CTM is dirty.
309
+ * @type {Boolean}
310
+ * @private
311
+ */
312
+ Blockly.WorkspaceSvg.prototype.inverseScreenCTMDirty_ = true;
313
+
314
+ /**
315
+ * Getter for the inverted screen CTM.
316
+ * @return {SVGMatrix} The matrix to use in mouseToSvg
317
+ */
318
+ Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() {
319
+
320
+ // Defer getting the screen CTM until we actually need it, this should
321
+ // avoid forced reflows from any calls to updateInverseScreenCTM.
322
+ if (this.inverseScreenCTMDirty_) {
323
+ var ctm = this.getParentSvg().getScreenCTM();
324
+ if (ctm) {
325
+ this.inverseScreenCTM_ = ctm.inverse();
326
+ this.inverseScreenCTMDirty_ = false;
327
+ }
328
+ }
329
+
330
+ return this.inverseScreenCTM_;
331
+ };
332
+
333
+ /**
334
+ * Getter for isVisible
335
+ * @return {boolean} Whether the workspace is visible. False if the workspace has been hidden
336
+ * by calling `setVisible(false)`.
337
+ */
338
+ Blockly.WorkspaceSvg.prototype.isVisible = function() {
339
+ return this.isVisible_;
340
+ };
341
+
342
+ /**
343
+ * Mark the inverse screen CTM as dirty.
344
+ */
345
+ Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() {
346
+ this.inverseScreenCTMDirty_ = true;
347
+ };
348
+
349
+ /**
350
+ * Return the absolute coordinates of the top-left corner of this element,
351
+ * scales that after canvas SVG element, if it's a descendant.
352
+ * The origin (0,0) is the top-left corner of the Blockly SVG.
353
+ * @param {!Element} element Element to find the coordinates of.
354
+ * @return {!goog.math.Coordinate} Object with .x and .y properties.
355
+ * @private
356
+ */
357
+ Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) {
358
+ var x = 0;
359
+ var y = 0;
360
+ var scale = 1;
361
+ if (goog.dom.contains(this.getCanvas(), element) ||
362
+ goog.dom.contains(this.getBubbleCanvas(), element)) {
363
+ // Before the SVG canvas, scale the coordinates.
364
+ scale = this.scale;
365
+ }
366
+ do {
367
+ // Loop through this block and every parent.
368
+ var xy = Blockly.utils.getRelativeXY(element);
369
+ if (element == this.getCanvas() ||
370
+ element == this.getBubbleCanvas()) {
371
+ // After the SVG canvas, don't scale the coordinates.
372
+ scale = 1;
373
+ }
374
+ x += xy.x * scale;
375
+ y += xy.y * scale;
376
+ element = element.parentNode;
377
+ } while (element && element != this.getParentSvg());
378
+ return new goog.math.Coordinate(x, y);
379
+ };
380
+
381
+ /**
382
+ * Return the position of the workspace origin relative to the injection div
383
+ * origin in pixels.
384
+ * The workspace origin is where a block would render at position (0, 0).
385
+ * It is not the upper left corner of the workspace SVG.
386
+ * @return {!goog.math.Coordinate} Offset in pixels.
387
+ * @package
388
+ */
389
+ Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() {
390
+ return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_);
391
+ };
392
+
393
+ /**
394
+ * Return the injection div that is a parent of this workspace.
395
+ * Walks the DOM the first time it's called, then returns a cached value.
396
+ * @return {!Element} The first parent div with 'injectionDiv' in the name.
397
+ * @package
398
+ */
399
+ Blockly.WorkspaceSvg.prototype.getInjectionDiv = function() {
400
+ // NB: it would be better to pass this in at createDom, but is more likely to
401
+ // break existing uses of Blockly.
402
+ if (!this.injectionDiv_) {
403
+ var element = this.svgGroup_;
404
+ while (element) {
405
+ var classes = element.getAttribute('class') || '';
406
+ if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) {
407
+ this.injectionDiv_ = element;
408
+ break;
409
+ }
410
+ element = element.parentNode;
411
+ }
412
+ }
413
+ return this.injectionDiv_;
414
+ };
415
+
416
+ /**
417
+ * Save resize handler data so we can delete it later in dispose.
418
+ * @param {!Array.<!Array>} handler Data that can be passed to unbindEvent_.
419
+ */
420
+ Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) {
421
+ this.resizeHandlerWrapper_ = handler;
422
+ };
423
+
424
+ /**
425
+ * Create the workspace DOM elements.
426
+ * @param {string=} opt_backgroundClass Either 'blocklyMainBackground' or
427
+ * 'blocklyMutatorBackground'.
428
+ * @return {!Element} The workspace's SVG group.
429
+ */
430
+ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
431
+ /**
432
+ * <g class="blocklyWorkspace">
433
+ * <rect class="blocklyMainBackground" height="100%" width="100%"></rect>
434
+ * [Trashcan and/or flyout may go here]
435
+ * <g class="blocklyBlockCanvas"></g>
436
+ * <g class="blocklyBubbleCanvas"></g>
437
+ * </g>
438
+ * @type {SVGElement}
439
+ */
440
+ this.svgGroup_ = Blockly.utils.createSvgElement('g',
441
+ {'class': 'blocklyWorkspace'}, null);
442
+
443
+ // Note that a <g> alone does not receive mouse events--it must have a
444
+ // valid target inside it. If no background class is specified, as in the
445
+ // flyout, the workspace will not receive mouse events.
446
+ if (opt_backgroundClass) {
447
+ /** @type {SVGElement} */
448
+ this.svgBackground_ = Blockly.utils.createSvgElement('rect',
449
+ {'height': '100%', 'width': '100%', 'class': opt_backgroundClass},
450
+ this.svgGroup_);
451
+
452
+ if (opt_backgroundClass == 'blocklyMainBackground' && this.grid_) {
453
+ this.svgBackground_.style.fill =
454
+ 'url(#' + this.grid_.getPatternId() + ')';
455
+ }
456
+ }
457
+ /** @type {SVGElement} */
458
+ this.svgBlockCanvas_ = Blockly.utils.createSvgElement('g',
459
+ {'class': 'blocklyBlockCanvas'}, this.svgGroup_, this);
460
+ /** @type {SVGElement} */
461
+ this.svgBubbleCanvas_ = Blockly.utils.createSvgElement('g',
462
+ {'class': 'blocklyBubbleCanvas'}, this.svgGroup_, this);
463
+ var bottom = Blockly.Scrollbar.scrollbarThickness;
464
+ if (this.options.hasTrashcan) {
465
+ bottom = this.addTrashcan_(bottom);
466
+ }
467
+ if (this.options.zoomOptions && this.options.zoomOptions.controls) {
468
+ this.addZoomControls_(bottom);
469
+ }
470
+
471
+ if (!this.isFlyout) {
472
+ Blockly.bindEventWithChecks_(this.svgGroup_, 'mousedown', this,
473
+ this.onMouseDown_);
474
+ if (this.options.zoomOptions && this.options.zoomOptions.wheel) {
475
+ // Mouse-wheel.
476
+ Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this,
477
+ this.onMouseWheel_);
478
+ }
479
+ }
480
+
481
+ this.intersectionObserver = new Blockly.IntersectionObserver(this);
482
+
483
+ // Determine if there needs to be a category tree, or a simple list of
484
+ // blocks. This cannot be changed later, since the UI is very different.
485
+ if (this.options.hasCategories) {
486
+ /**
487
+ * @type {Blockly.Toolbox}
488
+ * @private
489
+ */
490
+ this.toolbox_ = new Blockly.Toolbox(this);
491
+ }
492
+ if (this.grid_) {
493
+ this.grid_.update(this.scale);
494
+ }
495
+ this.recordCachedAreas();
496
+ return this.svgGroup_;
497
+ };
498
+
499
+ /**
500
+ * Dispose of this workspace.
501
+ * Unlink from all DOM elements to prevent memory leaks.
502
+ */
503
+ Blockly.WorkspaceSvg.prototype.dispose = function() {
504
+ // Stop rerendering.
505
+ this.rendered = false;
506
+ if (this.currentGesture_) {
507
+ this.currentGesture_.cancel();
508
+ }
509
+ if (this.intersectionObserver) {
510
+ this.intersectionObserver.dispose();
511
+ this.intersectionObserver = null;
512
+ }
513
+ Blockly.WorkspaceSvg.superClass_.dispose.call(this);
514
+ if (this.svgGroup_) {
515
+ goog.dom.removeNode(this.svgGroup_);
516
+ this.svgGroup_ = null;
517
+ }
518
+ this.svgBlockCanvas_ = null;
519
+ this.svgBubbleCanvas_ = null;
520
+ if (this.toolbox_) {
521
+ this.toolbox_.dispose();
522
+ this.toolbox_ = null;
523
+ }
524
+ if (this.flyout_) {
525
+ this.flyout_.dispose();
526
+ this.flyout_ = null;
527
+ }
528
+ if (this.trashcan) {
529
+ this.trashcan.dispose();
530
+ this.trashcan = null;
531
+ }
532
+ if (this.scrollbar) {
533
+ this.scrollbar.dispose();
534
+ this.scrollbar = null;
535
+ }
536
+ if (this.zoomControls_) {
537
+ this.zoomControls_.dispose();
538
+ this.zoomControls_ = null;
539
+ }
540
+
541
+ if (this.audioManager_) {
542
+ this.audioManager_.dispose();
543
+ this.audioManager_ = null;
544
+ }
545
+
546
+ if (this.grid_) {
547
+ this.grid_.dispose();
548
+ this.grid_ = null;
549
+ }
550
+
551
+ if (this.toolboxCategoryCallbacks_) {
552
+ this.toolboxCategoryCallbacks_ = null;
553
+ }
554
+ if (this.flyoutButtonCallbacks_) {
555
+ this.flyoutButtonCallbacks_ = null;
556
+ }
557
+ if (!this.options.parentWorkspace) {
558
+ // Top-most workspace. Dispose of the div that the
559
+ // SVG is injected into (i.e. injectionDiv).
560
+ goog.dom.removeNode(this.getParentSvg().parentNode);
561
+ }
562
+ if (this.resizeHandlerWrapper_) {
563
+ Blockly.unbindEvent_(this.resizeHandlerWrapper_);
564
+ this.resizeHandlerWrapper_ = null;
565
+ }
566
+ if (this.procedureReturnChangeTimeout_) {
567
+ clearTimeout(this.procedureReturnChangeTimeout_);
568
+ }
569
+ };
570
+
571
+ /**
572
+ * Obtain a newly created block.
573
+ * @param {?string} prototypeName Name of the language object containing
574
+ * type-specific functions for this block.
575
+ * @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
576
+ * create a new ID.
577
+ * @return {!Blockly.BlockSvg} The created block.
578
+ */
579
+ Blockly.WorkspaceSvg.prototype.newBlock = function(prototypeName, opt_id) {
580
+ return new Blockly.BlockSvg(this, prototypeName, opt_id);
581
+ };
582
+
583
+ /**
584
+ * Add a trashcan.
585
+ * @param {number} bottom Distance from workspace bottom to bottom of trashcan.
586
+ * @return {number} Distance from workspace bottom to the top of trashcan.
587
+ * @private
588
+ */
589
+ Blockly.WorkspaceSvg.prototype.addTrashcan_ = function(bottom) {
590
+ /** @type {Blockly.Trashcan} */
591
+ this.trashcan = new Blockly.Trashcan(this);
592
+ var svgTrashcan = this.trashcan.createDom();
593
+ this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_);
594
+ return this.trashcan.init(bottom);
595
+ };
596
+
597
+ /**
598
+ * Add zoom controls.
599
+ * @param {number} bottom Distance from workspace bottom to bottom of controls.
600
+ * @return {number} Distance from workspace bottom to the top of controls.
601
+ * @private
602
+ */
603
+ Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) {
604
+ /** @type {Blockly.ZoomControls} */
605
+ this.zoomControls_ = new Blockly.ZoomControls(this);
606
+ var svgZoomControls = this.zoomControls_.createDom();
607
+ this.svgGroup_.appendChild(svgZoomControls);
608
+ return this.zoomControls_.init(bottom);
609
+ };
610
+
611
+ /**
612
+ * Add a flyout element in an element with the given tag name.
613
+ * @param {string} tagName What type of tag the flyout belongs in.
614
+ * @return {!Element} The element containing the flyout DOM.
615
+ * @private
616
+ */
617
+ Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) {
618
+ var workspaceOptions = {
619
+ disabledPatternId: this.options.disabledPatternId,
620
+ parentWorkspace: this,
621
+ RTL: this.RTL,
622
+ oneBasedIndex: this.options.oneBasedIndex,
623
+ horizontalLayout: this.horizontalLayout,
624
+ toolboxPosition: this.options.toolboxPosition,
625
+ stackGlowFilterId: this.options.stackGlowFilterId
626
+ };
627
+ if (this.horizontalLayout) {
628
+ this.flyout_ = new Blockly.HorizontalFlyout(workspaceOptions);
629
+ } else {
630
+ this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
631
+ }
632
+ this.flyout_.autoClose = false;
633
+
634
+ // Return the element so that callers can place it in their desired
635
+ // spot in the DOM. For example, mutator flyouts do not go in the same place
636
+ // as main workspace flyouts.
637
+ return this.flyout_.createDom(tagName);
638
+ };
639
+
640
+ /**
641
+ * Getter for the flyout associated with this workspace. This flyout may be
642
+ * owned by either the toolbox or the workspace, depending on toolbox
643
+ * configuration. It will be null if there is no flyout.
644
+ * @return {Blockly.Flyout} The flyout on this workspace.
645
+ * @package
646
+ */
647
+ Blockly.WorkspaceSvg.prototype.getFlyout = function() {
648
+ if (this.flyout_) {
649
+ return this.flyout_;
650
+ }
651
+ if (this.toolbox_) {
652
+ return this.toolbox_.flyout_;
653
+ }
654
+ return null;
655
+ };
656
+
657
+ /**
658
+ * Getter for the toolbox associated with this workspace, if one exists.
659
+ * @return {Blockly.Toolbox} The toolbox on this workspace.
660
+ * @package
661
+ */
662
+ Blockly.WorkspaceSvg.prototype.getToolbox = function() {
663
+ return this.toolbox_;
664
+ };
665
+
666
+ /**
667
+ * Update items that use screen coordinate calculations
668
+ * because something has changed (e.g. scroll position, window size).
669
+ * @private
670
+ */
671
+ Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() {
672
+ this.updateInverseScreenCTM();
673
+ this.recordCachedAreas();
674
+ };
675
+
676
+ /**
677
+ * If enabled, resize the parts of the workspace that change when the workspace
678
+ * contents (e.g. block positions) change. This will also scroll the
679
+ * workspace contents if needed.
680
+ * @package
681
+ */
682
+ Blockly.WorkspaceSvg.prototype.resizeContents = function() {
683
+ if (!this.resizesEnabled_ || !this.rendered) {
684
+ return;
685
+ }
686
+ if (this.scrollbar) {
687
+ // TODO(picklesrus): Once rachel-fenichel's scrollbar refactoring
688
+ // is complete, call the method that only resizes scrollbar
689
+ // based on contents.
690
+ this.scrollbar.resize();
691
+ }
692
+ this.updateInverseScreenCTM();
693
+ };
694
+
695
+ Blockly.WorkspaceSvg.prototype.queueIntersectionCheck = function() {
696
+ if (this.intersectionObserver) {
697
+ this.intersectionObserver.queueIntersectionCheck();
698
+ }
699
+ };
700
+
701
+ /**
702
+ * Call *before* modifying scripts.
703
+ */
704
+ Blockly.WorkspaceSvg.prototype.procedureReturnsWillChange = function() {
705
+ if (this.initialProcedureReturnTypes_) {
706
+ // Already queued.
707
+ return;
708
+ }
709
+
710
+ this.initialProcedureReturnTypes_ = Blockly.Procedures.getAllProcedureReturnTypes(this);
711
+
712
+ if (this.currentGesture_) {
713
+ this.checkProcedureReturnAfterGesture_ = true;
714
+ } else {
715
+ this.procedureReturnChangeTimeout_ = setTimeout(this.processProcedureReturnsChanged_.bind(this));
716
+ }
717
+ };
718
+
719
+ /**
720
+ * @private
721
+ */
722
+ Blockly.WorkspaceSvg.prototype.processProcedureReturnsChanged_ = function() {
723
+ var initialTypes = this.initialProcedureReturnTypes_;
724
+ var finalTypes = Blockly.Procedures.getAllProcedureReturnTypes(this);
725
+
726
+ this.initialProcedureReturnTypes_ = null;
727
+ this.checkProcedureReturnAfterGesture_ = false;
728
+ this.procedureReturnChangeTimeout_ = null;
729
+
730
+ Blockly.Events.setGroup(true);
731
+ var topBlocks = this.getTopBlocks(false);
732
+ for (var i = 0; i < topBlocks.length; i++) {
733
+ var block = topBlocks[i];
734
+ if (block.type !== Blockly.PROCEDURES_CALL_BLOCK_TYPE) continue;
735
+
736
+ // After a gesture, we are called early enough that there could still be insertion markers.
737
+ if (block.isInsertionMarker()) continue;
738
+
739
+ // Because this block is a top block, it by definition won't have a parent, but if another
740
+ // block is connected below, we should leave it unchanged instead of unplugging.
741
+ if (block.getNextBlock()) continue;
742
+
743
+ var procCode = block.getProcCode();
744
+ // If the procedure doesn't exist or is new, ignore it.
745
+ if (
746
+ !Object.prototype.hasOwnProperty.call(initialTypes, procCode) ||
747
+ !Object.prototype.hasOwnProperty.call(finalTypes, procCode)
748
+ ) continue;
749
+
750
+ var actualReturnType = finalTypes[procCode];
751
+ if (
752
+ block.getReturn() !== actualReturnType &&
753
+ // If user is allowed to override call block shape, only update the shape if the definition's
754
+ // shape has actually changed.
755
+ (!Blockly.Procedures.USER_CAN_CHANGE_CALL_TYPE || initialTypes[procCode] !== actualReturnType)
756
+ ) {
757
+ Blockly.Procedures.changeReturnType(block, actualReturnType);
758
+ }
759
+ }
760
+ Blockly.Events.setGroup(false);
761
+
762
+ // Toolbox refresh can be slow, so only do when needed.
763
+ var toolboxOutdated = false;
764
+ for (var procCode in finalTypes) {
765
+ // If a current procedure existed but its type has changed, the toolbox must be updated.
766
+ // If a new procedure was created, the toolbox is already updated elsewhere.
767
+ if (
768
+ Object.prototype.hasOwnProperty.call(initialTypes, procCode) &&
769
+ initialTypes[procCode] !== finalTypes[procCode]
770
+ ) {
771
+ toolboxOutdated = true;
772
+ break;
773
+ }
774
+ }
775
+ if (toolboxOutdated) {
776
+ this.refreshToolboxSelection_();
777
+ }
778
+ };
779
+
780
+ /**
781
+ * Does not refresh toolbox.
782
+ */
783
+ Blockly.WorkspaceSvg.prototype.enableProcedureReturns = function() {
784
+ this.procedureReturnsEnabled = true;
785
+ };
786
+
787
+ /**
788
+ * Resize and reposition all of the workspace chrome (toolbox,
789
+ * trash, scrollbars etc.)
790
+ * This should be called when something changes that
791
+ * requires recalculating dimensions and positions of the
792
+ * trash, zoom, toolbox, etc. (e.g. window resize).
793
+ */
794
+ Blockly.WorkspaceSvg.prototype.resize = function() {
795
+ if (this.toolbox_) {
796
+ this.toolbox_.position();
797
+ }
798
+ if (this.flyout_) {
799
+ this.flyout_.position();
800
+ }
801
+ if (this.trashcan) {
802
+ this.trashcan.position();
803
+ }
804
+ if (this.zoomControls_) {
805
+ this.zoomControls_.position();
806
+ }
807
+ if (this.scrollbar) {
808
+ this.scrollbar.resize();
809
+ }
810
+ this.updateScreenCalculations_();
811
+ this.queueIntersectionCheck();
812
+ };
813
+
814
+ /**
815
+ * Resizes and repositions workspace chrome if the page has a new
816
+ * scroll position.
817
+ * @package
818
+ */
819
+ Blockly.WorkspaceSvg.prototype.updateScreenCalculationsIfScrolled
820
+ = function() {
821
+ /* eslint-disable indent */
822
+ var currScroll = goog.dom.getDocumentScroll();
823
+ if (!goog.math.Coordinate.equals(this.lastRecordedPageScroll_,
824
+ currScroll)) {
825
+ this.lastRecordedPageScroll_ = currScroll;
826
+ this.updateScreenCalculations_();
827
+ }
828
+ }; /* eslint-enable indent */
829
+
830
+ /**
831
+ * Get the SVG element that forms the drawing surface.
832
+ * @return {!Element} SVG element.
833
+ */
834
+ Blockly.WorkspaceSvg.prototype.getCanvas = function() {
835
+ return this.svgBlockCanvas_;
836
+ };
837
+
838
+ /**
839
+ * Get the SVG element that forms the bubble surface.
840
+ * @return {!SVGGElement} SVG element.
841
+ */
842
+ Blockly.WorkspaceSvg.prototype.getBubbleCanvas = function() {
843
+ return this.svgBubbleCanvas_;
844
+ };
845
+
846
+ /**
847
+ * Get the SVG element that contains this workspace.
848
+ * @return {!Element} SVG element.
849
+ */
850
+ Blockly.WorkspaceSvg.prototype.getParentSvg = function() {
851
+ if (this.cachedParentSvg_) {
852
+ return this.cachedParentSvg_;
853
+ }
854
+ var element = this.svgGroup_;
855
+ while (element) {
856
+ if (element.tagName == 'svg') {
857
+ this.cachedParentSvg_ = element;
858
+ return element;
859
+ }
860
+ element = element.parentNode;
861
+ }
862
+ return null;
863
+ };
864
+
865
+ /**
866
+ * Translate this workspace to new coordinates.
867
+ * @param {number} x Horizontal translation.
868
+ * @param {number} y Vertical translation.
869
+ */
870
+ Blockly.WorkspaceSvg.prototype.translate = function(x, y) {
871
+ if (this.useWorkspaceDragSurface_ && this.isDragSurfaceActive_) {
872
+ this.workspaceDragSurface_.translateSurface(x,y);
873
+ } else {
874
+ var translation = 'translate(' + x + ',' + y + ') ' +
875
+ 'scale(' + this.scale + ')';
876
+ this.svgBlockCanvas_.setAttribute('transform', translation);
877
+ this.svgBubbleCanvas_.setAttribute('transform', translation);
878
+ }
879
+ // Now update the block drag surface if we're using one.
880
+ if (this.blockDragSurface_) {
881
+ this.blockDragSurface_.translateAndScaleGroup(x, y, this.scale);
882
+ }
883
+ this.queueIntersectionCheck();
884
+ };
885
+
886
+ /**
887
+ * Called at the end of a workspace drag to take the contents
888
+ * out of the drag surface and put them back into the workspace SVG.
889
+ * Does nothing if the workspace drag surface is not enabled.
890
+ * @package
891
+ */
892
+ Blockly.WorkspaceSvg.prototype.resetDragSurface = function() {
893
+ // Don't do anything if we aren't using a drag surface.
894
+ if (!this.useWorkspaceDragSurface_) {
895
+ return;
896
+ }
897
+
898
+ this.isDragSurfaceActive_ = false;
899
+
900
+ var trans = this.workspaceDragSurface_.getSurfaceTranslation();
901
+ this.workspaceDragSurface_.clearAndHide(this.svgGroup_);
902
+ var translation = 'translate(' + trans.x + ',' + trans.y + ') ' +
903
+ 'scale(' + this.scale + ')';
904
+ this.svgBlockCanvas_.setAttribute('transform', translation);
905
+ this.svgBubbleCanvas_.setAttribute('transform', translation);
906
+ };
907
+
908
+ /**
909
+ * Called at the beginning of a workspace drag to move contents of
910
+ * the workspace to the drag surface.
911
+ * Does nothing if the drag surface is not enabled.
912
+ * @package
913
+ */
914
+ Blockly.WorkspaceSvg.prototype.setupDragSurface = function() {
915
+ // Don't do anything if we aren't using a drag surface.
916
+ if (!this.useWorkspaceDragSurface_) {
917
+ return;
918
+ }
919
+
920
+ // This can happen if the user starts a drag, mouses up outside of the
921
+ // document where the mouseup listener is registered (e.g. outside of an
922
+ // iframe) and then moves the mouse back in the workspace. On mobile and ff,
923
+ // we get the mouseup outside the frame. On chrome and safari desktop we do
924
+ // not.
925
+ if (this.isDragSurfaceActive_) {
926
+ return;
927
+ }
928
+
929
+ this.isDragSurfaceActive_ = true;
930
+
931
+ // Figure out where we want to put the canvas back. The order
932
+ // in the is important because things are layered.
933
+ var previousElement = this.svgBlockCanvas_.previousSibling;
934
+ var width = parseInt(this.getParentSvg().getAttribute('width'), 10);
935
+ var height = parseInt(this.getParentSvg().getAttribute('height'), 10);
936
+ var coord = Blockly.utils.getRelativeXY(this.svgBlockCanvas_);
937
+ this.workspaceDragSurface_.setContentsAndShow(this.svgBlockCanvas_,
938
+ this.svgBubbleCanvas_, previousElement, width, height, this.scale);
939
+ this.workspaceDragSurface_.translateSurface(coord.x, coord.y);
940
+ };
941
+
942
+ /**
943
+ * @return {?Blockly.BlockDragSurfaceSvg} This workspace's block drag surface,
944
+ * if one is in use.
945
+ * @package
946
+ */
947
+ Blockly.WorkspaceSvg.prototype.getBlockDragSurface = function() {
948
+ return this.blockDragSurface_;
949
+ };
950
+
951
+ /**
952
+ * Returns the horizontal offset of the workspace.
953
+ * Intended for LTR/RTL compatibility in XML.
954
+ * @return {number} Width.
955
+ */
956
+ Blockly.WorkspaceSvg.prototype.getWidth = function() {
957
+ var metrics = this.getMetrics();
958
+ return metrics ? metrics.viewWidth / this.scale : 0;
959
+ };
960
+
961
+ /**
962
+ * Toggles the visibility of the workspace.
963
+ * Currently only intended for main workspace.
964
+ * @param {boolean} isVisible True if workspace should be visible.
965
+ */
966
+ Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) {
967
+
968
+ // Tell the scrollbar whether its container is visible so it can
969
+ // tell when to hide itself.
970
+ if (this.scrollbar) {
971
+ this.scrollbar.setContainerVisible(isVisible);
972
+ }
973
+
974
+ // Tell the flyout whether its container is visible so it can
975
+ // tell when to hide itself.
976
+ if (this.getFlyout()) {
977
+ this.getFlyout().setContainerVisible(isVisible);
978
+ }
979
+
980
+ this.getParentSvg().style.display = isVisible ? 'block' : 'none';
981
+ if (this.toolbox_) {
982
+ // Currently does not support toolboxes in mutators.
983
+ this.toolbox_.HtmlDiv.style.display = isVisible ? 'block' : 'none';
984
+ }
985
+ if (isVisible) {
986
+ this.render();
987
+ // The window may have changed size while the workspace was hidden.
988
+ // Resize recalculates scrollbar position, delete areas, etc.
989
+ this.resize();
990
+ } else {
991
+ Blockly.hideChaff(true);
992
+ Blockly.DropDownDiv.hideWithoutAnimation();
993
+ }
994
+ this.isVisible_ = isVisible;
995
+ };
996
+
997
+ /**
998
+ * Render all blocks in workspace.
999
+ */
1000
+ Blockly.WorkspaceSvg.prototype.render = function() {
1001
+ // Generate list of all blocks.
1002
+ var blocks = this.getAllBlocks();
1003
+ // Render each block.
1004
+ for (var i = blocks.length - 1; i >= 0; i--) {
1005
+ blocks[i].render(false);
1006
+ }
1007
+ };
1008
+
1009
+ /**
1010
+ * Was used back when block highlighting (for execution) and block selection
1011
+ * (for editing) were the same thing.
1012
+ * Any calls of this function can be deleted.
1013
+ * @deprecated October 2016
1014
+ */
1015
+ Blockly.WorkspaceSvg.prototype.traceOn = function() {
1016
+ console.warn('Deprecated call to traceOn, delete this.');
1017
+ };
1018
+
1019
+ /**
1020
+ * Highlight or unhighlight a block in the workspace. Block highlighting is
1021
+ * often used to visually mark blocks currently being executed.
1022
+ * @param {?string} id ID of block to highlight/unhighlight,
1023
+ * or null for no block (used to unhighlight all blocks).
1024
+ * @param {boolean=} opt_state If undefined, highlight specified block and
1025
+ * automatically unhighlight all others. If true or false, manually
1026
+ * highlight/unhighlight the specified block.
1027
+ */
1028
+ Blockly.WorkspaceSvg.prototype.highlightBlock = function(id, opt_state) {
1029
+ if (opt_state === undefined) {
1030
+ // Unhighlight all blocks.
1031
+ for (var i = 0, block; block = this.highlightedBlocks_[i]; i++) {
1032
+ block.setHighlighted(false);
1033
+ }
1034
+ this.highlightedBlocks_.length = 0;
1035
+ }
1036
+ // Highlight/unhighlight the specified block.
1037
+ var block = id ? this.getBlockById(id) : null;
1038
+ if (block) {
1039
+ var state = (opt_state === undefined) || opt_state;
1040
+ // Using Set here would be great, but at the cost of IE10 support.
1041
+ if (!state) {
1042
+ goog.array.remove(this.highlightedBlocks_, block);
1043
+ } else if (this.highlightedBlocks_.indexOf(block) == -1) {
1044
+ this.highlightedBlocks_.push(block);
1045
+ }
1046
+ block.setHighlighted(state);
1047
+ }
1048
+ };
1049
+
1050
+ /**
1051
+ * Glow/unglow a block in the workspace.
1052
+ * @param {?string} id ID of block to find.
1053
+ * @param {boolean} isGlowingBlock Whether to glow the block.
1054
+ */
1055
+ Blockly.WorkspaceSvg.prototype.glowBlock = function(id, isGlowingBlock) {
1056
+ var block = null;
1057
+ if (id) {
1058
+ block = this.getBlockById(id);
1059
+ if (!block) {
1060
+ throw 'Tried to glow block that does not exist.';
1061
+ }
1062
+ }
1063
+ block.setGlowBlock(isGlowingBlock);
1064
+ };
1065
+
1066
+ /**
1067
+ * Glow/unglow a stack in the workspace.
1068
+ * @param {?string} id ID of block which starts the stack.
1069
+ * @param {boolean} isGlowingStack Whether to glow the stack.
1070
+ */
1071
+ Blockly.WorkspaceSvg.prototype.glowStack = function(id, isGlowingStack) {
1072
+ var block = null;
1073
+ if (id) {
1074
+ block = this.getBlockById(id);
1075
+ if (!block) {
1076
+ throw 'Tried to glow stack on block that does not exist.';
1077
+ }
1078
+ }
1079
+ block.setGlowStack(isGlowingStack);
1080
+ };
1081
+
1082
+ /**
1083
+ * Visually report a value associated with a block.
1084
+ * In Scratch, appears as a pop-up next to the block when a reporter block is clicked.
1085
+ * @param {?string} id ID of block to report associated value.
1086
+ * @param {?string} value String value to visually report.
1087
+ */
1088
+ Blockly.WorkspaceSvg.prototype.reportValue = function(id, value) {
1089
+ var block = this.getBlockById(id);
1090
+ if (!block) {
1091
+ throw 'Tried to report value on block that does not exist.';
1092
+ }
1093
+ Blockly.DropDownDiv.hideWithoutAnimation();
1094
+ Blockly.DropDownDiv.clearContent();
1095
+ var contentDiv = Blockly.DropDownDiv.getContentDiv();
1096
+ var valueReportBox = goog.dom.createElement('div');
1097
+ valueReportBox.setAttribute('class', 'valueReportBox');
1098
+ valueReportBox.textContent = value;
1099
+ contentDiv.appendChild(valueReportBox);
1100
+ Blockly.DropDownDiv.setColour(
1101
+ Blockly.Colours.valueReportBackground,
1102
+ Blockly.Colours.valueReportBorder
1103
+ );
1104
+ Blockly.DropDownDiv.showPositionedByBlock(this, block);
1105
+ };
1106
+
1107
+ /**
1108
+ * Paste the provided block onto the workspace.
1109
+ * @param {!Element} xmlBlock XML block element.
1110
+ */
1111
+ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
1112
+ if (!this.rendered) {
1113
+ return;
1114
+ }
1115
+ if (this.currentGesture_) {
1116
+ this.currentGesture_.cancel(); // Dragging while pasting? No.
1117
+ }
1118
+ if (xmlBlock.tagName.toLowerCase() == 'comment') {
1119
+ this.pasteWorkspaceComment_(xmlBlock);
1120
+ } else {
1121
+ this.pasteBlock_(xmlBlock);
1122
+ }
1123
+ };
1124
+
1125
+ /**
1126
+ * Paste the provided block onto the workspace.
1127
+ * @param {!Element} xmlBlock XML block element.
1128
+ */
1129
+ Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) {
1130
+ Blockly.Events.disable();
1131
+ try {
1132
+ Blockly.scratchBlocksUtils.changeCopiedBlockIds(xmlBlock);
1133
+ var block = Blockly.Xml.domToBlock(xmlBlock, this);
1134
+ // Scratch-specific: Give shadow dom new IDs to prevent duplicating on paste
1135
+ Blockly.scratchBlocksUtils.changeObscuredShadowIds(block);
1136
+ // Move the duplicate to original position.
1137
+ var blockX = parseInt(xmlBlock.getAttribute('x'), 10);
1138
+ var blockY = parseInt(xmlBlock.getAttribute('y'), 10);
1139
+ if (!isNaN(blockX) && !isNaN(blockY)) {
1140
+ if (this.RTL) {
1141
+ blockX = -blockX;
1142
+ }
1143
+ // Offset block until not clobbering another block and not in connection
1144
+ // distance with neighbouring blocks.
1145
+ do {
1146
+ var collide = false;
1147
+ var allBlocks = this.getAllBlocks();
1148
+ for (var i = 0, otherBlock; otherBlock = allBlocks[i]; i++) {
1149
+ var otherXY = otherBlock.getRelativeToSurfaceXY();
1150
+ if (Math.abs(blockX - otherXY.x) <= 1 &&
1151
+ Math.abs(blockY - otherXY.y) <= 1) {
1152
+ collide = true;
1153
+ break;
1154
+ }
1155
+ }
1156
+ if (!collide) {
1157
+ // Check for blocks in snap range to any of its connections.
1158
+ var connections = block.getConnections_(false);
1159
+ for (var i = 0, connection; connection = connections[i]; i++) {
1160
+ var neighbour = connection.closest(Blockly.SNAP_RADIUS,
1161
+ new goog.math.Coordinate(blockX, blockY));
1162
+ if (neighbour.connection) {
1163
+ collide = true;
1164
+ break;
1165
+ }
1166
+ }
1167
+ }
1168
+ if (collide) {
1169
+ if (this.RTL) {
1170
+ blockX -= Blockly.SNAP_RADIUS;
1171
+ } else {
1172
+ blockX += Blockly.SNAP_RADIUS;
1173
+ }
1174
+ blockY += Blockly.SNAP_RADIUS * 2;
1175
+ }
1176
+ } while (collide);
1177
+ block.moveBy(blockX, blockY);
1178
+ }
1179
+ } finally {
1180
+ Blockly.Events.enable();
1181
+ }
1182
+ if (Blockly.Events.isEnabled() && !block.isShadow()) {
1183
+ Blockly.Events.fire(new Blockly.Events.BlockCreate(block));
1184
+ }
1185
+ block.select();
1186
+ };
1187
+
1188
+ /**
1189
+ * Paste the provided comment onto the workspace.
1190
+ * @param {!Element} xmlComment XML workspace comment element.
1191
+ * @private
1192
+ */
1193
+ Blockly.WorkspaceSvg.prototype.pasteWorkspaceComment_ = function(xmlComment) {
1194
+ Blockly.Events.disable();
1195
+ try {
1196
+ var comment = Blockly.WorkspaceCommentSvg.fromXml(xmlComment, this);
1197
+ // Move the duplicate to original position.
1198
+ var commentX = parseInt(xmlComment.getAttribute('x'), 10);
1199
+ var commentY = parseInt(xmlComment.getAttribute('y'), 10);
1200
+ if (!isNaN(commentX) && !isNaN(commentY)) {
1201
+ if (this.RTL) {
1202
+ commentX = -commentX;
1203
+ }
1204
+ // Offset workspace comment.
1205
+ // TODO: (github.com/google/blockly/issues/1719) properly offset comment
1206
+ // such that it's not interfereing with any blocks
1207
+ commentX += 50;
1208
+ commentY += 50;
1209
+ comment.moveBy(commentX, commentY);
1210
+ }
1211
+ } finally {
1212
+ Blockly.Events.enable();
1213
+ }
1214
+ if (Blockly.Events.isEnabled()) {
1215
+ Blockly.WorkspaceComment.fireCreateEvent(comment);
1216
+ }
1217
+ comment.select();
1218
+ };
1219
+
1220
+ /**
1221
+ * Refresh the toolbox unless there's a drag in progress.
1222
+ * @private
1223
+ */
1224
+ Blockly.WorkspaceSvg.prototype.refreshToolboxSelection_ = function() {
1225
+ // Updating the toolbox can be expensive. Don't do it when when it is
1226
+ // disabled.
1227
+ if (this.toolbox_) {
1228
+ if (this.toolbox_.flyout_ && !this.currentGesture_ &&
1229
+ this.toolboxRefreshEnabled_) {
1230
+ this.toolbox_.refreshSelection();
1231
+ }
1232
+ } else {
1233
+ var thisTarget = this.targetWorkspace;
1234
+ if (thisTarget && thisTarget.toolbox_ && thisTarget.toolbox_.flyout_ &&
1235
+ !thisTarget.currentGesture_ && thisTarget.toolboxRefreshEnabled_) {
1236
+ thisTarget.toolbox_.refreshSelection();
1237
+ }
1238
+ }
1239
+ };
1240
+
1241
+ /**
1242
+ * Rename a variable by updating its name in the variable map. Update the
1243
+ * flyout to show the renamed variable immediately.
1244
+ * @param {string} id ID of the variable to rename.
1245
+ * @param {string} newName New variable name.
1246
+ * @package
1247
+ */
1248
+ Blockly.WorkspaceSvg.prototype.renameVariableById = function(id, newName) {
1249
+ Blockly.WorkspaceSvg.superClass_.renameVariableById.call(this, id, newName);
1250
+ this.refreshToolboxSelection_();
1251
+ };
1252
+
1253
+ /**
1254
+ * Delete a variable by the passed in ID. Update the flyout to show
1255
+ * immediately that the variable is deleted.
1256
+ * @param {string} id ID of variable to delete.
1257
+ * @package
1258
+ */
1259
+ Blockly.WorkspaceSvg.prototype.deleteVariableById = function(id) {
1260
+ Blockly.WorkspaceSvg.superClass_.deleteVariableById.call(this, id);
1261
+ this.refreshToolboxSelection_();
1262
+ };
1263
+
1264
+ /**
1265
+ * Create a new variable with the given name. Update the flyout to show the new
1266
+ * variable immediately.
1267
+ * @param {string} name The new variable's name.
1268
+ * @param {string=} opt_type The type of the variable like 'int' or 'string'.
1269
+ * Does not need to be unique. Field_variable can filter variables based on
1270
+ * their type. This will default to '' which is a specific type.
1271
+ * @param {string=} opt_id The unique ID of the variable. This will default to
1272
+ * a UUID.
1273
+ * @param {boolean=} opt_isLocal Whether the variable is locally scoped.
1274
+ * @param {boolean=} opt_isCloud Whether the variable is a cloud variable.
1275
+ * @return {?Blockly.VariableModel} The newly created variable.
1276
+ * @package
1277
+ */
1278
+ Blockly.WorkspaceSvg.prototype.createVariable = function(name, opt_type, opt_id,
1279
+ opt_isLocal, opt_isCloud) {
1280
+ var variableInMap = (this.getVariable(name, opt_type) != null);
1281
+ var newVar = Blockly.WorkspaceSvg.superClass_.createVariable.call(
1282
+ this, name, opt_type, opt_id, opt_isLocal, opt_isCloud);
1283
+ // For performance reasons, only refresh the the toolbox for new variables.
1284
+ // Variables that already exist should already be there.
1285
+ if (!variableInMap && (opt_type != Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE)) {
1286
+ this.refreshToolboxSelection_();
1287
+ }
1288
+ return newVar;
1289
+ };
1290
+
1291
+ /**
1292
+ * Update cached areas for this workspace.
1293
+ */
1294
+ Blockly.WorkspaceSvg.prototype.recordCachedAreas = function() {
1295
+ this.recordBlocksArea_();
1296
+ this.recordDeleteAreas_();
1297
+ };
1298
+
1299
+ /**
1300
+ * Make a list of all the delete areas for this workspace.
1301
+ * @private
1302
+ */
1303
+ Blockly.WorkspaceSvg.prototype.recordDeleteAreas_ = function() {
1304
+ if (this.trashcan) {
1305
+ this.deleteAreaTrash_ = this.trashcan.getClientRect();
1306
+ } else {
1307
+ this.deleteAreaTrash_ = null;
1308
+ }
1309
+ if (this.flyout_) {
1310
+ this.deleteAreaToolbox_ = this.flyout_.getClientRect();
1311
+ } else if (this.toolbox_) {
1312
+ this.deleteAreaToolbox_ = this.toolbox_.getClientRect();
1313
+ } else {
1314
+ this.deleteAreaToolbox_ = null;
1315
+ }
1316
+ };
1317
+
1318
+ /**
1319
+ * Record where all of blocks GUI is on the screen
1320
+ * @private
1321
+ */
1322
+ Blockly.WorkspaceSvg.prototype.recordBlocksArea_ = function() {
1323
+ var parentSvg = this.getParentSvg();
1324
+ if (parentSvg) {
1325
+ var bounds = parentSvg.getBoundingClientRect();
1326
+ this.blocksArea_ = new goog.math.Rect(bounds.left, bounds.top, bounds.width, bounds.height);
1327
+ } else {
1328
+ this.blocksArea_ = null;
1329
+ }
1330
+ };
1331
+
1332
+ /**
1333
+ * Is the mouse event over a delete area (toolbox or non-closing flyout)?
1334
+ * @param {!Event} e Mouse move event.
1335
+ * @return {?number} Null if not over a delete area, or an enum representing
1336
+ * which delete area the event is over.
1337
+ */
1338
+ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
1339
+ var xy = new goog.math.Coordinate(e.clientX, e.clientY);
1340
+ if (this.deleteAreaTrash_ && this.deleteAreaTrash_.contains(xy)) {
1341
+ return Blockly.DELETE_AREA_TRASH;
1342
+ }
1343
+ if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) {
1344
+ return Blockly.DELETE_AREA_TOOLBOX;
1345
+ }
1346
+ return Blockly.DELETE_AREA_NONE;
1347
+ };
1348
+
1349
+ /**
1350
+ * Is the mouse event inside the blocks UI?
1351
+ * @param {!Event} e Mouse move event.
1352
+ * @return {boolean} True if event is within the bounds of the blocks UI or delete area
1353
+ */
1354
+ Blockly.WorkspaceSvg.prototype.isInsideBlocksArea = function(e) {
1355
+ var xy = new goog.math.Coordinate(e.clientX, e.clientY);
1356
+ if (this.isDeleteArea(e) || (this.blocksArea_ && this.blocksArea_.contains(xy))) {
1357
+ return true;
1358
+ }
1359
+ return false;
1360
+ };
1361
+
1362
+ /**
1363
+ * Handle a mouse-down on SVG drawing surface.
1364
+ * @param {!Event} e Mouse down event.
1365
+ * @private
1366
+ */
1367
+ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
1368
+ var gesture = this.getGesture(e);
1369
+ if (gesture) {
1370
+ gesture.handleWsStart(e, this);
1371
+ }
1372
+ };
1373
+
1374
+ /**
1375
+ * Start tracking a drag of an object on this workspace.
1376
+ * @param {!Event} e Mouse down event.
1377
+ * @param {!goog.math.Coordinate} xy Starting location of object.
1378
+ */
1379
+ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) {
1380
+ // Record the starting offset between the bubble's location and the mouse.
1381
+ var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
1382
+ this.getInverseScreenCTM());
1383
+ // Fix scale of mouse event.
1384
+ point.x /= this.scale;
1385
+ point.y /= this.scale;
1386
+ this.dragDeltaXY_ = goog.math.Coordinate.difference(xy, point);
1387
+ };
1388
+
1389
+ /**
1390
+ * Track a drag of an object on this workspace.
1391
+ * @param {!Event} e Mouse move event.
1392
+ * @return {!goog.math.Coordinate} New location of object.
1393
+ */
1394
+ Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
1395
+ var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
1396
+ this.getInverseScreenCTM());
1397
+ // Fix scale of mouse event.
1398
+ point.x /= this.scale;
1399
+ point.y /= this.scale;
1400
+ return goog.math.Coordinate.sum(this.dragDeltaXY_, point);
1401
+ };
1402
+
1403
+ /**
1404
+ * Is the user currently dragging a block or scrolling the flyout/workspace?
1405
+ * @return {boolean} True if currently dragging or scrolling.
1406
+ */
1407
+ Blockly.WorkspaceSvg.prototype.isDragging = function() {
1408
+ return this.currentGesture_ && this.currentGesture_.isDragging();
1409
+ };
1410
+
1411
+ /**
1412
+ * Is this workspace draggable and scrollable?
1413
+ * @return {boolean} True if this workspace may be dragged.
1414
+ */
1415
+ Blockly.WorkspaceSvg.prototype.isDraggable = function() {
1416
+ return !!this.scrollbar;
1417
+ };
1418
+
1419
+ /**
1420
+ * Handle a mouse-wheel on SVG drawing surface.
1421
+ * @param {!Event} e Mouse wheel event.
1422
+ * @private
1423
+ */
1424
+ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
1425
+ // TODO: Remove gesture cancellation and compensate for coordinate skew during
1426
+ // zoom.
1427
+ if (this.currentGesture_) {
1428
+ this.currentGesture_.cancel();
1429
+ }
1430
+
1431
+ // Multiplier variable, so that non-pixel-deltaModes are supported.
1432
+ // See LLK/scratch-blocks#1190.
1433
+ var multiplier = e.deltaMode === 0x1 ? Blockly.LINE_SCROLL_MULTIPLIER : 1;
1434
+
1435
+ if (e.ctrlKey) {
1436
+ // The vertical scroll distance that corresponds to a click of a zoom button.
1437
+ var PIXELS_PER_ZOOM_STEP = 50;
1438
+ var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP * multiplier;
1439
+ var position = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
1440
+ this.getInverseScreenCTM());
1441
+ this.zoom(position.x, position.y, delta);
1442
+ } else {
1443
+ // This is a regular mouse wheel event - scroll the workspace
1444
+ // First hide the WidgetDiv without animation
1445
+ // (mouse scroll makes field out of place with div)
1446
+ Blockly.WidgetDiv.hide(true);
1447
+ Blockly.DropDownDiv.hideWithoutAnimation();
1448
+
1449
+ var x = this.scrollX - e.deltaX * multiplier;
1450
+ var y = this.scrollY - e.deltaY * multiplier;
1451
+
1452
+ if (e.shiftKey && e.deltaX === 0) {
1453
+ // Scroll horizontally (based on vertical scroll delta)
1454
+ // This is needed as for some browser/system combinations which do not
1455
+ // set deltaX. See #1662.
1456
+ x = this.scrollX - e.deltaY * multiplier;
1457
+ y = this.scrollY; // Don't scroll vertically
1458
+ }
1459
+
1460
+ this.startDragMetrics = this.getMetrics();
1461
+ this.scroll(x, y);
1462
+ }
1463
+ e.preventDefault();
1464
+ };
1465
+
1466
+ /**
1467
+ * Calculate the bounding box for the blocks on the workspace.
1468
+ * Coordinate system: workspace coordinates.
1469
+ *
1470
+ * @return {Object} Contains the position and size of the bounding box
1471
+ * containing the blocks on the workspace.
1472
+ */
1473
+ Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() {
1474
+ var topBlocks = this.getTopBlocks(false);
1475
+ var topComments = this.getTopComments(false);
1476
+ var topElements = topBlocks.concat(topComments);
1477
+ // There are no blocks, return empty rectangle.
1478
+ if (!topElements.length) {
1479
+ return {x: 0, y: 0, width: 0, height: 0};
1480
+ }
1481
+
1482
+ // Initialize boundary using the first block.
1483
+ var boundary = topElements[0].getBoundingRectangle();
1484
+
1485
+ // Start at 1 since the 0th block was used for initialization
1486
+ for (var i = 1; i < topElements.length; i++) {
1487
+ var blockBoundary = topElements[i].getBoundingRectangle();
1488
+ if (blockBoundary.topLeft.x < boundary.topLeft.x) {
1489
+ boundary.topLeft.x = blockBoundary.topLeft.x;
1490
+ }
1491
+ if (blockBoundary.bottomRight.x > boundary.bottomRight.x) {
1492
+ boundary.bottomRight.x = blockBoundary.bottomRight.x;
1493
+ }
1494
+ if (blockBoundary.topLeft.y < boundary.topLeft.y) {
1495
+ boundary.topLeft.y = blockBoundary.topLeft.y;
1496
+ }
1497
+ if (blockBoundary.bottomRight.y > boundary.bottomRight.y) {
1498
+ boundary.bottomRight.y = blockBoundary.bottomRight.y;
1499
+ }
1500
+ }
1501
+ return {
1502
+ x: boundary.topLeft.x,
1503
+ y: boundary.topLeft.y,
1504
+ width: boundary.bottomRight.x - boundary.topLeft.x,
1505
+ height: boundary.bottomRight.y - boundary.topLeft.y
1506
+ };
1507
+ };
1508
+
1509
+ /**
1510
+ * Clean up the workspace by ordering all the blocks in a column.
1511
+ */
1512
+ Blockly.WorkspaceSvg.prototype.cleanUp = function() {
1513
+ this.setResizesEnabled(false);
1514
+ Blockly.Events.setGroup(true);
1515
+ var topBlocks = this.getTopBlocks(true);
1516
+ var cursorY = 0;
1517
+ for (var i = 0, block; block = topBlocks[i]; i++) {
1518
+ var xy = block.getRelativeToSurfaceXY();
1519
+ block.moveBy(-xy.x, cursorY - xy.y);
1520
+ block.snapToGrid();
1521
+ cursorY = block.getRelativeToSurfaceXY().y +
1522
+ block.getHeightWidth().height + Blockly.BlockSvg.MIN_BLOCK_Y;
1523
+ }
1524
+ Blockly.Events.setGroup(false);
1525
+ this.setResizesEnabled(true);
1526
+ };
1527
+
1528
+ /**
1529
+ * Show the context menu for the workspace.
1530
+ * @param {!Event} e Mouse event.
1531
+ * @private
1532
+ */
1533
+ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
1534
+ if (this.options.readOnly || this.isFlyout) {
1535
+ return;
1536
+ }
1537
+ var menuOptions = [];
1538
+ var topBlocks = this.getTopBlocks(true);
1539
+ var eventGroup = Blockly.utils.genUid();
1540
+ var ws = this;
1541
+
1542
+ // Options to undo/redo previous action.
1543
+ menuOptions.push(Blockly.ContextMenu.wsUndoOption(this));
1544
+ menuOptions.push(Blockly.ContextMenu.wsRedoOption(this));
1545
+
1546
+ // Option to clean up blocks.
1547
+ if (this.scrollbar) {
1548
+ menuOptions.push(
1549
+ Blockly.ContextMenu.wsCleanupOption(this,topBlocks.length));
1550
+ }
1551
+
1552
+ if (this.options.collapse) {
1553
+ var hasCollapsedBlocks = false;
1554
+ var hasExpandedBlocks = false;
1555
+ for (var i = 0; i < topBlocks.length; i++) {
1556
+ var block = topBlocks[i];
1557
+ while (block) {
1558
+ if (block.isCollapsed()) {
1559
+ hasCollapsedBlocks = true;
1560
+ } else {
1561
+ hasExpandedBlocks = true;
1562
+ }
1563
+ block = block.getNextBlock();
1564
+ }
1565
+ }
1566
+
1567
+ menuOptions.push(Blockly.ContextMenu.wsCollapseOption(hasExpandedBlocks,
1568
+ topBlocks));
1569
+
1570
+ menuOptions.push(Blockly.ContextMenu.wsExpandOption(hasCollapsedBlocks,
1571
+ topBlocks));
1572
+ }
1573
+
1574
+ // Option to add a workspace comment.
1575
+ if (this.options.comments) {
1576
+ menuOptions.push(Blockly.ContextMenu.workspaceCommentOption(ws, e));
1577
+ }
1578
+
1579
+ // Option to delete all blocks.
1580
+ // Count the number of blocks that are deletable.
1581
+ var deleteList = Blockly.WorkspaceSvg.buildDeleteList_(topBlocks);
1582
+ // Scratch-specific: don't count shadow blocks in delete count
1583
+ var deleteCount = 0;
1584
+ for (var i = 0; i < deleteList.length; i++) {
1585
+ if (!deleteList[i].isShadow()) {
1586
+ deleteCount++;
1587
+ }
1588
+ }
1589
+
1590
+ var DELAY = 10;
1591
+ function deleteNext() {
1592
+ Blockly.Events.setGroup(eventGroup);
1593
+ var block = deleteList.shift();
1594
+ if (block) {
1595
+ if (block.workspace) {
1596
+ block.dispose(false, true);
1597
+ setTimeout(deleteNext, DELAY);
1598
+ } else {
1599
+ deleteNext();
1600
+ }
1601
+ }
1602
+ Blockly.Events.setGroup(false);
1603
+ }
1604
+
1605
+ var deleteOption = {
1606
+ text: deleteCount == 1 ? Blockly.Msg.DELETE_BLOCK :
1607
+ Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(deleteCount)),
1608
+ enabled: deleteCount > 0,
1609
+ callback: function() {
1610
+ if (ws.currentGesture_) {
1611
+ ws.currentGesture_.cancel();
1612
+ }
1613
+ if (deleteCount < 2 ) {
1614
+ deleteNext();
1615
+ } else {
1616
+ Blockly.confirm(
1617
+ Blockly.Msg.DELETE_ALL_BLOCKS.replace('%1', String(deleteCount)),
1618
+ function(ok) {
1619
+ if (ok) {
1620
+ deleteNext();
1621
+ }
1622
+ });
1623
+ }
1624
+ }
1625
+ };
1626
+ menuOptions.push(deleteOption);
1627
+
1628
+ Blockly.ContextMenu.show(e, menuOptions, this.RTL);
1629
+ };
1630
+
1631
+ /**
1632
+ * Build a list of all deletable blocks that are reachable from the given
1633
+ * list of top blocks.
1634
+ * @param {!Array.<!Blockly.BlockSvg>} topBlocks The list of top blocks on the
1635
+ * workspace.
1636
+ * @return {!Array.<!Blockly.BlockSvg>} A list of deletable blocks on the
1637
+ * workspace.
1638
+ * @private
1639
+ */
1640
+ Blockly.WorkspaceSvg.buildDeleteList_ = function(topBlocks) {
1641
+ var deleteList = [];
1642
+ function addDeletableBlocks(block) {
1643
+ if (block.isDeletable()) {
1644
+ deleteList = deleteList.concat(block.getDescendants(false));
1645
+ } else {
1646
+ var children = block.getChildren();
1647
+ for (var i = 0; i < children.length; i++) {
1648
+ addDeletableBlocks(children[i]);
1649
+ }
1650
+ }
1651
+ }
1652
+ for (var i = 0; i < topBlocks.length; i++) {
1653
+ addDeletableBlocks(topBlocks[i]);
1654
+ }
1655
+ return deleteList;
1656
+ };
1657
+
1658
+ /**
1659
+ * Modify the block tree on the existing toolbox.
1660
+ * @param {Node|string} tree DOM tree of blocks, or text representation of same.
1661
+ */
1662
+ Blockly.WorkspaceSvg.prototype.updateToolbox = function(tree) {
1663
+ tree = Blockly.Options.parseToolboxTree(tree);
1664
+ if (!tree) {
1665
+ if (this.options.languageTree) {
1666
+ throw 'Can\'t nullify an existing toolbox.';
1667
+ }
1668
+ return; // No change (null to null).
1669
+ }
1670
+ if (!this.options.languageTree) {
1671
+ throw 'Existing toolbox is null. Can\'t create new toolbox.';
1672
+ }
1673
+ if (tree.getElementsByTagName('category').length) {
1674
+ if (!this.toolbox_) {
1675
+ throw 'Existing toolbox has no categories. Can\'t change mode.';
1676
+ }
1677
+ this.options.languageTree = tree;
1678
+ this.toolbox_.populate_(tree);
1679
+ this.toolbox_.position();
1680
+ } else {
1681
+ if (!this.flyout_) {
1682
+ throw 'Existing toolbox has categories. Can\'t change mode.';
1683
+ }
1684
+ this.options.languageTree = tree;
1685
+ this.flyout_.show(tree.childNodes);
1686
+ }
1687
+ };
1688
+
1689
+ /**
1690
+ * Mark this workspace as the currently focused main workspace.
1691
+ */
1692
+ Blockly.WorkspaceSvg.prototype.markFocused = function() {
1693
+ if (this.options.parentWorkspace) {
1694
+ this.options.parentWorkspace.markFocused();
1695
+ } else {
1696
+ Blockly.mainWorkspace = this;
1697
+ // We call e.preventDefault in many event handlers which means we
1698
+ // need to explicitly grab focus (e.g from a textarea) because
1699
+ // the browser will not do it for us. How to do this is browser dependant.
1700
+ this.setBrowserFocus();
1701
+ }
1702
+ };
1703
+
1704
+ /**
1705
+ * Set the workspace to have focus in the browser.
1706
+ * @private
1707
+ */
1708
+ Blockly.WorkspaceSvg.prototype.setBrowserFocus = function() {
1709
+ // Blur whatever was focused since explcitly grabbing focus below does not
1710
+ // work in Edge.
1711
+ if (document.activeElement) {
1712
+ document.activeElement.blur();
1713
+ }
1714
+ try {
1715
+ // Focus the workspace SVG - this is for Chrome and Firefox.
1716
+ this.getParentSvg().focus();
1717
+ } catch (e) {
1718
+ // IE and Edge do not support focus on SVG elements. When that fails
1719
+ // above, get the injectionDiv (the workspace's parent) and focus that
1720
+ // instead. This doesn't work in Chrome.
1721
+ try {
1722
+ // In IE11, use setActive (which is IE only) so the page doesn't scroll
1723
+ // to the workspace gaining focus.
1724
+ this.getParentSvg().parentNode.setActive();
1725
+ } catch (e) {
1726
+ // setActive support was discontinued in Edge so when that fails, call
1727
+ // focus instead.
1728
+ this.getParentSvg().parentNode.focus();
1729
+ }
1730
+ }
1731
+ };
1732
+
1733
+ /**
1734
+ * Zooming the blocks centered in (x, y) coordinate with zooming in or out.
1735
+ * @param {number} x X coordinate of center.
1736
+ * @param {number} y Y coordinate of center.
1737
+ * @param {number} amount Amount of zooming
1738
+ * (negative zooms out and positive zooms in).
1739
+ */
1740
+ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) {
1741
+ var speed = this.options.zoomOptions.scaleSpeed;
1742
+ var metrics = this.getMetrics();
1743
+ var center = this.getParentSvg().createSVGPoint();
1744
+ center.x = x;
1745
+ center.y = y;
1746
+ center = center.matrixTransform(this.getCanvas().getCTM().inverse());
1747
+ x = center.x;
1748
+ y = center.y;
1749
+ var canvas = this.getCanvas();
1750
+ // Scale factor.
1751
+ var scaleChange = Math.pow(speed, amount);
1752
+ // Clamp scale within valid range.
1753
+ var newScale = this.scale * scaleChange;
1754
+ if (newScale > this.options.zoomOptions.maxScale) {
1755
+ scaleChange = this.options.zoomOptions.maxScale / this.scale;
1756
+ } else if (newScale < this.options.zoomOptions.minScale) {
1757
+ scaleChange = this.options.zoomOptions.minScale / this.scale;
1758
+ }
1759
+ if (this.scale == newScale) {
1760
+ return; // No change in zoom.
1761
+ }
1762
+ if (this.scrollbar) {
1763
+ var matrix = canvas.getCTM()
1764
+ .translate(x * (1 - scaleChange), y * (1 - scaleChange))
1765
+ .scale(scaleChange);
1766
+ // newScale and matrix.a should be identical (within a rounding error).
1767
+ // ScrollX and scrollY are in pixels.
1768
+ this.scrollX = matrix.e - metrics.absoluteLeft;
1769
+ this.scrollY = matrix.f - metrics.absoluteTop;
1770
+ }
1771
+ this.setScale(newScale);
1772
+ // Hide the WidgetDiv without animation (zoom makes field out of place with div)
1773
+ Blockly.WidgetDiv.hide(true);
1774
+ Blockly.DropDownDiv.hideWithoutAnimation();
1775
+ };
1776
+
1777
+ /**
1778
+ * Zooming the blocks centered in the center of view with zooming in or out.
1779
+ * @param {number} type Type of zooming (-1 zooming out and 1 zooming in).
1780
+ */
1781
+ Blockly.WorkspaceSvg.prototype.zoomCenter = function(type) {
1782
+ var metrics = this.getMetrics();
1783
+ var x = metrics.viewWidth / 2;
1784
+ var y = metrics.viewHeight / 2;
1785
+ this.zoom(x, y, type);
1786
+ };
1787
+
1788
+ /**
1789
+ * Zoom the blocks to fit in the workspace if possible.
1790
+ */
1791
+ Blockly.WorkspaceSvg.prototype.zoomToFit = function() {
1792
+ var metrics = this.getMetrics();
1793
+ var blocksBox = this.getBlocksBoundingBox();
1794
+ var blocksWidth = blocksBox.width;
1795
+ var blocksHeight = blocksBox.height;
1796
+ if (!blocksWidth) {
1797
+ return; // Prevents zooming to infinity.
1798
+ }
1799
+ var workspaceWidth = metrics.viewWidth;
1800
+ var workspaceHeight = metrics.viewHeight;
1801
+ if (this.flyout_) {
1802
+ workspaceWidth -= this.flyout_.width_;
1803
+ }
1804
+ if (!this.scrollbar) {
1805
+ // Origin point of 0,0 is fixed, blocks will not scroll to center.
1806
+ blocksWidth += metrics.contentLeft;
1807
+ blocksHeight += metrics.contentTop;
1808
+ }
1809
+ var ratioX = workspaceWidth / blocksWidth;
1810
+ var ratioY = workspaceHeight / blocksHeight;
1811
+ this.setScale(Math.min(ratioX, ratioY));
1812
+ this.scrollCenter();
1813
+ };
1814
+
1815
+ /**
1816
+ * Center the workspace.
1817
+ */
1818
+ Blockly.WorkspaceSvg.prototype.scrollCenter = function() {
1819
+ if (!this.scrollbar) {
1820
+ // Can't center a non-scrolling workspace.
1821
+ console.warn('Tried to scroll a non-scrollable workspace.');
1822
+ return;
1823
+ }
1824
+ // Hide the WidgetDiv without animation (zoom makes field out of place with div)
1825
+ Blockly.WidgetDiv.hide(true);
1826
+ Blockly.DropDownDiv.hideWithoutAnimation();
1827
+ Blockly.hideChaff(false);
1828
+ var metrics = this.getMetrics();
1829
+ var x = (metrics.contentWidth - metrics.viewWidth) / 2;
1830
+ if (this.flyout_) {
1831
+ x -= this.flyout_.width_ / 2;
1832
+ }
1833
+ var y = (metrics.contentHeight - metrics.viewHeight) / 2;
1834
+ this.scrollbar.set(x, y);
1835
+ };
1836
+
1837
+ /**
1838
+ * Scroll the workspace to center on the given block.
1839
+ * @param {?string} id ID of block center on.
1840
+ * @public
1841
+ */
1842
+ Blockly.WorkspaceSvg.prototype.centerOnBlock = function(id) {
1843
+ if (!this.scrollbar) {
1844
+ console.warn('Tried to scroll a non-scrollable workspace.');
1845
+ return;
1846
+ }
1847
+
1848
+ var block = this.getBlockById(id);
1849
+ if (!block) {
1850
+ return;
1851
+ }
1852
+
1853
+ // XY is in workspace coordinates.
1854
+ var xy = block.getRelativeToSurfaceXY();
1855
+ // Height/width is in workspace units.
1856
+ var heightWidth = block.getHeightWidth();
1857
+
1858
+ // Find the enter of the block in workspace units.
1859
+ var blockCenterY = xy.y + heightWidth.height / 2;
1860
+
1861
+ // In RTL the block's position is the top right of the block, not top left.
1862
+ var multiplier = this.RTL ? -1 : 1;
1863
+ var blockCenterX = xy.x + (multiplier * heightWidth.width / 2);
1864
+
1865
+ // Workspace scale, used to convert from workspace coordinates to pixels.
1866
+ var scale = this.scale;
1867
+
1868
+ // Center in pixels. 0, 0 is at the workspace origin. These numbers may
1869
+ // be negative.
1870
+ var pixelX = blockCenterX * scale;
1871
+ var pixelY = blockCenterY * scale;
1872
+
1873
+ var metrics = this.getMetrics();
1874
+
1875
+ // Scrolling to here would put the block in the top-left corner of the
1876
+ // visible workspace.
1877
+ var scrollToBlockX = pixelX - metrics.contentLeft;
1878
+ var scrollToBlockY = pixelY - metrics.contentTop;
1879
+
1880
+ // viewHeight and viewWidth are in pixels.
1881
+ var halfViewWidth = metrics.viewWidth / 2;
1882
+ var halfViewHeight = metrics.viewHeight / 2;
1883
+
1884
+ // Put the block in the center of the visible workspace instead.
1885
+ var scrollToCenterX = scrollToBlockX - halfViewWidth;
1886
+ var scrollToCenterY = scrollToBlockY - halfViewHeight;
1887
+
1888
+ Blockly.hideChaff();
1889
+ this.scrollbar.set(scrollToCenterX, scrollToCenterY);
1890
+ };
1891
+
1892
+ /**
1893
+ * Set the workspace's zoom factor.
1894
+ * @param {number} newScale Zoom factor.
1895
+ */
1896
+ Blockly.WorkspaceSvg.prototype.setScale = function(newScale) {
1897
+ if (this.options.zoomOptions.maxScale &&
1898
+ newScale > this.options.zoomOptions.maxScale) {
1899
+ newScale = this.options.zoomOptions.maxScale;
1900
+ } else if (this.options.zoomOptions.minScale &&
1901
+ newScale < this.options.zoomOptions.minScale) {
1902
+ newScale = this.options.zoomOptions.minScale;
1903
+ }
1904
+ this.scale = newScale;
1905
+ if (this.grid_) {
1906
+ this.grid_.update(this.scale);
1907
+ }
1908
+ if (this.scrollbar) {
1909
+ this.scrollbar.resize();
1910
+ } else {
1911
+ this.translate(this.scrollX, this.scrollY);
1912
+ }
1913
+ Blockly.hideChaff(false);
1914
+ if (this.flyout_) {
1915
+ // No toolbox, resize flyout.
1916
+ this.flyout_.reflow();
1917
+ }
1918
+ this.queueIntersectionCheck();
1919
+ };
1920
+
1921
+ /**
1922
+ * Scroll the workspace by a specified amount, keeping in the bounds.
1923
+ * Be sure to set this.startDragMetrics with cached metrics before calling.
1924
+ * @param {number} x Target X to scroll to
1925
+ * @param {number} y Target Y to scroll to
1926
+ */
1927
+ Blockly.WorkspaceSvg.prototype.scroll = function(x, y) {
1928
+ var metrics = this.startDragMetrics; // Cached values
1929
+ x = Math.min(x, -metrics.contentLeft);
1930
+ y = Math.min(y, -metrics.contentTop);
1931
+ x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
1932
+ metrics.contentWidth);
1933
+ y = Math.max(y, metrics.viewHeight - metrics.contentTop -
1934
+ metrics.contentHeight);
1935
+ // When the workspace starts scrolling, hide the WidgetDiv without animation.
1936
+ // This is to prevent a dispoal animation from happening in the wrong location.
1937
+ Blockly.WidgetDiv.hide(true);
1938
+ Blockly.DropDownDiv.hideWithoutAnimation();
1939
+ // Move the scrollbars and the page will scroll automatically.
1940
+ this.scrollbar.set(-x - metrics.contentLeft, -y - metrics.contentTop);
1941
+ };
1942
+
1943
+ /**
1944
+ * Update the workspace's stack glow radius to be proportional to scale.
1945
+ * Ensures that stack glows always appear to be a fixed size.
1946
+ */
1947
+ Blockly.WorkspaceSvg.prototype.updateStackGlowScale_ = function() {
1948
+ // No such def in the flyout workspace.
1949
+ if (this.options.stackGlowBlur) {
1950
+ this.options.stackGlowBlur.setAttribute('stdDeviation',
1951
+ Blockly.Colours.stackGlowSize / this.scale);
1952
+ }
1953
+ };
1954
+
1955
+ /**
1956
+ * Get the dimensions of the given workspace component, in pixels.
1957
+ * @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the
1958
+ * dimensions of, or null. It should be a toolbox or flyout, and should
1959
+ * implement getWidth() and getHeight().
1960
+ * @return {!Object} An object containing width and height attributes, which
1961
+ * will both be zero if elem did not exist.
1962
+ * @private
1963
+ */
1964
+ Blockly.WorkspaceSvg.getDimensionsPx_ = function(elem) {
1965
+ var width = 0;
1966
+ var height = 0;
1967
+ if (elem) {
1968
+ width = elem.getWidth();
1969
+ height = elem.getHeight();
1970
+ }
1971
+ return {
1972
+ width: width,
1973
+ height: height
1974
+ };
1975
+ };
1976
+
1977
+ /**
1978
+ * Get the content dimensions of the given workspace, taking into account
1979
+ * whether or not it is scrollable and what size the workspace div is on screen.
1980
+ * @param {!Blockly.WorkspaceSvg} ws The workspace to measure.
1981
+ * @param {!Object} svgSize An object containing height and width attributes in
1982
+ * CSS pixels. Together they specify the size of the visible workspace, not
1983
+ * including areas covered up by the toolbox.
1984
+ * @return {!Object} The dimensions of the contents of the given workspace, as
1985
+ * an object containing at least
1986
+ * - height and width in pixels
1987
+ * - left and top in pixels relative to the workspace origin.
1988
+ * @private
1989
+ */
1990
+ Blockly.WorkspaceSvg.getContentDimensions_ = function(ws, svgSize) {
1991
+ if (ws.scrollbar) {
1992
+ return Blockly.WorkspaceSvg.getContentDimensionsBounded_(ws, svgSize);
1993
+ } else {
1994
+ return Blockly.WorkspaceSvg.getContentDimensionsExact_(ws);
1995
+ }
1996
+ };
1997
+
1998
+ /**
1999
+ * Get the bounding box for all workspace contents, in pixels.
2000
+ * @param {!Blockly.WorkspaceSvg} ws The workspace to inspect.
2001
+ * @return {!Object} The dimensions of the contents of the given workspace, as
2002
+ * an object containing
2003
+ * - height and width in pixels
2004
+ * - left, right, top and bottom in pixels relative to the workspace origin.
2005
+ * @private
2006
+ */
2007
+ Blockly.WorkspaceSvg.getContentDimensionsExact_ = function(ws) {
2008
+ // Block bounding box is in workspace coordinates.
2009
+ var blockBox = ws.getBlocksBoundingBox();
2010
+ var scale = ws.scale;
2011
+
2012
+ // Convert to pixels.
2013
+ var width = blockBox.width * scale;
2014
+ var height = blockBox.height * scale;
2015
+ var left = blockBox.x * scale;
2016
+ var top = blockBox.y * scale;
2017
+
2018
+ return {
2019
+ left: left,
2020
+ top: top,
2021
+ right: left + width,
2022
+ bottom: top + height,
2023
+ width: width,
2024
+ height: height
2025
+ };
2026
+ };
2027
+
2028
+ /**
2029
+ * Calculate the size of a scrollable workspace, which should include room for a
2030
+ * half screen border around the workspace contents.
2031
+ * @param {!Blockly.WorkspaceSvg} ws The workspace to measure.
2032
+ * @param {!Object} svgSize An object containing height and width attributes in
2033
+ * CSS pixels. Together they specify the size of the visible workspace, not
2034
+ * including areas covered up by the toolbox.
2035
+ * @return {!Object} The dimensions of the contents of the given workspace, as
2036
+ * an object containing
2037
+ * - height and width in pixels
2038
+ * - left and top in pixels relative to the workspace origin.
2039
+ * @private
2040
+ */
2041
+ Blockly.WorkspaceSvg.getContentDimensionsBounded_ = function(ws, svgSize) {
2042
+ var content = Blockly.WorkspaceSvg.getContentDimensionsExact_(ws);
2043
+
2044
+ // View height and width are both in pixels, and are the same as the SVG size.
2045
+ var viewWidth = svgSize.width;
2046
+ var viewHeight = svgSize.height;
2047
+ var halfWidth = viewWidth / 2;
2048
+ var halfHeight = viewHeight / 2;
2049
+
2050
+ // Add a border around the content that is at least half a screenful wide.
2051
+ // Ensure border is wide enough that blocks can scroll over entire screen.
2052
+ var left = Math.min(content.left - halfWidth, content.right - viewWidth);
2053
+ var right = Math.max(content.right + halfWidth, content.left + viewWidth);
2054
+
2055
+ var top = Math.min(content.top - halfHeight, content.bottom - viewHeight);
2056
+ var bottom = Math.max(content.bottom + halfHeight, content.top + viewHeight);
2057
+
2058
+ var dimensions = {
2059
+ left: left,
2060
+ top: top,
2061
+ height: bottom - top,
2062
+ width: right - left
2063
+ };
2064
+ return dimensions;
2065
+ };
2066
+
2067
+ /**
2068
+ * Return an object with all the metrics required to size scrollbars for a
2069
+ * top level workspace. The following properties are computed:
2070
+ * Coordinate system: pixel coordinates.
2071
+ * .viewHeight: Height of the visible rectangle,
2072
+ * .viewWidth: Width of the visible rectangle,
2073
+ * .contentHeight: Height of the contents,
2074
+ * .contentWidth: Width of the content,
2075
+ * .viewTop: Offset of top edge of visible rectangle from parent,
2076
+ * .viewLeft: Offset of left edge of visible rectangle from parent,
2077
+ * .contentTop: Offset of the top-most content from the y=0 coordinate,
2078
+ * .contentLeft: Offset of the left-most content from the x=0 coordinate.
2079
+ * .absoluteTop: Top-edge of view.
2080
+ * .absoluteLeft: Left-edge of view.
2081
+ * .toolboxWidth: Width of toolbox, if it exists. Otherwise zero.
2082
+ * .toolboxHeight: Height of toolbox, if it exists. Otherwise zero.
2083
+ * .flyoutWidth: Width of the flyout if it is always open. Otherwise zero.
2084
+ * .flyoutHeight: Height of flyout if it is always open. Otherwise zero.
2085
+ * .toolboxPosition: Top, bottom, left or right.
2086
+ * @return {!Object} Contains size and position metrics of a top level
2087
+ * workspace.
2088
+ * @private
2089
+ * @this Blockly.WorkspaceSvg
2090
+ */
2091
+ Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_ = function() {
2092
+
2093
+ var toolboxDimensions =
2094
+ Blockly.WorkspaceSvg.getDimensionsPx_(this.toolbox_);
2095
+ var flyoutDimensions =
2096
+ Blockly.WorkspaceSvg.getDimensionsPx_(this.flyout_);
2097
+
2098
+ // Contains height and width in CSS pixels.
2099
+ // svgSize is equivalent to the size of the injectionDiv at this point.
2100
+ var svgSize = Blockly.svgSize(this.getParentSvg());
2101
+ if (this.toolbox_) {
2102
+ if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ||
2103
+ this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
2104
+ svgSize.height -= toolboxDimensions.height;
2105
+ } else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ||
2106
+ this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
2107
+ svgSize.width -= toolboxDimensions.width;
2108
+ }
2109
+ }
2110
+
2111
+ // svgSize is now the space taken up by the Blockly workspace, not including
2112
+ // the toolbox.
2113
+ var contentDimensions =
2114
+ Blockly.WorkspaceSvg.getContentDimensions_(this, svgSize);
2115
+
2116
+ var absoluteLeft = 0;
2117
+ if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
2118
+ absoluteLeft = toolboxDimensions.width;
2119
+ }
2120
+ var absoluteTop = 0;
2121
+ if (this.toolbox_ && this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
2122
+ absoluteTop = toolboxDimensions.height;
2123
+ }
2124
+
2125
+ var metrics = {
2126
+ contentHeight: contentDimensions.height,
2127
+ contentWidth: contentDimensions.width,
2128
+ contentTop: contentDimensions.top,
2129
+ contentLeft: contentDimensions.left,
2130
+
2131
+ viewHeight: svgSize.height,
2132
+ viewWidth: svgSize.width,
2133
+ viewTop: -this.scrollY, // Must be in pixels, somehow.
2134
+ viewLeft: -this.scrollX, // Must be in pixels, somehow.
2135
+
2136
+ absoluteTop: absoluteTop,
2137
+ absoluteLeft: absoluteLeft,
2138
+
2139
+ toolboxWidth: toolboxDimensions.width,
2140
+ toolboxHeight: toolboxDimensions.height,
2141
+
2142
+ flyoutWidth: flyoutDimensions.width,
2143
+ flyoutHeight: flyoutDimensions.height,
2144
+
2145
+ toolboxPosition: this.toolboxPosition
2146
+ };
2147
+ return metrics;
2148
+ };
2149
+
2150
+ /**
2151
+ * Sets the X/Y translations of a top level workspace to match the scrollbars.
2152
+ * @param {!Object} xyRatio Contains an x and/or y property which is a float
2153
+ * between 0 and 1 specifying the degree of scrolling.
2154
+ * @private
2155
+ * @this Blockly.WorkspaceSvg
2156
+ */
2157
+ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
2158
+ if (!this.scrollbar) {
2159
+ throw 'Attempt to set top level workspace scroll without scrollbars.';
2160
+ }
2161
+ var metrics = this.getMetrics();
2162
+ if (goog.isNumber(xyRatio.x)) {
2163
+ this.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft;
2164
+ }
2165
+ if (goog.isNumber(xyRatio.y)) {
2166
+ this.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop;
2167
+ }
2168
+ var x = this.scrollX + metrics.absoluteLeft;
2169
+ var y = this.scrollY + metrics.absoluteTop;
2170
+ this.translate(x, y);
2171
+ if (this.grid_) {
2172
+ this.grid_.moveTo(x, y);
2173
+ }
2174
+ };
2175
+
2176
+ /**
2177
+ * Update whether this workspace has resizes enabled.
2178
+ * If enabled, workspace will resize when appropriate.
2179
+ * If disabled, workspace will not resize until re-enabled.
2180
+ * Use to avoid resizing during a batch operation, for performance.
2181
+ * @param {boolean} enabled Whether resizes should be enabled.
2182
+ */
2183
+ Blockly.WorkspaceSvg.prototype.setResizesEnabled = function(enabled) {
2184
+ var reenabled = (!this.resizesEnabled_ && enabled);
2185
+ this.resizesEnabled_ = enabled;
2186
+ if (reenabled) {
2187
+ // Newly enabled. Trigger a resize.
2188
+ this.resizeContents();
2189
+ }
2190
+ };
2191
+
2192
+ /**
2193
+ * Update whether this workspace has toolbox refreshes enabled.
2194
+ * If enabled, the toolbox will refresh when appropriate.
2195
+ * If disabled, workspace will not refresh until re-enabled.
2196
+ * Use to avoid refreshing during a batch operation, for performance.
2197
+ * @param {boolean} enabled Whether refreshes should be enabled.
2198
+ */
2199
+ Blockly.WorkspaceSvg.prototype.setToolboxRefreshEnabled = function(enabled) {
2200
+ var reenabled = (!this.toolboxRefreshEnabled_ && enabled);
2201
+ this.toolboxRefreshEnabled_ = enabled;
2202
+ if (reenabled) {
2203
+ // Newly enabled. Trigger a refresh.
2204
+ this.refreshToolboxSelection_();
2205
+ }
2206
+ };
2207
+
2208
+
2209
+ /**
2210
+ * Dispose of all blocks in workspace, with an optimization to prevent resizes.
2211
+ */
2212
+ Blockly.WorkspaceSvg.prototype.clear = function() {
2213
+ this.setResizesEnabled(false);
2214
+ Blockly.WorkspaceSvg.superClass_.clear.call(this);
2215
+ this.setResizesEnabled(true);
2216
+ };
2217
+
2218
+ /**
2219
+ * Register a callback function associated with a given key, for clicks on
2220
+ * buttons and labels in the flyout.
2221
+ * For instance, a button specified by the XML
2222
+ * <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
2223
+ * should be matched by a call to
2224
+ * registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
2225
+ * @param {string} key The name to use to look up this function.
2226
+ * @param {function(!Blockly.FlyoutButton)} func The function to call when the
2227
+ * given button is clicked.
2228
+ */
2229
+ Blockly.WorkspaceSvg.prototype.registerButtonCallback = function(key, func) {
2230
+ goog.asserts.assert(goog.isFunction(func),
2231
+ 'Button callbacks must be functions.');
2232
+ this.flyoutButtonCallbacks_[key] = func;
2233
+ };
2234
+
2235
+ /**
2236
+ * Get the callback function associated with a given key, for clicks on buttons
2237
+ * and labels in the flyout.
2238
+ * @param {string} key The name to use to look up the function.
2239
+ * @return {?function(!Blockly.FlyoutButton)} The function corresponding to the
2240
+ * given key for this workspace; null if no callback is registered.
2241
+ */
2242
+ Blockly.WorkspaceSvg.prototype.getButtonCallback = function(key) {
2243
+ var result = this.flyoutButtonCallbacks_[key];
2244
+ return result ? result : null;
2245
+ };
2246
+
2247
+ /**
2248
+ * Remove a callback for a click on a button in the flyout.
2249
+ * @param {string} key The name associated with the callback function.
2250
+ */
2251
+ Blockly.WorkspaceSvg.prototype.removeButtonCallback = function(key) {
2252
+ this.flyoutButtonCallbacks_[key] = null;
2253
+ };
2254
+
2255
+ /**
2256
+ * Register a callback function associated with a given key, for populating
2257
+ * custom toolbox categories in this workspace. See the variable and procedure
2258
+ * categories as an example.
2259
+ * @param {string} key The name to use to look up this function.
2260
+ * @param {function(!Blockly.Workspace):!Array.<!Element>} func The function to
2261
+ * call when the given toolbox category is opened.
2262
+ */
2263
+ Blockly.WorkspaceSvg.prototype.registerToolboxCategoryCallback = function(key,
2264
+ func) {
2265
+ goog.asserts.assert(goog.isFunction(func),
2266
+ 'Toolbox category callbacks must be functions.');
2267
+ this.toolboxCategoryCallbacks_[key] = func;
2268
+ };
2269
+
2270
+ /**
2271
+ * Get the callback function associated with a given key, for populating
2272
+ * custom toolbox categories in this workspace.
2273
+ * @param {string} key The name to use to look up the function.
2274
+ * @return {?function(!Blockly.Workspace):!Array.<!Element>} The function
2275
+ * corresponding to the given key for this workspace, or null if no function
2276
+ * is registered.
2277
+ */
2278
+ Blockly.WorkspaceSvg.prototype.getToolboxCategoryCallback = function(key) {
2279
+ var result = this.toolboxCategoryCallbacks_[key];
2280
+ return result ? result : null;
2281
+ };
2282
+
2283
+ /**
2284
+ * Remove a callback for a click on a custom category's name in the toolbox.
2285
+ * @param {string} key The name associated with the callback function.
2286
+ */
2287
+ Blockly.WorkspaceSvg.prototype.removeToolboxCategoryCallback = function(key) {
2288
+ this.toolboxCategoryCallbacks_[key] = null;
2289
+ };
2290
+
2291
+ /**
2292
+ * Look up the gesture that is tracking this touch stream on this workspace.
2293
+ * May create a new gesture.
2294
+ * @param {!Event} e Mouse event or touch event
2295
+ * @return {Blockly.Gesture} The gesture that is tracking this touch stream,
2296
+ * or null if no valid gesture exists.
2297
+ * @package
2298
+ */
2299
+ Blockly.WorkspaceSvg.prototype.getGesture = function(e) {
2300
+ var isStart = (e.type == 'mousedown' || e.type == 'touchstart');
2301
+
2302
+ var gesture = this.currentGesture_;
2303
+ if (gesture) {
2304
+ if (isStart && gesture.hasStarted()) {
2305
+ // That's funny. We must have missed a mouse up.
2306
+ // Cancel it, rather than try to retrieve all of the state we need.
2307
+ gesture.cancel();
2308
+ return null;
2309
+ }
2310
+ return gesture;
2311
+ }
2312
+
2313
+ // No gesture existed on this workspace, but this looks like the start of a
2314
+ // new gesture.
2315
+ if (isStart) {
2316
+ this.currentGesture_ = new Blockly.Gesture(e, this);
2317
+ return this.currentGesture_;
2318
+ }
2319
+ // No gesture existed and this event couldn't be the start of a new gesture.
2320
+ return null;
2321
+ };
2322
+
2323
+ /**
2324
+ * Clear the reference to the current gesture.
2325
+ * @package
2326
+ */
2327
+ Blockly.WorkspaceSvg.prototype.clearGesture = function() {
2328
+ this.currentGesture_ = null;
2329
+
2330
+ if (this.checkProcedureReturnAfterGesture_) {
2331
+ this.processProcedureReturnsChanged_();
2332
+ }
2333
+ };
2334
+
2335
+ /**
2336
+ * Cancel the current gesture, if one exists.
2337
+ * @package
2338
+ */
2339
+ Blockly.WorkspaceSvg.prototype.cancelCurrentGesture = function() {
2340
+ if (this.currentGesture_) {
2341
+ this.currentGesture_.cancel();
2342
+ }
2343
+ };
2344
+
2345
+ /**
2346
+ * Don't even think about using this function before talking to rachel-fenichel.
2347
+ *
2348
+ * Force a drag to start without clicking and dragging the block itself. Used
2349
+ * to attach duplicated blocks to the mouse pointer.
2350
+ * @param {!Object} fakeEvent An object with the properties needed to start a
2351
+ * drag, including clientX and clientY.
2352
+ * @param {!Blockly.BlockSvg} block The block to start dragging.
2353
+ * @package
2354
+ */
2355
+ Blockly.WorkspaceSvg.prototype.startDragWithFakeEvent = function(fakeEvent,
2356
+ block) {
2357
+ Blockly.Touch.clearTouchIdentifier();
2358
+ Blockly.Touch.checkTouchIdentifier(fakeEvent);
2359
+ var gesture = block.workspace.getGesture(fakeEvent);
2360
+ gesture.forceStartBlockDrag(fakeEvent, block);
2361
+ };
2362
+
2363
+ /**
2364
+ * Get the audio manager for this workspace.
2365
+ * @return {Blockly.WorkspaceAudio} The audio manager for this workspace.
2366
+ */
2367
+ Blockly.WorkspaceSvg.prototype.getAudioManager = function() {
2368
+ return this.audioManager_;
2369
+ };
2370
+
2371
+ /**
2372
+ * Get the grid object for this workspace, or null if there is none.
2373
+ * @return {Blockly.Grid} The grid object for this workspace.
2374
+ * @package
2375
+ */
2376
+ Blockly.WorkspaceSvg.prototype.getGrid = function() {
2377
+ return this.grid_;
2378
+ };
2379
+
2380
+ // Export symbols that would otherwise be renamed by Closure compiler.
2381
+ Blockly.WorkspaceSvg.prototype['setVisible'] =
2382
+ Blockly.WorkspaceSvg.prototype.setVisible;