Final changes for comment events. Resizing and minimizing/maximizing changes are recorded in CommentChange event. Undo/Redo works for all events.

This commit is contained in:
Karishma Chadha 2018-06-01 17:22:10 -04:00
parent 40f89e11a9
commit 3d3b8f3427
7 changed files with 237 additions and 33 deletions

View file

@ -979,15 +979,18 @@ Blockly.BlockSvg.prototype.getCommentText = function() {
/**
* Set this block's comment text.
* @param {?string} text The text, or null to delete.
* @param {string=} commentId Id of the comment, or a new one will be generated if not provided.
* @param {number=} commentX Optional x position for scratch comment in workspace coordinates
* @param {number=} commentY Optional y position for scratch comment in workspace coordinates
* @param {boolean=} minimized Optional minimized state for scratch comment, defaults to false
*/
Blockly.BlockSvg.prototype.setCommentText = function(text, commentX, commentY, minimized) {
Blockly.BlockSvg.prototype.setCommentText = function(text, commentId,
commentX, commentY, minimized) {
var changedState = false;
if (goog.isString(text)) {
if (!this.comment) {
this.comment = new Blockly.ScratchBlockComment(this, commentX, commentY, minimized);
this.comment = new Blockly.ScratchBlockComment(this, commentId,
commentX, commentY, minimized);
changedState = true;
}
this.comment.setText(/** @type {string} */ (text));

View file

@ -39,26 +39,30 @@ goog.require('goog.userAgent');
/**
* Class for a comment.
* @param {!Blockly.Block} block The block associated with this comment.
* @param {string=} id Optional uid for comment; a new one will be generated if
* not provided.
* @param {number=} x Initial x position for comment, in workspace coordinates.
* @param {number=} y Initial y position for comment, in workspace coordinates.
* @param {boolean=} minimized Whether or not this comment is minimized
* (only the top bar displays), defaults to false.
* @param {string=} id Optional uid for comment, will be generated if
* not provided.
* @extends {Blockly.Comment}
* @constructor
*/
Blockly.ScratchBlockComment = function(block, x, y, minimized, id) {
Blockly.ScratchBlockComment = function(block, id, x, y, minimized) {
Blockly.ScratchBlockComment.superClass_.constructor.call(this, block);
this.x_ = x;
this.y_ = y;
this.isMinimized_ = minimized || false;
this.workspace = block.workspace;
this.id = (id && !this.workspace.getCommentById(id)) ?
id : Blockly.utils.genUid();
this.id = goog.isString(id) ? id : Blockly.utils.genUid();
this.blockId = block.id;
Blockly.ScratchBlockComment.fireCreateEvent(this);
if (!block.rendered) {
Blockly.ScratchBlockComment.fireCreateEvent(this);
}
// If the block is rendered, fire event the create event when the comment is made
// visible
};
goog.inherits(Blockly.ScratchBlockComment, Blockly.Comment);
@ -232,8 +236,6 @@ Blockly.ScratchBlockComment.prototype.setVisible = function(visible) {
// No change.
return;
}
Blockly.Events.fire(
new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) {
// Steal the code from warnings to make an uneditable text bubble.
// MSIE does not support foreignobject; textareas are impossible.
@ -296,6 +298,7 @@ Blockly.ScratchBlockComment.prototype.setVisible = function(visible) {
// Restore the bubble stats after the visibility switch.
this.setText(text);
this.setBubbleSize(size.width, size.height);
if (visible) Blockly.ScratchBlockComment.fireCreateEvent(this);
};
/**
@ -313,6 +316,8 @@ Blockly.ScratchBlockComment.prototype.toggleMinimize_ = function() {
*/
Blockly.ScratchBlockComment.prototype.setMinimized = function(minimize) {
if (this.isMinimized_ == minimize) return;
Blockly.Events.fire(new Blockly.Events.CommentChange(this,
{minimized: this.isMinimized_}, {minimized: minimize}));
this.isMinimized_ = minimize;
if (minimize) {
this.bubble_.setMinimized(true, this.getLabelText());
@ -486,8 +491,11 @@ Blockly.ScratchBlockComment.fireCreateEvent = function(comment) {
* Dispose of this comment.
*/
Blockly.ScratchBlockComment.prototype.dispose = function() {
Blockly.ScratchBlockComment.superClass_.dispose.call(this);
if (Blockly.Events.isEnabled()) {
// Emit delete event before disposal begins so that the
// event's reference to this comment contains all the relevant
// information (for undoing this event)
Blockly.Events.fire(new Blockly.Events.CommentDelete(this));
}
Blockly.ScratchBlockComment.superClass_.dispose.call(this);
};

View file

@ -94,6 +94,8 @@ Blockly.ScratchBubble = function(comment, workspace, content, anchorXY,
if (this.resizeGroup_) {
Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mouseup', this, this.resizeMouseUp_);
}
}
@ -343,6 +345,33 @@ Blockly.ScratchBubble.prototype.deleteMouseUp_ = function(e) {
e.stopPropagation();
};
/**
* Handle a mouse-down on bubble's resize corner.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.ScratchBubble.prototype.resizeMouseDown_ = function(e) {
this.resizeStartSize_ = {width: this.width_, height: this.height_};
Blockly.ScratchBubble.superClass_.resizeMouseDown_.call(this, e);
};
/**
* Handle a mouse-up on bubble's resize corner.
* @param {!Event} _e Mouse up event.
* @private
*/
Blockly.ScratchBubble.prototype.resizeMouseUp_ = function(_e) {
var oldHW = this.resizeStartSize_;
this.resizeStartSize_ = null;
if (this.width_ == oldHW.width &&
this.height_ == oldHW.height) return;
// Fire a change event for the new width/height after
// resize mouse up
Blockly.Events.fire(new Blockly.Events.CommentChange(
this.comment, {width: oldHW.width , height: oldHW.height},
{width: this.width_, height: this.height_}));
};
/**
* Set the minimized state of the bubble.
* @param {boolean} minimize Whether the bubble should be minimized
@ -549,7 +578,6 @@ Blockly.ScratchBubble.prototype.moveDuringDrag = function(dragSurface, newLoc) {
} else {
this.moveTo(newLoc.x, newLoc.y);
}
this.renderArrow_();
};
/**
@ -565,6 +593,7 @@ Blockly.ScratchBubble.prototype.updatePosition_ = function(x, y) {
this.relativeLeft_ = x - this.anchorXY_.x;
}
this.relativeTop_ = y - this.anchorXY_.y;
this.renderArrow_();
};
/**

View file

@ -253,6 +253,24 @@ Blockly.WorkspaceComment.prototype.setContent = function(content) {
}
};
/**
* Same as Blockly.WorkspaceComment.prototype.getContent.
* @return {string} The text content of this comment.
* @package
*/
Blockly.WorkspaceComment.prototype.getText = function() {
return this.getContent();
};
/**
* Same as Blockly.WorkspaceComment.prototype.setContent.
* @param {string} text The text content of this comment.
* @package
*/
Blockly.WorkspaceComment.prototype.setText = function(text) {
this.setContent(text);
};
/**
* Encode a comment subtree as XML with XY coordinates.
* @param {boolean=} opt_noId True if the encoder should skip the comment id.

View file

@ -110,7 +110,7 @@ Blockly.WorkspaceCommentSvg.prototype.render = function() {
this.addDeleteDom_();
}
this.setSize_(size.width, size.height);
this.setSize(size.width, size.height);
// Set the content
this.textarea_.value = this.content_;
@ -120,6 +120,8 @@ Blockly.WorkspaceCommentSvg.prototype.render = function() {
if (this.resizeGroup_) {
Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
Blockly.bindEventWithChecks_(
this.resizeGroup_, 'mouseup', this, this.resizeMouseUp_);
}
if (this.isDeletable()) {
@ -254,6 +256,7 @@ Blockly.WorkspaceCommentSvg.prototype.addDeleteDom_ = function() {
* @private
*/
Blockly.WorkspaceCommentSvg.prototype.resizeMouseDown_ = function(e) {
this.resizeStartSize_ = {width: this.width_, height: this.height_};
this.unbindDragEvents_();
if (Blockly.utils.isRightButton(e)) {
// No right-click.
@ -332,6 +335,15 @@ Blockly.WorkspaceCommentSvg.prototype.unbindDragEvents_ = function() {
Blockly.WorkspaceCommentSvg.prototype.resizeMouseUp_ = function(/*e*/) {
Blockly.Touch.clearTouchIdentifier();
this.unbindDragEvents_();
var oldHW = this.resizeStartSize_;
this.resizeStartSize_ = null;
if (this.width_ == oldHW.width &&
this.height_ == oldHW.height) return;
// Fire a change event for the new width/height after
// resize mouse up
Blockly.Events.fire(new Blockly.Events.CommentChange(
this, {width: oldHW.width , height: oldHW.height},
{width: this.width_, height: this.height_}));
};
/**
@ -342,7 +354,7 @@ Blockly.WorkspaceCommentSvg.prototype.resizeMouseUp_ = function(/*e*/) {
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);
this.setSize(this.RTL ? -newXY.x : newXY.x, newXY.y);
};
/**
@ -373,9 +385,9 @@ Blockly.WorkspaceCommentSvg.prototype.resizeComment_ = function() {
* Set size
* @param {number} width width of the container
* @param {number} height height of the container
* @private
* @package
*/
Blockly.WorkspaceCommentSvg.prototype.setSize_ = function(width, height) {
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);

View file

@ -38,8 +38,8 @@ goog.require('goog.math.Coordinate');
/**
* Abstract class for a comment event.
* @param {Blockly.WorkspaceComment} comment The comment this event corresponds
* to.
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
* The comment this event corresponds to.
* @extends {Blockly.Events.Abstract}
* @constructor
*/
@ -114,6 +114,11 @@ Blockly.Events.CommentChange = function(comment, oldContents, newContents) {
Blockly.Events.CommentChange.superClass_.constructor.call(this, comment);
this.oldContents_ = oldContents;
this.newContents_ = newContents;
/**
* The id of the block this comment belongs to or null if it's a workspace comment.
* @type {?string}
*/
this.blockId = comment.blockId || null;
};
goog.inherits(Blockly.Events.CommentChange, Blockly.Events.CommentBase);
@ -130,6 +135,7 @@ Blockly.Events.CommentChange.prototype.type = Blockly.Events.COMMENT_CHANGE;
Blockly.Events.CommentChange.prototype.toJson = function() {
var json = Blockly.Events.CommentChange.superClass_.toJson.call(this);
json['newContents'] = this.newContents_;
json['blockId'] = this.blockId;
return json;
};
@ -140,6 +146,7 @@ Blockly.Events.CommentChange.prototype.toJson = function() {
Blockly.Events.CommentChange.prototype.fromJson = function(json) {
Blockly.Events.CommentChange.superClass_.fromJson.call(this, json);
this.newContents_ = json['newValue'];
this.blockId = json['blockId'];
};
/**
@ -155,21 +162,55 @@ Blockly.Events.CommentChange.prototype.isNull = function() {
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.CommentChange.prototype.run = function(forward) {
var comment = null;
var workspace = this.getEventWorkspace_();
var comment = workspace.getCommentById(this.commentId);
if (this.blockId) {
var block = workspace.getBlockById(this.blockId);
if (block) comment = block.comment;
} else {
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);
if (goog.isString(contents)) {
if (comment instanceof Blockly.ScratchBlockComment) {
comment.setText(contents);
} else {
comment.setContent(contents);
}
return;
} else if (typeof contents == 'object') {
if (contents.hasOwnProperty('minimized')) {
if (comment instanceof Blockly.ScratchBlockComment) {
// TODO remove this check when workspace comments also get a minimized
// state
comment.setMinimized(contents.minimized);
}
return;
} else if (contents.hasOwnProperty('width') && contents.hasOwnProperty('height')) {
if (comment instanceof Blockly.ScratchBlockComment) {
comment.setBubbleSize(contents.width, contents.height);
} else {
comment.setSize(contents.width, contents.height);
}
return;
}
}
console.warn('Unrecognized comment change: ' + JSON.stringify(contents) + '; cannot undo/redo');
return;
};
/**
* Class for a comment creation event.
* @param {Blockly.WorkspaceComment} comment The created comment.
* Null for a blank event.
* @param {string=} opt_blockId Optional id for the block this comment belongs
* to, if it is a block comment.
* @extends {Blockly.Events.CommentBase}
* @constructor
*/
@ -179,6 +220,44 @@ Blockly.Events.CommentCreate = function(comment) {
}
Blockly.Events.CommentCreate.superClass_.constructor.call(this, comment);
/**
* The id of the block this comment belongs to or null if it's a workspace comment.
* @type {?string}
*/
this.blockId = comment.blockId || null;
/**
* The text content of this comment.
* @type{string}
*/
this.text = comment.getText();
/**
* The XY position of this comment on the workspace.
* @type{goog.math.Coordinate}
*/
this.xy = comment.getXY();
var hw = comment.getHeightWidth();
/**
* The width of this comment when it is full size.
* @type{number}
*/
this.width = hw.width;
/**
* The height of this comment when it is full size.
* @type{number}
*/
this.height = hw.height;
/**
* Whether or not this comment is minimized.
* @type {boolean}
*/
this.minimized = comment.minimized || false;
this.xml = comment.toXmlWithXY();
};
goog.inherits(Blockly.Events.CommentCreate, Blockly.Events.CommentBase);
@ -197,6 +276,7 @@ Blockly.Events.CommentCreate.prototype.type = Blockly.Events.COMMENT_CREATE;
*/
Blockly.Events.CommentCreate.prototype.toJson = function() {
var json = Blockly.Events.CommentCreate.superClass_.toJson.call(this);
json['blockId'] = this.blockId;
json['xml'] = Blockly.Xml.domToText(this.xml);
return json;
};
@ -207,6 +287,7 @@ Blockly.Events.CommentCreate.prototype.toJson = function() {
*/
Blockly.Events.CommentCreate.prototype.fromJson = function(json) {
Blockly.Events.CommentCreate.superClass_.fromJson.call(this, json);
this.blockId = json['blockId'];
this.xml = Blockly.Xml.textToDom('<xml>' + json['xml'] + '</xml>').firstChild;
};
@ -217,11 +298,23 @@ Blockly.Events.CommentCreate.prototype.fromJson = function(json) {
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);
if (this.blockId) {
var block = workspace.getBlockById(this.blockId);
if (block) {
block.setCommentText('', this.commentId, this.xy.x, this.xy.y, this.minimized);
}
} else {
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(xml, workspace);
}
} else {
var comment = workspace.getCommentById(this.commentId);
var comment;
if (this.blockId) {
comment = workspace.getBlockById(this.blockId).comment;
} else {
comment = workspace.getCommentById(this.commentId);
}
if (comment) {
comment.dispose(false, false);
} else {
@ -243,6 +336,9 @@ Blockly.Events.CommentDelete = function(comment) {
return; // Blank event to be populated by fromJson.
}
Blockly.Events.CommentDelete.superClass_.constructor.call(this, comment);
this.blockId = comment.blockId || null;
this.xy = comment.getXY();
this.minimized = comment.minimized || false;
this.xml = comment.toXmlWithXY();
};
@ -262,6 +358,7 @@ Blockly.Events.CommentDelete.prototype.type = Blockly.Events.COMMENT_DELETE;
*/
Blockly.Events.CommentDelete.prototype.toJson = function() {
var json = Blockly.Events.CommentDelete.superClass_.toJson.call(this);
json['blockId'] = this.blockId;
return json;
};
@ -271,6 +368,7 @@ Blockly.Events.CommentDelete.prototype.toJson = function() {
*/
Blockly.Events.CommentDelete.prototype.fromJson = function(json) {
Blockly.Events.CommentDelete.superClass_.fromJson.call(this, json);
this.blockId = json['blockId'];
};
/**
@ -279,8 +377,14 @@ Blockly.Events.CommentDelete.prototype.fromJson = function(json) {
*/
Blockly.Events.CommentDelete.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
var comment = null;
if (forward) {
var comment = workspace.getCommentById(this.commentId);
if (this.blockId) {
var block = workspace.getBlockById(this.blockId);
comment = block.comment;
} else {
comment = workspace.getCommentById(this.commentId);
}
if (comment) {
comment.dispose(false, false);
} else {
@ -288,9 +392,14 @@ Blockly.Events.CommentDelete.prototype.run = function(forward) {
console.warn("Can't delete non-existent comment: " + this.commentId);
}
} else {
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(xml, workspace);
if (this.blockId) {
var block = workspace.getBlockById(this.blockId);
block.setCommentText('', this.commentId, this.xy.x, this.xy.y, this.minimized);
} else {
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(xml, workspace);
}
}
};
@ -325,6 +434,12 @@ Blockly.Events.CommentMove = function(comment) {
* @type {!goog.math.Coordinate}
*/
this.newCoordinate_ = null;
/**
* The id of the block this comment belongs to or null if it's a workspace comment.
* @type {?string}
*/
this.blockId = comment.blockId || null;
};
goog.inherits(Blockly.Events.CommentMove, Blockly.Events.CommentBase);
@ -369,6 +484,9 @@ Blockly.Events.CommentMove.prototype.toJson = function() {
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
Math.round(this.newCoordinate_.y);
}
if (this.blockId) {
json['blockId'] = this.blockId;
}
return json;
};
@ -384,6 +502,9 @@ Blockly.Events.CommentMove.prototype.fromJson = function(json) {
this.newCoordinate_ =
new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
}
if (json['blockId']) {
this.blockId = json['blockId'];
}
};
/**
@ -400,14 +521,25 @@ Blockly.Events.CommentMove.prototype.isNull = function() {
*/
Blockly.Events.CommentMove.prototype.run = function(forward) {
var workspace = this.getEventWorkspace_();
var comment = workspace.getCommentById(this.commentId);
var comment = null;
if (this.blockId) {
var block = workspace.getBlockById(this.blockId);
comment = block.comment;
} else {
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);
if (comment instanceof Blockly.ScratchBlockComment) {
comment.moveTo(target.x, target.y);
} else {
// 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);
}
};

View file

@ -268,6 +268,7 @@ Blockly.Xml.scratchCommentToDom_ = function(block, element) {
if (commentText) {
var commentElement = goog.dom.createDom('comment', null, commentText);
if (typeof block.comment == 'object') {
commentElement.setAttribute('id', block.comment.id);
commentElement.setAttribute('pinned', block.comment.isVisible());
var xy = block.comment.getXY();
commentElement.setAttribute('x', xy.x);
@ -682,11 +683,12 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
}
break;
case 'comment':
var commentId = xmlChild.getAttribute('id');
var bubbleX = parseInt(xmlChild.getAttribute('x'), 10);
var bubbleY = parseInt(xmlChild.getAttribute('y'), 10);
var minimized = xmlChild.getAttribute('minimized') || false;
block.setCommentText(xmlChild.textContent, bubbleX, bubbleY,
block.setCommentText(xmlChild.textContent, commentId, bubbleX, bubbleY,
minimized == 'true');
var visible = xmlChild.getAttribute('pinned');