mirror of
https://github.com/google/blockly.git
synced 2026-01-10 18:37:09 +01:00
Comments v3 (#1817)
* Add skeleton for workspace comments * XML parsing and encoding of workspace comments. * Minor fix: piping the height and width from xml to newWorkspaceComment * Move height and width into workspace_comment_svg * rename newWorkspaceComment to newComment * minor refactoring. PR changes * Functions for managing the comment's lifecycle * Add initial tests * Add another test * Add basic rendering of a comment. * Cleanup remaining highlighting steps from render * Fix lint * Fix aslant * Add basic comment translate * Simplify render code into one setPath method * Move steps to setPath_ * Remove svg elements when disposing of a comment; some code cleanup * Add a workspace comment on context menu click and position it where the initial context menu was clicked. * Minor rendering changes, fixes RTL. Fix positioning of new (context menu) comments while workspace is scaled. * PR feedback * Gesture code for dragging comments * Add comment (block drag) surface methods * minor comment fix * Comment fixes * Add comment dragger * Making rendered private * Require CommentDragger * Make basic comment dragging work * Increase the border around the comment to make a bigger drag handle * Remove typo * Allow comments to be selected. Highlight selected comment. Only edit comment on click. Updated comment rendering. * minor refactor: remove commented out code * PR comments * lint and rebuild * Fix renamed function call * Fix workspace getMetrics by storing comment size as a number, not a string * Enable comment deletion when dragging over the toolbox or trash can * Give issue references to some todos * Create a helper function for workspace comment creation * Integrate sam's workspace comments, using the bubble dragger * Remove comment_dragger references * Remove comment dragger.js * Remove pointer handling * Fix lint * Move comment XML functions into the comment files. * Fix tests * Fix type annotations * Fix comments on comments * Fix compiler errors related to visibility. * Fix merge issues and add an issue number to a TODO * Add a new message for default text on workspace comments, and rebuild * Add support for a context menu on workspace comment showing delete and duplication options. Add copy and paste support. * PR comment feedback * Show a delete icon on the comment when selected. Delete icon deletes the comment. Comment can be deleted if dragged onto the toolbox or the trash icon. A normal bubble cannot be deleted that way. * use isDeletable instead * Support drag of the comment during editing mode using the top handle. * Add skeletons for all workspace comment events * Rebuild with new comments * Get rid of confused TODO * JSDoc on a function * Fix broken tests * More PR feedback * Fix lint * Delete comment on mouse out, highlight on mouse down. * Fix lint. * Show delete hand cursor when dragging a comment to delete over the toolbox * Focus textarea on select * Add delete events * Remove workspace comment create event, and add TODO placeholder * Provide default values if comment height and width are missing in XML * Set comment handle fill to none by default * Rebuild * Comment de/serialization should include location. * Add comment move events, with undo and redo * Add comment change events * Move files up to core * Add package/private annotations wherever possible * Move the workspace comment events up to core and into a single file * Mark things package or private where possible * Get rid of unnecessary changes to messge files * Fix lint * Fix some review feedback * Make changes to the comment db happen in addTopComment and removeTopComment * Add css classes for toggling comment focus * Clean up css for comment focus * Rebuild
This commit is contained in:
@@ -246,35 +246,41 @@ Blockly.onKeyDown_ = function(e) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block onto the local clipboard.
|
||||
* @param {!Blockly.Block} block Block to be copied.
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toCopy Block or Workspace Comment
|
||||
* to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.copy_ = function(block) {
|
||||
var xmlBlock = Blockly.Xml.blockToDom(block);
|
||||
// Copy only the selected block and internal blocks.
|
||||
Blockly.Xml.deleteNext(xmlBlock);
|
||||
// Encode start position in XML.
|
||||
var xy = block.getRelativeToSurfaceXY();
|
||||
xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
|
||||
xmlBlock.setAttribute('y', xy.y);
|
||||
Blockly.clipboardXml_ = xmlBlock;
|
||||
Blockly.clipboardSource_ = block.workspace;
|
||||
Blockly.copy_ = function(toCopy) {
|
||||
if (toCopy.isComment) {
|
||||
var xml = toCopy.toXmlWithXY();
|
||||
} else {
|
||||
var xml = Blockly.Xml.blockToDom(toCopy);
|
||||
// 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children.
|
||||
* @param {!Blockly.Block} block Block to be copied.
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toDuplicate Block or
|
||||
* Workspace Comment to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.duplicate_ = function(block) {
|
||||
Blockly.duplicate_ = function(toDuplicate) {
|
||||
// Save the clipboard.
|
||||
var clipboardXml = Blockly.clipboardXml_;
|
||||
var clipboardSource = Blockly.clipboardSource_;
|
||||
|
||||
// Create a duplicate via a copy/paste operation.
|
||||
Blockly.copy_(block);
|
||||
block.workspace.paste(Blockly.clipboardXml_);
|
||||
Blockly.copy_(toDuplicate);
|
||||
toDuplicate.workspace.paste(Blockly.clipboardXml_);
|
||||
|
||||
// Restore the clipboard.
|
||||
Blockly.clipboardXml_ = clipboardXml;
|
||||
|
||||
@@ -304,6 +304,25 @@ Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the context menu for this bubble.
|
||||
* @param {!Event} _e Mouse event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.showContextMenu_ = function(_e) {
|
||||
// NOP on bubbles, but used by the bubble dragger to pass events to
|
||||
// workspace comments.
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether this bubble is deletable or not.
|
||||
* @return {boolean} True if deletable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Bubble.prototype.isDeletable = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's resize corner.
|
||||
* @param {!Event} e Mouse down event.
|
||||
|
||||
@@ -26,21 +26,27 @@
|
||||
|
||||
goog.provide('Blockly.BubbleDragger');
|
||||
|
||||
goog.require('Blockly.Bubble');
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
goog.require('Blockly.WorkspaceCommentSvg');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a bubble dragger. It moves bubbles around the workspace when they
|
||||
* are being dragged by a mouse or touch.
|
||||
* @param {!Blockly.Bubble} bubble The bubble to drag.
|
||||
* 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 {!Blockly.Bubble|!Blockly.WorkspaceCommentSvg} bubble The item on the
|
||||
* bubble canvas to drag.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BubbleDragger = function(bubble, workspace) {
|
||||
/**
|
||||
* The bubble that is being dragged.
|
||||
* @type {!Blockly.Bubble}
|
||||
* The item on the bubble canvas that is being dragged.
|
||||
* @type {!Blockly.Bubble|!Blockly.WorkspaceCommentSvg}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBubble_ = bubble;
|
||||
@@ -52,6 +58,22 @@ Blockly.BubbleDragger = function(bubble, workspace) {
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Which delete area the mouse pointer is over, if any.
|
||||
* One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.deleteArea_ = null;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -95,6 +117,15 @@ Blockly.BubbleDragger.prototype.startBubbleDrag = function() {
|
||||
if (this.dragSurface_) {
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
|
||||
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
|
||||
|
||||
var toolbox = this.workspace_.getToolbox();
|
||||
if (toolbox) {
|
||||
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
toolbox.addStyle(style);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,8 +141,55 @@ Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) {
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
// TODO (fenichel): Possibly update the cursor if dragging to the trash can
|
||||
// is allowed.
|
||||
|
||||
if (this.draggingBubble_.isDeletable()) {
|
||||
this.deleteArea_ = this.workspace_.isDeleteArea(e);
|
||||
this.updateCursorDuringBubbleDrag_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shut the trash can and, if necessary, delete the dragging bubble.
|
||||
* Should be called at the end of a bubble drag.
|
||||
* @return {boolean} whether the bubble was deleted.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() {
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
|
||||
if (this.wouldDeleteBubble_) {
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBubble_.dispose(false, true);
|
||||
} else if (trashcan) {
|
||||
// Make sure the trash can is closed.
|
||||
trashcan.close();
|
||||
}
|
||||
return this.wouldDeleteBubble_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging bubble would be deleted if released immediately.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() {
|
||||
this.wouldDeleteBubble_ = this.deleteArea_ != Blockly.DELETE_AREA_NONE;
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
if (this.wouldDeleteBubble_) {
|
||||
this.draggingBubble_.setDeleteStyle(true);
|
||||
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
|
||||
trashcan.setOpen_(true);
|
||||
}
|
||||
} else {
|
||||
this.draggingBubble_.setDeleteStyle(false);
|
||||
if (trashcan) {
|
||||
trashcan.setOpen_(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -131,14 +209,24 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function(
|
||||
|
||||
// Move the bubble to its final location.
|
||||
this.draggingBubble_.moveTo(newLoc.x, newLoc.y);
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
|
||||
}
|
||||
var deleted = this.maybeDeleteBubble_();
|
||||
|
||||
this.fireMoveEvent_();
|
||||
if (!deleted) {
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
|
||||
}
|
||||
|
||||
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(false);
|
||||
this.fireMoveEvent_();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
this.workspace_.toolbox_.removeStyle(style);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
@@ -147,6 +235,12 @@ Blockly.BubbleDragger.prototype.endBubbleDrag = function(
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() {
|
||||
if (this.draggingBubble_.isComment) {
|
||||
var event = new Blockly.Events.CommentMove(this.draggingBubble_);
|
||||
event.setOldCoordinate(this.startXY_);
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
}
|
||||
// TODO (fenichel): move events for comments.
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -300,3 +300,98 @@ Blockly.ContextMenu.blockCommentOption = function(block) {
|
||||
}
|
||||
return commentOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for deleting the current workspace comment.
|
||||
* @param {!Blockly.WorkspaceCommentSvg} comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.commentDeleteOption = function(comment) {
|
||||
var deleteOption = {
|
||||
text: Blockly.Msg.REMOVE_COMMENT,
|
||||
enabled: true,
|
||||
callback: function() {
|
||||
Blockly.Events.setGroup(true);
|
||||
comment.dispose(true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
return deleteOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for duplicating the current workspace comment.
|
||||
* @param {!Blockly.WorkspaceCommentSvg} comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.commentDuplicateOption = function(comment) {
|
||||
var duplicateOption = {
|
||||
text: Blockly.Msg.DUPLICATE_COMMENT,
|
||||
enabled: true,
|
||||
callback: function() {
|
||||
Blockly.duplicate_(comment);
|
||||
}
|
||||
};
|
||||
return duplicateOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for adding a comment on the workspace.
|
||||
* @param {!Blockly.WorkspaceSvg} ws The workspace where the right-click
|
||||
* originated.
|
||||
* @param {!Event} e The right-click mouse event.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.workspaceCommentOption = function(ws, e) {
|
||||
// Helper function to create and position a comment correctly based on the
|
||||
// location of the mouse event.
|
||||
var addWsComment = function() {
|
||||
var comment = new Blockly.WorkspaceCommentSvg(
|
||||
ws, Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT,
|
||||
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE,
|
||||
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE);
|
||||
|
||||
var injectionDiv = ws.getInjectionDiv();
|
||||
// Bounding rect coordinates are in client coordinates, meaning that they
|
||||
// are in pixels relative to the upper left corner of the visible browser
|
||||
// window. These coordinates change when you scroll the browser window.
|
||||
var boundingRect = injectionDiv.getBoundingClientRect();
|
||||
|
||||
// The client coordinates offset by the injection div's upper left corner.
|
||||
var clientOffsetPixels = new goog.math.Coordinate(
|
||||
e.clientX - boundingRect.left, e.clientY - boundingRect.top);
|
||||
|
||||
// The offset in pixels between the main workspace's origin and the upper
|
||||
// left corner of the injection div.
|
||||
var mainOffsetPixels = ws.getOriginOffsetInPixels();
|
||||
|
||||
// The position of the new comment in pixels relative to the origin of the
|
||||
// main workspace.
|
||||
var finalOffsetPixels = goog.math.Coordinate.difference(clientOffsetPixels,
|
||||
mainOffsetPixels);
|
||||
|
||||
// The position of the new comment in main workspace coordinates.
|
||||
var finalOffsetMainWs = finalOffsetPixels.scale(1 / ws.scale);
|
||||
|
||||
var commentX = finalOffsetMainWs.x;
|
||||
var commentY = finalOffsetMainWs.y;
|
||||
comment.moveBy(commentX, commentY);
|
||||
if (ws.rendered) {
|
||||
comment.initSvg();
|
||||
comment.render(false);
|
||||
comment.select();
|
||||
}
|
||||
};
|
||||
|
||||
var wsCommentOption = {enabled: true};
|
||||
wsCommentOption.text = Blockly.Msg.ADD_COMMENT;
|
||||
wsCommentOption.callback = function() {
|
||||
addWsComment();
|
||||
};
|
||||
return wsCommentOption;
|
||||
};
|
||||
|
||||
65
core/css.js
65
core/css.js
@@ -189,7 +189,7 @@ Blockly.Css.CONTENT = [
|
||||
'}',
|
||||
|
||||
'.blocklyResizeLine {',
|
||||
'stroke: #888;',
|
||||
'stroke: #515A5A;',
|
||||
'stroke-width: 1;',
|
||||
'}',
|
||||
|
||||
@@ -383,17 +383,76 @@ Blockly.Css.CONTENT = [
|
||||
'padding: 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentForeignObject {',
|
||||
'position: relative;',
|
||||
'z-index: 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentRect {',
|
||||
'fill: #E7DE8E;',
|
||||
'stroke: #bcA903;',
|
||||
'stroke-width: 1px',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentTarget {',
|
||||
'fill: transparent;',
|
||||
'stroke: #bcA903;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentTargetFocused {',
|
||||
'fill: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentHandleTarget {',
|
||||
'fill: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentHandleTargetFocused {',
|
||||
'fill: transparent;',
|
||||
'}',
|
||||
|
||||
'.blocklyFocused>.blocklyCommentRect {',
|
||||
'fill: #B9B272;',
|
||||
'stroke: #B9B272;',
|
||||
'}',
|
||||
|
||||
'.blocklySelected>.blocklyCommentTarget {',
|
||||
'stroke: #fc3;',
|
||||
'stroke-width: 3px;',
|
||||
'}',
|
||||
|
||||
|
||||
'.blocklyCommentTextarea {',
|
||||
'background-color: #ffc;',
|
||||
'background-color: #fef49c;',
|
||||
'border: 0;',
|
||||
'outline: 0;',
|
||||
'margin: 0;',
|
||||
'padding: 2px;',
|
||||
'padding: 3px;',
|
||||
'resize: none;',
|
||||
'display: block;',
|
||||
'overflow: hidden;',
|
||||
'}',
|
||||
|
||||
'.blocklyCommentDeleteIcon {',
|
||||
'cursor: pointer;',
|
||||
'fill: #000;',
|
||||
'display: none',
|
||||
'}',
|
||||
|
||||
'.blocklySelected > .blocklyCommentDeleteIcon {',
|
||||
'display: block',
|
||||
'}',
|
||||
|
||||
'.blocklyDeleteIconShape {',
|
||||
'fill: #000;',
|
||||
'stroke: #000;',
|
||||
'stroke-width: 1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyDeleteIconShape.blocklyDeleteIconHighlighted {',
|
||||
'stroke: #fc3;',
|
||||
'}',
|
||||
|
||||
'.blocklyHtmlInput {',
|
||||
'border: none;',
|
||||
'border-radius: 4px;',
|
||||
|
||||
@@ -126,6 +126,30 @@ Blockly.Events.VAR_RENAME = 'var_rename';
|
||||
*/
|
||||
Blockly.Events.UI = 'ui';
|
||||
|
||||
/**
|
||||
* Name of event that creates a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_CREATE = 'comment_create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_DELETE = 'comment_delete';
|
||||
|
||||
/**
|
||||
* Name of event that changes a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_CHANGE = 'comment_change';
|
||||
|
||||
/**
|
||||
* Name of event that moves a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_MOVE = 'comment_move';
|
||||
|
||||
/**
|
||||
* List of events queued for firing.
|
||||
* @private
|
||||
@@ -328,6 +352,18 @@ Blockly.Events.fromJson = function(json, workspace) {
|
||||
case Blockly.Events.UI:
|
||||
event = new Blockly.Events.Ui(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_CREATE:
|
||||
event = new Blockly.Events.CommentCreate(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_CHANGE:
|
||||
event = new Blockly.Events.CommentChange(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_MOVE:
|
||||
event = new Blockly.Events.CommentMove(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_DELETE:
|
||||
event = new Blockly.Events.CommentDelete(null);
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown event type.';
|
||||
}
|
||||
|
||||
@@ -469,7 +469,6 @@ Blockly.Gesture.prototype.startDraggingBubble_ = function() {
|
||||
this.bubbleDragger_.dragBubble(this.mostRecentEvent_,
|
||||
this.currentDragDeltaXY_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a gesture: update the workspace to indicate that a gesture is in
|
||||
* progress and bind mousemove and mouseup handlers.
|
||||
@@ -628,6 +627,8 @@ Blockly.Gesture.prototype.handleRightClick = function(e) {
|
||||
this.bringBlockToFront_();
|
||||
Blockly.hideChaff(this.flyout_);
|
||||
this.targetBlock_.showContextMenu_(e);
|
||||
} else if (this.startBubble_) {
|
||||
this.startBubble_.showContextMenu_(e);
|
||||
} else if (this.startWorkspace_ && !this.flyout_) {
|
||||
Blockly.hideChaff();
|
||||
this.startWorkspace_.showContextMenu_(e);
|
||||
@@ -706,8 +707,9 @@ Blockly.Gesture.prototype.handleBubbleStart = function(e, bubble) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.doBubbleClick_ = function() {
|
||||
// TODO: This isn't really enough, is it.
|
||||
this.startBubble_.promote_();
|
||||
// TODO (#1673): Consistent handling of single clicks.
|
||||
this.startBubble_.setFocus && this.startBubble_.setFocus();
|
||||
this.startBubble_.select && this.startBubble_.select();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -752,6 +754,7 @@ Blockly.Gesture.prototype.doWorkspaceClick_ = function() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* End functions defining what actions to take to execute clicks on each type
|
||||
* of target. */
|
||||
|
||||
@@ -802,7 +805,8 @@ Blockly.Gesture.prototype.setStartBubble = function(bubble) {
|
||||
* @package
|
||||
*/
|
||||
Blockly.Gesture.prototype.setStartBlock = function(block) {
|
||||
if (!this.startBlock_) {
|
||||
// If the gesture already went through a bubble, don't set the start block.
|
||||
if (!this.startBlock_ && !this.startBubble_) {
|
||||
this.startBlock_ = block;
|
||||
if (block.isInFlyout && block != block.getRootBlock()) {
|
||||
this.setTargetBlock_(block.getRootBlock());
|
||||
@@ -849,6 +853,7 @@ Blockly.Gesture.prototype.setStartFlyout_ = function(flyout) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* End functions for populating a gesture at mouse down. */
|
||||
|
||||
/* Begin helper functions defining types of clicks. Any developer wanting
|
||||
@@ -899,7 +904,8 @@ Blockly.Gesture.prototype.isFieldClick_ = function() {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Gesture.prototype.isWorkspaceClick_ = function() {
|
||||
var onlyTouchedWorkspace = !this.startBlock_ && !this.startField_;
|
||||
var onlyTouchedWorkspace = !this.startBlock_ && !this.startBubble_ &&
|
||||
!this.startField_;
|
||||
return onlyTouchedWorkspace && !this.hasExceededDragRadius_;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
goog.provide('Blockly.Workspace');
|
||||
|
||||
goog.require('Blockly.VariableMap');
|
||||
goog.require('Blockly.WorkspaceComment');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math');
|
||||
|
||||
@@ -55,6 +56,16 @@ Blockly.Workspace = function(opt_options) {
|
||||
* @private
|
||||
*/
|
||||
this.topBlocks_ = [];
|
||||
/**
|
||||
* @type {!Array.<!Blockly.WorkspaceComment>}
|
||||
* @private
|
||||
*/
|
||||
this.topComments_ = [];
|
||||
/**
|
||||
* @type {!Object}
|
||||
* @private
|
||||
*/
|
||||
this.commentDB_ = Object.create(null);
|
||||
/**
|
||||
* @type {!Array.<!Function>}
|
||||
* @private
|
||||
@@ -170,6 +181,61 @@ Blockly.Workspace.prototype.getTopBlocks = function(ordered) {
|
||||
return blocks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a comment to the list of top comments.
|
||||
* @param {!Blockly.WorkspaceComment} comment comment to add.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Workspace.prototype.addTopComment = function(comment) {
|
||||
this.topComments_.push(comment);
|
||||
|
||||
// Note: If the comment database starts to hold block comments, this may need
|
||||
// to move to a separate function.
|
||||
if (this.commentDB_[comment.id]) {
|
||||
console.warn('Overriding an existing comment on this workspace, with id "' +
|
||||
comment.id + '"');
|
||||
}
|
||||
this.commentDB_[comment.id] = comment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a comment from the list of top comments.
|
||||
* @param {!Blockly.WorkspaceComment} comment comment to remove.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Workspace.prototype.removeTopComment = function(comment) {
|
||||
if (!goog.array.remove(this.topComments_, comment)) {
|
||||
throw 'Comment not present in workspace\'s list of top-most comments.';
|
||||
}
|
||||
// Note: If the comment database starts to hold block comments, this may need
|
||||
// to move to a separate function.
|
||||
delete this.commentDB_[comment.id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the top-level comments and returns them. Comments are optionally sorted
|
||||
* by position; top to bottom (with slight LTR or RTL bias).
|
||||
* @param {boolean} ordered Sort the list if true.
|
||||
* @return {!Array.<!Blockly.WorkspaceComment>} The top-level comment objects.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Workspace.prototype.getTopComments = function(ordered) {
|
||||
// Copy the topComments_ list.
|
||||
var comments = [].concat(this.topComments_);
|
||||
if (ordered && comments.length > 1) {
|
||||
var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE));
|
||||
if (this.RTL) {
|
||||
offset *= -1;
|
||||
}
|
||||
comments.sort(function(a, b) {
|
||||
var aXY = a.getRelativeToSurfaceXY();
|
||||
var bXY = b.getRelativeToSurfaceXY();
|
||||
return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x);
|
||||
});
|
||||
}
|
||||
return comments;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all blocks in workspace. Blocks are optionally sorted
|
||||
* by position; top to bottom (with slight LTR or RTL bias).
|
||||
@@ -195,7 +261,7 @@ Blockly.Workspace.prototype.getAllBlocks = function(ordered) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all blocks in workspace.
|
||||
* Dispose of all blocks and comments in workspace.
|
||||
*/
|
||||
Blockly.Workspace.prototype.clear = function() {
|
||||
var existingGroup = Blockly.Events.getGroup();
|
||||
@@ -205,6 +271,9 @@ Blockly.Workspace.prototype.clear = function() {
|
||||
while (this.topBlocks_.length) {
|
||||
this.topBlocks_[0].dispose();
|
||||
}
|
||||
while (this.topComments_.length) {
|
||||
this.topComments_[this.topComments_.length - 1].dispose();
|
||||
}
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
@@ -458,6 +527,17 @@ Blockly.Workspace.prototype.getBlockById = function(id) {
|
||||
return this.blockDB_[id] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the comment on this workspace with the specified ID.
|
||||
* @param {string} id ID of comment to find.
|
||||
* @return {Blockly.WorkspaceComment} The sought after comment or null if not
|
||||
* found.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Workspace.prototype.getCommentById = function(id) {
|
||||
return this.commentDB_[id] || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether all value and statement inputs in the workspace are filled
|
||||
* with blocks.
|
||||
|
||||
370
core/workspace_comment.js
Normal file
370
core/workspace_comment.js
Normal file
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a code comment on the workspace.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceComment');
|
||||
|
||||
goog.require('Blockly.Events.CommentChange');
|
||||
goog.require('Blockly.Events.CommentCreate');
|
||||
goog.require('Blockly.Events.CommentDelete');
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a workspace comment.
|
||||
* @param {!Blockly.Workspace} workspace The block's workspace.
|
||||
* @param {string} content The content of this workspace comment.
|
||||
* @param {number} height Height of the comment.
|
||||
* @param {number} width Width of the comment.
|
||||
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
|
||||
* create a new ID.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.WorkspaceComment = function(workspace, content, height, width, opt_id) {
|
||||
/** @type {string} */
|
||||
this.id = (opt_id && !workspace.getCommentById(opt_id)) ?
|
||||
opt_id : Blockly.utils.genUid();
|
||||
|
||||
workspace.addTopComment(this);
|
||||
|
||||
/**
|
||||
* The comment's position in workspace units. (0, 0) is at the workspace's
|
||||
* origin; scale does not change this value.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @protected
|
||||
*/
|
||||
this.xy_ = new goog.math.Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* The comment's height in workspace units. Scale does not change this value.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.height_ = height;
|
||||
|
||||
/**
|
||||
* The comment's width in workspace units. Scale does not change this value.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.width_ = width;
|
||||
|
||||
/**
|
||||
* @type {!Blockly.Workspace}
|
||||
*/
|
||||
this.workspace = workspace;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.RTL = workspace.RTL;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.deletable_ = true;
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.movable_ = true;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @type {!string}
|
||||
*/
|
||||
this.content_ = content;
|
||||
|
||||
/**
|
||||
* @package
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isComment = true;
|
||||
|
||||
Blockly.WorkspaceComment.fireCreateEvent(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.dispose = function() {
|
||||
if (!this.workspace) {
|
||||
// The comment has already been deleted.
|
||||
return;
|
||||
}
|
||||
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.CommentDelete(this));
|
||||
}
|
||||
|
||||
// Remove from the list of top comments and the comment database.
|
||||
this.workspace.removeTopComment(this);
|
||||
this.workspace = null;
|
||||
};
|
||||
|
||||
// Height, width, x, and y are all stored on even non-rendered comments, to
|
||||
// preserve state if you pass the contents through a headless workspace.
|
||||
|
||||
/**
|
||||
* Get comment height.
|
||||
* @return {number} comment height.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.getHeight = function() {
|
||||
return this.height_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set comment height.
|
||||
* @param {number} height comment height.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.setHeight = function(height) {
|
||||
this.height_ = height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get comment width.
|
||||
* @return {number} comment width.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.getWidth = function() {
|
||||
return this.width_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set comment width.
|
||||
* @param {number} width comment width.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.setWidth = function(width) {
|
||||
this.width_ = width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get stored location.
|
||||
* @return {!goog.math.Coordinate} The comment's stored location. This is not
|
||||
* valid if the comment is currently being dragged.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.getXY = function() {
|
||||
return this.xy_.clone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Move a comment by a relative offset.
|
||||
* @param {number} dx Horizontal offset, in workspace units.
|
||||
* @param {number} dy Vertical offset, in workspace units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.moveBy = function(dx, dy) {
|
||||
var event = new Blockly.Events.CommentMove(this);
|
||||
this.xy_.translate(dx, dy);
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether this comment is deletable or not.
|
||||
* @return {boolean} True if deletable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.isDeletable = function() {
|
||||
return this.deletable_ &&
|
||||
!(this.workspace && this.workspace.options.readOnly);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this comment is deletable or not.
|
||||
* @param {boolean} deletable True if deletable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.setDeletable = function(deletable) {
|
||||
this.deletable_ = deletable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether this comment is movable or not.
|
||||
* @return {boolean} True if movable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.isMovable = function() {
|
||||
return this.movable_ &&
|
||||
!(this.workspace && this.workspace.options.readOnly);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this comment is movable or not.
|
||||
* @param {boolean} movable True if movable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.setMovable = function(movable) {
|
||||
this.movable_ = movable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this comment's text.
|
||||
* @return {string} Comment text.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.getContent = function() {
|
||||
return this.content_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set this comment's content.
|
||||
* @param {string} content Comment content.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.setContent = function(content) {
|
||||
if (this.content_ != content) {
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.CommentChange(this, this.content_, content));
|
||||
this.content_ = content;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a comment subtree as XML with XY coordinates.
|
||||
* @param {boolean=} opt_noId True if the encoder should skip the comment id.
|
||||
* @return {!Element} Tree of XML elements.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.toXmlWithXY = function(opt_noId) {
|
||||
var element = this.toXml(opt_noId);
|
||||
element.setAttribute('x', Math.round(this.xy_.x));
|
||||
element.setAttribute('y', Math.round(this.xy_.y));
|
||||
element.setAttribute('h', this.height_);
|
||||
element.setAttribute('w', this.width_);
|
||||
return element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a comment subtree as XML, but don't serialize the XY coordinates.
|
||||
* This method avoids some expensive metrics-related calls that are made in
|
||||
* toXmlWithXY().
|
||||
* @param {boolean=} opt_noId True if the encoder should skip the comment id.
|
||||
* @return {!Element} Tree of XML elements.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.prototype.toXml = function(opt_noId) {
|
||||
var commentElement = goog.dom.createDom('comment');
|
||||
if (!opt_noId) {
|
||||
commentElement.setAttribute('id', this.id);
|
||||
}
|
||||
commentElement.textContent = this.getContent();
|
||||
return commentElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire a create event for the given workspace comment, if comments are enabled.
|
||||
* @param {!Blockly.WorkspaceComment} comment The comment that was just created.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.fireCreateEvent = function(comment) {
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
var existingGroup = Blockly.Events.getGroup();
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
try {
|
||||
Blockly.Events.fire(new Blockly.Events.CommentCreate(comment));
|
||||
} finally {
|
||||
if (!existingGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML comment tag and create a comment on the workspace.
|
||||
* @param {!Element} xmlComment XML comment element.
|
||||
* @param {!Blockly.Workspace} workspace The workspace.
|
||||
* @return {!Blockly.WorkspaceComment} The created workspace comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.fromXml = function(xmlComment, workspace) {
|
||||
var info = Blockly.WorkspaceComment.parseAttributes(xmlComment);
|
||||
|
||||
var comment = new Blockly.WorkspaceComment(
|
||||
workspace, info.content, info.h, info.w, info.id);
|
||||
|
||||
var commentX = parseInt(xmlComment.getAttribute('x'), 10);
|
||||
var commentY = parseInt(xmlComment.getAttribute('y'), 10);
|
||||
if (!isNaN(commentX) && !isNaN(commentY)) {
|
||||
comment.moveBy(commentX, commentY);
|
||||
}
|
||||
|
||||
Blockly.WorkspaceComment.fireCreateEvent(comment);
|
||||
return comment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML comment tag and return the results in an object.
|
||||
* @param {!Element} xml XML comment element.
|
||||
* @return {!Object} An object containing the information about the comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceComment.parseAttributes = function(xml) {
|
||||
var xmlH = xml.getAttribute('h');
|
||||
var xmlW = xml.getAttribute('w');
|
||||
|
||||
return {
|
||||
/* @type {string} */
|
||||
id: xml.getAttribute('id'),
|
||||
/**
|
||||
* The height of the comment in workspace units, or 100 if not specified.
|
||||
* @type {number}
|
||||
*/
|
||||
h: xmlH ? parseInt(xmlH, 10) : 100,
|
||||
/**
|
||||
* The width of the comment in workspace units, or 100 if not specified.
|
||||
* @type {number}
|
||||
*/
|
||||
w: xmlW ? parseInt(xmlW, 10) : 100,
|
||||
/**
|
||||
* The x position of the comment in workspace coordinates, or NaN if not
|
||||
* specified in the XML.
|
||||
* @type {number}
|
||||
*/
|
||||
x: parseInt(xml.getAttribute('x'), 10),
|
||||
/**
|
||||
* The y position of the comment in workspace coordinates, or NaN if not
|
||||
* specified in the XML.
|
||||
* @type {number}
|
||||
*/
|
||||
y: parseInt(xml.getAttribute('y'), 10),
|
||||
/* @type {string} */
|
||||
content: xml.textContent
|
||||
};
|
||||
};
|
||||
|
||||
463
core/workspace_comment_render_svg.js
Normal file
463
core/workspace_comment_render_svg.js
Normal file
@@ -0,0 +1,463 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for rendering a workspace comment as SVG
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceCommentSvg.render');
|
||||
|
||||
goog.require('Blockly.WorkspaceCommentSvg');
|
||||
|
||||
|
||||
/**
|
||||
* Size of the resize icon.
|
||||
* @type {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.RESIZE_SIZE = 8;
|
||||
|
||||
/**
|
||||
* Radius of the border around the comment.
|
||||
* @type {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.BORDER_RADIUS = 3;
|
||||
|
||||
/**
|
||||
* Offset from the foreignobject edge to the textarea edge.
|
||||
* @type {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.TEXTAREA_OFFSET = 2;
|
||||
|
||||
/**
|
||||
* Offset from the top to make room for a top bar.
|
||||
* @type {number}
|
||||
* @const
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.TOP_OFFSET = 10;
|
||||
|
||||
/**
|
||||
* Returns a bounding box describing the dimensions of this comment.
|
||||
* @return {!{height: number, width: number}} Object with height and width
|
||||
* properties in workspace units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.getHeightWidth = function() {
|
||||
return { width: this.getWidth(), height: this.getHeight() };
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the workspace comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.render = function() {
|
||||
if (this.rendered_) {
|
||||
return;
|
||||
}
|
||||
|
||||
var size = this.getHeightWidth();
|
||||
|
||||
// Add text area
|
||||
this.createEditor_();
|
||||
this.svgGroup_.appendChild(this.foreignObject_);
|
||||
|
||||
this.svgHandleTarget_ = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'class': 'blocklyCommentHandleTarget',
|
||||
'x': 0,
|
||||
'y': 0
|
||||
});
|
||||
this.svgGroup_.appendChild(this.svgHandleTarget_);
|
||||
this.svgRectTarget_ = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'class': 'blocklyCommentTarget',
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'rx': Blockly.WorkspaceCommentSvg.BORDER_RADIUS,
|
||||
'ry': Blockly.WorkspaceCommentSvg.BORDER_RADIUS
|
||||
});
|
||||
this.svgGroup_.appendChild(this.svgRectTarget_);
|
||||
|
||||
// Add the resize icon
|
||||
this.addResizeDom_();
|
||||
if (this.isDeletable()) {
|
||||
// Add the delete icon
|
||||
this.addDeleteDom_();
|
||||
}
|
||||
|
||||
this.setSize_(size.width, size.height);
|
||||
|
||||
// Set the content
|
||||
this.textarea_.value = this.content_;
|
||||
|
||||
this.rendered_ = true;
|
||||
|
||||
if (this.resizeGroup_) {
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
|
||||
}
|
||||
|
||||
if (this.isDeletable()) {
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.deleteGroup_, 'mousedown', this, this.deleteMouseDown_);
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.deleteGroup_, 'mouseout', this, this.deleteMouseOut_);
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.deleteGroup_, 'mouseup', this, this.deleteMouseUp_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the text area for the comment.
|
||||
* @return {!Element} The top-level node of the editor.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.createEditor_ = function() {
|
||||
/* Create the editor. Here's the markup that will be generated:
|
||||
<foreignObject class="blocklyCommentForeignObject" x="0" y="10" width="164" height="164">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
|
||||
<textarea xmlns="http://www.w3.org/1999/xhtml"
|
||||
class="blocklyCommentTextarea"
|
||||
style="height: 164px; width: 164px;"></textarea>
|
||||
</body>
|
||||
</foreignObject>
|
||||
*/
|
||||
this.foreignObject_ = Blockly.utils.createSvgElement(
|
||||
'foreignObject',
|
||||
{
|
||||
'x': 0,
|
||||
'y': Blockly.WorkspaceCommentSvg.TOP_OFFSET,
|
||||
'class': 'blocklyCommentForeignObject'
|
||||
},
|
||||
null);
|
||||
var body = document.createElementNS(Blockly.HTML_NS, 'body');
|
||||
body.setAttribute('xmlns', Blockly.HTML_NS);
|
||||
body.className = 'blocklyMinimalBody';
|
||||
var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea');
|
||||
textarea.className = 'blocklyCommentTextarea';
|
||||
textarea.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
|
||||
body.appendChild(textarea);
|
||||
this.textarea_ = textarea;
|
||||
this.foreignObject_.appendChild(body);
|
||||
// Don't zoom with mousewheel.
|
||||
Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
Blockly.bindEventWithChecks_(textarea, 'change', this, function(
|
||||
/* eslint-disable no-unused-vars */ e
|
||||
/* eslint-enable no-unused-vars */) {
|
||||
this.setContent(textarea.value);
|
||||
});
|
||||
return this.foreignObject_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the resize icon to the DOM
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.addResizeDom_ = function() {
|
||||
this.resizeGroup_ = Blockly.utils.createSvgElement(
|
||||
'g',
|
||||
{
|
||||
'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'
|
||||
},
|
||||
this.svgGroup_);
|
||||
var resizeSize = Blockly.WorkspaceCommentSvg.RESIZE_SIZE;
|
||||
Blockly.utils.createSvgElement(
|
||||
'polygon',
|
||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||
this.resizeGroup_);
|
||||
Blockly.utils.createSvgElement(
|
||||
'line',
|
||||
{
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize / 3, 'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1, 'y2': resizeSize / 3
|
||||
}, this.resizeGroup_);
|
||||
Blockly.utils.createSvgElement(
|
||||
'line',
|
||||
{
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3
|
||||
}, this.resizeGroup_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the delete icon to the DOM
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.addDeleteDom_ = function() {
|
||||
this.deleteGroup_ = Blockly.utils.createSvgElement(
|
||||
'g',
|
||||
{
|
||||
'class': 'blocklyCommentDeleteIcon'
|
||||
},
|
||||
this.svgGroup_);
|
||||
this.deleteIconBorder_ = Blockly.utils.createSvgElement('circle',
|
||||
{
|
||||
'class': 'blocklyDeleteIconShape',
|
||||
'r': '7',
|
||||
'cx': '7.5',
|
||||
'cy': '7.5'
|
||||
},
|
||||
this.deleteGroup_);
|
||||
// x icon.
|
||||
Blockly.utils.createSvgElement(
|
||||
'line',
|
||||
{
|
||||
'x1': '5', 'y1': '10',
|
||||
'x2': '10', 'y2': '5',
|
||||
'stroke': '#fff',
|
||||
'stroke-width': '2'
|
||||
},
|
||||
this.deleteGroup_);
|
||||
Blockly.utils.createSvgElement(
|
||||
'line',
|
||||
{
|
||||
'x1': '5', 'y1': '5',
|
||||
'x2': '10', 'y2': '10',
|
||||
'stroke': '#fff',
|
||||
'stroke-width': '2'
|
||||
},
|
||||
this.deleteGroup_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on comment's resize corner.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.resizeMouseDown_ = function(e) {
|
||||
//this.promote_();
|
||||
this.unbindDragEvents_();
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// No right-click.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
this.workspace.startDrag(e, new goog.math.Coordinate(
|
||||
this.workspace.RTL ? -this.width_ : this.width_, this.height_));
|
||||
|
||||
this.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mouseup', this, this.resizeMouseUp_);
|
||||
this.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(
|
||||
document, 'mousemove', this, this.resizeMouseMove_);
|
||||
Blockly.hideChaff();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on comment's delete icon.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.deleteMouseDown_ = function(e) {
|
||||
// highlight the delete icon
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.deleteIconBorder_), 'blocklyDeleteIconHighlighted');
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-out on comment's delete icon.
|
||||
* @param {!Event} e Mouse out event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.deleteMouseOut_ = function(/*e*/) {
|
||||
// restore highlight on the delete icon
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.deleteIconBorder_), 'blocklyDeleteIconHighlighted');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-up on comment's delete icon.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.deleteMouseUp_ = function(e) {
|
||||
// Delete this comment
|
||||
this.dispose(true, true);
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.unbindDragEvents_ = function() {
|
||||
if (this.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMouseUpWrapper_);
|
||||
this.onMouseUpWrapper_ = null;
|
||||
}
|
||||
if (this.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(this.onMouseMoveWrapper_);
|
||||
this.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Handle a mouse-up event while dragging a comment's border or resize handle.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.resizeMouseUp_ = function(/*e*/) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
this.unbindDragEvents_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize this comment to follow the mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.resizeMouseMove_ = function(e) {
|
||||
this.autoLayout_ = false;
|
||||
var newXY = this.workspace.moveDrag(e);
|
||||
this.setSize_(this.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function triggered when the comment has resized.
|
||||
* Resize the text area accordingly.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.resizeComment_ = function() {
|
||||
var size = this.getHeightWidth();
|
||||
var topOffset = Blockly.WorkspaceCommentSvg.TOP_OFFSET;
|
||||
var textOffset = Blockly.WorkspaceCommentSvg.TEXTAREA_OFFSET * 2;
|
||||
|
||||
this.foreignObject_.setAttribute('width',
|
||||
size.width);
|
||||
this.foreignObject_.setAttribute('height',
|
||||
size.height - topOffset);
|
||||
if (this.RTL) {
|
||||
this.foreignObject_.setAttribute('x',
|
||||
-size.width);
|
||||
}
|
||||
this.textarea_.style.width =
|
||||
(size.width - textOffset) + 'px';
|
||||
this.textarea_.style.height =
|
||||
(size.height - textOffset - topOffset) + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Set size
|
||||
* @param {number} width width of the container
|
||||
* @param {number} height height of the container
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setSize_ = function(width, height) {
|
||||
// Minimum size of a comment.
|
||||
width = Math.max(width, 45);
|
||||
height = Math.max(height, 20 + Blockly.WorkspaceCommentSvg.TOP_OFFSET);
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
this.svgRect_.setAttribute('width', width);
|
||||
this.svgRect_.setAttribute('height', height);
|
||||
this.svgRectTarget_.setAttribute('width', width);
|
||||
this.svgRectTarget_.setAttribute('height', height);
|
||||
this.svgHandleTarget_.setAttribute('width', width);
|
||||
this.svgHandleTarget_.setAttribute('height', Blockly.WorkspaceCommentSvg.TOP_OFFSET);
|
||||
if (this.RTL) {
|
||||
this.svgRect_.setAttribute('transform', 'scale(-1 1)');
|
||||
this.svgRectTarget_.setAttribute('transform', 'scale(-1 1)');
|
||||
}
|
||||
|
||||
var resizeSize = Blockly.WorkspaceCommentSvg.RESIZE_SIZE;
|
||||
if (this.resizeGroup_) {
|
||||
if (this.RTL) {
|
||||
// Mirror the resize group.
|
||||
this.resizeGroup_.setAttribute('transform', 'translate(' +
|
||||
(-width + resizeSize) + ',' + (height - resizeSize) + ') scale(-1 1)');
|
||||
this.deleteGroup_.setAttribute('transform', 'translate(' +
|
||||
(-width + resizeSize) + ',' + (-resizeSize) + ') scale(-1 1)');
|
||||
} else {
|
||||
this.resizeGroup_.setAttribute('transform', 'translate(' +
|
||||
(width - resizeSize) + ',' +
|
||||
(height - resizeSize) + ')');
|
||||
this.deleteGroup_.setAttribute('transform', 'translate(' +
|
||||
(width - resizeSize) + ',' +
|
||||
(-resizeSize) + ')');
|
||||
}
|
||||
}
|
||||
|
||||
// Allow the contents to resize.
|
||||
this.resizeComment_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of any rendered comment components.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.disposeInternal_ = function() {
|
||||
this.textarea_ = null;
|
||||
this.foreignObject_ = null;
|
||||
this.svgRectTarget_ = null;
|
||||
this.svgHandleTarget_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the focus on the text area.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setFocus = function() {
|
||||
var comment = this;
|
||||
this.focused_ = true;
|
||||
// Defer CSS changes.
|
||||
setTimeout(function() {
|
||||
comment.textarea_.focus();
|
||||
comment.addFocus();
|
||||
Blockly.utils.addClass(
|
||||
comment.svgRectTarget_, 'blocklyCommentTargetFocused');
|
||||
Blockly.utils.addClass(
|
||||
comment.svgHandleTarget_, 'blocklyCommentHandleTargetFocused');
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove focus from the text area.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.blurFocus = function() {
|
||||
var comment = this;
|
||||
this.focused_ = false;
|
||||
// Defer CSS changes.
|
||||
setTimeout(function() {
|
||||
comment.textarea_.blur();
|
||||
comment.removeFocus();
|
||||
Blockly.utils.removeClass(
|
||||
comment.svgRectTarget_, 'blocklyCommentTargetFocused');
|
||||
Blockly.utils.removeClass(
|
||||
comment.svgHandleTarget_, 'blocklyCommentHandleTargetFocused');
|
||||
}, 0);
|
||||
};
|
||||
591
core/workspace_comment_svg.js
Normal file
591
core/workspace_comment_svg.js
Normal file
@@ -0,0 +1,591 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a code comment on a rendered workspace.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceCommentSvg');
|
||||
|
||||
goog.require('Blockly.Events.CommentCreate');
|
||||
goog.require('Blockly.Events.CommentDelete');
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
goog.require('Blockly.WorkspaceComment');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a workspace comment's SVG representation.
|
||||
* @param {!Blockly.Workspace} workspace The block's workspace.
|
||||
* @param {string} content The content of this workspace comment.
|
||||
* @param {number} height Height of the comment.
|
||||
* @param {number} width Width of the comment.
|
||||
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
|
||||
* create a new ID.
|
||||
* @extends {Blockly.WorkspaceComment}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg = function(workspace, content, height, width,
|
||||
opt_id) {
|
||||
// Create core elements for the block.
|
||||
/**
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement(
|
||||
'g', {'class': 'blocklyComment'}, null);
|
||||
this.svgGroup_.translate_ = '';
|
||||
|
||||
this.svgRect_ = Blockly.utils.createSvgElement(
|
||||
'rect',
|
||||
{
|
||||
'class': 'blocklyCommentRect',
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'rx': Blockly.WorkspaceCommentSvg.BORDER_RADIUS,
|
||||
'ry': Blockly.WorkspaceCommentSvg.BORDER_RADIUS
|
||||
});
|
||||
this.svgGroup_.appendChild(this.svgRect_);
|
||||
|
||||
/**
|
||||
* Whether the comment is rendered onscreen and is a part of the DOM.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.rendered_ = false;
|
||||
|
||||
/**
|
||||
* Whether to move the comment to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.useDragSurface_ =
|
||||
Blockly.utils.is3dSupported() && !!workspace.blockDragSurface_;
|
||||
|
||||
Blockly.WorkspaceCommentSvg.superClass_.constructor.call(this,
|
||||
workspace, content, height, width, opt_id);
|
||||
|
||||
this.render();
|
||||
}; goog.inherits(Blockly.WorkspaceCommentSvg, Blockly.WorkspaceComment);
|
||||
|
||||
/**
|
||||
* The width and height to use to size a workspace comment when it is first
|
||||
* added, before it has been edited by the user.
|
||||
* @type {number}
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE = 100;
|
||||
|
||||
/**
|
||||
* Dispose of this comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.dispose = function() {
|
||||
if (!this.workspace) {
|
||||
// The comment has already been deleted.
|
||||
return;
|
||||
}
|
||||
// If this comment is being dragged, unlink the mouse events.
|
||||
if (Blockly.selected == this) {
|
||||
this.unselect();
|
||||
this.workspace.cancelCurrentGesture();
|
||||
}
|
||||
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.CommentDelete(this));
|
||||
}
|
||||
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
// Sever JavaScript to DOM connections.
|
||||
this.svgGroup_ = null;
|
||||
this.svgRect_ = null;
|
||||
// Dispose of any rendered components
|
||||
this.disposeInternal_();
|
||||
|
||||
Blockly.Events.disable();
|
||||
Blockly.WorkspaceCommentSvg.superClass_.dispose.call(this);
|
||||
Blockly.Events.enable();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and initialize the SVG representation of a workspace comment.
|
||||
* May be called more than once.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.initSvg = function() {
|
||||
goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
|
||||
if (!this.workspace.options.readOnly && !this.eventsInit_) {
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.svgRectTarget_, 'mousedown', this, this.pathMouseDown_);
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.svgHandleTarget_, 'mousedown', this, this.pathMouseDown_);
|
||||
}
|
||||
this.eventsInit_ = true;
|
||||
|
||||
this.updateMovable();
|
||||
if (!this.getSvgRoot().parentNode) {
|
||||
this.workspace.getBubbleCanvas().appendChild(this.getSvgRoot());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG comment.
|
||||
* @param {!Event} e Mouse down event or touch start event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.pathMouseDown_ = function(e) {
|
||||
var gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the context menu for this workspace comment.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.showContextMenu_ = function(e) {
|
||||
if (this.workspace.options.readOnly) {
|
||||
return;
|
||||
}
|
||||
// Save the current workspace comment in a variable for use in closures.
|
||||
var comment = this;
|
||||
var menuOptions = [];
|
||||
|
||||
if (this.isDeletable() && this.isMovable()) {
|
||||
menuOptions.push(Blockly.ContextMenu.commentDuplicateOption(comment));
|
||||
menuOptions.push(Blockly.ContextMenu.commentDeleteOption(comment));
|
||||
}
|
||||
|
||||
Blockly.ContextMenu.show(e, menuOptions, this.RTL);
|
||||
};
|
||||
|
||||
/**
|
||||
* Select this comment. Highlight it visually.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.select = function() {
|
||||
if (Blockly.selected == this) {
|
||||
return;
|
||||
}
|
||||
var oldId = null;
|
||||
if (Blockly.selected) {
|
||||
oldId = Blockly.selected.id;
|
||||
// Unselect any previously selected block.
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
Blockly.selected.unselect();
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
}
|
||||
var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
|
||||
event.workspaceId = this.workspace.id;
|
||||
Blockly.Events.fire(event);
|
||||
Blockly.selected = this;
|
||||
this.addSelect();
|
||||
};
|
||||
|
||||
/**
|
||||
* Unselect this comment. Remove its highlighting.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.unselect = function() {
|
||||
if (Blockly.selected != this) {
|
||||
return;
|
||||
}
|
||||
var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
|
||||
event.workspaceId = this.workspace.id;
|
||||
Blockly.Events.fire(event);
|
||||
Blockly.selected = null;
|
||||
this.removeSelect();
|
||||
this.blurFocus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Select this comment. Highlight it visually.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.addSelect = function() {
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklySelected');
|
||||
this.setFocus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Unselect this comment. Remove its highlighting.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.removeSelect = function() {
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklySelected');
|
||||
this.blurFocus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus this comment. Highlight it visually.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.addFocus = function() {
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyFocused');
|
||||
};
|
||||
|
||||
/**
|
||||
* Unfocus this comment. Remove its highlighting.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.removeFocus = function() {
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyFocused');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the coordinates of the top-left corner of this comment relative to the
|
||||
* drawing surface's origin (0,0), in workspace units.
|
||||
* If the comment is on the workspace, (0, 0) is the origin of the workspace
|
||||
* coordinate system.
|
||||
* This does not change with workspace scale.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.getRelativeToSurfaceXY = function() {
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
|
||||
var dragSurfaceGroup = this.useDragSurface_ ?
|
||||
this.workspace.blockDragSurface_.getGroup() : null;
|
||||
|
||||
var element = this.getSvgRoot();
|
||||
if (element) {
|
||||
do {
|
||||
// Loop through this comment and every parent.
|
||||
var xy = Blockly.utils.getRelativeXY(element);
|
||||
x += xy.x;
|
||||
y += xy.y;
|
||||
// If this element is the current element on the drag surface, include
|
||||
// the translation of the drag surface itself.
|
||||
if (this.useDragSurface_ &&
|
||||
this.workspace.blockDragSurface_.getCurrentBlock() == element) {
|
||||
var surfaceTranslation =
|
||||
this.workspace.blockDragSurface_.getSurfaceTranslation();
|
||||
x += surfaceTranslation.x;
|
||||
y += surfaceTranslation.y;
|
||||
}
|
||||
element = element.parentNode;
|
||||
} while (element && element != this.workspace.getBubbleCanvas() &&
|
||||
element != dragSurfaceGroup);
|
||||
}
|
||||
this.xy_ = new goog.math.Coordinate(x, y);
|
||||
return this.xy_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move a comment by a relative offset.
|
||||
* @param {number} dx Horizontal offset, in workspace units.
|
||||
* @param {number} dy Vertical offset, in workspace units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.moveBy = function(dx, dy) {
|
||||
var event = new Blockly.Events.CommentMove(this);
|
||||
// TODO: Do I need to look up the relative to surface XY position here?
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.translate(xy.x + dx, xy.y + dy);
|
||||
this.xy_ = new goog.math.Coordinate(xy.x + dx, xy.y + dy);
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
this.workspace.resizeContents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms a comment by setting the translation on the transform attribute
|
||||
* of the block's SVG.
|
||||
* @param {number} x The x coordinate of the translation in workspace units.
|
||||
* @param {number} y The y coordinate of the translation in workspace units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.translate = function(x, y) {
|
||||
this.xy_ = new goog.math.Coordinate(x, y);
|
||||
this.getSvgRoot().setAttribute('transform',
|
||||
'translate(' + x + ',' + y + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this comment to its workspace's drag surface, accounting for positioning.
|
||||
* Generally should be called at the same time as setDragging(true).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.moveToDragSurface_ = function() {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
// This is in workspace coordinates.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y);
|
||||
// Execute the move on the top-level SVG component
|
||||
this.workspace.blockDragSurface_.setBlocksAndShow(this.getSvgRoot());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this comment back to the workspace block canvas.
|
||||
* Generally should be called at the same time as setDragging(false).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @param {!goog.math.Coordinate} newXY The position the comment should take on
|
||||
* on the workspace canvas, in workspace coordinates.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.moveOffDragSurface_ = function(newXY) {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// Translate to current position, turning off 3d.
|
||||
this.translate(newXY.x, newXY.y);
|
||||
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this comment during a drag, taking into account whether we are using a
|
||||
* drag surface to translate blocks.
|
||||
* @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries
|
||||
* rendered items during a drag, or null if no drag surface is in use.
|
||||
* @param {!goog.math.Coordinate} newLoc The location to translate to, in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.moveDuringDrag = function(dragSurface, newLoc) {
|
||||
if (dragSurface) {
|
||||
dragSurface.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.svgGroup_.translate_ = 'translate(' + newLoc.x + ',' + newLoc.y + ')';
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
this.svgGroup_.translate_ + this.svgGroup_.skew_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the bubble group to the specified location in workspace coordinates.
|
||||
* @param {number} x The x position to move to.
|
||||
* @param {number} y The y position to move to.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.moveTo = function(x, y) {
|
||||
this.translate(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the comment of transform="..." attributes.
|
||||
* Used when the comment is switching from 3d to 2d transform or vice versa.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.clearTransformAttributes_ = function() {
|
||||
Blockly.utils.removeAttribute(this.getSvgRoot(), 'transform');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the coordinates of a bounding box describing the dimensions of this
|
||||
* comment.
|
||||
* Coordinate system: workspace coordinates.
|
||||
* @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
|
||||
* Object with top left and bottom right coordinates of the bounding box.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.getBoundingRectangle = function() {
|
||||
var blockXY = this.getRelativeToSurfaceXY();
|
||||
var commentBounds = this.getHeightWidth();
|
||||
var topLeft;
|
||||
var bottomRight;
|
||||
if (this.RTL) {
|
||||
topLeft = new goog.math.Coordinate(blockXY.x - (commentBounds.width),
|
||||
blockXY.y);
|
||||
// Add the width of the tab/puzzle piece knob to the x coordinate
|
||||
// since X is the corner of the rectangle, not the whole puzzle piece.
|
||||
bottomRight = new goog.math.Coordinate(blockXY.x,
|
||||
blockXY.y + commentBounds.height);
|
||||
} else {
|
||||
// Subtract the width of the tab/puzzle piece knob to the x coordinate
|
||||
// since X is the corner of the rectangle, not the whole puzzle piece.
|
||||
topLeft = new goog.math.Coordinate(blockXY.x, blockXY.y);
|
||||
bottomRight = new goog.math.Coordinate(blockXY.x + commentBounds.width,
|
||||
blockXY.y + commentBounds.height);
|
||||
}
|
||||
return {topLeft: topLeft, bottomRight: bottomRight};
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or remove the UI indicating if this comment is movable or not.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.updateMovable = function() {
|
||||
if (this.isMovable()) {
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable');
|
||||
} else {
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDraggable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this comment is movable or not.
|
||||
* @param {boolean} movable True if movable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setMovable = function(movable) {
|
||||
Blockly.WorkspaceCommentSvg.superClass_.setMovable.call(this, movable);
|
||||
this.updateMovable();
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively adds or removes the dragging class to this node and its children.
|
||||
* @param {boolean} adding True if adding, false if removing.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setDragging = function(adding) {
|
||||
if (adding) {
|
||||
var group = this.getSvgRoot();
|
||||
group.translate_ = '';
|
||||
group.skew_ = '';
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
|
||||
} else {
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDragging');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the root node of the SVG or null if none exists.
|
||||
* @return {Element} The root SVG node (probably a group).
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.getSvgRoot = function() {
|
||||
return this.svgGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this comment's text.
|
||||
* @return {string} Comment text.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.getContent = function() {
|
||||
return this.textarea_ ? this.textarea_.value : this.content_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set this comment's content.
|
||||
* @param {string} content Comment content.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setContent = function(content) {
|
||||
Blockly.WorkspaceCommentSvg.superClass_.setContent.call(this, content);
|
||||
if (this.textarea_) {
|
||||
this.textarea_.value = content;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor over this comment by adding or removing a class.
|
||||
* @param {boolean} enable True if the delete cursor should be shown, false
|
||||
* otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.setDeleteStyle = function(enable) {
|
||||
if (enable) {
|
||||
Blockly.utils.addClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDraggingDelete');
|
||||
} else {
|
||||
Blockly.utils.removeClass(
|
||||
/** @type {!Element} */ (this.svgGroup_), 'blocklyDraggingDelete');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.WorkspaceCommentSvg.prototype.setAutoLayout = function() {
|
||||
// NOP for compatibility with the bubble dragger.
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML comment tag and create a rendered comment on the workspace.
|
||||
* @param {!Element} xmlComment XML comment element.
|
||||
* @param {!Blockly.Workspace} workspace The workspace.
|
||||
* @param {number=} opt_wsWidth The width of the workspace, which is used to
|
||||
* position comments correctly in RTL.
|
||||
* @return {!Blockly.WorkspaceCommentSvg} The created workspace comment.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.fromXml = function(xmlComment, workspace,
|
||||
opt_wsWidth) {
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var info = Blockly.WorkspaceComment.parseAttributes(xmlComment);
|
||||
|
||||
var comment = new Blockly.WorkspaceCommentSvg(workspace,
|
||||
info.content, info.h, info.w, info.id);
|
||||
if (workspace.rendered) {
|
||||
comment.initSvg();
|
||||
comment.render(false);
|
||||
}
|
||||
// Position the comment correctly, taking into account the width of a
|
||||
// rendered RTL workspace.
|
||||
if (!isNaN(info.x) && !isNaN(info.y)) {
|
||||
if (workspace.RTL) {
|
||||
var wsWidth = opt_wsWidth || workspace.getWidth();
|
||||
comment.moveBy(wsWidth - info.x, info.y);
|
||||
} else {
|
||||
comment.moveBy(info.x, info.y);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
Blockly.WorkspaceComment.fireCreateEvent(comment);
|
||||
|
||||
return comment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a comment subtree as XML with XY coordinates.
|
||||
* @param {boolean=} opt_noId True if the encoder should skip the comment id.
|
||||
* @return {!Element} Tree of XML elements.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceCommentSvg.prototype.toXmlWithXY = function(opt_noId) {
|
||||
var width; // Not used in LTR.
|
||||
if (this.workspace.RTL) {
|
||||
// Here be performance dragons: This calls getMetrics().
|
||||
width = this.workspace.getWidth();
|
||||
}
|
||||
var element = this.toXml(opt_noId);
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
element.setAttribute('x',
|
||||
Math.round(this.workspace.RTL ? width - xy.x : xy.x));
|
||||
element.setAttribute('y', Math.round(xy.y));
|
||||
element.setAttribute('h', this.getHeight());
|
||||
element.setAttribute('w', this.getWidth());
|
||||
return element;
|
||||
};
|
||||
@@ -41,6 +41,8 @@ goog.require('Blockly.Trashcan');
|
||||
goog.require('Blockly.VariablesDynamic');
|
||||
goog.require('Blockly.Workspace');
|
||||
goog.require('Blockly.WorkspaceAudio');
|
||||
goog.require('Blockly.WorkspaceComment');
|
||||
goog.require('Blockly.WorkspaceCommentSvg');
|
||||
goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
goog.require('Blockly.Xml');
|
||||
goog.require('Blockly.ZoomControls');
|
||||
@@ -243,6 +245,14 @@ Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false;
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false;
|
||||
|
||||
/**
|
||||
* The first parent div with 'injectionDiv' in the name, or null if not set.
|
||||
* Access this with getInjectionDiv.
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.injectionDiv_ = null;
|
||||
|
||||
/**
|
||||
* Last known position of the page scroll.
|
||||
* This is used to determine whether we have recalculated screen coordinate
|
||||
@@ -352,6 +362,29 @@ Blockly.WorkspaceSvg.prototype.getOriginOffsetInPixels = function() {
|
||||
return Blockly.utils.getInjectionDivXY_(this.svgBlockCanvas_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the injection div that is a parent of this workspace.
|
||||
* Walks the DOM the first time it's called, then returns a cached value.
|
||||
* @return {!Element} The first parent div with 'injectionDiv' in the name.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getInjectionDiv = function() {
|
||||
// NB: it would be better to pass this in at createDom, but is more likely to
|
||||
// break existing uses of Blockly.
|
||||
if (!this.injectionDiv_) {
|
||||
var element = this.svgGroup_;
|
||||
while (element) {
|
||||
var classes = element.getAttribute('class') || '';
|
||||
if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) {
|
||||
this.injectionDiv_ = element;
|
||||
break;
|
||||
}
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
return this.injectionDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save resize handler data so we can delete it later in dispose.
|
||||
* @param {!Array.<!Array>} handler Data that can be passed to unbindEvent_.
|
||||
@@ -897,6 +930,18 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
||||
if (this.currentGesture_) {
|
||||
this.currentGesture_.cancel(); // Dragging while pasting? No.
|
||||
}
|
||||
if (xmlBlock.tagName.toLowerCase() == 'comment') {
|
||||
this.pasteWorkspaceComment_(xmlBlock);
|
||||
} else {
|
||||
this.pasteBlock_(xmlBlock);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Paste the provided block onto the workspace.
|
||||
* @param {!Element} xmlBlock XML block element.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) {
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var block = Blockly.Xml.domToBlock(xmlBlock, this);
|
||||
@@ -952,6 +997,37 @@ Blockly.WorkspaceSvg.prototype.paste = function(xmlBlock) {
|
||||
block.select();
|
||||
};
|
||||
|
||||
/**
|
||||
* Paste the provided comment onto the workspace.
|
||||
* @param {!Element} xmlComment XML workspace comment element.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.pasteWorkspaceComment_ = function(xmlComment) {
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var comment = Blockly.WorkspaceCommentSvg.fromXml(xmlComment, this);
|
||||
// Move the duplicate to original position.
|
||||
var commentX = parseInt(xmlComment.getAttribute('x'), 10);
|
||||
var commentY = parseInt(xmlComment.getAttribute('y'), 10);
|
||||
if (!isNaN(commentX) && !isNaN(commentY)) {
|
||||
if (this.RTL) {
|
||||
commentX = -commentX;
|
||||
}
|
||||
// Offset workspace comment.
|
||||
// TODO: #1719 properly offset comment such that it's not interfereing with any blocks
|
||||
commentX += 50;
|
||||
commentY += 50;
|
||||
comment.moveBy(commentX, commentY);
|
||||
}
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
// TODO: Fire a Workspace Comment Create event
|
||||
}
|
||||
comment.select();
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the toolbox unless there's a drag in progress.
|
||||
* @package
|
||||
@@ -1126,17 +1202,19 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() {
|
||||
var topBlocks = this.getTopBlocks(false);
|
||||
var topComments = this.getTopComments(false);
|
||||
var topElements = topBlocks.concat(topComments);
|
||||
// There are no blocks, return empty rectangle.
|
||||
if (!topBlocks.length) {
|
||||
if (!topElements.length) {
|
||||
return {x: 0, y: 0, width: 0, height: 0};
|
||||
}
|
||||
|
||||
// Initialize boundary using the first block.
|
||||
var boundary = topBlocks[0].getBoundingRectangle();
|
||||
var boundary = topElements[0].getBoundingRectangle();
|
||||
|
||||
// Start at 1 since the 0th block was used for initialization
|
||||
for (var i = 1; i < topBlocks.length; i++) {
|
||||
var blockBoundary = topBlocks[i].getBoundingRectangle();
|
||||
for (var i = 1; i < topElements.length; i++) {
|
||||
var blockBoundary = topElements[i].getBoundingRectangle();
|
||||
if (blockBoundary.topLeft.x < boundary.topLeft.x) {
|
||||
boundary.topLeft.x = blockBoundary.topLeft.x;
|
||||
}
|
||||
|
||||
410
core/ws_comment_events.js
Normal file
410
core/ws_comment_events.js
Normal file
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Classes for all comment events.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Events.CommentBase');
|
||||
goog.provide('Blockly.Events.CommentChange');
|
||||
goog.provide('Blockly.Events.CommentCreate');
|
||||
goog.provide('Blockly.Events.CommentDelete');
|
||||
goog.provide('Blockly.Events.CommentMove');
|
||||
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Events.Abstract');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
* @param {Blockly.WorkspaceComment} comment The comment this event corresponds
|
||||
* to.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentBase = function(comment) {
|
||||
/**
|
||||
* The ID of the comment this event pertains to.
|
||||
* @type {string}
|
||||
*/
|
||||
this.commentId = comment.id;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = comment.workspace.id;
|
||||
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = Blockly.Events.group_;
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = Blockly.Events.recordUndo;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentBase, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentBase.prototype.toJson = function() {
|
||||
var json = {
|
||||
'type': this.type
|
||||
};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
if (this.commentId) {
|
||||
json['commentId'] = this.commentId;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentBase.prototype.fromJson = function(json) {
|
||||
this.commentId = json['commentId'];
|
||||
this.group = json['group'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment change event.
|
||||
* @param {Blockly.WorkspaceComment} comment The comment that is being changed.
|
||||
* Null for a blank event.
|
||||
* @param {string} oldContents Previous contents of the comment.
|
||||
* @param {string} newContents New contents of the comment.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentChange = function(comment, oldContents, newContents) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentChange.superClass_.constructor.call(this, comment);
|
||||
this.oldContents_ = oldContents;
|
||||
this.newContents_ = newContents;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentChange, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.type = Blockly.Events.COMMENT_CHANGE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentChange.superClass_.toJson.call(this);
|
||||
json['newContents'] = this.newContents_;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentChange.superClass_.fromJson.call(this, json);
|
||||
this.newContents_ = json['newValue'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.isNull = function() {
|
||||
return this.oldContents_ == this.newContents_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
var contents = forward ? this.newContents_ : this.oldContents_;
|
||||
|
||||
comment.setContent(contents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment creation event.
|
||||
* @param {Blockly.WorkspaceComment} comment The created comment.
|
||||
* Null for a blank event.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentCreate = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentCreate.superClass_.constructor.call(this, comment);
|
||||
|
||||
this.xml = comment.toXmlWithXY();
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentCreate, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.type = Blockly.Events.COMMENT_CREATE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (#1266): "Full" and "minimal" serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentCreate.superClass_.toJson.call(this);
|
||||
json['xml'] = Blockly.Xml.domToText(this.xml);
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentCreate.superClass_.fromJson.call(this, json);
|
||||
this.xml = Blockly.Xml.textToDom('<xml>' + json['xml'] + '</xml>').firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
} else {
|
||||
var comment = workspace.getCommentById(this.commentId);
|
||||
if (comment) {
|
||||
comment.dispose(false, false);
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't uncreate non-existent comment: " + this.commentId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment deletion event.
|
||||
* @param {Blockly.WorkspaceComment} comment The deleted comment.
|
||||
* Null for a blank event.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentDelete = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentDelete.superClass_.constructor.call(this, comment);
|
||||
|
||||
this.xml = comment.toXmlWithXY();
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentDelete, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.type = Blockly.Events.COMMENT_DELETE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (#1266): "Full" and "minimal" serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentDelete.superClass_.toJson.call(this);
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentDelete.superClass_.fromJson.call(this, json);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
var comment = workspace.getCommentById(this.commentId);
|
||||
if (comment) {
|
||||
comment.dispose(false, false);
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't uncreate non-existent comment: " + this.commentId);
|
||||
}
|
||||
} else {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment move event. Created before the move.
|
||||
* @param {Blockly.WorkspaceComment} comment The comment that is being moved.
|
||||
* Null for a blank event.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentMove = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentMove.superClass_.constructor.call(this, comment);
|
||||
|
||||
/**
|
||||
* The comment that is being moved. Will be cleared after recording the new
|
||||
* location.
|
||||
* @type {?Blockly.WorkspaceComment}
|
||||
*/
|
||||
this.comment_ = comment;
|
||||
|
||||
/**
|
||||
* The location before the move, in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
*/
|
||||
this.oldCoordinate_ = comment.getXY();
|
||||
|
||||
/**
|
||||
* The location after the move, in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
*/
|
||||
this.newCoordinate_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentMove, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Record the comment's new location. Called after the move. Can only be
|
||||
* called once.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.recordNew = function() {
|
||||
if (!this.comment_) {
|
||||
throw new Error('Tried to record the new position of a comment on the ' +
|
||||
'same event twice.');
|
||||
}
|
||||
this.newCoordinate_ = this.comment_.getXY();
|
||||
this.comment_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.type = Blockly.Events.COMMENT_MOVE;
|
||||
|
||||
/**
|
||||
* Override the location before the move. Use this if you don't create the
|
||||
* event until the end of the move, but you know the original location.
|
||||
* @param {!goog.math.Coordinate} xy The location before the move, in workspace
|
||||
* coordinates.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.setOldCoordinate = function(xy) {
|
||||
this.oldCoordinate_ = xy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (#1266): "Full" and "minimal" serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentMove.superClass_.toJson.call(this);
|
||||
if (this.newCoordinate_) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
|
||||
Math.round(this.newCoordinate_.y);
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentMove.superClass_.fromJson.call(this, json);
|
||||
|
||||
if (json['newCoordinate']) {
|
||||
var xy = json['newCoordinate'].split(',');
|
||||
this.newCoordinate_ =
|
||||
new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.isNull = function() {
|
||||
return goog.math.Coordinate.equals(this.oldCoordinate_, this.newCoordinate_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a move event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t move non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = forward ? this.newCoordinate_ : this.oldCoordinate_;
|
||||
// TODO: Check if the comment is being dragged, and give up if so.
|
||||
var current = comment.getXY();
|
||||
comment.moveBy(target.x - current.x, target.y - current.y);
|
||||
};
|
||||
16
core/xml.js
16
core/xml.js
@@ -45,9 +45,11 @@ goog.require('goog.dom');
|
||||
*/
|
||||
Blockly.Xml.workspaceToDom = function(workspace, opt_noId) {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
var variables = Blockly.Variables.allUsedVarModels(workspace);
|
||||
if (variables.length) {
|
||||
xml.appendChild(Blockly.Xml.variablesToDom(variables));
|
||||
xml.appendChild(Blockly.Xml.variablesToDom(
|
||||
Blockly.Variables.allUsedVarModels(workspace)));
|
||||
var comments = workspace.getTopComments(true);
|
||||
for (var i = 0, comment; comment = comments[i]; i++) {
|
||||
xml.appendChild(comment.toXmlWithXY(opt_noId));
|
||||
}
|
||||
var blocks = workspace.getTopBlocks(true);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
@@ -376,6 +378,7 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
||||
console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' +
|
||||
'swap the arguments.');
|
||||
}
|
||||
|
||||
var width; // Not used in LTR.
|
||||
if (workspace.RTL) {
|
||||
width = workspace.getWidth();
|
||||
@@ -418,6 +421,12 @@ Blockly.Xml.domToWorkspace = function(xml, workspace) {
|
||||
} else if (name == 'shadow') {
|
||||
goog.asserts.fail('Shadow block cannot be a top-level block.');
|
||||
variablesFirst = false;
|
||||
} else if (name == 'comment') {
|
||||
if (workspace.rendered) {
|
||||
Blockly.WorkspaceCommentSvg.fromXml(xmlChild, workspace, width);
|
||||
} else {
|
||||
Blockly.WorkspaceComment.fromXml(xmlChild, workspace);
|
||||
}
|
||||
} else if (name == 'variables') {
|
||||
if (variablesFirst) {
|
||||
Blockly.Xml.domToVariables(xmlChild, workspace);
|
||||
@@ -562,6 +571,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
|
||||
return topBlock;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Decode an XML list of variables and add the variables to the workspace.
|
||||
* @param {!Element} xmlVariables List of XML variable elements.
|
||||
|
||||
Reference in New Issue
Block a user