diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js
index 3a2f3b23e..87104b08d 100644
--- a/blockly_uncompressed.js
+++ b/blockly_uncompressed.js
@@ -24,14 +24,13 @@ this.BLOCKLY_BOOT = function(root) {
goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.ASTNode', 'Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Tooltip', 'Blockly.Workspace', 'Blockly.constants', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.deprecation', 'Blockly.utils.object', 'Blockly.utils.string'], {'lang': 'es5'});
goog.addDependency('../../core/block_animations.js', ['Blockly.blockAnimations'], ['Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/block_drag_surface.js', ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
-goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
-goog.addDependency('../../core/block_events.js', ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
-goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
-goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.ShortcutRegistry', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.colour'], {});
+goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Drag', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
+goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Selected', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
+goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Events.UiBase', 'Blockly.Procedures', 'Blockly.ShortcutRegistry', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.colour'], {});
goog.addDependency('../../core/blocks.js', ['Blockly.Blocks'], [], {});
goog.addDependency('../../core/bubble.js', ['Blockly.Bubble'], ['Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate'], {});
-goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
+goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.deprecation'], {});
goog.addDependency('../../core/connection_checker.js', ['Blockly.ConnectionChecker'], ['Blockly.constants', 'Blockly.registry'], {});
goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection', 'Blockly.constants'], {});
@@ -41,8 +40,22 @@ goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems
goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRegistry'], ['Blockly.ContextMenuItems'], {'lang': 'es5'});
goog.addDependency('../../core/css.js', ['Blockly.Css'], [], {'lang': 'es5'});
goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {});
-goog.addDependency('../../core/events.js', ['Blockly.Events'], ['Blockly.registry', 'Blockly.utils'], {});
-goog.addDependency('../../core/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {});
+goog.addDependency('../../core/events/block_events.js', ['Blockly.Events.BlockBase', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Events.Change', 'Blockly.Events.Create', 'Blockly.Events.Delete', 'Blockly.Events.Move'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
+goog.addDependency('../../core/events/events.js', ['Blockly.Events'], ['Blockly.registry', 'Blockly.utils'], {});
+goog.addDependency('../../core/events/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {});
+goog.addDependency('../../core/events/events_bubble_open.js', ['Blockly.Events.BubbleOpen'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_click.js', ['Blockly.Events.Click'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_drag.js', ['Blockly.Events.Drag'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_marker_move.js', ['Blockly.Events.MarkerMove'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_selected.js', ['Blockly.Events.Selected'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_theme_change.js', ['Blockly.Events.ThemeChange'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_toolbox_select.js', ['Blockly.Events.ToolboxItemSelect'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_trashcan_open.js', ['Blockly.Events.TrashcanOpen'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/events_viewport.js', ['Blockly.Events.ViewportChange'], ['Blockly.Events', 'Blockly.Events.UiBase', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/ui_events.js', ['Blockly.Events.Ui', 'Blockly.Events.UiBase'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/variable_events.js', ['Blockly.Events.VarBase', 'Blockly.Events.VarCreate', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es5'});
+goog.addDependency('../../core/events/ws_comment_events.js', ['Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils'], {});
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.Tooltip', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {'lang': 'es5'});
goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
@@ -63,7 +76,7 @@ goog.addDependency('../../core/flyout_dragger.js', ['Blockly.FlyoutDragger'], ['
goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object'], {});
goog.addDependency('../../core/flyout_vertical.js', ['Blockly.VerticalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/generator.js', ['Blockly.Generator'], ['Blockly.Block', 'Blockly.constants'], {});
-goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.ASTNode', 'Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate'], {});
+goog.addDependency('../../core/gesture.js', ['Blockly.Gesture'], ['Blockly.ASTNode', 'Blockly.BlockDragger', 'Blockly.BubbleDragger', 'Blockly.Events', 'Blockly.Events.Click', 'Blockly.FlyoutDragger', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceDragger', 'Blockly.blockAnimations', 'Blockly.constants', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate'], {});
goog.addDependency('../../core/grid.js', ['Blockly.Grid'], ['Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/icon.js', ['Blockly.Icon'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {});
@@ -96,7 +109,7 @@ goog.addDependency('../../core/marker_manager.js', ['Blockly.MarkerManager'], ['
goog.addDependency('../../core/menu.js', ['Blockly.Menu'], ['Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.style'], {});
goog.addDependency('../../core/menuitem.js', ['Blockly.MenuItem'], ['Blockly.utils.IdGenerator', 'Blockly.utils.aria', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/msg.js', ['Blockly.Msg'], ['Blockly.utils.global'], {});
-goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {});
+goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg', 'Blockly.constants'], {});
goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.IdGenerator', 'Blockly.utils.Metrics', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/procedures.js', ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.xml'], {});
@@ -108,7 +121,7 @@ goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRen
goog.addDependency('../../core/renderers/common/drawer.js', ['Blockly.blockRendering.Drawer'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.utils.svgPaths'], {});
goog.addDependency('../../core/renderers/common/i_path_object.js', ['Blockly.blockRendering.IPathObject'], [], {});
goog.addDependency('../../core/renderers/common/info.js', ['Blockly.blockRendering.RenderInfo'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.ExternalValueInput', 'Blockly.blockRendering.Hat', 'Blockly.blockRendering.InRowSpacer', 'Blockly.blockRendering.InlineInput', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.NextConnection', 'Blockly.blockRendering.OutputConnection', 'Blockly.blockRendering.PreviousConnection', 'Blockly.blockRendering.RoundCorner', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.SquareCorner', 'Blockly.blockRendering.StatementInput', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types', 'Blockly.constants'], {});
-goog.addDependency('../../core/renderers/common/marker_svg.js', ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
+goog.addDependency('../../core/renderers/common/marker_svg.js', ['Blockly.blockRendering.MarkerSvg'], ['Blockly.ASTNode', 'Blockly.Events.MarkerMove', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/renderers/common/path_object.js', ['Blockly.blockRendering.PathObject'], ['Blockly.Theme', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.IPathObject', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/renderers/common/renderer.js', ['Blockly.blockRendering.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering.ConstantProvider', 'Blockly.blockRendering.Drawer', 'Blockly.blockRendering.IPathObject', 'Blockly.blockRendering.MarkerSvg', 'Blockly.blockRendering.PathObject', 'Blockly.blockRendering.RenderInfo', 'Blockly.constants'], {});
goog.addDependency('../../core/renderers/geras/constants.js', ['Blockly.geras.ConstantProvider'], ['Blockly.blockRendering.ConstantProvider', 'Blockly.utils.object'], {'lang': 'es5'});
@@ -156,13 +169,12 @@ goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Bl
goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'});
goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es5'});
-goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'});
+goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.ToolboxItemSelect', 'Blockly.Touch', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'});
goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], [], {});
goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.utils.string'], {});
goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string'], {});
goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {});
-goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Scrollbar', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {});
-goog.addDependency('../../core/ui_events.js', ['Blockly.Events.Ui'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Events.TrashcanOpen', 'Blockly.Scrollbar', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {});
goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], [], {});
goog.addDependency('../../core/utils/colour.js', ['Blockly.utils.colour'], [], {});
@@ -184,23 +196,20 @@ goog.addDependency('../../core/utils/svg_paths.js', ['Blockly.utils.svgPaths'],
goog.addDependency('../../core/utils/toolbox.js', ['Blockly.utils.toolbox'], ['Blockly.constants'], {});
goog.addDependency('../../core/utils/useragent.js', ['Blockly.utils.userAgent'], ['Blockly.utils.global'], {});
goog.addDependency('../../core/utils/xml.js', ['Blockly.utils.xml'], [], {});
-goog.addDependency('../../core/variable_events.js', ['Blockly.Events.VarBase', 'Blockly.Events.VarCreate', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.object'], {});
goog.addDependency('../../core/variable_map.js', ['Blockly.VariableMap'], ['Blockly.Events', 'Blockly.Events.VarDelete', 'Blockly.Events.VarRename', 'Blockly.Msg', 'Blockly.utils', 'Blockly.utils.object'], {});
goog.addDependency('../../core/variable_model.js', ['Blockly.VariableModel'], ['Blockly.Events', 'Blockly.Events.VarCreate', 'Blockly.utils'], {});
goog.addDependency('../../core/variables.js', ['Blockly.Variables'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/variables_dynamic.js', ['Blockly.VariablesDynamic'], ['Blockly.Blocks', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.utils.xml'], {});
-goog.addDependency('../../core/warning.js', ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/warning.js', ['Blockly.Warning'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BubbleOpen', 'Blockly.Icon', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/widgetdiv.js', ['Blockly.WidgetDiv'], ['Blockly.utils.style'], {});
goog.addDependency('../../core/workspace.js', ['Blockly.Workspace'], ['Blockly.ConnectionChecker', 'Blockly.Events', 'Blockly.Options', 'Blockly.VariableMap', 'Blockly.utils', 'Blockly.utils.math'], {});
goog.addDependency('../../core/workspace_audio.js', ['Blockly.WorkspaceAudio'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.userAgent'], {'lang': 'es5'});
goog.addDependency('../../core/workspace_comment.js', ['Blockly.WorkspaceComment'], ['Blockly.Events', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/workspace_comment_render_svg.js', ['Blockly.WorkspaceCommentSvg.render'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
-goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Ui', 'Blockly.WorkspaceComment', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
+goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Selected', 'Blockly.WorkspaceComment', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/workspace_drag_surface_svg.js', ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/workspace_dragger.js', ['Blockly.WorkspaceDragger'], ['Blockly.utils.Coordinate'], {});
-goog.addDependency('../../core/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es5'});
-goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
-goog.addDependency('../../core/ws_comment_events.js', ['Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.registry', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
+goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.ThemeChange', 'Blockly.Events.ViewportChange', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.constants', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'});
goog.addDependency("base.js", [], []);
diff --git a/core/block_dragger.js b/core/block_dragger.js
index 1cb36e4a3..ef55a2be1 100644
--- a/core/block_dragger.js
+++ b/core/block_dragger.js
@@ -16,7 +16,7 @@ goog.require('Blockly.blockAnimations');
goog.require('Blockly.constants');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockMove');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.Drag');
goog.require('Blockly.InsertionMarkerManager');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
@@ -184,8 +184,8 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
* @private
*/
Blockly.BlockDragger.prototype.fireDragStartEvent_ = function() {
- var event = new Blockly.Events.Ui(this.draggingBlock_, 'dragStart',
- null, this.draggingBlock_.getDescendants(false));
+ var event = new Blockly.Events.Drag(this.draggingBlock_, true,
+ this.draggingBlock_.getDescendants(false));
Blockly.Events.fire(event);
};
@@ -261,8 +261,8 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
* @private
*/
Blockly.BlockDragger.prototype.fireDragEndEvent_ = function() {
- var event = new Blockly.Events.Ui(this.draggingBlock_, 'dragStop',
- this.draggingBlock_.getDescendants(false), null);
+ var event = new Blockly.Events.Drag(this.draggingBlock_, false,
+ this.draggingBlock_.getDescendants(false));
Blockly.Events.fire(event);
};
diff --git a/core/block_svg.js b/core/block_svg.js
index 45b05cf41..95f711424 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -20,8 +20,8 @@ goog.require('Blockly.constants');
goog.require('Blockly.ContextMenu');
goog.require('Blockly.ContextMenuRegistry');
goog.require('Blockly.Events');
-goog.require('Blockly.Events.Ui');
goog.require('Blockly.Events.BlockMove');
+goog.require('Blockly.Events.Selected');
goog.require('Blockly.Msg');
goog.require('Blockly.navigation');
goog.require('Blockly.RenderedConnection');
@@ -300,8 +300,7 @@ Blockly.BlockSvg.prototype.select = function() {
Blockly.Events.enable();
}
}
- var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
- event.workspaceId = this.workspace.id;
+ var event = new Blockly.Events.Selected(oldId, this.id, this.workspace.id);
Blockly.Events.fire(event);
Blockly.selected = this;
this.addSelect();
@@ -314,7 +313,7 @@ Blockly.BlockSvg.prototype.unselect = function() {
if (Blockly.selected != this) {
return;
}
- var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
+ var event = new Blockly.Events.Selected(this.id, null, this.workspace.id);
event.workspaceId = this.workspace.id;
Blockly.Events.fire(event);
Blockly.selected = null;
diff --git a/core/blockly.js b/core/blockly.js
index c48d57b83..acf235efd 100644
--- a/core/blockly.js
+++ b/core/blockly.js
@@ -19,6 +19,7 @@ goog.provide('Blockly');
goog.require('Blockly.constants');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.UiBase');
goog.require('Blockly.inject');
goog.require('Blockly.Procedures');
goog.require('Blockly.ShortcutRegistry');
diff --git a/core/comment.js b/core/comment.js
index cc8b5a681..6dd541651 100644
--- a/core/comment.js
+++ b/core/comment.js
@@ -16,7 +16,7 @@ goog.require('Blockly.Bubble');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.utils.deprecation');
goog.require('Blockly.utils.dom');
@@ -236,7 +236,7 @@ Blockly.Comment.prototype.setVisible = function(visible) {
return;
}
Blockly.Events.fire(
- new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
+ new Blockly.Events.BubbleOpen(this.block_, visible, 'comment'));
this.model_.pinned = visible;
if (visible) {
this.createBubble_();
diff --git a/core/block_events.js b/core/events/block_events.js
similarity index 96%
rename from core/block_events.js
rename to core/events/block_events.js
index 03e80b26b..5f81cf26d 100644
--- a/core/block_events.js
+++ b/core/events/block_events.js
@@ -128,6 +128,7 @@ Blockly.Events.Change.prototype.toJson = function() {
if (this.name) {
json['name'] = this.name;
}
+ json['oldValue'] = this.oldValue;
json['newValue'] = this.newValue;
return json;
};
@@ -140,6 +141,7 @@ Blockly.Events.Change.prototype.fromJson = function(json) {
Blockly.Events.Change.superClass_.fromJson.call(this, json);
this.element = json['element'];
this.name = json['name'];
+ this.oldValue = json['oldValue'];
this.newValue = json['newValue'];
};
@@ -255,6 +257,9 @@ Blockly.Events.Create.prototype.toJson = function() {
var json = Blockly.Events.Create.superClass_.toJson.call(this);
json['xml'] = Blockly.Xml.domToText(this.xml);
json['ids'] = this.ids;
+ if (!this.recordUndo) {
+ json['recordUndo'] = this.recordUndo;
+ }
return json;
};
@@ -266,6 +271,9 @@ Blockly.Events.Create.prototype.fromJson = function(json) {
Blockly.Events.Create.superClass_.fromJson.call(this, json);
this.xml = Blockly.Xml.textToDom(json['xml']);
this.ids = json['ids'];
+ if (json['recordUndo'] !== undefined) {
+ this.recordUndo = json['recordUndo'];
+ }
};
/**
@@ -340,7 +348,11 @@ Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE;
*/
Blockly.Events.Delete.prototype.toJson = function() {
var json = Blockly.Events.Delete.superClass_.toJson.call(this);
+ json['oldXml'] = Blockly.Xml.domToText(this.oldXml);
json['ids'] = this.ids;
+ if (!this.recordUndo) {
+ json['recordUndo'] = this.recordUndo;
+ }
return json;
};
@@ -350,7 +362,11 @@ Blockly.Events.Delete.prototype.toJson = function() {
*/
Blockly.Events.Delete.prototype.fromJson = function(json) {
Blockly.Events.Delete.superClass_.fromJson.call(this, json);
+ this.oldXml = Blockly.Xml.textToDom(json['oldXml']);
this.ids = json['ids'];
+ if (json['recordUndo'] !== undefined) {
+ this.recordUndo = json['recordUndo'];
+ }
};
/**
@@ -430,6 +446,9 @@ Blockly.Events.Move.prototype.toJson = function() {
json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
Math.round(this.newCoordinate.y);
}
+ if (!this.recordUndo) {
+ json['recordUndo'] = this.recordUndo;
+ }
return json;
};
@@ -446,6 +465,9 @@ Blockly.Events.Move.prototype.fromJson = function(json) {
this.newCoordinate =
new Blockly.utils.Coordinate(Number(xy[0]), Number(xy[1]));
}
+ if (json['recordUndo'] !== undefined) {
+ this.recordUndo = json['recordUndo'];
+ }
};
/**
diff --git a/core/events.js b/core/events/events.js
similarity index 88%
rename from core/events.js
rename to core/events/events.js
index a15e99e2b..36469840a 100644
--- a/core/events.js
+++ b/core/events/events.js
@@ -107,11 +107,65 @@ Blockly.Events.VAR_DELETE = 'var_delete';
Blockly.Events.VAR_RENAME = 'var_rename';
/**
- * Name of event that records a UI change.
+ * Name of generic event that records a UI change.
* @const
*/
Blockly.Events.UI = 'ui';
+/**
+ * Name of event that record a block drags a block.
+ * @const
+ */
+Blockly.Events.BLOCK_DRAG = 'drag';
+
+/**
+ * Name of event that records a change in selected element.
+ * @const
+ */
+Blockly.Events.SELECTED = 'selected';
+
+/**
+ * Name of event that records a click.
+ * @const
+ */
+Blockly.Events.CLICK = 'click';
+
+/**
+ * Name of event that records a marker move.
+ * @const
+ */
+Blockly.Events.MARKER_MOVE = 'marker_move';
+
+/**
+ * Name of event that records a bubble open.
+ * @const
+ */
+Blockly.Events.BUBBLE_OPEN = 'bubble_open';
+
+/**
+ * Name of event that records a trashcan open.
+ * @const
+ */
+Blockly.Events.TRASHCAN_OPEN = 'trashcan_open';
+
+/**
+ * Name of event that records a toolbox item change.
+ * @const
+ */
+Blockly.Events.TOOLBOX_ITEM_CHANGE = 'toolbox_item_change';
+
+/**
+ * Name of event that records a theme change.
+ * @const
+ */
+Blockly.Events.THEME_CHANGE = 'theme_change';
+
+/**
+ * Name of event that records a viewport change.
+ * @const
+ */
+Blockly.Events.VIEWPORT_CHANGE = 'viewport_change';
+
/**
* Name of event that creates a comment.
* @const
@@ -212,7 +266,9 @@ Blockly.Events.filter = function(queueIn, forward) {
// Merge duplicates.
for (var i = 0, event; (event = queue[i]); i++) {
if (!event.isNull()) {
- var key = [event.type, event.blockId, event.workspaceId].join(' ');
+ // Treat all ui events as the same type in hash table.
+ var eventType = event.isUiEvent ? Blockly.Events.UI : event.type;
+ var key = [eventType, event.blockId, event.workspaceId].join(' ');
var lastEntry = hash[key];
var lastEvent = lastEntry ? lastEntry.event : null;
@@ -234,11 +290,8 @@ Blockly.Events.filter = function(queueIn, forward) {
event.name == lastEvent.name) {
// Merge change events.
lastEvent.newValue = event.newValue;
- } else if (event.type == Blockly.Events.UI &&
- event.element == 'click' &&
- (lastEvent.element == 'commentOpen' ||
- lastEvent.element == 'mutatorOpen' ||
- lastEvent.element == 'warningOpen')) {
+ } else if (event.type == Blockly.Events.CLICK &&
+ lastEvent.type == Blockly.Events.BUBBLE_OPEN) {
// Drop click events caused by opening/closing bubbles.
} else {
// Collision: newer events should merge into this event to maintain
diff --git a/core/events_abstract.js b/core/events/events_abstract.js
similarity index 95%
rename from core/events_abstract.js
rename to core/events/events_abstract.js
index 948649f9a..f36fcc3e6 100644
--- a/core/events_abstract.js
+++ b/core/events/events_abstract.js
@@ -49,6 +49,12 @@ Blockly.Events.Abstract = function() {
this.recordUndo = Blockly.Events.recordUndo;
};
+/**
+ * Whether or not the event is a ui event.
+ * @type {boolean}
+ */
+Blockly.Events.Abstract.prototype.isUiEvent = false;
+
/**
* Encode the event as JSON.
* @return {!Object} JSON representation.
diff --git a/core/events/events_bubble_open.js b/core/events/events_bubble_open.js
new file mode 100644
index 000000000..4f276f2f2
--- /dev/null
+++ b/core/events/events_bubble_open.js
@@ -0,0 +1,79 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of bubble open.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.BubbleOpen');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a bubble open event.
+ * @param {Blockly.BlockSvg} opt_block The associated block. Undefined for a
+ * blank event.
+ * @param {boolean=} opt_isOpen Whether the bubble is opening (false if closing).
+ * @param {string=} opt_element The type of bubble. One of 'mutator', 'comment'
+ * or 'warning'
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.BubbleOpen = function(opt_block, opt_isOpen, opt_element) {
+ var workspaceId = opt_block ? opt_block.workspace.id : undefined;
+ Blockly.Events.BubbleOpen.superClass_.constructor.call(this, workspaceId);
+ this.blockId = opt_block ? opt_block.id : null;
+
+ /**
+ * Whether the bubble is opening (false if closing).
+ * @type {boolean|undefined}
+ */
+ this.isOpen = opt_isOpen;
+
+ /**
+ * The type of bubble. One of 'mutator', 'comment', or 'warning'.
+ * @type {string|undefined}
+ */
+ this.element = opt_element;
+};
+Blockly.utils.object.inherits(Blockly.Events.BubbleOpen, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.BubbleOpen.prototype.type = Blockly.Events.BUBBLE_OPEN;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.BubbleOpen.prototype.toJson = function() {
+ var json = Blockly.Events.BubbleOpen.superClass_.toJson.call(this);
+ json['isOpen'] = this.isOpen;
+ json['element'] = this.element;
+ json['blockId'] = this.blockId;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.BubbleOpen.prototype.fromJson = function(json) {
+ Blockly.Events.BubbleOpen.superClass_.fromJson.call(this, json);
+ this.isOpen = json['isOpen'];
+ this.element = json['element'];
+ this.blockId = json['blockId'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.BUBBLE_OPEN, Blockly.Events.BubbleOpen);
diff --git a/core/events/events_click.js b/core/events/events_click.js
new file mode 100644
index 000000000..d5c355906
--- /dev/null
+++ b/core/events/events_click.js
@@ -0,0 +1,78 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of UI click in Blockly's editor.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.Click');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a click event.
+ * @param {?Blockly.Block=} opt_block The affected block. Null for click events
+ * that do not have an associated block (i.e. workspace click). Undefined
+ * for a blank event.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * @param {string=} opt_targetType The type of element targeted by this click
+ * event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.Click = function(opt_block, opt_workspaceId, opt_targetType) {
+ var workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
+ Blockly.Events.Click.superClass_.constructor.call(this, workspaceId);
+ this.blockId = opt_block ? opt_block.id : null;
+
+ if (!opt_targetType && !this.isBlank) {
+ opt_targetType = opt_block ? 'block' : 'workspace';
+ }
+
+ /**
+ * The type of element targeted by this click event.
+ * @type {string|undefined}
+ */
+ this.targetType = opt_targetType;
+};
+Blockly.utils.object.inherits(Blockly.Events.Click, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Click.prototype.type = Blockly.Events.CLICK;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Click.prototype.toJson = function() {
+ var json = Blockly.Events.Click.superClass_.toJson.call(this);
+ json['targetType'] = this.targetType;
+ if (this.blockId) {
+ json['blockId'] = this.blockId;
+ }
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Click.prototype.fromJson = function(json) {
+ Blockly.Events.Click.superClass_.fromJson.call(this, json);
+ this.targetType = json['targetType'];
+ this.blockId = json['blockId'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT, Blockly.Events.CLICK,
+ Blockly.Events.Click);
diff --git a/core/events/events_drag.js b/core/events/events_drag.js
new file mode 100644
index 000000000..07d7f6198
--- /dev/null
+++ b/core/events/events_drag.js
@@ -0,0 +1,79 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a block drag.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.Drag');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a click event.
+ * @param {!Blockly.Block=} opt_block The top block in the stack that is being
+ * dragged. Undefined for a blank event.
+ * @param {boolean=} opt_isStart Whether this is the start of a block drag.
+ * @param {!Array.=} opt_blocks The blocks affected by this
+ * drag. Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.Drag = function(opt_block, opt_isStart, opt_blocks) {
+ var workspaceId = opt_block ? opt_block.workspace.id : undefined;
+ Blockly.Events.Drag.superClass_.constructor.call(this, workspaceId);
+ this.blockId = opt_block ? opt_block.id : null;
+
+ /**
+ * Whether this is the start of a block drag. Undefined for blank event
+ * @type {boolean|undefined}
+ */
+ this.isStart = opt_isStart;
+
+ /**
+ * The blocks affected by this drag event. Undefined for blank event
+ * @type {!Array.|undefined}
+ */
+ this.blocks = opt_blocks;
+};
+Blockly.utils.object.inherits(Blockly.Events.Drag, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Drag.prototype.type = Blockly.Events.BLOCK_DRAG;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Drag.prototype.toJson = function() {
+ var json = Blockly.Events.Drag.superClass_.toJson.call(this);
+ json['isStart'] = this.isStart;
+ json['blockId'] = this.blockId;
+ json['blocks'] = this.blocks;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Drag.prototype.fromJson = function(json) {
+ Blockly.Events.Drag.superClass_.fromJson.call(this, json);
+ this.isStart = json['isStart'];
+ this.blockId = json['blockId'];
+ this.blocks = json['blocks'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.BLOCK_DRAG, Blockly.Events.Drag);
diff --git a/core/events/events_marker_move.js b/core/events/events_marker_move.js
new file mode 100644
index 000000000..08bbe2c1d
--- /dev/null
+++ b/core/events/events_marker_move.js
@@ -0,0 +1,100 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of a marker move.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.MarkerMove');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a marker move event.
+ * @param {?Blockly.Block=} opt_block The affected block. Null if current node
+ * is of type workspace. Undefined for a blank event.
+ * @param {boolean=} isCursor Whether this is a cursor event. Undefined for a
+ * blank event.
+ * @param {?Blockly.ASTNode=} opt_oldNode The old node the marker used to be on.
+ * Undefined for a blank event.
+ * @param {!Blockly.ASTNode=} opt_curNode The new node the marker is now on.
+ * Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.MarkerMove = function(opt_block, isCursor, opt_oldNode,
+ opt_curNode) {
+ var workspaceId = opt_block ? opt_block.workspace.id : undefined;
+ if (opt_curNode && opt_curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
+ workspaceId =
+ (/** @type {!Blockly.Workspace} */ (opt_curNode.getLocation())).id;
+ }
+ Blockly.Events.MarkerMove.superClass_.constructor.call(this, workspaceId);
+
+ /**
+ * The workspace identifier for this event.
+ * @type {?string}
+ */
+ this.blockId = opt_block ? opt_block.id : null;
+
+ /**
+ * The old node the marker used to be on.
+ * @type {?Blockly.ASTNode|undefined}
+ */
+ this.oldNode = opt_oldNode;
+
+ /**
+ * The new node the marker is now on.
+ * @type {Blockly.ASTNode|undefined}
+ */
+ this.curNode = opt_curNode;
+
+ /**
+ * Whether this is a cursor event.
+ * @type {boolean|undefined}
+ */
+ this.isCursor = isCursor;
+};
+Blockly.utils.object.inherits(Blockly.Events.MarkerMove, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.MarkerMove.prototype.type = Blockly.Events.MARKER_MOVE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.MarkerMove.prototype.toJson = function() {
+ var json = Blockly.Events.MarkerMove.superClass_.toJson.call(this);
+ json['isCursor'] = this.isCursor;
+ json['blockId'] = this.blockId;
+ json['oldNode'] = this.oldNode;
+ json['curNode'] = this.curNode;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.MarkerMove.prototype.fromJson = function(json) {
+ Blockly.Events.MarkerMove.superClass_.fromJson.call(this, json);
+ this.isCursor = json['isCursor'];
+ this.blockId = json['blockId'];
+ this.oldNode = json['oldNode'];
+ this.curNode = json['curNode'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.MARKER_MOVE, Blockly.Events.MarkerMove);
diff --git a/core/events/events_selected.js b/core/events/events_selected.js
new file mode 100644
index 000000000..bec0f55d2
--- /dev/null
+++ b/core/events/events_selected.js
@@ -0,0 +1,77 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of element select action.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.Selected');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a Selected event.
+ * @param {?string=} opt_oldElementId The id of the previously selected
+ * element. Null if no element last selected. Undefined for a blank event.
+ * @param {?string=} opt_elementId The id of the selected element. Null if no
+ * element currently selected (deselect). Undefined for a blank event.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * Null if no element previously selected. Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.Selected = function(opt_oldElementId, opt_elementId,
+ opt_workspaceId) {
+ Blockly.Events.Selected.superClass_.constructor.call(this, opt_workspaceId);
+
+ /**
+ * The id of the last selected element.
+ * @type {?string|undefined}
+ */
+ this.oldElementId = opt_oldElementId;
+
+ /**
+ * The id of the selected element.
+ * @type {?string|undefined}
+ */
+ this.elementId = opt_elementId;
+};
+Blockly.utils.object.inherits(Blockly.Events.Selected, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.Selected.prototype.type = Blockly.Events.SELECTED;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.Selected.prototype.toJson = function() {
+ var json = Blockly.Events.Selected.superClass_.toJson.call(this);
+ json['oldElementId'] = this.oldElementId;
+ json['elementId'] = this.elementId;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.Selected.prototype.fromJson = function(json) {
+ Blockly.Events.Selected.superClass_.fromJson.call(this, json);
+ this.oldElementId = json['oldElementId'];
+ this.elementId = json['elementId'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT, Blockly.Events.SELECTED,
+ Blockly.Events.Selected);
diff --git a/core/events/events_theme_change.js b/core/events/events_theme_change.js
new file mode 100644
index 000000000..39343846c
--- /dev/null
+++ b/core/events/events_theme_change.js
@@ -0,0 +1,65 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of a theme update.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.ThemeChange');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a theme change event.
+ * @param {string=} opt_themeName The theme name. Undefined for a blank event.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * event. Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.ThemeChange = function(opt_themeName, opt_workspaceId) {
+ Blockly.Events.ThemeChange.superClass_.constructor.call(this, opt_workspaceId);
+
+ /**
+ * The theme name.
+ * @type {string|undefined}
+ */
+ this.themeName = opt_themeName;
+};
+Blockly.utils.object.inherits(Blockly.Events.ThemeChange, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.ThemeChange.prototype.type = Blockly.Events.THEME_CHANGE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.ThemeChange.prototype.toJson = function() {
+ var json = Blockly.Events.ThemeChange.superClass_.toJson.call(this);
+ json['themeName'] = this.themeName;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.ThemeChange.prototype.fromJson = function(json) {
+ Blockly.Events.ThemeChange.superClass_.fromJson.call(this, json);
+ this.themeName = json['themeName'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.THEME_CHANGE, Blockly.Events.ThemeChange);
diff --git a/core/events/events_toolbox_select.js b/core/events/events_toolbox_select.js
new file mode 100644
index 000000000..d20d4288d
--- /dev/null
+++ b/core/events/events_toolbox_select.js
@@ -0,0 +1,78 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of selecting an item on the toolbox.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.ToolboxItemSelect');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a toolbox item select event.
+ * @param {?string=} opt_oldItem The previously selected toolbox item. Undefined
+ * for a blank event.
+ * @param {?string=} opt_newItem The newly selected toolbox item. Undefined for
+ * a blank event.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.ToolboxItemSelect = function(opt_oldItem, opt_newItem,
+ opt_workspaceId) {
+ Blockly.Events.ToolboxItemSelect.superClass_.constructor.call(
+ this, opt_workspaceId);
+
+ /**
+ * The previously selected toolbox item.
+ * @type {?string|undefined}
+ */
+ this.oldItem = opt_oldItem;
+
+ /**
+ * The newly selected toolbox item.
+ * @type {?string|undefined}
+ */
+ this.newItem = opt_newItem;
+};
+Blockly.utils.object.inherits(Blockly.Events.ToolboxItemSelect, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.ToolboxItemSelect.prototype.type = Blockly.Events.TOOLBOX_ITEM_CHANGE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.ToolboxItemSelect.prototype.toJson = function() {
+ var json = Blockly.Events.ToolboxItemSelect.superClass_.toJson.call(this);
+ json['oldItem'] = this.oldItem;
+ json['newItem'] = this.newItem;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.ToolboxItemSelect.prototype.fromJson = function(json) {
+ Blockly.Events.ToolboxItemSelect.superClass_.fromJson.call(this, json);
+ this.oldItem = json['oldItem'];
+ this.newItem = json['newItem'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.TOOLBOX_ITEM_CHANGE, Blockly.Events.ToolboxItemSelect);
diff --git a/core/events/events_trashcan_open.js b/core/events/events_trashcan_open.js
new file mode 100644
index 000000000..0ec08c674
--- /dev/null
+++ b/core/events/events_trashcan_open.js
@@ -0,0 +1,66 @@
+/**
+ * @license
+ * Copyright 2018 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of trashcan flyout open and close.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.TrashcanOpen');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a trashcan open event.
+ * @param {boolean=} opt_isOpen Whether the trashcan flyout is open. Undefined
+ * for a blank event.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * Undefined for a blank event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.TrashcanOpen = function(opt_isOpen, opt_workspaceId) {
+ Blockly.Events.TrashcanOpen.superClass_.constructor.call(this, opt_workspaceId);
+
+ /**
+ * Whether the trashcan flyout is open.
+ * @type {boolean|undefined}
+ */
+ this.isOpen = opt_isOpen;
+};
+Blockly.utils.object.inherits(Blockly.Events.TrashcanOpen, Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.TrashcanOpen.prototype.type = Blockly.Events.TRASHCAN_OPEN;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.TrashcanOpen.prototype.toJson = function() {
+ var json = Blockly.Events.TrashcanOpen.superClass_.toJson.call(this);
+ json['isOpen'] = this.isOpen;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.TrashcanOpen.prototype.fromJson = function(json) {
+ Blockly.Events.TrashcanOpen.superClass_.fromJson.call(this, json);
+ this.isOpen = json['isOpen'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.TRASHCAN_OPEN, Blockly.Events.TrashcanOpen);
diff --git a/core/events/events_viewport.js b/core/events/events_viewport.js
new file mode 100644
index 000000000..0086074ac
--- /dev/null
+++ b/core/events/events_viewport.js
@@ -0,0 +1,88 @@
+/**
+ * @license
+ * Copyright 2020 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @fileoverview Events fired as a result of a viewport change.
+ * @author kozbial@google.com (Monica Kozbial)
+ */
+'use strict';
+
+goog.provide('Blockly.Events.ViewportChange');
+
+goog.require('Blockly.Events');
+goog.require('Blockly.Events.UiBase');
+goog.require('Blockly.registry');
+goog.require('Blockly.utils.object');
+
+/**
+ * Class for a viewport change event.
+ * @param {number=} opt_top Top-edge of the visible portion of the workspace,
+ * relative to the workspace origin.
+ * @param {number=} opt_left Left-edge of the visible portion of the workspace,
+ * relative to the workspace origin.
+ * @param {number=} opt_scale The scale of the workspace.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * @extends {Blockly.Events.UiBase}
+ * @constructor
+ */
+Blockly.Events.ViewportChange = function(opt_top, opt_left, opt_scale,
+ opt_workspaceId) {
+ Blockly.Events.ViewportChange.superClass_.constructor.call(this, opt_workspaceId);
+
+ /**
+ * Top-edge of the visible portion of the workspace, relative to the workspace
+ * origin.
+ * @type {number|undefined}
+ */
+ this.viewTop = opt_top;
+
+ /**
+ * Left-edge of the visible portion of the workspace, relative to the
+ * workspace origin.
+ * @type {number|undefined}
+ */
+ this.viewLeft = opt_left;
+
+ /**
+ * The scale of the workspace.
+ * @type {number|undefined}
+ */
+ this.scale = opt_scale;
+};
+Blockly.utils.object.inherits(Blockly.Events.ViewportChange,
+ Blockly.Events.UiBase);
+
+/**
+ * Type of this event.
+ * @type {string}
+ */
+Blockly.Events.ViewportChange.prototype.type = Blockly.Events.VIEWPORT_CHANGE;
+
+/**
+ * Encode the event as JSON.
+ * @return {!Object} JSON representation.
+ */
+Blockly.Events.ViewportChange.prototype.toJson = function() {
+ var json = Blockly.Events.ViewportChange.superClass_.toJson.call(this);
+ json['viewTop'] = this.viewTop;
+ json['viewLeft'] = this.viewLeft;
+ json['scale'] = this.scale;
+ return json;
+};
+
+/**
+ * Decode the JSON event.
+ * @param {!Object} json JSON representation.
+ */
+Blockly.Events.ViewportChange.prototype.fromJson = function(json) {
+ Blockly.Events.ViewportChange.superClass_.fromJson.call(this, json);
+ this.viewTop = json['viewTop'];
+ this.viewLeft = json['viewLeft'];
+ this.scale = json['scale'];
+};
+
+Blockly.registry.register(Blockly.registry.Type.EVENT,
+ Blockly.Events.VIEWPORT_CHANGE, Blockly.Events.ViewportChange);
diff --git a/core/ui_events.js b/core/events/ui_events.js
similarity index 69%
rename from core/ui_events.js
rename to core/events/ui_events.js
index 7991f6b86..f4267958f 100644
--- a/core/ui_events.js
+++ b/core/events/ui_events.js
@@ -11,42 +11,73 @@
'use strict';
goog.provide('Blockly.Events.Ui');
+goog.provide('Blockly.Events.UiBase');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Abstract');
goog.require('Blockly.registry');
goog.require('Blockly.utils.object');
-
/**
- * Class for a UI event.
+ * Base class for a UI event.
* UI events are events that don't need to be sent over the wire for multi-user
* editing to work (e.g. scrolling the workspace, zooming, opening toolbox
* categories).
* UI events do not undo or redo.
+ * @param {string=} opt_workspaceId The workspace identifier for this event.
+ * Undefined for a blank event.
+ * @extends {Blockly.Events.Abstract}
+ * @constructor
+ */
+Blockly.Events.UiBase = function(opt_workspaceId) {
+ Blockly.Events.UiBase.superClass_.constructor.call(this);
+
+ /**
+ * Whether or not the event is blank (to be populated by fromJson).
+ * @type {boolean}
+ */
+ this.isBlank = typeof opt_workspaceId == 'undefined';
+
+ /**
+ * The workspace identifier for this event.
+ * @type {string}
+ */
+ this.workspaceId = opt_workspaceId ? opt_workspaceId : '';
+
+ // UI events do not undo or redo.
+ this.recordUndo = false;
+};
+Blockly.utils.object.inherits(Blockly.Events.UiBase, Blockly.Events.Abstract);
+
+/**
+ * Whether or not the event is a UI event.
+ * @type {boolean}
+ */
+Blockly.Events.UiBase.prototype.isUiEvent = true;
+
+/**
+ * Class for a UI event.
* @param {?Blockly.Block=} opt_block The affected block. Null for UI events
* that do not have an associated block. Undefined for a blank event.
* @param {string=} opt_element One of 'selected', 'comment', 'mutatorOpen',
* etc.
* @param {*=} opt_oldValue Previous value of element.
* @param {*=} opt_newValue New value of element.
- * @extends {Blockly.Events.Abstract}
+ * @extends {Blockly.Events.UiBase}
+ * @deprecated December 2020. Instead use a more specific UI event.
* @constructor
*/
Blockly.Events.Ui = function(opt_block, opt_element, opt_oldValue,
opt_newValue) {
- Blockly.Events.Ui.superClass_.constructor.call(this);
- this.isBlank = typeof opt_block == 'undefined';
+ var workspaceId = opt_block ? opt_block.workspace.id : undefined;
+ Blockly.Events.Ui.superClass_.constructor.call(this, workspaceId);
this.blockId = opt_block ? opt_block.id : null;
- this.workspaceId = opt_block ? opt_block.workspace.id : undefined;
this.element = typeof opt_element == 'undefined' ? '' : opt_element;
this.oldValue = typeof opt_oldValue == 'undefined' ? '' : opt_oldValue;
this.newValue = typeof opt_newValue == 'undefined' ? '' : opt_newValue;
- // UI events do not undo or redo.
- this.recordUndo = false;
};
-Blockly.utils.object.inherits(Blockly.Events.Ui, Blockly.Events.Abstract);
+Blockly.utils.object.inherits(Blockly.Events.Ui, Blockly.Events.UiBase);
/**
* Type of this event.
diff --git a/core/variable_events.js b/core/events/variable_events.js
similarity index 100%
rename from core/variable_events.js
rename to core/events/variable_events.js
diff --git a/core/workspace_events.js b/core/events/workspace_events.js
similarity index 95%
rename from core/workspace_events.js
rename to core/events/workspace_events.js
index f150eb454..3bbb27d40 100644
--- a/core/workspace_events.js
+++ b/core/events/workspace_events.js
@@ -13,7 +13,7 @@
goog.provide('Blockly.Events.FinishedLoading');
goog.require('Blockly.Events');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.Abstract');
goog.require('Blockly.registry');
goog.require('Blockly.utils.object');
@@ -25,7 +25,7 @@ goog.require('Blockly.utils.object');
* Finished loading events do not record undo or redo.
* @param {!Blockly.Workspace=} opt_workspace The workspace that has finished
* loading. Undefined for a blank event.
- * @extends {Blockly.Events.Ui}
+ * @extends {Blockly.Events.Abstract}
* @constructor
*/
Blockly.Events.FinishedLoading = function(opt_workspace) {
@@ -54,7 +54,7 @@ Blockly.Events.FinishedLoading = function(opt_workspace) {
this.recordUndo = false;
};
Blockly.utils.object.inherits(Blockly.Events.FinishedLoading,
- Blockly.Events.Ui);
+ Blockly.Events.Abstract);
/**
* Type of this event.
diff --git a/core/ws_comment_events.js b/core/events/ws_comment_events.js
similarity index 100%
rename from core/ws_comment_events.js
rename to core/events/ws_comment_events.js
diff --git a/core/gesture.js b/core/gesture.js
index 160724f91..9c0ee71e7 100644
--- a/core/gesture.js
+++ b/core/gesture.js
@@ -19,7 +19,7 @@ goog.require('Blockly.BlockDragger');
goog.require('Blockly.BubbleDragger');
goog.require('Blockly.constants');
goog.require('Blockly.Events');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.Click');
goog.require('Blockly.FlyoutDragger');
goog.require('Blockly.navigation');
goog.require('Blockly.Tooltip');
@@ -665,8 +665,7 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
* @private
*/
Blockly.Gesture.prototype.fireWorkspaceClick_ = function(ws) {
- var clickEvent = new Blockly.Events.Ui(null, 'click', null, 'workspace');
- clickEvent.workspaceId = ws.id;
+ var clickEvent = new Blockly.Events.Click(null, ws.id);
Blockly.Events.fire(clickEvent);
};
@@ -757,8 +756,7 @@ Blockly.Gesture.prototype.doBlockClick_ = function() {
}
} else {
// Clicks events are on the start block, even if it was a shadow.
- Blockly.Events.fire(
- new Blockly.Events.Ui(this.startBlock_, 'click', undefined, 'block'));
+ Blockly.Events.fire(new Blockly.Events.Click(this.startBlock_));
}
this.bringBlockToFront_();
Blockly.Events.setGroup(false);
diff --git a/core/mutator.js b/core/mutator.js
index 3e4ba64cc..c630238f9 100644
--- a/core/mutator.js
+++ b/core/mutator.js
@@ -16,7 +16,7 @@ goog.provide('Blockly.Mutator');
goog.require('Blockly.Bubble');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockChange');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.navigation');
goog.require('Blockly.utils');
@@ -286,7 +286,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
return;
}
Blockly.Events.fire(
- new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible));
+ new Blockly.Events.BubbleOpen(this.block_, visible, 'mutator'));
if (visible) {
// Create the bubble.
this.bubble_ = new Blockly.Bubble(
@@ -363,7 +363,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
* @private
*/
Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
- if (e.type == Blockly.Events.UI ||
+ if (e.isUiEvent ||
(e.type == Blockly.Events.CHANGE && e.element == 'disabled')) {
return;
}
diff --git a/core/procedures.js b/core/procedures.js
index 610c7890d..876845997 100644
--- a/core/procedures.js
+++ b/core/procedures.js
@@ -293,8 +293,7 @@ Blockly.Procedures.updateMutatorFlyout_ = function(workspace) {
* @package
*/
Blockly.Procedures.mutatorOpenListener = function(e) {
- if (e.type != Blockly.Events.UI || e.element != 'mutatorOpen' ||
- !e.newValue) {
+ if (!e.isUiEvent || e.element != 'mutatorOpen' || !e.newValue) {
return;
}
var workspaceId = /** @type {string} */ (e.workspaceId);
diff --git a/core/renderers/common/marker_svg.js b/core/renderers/common/marker_svg.js
index ddf047a70..fbb34e16d 100644
--- a/core/renderers/common/marker_svg.js
+++ b/core/renderers/common/marker_svg.js
@@ -15,6 +15,7 @@ goog.provide('Blockly.blockRendering.MarkerSvg');
goog.require('Blockly.ASTNode');
goog.require('Blockly.constants');
+goog.require('Blockly.Events.MarkerMove');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Svg');
@@ -551,12 +552,8 @@ Blockly.blockRendering.MarkerSvg.prototype.hide = function() {
Blockly.blockRendering.MarkerSvg.prototype.fireMarkerEvent_ = function(
oldNode, curNode) {
var curBlock = curNode.getSourceBlock();
- var eventType = this.isCursor() ? 'cursorMove' : 'markerMove';
- var event = new Blockly.Events.Ui(curBlock, eventType, oldNode, curNode);
- if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
- event.workspaceId =
- (/** @type {!Blockly.Workspace} */ (curNode.getLocation())).id;
- }
+ var event = new Blockly.Events.MarkerMove(
+ curBlock, this.isCursor(), oldNode, curNode);
Blockly.Events.fire(event);
};
diff --git a/core/toolbox/toolbox.js b/core/toolbox/toolbox.js
index bf82cb8c1..bb7a2243b 100644
--- a/core/toolbox/toolbox.js
+++ b/core/toolbox/toolbox.js
@@ -16,7 +16,7 @@ goog.require('Blockly.CollapsibleToolboxCategory');
goog.require('Blockly.constants');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.ToolboxItemSelect');
goog.require('Blockly.navigation');
goog.require('Blockly.registry');
goog.require('Blockly.Touch');
@@ -806,10 +806,8 @@ Blockly.Toolbox.prototype.fireSelectEvent_ = function(oldItem, newItem) {
if (oldItem == newItem) {
newElement = null;
}
- // TODO (#4187): Update Toolbox Events.
- var event = new Blockly.Events.Ui(null, 'category',
- oldElement, newElement);
- event.workspaceId = this.workspace_.id;
+ var event = new Blockly.Events.ToolboxItemSelect(
+ oldElement, newElement, this.workspace_.id);
Blockly.Events.fire(event);
};
diff --git a/core/trashcan.js b/core/trashcan.js
index bc6bdf6e1..459d7ba4b 100644
--- a/core/trashcan.js
+++ b/core/trashcan.js
@@ -13,6 +13,7 @@
goog.provide('Blockly.Trashcan');
goog.require('Blockly.constants');
+goog.require('Blockly.Events.TrashcanOpen');
goog.require('Blockly.Scrollbar');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Rect');
@@ -560,8 +561,8 @@ Blockly.Trashcan.prototype.click = function() {
* @private
*/
Blockly.Trashcan.prototype.fireUiEvent_ = function(trashcanOpen) {
- var uiEvent = new Blockly.Events.Ui(null, 'trashcanOpen', null, trashcanOpen);
- uiEvent.workspaceId = this.workspace_.id;
+ var uiEvent =
+ new Blockly.Events.TrashcanOpen(trashcanOpen,this.workspace_.id);
Blockly.Events.fire(uiEvent);
};
diff --git a/core/warning.js b/core/warning.js
index dc8f83666..8a70fcbe9 100644
--- a/core/warning.js
+++ b/core/warning.js
@@ -14,7 +14,7 @@ goog.provide('Blockly.Warning');
goog.require('Blockly.Bubble');
goog.require('Blockly.Events');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.BubbleOpen');
goog.require('Blockly.Icon');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
@@ -108,7 +108,7 @@ Blockly.Warning.prototype.setVisible = function(visible) {
return;
}
Blockly.Events.fire(
- new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible));
+ new Blockly.Events.BubbleOpen(this.block_, visible, 'warning'));
if (visible) {
this.createBubble();
} else {
diff --git a/core/workspace_comment_svg.js b/core/workspace_comment_svg.js
index ca27661f7..69e82fc50 100644
--- a/core/workspace_comment_svg.js
+++ b/core/workspace_comment_svg.js
@@ -17,7 +17,7 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.CommentCreate');
goog.require('Blockly.Events.CommentDelete');
goog.require('Blockly.Events.CommentMove');
-goog.require('Blockly.Events.Ui');
+goog.require('Blockly.Events.Selected');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
@@ -224,8 +224,7 @@ Blockly.WorkspaceCommentSvg.prototype.select = function() {
Blockly.Events.enable();
}
}
- var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
- event.workspaceId = this.workspace.id;
+ var event = new Blockly.Events.Selected(oldId, this.id, this.workspace.id);
Blockly.Events.fire(event);
Blockly.selected = this;
this.addSelect();
@@ -239,8 +238,7 @@ Blockly.WorkspaceCommentSvg.prototype.unselect = function() {
if (Blockly.selected != this) {
return;
}
- var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
- event.workspaceId = this.workspace.id;
+ var event = new Blockly.Events.Selected(this.id, null, this.workspace.id);
Blockly.Events.fire(event);
Blockly.selected = null;
this.removeSelect();
diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index 0b8628211..99d7ad566 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -19,6 +19,8 @@ goog.require('Blockly.constants');
goog.require('Blockly.ContextMenuRegistry');
goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockCreate');
+goog.require('Blockly.Events.ThemeChange');
+goog.require('Blockly.Events.ViewportChange');
goog.require('Blockly.Gesture');
goog.require('Blockly.Grid');
goog.require('Blockly.MarkerManager');
@@ -324,27 +326,26 @@ Blockly.WorkspaceSvg.prototype.dragDeltaXY_ = null;
*/
Blockly.WorkspaceSvg.prototype.scale = 1;
-// TODO(#4203) Enable viewport events after ui events refactor.
-// /**
-// * Cached scale value. Used to detect changes in viewport.
-// * @type {number}
-// * @private
-// */
-// Blockly.WorkspaceSvg.prototype.oldScale_ = 1;
-//
-// /**
-// * Cached viewport top value. Used to detect changes in viewport.
-// * @type {number}
-// * @private
-// */
-// Blockly.WorkspaceSvg.prototype.oldTop_ = 0;
-//
-// /**
-// * Cached viewport left value. Used to detect changes in viewport.
-// * @type {number}
-// * @private
-// */
-// Blockly.WorkspaceSvg.prototype.oldLeft_ = 0;
+/**
+ * Cached scale value. Used to detect changes in viewport.
+ * @type {number}
+ * @private
+ */
+Blockly.WorkspaceSvg.prototype.oldScale_ = 1;
+
+/**
+ * Cached viewport top value. Used to detect changes in viewport.
+ * @type {number}
+ * @private
+ */
+Blockly.WorkspaceSvg.prototype.oldTop_ = 0;
+
+/**
+ * Cached viewport left value. Used to detect changes in viewport.
+ * @type {number}
+ * @private
+ */
+Blockly.WorkspaceSvg.prototype.oldLeft_ = 0;
/**
* The workspace's trashcan (if any).
@@ -576,8 +577,7 @@ Blockly.WorkspaceSvg.prototype.refreshTheme = function() {
this.setVisible(true);
}
- var event = new Blockly.Events.Ui(null, 'theme', null, null);
- event.workspaceId = this.id;
+ var event = new Blockly.Events.ThemeChange(this.getTheme().name, this.id);
Blockly.Events.fire(event);
};
@@ -1106,23 +1106,24 @@ Blockly.WorkspaceSvg.prototype.getParentSvg = function() {
* @package
*/
Blockly.WorkspaceSvg.prototype.maybeFireViewportChangeEvent = function() {
- // TODO(#4203) Enable viewport events after ui events refactor.
- // if (!Blockly.Events.isEnabled()) {
- // return;
- // }
- // var scale = this.scale;
- // var top = -this.scrollY;
- // var left = -this.scrollX;
- // if (scale == this.oldScale_ && top == this.oldTop_ && left == this.oldLeft_) {
- // return;
- // }
- // this.oldScale_ = scale;
- // this.oldTop_ = top;
- // this.oldLeft_ = left;
- // var event = new Blockly.Events.Ui(null, 'viewport', null,
- // { scale: scale, top: top, left: left });
- // event.workspaceId = this.id;
- // Blockly.Events.fire(event);
+ if (!Blockly.Events.isEnabled()) {
+ return;
+ }
+ var scale = this.scale;
+ var top = -this.scrollY;
+ var left = -this.scrollX;
+ if (scale == this.oldScale_ &&
+ Math.abs(top - this.oldTop_) < 1 &&
+ Math.abs(left - this.oldLeft_) < 1) {
+ // Ignore sub-pixel changes in top and left. Due to #4192 there are a lot of
+ // negligible changes in viewport top/left.
+ return;
+ }
+ this.oldScale_ = scale;
+ this.oldTop_ = top;
+ this.oldLeft_ = left;
+ var event = new Blockly.Events.ViewportChange(top, left, scale, this.id);
+ Blockly.Events.fire(event);
};
/**
diff --git a/core/zoom_controls.js b/core/zoom_controls.js
index f48c387a3..82558a507 100644
--- a/core/zoom_controls.js
+++ b/core/zoom_controls.js
@@ -343,9 +343,8 @@ Blockly.ZoomControls.prototype.createZoomInSvg_ = function(rnd) {
*/
Blockly.ZoomControls.prototype.zoom_ = function(amount, e) {
this.workspace_.markFocused();
- var oldZoom = this.workspace_.scale;
this.workspace_.zoomCenter(amount);
- this.fireZoomEvent_(oldZoom);
+ this.fireZoomEvent_();
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
e.stopPropagation(); // Don't start a workspace scroll.
e.preventDefault(); // Stop double-clicking from selecting text.
@@ -408,12 +407,11 @@ Blockly.ZoomControls.prototype.createZoomResetSvg_ = function(rnd) {
*/
Blockly.ZoomControls.prototype.resetZoom_ = function(e) {
this.workspace_.markFocused();
- var oldZoom = this.workspace_.scale;
this.workspace_.setScale(this.workspace_.options.zoomOptions.startScale);
this.workspace_.beginCanvasTransition();
this.workspace_.scrollCenter();
setTimeout(this.workspace_.endCanvasTransition.bind(this.workspace_), 500);
- this.fireZoomEvent_(oldZoom);
+ this.fireZoomEvent_();
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
e.stopPropagation(); // Don't start a workspace scroll.
e.preventDefault(); // Stop double-clicking from selecting text.
@@ -421,12 +419,11 @@ Blockly.ZoomControls.prototype.resetZoom_ = function(e) {
/**
* Fires a zoom control ui event.
- * @param {number} oldZoom The workspace scale before zoom happened.
* @private
*/
-Blockly.ZoomControls.prototype.fireZoomEvent_ = function(oldZoom) {
- var uiEvent = new Blockly.Events.Ui(null, 'zoom', oldZoom, this.workspace_.scale);
- uiEvent.workspaceId = this.workspace_.id;
+Blockly.ZoomControls.prototype.fireZoomEvent_ = function() {
+ var uiEvent = new Blockly.Events.Click(
+ null, this.workspace_.id, 'zoom_controls');
Blockly.Events.fire(uiEvent);
};
diff --git a/demos/blockfactory/workspacefactory/wfactory_init.js b/demos/blockfactory/workspacefactory/wfactory_init.js
index 789f7314c..7db01d414 100644
--- a/demos/blockfactory/workspacefactory/wfactory_init.js
+++ b/demos/blockfactory/workspacefactory/wfactory_init.js
@@ -345,7 +345,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
// Only enable "Edit Block" when a block is selected and it has a
// surrounding parent, meaning it is nested in another block (blocks that
// are not nested in parents cannot be shadow blocks).
- if (e.type == Blockly.Events.BLOCK_MOVE || (e.type == Blockly.Events.UI &&
+ if (e.type == Blockly.Events.BLOCK_MOVE || (e.isUiEvent &&
e.element == 'selected')) {
var selected = Blockly.selected;
diff --git a/demos/minimap/minimap.js b/demos/minimap/minimap.js
index fe19065bf..6217b4df3 100644
--- a/demos/minimap/minimap.js
+++ b/demos/minimap/minimap.js
@@ -133,10 +133,11 @@ Minimap.mousemove = function(e) {
/**
* Run non-UI events from the main workspace on the minimap.
- * @param {!Event} event Event that triggered in the main workspace.
+ * @param {!Blockly.Events.Abstract} event Event that triggered in the main
+ * workspace.
*/
Minimap.mirrorEvent = function(event) {
- if (event.type == Blockly.Events.UI) {
+ if (event.isUiEvent) {
return; // Don't mirror UI events.
}
// Convert event to JSON. This could then be transmitted across the net.
diff --git a/scripts/gulpfiles/typings.js b/scripts/gulpfiles/typings.js
index e15d57474..a32cb00d3 100644
--- a/scripts/gulpfiles/typings.js
+++ b/scripts/gulpfiles/typings.js
@@ -27,6 +27,7 @@ function typings() {
const blocklySrcs = [
"core/",
"core/components",
+ "core/events",
"core/keyboard_nav",
"core/renderers/common",
"core/renderers/measurables",
diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js
index 00ede6586..bbd3ad9c9 100644
--- a/tests/mocha/comment_test.js
+++ b/tests/mocha/comment_test.js
@@ -48,9 +48,9 @@ suite('Comments', function() {
chai.assert.isTrue(this.comment.isVisible());
assertEditable(this.comment);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'commentOpen', oldValue: false, newValue: true},
- this.workspace.id, this.block.id);
+ this.eventsFireStub, Blockly.Events.BubbleOpen,
+ {element: 'comment', isOpen: true}, this.workspace.id,
+ this.block.id);
});
test('Not Editable', function() {
sinon.stub(this.block, 'isEditable').returns(false);
@@ -63,9 +63,9 @@ suite('Comments', function() {
chai.assert.isTrue(this.comment.isVisible());
assertNotEditable(this.comment);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'commentOpen', oldValue: false, newValue: true},
- this.workspace.id, this.block.id);
+ this.eventsFireStub, Blockly.Events.BubbleOpen,
+ {element: 'comment', isOpen: true}, this.workspace.id,
+ this.block.id);
});
test('Editable -> Not Editable', function() {
this.comment.setVisible(true);
@@ -79,9 +79,9 @@ suite('Comments', function() {
chai.assert.isTrue(this.comment.isVisible());
assertNotEditable(this.comment);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'commentOpen', oldValue: false, newValue: true},
- this.workspace.id, this.block.id);
+ this.eventsFireStub, Blockly.Events.BubbleOpen,
+ {element: 'comment', isOpen: true}, this.workspace.id,
+ this.block.id);
});
test('Not Editable -> Editable', function() {
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
@@ -97,9 +97,9 @@ suite('Comments', function() {
chai.assert.isTrue(this.comment.isVisible());
assertEditable(this.comment);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'commentOpen', oldValue: false, newValue: true},
- this.workspace.id, this.block.id);
+ this.eventsFireStub, Blockly.Events.BubbleOpen,
+ {element: 'comment', isOpen: true}, this.workspace.id,
+ this.block.id);
});
});
suite('Set/Get Bubble Size', function() {
diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js
index ea90c5bcf..c212a7366 100644
--- a/tests/mocha/event_test.js
+++ b/tests/mocha/event_test.js
@@ -56,16 +56,33 @@ suite('Events', function() {
});
test('UI event without block', function() {
+ var event = new Blockly.Events.UiBase(this.workspace.id);
+ assertEventEquals(event, undefined, this.workspace.id, undefined, {
+ 'recordUndo': false,
+ 'group': '',
+ }, true);
+ });
+
+ test('Click without block', function() {
+ var event = new Blockly.Events.Click(null, this.workspace.id);
+ assertEventEquals(event, Blockly.Events.CLICK, this.workspace.id, null, {
+ 'targetType': 'workspace',
+ 'recordUndo': false,
+ 'group': ''
+ }, true);
+ });
+
+ test('Old UI event without block', function() {
var TEST_GROUP_ID = 'testGroup';
Blockly.Events.setGroup(TEST_GROUP_ID);
var event = new Blockly.Events.Ui(null, 'foo', 'bar', 'baz');
- assertEventEquals(event, Blockly.Events.UI, null, null, {
+ assertEventEquals(event, Blockly.Events.UI, '', null, {
'element': 'foo',
'oldValue': 'bar',
'newValue': 'baz',
'recordUndo': false,
'group': TEST_GROUP_ID
- });
+ }, true);
});
suite('With simple blocks', function() {
@@ -134,7 +151,7 @@ suite('Events', function() {
});
});
- test('UI event with block', function() {
+ test('Old UI event with block', function() {
var TEST_GROUP_ID = 'testGroup';
Blockly.Events.setGroup(TEST_GROUP_ID);
var event = new Blockly.Events.Ui(this.block, 'foo', 'bar', 'baz');
@@ -147,7 +164,19 @@ suite('Events', function() {
'newValue': 'baz',
'recordUndo': false,
'group': TEST_GROUP_ID
- });
+ }, true);
+ });
+
+ test('Click with block', function() {
+ var TEST_GROUP_ID = 'testGroup';
+ Blockly.Events.setGroup(TEST_GROUP_ID);
+ var event = new Blockly.Events.Click(this.block);
+ assertEventEquals(event, Blockly.Events.CLICK, this.workspace.id,
+ this.TEST_BLOCK_ID, {
+ 'targetType': 'block',
+ 'recordUndo': false,
+ 'group': TEST_GROUP_ID
+ }, true);
});
suite('Move', function() {
@@ -409,6 +438,226 @@ suite('Events', function() {
});
});
+ suite('Serialization', function() {
+ var safeStringify = (json) => {
+ let cache = [];
+ return JSON.stringify(json, (key, value) => {
+ if (typeof value == 'object' && value != null) {
+ if (cache.includes(value)) {
+ // Discard duplicate reference.
+ console.log('discarding');
+ console.log(value);
+ return undefined;
+ }
+ cache.push(value);
+ return value;
+ }
+ return value;
+ });
+ };
+ var variableEventTestCases = [
+ {title: 'Var create', class: Blockly.Events.VarCreate,
+ getArgs: (thisObj) => [thisObj.variable],
+ getExpectedJson: () => ({type: 'var_create', varId: 'id1',
+ varType: 'type1', varName: 'name1'})},
+ {title: 'Var delete', class: Blockly.Events.VarDelete,
+ getArgs: (thisObj) => [thisObj.variable],
+ getExpectedJson: () => ({type: 'var_delete', varId: 'id1',
+ varType: 'type1', varName: 'name1'})},
+ {title: 'Var rename', class: Blockly.Events.VarRename,
+ getArgs: (thisObj) => [thisObj.variable, 'name2'],
+ getExpectedJson: () => ({type: 'var_rename', varId: 'id1',
+ oldName: 'name1', newName: 'name2'})},
+ ];
+ var uiEventTestCases = [
+ {title: 'Bubble open', class: Blockly.Events.BubbleOpen,
+ getArgs: (thisObj) => [thisObj.block, true, 'mutator'],
+ getExpectedJson: (thisObj) => ({type: 'bubble_open', isOpen: true,
+ element: 'mutator', blockId: thisObj.block.id})},
+ {title: 'Block click', class: Blockly.Events.Click,
+ getArgs: (thisObj) => [thisObj.block],
+ getExpectedJson: (thisObj) => ({type: 'click', targetType: 'block',
+ blockId: thisObj.block.id})},
+ {title: 'Workspace click', class: Blockly.Events.Click,
+ getArgs: (thisObj) => [null, thisObj.workspace.id],
+ getExpectedJson: (thisObj) => ({type: 'click',
+ targetType: 'workspace'})},
+ {title: 'Drag start', class: Blockly.Events.Drag,
+ getArgs: (thisObj) => [thisObj.block, true, [thisObj.block]],
+ getExpectedJson: (thisObj) => ({type: 'drag',
+ isStart: true, blockId: thisObj.block.id, blocks: [thisObj.block]})},
+ {title: 'Drag end', class: Blockly.Events.Drag,
+ getArgs: (thisObj) => [thisObj.block, false, [thisObj.block]],
+ getExpectedJson: (thisObj) => ({type: 'drag',
+ isStart: false, blockId: thisObj.block.id, blocks: [thisObj.block]})},
+ {title: 'null to Block Marker move', class: Blockly.Events.MarkerMove,
+ getArgs: (thisObj) => [thisObj.block, true, null,
+ new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK, thisObj.block)],
+ getExpectedJson: (thisObj) => ({type: 'marker_move',
+ isCursor: true, blockId: thisObj.block.id, oldNode: null,
+ curNode: new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK,
+ thisObj.block)})},
+ {title: 'null to Workspace Marker move', class: Blockly.Events.MarkerMove,
+ getArgs: (thisObj) => [null, true, null,
+ Blockly.ASTNode.createWorkspaceNode(thisObj.workspace,
+ new Blockly.utils.Coordinate(0, 0))],
+ getExpectedJson: (thisObj) => ({type: 'marker_move',
+ isCursor: true, blockId: null, oldNode: null,
+ curNode: Blockly.ASTNode.createWorkspaceNode(thisObj.workspace,
+ new Blockly.utils.Coordinate(0, 0))})},
+ {title: 'Workspace to Block Marker move',
+ class: Blockly.Events.MarkerMove,
+ getArgs: (thisObj) => [thisObj.block, true,
+ Blockly.ASTNode.createWorkspaceNode(thisObj.workspace,
+ new Blockly.utils.Coordinate(0, 0)),
+ new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK, thisObj.block)],
+ getExpectedJson: (thisObj) => ({type: 'marker_move',
+ isCursor: true, blockId: thisObj.block.id,
+ oldNode: Blockly.ASTNode.createWorkspaceNode(thisObj.workspace,
+ new Blockly.utils.Coordinate(0, 0)),
+ curNode: new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK,
+ thisObj.block)})},
+ {title: 'Block to Workspace Marker move',
+ class: Blockly.Events.MarkerMove,
+ getArgs: (thisObj) => [null, true,
+ new Blockly.ASTNode(Blockly.ASTNode.types.BLOCK, thisObj.block),
+ Blockly.ASTNode.createWorkspaceNode(thisObj.workspace,
+ new Blockly.utils.Coordinate(0, 0))]},
+ {title: 'Selected', class: Blockly.Events.Selected,
+ getArgs: (thisObj) => [null, thisObj.block.id, thisObj.workspace.id],
+ getExpectedJson: (thisObj) => ({type: 'selected', oldElementId: null,
+ elementId: thisObj.block.id})},
+ {title: 'Selected (deselect)', class: Blockly.Events.Selected,
+ getArgs: (thisObj) => [thisObj.block.id, null, thisObj.workspace.id],
+ getExpectedJson: (thisObj) => ({type: 'selected',
+ oldElementId: thisObj.block.id, elementId: null})},
+ {title: 'Theme Change', class: Blockly.Events.ThemeChange,
+ getArgs: (thisObj) => ['classic', thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'theme_change', themeName: 'classic'})},
+ {title: 'Toolbox item select',
+ class: Blockly.Events.ToolboxItemSelect,
+ getArgs: (thisObj) => ['Math', 'Loops', thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'toolbox_item_change', oldItem: 'Math',
+ newItem: 'Loops'})},
+ {title: 'Toolbox item select (no previous)',
+ class: Blockly.Events.ToolboxItemSelect,
+ getArgs: (thisObj) => [null, 'Loops', thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'toolbox_item_change', oldItem: null,
+ newItem: 'Loops'})},
+ {title: 'Toolbox item select (deselect)',
+ class: Blockly.Events.ToolboxItemSelect,
+ getArgs: (thisObj) => ['Math', null, thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'toolbox_item_change', oldItem: 'Math',
+ newItem: null})},
+ {title: 'Trashcan open', class: Blockly.Events.TrashcanOpen,
+ getArgs: (thisObj) => [true, thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'trashcan_open', isOpen: true})},
+ {title: 'Viewport change', class: Blockly.Events.ViewportChange,
+ getArgs: (thisObj) => [2.666, 1.333, 1.2, thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'viewport_change', viewTop: 2.666,
+ viewLeft: 1.333, scale: 1.2})},
+ {title: 'Viewport change (0,0)', class: Blockly.Events.ViewportChange,
+ getArgs: (thisObj) => [0, 0, 1.2, thisObj.workspace.id],
+ getExpectedJson: () => ({type: 'viewport_change', viewTop: 0,
+ viewLeft: 0, scale: 1.2})},
+ ];
+ var blockEventTestCases = [
+ {title: 'Block change', class: Blockly.Events.BlockChange,
+ getArgs: (thisObj) => [thisObj.block, 'collapsed', null, false, true],
+ getExpectedJson: (thisObj) => ({type: 'change',
+ blockId: thisObj.block.id, element: 'collapsed', oldValue: false,
+ newValue: true})},
+ {title: 'Block create', class: Blockly.Events.BlockCreate,
+ getArgs: (thisObj) => [thisObj.block],
+ getExpectedJson: (thisObj) => ({type: 'create',
+ blockId: thisObj.block.id,
+ xml: '',
+ ids: [thisObj.block.id]})},
+ {title: 'Block create (shadow)', class: Blockly.Events.BlockCreate,
+ getArgs: (thisObj) => [thisObj.shadowBlock],
+ getExpectedJson: (thisObj) => ({type: 'create',
+ blockId: thisObj.shadowBlock.id,
+ xml: '',
+ ids: [thisObj.shadowBlock.id], recordUndo: false})},
+ {title: 'Block delete', class: Blockly.Events.BlockDelete,
+ getArgs: (thisObj) => [thisObj.block],
+ getExpectedJson: (thisObj) => ({type: 'delete',
+ blockId: thisObj.block.id,
+ oldXml: '',
+ ids: [thisObj.block.id]})},
+ {title: 'Block delete (shadow)', class: Blockly.Events.BlockDelete,
+ getArgs: (thisObj) => [thisObj.shadowBlock],
+ getExpectedJson: (thisObj) => ({type: 'delete',
+ blockId: thisObj.shadowBlock.id,
+ oldXml: '',
+ ids: [thisObj.shadowBlock.id], recordUndo: false})},
+ {title: 'Block move', class: Blockly.Events.BlockMove,
+ getArgs: (thisObj) => [thisObj.block],
+ getExpectedJson: (thisObj) => ({type: 'move',
+ blockId: thisObj.block.id})},
+ {title: 'Block move (shadow)', class: Blockly.Events.BlockMove,
+ getArgs: (thisObj) => [thisObj.shadowBlock],
+ getExpectedJson: (thisObj) => ({type: 'move',
+ blockId: thisObj.shadowBlock.id, recordUndo: false})},
+ ];
+ var testSuites = [
+ {title: 'Variable events', testCases: variableEventTestCases,
+ setup: (thisObj) => {
+ thisObj.variable =
+ thisObj.workspace.createVariable('name1', 'type1', 'id1');
+ }},
+ {title: 'UI events', testCases: uiEventTestCases,
+ setup: (thisObj) => {
+ thisObj.block = createSimpleTestBlock(thisObj.workspace);
+ }},
+ {title: 'Block events', testCases: blockEventTestCases,
+ setup: (thisObj) => {
+ createGenUidStubWithReturns(['testBlockId1', 'testBlockId2']);
+ thisObj.block = createSimpleTestBlock(thisObj.workspace);
+ thisObj.shadowBlock = createSimpleTestBlock(thisObj.workspace);
+ thisObj.shadowBlock.setShadow(true);
+ }}
+ ];
+ testSuites.forEach((testSuite) => {
+ suite(testSuite.title, function() {
+ setup(function() {
+ testSuite.setup(this);
+ });
+ suite('fromJson', function() {
+ testSuite.testCases.forEach((testCase) => {
+ test(testCase.title, function() {
+ var event = new testCase.class(...testCase.getArgs(this));
+ var event2 = new testCase.class();
+ var json = event.toJson();
+ event2.fromJson(json);
+
+ chai.assert.equal(
+ safeStringify(event2.toJson()), safeStringify(json));
+ });
+ });
+ });
+ suite('toJson', function() {
+ testSuite.testCases.forEach((testCase) => {
+ if (testCase.getExpectedJson) {
+ test(testCase.title, function() {
+ var event = new testCase.class(...testCase.getArgs(this));
+ var json = event.toJson();
+ var expectedJson = testCase.getExpectedJson(this);
+
+ chai.assert.equal(
+ safeStringify(json), safeStringify(expectedJson));
+ });
+ }
+ });
+ });
+ });
+ });
+ });
+
suite('Variable events', function() {
setup(function() {
this.variable = this.workspace.createVariable('name1', 'type1', 'id1');
@@ -480,62 +729,6 @@ suite('Events', function() {
});
});
- suite('fromJson', function() {
- test('Var create', function() {
- var event = new Blockly.Events.VarCreate(this.variable);
- var event2 = new Blockly.Events.VarCreate();
- var json = event.toJson();
- event2.fromJson(json);
-
- chai.assert.equal(JSON.stringify(json), JSON.stringify(event2.toJson()));
- });
- test('Var delete', function() {
- var event = new Blockly.Events.VarDelete(this.variable);
- var event2 = new Blockly.Events.VarDelete();
- var json = event.toJson();
- event2.fromJson(json);
-
- chai.assert.equal(JSON.stringify(json), JSON.stringify(event2.toJson()));
- });
- test('Var rename', function() {
- var event = new Blockly.Events.VarRename(this.variable, '');
- var event2 = new Blockly.Events.VarRename();
- var json = event.toJson();
- event2.fromJson(json);
-
- chai.assert.equal(JSON.stringify(json), JSON.stringify(event2.toJson()));
- });
- });
-
- suite('toJson', function() {
- test('Var create', function() {
- var event = new Blockly.Events.VarCreate(this.variable);
- var json = event.toJson();
- var expectedJson = ({type: "var_create", varId: "id1", varType: "type1",
- varName: "name1"});
-
- chai.assert.equal(JSON.stringify(expectedJson), JSON.stringify(json));
- });
-
- test('Var delete', function() {
- var event = new Blockly.Events.VarDelete(this.variable);
- var json = event.toJson();
- var expectedJson = ({type: "var_delete", varId: "id1", varType: "type1",
- varName: "name1"});
-
- chai.assert.equal(JSON.stringify(expectedJson), JSON.stringify(json));
- });
-
- test('Var rename', function() {
- var event = new Blockly.Events.VarRename(this.variable, 'name2');
- var json = event.toJson();
- var expectedJson = ({type: "var_rename", varId: "id1", oldName: "name1",
- newName: "name2"});
-
- chai.assert.equal(JSON.stringify(expectedJson), JSON.stringify(json));
- });
- });
-
suite('Run Forward', function() {
test('Var create', function() {
var json = {type: "var_create", varId: "id2", varType: "type2",
@@ -605,7 +798,7 @@ suite('Events', function() {
new Blockly.Events.BlockCreate(block),
new Blockly.Events.BlockMove(block),
new Blockly.Events.BlockChange(block, 'field', 'VAR', 'id1', 'id2'),
- new Blockly.Events.Ui(block, 'click', undefined, undefined)
+ new Blockly.Events.Click(block)
];
var filteredEvents = Blockly.Events.filter(events, true);
chai.assert.equal(filteredEvents.length, 4); // no event should have been removed.
@@ -613,7 +806,7 @@ suite('Events', function() {
chai.assert.isTrue(filteredEvents[0] instanceof Blockly.Events.BlockCreate);
chai.assert.isTrue(filteredEvents[1] instanceof Blockly.Events.BlockMove);
chai.assert.isTrue(filteredEvents[2] instanceof Blockly.Events.BlockChange);
- chai.assert.isTrue(filteredEvents[3] instanceof Blockly.Events.Ui);
+ chai.assert.isTrue(filteredEvents[3] instanceof Blockly.Events.Click);
});
test('Different blocks no removed', function() {
@@ -687,19 +880,22 @@ suite('Events', function() {
var block2 = this.workspace.newBlock('field_variable_test_block', '2');
var block3 = this.workspace.newBlock('field_variable_test_block', '3');
var events = [
- new Blockly.Events.Ui(block1, 'commentOpen', 'false', 'true'),
- new Blockly.Events.Ui(block1, 'click', 'false', 'true'),
- new Blockly.Events.Ui(block2, 'mutatorOpen', 'false', 'true'),
- new Blockly.Events.Ui(block2, 'click', 'false', 'true'),
- new Blockly.Events.Ui(block3, 'warningOpen', 'false', 'true'),
- new Blockly.Events.Ui(block3, 'click', 'false', 'true')
+ new Blockly.Events.BubbleOpen(block1, true, 'comment'),
+ new Blockly.Events.Click(block1),
+ new Blockly.Events.BubbleOpen(block2, true, 'mutator'),
+ new Blockly.Events.Click(block2),
+ new Blockly.Events.BubbleOpen(block3, true,'warning'),
+ new Blockly.Events.Click(block3)
];
var filteredEvents = Blockly.Events.filter(events, true);
// click event merged into corresponding *Open event
chai.assert.equal(filteredEvents.length, 3);
- chai.assert.equal(filteredEvents[0].element, 'commentOpen');
- chai.assert.equal(filteredEvents[1].element, 'mutatorOpen');
- chai.assert.equal(filteredEvents[2].element, 'warningOpen');
+ chai.assert.isTrue(filteredEvents[0] instanceof Blockly.Events.BubbleOpen);
+ chai.assert.isTrue(filteredEvents[1] instanceof Blockly.Events.BubbleOpen);
+ chai.assert.isTrue(filteredEvents[2] instanceof Blockly.Events.BubbleOpen);
+ chai.assert.equal(filteredEvents[0].element, 'comment');
+ chai.assert.equal(filteredEvents[1].element, 'mutator');
+ chai.assert.equal(filteredEvents[2].element, 'warning');
});
test('Colliding events not dropped', function() {
@@ -707,13 +903,13 @@ suite('Events', function() {
// but cannot be merged do not get dropped during filtering.
var block = this.workspace.newBlock('field_variable_test_block', '1');
var events = [
- new Blockly.Events.Ui(block, 'click', undefined, undefined),
+ new Blockly.Events.Click(block),
new Blockly.Events.Ui(block, 'stackclick', undefined, undefined)
];
var filteredEvents = Blockly.Events.filter(events, true);
// click and stackclick should both exist
chai.assert.equal(filteredEvents.length, 2);
- chai.assert.equal(filteredEvents[0].element, 'click');
+ chai.assert.isTrue(filteredEvents[0] instanceof Blockly.Events.Click);
chai.assert.equal(filteredEvents[1].element, 'stackclick');
});
diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js
index 89e159d10..438c58f52 100644
--- a/tests/mocha/gesture_test.js
+++ b/tests/mocha/gesture_test.js
@@ -34,10 +34,9 @@ suite('Gesture', function() {
chai.assert.isTrue(isFieldClickSpy.alwaysReturned(isFieldClick));
- assertEventFired(eventsFireStub, Blockly.Events.Ui,
- {element: 'selected', oldValue: null, newValue: block.id},
- fieldWorkspace.id, null);
- assertEventNotFired(eventsFireStub, Blockly.Events.Ui, {element: 'click'});
+ assertEventFired(eventsFireStub, Blockly.Events.Selected,
+ {oldElementId: null, elementId: block.id}, fieldWorkspace.id);
+ assertEventNotFired(eventsFireStub, Blockly.Events.Click, {});
}
function getTopFlyoutBlock(flyout) {
diff --git a/tests/mocha/test_helpers.js b/tests/mocha/test_helpers.js
index 7ee94233b..645d48f15 100644
--- a/tests/mocha/test_helpers.js
+++ b/tests/mocha/test_helpers.js
@@ -341,16 +341,17 @@ function isXmlProperty_(key) {
/**
* Asserts that the given event has the expected values.
- * @param {!Blockly.Event.Abstract} event The event to check.
+ * @param {!Blockly.Events.Abstract} event The event to check.
* @param {string} expectedType Expected type of event fired.
* @param {string} expectedWorkspaceId Expected workspace id of event fired.
- * @param {string} expectedBlockId Expected block id of event fired.
+ * @param {?string} expectedBlockId Expected block id of event fired.
* @param {!Object} expectedProperties Map of of additional expected
* properties to check on fired event.
+ * @param {boolean=} [isUiEvent=false] Whether the event is a UI event.
* @param {string=} message Optional message to prepend assert messages.
*/
function assertEventEquals(event, expectedType,
- expectedWorkspaceId, expectedBlockId, expectedProperties, message) {
+ expectedWorkspaceId, expectedBlockId, expectedProperties, isUiEvent = false, message) {
var prependMessage = message ? message + ' ' : '';
prependMessage += 'Event fired ';
chai.assert.equal(event.type, expectedType,
@@ -375,6 +376,11 @@ function assertEventEquals(event, expectedType,
prependMessage + key);
}
});
+ if (isUiEvent) {
+ chai.assert.isTrue(event.isUiEvent);
+ } else {
+ chai.assert.isFalse(event.isUiEvent);
+ }
}
/**
diff --git a/tests/mocha/theme_test.js b/tests/mocha/theme_test.js
index 1220bfcc5..d8fcc7664 100644
--- a/tests/mocha/theme_test.js
+++ b/tests/mocha/theme_test.js
@@ -120,6 +120,7 @@ suite('Theme', function() {
defineThemeTestBlocks(this.sharedCleanup);
try {
var blockStyles = createBlockStyles();
+ var theme = new Blockly.Theme('themeName', blockStyles);
var workspace = new Blockly.WorkspaceSvg(new Blockly.Options({}));
var blockA = workspace.newBlock('stack_block');
@@ -132,10 +133,10 @@ suite('Theme', function() {
sinon.stub(Blockly, "getMainWorkspace").returns(workspace);
sinon.stub(Blockly, "hideChaff");
- workspace.setTheme(blockStyles);
+ workspace.setTheme(theme);
// Checks that the theme was set correctly on Blockly namespace
- stringifyAndCompare(workspace.getTheme(), blockStyles);
+ stringifyAndCompare(workspace.getTheme(), theme);
// Checks that the setTheme function was called on the block
chai.assert.equal(blockA.getStyleName(), 'styleTwo');
@@ -144,8 +145,8 @@ suite('Theme', function() {
sinon.assert.calledOnce(refreshToolboxSelectionStub);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'theme'},
- workspace.id, null);
+ this.eventsFireStub, Blockly.Events.ThemeChange,
+ {themeName: 'themeName'}, workspace.id);
} finally {
workspaceTeardown.call(this, workspace);
}
diff --git a/tests/mocha/trashcan_test.js b/tests/mocha/trashcan_test.js
index 93833021e..9e0122d71 100644
--- a/tests/mocha/trashcan_test.js
+++ b/tests/mocha/trashcan_test.js
@@ -62,10 +62,9 @@ suite("Trashcan", function() {
simulateClick(this.trashcan.svgGroup_);
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'trashcanOpen'});
+ this.eventsFireStub, Blockly.Events.TrashcanOpen, {});
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'click', oldValue: null, newValue: 'workspace'},
+ this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace'},
this.workspace.id, null);
});
test("Click with contents - fires trashcanOpen", function() {
@@ -79,11 +78,10 @@ suite("Trashcan", function() {
sinon.assert.calledOnce(showFlyoutStub);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'trashcanOpen', oldValue: null, newValue: true},
- this.workspace.id, null);
+ this.eventsFireStub, Blockly.Events.TrashcanOpen,
+ {isOpen: true}, this.workspace.id);
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'click'});
+ this.eventsFireStub, Blockly.Events.Click, {});
});
test("Click outside trashcan - fires trashcanClose", function() {
sinon.stub(this.trashcan.flyout, 'isVisible').returns(true);
@@ -95,12 +93,10 @@ suite("Trashcan", function() {
sinon.assert.calledOnce(hideFlyoutStub);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'trashcanOpen', oldValue: null, newValue: false},
- this.workspace.id, null);
+ this.eventsFireStub, Blockly.Events.TrashcanOpen,
+ {isOpen: false}, this.workspace.id);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'click', oldValue: null, newValue: 'workspace'},
+ this.eventsFireStub, Blockly.Events.Click, {targetType: 'workspace'},
this.workspace.id, null);
});
});
diff --git a/tests/mocha/workspace_svg_test.js b/tests/mocha/workspace_svg_test.js
index 27b4b07f3..84c617495 100644
--- a/tests/mocha/workspace_svg_test.js
+++ b/tests/mocha/workspace_svg_test.js
@@ -161,25 +161,25 @@ suite('WorkspaceSvg', function() {
});
});
- suite.skip('Viewport change events', function() {
+ suite('Viewport change events', function() {
function resetEventHistory(eventsFireStub, changeListenerSpy) {
eventsFireStub.resetHistory();
changeListenerSpy.resetHistory();
}
function assertSpyFiredViewportEvent(spy, workspace, expectedProperties) {
assertEventFired(
- spy, Blockly.Events.Ui, {element: 'viewport'},
- workspace.id, null);
- assertEventFired(spy, Blockly.Events.Ui, expectedProperties,
- workspace.id, null);
+ spy, Blockly.Events.ViewportChange, expectedProperties,
+ workspace.id);
+ assertEventFired(spy, Blockly.Events.ViewportChange, expectedProperties,
+ workspace.id);
}
function assertViewportEventFired(eventsFireStub, changeListenerSpy,
workspace, expectedEventCount = 1) {
var metrics = workspace.getMetrics();
var expectedProperties = {
- element: 'viewport',
- newValue: {scale: workspace.scale, top: metrics.viewTop,
- left: metrics.viewLeft}
+ scale: workspace.scale,
+ viewTop: metrics.viewTop,
+ viewLeft: metrics.viewLeft
};
assertSpyFiredViewportEvent(
eventsFireStub, workspace, expectedProperties);
@@ -259,35 +259,7 @@ suite('WorkspaceSvg', function() {
this.clock);
});
});
- suite('resize', function() {
- setup(function() {
- sinon.stub(Blockly, 'svgSize').callsFake((svg) => {
- return new Blockly.utils.Size(
- svg.cachedWidth_ + 10, svg.cachedHeight_ + 10);
- });
- });
- test('resize', function() {
- runViewportEventTest(() => this.workspace.resize(),
- this.eventsFireStub, this.changeListenerSpy, this.workspace,
- this.clock);
- });
- test('resizeContents', function() {
- runViewportEventTest(() => this.workspace.resizeContents(),
- this.eventsFireStub, this.changeListenerSpy, this.workspace,
- this.clock);
- });
- });
suite('Blocks triggering viewport changes', function() {
- test('block render that doesn\'t trigger scroll', function() {
- this.clock.runAll();
- resetEventHistory(this.eventsFireStub, this.changeListenerSpy);
- var block = this.workspace.newBlock('stack_block');
- block.initSvg();
- block.render();
- this.clock.runAll();
- assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
- });
test('block move that triggers scroll', function() {
var block = this.workspace.newBlock('stack_block');
block.initSvg();
@@ -300,8 +272,7 @@ suite('WorkspaceSvg', function() {
}, this.eventsFireStub, this.changeListenerSpy, this.workspace,
this.clock, 2);
});
- test.skip('domToWorkspace that doesn\'t trigger scroll' , function() {
- // TODO(#4192): un-skip after fixing bug with unintentional scroll.
+ test('domToWorkspace that doesn\'t trigger scroll' , function() {
// 4 blocks with space in center.
Blockly.Xml.domToWorkspace(
Blockly.Xml.textToDom(
@@ -321,9 +292,9 @@ suite('WorkspaceSvg', function() {
''), this.workspace);
this.clock.runAll();
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
+ this.eventsFireStub, Blockly.Events.ViewportChange, {});
assertEventNotFired(
- this.changeListenerSpy, Blockly.Events.Ui, {element: 'viewport'});
+ this.changeListenerSpy, Blockly.Events.ViewportChange, {});
});
test('domToWorkspace at 0,0 that doesn\'t trigger scroll' , function() {
// 4 blocks with space in center.
@@ -344,11 +315,12 @@ suite('WorkspaceSvg', function() {
Blockly.Xml.domToWorkspace(xmlDom, this.workspace);
this.clock.runAll();
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'viewport'});
+ this.eventsFireStub, Blockly.Events.ViewportChange, {});
assertEventNotFired(
- this.changeListenerSpy, Blockly.Events.Ui, {element: 'viewport'});
+ this.changeListenerSpy, Blockly.Events.ViewportChange, {});
});
- test('domToWorkspace multiple blocks triggers one viewport event', function() {
+ test.skip('domToWorkspace multiple blocks triggers one viewport event', function() {
+ // TODO: Un-skip after adding filtering for consecutive viewport events.
var addingMultipleBlocks = () => {
Blockly.Xml.domToWorkspace(
Blockly.Xml.textToDom(
diff --git a/tests/mocha/zoom_controls_test.js b/tests/mocha/zoom_controls_test.js
index ef0028d84..e8180bb9c 100644
--- a/tests/mocha/zoom_controls_test.js
+++ b/tests/mocha/zoom_controls_test.js
@@ -25,31 +25,34 @@ suite("Zoom Controls", function() {
simulateClick(this.zoomControls.zoomInGroup_);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'zoom', oldValue: 1, newValue: closeToMatcher(1.2, 0.05)},
- this.workspace.id, null);
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'zoom_controls'}, this.workspace.id, null);
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'click'});
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'workspace'});
+ chai.assert.closeTo(this.workspace.getScale(), 1.2, 0.05);
});
test("Zoom out", function() {
simulateClick(this.zoomControls.zoomOutGroup_);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'zoom', oldValue: 1, newValue: closeToMatcher(0.8, 0.05)},
- this.workspace.id, null);
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'zoom_controls'}, this.workspace.id, null);
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'click'});
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'workspace'});
+ chai.assert.closeTo(this.workspace.getScale(), 0.8, 0.05);
});
test("Reset zoom", function() {
simulateClick(this.zoomControls.zoomResetGroup_);
assertEventFired(
- this.eventsFireStub, Blockly.Events.Ui,
- {element: 'zoom', oldValue: 1, newValue: 1},
- this.workspace.id, null);
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'zoom_controls'}, this.workspace.id, null);
assertEventNotFired(
- this.eventsFireStub, Blockly.Events.Ui, {element: 'click'});
+ this.eventsFireStub, Blockly.Events.Click,
+ {targetType: 'workspace'});
+ chai.assert.equal(this.workspace.getScale(), 1);
});
});
});