refactor: convert some files to es6 classes (#5861)

* refactor: convert utils/coordinate.js to ES6 class

* refactor: convert utils/rect.js to ES6 class

* refactor: convert utils/size.js to ES6 class

* refactor: convert block_drag_surface.js to ES6 class

* refactor: convert block_dragger.js to ES6 class

* refactor: convert bubble_dragger.js to ES6 class

* chore: declare bubble property in the constructor

* refactor: convert bubble.js to ES6 class

* chore: clang-format

* chore(lint): lint and format
This commit is contained in:
Rachel Fenichel
2022-01-07 14:49:49 -08:00
committed by GitHub
parent df2eafb8dd
commit 9d8eeb30f6
7 changed files with 1867 additions and 1813 deletions

View File

@@ -35,233 +35,235 @@ const {Svg} = goog.require('Blockly.utils.Svg');
/**
* Class for a drag surface for the currently dragged block. This is a separate
* SVG that contains only the currently moving block, or nothing.
* @param {!Element} container Containing element.
* @constructor
* @alias Blockly.BlockDragSurfaceSvg
*/
const BlockDragSurfaceSvg = function(container) {
const BlockDragSurfaceSvg = class {
/**
* The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom.
* @type {?SVGElement}
* @private
* @param {!Element} container Containing element.
* @alias Blockly.BlockDragSurfaceSvg
*/
this.SVG_ = null;
constructor(container) {
/**
* The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom.
* @type {?SVGElement}
* @private
*/
this.SVG_ = null;
/**
* This is where blocks live while they are being dragged if the drag surface
* is enabled.
* @type {?SVGElement}
* @private
*/
this.dragGroup_ = null;
/**
* This is where blocks live while they are being dragged if the drag
* surface is enabled.
* @type {?SVGElement}
* @private
*/
this.dragGroup_ = null;
/**
* Containing HTML element; parent of the workspace and the drag surface.
* @type {!Element}
* @private
*/
this.container_ = container;
/**
* Containing HTML element; parent of the workspace and the drag surface.
* @type {!Element}
* @private
*/
this.container_ = container;
/**
* Cached value for the scale of the drag surface.
* Used to set/get the correct translation during and after a drag.
* @type {number}
* @private
*/
this.scale_ = 1;
/**
* Cached value for the scale of the drag surface.
* Used to set/get the correct translation during and after a drag.
* @type {number}
* @private
*/
this.scale_ = 1;
/**
* Cached value for the translation of the drag surface.
* This translation is in pixel units, because the scale is applied to the
* drag group rather than the top-level SVG.
* @type {?Coordinate}
* @private
*/
this.surfaceXY_ = null;
/**
* Cached value for the translation of the drag surface.
* This translation is in pixel units, because the scale is applied to the
* drag group rather than the top-level SVG.
* @type {?Coordinate}
* @private
*/
this.surfaceXY_ = null;
/**
* Cached value for the translation of the child drag surface in pixel units.
* Since the child drag surface tracks the translation of the workspace this
* is ultimately the translation of the workspace.
* @type {!Coordinate}
* @private
*/
this.childSurfaceXY_ = new Coordinate(0, 0);
/**
* Cached value for the translation of the child drag surface in pixel
* units. Since the child drag surface tracks the translation of the
* workspace this is ultimately the translation of the workspace.
* @type {!Coordinate}
* @private
*/
this.childSurfaceXY_ = new Coordinate(0, 0);
this.createDom();
};
/**
* Create the drag surface and inject it into the container.
*/
BlockDragSurfaceSvg.prototype.createDom = function() {
if (this.SVG_) {
return; // Already created.
this.createDom();
}
this.SVG_ = dom.createSvgElement(
Svg.SVG, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklyBlockDragSurface',
},
this.container_);
this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.SVG_);
};
/**
* Set the SVG blocks on the drag surface's group and show the surface.
* Only one block group should be on the drag surface at a time.
* @param {!SVGElement} blocks Block or group of blocks to place on the drag
* surface.
*/
BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
if (this.dragGroup_.childNodes.length) {
throw Error('Already dragging a block.');
}
// appendChild removes the blocks from the previous parent
this.dragGroup_.appendChild(blocks);
this.SVG_.style.display = 'block';
this.surfaceXY_ = new Coordinate(0, 0);
};
/**
* Translate and scale the entire drag surface group to the given position, to
* keep in sync with the workspace.
* @param {number} x X translation in pixel coordinates.
* @param {number} y Y translation in pixel coordinates.
* @param {number} scale Scale of the group.
*/
BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
this.scale_ = scale;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
const fixedX = x.toFixed(0);
const fixedY = y.toFixed(0);
this.childSurfaceXY_.x = parseInt(fixedX, 10);
this.childSurfaceXY_.y = parseInt(fixedY, 10);
this.dragGroup_.setAttribute(
'transform',
'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')');
};
/**
* Translate the drag surface's SVG based on its internal state.
* @private
*/
BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() {
let x = this.surfaceXY_.x;
let y = this.surfaceXY_.y;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
x = x.toFixed(0);
y = y.toFixed(0);
this.SVG_.style.display = 'block';
dom.setCssTransform(this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
};
/**
* Translates the entire surface by a relative offset.
* @param {number} deltaX Horizontal offset in pixel units.
* @param {number} deltaY Vertical offset in pixel units.
*/
BlockDragSurfaceSvg.prototype.translateBy = function(deltaX, deltaY) {
const x = this.surfaceXY_.x + deltaX;
const y = this.surfaceXY_.y + deltaY;
this.surfaceXY_ = new Coordinate(x, y);
this.translateSurfaceInternal_();
};
/**
* Translate the entire drag surface during a drag.
* We translate the drag surface instead of the blocks inside the surface
* so that the browser avoids repainting the SVG.
* Because of this, the drag coordinates must be adjusted by scale.
* @param {number} x X translation for the entire surface.
* @param {number} y Y translation for the entire surface.
*/
BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_);
this.translateSurfaceInternal_();
};
/**
* Reports the surface translation in scaled workspace coordinates.
* Use this when finishing a drag to return blocks to the correct position.
* @return {!Coordinate} Current translation of the surface.
*/
BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
};
/**
* Provide a reference to the drag group (primarily for
* BlockSvg.getRelativeToSurfaceXY).
* @return {?SVGElement} Drag surface group element.
*/
BlockDragSurfaceSvg.prototype.getGroup = function() {
return this.dragGroup_;
};
/**
* Returns the SVG drag surface.
* @returns {?SVGElement} The SVG drag surface.
*/
BlockDragSurfaceSvg.prototype.getSvgRoot = function() {
return this.SVG_;
};
/**
* Get the current blocks on the drag surface, if any (primarily
* for BlockSvg.getRelativeToSurfaceXY).
* @return {?Element} Drag surface block DOM element, or null if no blocks
* exist.
*/
BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
return /** @type {Element} */ (this.dragGroup_.firstChild);
};
/**
* Gets the translation of the child block surface
* This surface is in charge of keeping track of how much the workspace has
* moved.
* @return {!Coordinate} The amount the workspace has been moved.
*/
BlockDragSurfaceSvg.prototype.getWsTranslation = function() {
// Returning a copy so the coordinate can not be changed outside this class.
return this.childSurfaceXY_.clone();
};
/**
* Clear the group and hide the surface; move the blocks off onto the provided
* element.
* If the block is being deleted it doesn't need to go back to the original
* surface, since it would be removed immediately during dispose.
* @param {Element=} opt_newSurface Surface the dragging blocks should be moved
* to, or null if the blocks should be removed from this surface without
* being moved to a different surface.
*/
BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) {
const currentBlockElement = this.getCurrentBlock();
if (currentBlockElement) {
if (opt_newSurface) {
// appendChild removes the node from this.dragGroup_
opt_newSurface.appendChild(currentBlockElement);
} else {
this.dragGroup_.removeChild(currentBlockElement);
/**
* Create the drag surface and inject it into the container.
*/
createDom() {
if (this.SVG_) {
return; // Already created.
}
this.SVG_ = dom.createSvgElement(
Svg.SVG, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklyBlockDragSurface',
},
this.container_);
this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.SVG_);
}
this.SVG_.style.display = 'none';
if (this.dragGroup_.childNodes.length) {
throw Error('Drag group was not cleared.');
/**
* Set the SVG blocks on the drag surface's group and show the surface.
* Only one block group should be on the drag surface at a time.
* @param {!SVGElement} blocks Block or group of blocks to place on the drag
* surface.
*/
setBlocksAndShow(blocks) {
if (this.dragGroup_.childNodes.length) {
throw Error('Already dragging a block.');
}
// appendChild removes the blocks from the previous parent
this.dragGroup_.appendChild(blocks);
this.SVG_.style.display = 'block';
this.surfaceXY_ = new Coordinate(0, 0);
}
/**
* Translate and scale the entire drag surface group to the given position, to
* keep in sync with the workspace.
* @param {number} x X translation in pixel coordinates.
* @param {number} y Y translation in pixel coordinates.
* @param {number} scale Scale of the group.
*/
translateAndScaleGroup(x, y, scale) {
this.scale_ = scale;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
const fixedX = x.toFixed(0);
const fixedY = y.toFixed(0);
this.childSurfaceXY_.x = parseInt(fixedX, 10);
this.childSurfaceXY_.y = parseInt(fixedY, 10);
this.dragGroup_.setAttribute(
'transform',
'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')');
}
/**
* Translate the drag surface's SVG based on its internal state.
* @private
*/
translateSurfaceInternal_() {
let x = this.surfaceXY_.x;
let y = this.surfaceXY_.y;
// This is a work-around to prevent a the blocks from rendering
// fuzzy while they are being dragged on the drag surface.
x = x.toFixed(0);
y = y.toFixed(0);
this.SVG_.style.display = 'block';
dom.setCssTransform(this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
}
/**
* Translates the entire surface by a relative offset.
* @param {number} deltaX Horizontal offset in pixel units.
* @param {number} deltaY Vertical offset in pixel units.
*/
translateBy(deltaX, deltaY) {
const x = this.surfaceXY_.x + deltaX;
const y = this.surfaceXY_.y + deltaY;
this.surfaceXY_ = new Coordinate(x, y);
this.translateSurfaceInternal_();
}
/**
* Translate the entire drag surface during a drag.
* We translate the drag surface instead of the blocks inside the surface
* so that the browser avoids repainting the SVG.
* Because of this, the drag coordinates must be adjusted by scale.
* @param {number} x X translation for the entire surface.
* @param {number} y Y translation for the entire surface.
*/
translateSurface(x, y) {
this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_);
this.translateSurfaceInternal_();
}
/**
* Reports the surface translation in scaled workspace coordinates.
* Use this when finishing a drag to return blocks to the correct position.
* @return {!Coordinate} Current translation of the surface.
*/
getSurfaceTranslation() {
const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
}
/**
* Provide a reference to the drag group (primarily for
* BlockSvg.getRelativeToSurfaceXY).
* @return {?SVGElement} Drag surface group element.
*/
getGroup() {
return this.dragGroup_;
}
/**
* Returns the SVG drag surface.
* @returns {?SVGElement} The SVG drag surface.
*/
getSvgRoot() {
return this.SVG_;
}
/**
* Get the current blocks on the drag surface, if any (primarily
* for BlockSvg.getRelativeToSurfaceXY).
* @return {?Element} Drag surface block DOM element, or null if no blocks
* exist.
*/
getCurrentBlock() {
return /** @type {Element} */ (this.dragGroup_.firstChild);
}
/**
* Gets the translation of the child block surface
* This surface is in charge of keeping track of how much the workspace has
* moved.
* @return {!Coordinate} The amount the workspace has been moved.
*/
getWsTranslation() {
// Returning a copy so the coordinate can not be changed outside this class.
return this.childSurfaceXY_.clone();
}
/**
* Clear the group and hide the surface; move the blocks off onto the provided
* element.
* If the block is being deleted it doesn't need to go back to the original
* surface, since it would be removed immediately during dispose.
* @param {Element=} opt_newSurface Surface the dragging blocks should be
* moved to, or null if the blocks should be removed from this surface
* without being moved to a different surface.
*/
clearAndHide(opt_newSurface) {
const currentBlockElement = this.getCurrentBlock();
if (currentBlockElement) {
if (opt_newSurface) {
// appendChild removes the node from this.dragGroup_
opt_newSurface.appendChild(currentBlockElement);
} else {
this.dragGroup_.removeChild(currentBlockElement);
}
}
this.SVG_.style.display = 'none';
if (this.dragGroup_.childNodes.length) {
throw Error('Drag group was not cleared.');
}
this.surfaceXY_ = null;
}
this.surfaceXY_ = null;
};
exports.BlockDragSurfaceSvg = BlockDragSurfaceSvg;

View File

@@ -40,76 +40,411 @@ goog.require('Blockly.Events.BlockMove');
/**
* Class for a block dragger. It moves blocks around the workspace when they
* are being dragged by a mouse or touch.
* @param {!BlockSvg} block The block to drag.
* @param {!WorkspaceSvg} workspace The workspace to drag on.
* @constructor
* @implements {IBlockDragger}
* @alias Blockly.BlockDragger
*/
const BlockDragger = function(block, workspace) {
const BlockDragger = class {
/**
* The top block in the stack that is being dragged.
* @type {!BlockSvg}
* @param {!BlockSvg} block The block to drag.
* @param {!WorkspaceSvg} workspace The workspace to drag on.
* @alias Blockly.BlockDragger
*/
constructor(block, workspace) {
/**
* The top block in the stack that is being dragged.
* @type {!BlockSvg}
* @protected
*/
this.draggingBlock_ = block;
/**
* The workspace on which the block is being dragged.
* @type {!WorkspaceSvg}
* @protected
*/
this.workspace_ = workspace;
/**
* Object that keeps track of connections on dragged blocks.
* @type {!InsertionMarkerManager}
* @protected
*/
this.draggedConnectionManager_ =
new InsertionMarkerManager(this.draggingBlock_);
/**
* Which drag area the mouse pointer is over, if any.
* @type {?IDragTarget}
* @private
*/
this.dragTarget_ = null;
/**
* Whether the block would be deleted if dropped immediately.
* @type {boolean}
* @protected
*/
this.wouldDeleteBlock_ = false;
/**
* The location of the top left corner of the dragging block at the
* beginning of the drag in workspace coordinates.
* @type {!Coordinate}
* @protected
*/
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
/**
* A list of all of the icons (comment, warning, and mutator) that are
* on this block and its descendants. Moving an icon moves the bubble that
* extends from it if that bubble is open.
* @type {Array<!Object>}
* @protected
*/
this.dragIconData_ = initIconData(block);
}
/**
* Sever all links from this object.
* @package
*/
dispose() {
this.dragIconData_.length = 0;
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
}
}
/**
* Start dragging a block. This includes moving it to the drag surface.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @public
*/
startDrag(currentDragDeltaXY, healStack) {
if (!eventUtils.getGroup()) {
eventUtils.setGroup(true);
}
this.fireDragStartEvent_();
// Mutators don't have the same type of z-ordering as the normal workspace
// during a drag. They have to rely on the order of the blocks in the SVG.
// For performance reasons that usually happens at the end of a drag,
// but do it at the beginning for mutators.
if (this.workspace_.isMutator) {
this.draggingBlock_.bringToFront();
}
// During a drag there may be a lot of rerenders, but not field changes.
// Turn the cache on so we don't do spurious remeasures during the drag.
dom.startTextWidthCache();
this.workspace_.setResizesEnabled(false);
blockAnimation.disconnectUiStop();
if (this.shouldDisconnect_(healStack)) {
this.disconnectBlock_(healStack, currentDragDeltaXY);
}
this.draggingBlock_.setDragging(true);
// For future consideration: we may be able to put moveToDragSurface inside
// the block dragger, which would also let the block not track the block
// drag surface.
this.draggingBlock_.moveToDragSurface();
}
/**
* Whether or not we should disconnect the block when a drag is started.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @return {boolean} True to disconnect the block, false otherwise.
* @protected
*/
this.draggingBlock_ = block;
shouldDisconnect_(healStack) {
return !!(
this.draggingBlock_.getParent() ||
(healStack && this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock()));
}
/**
* The workspace on which the block is being dragged.
* @type {!WorkspaceSvg}
* Disconnects the block and moves it to a new location.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @protected
*/
this.workspace_ = workspace;
disconnectBlock_(healStack, currentDragDeltaXY) {
this.draggingBlock_.unplug(healStack);
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.translate(newLoc.x, newLoc.y);
blockAnimation.disconnectUiEffect(this.draggingBlock_);
this.draggedConnectionManager_.updateAvailableConnections();
}
/**
* Object that keeps track of connections on dragged blocks.
* @type {!InsertionMarkerManager}
* Fire a UI event at the start of a block drag.
* @protected
*/
this.draggedConnectionManager_ =
new InsertionMarkerManager(this.draggingBlock_);
fireDragStartEvent_() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, true, this.draggingBlock_.getDescendants(false));
eventUtils.fire(event);
}
/**
* Which drag area the mouse pointer is over, if any.
* @type {?IDragTarget}
* @private
* Execute a step of block dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @public
*/
this.dragTarget_ = null;
drag(e, currentDragDeltaXY) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveDuringDrag(newLoc);
this.dragIcons_(delta);
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace_.getDragTarget(e);
this.draggedConnectionManager_.update(delta, this.dragTarget_);
const oldWouldDeleteBlock = this.wouldDeleteBlock_;
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
if (oldWouldDeleteBlock !== this.wouldDeleteBlock_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBlockDrag_();
}
// Call drag enter/exit/over after wouldDeleteBlock is called in
// InsertionMarkerManager.update.
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.draggingBlock_);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBlock_);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBlock_);
}
/**
* Whether the block would be deleted if dropped immediately.
* @type {boolean}
* Finish a block drag and put the block back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @public
*/
endDrag(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.drag(e, currentDragDeltaXY);
this.dragIconData_ = [];
this.fireDragEndEvent_();
dom.stopTextWidthCache();
blockAnimation.disconnectUiStop();
const preventMove = !!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
/** @type {Coordinate} */
let newLoc;
/** @type {Coordinate} */
let delta;
if (preventMove) {
newLoc = this.startXY_;
} else {
const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY);
delta = newValues.delta;
newLoc = newValues.newLocation;
}
this.draggingBlock_.moveOffDragSurface(newLoc);
if (this.dragTarget_) {
this.dragTarget_.onDrop(this.draggingBlock_);
}
const deleted = this.maybeDeleteBlock_();
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.setDragging(false);
if (delta) { // !preventMove
this.updateBlockAfterMove_(delta);
} else {
// Blocks dragged directly from a flyout may need to be bumped into
// bounds.
bumpObjects.bumpIntoBounds(
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_);
}
}
this.workspace_.setResizesEnabled(true);
eventUtils.setGroup(false);
}
/**
* Calculates the drag delta and new location values after a block is dragged.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the start of the drag, in pixel units.
* @return {{delta: !Coordinate, newLocation:
* !Coordinate}} New location after drag. delta is in
* workspace units. newLocation is the new coordinate where the block
* should end up.
* @protected
*/
this.wouldDeleteBlock_ = false;
getNewLocationAfterDrag_(currentDragDeltaXY) {
const newValues = {};
newValues.delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
newValues.newLocation = Coordinate.sum(this.startXY_, newValues.delta);
return newValues;
}
/**
* The location of the top left corner of the dragging block at the beginning
* of the drag in workspace coordinates.
* @type {!Coordinate}
* May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is
* not true, the block will not be deleted. This should be called at the end
* of a block drag.
* @return {boolean} True if the block was deleted.
* @protected
*/
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
maybeDeleteBlock_() {
if (this.wouldDeleteBlock_) {
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBlock_.dispose(false, true);
common.draggingConnections.length = 0;
return true;
}
return false;
}
/**
* A list of all of the icons (comment, warning, and mutator) that are
* on this block and its descendants. Moving an icon moves the bubble that
* extends from it if that bubble is open.
* @type {Array<!Object>}
* Updates the necessary information to place a block at a certain location.
* @param {!Coordinate} delta The change in location from where
* the block started the drag to where it ended the drag.
* @protected
*/
this.dragIconData_ = initIconData(block);
};
updateBlockAfterMove_(delta) {
this.draggingBlock_.moveConnections(delta.x, delta.y);
this.fireMoveEvent_();
if (this.draggedConnectionManager_.wouldConnectBlock()) {
// Applying connections also rerenders the relevant blocks.
this.draggedConnectionManager_.applyConnections();
} else {
this.draggingBlock_.render();
}
this.draggingBlock_.scheduleSnapAndBump();
}
/**
* Sever all links from this object.
* @package
*/
BlockDragger.prototype.dispose = function() {
this.dragIconData_.length = 0;
/**
* Fire a UI event at the end of a block drag.
* @protected
*/
fireDragEndEvent_() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, false, this.draggingBlock_.getDescendants(false));
eventUtils.fire(event);
}
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
/**
* Adds or removes the style of the cursor for the toolbox.
* This is what changes the cursor to display an x when a deletable block is
* held over the toolbox.
* @param {boolean} isEnd True if we are at the end of a drag, false
* otherwise.
* @protected
*/
updateToolboxStyle_(isEnd) {
const toolbox = this.workspace_.getToolbox();
if (toolbox) {
const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
if (isEnd && typeof toolbox.removeStyle === 'function') {
toolbox.removeStyle(style);
} else if (!isEnd && typeof toolbox.addStyle === 'function') {
toolbox.addStyle(style);
}
}
}
/**
* Fire a move event at the end of a block drag.
* @protected
*/
fireMoveEvent_() {
const event =
new (eventUtils.get(eventUtils.BLOCK_MOVE))(this.draggingBlock_);
event.oldCoordinate = this.startXY_;
event.recordNew();
eventUtils.fire(event);
}
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging block would be deleted if released immediately.
* @protected
*/
updateCursorDuringBlockDrag_() {
this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_);
}
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!Coordinate} pixelCoord A coordinate with x and y
* values in CSS pixel units.
* @return {!Coordinate} The input coordinate divided by the
* workspace scale.
* @protected
*/
pixelsToWorkspaceUnits_(pixelCoord) {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same
// as the scale on the parent workspace. Fix that for dragging.
const mainScale = this.workspace_.options.parentWorkspace.scale;
result.scale(1 / mainScale);
}
return result;
}
/**
* Move all of the icons connected to this drag.
* @param {!Coordinate} dxy How far to move the icons from their
* original positions, in workspace units.
* @protected
*/
dragIcons_(dxy) {
// Moving icons moves their associated bubbles.
for (let i = 0; i < this.dragIconData_.length; i++) {
const data = this.dragIconData_[i];
data.icon.setIconLocation(Coordinate.sum(data.location, dxy));
}
}
/**
* Get a list of the insertion markers that currently exist. Drags have 0, 1,
* or 2 insertion markers.
* @return {!Array<!BlockSvg>} A possibly empty list of insertion
* marker blocks.
* @public
*/
getInsertionMarkers() {
// No insertion markers with the old style of dragged connection managers.
if (this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers) {
return this.draggedConnectionManager_.getInsertionMarkers();
}
return [];
}
};
@@ -141,340 +476,6 @@ const initIconData = function(block) {
return dragIconData;
};
/**
* Start dragging a block. This includes moving it to the drag surface.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @public
*/
BlockDragger.prototype.startDrag = function(currentDragDeltaXY, healStack) {
if (!eventUtils.getGroup()) {
eventUtils.setGroup(true);
}
this.fireDragStartEvent_();
// Mutators don't have the same type of z-ordering as the normal workspace
// during a drag. They have to rely on the order of the blocks in the SVG.
// For performance reasons that usually happens at the end of a drag,
// but do it at the beginning for mutators.
if (this.workspace_.isMutator) {
this.draggingBlock_.bringToFront();
}
// During a drag there may be a lot of rerenders, but not field changes.
// Turn the cache on so we don't do spurious remeasures during the drag.
dom.startTextWidthCache();
this.workspace_.setResizesEnabled(false);
blockAnimation.disconnectUiStop();
if (this.shouldDisconnect_(healStack)) {
this.disconnectBlock_(healStack, currentDragDeltaXY);
}
this.draggingBlock_.setDragging(true);
// For future consideration: we may be able to put moveToDragSurface inside
// the block dragger, which would also let the block not track the block drag
// surface.
this.draggingBlock_.moveToDragSurface();
};
/**
* Whether or not we should disconnect the block when a drag is started.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @return {boolean} True to disconnect the block, false otherwise.
* @protected
*/
BlockDragger.prototype.shouldDisconnect_ = function(healStack) {
return !!(
this.draggingBlock_.getParent() ||
(healStack && this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock()));
};
/**
* Disconnects the block and moves it to a new location.
* @param {boolean} healStack Whether or not to heal the stack after
* disconnecting.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at mouse down, in pixel units.
* @protected
*/
BlockDragger.prototype.disconnectBlock_ = function(
healStack, currentDragDeltaXY) {
this.draggingBlock_.unplug(healStack);
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.translate(newLoc.x, newLoc.y);
blockAnimation.disconnectUiEffect(this.draggingBlock_);
this.draggedConnectionManager_.updateAvailableConnections();
};
/**
* Fire a UI event at the start of a block drag.
* @protected
*/
BlockDragger.prototype.fireDragStartEvent_ = function() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, true, this.draggingBlock_.getDescendants(false));
eventUtils.fire(event);
};
/**
* Execute a step of block dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @public
*/
BlockDragger.prototype.drag = function(e, currentDragDeltaXY) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveDuringDrag(newLoc);
this.dragIcons_(delta);
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace_.getDragTarget(e);
this.draggedConnectionManager_.update(delta, this.dragTarget_);
const oldWouldDeleteBlock = this.wouldDeleteBlock_;
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
if (oldWouldDeleteBlock !== this.wouldDeleteBlock_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBlockDrag_();
}
// Call drag enter/exit/over after wouldDeleteBlock is called in
// InsertionMarkerManager.update.
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.draggingBlock_);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBlock_);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBlock_);
};
/**
* Finish a block drag and put the block back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @public
*/
BlockDragger.prototype.endDrag = function(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.drag(e, currentDragDeltaXY);
this.dragIconData_ = [];
this.fireDragEndEvent_();
dom.stopTextWidthCache();
blockAnimation.disconnectUiStop();
const preventMove = !!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
/** @type {Coordinate} */
let newLoc;
/** @type {Coordinate} */
let delta;
if (preventMove) {
newLoc = this.startXY_;
} else {
const newValues = this.getNewLocationAfterDrag_(currentDragDeltaXY);
delta = newValues.delta;
newLoc = newValues.newLocation;
}
this.draggingBlock_.moveOffDragSurface(newLoc);
if (this.dragTarget_) {
this.dragTarget_.onDrop(this.draggingBlock_);
}
const deleted = this.maybeDeleteBlock_();
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.setDragging(false);
if (delta) { // !preventMove
this.updateBlockAfterMove_(delta);
} else {
// Blocks dragged directly from a flyout may need to be bumped into
// bounds.
bumpObjects.bumpIntoBounds(
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_);
}
}
this.workspace_.setResizesEnabled(true);
eventUtils.setGroup(false);
};
/**
* Calculates the drag delta and new location values after a block is dragged.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the start of the drag, in pixel units.
* @return {{delta: !Coordinate, newLocation:
* !Coordinate}} New location after drag. delta is in
* workspace units. newLocation is the new coordinate where the block should
* end up.
* @protected
*/
BlockDragger.prototype.getNewLocationAfterDrag_ = function(currentDragDeltaXY) {
const newValues = {};
newValues.delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
newValues.newLocation = Coordinate.sum(this.startXY_, newValues.delta);
return newValues;
};
/**
* May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is not
* true, the block will not be deleted. This should be called at the end of a
* block drag.
* @return {boolean} True if the block was deleted.
* @protected
*/
BlockDragger.prototype.maybeDeleteBlock_ = function() {
if (this.wouldDeleteBlock_) {
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBlock_.dispose(false, true);
common.draggingConnections.length = 0;
return true;
}
return false;
};
/**
* Updates the necessary information to place a block at a certain location.
* @param {!Coordinate} delta The change in location from where
* the block started the drag to where it ended the drag.
* @protected
*/
BlockDragger.prototype.updateBlockAfterMove_ = function(delta) {
this.draggingBlock_.moveConnections(delta.x, delta.y);
this.fireMoveEvent_();
if (this.draggedConnectionManager_.wouldConnectBlock()) {
// Applying connections also rerenders the relevant blocks.
this.draggedConnectionManager_.applyConnections();
} else {
this.draggingBlock_.render();
}
this.draggingBlock_.scheduleSnapAndBump();
};
/**
* Fire a UI event at the end of a block drag.
* @protected
*/
BlockDragger.prototype.fireDragEndEvent_ = function() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, false, this.draggingBlock_.getDescendants(false));
eventUtils.fire(event);
};
/**
* Adds or removes the style of the cursor for the toolbox.
* This is what changes the cursor to display an x when a deletable block is
* held over the toolbox.
* @param {boolean} isEnd True if we are at the end of a drag, false otherwise.
* @protected
*/
BlockDragger.prototype.updateToolboxStyle_ = function(isEnd) {
const toolbox = this.workspace_.getToolbox();
if (toolbox) {
const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
if (isEnd && typeof toolbox.removeStyle === 'function') {
toolbox.removeStyle(style);
} else if (!isEnd && typeof toolbox.addStyle === 'function') {
toolbox.addStyle(style);
}
}
};
/**
* Fire a move event at the end of a block drag.
* @protected
*/
BlockDragger.prototype.fireMoveEvent_ = function() {
const event =
new (eventUtils.get(eventUtils.BLOCK_MOVE))(this.draggingBlock_);
event.oldCoordinate = this.startXY_;
event.recordNew();
eventUtils.fire(event);
};
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging block would be deleted if released immediately.
* @protected
*/
BlockDragger.prototype.updateCursorDuringBlockDrag_ = function() {
this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_);
};
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!Coordinate} pixelCoord A coordinate with x and y
* values in CSS pixel units.
* @return {!Coordinate} The input coordinate divided by the
* workspace scale.
* @protected
*/
BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same as
// the scale on the parent workspace.
// Fix that for dragging.
const mainScale = this.workspace_.options.parentWorkspace.scale;
result.scale(1 / mainScale);
}
return result;
};
/**
* Move all of the icons connected to this drag.
* @param {!Coordinate} dxy How far to move the icons from their
* original positions, in workspace units.
* @protected
*/
BlockDragger.prototype.dragIcons_ = function(dxy) {
// Moving icons moves their associated bubbles.
for (let i = 0; i < this.dragIconData_.length; i++) {
const data = this.dragIconData_[i];
data.icon.setIconLocation(Coordinate.sum(data.location, dxy));
}
};
/**
* Get a list of the insertion markers that currently exist. Drags have 0, 1,
* or 2 insertion markers.
* @return {!Array<!BlockSvg>} A possibly empty list of insertion
* marker blocks.
* @public
*/
BlockDragger.prototype.getInsertionMarkers = function() {
// No insertion markers with the old style of dragged connection managers.
if (this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers) {
return this.draggedConnectionManager_.getInsertionMarkers();
}
return [];
};
registry.register(registry.Type.BLOCK_DRAGGER, registry.DEFAULT, BlockDragger);
exports.BlockDragger = BlockDragger;

File diff suppressed because it is too large Load Diff

View File

@@ -43,249 +43,253 @@ goog.require('Blockly.constants');
* Class for a bubble dragger. It moves things on the bubble canvas around the
* workspace when they are being dragged by a mouse or touch. These can be
* block comments, mutators, warnings, or workspace comments.
* @param {!IBubble} bubble The item on the bubble canvas to drag.
* @param {!WorkspaceSvg} workspace The workspace to drag on.
* @constructor
* @alias Blockly.BubbleDragger
*/
const BubbleDragger = function(bubble, workspace) {
const BubbleDragger = class {
/**
* The item on the bubble canvas that is being dragged.
* @type {!IBubble}
* @private
* @param {!IBubble} bubble The item on the bubble canvas to drag.
* @param {!WorkspaceSvg} workspace The workspace to drag on.
* @alias Blockly.BubbleDragger
*/
this.draggingBubble_ = bubble;
constructor(bubble, workspace) {
/**
* The item on the bubble canvas that is being dragged.
* @type {!IBubble}
* @private
*/
this.draggingBubble_ = bubble;
/**
* The workspace on which the bubble is being dragged.
* @type {!WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* The workspace on which the bubble is being dragged.
* @type {!WorkspaceSvg}
* @private
*/
this.workspace_ = workspace;
/**
* Which drag target the mouse pointer is over, if any.
* @type {?IDragTarget}
* @private
*/
this.dragTarget_ = null;
/**
* Which drag target the mouse pointer is over, if any.
* @type {?IDragTarget}
* @private
*/
this.dragTarget_ = null;
/**
* Whether the bubble would be deleted if dropped immediately.
* @type {boolean}
* @private
*/
this.wouldDeleteBubble_ = false;
/**
* Whether the bubble would be deleted if dropped immediately.
* @type {boolean}
* @private
*/
this.wouldDeleteBubble_ = false;
/**
* The location of the top left corner of the dragging bubble's body at the
* beginning of the drag, in workspace coordinates.
* @type {!Coordinate}
* @private
*/
this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY();
/**
* The location of the top left corner of the dragging bubble's body at the
* beginning of the drag, in workspace coordinates.
* @type {!Coordinate}
* @private
*/
this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY();
/**
* The drag surface to move bubbles to during a drag, or null if none should
* be used. Block dragging and bubble dragging use the same surface.
* @type {BlockDragSurfaceSvg}
* @private
*/
this.dragSurface_ =
svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ?
workspace.getBlockDragSurface() :
null;
};
/**
* Sever all links from this object.
* @package
* @suppress {checkTypes}
*/
BubbleDragger.prototype.dispose = function() {
this.draggingBubble_ = null;
this.workspace_ = null;
this.dragSurface_ = null;
};
/**
* Start dragging a bubble. This includes moving it to the drag surface.
* @package
*/
BubbleDragger.prototype.startBubbleDrag = function() {
if (!eventUtils.getGroup()) {
eventUtils.setGroup(true);
/**
* The drag surface to move bubbles to during a drag, or null if none should
* be used. Block dragging and bubble dragging use the same surface.
* @type {BlockDragSurfaceSvg}
* @private
*/
this.dragSurface_ =
svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ?
workspace.getBlockDragSurface() :
null;
}
this.workspace_.setResizesEnabled(false);
this.draggingBubble_.setAutoLayout(false);
if (this.dragSurface_) {
this.moveToDragSurface_();
/**
* Sever all links from this object.
* @package
* @suppress {checkTypes}
*/
dispose() {
this.draggingBubble_ = null;
this.workspace_ = null;
this.dragSurface_ = null;
}
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
};
/**
* Execute a step of bubble dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace_.getDragTarget(e);
const oldWouldDeleteBubble = this.wouldDeleteBubble_;
this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_);
if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBubbleDrag_();
}
// Call drag enter/exit/over after wouldDeleteBlock is called in shouldDelete_
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.draggingBubble_);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBubble_);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBubble_);
};
/**
* Whether ending the drag would delete the bubble.
* @param {?IDragTarget} dragTarget The drag target that the bubblee is
* currently over.
* @return {boolean} Whether dropping the bubble immediately would delete the
* block.
* @private
*/
BubbleDragger.prototype.shouldDelete_ = function(dragTarget) {
if (dragTarget) {
const componentManager = this.workspace_.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
if (isDeleteArea) {
return (/** @type {!IDeleteArea} */ (dragTarget))
.wouldDelete(this.draggingBubble_, false);
/**
* Start dragging a bubble. This includes moving it to the drag surface.
* @package
*/
startBubbleDrag() {
if (!eventUtils.getGroup()) {
eventUtils.setGroup(true);
}
}
return false;
};
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging bubble would be deleted if released immediately.
* @private
*/
BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() {
this.draggingBubble_.setDeleteStyle(this.wouldDeleteBubble_);
};
/**
* Finish a bubble drag and put the bubble back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
BubbleDragger.prototype.endBubbleDrag = function(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.dragBubble(e, currentDragDeltaXY);
const preventMove = this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBubble_);
let newLoc;
if (preventMove) {
newLoc = this.startXY_;
} else {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
newLoc = Coordinate.sum(this.startXY_, delta);
}
// Move the bubble to its final location.
this.draggingBubble_.moveTo(newLoc.x, newLoc.y);
if (this.dragTarget_) {
this.dragTarget_.onDrop(this.draggingBubble_);
}
if (this.wouldDeleteBubble_) {
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBubble_.dispose(false, true);
} else {
// Put everything back onto the bubble canvas.
this.workspace_.setResizesEnabled(false);
this.draggingBubble_.setAutoLayout(false);
if (this.dragSurface_) {
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
this.moveToDragSurface_();
}
if (this.draggingBubble_.setDragging) {
this.draggingBubble_.setDragging(false);
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
}
/**
* Execute a step of bubble dragging, based on the given event. Update the
* display accordingly.
* @param {!Event} e The most recent move event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
dragBubble(e, currentDragDeltaXY) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace_.getDragTarget(e);
const oldWouldDeleteBubble = this.wouldDeleteBubble_;
this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_);
if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBubbleDrag_();
}
this.fireMoveEvent_();
// Call drag enter/exit/over after wouldDeleteBlock is called in
// shouldDelete_
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.draggingBubble_);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBubble_);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBubble_);
}
this.workspace_.setResizesEnabled(true);
eventUtils.setGroup(false);
};
/**
* Fire a move event at the end of a bubble drag.
* @private
*/
BubbleDragger.prototype.fireMoveEvent_ = function() {
if (this.draggingBubble_.isComment) {
// TODO (adodson): Resolve build errors when requiring WorkspaceCommentSvg.
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
/** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_));
event.setOldCoordinate(this.startXY_);
event.recordNew();
eventUtils.fire(event);
/**
* Whether ending the drag would delete the bubble.
* @param {?IDragTarget} dragTarget The drag target that the bubblee is
* currently over.
* @return {boolean} Whether dropping the bubble immediately would delete the
* block.
* @private
*/
shouldDelete_(dragTarget) {
if (dragTarget) {
const componentManager = this.workspace_.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
if (isDeleteArea) {
return (/** @type {!IDeleteArea} */ (dragTarget))
.wouldDelete(this.draggingBubble_, false);
}
}
return false;
}
// TODO (fenichel): move events for comments.
return;
};
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!Coordinate} pixelCoord A coordinate with x and y
* values in CSS pixel units.
* @return {!Coordinate} The input coordinate divided by the
* workspace scale.
* @private
*/
BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same as
// the scale on the parent workspace.
// Fix that for dragging.
const mainScale = this.workspace_.options.parentWorkspace.scale;
result.scale(1 / mainScale);
/**
* Update the cursor (and possibly the trash can lid) to reflect whether the
* dragging bubble would be deleted if released immediately.
* @private
*/
updateCursorDuringBubbleDrag_() {
this.draggingBubble_.setDeleteStyle(this.wouldDeleteBubble_);
}
return result;
};
/**
* Move the bubble onto the drag surface at the beginning of a drag. Move the
* drag surface to preserve the apparent location of the bubble.
* @private
*/
BubbleDragger.prototype.moveToDragSurface_ = function() {
this.draggingBubble_.moveTo(0, 0);
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
// Execute the move on the top-level SVG component.
this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot());
/**
* Finish a bubble drag and put the bubble back on the workspace.
* @param {!Event} e The mouseup/touchend event.
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
* moved from the position at the start of the drag, in pixel units.
* @package
*/
endBubbleDrag(e, currentDragDeltaXY) {
// Make sure internal state is fresh.
this.dragBubble(e, currentDragDeltaXY);
const preventMove = this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBubble_);
let newLoc;
if (preventMove) {
newLoc = this.startXY_;
} else {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
newLoc = Coordinate.sum(this.startXY_, delta);
}
// Move the bubble to its final location.
this.draggingBubble_.moveTo(newLoc.x, newLoc.y);
if (this.dragTarget_) {
this.dragTarget_.onDrop(this.draggingBubble_);
}
if (this.wouldDeleteBubble_) {
// Fire a move event, so we know where to go back to for an undo.
this.fireMoveEvent_();
this.draggingBubble_.dispose(false, true);
} else {
// Put everything back onto the bubble canvas.
if (this.dragSurface_) {
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
}
if (this.draggingBubble_.setDragging) {
this.draggingBubble_.setDragging(false);
}
this.fireMoveEvent_();
}
this.workspace_.setResizesEnabled(true);
eventUtils.setGroup(false);
}
/**
* Fire a move event at the end of a bubble drag.
* @private
*/
fireMoveEvent_() {
if (this.draggingBubble_.isComment) {
// TODO (adodson): Resolve build errors when requiring
// WorkspaceCommentSvg.
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
/** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_));
event.setOldCoordinate(this.startXY_);
event.recordNew();
eventUtils.fire(event);
}
// TODO (fenichel): move events for comments.
return;
}
/**
* Convert a coordinate object from pixels to workspace units, including a
* correction for mutator workspaces.
* This function does not consider differing origins. It simply scales the
* input's x and y values.
* @param {!Coordinate} pixelCoord A coordinate with x and y
* values in CSS pixel units.
* @return {!Coordinate} The input coordinate divided by the
* workspace scale.
* @private
*/
pixelsToWorkspaceUnits_(pixelCoord) {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same
// as the scale on the parent workspace. Fix that for dragging.
const mainScale = this.workspace_.options.parentWorkspace.scale;
result.scale(1 / mainScale);
}
return result;
}
/**
* Move the bubble onto the drag surface at the beginning of a drag. Move the
* drag surface to preserve the apparent location of the bubble.
* @private
*/
moveToDragSurface_() {
this.draggingBubble_.moveTo(0, 0);
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
// Execute the move on the top-level SVG component.
this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot());
}
};
exports.BubbleDragger = BubbleDragger;

View File

@@ -21,116 +21,118 @@ goog.module('Blockly.utils.Coordinate');
/**
* Class for representing coordinates and positions.
* @param {number} x Left.
* @param {number} y Top.
* @struct
* @constructor
* @alias Blockly.utils.Coordinate
*/
const Coordinate = function(x, y) {
const Coordinate = class {
/**
* X-value
* @type {number}
* @param {number} x Left.
* @param {number} y Top.
* @alias Blockly.utils.Coordinate
*/
this.x = x;
constructor(x, y) {
/**
* X-value
* @type {number}
*/
this.x = x;
/**
* Y-value
* @type {number}
*/
this.y = y;
}
/**
* Y-value
* @type {number}
* Creates a new copy of this coordinate.
* @return {!Coordinate} A copy of this coordinate.
*/
this.y = y;
};
/**
* Compares coordinates for equality.
* @param {?Coordinate} a A Coordinate.
* @param {?Coordinate} b A Coordinate.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
Coordinate.equals = function(a, b) {
if (a === b) {
return true;
clone() {
return new Coordinate(this.x, this.y);
}
if (!a || !b) {
return false;
/**
* Scales this coordinate by the given scale factor.
* @param {number} s The scale factor to use for both x and y dimensions.
* @return {!Coordinate} This coordinate after scaling.
*/
scale(s) {
this.x *= s;
this.y *= s;
return this;
}
return a.x === b.x && a.y === b.y;
};
/**
* Returns the distance between two coordinates.
* @param {!Coordinate} a A Coordinate.
* @param {!Coordinate} b A Coordinate.
* @return {number} The distance between `a` and `b`.
*/
Coordinate.distance = function(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Translates this coordinate by the given offsets.
* respectively.
* @param {number} tx The value to translate x by.
* @param {number} ty The value to translate y by.
* @return {!Coordinate} This coordinate after translating.
*/
translate(tx, ty) {
this.x += tx;
this.y += ty;
return this;
}
/**
* Returns the magnitude of a coordinate.
* @param {!Coordinate} a A Coordinate.
* @return {number} The distance between the origin and `a`.
*/
Coordinate.magnitude = function(a) {
return Math.sqrt(a.x * a.x + a.y * a.y);
};
/**
* Compares coordinates for equality.
* @param {?Coordinate} a A Coordinate.
* @param {?Coordinate} b A Coordinate.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
static equals(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
return a.x === b.x && a.y === b.y;
}
/**
* Returns the difference between two coordinates as a new
* Coordinate.
* @param {!Coordinate|!SVGPoint} a An x/y coordinate.
* @param {!Coordinate|!SVGPoint} b An x/y coordinate.
* @return {!Coordinate} A Coordinate representing the difference
* between `a` and `b`.
*/
Coordinate.difference = function(a, b) {
return new Coordinate(a.x - b.x, a.y - b.y);
};
/**
* Returns the distance between two coordinates.
* @param {!Coordinate} a A Coordinate.
* @param {!Coordinate} b A Coordinate.
* @return {number} The distance between `a` and `b`.
*/
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Returns the sum of two coordinates as a new Coordinate.
* @param {!Coordinate|!SVGPoint} a An x/y coordinate.
* @param {!Coordinate|!SVGPoint} b An x/y coordinate.
* @return {!Coordinate} A Coordinate representing the sum of
* the two coordinates.
*/
Coordinate.sum = function(a, b) {
return new Coordinate(a.x + b.x, a.y + b.y);
};
/**
* Returns the magnitude of a coordinate.
* @param {!Coordinate} a A Coordinate.
* @return {number} The distance between the origin and `a`.
*/
static magnitude(a) {
return Math.sqrt(a.x * a.x + a.y * a.y);
}
/**
* Creates a new copy of this coordinate.
* @return {!Coordinate} A copy of this coordinate.
*/
Coordinate.prototype.clone = function() {
return new Coordinate(this.x, this.y);
};
/**
* Returns the difference between two coordinates as a new
* Coordinate.
* @param {!Coordinate|!SVGPoint} a An x/y coordinate.
* @param {!Coordinate|!SVGPoint} b An x/y coordinate.
* @return {!Coordinate} A Coordinate representing the difference
* between `a` and `b`.
*/
static difference(a, b) {
return new Coordinate(a.x - b.x, a.y - b.y);
}
/**
* Scales this coordinate by the given scale factor.
* @param {number} s The scale factor to use for both x and y dimensions.
* @return {!Coordinate} This coordinate after scaling.
*/
Coordinate.prototype.scale = function(s) {
this.x *= s;
this.y *= s;
return this;
};
/**
* Translates this coordinate by the given offsets.
* respectively.
* @param {number} tx The value to translate x by.
* @param {number} ty The value to translate y by.
* @return {!Coordinate} This coordinate after translating.
*/
Coordinate.prototype.translate = function(tx, ty) {
this.x += tx;
this.y += ty;
return this;
/**
* Returns the sum of two coordinates as a new Coordinate.
* @param {!Coordinate|!SVGPoint} a An x/y coordinate.
* @param {!Coordinate|!SVGPoint} b An x/y coordinate.
* @return {!Coordinate} A Coordinate representing the sum of
* the two coordinates.
*/
static sum(a, b) {
return new Coordinate(a.x + b.x, a.y + b.y);
}
};
exports.Coordinate = Coordinate;

View File

@@ -22,50 +22,54 @@ goog.module('Blockly.utils.Rect');
/**
* Class for representing rectangular regions.
* @param {number} top Top.
* @param {number} bottom Bottom.
* @param {number} left Left.
* @param {number} right Right.
* @struct
* @constructor
* @alias Blockly.utils.Rect
*/
const Rect = function(top, bottom, left, right) {
/** @type {number} */
this.top = top;
const Rect = class {
/**
* @param {number} top Top.
* @param {number} bottom Bottom.
* @param {number} left Left.
* @param {number} right Right.
* @struct
* @alias Blockly.utils.Rect
*/
constructor(top, bottom, left, right) {
/** @type {number} */
this.top = top;
/** @type {number} */
this.bottom = bottom;
/** @type {number} */
this.bottom = bottom;
/** @type {number} */
this.left = left;
/** @type {number} */
this.left = left;
/** @type {number} */
this.right = right;
};
/** @type {number} */
this.right = right;
}
/**
* Tests whether this rectangle contains a x/y coordinate.
*
* @param {number} x The x coordinate to test for containment.
* @param {number} y The y coordinate to test for containment.
* @return {boolean} Whether this rectangle contains given coordinate.
*/
Rect.prototype.contains = function(x, y) {
return x >= this.left && x <= this.right && y >= this.top && y <= this.bottom;
};
/**
* Tests whether this rectangle contains a x/y coordinate.
*
* @param {number} x The x coordinate to test for containment.
* @param {number} y The y coordinate to test for containment.
* @return {boolean} Whether this rectangle contains given coordinate.
*/
contains(x, y) {
return x >= this.left && x <= this.right && y >= this.top &&
y <= this.bottom;
}
/**
* Tests whether this rectangle intersects the provided rectangle.
* Assumes that the coordinate system increases going down and left.
* @param {!Rect} other The other rectangle to check for
* intersection with.
* @return {boolean} Whether this rectangle intersects the provided rectangle.
*/
Rect.prototype.intersects = function(other) {
return !(
this.left > other.right || this.right < other.left ||
this.top > other.bottom || this.bottom < other.top);
/**
* Tests whether this rectangle intersects the provided rectangle.
* Assumes that the coordinate system increases going down and left.
* @param {!Rect} other The other rectangle to check for
* intersection with.
* @return {boolean} Whether this rectangle intersects the provided rectangle.
*/
intersects(other) {
return !(
this.left > other.right || this.right < other.left ||
this.top > other.bottom || this.bottom < other.top);
}
};
exports.Rect = Rect;

View File

@@ -22,41 +22,44 @@ goog.module('Blockly.utils.Size');
/**
* Class for representing sizes consisting of a width and height.
* @param {number} width Width.
* @param {number} height Height.
* @struct
* @constructor
* @alias Blockly.utils.Size
*/
const Size = function(width, height) {
const Size = class {
/**
* Width
* @type {number}
* @param {number} width Width.
* @param {number} height Height.
* @struct
* @alias Blockly.utils.Size
*/
this.width = width;
constructor(width, height) {
/**
* Width
* @type {number}
*/
this.width = width;
/**
* Height
* @type {number}
*/
this.height = height;
}
/**
* Height
* @type {number}
* Compares sizes for equality.
* @param {?Size} a A Size.
* @param {?Size} b A Size.
* @return {boolean} True iff the sizes have equal widths and equal
* heights, or if both are null.
*/
this.height = height;
};
/**
* Compares sizes for equality.
* @param {?Size} a A Size.
* @param {?Size} b A Size.
* @return {boolean} True iff the sizes have equal widths and equal
* heights, or if both are null.
*/
Size.equals = function(a, b) {
if (a === b) {
return true;
static equals(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
return a.width === b.width && a.height === b.height;
}
if (!a || !b) {
return false;
}
return a.width === b.width && a.height === b.height;
};
exports.Size = Size;