blockly 7.20211209.4 → 8.0.1

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 (231) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +5 -4
  3. package/blockly_compressed.js +4 -3
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +45 -32
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +1 -1
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +26 -10
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/generators/dart/colour.js +56 -64
  208. package/generators/dart/lists.js +61 -50
  209. package/generators/dart/math.js +160 -148
  210. package/generators/dart/text.js +83 -61
  211. package/generators/javascript/colour.js +37 -34
  212. package/generators/javascript/lists.js +50 -43
  213. package/generators/javascript/math.js +123 -139
  214. package/generators/javascript/text.js +67 -81
  215. package/generators/lua/colour.js +25 -23
  216. package/generators/lua/lists.js +97 -69
  217. package/generators/lua/logic.js +1 -2
  218. package/generators/lua/math.js +182 -144
  219. package/generators/lua/text.js +116 -99
  220. package/generators/php/colour.js +38 -32
  221. package/generators/php/lists.js +109 -89
  222. package/generators/php/math.js +90 -81
  223. package/generators/php/text.js +63 -61
  224. package/generators/python/colour.js +18 -18
  225. package/generators/python/lists.js +38 -30
  226. package/generators/python/loops.js +12 -8
  227. package/generators/python/math.js +104 -106
  228. package/generators/python/text.js +34 -30
  229. package/msg/smn.js +436 -0
  230. package/package.json +7 -6
  231. package/blocks/all.js +0 -23
package/core/trashcan.js CHANGED
@@ -15,19 +15,19 @@
15
15
  */
16
16
  goog.module('Blockly.Trashcan');
17
17
 
18
- /* eslint-disable-next-line no-unused-vars */
19
- const Abstract = goog.requireType('Blockly.Events.Abstract');
20
18
  /* eslint-disable-next-line no-unused-vars */
21
19
  const blocks = goog.requireType('Blockly.serialization.blocks');
22
20
  const browserEvents = goog.require('Blockly.browserEvents');
23
21
  const dom = goog.require('Blockly.utils.dom');
24
22
  const eventUtils = goog.require('Blockly.Events.utils');
25
- const internalConstants = goog.require('Blockly.internalConstants');
26
- const object = goog.require('Blockly.utils.object');
27
23
  const registry = goog.require('Blockly.registry');
28
24
  const toolbox = goog.require('Blockly.utils.toolbox');
29
25
  const uiPosition = goog.require('Blockly.uiPosition');
30
26
  /* eslint-disable-next-line no-unused-vars */
27
+ const {Abstract} = goog.requireType('Blockly.Events.Abstract');
28
+ /* eslint-disable-next-line no-unused-vars */
29
+ const {BlockDelete} = goog.requireType('Blockly.Events.BlockDelete');
30
+ /* eslint-disable-next-line no-unused-vars */
31
31
  const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions');
32
32
  const {ComponentManager} = goog.require('Blockly.ComponentManager');
33
33
  const {DeleteArea} = goog.require('Blockly.DeleteArea');
@@ -44,6 +44,7 @@ const {MetricsManager} = goog.requireType('Blockly.MetricsManager');
44
44
  const {Options} = goog.require('Blockly.Options');
45
45
  const {Rect} = goog.require('Blockly.utils.Rect');
46
46
  const {Size} = goog.require('Blockly.utils.Size');
47
+ const {SPRITE} = goog.require('Blockly.sprite');
47
48
  const {Svg} = goog.require('Blockly.utils.Svg');
48
49
  /* eslint-disable-next-line no-unused-vars */
49
50
  const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
@@ -53,694 +54,704 @@ goog.require('Blockly.Events.TrashcanOpen');
53
54
 
54
55
  /**
55
56
  * Class for a trash can.
56
- * @param {!WorkspaceSvg} workspace The workspace to sit in.
57
- * @constructor
58
57
  * @implements {IAutoHideable}
59
58
  * @implements {IPositionable}
60
59
  * @extends {DeleteArea}
61
60
  * @alias Blockly.Trashcan
62
61
  */
63
- const Trashcan = function(workspace) {
64
- Trashcan.superClass_.constructor.call(this);
62
+ class Trashcan extends DeleteArea {
65
63
  /**
66
- * The workspace the trashcan sits in.
67
- * @type {!WorkspaceSvg}
68
- * @private
64
+ * @param {!WorkspaceSvg} workspace The workspace to sit in.
69
65
  */
70
- this.workspace_ = workspace;
66
+ constructor(workspace) {
67
+ super();
68
+ /**
69
+ * The workspace the trashcan sits in.
70
+ * @type {!WorkspaceSvg}
71
+ * @private
72
+ */
73
+ this.workspace_ = workspace;
74
+
75
+ /**
76
+ * The unique id for this component that is used to register with the
77
+ * ComponentManager.
78
+ * @type {string}
79
+ */
80
+ this.id = 'trashcan';
81
+
82
+ /**
83
+ * A list of JSON (stored as strings) representing blocks in the trashcan.
84
+ * @type {!Array<string>}
85
+ * @private
86
+ */
87
+ this.contents_ = [];
88
+
89
+ /**
90
+ * The trashcan flyout.
91
+ * @type {IFlyout}
92
+ * @package
93
+ */
94
+ this.flyout = null;
95
+
96
+ if (this.workspace_.options.maxTrashcanContents <= 0) {
97
+ return;
98
+ }
99
+
100
+ /**
101
+ * Current open/close state of the lid.
102
+ * @type {boolean}
103
+ */
104
+ this.isLidOpen = false;
105
+
106
+ /**
107
+ * The minimum openness of the lid. Used to indicate if the trashcan
108
+ * contains blocks.
109
+ * @type {number}
110
+ * @private
111
+ */
112
+ this.minOpenness_ = 0;
113
+
114
+ /**
115
+ * The SVG group containing the trash can.
116
+ * @type {SVGElement}
117
+ * @private
118
+ */
119
+ this.svgGroup_ = null;
120
+
121
+ /**
122
+ * The SVG image element of the trash can lid.
123
+ * @type {SVGElement}
124
+ * @private
125
+ */
126
+ this.svgLid_ = null;
127
+
128
+ /**
129
+ * Task ID of opening/closing animation.
130
+ * @type {number}
131
+ * @private
132
+ */
133
+ this.lidTask_ = 0;
134
+
135
+ /**
136
+ * Current state of lid opening (0.0 = closed, 1.0 = open).
137
+ * @type {number}
138
+ * @private
139
+ */
140
+ this.lidOpen_ = 0;
141
+
142
+ /**
143
+ * Left coordinate of the trash can.
144
+ * @type {number}
145
+ * @private
146
+ */
147
+ this.left_ = 0;
148
+
149
+ /**
150
+ * Top coordinate of the trash can.
151
+ * @type {number}
152
+ * @private
153
+ */
154
+ this.top_ = 0;
155
+
156
+ /**
157
+ * Whether this trash can has been initialized.
158
+ * @type {boolean}
159
+ * @private
160
+ */
161
+ this.initialized_ = false;
162
+
163
+ // Create flyout options.
164
+ const flyoutWorkspaceOptions = new Options(
165
+ /** @type {!BlocklyOptions} */
166
+ ({
167
+ 'scrollbars': true,
168
+ 'parentWorkspace': this.workspace_,
169
+ 'rtl': this.workspace_.RTL,
170
+ 'oneBasedIndex': this.workspace_.options.oneBasedIndex,
171
+ 'renderer': this.workspace_.options.renderer,
172
+ 'rendererOverrides': this.workspace_.options.rendererOverrides,
173
+ 'move': {
174
+ 'scrollbars': true,
175
+ },
176
+ }));
177
+ // Create vertical or horizontal flyout.
178
+ if (this.workspace_.horizontalLayout) {
179
+ flyoutWorkspaceOptions.toolboxPosition =
180
+ this.workspace_.toolboxPosition === toolbox.Position.TOP ?
181
+ toolbox.Position.BOTTOM :
182
+ toolbox.Position.TOP;
183
+ const HorizontalFlyout = registry.getClassFromOptions(
184
+ registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, this.workspace_.options,
185
+ true);
186
+ this.flyout = new HorizontalFlyout(flyoutWorkspaceOptions);
187
+ } else {
188
+ flyoutWorkspaceOptions.toolboxPosition =
189
+ this.workspace_.toolboxPosition === toolbox.Position.RIGHT ?
190
+ toolbox.Position.LEFT :
191
+ toolbox.Position.RIGHT;
192
+ const VerticalFlyout = registry.getClassFromOptions(
193
+ registry.Type.FLYOUTS_VERTICAL_TOOLBOX, this.workspace_.options,
194
+ true);
195
+ this.flyout = new VerticalFlyout(flyoutWorkspaceOptions);
196
+ }
197
+ this.workspace_.addChangeListener(this.onDelete_.bind(this));
198
+ }
71
199
 
72
200
  /**
73
- * The unique id for this component that is used to register with the
74
- * ComponentManager.
75
- * @type {string}
201
+ * Create the trash can elements.
202
+ * @return {!SVGElement} The trash can's SVG group.
76
203
  */
77
- this.id = 'trashcan';
204
+ createDom() {
205
+ /* Here's the markup that will be generated:
206
+ <g class="blocklyTrash">
207
+ <clippath id="blocklyTrashBodyClipPath837493">
208
+ <rect width="47" height="45" y="15"></rect>
209
+ </clippath>
210
+ <image width="64" height="92" y="-32" xlink:href="media/sprites.png"
211
+ clip-path="url(#blocklyTrashBodyClipPath837493)"></image>
212
+ <clippath id="blocklyTrashLidClipPath837493">
213
+ <rect width="47" height="15"></rect>
214
+ </clippath>
215
+ <image width="84" height="92" y="-32" xlink:href="media/sprites.png"
216
+ clip-path="url(#blocklyTrashLidClipPath837493)"></image>
217
+ </g>
218
+ */
219
+ this.svgGroup_ =
220
+ dom.createSvgElement(Svg.G, {'class': 'blocklyTrash'}, null);
221
+ let clip;
222
+ const rnd = String(Math.random()).substring(2);
223
+ clip = dom.createSvgElement(
224
+ Svg.CLIPPATH, {'id': 'blocklyTrashBodyClipPath' + rnd}, this.svgGroup_);
225
+ dom.createSvgElement(
226
+ Svg.RECT, {'width': WIDTH, 'height': BODY_HEIGHT, 'y': LID_HEIGHT},
227
+ clip);
228
+ const body = dom.createSvgElement(
229
+ Svg.IMAGE, {
230
+ 'width': SPRITE.width,
231
+ 'x': -SPRITE_LEFT,
232
+ 'height': SPRITE.height,
233
+ 'y': -SPRITE_TOP,
234
+ 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')',
235
+ },
236
+ this.svgGroup_);
237
+ body.setAttributeNS(
238
+ dom.XLINK_NS, 'xlink:href',
239
+ this.workspace_.options.pathToMedia + SPRITE.url);
240
+
241
+ clip = dom.createSvgElement(
242
+ Svg.CLIPPATH, {'id': 'blocklyTrashLidClipPath' + rnd}, this.svgGroup_);
243
+ dom.createSvgElement(
244
+ Svg.RECT, {'width': WIDTH, 'height': LID_HEIGHT}, clip);
245
+ this.svgLid_ = dom.createSvgElement(
246
+ Svg.IMAGE, {
247
+ 'width': SPRITE.width,
248
+ 'x': -SPRITE_LEFT,
249
+ 'height': SPRITE.height,
250
+ 'y': -SPRITE_TOP,
251
+ 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')',
252
+ },
253
+ this.svgGroup_);
254
+ this.svgLid_.setAttributeNS(
255
+ dom.XLINK_NS, 'xlink:href',
256
+ this.workspace_.options.pathToMedia + SPRITE.url);
257
+
258
+ // bindEventWithChecks_ quashes events too aggressively. See:
259
+ // https://groups.google.com/forum/#!topic/blockly/QF4yB9Wx00s
260
+ // Using bindEventWithChecks_ for blocking mousedown causes issue in mobile.
261
+ // See #4303
262
+ browserEvents.bind(
263
+ this.svgGroup_, 'mousedown', this, this.blockMouseDownWhenOpenable_);
264
+ browserEvents.bind(this.svgGroup_, 'mouseup', this, this.click);
265
+ // Bind to body instead of this.svgGroup_ so that we don't get lid jitters
266
+ browserEvents.bind(body, 'mouseover', this, this.mouseOver_);
267
+ browserEvents.bind(body, 'mouseout', this, this.mouseOut_);
268
+ this.animateLid_();
269
+ return this.svgGroup_;
270
+ }
78
271
 
79
272
  /**
80
- * A list of JSON (stored as strings) representing blocks in the trashcan.
81
- * @type {!Array<string>}
82
- * @private
273
+ * Initializes the trash can.
83
274
  */
84
- this.contents_ = [];
275
+ init() {
276
+ if (this.workspace_.options.maxTrashcanContents > 0) {
277
+ dom.insertAfter(
278
+ this.flyout.createDom(Svg.SVG), this.workspace_.getParentSvg());
279
+ this.flyout.init(this.workspace_);
280
+ }
281
+ this.workspace_.getComponentManager().addComponent({
282
+ component: this,
283
+ weight: 1,
284
+ capabilities: [
285
+ ComponentManager.Capability.AUTOHIDEABLE,
286
+ ComponentManager.Capability.DELETE_AREA,
287
+ ComponentManager.Capability.DRAG_TARGET,
288
+ ComponentManager.Capability.POSITIONABLE,
289
+ ],
290
+ });
291
+ this.initialized_ = true;
292
+ this.setLidOpen(false);
293
+ }
85
294
 
86
295
  /**
87
- * The trashcan flyout.
88
- * @type {IFlyout}
89
- * @package
296
+ * Dispose of this trash can.
297
+ * Unlink from all DOM elements to prevent memory leaks.
298
+ * @suppress {checkTypes}
90
299
  */
91
- this.flyout = null;
92
-
93
- if (this.workspace_.options.maxTrashcanContents <= 0) {
94
- return;
95
- }
96
- // Create flyout options.
97
- const flyoutWorkspaceOptions = new Options(
98
- /** @type {!BlocklyOptions} */
99
- ({
100
- 'scrollbars': true,
101
- 'parentWorkspace': this.workspace_,
102
- 'rtl': this.workspace_.RTL,
103
- 'oneBasedIndex': this.workspace_.options.oneBasedIndex,
104
- 'renderer': this.workspace_.options.renderer,
105
- 'rendererOverrides': this.workspace_.options.rendererOverrides,
106
- 'move': {
107
- 'scrollbars': true,
108
- },
109
- }));
110
- // Create vertical or horizontal flyout.
111
- if (this.workspace_.horizontalLayout) {
112
- flyoutWorkspaceOptions.toolboxPosition =
113
- this.workspace_.toolboxPosition === toolbox.Position.TOP ?
114
- toolbox.Position.BOTTOM :
115
- toolbox.Position.TOP;
116
- const HorizontalFlyout = registry.getClassFromOptions(
117
- registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, this.workspace_.options,
118
- true);
119
- this.flyout = new HorizontalFlyout(flyoutWorkspaceOptions);
120
- } else {
121
- flyoutWorkspaceOptions.toolboxPosition =
122
- this.workspace_.toolboxPosition === toolbox.Position.RIGHT ?
123
- toolbox.Position.LEFT :
124
- toolbox.Position.RIGHT;
125
- const VerticalFlyout = registry.getClassFromOptions(
126
- registry.Type.FLYOUTS_VERTICAL_TOOLBOX, this.workspace_.options, true);
127
- this.flyout = new VerticalFlyout(flyoutWorkspaceOptions);
300
+ dispose() {
301
+ this.workspace_.getComponentManager().removeComponent('trashcan');
302
+ if (this.svgGroup_) {
303
+ dom.removeNode(this.svgGroup_);
304
+ this.svgGroup_ = null;
305
+ }
306
+ this.svgLid_ = null;
307
+ this.workspace_ = null;
308
+ clearTimeout(this.lidTask_);
128
309
  }
129
- this.workspace_.addChangeListener(this.onDelete_.bind(this));
130
- };
131
- object.inherits(Trashcan, DeleteArea);
132
-
133
- /**
134
- * Width of both the trash can and lid images.
135
- */
136
- const WIDTH = 47;
137
-
138
- /**
139
- * Height of the trashcan image (minus lid).
140
- */
141
- const BODY_HEIGHT = 44;
142
-
143
- /**
144
- * Height of the lid image.
145
- */
146
- const LID_HEIGHT = 16;
147
-
148
- /**
149
- * Distance between trashcan and bottom or top edge of workspace.
150
- */
151
- const MARGIN_VERTICAL = 20;
152
310
 
153
- /**
154
- * Distance between trashcan and right or left edge of workspace.
155
- */
156
- const MARGIN_HORIZONTAL = 20;
157
-
158
- /**
159
- * Extent of hotspot on all sides beyond the size of the image.
160
- */
161
- const MARGIN_HOTSPOT = 10;
311
+ /**
312
+ * Whether the trashcan has contents.
313
+ * @return {boolean} True if the trashcan has contents.
314
+ * @private
315
+ */
316
+ hasContents_() {
317
+ return !!this.contents_.length;
318
+ }
162
319
 
163
- /**
164
- * Location of trashcan in sprite image.
165
- */
166
- const SPRITE_LEFT = 0;
320
+ /**
321
+ * Returns true if the trashcan contents-flyout is currently open.
322
+ * @return {boolean} True if the trashcan contents-flyout is currently open.
323
+ */
324
+ contentsIsOpen() {
325
+ return !!this.flyout && this.flyout.isVisible();
326
+ }
167
327
 
168
- /**
169
- * Location of trashcan in sprite image.
170
- */
171
- const SPRITE_TOP = 32;
328
+ /**
329
+ * Opens the trashcan flyout.
330
+ */
331
+ openFlyout() {
332
+ if (this.contentsIsOpen()) {
333
+ return;
334
+ }
335
+ const contents = this.contents_.map(function(string) {
336
+ return JSON.parse(string);
337
+ });
338
+ this.flyout.show(contents);
339
+ this.fireUiEvent_(true);
340
+ }
172
341
 
173
- /**
174
- * The openness of the lid when the trashcan contains blocks.
175
- * (0.0 = closed, 1.0 = open)
176
- */
177
- const HAS_BLOCKS_LID_ANGLE = 0.1;
342
+ /**
343
+ * Closes the trashcan flyout.
344
+ */
345
+ closeFlyout() {
346
+ if (!this.contentsIsOpen()) {
347
+ return;
348
+ }
349
+ this.flyout.hide();
350
+ this.fireUiEvent_(false);
351
+ this.workspace_.recordDragTargets();
352
+ }
178
353
 
179
- /**
180
- * The length of the lid open/close animation in milliseconds.
181
- */
182
- const ANIMATION_LENGTH = 80;
354
+ /**
355
+ * Hides the component. Called in WorkspaceSvg.hideChaff.
356
+ * @param {boolean} onlyClosePopups Whether only popups should be closed.
357
+ * Flyouts should not be closed if this is true.
358
+ */
359
+ autoHide(onlyClosePopups) {
360
+ // For now the trashcan flyout always autocloses because it overlays the
361
+ // trashcan UI (no trashcan to click to close it).
362
+ if (!onlyClosePopups && this.flyout) {
363
+ this.closeFlyout();
364
+ }
365
+ }
183
366
 
184
- /**
185
- * The number of frames in the animation.
186
- */
187
- const ANIMATION_FRAMES = 4;
367
+ /**
368
+ * Empties the trashcan's contents. If the contents-flyout is currently open
369
+ * it will be closed.
370
+ */
371
+ emptyContents() {
372
+ if (!this.hasContents_()) {
373
+ return;
374
+ }
375
+ this.contents_.length = 0;
376
+ this.setMinOpenness_(0);
377
+ this.closeFlyout();
378
+ }
188
379
 
189
- /**
190
- * The minimum (resting) opacity of the trashcan and lid.
191
- */
192
- const OPACITY_MIN = 0.4;
380
+ /**
381
+ * Positions the trashcan.
382
+ * It is positioned in the opposite corner to the corner the
383
+ * categories/toolbox starts at.
384
+ * @param {!MetricsManager.UiMetrics} metrics The workspace metrics.
385
+ * @param {!Array<!Rect>} savedPositions List of rectangles that
386
+ * are already on the workspace.
387
+ */
388
+ position(metrics, savedPositions) {
389
+ // Not yet initialized.
390
+ if (!this.initialized_) {
391
+ return;
392
+ }
193
393
 
194
- /**
195
- * The maximum (hovered) opacity of the trashcan and lid.
196
- */
197
- const OPACITY_MAX = 0.8;
394
+ const cornerPosition =
395
+ uiPosition.getCornerOppositeToolbox(this.workspace_, metrics);
396
+
397
+ const height = BODY_HEIGHT + LID_HEIGHT;
398
+ const startRect = uiPosition.getStartPositionRect(
399
+ cornerPosition, new Size(WIDTH, height), MARGIN_HORIZONTAL,
400
+ MARGIN_VERTICAL, metrics, this.workspace_);
401
+
402
+ const verticalPosition = cornerPosition.vertical;
403
+ const bumpDirection = verticalPosition === uiPosition.verticalPosition.TOP ?
404
+ uiPosition.bumpDirection.DOWN :
405
+ uiPosition.bumpDirection.UP;
406
+ const positionRect = uiPosition.bumpPositionRect(
407
+ startRect, MARGIN_VERTICAL, bumpDirection, savedPositions);
408
+
409
+ this.top_ = positionRect.top;
410
+ this.left_ = positionRect.left;
411
+ this.svgGroup_.setAttribute(
412
+ 'transform', 'translate(' + this.left_ + ',' + this.top_ + ')');
413
+ }
198
414
 
199
- /**
200
- * The maximum angle the trashcan lid can opens to. At the end of the open
201
- * animation the lid will be open to this angle.
202
- */
203
- const MAX_LID_ANGLE = 45;
415
+ /**
416
+ * Returns the bounding rectangle of the UI element in pixel units relative to
417
+ * the Blockly injection div.
418
+ * @return {?Rect} The UI elements's bounding box. Null if
419
+ * bounding box should be ignored by other UI elements.
420
+ */
421
+ getBoundingRectangle() {
422
+ const bottom = this.top_ + BODY_HEIGHT + LID_HEIGHT;
423
+ const right = this.left_ + WIDTH;
424
+ return new Rect(this.top_, bottom, this.left_, right);
425
+ }
204
426
 
205
- /**
206
- * Current open/close state of the lid.
207
- * @type {boolean}
208
- */
209
- Trashcan.prototype.isLidOpen = false;
427
+ /**
428
+ * Returns the bounding rectangle of the drag target area in pixel units
429
+ * relative to viewport.
430
+ * @return {?Rect} The component's bounding box. Null if drag
431
+ * target area should be ignored.
432
+ */
433
+ getClientRect() {
434
+ if (!this.svgGroup_) {
435
+ return null;
436
+ }
210
437
 
211
- /**
212
- * The minimum openness of the lid. Used to indicate if the trashcan contains
213
- * blocks.
214
- * @type {number}
215
- * @private
216
- */
217
- Trashcan.prototype.minOpenness_ = 0;
438
+ const trashRect = this.svgGroup_.getBoundingClientRect();
439
+ const top = trashRect.top + SPRITE_TOP - MARGIN_HOTSPOT;
440
+ const bottom = top + LID_HEIGHT + BODY_HEIGHT + 2 * MARGIN_HOTSPOT;
441
+ const left = trashRect.left + SPRITE_LEFT - MARGIN_HOTSPOT;
442
+ const right = left + WIDTH + 2 * MARGIN_HOTSPOT;
443
+ return new Rect(top, bottom, left, right);
444
+ }
218
445
 
219
- /**
220
- * The SVG group containing the trash can.
221
- * @type {SVGElement}
222
- * @private
223
- */
224
- Trashcan.prototype.svgGroup_ = null;
446
+ /**
447
+ * Handles when a cursor with a block or bubble is dragged over this drag
448
+ * target.
449
+ * @param {!IDraggable} _dragElement The block or bubble currently being
450
+ * dragged.
451
+ * @override
452
+ */
453
+ onDragOver(_dragElement) {
454
+ this.setLidOpen(this.wouldDelete_);
455
+ }
225
456
 
226
- /**
227
- * The SVG image element of the trash can lid.
228
- * @type {SVGElement}
229
- * @private
230
- */
231
- Trashcan.prototype.svgLid_ = null;
457
+ /**
458
+ * Handles when a cursor with a block or bubble exits this drag target.
459
+ * @param {!IDraggable} _dragElement The block or bubble currently being
460
+ * dragged.
461
+ * @override
462
+ */
463
+ onDragExit(_dragElement) {
464
+ this.setLidOpen(false);
465
+ }
232
466
 
233
- /**
234
- * Task ID of opening/closing animation.
235
- * @type {number}
236
- * @private
237
- */
238
- Trashcan.prototype.lidTask_ = 0;
467
+ /**
468
+ * Handles when a block or bubble is dropped on this component.
469
+ * Should not handle delete here.
470
+ * @param {!IDraggable} _dragElement The block or bubble currently being
471
+ * dragged.
472
+ * @override
473
+ */
474
+ onDrop(_dragElement) {
475
+ setTimeout(this.setLidOpen.bind(this, false), 100);
476
+ }
239
477
 
240
- /**
241
- * Current state of lid opening (0.0 = closed, 1.0 = open).
242
- * @type {number}
243
- * @private
244
- */
245
- Trashcan.prototype.lidOpen_ = 0;
478
+ /**
479
+ * Flip the lid open or shut.
480
+ * @param {boolean} state True if open.
481
+ * @package
482
+ */
483
+ setLidOpen(state) {
484
+ if (this.isLidOpen === state) {
485
+ return;
486
+ }
487
+ clearTimeout(this.lidTask_);
488
+ this.isLidOpen = state;
489
+ this.animateLid_();
490
+ }
246
491
 
247
- /**
248
- * Left coordinate of the trash can.
249
- * @type {number}
250
- * @private
251
- */
252
- Trashcan.prototype.left_ = 0;
492
+ /**
493
+ * Rotate the lid open or closed by one step. Then wait and recurse.
494
+ * @private
495
+ */
496
+ animateLid_() {
497
+ const frames = ANIMATION_FRAMES;
253
498
 
254
- /**
255
- * Top coordinate of the trash can.
256
- * @type {number}
257
- * @private
258
- */
259
- Trashcan.prototype.top_ = 0;
499
+ const delta = 1 / (frames + 1);
500
+ this.lidOpen_ += this.isLidOpen ? delta : -delta;
501
+ this.lidOpen_ = Math.min(Math.max(this.lidOpen_, this.minOpenness_), 1);
260
502
 
261
- /**
262
- * Whether this has been initialized.
263
- * @type {boolean}
264
- * @private
265
- */
266
- Trashcan.prototype.initialized_ = false;
503
+ this.setLidAngle_(this.lidOpen_ * MAX_LID_ANGLE);
267
504
 
268
- /**
269
- * Create the trash can elements.
270
- * @return {!SVGElement} The trash can's SVG group.
271
- */
272
- Trashcan.prototype.createDom = function() {
273
- /* Here's the markup that will be generated:
274
- <g class="blocklyTrash">
275
- <clippath id="blocklyTrashBodyClipPath837493">
276
- <rect width="47" height="45" y="15"></rect>
277
- </clippath>
278
- <image width="64" height="92" y="-32" xlink:href="media/sprites.png"
279
- clip-path="url(#blocklyTrashBodyClipPath837493)"></image>
280
- <clippath id="blocklyTrashLidClipPath837493">
281
- <rect width="47" height="15"></rect>
282
- </clippath>
283
- <image width="84" height="92" y="-32" xlink:href="media/sprites.png"
284
- clip-path="url(#blocklyTrashLidClipPath837493)"></image>
285
- </g>
286
- */
287
- this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyTrash'}, null);
288
- let clip;
289
- const rnd = String(Math.random()).substring(2);
290
- clip = dom.createSvgElement(
291
- Svg.CLIPPATH, {'id': 'blocklyTrashBodyClipPath' + rnd}, this.svgGroup_);
292
- dom.createSvgElement(
293
- Svg.RECT, {'width': WIDTH, 'height': BODY_HEIGHT, 'y': LID_HEIGHT}, clip);
294
- const body = dom.createSvgElement(
295
- Svg.IMAGE, {
296
- 'width': internalConstants.SPRITE.width,
297
- 'x': -SPRITE_LEFT,
298
- 'height': internalConstants.SPRITE.height,
299
- 'y': -SPRITE_TOP,
300
- 'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')',
301
- },
302
- this.svgGroup_);
303
- body.setAttributeNS(
304
- dom.XLINK_NS, 'xlink:href',
305
- this.workspace_.options.pathToMedia + internalConstants.SPRITE.url);
306
-
307
- clip = dom.createSvgElement(
308
- Svg.CLIPPATH, {'id': 'blocklyTrashLidClipPath' + rnd}, this.svgGroup_);
309
- dom.createSvgElement(Svg.RECT, {'width': WIDTH, 'height': LID_HEIGHT}, clip);
310
- this.svgLid_ = dom.createSvgElement(
311
- Svg.IMAGE, {
312
- 'width': internalConstants.SPRITE.width,
313
- 'x': -SPRITE_LEFT,
314
- 'height': internalConstants.SPRITE.height,
315
- 'y': -SPRITE_TOP,
316
- 'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')',
317
- },
318
- this.svgGroup_);
319
- this.svgLid_.setAttributeNS(
320
- dom.XLINK_NS, 'xlink:href',
321
- this.workspace_.options.pathToMedia + internalConstants.SPRITE.url);
322
-
323
- // bindEventWithChecks_ quashes events too aggressively. See:
324
- // https://groups.google.com/forum/#!topic/blockly/QF4yB9Wx00s
325
- // Using bindEventWithChecks_ for blocking mousedown causes issue in mobile.
326
- // See #4303
327
- browserEvents.bind(
328
- this.svgGroup_, 'mousedown', this, this.blockMouseDownWhenOpenable_);
329
- browserEvents.bind(this.svgGroup_, 'mouseup', this, this.click);
330
- // Bind to body instead of this.svgGroup_ so that we don't get lid jitters
331
- browserEvents.bind(body, 'mouseover', this, this.mouseOver_);
332
- browserEvents.bind(body, 'mouseout', this, this.mouseOut_);
333
- this.animateLid_();
334
- return this.svgGroup_;
335
- };
505
+ // Linear interpolation between min and max.
506
+ const opacity = OPACITY_MIN + this.lidOpen_ * (OPACITY_MAX - OPACITY_MIN);
507
+ this.svgGroup_.style.opacity = opacity;
336
508
 
337
- /**
338
- * Initializes the trash can.
339
- */
340
- Trashcan.prototype.init = function() {
341
- if (this.workspace_.options.maxTrashcanContents > 0) {
342
- dom.insertAfter(
343
- this.flyout.createDom(Svg.SVG), this.workspace_.getParentSvg());
344
- this.flyout.init(this.workspace_);
509
+ if (this.lidOpen_ > this.minOpenness_ && this.lidOpen_ < 1) {
510
+ this.lidTask_ =
511
+ setTimeout(this.animateLid_.bind(this), ANIMATION_LENGTH / frames);
512
+ }
345
513
  }
346
- this.workspace_.getComponentManager().addComponent({
347
- component: this,
348
- weight: 1,
349
- capabilities: [
350
- ComponentManager.Capability.AUTOHIDEABLE,
351
- ComponentManager.Capability.DELETE_AREA,
352
- ComponentManager.Capability.DRAG_TARGET,
353
- ComponentManager.Capability.POSITIONABLE,
354
- ],
355
- });
356
- this.initialized_ = true;
357
- this.setLidOpen(false);
358
- };
359
514
 
360
- /**
361
- * Dispose of this trash can.
362
- * Unlink from all DOM elements to prevent memory leaks.
363
- * @suppress {checkTypes}
364
- */
365
- Trashcan.prototype.dispose = function() {
366
- this.workspace_.getComponentManager().removeComponent('trashcan');
367
- if (this.svgGroup_) {
368
- dom.removeNode(this.svgGroup_);
369
- this.svgGroup_ = null;
515
+ /**
516
+ * Set the angle of the trashcan's lid.
517
+ * @param {number} lidAngle The angle at which to set the lid.
518
+ * @private
519
+ */
520
+ setLidAngle_(lidAngle) {
521
+ const openAtRight =
522
+ this.workspace_.toolboxPosition === toolbox.Position.RIGHT ||
523
+ (this.workspace_.horizontalLayout && this.workspace_.RTL);
524
+ this.svgLid_.setAttribute(
525
+ 'transform',
526
+ 'rotate(' + (openAtRight ? -lidAngle : lidAngle) + ',' +
527
+ (openAtRight ? 4 : WIDTH - 4) + ',' + (LID_HEIGHT - 2) + ')');
370
528
  }
371
- this.svgLid_ = null;
372
- this.workspace_ = null;
373
- clearTimeout(this.lidTask_);
374
- };
375
-
376
- /**
377
- * Whether the trashcan has contents.
378
- * @return {boolean} True if the trashcan has contents.
379
- * @private
380
- */
381
- Trashcan.prototype.hasContents_ = function() {
382
- return !!this.contents_.length;
383
- };
384
529
 
385
- /**
386
- * Returns true if the trashcan contents-flyout is currently open.
387
- * @return {boolean} True if the trashcan contents-flyout is currently open.
388
- */
389
- Trashcan.prototype.contentsIsOpen = function() {
390
- return !!this.flyout && this.flyout.isVisible();
391
- };
392
-
393
- /**
394
- * Opens the trashcan flyout.
395
- */
396
- Trashcan.prototype.openFlyout = function() {
397
- if (this.contentsIsOpen()) {
398
- return;
530
+ /**
531
+ * Sets the minimum openness of the trashcan lid. If the lid is currently
532
+ * closed, this will update lid's position.
533
+ * @param {number} newMin The new minimum openness of the lid. Should be
534
+ * between 0 and 1.
535
+ * @private
536
+ */
537
+ setMinOpenness_(newMin) {
538
+ this.minOpenness_ = newMin;
539
+ if (!this.isLidOpen) {
540
+ this.setLidAngle_(newMin * MAX_LID_ANGLE);
541
+ }
399
542
  }
400
- const contents = this.contents_.map(function(string) {
401
- return JSON.parse(string);
402
- });
403
- this.flyout.show(contents);
404
- this.fireUiEvent_(true);
405
- };
406
543
 
407
- /**
408
- * Closes the trashcan flyout.
409
- */
410
- Trashcan.prototype.closeFlyout = function() {
411
- if (!this.contentsIsOpen()) {
412
- return;
544
+ /**
545
+ * Flip the lid shut.
546
+ * Called externally after a drag.
547
+ */
548
+ closeLid() {
549
+ this.setLidOpen(false);
413
550
  }
414
- this.flyout.hide();
415
- this.fireUiEvent_(false);
416
- this.workspace_.recordDragTargets();
417
- };
418
551
 
419
- /**
420
- * Hides the component. Called in WorkspaceSvg.hideChaff.
421
- * @param {boolean} onlyClosePopups Whether only popups should be closed.
422
- * Flyouts should not be closed if this is true.
423
- */
424
- Trashcan.prototype.autoHide = function(onlyClosePopups) {
425
- // For now the trashcan flyout always autocloses because it overlays the
426
- // trashcan UI (no trashcan to click to close it).
427
- if (!onlyClosePopups && this.flyout) {
428
- this.closeFlyout();
552
+ /**
553
+ * Inspect the contents of the trash.
554
+ */
555
+ click() {
556
+ if (!this.hasContents_()) {
557
+ return;
558
+ }
559
+ this.openFlyout();
429
560
  }
430
- };
431
561
 
432
- /**
433
- * Empties the trashcan's contents. If the contents-flyout is currently open
434
- * it will be closed.
435
- */
436
- Trashcan.prototype.emptyContents = function() {
437
- if (!this.hasContents_()) {
438
- return;
562
+ /**
563
+ * Fires a UI event for trashcan flyout open or close.
564
+ * @param {boolean} trashcanOpen Whether the flyout is opening.
565
+ * @private
566
+ */
567
+ fireUiEvent_(trashcanOpen) {
568
+ const uiEvent = new (eventUtils.get(eventUtils.TRASHCAN_OPEN))(
569
+ trashcanOpen, this.workspace_.id);
570
+ eventUtils.fire(uiEvent);
439
571
  }
440
- this.contents_.length = 0;
441
- this.setMinOpenness_(0);
442
- this.closeFlyout();
443
- };
444
572
 
445
- /**
446
- * Positions the trashcan.
447
- * It is positioned in the opposite corner to the corner the
448
- * categories/toolbox starts at.
449
- * @param {!MetricsManager.UiMetrics} metrics The workspace metrics.
450
- * @param {!Array<!Rect>} savedPositions List of rectangles that
451
- * are already on the workspace.
452
- */
453
- Trashcan.prototype.position = function(metrics, savedPositions) {
454
- // Not yet initialized.
455
- if (!this.initialized_) {
456
- return;
573
+ /**
574
+ * Prevents a workspace scroll and click event if the trashcan has blocks.
575
+ * @param {!Event} e A mouse down event.
576
+ * @private
577
+ */
578
+ blockMouseDownWhenOpenable_(e) {
579
+ if (!this.contentsIsOpen() && this.hasContents_()) {
580
+ e.stopPropagation(); // Don't start a workspace scroll.
581
+ }
457
582
  }
458
583
 
459
- const cornerPosition =
460
- uiPosition.getCornerOppositeToolbox(this.workspace_, metrics);
461
-
462
- const height = BODY_HEIGHT + LID_HEIGHT;
463
- const startRect = uiPosition.getStartPositionRect(
464
- cornerPosition, new Size(WIDTH, height), MARGIN_HORIZONTAL,
465
- MARGIN_VERTICAL, metrics, this.workspace_);
466
-
467
- const verticalPosition = cornerPosition.vertical;
468
- const bumpDirection = verticalPosition === uiPosition.verticalPosition.TOP ?
469
- uiPosition.bumpDirection.DOWN :
470
- uiPosition.bumpDirection.UP;
471
- const positionRect = uiPosition.bumpPositionRect(
472
- startRect, MARGIN_VERTICAL, bumpDirection, savedPositions);
473
-
474
- this.top_ = positionRect.top;
475
- this.left_ = positionRect.left;
476
- this.svgGroup_.setAttribute(
477
- 'transform', 'translate(' + this.left_ + ',' + this.top_ + ')');
478
- };
584
+ /**
585
+ * Indicate that the trashcan can be clicked (by opening it) if it has blocks.
586
+ * @private
587
+ */
588
+ mouseOver_() {
589
+ if (this.hasContents_()) {
590
+ this.setLidOpen(true);
591
+ }
592
+ }
479
593
 
480
- /**
481
- * Returns the bounding rectangle of the UI element in pixel units relative to
482
- * the Blockly injection div.
483
- * @return {?Rect} The UI elements's bounding box. Null if
484
- * bounding box should be ignored by other UI elements.
485
- */
486
- Trashcan.prototype.getBoundingRectangle = function() {
487
- const bottom = this.top_ + BODY_HEIGHT + LID_HEIGHT;
488
- const right = this.left_ + WIDTH;
489
- return new Rect(this.top_, bottom, this.left_, right);
490
- };
594
+ /**
595
+ * Close the lid of the trashcan if it was open (Vis. it was indicating it had
596
+ * blocks).
597
+ * @private
598
+ */
599
+ mouseOut_() {
600
+ // No need to do a .hasBlocks check here because if it doesn't the trashcan
601
+ // won't be open in the first place, and setOpen won't run.
602
+ this.setLidOpen(false);
603
+ }
491
604
 
492
- /**
493
- * Returns the bounding rectangle of the drag target area in pixel units
494
- * relative to viewport.
495
- * @return {?Rect} The component's bounding box. Null if drag
496
- * target area should be ignored.
497
- */
498
- Trashcan.prototype.getClientRect = function() {
499
- if (!this.svgGroup_) {
500
- return null;
605
+ /**
606
+ * Handle a BLOCK_DELETE event. Adds deleted blocks oldXml to the content
607
+ * array.
608
+ * @param {!Abstract} event Workspace event.
609
+ * @private
610
+ */
611
+ onDelete_(event) {
612
+ if (this.workspace_.options.maxTrashcanContents <= 0 ||
613
+ event.type !== eventUtils.BLOCK_DELETE) {
614
+ return;
615
+ }
616
+ const deleteEvent = /** @type {!BlockDelete} */ (event);
617
+ if (event.type === eventUtils.BLOCK_DELETE && !deleteEvent.wasShadow) {
618
+ const cleanedJson = this.cleanBlockJson_(deleteEvent.oldJson);
619
+ if (this.contents_.indexOf(cleanedJson) !== -1) {
620
+ return;
621
+ }
622
+ this.contents_.unshift(cleanedJson);
623
+ while (this.contents_.length >
624
+ this.workspace_.options.maxTrashcanContents) {
625
+ this.contents_.pop();
626
+ }
627
+
628
+ this.setMinOpenness_(HAS_BLOCKS_LID_ANGLE);
629
+ }
501
630
  }
502
631
 
503
- const trashRect = this.svgGroup_.getBoundingClientRect();
504
- const top = trashRect.top + SPRITE_TOP - MARGIN_HOTSPOT;
505
- const bottom = top + LID_HEIGHT + BODY_HEIGHT + 2 * MARGIN_HOTSPOT;
506
- const left = trashRect.left + SPRITE_LEFT - MARGIN_HOTSPOT;
507
- const right = left + WIDTH + 2 * MARGIN_HOTSPOT;
508
- return new Rect(top, bottom, left, right);
509
- };
632
+ /**
633
+ * Converts JSON representing a block into text that can be stored in the
634
+ * content array.
635
+ * @param {!blocks.State} json A JSON representation of
636
+ * a block's state.
637
+ * @return {string} Text representing the JSON, cleaned of all unnecessary
638
+ * attributes.
639
+ * @private
640
+ */
641
+ cleanBlockJson_(json) {
642
+ // Create a deep copy.
643
+ json = /** @type {!blocks.State} */ (JSON.parse(JSON.stringify(json)));
644
+
645
+ /**
646
+ * Reshape JSON into a nicer format.
647
+ * @param {!blocks.State} json The JSON to clean.
648
+ */
649
+ function cleanRec(json) {
650
+ if (!json) {
651
+ return;
652
+ }
653
+
654
+ delete json['id'];
655
+ delete json['x'];
656
+ delete json['y'];
657
+ delete json['enabled'];
658
+
659
+ if (json['icons'] && json['icons']['comment']) {
660
+ const comment = json['icons']['comment'];
661
+ delete comment['height'];
662
+ delete comment['width'];
663
+ delete comment['pinned'];
664
+ }
665
+
666
+ const inputs = json['inputs'];
667
+ for (const name in inputs) {
668
+ const input = inputs[name];
669
+ cleanRec(input['block']);
670
+ cleanRec(input['shadow']);
671
+ }
672
+ if (json['next']) {
673
+ const next = json['next'];
674
+ cleanRec(next['block']);
675
+ cleanRec(next['shadow']);
676
+ }
677
+ }
510
678
 
511
- /**
512
- * Handles when a cursor with a block or bubble is dragged over this drag
513
- * target.
514
- * @param {!IDraggable} _dragElement The block or bubble currently being
515
- * dragged.
516
- * @override
517
- */
518
- Trashcan.prototype.onDragOver = function(_dragElement) {
519
- this.setLidOpen(this.wouldDelete_);
520
- };
679
+ cleanRec(json);
680
+ json['kind'] = 'BLOCK';
681
+ return JSON.stringify(json);
682
+ }
683
+ }
521
684
 
522
685
  /**
523
- * Handles when a cursor with a block or bubble exits this drag target.
524
- * @param {!IDraggable} _dragElement The block or bubble currently being
525
- * dragged.
526
- * @override
686
+ * Width of both the trash can and lid images.
527
687
  */
528
- Trashcan.prototype.onDragExit = function(_dragElement) {
529
- this.setLidOpen(false);
530
- };
688
+ const WIDTH = 47;
531
689
 
532
690
  /**
533
- * Handles when a block or bubble is dropped on this component.
534
- * Should not handle delete here.
535
- * @param {!IDraggable} _dragElement The block or bubble currently being
536
- * dragged.
537
- * @override
691
+ * Height of the trashcan image (minus lid).
538
692
  */
539
- Trashcan.prototype.onDrop = function(_dragElement) {
540
- setTimeout(this.setLidOpen.bind(this, false), 100);
541
- };
693
+ const BODY_HEIGHT = 44;
542
694
 
543
695
  /**
544
- * Flip the lid open or shut.
545
- * @param {boolean} state True if open.
546
- * @package
696
+ * Height of the lid image.
547
697
  */
548
- Trashcan.prototype.setLidOpen = function(state) {
549
- if (this.isLidOpen === state) {
550
- return;
551
- }
552
- clearTimeout(this.lidTask_);
553
- this.isLidOpen = state;
554
- this.animateLid_();
555
- };
698
+ const LID_HEIGHT = 16;
556
699
 
557
700
  /**
558
- * Rotate the lid open or closed by one step. Then wait and recurse.
559
- * @private
701
+ * Distance between trashcan and bottom or top edge of workspace.
560
702
  */
561
- Trashcan.prototype.animateLid_ = function() {
562
- const frames = ANIMATION_FRAMES;
563
-
564
- const delta = 1 / (frames + 1);
565
- this.lidOpen_ += this.isLidOpen ? delta : -delta;
566
- this.lidOpen_ = Math.min(Math.max(this.lidOpen_, this.minOpenness_), 1);
567
-
568
- this.setLidAngle_(this.lidOpen_ * MAX_LID_ANGLE);
569
-
570
- // Linear interpolation between min and max.
571
- const opacity = OPACITY_MIN + this.lidOpen_ * (OPACITY_MAX - OPACITY_MIN);
572
- this.svgGroup_.style.opacity = opacity;
573
-
574
- if (this.lidOpen_ > this.minOpenness_ && this.lidOpen_ < 1) {
575
- this.lidTask_ =
576
- setTimeout(this.animateLid_.bind(this), ANIMATION_LENGTH / frames);
577
- }
578
- };
703
+ const MARGIN_VERTICAL = 20;
579
704
 
580
705
  /**
581
- * Set the angle of the trashcan's lid.
582
- * @param {number} lidAngle The angle at which to set the lid.
583
- * @private
706
+ * Distance between trashcan and right or left edge of workspace.
584
707
  */
585
- Trashcan.prototype.setLidAngle_ = function(lidAngle) {
586
- const openAtRight =
587
- this.workspace_.toolboxPosition === toolbox.Position.RIGHT ||
588
- (this.workspace_.horizontalLayout && this.workspace_.RTL);
589
- this.svgLid_.setAttribute(
590
- 'transform',
591
- 'rotate(' + (openAtRight ? -lidAngle : lidAngle) + ',' +
592
- (openAtRight ? 4 : WIDTH - 4) + ',' + (LID_HEIGHT - 2) + ')');
593
- };
708
+ const MARGIN_HORIZONTAL = 20;
594
709
 
595
710
  /**
596
- * Sets the minimum openness of the trashcan lid. If the lid is currently
597
- * closed, this will update lid's position.
598
- * @param {number} newMin The new minimum openness of the lid. Should be between
599
- * 0 and 1.
600
- * @private
711
+ * Extent of hotspot on all sides beyond the size of the image.
601
712
  */
602
- Trashcan.prototype.setMinOpenness_ = function(newMin) {
603
- this.minOpenness_ = newMin;
604
- if (!this.isLidOpen) {
605
- this.setLidAngle_(newMin * MAX_LID_ANGLE);
606
- }
607
- };
713
+ const MARGIN_HOTSPOT = 10;
608
714
 
609
715
  /**
610
- * Flip the lid shut.
611
- * Called externally after a drag.
716
+ * Location of trashcan in sprite image.
612
717
  */
613
- Trashcan.prototype.closeLid = function() {
614
- this.setLidOpen(false);
615
- };
718
+ const SPRITE_LEFT = 0;
616
719
 
617
720
  /**
618
- * Inspect the contents of the trash.
721
+ * Location of trashcan in sprite image.
619
722
  */
620
- Trashcan.prototype.click = function() {
621
- if (!this.hasContents_()) {
622
- return;
623
- }
624
- this.openFlyout();
625
- };
723
+ const SPRITE_TOP = 32;
626
724
 
627
725
  /**
628
- * Fires a UI event for trashcan flyout open or close.
629
- * @param {boolean} trashcanOpen Whether the flyout is opening.
630
- * @private
726
+ * The openness of the lid when the trashcan contains blocks.
727
+ * (0.0 = closed, 1.0 = open)
631
728
  */
632
- Trashcan.prototype.fireUiEvent_ = function(trashcanOpen) {
633
- const uiEvent = new (eventUtils.get(eventUtils.TRASHCAN_OPEN))(
634
- trashcanOpen, this.workspace_.id);
635
- eventUtils.fire(uiEvent);
636
- };
729
+ const HAS_BLOCKS_LID_ANGLE = 0.1;
637
730
 
638
731
  /**
639
- * Prevents a workspace scroll and click event if the trashcan has blocks.
640
- * @param {!Event} e A mouse down event.
641
- * @private
732
+ * The length of the lid open/close animation in milliseconds.
642
733
  */
643
- Trashcan.prototype.blockMouseDownWhenOpenable_ = function(e) {
644
- if (!this.contentsIsOpen() && this.hasContents_()) {
645
- e.stopPropagation(); // Don't start a workspace scroll.
646
- }
647
- };
734
+ const ANIMATION_LENGTH = 80;
648
735
 
649
736
  /**
650
- * Indicate that the trashcan can be clicked (by opening it) if it has blocks.
651
- * @private
737
+ * The number of frames in the animation.
652
738
  */
653
- Trashcan.prototype.mouseOver_ = function() {
654
- if (this.hasContents_()) {
655
- this.setLidOpen(true);
656
- }
657
- };
739
+ const ANIMATION_FRAMES = 4;
658
740
 
659
741
  /**
660
- * Close the lid of the trashcan if it was open (Vis. it was indicating it had
661
- * blocks).
662
- * @private
742
+ * The minimum (resting) opacity of the trashcan and lid.
663
743
  */
664
- Trashcan.prototype.mouseOut_ = function() {
665
- // No need to do a .hasBlocks check here because if it doesn't the trashcan
666
- // won't be open in the first place, and setOpen won't run.
667
- this.setLidOpen(false);
668
- };
744
+ const OPACITY_MIN = 0.4;
669
745
 
670
746
  /**
671
- * Handle a BLOCK_DELETE event. Adds deleted blocks oldXml to the content array.
672
- * @param {!Abstract} event Workspace event.
673
- * @private
747
+ * The maximum (hovered) opacity of the trashcan and lid.
674
748
  */
675
- Trashcan.prototype.onDelete_ = function(event) {
676
- if (this.workspace_.options.maxTrashcanContents <= 0) {
677
- return;
678
- }
679
- if (event.type === eventUtils.BLOCK_DELETE && !event.wasShadow) {
680
- const cleanedJson = this.cleanBlockJson_(event.oldJson);
681
- if (this.contents_.indexOf(cleanedJson) !== -1) {
682
- return;
683
- }
684
- this.contents_.unshift(cleanedJson);
685
- while (this.contents_.length >
686
- this.workspace_.options.maxTrashcanContents) {
687
- this.contents_.pop();
688
- }
689
-
690
- this.setMinOpenness_(HAS_BLOCKS_LID_ANGLE);
691
- }
692
- };
749
+ const OPACITY_MAX = 0.8;
693
750
 
694
751
  /**
695
- * Converts JSON representing a block into text that can be stored in the
696
- * content array.
697
- * @param {!blocks.State} json A JSON representation of
698
- * a block's state.
699
- * @return {string} Text representing the JSON, cleaned of all unnecessary
700
- * attributes.
701
- * @private
752
+ * The maximum angle the trashcan lid can opens to. At the end of the open
753
+ * animation the lid will be open to this angle.
702
754
  */
703
- Trashcan.prototype.cleanBlockJson_ = function(json) {
704
- // Create a deep copy.
705
- json = /** @type {!blocks.State} */ (JSON.parse(JSON.stringify(json)));
706
-
707
- /**
708
- * Reshape JSON into a nicer format.
709
- * @param {!blocks.State} json The JSON to clean.
710
- */
711
- function cleanRec(json) {
712
- if (!json) {
713
- return;
714
- }
715
-
716
- delete json['id'];
717
- delete json['x'];
718
- delete json['y'];
719
- delete json['enabled'];
720
-
721
- if (json['icons'] && json['icons']['comment']) {
722
- const comment = json['icons']['comment'];
723
- delete comment['height'];
724
- delete comment['width'];
725
- delete comment['pinned'];
726
- }
727
-
728
- const inputs = json['inputs'];
729
- for (const name in inputs) {
730
- const input = inputs[name];
731
- cleanRec(input['block']);
732
- cleanRec(input['shadow']);
733
- }
734
- if (json['next']) {
735
- const next = json['next'];
736
- cleanRec(next['block']);
737
- cleanRec(next['shadow']);
738
- }
739
- }
740
-
741
- cleanRec(json);
742
- json['kind'] = 'BLOCK';
743
- return JSON.stringify(json);
744
- };
755
+ const MAX_LID_ANGLE = 45;
745
756
 
746
757
  exports.Trashcan = Trashcan;