mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
Fixed comment ownership. (#2923)
* Moved comment icons to use a model-based system. The block holds the model of the comment, and the comment icon holds a reference to it. * Reorganized the setVisible function. * Changed how xml.js serializes and deserializes comments.
This commit is contained in:
committed by
Sam El-Husseini
parent
0726e4a909
commit
9e5df6216a
@@ -40,7 +40,6 @@ goog.require('Blockly.utils.Coordinate');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.fieldRegistry');
|
||||
goog.require('Blockly.utils.string');
|
||||
goog.require('Blockly.Warning');
|
||||
goog.require('Blockly.Workspace');
|
||||
|
||||
|
||||
@@ -128,9 +127,24 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
*/
|
||||
this.collapsed_ = false;
|
||||
|
||||
/** @type {string|Blockly.Comment} */
|
||||
/**
|
||||
* A string representing the comment attached to this block.
|
||||
* @type {string|Blockly.Comment}
|
||||
* @deprecated August 2019. Use getCommentText instead.
|
||||
*/
|
||||
this.comment = null;
|
||||
|
||||
/**
|
||||
* A model of the comment attached to this block.
|
||||
* @type {!Blockly.Block.CommentModel}
|
||||
* @package
|
||||
*/
|
||||
this.commentModel = {
|
||||
text: null,
|
||||
pinned: false,
|
||||
size: new Blockly.utils.Size(160, 80)
|
||||
};
|
||||
|
||||
/**
|
||||
* The block's position in workspace units. (0, 0) is at the workspace's
|
||||
* origin; scale does not change this value.
|
||||
@@ -205,6 +219,15 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* text:?string,
|
||||
* pinned:boolean,
|
||||
* size:Blockly.utils.Size
|
||||
* }}
|
||||
*/
|
||||
Blockly.Block.CommentModel;
|
||||
|
||||
/**
|
||||
* Optional text data that round-trips between blocks and XML.
|
||||
* Has no effect. May be used by 3rd parties for meta information.
|
||||
@@ -1783,11 +1806,11 @@ Blockly.Block.prototype.getInputTargetBlock = function(name) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the comment on this block (or '' if none).
|
||||
* Returns the comment on this block (or null if there is no comment).
|
||||
* @return {string} Block's comment.
|
||||
*/
|
||||
Blockly.Block.prototype.getCommentText = function() {
|
||||
return this.comment || '';
|
||||
return this.commentModel.text;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1795,11 +1818,13 @@ Blockly.Block.prototype.getCommentText = function() {
|
||||
* @param {?string} text The text, or null to delete.
|
||||
*/
|
||||
Blockly.Block.prototype.setCommentText = function(text) {
|
||||
if (this.comment != text) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'comment', null, this.comment, text || ''));
|
||||
this.comment = text;
|
||||
if (this.commentModel.text == text) {
|
||||
return;
|
||||
}
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this, 'comment', null, this.commentModel.text, text));
|
||||
this.commentModel.text = text;
|
||||
this.comment = text; // For backwards compatibility.
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,7 @@ goog.require('Blockly.utils.Coordinate');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.Rect');
|
||||
goog.require('Blockly.Warning');
|
||||
|
||||
|
||||
/**
|
||||
@@ -258,9 +259,17 @@ Blockly.BlockSvg.prototype.mutator = null;
|
||||
/**
|
||||
* Block's comment icon (if any).
|
||||
* @type {Blockly.Comment}
|
||||
* @deprecated August 2019. Use getCommentIcon instead.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.comment = null;
|
||||
|
||||
/**
|
||||
* Block's comment icon (if any).
|
||||
* @type {Blockly.Comment}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.commentIcon_ = null;
|
||||
|
||||
/**
|
||||
* Block's warning icon (if any).
|
||||
* @type {Blockly.Warning}
|
||||
@@ -276,8 +285,8 @@ Blockly.BlockSvg.prototype.getIcons = function() {
|
||||
if (this.mutator) {
|
||||
icons.push(this.mutator);
|
||||
}
|
||||
if (this.comment) {
|
||||
icons.push(this.comment);
|
||||
if (this.commentIcon_) {
|
||||
icons.push(this.commentIcon_);
|
||||
}
|
||||
if (this.warning) {
|
||||
icons.push(this.warning);
|
||||
@@ -951,28 +960,11 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
||||
this.warningTextDb_ = null;
|
||||
}
|
||||
|
||||
// If the block is rendered we need to record the event before disposing of
|
||||
// the icons to prevent losing information.
|
||||
// TODO (#1969): Remove event generation/firing once comments are fixed.
|
||||
this.unplug(healStack);
|
||||
var deleteEvent;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
deleteEvent = new Blockly.Events.BlockDelete(this);
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var icons = this.getIcons();
|
||||
for (var i = 0; i < icons.length; i++) {
|
||||
icons[i].dispose();
|
||||
}
|
||||
// TODO (#1969): Move out of disable block once comments are fixed.
|
||||
Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled() && deleteEvent) {
|
||||
Blockly.Events.fire(deleteEvent);
|
||||
var icons = this.getIcons();
|
||||
for (var i = 0; i < icons.length; i++) {
|
||||
icons[i].dispose();
|
||||
}
|
||||
Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
|
||||
|
||||
Blockly.utils.dom.removeNode(this.svgGroup_);
|
||||
blockWorkspace.resizeContents();
|
||||
@@ -1072,16 +1064,12 @@ Blockly.BlockSvg.prototype.updateDisabled = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the comment on this block (or '' if none).
|
||||
* @return {string} Block's comment.
|
||||
* Get the comment icon attached to this block, or null if the block has no
|
||||
* comment.
|
||||
* @return {Blockly.Comment} The comment icon attached to this block, or null.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getCommentText = function() {
|
||||
if (this.comment) {
|
||||
var comment = this.comment.getText();
|
||||
// Trim off trailing whitespace.
|
||||
return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
|
||||
}
|
||||
return '';
|
||||
Blockly.BlockSvg.prototype.getCommentIcon = function() {
|
||||
return this.commentIcon_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1089,23 +1077,30 @@ Blockly.BlockSvg.prototype.getCommentText = function() {
|
||||
* @param {?string} text The text, or null to delete.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.setCommentText = function(text) {
|
||||
var changedState = false;
|
||||
if (typeof text == 'string') {
|
||||
if (!this.comment) {
|
||||
if (!Blockly.Comment) {
|
||||
throw Error('Missing require for Blockly.Comment');
|
||||
}
|
||||
this.comment = new Blockly.Comment(this);
|
||||
changedState = true;
|
||||
}
|
||||
this.comment.setText(/** @type {string} */ (text));
|
||||
} else {
|
||||
if (this.comment) {
|
||||
this.comment.dispose();
|
||||
changedState = true;
|
||||
}
|
||||
if (!Blockly.Comment) {
|
||||
throw Error('Missing require for Blockly.Comment');
|
||||
}
|
||||
if (changedState && this.rendered) {
|
||||
if (this.commentModel.text == text) {
|
||||
return;
|
||||
}
|
||||
Blockly.BlockSvg.superClass_.setCommentText.call(this, text);
|
||||
|
||||
var shouldHaveComment = text != null;
|
||||
if (!!this.commentIcon_ == shouldHaveComment) {
|
||||
// If the comment's state of existence is correct, but the text is new
|
||||
// that means we're just updating a comment.
|
||||
this.commentIcon_.updateText();
|
||||
return;
|
||||
}
|
||||
if (shouldHaveComment) {
|
||||
this.commentIcon_ = new Blockly.Comment(this);
|
||||
this.comment = this.commentIcon_; // For backwards compatibility.
|
||||
} else {
|
||||
this.commentIcon_.dispose();
|
||||
this.commentIcon_ = null;
|
||||
this.comment = null; // For backwards compatibility.
|
||||
}
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
// Adding or removing a comment icon will cause the block to change shape.
|
||||
this.bumpNeighbours_();
|
||||
|
||||
@@ -625,10 +625,10 @@ Blockly.Bubble.prototype.moveTo = function(x, y) {
|
||||
|
||||
/**
|
||||
* Get the dimensions of this bubble.
|
||||
* @return {!Object} Object with width and height properties.
|
||||
* @return {!Blockly.utils.Size} The height and width of the bubble.
|
||||
*/
|
||||
Blockly.Bubble.prototype.getBubbleSize = function() {
|
||||
return {width: this.width_, height: this.height_};
|
||||
return new Blockly.utils.Size(this.width_, this.height_);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -657,13 +657,12 @@ Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
|
||||
(height - doubleBorderWidth) + ')');
|
||||
}
|
||||
}
|
||||
if (this.rendered_) {
|
||||
if (this.autoLayout_) {
|
||||
this.layoutBubble_();
|
||||
}
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
if (this.autoLayout_) {
|
||||
this.layoutBubble_();
|
||||
}
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
|
||||
// Allow the contents to resize.
|
||||
if (this.resizeCallback_) {
|
||||
this.resizeCallback_();
|
||||
|
||||
252
core/comment.js
252
core/comment.js
@@ -44,28 +44,29 @@ goog.require('Blockly.utils.userAgent');
|
||||
*/
|
||||
Blockly.Comment = function(block) {
|
||||
Blockly.Comment.superClass_.constructor.call(this, block);
|
||||
|
||||
/**
|
||||
* The model for this comment.
|
||||
* @type {!Blockly.Block.CommentModel}
|
||||
* @private
|
||||
*/
|
||||
this.model_ = block.commentModel;
|
||||
// If someone creates the comment directly instead of calling
|
||||
// block.setCommentText we want to make sure the text is non-null;
|
||||
this.model_.text = this.model_.text || '';
|
||||
|
||||
/**
|
||||
* The model's text value at the start of an edit.
|
||||
* Used to tell if an event should be fired at the end of an edit.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.cachedText_ = '';
|
||||
|
||||
this.createIcon();
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.Comment, Blockly.Icon);
|
||||
|
||||
/**
|
||||
* Comment text (if bubble is not visible).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.text_ = '';
|
||||
|
||||
/**
|
||||
* Width of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.width_ = 160;
|
||||
|
||||
/**
|
||||
* Height of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.height_ = 80;
|
||||
|
||||
/**
|
||||
* Draw the comment icon.
|
||||
* @param {!Element} group The icon group.
|
||||
@@ -104,7 +105,8 @@ Blockly.Comment.prototype.drawIcon_ = function(group) {
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.createEditor_ = function() {
|
||||
/* Create the editor. Here's the markup that will be generated:
|
||||
/* Create the editor. Here's the markup that will be generated in
|
||||
* editable mode:
|
||||
<foreignObject x="8" y="8" width="164" height="164">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
|
||||
<textarea xmlns="http://www.w3.org/1999/xhtml"
|
||||
@@ -112,33 +114,49 @@ Blockly.Comment.prototype.createEditor_ = function() {
|
||||
style="height: 164px; width: 164px;"></textarea>
|
||||
</body>
|
||||
</foreignObject>
|
||||
*/
|
||||
* For non-editable mode see Warning.textToDom_.
|
||||
*/
|
||||
|
||||
this.foreignObject_ = Blockly.utils.dom.createSvgElement('foreignObject',
|
||||
{'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
|
||||
null);
|
||||
|
||||
var body = document.createElementNS(Blockly.utils.dom.HTML_NS, 'body');
|
||||
body.setAttribute('xmlns', Blockly.utils.dom.HTML_NS);
|
||||
body.className = 'blocklyMinimalBody';
|
||||
var textarea = document.createElementNS(Blockly.utils.dom.HTML_NS, 'textarea');
|
||||
|
||||
this.textarea_ = document.createElementNS(
|
||||
Blockly.utils.dom.HTML_NS, 'textarea');
|
||||
var textarea = this.textarea_;
|
||||
textarea.className = 'blocklyCommentTextarea';
|
||||
textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
|
||||
textarea.value = this.model_.text;
|
||||
this.resizeTextarea_();
|
||||
|
||||
body.appendChild(textarea);
|
||||
this.textarea_ = textarea;
|
||||
this.foreignObject_.appendChild(body);
|
||||
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_,
|
||||
|
||||
// Ideally this would be hooked to the focus event for the comment.
|
||||
// However doing so in Firefox swallows the cursor for unknown reasons.
|
||||
// So this is hooked to mouseup instead. No big deal.
|
||||
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.startEdit_,
|
||||
true, true);
|
||||
// Don't zoom with mousewheel.
|
||||
Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
Blockly.bindEventWithChecks_(textarea, 'change', this, function(_e) {
|
||||
if (this.text_ != textarea.value) {
|
||||
if (this.cachedText_ != this.model_.text) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, textarea.value));
|
||||
this.text_ = textarea.value;
|
||||
this.block_, 'comment', null, this.cachedText_, this.model_.text));
|
||||
}
|
||||
});
|
||||
Blockly.bindEventWithChecks_(textarea, 'input', this, function(_e) {
|
||||
this.model_.text = textarea.value;
|
||||
});
|
||||
|
||||
setTimeout(textarea.focus.bind(textarea), 0);
|
||||
|
||||
return this.foreignObject_;
|
||||
};
|
||||
|
||||
@@ -147,13 +165,12 @@ Blockly.Comment.prototype.createEditor_ = function() {
|
||||
* @override
|
||||
*/
|
||||
Blockly.Comment.prototype.updateEditable = function() {
|
||||
Blockly.Comment.superClass_.updateEditable.call(this);
|
||||
if (this.isVisible()) {
|
||||
// Toggling visibility will force a rerendering.
|
||||
this.setVisible(false);
|
||||
this.setVisible(true);
|
||||
// Recreate the bubble with the correct UI.
|
||||
this.disposeBubble_();
|
||||
this.createBubble_();
|
||||
}
|
||||
// Allow the icon to update.
|
||||
Blockly.Icon.prototype.updateEditable.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -161,15 +178,28 @@ Blockly.Comment.prototype.updateEditable = function() {
|
||||
* Resize the text area accordingly.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.resizeBubble_ = function() {
|
||||
if (this.isVisible()) {
|
||||
var size = this.bubble_.getBubbleSize();
|
||||
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
|
||||
this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
|
||||
this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
|
||||
this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
|
||||
Blockly.Comment.prototype.onBubbleResize_ = function() {
|
||||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
this.model_.size = this.bubble_.getBubbleSize();
|
||||
this.resizeTextarea_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the text area to match the size defined on the model (which is
|
||||
* the size of the bubble).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.resizeTextarea_ = function() {
|
||||
var size = this.model_.size;
|
||||
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
var widthMinusBorder = size.width - doubleBorderWidth;
|
||||
var heightMinusBorder = size.height - doubleBorderWidth;
|
||||
this.foreignObject_.setAttribute('width', widthMinusBorder);
|
||||
this.foreignObject_.setAttribute('height', heightMinusBorder);
|
||||
this.textarea_.style.width = (widthMinusBorder - 4) + 'px';
|
||||
this.textarea_.style.height = (heightMinusBorder - 4) + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -178,71 +208,99 @@ Blockly.Comment.prototype.resizeBubble_ = function() {
|
||||
*/
|
||||
Blockly.Comment.prototype.setVisible = function(visible) {
|
||||
if (visible == this.isVisible()) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
|
||||
if ((!this.block_.isEditable() && !this.textarea_) ||
|
||||
Blockly.utils.userAgent.IE) {
|
||||
this.model_.pinned = visible;
|
||||
if (visible) {
|
||||
this.createBubble_();
|
||||
} else {
|
||||
this.disposeBubble_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the bubble. Handles deciding if it should be editable or not.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.createBubble_ = function() {
|
||||
if (!this.block_.isEditable() || Blockly.utils.userAgent.IE) {
|
||||
// Steal the code from warnings to make an uneditable text bubble.
|
||||
// MSIE does not support foreignobject; textareas are impossible.
|
||||
// https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034
|
||||
// Always treat comments in IE as uneditable.
|
||||
Blockly.Warning.prototype.setVisible.call(this, visible);
|
||||
return;
|
||||
}
|
||||
// Save the bubble stats before the visibility switch.
|
||||
var text = this.getText();
|
||||
var size = this.getBubbleSize();
|
||||
if (visible) {
|
||||
// Create the bubble.
|
||||
this.bubble_ = new Blockly.Bubble(
|
||||
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
|
||||
this.createEditor_(), this.block_.svgPath_,
|
||||
this.iconXY_, this.width_, this.height_);
|
||||
// Expose this comment's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
|
||||
this.updateColour();
|
||||
this.createNonEditableBubble_();
|
||||
} else {
|
||||
// Dispose of the bubble.
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
this.textarea_ = null;
|
||||
this.foreignObject_ = null;
|
||||
this.createEditableBubble_();
|
||||
}
|
||||
// Restore the bubble stats after the visibility switch.
|
||||
this.setText(text);
|
||||
this.setBubbleSize(size.width, size.height);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bring the comment to the top of the stack when clicked on.
|
||||
* Show an editable bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.createEditableBubble_ = function() {
|
||||
this.bubble_ = new Blockly.Bubble(
|
||||
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
|
||||
this.createEditor_(), this.block_.svgPath_,
|
||||
this.iconXY_, this.model_.size.width, this.model_.size.height);
|
||||
// Expose this comment's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
|
||||
this.updateColour();
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a non-editable bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.createNonEditableBubble_ = function() {
|
||||
// TODO (#2917): It would be great if the comment could support line breaks.
|
||||
Blockly.Warning.prototype.createBubble.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of the bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.disposeBubble_ = function() {
|
||||
if (this.paragraphElement_) {
|
||||
// We're using the warning UI so we have to let it dispose.
|
||||
Blockly.Warning.prototype.disposeBubble.call(this);
|
||||
return;
|
||||
}
|
||||
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
this.textarea_ = null;
|
||||
this.foreignObject_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback fired when an edit starts.
|
||||
*
|
||||
* Bring the comment to the top of the stack when clicked on. Also cache the
|
||||
* current text so it can be used to fire a change event.
|
||||
* @param {!Event} _e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.textareaFocus_ = function(_e) {
|
||||
// Ideally this would be hooked to the focus event for the comment.
|
||||
// However doing so in Firefox swallows the cursor for unknown reasons.
|
||||
// So this is hooked to mouseup instead. No big deal.
|
||||
Blockly.Comment.prototype.startEdit_ = function(_e) {
|
||||
if (this.bubble_.promote_()) {
|
||||
// Since the act of moving this node within the DOM causes a loss of focus,
|
||||
// we need to reapply the focus.
|
||||
this.textarea_.focus();
|
||||
}
|
||||
|
||||
this.cachedText_ = this.model_.text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the dimensions of this comment's bubble.
|
||||
* @return {!Object} Object with width and height properties.
|
||||
* @return {Blockly.utils.Size} Object with width and height properties.
|
||||
*/
|
||||
Blockly.Comment.prototype.getBubbleSize = function() {
|
||||
if (this.isVisible()) {
|
||||
return this.bubble_.getBubbleSize();
|
||||
} else {
|
||||
return {width: this.width_, height: this.height_};
|
||||
}
|
||||
return this.model_.size;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -251,44 +309,60 @@ Blockly.Comment.prototype.getBubbleSize = function() {
|
||||
* @param {number} height Height of the bubble.
|
||||
*/
|
||||
Blockly.Comment.prototype.setBubbleSize = function(width, height) {
|
||||
if (this.textarea_) {
|
||||
if (this.bubble_) {
|
||||
this.bubble_.setBubbleSize(width, height);
|
||||
} else {
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
this.model_.size.width = width;
|
||||
this.model_.size.height = height;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this comment's text.
|
||||
* @return {string} Comment text.
|
||||
* @deprecated August 2019 Use block.getCommentText() instead.
|
||||
*/
|
||||
Blockly.Comment.prototype.getText = function() {
|
||||
return this.textarea_ ? this.textarea_.value : this.text_;
|
||||
return this.model_.text || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Set this comment's text.
|
||||
*
|
||||
* If you want to receive a comment change event, then this should not be called
|
||||
* directly. Instead call block.setCommentText();
|
||||
* @param {string} text Comment text.
|
||||
* @deprecated August 2019 Use block.setCommentText() instead.
|
||||
*/
|
||||
Blockly.Comment.prototype.setText = function(text) {
|
||||
if (this.text_ != text) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, text));
|
||||
this.text_ = text;
|
||||
if (this.model_.text == text) {
|
||||
return;
|
||||
}
|
||||
this.model_.text = text;
|
||||
this.updateText();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the comment's view to match the model.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Comment.prototype.updateText = function() {
|
||||
if (this.textarea_) {
|
||||
this.textarea_.value = text;
|
||||
this.textarea_.value = this.model_.text;
|
||||
} else if (this.paragraphElement_) {
|
||||
// Non-Editable mode.
|
||||
// TODO (#2917): If 2917 gets added this will probably need to be updated.
|
||||
this.paragraphElement_.firstChild.textContent = this.model_.text;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this comment.
|
||||
*
|
||||
* If you want to receive a comment "delete" event (newValue: null), then this
|
||||
* should not be called directly. Instead call block.setCommentText(null);
|
||||
*/
|
||||
Blockly.Comment.prototype.dispose = function() {
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
this.setText(''); // Fire event to delete comment.
|
||||
}
|
||||
this.block_.comment = null;
|
||||
Blockly.Icon.prototype.dispose.call(this);
|
||||
};
|
||||
|
||||
@@ -34,10 +34,15 @@ goog.require('Blockly.utils.Size');
|
||||
|
||||
/**
|
||||
* Class for an icon.
|
||||
* @param {Blockly.Block} block The block associated with this icon.
|
||||
* @param {Blockly.BlockSvg} block The block associated with this icon.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Icon = function(block) {
|
||||
/**
|
||||
* The block this icon is attached to.
|
||||
* @type {Blockly.BlockSvg}
|
||||
* @protected
|
||||
*/
|
||||
this.block_ = block;
|
||||
};
|
||||
|
||||
@@ -108,6 +113,7 @@ Blockly.Icon.prototype.dispose = function() {
|
||||
* Add or remove the UI indicating if this icon may be clicked or not.
|
||||
*/
|
||||
Blockly.Icon.prototype.updateEditable = function() {
|
||||
// No-op on the base class.
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -183,6 +183,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
* Add or remove the UI indicating if this icon may be clicked or not.
|
||||
*/
|
||||
Blockly.Mutator.prototype.updateEditable = function() {
|
||||
Blockly.Mutator.superClass_.updateEditable.call(this);
|
||||
if (!this.block_.isInFlyout) {
|
||||
if (this.block_.isEditable()) {
|
||||
if (this.iconGroup_) {
|
||||
@@ -200,8 +201,6 @@ Blockly.Mutator.prototype.updateEditable = function() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default behaviour for an icon.
|
||||
Blockly.Icon.prototype.updateEditable.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -297,7 +296,6 @@ Blockly.Mutator.prototype.setVisible = function(visible) {
|
||||
};
|
||||
this.block_.workspace.addChangeListener(this.sourceListener_);
|
||||
}
|
||||
this.resizeBubble_();
|
||||
// When the mutator's workspace changes, update the source block.
|
||||
this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
|
||||
this.updateColour();
|
||||
|
||||
@@ -116,40 +116,57 @@ Blockly.Warning.textToDom_ = function(text) {
|
||||
*/
|
||||
Blockly.Warning.prototype.setVisible = function(visible) {
|
||||
if (visible == this.isVisible()) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible));
|
||||
if (visible) {
|
||||
// Create the bubble to display all warnings.
|
||||
var paragraph = Blockly.Warning.textToDom_(this.getText());
|
||||
this.bubble_ = new Blockly.Bubble(
|
||||
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
|
||||
paragraph, this.block_.svgPath_, this.iconXY_, null, null);
|
||||
// Expose this warning's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
if (this.block_.RTL) {
|
||||
// Right-align the paragraph.
|
||||
// This cannot be done until the bubble is rendered on screen.
|
||||
var maxWidth = paragraph.getBBox().width;
|
||||
for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) {
|
||||
textElement.setAttribute('text-anchor', 'end');
|
||||
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
|
||||
}
|
||||
}
|
||||
this.updateColour();
|
||||
// Bump the warning into the right location.
|
||||
var size = this.bubble_.getBubbleSize();
|
||||
this.bubble_.setBubbleSize(size.width, size.height);
|
||||
this.createBubble();
|
||||
} else {
|
||||
// Dispose of the bubble.
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
this.body_ = null;
|
||||
this.disposeBubble();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the bubble.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Warning.prototype.createBubble = function() {
|
||||
// TODO (#2943): This is package because comments steal this UI for
|
||||
// non-editable comments, but really this should be private.
|
||||
this.paragraphElement_ = Blockly.Warning.textToDom_(this.getText());
|
||||
this.bubble_ = new Blockly.Bubble(
|
||||
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
|
||||
this.paragraphElement_, this.block_.svgPath_, this.iconXY_, null, null);
|
||||
// Expose this warning's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
if (this.block_.RTL) {
|
||||
// Right-align the paragraph.
|
||||
// This cannot be done until the bubble is rendered on screen.
|
||||
var maxWidth = this.paragraphElement_.getBBox().width;
|
||||
for (var i = 0, textElement;
|
||||
textElement = this.paragraphElement_.childNodes[i]; i++) {
|
||||
|
||||
textElement.setAttribute('text-anchor', 'end');
|
||||
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
|
||||
}
|
||||
}
|
||||
this.updateColour();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of the bubble and references to it.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Warning.prototype.disposeBubble = function() {
|
||||
// TODO (#2943): This is package because comments steal this UI for
|
||||
// non-editable comments, but really this should be private.
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
this.body_ = null;
|
||||
this.paragraphElement_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bring the warning to the top of the stack when clicked on.
|
||||
* @param {!Event} _e Mouse up event.
|
||||
@@ -162,7 +179,8 @@ Blockly.Warning.prototype.bodyFocus_ = function(_e) {
|
||||
|
||||
/**
|
||||
* Set this warning's text.
|
||||
* @param {string} text Warning text (or '' to delete).
|
||||
* @param {string} text Warning text (or '' to delete). This supports
|
||||
* linebreaks.
|
||||
* @param {string} id An ID for this text entry to be able to maintain
|
||||
* multiple warnings.
|
||||
*/
|
||||
|
||||
42
core/xml.js
42
core/xml.js
@@ -163,14 +163,15 @@ Blockly.Xml.blockToDom = function(block, opt_noId) {
|
||||
|
||||
var commentText = block.getCommentText();
|
||||
if (commentText) {
|
||||
var size = block.commentModel.size;
|
||||
var pinned = block.commentModel.pinned;
|
||||
|
||||
var commentElement = Blockly.utils.xml.createElement('comment');
|
||||
commentElement.appendChild(Blockly.utils.xml.createTextNode(commentText));
|
||||
if (typeof block.comment == 'object') {
|
||||
commentElement.setAttribute('pinned', block.comment.isVisible());
|
||||
var hw = block.comment.getBubbleSize();
|
||||
commentElement.setAttribute('h', hw.height);
|
||||
commentElement.setAttribute('w', hw.width);
|
||||
}
|
||||
commentElement.setAttribute('pinned', pinned);
|
||||
commentElement.setAttribute('h', size.height);
|
||||
commentElement.setAttribute('w', size.width);
|
||||
|
||||
element.appendChild(commentElement);
|
||||
}
|
||||
|
||||
@@ -655,22 +656,21 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
|
||||
}
|
||||
break;
|
||||
case 'comment':
|
||||
block.setCommentText(xmlChild.textContent);
|
||||
var visible = xmlChild.getAttribute('pinned');
|
||||
if (visible && !block.isInFlyout) {
|
||||
// Give the renderer a millisecond to render and position the block
|
||||
// before positioning the comment bubble.
|
||||
setTimeout(function() {
|
||||
if (block.comment && block.comment.setVisible) {
|
||||
block.comment.setVisible(visible == 'true');
|
||||
}
|
||||
}, 1);
|
||||
var text = xmlChild.textContent;
|
||||
var pinned = xmlChild.getAttribute('pinned') == 'true';
|
||||
var width = parseInt(xmlChild.getAttribute('w'), 10);
|
||||
var height = parseInt(xmlChild.getAttribute('h'), 10);
|
||||
|
||||
block.setCommentText(text);
|
||||
block.commentModel.pinned = pinned;
|
||||
if (!isNaN(width) && !isNaN(height)) {
|
||||
block.commentModel.size = new Blockly.utils.Size(width, height);
|
||||
}
|
||||
var bubbleW = parseInt(xmlChild.getAttribute('w'), 10);
|
||||
var bubbleH = parseInt(xmlChild.getAttribute('h'), 10);
|
||||
if (!isNaN(bubbleW) && !isNaN(bubbleH) &&
|
||||
block.comment && block.comment.setVisible) {
|
||||
block.comment.setBubbleSize(bubbleW, bubbleH);
|
||||
|
||||
if (pinned && block.getCommentIcon && !block.isInFlyout) {
|
||||
setTimeout(function() {
|
||||
block.getCommentIcon().setVisible(true);
|
||||
}, 1);
|
||||
}
|
||||
break;
|
||||
case 'data':
|
||||
|
||||
@@ -213,8 +213,9 @@ Blockly.Dart.scrub_ = function(block, code, opt_thisOnly) {
|
||||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.string.wrap(comment, Blockly.Dart.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.Dart.COMMENT_WRAP - 3);
|
||||
if (block.getProcedureDef) {
|
||||
// Use documentation comment for function comments.
|
||||
commentCode += Blockly.Dart.prefixLines(comment + '\n', '/// ');
|
||||
|
||||
@@ -254,9 +254,9 @@ Blockly.JavaScript.scrub_ = function(block, code, opt_thisOnly) {
|
||||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.JavaScript.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.JavaScript.COMMENT_WRAP - 3);
|
||||
if (block.getProcedureDef) {
|
||||
// Use a comment block for function comments.
|
||||
commentCode += '/**\n' +
|
||||
|
||||
@@ -187,8 +187,9 @@ Blockly.Lua.scrub_ = function(block, code, opt_thisOnly) {
|
||||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.string.wrap(comment, Blockly.Lua.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.Lua.COMMENT_WRAP - 3);
|
||||
commentCode += Blockly.Lua.prefixLines(comment, '-- ') + '\n';
|
||||
}
|
||||
// Collect comments for all value arguments.
|
||||
|
||||
@@ -240,8 +240,9 @@ Blockly.PHP.scrub_ = function(block, code, opt_thisOnly) {
|
||||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.string.wrap(comment, Blockly.PHP.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.PHP.COMMENT_WRAP - 3);
|
||||
commentCode += Blockly.PHP.prefixLines(comment, '// ') + '\n';
|
||||
}
|
||||
// Collect comments for all value arguments.
|
||||
|
||||
@@ -268,9 +268,9 @@ Blockly.Python.scrub_ = function(block, code, opt_thisOnly) {
|
||||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.Python.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
comment = Blockly.utils.string.wrap(comment,
|
||||
Blockly.Python.COMMENT_WRAP - 3);
|
||||
if (block.getProcedureDef) {
|
||||
// Use a comment block for function comments.
|
||||
commentCode += '"""' + comment + '\n"""\n';
|
||||
|
||||
@@ -410,11 +410,137 @@ suite('Blocks', function() {
|
||||
.connect(blockB.previousConnection);
|
||||
|
||||
this.blockA.removeInput('STATEMENT');
|
||||
console.log(blockB.disposed, blockB);
|
||||
chai.assert.isTrue(blockB.disposed);
|
||||
chai.assert.equal(this.blockA.getChildren().length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('Comments', function() {
|
||||
setup(function() {
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "empty_block",
|
||||
"message0": "",
|
||||
"args0": []
|
||||
},
|
||||
]);
|
||||
this.eventSpy = sinon.spy(Blockly.Events, 'fire');
|
||||
});
|
||||
teardown(function() {
|
||||
delete Blockly.Blocks['empty_block'];
|
||||
this.eventSpy.restore();
|
||||
});
|
||||
suite('Set/Get Text', function() {
|
||||
function assertCommentEvent(eventSpy, oldValue, newValue) {
|
||||
var calls = eventSpy.getCalls();
|
||||
var event = calls[calls.length - 1].args[0];
|
||||
chai.assert.equal(event.type, Blockly.Events.BLOCK_CHANGE);
|
||||
chai.assert.equal(event.element, 'comment');
|
||||
chai.assert.equal(event.oldValue, oldValue);
|
||||
chai.assert.equal(event.newValue, newValue);
|
||||
}
|
||||
function assertNoCommentEvent(eventSpy) {
|
||||
var calls = eventSpy.getCalls();
|
||||
var event = calls[calls.length - 1].args[0];
|
||||
chai.assert.notEqual(event.type, Blockly.Events.BLOCK_CHANGE);
|
||||
}
|
||||
suite('Headless', function() {
|
||||
setup(function() {
|
||||
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.workspace);
|
||||
});
|
||||
test('Text', function() {
|
||||
this.block.setCommentText('test text');
|
||||
chai.assert.equal(this.block.getCommentText(), 'test text');
|
||||
assertCommentEvent(this.eventSpy, null, 'test text');
|
||||
});
|
||||
test('Text Empty', function() {
|
||||
this.block.setCommentText('');
|
||||
chai.assert.equal(this.block.getCommentText(), '');
|
||||
assertCommentEvent(this.eventSpy, null, '');
|
||||
});
|
||||
test('Text Null', function() {
|
||||
this.block.setCommentText(null);
|
||||
chai.assert.equal(this.block.getCommentText(), null);
|
||||
assertNoCommentEvent(this.eventSpy);
|
||||
});
|
||||
test('Text -> Null', function() {
|
||||
this.block.setCommentText('first text');
|
||||
|
||||
this.block.setCommentText(null);
|
||||
chai.assert.equal(this.block.getCommentText(), null);
|
||||
assertCommentEvent(this.eventSpy, 'first text', null);
|
||||
});
|
||||
});
|
||||
suite('Rendered', function() {
|
||||
setup(function() {
|
||||
// Let the parent teardown take care of this.
|
||||
this.workspace = Blockly.inject('blocklyDiv', {
|
||||
comments: true,
|
||||
scrollbars: true
|
||||
});
|
||||
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.workspace);
|
||||
});
|
||||
test('Text', function() {
|
||||
this.block.setCommentText('test text');
|
||||
chai.assert.equal(this.block.getCommentText(), 'test text');
|
||||
assertCommentEvent(this.eventSpy, null, 'test text');
|
||||
});
|
||||
test('Text Empty', function() {
|
||||
this.block.setCommentText('');
|
||||
chai.assert.equal(this.block.getCommentText(), '');
|
||||
assertCommentEvent(this.eventSpy, null, '');
|
||||
});
|
||||
test('Text Null', function() {
|
||||
this.block.setCommentText(null);
|
||||
chai.assert.equal(this.block.getCommentText(), null);
|
||||
assertNoCommentEvent(this.eventSpy);
|
||||
});
|
||||
test('Text -> Null', function() {
|
||||
this.block.setCommentText('first text');
|
||||
|
||||
this.block.setCommentText(null);
|
||||
chai.assert.equal(this.block.getCommentText(), null);
|
||||
assertCommentEvent(this.eventSpy, 'first text', null);
|
||||
});
|
||||
test('Set While Visible - Editable', function() {
|
||||
this.block.setCommentText('test1');
|
||||
var icon = this.block.getCommentIcon();
|
||||
icon.setVisible(true);
|
||||
|
||||
this.block.setCommentText('test2');
|
||||
chai.assert.equal(this.block.getCommentText(), 'test2');
|
||||
assertCommentEvent(this.eventSpy, 'test1', 'test2');
|
||||
chai.assert.equal(icon.textarea_.value, 'test2');
|
||||
});
|
||||
test('Set While Visible - NonEditable', function() {
|
||||
this.block.setCommentText('test1');
|
||||
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
|
||||
var icon = this.block.getCommentIcon();
|
||||
icon.setVisible(true);
|
||||
|
||||
this.block.setCommentText('test2');
|
||||
chai.assert.equal(this.block.getCommentText(), 'test2');
|
||||
assertCommentEvent(this.eventSpy, 'test1', 'test2');
|
||||
chai.assert.equal(icon.paragraphElement_.firstChild.textContent,
|
||||
'test2');
|
||||
|
||||
editableStub.restore();
|
||||
});
|
||||
test('Get Text While Editing', function() {
|
||||
this.block.setCommentText('test1');
|
||||
var icon = this.block.getCommentIcon();
|
||||
icon.setVisible(true);
|
||||
icon.textarea_.value = 'test2';
|
||||
icon.textarea_.dispatchEvent(new Event('input'));
|
||||
|
||||
chai.assert.equal(this.block.getCommentText(), 'test2');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
144
tests/mocha/comment_test.js
Normal file
144
tests/mocha/comment_test.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
suite('Comments', function() {
|
||||
setup(function() {
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "empty_block",
|
||||
"message0": "",
|
||||
"args0": []
|
||||
},
|
||||
]);
|
||||
|
||||
this.workspace = Blockly.inject('blocklyDiv', {
|
||||
comments: true,
|
||||
scrollbars: true
|
||||
});
|
||||
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.workspace);
|
||||
this.comment = new Blockly.Comment(this.block);
|
||||
this.comment.computeIconLocation();
|
||||
});
|
||||
teardown(function() {
|
||||
delete Blockly.Blocks['empty_block'];
|
||||
this.workspace.dispose();
|
||||
});
|
||||
suite('Visibility and Editability', function() {
|
||||
setup(function() {
|
||||
this.comment.setText('test text');
|
||||
this.eventSpy = sinon.stub(Blockly.Events, 'fire');
|
||||
});
|
||||
teardown(function() {
|
||||
this.eventSpy.restore();
|
||||
});
|
||||
function assertEvent(eventSpy, type, element, oldValue, newValue) {
|
||||
var calls = eventSpy.getCalls();
|
||||
var event = calls[calls.length - 1].args[0];
|
||||
chai.assert.equal(event.type, type);
|
||||
chai.assert.equal(event.element, element);
|
||||
chai.assert.equal(event.oldValue, oldValue);
|
||||
chai.assert.equal(event.newValue, newValue);
|
||||
}
|
||||
function assertEditable(comment) {
|
||||
chai.assert.isNotOk(comment.paragraphElement_);
|
||||
chai.assert.isOk(comment.textarea_);
|
||||
chai.assert.equal(comment.textarea_.value, 'test text');
|
||||
}
|
||||
function assertNotEditable(comment) {
|
||||
chai.assert.isNotOk(comment.textarea_);
|
||||
chai.assert.isOk(comment.paragraphElement_);
|
||||
chai.assert.equal(comment.paragraphElement_.firstChild.textContent,
|
||||
'test text');
|
||||
}
|
||||
test('Editable', function() {
|
||||
this.comment.setVisible(true);
|
||||
chai.assert.isTrue(this.comment.isVisible());
|
||||
assertEditable(this.comment);
|
||||
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
|
||||
});
|
||||
test('Not Editable', function() {
|
||||
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
|
||||
|
||||
this.comment.setVisible(true);
|
||||
chai.assert.isTrue(this.comment.isVisible());
|
||||
assertNotEditable(this.comment);
|
||||
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
|
||||
|
||||
editableStub.restore();
|
||||
});
|
||||
test('Editable -> Not Editable', function() {
|
||||
this.comment.setVisible(true);
|
||||
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
|
||||
|
||||
this.comment.updateEditable();
|
||||
chai.assert.isTrue(this.comment.isVisible());
|
||||
assertNotEditable(this.comment);
|
||||
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
|
||||
|
||||
editableStub.restore();
|
||||
});
|
||||
test('Not Editable -> Editable', function() {
|
||||
var editableStub = sinon.stub(this.block, 'isEditable').returns(false);
|
||||
this.comment.setVisible(true);
|
||||
editableStub.returns(true);
|
||||
|
||||
this.comment.updateEditable();
|
||||
chai.assert.isTrue(this.comment.isVisible());
|
||||
assertEditable(this.comment);
|
||||
assertEvent(this.eventSpy, Blockly.Events.UI, 'commentOpen', false, true);
|
||||
|
||||
editableStub.restore();
|
||||
});
|
||||
});
|
||||
suite('Set/Get Bubble Size', function() {
|
||||
function assertBubbleSize(comment, height, width) {
|
||||
var size = comment.getBubbleSize();
|
||||
chai.assert.equal(size.height, height);
|
||||
chai.assert.equal(size.width, width);
|
||||
}
|
||||
function assertBubbleSizeDefault(comment) {
|
||||
assertBubbleSize(comment, 80, 160);
|
||||
}
|
||||
test('Set Size While Visible', function() {
|
||||
this.comment.setVisible(true);
|
||||
var bubbleSizeSpy = sinon.spy(this.comment.bubble_, 'setBubbleSize');
|
||||
|
||||
assertBubbleSizeDefault(this.comment);
|
||||
this.comment.setBubbleSize(100, 100);
|
||||
assertBubbleSize(this.comment, 100, 100);
|
||||
chai.assert(bubbleSizeSpy.calledOnce);
|
||||
|
||||
this.comment.setVisible(false);
|
||||
assertBubbleSize(this.comment, 100, 100);
|
||||
|
||||
bubbleSizeSpy.restore();
|
||||
});
|
||||
test('Set Size While Invisible', function() {
|
||||
assertBubbleSizeDefault(this.comment);
|
||||
this.comment.setBubbleSize(100, 100);
|
||||
assertBubbleSize(this.comment, 100, 100);
|
||||
|
||||
this.comment.setVisible(true);
|
||||
assertBubbleSize(this.comment, 100, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
<script src="astnode_test.js"></script>
|
||||
<script src="block_test.js"></script>
|
||||
<script src="comment_test.js"></script>
|
||||
<script src="connection_db_test.js"></script>
|
||||
<script src="connection_test.js"></script>
|
||||
<script src="cursor_test.js"></script>
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
*/
|
||||
|
||||
suite('XML', function() {
|
||||
setup(function() {
|
||||
this.workspace = new Blockly.Workspace();
|
||||
});
|
||||
teardown(function() {
|
||||
this.workspace.dispose();
|
||||
});
|
||||
var assertSimpleField = function(fieldDom, name, text) {
|
||||
assertEquals(text, fieldDom.textContent);
|
||||
assertEquals(name, fieldDom.getAttribute('name'));
|
||||
@@ -38,7 +32,25 @@ suite('XML', function() {
|
||||
assertEquals(id, fieldDom.getAttribute('id'));
|
||||
assertEquals(text, fieldDom.textContent);
|
||||
};
|
||||
setup(function() {
|
||||
Blockly.defineBlocksWithJsonArray([
|
||||
{
|
||||
"type": "empty_block",
|
||||
"message0": "",
|
||||
"args0": []
|
||||
},
|
||||
]);
|
||||
});
|
||||
teardown(function() {
|
||||
delete Blockly.Blocks['empty_block'];
|
||||
});
|
||||
suite('Serialization', function() {
|
||||
setup(function() {
|
||||
this.workspace = new Blockly.Workspace();
|
||||
});
|
||||
teardown(function() {
|
||||
this.workspace.dispose();
|
||||
});
|
||||
suite('Fields', function() {
|
||||
test('Angle', function() {
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
@@ -201,7 +213,6 @@ suite('XML', function() {
|
||||
var block = new Blockly.Block(this.workspace,
|
||||
'field_label_serializable_test_block');
|
||||
var resultFieldDom = Blockly.Xml.blockToDom(block).childNodes[0];
|
||||
console.log(resultFieldDom);
|
||||
assertSimpleField(resultFieldDom, 'LABEL', 'default');
|
||||
delete Blockly.Blocks['field_label_serializable_test_block'];
|
||||
});
|
||||
@@ -289,8 +300,88 @@ suite('XML', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('Comments', function() {
|
||||
suite('Headless', function() {
|
||||
setup(function() {
|
||||
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.workspace);
|
||||
});
|
||||
test('Text', function() {
|
||||
this.block.setCommentText('test text');
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
var commentXml = xml.firstChild;
|
||||
chai.assert.equal(commentXml.tagName, 'comment');
|
||||
chai.assert.equal(commentXml.innerHTML, 'test text');
|
||||
});
|
||||
test('No Text', function() {
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
chai.assert.isNull(xml.firstChild);
|
||||
});
|
||||
test('Empty Text', function() {
|
||||
this.block.setCommentText('');
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
chai.assert.isNull(xml.firstChild);
|
||||
});
|
||||
});
|
||||
suite('Rendered', function() {
|
||||
setup(function() {
|
||||
// Let the parent teardown dispose of it.
|
||||
this.workspace = Blockly.inject('blocklyDiv', {comments: true});
|
||||
this.block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.workspace);
|
||||
});
|
||||
test('Text', function() {
|
||||
this.block.setCommentText('test text');
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
var commentXml = xml.firstChild;
|
||||
chai.assert.equal(commentXml.tagName, 'comment');
|
||||
chai.assert.equal(commentXml.innerHTML, 'test text');
|
||||
});
|
||||
test('No Text', function() {
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
chai.assert.isNull(xml.firstChild);
|
||||
});
|
||||
test('Empty Text', function() {
|
||||
this.block.setCommentText('');
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
chai.assert.isNull(xml.firstChild);
|
||||
});
|
||||
test('Size', function() {
|
||||
this.block.setCommentText('test text');
|
||||
this.block.getCommentIcon().setBubbleSize(100, 200);
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
var commentXml = xml.firstChild;
|
||||
chai.assert.equal(commentXml.tagName, 'comment');
|
||||
chai.assert.equal(commentXml.getAttribute('w'), 100);
|
||||
chai.assert.equal(commentXml.getAttribute('h'), 200);
|
||||
});
|
||||
test('Pinned True', function() {
|
||||
this.block.setCommentText('test text');
|
||||
this.block.getCommentIcon().setVisible(true);
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
var commentXml = xml.firstChild;
|
||||
chai.assert.equal(commentXml.tagName, 'comment');
|
||||
chai.assert.equal(commentXml.getAttribute('pinned'), 'true');
|
||||
});
|
||||
test('Pinned False', function() {
|
||||
this.block.setCommentText('test text');
|
||||
var xml = Blockly.Xml.blockToDom(this.block);
|
||||
var commentXml = xml.firstChild;
|
||||
chai.assert.equal(commentXml.tagName, 'comment');
|
||||
chai.assert.equal(commentXml.getAttribute('pinned'), 'false');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('Deserialization', function() {
|
||||
setup(function() {
|
||||
this.workspace = new Blockly.Workspace();
|
||||
});
|
||||
teardown(function() {
|
||||
this.workspace.dispose();
|
||||
});
|
||||
suite('Dynamic Category Blocks', function() {
|
||||
test('Untyped Variables', function() {
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
@@ -424,5 +515,196 @@ suite('XML', function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
suite('Comments', function() {
|
||||
suite('Headless', function() {
|
||||
test('Text', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment>test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.equal(block.getCommentText(), 'test text');
|
||||
});
|
||||
test('No Text', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment></comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.equal(block.getCommentText(), '');
|
||||
});
|
||||
test('Size', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment w="100" h="200">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.deepEqual(block.commentModel.size,
|
||||
{width: 100, height: 200});
|
||||
});
|
||||
test('Pinned True', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment pinned="true">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.isTrue(block.commentModel.pinned);
|
||||
});
|
||||
test('Pinned False', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment pinned="false">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.isFalse(block.commentModel.pinned);
|
||||
});
|
||||
test('Pinned Undefined', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment>test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.isFalse(block.commentModel.pinned);
|
||||
});
|
||||
});
|
||||
suite('Rendered', function() {
|
||||
setup(function() {
|
||||
// Let the parent teardown dispose of it.
|
||||
this.workspace = Blockly.inject('blocklyDiv', {comments: true});
|
||||
});
|
||||
test('Text', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment>test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.equal(block.getCommentText(), 'test text');
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
});
|
||||
test('No Text', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment></comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.equal(block.getCommentText(), '');
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
});
|
||||
test('Size', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment w="100" h="200">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
chai.assert.deepEqual(block.commentModel.size,
|
||||
{width: 100, height: 200});
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
chai.assert.deepEqual(block.getCommentIcon().getBubbleSize(),
|
||||
{width: 100, height: 200});
|
||||
});
|
||||
suite('Pinned', function() {
|
||||
setup(function() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
});
|
||||
teardown(function() {
|
||||
this.clock.restore();
|
||||
});
|
||||
test('Pinned True', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment pinned="true">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
this.clock.tick(1);
|
||||
chai.assert.isTrue(block.commentModel.pinned);
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
chai.assert.isTrue(block.getCommentIcon().isVisible());
|
||||
});
|
||||
test('Pinned False', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment pinned="false">test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
this.clock.tick(1);
|
||||
chai.assert.isFalse(block.commentModel.pinned);
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
chai.assert.isFalse(block.getCommentIcon().isVisible());
|
||||
});
|
||||
test('Pinned Undefined', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block">' +
|
||||
' <comment>test text</comment>' +
|
||||
'</block>'
|
||||
), this.workspace);
|
||||
this.clock.tick(1);
|
||||
chai.assert.isFalse(block.commentModel.pinned);
|
||||
chai.assert.isNotNull(block.getCommentIcon());
|
||||
chai.assert.isFalse(block.getCommentIcon().isVisible());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('Round Tripping', function() {
|
||||
setup(function() {
|
||||
var options = {
|
||||
comments: true
|
||||
};
|
||||
this.renderedWorkspace = Blockly.inject('blocklyDiv', options);
|
||||
this.headlessWorkspace = new Blockly.Workspace(options);
|
||||
});
|
||||
teardown(function() {
|
||||
this.renderedWorkspace.dispose();
|
||||
this.headlessWorkspace.dispose();
|
||||
});
|
||||
suite('Rendered -> XML -> Headless -> XML', function() {
|
||||
setup(function() {
|
||||
this.assertRoundTrip = function() {
|
||||
var renderedXml = Blockly.Xml.workspaceToDom(this.renderedWorkspace);
|
||||
Blockly.Xml.domToWorkspace(renderedXml, this.headlessWorkspace);
|
||||
var headlessXml = Blockly.Xml.workspaceToDom(this.headlessWorkspace);
|
||||
|
||||
var renderedText = Blockly.Xml.domToText(renderedXml);
|
||||
var headlessText = Blockly.Xml.domToText(headlessXml);
|
||||
|
||||
chai.assert.equal(headlessText, renderedText);
|
||||
};
|
||||
});
|
||||
test('Comment', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.renderedWorkspace);
|
||||
block.setCommentText('test text');
|
||||
block.getCommentIcon().setBubbleSize(100, 100);
|
||||
block.getCommentIcon().setVisible(true);
|
||||
|
||||
this.assertRoundTrip();
|
||||
});
|
||||
});
|
||||
suite('Headless -> XML -> Rendered -> XML', function() {
|
||||
setup(function() {
|
||||
this.assertRoundTrip = function() {
|
||||
var headlessXml = Blockly.Xml.workspaceToDom(this.headlessWorkspace);
|
||||
Blockly.Xml.domToWorkspace(headlessXml, this.renderedWorkspace);
|
||||
var renderedXml = Blockly.Xml.workspaceToDom(this.renderedWorkspace);
|
||||
|
||||
var renderedText = Blockly.Xml.domToText(renderedXml);
|
||||
var headlessText = Blockly.Xml.domToText(headlessXml);
|
||||
|
||||
chai.assert.equal(renderedText, headlessText);
|
||||
};
|
||||
});
|
||||
test('Comment', function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(
|
||||
'<block type="empty_block"/>'
|
||||
), this.headlessWorkspace);
|
||||
block.setCommentText('test text');
|
||||
block.commentModel.size = new Blockly.utils.Size(100, 100);
|
||||
block.commentModel.pinned = true;
|
||||
|
||||
this.assertRoundTrip();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user