Merge pull request #4002 from google/rc_2020_6

Rc 2020 6
This commit is contained in:
alschmiedt
2020-06-25 16:39:10 -07:00
committed by GitHub
415 changed files with 19307 additions and 14959 deletions
+1
View File
@@ -2,6 +2,7 @@
*_uncompressed*.js
gulpfile.js
/msg/*
/dist/*
/core/utils/global.js
/tests/blocks/*
/tests/themes/*
+346 -324
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+27 -16
View File
@@ -21,44 +21,41 @@ this.BLOCKLY_DIR = (function(root) {
this.BLOCKLY_BOOT = function(root) {
// Execute after Closure has loaded.
goog.addDependency('../../core/block.js', ['Blockly.Block'], ['Blockly.Blocks', 'Blockly.Connection', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BlockCreate', 'Blockly.Events.BlockDelete', 'Blockly.Events.BlockMove', 'Blockly.Extensions', 'Blockly.Input', 'Blockly.Workspace', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.string'], {});
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.Workspace', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.string'], {'lang': 'es5'});
goog.addDependency('../../core/block_animations.js', ['Blockly.blockAnimations'], ['Blockly.utils.dom'], {});
goog.addDependency('../../core/block_drag_surface.js', ['Blockly.BlockDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/block_dragger.js', ['Blockly.BlockDragger'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.InsertionMarkerManager', 'Blockly.blockAnimations', '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.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/block_svg.js', ['Blockly.BlockSvg'], ['Blockly.ASTNode', 'Blockly.Block', 'Blockly.ContextMenu', 'Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Events.Ui', 'Blockly.Msg', 'Blockly.RenderedConnection', 'Blockly.TabNavigateCursor', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.blockAnimations', 'Blockly.blockRendering.IPathObject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.colour'], {});
goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Procedures', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.Variables', 'Blockly.WidgetDiv', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.constants', 'Blockly.inject', 'Blockly.navigation', '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.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', '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.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/components/component.js', ['Blockly.Component', 'Blockly.Component.Error'], ['Blockly.utils.IdGenerator', 'Blockly.utils.dom', 'Blockly.utils.style'], {});
goog.addDependency('../../core/components/menu/menu.js', ['Blockly.Menu'], ['Blockly.Component', 'Blockly.utils.Coordinate', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/components/menu/menuitem.js', ['Blockly.MenuItem'], ['Blockly.Component', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/components/tree/basenode.js', ['Blockly.tree.BaseNode'], ['Blockly.Component', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style'], {});
goog.addDependency('../../core/components/tree/treecontrol.js', ['Blockly.tree.TreeControl'], ['Blockly.tree.BaseNode', 'Blockly.tree.TreeNode', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style'], {});
goog.addDependency('../../core/components/tree/treenode.js', ['Blockly.tree.TreeNode'], ['Blockly.tree.BaseNode', 'Blockly.utils.KeyCodes', 'Blockly.utils.object'], {});
goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml'], {});
goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection'], {});
goog.addDependency('../../core/constants.js', ['Blockly.constants'], [], {});
goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.uiMenu', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {});
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.utils'], {});
goog.addDependency('../../core/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {});
goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils'], {});
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {'lang': 'es5'});
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', '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.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/field_checkbox.js', ['Blockly.FieldCheckbox'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_colour.js', ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils.IdGenerator', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_date.js', ['Blockly.FieldDate'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.string', 'goog.date', 'goog.date.DateTime', 'goog.events', 'goog.i18n.DateTimeSymbols', 'goog.i18n.DateTimeSymbols_he', 'goog.ui.DatePicker'], {});
goog.addDependency('../../core/field_dropdown.js', ['Blockly.FieldDropdown'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.string', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/field_image.js', ['Blockly.FieldImage'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_label.js', ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_label_serializable.js', ['Blockly.FieldLabelSerializable'], ['Blockly.FieldLabel', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_multilineinput.js', ['Blockly.FieldMultilineInput'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {'lang': 'es5'});
goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.aria', 'Blockly.utils.object'], {});
goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], [], {});
goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], ['Blockly.registry'], {});
goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object'], {});
goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.Marker', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
@@ -73,6 +70,16 @@ goog.addDependency('../../core/icon.js', ['Blockly.Icon'], ['Blockly.utils', 'Bl
goog.addDependency('../../core/inject.js', ['Blockly.inject'], ['Blockly.BlockDragSurfaceSvg', 'Blockly.Component', 'Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Grid', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ScrollbarPair', 'Blockly.Tooltip', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.WorkspaceSvg', 'Blockly.user.keyMap', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.Connection', 'Blockly.FieldLabel'], {});
goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations'], {'lang': 'es5'});
goog.addDependency('../../core/interfaces/i_accessibility.js', ['Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IBlocklyActionable'], [], {});
goog.addDependency('../../core/interfaces/i_bounded_element.js', ['Blockly.IBoundedElement'], [], {});
goog.addDependency('../../core/interfaces/i_copyable.js', ['Blockly.ICopyable'], [], {});
goog.addDependency('../../core/interfaces/i_deletable.js', ['Blockly.IDeletable'], [], {});
goog.addDependency('../../core/interfaces/i_deletearea.js', ['Blockly.IDeleteArea'], [], {});
goog.addDependency('../../core/interfaces/i_movable.js', ['Blockly.IMovable'], [], {});
goog.addDependency('../../core/interfaces/i_registrable.js', ['Blockly.IRegistrable'], [], {});
goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectable'], [], {});
goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {});
goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {});
goog.addDependency('../../core/keyboard_nav/action.js', ['Blockly.Action'], [], {});
goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.utils.Coordinate'], {'lang': 'es5'});
goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor'], {'lang': 'es5'});
@@ -83,13 +90,16 @@ goog.addDependency('../../core/keyboard_nav/marker.js', ['Blockly.Marker'], ['Bl
goog.addDependency('../../core/keyboard_nav/navigation.js', ['Blockly.navigation'], ['Blockly.ASTNode', 'Blockly.Action', 'Blockly.user.keyMap', 'Blockly.utils.Coordinate'], {});
goog.addDependency('../../core/keyboard_nav/tab_navigate_cursor.js', ['Blockly.TabNavigateCursor'], ['Blockly.ASTNode', 'Blockly.BasicCursor', 'Blockly.utils.object'], {});
goog.addDependency('../../core/marker_manager.js', ['Blockly.MarkerManager'], ['Blockly.Cursor', 'Blockly.Marker'], {});
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.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg'], {});
goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.user.keyMap', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.user.keyMap', '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'], {});
goog.addDependency('../../core/registry.js', ['Blockly.registry'], [], {});
goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/renderers/common/block_rendering.js', ['Blockly.blockRendering'], ['Blockly.utils.object'], {});
goog.addDependency('../../core/renderers/common/block_rendering.js', ['Blockly.blockRendering'], ['Blockly.registry', 'Blockly.utils.object'], {});
goog.addDependency('../../core/renderers/common/constants.js', ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent'], {'lang': 'es5'});
goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types'], {'lang': 'es5'});
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'], {});
@@ -128,8 +138,8 @@ goog.addDependency('../../core/renderers/zelos/measurables/rows.js', ['Blockly.z
goog.addDependency('../../core/renderers/zelos/path_object.js', ['Blockly.zelos.PathObject'], ['Blockly.blockRendering.PathObject', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider'], {});
goog.addDependency('../../core/renderers/zelos/renderer.js', ['Blockly.zelos.Renderer'], ['Blockly.InsertionMarkerManager', 'Blockly.blockRendering', 'Blockly.blockRendering.Renderer', 'Blockly.utils.object', 'Blockly.zelos.ConstantProvider', 'Blockly.zelos.Drawer', 'Blockly.zelos.MarkerSvg', 'Blockly.zelos.PathObject', 'Blockly.zelos.RenderInfo'], {});
goog.addDependency('../../core/requires.js', ['Blockly.requires'], ['Blockly', 'Blockly.Comment', 'Blockly.FieldAngle', 'Blockly.FieldCheckbox', 'Blockly.FieldColour', 'Blockly.FieldDropdown', 'Blockly.FieldImage', 'Blockly.FieldLabelSerializable', 'Blockly.FieldMultilineInput', 'Blockly.FieldNumber', 'Blockly.FieldTextInput', 'Blockly.FieldVariable', 'Blockly.FlyoutButton', 'Blockly.Generator', 'Blockly.HorizontalFlyout', 'Blockly.Mutator', 'Blockly.Themes.Classic', 'Blockly.Themes.Dark', 'Blockly.Themes.Deuteranopia', 'Blockly.Themes.HighContrast', 'Blockly.Themes.Tritanopia', 'Blockly.Toolbox', 'Blockly.Trashcan', 'Blockly.VariablesDynamic', 'Blockly.VerticalFlyout', 'Blockly.Warning', 'Blockly.ZoomControls', 'Blockly.geras.Renderer', 'Blockly.thrasos.Renderer', 'Blockly.zelos.Renderer'], {});
goog.addDependency('../../core/scrollbar.js', ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.object'], {});
goog.addDependency('../../core/scrollbar.js', ['Blockly.Scrollbar', 'Blockly.ScrollbarPair'], ['Blockly.Touch', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/theme.js', ['Blockly.Theme'], ['Blockly.registry', 'Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.object'], {});
goog.addDependency('../../core/theme/classic.js', ['Blockly.Themes.Classic'], ['Blockly.Theme'], {});
goog.addDependency('../../core/theme/dark.js', ['Blockly.Themes.Dark'], ['Blockly.Theme'], {});
goog.addDependency('../../core/theme/deuteranopia.js', ['Blockly.Themes.Deuteranopia'], ['Blockly.Theme'], {});
@@ -138,14 +148,13 @@ goog.addDependency('../../core/theme/modern.js', ['Blockly.Themes.Modern'], ['Bl
goog.addDependency('../../core/theme/tritanopia.js', ['Blockly.Themes.Tritanopia'], ['Blockly.Theme'], {});
goog.addDependency('../../core/theme/zelos.js', ['Blockly.Themes.Zelos'], ['Blockly.Theme'], {});
goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Blockly.Theme'], {});
goog.addDependency('../../core/toolbox.js', ['Blockly.Toolbox'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.tree.TreeControl', 'Blockly.tree.TreeNode', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/toolbox.js', ['Blockly.Toolbox'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.registry', 'Blockly.tree.TreeControl', 'Blockly.tree.TreeNode', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.utils.string'], {});
goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['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.utils.Rect', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/ui_events.js', ['Blockly.Events.Ui'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.object'], {});
goog.addDependency('../../core/ui_menu_utils.js', ['Blockly.utils.uiMenu'], ['Blockly.utils.style'], {});
goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {});
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'], [], {});
goog.addDependency('../../core/utils/coordinate.js', ['Blockly.utils.Coordinate'], [], {});
@@ -154,12 +163,14 @@ goog.addDependency('../../core/utils/global.js', ['Blockly.utils.global'], [], {
goog.addDependency('../../core/utils/idgenerator.js', ['Blockly.utils.IdGenerator'], [], {});
goog.addDependency('../../core/utils/keycodes.js', ['Blockly.utils.KeyCodes'], [], {});
goog.addDependency('../../core/utils/math.js', ['Blockly.utils.math'], [], {});
goog.addDependency('../../core/utils/metrics.js', ['Blockly.utils.Metrics'], [], {});
goog.addDependency('../../core/utils/object.js', ['Blockly.utils.object'], [], {});
goog.addDependency('../../core/utils/rect.js', ['Blockly.utils.Rect'], [], {});
goog.addDependency('../../core/utils/size.js', ['Blockly.utils.Size'], [], {});
goog.addDependency('../../core/utils/string.js', ['Blockly.utils.string'], [], {});
goog.addDependency('../../core/utils/style.js', ['Blockly.utils.style'], ['Blockly.utils.Coordinate', 'Blockly.utils.Size'], {});
goog.addDependency('../../core/utils/svg_paths.js', ['Blockly.utils.svgPaths'], [], {});
goog.addDependency('../../core/utils/toolbox.js', ['Blockly.utils.toolbox'], [], {});
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.utils.object'], {});
@@ -177,7 +188,7 @@ goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCom
goog.addDependency('../../core/workspace_drag_surface_svg.js', ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', '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.utils.object'], {'lang': 'es5'});
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', '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.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', '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.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.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', '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.utils.dom'], {'lang': 'es5'});
+10 -1
View File
@@ -805,6 +805,14 @@ Blockly.Blocks['procedures_callnoreturn'] = {
}
this.setProcedureParameters_(args, paramIds);
},
/**
* Return all variables referenced by this block.
* @return {!Array.<string>} List of variable names.
* @this {Blockly.Block}
*/
getVars: function() {
return this.arguments_;
},
/**
* Return all variables referenced by this block.
* @return {!Array.<!Blockly.VariableModel>} List of variable models.
@@ -836,7 +844,7 @@ Blockly.Blocks['procedures_callnoreturn'] = {
var name = this.getProcedureCall();
var def = Blockly.Procedures.getDefinition(name, this.workspace);
if (def && (def.type != this.defType_ ||
JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
JSON.stringify(def.getVars()) != JSON.stringify(this.arguments_))) {
// The signatures don't match.
def = null;
}
@@ -959,6 +967,7 @@ Blockly.Blocks['procedures_callreturn'] = {
updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
getVars: Blockly.Blocks['procedures_callnoreturn'].getVars,
getVarModels: Blockly.Blocks['procedures_callnoreturn'].getVarModels,
onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
customContextMenu:
+22 -9
View File
@@ -1,8 +1,16 @@
// Do not edit this file; automatically generated by gulp.
'use strict';
Blockly.Blocks.colour={};Blockly.Constants={};Blockly.Constants.Colour={};Blockly.Constants.Colour.HUE=20;
/* eslint-disable */
;(function(root, factory) {
if (typeof define === 'function' && define.amd) { // AMD
define(['./blockly_compressed.js'], factory);
} else if (typeof exports === 'object') { // Node.js
module.exports = factory(require('./blockly_compressed.js'));
} else { // Browser
root.Blockly.Blocks = factory(root.Blockly);
}
}(this, function(Blockly) {
'use strict';Blockly.Blocks.colour={};Blockly.Constants={};Blockly.Constants.Colour={};Blockly.Constants.Colour.HUE=20;
Blockly.defineBlocksWithJsonArray([{type:"colour_picker",message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:"#ff0000"}],output:"Colour",helpUrl:"%{BKY_COLOUR_PICKER_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_PICKER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"colour_random",message0:"%{BKY_COLOUR_RANDOM_TITLE}",output:"Colour",helpUrl:"%{BKY_COLOUR_RANDOM_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_RANDOM_TOOLTIP}"},{type:"colour_rgb",message0:"%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3",
args0:[{type:"input_value",name:"RED",check:"Number",align:"RIGHT"},{type:"input_value",name:"GREEN",check:"Number",align:"RIGHT"},{type:"input_value",name:"BLUE",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_RGB_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_RGB_TOOLTIP}"},{type:"colour_blend",message0:"%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} %1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3",args0:[{type:"input_value",name:"COLOUR1",check:"Colour",
align:"RIGHT"},{type:"input_value",name:"COLOUR2",check:"Colour",align:"RIGHT"},{type:"input_value",name:"RATIO",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_BLEND_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_BLEND_TOOLTIP}"}]);Blockly.Blocks.lists={};Blockly.Constants.Lists={};Blockly.Constants.Lists.HUE=260;
@@ -113,13 +121,13 @@ this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.ou
this.setCollapsed(!1);this.quarkIds_||(this.quarkConnections_={},this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var e=0;e<this.arguments_.length;e++){var f=this.getInput("ARG"+e);f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=f,d&&f&&-1==b.indexOf(this.quarkIds_[e])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(e=0;e<this.arguments_.length;e++)a=Blockly.Variables.getOrCreateVariablePackage(this.workspace,
null,this.arguments_[e],""),this.argumentVarModels_.push(a);this.updateShape_();if(this.quarkIds_=b)for(e=0;e<this.arguments_.length;e++)b=this.quarkIds_[e],b in this.quarkConnections_&&(f=this.quarkConnections_[b],Blockly.Mutator.reconnect(f,this,"ARG"+e)||delete this.quarkConnections_[b]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){Blockly.Events.disable();try{b.setValue(this.arguments_[a])}finally{Blockly.Events.enable()}}else b=
new Blockly.FieldLabel(this.arguments_[a]),this.appendValueInput("ARG"+a).setAlign(Blockly.ALIGN_RIGHT).appendField(b,"ARGNAME"+a).init()}for(;this.getInput("ARG"+a);)this.removeInput("ARG"+a),a++;if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){var a=Blockly.utils.xml.createElement("mutation");a.setAttribute("name",this.getProcedureCall());
for(var b=0;b<this.arguments_.length;b++){var c=Blockly.utils.xml.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];for(var c=[],d=0,e;e=a.childNodes[d];d++)"arg"==e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,c)},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&
!this.workspace.isFlyout&&a.recordUndo)if(a.type==Blockly.Events.BLOCK_CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.arguments_)==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=Blockly.utils.xml.createElement("xml");b=Blockly.utils.xml.createElement("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),d=c.y+2*Blockly.SNAP_RADIUS;
b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","NAME");c.appendChild(Blockly.utils.xml.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.BLOCK_DELETE?(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,this.workspace),b||(Blockly.Events.setGroup(a.group),
this.dispose(!0),Blockly.Events.setGroup(!1))):a.type==Blockly.Events.CHANGE&&"disabled"==a.element&&(b=this.getProcedureCall(),(b=Blockly.Procedures.getDefinition(b,this.workspace))&&b.id==a.blockId&&((b=Blockly.Events.getGroup())&&console.log("Saw an existing group while responding to a definition change"),Blockly.Events.setGroup(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),Blockly.Events.setGroup(b)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=
{enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);a&&(d.centerOnBlock(a.id),a.select())};a.push(b)}},defType_:"procedures_defnoreturn"};
for(var b=0;b<this.arguments_.length;b++){var c=Blockly.utils.xml.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];for(var c=[],d=0,e;e=a.childNodes[d];d++)"arg"==e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,c)},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},
onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&a.recordUndo)if(a.type==Blockly.Events.BLOCK_CREATE&&-1!=a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=Blockly.Procedures.getDefinition(b,this.workspace);!b||b.type==this.defType_&&JSON.stringify(b.getVars())==JSON.stringify(this.arguments_)||(b=null);if(!b){Blockly.Events.setGroup(a.group);a=Blockly.utils.xml.createElement("xml");b=Blockly.utils.xml.createElement("block");b.setAttribute("type",this.defType_);var c=this.getRelativeToSurfaceXY(),
d=c.y+2*Blockly.SNAP_RADIUS;b.setAttribute("x",c.x+Blockly.SNAP_RADIUS*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=Blockly.utils.xml.createElement("field");c.setAttribute("name","NAME");c.appendChild(Blockly.utils.xml.createTextNode(this.getProcedureCall()));b.appendChild(c);a.appendChild(b);Blockly.Xml.domToWorkspace(a,this.workspace);Blockly.Events.setGroup(!1)}}else a.type==Blockly.Events.BLOCK_DELETE?(b=this.getProcedureCall(),b=Blockly.Procedures.getDefinition(b,
this.workspace),b||(Blockly.Events.setGroup(a.group),this.dispose(!0),Blockly.Events.setGroup(!1))):a.type==Blockly.Events.CHANGE&&"disabled"==a.element&&(b=this.getProcedureCall(),(b=Blockly.Procedures.getDefinition(b,this.workspace))&&b.id==a.blockId&&((b=Blockly.Events.getGroup())&&console.log("Saw an existing group while responding to a definition change"),Blockly.Events.setGroup(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),
Blockly.Events.setGroup(b)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b={enabled:!0};b.text=Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var a=Blockly.Procedures.getDefinition(c,d);a&&(d.centerOnBlock(a.id),a.select())};a.push(b)}},defType_:"procedures_defnoreturn"};
Blockly.Blocks.procedures_callreturn={init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.quarkConnections_={};this.quarkIds_=null;this.previousEnabledState_=!0},getProcedureCall:Blockly.Blocks.procedures_callnoreturn.getProcedureCall,renameProcedure:Blockly.Blocks.procedures_callnoreturn.renameProcedure,setProcedureParameters_:Blockly.Blocks.procedures_callnoreturn.setProcedureParameters_,
updateShape_:Blockly.Blocks.procedures_callnoreturn.updateShape_,mutationToDom:Blockly.Blocks.procedures_callnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_callnoreturn.domToMutation,getVarModels:Blockly.Blocks.procedures_callnoreturn.getVarModels,onchange:Blockly.Blocks.procedures_callnoreturn.onchange,customContextMenu:Blockly.Blocks.procedures_callnoreturn.customContextMenu,defType_:"procedures_defreturn"};
updateShape_:Blockly.Blocks.procedures_callnoreturn.updateShape_,mutationToDom:Blockly.Blocks.procedures_callnoreturn.mutationToDom,domToMutation:Blockly.Blocks.procedures_callnoreturn.domToMutation,getVars:Blockly.Blocks.procedures_callnoreturn.getVars,getVarModels:Blockly.Blocks.procedures_callnoreturn.getVarModels,onchange:Blockly.Blocks.procedures_callnoreturn.onchange,customContextMenu:Blockly.Blocks.procedures_callnoreturn.customContextMenu,defType_:"procedures_defreturn"};
Blockly.Blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){var a=
Blockly.utils.xml.createElement("mutation");a.setAttribute("value",Number(this.hasReturnValue_));return a},domToMutation:function(a){this.hasReturnValue_=1==a.getAttribute("value");this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN))},onchange:function(a){if(this.workspace.isDragging&&!this.workspace.isDragging()){a=!1;var b=this;do{if(-1!=this.FUNCTION_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);
a?("procedures_defnoreturn"==b.type&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!1):"procedures_defreturn"!=b.type||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null),this.isInFlyout||this.setEnabled(!0)):(this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING),
@@ -167,3 +175,8 @@ Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MI
d=Blockly.utils.xml.createElement("field");d.setAttribute("name","VAR");d.setAttribute("variabletype",c);d.appendChild(Blockly.utils.xml.createTextNode(f));f=Blockly.utils.xml.createElement("block");f.setAttribute("type",b);f.appendChild(d);e.callback=Blockly.ContextMenu.callbackFactory(this,f);a.push(e)}else if("variables_get_dynamic"==this.type||"variables_get_reporter_dynamic"==this.type)b={text:Blockly.Msg.RENAME_VARIABLE,enabled:!0,callback:Blockly.Constants.Variables.RENAME_OPTION_CALLBACK_FACTORY(this)},
f=this.getField("VAR").getText(),e={text:Blockly.Msg.DELETE_VARIABLE.replace("%1",f),enabled:!0,callback:Blockly.Constants.Variables.DELETE_OPTION_CALLBACK_FACTORY(this)},a.unshift(b),a.unshift(e)},onchange:function(a){a=this.getFieldValue("VAR");a=Blockly.Variables.getVariable(this.workspace,a);"variables_get_dynamic"==this.type?this.outputConnection.setCheck(a.type):this.getInput("VALUE").connection.setCheck(a.type)}};
Blockly.Constants.VariablesDynamic.RENAME_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();Blockly.Variables.renameVariable(b,c)}};Blockly.Constants.VariablesDynamic.DELETE_OPTION_CALLBACK_FACTORY=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};Blockly.Extensions.registerMixin("contextMenu_variableDynamicSetterGetter",Blockly.Constants.VariablesDynamic.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
return Blockly.Blocks;
}));
//# sourceMappingURL=blocks_compressed.js.map
File diff suppressed because one or more lines are too long
+111 -19
View File
@@ -12,6 +12,7 @@
goog.provide('Blockly.Block');
goog.require('Blockly.ASTNode');
goog.require('Blockly.Blocks');
goog.require('Blockly.Connection');
goog.require('Blockly.Events');
@@ -29,6 +30,8 @@ goog.require('Blockly.utils.object');
goog.require('Blockly.utils.string');
goog.require('Blockly.Workspace');
goog.requireType('Blockly.IASTNodeLocation');
/**
* Class for one block.
@@ -39,6 +42,7 @@ goog.require('Blockly.Workspace');
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
* create a new ID.
* @constructor
* @implements {Blockly.IASTNodeLocation}
* @throws When block is not valid or block name is not allowed.
*/
Blockly.Block = function(workspace, prototypeName, opt_id) {
@@ -170,6 +174,9 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
*/
this.hat = undefined;
/** @type {?boolean} */
this.rendered = null;
/**
* A count of statement inputs on the block.
* @type {number}
@@ -229,6 +236,17 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
*/
Blockly.Block.CommentModel;
/**
* The language-neutral id given to the collapsed input.
* @const {string}
*/
Blockly.Block.COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
/**
* The language-neutral id given to the collapsed field.
* @const {string}
*/
Blockly.Block.COLLAPSED_FIELD_NAME = '_TEMP_COLLAPSED_FIELD';
/**
* Optional text data that round-trips between blocks and XML.
* Has no effect. May be used by 3rd parties for meta information.
@@ -550,7 +568,9 @@ Blockly.Block.prototype.bumpNeighbours = function() {
};
/**
* Return the parent block or null if this block is at the top level.
* Return the parent block or null if this block is at the top level. The parent
* block is either the block connected to the previous connection (for a statement
* block) or the block connected to the output connection (for a value block).
* @return {Blockly.Block} The block that holds the current block.
*/
Blockly.Block.prototype.getParent = function() {
@@ -601,7 +621,7 @@ Blockly.Block.prototype.getNextBlock = function() {
};
/**
* Return the previous statement block directly connected to this block.
* Returns the block connected to the previous connection.
* @return {Blockly.Block} The previous statement block or null.
*/
Blockly.Block.prototype.getPreviousBlock = function() {
@@ -974,7 +994,6 @@ Blockly.Block.prototype.getField = function(name) {
/**
* Return all variables referenced by this block.
* @return {!Array.<string>} List of variable names.
* @package
*/
Blockly.Block.prototype.getVars = function() {
var vars = [];
@@ -1295,24 +1314,94 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) {
Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) {
var text = [];
var emptyFieldPlaceholder = opt_emptyToken || '?';
if (this.collapsed_) {
text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].getText());
} else {
for (var i = 0, input; (input = this.inputList[i]); i++) {
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
text.push(field.getText());
}
if (input.connection) {
var child = input.connection.targetBlock();
if (child) {
text.push(child.toString(undefined, opt_emptyToken));
} else {
// Temporarily set flag to navigate to all fields.
var prevNavigateFields = Blockly.ASTNode.NAVIGATE_ALL_FIELDS;
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = true;
var node = Blockly.ASTNode.createBlockNode(this);
var rootNode = node;
/**
* Whether or not to add parentheses around an input.
* @param {!Blockly.Connection} connection The connection.
* @return {boolean} True if we should add parentheses around the input.
*/
function shouldAddParentheses(connection) {
var checks = connection.getCheck();
if (!checks && connection.targetConnection) {
checks = connection.targetConnection.getCheck();
}
return !!checks && (checks.indexOf('Boolean') != -1 ||
checks.indexOf('Number') != -1);
}
/**
* Check that we haven't circled back to the original root node.
*/
function checkRoot() {
if (node && node.getType() == rootNode.getType() &&
node.getLocation() == rootNode.getLocation()) {
node = null;
}
}
// Traverse the AST building up our text string.
while (node) {
switch (node.getType()) {
case Blockly.ASTNode.types.INPUT:
var connection = /** @type {!Blockly.Connection} */ (node.getLocation());
if (!node.in()) {
text.push(emptyFieldPlaceholder);
} else if (shouldAddParentheses(connection)) {
text.push('(');
}
break;
case Blockly.ASTNode.types.FIELD:
var field = /** @type {Blockly.Field} */ (node.getLocation());
if (field.name != Blockly.Block.COLLAPSED_FIELD_NAME) {
text.push(field.getText());
}
break;
}
var current = node;
node = current.in() || current.next();
if (!node) {
// Can't go in or next, keep going out until we can go next.
node = current.out();
checkRoot();
while (node && !node.next()) {
node = node.out();
checkRoot();
// If we hit an input on the way up, possibly close out parentheses.
if (node && node.getType() == Blockly.ASTNode.types.INPUT &&
shouldAddParentheses(
/** @type {!Blockly.Connection} */ (node.getLocation()))) {
text.push(')');
}
}
if (node) {
node = node.next();
}
}
}
text = text.join(' ').trim() || '???';
// Restore state of NAVIGATE_ALL_FIELDS.
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields;
// Run through our text array and simplify expression to remove parentheses
// around single field blocks.
for (var i = 2, l = text.length; i < l; i++) {
if (text[i - 2] == '(' && text[i] == ')') {
text[i - 2] = text[i - 1];
text.splice(i - 1, 2);
l -= 2;
}
}
// Join the text array, removing spaces around added paranthesis.
text = text.join(' ').replace(/(\() | (\))/gmi, '$1$2').trim() || '???';
if (opt_maxLength) {
// TODO: Improve truncation so that text from this block is given priority.
// E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...".
@@ -1716,7 +1805,8 @@ Blockly.Block.prototype.moveNumberedInputBefore = function(
/**
* Remove an input from this block.
* @param {string} name The name of the input.
* @param {boolean=} opt_quiet True to prevent error if input is not present.
* @param {boolean=} opt_quiet True to prevent an error if input is not present.
* @return {boolean} True if operation succeeds, false if input is not present and opt_quiet is true
* @throws {Error} if the input is not present and opt_quiet is not true.
*/
Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
@@ -1727,10 +1817,12 @@ Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
}
input.dispose();
this.inputList.splice(i, 1);
return;
return true;
}
}
if (!opt_quiet) {
if (opt_quiet) {
return false;
} else {
throw Error('Input not found: ' + name);
}
};
+4 -3
View File
@@ -162,6 +162,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
this.draggingBlock_.translate(newLoc.x, newLoc.y);
Blockly.blockAnimations.disconnectUiEffect(this.draggingBlock_);
this.draggedConnectionManager_.updateAvailableConnections();
}
this.draggingBlock_.setDragging(true);
// For future consideration: we may be able to put moveToDragSurface inside
@@ -170,7 +171,7 @@ Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY,
this.draggingBlock_.moveToDragSurface();
var toolbox = this.workspace_.getToolbox();
if (toolbox) {
if (toolbox && typeof toolbox.addStyle == 'function') {
var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
toolbox.addStyle(style);
@@ -220,7 +221,7 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
this.dragBlock(e, currentDragDeltaXY);
this.dragIconData_ = [];
this.fireDragEndEvent_();
Blockly.utils.dom.stopTextWidthCache();
Blockly.blockAnimations.disconnectUiStop();
@@ -246,7 +247,7 @@ Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
this.workspace_.setResizesEnabled(true);
var toolbox = this.workspace_.getToolbox();
if (toolbox) {
if (toolbox && typeof toolbox.removeStyle == 'function') {
var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
toolbox.removeStyle(style);
+125 -93
View File
@@ -32,6 +32,10 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IBoundedElement');
goog.requireType('Blockly.ICopyable');
/**
* Class for a block's SVG representation.
@@ -42,15 +46,19 @@ goog.require('Blockly.utils.Rect');
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
* create a new ID.
* @extends {Blockly.Block}
* @implements {Blockly.IASTNodeLocationSvg}
* @implements {Blockly.IBoundedElement}
* @implements {Blockly.ICopyable}
* @constructor
*/
Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
// Create core elements for the block.
/**
* @type {!SVGElement}
* @type {!SVGGElement}
* @private
*/
this.svgGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
this.svgGroup_ = /** @type {!SVGGElement} */ (
Blockly.utils.dom.createSvgElement('g', {}, null));
this.svgGroup_.translate_ = '';
/**
@@ -69,6 +77,14 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
/** @type {boolean} */
this.rendered = false;
/**
* Is this block currently rendering? Used to stop recursive render calls
* from actually triggering a re-render.
* @type {boolean}
* @private
*/
this.renderIsInProgress_ = false;
/** @type {!Blockly.WorkspaceSvg} */
this.workspace = workspace;
@@ -114,13 +130,6 @@ Blockly.BlockSvg.prototype.height = 0;
*/
Blockly.BlockSvg.prototype.width = 0;
/**
* Original location of block being dragged.
* @type {Blockly.utils.Coordinate}
* @private
*/
Blockly.BlockSvg.prototype.dragStartXY_ = null;
/**
* Map from IDs for warnings text to PIDs of functions to apply them.
* Used to be able to maintain multiple warnings.
@@ -602,60 +611,51 @@ Blockly.BlockSvg.prototype.setCollapsed = function(collapsed) {
if (this.collapsed_ == collapsed) {
return;
}
var renderList = [];
// Show/hide the inputs.
for (var i = 0, input; (input = this.inputList[i]); i++) {
renderList.push.apply(renderList, input.setVisible(!collapsed));
}
var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
if (collapsed) {
var icons = this.getIcons();
for (var i = 0; i < icons.length; i++) {
icons[i].setVisible(false);
}
var text = this.toString(Blockly.COLLAPSE_CHARS);
this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text).init();
// Add any warnings on enclosed blocks to this block.
var descendants = this.getDescendants(true);
var nextBlock = this.getNextBlock();
if (nextBlock) {
var index = descendants.indexOf(nextBlock);
descendants.splice(index, descendants.length - index);
}
for (var i = 1, block; (block = descendants[i]); i++) {
if (block.warning) {
this.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'],
Blockly.BlockSvg.COLLAPSED_WARNING_ID);
break;
}
}
} else {
this.removeInput(COLLAPSED_INPUT_NAME);
// Clear any warnings inherited from enclosed blocks.
if (this.warning) {
this.warning.setText('', Blockly.BlockSvg.COLLAPSED_WARNING_ID);
if (!Object.keys(this.warning.text_).length) {
this.setWarningText(null);
}
}
}
Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
if (!collapsed) {
this.updateCollapsed_();
} else if (this.rendered) {
this.render();
// Don't bump neighbours. Users like to store collapsed functions together
// and bumping makes them go out of alignment.
}
};
if (!renderList.length) {
// No child blocks, just render this block.
renderList[0] = this;
}
if (this.rendered) {
for (var i = 0, block; (block = renderList[i]); i++) {
block.render();
/**
* Makes sure that when the block is collapsed, it is rendered correctly
* for that state.
* @private
*/
Blockly.BlockSvg.prototype.updateCollapsed_ = function() {
var collapsed = this.isCollapsed();
var collapsedInputName = Blockly.Block.COLLAPSED_INPUT_NAME;
var collapsedFieldName = Blockly.Block.COLLAPSED_FIELD_NAME;
for (var i = 0, input; (input = this.inputList[i]); i++) {
if (input.name != collapsedInputName) {
input.setVisible(!collapsed);
}
// Don't bump neighbours.
// Although bumping neighbours would make sense, users often collapse
// all their functions and store them next to each other. Expanding and
// bumping causes all their definitions to go out of alignment.
}
if (!collapsed) {
this.removeInput(collapsedInputName);
return;
}
var icons = this.getIcons();
for (var i = 0, icon; (icon = icons[i]); i++) {
icon.setVisible(false);
}
var text = this.toString(Blockly.COLLAPSE_CHARS);
var field = this.getField(collapsedFieldName);
if (field) {
field.setValue(text);
return;
}
var input = this.getInput(collapsedInputName) ||
this.appendDummyInput(collapsedInputName);
input.appendField(new Blockly.FieldLabel(text), collapsedFieldName);
};
/**
@@ -925,7 +925,7 @@ Blockly.BlockSvg.prototype.setInsertionMarker = function(insertionMarker) {
/**
* Return the root node of the SVG or null if none exists.
* @return {!SVGElement} The root SVG node (probably a group).
* @return {!SVGGElement} The root SVG node (probably a group).
*/
Blockly.BlockSvg.prototype.getSvgRoot = function() {
return this.svgGroup_;
@@ -992,6 +992,26 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
Blockly.utils.dom.stopTextWidthCache();
};
/**
* Encode a block for copying.
* @return {!Blockly.ICopyable.CopyData} Copy metadata.
* @package
*/
Blockly.BlockSvg.prototype.toCopyData = function() {
var xml = Blockly.Xml.blockToDom(this, true);
// Copy only the selected block and internal blocks.
Blockly.Xml.deleteNext(xml);
// Encode start position in XML.
var xy = this.getRelativeToSurfaceXY();
xml.setAttribute('x', this.RTL ? -xy.x : xy.x);
xml.setAttribute('y', xy.y);
return {
xml: xml,
source: this.workspace,
typeCounts: Blockly.utils.getBlockTypeCounts(this, true)
};
};
/**
* Change the colour of a block.
* @package
@@ -1109,22 +1129,22 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
text = null;
}
// Bubble up to add a warning on top-most collapsed block.
var parent = this.getSurroundParent();
var collapsedParent = null;
while (parent) {
if (parent.isCollapsed()) {
collapsedParent = parent;
}
parent = parent.getSurroundParent();
}
if (collapsedParent) {
collapsedParent.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'],
Blockly.BlockSvg.COLLAPSED_WARNING_ID);
}
var changedState = false;
if (typeof text == 'string') {
// Bubble up to add a warning on top-most collapsed block.
var parent = this.getSurroundParent();
var collapsedParent = null;
while (parent) {
if (parent.isCollapsed()) {
collapsedParent = parent;
}
parent = parent.getSurroundParent();
}
if (collapsedParent) {
collapsedParent.setWarningText(Blockly.Msg['COLLAPSED_WARNINGS_WARNING'],
Blockly.BlockSvg.COLLAPSED_WARNING_ID);
}
if (!this.warning) {
this.warning = new Blockly.Warning(this);
changedState = true;
@@ -1369,17 +1389,20 @@ Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) {
* Remove an input from this block.
* @param {string} name The name of the input.
* @param {boolean=} opt_quiet True to prevent error if input is not present.
* @return {boolean} True if operation succeeds, false if input is not present and opt_quiet is true
* @throws {Error} if the input is not present and
* opt_quiet is not true.
*/
Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) {
Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet);
var removed = Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet);
if (this.rendered) {
this.render();
// Removing an input will cause the block to change shape.
this.bumpNeighbours();
}
return removed;
};
/**
@@ -1647,31 +1670,40 @@ Blockly.BlockSvg.prototype.getRootBlock = function() {
};
/**
* Render the block.
* Lays out and reflows a block based on its contents and settings.
* @param {boolean=} opt_bubble If false, just render this block.
* If true, also render block's parent, grandparent, etc. Defaults to true.
*/
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
Blockly.utils.dom.startTextWidthCache();
this.rendered = true;
(/** @type {!Blockly.WorkspaceSvg} */ (this.workspace))
.getRenderer().render(this);
// No matter how we rendered, connection locations should now be correct.
this.updateConnectionLocations_();
if (opt_bubble !== false) {
// Render all blocks above this one (propagate a reflow).
var parentBlock = this.getParent();
if (parentBlock) {
parentBlock.render(true);
} else {
// Top-most block. Fire an event to allow scrollbars to resize.
this.workspace.resizeContents();
}
if (this.renderIsInProgress_) {
return; // Don't allow recursive renders.
}
Blockly.utils.dom.stopTextWidthCache();
this.renderIsInProgress_ = true;
try {
this.rendered = true;
Blockly.utils.dom.startTextWidthCache();
this.updateMarkers_();
if (this.isCollapsed()) {
this.updateCollapsed_();
}
this.workspace.getRenderer().render(this);
this.updateConnectionLocations_();
if (opt_bubble !== false) {
var parentBlock = this.getParent();
if (parentBlock) {
parentBlock.render(true);
} else {
// Top-most block. Fire an event to allow scrollbars to resize.
this.workspace.resizeContents();
}
}
Blockly.utils.dom.stopTextWidthCache();
this.updateMarkers_();
} finally {
this.renderIsInProgress_ = false;
}
};
/**
+18 -27
View File
@@ -26,6 +26,7 @@ goog.require('Blockly.Tooltip');
goog.require('Blockly.Touch');
goog.require('Blockly.utils');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.Size');
goog.require('Blockly.Variables');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.WorkspaceSvg');
@@ -51,7 +52,7 @@ Blockly.mainWorkspace = null;
/**
* Currently selected block.
* @type {Blockly.Block}
* @type {?Blockly.ICopyable}
*/
Blockly.selected = null;
@@ -107,13 +108,11 @@ Blockly.EventData;
/**
* Returns the dimensions of the specified SVG image.
* @param {!SVGElement} svg SVG image.
* @return {!Object} Contains width and height properties.
* @return {!Blockly.utils.Size} Contains width and height properties.
*/
Blockly.svgSize = function(svg) {
return {
width: svg.cachedWidth_,
height: svg.cachedHeight_
};
svg = /** @type {?} */ (svg);
return new Blockly.utils.Size(svg.cachedWidth_, svg.cachedHeight_);
};
/**
@@ -160,7 +159,7 @@ Blockly.svgResize = function(workspace) {
/**
* Handle a key-down on SVG drawing surface. Does nothing if the main workspace
* is not visible.
* @param {!Event} e Key down event.
* @param {!KeyboardEvent} e Key down event.
* @package
*/
// TODO (https://github.com/google/blockly/issues/1998) handle cases where there
@@ -190,7 +189,7 @@ Blockly.onKeyDown = function(e) {
// Pressing esc closes the context menu.
Blockly.hideChaff();
Blockly.navigation.onBlocklyAction(Blockly.navigation.ACTION_EXIT);
} else if (Blockly.navigation.onKeyPress(e)) {
} else if (!Blockly.Gesture.inProgress() && Blockly.navigation.onKeyPress(e)) {
// If the keyboard or field handled the key press return.
return;
} else if (e.keyCode == Blockly.utils.KeyCodes.BACKSPACE ||
@@ -249,6 +248,10 @@ Blockly.onKeyDown = function(e) {
// 'z' for undo 'Z' is for redo.
Blockly.hideChaff();
mainWorkspace.undo(e.shiftKey);
} else if (e.ctrlKey && e.keyCode == Blockly.utils.KeyCodes.Y) {
// Ctrl-y is redo in Windows. Command-y is never valid on Macs.
Blockly.hideChaff();
mainWorkspace.undo(true);
}
}
// Common code for delete and cut.
@@ -264,32 +267,20 @@ Blockly.onKeyDown = function(e) {
/**
* Copy a block or workspace comment onto the local clipboard.
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toCopy Block or
* Workspace Comment to be copied.
* @param {!Blockly.ICopyable} toCopy Block or Workspace Comment to be copied.
* @private
*/
Blockly.copy_ = function(toCopy) {
if (toCopy.isComment) {
var xml = toCopy.toXmlWithXY();
} else {
var xml = Blockly.Xml.blockToDom(toCopy, true);
// Copy only the selected block and internal blocks.
Blockly.Xml.deleteNext(xml);
// Encode start position in XML.
var xy = toCopy.getRelativeToSurfaceXY();
xml.setAttribute('x', toCopy.RTL ? -xy.x : xy.x);
xml.setAttribute('y', xy.y);
}
Blockly.clipboardXml_ = xml;
Blockly.clipboardSource_ = toCopy.workspace;
Blockly.clipboardTypeCounts_ = toCopy.isComment ? null :
Blockly.utils.getBlockTypeCounts(toCopy, true);
var data = toCopy.toCopyData();
Blockly.clipboardXml_ = data.xml;
Blockly.clipboardSource_ = data.source;
Blockly.clipboardTypeCounts_ = data.typeCounts;
};
/**
* Duplicate this block and its children, or a workspace comment.
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toDuplicate Block or
* Workspace Comment to be copied.
* @param {!Blockly.ICopyable} toDuplicate Block or Workspace Comment to be
* copied.
* @package
*/
Blockly.duplicate = function(toDuplicate) {
+10 -8
View File
@@ -21,6 +21,8 @@ goog.require('Blockly.utils.math');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Workspace');
goog.requireType('Blockly.utils.Metrics');
/**
* Class for UI bubble.
@@ -489,8 +491,8 @@ Blockly.Bubble.prototype.layoutBubble_ = function() {
* workspace (what percentage of the bubble is visible).
* @param {!{x: number, y: number}} relativeMin The position of the top-left
* corner of the bubble relative to the anchor point.
* @param {!Object} metrics The metrics of the workspace the bubble will
* appear in.
* @param {!Blockly.utils.Metrics} metrics The metrics of the workspace the
* bubble will appear in.
* @return {number} The percentage of the bubble that is visible.
* @private
*/
@@ -535,10 +537,10 @@ Blockly.Bubble.prototype.getOverlap_ = function(relativeMin, metrics) {
* Calculate what the optimal horizontal position of the top-left corner of the
* bubble is (relative to the anchor point) so that the most area of the
* bubble is shown.
* @param {!Object} metrics The metrics of the workspace the bubble will
* appear in.
* @param {!Blockly.utils.Metrics} metrics The metrics of the workspace the
* bubble will appear in.
* @return {number} The optimal horizontal position of the top-left corner
* of the bubble.
* of the bubble.
* @private
*/
Blockly.Bubble.prototype.getOptimalRelativeLeft_ = function(metrics) {
@@ -593,10 +595,10 @@ Blockly.Bubble.prototype.getOptimalRelativeLeft_ = function(metrics) {
* Calculate what the optimal vertical position of the top-left corner of
* the bubble is (relative to the anchor point) so that the most area of the
* bubble is shown.
* @param {!Object} metrics The metrics of the workspace the bubble will
* appear in.
* @param {!Blockly.utils.Metrics} metrics The metrics of the workspace the
* bubble will appear in.
* @return {number} The optimal vertical position of the top-left corner
* of the bubble.
* of the bubble.
* @private
*/
Blockly.Bubble.prototype.getOptimalRelativeTop_ = function(metrics) {
+4 -3
View File
@@ -107,7 +107,7 @@ Blockly.BubbleDragger.prototype.startBubbleDrag = function() {
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
var toolbox = this.workspace_.getToolbox();
if (toolbox) {
if (toolbox && typeof toolbox.addStyle == 'function') {
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
toolbox.addStyle(style);
@@ -208,10 +208,11 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function(
}
this.workspace_.setResizesEnabled(true);
if (this.workspace_.getToolbox()) {
var toolbox = this.workspace_.getToolbox();
if (toolbox && typeof toolbox.removeStyle == 'function') {
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
this.workspace_.getToolbox().removeStyle(style);
toolbox.removeStyle(style);
}
Blockly.Events.setGroup(false);
};
+1 -1
View File
@@ -86,7 +86,7 @@ Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);
/**
* Draw the comment icon.
* @param {!Element} group The icon group.
* @private
* @protected
*/
Blockly.Comment.prototype.drawIcon_ = function(group) {
// Circle.
+17 -44
View File
@@ -84,6 +84,13 @@ Blockly.Component = function() {
* @private
*/
this.childIndex_ = {};
/**
* Whether or not the component has been disposed.
* @type {boolean}
* @private
*/
this.disposed_ = false;
};
@@ -115,7 +122,12 @@ Blockly.Component.Error = {
* Error when an attempt is made to add a child component at an out-of-bounds
* index. We don't support sparse child arrays.
*/
CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds'
CHILD_INDEX_OUT_OF_BOUNDS: 'Child component index out of bounds',
/**
* Error when calling an abstract method that should be overriden.
*/
ABSTRACT_METHOD: 'Unimplemented abstract method'
};
/**
@@ -195,12 +207,11 @@ Blockly.Component.prototype.isInDocument = function() {
};
/**
* Creates the initial DOM representation for the component. The default
* implementation is to set this.element_ = div.
* Creates the initial DOM representation for the component.
* @protected
*/
Blockly.Component.prototype.createDom = function() {
this.element_ = document.createElement('div');
throw Error(Blockly.Component.Error.ABSTRACT_METHOD);
};
/**
@@ -223,19 +234,6 @@ Blockly.Component.prototype.render = function(opt_parentElement) {
this.render_(opt_parentElement);
};
/**
* Renders the component before another element. The other element should be in
* the document already.
*
* Throws an Error if the component is already rendered.
*
* @param {Node} sibling Node to render the component before.
* @protected
*/
Blockly.Component.prototype.renderBefore = function(sibling) {
this.render_(/** @type {Element} */ (sibling.parentNode), sibling);
};
/**
* Renders the component. If a parent element is supplied, the component's
* element will be appended to it. If there is no optional parent element and
@@ -476,7 +474,8 @@ Blockly.Component.prototype.addChildAt = function(child, index, opt_render) {
child.element_.parentNode &&
// Under some circumstances, IE8 implicitly creates a Document Fragment
// for detached nodes, so ensure the parent is an Element as it should be.
child.element_.parentNode.nodeType == Blockly.utils.dom.Node.ELEMENT_NODE) {
child.element_.parentNode.nodeType ==
Blockly.utils.dom.NodeType.ELEMENT_NODE) {
// We don't touch the DOM, but if the parent is in the document, and the
// child element is in the document but not marked as such, then we call
// enterDocument on the child.
@@ -496,21 +495,6 @@ Blockly.Component.prototype.getContentElement = function() {
return this.element_;
};
/**
* Set is right-to-left. This function should be used if the component needs
* to know the rendering direction during DOM creation (i.e. before
* {@link #enterDocument} is called and is right-to-left is set).
* @param {boolean} rightToLeft Whether the component is rendered
* right-to-left.
* @package
*/
Blockly.Component.prototype.setRightToLeft = function(rightToLeft) {
if (this.inDocument_) {
throw Error(Blockly.Component.Error.ALREADY_RENDERED);
}
this.rightToLeft_ = rightToLeft;
};
/**
* Returns true if the component has children.
* @return {boolean} True if the component has children.
@@ -568,14 +552,3 @@ Blockly.Component.prototype.forEachChild = function(f, opt_obj) {
f.call(/** @type {?} */ (opt_obj), this.children_[i], i);
}
};
/**
* Returns the 0-based index of the given child component, or -1 if no such
* child is found.
* @param {?Blockly.Component} child The child component.
* @return {number} 0-based index of the child component; -1 if not found.
* @protected
*/
Blockly.Component.prototype.indexOfChild = function(child) {
return this.children_.indexOf(child);
};
-543
View File
@@ -1,543 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Blockly menu similar to Closure's goog.ui.Menu
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.Menu');
goog.require('Blockly.Component');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
/**
* A basic menu class.
* @constructor
* @extends {Blockly.Component}
*/
Blockly.Menu = function() {
Blockly.Component.call(this);
/**
* Coordinates of the mousedown event that caused this menu to open. Used to
* prevent the consequent mouseup event due to a simple click from activating
* a menu item immediately.
* @type {?Blockly.utils.Coordinate}
* @package
*/
this.openingCoords = null;
/**
* This is the element that we will listen to the real focus events on.
* A value of -1 means no menuitem is highlighted.
* @type {number}
* @private
*/
this.highlightedIndex_ = -1;
/**
* Mouse over event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseOverHandler_ = null;
/**
* Click event data.
* @type {?Blockly.EventData}
* @private
*/
this.clickHandler_ = null;
/**
* Mouse enter event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseEnterHandler_ = null;
/**
* Mouse leave event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseLeaveHandler_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyDownWrapper_ = null;
};
Blockly.utils.object.inherits(Blockly.Menu, Blockly.Component);
/**
* Creates the menu DOM.
* @override
*/
Blockly.Menu.prototype.createDom = function() {
var element = document.createElement('div');
element.id = this.getId();
this.setElementInternal(element);
// Set class
element.className = 'goog-menu goog-menu-vertical blocklyNonSelectable';
element.tabIndex = 0;
// Initialize ARIA role.
Blockly.utils.aria.setRole(element,
this.roleName_ || Blockly.utils.aria.Role.MENU);
};
/**
* Focus the menu element.
* @package
*/
Blockly.Menu.prototype.focus = function() {
var el = this.getElement();
if (el) {
el.focus({preventScroll:true});
Blockly.utils.dom.addClass(el, 'focused');
}
};
/**
* Blur the menu element.
* @package
*/
Blockly.Menu.prototype.blur = function() {
var el = this.getElement();
if (el) {
el.blur();
Blockly.utils.dom.removeClass(el, 'focused');
}
};
/**
* Set the menu accessibility role.
* @param {!Blockly.utils.aria.Role} roleName role name.
* @package
*/
Blockly.Menu.prototype.setRole = function(roleName) {
this.roleName_ = roleName;
};
/** @override */
Blockly.Menu.prototype.enterDocument = function() {
Blockly.Menu.superClass_.enterDocument.call(this);
this.forEachChild(function(child) {
if (child.isInDocument()) {
this.registerChildId_(child);
}
}, this);
this.attachEvents_();
};
/**
* Cleans up the container before its DOM is removed from the document, and
* removes event handlers. Overrides {@link Blockly.Component#exitDocument}.
* @override
*/
Blockly.Menu.prototype.exitDocument = function() {
// {@link #setHighlightedIndex} has to be called before
// {@link Blockly.Component#exitDocument}, otherwise it has no effect.
this.setHighlightedIndex(-1);
Blockly.Menu.superClass_.exitDocument.call(this);
};
/** @override */
Blockly.Menu.prototype.disposeInternal = function() {
Blockly.Menu.superClass_.disposeInternal.call(this);
this.detachEvents_();
};
/**
* Adds the event listeners to the menu.
* @private
*/
Blockly.Menu.prototype.attachEvents_ = function() {
var el = /** @type {!EventTarget} */ (this.getElement());
this.mouseOverHandler_ = Blockly.bindEventWithChecks_(el,
'mouseover', this, this.handleMouseOver_, true);
this.clickHandler_ = Blockly.bindEventWithChecks_(el,
'click', this, this.handleClick_, true);
this.mouseEnterHandler_ = Blockly.bindEventWithChecks_(el,
'mouseenter', this, this.handleMouseEnter_, true);
this.mouseLeaveHandler_ = Blockly.bindEventWithChecks_(el,
'mouseleave', this, this.handleMouseLeave_, true);
this.onKeyDownWrapper_ = Blockly.bindEventWithChecks_(el,
'keydown', this, this.handleKeyEvent);
};
/**
* Removes the event listeners from the menu.
* @private
*/
Blockly.Menu.prototype.detachEvents_ = function() {
if (this.mouseOverHandler_) {
Blockly.unbindEvent_(this.mouseOverHandler_);
this.mouseOverHandler_ = null;
}
if (this.clickHandler_) {
Blockly.unbindEvent_(this.clickHandler_);
this.clickHandler_ = null;
}
if (this.mouseEnterHandler_) {
Blockly.unbindEvent_(this.mouseEnterHandler_);
this.mouseEnterHandler_ = null;
}
if (this.mouseLeaveHandler_) {
Blockly.unbindEvent_(this.mouseLeaveHandler_);
this.mouseLeaveHandler_ = null;
}
if (this.onKeyDownWrapper_) {
Blockly.unbindEvent_(this.onKeyDownWrapper_);
this.onKeyDownWrapper_ = null;
}
};
// Child component management.
/**
* Map of DOM IDs to child menuitems. Each key is the DOM ID of a child
* menuitems's root element; each value is a reference to the child menu
* item itself.
* @type {?Object}
* @private
*/
Blockly.Menu.prototype.childElementIdMap_ = null;
/**
* Creates a DOM ID for the child menuitem and registers it to an internal
* hash table to be able to find it fast by id.
* @param {Blockly.Component} child The child menuitem. Its root element has
* to be created yet.
* @private
*/
Blockly.Menu.prototype.registerChildId_ = function(child) {
// Map the DOM ID of the menuitem's root element to the menuitem itself.
var childElem = child.getElement();
// If the menuitem's root element doesn't have a DOM ID assign one.
var id = childElem.id || (childElem.id = child.getId());
// Lazily create the child element ID map on first use.
if (!this.childElementIdMap_) {
this.childElementIdMap_ = {};
}
this.childElementIdMap_[id] = child;
};
/**
* Returns the child menuitem that owns the given DOM node, or null if no such
* menuitem is found.
* @param {Node} node DOM node whose owner is to be returned.
* @return {?Blockly.MenuItem} menuitem for which the DOM node belongs to.
* @protected
*/
Blockly.Menu.prototype.getMenuItem = function(node) {
// Ensure that this menu actually has child menuitems before
// looking up the menuitem.
if (this.childElementIdMap_) {
var elem = this.getElement();
while (node && node !== elem) {
var id = node.id;
if (id in this.childElementIdMap_) {
return this.childElementIdMap_[id];
}
node = node.parentNode;
}
}
return null;
};
// Highlight management.
/**
* Unhighlight the current highlighted item.
* @protected
*/
Blockly.Menu.prototype.unhighlightCurrent = function() {
var highlighted = this.getHighlighted();
if (highlighted) {
highlighted.setHighlighted(false);
}
};
/**
* Clears the currently highlighted item.
* @protected
*/
Blockly.Menu.prototype.clearHighlighted = function() {
this.unhighlightCurrent();
this.setHighlightedIndex(-1);
};
/**
* Returns the currently highlighted item (if any).
* @return {?Blockly.Component} Highlighted item (null if none).
* @protected
*/
Blockly.Menu.prototype.getHighlighted = function() {
return this.getChildAt(this.highlightedIndex_);
};
/**
* Highlights the item at the given 0-based index (if any). If another item
* was previously highlighted, it is un-highlighted.
* @param {number} index Index of item to highlight (-1 removes the current
* highlight).
* @protected
*/
Blockly.Menu.prototype.setHighlightedIndex = function(index) {
var child = this.getChildAt(index);
if (child) {
child.setHighlighted(true);
this.highlightedIndex_ = index;
} else if (this.highlightedIndex_ > -1) {
this.getHighlighted().setHighlighted(false);
this.highlightedIndex_ = -1;
}
// Bring the highlighted item into view. This has no effect if the menu is not
// scrollable.
if (child) {
Blockly.utils.style.scrollIntoContainerView(
/** @type {!Element} */ (child.getElement()),
/** @type {!Element} */ (this.getElement()));
}
};
/**
* Highlights the given item if it exists and is a child of the container;
* otherwise un-highlights the currently highlighted item.
* @param {Blockly.MenuItem} item Item to highlight.
* @protected
*/
Blockly.Menu.prototype.setHighlighted = function(item) {
this.setHighlightedIndex(this.indexOfChild(item));
};
/**
* Highlights the next highlightable item (or the first if nothing is currently
* highlighted).
* @package
*/
Blockly.Menu.prototype.highlightNext = function() {
this.unhighlightCurrent();
this.highlightHelper(function(index, max) {
return (index + 1) % max;
}, this.highlightedIndex_);
};
/**
* Highlights the previous highlightable item (or the last if nothing is
* currently highlighted).
* @package
*/
Blockly.Menu.prototype.highlightPrevious = function() {
this.unhighlightCurrent();
this.highlightHelper(function(index, max) {
index--;
return index < 0 ? max - 1 : index;
}, this.highlightedIndex_);
};
/**
* Helper function that manages the details of moving the highlight among
* child menuitems in response to keyboard events.
* @param {function(this: Blockly.Component, number, number) : number} fn
* Function that accepts the current and maximum indices, and returns the
* next index to check.
* @param {number} startIndex Start index.
* @return {boolean} Whether the highlight has changed.
* @protected
*/
Blockly.Menu.prototype.highlightHelper = function(fn, startIndex) {
// If the start index is -1 (meaning there's nothing currently highlighted),
// try starting from the currently open item, if any.
var curIndex =
startIndex < 0 ? -1 : startIndex;
var numItems = this.getChildCount();
curIndex = fn.call(this, curIndex, numItems);
var visited = 0;
while (visited <= numItems) {
var menuItem = /** @type {Blockly.MenuItem} */ (this.getChildAt(curIndex));
if (menuItem && this.canHighlightItem(menuItem)) {
this.setHighlightedIndex(curIndex);
return true;
}
visited++;
curIndex = fn.call(this, curIndex, numItems);
}
return false;
};
/**
* Returns whether the given item can be highlighted.
* @param {Blockly.MenuItem} item The item to check.
* @return {boolean} Whether the item can be highlighted.
* @protected
*/
Blockly.Menu.prototype.canHighlightItem = function(item) {
return item.isEnabled();
};
// Mouse events.
/**
* Handles mouseover events. Highlight menuitems as the user
* hovers over them.
* @param {Event} e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseOver_ = function(e) {
var menuItem = this.getMenuItem(/** @type {Node} */ (e.target));
if (menuItem) {
if (menuItem.isEnabled()) {
var currentHighlighted = this.getHighlighted();
if (currentHighlighted === menuItem) {
return;
}
this.unhighlightCurrent();
this.setHighlighted(menuItem);
} else {
this.unhighlightCurrent();
}
}
};
/**
* Handles click events. Pass the event onto the child
* menuitem to handle.
* @param {Event} e Click to handle.
* @private
*/
Blockly.Menu.prototype.handleClick_ = function(e) {
var oldCoords = this.openingCoords;
// Clear out the saved opening coords immediately so they're not used twice.
this.openingCoords = null;
if (oldCoords && typeof e.clientX === 'number') {
var newCoords = new Blockly.utils.Coordinate(e.clientX, e.clientY);
if (Blockly.utils.Coordinate.distance(oldCoords, newCoords) < 1) {
// This menu was opened by a mousedown and we're handling the consequent
// click event. The coords haven't changed, meaning this was the same
// opening event. Don't do the usual behavior because the menu just popped
// up under the mouse and the user didn't mean to activate this item.
return;
}
}
var menuItem = this.getMenuItem(/** @type {Node} */ (e.target));
if (menuItem && menuItem.handleClick(e)) {
e.preventDefault();
}
};
/**
* Handles mouse enter events. Focus the element.
* @param {Event} _e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseEnter_ = function(_e) {
this.focus();
};
/**
* Handles mouse leave events. Blur and clear highlight.
* @param {Event} _e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseLeave_ = function(_e) {
if (this.getElement()) {
this.blur();
this.clearHighlighted();
}
};
// Keyboard events.
/**
* Attempts to handle a keyboard event, if the menuitem is enabled, by calling
* {@link handleKeyEventInternal}. Considered protected; should only be used
* within this package and by subclasses.
* @param {Event} e Key event to handle.
* @return {boolean} Whether the key event was handled.
* @protected
*/
Blockly.Menu.prototype.handleKeyEvent = function(e) {
if (this.getChildCount() != 0 &&
this.handleKeyEventInternal(e)) {
e.preventDefault();
e.stopPropagation();
return true;
}
return false;
};
/**
* Attempts to handle a keyboard event; returns true if the event was handled,
* false otherwise. If the container is enabled, and a child is highlighted,
* calls the child menuitem's `handleKeyEvent` method to give the menuitem
* a chance to handle the event first.
* @param {Event} e Key event to handle.
* @return {boolean} Whether the event was handled by the container (or one of
* its children).
* @protected
*/
Blockly.Menu.prototype.handleKeyEventInternal = function(e) {
// Give the highlighted menuitem the chance to handle the key event.
var highlighted = this.getHighlighted();
if (highlighted && typeof highlighted.handleKeyEvent == 'function' &&
highlighted.handleKeyEvent(e)) {
return true;
}
// Do not handle the key event if any modifier key is pressed.
if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) {
return false;
}
// Either nothing is highlighted, or the highlighted menuitem didn't handle
// the key event, so attempt to handle it here.
switch (e.keyCode) {
case Blockly.utils.KeyCodes.ENTER:
if (highlighted) {
highlighted.performActionInternal(e);
}
break;
case Blockly.utils.KeyCodes.UP:
this.highlightPrevious();
break;
case Blockly.utils.KeyCodes.DOWN:
this.highlightNext();
break;
default:
return false;
}
return true;
};
-282
View File
@@ -1,282 +0,0 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Blockly menu item similar to Closure's goog.ui.MenuItem
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.MenuItem');
goog.require('Blockly.Component');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
/**
* Class representing an item in a menu.
*
* @param {string} content Text caption to display as the content of
* the item.
* @param {string=} opt_value Data/model associated with the menu item.
* @constructor
* @extends {Blockly.Component}
*/
Blockly.MenuItem = function(content, opt_value) {
Blockly.Component.call(this);
this.setContentInternal(content);
this.setValue(opt_value);
/**
* @type {boolean}
* @private
*/
this.enabled_ = true;
/**
* @type {Blockly.MenuItem}
* @private
*/
this.previousSibling_;
/**
* @type {Blockly.MenuItem}
* @private
*/
this.nextSibling_;
};
Blockly.utils.object.inherits(Blockly.MenuItem, Blockly.Component);
/**
* Creates the menuitem's DOM.
* @override
*/
Blockly.MenuItem.prototype.createDom = function() {
var element = document.createElement('div');
element.id = this.getId();
this.setElementInternal(element);
// Set class and style
element.className = 'goog-menuitem goog-option ' +
(!this.enabled_ ? 'goog-menuitem-disabled ' : '') +
(this.checked_ ? 'goog-option-selected ' : '') +
(this.rightToLeft_ ? 'goog-menuitem-rtl ' : '');
var content = this.getContentWrapperDom();
element.appendChild(content);
// Add a checkbox for checkable menu items.
var checkboxDom = this.getCheckboxDom();
if (checkboxDom) {
content.appendChild(checkboxDom);
}
content.appendChild(this.getContentDom());
// Initialize ARIA role and state.
Blockly.utils.aria.setRole(element, this.roleName_ || (this.checkable_ ?
Blockly.utils.aria.Role.MENUITEMCHECKBOX :
Blockly.utils.aria.Role.MENUITEM));
Blockly.utils.aria.setState(element, Blockly.utils.aria.State.SELECTED,
(this.checkable_ && this.checked_) || false);
};
/**
* @return {Element} The HTML element for the checkbox.
* @protected
*/
Blockly.MenuItem.prototype.getCheckboxDom = function() {
if (!this.checkable_) {
return null;
}
var menuItemCheckbox = document.createElement('div');
menuItemCheckbox.className = 'goog-menuitem-checkbox';
return menuItemCheckbox;
};
/**
* @return {!Element} The HTML for the content.
* @protected
*/
Blockly.MenuItem.prototype.getContentDom = function() {
var content = this.content_;
if (typeof content === 'string') {
content = document.createTextNode(content);
}
return content;
};
/**
* @return {!Element} The HTML for the content wrapper.
* @protected
*/
Blockly.MenuItem.prototype.getContentWrapperDom = function() {
var contentWrapper = document.createElement('div');
contentWrapper.className = 'goog-menuitem-content';
return contentWrapper;
};
/**
* Sets the content associated with the menu item.
* @param {string} content Text caption to set as the
* menuitem's contents.
* @protected
*/
Blockly.MenuItem.prototype.setContentInternal = function(content) {
this.content_ = content;
};
/**
* Sets the value associated with the menu item.
* @param {*} value Value to be associated with the menu item.
* @package
*/
Blockly.MenuItem.prototype.setValue = function(value) {
this.value_ = value;
};
/**
* Gets the value associated with the menu item.
* @return {*} value Value associated with the menu item.
* @package
*/
Blockly.MenuItem.prototype.getValue = function() {
return this.value_;
};
/**
* Set the menu accessibility role.
* @param {!Blockly.utils.aria.Role} roleName Role name.
* @package
*/
Blockly.MenuItem.prototype.setRole = function(roleName) {
this.roleName_ = roleName;
};
/**
* Sets the menu item to be checkable or not. Set to true for menu items
* that represent checkable options.
* @param {boolean} checkable Whether the menu item is checkable.
* @package
*/
Blockly.MenuItem.prototype.setCheckable = function(checkable) {
this.checkable_ = checkable;
};
/**
* Checks or unchecks the component.
* @param {boolean} checked Whether to check or uncheck the component.
* @package
*/
Blockly.MenuItem.prototype.setChecked = function(checked) {
if (!this.checkable_) {
return;
}
this.checked_ = checked;
var el = this.getElement();
if (el && this.isEnabled()) {
if (checked) {
Blockly.utils.dom.addClass(el, 'goog-option-selected');
Blockly.utils.aria.setState(el,
Blockly.utils.aria.State.SELECTED, true);
} else {
Blockly.utils.dom.removeClass(el, 'goog-option-selected');
Blockly.utils.aria.setState(el,
Blockly.utils.aria.State.SELECTED, false);
}
}
};
/**
* Highlights or unhighlights the component.
* @param {boolean} highlight Whether to highlight or unhighlight the component.
* @package
*/
Blockly.MenuItem.prototype.setHighlighted = function(highlight) {
this.highlight_ = highlight;
var el = this.getElement();
if (el && this.isEnabled()) {
if (highlight) {
Blockly.utils.dom.addClass(el, 'goog-menuitem-highlight');
} else {
Blockly.utils.dom.removeClass(el, 'goog-menuitem-highlight');
}
}
};
/**
* Returns true if the menu item is enabled, false otherwise.
* @return {boolean} Whether the menu item is enabled.
* @package
*/
Blockly.MenuItem.prototype.isEnabled = function() {
return this.enabled_;
};
/**
* Enables or disables the menu item.
* @param {boolean} enabled Whether to enable or disable the menu item.
* @package
*/
Blockly.MenuItem.prototype.setEnabled = function(enabled) {
this.enabled_ = enabled;
var el = this.getElement();
if (el) {
if (!this.enabled_) {
Blockly.utils.dom.addClass(el, 'goog-menuitem-disabled');
} else {
Blockly.utils.dom.removeClass(el, 'goog-menuitem-disabled');
}
}
};
/**
* Handles click events. If the component is enabled, trigger
* the action associated with this menu item.
* @param {Event} _e Mouse event to handle.
* @package
*/
Blockly.MenuItem.prototype.handleClick = function(_e) {
if (this.isEnabled()) {
this.setHighlighted(true);
this.performActionInternal();
}
};
/**
* Performs the appropriate action when the menu item is activated
* by the user.
* @protected
*/
Blockly.MenuItem.prototype.performActionInternal = function() {
if (this.checkable_) {
this.setChecked(!this.checked_);
}
if (this.actionHandler_) {
this.actionHandler_.call(/** @type {?} */ (this.actionHandlerObj_), this);
}
};
/**
* Set the handler that's triggered when the menu item is activated
* by the user. If `opt_obj` is provided, it will be used as the
* 'this' object in the function when called.
* @param {function(this:T,!Blockly.MenuItem):?} fn The handler.
* @param {T=} opt_obj Used as the 'this' object in f when called.
* @template T
* @package
*/
Blockly.MenuItem.prototype.onAction = function(fn, opt_obj) {
this.actionHandler_ = fn;
this.actionHandlerObj_ = opt_obj;
};
+43 -58
View File
@@ -91,13 +91,6 @@ Blockly.tree.BaseNode = function(content, config) {
*/
this.expanded_ = false;
/**
* Whether to allow user to collapse this node.
* @type {boolean}
* @protected
*/
this.isUserCollapsible_ = true;
/**
* Nesting depth of this node; cached result of getDepth.
* -1 if value has not been cached.
@@ -174,8 +167,7 @@ Blockly.tree.BaseNode.prototype.initAccessibility = function() {
var ce = this.getChildrenElement();
if (ce) {
Blockly.utils.aria.setRole(ce,
Blockly.utils.aria.Role.GROUP);
Blockly.utils.aria.setRole(ce, Blockly.utils.aria.Role.GROUP);
// In case the children will be created lazily.
if (ce.hasChildNodes()) {
@@ -201,7 +193,7 @@ Blockly.tree.BaseNode.prototype.initAccessibility = function() {
Blockly.tree.BaseNode.prototype.createDom = function() {
var element = document.createElement('div');
element.appendChild(this.toDom());
this.setElementInternal(/** @type {!Element} */ (element));
this.setElementInternal(/** @type {!HTMLElement} */ (element));
};
@@ -340,25 +332,24 @@ Blockly.tree.BaseNode.prototype.setDepth_ = function(depth) {
};
/**
* Returns true if the node is a descendant of this node
* @param {Blockly.tree.BaseNode} node The node to check.
* Returns true if the node is a descendant of this node.
* @param {Blockly.Component} node The node to check.
* @return {boolean} True if the node is a descendant of this node, false
* otherwise.
* @protected
*/
Blockly.tree.BaseNode.prototype.contains = function(node) {
var current = node;
while (current) {
if (current == this) {
while (node) {
if (node == this) {
return true;
}
current = current.getParent();
node = node.getParent();
}
return false;
};
/**
* This is re-defined here to indicate to the closure compiler the correct
* This is re-defined here to indicate to the Closure Compiler the correct
* child return type.
* @param {number} index 0-based index.
* @return {Blockly.tree.BaseNode} The child at the given index; null if none.
@@ -377,6 +368,16 @@ Blockly.tree.BaseNode.prototype.getChildren = function() {
return children;
};
/**
* Returns the node's parent, if any.
* @return {?Blockly.tree.BaseNode} The parent node.
* @protected
*/
Blockly.tree.BaseNode.prototype.getParent = function() {
return /** @type {Blockly.tree.BaseNode} */ (
Blockly.tree.BaseNode.superClass_.getParent.call(this));
};
/**
* @return {Blockly.tree.BaseNode} The previous sibling of this node.
* @protected
@@ -551,6 +552,7 @@ Blockly.tree.BaseNode.prototype.toDom = function() {
};
/**
* Calculates correct padding for each row. Nested categories are indented more.
* @return {number} The pixel indent of the row.
* @private
*/
@@ -559,6 +561,7 @@ Blockly.tree.BaseNode.prototype.getPixelIndent_ = function() {
};
/**
* Creates row with icon and label dom.
* @return {!Element} The HTML element for the row.
* @protected
*/
@@ -575,6 +578,8 @@ Blockly.tree.BaseNode.prototype.getRowDom = function() {
};
/**
* Adds the selected class name to the default row class name if node is
* selected.
* @return {string} The class name for the row.
* @protected
*/
@@ -613,10 +618,11 @@ Blockly.tree.BaseNode.prototype.getIconDom = function() {
* @protected
*/
Blockly.tree.BaseNode.prototype.getCalculatedIconClass = function() {
throw Error('unimplemented abstract method');
throw Error(Blockly.Component.Error.ABSTRACT_METHOD);
};
/**
* Gets a string containing the x and y position of the node's background.
* @return {string} The background position style value.
* @protected
*/
@@ -626,7 +632,7 @@ Blockly.tree.BaseNode.prototype.getBackgroundPosition = function() {
};
/**
* @return {Element} The element for the tree node.
* @return {HTMLElement} The element for the tree node.
* @override
*/
Blockly.tree.BaseNode.prototype.getElement = function() {
@@ -635,7 +641,7 @@ Blockly.tree.BaseNode.prototype.getElement = function() {
el = document.getElementById(this.getId());
this.setElementInternal(el);
}
return el;
return /** @type {!HTMLElement} */ (el);
};
/**
@@ -707,26 +713,6 @@ Blockly.tree.BaseNode.prototype.updateIcon_ = function() {
this.getIconElement().className = this.getCalculatedIconClass();
};
/**
* Handles mouse down event.
* @param {!Event} e The browser event.
* @protected
*/
Blockly.tree.BaseNode.prototype.onMouseDown = function(e) {
var el = e.target;
// expand icon
var type = el.getAttribute('type');
if (type == 'expand' && this.hasChildren()) {
if (this.isUserCollapsible_) {
this.toggle();
}
return;
}
this.select();
this.updateRow();
};
/**
* Handles a click event.
* @param {!Event} e The browser event.
@@ -746,16 +732,10 @@ Blockly.tree.BaseNode.prototype.onKeyDown = function(e) {
var handled = true;
switch (e.keyCode) {
case Blockly.utils.KeyCodes.RIGHT:
if (e.altKey) {
break;
}
handled = this.selectChild();
break;
case Blockly.utils.KeyCodes.LEFT:
if (e.altKey) {
break;
}
handled = this.selectParent();
break;
@@ -767,6 +747,12 @@ Blockly.tree.BaseNode.prototype.onKeyDown = function(e) {
handled = this.selectPrevious();
break;
case Blockly.utils.KeyCodes.ENTER:
case Blockly.utils.KeyCodes.SPACE:
this.toggle();
handled = true;
break;
default:
handled = false;
}
@@ -811,7 +797,7 @@ Blockly.tree.BaseNode.prototype.selectPrevious = function() {
* @package
*/
Blockly.tree.BaseNode.prototype.selectParent = function() {
if (this.hasChildren() && this.expanded_ && this.isUserCollapsible_) {
if (this.hasChildren() && this.expanded_) {
this.setExpanded(false);
} else {
var parent = this.getParent();
@@ -862,18 +848,17 @@ Blockly.tree.BaseNode.prototype.getLastShownDescendant = function() {
Blockly.tree.BaseNode.prototype.getNextShownNode = function() {
if (this.hasChildren() && this.expanded_) {
return this.getChildAt(0);
} else {
var parent = this;
var next;
while (parent != this.getTree()) {
next = parent.getNextSibling();
if (next != null) {
return next;
}
parent = parent.getParent();
}
return null;
}
var parent = this;
var next;
while (parent != this.getTree()) {
next = parent.getNextSibling();
if (next != null) {
return next;
}
parent = parent.getParent();
}
return null;
};
/**
+19 -80
View File
@@ -33,20 +33,6 @@ goog.require('Blockly.utils.style');
Blockly.tree.TreeControl = function(toolbox, config) {
this.toolbox_ = toolbox;
/**
* Focus event data.
* @type {?Blockly.EventData}
* @private
*/
this.onFocusWrapper_ = null;
/**
* Blur event data.
* @type {?Blockly.EventData}
* @private
*/
this.onBlurWrapper_ = null;
/**
* Click event data.
* @type {?Blockly.EventData}
@@ -66,13 +52,27 @@ Blockly.tree.TreeControl = function(toolbox, config) {
// The root is open and selected by default.
this.expanded_ = true;
this.selected_ = true;
/**
* Currently selected item.
* @type {Blockly.tree.BaseNode}
* @private
*/
this.selectedItem_ = this;
/**
* A handler that's triggered before a node is selected.
* @type {?function(Blockly.tree.BaseNode):boolean}
* @private
*/
this.onBeforeSelected_ = null;
/**
* A handler that's triggered before a node is selected.
* @type {?function(Blockly.tree.BaseNode, Blockly.tree.BaseNode):?}
* @private
*/
this.onAfterSelected_ = null;
};
Blockly.utils.object.inherits(Blockly.tree.TreeControl, Blockly.tree.BaseNode);
@@ -101,41 +101,6 @@ Blockly.tree.TreeControl.prototype.getDepth = function() {
return 0;
};
/**
* Handles focus on the tree.
* @param {!Event} _e The browser event.
* @private
*/
Blockly.tree.TreeControl.prototype.handleFocus_ = function(_e) {
this.focused_ = true;
var el = /** @type {!Element} */ (this.getElement());
Blockly.utils.dom.addClass(el, 'focused');
if (this.selectedItem_) {
this.selectedItem_.select();
}
};
/**
* Handles blur on the tree.
* @param {!Event} _e The browser event.
* @private
*/
Blockly.tree.TreeControl.prototype.handleBlur_ = function(_e) {
this.focused_ = false;
var el = /** @type {!Element} */ (this.getElement());
Blockly.utils.dom.removeClass(el, 'focused');
};
/**
* Get whether this tree has focus or not.
* @return {boolean} True if it has focus.
* @package
*/
Blockly.tree.TreeControl.prototype.hasFocus = function() {
return this.focused_;
};
/** @override */
Blockly.tree.TreeControl.prototype.setExpanded = function(expanded) {
this.expanded_ = expanded;
@@ -172,11 +137,6 @@ Blockly.tree.TreeControl.prototype.getCalculatedIconClass = function() {
if (!expanded && iconClass) {
return iconClass;
}
// fall back on default icons
if (expanded && this.config_.cssExpandedRootIcon) {
return this.config_.cssTreeIcon + ' ' + this.config_.cssExpandedRootIcon;
}
return '';
};
@@ -278,10 +238,6 @@ Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
var el = this.getElement();
el.tabIndex = 0;
this.onFocusWrapper_ = Blockly.bindEvent_(el,
'focus', this, this.handleFocus_);
this.onBlurWrapper_ = Blockly.bindEvent_(el,
'blur', this, this.handleBlur_);
this.onClickWrapper_ = Blockly.bindEventWithChecks_(el,
'click', this, this.handleMouseEvent_);
this.onKeydownWrapper_ = Blockly.bindEvent_(el,
@@ -293,14 +249,6 @@ Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
* @private
*/
Blockly.tree.TreeControl.prototype.detachEvents_ = function() {
if (this.onFocusWrapper_) {
Blockly.unbindEvent_(this.onFocusWrapper_);
this.onFocusWrapper_ = null;
}
if (this.onBlurWrapper_) {
Blockly.unbindEvent_(this.onBlurWrapper_);
this.onBlurWrapper_ = null;
}
if (this.onClickWrapper_) {
Blockly.unbindEvent_(this.onClickWrapper_);
this.onClickWrapper_ = null;
@@ -318,15 +266,8 @@ Blockly.tree.TreeControl.prototype.detachEvents_ = function() {
*/
Blockly.tree.TreeControl.prototype.handleMouseEvent_ = function(e) {
var node = this.getNodeFromEvent_(e);
if (node) {
switch (e.type) {
case 'mousedown':
node.onMouseDown(e);
break;
case 'click':
node.onClick_(e);
break;
}
if (node && e.type == 'click') {
node.onClick_(e);
}
};
@@ -337,10 +278,8 @@ Blockly.tree.TreeControl.prototype.handleMouseEvent_ = function(e) {
* @private
*/
Blockly.tree.TreeControl.prototype.handleKeyEvent_ = function(e) {
var handled = false;
// Handle navigation keystrokes.
handled = (this.selectedItem_ && this.selectedItem_.onKeyDown(e)) || handled;
var handled = !!(this.selectedItem_ && this.selectedItem_.onKeyDown(e));
if (handled) {
Blockly.utils.style.scrollIntoContainerView(
@@ -363,7 +302,7 @@ Blockly.tree.TreeControl.prototype.getNodeFromEvent_ = function(e) {
// find the right node
var node = null;
var target = e.target;
while (target != null) {
while (target) {
var id = target.id;
node = Blockly.tree.BaseNode.allNodes[id];
if (node) {
+8 -10
View File
@@ -32,6 +32,13 @@ goog.require('Blockly.utils.KeyCodes');
Blockly.tree.TreeNode = function(toolbox, content, config) {
this.toolbox_ = toolbox;
Blockly.tree.BaseNode.call(this, content, config);
/**
* A handler that's triggered when the size of node has changed.
* @type {?function():?}
* @private
*/
this.onSizeChanged_ = null;
};
Blockly.utils.object.inherits(Blockly.tree.TreeNode, Blockly.tree.BaseNode);
@@ -93,7 +100,7 @@ Blockly.tree.TreeNode.prototype.getCalculatedIconClass = function() {
*/
Blockly.tree.TreeNode.prototype.onClick_ = function(_e) {
// Expand icon.
if (this.hasChildren() && this.isUserCollapsible_) {
if (this.hasChildren()) {
this.toggle();
this.select();
} else if (this.isSelected()) {
@@ -104,15 +111,6 @@ Blockly.tree.TreeNode.prototype.onClick_ = function(_e) {
this.updateRow();
};
/**
* Suppress the inherited mouse down behaviour.
* @param {!Event} _e The browser event.
* @override
* @private
*/
Blockly.tree.TreeNode.prototype.onMouseDown = function(_e) {
// NOP
};
/**
* Remap event.keyCode in horizontalLayout so that arrow
+5 -2
View File
@@ -16,12 +16,15 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
/**
* Class for a connection between blocks.
* @param {!Blockly.Block} source The block establishing this connection.
* @param {number} type The type of the connection.
* @constructor
* @implements {Blockly.IASTNodeLocationWithBlock}
*/
Blockly.Connection = function(source, type) {
/**
@@ -242,7 +245,6 @@ Blockly.Connection.prototype.isConnected = function() {
* @param {Blockly.Connection} target Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
* @package
*/
Blockly.Connection.prototype.canConnectWithReason = function(target) {
if (!target) {
@@ -617,6 +619,7 @@ Blockly.Connection.prototype.checkType = function(otherConnection) {
* @return {boolean} True if the connections share a type.
* @private
* @deprecated October 2019, use connection.checkType instead.
* @suppress {unusedPrivateMembers}
*/
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
console.warn('Deprecated call to Blockly.Connection.prototype.checkType_, ' +
@@ -639,7 +642,7 @@ Blockly.Connection.prototype.onCheckChanged_ = function() {
/**
* Change a connection's compatibility.
* @param {?(string|!Array<string>)} check Compatible value type or list of
* @param {?(string|!Array.<string>)} check Compatible value type or list of
* value types. Null if all types are compatible.
* @return {!Blockly.Connection} The connection being modified
* (to allow chaining).
+34 -23
View File
@@ -24,7 +24,7 @@ goog.require('Blockly.Msg');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.uiMenu');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Xml');
@@ -36,11 +36,11 @@ goog.require('Blockly.Xml');
Blockly.ContextMenu.currentBlock = null;
/**
* Opaque data that can be passed to unbindEvent_.
* @type {Array.<!Array>}
* Menu object.
* @type {Blockly.Menu}
* @private
*/
Blockly.ContextMenu.eventWrapper_ = null;
Blockly.ContextMenu.menu_ = null;
/**
* Construct the menu based on the list of options and show the menu.
@@ -49,17 +49,18 @@ Blockly.ContextMenu.eventWrapper_ = null;
* @param {boolean} rtl True if RTL, false if LTR.
*/
Blockly.ContextMenu.show = function(e, options, rtl) {
Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null);
Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, Blockly.ContextMenu.dispose);
if (!options.length) {
Blockly.ContextMenu.hide();
return;
}
var menu = Blockly.ContextMenu.populate_(options, rtl);
Blockly.ContextMenu.menu_ = menu;
Blockly.ContextMenu.position_(menu, e, rtl);
// 1ms delay is required for focusing on context menus because some other
// mouse event is still waiting in the queue and clears focus.
setTimeout(function() {menu.getElement().focus();}, 1);
setTimeout(function() {menu.focus();}, 1);
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
};
@@ -77,14 +78,15 @@ Blockly.ContextMenu.populate_ = function(options, rtl) {
callback: Blockly.MakeItSo}
*/
var menu = new Blockly.Menu();
menu.setRightToLeft(rtl);
menu.setRole(Blockly.utils.aria.Role.MENU);
for (var i = 0, option; (option = options[i]); i++) {
var menuItem = new Blockly.MenuItem(option.text);
menuItem.setRightToLeft(rtl);
menu.addChild(menuItem, true);
menuItem.setRole(Blockly.utils.aria.Role.MENUITEM);
menu.addChild(menuItem);
menuItem.setEnabled(option.enabled);
if (option.enabled) {
var actionHandler = function() {
var actionHandler = function(_menuItem) {
var option = this;
Blockly.ContextMenu.hide();
option.callback();
@@ -108,25 +110,28 @@ Blockly.ContextMenu.position_ = function(menu, e, rtl) {
var viewportBBox = Blockly.utils.getViewportBBox();
// This one is just a point, but we'll pretend that it's a rect so we can use
// some helper functions.
var anchorBBox = {
top: e.clientY + viewportBBox.top,
bottom: e.clientY + viewportBBox.top,
left: e.clientX + viewportBBox.left,
right: e.clientX + viewportBBox.left
};
var anchorBBox = new Blockly.utils.Rect(
e.clientY + viewportBBox.top,
e.clientY + viewportBBox.top,
e.clientX + viewportBBox.left,
e.clientX + viewportBBox.left
);
Blockly.ContextMenu.createWidget_(menu);
var menuSize = Blockly.utils.uiMenu.getSize(menu);
var menuSize = menu.getSize();
if (rtl) {
Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize);
anchorBBox.left += menuSize.width;
anchorBBox.right += menuSize.width;
viewportBBox.left += menuSize.width;
viewportBBox.right += menuSize.width;
}
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, rtl);
// Calling menuDom.focus() has to wait until after the menu has been placed
// correctly. Otherwise it will cause a page scroll to get the misplaced menu
// in view. See issue #1329.
menu.getElement().focus();
menu.focus();
};
/**
@@ -141,8 +146,8 @@ Blockly.ContextMenu.createWidget_ = function(menu) {
Blockly.utils.dom.addClass(
/** @type {!Element} */ (menuDom), 'blocklyContextMenu');
// Prevent system context menu when right-clicking a Blockly context menu.
Blockly.bindEventWithChecks_(
/** @type {!EventTarget} */ (menuDom), 'contextmenu', null, Blockly.utils.noEvent);
Blockly.bindEventWithChecks_(/** @type {!EventTarget} */ (menuDom),
'contextmenu', null, Blockly.utils.noEvent);
// Focus only after the initial render to avoid issue #1329.
menu.focus();
};
@@ -153,9 +158,15 @@ Blockly.ContextMenu.createWidget_ = function(menu) {
Blockly.ContextMenu.hide = function() {
Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
Blockly.ContextMenu.currentBlock = null;
if (Blockly.ContextMenu.eventWrapper_) {
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
Blockly.ContextMenu.eventWrapper_ = null;
};
/**
* Dispose of the menu.
*/
Blockly.ContextMenu.dispose = function() {
if (Blockly.ContextMenu.menu_) {
Blockly.ContextMenu.menu_.dispose();
Blockly.ContextMenu.menu_ = null;
}
};
+102 -218
View File
@@ -74,13 +74,13 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
/**
* Set the cursor to be displayed when over something draggable.
* See See https://github.com/google/blockly/issues/981 for context.
* See https://github.com/google/blockly/issues/981 for context.
* @param {*} _cursor Enum.
* @deprecated April 2017.
*/
Blockly.Css.setCursor = function(_cursor) {
console.warn('Deprecated call to Blockly.Css.setCursor. ' +
'See https://github.com/google/blockly/issues/981 for context');
'See issue #981 for context');
};
/**
@@ -150,8 +150,7 @@ Blockly.Css.CONTENT = [
'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);',
'color: #000;',
'display: none;',
'font-family: sans-serif;',
'font-size: 9pt;',
'font: 9pt sans-serif;',
'opacity: .9;',
'padding: 2px;',
'position: absolute;',
@@ -169,11 +168,11 @@ Blockly.Css.CONTENT = [
'background-color: #fff;',
'border-radius: 2px;',
'padding: 4px;',
'box-shadow: 0px 0px 3px 1px rgba(0,0,0,.3);',
'box-shadow: 0 0 3px 1px rgba(0,0,0,.3);',
'}',
'.blocklyDropDownDiv.focused {',
'box-shadow: 0px 0px 6px 1px rgba(0,0,0,.3);',
'.blocklyDropDownDiv.blocklyFocused {',
'box-shadow: 0 0 6px 1px rgba(0,0,0,.3);',
'}',
'.blocklyDropDownContent {',
@@ -309,7 +308,7 @@ Blockly.Css.CONTENT = [
'.blocklyInsertionMarker>.blocklyPathLight,',
'.blocklyInsertionMarker>.blocklyPathDark {',
'fill-opacity: .2;',
'stroke: none',
'stroke: none;',
'}',
'.blocklyMultilineText {',
@@ -333,7 +332,8 @@ Blockly.Css.CONTENT = [
Don't allow users to select text. It gets annoying when trying to
drag a block and selected text moves instead.
*/
'.blocklySvg text, .blocklyBlockDragSurface text {',
'.blocklySvg text,',
'.blocklyBlockDragSurface text {',
'user-select: none;',
'-ms-user-select: none;',
'-webkit-user-select: none;',
@@ -416,7 +416,8 @@ Blockly.Css.CONTENT = [
'z-index: 30;',
'}',
'.blocklyScrollbarHorizontal, .blocklyScrollbarVertical {',
'.blocklyScrollbarHorizontal,',
'.blocklyScrollbarVertical {',
'position: absolute;',
'outline: none;',
'}',
@@ -449,217 +450,10 @@ Blockly.Css.CONTENT = [
'background: #faa;',
'}',
'.blocklyContextMenu {',
'border-radius: 4px;',
'max-height: 100%;',
'}',
'.blocklyDropdownMenu {',
'border-radius: 2px;',
'padding: 0 !important;',
'}',
'.blocklyWidgetDiv .blocklyDropdownMenu .goog-menuitem,',
'.blocklyDropDownDiv .blocklyDropdownMenu .goog-menuitem {',
/* 28px on the left for icon or checkbox. */
'padding-left: 28px;',
'}',
/* BiDi override for the resting state. */
/* #noflip */
'.blocklyWidgetDiv .blocklyDropdownMenu .goog-menuitem.goog-menuitem-rtl,',
'.blocklyDropDownDiv .blocklyDropdownMenu .goog-menuitem.goog-menuitem-rtl {',
/* Flip left/right padding for BiDi. */
'padding-left: 5px;',
'padding-right: 28px;',
'}',
'.blocklyVerticalMarker {',
'stroke-width: 3px;',
'fill: rgba(255,255,255,.5);',
'pointer-events: none',
'}',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-icon {',
'background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px;',
'}',
/* Copied from: goog/css/menu.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/**
* Standard styling for menus created by goog.ui.MenuRenderer.
*
* @author attila@google.com (Attila Bodis)
*/
'.blocklyWidgetDiv .goog-menu {',
'background: #fff;',
'border-color: transparent;',
'border-style: solid;',
'border-width: 1px;',
'cursor: default;',
'font: normal 13px Arial, sans-serif;',
'margin: 0;',
'outline: none;',
'padding: 4px 0;',
'position: absolute;',
'overflow-y: auto;',
'overflow-x: hidden;',
'max-height: 100%;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'box-shadow: 0px 0px 3px 1px rgba(0,0,0,.3);',
'}',
'.blocklyWidgetDiv .goog-menu.focused {',
'box-shadow: 0px 0px 6px 1px rgba(0,0,0,.3);',
'}',
'.blocklyDropDownDiv .goog-menu {',
'cursor: default;',
'font: normal 13px "Helvetica Neue", Helvetica, sans-serif;',
'outline: none;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
/* Copied from: goog/css/menuitem.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/**
* Standard styling for menus created by goog.ui.MenuItemRenderer.
*
* @author attila@google.com (Attila Bodis)
*/
/**
* State: resting.
*
* NOTE(mleibman,chrishenry):
* The RTL support in Closure is provided via two mechanisms -- "rtl" CSS
* classes and BiDi flipping done by the CSS compiler. Closure supports RTL
* with or without the use of the CSS compiler. In order for them not to
* conflict with each other, the "rtl" CSS classes need to have the #noflip
* annotation. The non-rtl counterparts should ideally have them as well,
* but, since .goog-menuitem existed without .goog-menuitem-rtl for so long
* before being added, there is a risk of people having templates where they
* are not rendering the .goog-menuitem-rtl class when in RTL and instead
* rely solely on the BiDi flipping by the CSS compiler. That's why we're
* not adding the #noflip to .goog-menuitem.
*/
'.blocklyWidgetDiv .goog-menuitem,',
'.blocklyDropDownDiv .goog-menuitem {',
'color: #000;',
'font: normal 13px Arial, sans-serif;',
'list-style: none;',
'margin: 0;',
/* 7em on the right for shortcut. */
'min-width: 7em;',
'border: none;',
'padding: 6px 15px;',
'white-space: nowrap;',
'cursor: pointer;',
'}',
/* If a menu doesn't have checkable items or items with icons,
* remove padding.
*/
'.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,',
'.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem,',
'.blocklyDropDownDiv .goog-menu-nocheckbox .goog-menuitem,',
'.blocklyDropDownDiv .goog-menu-noicon .goog-menuitem {',
'padding-left: 12px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-content,',
'.blocklyDropDownDiv .goog-menuitem-content {',
'font-family: Arial, sans-serif;',
'font-size: 13px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-content {',
'color: #000;',
'}',
'.blocklyDropDownDiv .goog-menuitem-content {',
'color: #000;',
'}',
/* State: disabled. */
'.blocklyWidgetDiv .goog-menuitem-disabled,',
'.blocklyDropDownDiv .goog-menuitem-disabled {',
'cursor: inherit;',
'}',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content,',
'.blocklyDropDownDiv .goog-menuitem-disabled .goog-menuitem-content {',
'color: #ccc !important;',
'}',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-menuitem-disabled .goog-menuitem-icon {',
'opacity: .3;',
'filter: alpha(opacity=30);',
'}',
/* State: hover. */
'.blocklyWidgetDiv .goog-menuitem-highlight ,',
'.blocklyDropDownDiv .goog-menuitem-highlight {',
'background-color: rgba(0,0,0,.1);',
'}',
/* State: selected/checked. */
'.blocklyWidgetDiv .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-menuitem-icon {',
'background-repeat: no-repeat;',
'height: 16px;',
'left: 6px;',
'position: absolute;',
'right: auto;',
'vertical-align: middle;',
'width: 16px;',
'}',
/* BiDi override for the selected/checked state. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-icon {',
/* Flip left/right positioning. */
'left: auto;',
'right: 6px;',
'}',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-option-selected .goog-menuitem-icon {',
'position: static;', /* Scroll with the menu. */
'float: left;',
'margin-left: -24px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyDropDownDiv .goog-menuitem-rtl .goog-menuitem-icon {',
'float: right;',
'margin-right: -24px;',
'pointer-events: none;',
'}',
'.blocklyComputeCanvas {',
@@ -671,5 +465,95 @@ Blockly.Css.CONTENT = [
'.blocklyNoPointerEvents {',
'pointer-events: none;',
'}',
'.blocklyContextMenu {',
'border-radius: 4px;',
'max-height: 100%;',
'}',
'.blocklyDropdownMenu {',
'border-radius: 2px;',
'padding: 0 !important;',
'}',
'.blocklyDropdownMenu .blocklyMenuItem {',
/* 28px on the left for icon or checkbox. */
'padding-left: 28px;',
'}',
/* BiDi override for the resting state. */
'.blocklyDropdownMenu .blocklyMenuItemRtl {',
/* Flip left/right padding for BiDi. */
'padding-left: 5px;',
'padding-right: 28px;',
'}',
'.blocklyWidgetDiv .blocklyMenu {',
'background: #fff;',
'border: 1px solid transparent;',
'box-shadow: 0 0 3px 1px rgba(0,0,0,.3);',
'font: normal 13px Arial, sans-serif;',
'margin: 0;',
'outline: none;',
'padding: 4px 0;',
'position: absolute;',
'overflow-y: auto;',
'overflow-x: hidden;',
'max-height: 100%;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
'.blocklyWidgetDiv .blocklyMenu.blocklyFocused {',
'box-shadow: 0 0 6px 1px rgba(0,0,0,.3);',
'}',
'.blocklyDropDownDiv .blocklyMenu {',
'font: normal 13px "Helvetica Neue", Helvetica, sans-serif;',
'outline: none;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
/* State: resting. */
'.blocklyMenuItem {',
'border: none;',
'color: #000;',
'cursor: pointer;',
'list-style: none;',
'margin: 0;',
/* 7em on the right for shortcut. */
'min-width: 7em;',
'padding: 6px 15px;',
'white-space: nowrap;',
'}',
/* State: disabled. */
'.blocklyMenuItemDisabled {',
'color: #ccc;',
'cursor: inherit;',
'}',
/* State: hover. */
'.blocklyMenuItemHighlight {',
'background-color: rgba(0,0,0,.1);',
'}',
/* State: selected/checked. */
'.blocklyMenuItemCheckbox {',
'height: 16px;',
'position: absolute;',
'width: 16px;',
'}',
'.blocklyMenuItemSelected .blocklyMenuItemCheckbox {',
'background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px;',
'float: left;',
'margin-left: -24px;',
'position: static;', /* Scroll with the menu. */
'}',
'.blocklyMenuItemRtl .blocklyMenuItemCheckbox {',
'float: right;',
'margin-right: -24px;',
'}',
/* eslint-enable indent */
];
+82 -40
View File
@@ -19,6 +19,9 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.math');
goog.require('Blockly.utils.style');
goog.requireType('Blockly.utils.Rect');
goog.requireType('Blockly.utils.Size');
/**
* Class for drop-down div.
@@ -117,6 +120,35 @@ Blockly.DropDownDiv.rendererClassName_ = '';
*/
Blockly.DropDownDiv.themeClassName_ = '';
/**
* Dropdown bounds info object used to encapsulate sizing information about a
* bounding element (bounding box and width/height).
* @typedef {{
* top:number,
* left:number,
* bottom:number,
* right:number,
* width:number,
* height:number
* }}
*/
Blockly.DropDownDiv.BoundsInfo;
/**
* Dropdown position metrics.
* @typedef {{
* initialX:number,
* initialY:number,
* finalX:number,
* finalY:number,
* arrowX:?number,
* arrowY:?number,
* arrowAtTop:?boolean,
* arrowVisible:boolean
* }}
*/
Blockly.DropDownDiv.PositionMetrics;
/**
* Create and insert the DOM element for this div.
* @package
@@ -166,10 +198,10 @@ Blockly.DropDownDiv.createDom = function() {
// Handle focusin/out events to add a visual indicator when
// a child is focused or blurred.
div.addEventListener('focusin', function() {
Blockly.utils.dom.addClass(div, 'focused');
Blockly.utils.dom.addClass(div, 'blocklyFocused');
});
div.addEventListener('focusout', function() {
Blockly.utils.dom.removeClass(div, 'focused');
Blockly.utils.dom.removeClass(div, 'blocklyFocused');
});
};
@@ -184,7 +216,7 @@ Blockly.DropDownDiv.setBoundsElement = function(boundsElement) {
/**
* Provide the div for inserting content into the drop-down.
* @return {Element} Div to populate with content
* @return {!Element} Div to populate with content.
*/
Blockly.DropDownDiv.getContentDiv = function() {
return Blockly.DropDownDiv.content_;
@@ -214,7 +246,7 @@ Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) {
* and the secondary position above the block. Drop-down will be
* constrained to the block's workspace.
* @param {!Blockly.Field} field The field showing the drop-down.
* @param {!Blockly.Block} block Block to position the drop-down around.
* @param {!Blockly.BlockSvg} block Block to position the drop-down around.
* @param {Function=} opt_onHide Optional callback for when the drop-down is
* hidden.
* @param {number=} opt_secondaryYOffset Optional Y offset for above-block
@@ -250,7 +282,7 @@ Blockly.DropDownDiv.showPositionedByField = function(field,
/**
* Get the scaled bounding box of a block.
* @param {!Blockly.Block} block The block.
* @param {!Blockly.BlockSvg} block The block.
* @return {!Blockly.utils.Rect} The scaled bounding box of the block.
* @private
*/
@@ -302,10 +334,15 @@ Blockly.DropDownDiv.showPositionedByRect_ = function(bBox, field,
if (opt_secondaryYOffset) {
secondaryY += opt_secondaryYOffset;
}
var sourceBlock = field.getSourceBlock();
// Set bounds to workspace; show the drop-down.
var sourceBlock = /** @type {!Blockly.BlockSvg} */ (field.getSourceBlock());
// Set bounds to main workspace; show the drop-down.
var workspace = sourceBlock.workspace;
while (workspace.options.parentWorkspace) {
workspace = /** @type {!Blockly.WorkspaceSvg} */ (
workspace.options.parentWorkspace);
}
Blockly.DropDownDiv.setBoundsElement(
sourceBlock.workspace.getParentSvg().parentNode);
/** @type {Element} */ (workspace.getParentSvg().parentNode));
return Blockly.DropDownDiv.show(
field, sourceBlock.RTL,
primaryX, primaryY, secondaryX, secondaryY, opt_onHide);
@@ -340,10 +377,11 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY,
var div = Blockly.DropDownDiv.DIV_;
div.style.direction = rtl ? 'rtl' : 'ltr';
var mainWorkspace =
/** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace());
Blockly.DropDownDiv.rendererClassName_ =
Blockly.getMainWorkspace().getRenderer().getClassName();
Blockly.DropDownDiv.themeClassName_ =
Blockly.getMainWorkspace().getTheme().getClassName();
mainWorkspace.getRenderer().getClassName();
Blockly.DropDownDiv.themeClassName_ = mainWorkspace.getTheme().getClassName();
Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.rendererClassName_);
Blockly.utils.dom.addClass(div, Blockly.DropDownDiv.themeClassName_);
@@ -362,8 +400,8 @@ Blockly.DropDownDiv.show = function(owner, rtl, primaryX, primaryY,
/**
* Get sizing info about the bounding element.
* @return {!Object} An object containing size information about the bounding
* element (bounding box and width/height).
* @return {!Blockly.DropDownDiv.BoundsInfo} An object containing size
* information about the bounding element (bounding box and width/height).
* @private
*/
Blockly.DropDownDiv.getBoundsInfo_ = function() {
@@ -388,11 +426,11 @@ Blockly.DropDownDiv.getBoundsInfo_ = function() {
* @param {number} primaryX Desired origin point x, in absolute px.
* @param {number} primaryY Desired origin point y, in absolute px.
* @param {number} secondaryX Secondary/alternative origin point x,
* in absolute px.
* in absolute px.
* @param {number} secondaryY Secondary/alternative origin point y,
* in absolute px.
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* in absolute px.
* @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics,
* including rendered positions for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionMetrics_ = function(primaryX, primaryY,
@@ -431,12 +469,12 @@ Blockly.DropDownDiv.getPositionMetrics_ = function(primaryX, primaryY,
* Get the metrics for positioning the div below the source.
* @param {number} primaryX Desired origin point x, in absolute px.
* @param {number} primaryY Desired origin point y, in absolute px.
* @param {!Object} boundsInfo An object containing size information about the
* bounding element (bounding box and width/height).
* @param {!Object} divSize An object containing information about the size
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size
* information about the bounding element (bounding box and width/height).
* @param {!Blockly.utils.Size} divSize An object containing information about
* the size of the DropDownDiv (width & height).
* @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics,
* including rendered positions for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionBelowMetrics_ = function(
@@ -464,15 +502,15 @@ Blockly.DropDownDiv.getPositionBelowMetrics_ = function(
/**
* Get the metrics for positioning the div above the source.
* @param {number} secondaryX Secondary/alternative origin point x,
* in absolute px.
* in absolute px.
* @param {number} secondaryY Secondary/alternative origin point y,
* in absolute px.
* @param {!Object} boundsInfo An object containing size information about the
* bounding element (bounding box and width/height).
* @param {!Object} divSize An object containing information about the size
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* in absolute px.
* @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size
* information about the bounding element (bounding box and width/height).
* @param {!Blockly.utils.Size} divSize An object containing information about
* the size of the DropDownDiv (width & height).
* @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics,
* including rendered positions for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionAboveMetrics_ = function(
@@ -501,12 +539,12 @@ Blockly.DropDownDiv.getPositionAboveMetrics_ = function(
/**
* Get the metrics for positioning the div at the top of the page.
* @param {number} sourceX Desired origin point x, in absolute px.
* @param {!Object} boundsInfo An object containing size information about the
* bounding element (bounding box and width/height).
* @param {!Object} divSize An object containing information about the size
* of the DropDownDiv (width & height).
* @return {Object} Various final metrics, including rendered positions
* for drop-down and arrow.
* @param {!Blockly.DropDownDiv.BoundsInfo} boundsInfo An object containing size
* information about the bounding element (bounding box and width/height).
* @param {!Blockly.utils.Size} divSize An object containing information about
* the size of the DropDownDiv (width & height).
* @return {!Blockly.DropDownDiv.PositionMetrics} Various final metrics,
* including rendered positions for drop-down and arrow.
* @private
*/
Blockly.DropDownDiv.getPositionTopOfPageMetrics_ = function(
@@ -521,6 +559,9 @@ Blockly.DropDownDiv.getPositionTopOfPageMetrics_ = function(
initialY : 0,
finalX: xCoords.divX, // X position remains constant during animation.
finalY: 0, // Y position remains constant during animation.
arrowAtTop: null,
arrowX: null,
arrowY: null,
arrowVisible: false
};
};
@@ -649,7 +690,8 @@ Blockly.DropDownDiv.hideWithoutAnimation = function() {
Blockly.utils.dom.removeClass(div, Blockly.DropDownDiv.themeClassName_);
Blockly.DropDownDiv.themeClassName_ = '';
}
Blockly.getMainWorkspace().markFocused();
(/** @type {!Blockly.WorkspaceSvg} */ (
Blockly.getMainWorkspace())).markFocused();
};
/**
@@ -700,7 +742,7 @@ Blockly.DropDownDiv.positionInternal_ = function(
var dy = finalY - initialY;
div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
return metrics.arrowAtTop;
return !!metrics.arrowAtTop;
};
/**
@@ -716,7 +758,7 @@ Blockly.DropDownDiv.repositionForWindowResize = function() {
// it.
if (Blockly.DropDownDiv.owner_) {
var field = /** @type {!Blockly.Field} */ (Blockly.DropDownDiv.owner_);
var block = Blockly.DropDownDiv.owner_.getSourceBlock();
var block = /** @type {!Blockly.BlockSvg} */ (field.getSourceBlock());
var bBox = Blockly.DropDownDiv.positionToField_ ?
Blockly.DropDownDiv.getScaledBboxOfField_(field) :
Blockly.DropDownDiv.getScaledBboxOfBlock_(block);
+34 -13
View File
@@ -19,11 +19,16 @@ goog.require('Blockly.Events.BlockChange');
goog.require('Blockly.Gesture');
goog.require('Blockly.utils');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.Size');
goog.require('Blockly.utils.style');
goog.require('Blockly.utils.userAgent');
goog.requireType('Blockly.blockRendering.ConstantProvider');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IRegistrable');
/**
@@ -36,6 +41,10 @@ goog.requireType('Blockly.blockRendering.ConstantProvider');
* the individual field's documentation for a list of properties this
* parameter supports.
* @constructor
* @implements {Blockly.IASTNodeLocationSvg}
* @implements {Blockly.IASTNodeLocationWithBlock}
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IRegistrable}
*/
Blockly.Field = function(value, opt_validator, opt_config) {
/**
@@ -44,7 +53,7 @@ Blockly.Field = function(value, opt_validator, opt_config) {
* @type {*}
* @protected
*/
this.value_ = null;
this.value_ = this.DEFAULT_VALUE;
/**
* Validation function called when user edits an editable field.
@@ -131,6 +140,13 @@ Blockly.Field = function(value, opt_validator, opt_config) {
opt_validator && this.setValidator(opt_validator);
};
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.Field.prototype.DEFAULT_VALUE = null;
/**
* Name of field. Unique within each block.
* Static labels are usually unnamed.
@@ -708,8 +724,8 @@ Blockly.Field.prototype.getSize = function() {
/**
* Returns the bounding box of the rendered field, accounting for workspace
* scaling.
* @return {!Object} An object with top, bottom, left, and right in pixels
* relative to the top left corner of the page (window coordinates).
* @return {!Blockly.utils.Rect} An object with top, bottom, left, and right in
* pixels relative to the top left corner of the page (window coordinates).
* @package
*/
Blockly.Field.prototype.getScaledBBox = function() {
@@ -742,12 +758,12 @@ Blockly.Field.prototype.getScaledBBox = function() {
var scaledWidth = bBox.width;
var scaledHeight = bBox.height;
}
return {
top: xy.y,
bottom: xy.y + scaledHeight,
left: xy.x,
right: xy.x + scaledWidth
};
return new Blockly.utils.Rect(
xy.y,
xy.y + scaledHeight,
xy.x,
xy.x + scaledWidth
);
};
/**
@@ -858,16 +874,20 @@ Blockly.Field.prototype.setValue = function(newValue) {
return;
}
}
var source = this.sourceBlock_;
if (source && source.disposed) {
doLogging && console.log('source disposed, return');
return;
}
var oldValue = this.getValue();
if (oldValue === newValue) {
doLogging && console.log('same, return');
// No change.
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
if (source && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
this.sourceBlock_, 'field', this.name || null, oldValue, newValue));
source, 'field', this.name || null, oldValue, newValue));
}
this.doValueUpdate_(newValue);
if (this.isDirty_) {
@@ -1101,7 +1121,8 @@ Blockly.Field.prototype.setMarkerSvg = function(markerSvg) {
* @protected
*/
Blockly.Field.prototype.updateMarkers_ = function() {
var workspace = this.sourceBlock_.workspace;
var workspace =
/** @type {!Blockly.WorkspaceSvg} */ (this.sourceBlock_.workspace);
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
workspace.getCursor().draw();
}
+10 -2
View File
@@ -71,7 +71,7 @@ Blockly.FieldAngle = function(opt_value, opt_validator, opt_config) {
this.round_ = Blockly.FieldAngle.ROUND;
Blockly.FieldAngle.superClass_.constructor.call(
this, opt_value || 0, opt_validator, opt_config);
this, opt_value, opt_validator, opt_config);
/**
* The angle picker's gauge path depending on the value.
@@ -108,6 +108,14 @@ Blockly.FieldAngle = function(opt_value, opt_validator, opt_config) {
};
Blockly.utils.object.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldAngle.prototype.DEFAULT_VALUE = 0;
/**
* Construct a FieldAngle from a JSON arg object.
* @param {!Object} options A JSON object with options (angle).
@@ -247,7 +255,7 @@ Blockly.FieldAngle.prototype.render_ = function() {
* Create and show the angle field's editor.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programmatically.
* @private
* @protected
*/
Blockly.FieldAngle.prototype.showEditor_ = function(opt_e) {
// Mobile browsers have issues with in-line textareas (focus & keyboards).
+7 -3
View File
@@ -44,14 +44,18 @@ Blockly.FieldCheckbox = function(opt_value, opt_validator, opt_config) {
*/
this.checkChar_ = null;
if (opt_value == null) {
opt_value = 'FALSE';
}
Blockly.FieldCheckbox.superClass_.constructor.call(
this, opt_value, opt_validator, opt_config);
};
Blockly.utils.object.inherits(Blockly.FieldCheckbox, Blockly.Field);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldCheckbox.prototype.DEFAULT_VALUE = false;
/**
* Construct a FieldCheckbox from a JSON arg object.
* @param {!Object} options A JSON object with options (checked).
+9 -3
View File
@@ -43,8 +43,7 @@ goog.require('Blockly.utils.Size');
*/
Blockly.FieldColour = function(opt_value, opt_validator, opt_config) {
Blockly.FieldColour.superClass_.constructor.call(
this, opt_value || Blockly.FieldColour.COLOURS[0],
opt_validator, opt_config);
this, opt_value, opt_validator, opt_config);
/**
* The field's colour picker element.
@@ -268,6 +267,13 @@ Blockly.FieldColour.COLOURS = [
'#ffccff', '#ff99ff', '#cc66cc', '#cc33cc', '#993399', '#663366', '#330033'
];
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldColour.prototype.DEFAULT_VALUE = Blockly.FieldColour.COLOURS[0];
/**
* An array of tooltip strings for the palette. If not the same length as
* COLOURS, the colour's hex code will be used for any missing titles.
@@ -311,7 +317,7 @@ Blockly.FieldColour.prototype.setColumns = function(columns) {
/**
* Create and show the colour field's editor.
* @private
* @protected
*/
Blockly.FieldColour.prototype.showEditor_ = function() {
this.picker_ = this.dropdownCreate_();
-344
View File
@@ -1,344 +0,0 @@
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Date input field.
* @author pkendall64@gmail.com (Paul Kendall)
*/
'use strict';
goog.provide('Blockly.FieldDate');
goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Field');
goog.require('Blockly.fieldRegistry');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.string');
goog.require('goog.date');
goog.require('goog.date.DateTime');
goog.require('goog.events');
goog.require('goog.i18n.DateTimeSymbols');
goog.require('goog.i18n.DateTimeSymbols_he');
goog.require('goog.ui.DatePicker');
/**
* Class for a date input field.
* @param {string=} opt_value The initial value of the field. Should be in
* 'YYYY-MM-DD' format. Defaults to the current date.
* @param {Function=} opt_validator A function that is called to validate
* changes to the field's value. Takes in a date string & returns a
* validated date string ('YYYY-MM-DD' format), or null to abort the change.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldDate = function(opt_value, opt_validator) {
Blockly.FieldDate.superClass_.constructor.call(this,
opt_value || new goog.date.Date().toIsoString(true), opt_validator);
};
Blockly.utils.object.inherits(Blockly.FieldDate, Blockly.Field);
/**
* Construct a FieldDate from a JSON arg object.
* @param {!Object} options A JSON object with options (date).
* @return {!Blockly.FieldDate} The new field instance.
* @package
* @nocollapse
*/
Blockly.FieldDate.fromJson = function(options) {
return new Blockly.FieldDate(options['date']);
};
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
* @type {boolean}
*/
Blockly.FieldDate.prototype.SERIALIZABLE = true;
/**
* Mouse cursor style when over the hotspot that initiates the editor.
*/
Blockly.FieldDate.prototype.CURSOR = 'text';
/**
* Border colour for the dropdown div showing the date picker. Must be a CSS
* string.
* @type {string}
* @private
*/
Blockly.FieldDate.prototype.DROPDOWN_BORDER_COLOUR = 'silver';
/**
* Background colour for the dropdown div showing the date picker. Must be a
* CSS string.
* @type {string}
* @private
*/
Blockly.FieldDate.prototype.DROPDOWN_BACKGROUND_COLOUR = 'white';
/**
* Ensure that the input value is a valid date.
* @param {*=} opt_newValue The input value.
* @return {?string} A valid date, or null if invalid.
* @protected
*/
Blockly.FieldDate.prototype.doClassValidation_ = function(opt_newValue) {
if (!opt_newValue) {
return null;
}
// Check if the new value is parsable or not.
var date = goog.date.Date.fromIsoString(opt_newValue);
if (!date || date.toIsoString(true) != opt_newValue) {
return null;
}
return opt_newValue;
};
/**
* Render the field. If the picker is shown make sure it has the current
* date selected.
* @protected
*/
Blockly.FieldDate.prototype.render_ = function() {
Blockly.FieldDate.superClass_.render_.call(this);
if (this.picker_) {
this.picker_.setDate(goog.date.Date.fromIsoString(this.getValue()));
this.updateEditor_();
}
};
/**
* Updates the field's colours to match those of the block.
* @package
*/
Blockly.FieldDate.prototype.applyColour = function() {
this.todayColour_ = this.sourceBlock_.style.colourPrimary;
this.selectedColour_ = this.sourceBlock_.style.colourSecondary;
this.updateEditor_();
};
/**
* Updates the picker to show the current date and currently selected date.
* @private
*/
Blockly.FieldDate.prototype.updateEditor_ = function() {
if (!this.picker_) {
// Nothing to update.
return;
}
// Updating today should come before updating selected, so that if the
// current day is selected, it will appear so.
if (this.oldTodayElement_) {
this.oldTodayElement_.style.backgroundColor = null;
this.oldTodayElement_.style.color = null;
}
var today = this.picker_.getElementByClass('goog-date-picker-today');
this.oldTodayElement_ = today;
if (today) {
today.style.backgroundColor = this.todayColour_;
today.style.color = 'white';
}
if (this.oldSelectedElement_ && this.oldSelectedElement_ != today) {
this.oldSelectedElement_.style.backgroundColor = null;
this.oldSelectedElement_.style.color = null;
}
var selected = this.picker_.getElementByClass('goog-date-picker-selected');
this.oldSelectedElement_ = selected;
if (selected) {
selected.style.backgroundColor = this.selectedColour_;
selected.style.color = this.todayColour_;
}
};
/**
* Create and show the date field's editor.
* @private
*/
Blockly.FieldDate.prototype.showEditor_ = function() {
this.picker_ = this.dropdownCreate_();
this.picker_.render(Blockly.DropDownDiv.getContentDiv());
Blockly.utils.dom.addClass(this.picker_.getElement(), 'blocklyDatePicker');
Blockly.DropDownDiv.setColour(
this.DROPDOWN_BACKGROUND_COLOUR, this.DROPDOWN_BORDER_COLOUR);
Blockly.DropDownDiv.showPositionedByField(
this, this.dropdownDispose_.bind(this));
this.updateEditor_();
};
/**
* Create the date dropdown editor.
* @return {!goog.ui.DatePicker} The newly created date picker.
* @private
*/
Blockly.FieldDate.prototype.dropdownCreate_ = function() {
// Create the date picker using Closure.
Blockly.FieldDate.loadLanguage_();
var picker = new goog.ui.DatePicker();
picker.setAllowNone(false);
picker.setShowWeekNum(false);
picker.setUseNarrowWeekdayNames(true);
picker.setUseSimpleNavigationMenu(true);
picker.setDate(goog.date.DateTime.fromIsoString(this.getValue()));
this.changeEventKey_ = goog.events.listen(
picker,
goog.ui.DatePicker.Events.CHANGE,
this.onDateSelected_,
null,
this);
this.activeMonthEventKey_ = goog.events.listen(
picker,
goog.ui.DatePicker.Events.CHANGE_ACTIVE_MONTH,
this.updateEditor_,
null,
this);
return picker;
};
/**
* Dispose of references to DOM elements and events belonging
* to the date editor.
* @private
*/
Blockly.FieldDate.prototype.dropdownDispose_ = function() {
goog.events.unlistenByKey(this.changeEventKey_);
goog.events.unlistenByKey(this.activeMonthEventKey_);
};
/**
* Handle a CHANGE event in the date picker.
* @param {!Event} event The CHANGE event.
* @private
*/
Blockly.FieldDate.prototype.onDateSelected_ = function(event) {
var date = event.date ? event.date.toIsoString(true) : '';
this.setValue(date);
Blockly.DropDownDiv.hideIfOwner(this);
};
/**
* Load the best language pack by scanning the Blockly.Msg object for a
* language that matches the available languages in Closure.
* @private
*/
Blockly.FieldDate.loadLanguage_ = function() {
for (var prop in goog.i18n) {
if (Blockly.utils.string.startsWith(prop, 'DateTimeSymbols_')) {
var lang = prop.substr(16).toLowerCase().replace('_', '.');
// E.g. 'DateTimeSymbols_pt_BR' -> 'pt.br'
if (goog.getObjectByName(lang, Blockly.Msg)) {
goog.i18n.DateTimeSymbols = goog.i18n[prop];
break;
}
}
}
};
/**
* CSS for date picker. See css.js for use.
*/
Blockly.Css.register([
/* eslint-disable indent */
'.blocklyDatePicker,',
'.blocklyDatePicker th,',
'.blocklyDatePicker td {',
'font: 13px Arial, sans-serif;',
'color: #3c4043;',
'}',
'.blocklyDatePicker th,',
'.blocklyDatePicker td {',
'text-align: center;',
'vertical-align: middle;',
'}',
'.blocklyDatePicker .goog-date-picker-wday,',
'.blocklyDatePicker .goog-date-picker-date {',
'padding: 6px 6px;',
'}',
'.blocklyDatePicker button {',
'cursor: pointer;',
'padding: 6px 6px;',
'margin: 1px 0;',
'border: 0;',
'color: #3c4043;',
'font-weight: bold;',
'background: transparent;',
'}',
'.blocklyDatePicker .goog-date-picker-previousMonth,',
'.blocklyDatePicker .goog-date-picker-nextMonth {',
'height: 24px;',
'width: 24px;',
'}',
'.blocklyDatePicker .goog-date-picker-monthyear {',
'font-weight: bold;',
'}',
'.blocklyDatePicker .goog-date-picker-wday, ',
'.blocklyDatePicker .goog-date-picker-other-month {',
'color: #70757a;',
'border-radius: 12px;',
'}',
'.blocklyDatePicker button,',
'.blocklyDatePicker .goog-date-picker-date {',
'cursor: pointer;',
'background-color: rgb(218, 220, 224, 0);',
'border-radius: 12px;',
'transition: background-color,opacity 100ms linear;',
'}',
'.blocklyDatePicker button:hover,',
'.blocklyDatePicker .goog-date-picker-date:hover {',
'background-color: rgb(218, 220, 224, .5);',
'}'
/* eslint-enable indent */
]);
Blockly.fieldRegistry.register('field_date', Blockly.FieldDate);
/**
* Back up original getMsg function.
* @type {!Function}
*/
goog.getMsgOrig = goog.getMsg;
/**
* Gets a localized message.
* Overrides the default Closure function to check for a Blockly.Msg first.
* Used infrequently, only known case is TODAY button in date picker.
* @param {string} str Translatable string, places holders in the form {$foo}.
* @param {Object.<string, string>=} opt_values Maps place holder name to value.
* @return {string} Message with placeholders filled.
* @suppress {duplicate}
*/
goog.getMsg = function(str, opt_values) {
var key = goog.getMsg.blocklyMsgMap[str];
if (key) {
str = Blockly.Msg[key];
}
return goog.getMsgOrig(str, opt_values);
};
/**
* Mapping of Closure messages to Blockly.Msg names.
*/
goog.getMsg.blocklyMsgMap = {
'Today': 'TODAY'
};
+21 -18
View File
@@ -67,6 +67,20 @@ Blockly.FieldDropdown = function(menuGenerator, opt_validator, opt_config) {
*/
this.generatedOptions_ = null;
/**
* The prefix field label, of common words set after options are trimmed.
* @type {?string}
* @package
*/
this.prefixField = null;
/**
* The suffix field label, of common words set after options are trimmed.
* @type {?string}
* @package
*/
this.suffixField = null;
this.trimOptions_();
/**
@@ -258,7 +272,7 @@ Blockly.FieldDropdown.prototype.createSVGArrow_ = function() {
* Create a dropdown menu under the text.
* @param {Event=} opt_e Optional mouse event that triggered the field to open,
* or undefined if triggered programmatically.
* @private
* @protected
*/
Blockly.FieldDropdown.prototype.showEditor_ = function(opt_e) {
this.menu_ = this.dropdownCreate_();
@@ -270,8 +284,8 @@ Blockly.FieldDropdown.prototype.showEditor_ = function(opt_e) {
}
// Element gets created in render.
this.menu_.render(Blockly.DropDownDiv.getContentDiv());
Blockly.utils.dom.addClass(
/** @type {!Element} */ (this.menu_.getElement()), 'blocklyDropdownMenu');
var menuElement = /** @type {!Element} */ (this.menu_.getElement());
Blockly.utils.dom.addClass(menuElement, 'blocklyDropdownMenu');
if (this.getConstants().FIELD_DROPDOWN_COLOURED_DIV) {
var primaryColour = (this.sourceBlock_.isShadow()) ?
@@ -291,11 +305,8 @@ Blockly.FieldDropdown.prototype.showEditor_ = function(opt_e) {
// view. See issue #1329.
this.menu_.focus();
// Scroll the dropdown to show the selected menu item.
if (this.selectedMenuItem_) {
Blockly.utils.style.scrollIntoContainerView(
/** @type {!Element} */ (this.selectedMenuItem_.getElement()),
/** @type {!Element} */ (this.menu_.getElement()));
this.menu_.setHighlighted(this.selectedMenuItem_);
}
this.applyColour();
@@ -308,7 +319,6 @@ Blockly.FieldDropdown.prototype.showEditor_ = function(opt_e) {
*/
Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
var menu = new Blockly.Menu();
menu.setRightToLeft(this.sourceBlock_.RTL);
menu.setRole(Blockly.utils.aria.Role.LISTBOX);
var options = this.getOptions(false);
@@ -323,12 +333,11 @@ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
image.alt = content['alt'] || '';
content = image;
}
var menuItem = new Blockly.MenuItem(content);
var menuItem = new Blockly.MenuItem(content, value);
menuItem.setRole(Blockly.utils.aria.Role.OPTION);
menuItem.setRightToLeft(this.sourceBlock_.RTL);
menuItem.setValue(value);
menuItem.setCheckable(true);
menu.addChild(menuItem, true);
menu.addChild(menuItem);
menuItem.setChecked(value == this.value_);
if (value == this.value_) {
this.selectedMenuItem_ = menuItem;
@@ -336,10 +345,6 @@ Blockly.FieldDropdown.prototype.dropdownCreate_ = function() {
menuItem.onAction(this.handleMenuActionEvent_, this);
}
Blockly.utils.aria.setState(/** @type {!Element} */ (menu.getElement()),
Blockly.utils.aria.State.ACTIVEDESCENDANT,
this.selectedMenuItem_ ? this.selectedMenuItem_.getId() : '');
return menu;
};
@@ -382,8 +387,6 @@ Blockly.FieldDropdown.prototype.onItemSelected_ = function(menu, menuItem) {
* @private
*/
Blockly.FieldDropdown.prototype.trimOptions_ = function() {
this.prefixField = null;
this.suffixField = null;
var options = this.menuGenerator_;
if (!Array.isArray(options)) {
return;
@@ -549,7 +552,7 @@ Blockly.FieldDropdown.prototype.applyColour = function() {
/**
* Draws the border with the correct width.
* @private
* @protected
*/
Blockly.FieldDropdown.prototype.render_ = function() {
// Hide both elements.
+9 -2
View File
@@ -22,7 +22,7 @@ goog.require('Blockly.utils.Size');
/**
* Class for an image on a block.
* @param {string} src The URL of the image. Defaults to an empty string.
* @param {string} src The URL of the image.
* @param {!(string|number)} width Width of the image.
* @param {!(string|number)} height Height of the image.
* @param {string=} opt_alt Optional alt text for when block is collapsed.
@@ -70,7 +70,7 @@ Blockly.FieldImage = function(src, width, height,
this.altText_ = '';
Blockly.FieldImage.superClass_.constructor.call(
this, src || '', null, opt_config);
this, src, null, opt_config);
if (!opt_config) { // If the config wasn't passed, do old configuration.
this.flipRtl_ = !!opt_flipRtl;
@@ -114,6 +114,13 @@ Blockly.FieldImage = function(src, width, height,
};
Blockly.utils.object.inherits(Blockly.FieldImage, Blockly.Field);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldImage.prototype.DEFAULT_VALUE = '';
/**
* Construct a FieldImage from a JSON arg object,
* dereferencing any string table references.
+7 -3
View File
@@ -40,9 +40,6 @@ Blockly.FieldLabel = function(opt_value, opt_class, opt_config) {
*/
this.class_ = null;
if (opt_value == null) {
opt_value = '';
}
Blockly.FieldLabel.superClass_.constructor.call(
this, opt_value, null, opt_config);
@@ -52,6 +49,13 @@ Blockly.FieldLabel = function(opt_value, opt_class, opt_config) {
};
Blockly.utils.object.inherits(Blockly.FieldLabel, Blockly.Field);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldLabel.prototype.DEFAULT_VALUE = '';
/**
* Construct a FieldLabel from a JSON arg object,
* dereferencing any string table references.
-4
View File
@@ -43,9 +43,6 @@ goog.require('Blockly.utils.userAgent');
Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) {
// TODO: Once this field is documented the opt_config link should point to its
// creation documentation, rather than the text input field's.
if (opt_value == null) {
opt_value = '';
}
Blockly.FieldMultilineInput.superClass_.constructor.call(this,
opt_value, opt_validator, opt_config);
@@ -59,7 +56,6 @@ Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) {
Blockly.utils.object.inherits(Blockly.FieldMultilineInput,
Blockly.FieldTextInput);
/**
* Construct a FieldMultilineInput from a JSON arg object,
* dereferencing any string table references.
+8 -1
View File
@@ -67,7 +67,7 @@ Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision,
this.decimalPlaces_ = null;
Blockly.FieldNumber.superClass_.constructor.call(
this, opt_value || 0, opt_validator, opt_config);
this, opt_value, opt_validator, opt_config);
if (!opt_config) { // Only do one kind of configuration or the other.
this.setConstraints(opt_min, opt_max, opt_precision);
@@ -75,6 +75,13 @@ Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision,
};
Blockly.utils.object.inherits(Blockly.FieldNumber, Blockly.FieldTextInput);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldNumber.prototype.DEFAULT_VALUE = 0;
/**
* Construct a FieldNumber from a JSON arg object.
* @param {!Object} options A JSON object with options (value, min, max, and
+8 -29
View File
@@ -14,39 +14,23 @@
goog.provide('Blockly.fieldRegistry');
goog.require('Blockly.registry');
/**
* The set of all registered fields, keyed by field type as used in the JSON
* definition of a block.
* @type {!Object<string, !{fromJson: Function}>}
* @private
*/
Blockly.fieldRegistry.typeMap_ = {};
/**
* Registers a field type.
* Blockly.fieldRegistry.fromJson uses this registry to
* find the appropriate field type.
* @param {string} type The field type name as used in the JSON definition.
* @param {!{fromJson: Function}} fieldClass The field class containing a
* fromJson function that can construct an instance of the field.
* @param {?function(new:Blockly.Field, ...?)} fieldClass The field class
* containing a fromJson function that can construct an instance of the
* field.
* @throws {Error} if the type name is empty, the field is already
* registered, or the fieldClass is not an object containing a fromJson
* function.
*/
Blockly.fieldRegistry.register = function(type, fieldClass) {
if ((typeof type != 'string') || (type.trim() == '')) {
throw Error('Invalid field type "' + type + '". The type must be a' +
' non-empty string.');
}
if (Blockly.fieldRegistry.typeMap_[type]) {
throw Error('Error: Field "' + type + '" is already registered.');
}
if (!fieldClass || (typeof fieldClass.fromJson != 'function')) {
throw Error('Field "' + fieldClass + '" must have a fromJson function');
}
type = type.toLowerCase();
Blockly.fieldRegistry.typeMap_[type] = fieldClass;
Blockly.registry.register(Blockly.registry.Type.FIELD, type, fieldClass);
};
/**
@@ -54,12 +38,7 @@ Blockly.fieldRegistry.register = function(type, fieldClass) {
* @param {string} type The field type name as used in the JSON definition.
*/
Blockly.fieldRegistry.unregister = function(type) {
if (Blockly.fieldRegistry.typeMap_[type]) {
delete Blockly.fieldRegistry.typeMap_[type];
} else {
console.warn('No field mapping for type "' + type +
'" found to unregister');
}
Blockly.registry.unregister(Blockly.registry.Type.FIELD, type);
};
/**
@@ -73,8 +52,8 @@ Blockly.fieldRegistry.unregister = function(type) {
* @package
*/
Blockly.fieldRegistry.fromJson = function(options) {
var type = options['type'].toLowerCase();
var fieldClass = Blockly.fieldRegistry.typeMap_[type];
var fieldClass = /** @type {{fromJson:function(!Object):!Blockly.Field}} */ (
Blockly.registry.getClass(Blockly.registry.Type.FIELD, options['type']));
if (!fieldClass) {
console.warn('Blockly could not create a field of type ' + options['type'] +
'. The field is probably not being registered. This could be because' +
+16 -4
View File
@@ -48,9 +48,6 @@ Blockly.FieldTextInput = function(opt_value, opt_validator, opt_config) {
*/
this.spellcheck_ = true;
if (opt_value == null) {
opt_value = '';
}
Blockly.FieldTextInput.superClass_.constructor.call(this,
opt_value, opt_validator, opt_config);
@@ -80,9 +77,23 @@ Blockly.FieldTextInput = function(opt_value, opt_validator, opt_config) {
* @type {?boolean}
*/
this.fullBlockClickTarget_ = false;
/**
* The workspace that this field belongs to.
* @type {?Blockly.WorkspaceSvg}
* @protected
*/
this.workspace_ = null;
};
Blockly.utils.object.inherits(Blockly.FieldTextInput, Blockly.Field);
/**
* The default value for this field.
* @type {*}
* @protected
*/
Blockly.FieldTextInput.prototype.DEFAULT_VALUE = '';
/**
* Construct a FieldTextInput from a JSON arg object,
* dereferencing any string table references.
@@ -275,7 +286,8 @@ Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
*/
Blockly.FieldTextInput.prototype.showEditor_ = function(_opt_e,
opt_quietInput) {
this.workspace_ = this.sourceBlock_.workspace;
this.workspace_ =
(/** @type {!Blockly.BlockSvg} */ (this.sourceBlock_)).workspace;
var quietInput = opt_quietInput || false;
if (!quietInput && (Blockly.utils.userAgent.MOBILE ||
Blockly.utils.userAgent.ANDROID ||
-7
View File
@@ -98,13 +98,6 @@ Blockly.FieldVariable.fromJson = function(options) {
varName, undefined, undefined, undefined, options);
};
/**
* The workspace that this variable field belongs to.
* @type {?Blockly.Workspace}
* @private
*/
Blockly.FieldVariable.prototype.workspace_ = null;
/**
* Serializable fields are saved by the XML renderer, non-serializable fields
* are not. Editable fields should also be serializable.
+275 -90
View File
@@ -29,15 +29,24 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IDeleteArea');
goog.requireType('Blockly.utils.Metrics');
/**
* Class for a flyout.
* @param {!Blockly.Options} workspaceOptions Dictionary of options for the
* workspace.
* @constructor
* @abstract
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IDeleteArea}
*/
Blockly.Flyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);
workspaceOptions.getMetrics =
/** @type {function():!Blockly.utils.Metrics} */ (
this.getMetrics_.bind(this));
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
/**
@@ -55,6 +64,13 @@ Blockly.Flyout = function(workspaceOptions) {
*/
this.RTL = !!workspaceOptions.RTL;
/**
* Whether the flyout should be laid out horizontally or not.
* @type {boolean}
* @package
*/
this.horizontalLayout = false;
/**
* Position of the toolbox and flyout relative to the workspace.
* @type {number}
@@ -105,6 +121,13 @@ Blockly.Flyout = function(workspaceOptions) {
* @const
*/
this.tabWidth_ = this.workspace_.getRenderer().getConstants().TAB_WIDTH;
/**
* The target workspace
* @type {?Blockly.WorkspaceSvg}
* @package
*/
this.targetWorkspace = null;
};
/**
@@ -232,12 +255,15 @@ Blockly.Flyout.prototype.createDom = function(tagName) {
* create new blocks.
*/
Blockly.Flyout.prototype.init = function(targetWorkspace) {
this.targetWorkspace_ = targetWorkspace;
this.targetWorkspace = targetWorkspace;
this.workspace_.targetWorkspace = targetWorkspace;
// Add scrollbar.
this.scrollbar_ = new Blockly.Scrollbar(this.workspace_,
this.horizontalLayout_, false, 'blocklyFlyoutScrollbar');
/**
* @type {!Blockly.Scrollbar}
* @package
*/
this.scrollbar = new Blockly.Scrollbar(this.workspace_,
this.horizontalLayout, false, 'blocklyFlyoutScrollbar');
this.hide();
@@ -245,7 +271,7 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
Blockly.bindEventWithChecks_(this.svgGroup_, 'wheel', this, this.wheel_));
if (!this.autoClose) {
this.filterWrapper_ = this.filterForCapacity_.bind(this);
this.targetWorkspace_.addChangeListener(this.filterWrapper_);
this.targetWorkspace.addChangeListener(this.filterWrapper_);
}
// Dragging the flyout up and down.
@@ -255,10 +281,10 @@ Blockly.Flyout.prototype.init = function(targetWorkspace) {
// A flyout connected to a workspace doesn't have its own current gesture.
this.workspace_.getGesture =
this.targetWorkspace_.getGesture.bind(this.targetWorkspace_);
this.targetWorkspace.getGesture.bind(this.targetWorkspace);
// Get variables from the main workspace rather than the target workspace.
this.workspace_.setVariableMap(this.targetWorkspace_.getVariableMap());
this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap());
this.workspace_.createPotentialVariableMap();
};
@@ -272,12 +298,12 @@ Blockly.Flyout.prototype.dispose = function() {
this.hide();
Blockly.unbindEvent_(this.eventWrappers_);
if (this.filterWrapper_) {
this.targetWorkspace_.removeChangeListener(this.filterWrapper_);
this.targetWorkspace.removeChangeListener(this.filterWrapper_);
this.filterWrapper_ = null;
}
if (this.scrollbar_) {
this.scrollbar_.dispose();
this.scrollbar_ = null;
if (this.scrollbar) {
this.scrollbar.dispose();
this.scrollbar = null;
}
if (this.workspace_) {
this.workspace_.getThemeManager().unsubscribe(this.svgBackground_);
@@ -290,7 +316,7 @@ Blockly.Flyout.prototype.dispose = function() {
this.svgGroup_ = null;
}
this.svgBackground_ = null;
this.targetWorkspace_ = null;
this.targetWorkspace = null;
};
/**
@@ -367,7 +393,7 @@ Blockly.Flyout.prototype.updateDisplay_ = function() {
this.svgGroup_.style.display = show ? 'block' : 'none';
// Update the scrollbar's visibility too since it should mimic the
// flyout's visibility.
this.scrollbar_.setContainerVisible(show);
this.scrollbar.setContainerVisible(show);
};
/**
@@ -392,14 +418,14 @@ Blockly.Flyout.prototype.positionAt_ = function(width, height, x, y) {
}
// Update the scrollbar (if one exists).
if (this.scrollbar_) {
if (this.scrollbar) {
// Set the scrollbars origin to be the top left of the flyout.
this.scrollbar_.setOrigin(x, y);
this.scrollbar_.resize();
this.scrollbar.setOrigin(x, y);
this.scrollbar.resize();
// Set the position again so that if the metrics were the same (and the
// resize failed) our position is still updated.
this.scrollbar_.setPosition_(
this.scrollbar_.position_.x, this.scrollbar_.position_.y);
this.scrollbar.setPosition(
this.scrollbar.position.x, this.scrollbar.position.y);
}
};
@@ -426,82 +452,41 @@ Blockly.Flyout.prototype.hide = function() {
/**
* Show and populate the flyout.
* @param {!Array|!NodeList|string} xmlList List of blocks to show.
* Variables and procedures have a custom set of blocks.
* @param {!Blockly.utils.toolbox.ToolboxDefinition|string} flyoutDef
* List of contents to display in the flyout as an array of xml an
* array of Nodes, a NodeList or a string with the name of the dynamic category.
* Variables and procedures have a custom set of blocks.
*/
Blockly.Flyout.prototype.show = function(xmlList) {
Blockly.Flyout.prototype.show = function(flyoutDef) {
this.workspace_.setResizesEnabled(false);
this.hide();
this.clearOldBlocks_();
// Handle dynamic categories, represented by a name instead of a list of XML.
// Handle dynamic categories, represented by a name instead of a list.
// Look up the correct category generation function and call that to get a
// valid XML list.
if (typeof xmlList == 'string') {
if (typeof flyoutDef == 'string') {
var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback(
xmlList);
flyoutDef);
if (typeof fnToApply != 'function') {
throw TypeError('Couldn\'t find a callback function when opening' +
' a toolbox category.');
}
xmlList = fnToApply(this.workspace_.targetWorkspace);
if (!Array.isArray(xmlList)) {
flyoutDef = fnToApply(this.workspace_.targetWorkspace);
if (!Array.isArray(flyoutDef)) {
throw TypeError('Result of toolbox category callback must be an array.');
}
}
this.setVisible(true);
// Create the blocks to be shown in this flyout.
var contents = [];
var gaps = [];
this.permanentlyDisabled_.length = 0;
var default_gap = this.horizontalLayout_ ? this.GAP_X : this.GAP_Y;
for (var i = 0, xml; (xml = xmlList[i]); i++) {
if (!xml.tagName) {
continue;
}
switch (xml.tagName.toUpperCase()) {
case 'BLOCK':
var curBlock = Blockly.Xml.domToBlock(xml, this.workspace_);
if (!curBlock.isEnabled()) {
// Record blocks that were initially disabled.
// Do not enable these blocks as a result of capacity filtering.
this.permanentlyDisabled_.push(curBlock);
}
contents.push({type: 'block', block: curBlock});
// This is a deprecated method for adding gap to a block.
// <block type="math_arithmetic" gap="8"></block>
var gap = parseInt(xml.getAttribute('gap'), 10);
gaps.push(isNaN(gap) ? default_gap : gap);
break;
case 'SEP':
// Change the gap between two toolbox elements.
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
// This overwrites the gap attribute on the previous element.
var newGap = parseInt(xml.getAttribute('gap'), 10);
// Ignore gaps before the first block.
if (!isNaN(newGap) && gaps.length > 0) {
gaps[gaps.length - 1] = newGap;
} else {
gaps.push(default_gap);
}
break;
case 'LABEL':
case 'BUTTON':
var isLabel = xml.tagName.toUpperCase() == 'LABEL';
if (!Blockly.FlyoutButton) {
throw Error('Missing require for Blockly.FlyoutButton');
}
var curButton = new Blockly.FlyoutButton(this.workspace_,
this.targetWorkspace_, xml, isLabel);
contents.push({type: 'button', button: curButton});
gaps.push(default_gap);
break;
}
}
// Parse the Array or NodeList passed in into an Array of
// Blockly.utils.toolbox.Toolbox.
var parsedContent = Blockly.utils.toolbox.convertToolboxToJSON(flyoutDef);
var flyoutInfo =
/** @type {{contents:!Array.<!Object>, gaps:!Array.<number>}} */ (
this.createFlyoutInfo_(parsedContent));
this.layout_(contents, gaps);
this.layout_(flyoutInfo.contents, flyoutInfo.gaps);
// IE 11 is an incompetent browser that fails to fire mouseout events.
// When the mouse is over the background, deselect all blocks.
@@ -515,7 +500,7 @@ Blockly.Flyout.prototype.show = function(xmlList) {
this.listeners_.push(Blockly.bindEventWithChecks_(this.svgBackground_,
'mouseover', this, deselectAll));
if (this.horizontalLayout_) {
if (this.horizontalLayout) {
this.height_ = 0;
} else {
this.width_ = 0;
@@ -532,6 +517,142 @@ Blockly.Flyout.prototype.show = function(xmlList) {
this.workspace_.addChangeListener(this.reflowWrapper_);
};
/**
* Create the contents array and gaps array necessary to create the layout for
* the flyout.
* @param {Array.<Blockly.utils.toolbox.Toolbox>} parsedContent The array
* of objects to show in the flyout.
* @return {{contents:Array.<Object>, gaps:Array.<number>}} The list of contents
* and gaps needed to lay out the flyout.
* @private
*/
Blockly.Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
var contents = [];
var gaps = [];
this.permanentlyDisabled_.length = 0;
var defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
for (var i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) {
switch (contentInfo['kind'].toUpperCase()) {
case 'BLOCK':
var blockInfo = /** @type {Blockly.utils.toolbox.Block} */ (contentInfo);
var blockXml = this.getBlockXml_(blockInfo);
var block = this.createBlock_(blockXml);
// This is a deprecated method for adding gap to a block.
// <block type="math_arithmetic" gap="8"></block>
var gap = parseInt(blockInfo['gap'] || blockXml.getAttribute('gap'), 10);
gaps.push(isNaN(gap) ? defaultGap : gap);
contents.push({type: 'block', block: block});
break;
case 'SEP':
var sepInfo = /** @type {Blockly.utils.toolbox.Separator} */ (contentInfo);
this.addSeparatorGap_(sepInfo, gaps, defaultGap);
break;
case 'LABEL':
var labelInfo = /** @type {Blockly.utils.toolbox.Label} */ (contentInfo);
// A label is a button with different styling.
// Rename this function.
var label = this.createButton_(labelInfo, /** isLabel */ true);
contents.push({type: 'button', button: label});
gaps.push(defaultGap);
break;
case 'BUTTON':
var buttonInfo = /** @type {Blockly.utils.toolbox.Button} */ (contentInfo);
var button = this.createButton_(buttonInfo, /** isLabel */ false);
contents.push({type: 'button', button: button});
gaps.push(defaultGap);
break;
}
}
return {contents: contents, gaps: gaps};
};
/**
* Creates a flyout button or a flyout label.
* @param {!Blockly.utils.toolbox.Button|!Blockly.utils.toolbox.Label} btnInfo
* The object holding information about a button or a label.
* @param {boolean} isLabel True if the button is a label, false otherwise.
* @return {!Blockly.FlyoutButton} The object used to display the button in the
* flyout.
* @private
*/
Blockly.Flyout.prototype.createButton_ = function(btnInfo, isLabel) {
if (!Blockly.FlyoutButton) {
throw Error('Missing require for Blockly.FlyoutButton');
}
var curButton = new Blockly.FlyoutButton(this.workspace_,
/** @type {!Blockly.WorkspaceSvg} */ (this.targetWorkspace), btnInfo,
isLabel);
return curButton;
};
/**
* Create a block from the xml and permanently disable any blocks that were
* defined as disabled.
* @param {!Element} blockXml The xml of the block.
* @return {!Blockly.BlockSvg} The block created from the blockXml.
* @private
*/
Blockly.Flyout.prototype.createBlock_ = function(blockXml) {
var curBlock = /** @type {!Blockly.BlockSvg} */ (
Blockly.Xml.domToBlock(blockXml, this.workspace_));
if (!curBlock.isEnabled()) {
// Record blocks that were initially disabled.
// Do not enable these blocks as a result of capacity filtering.
this.permanentlyDisabled_.push(curBlock);
}
return curBlock;
};
/**
* Get the xml from the block info object.
* @param {!Blockly.utils.toolbox.Block} blockInfo The object holding
* information about a block.
* @return {!Element} The xml for the block.
* @throws {Error} if the xml is not a valid block definition.
* @private
*/
Blockly.Flyout.prototype.getBlockXml_ = function(blockInfo) {
var blockElement = null;
var blockXml = blockInfo['blockxml'];
if (blockXml && typeof blockXml != 'string') {
blockElement = blockXml;
} else if (blockXml && typeof blockXml == 'string') {
blockElement = Blockly.Xml.textToDom(blockXml);
} else if (blockInfo['type']) {
blockElement = Blockly.utils.xml.createElement('xml');
blockElement.setAttribute('type', blockInfo['type']);
blockElement.setAttribute('disabled', blockInfo['disabled']);
}
if (!blockElement) {
throw Error('Error: Invalid block definition. Block definition must have blockxml or type.');
}
return blockElement;
};
/**
* Add the necessary gap in the flyout for a separator.
* @param {!Blockly.utils.toolbox.Separator} sepInfo The object holding
* information about a separator.
* @param {!Array.<number>} gaps The list gaps between items in the flyout.
* @param {number} defaultGap The default gap between the button and next element.
* @private
*/
Blockly.Flyout.prototype.addSeparatorGap_ = function(sepInfo, gaps, defaultGap) {
// Change the gap between two toolbox elements.
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
// This overwrites the gap attribute on the previous element.
var newGap = parseInt(sepInfo['gap'], 10);
// Ignore gaps before the first block.
if (!isNaN(newGap) && gaps.length > 0) {
gaps[gaps.length - 1] = newGap;
} else {
gaps.push(defaultGap);
}
};
/**
* Delete blocks, mats and buttons from a previous showing of the flyout.
* @private
@@ -566,7 +687,7 @@ Blockly.Flyout.prototype.clearOldBlocks_ = function() {
/**
* Add listeners to a block that has been added to the flyout.
* @param {!SVGElement} root The root node of the SVG group the block is in.
* @param {!Blockly.Block} block The block to add listeners for.
* @param {!Blockly.BlockSvg} block The block to add listeners for.
* @param {!SVGElement} rect The invisible rectangle under the block that acts
* as a mat for that block.
* @protected
@@ -588,14 +709,14 @@ Blockly.Flyout.prototype.addBlockListeners_ = function(root, block, rect) {
/**
* Handle a mouse-down on an SVG block in a non-closing flyout.
* @param {!Blockly.Block} block The flyout block to copy.
* @param {!Blockly.BlockSvg} block The flyout block to copy.
* @return {!Function} Function to call when block is clicked.
* @private
*/
Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
var flyout = this;
return function(e) {
var gesture = flyout.targetWorkspace_.getGesture(e);
var gesture = flyout.targetWorkspace.getGesture(e);
if (gesture) {
gesture.setStartBlock(block);
gesture.handleFlyoutStart(e, flyout);
@@ -609,7 +730,7 @@ Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
* @private
*/
Blockly.Flyout.prototype.onMouseDown_ = function(e) {
var gesture = this.targetWorkspace_.getGesture(e);
var gesture = this.targetWorkspace.getGesture(e);
if (gesture) {
gesture.handleFlyoutStart(e, this);
}
@@ -637,8 +758,8 @@ Blockly.Flyout.prototype.isBlockCreatable_ = function(block) {
Blockly.Flyout.prototype.createBlock = function(originalBlock) {
var newBlock = null;
Blockly.Events.disable();
var variablesBeforeCreation = this.targetWorkspace_.getAllVariables();
this.targetWorkspace_.setResizesEnabled(false);
var variablesBeforeCreation = this.targetWorkspace.getAllVariables();
this.targetWorkspace.setResizesEnabled(false);
try {
newBlock = this.placeNewBlock_(originalBlock);
// Close the flyout.
@@ -647,7 +768,7 @@ Blockly.Flyout.prototype.createBlock = function(originalBlock) {
Blockly.Events.enable();
}
var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace_,
var newVariables = Blockly.Variables.getAddedVariables(this.targetWorkspace,
variablesBeforeCreation);
if (Blockly.Events.isEnabled()) {
@@ -690,7 +811,7 @@ Blockly.Flyout.prototype.initFlyoutButton_ = function(button, x, y) {
/**
* Create and place a rectangle corresponding to the given block.
* @param {!Blockly.Block} block The block to associate the rect to.
* @param {!Blockly.BlockSvg} block The block to associate the rect to.
* @param {number} x The x position of the cursor during this layout pass.
* @param {number} y The y position of the cursor during this layout pass.
* @param {!{height: number, width: number}} blockHW The height and width of the
@@ -749,7 +870,7 @@ Blockly.Flyout.prototype.filterForCapacity_ = function() {
var blocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; (block = blocks[i]); i++) {
if (this.permanentlyDisabled_.indexOf(block) == -1) {
var enable = this.targetWorkspace_
var enable = this.targetWorkspace
.isCapacityAvailable(Blockly.utils.getBlockTypeCounts(block));
while (block) {
block.setEnabled(enable);
@@ -778,7 +899,7 @@ Blockly.Flyout.prototype.reflow = function() {
* @package
*/
Blockly.Flyout.prototype.isScrollable = function() {
return this.scrollbar_ ? this.scrollbar_.isVisible() : false;
return this.scrollbar ? this.scrollbar.isVisible() : false;
};
/**
@@ -788,7 +909,7 @@ Blockly.Flyout.prototype.isScrollable = function() {
* @private
*/
Blockly.Flyout.prototype.placeNewBlock_ = function(oldBlock) {
var targetWorkspace = this.targetWorkspace_;
var targetWorkspace = this.targetWorkspace;
var svgRootOld = oldBlock.getSvgRoot();
if (!svgRootOld) {
throw Error('oldBlock is not rendered.');
@@ -850,3 +971,67 @@ Blockly.Flyout.prototype.onBlocklyAction = function(action) {
var cursor = this.workspace_.getCursor();
return cursor.onBlocklyAction(action);
};
/**
* Return the deletion rectangle for this flyout in viewport coordinates.
* @return {Blockly.utils.Rect} Rectangle in which to delete.
*/
Blockly.Flyout.prototype.getClientRect;
/**
* Position the flyout.
* @return {void}
*/
Blockly.Flyout.prototype.position;
/**
* Determine if a drag delta is toward the workspace, based on the position
* and orientation of the flyout. This is used in determineDragIntention_ to
* determine if a new block should be created or if the flyout should scroll.
* @param {!Blockly.utils.Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @return {boolean} True if the drag is toward the workspace.
* @package
*/
Blockly.Flyout.prototype.isDragTowardWorkspace;
/**
* Return an object with all the metrics required to size scrollbars for the
* flyout.
* @return {Blockly.utils.Metrics} Contains size and position metrics of the
* flyout.
* @protected
*/
Blockly.Flyout.prototype.getMetrics_;
/**
* Sets the translation of the flyout to match the scrollbars.
* @param {!{x:number,y:number}} xyRatio Contains a y property which is a float
* between 0 and 1 specifying the degree of scrolling and a
* similar x property.
* @protected
*/
Blockly.Flyout.prototype.setMetrics_;
/**
* Lay out the blocks in the flyout.
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
* @param {!Array.<number>} gaps The visible gaps between blocks.
* @protected
*/
Blockly.Flyout.prototype.layout_;
/**
* Scroll the flyout.
* @param {!Event} e Mouse wheel scroll event.
* @protected
*/
Blockly.Flyout.prototype.wheel_;
/**
* Compute height of flyout. Position mat under each block.
* For RTL: Lay out the blocks right-aligned.
* @return {void}
* @protected
*/
Blockly.Flyout.prototype.reflowInternal_;
+9 -7
View File
@@ -23,11 +23,13 @@ goog.require('Blockly.utils.dom');
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to place this
* button.
* @param {!Blockly.WorkspaceSvg} targetWorkspace The flyout's target workspace.
* @param {!Element} xml The XML specifying the label/button.
* @param {!Blockly.utils.toolbox.Button|!Blockly.utils.toolbox.Label} json
* The JSON specifying the label/button.
* @param {boolean} isLabel Whether this button should be styled as a label.
* @constructor
* @package
*/
Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
Blockly.FlyoutButton = function(workspace, targetWorkspace, json, isLabel) {
// Labels behave the same as buttons, but are styled differently.
/**
@@ -46,7 +48,7 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
* @type {string}
* @private
*/
this.text_ = xml.getAttribute('text');
this.text_ = json['text'];
/**
* @type {!Blockly.utils.Coordinate}
@@ -66,16 +68,16 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
* @type {string}
* @private
*/
this.callbackKey_ = xml.getAttribute('callbackKey') ||
this.callbackKey_ = json['callbackKey'] ||
/* Check the lower case version too to satisfy IE */
xml.getAttribute('callbackkey');
json['callbackkey'];
/**
* If specified, a CSS class to add to this button.
* @type {?string}
* @private
*/
this.cssClass_ = xml.getAttribute('web-class') || null;
this.cssClass_ = json['web-class'] || null;
/**
* Mouse up event data.
@@ -166,7 +168,7 @@ Blockly.FlyoutButton.prototype.createDom = function() {
var fontMetrics = Blockly.utils.dom.measureFontMetrics(text, fontSize,
fontWeight, fontFamily);
this.height = fontMetrics.height;
if (!this.isLabel_) {
this.width += 2 * Blockly.FlyoutButton.MARGIN_X;
this.height += 2 * Blockly.FlyoutButton.MARGIN_Y;
+2 -2
View File
@@ -37,7 +37,7 @@ Blockly.FlyoutDragger = function(flyout) {
* @type {!Blockly.Scrollbar}
* @private
*/
this.scrollbar_ = flyout.scrollbar_;
this.scrollbar_ = flyout.scrollbar;
/**
* Whether the flyout scrolls horizontally. If false, the flyout scrolls
@@ -45,7 +45,7 @@ Blockly.FlyoutDragger = function(flyout) {
* @type {boolean}
* @private
*/
this.horizontalLayout_ = flyout.horizontalLayout_;
this.horizontalLayout_ = flyout.horizontalLayout;
};
Blockly.utils.object.inherits(Blockly.FlyoutDragger, Blockly.WorkspaceDragger);
+26 -28
View File
@@ -20,6 +20,8 @@ goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.WidgetDiv');
goog.requireType('Blockly.utils.Metrics');
/**
* Class for a flyout.
@@ -29,17 +31,9 @@ goog.require('Blockly.WidgetDiv');
* @constructor
*/
Blockly.HorizontalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = /** @type {function():!Object} */ (
this.getMetrics_.bind(this));
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.HorizontalFlyout.superClass_.constructor.call(this, workspaceOptions);
/**
* Flyout should be laid out horizontally.
* @type {boolean}
* @private
*/
this.horizontalLayout_ = true;
this.horizontalLayout = true;
};
Blockly.utils.object.inherits(Blockly.HorizontalFlyout, Blockly.Flyout);
@@ -56,8 +50,9 @@ Blockly.utils.object.inherits(Blockly.HorizontalFlyout, Blockly.Flyout);
* .viewLeft: Offset of the left edge of visible rectangle from parent,
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
* .absoluteLeft: Left-edge of view.
* @return {Object} Contains size and position metrics of the flyout.
* @private
* @return {Blockly.utils.Metrics} Contains size and position metrics of the
* flyout.
* @protected
*/
Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
if (!this.isVisible()) {
@@ -84,14 +79,16 @@ Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
var metrics = {
viewHeight: viewHeight,
viewWidth: viewWidth,
contentHeight: (optionBox.height + 2 * this.MARGIN) * this.workspace_.scale,
contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
viewTop: -this.workspace_.scrollY,
viewLeft: -this.workspace_.scrollX,
contentTop: 0,
contentLeft: 0,
viewHeight: viewHeight,
viewWidth: viewWidth,
viewTop: -this.workspace_.scrollY,
viewLeft: -this.workspace_.scrollX,
absoluteTop: absoluteTop,
absoluteLeft: absoluteLeft
};
@@ -100,10 +97,10 @@ Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
/**
* Sets the translation of the flyout to match the scrollbars.
* @param {!Object} xyRatio Contains a y property which is a float
* @param {!{x:number,y:number}} xyRatio Contains a y property which is a float
* between 0 and 1 specifying the degree of scrolling and a
* similar x property.
* @private
* @protected
*/
Blockly.HorizontalFlyout.prototype.setMetrics_ = function(xyRatio) {
var metrics = this.getMetrics_();
@@ -127,7 +124,7 @@ Blockly.HorizontalFlyout.prototype.position = function() {
if (!this.isVisible()) {
return;
}
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
var targetWorkspaceMetrics = this.targetWorkspace.getMetrics();
if (!targetWorkspaceMetrics) {
// Hidden components will return null.
return;
@@ -142,7 +139,7 @@ Blockly.HorizontalFlyout.prototype.position = function() {
// X is always 0 since this is a horizontal flyout.
var x = 0;
// If this flyout is the toolbox flyout.
if (this.targetWorkspace_.toolboxPosition == this.toolboxPosition_) {
if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) {
// If there is a toolbox.
if (targetWorkspaceMetrics.toolboxHeight) {
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
@@ -220,13 +217,13 @@ Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width,
* Scroll the flyout to the top.
*/
Blockly.HorizontalFlyout.prototype.scrollToStart = function() {
this.scrollbar_.set(this.RTL ? Infinity : 0);
this.scrollbar.set(this.RTL ? Infinity : 0);
};
/**
* Scroll the flyout.
* @param {!Event} e Mouse wheel scroll event.
* @private
* @protected
*/
Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
var scrollDelta = Blockly.utils.getScrollDeltaPixels(e);
@@ -238,9 +235,10 @@ Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
var limit = metrics.contentWidth - metrics.viewWidth;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.scrollbar_.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv.
this.scrollbar.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv.
Blockly.WidgetDiv.hide();
Blockly.DropDownDiv.hideWithoutAnimation();
}
// Don't scroll the page.
@@ -253,10 +251,10 @@ Blockly.HorizontalFlyout.prototype.wheel_ = function(e) {
* Lay out the blocks in the flyout.
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
* @param {!Array.<number>} gaps The visible gaps between blocks.
* @private
* @protected
*/
Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
this.workspace_.scale = this.targetWorkspace_.scale;
this.workspace_.scale = this.targetWorkspace.scale;
var margin = this.MARGIN;
var cursorX = margin + this.tabWidth_;
var cursorY = margin;
@@ -350,10 +348,10 @@ Blockly.HorizontalFlyout.prototype.getClientRect = function() {
/**
* Compute height of flyout. Position mat under each block.
* For RTL: Lay out the blocks right-aligned.
* @private
* @protected
*/
Blockly.HorizontalFlyout.prototype.reflowInternal_ = function() {
this.workspace_.scale = this.targetWorkspace_.scale;
this.workspace_.scale = this.targetWorkspace.scale;
var flyoutHeight = 0;
var blocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; (block = blocks[i]); i++) {
+24 -28
View File
@@ -21,6 +21,8 @@ goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.WidgetDiv');
goog.requireType('Blockly.utils.Metrics');
/**
* Class for a flyout.
@@ -30,17 +32,7 @@ goog.require('Blockly.WidgetDiv');
* @constructor
*/
Blockly.VerticalFlyout = function(workspaceOptions) {
workspaceOptions.getMetrics = /** @type {function():!Object} */ (
this.getMetrics_.bind(this));
workspaceOptions.setMetrics = this.setMetrics_.bind(this);
Blockly.VerticalFlyout.superClass_.constructor.call(this, workspaceOptions);
/**
* Flyout should be laid out vertically.
* @type {boolean}
* @private
*/
this.horizontalLayout_ = false;
};
Blockly.utils.object.inherits(Blockly.VerticalFlyout, Blockly.Flyout);
@@ -57,8 +49,9 @@ Blockly.utils.object.inherits(Blockly.VerticalFlyout, Blockly.Flyout);
* .viewLeft: Offset of the left edge of visible rectangle from parent,
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
* .absoluteLeft: Left-edge of view.
* @return {Object} Contains size and position metrics of the flyout.
* @private
* @return {Blockly.utils.Metrics} Contains size and position metrics of the
* flyout.
* @protected
*/
Blockly.VerticalFlyout.prototype.getMetrics_ = function() {
if (!this.isVisible()) {
@@ -84,14 +77,16 @@ Blockly.VerticalFlyout.prototype.getMetrics_ = function() {
}
var metrics = {
viewHeight: viewHeight,
viewWidth: viewWidth,
contentHeight: optionBox.height * this.workspace_.scale + 2 * this.MARGIN,
contentWidth: optionBox.width * this.workspace_.scale + 2 * this.MARGIN,
viewTop: -this.workspace_.scrollY + optionBox.y,
viewLeft: -this.workspace_.scrollX,
contentTop: optionBox.y,
contentLeft: optionBox.x,
viewHeight: viewHeight,
viewWidth: viewWidth,
viewTop: -this.workspace_.scrollY + optionBox.y,
viewLeft: -this.workspace_.scrollX,
absoluteTop: absoluteTop,
absoluteLeft: absoluteLeft
};
@@ -100,10 +95,10 @@ Blockly.VerticalFlyout.prototype.getMetrics_ = function() {
/**
* Sets the translation of the flyout to match the scrollbars.
* @param {!Object} xyRatio Contains a y property which is a float
* @param {!{x:number,y:number}} xyRatio Contains a y property which is a float
* between 0 and 1 specifying the degree of scrolling and a
* similar x property.
* @private
* @protected
*/
Blockly.VerticalFlyout.prototype.setMetrics_ = function(xyRatio) {
var metrics = this.getMetrics_();
@@ -125,7 +120,7 @@ Blockly.VerticalFlyout.prototype.position = function() {
if (!this.isVisible()) {
return;
}
var targetWorkspaceMetrics = this.targetWorkspace_.getMetrics();
var targetWorkspaceMetrics = this.targetWorkspace.getMetrics();
if (!targetWorkspaceMetrics) {
// Hidden components will return null.
return;
@@ -140,7 +135,7 @@ Blockly.VerticalFlyout.prototype.position = function() {
// Y is always 0 since this is a vertical flyout.
var y = 0;
// If this flyout is the toolbox flyout.
if (this.targetWorkspace_.toolboxPosition == this.toolboxPosition_) {
if (this.targetWorkspace.toolboxPosition == this.toolboxPosition_) {
// If there is a category toolbox.
if (targetWorkspaceMetrics.toolboxWidth) {
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_LEFT) {
@@ -208,13 +203,13 @@ Blockly.VerticalFlyout.prototype.setBackgroundPath_ = function(width, height) {
* Scroll the flyout to the top.
*/
Blockly.VerticalFlyout.prototype.scrollToStart = function() {
this.scrollbar_.set(0);
this.scrollbar.set(0);
};
/**
* Scroll the flyout.
* @param {!Event} e Mouse wheel scroll event.
* @private
* @protected
*/
Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
var scrollDelta = Blockly.utils.getScrollDeltaPixels(e);
@@ -225,9 +220,10 @@ Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
var limit = metrics.contentHeight - metrics.viewHeight;
pos = Math.min(pos, limit);
pos = Math.max(pos, 0);
this.scrollbar_.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv.
this.scrollbar.set(pos);
// When the flyout moves from a wheel event, hide WidgetDiv and DropDownDiv.
Blockly.WidgetDiv.hide();
Blockly.DropDownDiv.hideWithoutAnimation();
}
// Don't scroll the page.
@@ -240,10 +236,10 @@ Blockly.VerticalFlyout.prototype.wheel_ = function(e) {
* Lay out the blocks in the flyout.
* @param {!Array.<!Object>} contents The blocks and buttons to lay out.
* @param {!Array.<number>} gaps The visible gaps between blocks.
* @private
* @protected
*/
Blockly.VerticalFlyout.prototype.layout_ = function(contents, gaps) {
this.workspace_.scale = this.targetWorkspace_.scale;
this.workspace_.scale = this.targetWorkspace.scale;
var margin = this.MARGIN;
var cursorX = this.RTL ? margin : margin + this.tabWidth_;
var cursorY = margin;
@@ -329,10 +325,10 @@ Blockly.VerticalFlyout.prototype.getClientRect = function() {
/**
* Compute width of flyout. Position mat under each block.
* For RTL: Lay out the blocks and buttons to be right-aligned.
* @private
* @protected
*/
Blockly.VerticalFlyout.prototype.reflowInternal_ = function() {
this.workspace_.scale = this.targetWorkspace_.scale;
this.workspace_.scale = this.targetWorkspace.scale;
var flyoutWidth = 0;
var blocks = this.workspace_.getTopBlocks(false);
for (var i = 0, block; (block = blocks[i]); i++) {
+4
View File
@@ -172,6 +172,10 @@ Blockly.Generator.prototype.blockToCode = function(block, opt_thisOnly) {
// Skip past this block if it is disabled.
return opt_thisOnly ? '' : this.blockToCode(block.getNextBlock());
}
if (block.isInsertionMarker()) {
// Skip past insertion markers.
return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]);
}
var func = this[block.type];
if (typeof func != 'function') {
+14 -2
View File
@@ -314,7 +314,7 @@ Blockly.Gesture.prototype.updateIsDraggingFromFlyout_ = function() {
}
if (!this.flyout_.isScrollable() ||
this.flyout_.isDragTowardWorkspace(this.currentDragDeltaXY_)) {
this.startWorkspace_ = this.flyout_.targetWorkspace_;
this.startWorkspace_ = this.flyout_.targetWorkspace;
this.startWorkspace_.updateScreenCalculationsIfScrolled();
// Start the event group now, so that the same event group is used for block
// creation and block dragging.
@@ -489,7 +489,7 @@ Blockly.Gesture.prototype.doStart = function(e) {
e.shiftKey &&
this.targetBlock_.workspace.keyboardAccessibilityMode) {
this.creatorWorkspace_.getCursor().setCurNode(
Blockly.navigation.getTopNode(this.targetBlock_));
Blockly.ASTNode.createTopNode(this.targetBlock_));
} else {
this.targetBlock_.select();
}
@@ -656,6 +656,17 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
}
};
/**
* Fires a workspace click event.
* @param {!Blockly.WorkspaceSvg} ws The workspace that a user clicks on.
* @private
*/
Blockly.Gesture.prototype.fireWorkspaceClick_ = function(ws) {
var clickEvent = new Blockly.Events.Ui(null, 'workspaceClick', null, null);
clickEvent.workspaceId = ws.id;
Blockly.Events.fire(clickEvent);
};
/**
* Handle a mousedown/touchstart event on a flyout.
* @param {!Event} e A mouse down or touch start event.
@@ -763,6 +774,7 @@ Blockly.Gesture.prototype.doWorkspaceClick_ = function(e) {
} else if (Blockly.selected) {
Blockly.selected.unselect();
}
this.fireWorkspaceClick_(ws);
};
/* End functions defining what actions to take to execute clicks on each type
+8
View File
@@ -22,6 +22,7 @@ goog.require('Blockly.utils.Size');
* Class for an icon.
* @param {Blockly.BlockSvg} block The block associated with this icon.
* @constructor
* @abstract
*/
Blockly.Icon = function(block) {
/**
@@ -181,3 +182,10 @@ Blockly.Icon.prototype.getCorrectedSize = function() {
return new Blockly.utils.Size(
Blockly.Icon.prototype.SIZE, Blockly.Icon.prototype.SIZE - 2);
};
/**
* Draw the icon.
* @param {!Element} group The icon group.
* @protected
*/
Blockly.Icon.prototype.drawIcon_;
+3 -1
View File
@@ -29,6 +29,8 @@ goog.require('Blockly.utils.userAgent');
goog.require('Blockly.WorkspaceDragSurfaceSvg');
goog.require('Blockly.WorkspaceSvg');
goog.requireType('Blockly.utils.Metrics');
/**
* Inject a Blockly editor into the specified container element (usually a div).
@@ -364,7 +366,7 @@ Blockly.init_ = function(mainWorkspace) {
} else if (flyout) {
// Build a fixed flyout with the root blocks.
flyout.init(mainWorkspace);
flyout.show(options.languageTree.childNodes);
flyout.show(options.languageTree);
flyout.scrollToStart();
}
}
+26 -15
View File
@@ -91,35 +91,39 @@ Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) {
if (index < 0 || index > this.fieldRow.length) {
throw Error('index ' + index + ' out of bounds.');
}
// Falsy field values don't generate a field, unless the field is an empty
// string and named.
if (!field && !(field == '' && opt_name)) {
return index;
}
// Generate a FieldLabel when given a plain text field.
if (typeof field == 'string') {
field = new Blockly.FieldLabel(/** @type {string} */ (field));
}
field.setSourceBlock(this.sourceBlock_);
if (this.sourceBlock_.rendered) {
field.init();
}
field.name = opt_name;
field.setVisible(this.isVisible());
if (field.prefixField) {
var fieldDropdown = /** @type {Blockly.FieldDropdown} */ (field);
if (fieldDropdown.prefixField) {
// Add any prefix.
index = this.insertFieldAt(index, field.prefixField);
index = this.insertFieldAt(index, fieldDropdown.prefixField);
}
// Add the field to the field row.
this.fieldRow.splice(index, 0, field);
++index;
if (field.suffixField) {
if (fieldDropdown.suffixField) {
// Add any suffix.
index = this.insertFieldAt(index, field.suffixField);
index = this.insertFieldAt(index, fieldDropdown.suffixField);
}
if (this.sourceBlock_.rendered) {
this.sourceBlock_ = /** @type {!Blockly.BlockSvg} */ (this.sourceBlock_);
this.sourceBlock_.render();
// Adding a field will cause the block to change shape.
this.sourceBlock_.bumpNeighbours();
@@ -130,22 +134,30 @@ Blockly.Input.prototype.insertFieldAt = function(index, field, opt_name) {
/**
* Remove a field from this input.
* @param {string} name The name of the field.
* @throws {Error} if the field is not present.
* @param {boolean=} opt_quiet True to prevent an error if field is not present.
* @return {boolean} True if operation succeeds, false if field is not present
* and opt_quiet is true.
* @throws {Error} if the field is not present and opt_quiet is false.
*/
Blockly.Input.prototype.removeField = function(name) {
Blockly.Input.prototype.removeField = function(name, opt_quiet) {
for (var i = 0, field; (field = this.fieldRow[i]); i++) {
if (field.name === name) {
field.dispose();
this.fieldRow.splice(i, 1);
if (this.sourceBlock_.rendered) {
this.sourceBlock_ = /** @type {!Blockly.BlockSvg} */ (this.sourceBlock_);
this.sourceBlock_.render();
// Removing a field will cause the block to change shape.
this.sourceBlock_.bumpNeighbours();
}
return;
return true;
}
}
throw Error('Field "%s" not found.', name);
if (opt_quiet) {
return false;
} else {
throw Error('Field "' + name + '" not found.');
}
};
/**
@@ -160,7 +172,7 @@ Blockly.Input.prototype.isVisible = function() {
* Sets whether this input is visible or not.
* Should only be used to collapse/uncollapse a block.
* @param {boolean} visible True if visible.
* @return {!Array.<!Blockly.Block>} List of blocks to render.
* @return {!Array.<!Blockly.BlockSvg>} List of blocks to render.
* @package
*/
Blockly.Input.prototype.setVisible = function(visible) {
@@ -173,11 +185,12 @@ Blockly.Input.prototype.setVisible = function(visible) {
}
this.visible_ = visible;
var display = visible ? 'block' : 'none';
for (var y = 0, field; (field = this.fieldRow[y]); y++) {
field.setVisible(visible);
}
if (this.connection) {
this.connection =
/** @type {!Blockly.RenderedConnection} */ (this.connection);
// Has a connection.
if (visible) {
renderList = this.connection.startTrackingAll();
@@ -186,10 +199,7 @@ Blockly.Input.prototype.setVisible = function(visible) {
}
var child = this.connection.targetBlock();
if (child) {
child.getSvgRoot().style.display = display;
if (!visible) {
child.rendered = false;
}
child.getSvgRoot().style.display = visible ? 'block' : 'none';
}
}
return renderList;
@@ -228,6 +238,7 @@ Blockly.Input.prototype.setCheck = function(check) {
Blockly.Input.prototype.setAlign = function(align) {
this.align = align;
if (this.sourceBlock_.rendered) {
this.sourceBlock_ = /** @type {!Blockly.BlockSvg} */ (this.sourceBlock_);
this.sourceBlock_.render();
}
return this;
+23 -12
View File
@@ -160,6 +160,15 @@ Blockly.InsertionMarkerManager.prototype.dispose = function() {
}
};
/**
* Update the available connections for the top block. These connections can
* change if a block is unplugged and the stack is healed.
* @package
*/
Blockly.InsertionMarkerManager.prototype.updateAvailableConnections = function() {
this.availableConnections_ = this.initAvailableConnections_();
};
/**
* Return whether the block would be deleted if dropped immediately, based on
* information from the most recent move event.
@@ -253,23 +262,25 @@ Blockly.InsertionMarkerManager.prototype.createMarkerBlock_ = function(sourceBlo
result.domToMutation(oldMutationDom);
}
}
result.setCollapsed(sourceBlock.isCollapsed());
result.setInputsInline(sourceBlock.getInputsInline());
// Copy visible field values from the other block. These values may impact
// the rendered size of the insertion marker. Note that we do not care
// about child blocks here.
// Copy field values from the other block. These values may impact the
// rendered size of the insertion marker. Note that we do not care about
// child blocks here.
for (var i = 0; i < sourceBlock.inputList.length; i++) {
var sourceInput = sourceBlock.inputList[i];
if (sourceInput.isVisible()) {
var resultInput = result.inputList[i];
for (var j = 0; j < sourceInput.fieldRow.length; j++) {
var sourceField = sourceInput.fieldRow[j];
var resultField = resultInput.fieldRow[j];
resultField.setValue(sourceField.getValue());
}
if (sourceInput.name == Blockly.Block.COLLAPSED_INPUT_NAME) {
continue; // Ignore the collapsed input.
}
var resultInput = result.inputList[i];
for (var j = 0; j < sourceInput.fieldRow.length; j++) {
var sourceField = sourceInput.fieldRow[j];
var resultField = resultInput.fieldRow[j];
resultField.setValue(sourceField.getValue());
}
}
result.setCollapsed(sourceBlock.isCollapsed());
result.setInputsInline(sourceBlock.getInputsInline());
result.initSvg();
result.getSvgRoot().setAttribute('visibility', 'hidden');
} finally {
+72
View File
@@ -0,0 +1,72 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview AST Node and keyboard navigation interfaces.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.IASTNodeLocation');
goog.provide('Blockly.IASTNodeLocationSvg');
goog.provide('Blockly.IASTNodeLocationWithBlock');
goog.provide('Blockly.IBlocklyActionable');
/**
* An AST node location interface.
* @interface
*/
Blockly.IASTNodeLocation = function() {};
/**
* An AST node location SVG interface.
* @interface
* @extends {Blockly.IASTNodeLocation}
*/
Blockly.IASTNodeLocationSvg = function() {};
/**
* Add the marker svg to this node's svg group.
* @param {SVGElement} markerSvg The svg root of the marker to be added to the
* svg group.
*/
Blockly.IASTNodeLocationSvg.prototype.setMarkerSvg;
/**
* Add the cursor svg to this node's svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
* svg group.
*/
Blockly.IASTNodeLocationSvg.prototype.setCursorSvg;
/**
* An AST node location that has an associated block.
* @interface
* @extends {Blockly.IASTNodeLocation}
*/
Blockly.IASTNodeLocationWithBlock = function() {};
/**
* Get the source block associated with this node.
* @return {Blockly.Block} The source block.
*/
Blockly.IASTNodeLocationWithBlock.prototype.getSourceBlock;
/**
* An interface for an object that handles Blockly actions when keyboard
* navigation is enabled.
* @interface
*/
Blockly.IBlocklyActionable = function() {};
/**
* Handles the given action.
* @param {!Blockly.Action} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.IBlocklyActionable.prototype.onBlocklyAction;
+31
View File
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for a bounded element.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.IBoundedElement');
goog.requireType('Blockly.utils.Rect');
/**
* A bounded element interface.
* @interface
*/
Blockly.IBoundedElement = function() {};
/**
* Returns the coordinates of a bounded element describing the dimensions of the
* element.
* Coordinate system: workspace coordinates.
* @return {!Blockly.utils.Rect} Object with coordinates of the bounded element.
*/
Blockly.IBoundedElement.prototype.getBoundingRectangle;
+40
View File
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for an object that is copyable.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.ICopyable');
goog.requireType('Blockly.ISelectable');
goog.requireType('Blockly.WorkspaceSvg');
/**
* @extends {Blockly.ISelectable}
* @interface
*/
Blockly.ICopyable = function() {};
/**
* Encode for copying.
* @return {!Blockly.ICopyable.CopyData} Copy metadata.
*/
Blockly.ICopyable.prototype.toCopyData;
/**
* Copy Metadata.
* @typedef {{
* xml:!Element,
* source:Blockly.WorkspaceSvg,
* typeCounts:?Object
* }}
*/
Blockly.ICopyable.CopyData;
+27
View File
@@ -0,0 +1,27 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for an object that is deletable.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.IDeletable');
/**
* The interface for an object that can be deleted.
* @interface
*/
Blockly.IDeletable = function() {};
/**
* Get whether this object is deletable or not.
* @return {boolean} True if deletable.
*/
Blockly.IDeletable.prototype.isDeletable;
+28
View File
@@ -0,0 +1,28 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for a component that can delete a block that is
* dropped on top of it.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.IDeleteArea');
/**
* Interface for a component that can delete a block that is dropped on top of it.
* @interface
*/
Blockly.IDeleteArea = function() {};
/**
* Return the deletion rectangle.
* @return {Blockly.utils.Rect} Rectangle in which to delete.
*/
Blockly.IDeleteArea.prototype.getClientRect;
+27
View File
@@ -0,0 +1,27 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for an object that is movable.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.IMovable');
/**
* The interface for an object that is movable.
* @interface
*/
Blockly.IMovable = function() {};
/**
* Get whether this is movable or not.
* @return {boolean} True if movable.
*/
Blockly.IMovable.prototype.isMovable;
+22
View File
@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for a Blockly component that can be registered.
* (Ex. Toolbox, Fields, Renderers)
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.IRegistrable');
/**
* The interface for a Blockly component that can be registered.
* @interface
* */
Blockly.IRegistrable = function() {};
+43
View File
@@ -0,0 +1,43 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for an object that is selectable.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.ISelectable');
goog.requireType('Blockly.IDeletable');
goog.requireType('Blockly.IMovable');
/**
* The interface for an object that is selectable.
* @extends {Blockly.IDeletable}
* @extends {Blockly.IMovable}
* @interface
*/
Blockly.ISelectable = function() {};
/**
* @type {string}
*/
Blockly.ISelectable.prototype.id;
/**
* Select this. Highlight it visually.
* @return {void}
*/
Blockly.ISelectable.prototype.select;
/**
* Unselect this. Unhighlight it visually.
* @return {void}
*/
Blockly.ISelectable.prototype.unselect;
+33
View File
@@ -0,0 +1,33 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for an object that a style can be added to.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.IStyleable');
/**
* Interface for an object that a style can be added to.
* @interface
*/
Blockly.IStyleable = function() {};
/**
* Adds a style on the toolbox. Usually used to change the cursor.
* @param {string} style The name of the class to add.
*/
Blockly.IStyleable.prototype.addStyle;
/**
* Removes a style from the toolbox. Usually used to change the cursor.
* @param {string} style The name of the class to remove.
*/
Blockly.IStyleable.prototype.removeStyle;
+99
View File
@@ -0,0 +1,99 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The interface for a toolbox.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.IToolbox');
goog.requireType('Blockly.IRegistrable');
/**
* Interface for a toolbox.
* @extends {Blockly.IRegistrable}
* @interface
*/
Blockly.IToolbox = function() {};
/**
* Initializes the toolbox.
* @return {void}
*/
Blockly.IToolbox.prototype.init;
/**
* Fill the toolbox with categories and blocks.
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef Array holding objects
* containing information on the contents of the toolbox.
*/
Blockly.IToolbox.prototype.render;
/**
* Dispose of this toolbox.
* @return {void}
*/
Blockly.IToolbox.prototype.dispose;
/**
* Get the width of the toolbox.
* @return {number} The width of the toolbox.
*/
Blockly.IToolbox.prototype.getWidth;
/**
* Get the height of the toolbox.
* @return {number} The width of the toolbox.
*/
Blockly.IToolbox.prototype.getHeight;
/**
* Get the toolbox flyout.
* @return {Blockly.Flyout} The toolbox flyout.
*/
Blockly.IToolbox.prototype.getFlyout;
/**
* Move the toolbox to the edge.
* @return {void}
*/
Blockly.IToolbox.prototype.position;
/**
* Unhighlight any previously specified option.
* @return {void}
*/
Blockly.IToolbox.prototype.clearSelection;
/**
* Updates the category colours and background colour of selected categories.
* @return {void}
*/
Blockly.IToolbox.prototype.refreshTheme;
/**
* Update the flyout's contents without closing it. Should be used in response
* to a change in one of the dynamic categories, such as variables or
* procedures.
* @return {void}
*/
Blockly.IToolbox.prototype.refreshSelection;
/**
* Toggles the visibility of the toolbox.
* @param {boolean} isVisible True if the toolbox should be visible.
*/
Blockly.IToolbox.prototype.setVisible;
/**
* Select the first toolbox category if no category is selected.
* @return {void}
*/
Blockly.IToolbox.prototype.selectFirstCategory;
+83 -32
View File
@@ -14,6 +14,9 @@ goog.provide('Blockly.ASTNode');
goog.require('Blockly.utils.Coordinate');
goog.requireType('Blockly.IASTNodeLocation');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
/**
* Class for an AST node.
@@ -21,9 +24,8 @@ goog.require('Blockly.utils.Coordinate');
* creating a node directly.
* @param {string} type The type of the location.
* Must be in Blockly.ASTNode.types.
* @param {!(Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace)}
* location The position in the AST.
* @param {!Object=} opt_params Optional dictionary of options.
* @param {!Blockly.IASTNodeLocation} location The position in the AST.
* @param {!Blockly.ASTNode.Params=} opt_params Optional dictionary of options.
* @constructor
*/
Blockly.ASTNode = function(type, location, opt_params) {
@@ -48,14 +50,28 @@ Blockly.ASTNode = function(type, location, opt_params) {
/**
* The location of the AST node.
* @type {!(Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace)}
* @type {!Blockly.IASTNodeLocation}
* @private
*/
this.location_ = location;
/**
* The coordinate on the workspace.
* @type {Blockly.utils.Coordinate}
* @private
*/
this.wsCoordinate_ = null;
this.processParams_(opt_params || null);
};
/**
* @typedef {{
* wsCoordinate: Blockly.utils.Coordinate
* }}
*/
Blockly.ASTNode.Params;
/**
* Object holding different types for an AST node.
* @enum {string}
@@ -199,9 +215,27 @@ Blockly.ASTNode.createWorkspaceNode = function(workspace, wsCoordinate) {
Blockly.ASTNode.types.WORKSPACE, workspace, params);
};
/**
* Creates an AST node for the top position on a block.
* This is either an output connection, previous connection, or block.
* @param {!Blockly.Block} block The block to find the top most AST node on.
* @return {Blockly.ASTNode} The AST node holding the top most position on the
* block.
*/
Blockly.ASTNode.createTopNode = function(block) {
var astNode;
var topConnection = block.previousConnection || block.outputConnection;
if (topConnection) {
astNode = Blockly.ASTNode.createConnectionNode(topConnection);
} else {
astNode = Blockly.ASTNode.createBlockNode(block);
}
return astNode;
};
/**
* Parse the optional parameters.
* @param {Object} params The user specified parameters.
* @param {?Blockly.ASTNode.Params} params The user specified parameters.
* @private
*/
Blockly.ASTNode.prototype.processParams_ = function(params) {
@@ -217,8 +251,8 @@ Blockly.ASTNode.prototype.processParams_ = function(params) {
* Gets the value pointed to by this node.
* It is the callers responsibility to check the node type to figure out what
* type of object they get back from this.
* @return {!(Blockly.Field|Blockly.Connection|Blockly.Block|Blockly.Workspace)}
* The current field, connection, workspace, or block the cursor is on.
* @return {!Blockly.IASTNodeLocation} The current field, connection, workspace, or
* block the cursor is on.
*/
Blockly.ASTNode.prototype.getLocation = function() {
return this.location_;
@@ -261,7 +295,8 @@ Blockly.ASTNode.prototype.isConnection = function() {
* @private
*/
Blockly.ASTNode.prototype.findNextForInput_ = function() {
var parentInput = this.location_.getParentInput();
var location = /** @type {!Blockly.Connection} */ (this.location_);
var parentInput = location.getParentInput();
var block = parentInput.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
for (var i = curIdx + 1, input; (input = block.inputList[i]); i++) {
@@ -317,11 +352,12 @@ Blockly.ASTNode.prototype.findNextForField_ = function() {
* @private
*/
Blockly.ASTNode.prototype.findPrevForInput_ = function() {
var location = this.location_.getParentInput();
var block = location.getSourceBlock();
var curIdx = block.inputList.indexOf(location);
var location = /** @type {!Blockly.Connection} */ (this.location_);
var parentInput = location.getParentInput();
var block = parentInput.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
for (var i = curIdx, input; (input = block.inputList[i]); i--) {
if (input.connection && input !== location) {
if (input.connection && input !== parentInput) {
return Blockly.ASTNode.createInputNode(input);
}
var fieldRow = input.fieldRow;
@@ -376,7 +412,8 @@ Blockly.ASTNode.prototype.findPrevForField_ = function() {
Blockly.ASTNode.prototype.navigateBetweenStacks_ = function(forward) {
var curLocation = this.getLocation();
if (!(curLocation instanceof Blockly.Block)) {
curLocation = curLocation.getSourceBlock();
curLocation = /** @type {!Blockly.IASTNodeLocationWithBlock} */ (
curLocation).getSourceBlock();
}
if (!curLocation || !curLocation.workspace) {
return null;
@@ -481,7 +518,8 @@ Blockly.ASTNode.prototype.getSourceBlock = function() {
} else if (this.getType() === Blockly.ASTNode.types.WORKSPACE) {
return null;
} else {
return this.getLocation().getSourceBlock();
return /** @type {Blockly.IASTNodeLocationWithBlock} */ (
this.getLocation()).getSourceBlock();
}
};
@@ -496,7 +534,8 @@ Blockly.ASTNode.prototype.next = function() {
return this.navigateBetweenStacks_(true);
case Blockly.ASTNode.types.OUTPUT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.FIELD:
return this.findNextForField_();
@@ -505,14 +544,17 @@ Blockly.ASTNode.prototype.next = function() {
return this.findNextForInput_();
case Blockly.ASTNode.types.BLOCK:
var nextConnection = this.location_.nextConnection;
var block = /** @type {!Blockly.Block} */ (this.location_);
var nextConnection = block.nextConnection;
return Blockly.ASTNode.createConnectionNode(nextConnection);
case Blockly.ASTNode.types.PREVIOUS:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.NEXT:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
@@ -528,7 +570,8 @@ Blockly.ASTNode.prototype.next = function() {
Blockly.ASTNode.prototype.in = function() {
switch (this.type_) {
case Blockly.ASTNode.types.WORKSPACE:
var topBlocks = this.location_.getTopBlocks(true);
var workspace = /** @type {!Blockly.Workspace} */ (this.location_);
var topBlocks = workspace.getTopBlocks(true);
if (topBlocks.length > 0) {
return Blockly.ASTNode.createStackNode(topBlocks[0]);
}
@@ -543,7 +586,8 @@ Blockly.ASTNode.prototype.in = function() {
return this.findFirstFieldOrInput_(block);
case Blockly.ASTNode.types.INPUT:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
@@ -571,19 +615,21 @@ Blockly.ASTNode.prototype.prev = function() {
return this.findPrevForInput_();
case Blockly.ASTNode.types.BLOCK:
var block = this.location_;
var block = /** @type {!Blockly.Block} */ (this.location_);
var topConnection = block.previousConnection || block.outputConnection;
return Blockly.ASTNode.createConnectionNode(topConnection);
case Blockly.ASTNode.types.PREVIOUS:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
if (targetConnection && !targetConnection.getParentInput()) {
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
break;
case Blockly.ASTNode.types.NEXT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
}
return null;
@@ -598,35 +644,40 @@ Blockly.ASTNode.prototype.prev = function() {
Blockly.ASTNode.prototype.out = function() {
switch (this.type_) {
case Blockly.ASTNode.types.STACK:
var blockPos = this.location_.getRelativeToSurfaceXY();
var block = /** @type {!Blockly.Block} */ (this.location_);
var blockPos = block.getRelativeToSurfaceXY();
// TODO: Make sure this is in the bounds of the workspace.
var wsCoordinate = new Blockly.utils.Coordinate(
blockPos.x, blockPos.y + Blockly.ASTNode.DEFAULT_OFFSET_Y);
return Blockly.ASTNode.createWorkspaceNode(
this.location_.workspace, wsCoordinate);
return Blockly.ASTNode.createWorkspaceNode(block.workspace, wsCoordinate);
case Blockly.ASTNode.types.OUTPUT:
var target = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var target = connection.targetConnection;
if (target) {
return Blockly.ASTNode.createConnectionNode(target);
}
return Blockly.ASTNode.createStackNode(this.location_.getSourceBlock());
return Blockly.ASTNode.createStackNode(connection.getSourceBlock());
case Blockly.ASTNode.types.FIELD:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var field = /** @type {!Blockly.Field} */ (this.location_);
return Blockly.ASTNode.createBlockNode(field.getSourceBlock());
case Blockly.ASTNode.types.INPUT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.BLOCK:
var block = /** @type {!Blockly.Block} */ (this.location_);
return this.getOutAstNodeForBlock_(block);
case Blockly.ASTNode.types.PREVIOUS:
return this.getOutAstNodeForBlock_(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return this.getOutAstNodeForBlock_(connection.getSourceBlock());
case Blockly.ASTNode.types.NEXT:
return this.getOutAstNodeForBlock_(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return this.getOutAstNodeForBlock_(connection.getSourceBlock());
}
return null;
+1 -1
View File
@@ -71,7 +71,7 @@ Blockly.BasicCursor.prototype.prev = function() {
return null;
}
var newNode = this.getPreviousNode_(curNode, this.validNode_);
if (newNode) {
this.setCurNode(newNode);
}
+5 -1
View File
@@ -19,12 +19,15 @@ goog.require('Blockly.Marker');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
goog.requireType('Blockly.IBlocklyActionable');
/**
* Class for a cursor.
* A cursor controls how a user navigates the Blockly AST.
* @constructor
* @extends {Blockly.Marker}
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Cursor = function() {
Blockly.Cursor.superClass_.constructor.call(this);
@@ -144,7 +147,8 @@ Blockly.Cursor.prototype.onBlocklyAction = function(action) {
// If we are on a field give it the option to handle the action
if (this.getCurNode() &&
this.getCurNode().getType() === Blockly.ASTNode.types.FIELD &&
this.getCurNode().getLocation().onBlocklyAction(action)) {
(/** @type {!Blockly.Field} */ (this.getCurNode().getLocation()))
.onBlocklyAction(action)) {
return true;
}
switch (action.name) {
+1 -1
View File
@@ -101,7 +101,7 @@ Blockly.user.keyMap.getKeyByAction = function(action) {
/**
* Serialize the key event.
* @param {!Event} e A key up event holding the key code.
* @param {!KeyboardEvent} e A key up event holding the key code.
* @return {string} A string containing the serialized key event.
* @package
*/
-2
View File
@@ -23,7 +23,6 @@ goog.require('Blockly.navigation');
* @constructor
*/
Blockly.Marker = function() {
/**
* The colour of the marker.
* @type {?string}
@@ -119,4 +118,3 @@ Blockly.Marker.prototype.dispose = function() {
this.getDrawer().dispose();
}
};
+92 -85
View File
@@ -18,7 +18,6 @@ goog.require('Blockly.ASTNode');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.user.keyMap');
/**
* A function to call to give feedback to the user about logs, warnings, and
* errors. You can override this to customize feedback (e.g. warning sounds,
@@ -102,10 +101,19 @@ Blockly.navigation.MARKER_NAME = 'local_marker_1';
/**
* Get the local marker.
* @return {!Blockly.Marker} The local marker for the main workspace.
* @return {Blockly.Marker} The local marker for the main workspace.
*/
Blockly.navigation.getMarker = function() {
return Blockly.getMainWorkspace().getMarker(Blockly.navigation.MARKER_NAME);
return Blockly.navigation.getNavigationWorkspace()
.getMarker(Blockly.navigation.MARKER_NAME);
};
/**
* Get the workspace that is being navigated.
* @return {!Blockly.WorkspaceSvg} The workspace being navigated.
*/
Blockly.navigation.getNavigationWorkspace = function() {
return /** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace());
};
/**
@@ -114,8 +122,7 @@ Blockly.navigation.getMarker = function() {
* @private
*/
Blockly.navigation.focusToolbox_ = function() {
var workspace = Blockly.getMainWorkspace();
var toolbox = workspace.getToolbox();
var toolbox = Blockly.navigation.getNavigationWorkspace().getToolbox();
if (toolbox) {
Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX;
Blockly.navigation.resetFlyout_(false /* shouldHide */);
@@ -134,9 +141,9 @@ Blockly.navigation.focusToolbox_ = function() {
Blockly.navigation.focusFlyout_ = function() {
var topBlock = null;
Blockly.navigation.currentState_ = Blockly.navigation.STATE_FLYOUT;
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
if (!Blockly.navigation.getMarker().getCurNode()) {
Blockly.navigation.markAtCursor_();
@@ -159,7 +166,7 @@ Blockly.navigation.focusFlyout_ = function() {
*/
Blockly.navigation.focusWorkspace_ = function() {
Blockly.hideChaff();
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = workspace.getCursor();
var reset = !!workspace.getToolbox();
var topBlocks = workspace.getTopBlocks(true);
@@ -167,7 +174,7 @@ Blockly.navigation.focusWorkspace_ = function() {
Blockly.navigation.resetFlyout_(reset);
Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS;
if (topBlocks.length > 0) {
cursor.setCurNode(Blockly.navigation.getTopNode(topBlocks[0]));
cursor.setCurNode(Blockly.ASTNode.createTopNode(topBlocks[0]));
} else {
// TODO: Find the center of the visible workspace.
var wsCoord = new Blockly.utils.Coordinate(100, 100);
@@ -186,14 +193,14 @@ Blockly.navigation.focusWorkspace_ = function() {
* @private
*/
Blockly.navigation.getFlyoutCursor_ = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = null;
if (workspace.rendered) {
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
cursor = flyout ? flyout.workspace_.getCursor() : null;
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
cursor = flyout ? flyout.getWorkspace().getCursor() : null;
}
return cursor;
return /** @type {Blockly.FlyoutCursor} */ (cursor);
};
/**
@@ -202,7 +209,7 @@ Blockly.navigation.getFlyoutCursor_ = function() {
* it on the workspace.
*/
Blockly.navigation.insertFromFlyout = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var flyout = workspace.getFlyout();
if (!flyout || !flyout.isVisible()) {
Blockly.navigation.warn_('Trying to insert from the flyout when the flyout does not ' +
@@ -210,7 +217,8 @@ Blockly.navigation.insertFromFlyout = function() {
return;
}
var curBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation();
var curBlock = /** @type {!Blockly.BlockSvg} */ (
Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation());
if (!curBlock.isEnabled()) {
Blockly.navigation.warn_('Can\'t insert a disabled block.');
return;
@@ -230,7 +238,7 @@ Blockly.navigation.insertFromFlyout = function() {
}
Blockly.navigation.focusWorkspace_();
workspace.getCursor().setCurNode(Blockly.navigation.getTopNode(newBlock));
workspace.getCursor().setCurNode(Blockly.ASTNode.createTopNode(newBlock));
Blockly.navigation.removeMark_();
};
@@ -243,7 +251,7 @@ Blockly.navigation.resetFlyout_ = function(shouldHide) {
if (Blockly.navigation.getFlyoutCursor_()) {
Blockly.navigation.getFlyoutCursor_().hide();
if (shouldHide) {
Blockly.getMainWorkspace().getFlyout().hide();
Blockly.navigation.getNavigationWorkspace().getFlyout().hide();
}
}
};
@@ -260,7 +268,8 @@ Blockly.navigation.resetFlyout_ = function(shouldHide) {
*/
Blockly.navigation.modifyWarn_ = function() {
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var cursorNode = Blockly.navigation.getNavigationWorkspace()
.getCursor().getCurNode();
if (!markerNode) {
Blockly.navigation.warn_('Cannot insert with no marked node.');
@@ -300,7 +309,7 @@ Blockly.navigation.modifyWarn_ = function() {
/**
* Disconnect the block from its parent and move to the position of the
* workspace node.
* @param {Blockly.Block} block The block to be moved to the workspace.
* @param {Blockly.BlockSvg} block The block to be moved to the workspace.
* @param {!Blockly.ASTNode} wsNode The workspace node holding the position the
* block will be moved to.
* @return {boolean} True if the block can be moved to the workspace,
@@ -331,7 +340,8 @@ Blockly.navigation.moveBlockToWorkspace_ = function(block, wsNode) {
*/
Blockly.navigation.modify_ = function() {
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var cursorNode = Blockly.navigation.getNavigationWorkspace()
.getCursor().getCurNode();
if (!Blockly.navigation.modifyWarn_()) {
return false;
}
@@ -343,18 +353,19 @@ Blockly.navigation.modify_ = function() {
var markerLoc = markerNode.getLocation();
if (markerNode.isConnection() && cursorNode.isConnection()) {
cursorLoc = /** @type {!Blockly.Connection} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
cursorLoc = /** @type {!Blockly.RenderedConnection} */ (cursorLoc);
markerLoc = /** @type {!Blockly.RenderedConnection} */ (markerLoc);
return Blockly.navigation.connect_(cursorLoc, markerLoc);
} else if (markerNode.isConnection() &&
(cursorType == Blockly.ASTNode.types.BLOCK ||
cursorType == Blockly.ASTNode.types.STACK)) {
cursorLoc = /** @type {!Blockly.Block} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
cursorLoc = /** @type {!Blockly.BlockSvg} */ (cursorLoc);
markerLoc = /** @type {!Blockly.RenderedConnection} */ (markerLoc);
return Blockly.navigation.insertBlock(cursorLoc, markerLoc);
} else if (markerType == Blockly.ASTNode.types.WORKSPACE) {
var block = cursorNode ? cursorNode.getSourceBlock() : null;
return Blockly.navigation.moveBlockToWorkspace_(block, markerNode);
return Blockly.navigation.moveBlockToWorkspace_(
/** @type {Blockly.BlockSvg} */ (block), markerNode);
}
Blockly.navigation.warn_('Unexpected state in Blockly.navigation.modify_.');
return false;
@@ -363,9 +374,10 @@ Blockly.navigation.modify_ = function() {
/**
* If one of the connections source blocks is a child of the other, disconnect
* the child.
* @param {!Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {!Blockly.Connection} destConnection The connection to be moved to.
* @param {!Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {!Blockly.RenderedConnection} destConnection The connection to be
* moved to.
* @private
*/
Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection) {
@@ -384,9 +396,10 @@ Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection)
/**
* If the two blocks are compatible move the moving connection to the target
* connection and connect them.
* @param {Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {Blockly.Connection} destConnection The connection to be moved to.
* @param {Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {Blockly.RenderedConnection} destConnection The connection to be moved
* to.
* @return {boolean} True if the connections were connected, false otherwise.
* @private
*/
@@ -414,8 +427,10 @@ Blockly.navigation.moveAndConnect_ = function(movingConnection, destConnection)
/**
* If the given connection is superior find the inferior connection on the
* source block.
* @param {Blockly.Connection} connection The connection trying to be connected.
* @return {Blockly.Connection} The inferior connection or null if none exists.
* @param {Blockly.RenderedConnection} connection The connection trying to be
* connected.
* @return {Blockly.RenderedConnection} The inferior connection or null if none
* exists.
* @private
*/
Blockly.navigation.getInferiorConnection_ = function(connection) {
@@ -434,8 +449,10 @@ Blockly.navigation.getInferiorConnection_ = function(connection) {
/**
* If the given connection is inferior tries to find a superior connection to
* connect to.
* @param {Blockly.Connection} connection The connection trying to be connected.
* @return {Blockly.Connection} The superior connection or null if none exists.
* @param {Blockly.RenderedConnection} connection The connection trying to be
* connected.
* @return {Blockly.RenderedConnection} The superior connection or null if none
* exists.
* @private
*/
Blockly.navigation.getSuperiorConnection_ = function(connection) {
@@ -453,9 +470,10 @@ Blockly.navigation.getSuperiorConnection_ = function(connection) {
* If the given connections are not compatible try finding compatible connections
* on the source blocks of the given connections.
*
* @param {Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {Blockly.Connection} destConnection The connection to be moved to.
* @param {Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {Blockly.RenderedConnection} destConnection The connection to be moved
* to.
* @return {boolean} True if the two connections or their target connections
* were connected, false otherwise.
* @private
@@ -495,8 +513,9 @@ Blockly.navigation.connect_ = function(movingConnection, destConnection) {
/**
* Tries to connect the given block to the destination connection, making an
* intelligent guess about which connection to use to on the moving block.
* @param {!Blockly.Block} block The block to move.
* @param {!Blockly.Connection} destConnection The connection to connect to.
* @param {!Blockly.BlockSvg} block The block to move.
* @param {!Blockly.RenderedConnection} destConnection The connection to connect
* to.
* @return {boolean} Whether the connection was successful.
*/
Blockly.navigation.insertBlock = function(block, destConnection) {
@@ -518,7 +537,8 @@ Blockly.navigation.insertBlock = function(block, destConnection) {
break;
case Blockly.OUTPUT_VALUE:
for (var i = 0; i < block.inputList.length; i++) {
var inputConnection = block.inputList[i].connection;
var inputConnection = /** @type {Blockly.RenderedConnection} */ (
block.inputList[i].connection);
if (inputConnection && inputConnection.type === Blockly.INPUT_VALUE &&
Blockly.navigation.connect_(inputConnection, destConnection)) {
return true;
@@ -543,13 +563,14 @@ Blockly.navigation.insertBlock = function(block, destConnection) {
* @private
*/
Blockly.navigation.disconnectBlocks_ = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var curNode = workspace.getCursor().getCurNode();
if (!curNode.isConnection()) {
Blockly.navigation.log_('Cannot disconnect blocks when the cursor is not on a connection');
return;
}
var curConnection = /** @type {!Blockly.Connection} */ (curNode.getLocation());
var curConnection =
/** @type {!Blockly.RenderedConnection} */ (curNode.getLocation());
if (!curConnection.isConnected()) {
Blockly.navigation.log_('Cannot disconnect unconnected connection');
return;
@@ -584,7 +605,7 @@ Blockly.navigation.disconnectBlocks_ = function() {
*/
Blockly.navigation.markAtCursor_ = function() {
Blockly.navigation.getMarker().setCurNode(
Blockly.getMainWorkspace().getCursor().getCurNode());
Blockly.navigation.getNavigationWorkspace().getCursor().getCurNode());
};
/**
@@ -606,31 +627,12 @@ Blockly.navigation.setState = function(newState) {
Blockly.navigation.currentState_ = newState;
};
/**
* Gets the top node on a block.
* This is either the previous connection, output connection or the block.
* @param {!Blockly.Block} block The block to find the top most AST node on.
* @return {Blockly.ASTNode} The AST node holding the top most node on the
* block.
* @package
*/
Blockly.navigation.getTopNode = function(block) {
var astNode;
var topConnection = block.previousConnection || block.outputConnection;
if (topConnection) {
astNode = Blockly.ASTNode.createConnectionNode(topConnection);
} else {
astNode = Blockly.ASTNode.createBlockNode(block);
}
return astNode;
};
/**
* Before a block is deleted move the cursor to the appropriate position.
* @param {!Blockly.Block} deletedBlock The block that is being deleted.
* @param {!Blockly.BlockSvg} deletedBlock The block that is being deleted.
*/
Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
if (!workspace) {
return;
}
@@ -664,11 +666,11 @@ Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
/**
* When a block that the cursor is on is mutated move the cursor to the block
* level.
* @param {!Blockly.Block} mutatedBlock The block that is being mutated.
* @param {!Blockly.BlockSvg} mutatedBlock The block that is being mutated.
* @package
*/
Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
var cursor = Blockly.getMainWorkspace().getCursor();
var cursor = Blockly.navigation.getNavigationWorkspace().getCursor();
if (cursor) {
var curNode = cursor.getCurNode();
var block = curNode ? curNode.getSourceBlock() : null;
@@ -683,8 +685,9 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
* Enable accessibility mode.
*/
Blockly.navigation.enableKeyboardAccessibility = function() {
if (!Blockly.getMainWorkspace().keyboardAccessibilityMode) {
Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
var workspace = Blockly.navigation.getNavigationWorkspace();
if (!workspace.keyboardAccessibilityMode) {
workspace.keyboardAccessibilityMode = true;
Blockly.navigation.focusWorkspace_();
}
};
@@ -693,9 +696,9 @@ Blockly.navigation.enableKeyboardAccessibility = function() {
* Disable accessibility mode.
*/
Blockly.navigation.disableKeyboardAccessibility = function() {
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
var workspace = Blockly.getMainWorkspace();
Blockly.getMainWorkspace().keyboardAccessibilityMode = false;
var workspace = Blockly.navigation.getNavigationWorkspace();
if (workspace.keyboardAccessibilityMode) {
workspace.keyboardAccessibilityMode = false;
workspace.getCursor().hide();
Blockly.navigation.getMarker().hide();
if (Blockly.navigation.getFlyoutCursor_()) {
@@ -752,7 +755,7 @@ Blockly.navigation.error_ = function(msg) {
/**
* Handler for all the keyboard navigation events.
* @param {!Event} e The keyboard event.
* @param {!KeyboardEvent} e The keyboard event.
* @return {boolean} True if the key was handled false otherwise.
*/
Blockly.navigation.onKeyPress = function(e) {
@@ -772,10 +775,11 @@ Blockly.navigation.onKeyPress = function(e) {
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.navigation.onBlocklyAction = function(action) {
var readOnly = Blockly.getMainWorkspace().options.readOnly;
var workspace = Blockly.navigation.getNavigationWorkspace();
var readOnly = workspace.options.readOnly;
var actionHandled = false;
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if (workspace.keyboardAccessibilityMode) {
if (!readOnly) {
actionHandled = Blockly.navigation.handleActions_(action);
// If in readonly mode only handle valid actions.
@@ -818,9 +822,9 @@ Blockly.navigation.handleActions_ = function(action) {
* @private
*/
Blockly.navigation.flyoutOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
if (flyout && flyout.onBlocklyAction(action)) {
return true;
@@ -848,9 +852,10 @@ Blockly.navigation.flyoutOnAction_ = function(action) {
* @private
*/
Blockly.navigation.toolboxOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var handled = toolbox ? toolbox.onBlocklyAction(action) : false;
var handled = toolbox && typeof toolbox.onBlocklyAction == 'function' ?
toolbox.onBlocklyAction(action) : false;
if (handled) {
return true;
@@ -881,8 +886,9 @@ Blockly.navigation.toolboxOnAction_ = function(action) {
* @private
*/
Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
var cursor = Blockly.getMainWorkspace().getCursor();
var curNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = workspace.getCursor();
var curNode = workspace.getCursor().getCurNode();
if (curNode.getType() !== Blockly.ASTNode.types.WORKSPACE) {
return false;
@@ -893,7 +899,7 @@ Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
var newY = yDirection * Blockly.navigation.WS_MOVE_DISTANCE + wsCoord.y;
cursor.setCurNode(Blockly.ASTNode.createWorkspaceNode(
Blockly.getMainWorkspace(), new Blockly.utils.Coordinate(newX, newY)));
workspace, new Blockly.utils.Coordinate(newX, newY)));
return true;
};
@@ -904,7 +910,8 @@ Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
* @private
*/
Blockly.navigation.workspaceOnAction_ = function(action) {
if (Blockly.getMainWorkspace().getCursor().onBlocklyAction(action)) {
var workspace = Blockly.navigation.getNavigationWorkspace();
if (workspace.getCursor().onBlocklyAction(action)) {
return true;
}
switch (action.name) {
@@ -935,11 +942,11 @@ Blockly.navigation.workspaceOnAction_ = function(action) {
* @private
*/
Blockly.navigation.handleEnterForWS_ = function() {
var cursor = Blockly.getMainWorkspace().getCursor();
var cursor = Blockly.navigation.getNavigationWorkspace().getCursor();
var curNode = cursor.getCurNode();
var nodeType = curNode.getType();
if (nodeType == Blockly.ASTNode.types.FIELD) {
curNode.getLocation().showEditor();
(/** @type {!Blockly.Field} */(curNode.getLocation())).showEditor();
} else if (curNode.isConnection() ||
nodeType == Blockly.ASTNode.types.WORKSPACE) {
Blockly.navigation.markAtCursor_();
+2 -3
View File
@@ -38,10 +38,9 @@ Blockly.TabNavigateCursor.prototype.validNode_ = function(node) {
var isValid = false;
var type = node && node.getType();
if (node) {
var location = node.getLocation();
var location = /** @type {Blockly.Field} */ (node.getLocation());
if (type == Blockly.ASTNode.types.FIELD &&
location && location.isTabNavigable() &&
(/** @type {!Blockly.Field} */ (location)).isClickable()) {
location && location.isTabNavigable() && location.isClickable()) {
isValid = true;
}
}
+465
View File
@@ -0,0 +1,465 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Blockly menu similar to Closure's goog.ui.Menu
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.Menu');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.KeyCodes');
goog.require('Blockly.utils.style');
/**
* A basic menu class.
* @constructor
*/
Blockly.Menu = function() {
/**
* Array of menu items.
* (Nulls are never in the array, but typing the array as nullable prevents
* the compiler from objecting to .indexOf(null))
* @type {!Array.<Blockly.MenuItem>}
* @private
*/
this.menuItems_ = [];
/**
* Coordinates of the mousedown event that caused this menu to open. Used to
* prevent the consequent mouseup event due to a simple click from activating
* a menu item immediately.
* @type {?Blockly.utils.Coordinate}
* @package
*/
this.openingCoords = null;
/**
* This is the element that we will listen to the real focus events on.
* A value of null means no menu item is highlighted.
* @type {Blockly.MenuItem}
* @private
*/
this.highlightedItem_ = null;
/**
* Mouse over event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseOverHandler_ = null;
/**
* Click event data.
* @type {?Blockly.EventData}
* @private
*/
this.clickHandler_ = null;
/**
* Mouse enter event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseEnterHandler_ = null;
/**
* Mouse leave event data.
* @type {?Blockly.EventData}
* @private
*/
this.mouseLeaveHandler_ = null;
/**
* Key down event data.
* @type {?Blockly.EventData}
* @private
*/
this.onKeyDownHandler_ = null;
/**
* The menu's root DOM element.
* @type {Element}
* @private
*/
this.element_ = null;
/**
* ARIA name for this menu.
* @type {?Blockly.utils.aria.Role}
* @private
*/
this.roleName_ = null;
};
/**
* Add a new menu item to the bottom of this menu.
* @param {!Blockly.MenuItem} menuItem Menu item to append.
*/
Blockly.Menu.prototype.addChild = function(menuItem) {
this.menuItems_.push(menuItem);
};
/**
* Creates the menu DOM.
* @param {!Element} container Element upon which to append this menu.
*/
Blockly.Menu.prototype.render = function(container) {
var element = /** @type {!HTMLDivElement} */ (document.createElement('div'));
// goog-menu is deprecated, use blocklyMenu. May 2020.
element.className = 'blocklyMenu goog-menu blocklyNonSelectable';
element.tabIndex = 0;
if (this.roleName_) {
Blockly.utils.aria.setRole(element, this.roleName_);
}
this.element_ = element;
// Add menu items.
for (var i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) {
element.appendChild(menuItem.createDom());
}
// Add event handlers.
this.mouseOverHandler_ = Blockly.bindEventWithChecks_(element,
'mouseover', this, this.handleMouseOver_, true);
this.clickHandler_ = Blockly.bindEventWithChecks_(element,
'click', this, this.handleClick_, true);
this.mouseEnterHandler_ = Blockly.bindEventWithChecks_(element,
'mouseenter', this, this.handleMouseEnter_, true);
this.mouseLeaveHandler_ = Blockly.bindEventWithChecks_(element,
'mouseleave', this, this.handleMouseLeave_, true);
this.onKeyDownHandler_ = Blockly.bindEventWithChecks_(element,
'keydown', this, this.handleKeyEvent_);
container.appendChild(element);
};
/**
* Gets the menu's element.
* @return {Element} The DOM element.
* @package
*/
Blockly.Menu.prototype.getElement = function() {
return this.element_;
};
/**
* Focus the menu element.
* @package
*/
Blockly.Menu.prototype.focus = function() {
var el = this.getElement();
if (el) {
el.focus({preventScroll:true});
Blockly.utils.dom.addClass(el, 'blocklyFocused');
}
};
/**
* Blur the menu element.
* @private
*/
Blockly.Menu.prototype.blur_ = function() {
var el = this.getElement();
if (el) {
el.blur();
Blockly.utils.dom.removeClass(el, 'blocklyFocused');
}
};
/**
* Set the menu accessibility role.
* @param {!Blockly.utils.aria.Role} roleName role name.
* @package
*/
Blockly.Menu.prototype.setRole = function(roleName) {
this.roleName_ = roleName;
};
/**
* Dispose of this menu.
*/
Blockly.Menu.prototype.dispose = function() {
// Remove event handlers.
if (this.mouseOverHandler_) {
Blockly.unbindEvent_(this.mouseOverHandler_);
this.mouseOverHandler_ = null;
}
if (this.clickHandler_) {
Blockly.unbindEvent_(this.clickHandler_);
this.clickHandler_ = null;
}
if (this.mouseEnterHandler_) {
Blockly.unbindEvent_(this.mouseEnterHandler_);
this.mouseEnterHandler_ = null;
}
if (this.mouseLeaveHandler_) {
Blockly.unbindEvent_(this.mouseLeaveHandler_);
this.mouseLeaveHandler_ = null;
}
if (this.onKeyDownHandler_) {
Blockly.unbindEvent_(this.onKeyDownHandler_);
this.onKeyDownHandler_ = null;
}
// Remove menu items.
for (var i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) {
menuItem.dispose();
}
this.element_ = null;
};
// Child component management.
/**
* Returns the child menu item that owns the given DOM element,
* or null if no such menu item is found.
* @param {Element} elem DOM element whose owner is to be returned.
* @return {?Blockly.MenuItem} Menu item for which the DOM element belongs to.
* @private
*/
Blockly.Menu.prototype.getMenuItem_ = function(elem) {
var menuElem = this.getElement();
// Node might be the menu border (resulting in no associated menu item), or
// a menu item's div, or some element within the menu item.
// Walk up parents until one meets either the menu's root element, or
// a menu item's div.
while (elem && elem != menuElem) {
if (Blockly.utils.dom.hasClass(elem, 'blocklyMenuItem')) {
// Having found a menu item's div, locate that menu item in this menu.
for (var i = 0, menuItem; (menuItem = this.menuItems_[i]); i++) {
if (menuItem.getElement() == elem) {
return menuItem;
}
}
}
elem = elem.parentElement;
}
return null;
};
// Highlight management.
/**
* Highlights the given menu item, or clears highlighting if null.
* @param {Blockly.MenuItem} item Item to highlight, or null.
* @package
*/
Blockly.Menu.prototype.setHighlighted = function(item) {
var currentHighlighted = this.highlightedItem_;
if (currentHighlighted) {
currentHighlighted.setHighlighted(false);
this.highlightedItem_ = null;
}
if (item) {
item.setHighlighted(true);
this.highlightedItem_ = item;
// Bring the highlighted item into view. This has no effect if the menu is
// not scrollable.
var el = /** @type {!Element} */ (this.getElement());
Blockly.utils.style.scrollIntoContainerView(
/** @type {!Element} */ (item.getElement()), el);
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.ACTIVEDESCENDANT,
item.getId());
}
};
/**
* Highlights the next highlightable item (or the first if nothing is currently
* highlighted).
* @package
*/
Blockly.Menu.prototype.highlightNext = function() {
var index = this.menuItems_.indexOf(this.highlightedItem_);
this.highlightHelper_(index, 1);
};
/**
* Highlights the previous highlightable item (or the last if nothing is
* currently highlighted).
* @package
*/
Blockly.Menu.prototype.highlightPrevious = function() {
var index = this.menuItems_.indexOf(this.highlightedItem_);
this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1);
};
/**
* Highlights the first highlightable item.
* @private
*/
Blockly.Menu.prototype.highlightFirst_ = function() {
this.highlightHelper_(-1, 1);
};
/**
* Highlights the last highlightable item.
* @private
*/
Blockly.Menu.prototype.highlightLast_ = function() {
this.highlightHelper_(this.menuItems_.length, -1);
};
/**
* Helper function that manages the details of moving the highlight among
* child menuitems in response to keyboard events.
* @param {number} startIndex Start index.
* @param {number} delta Step direction: 1 to go down, -1 to go up.
* @private
*/
Blockly.Menu.prototype.highlightHelper_ = function(startIndex, delta) {
var index = startIndex + delta;
var menuItem;
while ((menuItem = this.menuItems_[index])) {
if (menuItem.isEnabled()) {
this.setHighlighted(menuItem);
break;
}
index += delta;
}
};
// Mouse events.
/**
* Handles mouseover events. Highlight menuitems as the user hovers over them.
* @param {!Event} e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseOver_ = function(e) {
var menuItem = this.getMenuItem_(/** @type {Element} */ (e.target));
if (menuItem) {
if (menuItem.isEnabled()) {
if (this.highlightedItem_ != menuItem) {
this.setHighlighted(menuItem);
}
} else {
this.setHighlighted(null);
}
}
};
/**
* Handles click events. Pass the event onto the child menuitem to handle.
* @param {!Event} e Click event to handle.
* @private
*/
Blockly.Menu.prototype.handleClick_ = function(e) {
var oldCoords = this.openingCoords;
// Clear out the saved opening coords immediately so they're not used twice.
this.openingCoords = null;
if (oldCoords && typeof e.clientX == 'number') {
var newCoords = new Blockly.utils.Coordinate(e.clientX, e.clientY);
if (Blockly.utils.Coordinate.distance(oldCoords, newCoords) < 1) {
// This menu was opened by a mousedown and we're handling the consequent
// click event. The coords haven't changed, meaning this was the same
// opening event. Don't do the usual behavior because the menu just popped
// up under the mouse and the user didn't mean to activate this item.
return;
}
}
var menuItem = this.getMenuItem_(/** @type {Element} */ (e.target));
if (menuItem) {
menuItem.performAction();
}
};
/**
* Handles mouse enter events. Focus the element.
* @param {Event} _e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseEnter_ = function(_e) {
this.focus();
};
/**
* Handles mouse leave events. Blur and clear highlight.
* @param {Event} _e Mouse event to handle.
* @private
*/
Blockly.Menu.prototype.handleMouseLeave_ = function(_e) {
if (this.getElement()) {
this.blur_();
this.setHighlighted(null);
}
};
// Keyboard events.
/**
* Attempts to handle a keyboard event, if the menu item is enabled, by calling
* {@link handleKeyEventInternal_}.
* @param {!Event} e Key event to handle.
* @private
*/
Blockly.Menu.prototype.handleKeyEvent_ = function(e) {
if (!this.menuItems_.length) {
// Empty menu.
return;
}
if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) {
// Do not handle the key event if any modifier key is pressed.
return;
}
var highlighted = this.highlightedItem_;
switch (e.keyCode) {
case Blockly.utils.KeyCodes.ENTER:
case Blockly.utils.KeyCodes.SPACE:
if (highlighted) {
highlighted.performAction();
}
break;
case Blockly.utils.KeyCodes.UP:
this.highlightPrevious();
break;
case Blockly.utils.KeyCodes.DOWN:
this.highlightNext();
break;
case Blockly.utils.KeyCodes.PAGE_UP:
case Blockly.utils.KeyCodes.HOME:
this.highlightFirst_();
break;
case Blockly.utils.KeyCodes.PAGE_DOWN:
case Blockly.utils.KeyCodes.END:
this.highlightLast_();
break;
default:
// Not a key the menu is interested in.
return;
}
// The menu used this key, don't let it have secondary effects.
e.preventDefault();
e.stopPropagation();
};
/**
* Get the size of a rendered menu.
* @return {!Blockly.utils.Size} Object with width and height properties.
* @package
*/
Blockly.Menu.prototype.getSize = function() {
var menuDom = this.getElement();
var menuSize = Blockly.utils.style.getSize(/** @type {!Element} */ (menuDom));
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
return menuSize;
};
+276
View File
@@ -0,0 +1,276 @@
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Blockly menu item similar to Closure's goog.ui.MenuItem
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.MenuItem');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.IdGenerator');
/**
* Class representing an item in a menu.
*
* @param {string} content Text caption to display as the content of
* the item.
* @param {string=} opt_value Data/model associated with the menu item.
* @constructor
*/
Blockly.MenuItem = function(content, opt_value) {
/**
* Human-readable text of this menu item.
* @type {string}
* @private
*/
this.content_ = content;
/**
* Machine-readable value of this menu item.
* @type {string|undefined}
* @private
*/
this.value_ = opt_value;
/**
* Is the menu item clickable, as opposed to greyed-out.
* @type {boolean}
* @private
*/
this.enabled_ = true;
/**
* The DOM element for the menu item.
* @type {?Element}
* @private
*/
this.element_ = null;
/**
* Whether the menu item is rendered right-to-left.
* @type {boolean}
* @private
*/
this.rightToLeft_ = false;
/**
* ARIA name for this menu.
* @type {?Blockly.utils.aria.Role}
* @private
*/
this.roleName_ = null;
/**
* Is this menu item checkable.
* @type {boolean}
* @private
*/
this.checkable_ = false;
/**
* Is this menu item currently checked.
* @type {boolean}
* @private
*/
this.checked_ = false;
/**
* Is this menu item currently highlighted.
* @type {boolean}
* @private
*/
this.highlight_ = false;
/**
* Bound function to call when this menu item is clicked.
* @type {Function}
* @private
*/
this.actionHandler_ = null;
};
/**
* Creates the menuitem's DOM.
* @return {!Element} Completed DOM.
*/
Blockly.MenuItem.prototype.createDom = function() {
var element = document.createElement('div');
element.id = Blockly.utils.IdGenerator.getNextUniqueId();
this.element_ = element;
// Set class and style
// goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020.
element.className = 'blocklyMenuItem goog-menuitem ' +
(this.enabled_ ? '' : 'blocklyMenuItemDisabled goog-menuitem-disabled ') +
(this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') +
(this.highlight_ ?
'blocklyMenuItemHighlight goog-menuitem-highlight ' : '') +
(this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : '');
var content = document.createElement('div');
content.className = 'blocklyMenuItemContent goog-menuitem-content';
// Add a checkbox for checkable menu items.
if (this.checkable_) {
var checkbox = document.createElement('div');
checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox';
content.appendChild(checkbox);
}
content.appendChild(document.createTextNode(this.content_));
element.appendChild(content);
// Initialize ARIA role and state.
if (this.roleName_) {
Blockly.utils.aria.setRole(element, this.roleName_);
}
Blockly.utils.aria.setState(element, Blockly.utils.aria.State.SELECTED,
(this.checkable_ && this.checked_) || false);
Blockly.utils.aria.setState(element, Blockly.utils.aria.State.DISABLED,
!this.enabled_);
return element;
};
/**
* Dispose of this menu item.
*/
Blockly.MenuItem.prototype.dispose = function() {
this.element_ = null;
};
/**
* Gets the menu item's element.
* @return {Element} The DOM element.
* @package
*/
Blockly.MenuItem.prototype.getElement = function() {
return this.element_;
};
/**
* Gets the unique ID for this menu item.
* @return {string} Unique component ID.
* @package
*/
Blockly.MenuItem.prototype.getId = function() {
return this.element_.id;
};
/**
* Gets the value associated with the menu item.
* @return {*} value Value associated with the menu item.
* @package
*/
Blockly.MenuItem.prototype.getValue = function() {
return this.value_;
};
/**
* Set menu item's rendering direction.
* @param {boolean} rtl True if RTL, false if LTR.
* @package
*/
Blockly.MenuItem.prototype.setRightToLeft = function(rtl) {
this.rightToLeft_ = rtl;
};
/**
* Set the menu item's accessibility role.
* @param {!Blockly.utils.aria.Role} roleName Role name.
* @package
*/
Blockly.MenuItem.prototype.setRole = function(roleName) {
this.roleName_ = roleName;
};
/**
* Sets the menu item to be checkable or not. Set to true for menu items
* that represent checkable options.
* @param {boolean} checkable Whether the menu item is checkable.
* @package
*/
Blockly.MenuItem.prototype.setCheckable = function(checkable) {
this.checkable_ = checkable;
};
/**
* Checks or unchecks the component.
* @param {boolean} checked Whether to check or uncheck the component.
* @package
*/
Blockly.MenuItem.prototype.setChecked = function(checked) {
this.checked_ = checked;
};
/**
* Highlights or unhighlights the component.
* @param {boolean} highlight Whether to highlight or unhighlight the component.
* @package
*/
Blockly.MenuItem.prototype.setHighlighted = function(highlight) {
this.highlight_ = highlight;
var el = this.getElement();
if (el && this.isEnabled()) {
// goog-menuitem-highlight is deprecated, use blocklyMenuItemHighlight.
// May 2020.
var name = 'blocklyMenuItemHighlight';
var nameDep = 'goog-menuitem-highlight';
if (highlight) {
Blockly.utils.dom.addClass(el, name);
Blockly.utils.dom.addClass(el, nameDep);
} else {
Blockly.utils.dom.removeClass(el, name);
Blockly.utils.dom.removeClass(el, nameDep);
}
}
};
/**
* Returns true if the menu item is enabled, false otherwise.
* @return {boolean} Whether the menu item is enabled.
* @package
*/
Blockly.MenuItem.prototype.isEnabled = function() {
return this.enabled_;
};
/**
* Enables or disables the menu item.
* @param {boolean} enabled Whether to enable or disable the menu item.
* @package
*/
Blockly.MenuItem.prototype.setEnabled = function(enabled) {
this.enabled_ = enabled;
};
/**
* Performs the appropriate action when the menu item is activated
* by the user.
* @package
*/
Blockly.MenuItem.prototype.performAction = function() {
if (this.isEnabled() && this.actionHandler_) {
this.actionHandler_(this);
}
};
/**
* Set the handler that's called when the menu item is activated by the user.
* `obj` will be used as the 'this' object in the function when called.
* @param {function(!Blockly.MenuItem)} fn The handler.
* @param {!Object} obj Used as the 'this' object in fn when called.
* @package
*/
Blockly.MenuItem.prototype.onAction = function(fn, obj) {
this.actionHandler_ = fn.bind(obj);
};
+33 -20
View File
@@ -27,6 +27,8 @@ goog.require('Blockly.utils.xml');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.Xml');
goog.requireType('Blockly.utils.Metrics');
/**
* Class for a mutator dialog.
@@ -74,7 +76,7 @@ Blockly.Mutator.prototype.getWorkspace = function() {
/**
* Draw the mutator icon.
* @param {!Element} group The icon group.
* @private
* @protected
*/
Blockly.Mutator.prototype.drawIcon_ = function(group) {
// Square with rounded corners.
@@ -165,8 +167,12 @@ Blockly.Mutator.prototype.createEditor_ = function() {
}));
workspaceOptions.toolboxPosition = this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT :
Blockly.TOOLBOX_AT_LEFT;
workspaceOptions.languageTree = quarkXml;
workspaceOptions.getMetrics = this.getFlyoutMetrics_.bind(this);
var hasFlyout = !!quarkXml;
if (hasFlyout) {
workspaceOptions.languageTree =
Blockly.utils.toolbox.convertToolboxToJSON(quarkXml);
workspaceOptions.getMetrics = this.getFlyoutMetrics_.bind(this);
}
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_.isMutator = true;
this.workspace_.addChangeListener(Blockly.Events.disableOrphans);
@@ -175,13 +181,15 @@ Blockly.Mutator.prototype.createEditor_ = function() {
// a top level svg. Instead of handling scale themselves, mutators
// inherit scale from the parent workspace.
// To fix this, scale needs to be applied at a different level in the dom.
var flyoutSvg = this.workspace_.addFlyout('g');
var flyoutSvg = hasFlyout ? this.workspace_.addFlyout('g') : null;
var background = this.workspace_.createDom('blocklyMutatorBackground');
// Insert the flyout after the <rect> but before the block canvas so that
// the flyout is underneath in z-order. This makes blocks layering during
// dragging work properly.
background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_);
if (flyoutSvg) {
// Insert the flyout after the <rect> but before the block canvas so that
// the flyout is underneath in z-order. This makes blocks layering during
// dragging work properly.
background.insertBefore(flyoutSvg, this.workspace_.svgBlockCanvas_);
}
this.svgDialog_.appendChild(background);
return this.svgDialog_;
@@ -286,7 +294,7 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
var flyout = this.workspace_.getFlyout();
if (tree) {
flyout.init(this.workspace_);
flyout.show(tree.childNodes);
flyout.show(tree);
}
this.rootBlock_ = this.block_.decompose(this.workspace_);
@@ -378,7 +386,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
block.initSvg();
block.render();
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if ((/** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace()))
.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block);
}
var newMutationDom = block.mutationToDom();
@@ -386,13 +395,6 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
if (oldMutation != newMutation) {
Blockly.Events.fire(new Blockly.Events.BlockChange(
block, 'mutation', null, oldMutation, newMutation));
// Ensure that any bump is part of this mutation's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Blockly.Events.setGroup(group);
block.bumpNeighbours();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY);
}
// Don't update the bubble until the drag has ended, to avoid moving blocks
@@ -411,15 +413,26 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
* .viewWidth: Width of the visible rectangle,
* .absoluteTop: Top-edge of view.
* .absoluteLeft: Left-edge of view.
* @return {!Object} Contains size and position metrics of mutator dialog's
* workspace.
* @return {!Blockly.utils.Metrics} Contains size and position metrics of
* mutator dialog's workspace.
* @private
*/
Blockly.Mutator.prototype.getFlyoutMetrics_ = function() {
// The mutator workspace only uses a subset of Blockly.utils.Metrics
// properties as features such as scroll and zoom are unsupported.
var unsupported = 0;
return {
contentHeight: unsupported,
contentWidth: unsupported,
contentTop: unsupported,
contentLeft: unsupported,
viewHeight: this.workspaceHeight_,
viewWidth: this.workspaceWidth_ - this.workspace_.getFlyout().getWidth(),
absoluteTop: 0,
viewTop: unsupported,
viewLeft: unsupported,
absoluteTop: unsupported,
absoluteLeft: this.workspace_.RTL ? 0 :
this.workspace_.getFlyout().getWidth()
};
+57 -14
View File
@@ -14,7 +14,11 @@ goog.provide('Blockly.Options');
goog.require('Blockly.Theme');
goog.require('Blockly.Themes.Classic');
goog.require('Blockly.registry');
goog.require('Blockly.user.keyMap');
goog.require('Blockly.utils.IdGenerator');
goog.require('Blockly.utils.Metrics');
goog.require('Blockly.utils.toolbox');
goog.require('Blockly.utils.userAgent');
goog.require('Blockly.Xml');
@@ -29,7 +33,7 @@ goog.require('Blockly.Xml');
Blockly.Options = function(options) {
var readOnly = !!options['readOnly'];
if (readOnly) {
var languageTree = null;
var toolboxContents = null;
var hasCategories = false;
var hasTrashcan = false;
var hasCollapse = false;
@@ -37,10 +41,12 @@ Blockly.Options = function(options) {
var hasDisable = false;
var hasSounds = false;
} else {
var languageTree =
Blockly.Options.parseToolboxTree(options['toolbox'] || null);
var hasCategories = Boolean(languageTree &&
languageTree.getElementsByTagName('category').length);
var toolboxDef = options['toolbox'];
if (!Array.isArray(toolboxDef)) {
toolboxDef = Blockly.Options.parseToolboxTree(toolboxDef || null);
}
var toolboxContents = Blockly.utils.toolbox.convertToolboxToJSON(toolboxDef);
var hasCategories = Blockly.utils.toolbox.hasCategories(toolboxContents);
var hasTrashcan = options['trashcan'];
if (hasTrashcan === undefined) {
hasTrashcan = hasCategories;
@@ -106,39 +112,65 @@ Blockly.Options = function(options) {
var renderer = options['renderer'] || 'geras';
var plugins = options['plugins'] || {};
/** @type {boolean} */
this.RTL = rtl;
/** @type {boolean} */
this.oneBasedIndex = oneBasedIndex;
/** @type {boolean} */
this.collapse = hasCollapse;
/** @type {boolean} */
this.comments = hasComments;
/** @type {boolean} */
this.disable = hasDisable;
/** @type {boolean} */
this.readOnly = readOnly;
/** @type {number} */
this.maxBlocks = options['maxBlocks'] || Infinity;
/** @type {?Object.<string, number>} */
this.maxInstances = options['maxInstances'];
/** @type {string} */
this.pathToMedia = pathToMedia;
/** @type {boolean} */
this.hasCategories = hasCategories;
/** @type {!Object} */
this.moveOptions = Blockly.Options.parseMoveOptions(options, hasCategories);
/** @deprecated January 2019 */
this.hasScrollbars = this.moveOptions.scrollbars;
/** @type {boolean} */
this.hasTrashcan = hasTrashcan;
/** @type {number} */
this.maxTrashcanContents = maxTrashcanContents;
/** @type {boolean} */
this.hasSounds = hasSounds;
/** @type {boolean} */
this.hasCss = hasCss;
/** @type {boolean} */
this.horizontalLayout = horizontalLayout;
this.languageTree = languageTree;
/** @type {Array.<Blockly.utils.toolbox.Toolbox>} */
this.languageTree = toolboxContents;
/** @type {!Object} */
this.gridOptions = Blockly.Options.parseGridOptions_(options);
/** @type {!Object} */
this.zoomOptions = Blockly.Options.parseZoomOptions_(options);
/** @type {number} */
this.toolboxPosition = toolboxPosition;
/** @type {!Blockly.Theme} */
this.theme = Blockly.Options.parseThemeOptions_(options);
/** @type {!Object<string,Blockly.Action>} */
this.keyMap = keyMap;
/** @type {string} */
this.renderer = renderer;
/** @type {?Object} */
this.rendererOverrides = options['rendererOverrides'];
/**
* The SVG element for the grid pattern.
* Created during injection.
* @type {!SVGElement}
* @type {SVGElement}
*/
this.gridPattern = undefined;
this.gridPattern = null;
/**
* The parent of the current workspace, or null if there is no parent
@@ -146,6 +178,12 @@ Blockly.Options = function(options) {
* @type {Blockly.Workspace}
*/
this.parentWorkspace = options['parentWorkspace'];
/**
* Map of plugin type to name of registered plugin or plugin class.
* @type {!Object.<string, (function(new:?, ...?)|string)>}
*/
this.plugins = plugins;
};
/**
@@ -158,15 +196,15 @@ Blockly.BlocklyOptions = function() {};
/**
* If set, sets the translation of the workspace to match the scrollbars.
* @param {!Object} xyRatio Contains an x and/or y property which is a float
* between 0 and 1 specifying the degree of scrolling.
* @param {!{x:number,y:number}} xyRatio Contains an x and/or y property which
* is a float between 0 and 1 specifying the degree of scrolling.
* @return {void}
*/
Blockly.Options.prototype.setMetrics;
/**
* Return an object with the metrics required to size the workspace.
* @return {!Object} Contains size and position metrics.
* @return {!Blockly.utils.Metrics} Contains size and position metrics.
*/
Blockly.Options.prototype.getMetrics;
@@ -280,15 +318,20 @@ Blockly.Options.parseGridOptions_ = function(options) {
*/
Blockly.Options.parseThemeOptions_ = function(options) {
var theme = options['theme'] || Blockly.Themes.Classic;
if (theme instanceof Blockly.Theme) {
if (typeof theme == 'string') {
return /** @type {!Blockly.Theme} */ (
Blockly.registry.getObject(Blockly.registry.Type.THEME, theme));
} else if (theme instanceof Blockly.Theme) {
return /** @type {!Blockly.Theme} */ (theme);
}
return Blockly.Theme.defineTheme(theme.name || 'builtin', theme);
return Blockly.Theme.defineTheme(theme.name ||
('builtin' + Blockly.utils.IdGenerator.getNextUniqueId()), theme);
};
/**
* Parse the provided toolbox tree into a consistent DOM format.
* @param {Node|string} tree DOM tree of blocks, or text representation of same.
* @param {Node|NodeList|?string} tree DOM tree of blocks, or text representation
* of same.
* @return {Node} DOM tree of blocks, or null.
*/
Blockly.Options.parseToolboxTree = function(tree) {
+7 -17
View File
@@ -60,23 +60,13 @@ Blockly.Procedures.ProcedureBlock;
* list, and return value boolean.
*/
Blockly.Procedures.allProcedures = function(root) {
var blocks = root.getAllBlocks(false);
var proceduresReturn = [];
var proceduresNoReturn = [];
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var procedureBlock = /** @type {!Blockly.Procedures.ProcedureBlock} */ (
blocks[i]);
var tuple = procedureBlock.getProcedureDef();
if (tuple) {
if (tuple[2]) {
proceduresReturn.push(tuple);
} else {
proceduresNoReturn.push(tuple);
}
}
}
}
var proceduresNoReturn = root.getBlocksByType('procedures_defnoreturn', false)
.map(function(block) {
return /** @type {!Blockly.Procedures.ProcedureBlock} */ (block).getProcedureDef();
});
var proceduresReturn = root.getBlocksByType('procedures_defreturn', false).map(function(block) {
return /** @type {!Blockly.Procedures.ProcedureBlock} */ (block).getProcedureDef();
});
proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
return [proceduresNoReturn, proceduresReturn];
+223
View File
@@ -0,0 +1,223 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview This file is a universal registry that provides generic methods
* for registering and unregistering different types of classes.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.registry');
goog.requireType('Blockly.blockRendering.Renderer');
goog.requireType('Blockly.Field');
goog.requireType('Blockly.IToolbox');
goog.requireType('Blockly.Theme');
goog.requireType('Blockly.utils.toolbox');
/**
* A map of maps. With the keys being the type and name of the class we are
* registering and the value being the constructor function.
* e.g. {'field': {'field_angle': Blockly.FieldAngle}}
*
* @type {Object<string, Object<string, function(new:?)>>}
*/
Blockly.registry.typeMap_ = {};
/**
* The string used to register the default class for a type of plugin.
* @type {string}
*/
Blockly.registry.DEFAULT = 'default';
/**
* A name with the type of the element stored in the generic.
* @param {string} name The name of the registry type.
* @constructor
* @template T
*/
Blockly.registry.Type = function(name) {
/**
* @type {string}
* @private
*/
this.name_ = name;
};
/**
* Returns the name of the type.
* @return {string} The name.
* @override
*/
Blockly.registry.Type.prototype.toString = function() {
return this.name_;
};
/** @type {!Blockly.registry.Type<Blockly.blockRendering.Renderer>} */
Blockly.registry.Type.RENDERER = new Blockly.registry.Type('renderer');
/** @type {!Blockly.registry.Type<Blockly.Field>} */
Blockly.registry.Type.FIELD = new Blockly.registry.Type('field');
/** @type {!Blockly.registry.Type<Blockly.IToolbox>} */
Blockly.registry.Type.TOOLBOX = new Blockly.registry.Type('toolbox');
/** @type {!Blockly.registry.Type<Blockly.Theme>} */
Blockly.registry.Type.THEME = new Blockly.registry.Type('theme');
/**
* Registers a class based on a type and name.
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
* (e.g. Field, Renderer)
* @param {string} name The plugin's name. (Ex. field_angle, geras)
* @param {?function(new:T, ...?)|Object} registryItem The class or object to
* register.
* @throws {Error} if the type or name is empty, a name with the given type has
* already been registered, or if the given class or object is not valid for it's type.
* @template T
*/
Blockly.registry.register = function(type, name, registryItem) {
if ((!(type instanceof Blockly.registry.Type) && typeof type != 'string') || String(type).trim() == '') {
throw Error('Invalid type "' + type + '". The type must be a' +
' non-empty string or a Blockly.registry.Type.');
}
type = String(type).toLowerCase();
if ((typeof name != 'string') || (name.trim() == '')) {
throw Error('Invalid name "' + name + '". The name must be a' +
' non-empty string.');
}
name = name.toLowerCase();
if (!registryItem) {
throw Error('Can not register a null value');
}
var typeRegistry = Blockly.registry.typeMap_[type];
// If the type registry has not been created, create it.
if (!typeRegistry) {
typeRegistry = Blockly.registry.typeMap_[type] = {};
}
// Validate that the given class has all the required properties.
Blockly.registry.validate_(type, registryItem);
// If the name already exists throw an error.
if (typeRegistry[name]) {
throw Error('Name "' + name + '" with type "' + type + '" already registered.');
}
typeRegistry[name] = registryItem;
};
/**
* Checks the given registry item for properties that are required based on the
* type.
* @param {string} type The type of the plugin. (e.g. Field, Renderer)
* @param {Function|Object} registryItem A class or object that we are checking
* for the required properties.
* @private
*/
Blockly.registry.validate_ = function(type, registryItem) {
switch (type) {
case String(Blockly.registry.Type.FIELD):
if (typeof registryItem.fromJson != 'function') {
throw Error('Type "' + type + '" must have a fromJson function');
}
break;
}
};
/**
* Unregisters the registry item with the given type and name.
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
* (e.g. Field, Renderer)
* @param {string} name The plugin's name. (Ex. field_angle, geras)
* @template T
*/
Blockly.registry.unregister = function(type, name) {
type = String(type).toLowerCase();
name = name.toLowerCase();
var typeRegistry = Blockly.registry.typeMap_[type];
if (!typeRegistry) {
console.warn('No type "' + type + '" found');
return;
}
if (!typeRegistry[name]) {
console.warn('No name "' + name + '" with type "' + type + '" found');
return;
}
delete Blockly.registry.typeMap_[type][name];
};
/**
* Gets the registry item for the given name and type. This can be either a
* class or an object.l
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
* (e.g. Field, Renderer)
* @param {string} name The plugin's name. (Ex. field_angle, geras)
* @return {?function(new:T, ...?)|Object} The class or object with the given
* name and type or null if none exists.
* @template T
*/
Blockly.registry.getItem_ = function(type, name) {
type = String(type).toLowerCase();
name = name.toLowerCase();
var typeRegistry = Blockly.registry.typeMap_[type];
if (!typeRegistry) {
console.warn('No type "' + type + '" found');
return null;
}
if (!typeRegistry[name]) {
console.warn('No name "' + name + '" with type "' + type + '" found');
return null;
}
return typeRegistry[name];
};
/**
* Gets the class for the given name and type.
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
* (e.g. Field, Renderer)
* @param {string} name The plugin's name. (Ex. field_angle, geras)
* @return {?function(new:T, ...?)} The class with the given name and type or
* null if none exists.
* @template T
*/
Blockly.registry.getClass = function(type, name) {
return /** @type {?function(new:T, ...?)} */ (Blockly.registry.getItem_(type, name));
};
/**
* Gets the object for the given name and type.
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
* (e.g. Category)
* @param {string} name The plugin's name. (Ex. logic_category)
* @returns {T} The object with the given name and type or null if none exists.
* @template T
*/
Blockly.registry.getObject = function(type, name) {
return /** @type {T} */ (Blockly.registry.getItem_(type, name));
};
/**
* Gets the class from Blockly options for the given type.
* This is used for plugins that override a built in feature. (e.g. Toolbox)
* @param {Blockly.registry.Type<T>} type The type of the plugin.
* @param {!Blockly.Options} options The option object to check for the given
* plugin.
* @return {?function(new:T, ...?)} The class for the plugin.
* @template T
*/
Blockly.registry.getClassFromOptions = function(type, options) {
var typeName = type.toString();
var plugin = options.plugins[typeName] || Blockly.registry.DEFAULT;
// If the user passed in a plugin class instead of a registered plugin name.
if (typeof plugin == 'function') {
return plugin;
}
return Blockly.registry.getClass(type, plugin);
};
+20 -3
View File
@@ -59,6 +59,12 @@ Blockly.RenderedConnection = function(source, type) {
* @private
*/
this.trackedState_ = Blockly.RenderedConnection.TrackedState.WILL_TRACK;
/**
* Connection this connection connects to. Null if not connected.
* @type {Blockly.RenderedConnection}
*/
this.targetConnection = null;
};
Blockly.utils.object.inherits(Blockly.RenderedConnection, Blockly.Connection);
@@ -454,6 +460,8 @@ Blockly.RenderedConnection.prototype.disconnectInternal_ = function(parentBlock,
if (childBlock.rendered) {
childBlock.updateDisabled();
childBlock.render();
// Reset visibility, since the child is now a top block.
childBlock.getSvgRoot().style.display = 'block';
}
};
@@ -504,14 +512,16 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
var parentConnection = this;
var parentBlock = parentConnection.getSourceBlock();
var childBlock = childConnection.getSourceBlock();
var parentRendered = parentBlock.rendered;
var childRendered = childBlock.rendered;
if (parentBlock.rendered) {
if (parentRendered) {
parentBlock.updateDisabled();
}
if (childBlock.rendered) {
if (childRendered) {
childBlock.updateDisabled();
}
if (parentBlock.rendered && childBlock.rendered) {
if (parentRendered && childRendered) {
if (parentConnection.type == Blockly.NEXT_STATEMENT ||
parentConnection.type == Blockly.PREVIOUS_STATEMENT) {
// Child block may need to square off its corners if it is in a stack.
@@ -523,6 +533,13 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
parentBlock.render();
}
}
// The input the child block is connected to (if any).
var parentInput = parentBlock.getInputWithBlock(childBlock);
if (parentInput) {
var visible = parentInput.isVisible();
childBlock.getSvgRoot().style.display = visible ? 'block' : 'none';
}
};
/**
+8 -23
View File
@@ -16,16 +16,10 @@
*/
goog.provide('Blockly.blockRendering');
goog.require('Blockly.registry');
goog.require('Blockly.utils.object');
/**
* The set of all registered renderers, keyed by their name.
* @type {!Object<string, !Function>}
* @private
*/
Blockly.blockRendering.rendererMap_ = {};
/**
* Whether or not the debugger is turned on.
* @type {boolean}
@@ -41,10 +35,8 @@ Blockly.blockRendering.useDebugger = false;
* @throws {Error} if a renderer with the same name has already been registered.
*/
Blockly.blockRendering.register = function(name, rendererClass) {
if (Blockly.blockRendering.rendererMap_[name]) {
throw Error('Renderer has already been registered.');
}
Blockly.blockRendering.rendererMap_[name] = rendererClass;
Blockly.registry.register(Blockly.registry.Type.RENDERER, name,
rendererClass);
};
/**
@@ -52,14 +44,8 @@ Blockly.blockRendering.register = function(name, rendererClass) {
* @param {string} name The name of the renderer.
*/
Blockly.blockRendering.unregister = function(name) {
if (Blockly.blockRendering.rendererMap_[name]) {
delete Blockly.blockRendering.rendererMap_[name];
} else {
console.warn('No renderer mapping for name "' + name +
'" found to unregister');
}
Blockly.registry.unregister(Blockly.registry.Type.RENDERER, name);
};
/**
* Turn on the blocks debugger.
* @package
@@ -85,12 +71,11 @@ Blockly.blockRendering.stopDebugger = function() {
* Already initialized.
* @package
*/
Blockly.blockRendering.init = function(name, theme, opt_rendererOverrides) {
if (!Blockly.blockRendering.rendererMap_[name]) {
throw Error('Renderer not registered: ', name);
}
var renderer = (/** @type {!Blockly.blockRendering.Renderer} */ (
new Blockly.blockRendering.rendererMap_[name](name)));
var rendererClass = Blockly.registry.getClass(
Blockly.registry.Type.RENDERER, name);
var renderer = new rendererClass(name);
renderer.init(theme, opt_rendererOverrides);
return renderer;
};
+4 -5
View File
@@ -256,7 +256,7 @@ Blockly.blockRendering.ConstantProvider = function() {
* @type {number}
*/
this.FIELD_TEXT_HEIGHT = -1; // Dynamically set
/**
* Text baseline. This constant is dynamically set in ``setFontConstants_``
* to be the baseline of the text based on the font used.
@@ -1168,9 +1168,8 @@ Blockly.blockRendering.ConstantProvider.prototype.getCSS_ = function(selector) {
// Text.
selector + ' .blocklyText, ',
selector + ' .blocklyFlyoutLabelText {',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-size: ' + this.FIELD_TEXT_FONTSIZE + 'pt;',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'font: ' + this.FIELD_TEXT_FONTWEIGHT + ' ' +
this.FIELD_TEXT_FONTSIZE + 'pt ' + this.FIELD_TEXT_FONTFAMILY + ';',
'}',
// Fields.
@@ -1233,7 +1232,7 @@ Blockly.blockRendering.ConstantProvider.prototype.getCSS_ = function(selector) {
// Insertion marker.
selector + ' .blocklyInsertionMarker>.blocklyPath {',
'fill-opacity: ' + this.INSERTION_MARKER_OPACITY + ';',
'stroke: none',
'stroke: none;',
'}',
/* eslint-enable indent */
];
+32
View File
@@ -27,6 +27,38 @@ goog.requireType('Blockly.Theme');
*/
Blockly.blockRendering.IPathObject = function(_root, _constants) {};
/**
* The primary path of the block.
* @type {!SVGElement}
*/
Blockly.blockRendering.IPathObject.prototype.svgPath;
/**
* The renderer's constant provider.
* @type {!Blockly.blockRendering.ConstantProvider}
*/
Blockly.blockRendering.IPathObject.prototype.constants;
/**
* The primary path of the block.
* @type {!Blockly.Theme.BlockStyle}
*/
Blockly.blockRendering.IPathObject.prototype.style;
/**
* Holds the cursors svg element when the cursor is attached to the block.
* This is null if there is no cursor on the block.
* @type {SVGElement}
*/
Blockly.blockRendering.IPathObject.prototype.cursorSvg;
/**
* Holds the markers svg element when the marker is attached to the block.
* This is null if there is no marker on the block.
* @type {SVGElement}
*/
Blockly.blockRendering.IPathObject.prototype.markerSvg;
/**
* Set the path generated by the renderer onto the respective SVG element.
* @param {string} pathString The path.
+1 -1
View File
@@ -581,7 +581,7 @@ Blockly.blockRendering.RenderInfo.prototype.addAlignmentPadding_ = function(row,
if (row.hasExternalInput || row.hasStatement) {
row.widthWithConnectedBlocks += missingSpace;
}
// Decide where the extra padding goes.
if (row.align == Blockly.ALIGN_LEFT) {
// Add padding to the end of the row.
+142 -101
View File
@@ -42,7 +42,7 @@ Blockly.blockRendering.MarkerSvg = function(workspace, constants, marker) {
/**
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @type {Blockly.WorkspaceSvg|Blockly.Field|Blockly.BlockSvg}
* @type {Blockly.IASTNodeLocationSvg}
* @private
*/
this.parent_ = null;
@@ -85,8 +85,7 @@ Blockly.blockRendering.MarkerSvg.MARKER_CLASS = 'blocklyMarker';
/**
* What we multiply the height by to get the height of the marker.
* Only used for the block and block connections.
* @type {number}
* @const
* @const {number}
*/
Blockly.blockRendering.MarkerSvg.HEIGHT_MULTIPLIER = 3 / 4;
@@ -98,6 +97,14 @@ Blockly.blockRendering.MarkerSvg.prototype.getSvgRoot = function() {
return this.svgGroup_;
};
/**
* Get the marker.
* @return {!Blockly.Marker} The marker to draw for.
*/
Blockly.blockRendering.MarkerSvg.prototype.getMarker = function() {
return this.marker_;
};
/**
* True if the marker should be drawn as a cursor, false otherwise.
* A cursor is drawn as a flashing line. A marker is drawn as a solid line.
@@ -123,15 +130,13 @@ Blockly.blockRendering.MarkerSvg.prototype.createDom = function() {
}, null);
this.createDomInternal_();
this.applyColour_();
return this.svgGroup_;
};
/**
* Attaches the SVG root of the marker to the SVG group of the parent.
* @param {!Blockly.WorkspaceSvg|!Blockly.Field|!Blockly.BlockSvg} newParent
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @param {!Blockly.IASTNodeLocationSvg} newParent The workspace, field, or
* block that the marker SVG element should be attached to.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.setParent_ = function(newParent) {
@@ -149,6 +154,63 @@ Blockly.blockRendering.MarkerSvg.prototype.setParent_ = function(newParent) {
this.parent_ = newParent;
};
/**
* Update the marker.
* @param {Blockly.ASTNode} oldNode The previous node the marker was on or null.
* @param {Blockly.ASTNode} curNode The node that we want to draw the marker for.
*/
Blockly.blockRendering.MarkerSvg.prototype.draw = function(oldNode, curNode) {
if (!curNode) {
this.hide();
return;
}
this.constants_ = this.workspace_.getRenderer().getConstants();
var defaultColour = this.isCursor() ? this.constants_.CURSOR_COLOUR :
this.constants_.MARKER_COLOUR;
this.colour_ = this.marker_.colour || defaultColour;
this.applyColour_(curNode);
this.showAtLocation_(curNode);
this.fireMarkerEvent_(oldNode, curNode);
// Ensures the marker will be visible immediately after the move.
var animate = this.currentMarkerSvg.childNodes[0];
if (animate !== undefined) {
animate.beginElement && animate.beginElement();
}
};
/**
* Update the marker's visible state based on the type of curNode..
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
var curNodeAsConnection =
/** @type {!Blockly.Connection} */ (curNode.getLocation());
if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
this.showWithBlock_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
this.showWithOutput_(curNode);
} else if (curNodeAsConnection.type == Blockly.INPUT_VALUE) {
this.showWithInput_(curNode);
} else if (curNodeAsConnection.type == Blockly.NEXT_STATEMENT) {
this.showWithNext_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.PREVIOUS) {
this.showWithPrevious_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.FIELD) {
this.showWithField_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
this.showWithCoordinates_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.STACK) {
this.showWithStack_(curNode);
}
};
/**************************
* Display
**************************/
@@ -156,13 +218,12 @@ Blockly.blockRendering.MarkerSvg.prototype.setParent_ = function(newParent) {
/**
* Show the marker as a combination of the previous connection and block,
* the output connection and block, or just the block.
* @param {Blockly.BlockSvg} block The block the marker is currently on.
* @protected
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithBlockPrevOutput_ = function(block) {
if (!block) {
return;
}
Blockly.blockRendering.MarkerSvg.prototype.showWithBlockPrevOutput_ = function(
curNode) {
var block = /** @type {!Blockly.BlockSvg} */ (curNode.getSourceBlock());
var width = block.width;
var height = block.height;
var markerHeight = height * Blockly.blockRendering.MarkerSvg.HEIGHT_MULTIPLIER;
@@ -177,18 +238,46 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithBlockPrevOutput_ = function(b
} else {
this.positionBlock_(width, markerOffset, markerHeight);
}
this.setParent_(block);
this.showCurrent_();
};
/**
* Show the visual representation of a workspace coordinate.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* Position and display the marker for a block.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithCoordinates_ = function(curNode) {
Blockly.blockRendering.MarkerSvg.prototype.showWithBlock_ = function(curNode) {
this.showWithBlockPrevOutput_(curNode);
};
/**
* Position and display the marker for a previous connection.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithPrevious_ = function(
curNode) {
this.showWithBlockPrevOutput_(curNode);
};
/**
* Position and display the marker for an output connection.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithOutput_ = function(curNode) {
this.showWithBlockPrevOutput_(curNode);
};
/**
* Position and display the marker for a workspace coordinate.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithCoordinates_ = function(
curNode) {
var wsCoordinate = curNode.getWsCoordinate();
var x = wsCoordinate.x;
var y = wsCoordinate.y;
@@ -203,9 +292,9 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithCoordinates_ = function(curNo
};
/**
* Show the visual representation of a field.
* Position and display the marker for a field.
* This is a box around the field.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithField_ = function(curNode) {
@@ -219,9 +308,9 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithField_ = function(curNode) {
};
/**
* Show the visual representation of an input.
* Position and display the marker for an input.
* This is a puzzle piece.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithInput_ = function(curNode) {
@@ -236,14 +325,16 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithInput_ = function(curNode) {
/**
* Show the visual representation of a next connection.
* Position and display the marker for a next connection.
* This is a horizontal line.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithNext_ = function(curNode) {
var connection = curNode.getLocation();
var targetBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var connection =
/** @type {!Blockly.RenderedConnection} */ (curNode.getLocation());
var targetBlock =
/** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var x = 0;
var y = connection.getOffsetInBlock().y;
var width = targetBlock.getHeightWidth().width;
@@ -256,9 +347,9 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithNext_ = function(curNode) {
};
/**
* Show the visual representation of a stack.
* Position and display the marker for a stack.
* This is a box with extra padding around the entire stack of blocks.
* @param {!Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithStack_ = function(curNode) {
@@ -305,7 +396,7 @@ Blockly.blockRendering.MarkerSvg.prototype.showCurrent_ = function() {
* @param {number} width The width of the block.
* @param {number} markerOffset The extra padding for around the block.
* @param {number} markerHeight The height of the marker.
* @private
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionBlock_ = function(
width, markerOffset, markerHeight) {
@@ -323,10 +414,12 @@ Blockly.blockRendering.MarkerSvg.prototype.positionBlock_ = function(
/**
* Position the marker for an input connection.
* Displays a filled in puzzle piece.
* @param {!Blockly.RenderedConnection} connection The connection to position marker around.
* @private
* @param {!Blockly.RenderedConnection} connection The connection to position
* marker around.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionInput_ = function(connection) {
Blockly.blockRendering.MarkerSvg.prototype.positionInput_ = function(
connection) {
var x = connection.getOffsetInBlock().x;
var y = connection.getOffsetInBlock().y;
@@ -335,7 +428,8 @@ Blockly.blockRendering.MarkerSvg.prototype.positionInput_ = function(connection)
this.markerInput_.setAttribute('d', path);
this.markerInput_.setAttribute('transform',
'translate(' + x + ',' + y + ')' + (this.workspace_.RTL ? ' scale(-1 1)' : ''));
'translate(' + x + ',' + y + ')' +
(this.workspace_.RTL ? ' scale(-1 1)' : ''));
this.currentMarkerSvg = this.markerInput_;
};
@@ -347,7 +441,8 @@ Blockly.blockRendering.MarkerSvg.prototype.positionInput_ = function(connection)
* @param {number} width The new width, in workspace units.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionLine_ = function(x, y, width) {
Blockly.blockRendering.MarkerSvg.prototype.positionLine_ = function(
x, y, width) {
this.markerSvgLine_.setAttribute('x', x);
this.markerSvgLine_.setAttribute('y', y);
this.markerSvgLine_.setAttribute('width', width);
@@ -360,7 +455,7 @@ Blockly.blockRendering.MarkerSvg.prototype.positionLine_ = function(x, y, width)
* @param {number} width The width of the block.
* @param {number} height The height of the block.
* @param {!Object} connectionShape The shape object for the connection.
* @private
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionOutput_ = function(
width, height, connectionShape) {
@@ -387,7 +482,7 @@ Blockly.blockRendering.MarkerSvg.prototype.positionOutput_ = function(
* @param {number} markerOffset The offset of the marker from around the block.
* @param {number} markerHeight The height of the marker.
* @param {!Object} connectionShape The shape object for the connection.
* @private
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionPrevious_ = function(
width, markerOffset, markerHeight, connectionShape) {
@@ -415,7 +510,8 @@ Blockly.blockRendering.MarkerSvg.prototype.positionPrevious_ = function(
* @param {number} height The new height, in workspace units.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.positionRect_ = function(x, y, width, height) {
Blockly.blockRendering.MarkerSvg.prototype.positionRect_ = function(
x, y, width, height) {
this.markerSvgRect_.setAttribute('x', x);
this.markerSvgRect_.setAttribute('y', y);
this.markerSvgRect_.setAttribute('width', width);
@@ -434,7 +530,6 @@ Blockly.blockRendering.MarkerSvg.prototype.flipRtl_ = function(markerSvg) {
/**
* Hide the marker.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.hide = function() {
this.markerSvgLine_.style.display = 'none';
@@ -443,64 +538,6 @@ Blockly.blockRendering.MarkerSvg.prototype.hide = function() {
this.markerBlock_.style.display = 'none';
};
/**
* Update the marker.
* @param {Blockly.ASTNode} oldNode The previous node the marker was on or null.
* @param {Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.draw = function(oldNode, curNode) {
if (!curNode) {
this.hide();
return;
}
this.constants_ = this.workspace_.getRenderer().getConstants();
var defaultColour = this.isCursor() ? this.constants_.CURSOR_COLOUR :
this.constants_.MARKER_COLOUR;
this.colour_ = this.marker_.colour || defaultColour;
this.applyColour_();
this.showAtLocation_(curNode);
this.firemarkerEvent_(oldNode, curNode);
// Ensures the marker will be visible immediately after the move.
var animate = this.currentMarkerSvg.childNodes[0];
if (animate !== undefined) {
animate.beginElement && animate.beginElement();
}
};
/**
* Update the marker's visible state based on the type of curNode..
* @param {Blockly.ASTNode} curNode The node that we want to draw the marker for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
var block = /** @type {Blockly.BlockSvg} */ (curNode.getLocation());
this.showWithBlockPrevOutput_(block);
} else if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
var outputBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(outputBlock);
} else if (curNode.getLocation().type == Blockly.INPUT_VALUE) {
this.showWithInput_(curNode);
} else if (curNode.getLocation().type == Blockly.NEXT_STATEMENT) {
this.showWithNext_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.PREVIOUS) {
var previousBlock = /** @type {Blockly.BlockSvg} */ (curNode.getLocation().getSourceBlock());
this.showWithBlockPrevOutput_(previousBlock);
} else if (curNode.getType() == Blockly.ASTNode.types.FIELD) {
this.showWithField_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
this.showWithCoordinates_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.STACK) {
this.showWithStack_(curNode);
}
};
/**
* Fire event for the marker or marker.
@@ -508,12 +545,14 @@ Blockly.blockRendering.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
* @param {!Blockly.ASTNode} curNode The new node the marker is currently on.
* @private
*/
Blockly.blockRendering.MarkerSvg.prototype.firemarkerEvent_ = function(oldNode, curNode) {
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 = curNode.getLocation().id;
event.workspaceId =
(/** @type {!Blockly.Workspace} */ (curNode.getLocation())).id;
}
Blockly.Events.fire(event);
};
@@ -555,7 +594,8 @@ Blockly.blockRendering.MarkerSvg.prototype.createDomInternal_ = function() {
'height': this.constants_.WS_CURSOR_HEIGHT
}, this.svgGroup_);
// A horizontal line used to represent a workspace coordinate or next connection.
// A horizontal line used to represent a workspace coordinate or next
// connection.
this.markerSvgLine_ = Blockly.utils.dom.createSvgElement('rect',
{
'width': this.constants_.CURSOR_WS_WIDTH,
@@ -609,9 +649,11 @@ Blockly.blockRendering.MarkerSvg.prototype.createDomInternal_ = function() {
/**
* Apply the marker's colour.
* @param {!Blockly.ASTNode} _curNode The node that we want to draw the marker
* for.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.applyColour_ = function() {
Blockly.blockRendering.MarkerSvg.prototype.applyColour_ = function(_curNode) {
this.markerSvgLine_.setAttribute('fill', this.colour_);
this.markerSvgRect_.setAttribute('stroke', this.colour_);
this.markerInput_.setAttribute('fill', this.colour_);
@@ -627,7 +669,6 @@ Blockly.blockRendering.MarkerSvg.prototype.applyColour_ = function() {
/**
* Dispose of this marker.
* @package
*/
Blockly.blockRendering.MarkerSvg.prototype.dispose = function() {
if (this.svgGroup_) {
+1 -1
View File
@@ -43,7 +43,7 @@ Blockly.blockRendering.PathObject = function(root, style, constants) {
/**
* The primary path of the block.
* @type {SVGElement}
* @type {!SVGElement}
* @package
*/
this.svgPath = Blockly.utils.dom.createSvgElement('path',
+2
View File
@@ -21,6 +21,7 @@ goog.require('Blockly.blockRendering.RenderInfo');
goog.require('Blockly.InsertionMarkerManager');
goog.requireType('Blockly.blockRendering.Debug');
goog.requireType('Blockly.IRegistrable');
/**
@@ -28,6 +29,7 @@ goog.requireType('Blockly.blockRendering.Debug');
* @param {string} name The renderer name.
* @package
* @constructor
* @implements {Blockly.IRegistrable}
*/
Blockly.blockRendering.Renderer = function(name) {
+1 -1
View File
@@ -57,7 +57,7 @@ Blockly.geras.ConstantProvider.prototype.getCSS_ = function(selector) {
selector + ' .blocklyInsertionMarker>.blocklyPathLight,',
selector + ' .blocklyInsertionMarker>.blocklyPathDark {',
'fill-opacity: ' + this.INSERTION_MARKER_OPACITY + ';',
'stroke: none',
'stroke: none;',
'}',
/* eslint-enable indent */
]);
+2 -2
View File
@@ -54,7 +54,7 @@ Blockly.geras.PathObject = function(root, style, constants) {
/**
* The primary path of the block.
* @type {SVGElement}
* @type {!SVGElement}
* @package
*/
this.svgPath = Blockly.utils.dom.createSvgElement('path',
@@ -122,7 +122,7 @@ Blockly.geras.PathObject.prototype.applyColour = function(block) {
this.svgPathDark.setAttribute('fill', this.colourDark);
Blockly.geras.PathObject.superClass_.applyColour.call(this, block);
this.svgPath.setAttribute('stroke', 'none');
};
+10 -11
View File
@@ -69,7 +69,7 @@ Blockly.zelos.ConstantProvider = function() {
* @override
*/
this.NOTCH_OFFSET_LEFT = 3 * this.GRID_UNIT;
/**
* @override
*/
@@ -259,7 +259,7 @@ Blockly.zelos.ConstantProvider = function() {
* @override
*/
this.FIELD_BORDER_RECT_X_PADDING = 2 * this.GRID_UNIT;
/**
* @override
*/
@@ -905,13 +905,12 @@ Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(selector) {
return [
/* eslint-disable indent */
// Text.
selector + ' .blocklyText, ',
selector + ' .blocklyText,',
selector + ' .blocklyFlyoutLabelText {',
'font-family: ' + this.FIELD_TEXT_FONTFAMILY + ';',
'font-size: ' + this.FIELD_TEXT_FONTSIZE + 'pt;',
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'font: ' + this.FIELD_TEXT_FONTWEIGHT + ' ' +
this.FIELD_TEXT_FONTSIZE + 'pt ' + this.FIELD_TEXT_FONTFAMILY + ';',
'}',
// Fields.
selector + ' .blocklyText {',
'fill: #fff;',
@@ -926,7 +925,7 @@ Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(selector) {
selector + ' .blocklyEditableText>g>text {',
'fill: #575E75;',
'}',
// Flyout labels.
selector + ' .blocklyFlyoutLabelText {',
'fill: #575E75;',
@@ -939,7 +938,7 @@ Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(selector) {
// Editable field hover.
selector + ' .blocklyDraggable:not(.blocklyDisabled)',
' .blocklyEditableText:not(.editing):hover>rect ,',
' .blocklyEditableText:not(.editing):hover>rect,',
selector + ' .blocklyDraggable:not(.blocklyDisabled)',
' .blocklyEditableText:not(.editing):hover>.blocklyPath {',
'stroke: #fff;',
@@ -952,7 +951,7 @@ Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(selector) {
'font-weight: ' + this.FIELD_TEXT_FONTWEIGHT + ';',
'color: #575E75;',
'}',
// Dropdown field.
selector + ' .blocklyDropdownText {',
'fill: #fff !important;',
@@ -979,7 +978,7 @@ Blockly.zelos.ConstantProvider.prototype.getCSS_ = function(selector) {
// Insertion marker.
selector + ' .blocklyInsertionMarker>.blocklyPath {',
'fill-opacity: ' + this.INSERTION_MARKER_OPACITY + ';',
'stroke: none',
'stroke: none;',
'}',
/* eslint-enable indent */
];
+21 -24
View File
@@ -32,11 +32,13 @@ Blockly.utils.object.inherits(Blockly.zelos.MarkerSvg,
Blockly.blockRendering.MarkerSvg);
/**
* @override
* Position and display the marker for an input or an output connection.
* @param {!Blockly.ASTNode} curNode The node to draw the marker for.
* @private
*/
Blockly.zelos.MarkerSvg.prototype.showWithInput_ = function(curNode) {
Blockly.zelos.MarkerSvg.prototype.showWithInputOutput_ = function(curNode) {
var block = /** @type {!Blockly.BlockSvg} */ (curNode.getSourceBlock());
var connection = curNode.getLocation();
var connection = /** @type {!Blockly.Connection} */ (curNode.getLocation());
var offsetInBlock = connection.getOffsetInBlock();
this.positionCircle_(offsetInBlock.x, offsetInBlock.y);
@@ -44,6 +46,20 @@ Blockly.zelos.MarkerSvg.prototype.showWithInput_ = function(curNode) {
this.showCurrent_();
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.showWithOutput_ = function(curNode) {
this.showWithInputOutput_(curNode);
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.showWithInput_ = function(curNode) {
this.showWithInputOutput_(curNode);
};
/**
* Draw a rectangle around the block.
* @param {!Blockly.ASTNode} curNode The current node of the marker.
@@ -72,25 +88,6 @@ Blockly.zelos.MarkerSvg.prototype.positionCircle_ = function(x, y) {
this.currentMarkerSvg = this.markerCircle_;
};
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
var handled = false;
if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
// Inputs and outputs are drawn the same.
this.showWithInput_(curNode);
handled = true;
} else if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
this.showWithBlock_(curNode);
handled = true;
}
if (!handled) {
Blockly.zelos.MarkerSvg.superClass_.showAtLocation_.call(this, curNode);
}
};
/**
* @override
*/
@@ -134,8 +131,8 @@ Blockly.zelos.MarkerSvg.prototype.createDomInternal_ = function() {
/**
* @override
*/
Blockly.zelos.MarkerSvg.prototype.applyColour_ = function() {
Blockly.zelos.MarkerSvg.superClass_.applyColour_.call(this);
Blockly.zelos.MarkerSvg.prototype.applyColour_ = function(curNode) {
Blockly.zelos.MarkerSvg.superClass_.applyColour_.call(this, curNode);
this.markerCircle_.setAttribute('fill', this.colour_);
this.markerCircle_.setAttribute('stroke', this.colour_);
-7
View File
@@ -63,13 +63,6 @@ goog.require('Blockly.FieldMultilineInput');
goog.require('Blockly.FieldNumber');
goog.require('Blockly.FieldVariable');
// If you'd like to include the date field in your build, you will also need to
// include the Closure library as a build dependency. You can do so by running:
// gulp build-compressed --closure-library
// Be sure to also include "google-closure-library" to your list of
// devDependencies.
// goog.require('Blockly.FieldDate');
// Blockly Renderers.
// At least one renderer is mandatory. Geras is the default one.
+62 -55
View File
@@ -17,6 +17,7 @@ goog.require('Blockly.Touch');
goog.require('Blockly.utils');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Metrics');
/**
@@ -26,7 +27,7 @@ goog.require('Blockly.utils.dom');
/**
* Class for a pair of scrollbars. Horizontal and vertical.
* @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to.
* @param {!Blockly.WorkspaceSvg} workspace Workspace to bind the scrollbars to.
* @constructor
*/
Blockly.ScrollbarPair = function(workspace) {
@@ -44,14 +45,14 @@ Blockly.ScrollbarPair = function(workspace) {
},
null);
Blockly.utils.dom.insertAfter(this.corner_, workspace.getBubbleCanvas());
};
/**
* Previously recorded metrics from the workspace.
* @type {Object}
* @private
*/
Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null;
/**
* Previously recorded metrics from the workspace.
* @type {?Blockly.utils.Metrics}
* @private
*/
this.oldHostMetrics_ = null;
};
/**
* Dispose of this pair of scrollbars.
@@ -117,12 +118,12 @@ Blockly.ScrollbarPair.prototype.resize = function() {
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
this.corner_.setAttribute('x', this.vScroll.position_.x);
this.corner_.setAttribute('x', this.vScroll.position.x);
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) {
this.corner_.setAttribute('y', this.hScroll.position_.y);
this.corner_.setAttribute('y', this.hScroll.position.y);
}
// Cache the current metrics to potentially short-cut the next resize event.
@@ -144,8 +145,8 @@ Blockly.ScrollbarPair.prototype.set = function(x, y) {
// Combining them speeds up rendering.
var xyRatio = {};
var hHandlePosition = x * this.hScroll.ratio_;
var vHandlePosition = y * this.vScroll.ratio_;
var hHandlePosition = x * this.hScroll.ratio;
var vHandlePosition = y * this.vScroll.ratio;
var hBarLength = this.hScroll.scrollViewSize_;
var vBarLength = this.vScroll.scrollViewSize_;
@@ -179,7 +180,7 @@ Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) {
* Class for a pure SVG scrollbar.
* This technique offers a scrollbar that is guaranteed to work, but may not
* look or behave like the system's scrollbars.
* @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to.
* @param {!Blockly.WorkspaceSvg} workspace Workspace to bind the scrollbar to.
* @param {boolean} horizontal True if horizontal, false if vertical.
* @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
* @param {string=} opt_class A class to be applied to this scrollbar.
@@ -191,6 +192,12 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) {
this.horizontal_ = horizontal;
this.oldHostMetrics_ = null;
/**
* @type {?number}
* @package
*/
this.ratio = null;
this.createDom_(opt_class);
/**
@@ -198,9 +205,9 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair, opt_class) {
* to the scrollbar's origin. This is usually relative to the injection div
* origin.
* @type {Blockly.utils.Coordinate}
* @private
* @package
*/
this.position_ = new Blockly.utils.Coordinate(0, 0);
this.position = new Blockly.utils.Coordinate(0, 0);
// Store the thickness in a temp variable for readability.
var scrollbarThickness = Blockly.Scrollbar.scrollbarThickness;
@@ -295,10 +302,10 @@ if (Blockly.Touch.TOUCH_ENABLED) {
}
/**
* @param {Object} first An object containing computed measurements of a
* workspace.
* @param {Object} second Another object containing computed measurements of a
* workspace.
* @param {Blockly.utils.Metrics} first An object containing computed
* measurements of a workspace.
* @param {Blockly.utils.Metrics} second Another object containing computed
* measurements of a workspace.
* @return {boolean} Whether the two sets of metrics are equivalent.
* @private
*/
@@ -392,23 +399,23 @@ Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) {
* scrollbar's origin. This sets the scrollbar's location within the workspace.
* @param {number} x The new x coordinate.
* @param {number} y The new y coordinate.
* @private
* @package
*/
Blockly.Scrollbar.prototype.setPosition_ = function(x, y) {
this.position_.x = x;
this.position_.y = y;
Blockly.Scrollbar.prototype.setPosition = function(x, y) {
this.position.x = x;
this.position.y = y;
var tempX = this.position_.x + this.origin_.x;
var tempY = this.position_.y + this.origin_.y;
var tempX = this.position.x + this.origin_.x;
var tempY = this.position.y + this.origin_.y;
var transform = 'translate(' + tempX + 'px,' + tempY + 'px)';
Blockly.utils.dom.setCssTransform(this.outerSvg_, transform);
};
/**
* Recalculate the scrollbar's location and its length.
* @param {Object=} opt_metrics A data structure of from the describing all the
* required dimensions. If not provided, it will be fetched from the host
* object.
* @param {Blockly.utils.Metrics=} opt_metrics A data structure of from the
* describing all the required dimensions. If not provided, it will be
* fetched from the host object.
*/
Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
// Determine the location, height and width of the host element.
@@ -450,8 +457,8 @@ Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
/**
* Recalculate a horizontal scrollbar's location and length.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
* @private
*/
Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
@@ -463,8 +470,8 @@ Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
/**
* Recalculate a horizontal scrollbar's location on the screen and path length.
* This should be called when the layout or size of the window has changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
var viewSize = hostMetrics.viewWidth - 1;
@@ -482,7 +489,7 @@ Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
// Horizontal toolbar should always be just above the bottom of the workspace.
var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
Blockly.Scrollbar.scrollbarThickness - 0.5;
this.setPosition_(xCoordinate, yCoordinate);
this.setPosition(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
@@ -492,8 +499,8 @@ Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
/**
* Recalculate a horizontal scrollbar's location within its path and length.
* This should be called when the contents of the workspace have changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
if (!this.pair_) {
@@ -503,24 +510,24 @@ Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth);
}
this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth;
if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
isNaN(this.ratio_)) {
this.ratio_ = 0;
this.ratio = this.scrollViewSize_ / hostMetrics.contentWidth;
if (this.ratio == -Infinity || this.ratio == Infinity ||
isNaN(this.ratio)) {
this.ratio = 0;
}
var handleLength = hostMetrics.viewWidth * this.ratio_;
var handleLength = hostMetrics.viewWidth * this.ratio;
this.setHandleLength_(Math.max(0, handleLength));
var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) *
this.ratio_;
this.ratio;
this.setHandlePosition(this.constrainHandle_(handlePosition));
};
/**
* Recalculate a vertical scrollbar's location and length.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
* @private
*/
Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
@@ -532,8 +539,8 @@ Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
/**
* Recalculate a vertical scrollbar's location on the screen and path length.
* This should be called when the layout or size of the window has changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
var viewSize = hostMetrics.viewHeight - 1;
@@ -549,7 +556,7 @@ Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
Blockly.Scrollbar.scrollbarThickness - 1;
}
var yCoordinate = hostMetrics.absoluteTop + 0.5;
this.setPosition_(xCoordinate, yCoordinate);
this.setPosition(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
@@ -559,8 +566,8 @@ Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
/**
* Recalculate a vertical scrollbar's location within its path and length.
* This should be called when the contents of the workspace have changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @param {!Blockly.utils.Metrics} hostMetrics A data structure describing all
* the required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
if (!this.pair_) {
@@ -568,17 +575,17 @@ Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight);
}
this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight;
if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
isNaN(this.ratio_)) {
this.ratio_ = 0;
this.ratio = this.scrollViewSize_ / hostMetrics.contentHeight;
if (this.ratio == -Infinity || this.ratio == Infinity ||
isNaN(this.ratio)) {
this.ratio = 0;
}
var handleLength = hostMetrics.viewHeight * this.ratio_;
var handleLength = hostMetrics.viewHeight * this.ratio;
this.setHandleLength_(Math.max(0, handleLength));
var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) *
this.ratio_;
this.ratio;
this.setHandlePosition(this.constrainHandle_(handlePosition));
};
@@ -844,7 +851,7 @@ Blockly.Scrollbar.prototype.onScroll_ = function() {
* scrollbar handle.
*/
Blockly.Scrollbar.prototype.set = function(value) {
this.setHandlePosition(this.constrainHandle_(value * this.ratio_));
this.setHandlePosition(this.constrainHandle_(value * this.ratio));
this.onScroll_();
};
+33 -23
View File
@@ -11,6 +11,7 @@
goog.provide('Blockly.Theme');
goog.require('Blockly.registry');
goog.require('Blockly.utils');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.object');
@@ -73,6 +74,9 @@ Blockly.Theme = function(name, opt_blockStyles, opt_categoryStyles,
* @package
*/
this.startHats = null;
// Register the theme by name.
Blockly.registry.register(Blockly.registry.Type.THEME, name, this);
};
/**
@@ -97,22 +101,22 @@ Blockly.Theme.CategoryStyle;
/**
* A component style.
* @typedef {{
* workspaceBackgroundColour:string?,
* toolboxBackgroundColour:string?,
* toolboxForegroundColour:string?,
* flyoutBackgroundColour:string?,
* flyoutForegroundColour:string?,
* flyoutOpacity:number?,
* scrollbarColour:string?,
* scrollbarOpacity:number?,
* insertionMarkerColour:string?,
* insertionMarkerOpacity:number?,
* markerColour:string?,
* cursorColour:string?,
* selectedGlowColour:string?,
* selectedGlowOpacity:number?,
* replacementGlowColour:string?,
* replacementGlowOpacity:number?
* workspaceBackgroundColour:?string,
* toolboxBackgroundColour:?string,
* toolboxForegroundColour:?string,
* flyoutBackgroundColour:?string,
* flyoutForegroundColour:?string,
* flyoutOpacity:?number,
* scrollbarColour:?string,
* scrollbarOpacity:?number,
* insertionMarkerColour:?string,
* insertionMarkerOpacity:?number,
* markerColour:?string,
* cursorColour:?string,
* selectedGlowColour:?string,
* selectedGlowOpacity:?number,
* replacementGlowColour:?string,
* replacementGlowOpacity:?number
* }}
*/
Blockly.Theme.ComponentStyle;
@@ -120,9 +124,9 @@ Blockly.Theme.ComponentStyle;
/**
* A font style.
* @typedef {{
* family:string?,
* weight:string?,
* size:number?
* family:?string,
* weight:?string,
* size:?number
* }}
*/
Blockly.Theme.FontStyle;
@@ -206,11 +210,16 @@ Blockly.Theme.prototype.setStartHats = function(startHats) {
Blockly.Theme.defineTheme = function(name, themeObj) {
var theme = new Blockly.Theme(name);
var base = themeObj['base'];
if (base && base instanceof Blockly.Theme) {
Blockly.utils.object.deepMerge(theme, base);
theme.name = name;
if (base) {
if (typeof base == "string") {
base = Blockly.registry.getObject(Blockly.registry.Type.THEME, base);
}
if (base instanceof Blockly.Theme) {
Blockly.utils.object.deepMerge(theme, base);
theme.name = name;
}
}
Blockly.utils.object.deepMerge(theme.blockStyles,
themeObj['blockStyles']);
Blockly.utils.object.deepMerge(theme.categoryStyles,
@@ -222,5 +231,6 @@ Blockly.Theme.defineTheme = function(name, themeObj) {
if (themeObj['startHats'] != null) {
theme.startHats = themeObj['startHats'];
}
return theme;
};
+207 -129
View File
@@ -16,6 +16,7 @@ goog.require('Blockly.Css');
goog.require('Blockly.Events');
goog.require('Blockly.Events.Ui');
goog.require('Blockly.navigation');
goog.require('Blockly.registry');
goog.require('Blockly.Touch');
goog.require('Blockly.tree.TreeControl');
goog.require('Blockly.tree.TreeNode');
@@ -25,6 +26,12 @@ goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.toolbox');
goog.requireType('Blockly.IBlocklyActionable');
goog.requireType('Blockly.IDeleteArea');
goog.requireType('Blockly.IStyleable');
goog.requireType('Blockly.IToolbox');
/**
@@ -33,6 +40,10 @@ goog.require('Blockly.utils.Rect');
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new
* blocks.
* @constructor
* @implements {Blockly.IBlocklyActionable}
* @implements {Blockly.IDeleteArea}
* @implements {Blockly.IStyleable}
* @implements {Blockly.IToolbox}
*/
Blockly.Toolbox = function(workspace) {
/**
@@ -106,34 +117,27 @@ Blockly.Toolbox = function(workspace) {
* @private
*/
this.flyout_ = null;
/**
* Width of the toolbox, which changes only in vertical layout.
* @type {number}
*/
this.width = 0;
/**
* Height of the toolbox, which changes only in horizontal layout.
* @type {number}
*/
this.height = 0;
/**
* The TreeNode most recently selected.
* @type {Blockly.tree.BaseNode}
* @private
*/
this.lastCategory_ = null;
};
/**
* Width of the toolbox, which changes only in vertical layout.
* @type {number}
*/
Blockly.Toolbox.prototype.width = 0;
/**
* Height of the toolbox, which changes only in horizontal layout.
* @type {number}
*/
Blockly.Toolbox.prototype.height = 0;
/**
* The SVG group currently selected.
* @type {SVGGElement}
* @private
*/
Blockly.Toolbox.prototype.selectedOption_ = null;
/**
* The tree node most recently selected.
* @type {Blockly.tree.BaseNode}
* @private
*/
Blockly.Toolbox.prototype.lastCategory_ = null;
/**
* Initializes the toolbox.
* @throws {Error} If missing a require for both `Blockly.HorizontalFlyout` and
@@ -179,7 +183,7 @@ Blockly.Toolbox.prototype.init = function() {
'rendererOverrides': workspace.options.rendererOverrides
}));
workspaceOptions.toolboxPosition = workspace.options.toolboxPosition;
if (workspace.horizontalLayout) {
if (!Blockly.HorizontalFlyout) {
throw Error('Missing require for Blockly.HorizontalFlyout');
@@ -195,23 +199,23 @@ Blockly.Toolbox.prototype.init = function() {
throw Error('One of Blockly.VerticalFlyout or Blockly.Horizontal must be' +
'required.');
}
// Insert the flyout after the workspace.
Blockly.utils.dom.insertAfter(this.flyout_.createDom('svg'), svg);
this.flyout_.init(workspace);
this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif';
this.config_['cssCollapsedFolderIcon'] =
'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr');
this.renderTree(workspace.options.languageTree);
this.render(workspace.options.languageTree);
};
/**
* Fill the toolbox with categories and blocks.
* @param {Node} languageTree DOM tree of blocks.
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef Array holding objects
* containing information on the contents of the toolbox.
* @package
*/
Blockly.Toolbox.prototype.renderTree = function(languageTree) {
Blockly.Toolbox.prototype.render = function(toolboxDef) {
if (this.tree_) {
this.tree_.dispose(); // Delete any existing content.
this.lastCategory_ = null;
@@ -223,13 +227,12 @@ Blockly.Toolbox.prototype.renderTree = function(languageTree) {
tree.onBeforeSelected(this.handleBeforeTreeSelected_);
tree.onAfterSelected(this.handleAfterTreeSelected_);
var openNode = null;
if (languageTree) {
this.tree_.blocks = [];
if (toolboxDef) {
this.tree_.contents = [];
this.hasColours_ = false;
openNode = this.syncTrees_(
languageTree, this.tree_, this.workspace_.options.pathToMedia);
openNode = this.createTree_(toolboxDef, this.tree_);
if (this.tree_.blocks.length) {
if (this.tree_.contents.length) {
throw Error('Toolbox cannot have both blocks and categories ' +
'in the root level.');
}
@@ -252,6 +255,156 @@ Blockly.Toolbox.prototype.renderTree = function(languageTree) {
}
};
/**
* Create the toolbox tree.
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef List of objects
* holding information on toolbox contents.
* @param {!Blockly.tree.BaseNode} treeOut The output tree for the toolbox. Due
* to the recursive nature of this function, treeOut can be either the root of
* the tree (Blockly.tree.TreeControl) or a child node of the tree
* (Blockly.tree.TreeNode). These nodes are built from the toolboxDef.
* @return {Blockly.tree.BaseNode} The TreeNode to expand when the toolbox is
* first loaded (or null).
* @private
*/
Blockly.Toolbox.prototype.createTree_ = function(toolboxDef, treeOut) {
var openNode = null;
var lastElement = null;
if (!toolboxDef) {
return null;
}
for (var i = 0, childIn; (childIn = toolboxDef[i]); i++) {
switch (childIn['kind'].toUpperCase()) {
case 'CATEGORY':
var categoryInfo = /** @type {Blockly.utils.toolbox.Category} */ (childIn);
openNode = this.addCategory_(categoryInfo, treeOut) || openNode;
lastElement = childIn;
break;
case 'SEP':
var separatorInfo = /** @type {Blockly.utils.toolbox.Separator} */ (childIn);
lastElement = this.addSeparator_(separatorInfo, treeOut, lastElement) || lastElement;
break;
case 'BLOCK':
case 'SHADOW':
case 'LABEL':
case 'BUTTON':
treeOut.contents.push(childIn);
lastElement = childIn;
break;
}
}
return openNode;
};
/**
* Add a category to the toolbox tree.
* @param {!Blockly.utils.toolbox.Category} categoryInfo The object holding
* information on the category.
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
* object built from the childNodes.
* @return {Blockly.tree.BaseNode} TreeNode to open at startup (or null).
* @private
*/
Blockly.Toolbox.prototype.addCategory_ = function(categoryInfo, treeOut) {
var openNode = null;
// Decode the category name for any potential message references
// (eg. `%{BKY_CATEGORY_NAME_LOGIC}`).
var categoryName = Blockly.utils.replaceMessageReferences(categoryInfo['name']);
// Create and add the tree node for the category.
var childOut = this.tree_.createNode(categoryName);
childOut.onSizeChanged(this.handleNodeSizeChanged_);
childOut.contents = [];
treeOut.add(childOut);
var custom = categoryInfo['custom'];
if (custom) {
// Variables and procedures are special dynamic categories.
childOut.contents = custom;
} else {
openNode = this.createTree_(categoryInfo['contents'], childOut) || openNode;
}
this.setColourOrStyle_(categoryInfo, childOut, categoryName);
openNode = this.setExpanded_(categoryInfo, childOut) || openNode;
return openNode;
};
/**
* Add either the colour or the style for a category.
* @param {!Blockly.utils.toolbox.Category} categoryInfo The object holding
* information on the category.
* @param {!Blockly.tree.TreeNode} childOut The TreeNode for a category.
* @param {string} categoryName The name of the category.
* @private
*/
Blockly.Toolbox.prototype.setColourOrStyle_ = function(
categoryInfo, childOut, categoryName) {
var styleName = categoryInfo['categorystyle'];
var colour = categoryInfo['colour'];
if (colour && styleName) {
childOut.hexColour = '';
console.warn('Toolbox category "' + categoryName +
'" must not have both a style and a colour');
} else if (styleName) {
this.setColourFromStyle_(styleName, childOut, categoryName);
} else {
this.setColour_(colour, childOut, categoryName);
}
};
/**
* Add a separator to the toolbox tree if it is between categories. Otherwise,
* add the separator to the list of contents.
* @param {!Blockly.utils.toolbox.Separator} separatorInfo The object holding
* information on the separator.
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
* object built from the childNodes.
* @param {Object} lastElement The last element to be added to the tree.
* @return {Object} The last element to be added to the tree, or
* null.
* @private
*/
Blockly.Toolbox.prototype.addSeparator_ = function(
separatorInfo, treeOut, lastElement) {
if (lastElement && lastElement['kind'].toUpperCase() == 'CATEGORY') {
// Separator between two categories.
// <sep></sep>
treeOut.add(new Blockly.Toolbox.TreeSeparator(
/** @type {!Blockly.tree.BaseNode.Config} */
(this.treeSeparatorConfig_)));
} else {
// Otherwise add to contents array.
treeOut.contents.push(separatorInfo);
return separatorInfo;
}
return null;
};
/**
* Checks whether a node should be expanded, and expands if necessary.
* @param {!Blockly.utils.toolbox.Category} categoryInfo The child to expand.
* @param {!Blockly.tree.TreeNode} childOut The TreeNode created from childIn.
* @return {Blockly.tree.BaseNode} TreeNode to open at startup (or null).
* @private
*/
Blockly.Toolbox.prototype.setExpanded_ = function(categoryInfo, childOut) {
var openNode = null;
if (categoryInfo['expanded'] == 'true') {
if (childOut.contents.length) {
// This is a category that directly contains blocks.
// After the tree is rendered, open this category and show flyout.
openNode = childOut;
}
childOut.setExpanded(true);
} else {
childOut.setExpanded(false);
}
return openNode;
};
/**
* Handle the before tree item selected action.
* @param {Blockly.tree.BaseNode} node The newly selected node.
@@ -283,8 +436,8 @@ Blockly.Toolbox.prototype.handleBeforeTreeSelected_ = function(node) {
*/
Blockly.Toolbox.prototype.handleAfterTreeSelected_ = function(
oldNode, newNode) {
if (newNode && newNode.blocks && newNode.blocks.length) {
this.flyout_.show(newNode.blocks);
if (newNode && newNode.contents && newNode.contents.length) {
this.flyout_.show(newNode.contents);
// Scroll the flyout to the top if the category has changed.
if (this.lastCategory_ != newNode) {
this.flyout_.scrollToStart();
@@ -358,6 +511,14 @@ Blockly.Toolbox.prototype.dispose = function() {
this.lastCategory_ = null;
};
/**
* Toggles the visibility of the toolbox.
* @param {boolean} isVisible True if toolbox should be visible.
*/
Blockly.Toolbox.prototype.setVisible = function(isVisible) {
this.HtmlDiv.style.display = isVisible ? 'block' : 'none';
};
/**
* Get the width of the toolbox.
* @return {number} The width of the toolbox.
@@ -414,91 +575,6 @@ Blockly.Toolbox.prototype.position = function() {
this.flyout_.position();
};
/**
* Sync trees of the toolbox.
* @param {!Node} treeIn DOM tree of blocks.
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
* object built from treeIn.
* @param {string} pathToMedia The path to the Blockly media directory.
* @return {Blockly.tree.BaseNode} Tree node to open at startup (or null).
* @private
*/
Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
var openNode = null;
var lastElement = null;
for (var i = 0, childIn; (childIn = treeIn.childNodes[i]); i++) {
if (!childIn.tagName) {
// Skip over text.
continue;
}
switch (childIn.tagName.toUpperCase()) {
case 'CATEGORY':
// Decode the category name for any potential message references
// (eg. `%{BKY_CATEGORY_NAME_LOGIC}`).
var categoryName = Blockly.utils.replaceMessageReferences(
childIn.getAttribute('name'));
var childOut = this.tree_.createNode(categoryName);
childOut.onSizeChanged(this.handleNodeSizeChanged_);
childOut.blocks = [];
treeOut.add(childOut);
var custom = childIn.getAttribute('custom');
if (custom) {
// Variables and procedures are special dynamic categories.
childOut.blocks = custom;
} else {
var newOpenNode = this.syncTrees_(childIn, childOut, pathToMedia);
if (newOpenNode) {
openNode = newOpenNode;
}
}
var styleName = childIn.getAttribute('categorystyle');
var colour = childIn.getAttribute('colour');
if (colour && styleName) {
childOut.hexColour = '';
console.warn('Toolbox category "' + categoryName +
'" can not have both a style and a colour');
} else if (styleName) {
this.setColourFromStyle_(styleName, childOut, categoryName);
} else {
this.setColour_(colour, childOut, categoryName);
}
if (childIn.getAttribute('expanded') == 'true') {
if (childOut.blocks.length) {
// This is a category that directly contains blocks.
// After the tree is rendered, open this category and show flyout.
openNode = childOut;
}
childOut.setExpanded(true);
} else {
childOut.setExpanded(false);
}
lastElement = childIn;
break;
case 'SEP':
if (lastElement && lastElement.tagName.toUpperCase() == 'CATEGORY') {
// Separator between two categories.
// <sep></sep>
treeOut.add(new Blockly.Toolbox.TreeSeparator(
/** @type {!Blockly.tree.BaseNode.Config} */
(this.treeSeparatorConfig_)));
break;
}
// Otherwise falls through.
case 'BLOCK':
case 'SHADOW':
case 'LABEL':
case 'BUTTON':
treeOut.blocks.push(childIn);
lastElement = childIn;
break;
}
}
return openNode;
};
/**
* Sets the colour on the category.
* @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB string,
@@ -581,7 +657,7 @@ Blockly.Toolbox.prototype.updateColourFromTheme_ = function(opt_tree) {
* Updates the category colours and background colour of selected categories.
* @package
*/
Blockly.Toolbox.prototype.updateColourFromTheme = function() {
Blockly.Toolbox.prototype.refreshTheme = function() {
var tree = this.tree_;
if (tree) {
this.updateColourFromTheme_(tree);
@@ -697,8 +773,8 @@ Blockly.Toolbox.prototype.getClientRect = function() {
*/
Blockly.Toolbox.prototype.refreshSelection = function() {
var selectedItem = this.tree_.getSelectedItem();
if (selectedItem && selectedItem.blocks) {
this.flyout_.show(selectedItem.blocks);
if (selectedItem && selectedItem.contents) {
this.flyout_.show(selectedItem.contents);
}
};
@@ -835,8 +911,7 @@ Blockly.Css.register([
'.blocklyTreeLabel {',
'cursor: default;',
'font-family: sans-serif;',
'font-size: 16px;',
'font: 16px sans-serif;',
'padding: 0 3px;',
'vertical-align: middle;',
'}',
@@ -850,3 +925,6 @@ Blockly.Css.register([
'}'
/* eslint-enable indent */
]);
Blockly.registry.register(Blockly.registry.Type.TOOLBOX,
Blockly.registry.DEFAULT, Blockly.Toolbox);
+3
View File
@@ -17,11 +17,14 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IDeleteArea');
/**
* Class for a trash can.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to sit in.
* @constructor
* @implements {Blockly.IDeleteArea}
*/
Blockly.Trashcan = function(workspace) {
/**
-57
View File
@@ -1,57 +0,0 @@
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utility methods for working with the Closure menu
* (goog.ui.menu).
* @author fenichel@google.com (Rachel Fenichel)
*/
'use strict';
/**
* @name Blockly.utils.uiMenu
* @namespace
*/
goog.provide('Blockly.utils.uiMenu');
goog.require('Blockly.utils.style');
/**
* Get the size of a rendered goog.ui.Menu.
* @param {!Blockly.Menu} menu The menu to measure.
* @return {!Blockly.utils.Size} Object with width and height properties.
* @package
*/
Blockly.utils.uiMenu.getSize = function(menu) {
var menuDom = menu.getElement();
var menuSize = Blockly.utils.style.getSize(/** @type {!Element} */ (menuDom));
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
return menuSize;
};
/**
* Adjust the bounding boxes used to position the widget div to deal with RTL
* goog.ui.Menu positioning. In RTL mode the menu renders down and to the left
* of its start point, instead of down and to the right. Adjusting all of the
* bounding boxes accordingly allows us to use the same code for all widgets.
* This function in-place modifies the provided bounding boxes.
* @param {!Object} viewportBBox The bounding rectangle of the current viewport,
* in window coordinates.
* @param {!Object} anchorBBox The bounding rectangle of the anchor, in window
* coordinates.
* @param {!Blockly.utils.Size} menuSize The size of the menu that is inside the
* widget div, in window coordinates.
* @package
*/
Blockly.utils.uiMenu.adjustBBoxesForRTL = function(viewportBBox, anchorBBox,
menuSize) {
anchorBBox.left += menuSize.width;
anchorBBox.right += menuSize.width;
viewportBBox.left += menuSize.width;
viewportBBox.right += menuSize.width;
};
+9 -8
View File
@@ -23,6 +23,7 @@ goog.require('Blockly.constants');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.Coordinate');
goog.require('Blockly.utils.global');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.string');
goog.require('Blockly.utils.style');
goog.require('Blockly.utils.userAgent');
@@ -496,19 +497,19 @@ Blockly.utils.runAfterPageLoad = function(fn) {
/**
* Get the position of the current viewport in window coordinates. This takes
* scroll into account.
* @return {!Object} An object containing window width, height, and scroll
* position in window coordinates.
* @return {!Blockly.utils.Rect} An object containing window width, height, and
* scroll position in window coordinates.
* @package
*/
Blockly.utils.getViewportBBox = function() {
// Pixels, in window coordinates.
var scrollOffset = Blockly.utils.style.getViewportPageOffset();
return {
right: document.documentElement.clientWidth + scrollOffset.x,
bottom: document.documentElement.clientHeight + scrollOffset.y,
top: scrollOffset.y,
left: scrollOffset.x
};
return new Blockly.utils.Rect(
scrollOffset.y,
document.documentElement.clientHeight + scrollOffset.y,
scrollOffset.x,
document.documentElement.clientWidth + scrollOffset.x
);
};
/**
+3
View File
@@ -86,6 +86,9 @@ Blockly.utils.aria.State = {
// Value: integer.
COLCOUNT: 'colcount',
// ARIA state for a disabled item. Value: one of {true, false}.
DISABLED: 'disabled',
// ARIA state for setting whether the element like a tree node is expanded.
// Value: one of {true, false, undefined}.
EXPANDED: 'expanded',
+6 -6
View File
@@ -44,7 +44,7 @@ Blockly.utils.dom.XLINK_NS = 'http://www.w3.org/1999/xlink';
* https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
* @enum {number}
*/
Blockly.utils.dom.Node = {
Blockly.utils.dom.NodeType = {
ELEMENT_NODE: 1,
TEXT_NODE: 3,
COMMENT_NODE: 8,
@@ -76,10 +76,10 @@ Blockly.utils.dom.canvasContext_ = null;
* Helper method for creating SVG elements.
* @param {string} name Element's tag name.
* @param {!Object} attrs Dictionary of attribute names and values.
* @param {Element} parent Optional parent on which to append the element.
* @param {Element=} opt_parent Optional parent on which to append the element.
* @return {!SVGElement} Newly created SVG element.
*/
Blockly.utils.dom.createSvgElement = function(name, attrs, parent) {
Blockly.utils.dom.createSvgElement = function(name, attrs, opt_parent) {
var e = /** @type {!SVGElement} */
(document.createElementNS(Blockly.utils.dom.SVG_NS, name));
for (var key in attrs) {
@@ -91,8 +91,8 @@ Blockly.utils.dom.createSvgElement = function(name, attrs, parent) {
if (document.body.runtimeStyle) { // Indicates presence of IE-only attr.
e.runtimeStyle = e.currentStyle = e.style;
}
if (parent) {
parent.appendChild(e);
if (opt_parent) {
opt_parent.appendChild(e);
}
return e;
};
@@ -192,7 +192,7 @@ Blockly.utils.dom.insertAfter = function(newNode, refNode) {
*/
Blockly.utils.dom.containsNode = function(parent, descendant) {
return !!(parent.compareDocumentPosition(descendant) &
Blockly.utils.dom.Node.DOCUMENT_POSITION_CONTAINED_BY);
Blockly.utils.dom.NodeType.DOCUMENT_POSITION_CONTAINED_BY);
};
/**
+1 -1
View File
@@ -29,5 +29,5 @@ Blockly.utils.IdGenerator.nextId_ = 0;
* @return {string} The next unique identifier.
*/
Blockly.utils.IdGenerator.getNextUniqueId = function() {
return 'blockly:' + (Blockly.utils.IdGenerator.nextId_++).toString(36);
return 'blockly-' + (Blockly.utils.IdGenerator.nextId_++).toString(36);
};
+124
View File
@@ -0,0 +1,124 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Workspace metrics definitions.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.utils.Metrics');
/**
* @record
*/
Blockly.utils.Metrics = function() {};
/**
* Height of the visible portion of the workspace.
* @type {number}
*/
Blockly.utils.Metrics.prototype.viewHeight;
/**
* Width of the visible portion of the workspace.
* @type {number}
*/
Blockly.utils.Metrics.prototype.viewWidth;
/**
* Height of the content.
* @type {number}
*/
Blockly.utils.Metrics.prototype.contentHeight;
/**
* Width of the content.
* @type {number}
*/
Blockly.utils.Metrics.prototype.contentWidth;
/**
* Top-edge of the visible portion of the workspace, relative to the workspace
* origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.viewTop;
/**
* Left-edge of the visible portion of the workspace, relative to the workspace
* origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.viewLeft;
/**
* Top-edge of the content, relative to the workspace origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.contentTop;
/**
* Left-edge of the content relative to the workspace origin.
* @type {number}
*/
Blockly.utils.Metrics.prototype.contentLeft;
/**
* Top-edge of the visible portion of the workspace, relative to the blocklyDiv.
* @type {number}
*/
Blockly.utils.Metrics.prototype.absoluteTop;
/**
* Left-edge of the visible portion of the workspace, relative to the
* blocklyDiv.
* @type {number}
*/
Blockly.utils.Metrics.prototype.absoluteLeft;
/**
* Height of the Blockly div (the view + the toolbox, simple of otherwise).
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.svgHeight;
/**
* Width of the Blockly div (the view + the toolbox, simple or otherwise).
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.svgWidth;
/**
* Width of the toolbox, if it exists. Otherwise zero.
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.toolboxWidth;
/**
* Height of the toolbox, if it exists. Otherwise zero.
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.toolboxHeight;
/**
* Top, bottom, left or right. Use TOOLBOX_AT constants to compare.
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.toolboxPosition;
/**
* Width of the flyout if it is always open. Otherwise zero.
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.flyoutWidth;
/**
* Height of the flyout if it is always open. Otherwise zero.
* @type {number|undefined}
*/
Blockly.utils.Metrics.prototype.flyoutHeight;
+1 -1
View File
@@ -45,7 +45,7 @@ Blockly.utils.object.mixin = function(target, source) {
*/
Blockly.utils.object.deepMerge = function(target, source) {
for (var x in source) {
if (typeof source[x] === 'object') {
if (source[x] != null && typeof source[x] === 'object') {
target[x] = Blockly.utils.object.deepMerge(
target[x] || Object.create(null), source[x]);
} else {
+169
View File
@@ -0,0 +1,169 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Utility functions for the toolbox and flyout.
* @author aschmiedt@google.com (Abby Schmiedt)
*/
'use strict';
goog.provide('Blockly.utils.toolbox');
/**
* The information needed to create a block in the toolbox.
* @typedef {{
* kind:string,
* blockxml:(?string|Node),
* type: ?string,
* gap: (?string|?number),
* disabled: (?string|?boolean)
* }}
*/
Blockly.utils.toolbox.Block;
/**
* The information needed to create a separator in the toolbox.
* @typedef {{
* kind:string,
* gap:?number
* }}
*/
Blockly.utils.toolbox.Separator;
/**
* The information needed to create a button in the toolbox.
* @typedef {{
* kind:string,
* text:string,
* callbackkey:string
* }}
*/
Blockly.utils.toolbox.Button;
/**
* The information needed to create a label in the toolbox.
* @typedef {{
* kind:string,
* text:string
* }}
*/
Blockly.utils.toolbox.Label;
/**
* The information needed to create a category in the toolbox.
* @typedef {{
* kind:string,
* name:string,
* categorystyle:?string,
* colour:?string,
* contents:Array.<Blockly.utils.toolbox.Toolbox>
* }}
*/
Blockly.utils.toolbox.Category;
/**
* Any information that can be used to create an item in the toolbox.
* @typedef {Blockly.utils.toolbox.Block|
* Blockly.utils.toolbox.Separator|
* Blockly.utils.toolbox.Button|
* Blockly.utils.toolbox.Label|
* Blockly.utils.toolbox.Category}
*/
Blockly.utils.toolbox.Toolbox;
/**
* All of the different types that can create a toolbox.
* @typedef {Node|
* NodeList|
* Array.<Blockly.utils.toolbox.Toolbox>|
* Array.<Node>}
*/
Blockly.utils.toolbox.ToolboxDefinition;
/**
* Parse the provided toolbox definition into a consistent format.
* @param {Blockly.utils.toolbox.ToolboxDefinition} toolboxDef The definition of the
* toolbox in one of its many forms.
* @return {Array.<Blockly.utils.toolbox.Toolbox>} Array of JSON holding
* information on toolbox contents.
* @package
*/
Blockly.utils.toolbox.convertToolboxToJSON = function(toolboxDef) {
if (!toolboxDef) {
return null;
}
// If it is an array of JSON, then it is already in the correct format.
if (Array.isArray(toolboxDef) && toolboxDef.length && !(toolboxDef[0].nodeType)) {
if (Blockly.utils.toolbox.hasCategories(toolboxDef)) {
// TODO: Remove after #3985 has been looked into.
console.warn('Due to some performance issues, defining a toolbox using' +
'JSON is not ready yet. Please define your toolbox using xml.');
}
return /** @type {!Array.<Blockly.utils.toolbox.Toolbox>} */ (toolboxDef);
}
return Blockly.utils.toolbox.toolboxXmlToJson_(toolboxDef);
};
/**
* Convert the xml for a toolbox to JSON.
* @param {!NodeList|!Node|!Array.<Node>} toolboxDef The
* definition of the toolbox in one of its many forms.
* @return {!Array.<Blockly.utils.toolbox.Toolbox>} A list of objects in the
* toolbox.
* @private
*/
Blockly.utils.toolbox.toolboxXmlToJson_ = function(toolboxDef) {
var arr = [];
// If it is a node it will have children.
var childNodes = toolboxDef.childNodes;
if (!childNodes) {
// Otherwise the toolboxDef is an array or collection.
childNodes = toolboxDef;
}
for (var i = 0, child; (child = childNodes[i]); i++) {
if (!child.tagName) {
continue;
}
var obj = {};
var tagName = child.tagName.toUpperCase();
obj['kind'] = tagName;
// Store the xml for a block
if (tagName == 'BLOCK') {
obj['blockxml'] = child;
} else if (tagName == 'CATEGORY') {
// Get the contents of a category
obj['contents'] = Blockly.utils.toolbox.toolboxXmlToJson_(child);
}
// Add xml attributes to object
for (var j = 0; j < child.attributes.length; j++) {
var attr = child.attributes[j];
obj[attr.nodeName] = attr.value;
}
arr.push(obj);
}
return arr;
};
/**
* Whether or not the toolbox definition has categories or not.
* @param {Node|Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef The definition
* of the toolbox. Either in xml or JSON.
* @return {boolean} True if the toolbox has categories.
* @package
*/
Blockly.utils.toolbox.hasCategories = function(toolboxDef) {
if (Array.isArray(toolboxDef)) {
// Search for categories
return !!(toolboxDef.length && toolboxDef[0]['kind'].toUpperCase() == 'CATEGORY');
} else {
return !!(toolboxDef && toolboxDef.getElementsByTagName('category').length);
}
};
+39
View File
@@ -20,6 +20,45 @@ goog.provide('Blockly.utils.userAgent');
goog.require('Blockly.utils.global');
/** @const {boolean} */
Blockly.utils.userAgent.IE;
/** @const {boolean} */
Blockly.utils.userAgent.EDGE;
/** @const {boolean} */
Blockly.utils.userAgent.JAVA_FX;
/** @const {boolean} */
Blockly.utils.userAgent.CHROME;
/** @const {boolean} */
Blockly.utils.userAgent.WEBKIT;
/** @const {boolean} */
Blockly.utils.userAgent.GECKO;
/** @const {boolean} */
Blockly.utils.userAgent.ANDROID;
/** @const {boolean} */
Blockly.utils.userAgent.IPAD;
/** @const {boolean} */
Blockly.utils.userAgent.IPOD;
/** @const {boolean} */
Blockly.utils.userAgent.IPHONE;
/** @const {boolean} */
Blockly.utils.userAgent.MAC;
/** @const {boolean} */
Blockly.utils.userAgent.TABLET;
/** @const {boolean} */
Blockly.utils.userAgent.MOBILE;
(function(raw) {
Blockly.utils.userAgent.raw = raw;
var rawUpper = Blockly.utils.userAgent.raw.toUpperCase();
+1 -1
View File
@@ -378,7 +378,7 @@ Blockly.VariableMap.prototype.getAllVariables = function() {
/**
* Returns all of the variable names of all types.
* @return {!Array<string>} All of the variable names of all types.
* @return {!Array.<string>} All of the variable names of all types.
*/
Blockly.VariableMap.prototype.getAllVariableNames = function() {
var allNames = [];
+6 -5
View File
@@ -217,7 +217,7 @@ Blockly.Variables.generateUniqueName = function(workspace) {
* will try to generate single letter names in the range a -> z (skip l). It
* will start with the character passed to startChar.
* @param {string} startChar The character to start the search at.
* @param {!Array<string>} usedNames A list of all of the used names.
* @param {!Array.<string>} usedNames A list of all of the used names.
* @return {string} A unique name that is not present in the usedNames array.
*/
Blockly.Variables.generateUniqueNameFromOptions = function(startChar, usedNames) {
@@ -279,7 +279,7 @@ Blockly.Variables.createVariableButtonHandler = function(
function(text) {
if (text) {
var existing =
Blockly.Variables.nameUsedWithAnyType_(text, workspace);
Blockly.Variables.nameUsedWithAnyType(text, workspace);
if (existing) {
if (existing.type == type) {
var msg = Blockly.Msg['VARIABLE_ALREADY_EXISTS'].replace(
@@ -327,7 +327,9 @@ Blockly.Variables.createVariable =
Blockly.Variables.createVariableButtonHandler;
/**
* Rename a variable with the given workspace, variableType, and oldName.
* Opens a prompt that allows the user to enter a new name for a variable.
* Triggers a rename if the new name is valid. Or re-prompts if there is a
* collision.
* @param {!Blockly.Workspace} workspace The workspace on which to rename the
* variable.
* @param {Blockly.VariableModel} variable Variable to rename.
@@ -424,9 +426,8 @@ Blockly.Variables.nameUsedWithOtherType_ = function(name, type, workspace) {
* variable.
* @return {Blockly.VariableModel} The variable with the given name,
* or null if none was found.
* @private
*/
Blockly.Variables.nameUsedWithAnyType_ = function(name, workspace) {
Blockly.Variables.nameUsedWithAnyType = function(name, workspace) {
var allVariables = workspace.getVariableMap().getAllVariables();
name = name.toLowerCase();
+1 -11
View File
@@ -42,7 +42,7 @@ Blockly.Warning.prototype.collapseHidden = false;
/**
* Draw the warning icon.
* @param {!Element} group The icon group.
* @private
* @protected
*/
Blockly.Warning.prototype.drawIcon_ = function(group) {
// Triangle with rounded corners.
@@ -154,16 +154,6 @@ Blockly.Warning.prototype.disposeBubble = function() {
this.paragraphElement_ = null;
};
/**
* Bring the warning to the top of the stack when clicked on.
* @param {!Event} _e Mouse up event.
* @private
*/
Blockly.Warning.prototype.bodyFocus_ = function(_e) {
this.bubble_.promote();
};
/**
* Set this warning's text.
* @param {string} text Warning text (or '' to delete). This supports

Some files were not shown because too many files have changed in this diff Show More