blockly 7.20211209.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blockly.d.ts +18963 -18432
- package/blockly.min.js +852 -844
- package/blockly_compressed.js +669 -664
- package/blockly_compressed.js.map +1 -1
- package/blocks/blocks.js +47 -0
- package/blocks/colour.js +13 -3
- package/blocks/lists.js +22 -13
- package/blocks/logic.js +13 -3
- package/blocks/loops.js +24 -11
- package/blocks/math.js +12 -3
- package/blocks/procedures.js +41 -27
- package/blocks/text.js +22 -13
- package/blocks/variables.js +14 -3
- package/blocks/variables_dynamic.js +13 -3
- package/blocks_compressed.js +146 -141
- package/blocks_compressed.js.map +1 -1
- package/core/block.js +1869 -1814
- package/core/block_drag_surface.js +201 -200
- package/core/block_dragger.js +377 -373
- package/core/block_svg.js +1593 -1479
- package/core/blockly.js +8 -22
- package/core/blocks.js +9 -2
- package/core/browser_events.js +22 -5
- package/core/bubble.js +841 -797
- package/core/bubble_dragger.js +213 -206
- package/core/bump_objects.js +2 -2
- package/core/clipboard.js +9 -9
- package/core/comment.js +353 -332
- package/core/common.js +46 -17
- package/core/component_manager.js +181 -174
- package/core/config.js +87 -0
- package/core/connection.js +595 -584
- package/core/connection_checker.js +242 -244
- package/core/connection_db.js +235 -230
- package/core/contextmenu.js +9 -6
- package/core/contextmenu_items.js +1 -2
- package/core/contextmenu_registry.js +93 -89
- package/core/css.js +474 -474
- package/core/delete_area.js +45 -42
- package/core/drag_target.js +57 -56
- package/core/dropdowndiv.js +153 -163
- package/core/events/events.js +2 -2
- package/core/events/events_abstract.js +89 -77
- package/core/events/events_block_base.js +37 -36
- package/core/events/events_block_change.js +130 -124
- package/core/events/events_block_create.js +73 -71
- package/core/events/events_block_delete.js +84 -82
- package/core/events/events_block_drag.js +50 -49
- package/core/events/events_block_move.js +147 -140
- package/core/events/events_bubble_open.js +51 -50
- package/core/events/events_click.js +48 -44
- package/core/events/events_comment_base.js +72 -69
- package/core/events/events_comment_change.js +63 -61
- package/core/events/events_comment_create.js +44 -42
- package/core/events/events_comment_delete.js +42 -40
- package/core/events/events_comment_move.js +106 -104
- package/core/events/events_marker_move.js +65 -64
- package/core/events/events_selected.js +46 -45
- package/core/events/events_theme_change.js +36 -35
- package/core/events/events_toolbox_item_select.js +46 -45
- package/core/events/events_trashcan_open.js +37 -36
- package/core/events/events_ui.js +47 -46
- package/core/events/events_ui_base.js +30 -29
- package/core/events/events_var_base.js +37 -36
- package/core/events/events_var_create.js +50 -48
- package/core/events/events_var_delete.js +50 -48
- package/core/events/events_var_rename.js +51 -49
- package/core/events/events_viewport.js +66 -65
- package/core/events/utils.js +29 -14
- package/core/events/workspace_events.js +49 -55
- package/core/extensions.js +4 -3
- package/core/field.js +1061 -997
- package/core/field_angle.js +462 -442
- package/core/field_checkbox.js +194 -182
- package/core/field_colour.js +519 -505
- package/core/field_dropdown.js +617 -598
- package/core/field_image.js +229 -220
- package/core/field_label.js +102 -91
- package/core/field_label_serializable.js +42 -41
- package/core/field_multilineinput.js +372 -358
- package/core/field_number.js +272 -253
- package/core/field_textinput.js +499 -467
- package/core/field_variable.js +458 -420
- package/core/flyout_base.js +1005 -952
- package/core/flyout_button.js +277 -260
- package/core/flyout_horizontal.js +304 -302
- package/core/flyout_metrics_manager.js +64 -64
- package/core/flyout_vertical.js +306 -300
- package/core/generator.js +459 -446
- package/core/gesture.js +829 -813
- package/core/grid.js +166 -163
- package/core/icon.js +168 -159
- package/core/inject.js +7 -5
- package/core/input.js +257 -248
- package/core/insertion_marker_manager.js +655 -624
- package/core/internal_constants.js +0 -129
- package/core/keyboard_nav/ast_node.js +605 -596
- package/core/keyboard_nav/basic_cursor.js +166 -165
- package/core/keyboard_nav/cursor.js +99 -97
- package/core/keyboard_nav/marker.js +83 -79
- package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
- package/core/marker_manager.js +153 -141
- package/core/menu.js +377 -372
- package/core/menuitem.js +223 -217
- package/core/metrics_manager.js +403 -390
- package/core/mutator.js +468 -437
- package/core/names.js +229 -188
- package/core/options.js +290 -284
- package/core/procedures.js +29 -17
- package/core/registry.js +19 -16
- package/core/rendered_connection.js +482 -463
- package/core/renderers/common/block_rendering.js +9 -3
- package/core/renderers/common/constants.js +1119 -1112
- package/core/renderers/common/debug.js +14 -0
- package/core/renderers/common/debugger.js +338 -316
- package/core/renderers/common/drawer.js +380 -370
- package/core/renderers/common/i_path_object.js +2 -2
- package/core/renderers/common/info.js +626 -618
- package/core/renderers/common/marker_svg.js +579 -541
- package/core/renderers/common/path_object.js +203 -200
- package/core/renderers/common/renderer.js +220 -218
- package/core/renderers/geras/constants.js +36 -36
- package/core/renderers/geras/drawer.js +155 -147
- package/core/renderers/geras/highlight_constants.js +244 -238
- package/core/renderers/geras/highlighter.js +231 -179
- package/core/renderers/geras/info.js +392 -369
- package/core/renderers/geras/measurables/inline_input.js +25 -19
- package/core/renderers/geras/measurables/statement_input.js +23 -17
- package/core/renderers/geras/path_object.js +106 -121
- package/core/renderers/geras/renderer.js +96 -98
- package/core/renderers/measurables/base.js +30 -18
- package/core/renderers/measurables/bottom_row.js +83 -80
- package/core/renderers/measurables/connection.js +22 -15
- package/core/renderers/measurables/external_value_input.js +35 -22
- package/core/renderers/measurables/field.js +35 -20
- package/core/renderers/measurables/hat.js +18 -13
- package/core/renderers/measurables/icon.js +24 -17
- package/core/renderers/measurables/in_row_spacer.js +15 -13
- package/core/renderers/measurables/inline_input.js +43 -33
- package/core/renderers/measurables/input_connection.js +41 -28
- package/core/renderers/measurables/input_row.js +50 -44
- package/core/renderers/measurables/jagged_edge.js +14 -12
- package/core/renderers/measurables/next_connection.js +16 -14
- package/core/renderers/measurables/output_connection.js +26 -20
- package/core/renderers/measurables/previous_connection.js +16 -15
- package/core/renderers/measurables/round_corner.js +20 -18
- package/core/renderers/measurables/row.js +184 -168
- package/core/renderers/measurables/spacer_row.js +38 -23
- package/core/renderers/measurables/square_corner.js +18 -16
- package/core/renderers/measurables/statement_input.js +23 -20
- package/core/renderers/measurables/top_row.js +88 -85
- package/core/renderers/minimalist/constants.js +8 -7
- package/core/renderers/minimalist/drawer.js +11 -10
- package/core/renderers/minimalist/info.js +18 -18
- package/core/renderers/minimalist/renderer.js +40 -39
- package/core/renderers/thrasos/info.js +258 -248
- package/core/renderers/thrasos/renderer.js +20 -20
- package/core/renderers/zelos/constants.js +898 -873
- package/core/renderers/zelos/drawer.js +186 -169
- package/core/renderers/zelos/info.js +502 -479
- package/core/renderers/zelos/marker_svg.js +129 -115
- package/core/renderers/zelos/measurables/bottom_row.js +31 -30
- package/core/renderers/zelos/measurables/inputs.js +22 -21
- package/core/renderers/zelos/measurables/row_elements.js +14 -13
- package/core/renderers/zelos/measurables/top_row.js +34 -33
- package/core/renderers/zelos/path_object.js +181 -180
- package/core/renderers/zelos/renderer.js +91 -92
- package/core/scrollbar.js +759 -713
- package/core/scrollbar_pair.js +250 -245
- package/core/serialization/blocks.js +19 -9
- package/core/serialization/workspaces.js +3 -2
- package/core/shortcut_registry.js +286 -277
- package/core/sprites.js +31 -0
- package/core/theme.js +135 -141
- package/core/theme_manager.js +147 -143
- package/core/toolbox/category.js +602 -576
- package/core/toolbox/collapsible_category.js +226 -227
- package/core/toolbox/separator.js +70 -61
- package/core/toolbox/toolbox.js +934 -927
- package/core/toolbox/toolbox_item.js +115 -99
- package/core/tooltip.js +108 -35
- package/core/touch.js +8 -3
- package/core/touch_gesture.js +254 -251
- package/core/trashcan.js +606 -595
- package/core/utils/coordinate.js +97 -95
- package/core/utils/dom.js +2 -2
- package/core/utils/global.js +2 -0
- package/core/utils/rect.js +41 -37
- package/core/utils/sentinel.js +25 -0
- package/core/utils/size.js +30 -27
- package/core/utils/svg.js +18 -16
- package/core/variable_map.js +325 -341
- package/core/variable_model.js +55 -54
- package/core/variables.js +9 -2
- package/core/variables_dynamic.js +3 -1
- package/core/warning.js +126 -120
- package/core/widgetdiv.js +4 -4
- package/core/workspace.js +685 -664
- package/core/workspace_audio.js +124 -118
- package/core/workspace_comment.js +308 -298
- package/core/workspace_comment_svg.js +1029 -951
- package/core/workspace_drag_surface_svg.js +147 -140
- package/core/workspace_dragger.js +70 -71
- package/core/workspace_svg.js +2322 -2297
- package/core/xml.js +30 -20
- package/core/zoom_controls.js +431 -439
- package/dart_compressed.js +40 -43
- package/dart_compressed.js.map +1 -1
- package/generators/dart/colour.js +56 -64
- package/generators/dart/lists.js +61 -50
- package/generators/dart/math.js +160 -148
- package/generators/dart/text.js +83 -61
- package/generators/javascript/colour.js +37 -34
- package/generators/javascript/lists.js +50 -43
- package/generators/javascript/math.js +123 -139
- package/generators/javascript/text.js +67 -81
- package/generators/lua/colour.js +25 -23
- package/generators/lua/lists.js +97 -69
- package/generators/lua/logic.js +1 -2
- package/generators/lua/math.js +182 -144
- package/generators/lua/text.js +116 -99
- package/generators/php/colour.js +38 -32
- package/generators/php/lists.js +109 -89
- package/generators/php/math.js +90 -81
- package/generators/php/text.js +63 -61
- package/generators/python/colour.js +18 -18
- package/generators/python/lists.js +38 -30
- package/generators/python/loops.js +12 -8
- package/generators/python/math.js +104 -106
- package/generators/python/text.js +34 -30
- package/javascript_compressed.js +37 -39
- package/javascript_compressed.js.map +1 -1
- package/lua_compressed.js +39 -42
- package/lua_compressed.js.map +1 -1
- package/msg/az.js +2 -2
- package/msg/be.js +4 -4
- package/msg/cs.js +15 -15
- package/msg/de.js +1 -1
- package/msg/diq.js +1 -1
- package/msg/eo.js +1 -1
- package/msg/es.js +1 -1
- package/msg/fa.js +1 -1
- package/msg/fr.js +4 -4
- package/msg/he.js +1 -1
- package/msg/hr.js +2 -2
- package/msg/hy.js +2 -2
- package/msg/id.js +12 -12
- package/msg/inh.js +14 -14
- package/msg/ja.js +7 -7
- package/msg/lv.js +29 -29
- package/msg/pa.js +3 -3
- package/msg/smn.js +436 -0
- package/msg/te.js +1 -1
- package/msg/yue.js +1 -1
- package/msg/zh-hans.js +3 -3
- package/msg/zh-hant.js +3 -3
- package/package.json +7 -6
- package/php_compressed.js +38 -42
- package/php_compressed.js.map +1 -1
- package/python_compressed.js +26 -25
- package/python_compressed.js.map +1 -1
- package/blocks/all.js +0 -23
package/core/flyout_base.js
CHANGED
|
@@ -24,7 +24,6 @@ const common = goog.require('Blockly.common');
|
|
|
24
24
|
const dom = goog.require('Blockly.utils.dom');
|
|
25
25
|
const eventUtils = goog.require('Blockly.Events.utils');
|
|
26
26
|
const idGenerator = goog.require('Blockly.utils.idGenerator');
|
|
27
|
-
const object = goog.require('Blockly.utils.object');
|
|
28
27
|
const toolbox = goog.require('Blockly.utils.toolbox');
|
|
29
28
|
/* eslint-disable-next-line no-unused-vars */
|
|
30
29
|
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
|
@@ -59,1076 +58,1130 @@ goog.require('Blockly.blockRendering');
|
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
60
|
* Class for a flyout.
|
|
62
|
-
* @param {!Options} workspaceOptions Dictionary of options for the
|
|
63
|
-
* workspace.
|
|
64
|
-
* @constructor
|
|
65
61
|
* @abstract
|
|
66
62
|
* @implements {IFlyout}
|
|
67
63
|
* @extends {DeleteArea}
|
|
68
64
|
* @alias Blockly.Flyout
|
|
69
65
|
*/
|
|
70
|
-
|
|
71
|
-
Flyout.superClass_.constructor.call(this);
|
|
72
|
-
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
|
|
73
|
-
|
|
66
|
+
class Flyout extends DeleteArea {
|
|
74
67
|
/**
|
|
75
|
-
* @
|
|
76
|
-
*
|
|
68
|
+
* @param {!Options} workspaceOptions Dictionary of options for the
|
|
69
|
+
* workspace.
|
|
77
70
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
constructor(workspaceOptions) {
|
|
72
|
+
super();
|
|
73
|
+
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @type {!WorkspaceSvg}
|
|
77
|
+
* @protected
|
|
78
|
+
*/
|
|
79
|
+
this.workspace_ = new WorkspaceSvg(workspaceOptions);
|
|
80
|
+
this.workspace_.setMetricsManager(
|
|
81
|
+
new FlyoutMetricsManager(this.workspace_, this));
|
|
82
|
+
|
|
83
|
+
this.workspace_.isFlyout = true;
|
|
84
|
+
// Keep the workspace visibility consistent with the flyout's visibility.
|
|
85
|
+
this.workspace_.setVisible(this.isVisible_);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The unique id for this component that is used to register with the
|
|
89
|
+
* ComponentManager.
|
|
90
|
+
* @type {string}
|
|
91
|
+
*/
|
|
92
|
+
this.id = idGenerator.genUid();
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Is RTL vs LTR.
|
|
96
|
+
* @type {boolean}
|
|
97
|
+
*/
|
|
98
|
+
this.RTL = !!workspaceOptions.RTL;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether the flyout should be laid out horizontally or not.
|
|
102
|
+
* @type {boolean}
|
|
103
|
+
* @package
|
|
104
|
+
*/
|
|
105
|
+
this.horizontalLayout = false;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Position of the toolbox and flyout relative to the workspace.
|
|
109
|
+
* @type {number}
|
|
110
|
+
* @protected
|
|
111
|
+
*/
|
|
112
|
+
this.toolboxPosition_ = workspaceOptions.toolboxPosition;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Opaque data that can be passed to Blockly.unbindEvent_.
|
|
116
|
+
* @type {!Array<!Array>}
|
|
117
|
+
* @private
|
|
118
|
+
*/
|
|
119
|
+
this.eventWrappers_ = [];
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Function that will be registered as a change listener on the workspace
|
|
123
|
+
* to reflow when blocks in the flyout workspace change.
|
|
124
|
+
* @type {?Function}
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
this.reflowWrapper_ = null;
|
|
81
128
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Function that disables blocks in the flyout based on max block counts
|
|
131
|
+
* allowed in the target workspace. Registered as a change listener on the
|
|
132
|
+
* target workspace.
|
|
133
|
+
* @type {?Function}
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
this.filterWrapper_ = null;
|
|
85
137
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
138
|
+
/**
|
|
139
|
+
* List of background mats that lurk behind each block to catch clicks
|
|
140
|
+
* landing in the blocks' lakes and bays.
|
|
141
|
+
* @type {!Array<!SVGElement>}
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
this.mats_ = [];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* List of visible buttons.
|
|
148
|
+
* @type {!Array<!FlyoutButton>}
|
|
149
|
+
* @protected
|
|
150
|
+
*/
|
|
151
|
+
this.buttons_ = [];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* List of event listeners.
|
|
155
|
+
* @type {!Array<!Array>}
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
this.listeners_ = [];
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* List of blocks that should always be disabled.
|
|
162
|
+
* @type {!Array<!Block>}
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
this.permanentlyDisabled_ = [];
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Width of output tab.
|
|
169
|
+
* @type {number}
|
|
170
|
+
* @protected
|
|
171
|
+
* @const
|
|
172
|
+
*/
|
|
173
|
+
this.tabWidth_ = this.workspace_.getRenderer().getConstants().TAB_WIDTH;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The target workspace
|
|
177
|
+
* @type {?WorkspaceSvg}
|
|
178
|
+
* @package
|
|
179
|
+
*/
|
|
180
|
+
this.targetWorkspace = null;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A list of blocks that can be reused.
|
|
184
|
+
* @type {!Array<!BlockSvg>}
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
this.recycledBlocks_ = [];
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Does the flyout automatically close when a block is created?
|
|
191
|
+
* @type {boolean}
|
|
192
|
+
*/
|
|
193
|
+
this.autoClose = true;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Whether the flyout is visible.
|
|
197
|
+
* @type {boolean}
|
|
198
|
+
* @private
|
|
199
|
+
*/
|
|
200
|
+
this.isVisible_ = false;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Whether the workspace containing this flyout is visible.
|
|
204
|
+
* @type {boolean}
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
this.containerVisible_ = true;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A map from blocks to the rects which are beneath them to act as input
|
|
211
|
+
* targets.
|
|
212
|
+
* @type {!WeakMap<!BlockSvg, !SVGElement>}
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
this.rectMap_ = new WeakMap();
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Corner radius of the flyout background.
|
|
219
|
+
* @type {number}
|
|
220
|
+
* @const
|
|
221
|
+
*/
|
|
222
|
+
this.CORNER_RADIUS = 8;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Margin around the edges of the blocks in the flyout.
|
|
226
|
+
* @type {number}
|
|
227
|
+
* @const
|
|
228
|
+
*/
|
|
229
|
+
this.MARGIN = this.CORNER_RADIUS;
|
|
230
|
+
|
|
231
|
+
// TODO: Move GAP_X and GAP_Y to their appropriate files.
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Gap between items in horizontal flyouts. Can be overridden with the "sep"
|
|
235
|
+
* element.
|
|
236
|
+
* @const {number}
|
|
237
|
+
*/
|
|
238
|
+
this.GAP_X = this.MARGIN * 3;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Gap between items in vertical flyouts. Can be overridden with the "sep"
|
|
242
|
+
* element.
|
|
243
|
+
* @const {number}
|
|
244
|
+
*/
|
|
245
|
+
this.GAP_Y = this.MARGIN * 3;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Top/bottom padding between scrollbar and edge of flyout background.
|
|
249
|
+
* @type {number}
|
|
250
|
+
* @const
|
|
251
|
+
*/
|
|
252
|
+
this.SCROLLBAR_MARGIN = 2.5;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Width of flyout.
|
|
256
|
+
* @type {number}
|
|
257
|
+
* @protected
|
|
258
|
+
*/
|
|
259
|
+
this.width_ = 0;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Height of flyout.
|
|
263
|
+
* @type {number}
|
|
264
|
+
* @protected
|
|
265
|
+
*/
|
|
266
|
+
this.height_ = 0;
|
|
267
|
+
|
|
268
|
+
// clang-format off
|
|
269
|
+
/**
|
|
270
|
+
* Range of a drag angle from a flyout considered "dragging toward
|
|
271
|
+
* workspace". Drags that are within the bounds of this many degrees from
|
|
272
|
+
* the orthogonal line to the flyout edge are considered to be "drags toward
|
|
273
|
+
* the workspace".
|
|
274
|
+
* Example:
|
|
275
|
+
* Flyout Edge Workspace
|
|
276
|
+
* [block] / <-within this angle, drags "toward workspace" |
|
|
277
|
+
* [block] ---- orthogonal to flyout boundary ---- |
|
|
278
|
+
* [block] \ |
|
|
279
|
+
* The angle is given in degrees from the orthogonal.
|
|
280
|
+
*
|
|
281
|
+
* This is used to know when to create a new block and when to scroll the
|
|
282
|
+
* flyout. Setting it to 360 means that all drags create a new block.
|
|
283
|
+
* @type {number}
|
|
284
|
+
* @protected
|
|
285
|
+
*/
|
|
286
|
+
// clang-format on
|
|
287
|
+
this.dragAngleRange_ = 70;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The path around the background of the flyout, which will be filled with a
|
|
291
|
+
* background colour.
|
|
292
|
+
* @type {?SVGPathElement}
|
|
293
|
+
* @protected
|
|
294
|
+
*/
|
|
295
|
+
this.svgBackground_ = null;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The root SVG group for the button or label.
|
|
299
|
+
* @type {?SVGGElement}
|
|
300
|
+
* @protected
|
|
301
|
+
*/
|
|
302
|
+
this.svgGroup_ = null;
|
|
303
|
+
}
|
|
92
304
|
|
|
93
305
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
306
|
+
* Creates the flyout's DOM. Only needs to be called once. The flyout can
|
|
307
|
+
* either exist as its own SVG element or be a g element nested inside a
|
|
308
|
+
* separate SVG element.
|
|
309
|
+
* @param {string|
|
|
310
|
+
* !Svg<!SVGSVGElement>|
|
|
311
|
+
* !Svg<!SVGGElement>} tagName The type of tag to
|
|
312
|
+
* put the flyout in. This should be <svg> or <g>.
|
|
313
|
+
* @return {!SVGElement} The flyout's SVG group.
|
|
96
314
|
*/
|
|
97
|
-
|
|
315
|
+
createDom(tagName) {
|
|
316
|
+
/*
|
|
317
|
+
<svg | g>
|
|
318
|
+
<path class="blocklyFlyoutBackground"/>
|
|
319
|
+
<g class="blocklyFlyout"></g>
|
|
320
|
+
</ svg | g>
|
|
321
|
+
*/
|
|
322
|
+
// Setting style to display:none to start. The toolbox and flyout
|
|
323
|
+
// hide/show code will set up proper visibility and size later.
|
|
324
|
+
this.svgGroup_ = dom.createSvgElement(
|
|
325
|
+
tagName, {'class': 'blocklyFlyout', 'style': 'display: none'}, null);
|
|
326
|
+
this.svgBackground_ = dom.createSvgElement(
|
|
327
|
+
Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
|
|
328
|
+
this.svgGroup_.appendChild(this.workspace_.createDom());
|
|
329
|
+
this.workspace_.getThemeManager().subscribe(
|
|
330
|
+
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
|
|
331
|
+
this.workspace_.getThemeManager().subscribe(
|
|
332
|
+
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
|
|
333
|
+
return this.svgGroup_;
|
|
334
|
+
}
|
|
98
335
|
|
|
99
336
|
/**
|
|
100
|
-
*
|
|
101
|
-
* @
|
|
102
|
-
*
|
|
337
|
+
* Initializes the flyout.
|
|
338
|
+
* @param {!WorkspaceSvg} targetWorkspace The workspace in which to
|
|
339
|
+
* create new blocks.
|
|
103
340
|
*/
|
|
104
|
-
|
|
341
|
+
init(targetWorkspace) {
|
|
342
|
+
this.targetWorkspace = targetWorkspace;
|
|
343
|
+
this.workspace_.targetWorkspace = targetWorkspace;
|
|
344
|
+
|
|
345
|
+
this.workspace_.scrollbar = new ScrollbarPair(
|
|
346
|
+
this.workspace_, this.horizontalLayout, !this.horizontalLayout,
|
|
347
|
+
'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN);
|
|
348
|
+
|
|
349
|
+
this.hide();
|
|
350
|
+
|
|
351
|
+
Array.prototype.push.apply(
|
|
352
|
+
this.eventWrappers_,
|
|
353
|
+
browserEvents.conditionalBind(
|
|
354
|
+
/** @type {!SVGGElement} */ (this.svgGroup_), 'wheel', this,
|
|
355
|
+
this.wheel_));
|
|
356
|
+
if (!this.autoClose) {
|
|
357
|
+
this.filterWrapper_ = this.filterForCapacity_.bind(this);
|
|
358
|
+
this.targetWorkspace.addChangeListener(this.filterWrapper_);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Dragging the flyout up and down.
|
|
362
|
+
Array.prototype.push.apply(
|
|
363
|
+
this.eventWrappers_,
|
|
364
|
+
browserEvents.conditionalBind(
|
|
365
|
+
/** @type {!SVGPathElement} */ (this.svgBackground_), 'mousedown',
|
|
366
|
+
this, this.onMouseDown_));
|
|
367
|
+
|
|
368
|
+
// A flyout connected to a workspace doesn't have its own current gesture.
|
|
369
|
+
this.workspace_.getGesture =
|
|
370
|
+
this.targetWorkspace.getGesture.bind(this.targetWorkspace);
|
|
371
|
+
|
|
372
|
+
// Get variables from the main workspace rather than the target workspace.
|
|
373
|
+
this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap());
|
|
374
|
+
|
|
375
|
+
this.workspace_.createPotentialVariableMap();
|
|
376
|
+
|
|
377
|
+
targetWorkspace.getComponentManager().addComponent({
|
|
378
|
+
component: this,
|
|
379
|
+
weight: 1,
|
|
380
|
+
capabilities: [
|
|
381
|
+
ComponentManager.Capability.DELETE_AREA,
|
|
382
|
+
ComponentManager.Capability.DRAG_TARGET,
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
}
|
|
105
386
|
|
|
106
387
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* @
|
|
388
|
+
* Dispose of this flyout.
|
|
389
|
+
* Unlink from all DOM elements to prevent memory leaks.
|
|
390
|
+
* @suppress {checkTypes}
|
|
110
391
|
*/
|
|
111
|
-
|
|
392
|
+
dispose() {
|
|
393
|
+
this.hide();
|
|
394
|
+
this.workspace_.getComponentManager().removeComponent(this.id);
|
|
395
|
+
browserEvents.unbind(this.eventWrappers_);
|
|
396
|
+
if (this.filterWrapper_) {
|
|
397
|
+
this.targetWorkspace.removeChangeListener(this.filterWrapper_);
|
|
398
|
+
this.filterWrapper_ = null;
|
|
399
|
+
}
|
|
400
|
+
if (this.workspace_) {
|
|
401
|
+
this.workspace_.getThemeManager().unsubscribe(this.svgBackground_);
|
|
402
|
+
this.workspace_.targetWorkspace = null;
|
|
403
|
+
this.workspace_.dispose();
|
|
404
|
+
this.workspace_ = null;
|
|
405
|
+
}
|
|
406
|
+
if (this.svgGroup_) {
|
|
407
|
+
dom.removeNode(this.svgGroup_);
|
|
408
|
+
this.svgGroup_ = null;
|
|
409
|
+
}
|
|
410
|
+
this.svgBackground_ = null;
|
|
411
|
+
this.targetWorkspace = null;
|
|
412
|
+
}
|
|
112
413
|
|
|
113
414
|
/**
|
|
114
|
-
*
|
|
115
|
-
* @
|
|
116
|
-
* @private
|
|
415
|
+
* Get the width of the flyout.
|
|
416
|
+
* @return {number} The width of the flyout.
|
|
117
417
|
*/
|
|
118
|
-
|
|
418
|
+
getWidth() {
|
|
419
|
+
return this.width_;
|
|
420
|
+
}
|
|
119
421
|
|
|
120
422
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* @type {!Array<!SVGElement>}
|
|
124
|
-
* @private
|
|
423
|
+
* Get the height of the flyout.
|
|
424
|
+
* @return {number} The width of the flyout.
|
|
125
425
|
*/
|
|
126
|
-
|
|
426
|
+
getHeight() {
|
|
427
|
+
return this.height_;
|
|
428
|
+
}
|
|
127
429
|
|
|
128
430
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* @
|
|
431
|
+
* Get the scale (zoom level) of the flyout. By default,
|
|
432
|
+
* this matches the target workspace scale, but this can be overridden.
|
|
433
|
+
* @return {number} Flyout workspace scale.
|
|
132
434
|
*/
|
|
133
|
-
|
|
435
|
+
getFlyoutScale() {
|
|
436
|
+
return this.targetWorkspace.scale;
|
|
437
|
+
}
|
|
134
438
|
|
|
135
439
|
/**
|
|
136
|
-
*
|
|
137
|
-
* @
|
|
138
|
-
* @
|
|
440
|
+
* Get the workspace inside the flyout.
|
|
441
|
+
* @return {!WorkspaceSvg} The workspace inside the flyout.
|
|
442
|
+
* @package
|
|
139
443
|
*/
|
|
140
|
-
|
|
444
|
+
getWorkspace() {
|
|
445
|
+
return this.workspace_;
|
|
446
|
+
}
|
|
141
447
|
|
|
142
448
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @
|
|
145
|
-
* @private
|
|
449
|
+
* Is the flyout visible?
|
|
450
|
+
* @return {boolean} True if visible.
|
|
146
451
|
*/
|
|
147
|
-
|
|
452
|
+
isVisible() {
|
|
453
|
+
return this.isVisible_;
|
|
454
|
+
}
|
|
148
455
|
|
|
149
456
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* @
|
|
457
|
+
* Set whether the flyout is visible. A value of true does not necessarily
|
|
458
|
+
* mean that the flyout is shown. It could be hidden because its container is
|
|
459
|
+
* hidden.
|
|
460
|
+
* @param {boolean} visible True if visible.
|
|
154
461
|
*/
|
|
155
|
-
|
|
462
|
+
setVisible(visible) {
|
|
463
|
+
const visibilityChanged = (visible !== this.isVisible());
|
|
464
|
+
|
|
465
|
+
this.isVisible_ = visible;
|
|
466
|
+
if (visibilityChanged) {
|
|
467
|
+
if (!this.autoClose) {
|
|
468
|
+
// Auto-close flyouts are ignored as drag targets, so only non
|
|
469
|
+
// auto-close flyouts need to have their drag target updated.
|
|
470
|
+
this.workspace_.recordDragTargets();
|
|
471
|
+
}
|
|
472
|
+
this.updateDisplay_();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
156
475
|
|
|
157
476
|
/**
|
|
158
|
-
*
|
|
159
|
-
* @
|
|
160
|
-
* @package
|
|
477
|
+
* Set whether this flyout's container is visible.
|
|
478
|
+
* @param {boolean} visible Whether the container is visible.
|
|
161
479
|
*/
|
|
162
|
-
|
|
480
|
+
setContainerVisible(visible) {
|
|
481
|
+
const visibilityChanged = (visible !== this.containerVisible_);
|
|
482
|
+
this.containerVisible_ = visible;
|
|
483
|
+
if (visibilityChanged) {
|
|
484
|
+
this.updateDisplay_();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
163
487
|
|
|
164
488
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
489
|
+
* Update the display property of the flyout based whether it thinks it should
|
|
490
|
+
* be visible and whether its containing workspace is visible.
|
|
167
491
|
* @private
|
|
168
492
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
* Whether the flyout is visible.
|
|
181
|
-
* @type {boolean}
|
|
182
|
-
* @private
|
|
183
|
-
*/
|
|
184
|
-
Flyout.prototype.isVisible_ = false;
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Whether the workspace containing this flyout is visible.
|
|
188
|
-
* @type {boolean}
|
|
189
|
-
* @private
|
|
190
|
-
*/
|
|
191
|
-
Flyout.prototype.containerVisible_ = true;
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Corner radius of the flyout background.
|
|
195
|
-
* @type {number}
|
|
196
|
-
* @const
|
|
197
|
-
*/
|
|
198
|
-
Flyout.prototype.CORNER_RADIUS = 8;
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Margin around the edges of the blocks in the flyout.
|
|
202
|
-
* @type {number}
|
|
203
|
-
* @const
|
|
204
|
-
*/
|
|
205
|
-
Flyout.prototype.MARGIN = Flyout.prototype.CORNER_RADIUS;
|
|
206
|
-
|
|
207
|
-
// TODO: Move GAP_X and GAP_Y to their appropriate files.
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Gap between items in horizontal flyouts. Can be overridden with the "sep"
|
|
211
|
-
* element.
|
|
212
|
-
* @const {number}
|
|
213
|
-
*/
|
|
214
|
-
Flyout.prototype.GAP_X = Flyout.prototype.MARGIN * 3;
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Gap between items in vertical flyouts. Can be overridden with the "sep"
|
|
218
|
-
* element.
|
|
219
|
-
* @const {number}
|
|
220
|
-
*/
|
|
221
|
-
Flyout.prototype.GAP_Y = Flyout.prototype.MARGIN * 3;
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Top/bottom padding between scrollbar and edge of flyout background.
|
|
225
|
-
* @type {number}
|
|
226
|
-
* @const
|
|
227
|
-
*/
|
|
228
|
-
Flyout.prototype.SCROLLBAR_MARGIN = 2.5;
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Width of flyout.
|
|
232
|
-
* @type {number}
|
|
233
|
-
* @protected
|
|
234
|
-
*/
|
|
235
|
-
Flyout.prototype.width_ = 0;
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Height of flyout.
|
|
239
|
-
* @type {number}
|
|
240
|
-
* @protected
|
|
241
|
-
*/
|
|
242
|
-
Flyout.prototype.height_ = 0;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Range of a drag angle from a flyout considered "dragging toward workspace".
|
|
246
|
-
* Drags that are within the bounds of this many degrees from the orthogonal
|
|
247
|
-
* line to the flyout edge are considered to be "drags toward the workspace".
|
|
248
|
-
* Example:
|
|
249
|
-
* Flyout Edge Workspace
|
|
250
|
-
* [block] / <-within this angle, drags "toward workspace" |
|
|
251
|
-
* [block] ---- orthogonal to flyout boundary ---- |
|
|
252
|
-
* [block] \ |
|
|
253
|
-
* The angle is given in degrees from the orthogonal.
|
|
254
|
-
*
|
|
255
|
-
* This is used to know when to create a new block and when to scroll the
|
|
256
|
-
* flyout. Setting it to 360 means that all drags create a new block.
|
|
257
|
-
* @type {number}
|
|
258
|
-
* @protected
|
|
259
|
-
*/
|
|
260
|
-
Flyout.prototype.dragAngleRange_ = 70;
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Creates the flyout's DOM. Only needs to be called once. The flyout can
|
|
264
|
-
* either exist as its own SVG element or be a g element nested inside a
|
|
265
|
-
* separate SVG element.
|
|
266
|
-
* @param {string|
|
|
267
|
-
* !Svg<!SVGSVGElement>|
|
|
268
|
-
* !Svg<!SVGGElement>} tagName The type of tag to
|
|
269
|
-
* put the flyout in. This should be <svg> or <g>.
|
|
270
|
-
* @return {!SVGElement} The flyout's SVG group.
|
|
271
|
-
*/
|
|
272
|
-
Flyout.prototype.createDom = function(tagName) {
|
|
273
|
-
/*
|
|
274
|
-
<svg | g>
|
|
275
|
-
<path class="blocklyFlyoutBackground"/>
|
|
276
|
-
<g class="blocklyFlyout"></g>
|
|
277
|
-
</ svg | g>
|
|
278
|
-
*/
|
|
279
|
-
// Setting style to display:none to start. The toolbox and flyout
|
|
280
|
-
// hide/show code will set up proper visibility and size later.
|
|
281
|
-
this.svgGroup_ = dom.createSvgElement(
|
|
282
|
-
tagName, {'class': 'blocklyFlyout', 'style': 'display: none'}, null);
|
|
283
|
-
this.svgBackground_ = dom.createSvgElement(
|
|
284
|
-
Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
|
|
285
|
-
this.svgGroup_.appendChild(this.workspace_.createDom());
|
|
286
|
-
this.workspace_.getThemeManager().subscribe(
|
|
287
|
-
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
|
|
288
|
-
this.workspace_.getThemeManager().subscribe(
|
|
289
|
-
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
|
|
290
|
-
return this.svgGroup_;
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Initializes the flyout.
|
|
295
|
-
* @param {!WorkspaceSvg} targetWorkspace The workspace in which to
|
|
296
|
-
* create new blocks.
|
|
297
|
-
*/
|
|
298
|
-
Flyout.prototype.init = function(targetWorkspace) {
|
|
299
|
-
this.targetWorkspace = targetWorkspace;
|
|
300
|
-
this.workspace_.targetWorkspace = targetWorkspace;
|
|
301
|
-
|
|
302
|
-
this.workspace_.scrollbar = new ScrollbarPair(
|
|
303
|
-
this.workspace_, this.horizontalLayout, !this.horizontalLayout,
|
|
304
|
-
'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN);
|
|
305
|
-
|
|
306
|
-
this.hide();
|
|
307
|
-
|
|
308
|
-
Array.prototype.push.apply(
|
|
309
|
-
this.eventWrappers_,
|
|
310
|
-
browserEvents.conditionalBind(
|
|
311
|
-
this.svgGroup_, 'wheel', this, this.wheel_));
|
|
312
|
-
if (!this.autoClose) {
|
|
313
|
-
this.filterWrapper_ = this.filterForCapacity_.bind(this);
|
|
314
|
-
this.targetWorkspace.addChangeListener(this.filterWrapper_);
|
|
493
|
+
updateDisplay_() {
|
|
494
|
+
let show = true;
|
|
495
|
+
if (!this.containerVisible_) {
|
|
496
|
+
show = false;
|
|
497
|
+
} else {
|
|
498
|
+
show = this.isVisible();
|
|
499
|
+
}
|
|
500
|
+
this.svgGroup_.style.display = show ? 'block' : 'none';
|
|
501
|
+
// Update the scrollbar's visibility too since it should mimic the
|
|
502
|
+
// flyout's visibility.
|
|
503
|
+
this.workspace_.scrollbar.setContainerVisible(show);
|
|
315
504
|
}
|
|
316
505
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
});
|
|
340
|
-
};
|
|
506
|
+
/**
|
|
507
|
+
* Update the view based on coordinates calculated in position().
|
|
508
|
+
* @param {number} width The computed width of the flyout's SVG group
|
|
509
|
+
* @param {number} height The computed height of the flyout's SVG group.
|
|
510
|
+
* @param {number} x The computed x origin of the flyout's SVG group.
|
|
511
|
+
* @param {number} y The computed y origin of the flyout's SVG group.
|
|
512
|
+
* @protected
|
|
513
|
+
*/
|
|
514
|
+
positionAt_(width, height, x, y) {
|
|
515
|
+
this.svgGroup_.setAttribute('width', width);
|
|
516
|
+
this.svgGroup_.setAttribute('height', height);
|
|
517
|
+
this.workspace_.setCachedParentSvgSize(width, height);
|
|
518
|
+
|
|
519
|
+
if (this.svgGroup_.tagName === 'svg') {
|
|
520
|
+
const transform = 'translate(' + x + 'px,' + y + 'px)';
|
|
521
|
+
dom.setCssTransform(this.svgGroup_, transform);
|
|
522
|
+
} else {
|
|
523
|
+
// IE and Edge don't support CSS transforms on SVG elements so
|
|
524
|
+
// it's important to set the transform on the SVG element itself
|
|
525
|
+
const transform = 'translate(' + x + ',' + y + ')';
|
|
526
|
+
this.svgGroup_.setAttribute('transform', transform);
|
|
527
|
+
}
|
|
341
528
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.workspace_ = null;
|
|
360
|
-
}
|
|
361
|
-
if (this.svgGroup_) {
|
|
362
|
-
dom.removeNode(this.svgGroup_);
|
|
363
|
-
this.svgGroup_ = null;
|
|
529
|
+
// Update the scrollbar (if one exists).
|
|
530
|
+
const scrollbar = this.workspace_.scrollbar;
|
|
531
|
+
if (scrollbar) {
|
|
532
|
+
// Set the scrollbars origin to be the top left of the flyout.
|
|
533
|
+
scrollbar.setOrigin(x, y);
|
|
534
|
+
scrollbar.resize();
|
|
535
|
+
// If origin changed and metrics haven't changed enough to trigger
|
|
536
|
+
// reposition in resize, we need to call setPosition. See issue #4692.
|
|
537
|
+
if (scrollbar.hScroll) {
|
|
538
|
+
scrollbar.hScroll.setPosition(
|
|
539
|
+
scrollbar.hScroll.position.x, scrollbar.hScroll.position.y);
|
|
540
|
+
}
|
|
541
|
+
if (scrollbar.vScroll) {
|
|
542
|
+
scrollbar.vScroll.setPosition(
|
|
543
|
+
scrollbar.vScroll.position.x, scrollbar.vScroll.position.y);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
364
546
|
}
|
|
365
|
-
this.svgBackground_ = null;
|
|
366
|
-
this.targetWorkspace = null;
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Get the width of the flyout.
|
|
371
|
-
* @return {number} The width of the flyout.
|
|
372
|
-
*/
|
|
373
|
-
Flyout.prototype.getWidth = function() {
|
|
374
|
-
return this.width_;
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Get the height of the flyout.
|
|
379
|
-
* @return {number} The width of the flyout.
|
|
380
|
-
*/
|
|
381
|
-
Flyout.prototype.getHeight = function() {
|
|
382
|
-
return this.height_;
|
|
383
|
-
};
|
|
384
547
|
|
|
385
|
-
/**
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
548
|
+
/**
|
|
549
|
+
* Hide and empty the flyout.
|
|
550
|
+
*/
|
|
551
|
+
hide() {
|
|
552
|
+
if (!this.isVisible()) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
this.setVisible(false);
|
|
556
|
+
// Delete all the event listeners.
|
|
557
|
+
for (let i = 0, listen; (listen = this.listeners_[i]); i++) {
|
|
558
|
+
browserEvents.unbind(listen);
|
|
559
|
+
}
|
|
560
|
+
this.listeners_.length = 0;
|
|
561
|
+
if (this.reflowWrapper_) {
|
|
562
|
+
this.workspace_.removeChangeListener(this.reflowWrapper_);
|
|
563
|
+
this.reflowWrapper_ = null;
|
|
564
|
+
}
|
|
565
|
+
// Do NOT delete the blocks here. Wait until Flyout.show.
|
|
566
|
+
// https://neil.fraser.name/news/2014/08/09/
|
|
567
|
+
}
|
|
393
568
|
|
|
394
|
-
/**
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Show and populate the flyout.
|
|
571
|
+
* @param {!toolbox.FlyoutDefinition|string} flyoutDef Contents to display
|
|
572
|
+
* in the flyout. This is either an array of Nodes, a NodeList, a
|
|
573
|
+
* toolbox definition, or a string with the name of the dynamic category.
|
|
574
|
+
*/
|
|
575
|
+
show(flyoutDef) {
|
|
576
|
+
this.workspace_.setResizesEnabled(false);
|
|
577
|
+
this.hide();
|
|
578
|
+
this.clearOldBlocks_();
|
|
402
579
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
580
|
+
// Handle dynamic categories, represented by a name instead of a list.
|
|
581
|
+
if (typeof flyoutDef === 'string') {
|
|
582
|
+
flyoutDef = this.getDynamicCategoryContents_(flyoutDef);
|
|
583
|
+
}
|
|
584
|
+
this.setVisible(true);
|
|
585
|
+
|
|
586
|
+
// Parse the Array, Node or NodeList into a a list of flyout items.
|
|
587
|
+
const parsedContent = toolbox.convertFlyoutDefToJsonArray(flyoutDef);
|
|
588
|
+
const flyoutInfo =
|
|
589
|
+
/** @type {{contents:!Array<!Object>, gaps:!Array<number>}} */ (
|
|
590
|
+
this.createFlyoutInfo_(parsedContent));
|
|
591
|
+
|
|
592
|
+
this.layout_(flyoutInfo.contents, flyoutInfo.gaps);
|
|
593
|
+
|
|
594
|
+
// IE 11 is an incompetent browser that fails to fire mouseout events.
|
|
595
|
+
// When the mouse is over the background, deselect all blocks.
|
|
596
|
+
const deselectAll =
|
|
597
|
+
/** @this {Flyout} */
|
|
598
|
+
function() {
|
|
599
|
+
const topBlocks = this.workspace_.getTopBlocks(false);
|
|
600
|
+
for (let i = 0, block; (block = topBlocks[i]); i++) {
|
|
601
|
+
block.removeSelect();
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
this.listeners_.push(browserEvents.conditionalBind(
|
|
606
|
+
/** @type {!SVGPathElement} */ (this.svgBackground_), 'mouseover', this,
|
|
607
|
+
deselectAll));
|
|
608
|
+
|
|
609
|
+
if (this.horizontalLayout) {
|
|
610
|
+
this.height_ = 0;
|
|
611
|
+
} else {
|
|
612
|
+
this.width_ = 0;
|
|
613
|
+
}
|
|
614
|
+
this.workspace_.setResizesEnabled(true);
|
|
615
|
+
this.reflow();
|
|
410
616
|
|
|
411
|
-
|
|
412
|
-
* Set whether the flyout is visible. A value of true does not necessarily mean
|
|
413
|
-
* that the flyout is shown. It could be hidden because its container is hidden.
|
|
414
|
-
* @param {boolean} visible True if visible.
|
|
415
|
-
*/
|
|
416
|
-
Flyout.prototype.setVisible = function(visible) {
|
|
417
|
-
const visibilityChanged = (visible !== this.isVisible());
|
|
617
|
+
this.filterForCapacity_();
|
|
418
618
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!this.autoClose) {
|
|
422
|
-
// Auto-close flyouts are ignored as drag targets, so only non auto-close
|
|
423
|
-
// flyouts need to have their drag target updated.
|
|
424
|
-
this.workspace_.recordDragTargets();
|
|
425
|
-
}
|
|
426
|
-
this.updateDisplay_();
|
|
427
|
-
}
|
|
428
|
-
};
|
|
619
|
+
// Correctly position the flyout's scrollbar when it opens.
|
|
620
|
+
this.position();
|
|
429
621
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
*/
|
|
434
|
-
Flyout.prototype.setContainerVisible = function(visible) {
|
|
435
|
-
const visibilityChanged = (visible !== this.containerVisible_);
|
|
436
|
-
this.containerVisible_ = visible;
|
|
437
|
-
if (visibilityChanged) {
|
|
438
|
-
this.updateDisplay_();
|
|
622
|
+
this.reflowWrapper_ = this.reflow.bind(this);
|
|
623
|
+
this.workspace_.addChangeListener(this.reflowWrapper_);
|
|
624
|
+
this.emptyRecycledBlocks_();
|
|
439
625
|
}
|
|
440
|
-
};
|
|
441
626
|
|
|
442
|
-
/**
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
627
|
+
/**
|
|
628
|
+
* Create the contents array and gaps array necessary to create the layout for
|
|
629
|
+
* the flyout.
|
|
630
|
+
* @param {!toolbox.FlyoutItemInfoArray} parsedContent The array
|
|
631
|
+
* of objects to show in the flyout.
|
|
632
|
+
* @return {{contents:Array<Object>, gaps:Array<number>}} The list of contents
|
|
633
|
+
* and gaps needed to lay out the flyout.
|
|
634
|
+
* @private
|
|
635
|
+
*/
|
|
636
|
+
createFlyoutInfo_(parsedContent) {
|
|
637
|
+
const contents = [];
|
|
638
|
+
const gaps = [];
|
|
639
|
+
this.permanentlyDisabled_.length = 0;
|
|
640
|
+
const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
|
|
641
|
+
for (let i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) {
|
|
642
|
+
if (contentInfo['custom']) {
|
|
643
|
+
const customInfo =
|
|
644
|
+
/** @type {!toolbox.DynamicCategoryInfo} */ (contentInfo);
|
|
645
|
+
const categoryName = customInfo['custom'];
|
|
646
|
+
const flyoutDef = this.getDynamicCategoryContents_(categoryName);
|
|
647
|
+
const parsedDynamicContent = /** @type {!toolbox.FlyoutItemInfoArray} */
|
|
648
|
+
(toolbox.convertFlyoutDefToJsonArray(flyoutDef));
|
|
649
|
+
// Replace the element at i with the dynamic content it represents.
|
|
650
|
+
parsedContent.splice.apply(
|
|
651
|
+
parsedContent, [i, 1].concat(parsedDynamicContent));
|
|
652
|
+
contentInfo = parsedContent[i];
|
|
653
|
+
}
|
|
459
654
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
655
|
+
switch (contentInfo['kind'].toUpperCase()) {
|
|
656
|
+
case 'BLOCK': {
|
|
657
|
+
const blockInfo = /** @type {!toolbox.BlockInfo} */ (contentInfo);
|
|
658
|
+
const block = this.createFlyoutBlock_(blockInfo);
|
|
659
|
+
contents.push({type: 'block', block: block});
|
|
660
|
+
this.addBlockGap_(blockInfo, gaps, defaultGap);
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
case 'SEP': {
|
|
664
|
+
const sepInfo = /** @type {!toolbox.SeparatorInfo} */ (contentInfo);
|
|
665
|
+
this.addSeparatorGap_(sepInfo, gaps, defaultGap);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
case 'LABEL': {
|
|
669
|
+
const labelInfo = /** @type {!toolbox.LabelInfo} */ (contentInfo);
|
|
670
|
+
// A label is a button with different styling.
|
|
671
|
+
const label = this.createButton_(labelInfo, /** isLabel */ true);
|
|
672
|
+
contents.push({type: 'button', button: label});
|
|
673
|
+
gaps.push(defaultGap);
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
case 'BUTTON': {
|
|
677
|
+
const buttonInfo = /** @type {!toolbox.ButtonInfo} */ (contentInfo);
|
|
678
|
+
const button = this.createButton_(buttonInfo, /** isLabel */ false);
|
|
679
|
+
contents.push({type: 'button', button: button});
|
|
680
|
+
gaps.push(defaultGap);
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return {contents: contents, gaps: gaps};
|
|
481
686
|
}
|
|
482
687
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Gets the flyout definition for the dynamic category.
|
|
690
|
+
* @param {string} categoryName The name of the dynamic category.
|
|
691
|
+
* @return {!toolbox.FlyoutDefinition} The definition of the
|
|
692
|
+
* flyout in one of its many forms.
|
|
693
|
+
* @private
|
|
694
|
+
*/
|
|
695
|
+
getDynamicCategoryContents_(categoryName) {
|
|
696
|
+
// Look up the correct category generation function and call that to get a
|
|
697
|
+
// valid XML list.
|
|
698
|
+
const fnToApply =
|
|
699
|
+
this.workspace_.targetWorkspace.getToolboxCategoryCallback(
|
|
700
|
+
categoryName);
|
|
701
|
+
if (typeof fnToApply !== 'function') {
|
|
702
|
+
throw TypeError(
|
|
703
|
+
'Couldn\'t find a callback function when opening' +
|
|
704
|
+
' a toolbox category.');
|
|
498
705
|
}
|
|
706
|
+
return fnToApply(this.workspace_.targetWorkspace);
|
|
499
707
|
}
|
|
500
|
-
};
|
|
501
708
|
|
|
502
|
-
/**
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Creates a flyout button or a flyout label.
|
|
711
|
+
* @param {!toolbox.ButtonOrLabelInfo} btnInfo
|
|
712
|
+
* The object holding information about a button or a label.
|
|
713
|
+
* @param {boolean} isLabel True if the button is a label, false otherwise.
|
|
714
|
+
* @return {!FlyoutButton} The object used to display the button in the
|
|
715
|
+
* flyout.
|
|
716
|
+
* @private
|
|
717
|
+
*/
|
|
718
|
+
createButton_(btnInfo, isLabel) {
|
|
719
|
+
const {FlyoutButton} = goog.module.get('Blockly.FlyoutButton');
|
|
720
|
+
if (!FlyoutButton) {
|
|
721
|
+
throw Error('Missing require for Blockly.FlyoutButton');
|
|
722
|
+
}
|
|
723
|
+
const curButton = new FlyoutButton(
|
|
724
|
+
this.workspace_,
|
|
725
|
+
/** @type {!WorkspaceSvg} */ (this.targetWorkspace), btnInfo, isLabel);
|
|
726
|
+
return curButton;
|
|
518
727
|
}
|
|
519
|
-
// Do NOT delete the blocks here. Wait until Flyout.show.
|
|
520
|
-
// https://neil.fraser.name/news/2014/08/09/
|
|
521
|
-
};
|
|
522
728
|
|
|
523
|
-
/**
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
this.layout_(flyoutInfo.contents, flyoutInfo.gaps);
|
|
547
|
-
|
|
548
|
-
// IE 11 is an incompetent browser that fails to fire mouseout events.
|
|
549
|
-
// When the mouse is over the background, deselect all blocks.
|
|
550
|
-
const deselectAll =
|
|
551
|
-
/** @this {Flyout} */
|
|
552
|
-
function() {
|
|
553
|
-
const topBlocks = this.workspace_.getTopBlocks(false);
|
|
554
|
-
for (let i = 0, block; (block = topBlocks[i]); i++) {
|
|
555
|
-
block.removeSelect();
|
|
729
|
+
/**
|
|
730
|
+
* Create a block from the xml and permanently disable any blocks that were
|
|
731
|
+
* defined as disabled.
|
|
732
|
+
* @param {!toolbox.BlockInfo} blockInfo The info of the block.
|
|
733
|
+
* @return {!BlockSvg} The block created from the blockInfo.
|
|
734
|
+
* @private
|
|
735
|
+
*/
|
|
736
|
+
createFlyoutBlock_(blockInfo) {
|
|
737
|
+
let block;
|
|
738
|
+
if (blockInfo['blockxml']) {
|
|
739
|
+
const xml = typeof blockInfo['blockxml'] === 'string' ?
|
|
740
|
+
Xml.textToDom(blockInfo['blockxml']) :
|
|
741
|
+
blockInfo['blockxml'];
|
|
742
|
+
block = this.getRecycledBlock_(xml.getAttribute('type'));
|
|
743
|
+
if (!block) {
|
|
744
|
+
block = Xml.domToBlock(xml, this.workspace_);
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
block = this.getRecycledBlock_(blockInfo['type']);
|
|
748
|
+
if (!block) {
|
|
749
|
+
if (blockInfo['enabled'] === undefined) {
|
|
750
|
+
blockInfo['enabled'] = blockInfo['disabled'] !== 'true' &&
|
|
751
|
+
blockInfo['disabled'] !== true;
|
|
556
752
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (this.horizontalLayout) {
|
|
563
|
-
this.height_ = 0;
|
|
564
|
-
} else {
|
|
565
|
-
this.width_ = 0;
|
|
566
|
-
}
|
|
567
|
-
this.workspace_.setResizesEnabled(true);
|
|
568
|
-
this.reflow();
|
|
569
|
-
|
|
570
|
-
this.filterForCapacity_();
|
|
571
|
-
|
|
572
|
-
// Correctly position the flyout's scrollbar when it opens.
|
|
573
|
-
this.position();
|
|
574
|
-
|
|
575
|
-
this.reflowWrapper_ = this.reflow.bind(this);
|
|
576
|
-
this.workspace_.addChangeListener(this.reflowWrapper_);
|
|
577
|
-
this.emptyRecycledBlocks_();
|
|
578
|
-
};
|
|
753
|
+
block = blocks.append(
|
|
754
|
+
/** @type {blocks.State} */ (blockInfo), this.workspace_);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
579
757
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
* of objects to show in the flyout.
|
|
585
|
-
* @return {{contents:Array<Object>, gaps:Array<number>}} The list of contents
|
|
586
|
-
* and gaps needed to lay out the flyout.
|
|
587
|
-
* @private
|
|
588
|
-
*/
|
|
589
|
-
Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
|
|
590
|
-
const contents = [];
|
|
591
|
-
const gaps = [];
|
|
592
|
-
this.permanentlyDisabled_.length = 0;
|
|
593
|
-
const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
|
|
594
|
-
for (let i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) {
|
|
595
|
-
if (contentInfo['custom']) {
|
|
596
|
-
const customInfo =
|
|
597
|
-
/** @type {!toolbox.DynamicCategoryInfo} */ (contentInfo);
|
|
598
|
-
const categoryName = customInfo['custom'];
|
|
599
|
-
const flyoutDef = this.getDynamicCategoryContents_(categoryName);
|
|
600
|
-
const parsedDynamicContent = /** @type {!toolbox.FlyoutItemInfoArray} */
|
|
601
|
-
(toolbox.convertFlyoutDefToJsonArray(flyoutDef));
|
|
602
|
-
// Replace the element at i with the dynamic content it represents.
|
|
603
|
-
parsedContent.splice.apply(
|
|
604
|
-
parsedContent, [i, 1].concat(parsedDynamicContent));
|
|
605
|
-
contentInfo = parsedContent[i];
|
|
758
|
+
if (!block.isEnabled()) {
|
|
759
|
+
// Record blocks that were initially disabled.
|
|
760
|
+
// Do not enable these blocks as a result of capacity filtering.
|
|
761
|
+
this.permanentlyDisabled_.push(block);
|
|
606
762
|
}
|
|
763
|
+
return /** @type {!BlockSvg} */ (block);
|
|
764
|
+
}
|
|
607
765
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
case 'LABEL': {
|
|
622
|
-
const labelInfo = /** @type {!toolbox.LabelInfo} */ (contentInfo);
|
|
623
|
-
// A label is a button with different styling.
|
|
624
|
-
const label = this.createButton_(labelInfo, /** isLabel */ true);
|
|
625
|
-
contents.push({type: 'button', button: label});
|
|
626
|
-
gaps.push(defaultGap);
|
|
627
|
-
break;
|
|
628
|
-
}
|
|
629
|
-
case 'BUTTON': {
|
|
630
|
-
const buttonInfo = /** @type {!toolbox.ButtonInfo} */ (contentInfo);
|
|
631
|
-
const button = this.createButton_(buttonInfo, /** isLabel */ false);
|
|
632
|
-
contents.push({type: 'button', button: button});
|
|
633
|
-
gaps.push(defaultGap);
|
|
766
|
+
/**
|
|
767
|
+
* Returns a block from the array of recycled blocks with the given type, or
|
|
768
|
+
* undefined if one cannot be found.
|
|
769
|
+
* @param {string} blockType The type of the block to try to recycle.
|
|
770
|
+
* @return {(!BlockSvg|undefined)} The recycled block, or undefined if
|
|
771
|
+
* one could not be recycled.
|
|
772
|
+
* @private
|
|
773
|
+
*/
|
|
774
|
+
getRecycledBlock_(blockType) {
|
|
775
|
+
let index = -1;
|
|
776
|
+
for (let i = 0; i < this.recycledBlocks_.length; i++) {
|
|
777
|
+
if (this.recycledBlocks_[i].type === blockType) {
|
|
778
|
+
index = i;
|
|
634
779
|
break;
|
|
635
780
|
}
|
|
636
781
|
}
|
|
782
|
+
return index === -1 ? undefined : this.recycledBlocks_.splice(index, 1)[0];
|
|
637
783
|
}
|
|
638
|
-
return {contents: contents, gaps: gaps};
|
|
639
|
-
};
|
|
640
784
|
|
|
641
|
-
/**
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
785
|
+
/**
|
|
786
|
+
* Adds a gap in the flyout based on block info.
|
|
787
|
+
* @param {!toolbox.BlockInfo} blockInfo Information about a block.
|
|
788
|
+
* @param {!Array<number>} gaps The list of gaps between items in the flyout.
|
|
789
|
+
* @param {number} defaultGap The default gap between one element and the
|
|
790
|
+
* next.
|
|
791
|
+
* @private
|
|
792
|
+
*/
|
|
793
|
+
addBlockGap_(blockInfo, gaps, defaultGap) {
|
|
794
|
+
let gap;
|
|
795
|
+
if (blockInfo['gap']) {
|
|
796
|
+
gap = parseInt(blockInfo['gap'], 10);
|
|
797
|
+
} else if (blockInfo['blockxml']) {
|
|
798
|
+
const xml = typeof blockInfo['blockxml'] === 'string' ?
|
|
799
|
+
Xml.textToDom(blockInfo['blockxml']) :
|
|
800
|
+
blockInfo['blockxml'];
|
|
801
|
+
gap = parseInt(xml.getAttribute('gap'), 10);
|
|
802
|
+
}
|
|
803
|
+
gaps.push(isNaN(gap) ? defaultGap : gap);
|
|
657
804
|
}
|
|
658
|
-
return fnToApply(this.workspace_.targetWorkspace);
|
|
659
|
-
};
|
|
660
805
|
|
|
661
|
-
/**
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
806
|
+
/**
|
|
807
|
+
* Add the necessary gap in the flyout for a separator.
|
|
808
|
+
* @param {!toolbox.SeparatorInfo} sepInfo The object holding
|
|
809
|
+
* information about a separator.
|
|
810
|
+
* @param {!Array<number>} gaps The list gaps between items in the flyout.
|
|
811
|
+
* @param {number} defaultGap The default gap between the button and next
|
|
812
|
+
* element.
|
|
813
|
+
* @private
|
|
814
|
+
*/
|
|
815
|
+
addSeparatorGap_(sepInfo, gaps, defaultGap) {
|
|
816
|
+
// Change the gap between two toolbox elements.
|
|
817
|
+
// <sep gap="36"></sep>
|
|
818
|
+
// The default gap is 24, can be set larger or smaller.
|
|
819
|
+
// This overwrites the gap attribute on the previous element.
|
|
820
|
+
const newGap = parseInt(sepInfo['gap'], 10);
|
|
821
|
+
// Ignore gaps before the first block.
|
|
822
|
+
if (!isNaN(newGap) && gaps.length > 0) {
|
|
823
|
+
gaps[gaps.length - 1] = newGap;
|
|
824
|
+
} else {
|
|
825
|
+
gaps.push(defaultGap);
|
|
826
|
+
}
|
|
674
827
|
}
|
|
675
|
-
const curButton = new FlyoutButton(
|
|
676
|
-
this.workspace_,
|
|
677
|
-
/** @type {!WorkspaceSvg} */ (this.targetWorkspace), btnInfo, isLabel);
|
|
678
|
-
return curButton;
|
|
679
|
-
};
|
|
680
828
|
|
|
681
|
-
/**
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
block = this.getRecycledBlock_(xml.getAttribute('type'));
|
|
695
|
-
if (!block) {
|
|
696
|
-
block = Xml.domToBlock(xml, this.workspace_);
|
|
829
|
+
/**
|
|
830
|
+
* Delete blocks, mats and buttons from a previous showing of the flyout.
|
|
831
|
+
* @private
|
|
832
|
+
*/
|
|
833
|
+
clearOldBlocks_() {
|
|
834
|
+
// Delete any blocks from a previous showing.
|
|
835
|
+
const oldBlocks = this.workspace_.getTopBlocks(false);
|
|
836
|
+
for (let i = 0, block; (block = oldBlocks[i]); i++) {
|
|
837
|
+
if (this.blockIsRecyclable_(block)) {
|
|
838
|
+
this.recycleBlock_(block);
|
|
839
|
+
} else {
|
|
840
|
+
block.dispose(false, false);
|
|
841
|
+
}
|
|
697
842
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
843
|
+
// Delete any mats from a previous showing.
|
|
844
|
+
for (let j = 0; j < this.mats_.length; j++) {
|
|
845
|
+
const rect = this.mats_[j];
|
|
846
|
+
if (rect) {
|
|
847
|
+
Tooltip.unbindMouseEvents(rect);
|
|
848
|
+
dom.removeNode(rect);
|
|
704
849
|
}
|
|
705
|
-
block = blocks.append(
|
|
706
|
-
/** @type {blocks.State} */ (blockInfo), this.workspace_);
|
|
707
850
|
}
|
|
708
|
-
|
|
851
|
+
this.mats_.length = 0;
|
|
852
|
+
// Delete any buttons from a previous showing.
|
|
853
|
+
for (let i = 0, button; (button = this.buttons_[i]); i++) {
|
|
854
|
+
button.dispose();
|
|
855
|
+
}
|
|
856
|
+
this.buttons_.length = 0;
|
|
709
857
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
// Do not enable these blocks as a result of capacity filtering.
|
|
713
|
-
this.permanentlyDisabled_.push(block);
|
|
858
|
+
// Clear potential variables from the previous showing.
|
|
859
|
+
this.workspace_.getPotentialVariableMap().clear();
|
|
714
860
|
}
|
|
715
|
-
return /** @type {!BlockSvg} */ (block);
|
|
716
|
-
};
|
|
717
861
|
|
|
718
|
-
/**
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
*/
|
|
726
|
-
Flyout.prototype.getRecycledBlock_ = function(blockType) {
|
|
727
|
-
let index = -1;
|
|
728
|
-
for (let i = 0; i < this.recycledBlocks_.length; i++) {
|
|
729
|
-
if (this.recycledBlocks_[i].type === blockType) {
|
|
730
|
-
index = i;
|
|
731
|
-
break;
|
|
862
|
+
/**
|
|
863
|
+
* Empties all of the recycled blocks, properly disposing of them.
|
|
864
|
+
* @private
|
|
865
|
+
*/
|
|
866
|
+
emptyRecycledBlocks_() {
|
|
867
|
+
for (let i = 0; i < this.recycledBlocks_.length; i++) {
|
|
868
|
+
this.recycledBlocks_[i].dispose();
|
|
732
869
|
}
|
|
870
|
+
this.recycledBlocks_ = [];
|
|
733
871
|
}
|
|
734
|
-
return index === -1 ? undefined : this.recycledBlocks_.splice(index, 1)[0];
|
|
735
|
-
};
|
|
736
872
|
|
|
737
|
-
/**
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (blockInfo['gap']) {
|
|
747
|
-
gap = parseInt(blockInfo['gap'], 10);
|
|
748
|
-
} else if (blockInfo['blockxml']) {
|
|
749
|
-
const xml = typeof blockInfo['blockxml'] === 'string' ?
|
|
750
|
-
Xml.textToDom(blockInfo['blockxml']) :
|
|
751
|
-
blockInfo['blockxml'];
|
|
752
|
-
gap = parseInt(xml.getAttribute('gap'), 10);
|
|
873
|
+
/**
|
|
874
|
+
* Returns whether the given block can be recycled or not.
|
|
875
|
+
* @param {!BlockSvg} _block The block to check for recyclability.
|
|
876
|
+
* @return {boolean} True if the block can be recycled. False otherwise.
|
|
877
|
+
* @protected
|
|
878
|
+
*/
|
|
879
|
+
blockIsRecyclable_(_block) {
|
|
880
|
+
// By default, recycling is disabled.
|
|
881
|
+
return false;
|
|
753
882
|
}
|
|
754
|
-
gaps.push(isNaN(gap) ? defaultGap : gap);
|
|
755
|
-
};
|
|
756
883
|
|
|
757
|
-
/**
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
// <sep gap="36"></sep>
|
|
769
|
-
// The default gap is 24, can be set larger or smaller.
|
|
770
|
-
// This overwrites the gap attribute on the previous element.
|
|
771
|
-
const newGap = parseInt(sepInfo['gap'], 10);
|
|
772
|
-
// Ignore gaps before the first block.
|
|
773
|
-
if (!isNaN(newGap) && gaps.length > 0) {
|
|
774
|
-
gaps[gaps.length - 1] = newGap;
|
|
775
|
-
} else {
|
|
776
|
-
gaps.push(defaultGap);
|
|
884
|
+
/**
|
|
885
|
+
* Puts a previously created block into the recycle bin and moves it to the
|
|
886
|
+
* top of the workspace. Used during large workspace swaps to limit the number
|
|
887
|
+
* of new DOM elements we need to create.
|
|
888
|
+
* @param {!BlockSvg} block The block to recycle.
|
|
889
|
+
* @private
|
|
890
|
+
*/
|
|
891
|
+
recycleBlock_(block) {
|
|
892
|
+
const xy = block.getRelativeToSurfaceXY();
|
|
893
|
+
block.moveBy(-xy.x, -xy.y);
|
|
894
|
+
this.recycledBlocks_.push(block);
|
|
777
895
|
}
|
|
778
|
-
};
|
|
779
896
|
|
|
780
|
-
/**
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
802
|
-
this.mats_.length = 0;
|
|
803
|
-
// Delete any buttons from a previous showing.
|
|
804
|
-
for (let i = 0, button; (button = this.buttons_[i]); i++) {
|
|
805
|
-
button.dispose();
|
|
897
|
+
/**
|
|
898
|
+
* Add listeners to a block that has been added to the flyout.
|
|
899
|
+
* @param {!SVGElement} root The root node of the SVG group the block is in.
|
|
900
|
+
* @param {!BlockSvg} block The block to add listeners for.
|
|
901
|
+
* @param {!SVGElement} rect The invisible rectangle under the block that acts
|
|
902
|
+
* as a mat for that block.
|
|
903
|
+
* @protected
|
|
904
|
+
*/
|
|
905
|
+
addBlockListeners_(root, block, rect) {
|
|
906
|
+
this.listeners_.push(browserEvents.conditionalBind(
|
|
907
|
+
root, 'mousedown', null, this.blockMouseDown_(block)));
|
|
908
|
+
this.listeners_.push(browserEvents.conditionalBind(
|
|
909
|
+
rect, 'mousedown', null, this.blockMouseDown_(block)));
|
|
910
|
+
this.listeners_.push(
|
|
911
|
+
browserEvents.bind(root, 'mouseenter', block, block.addSelect));
|
|
912
|
+
this.listeners_.push(
|
|
913
|
+
browserEvents.bind(root, 'mouseleave', block, block.removeSelect));
|
|
914
|
+
this.listeners_.push(
|
|
915
|
+
browserEvents.bind(rect, 'mouseenter', block, block.addSelect));
|
|
916
|
+
this.listeners_.push(
|
|
917
|
+
browserEvents.bind(rect, 'mouseleave', block, block.removeSelect));
|
|
806
918
|
}
|
|
807
|
-
this.buttons_.length = 0;
|
|
808
919
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
920
|
+
/**
|
|
921
|
+
* Handle a mouse-down on an SVG block in a non-closing flyout.
|
|
922
|
+
* @param {!BlockSvg} block The flyout block to copy.
|
|
923
|
+
* @return {!Function} Function to call when block is clicked.
|
|
924
|
+
* @private
|
|
925
|
+
*/
|
|
926
|
+
blockMouseDown_(block) {
|
|
927
|
+
const flyout = this;
|
|
928
|
+
return function(e) {
|
|
929
|
+
const gesture = flyout.targetWorkspace.getGesture(e);
|
|
930
|
+
if (gesture) {
|
|
931
|
+
gesture.setStartBlock(block);
|
|
932
|
+
gesture.handleFlyoutStart(e, flyout);
|
|
933
|
+
}
|
|
934
|
+
};
|
|
820
935
|
}
|
|
821
|
-
this.recycledBlocks_ = [];
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
/**
|
|
825
|
-
* Returns whether the given block can be recycled or not.
|
|
826
|
-
* @param {!BlockSvg} _block The block to check for recyclability.
|
|
827
|
-
* @return {boolean} True if the block can be recycled. False otherwise.
|
|
828
|
-
* @protected
|
|
829
|
-
*/
|
|
830
|
-
Flyout.prototype.blockIsRecyclable_ = function(_block) {
|
|
831
|
-
// By default, recycling is disabled.
|
|
832
|
-
return false;
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
* Puts a previously created block into the recycle bin and moves it to the
|
|
837
|
-
* top of the workspace. Used during large workspace swaps to limit the number
|
|
838
|
-
* of new DOM elements we need to create.
|
|
839
|
-
* @param {!BlockSvg} block The block to recycle.
|
|
840
|
-
* @private
|
|
841
|
-
*/
|
|
842
|
-
Flyout.prototype.recycleBlock_ = function(block) {
|
|
843
|
-
const xy = block.getRelativeToSurfaceXY();
|
|
844
|
-
block.moveBy(-xy.x, -xy.y);
|
|
845
|
-
this.recycledBlocks_.push(block);
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
/**
|
|
849
|
-
* Add listeners to a block that has been added to the flyout.
|
|
850
|
-
* @param {!SVGElement} root The root node of the SVG group the block is in.
|
|
851
|
-
* @param {!BlockSvg} block The block to add listeners for.
|
|
852
|
-
* @param {!SVGElement} rect The invisible rectangle under the block that acts
|
|
853
|
-
* as a mat for that block.
|
|
854
|
-
* @protected
|
|
855
|
-
*/
|
|
856
|
-
Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
|
|
857
|
-
this.listeners_.push(browserEvents.conditionalBind(
|
|
858
|
-
root, 'mousedown', null, this.blockMouseDown_(block)));
|
|
859
|
-
this.listeners_.push(browserEvents.conditionalBind(
|
|
860
|
-
rect, 'mousedown', null, this.blockMouseDown_(block)));
|
|
861
|
-
this.listeners_.push(
|
|
862
|
-
browserEvents.bind(root, 'mouseenter', block, block.addSelect));
|
|
863
|
-
this.listeners_.push(
|
|
864
|
-
browserEvents.bind(root, 'mouseleave', block, block.removeSelect));
|
|
865
|
-
this.listeners_.push(
|
|
866
|
-
browserEvents.bind(rect, 'mouseenter', block, block.addSelect));
|
|
867
|
-
this.listeners_.push(
|
|
868
|
-
browserEvents.bind(rect, 'mouseleave', block, block.removeSelect));
|
|
869
|
-
};
|
|
870
936
|
|
|
871
|
-
/**
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
const flyout = this;
|
|
879
|
-
return function(e) {
|
|
880
|
-
const gesture = flyout.targetWorkspace.getGesture(e);
|
|
937
|
+
/**
|
|
938
|
+
* Mouse down on the flyout background. Start a vertical scroll drag.
|
|
939
|
+
* @param {!Event} e Mouse down event.
|
|
940
|
+
* @private
|
|
941
|
+
*/
|
|
942
|
+
onMouseDown_(e) {
|
|
943
|
+
const gesture = this.targetWorkspace.getGesture(e);
|
|
881
944
|
if (gesture) {
|
|
882
|
-
gesture.
|
|
883
|
-
gesture.handleFlyoutStart(e, flyout);
|
|
945
|
+
gesture.handleFlyoutStart(e, this);
|
|
884
946
|
}
|
|
885
|
-
}
|
|
886
|
-
};
|
|
947
|
+
}
|
|
887
948
|
|
|
888
|
-
/**
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
949
|
+
/**
|
|
950
|
+
* Does this flyout allow you to create a new instance of the given block?
|
|
951
|
+
* Used for deciding if a block can be "dragged out of" the flyout.
|
|
952
|
+
* @param {!BlockSvg} block The block to copy from the flyout.
|
|
953
|
+
* @return {boolean} True if you can create a new instance of the block, false
|
|
954
|
+
* otherwise.
|
|
955
|
+
* @package
|
|
956
|
+
*/
|
|
957
|
+
isBlockCreatable_(block) {
|
|
958
|
+
return block.isEnabled();
|
|
897
959
|
}
|
|
898
|
-
};
|
|
899
960
|
|
|
900
|
-
/**
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
961
|
+
/**
|
|
962
|
+
* Create a copy of this block on the workspace.
|
|
963
|
+
* @param {!BlockSvg} originalBlock The block to copy from the flyout.
|
|
964
|
+
* @return {!BlockSvg} The newly created block.
|
|
965
|
+
* @throws {Error} if something went wrong with deserialization.
|
|
966
|
+
* @package
|
|
967
|
+
*/
|
|
968
|
+
createBlock(originalBlock) {
|
|
969
|
+
let newBlock = null;
|
|
970
|
+
eventUtils.disable();
|
|
971
|
+
const variablesBeforeCreation = this.targetWorkspace.getAllVariables();
|
|
972
|
+
this.targetWorkspace.setResizesEnabled(false);
|
|
973
|
+
try {
|
|
974
|
+
newBlock = this.placeNewBlock_(originalBlock);
|
|
975
|
+
} finally {
|
|
976
|
+
eventUtils.enable();
|
|
977
|
+
}
|
|
911
978
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
* @param {!BlockSvg} originalBlock The block to copy from the flyout.
|
|
915
|
-
* @return {!BlockSvg} The newly created block.
|
|
916
|
-
* @throws {Error} if something went wrong with deserialization.
|
|
917
|
-
* @package
|
|
918
|
-
*/
|
|
919
|
-
Flyout.prototype.createBlock = function(originalBlock) {
|
|
920
|
-
let newBlock = null;
|
|
921
|
-
eventUtils.disable();
|
|
922
|
-
const variablesBeforeCreation = this.targetWorkspace.getAllVariables();
|
|
923
|
-
this.targetWorkspace.setResizesEnabled(false);
|
|
924
|
-
try {
|
|
925
|
-
newBlock = this.placeNewBlock_(originalBlock);
|
|
926
|
-
} finally {
|
|
927
|
-
eventUtils.enable();
|
|
928
|
-
}
|
|
979
|
+
// Close the flyout.
|
|
980
|
+
this.targetWorkspace.hideChaff();
|
|
929
981
|
|
|
930
|
-
|
|
931
|
-
|
|
982
|
+
const newVariables = Variables.getAddedVariables(
|
|
983
|
+
this.targetWorkspace, variablesBeforeCreation);
|
|
932
984
|
|
|
933
|
-
|
|
934
|
-
|
|
985
|
+
if (eventUtils.isEnabled()) {
|
|
986
|
+
eventUtils.setGroup(true);
|
|
987
|
+
// Fire a VarCreate event for each (if any) new variable created.
|
|
988
|
+
for (let i = 0; i < newVariables.length; i++) {
|
|
989
|
+
const thisVariable = newVariables[i];
|
|
990
|
+
eventUtils.fire(
|
|
991
|
+
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable));
|
|
992
|
+
}
|
|
935
993
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
for (let i = 0; i < newVariables.length; i++) {
|
|
940
|
-
const thisVariable = newVariables[i];
|
|
941
|
-
eventUtils.fire(
|
|
942
|
-
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable));
|
|
994
|
+
// Block events come after var events, in case they refer to newly created
|
|
995
|
+
// variables.
|
|
996
|
+
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock));
|
|
943
997
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
this.hide();
|
|
951
|
-
} else {
|
|
952
|
-
this.filterForCapacity_();
|
|
998
|
+
if (this.autoClose) {
|
|
999
|
+
this.hide();
|
|
1000
|
+
} else {
|
|
1001
|
+
this.filterForCapacity_();
|
|
1002
|
+
}
|
|
1003
|
+
return newBlock;
|
|
953
1004
|
}
|
|
954
|
-
return newBlock;
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* Initialize the given button: move it to the correct location,
|
|
959
|
-
* add listeners, etc.
|
|
960
|
-
* @param {!FlyoutButton} button The button to initialize and place.
|
|
961
|
-
* @param {number} x The x position of the cursor during this layout pass.
|
|
962
|
-
* @param {number} y The y position of the cursor during this layout pass.
|
|
963
|
-
* @protected
|
|
964
|
-
*/
|
|
965
|
-
Flyout.prototype.initFlyoutButton_ = function(button, x, y) {
|
|
966
|
-
const buttonSvg = button.createDom();
|
|
967
|
-
button.moveTo(x, y);
|
|
968
|
-
button.show();
|
|
969
|
-
// Clicking on a flyout button or label is a lot like clicking on the
|
|
970
|
-
// flyout background.
|
|
971
|
-
this.listeners_.push(browserEvents.conditionalBind(
|
|
972
|
-
buttonSvg, 'mousedown', this, this.onMouseDown_));
|
|
973
|
-
|
|
974
|
-
this.buttons_.push(button);
|
|
975
|
-
};
|
|
976
1005
|
|
|
977
|
-
/**
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
'x': x,
|
|
997
|
-
'y': y,
|
|
998
|
-
'height': blockHW.height,
|
|
999
|
-
'width': blockHW.width,
|
|
1000
|
-
},
|
|
1001
|
-
null);
|
|
1002
|
-
rect.tooltip = block;
|
|
1003
|
-
Tooltip.bindMouseEvents(rect);
|
|
1004
|
-
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
|
1005
|
-
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
|
1006
|
-
|
|
1007
|
-
block.flyoutRect_ = rect;
|
|
1008
|
-
this.mats_[index] = rect;
|
|
1009
|
-
return rect;
|
|
1010
|
-
};
|
|
1006
|
+
/**
|
|
1007
|
+
* Initialize the given button: move it to the correct location,
|
|
1008
|
+
* add listeners, etc.
|
|
1009
|
+
* @param {!FlyoutButton} button The button to initialize and place.
|
|
1010
|
+
* @param {number} x The x position of the cursor during this layout pass.
|
|
1011
|
+
* @param {number} y The y position of the cursor during this layout pass.
|
|
1012
|
+
* @protected
|
|
1013
|
+
*/
|
|
1014
|
+
initFlyoutButton_(button, x, y) {
|
|
1015
|
+
const buttonSvg = button.createDom();
|
|
1016
|
+
button.moveTo(x, y);
|
|
1017
|
+
button.show();
|
|
1018
|
+
// Clicking on a flyout button or label is a lot like clicking on the
|
|
1019
|
+
// flyout background.
|
|
1020
|
+
this.listeners_.push(browserEvents.conditionalBind(
|
|
1021
|
+
buttonSvg, 'mousedown', this, this.onMouseDown_));
|
|
1022
|
+
|
|
1023
|
+
this.buttons_.push(button);
|
|
1024
|
+
}
|
|
1011
1025
|
|
|
1012
|
-
/**
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1026
|
+
/**
|
|
1027
|
+
* Create and place a rectangle corresponding to the given block.
|
|
1028
|
+
* @param {!BlockSvg} block The block to associate the rect to.
|
|
1029
|
+
* @param {number} x The x position of the cursor during this layout pass.
|
|
1030
|
+
* @param {number} y The y position of the cursor during this layout pass.
|
|
1031
|
+
* @param {!{height: number, width: number}} blockHW The height and width of
|
|
1032
|
+
* the block.
|
|
1033
|
+
* @param {number} index The index into the mats list where this rect should
|
|
1034
|
+
* be placed.
|
|
1035
|
+
* @return {!SVGElement} Newly created SVG element for the rectangle behind
|
|
1036
|
+
* the block.
|
|
1037
|
+
* @protected
|
|
1038
|
+
*/
|
|
1039
|
+
createRect_(block, x, y, blockHW, index) {
|
|
1040
|
+
// Create an invisible rectangle under the block to act as a button. Just
|
|
1041
|
+
// using the block as a button is poor, since blocks have holes in them.
|
|
1042
|
+
const rect = dom.createSvgElement(
|
|
1043
|
+
Svg.RECT, {
|
|
1044
|
+
'fill-opacity': 0,
|
|
1045
|
+
'x': x,
|
|
1046
|
+
'y': y,
|
|
1047
|
+
'height': blockHW.height,
|
|
1048
|
+
'width': blockHW.width,
|
|
1049
|
+
},
|
|
1050
|
+
null);
|
|
1051
|
+
rect.tooltip = block;
|
|
1052
|
+
Tooltip.bindMouseEvents(rect);
|
|
1053
|
+
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
|
1054
|
+
this.workspace_.getCanvas().insertBefore(rect, block.getSvgRoot());
|
|
1055
|
+
|
|
1056
|
+
this.rectMap_.set(block, rect);
|
|
1057
|
+
this.mats_[index] = rect;
|
|
1058
|
+
return rect;
|
|
1059
|
+
}
|
|
1023
1060
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Move a rectangle to sit exactly behind a block, taking into account tabs,
|
|
1063
|
+
* hats, and any other protrusions we invent.
|
|
1064
|
+
* @param {!SVGElement} rect The rectangle to move directly behind the block.
|
|
1065
|
+
* @param {!BlockSvg} block The block the rectangle should be behind.
|
|
1066
|
+
* @protected
|
|
1067
|
+
*/
|
|
1068
|
+
moveRectToBlock_(rect, block) {
|
|
1069
|
+
const blockHW = block.getHeightWidth();
|
|
1070
|
+
rect.setAttribute('width', blockHW.width);
|
|
1071
|
+
rect.setAttribute('height', blockHW.height);
|
|
1072
|
+
|
|
1073
|
+
const blockXY = block.getRelativeToSurfaceXY();
|
|
1074
|
+
rect.setAttribute('y', blockXY.y);
|
|
1075
|
+
rect.setAttribute('x', this.RTL ? blockXY.x - blockHW.width : blockXY.x);
|
|
1076
|
+
}
|
|
1028
1077
|
|
|
1029
|
-
/**
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
block
|
|
1043
|
-
|
|
1078
|
+
/**
|
|
1079
|
+
* Filter the blocks on the flyout to disable the ones that are above the
|
|
1080
|
+
* capacity limit. For instance, if the user may only place two more blocks
|
|
1081
|
+
* on the workspace, an "a + b" block that has two shadow blocks would be
|
|
1082
|
+
* disabled.
|
|
1083
|
+
* @private
|
|
1084
|
+
*/
|
|
1085
|
+
filterForCapacity_() {
|
|
1086
|
+
const blocks = this.workspace_.getTopBlocks(false);
|
|
1087
|
+
for (let i = 0, block; (block = blocks[i]); i++) {
|
|
1088
|
+
if (this.permanentlyDisabled_.indexOf(block) === -1) {
|
|
1089
|
+
const enable = this.targetWorkspace.isCapacityAvailable(
|
|
1090
|
+
common.getBlockTypeCounts(block));
|
|
1091
|
+
while (block) {
|
|
1092
|
+
block.setEnabled(enable);
|
|
1093
|
+
block = block.getNextBlock();
|
|
1094
|
+
}
|
|
1044
1095
|
}
|
|
1045
1096
|
}
|
|
1046
1097
|
}
|
|
1047
|
-
};
|
|
1048
1098
|
|
|
1049
|
-
/**
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1099
|
+
/**
|
|
1100
|
+
* Reflow blocks and their mats.
|
|
1101
|
+
*/
|
|
1102
|
+
reflow() {
|
|
1103
|
+
if (this.reflowWrapper_) {
|
|
1104
|
+
this.workspace_.removeChangeListener(this.reflowWrapper_);
|
|
1105
|
+
}
|
|
1106
|
+
this.reflowInternal_();
|
|
1107
|
+
if (this.reflowWrapper_) {
|
|
1108
|
+
this.workspace_.addChangeListener(this.reflowWrapper_);
|
|
1109
|
+
}
|
|
1059
1110
|
}
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* @return {boolean} True if this flyout may be scrolled with a scrollbar or by
|
|
1064
|
-
* dragging.
|
|
1065
|
-
* @package
|
|
1066
|
-
*/
|
|
1067
|
-
Flyout.prototype.isScrollable = function() {
|
|
1068
|
-
return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() :
|
|
1069
|
-
false;
|
|
1070
|
-
};
|
|
1071
1111
|
|
|
1072
|
-
/**
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const svgRootOld = oldBlock.getSvgRoot();
|
|
1081
|
-
if (!svgRootOld) {
|
|
1082
|
-
throw Error('oldBlock is not rendered.');
|
|
1112
|
+
/**
|
|
1113
|
+
* @return {boolean} True if this flyout may be scrolled with a scrollbar or
|
|
1114
|
+
* by dragging.
|
|
1115
|
+
* @package
|
|
1116
|
+
*/
|
|
1117
|
+
isScrollable() {
|
|
1118
|
+
return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() :
|
|
1119
|
+
false;
|
|
1083
1120
|
}
|
|
1084
1121
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1122
|
+
/**
|
|
1123
|
+
* Copy a block from the flyout to the workspace and position it correctly.
|
|
1124
|
+
* @param {!BlockSvg} oldBlock The flyout block to copy.
|
|
1125
|
+
* @return {!BlockSvg} The new block in the main workspace.
|
|
1126
|
+
* @private
|
|
1127
|
+
*/
|
|
1128
|
+
placeNewBlock_(oldBlock) {
|
|
1129
|
+
const targetWorkspace = this.targetWorkspace;
|
|
1130
|
+
const svgRootOld = oldBlock.getSvgRoot();
|
|
1131
|
+
if (!svgRootOld) {
|
|
1132
|
+
throw Error('oldBlock is not rendered.');
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Clone the block.
|
|
1136
|
+
const json = /** @type {!blocks.State} */ (blocks.save(oldBlock));
|
|
1137
|
+
// Normallly this resizes leading to weird jumps. Save it for terminateDrag.
|
|
1138
|
+
targetWorkspace.setResizesEnabled(false);
|
|
1139
|
+
const block =
|
|
1140
|
+
/** @type {!BlockSvg} */ (blocks.append(json, targetWorkspace));
|
|
1090
1141
|
|
|
1091
|
-
|
|
1142
|
+
this.positionNewBlock_(oldBlock, block);
|
|
1092
1143
|
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1144
|
+
return block;
|
|
1145
|
+
}
|
|
1095
1146
|
|
|
1096
|
-
/**
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1147
|
+
/**
|
|
1148
|
+
* Positions a block on the target workspace.
|
|
1149
|
+
* @param {!BlockSvg} oldBlock The flyout block being copied.
|
|
1150
|
+
* @param {!BlockSvg} block The block to posiiton.
|
|
1151
|
+
* @private
|
|
1152
|
+
*/
|
|
1153
|
+
positionNewBlock_(oldBlock, block) {
|
|
1154
|
+
const targetWorkspace = this.targetWorkspace;
|
|
1155
|
+
|
|
1156
|
+
// The offset in pixels between the main workspace's origin and the upper
|
|
1157
|
+
// left corner of the injection div.
|
|
1158
|
+
const mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels();
|
|
1159
|
+
|
|
1160
|
+
// The offset in pixels between the flyout workspace's origin and the upper
|
|
1161
|
+
// left corner of the injection div.
|
|
1162
|
+
const flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels();
|
|
1163
|
+
|
|
1164
|
+
// The position of the old block in flyout workspace coordinates.
|
|
1165
|
+
const oldBlockPos = oldBlock.getRelativeToSurfaceXY();
|
|
1166
|
+
// The position of the old block in pixels relative to the flyout
|
|
1167
|
+
// workspace's origin.
|
|
1168
|
+
oldBlockPos.scale(this.workspace_.scale);
|
|
1169
|
+
|
|
1170
|
+
// The position of the old block in pixels relative to the upper left corner
|
|
1171
|
+
// of the injection div.
|
|
1172
|
+
const oldBlockOffsetPixels =
|
|
1173
|
+
Coordinate.sum(flyoutOffsetPixels, oldBlockPos);
|
|
1174
|
+
|
|
1175
|
+
// The position of the old block in pixels relative to the origin of the
|
|
1176
|
+
// main workspace.
|
|
1177
|
+
const finalOffset =
|
|
1178
|
+
Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels);
|
|
1179
|
+
// The position of the old block in main workspace coordinates.
|
|
1180
|
+
finalOffset.scale(1 / targetWorkspace.scale);
|
|
1181
|
+
|
|
1182
|
+
block.moveTo(new Coordinate(finalOffset.x, finalOffset.y));
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1132
1185
|
|
|
1133
1186
|
/**
|
|
1134
1187
|
* Returns the bounding rectangle of the drag target area in pixel units
|