From 0c2470183cf857d22f05333d9221cde8db3ff97c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 26 Jun 2024 11:19:21 -0700 Subject: [PATCH 1/3] release: Update version number to 12.0.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c5d8fe9826..5ce76f3c3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "11.1.1", + "version": "12.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "11.1.1", + "version": "12.0.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 7826c53ef43..03443612832 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "11.1.1", + "version": "12.0.0", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" From 989c91f6267a4a561aedb8ee532d4e3422db4dbf Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 27 Jun 2024 11:11:45 -0700 Subject: [PATCH 2/3] feat!: Add support for preserving block comment locations. (#8231) * feat: Add support for preserving block comment locations. * chore: format the tests. --- core/bubbles/textinput_bubble.ts | 32 ++++++++++++++- core/icons/comment_icon.ts | 67 ++++++++++++++++++++++++++++++- core/interfaces/i_comment_icon.ts | 7 ++++ core/xml.ts | 27 +++++++++++-- tests/mocha/block_test.js | 4 ++ tests/mocha/comment_test.js | 26 ++++++++++++ 6 files changed, 158 insertions(+), 5 deletions(-) diff --git a/core/bubbles/textinput_bubble.ts b/core/bubbles/textinput_bubble.ts index d7d1f5ae7db..d10619846a8 100644 --- a/core/bubbles/textinput_bubble.ts +++ b/core/bubbles/textinput_bubble.ts @@ -47,6 +47,9 @@ export class TextInputBubble extends Bubble { /** Functions listening for changes to the size of this bubble. */ private sizeChangeListeners: (() => void)[] = []; + /** Functions listening for changes to the location of this bubble. */ + private locationChangeListeners: (() => void)[] = []; + /** The text of this bubble. */ private text = ''; @@ -105,6 +108,11 @@ export class TextInputBubble extends Bubble { this.sizeChangeListeners.push(listener); } + /** Adds a change listener to be notified when this bubble's location changes. */ + addLocationChangeListener(listener: () => void) { + this.locationChangeListeners.push(listener); + } + /** Creates the editor UI for this bubble. */ private createEditor(container: SVGGElement): { inputRoot: SVGForeignObjectElement; @@ -212,10 +220,25 @@ export class TextInputBubble extends Bubble { /** @returns the size of this bubble. */ getSize(): Size { - // Overriden to be public. + // Overridden to be public. return super.getSize(); } + override moveDuringDrag(newLoc: Coordinate) { + super.moveDuringDrag(newLoc); + this.onLocationChange(); + } + + override setPositionRelativeToAnchor(left: number, top: number) { + super.setPositionRelativeToAnchor(left, top); + this.onLocationChange(); + } + + protected override positionByRect(rect = new Rect(0, 0, 0, 0)) { + super.positionByRect(rect); + this.onLocationChange(); + } + /** Handles mouse down events on the resize target. */ private onResizePointerDown(e: PointerEvent) { this.bringToFront(); @@ -297,6 +320,13 @@ export class TextInputBubble extends Bubble { listener(); } } + + /** Handles a location change event for the text area. Calls event listeners. */ + private onLocationChange() { + for (const listener of this.locationChangeListeners) { + listener(); + } + } } Css.register(` diff --git a/core/icons/comment_icon.ts b/core/icons/comment_icon.ts index df54560c557..c05748dcc5e 100644 --- a/core/icons/comment_icon.ts +++ b/core/icons/comment_icon.ts @@ -58,6 +58,9 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { /** The size of this comment (which is applied to the editable bubble). */ private bubbleSize = new Size(DEFAULT_BUBBLE_WIDTH, DEFAULT_BUBBLE_HEIGHT); + /** The location of the comment bubble in workspace coordinates. */ + private bubbleLocation?: Coordinate; + /** * The visibility of the bubble for this comment. * @@ -149,7 +152,13 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { } override onLocationChange(blockOrigin: Coordinate): void { + const oldLocation = this.workspaceLocation; super.onLocationChange(blockOrigin); + if (this.bubbleLocation) { + const newLocation = this.workspaceLocation; + const delta = Coordinate.difference(newLocation, oldLocation); + this.bubbleLocation = Coordinate.sum(this.bubbleLocation, delta); + } const anchorLocation = this.getAnchorLocation(); this.textInputBubble?.setAnchorLocation(anchorLocation); this.textBubble?.setAnchorLocation(anchorLocation); @@ -191,18 +200,43 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { return this.bubbleSize; } + /** + * Sets the location of the comment bubble in the workspace. + */ + setBubbleLocation(location: Coordinate) { + this.bubbleLocation = location; + this.textInputBubble?.moveDuringDrag(location); + this.textBubble?.moveDuringDrag(location); + } + + /** + * @returns the location of the comment bubble in the workspace. + */ + getBubbleLocation(): Coordinate | undefined { + return this.bubbleLocation; + } + /** * @returns the state of the comment as a JSON serializable value if the * comment has text. Otherwise returns null. */ saveState(): CommentState | null { if (this.text) { - return { + const state: CommentState = { 'text': this.text, 'pinned': this.bubbleIsVisible(), 'height': this.bubbleSize.height, 'width': this.bubbleSize.width, }; + const location = this.getBubbleLocation(); + if (location) { + state['x'] = this.sourceBlock.workspace.RTL + ? this.sourceBlock.workspace.getWidth() - + (location.x + this.bubbleSize.width) + : location.x; + state['y'] = location.y; + } + return state; } return null; } @@ -216,6 +250,16 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { ); this.bubbleVisiblity = state['pinned'] ?? false; this.setBubbleVisible(this.bubbleVisiblity); + let x = state['x']; + const y = state['y']; + renderManagement.finishQueuedRenders().then(() => { + if (x && y) { + x = this.sourceBlock.workspace.RTL + ? this.sourceBlock.workspace.getWidth() - (x + this.bubbleSize.width) + : x; + this.setBubbleLocation(new Coordinate(x, y)); + } + }); } override onClick(): void { @@ -259,6 +303,12 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { } } + onBubbleLocationChange(): void { + if (this.textInputBubble) { + this.bubbleLocation = this.textInputBubble.getRelativeToSurfaceXY(); + } + } + bubbleIsVisible(): boolean { return this.bubbleVisiblity; } @@ -308,8 +358,14 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { ); this.textInputBubble.setText(this.getText()); this.textInputBubble.setSize(this.bubbleSize, true); + if (this.bubbleLocation) { + this.textInputBubble.moveDuringDrag(this.bubbleLocation); + } this.textInputBubble.addTextChangeListener(() => this.onTextChange()); this.textInputBubble.addSizeChangeListener(() => this.onSizeChange()); + this.textInputBubble.addLocationChangeListener(() => + this.onBubbleLocationChange(), + ); } /** Shows the non editable text bubble for this comment. */ @@ -320,6 +376,9 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable { this.getAnchorLocation(), this.getBubbleOwnerRect(), ); + if (this.bubbleLocation) { + this.textBubble.moveDuringDrag(this.bubbleLocation); + } } /** Hides any open bubbles owned by this comment. */ @@ -365,6 +424,12 @@ export interface CommentState { /** The width of the comment bubble. */ width?: number; + + /** The X coordinate of the comment bubble. */ + x?: number; + + /** The Y coordinate of the comment bubble. */ + y?: number; } registry.register(CommentIcon.TYPE, CommentIcon); diff --git a/core/interfaces/i_comment_icon.ts b/core/interfaces/i_comment_icon.ts index 09b071110dd..2762348dea2 100644 --- a/core/interfaces/i_comment_icon.ts +++ b/core/interfaces/i_comment_icon.ts @@ -8,6 +8,7 @@ import {IconType} from '../icons/icon_types.js'; import {CommentState} from '../icons/comment_icon.js'; import {IIcon, isIcon} from './i_icon.js'; import {Size} from '../utils/size.js'; +import {Coordinate} from '../utils/coordinate.js'; import {IHasBubble, hasBubble} from './i_has_bubble.js'; import {ISerializable, isSerializable} from './i_serializable.js'; @@ -20,6 +21,10 @@ export interface ICommentIcon extends IIcon, IHasBubble, ISerializable { getBubbleSize(): Size; + setBubbleLocation(location: Coordinate): void; + + getBubbleLocation(): Coordinate | undefined; + saveState(): CommentState; loadState(state: CommentState): void; @@ -35,6 +40,8 @@ export function isCommentIcon(obj: Object): obj is ICommentIcon { (obj as any)['getText'] !== undefined && (obj as any)['setBubbleSize'] !== undefined && (obj as any)['getBubbleSize'] !== undefined && + (obj as any)['setBubbleLocation'] !== undefined && + (obj as any)['getBubbleLocation'] !== undefined && obj.getType() === IconType.COMMENT ); } diff --git a/core/xml.ts b/core/xml.ts index bad381c5d85..b8ecf6433d8 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -217,12 +217,24 @@ export function blockToDom( const comment = block.getIcon(IconType.COMMENT)!; const size = comment.getBubbleSize(); const pinned = comment.bubbleIsVisible(); + const location = comment.getBubbleLocation(); const commentElement = utilsXml.createElement('comment'); commentElement.appendChild(utilsXml.createTextNode(commentText)); commentElement.setAttribute('pinned', `${pinned}`); - commentElement.setAttribute('h', String(size.height)); - commentElement.setAttribute('w', String(size.width)); + commentElement.setAttribute('h', `${size.height}`); + commentElement.setAttribute('w', `${size.width}`); + if (location) { + commentElement.setAttribute( + 'x', + `${ + block.workspace.RTL + ? block.workspace.getWidth() - (location.x + size.width) + : location.x + }`, + ); + commentElement.setAttribute('y', `${location.y}`); + } element.appendChild(commentElement); } @@ -795,6 +807,8 @@ function applyCommentTagNodes(xmlChildren: Element[], block: Block) { const pinned = xmlChild.getAttribute('pinned') === 'true'; const width = parseInt(xmlChild.getAttribute('w') ?? '50', 10); const height = parseInt(xmlChild.getAttribute('h') ?? '50', 10); + let x = parseInt(xmlChild.getAttribute('x') ?? '', 10); + const y = parseInt(xmlChild.getAttribute('y') ?? '', 10); block.setCommentText(text); const comment = block.getIcon(IconType.COMMENT)!; @@ -803,8 +817,15 @@ function applyCommentTagNodes(xmlChildren: Element[], block: Block) { } // Set the pinned state of the bubble. comment.setBubbleVisible(pinned); + // Actually show the bubble after the block has been rendered. - setTimeout(() => comment.setBubbleVisible(pinned), 1); + setTimeout(() => { + if (!isNaN(x) && !isNaN(y)) { + x = block.workspace.RTL ? block.workspace.getWidth() - (x + width) : x; + comment.setBubbleLocation(new Coordinate(x, y)); + } + comment.setBubbleVisible(pinned); + }, 1); } } diff --git a/tests/mocha/block_test.js b/tests/mocha/block_test.js index dd070f86cbb..d7c0d7c58a3 100644 --- a/tests/mocha/block_test.js +++ b/tests/mocha/block_test.js @@ -1388,6 +1388,10 @@ suite('Blocks', function () { return Blockly.utils.Size(0, 0); } + setBubbleLocation() {} + + getBubbleLocation() {} + bubbleIsVisible() { return true; } diff --git a/tests/mocha/comment_test.js b/tests/mocha/comment_test.js index d4091b9c2d3..1f392194fd2 100644 --- a/tests/mocha/comment_test.js +++ b/tests/mocha/comment_test.js @@ -141,4 +141,30 @@ suite('Comments', function () { assertBubbleSize(this.comment, 100, 100); }); }); + suite('Set/Get Bubble Location', function () { + teardown(function () { + sinon.restore(); + }); + function assertBubbleLocation(comment, x, y) { + const location = comment.getBubbleLocation(); + assert.equal(location.x, x); + assert.equal(location.y, y); + } + test('Set Location While Visible', function () { + this.comment.setBubbleVisible(true); + + this.comment.setBubbleLocation(new Blockly.utils.Coordinate(100, 100)); + assertBubbleLocation(this.comment, 100, 100); + + this.comment.setBubbleVisible(false); + assertBubbleLocation(this.comment, 100, 100); + }); + test('Set Location While Invisible', function () { + this.comment.setBubbleLocation(new Blockly.utils.Coordinate(100, 100)); + assertBubbleLocation(this.comment, 100, 100); + + this.comment.setBubbleVisible(true); + assertBubbleLocation(this.comment, 100, 100); + }); + }); }); From 8a2c2b0c1846adb90605e12fe30d6e906c14206b Mon Sep 17 00:00:00 2001 From: Gabriel Fleury Date: Wed, 10 Jul 2024 18:33:53 -0300 Subject: [PATCH 3/3] Rename editing CSS class to blocklyEditing (#8287) --- core/field_input.ts | 2 +- core/renderers/common/constants.ts | 2 +- core/renderers/zelos/constants.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/field_input.ts b/core/field_input.ts index 85431cc5b33..7610b93b2c9 100644 --- a/core/field_input.ts +++ b/core/field_input.ts @@ -405,7 +405,7 @@ export abstract class FieldInput extends Field< const clickTarget = this.getClickTarget_(); if (!clickTarget) throw new Error('A click target has not been set.'); - dom.addClass(clickTarget, 'editing'); + dom.addClass(clickTarget, 'blocklyEditing'); const htmlInput = document.createElement('input'); htmlInput.className = 'blocklyHtmlInput'; diff --git a/core/renderers/common/constants.ts b/core/renderers/common/constants.ts index 078fc01d648..568b7f444d5 100644 --- a/core/renderers/common/constants.ts +++ b/core/renderers/common/constants.ts @@ -1154,7 +1154,7 @@ export class ConstantProvider { `}`, // Editable field hover. - `${selector} .blocklyEditableText:not(.editing):hover>rect {`, + `${selector} .blocklyEditableText:not(.blocklyEditing):hover>rect {`, `stroke: #fff;`, `stroke-width: 2;`, `}`, diff --git a/core/renderers/zelos/constants.ts b/core/renderers/zelos/constants.ts index c50e66510a0..22e3f3782e9 100644 --- a/core/renderers/zelos/constants.ts +++ b/core/renderers/zelos/constants.ts @@ -825,9 +825,9 @@ export class ConstantProvider extends BaseConstantProvider { // Editable field hover. `${selector} .blocklyDraggable:not(.blocklyDisabled)`, - ` .blocklyEditableText:not(.editing):hover>rect,`, + ` .blocklyEditableText:not(.blocklyEditing):hover>rect,`, `${selector} .blocklyDraggable:not(.blocklyDisabled)`, - ` .blocklyEditableText:not(.editing):hover>.blocklyPath {`, + ` .blocklyEditableText:not(.blocklyEditing):hover>.blocklyPath {`, `stroke: #fff;`, `stroke-width: 2;`, `}`,