From 7d8f88a4f1c1cfca5a7038519f808939057040a1 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 17 Apr 2024 21:33:36 +0000 Subject: [PATCH] chore: delete old comments (#8038) * chore!: delete old comment classes and references * chore: PR comments --- core/blockly.ts | 4 - core/bump_objects.ts | 4 +- core/comments/workspace_comment.ts | 5 +- core/workspace.ts | 19 +- core/workspace_comment.ts | 397 ---------- core/workspace_comment_svg.ts | 1152 ---------------------------- core/workspace_svg.ts | 11 +- tests/mocha/event_test.js | 1 - 8 files changed, 18 insertions(+), 1575 deletions(-) delete mode 100644 core/workspace_comment.ts delete mode 100644 core/workspace_comment_svg.ts diff --git a/core/blockly.ts b/core/blockly.ts index 510b5b8ed..cee509480 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -203,8 +203,6 @@ import * as VariablesDynamic from './variables_dynamic.js'; import * as WidgetDiv from './widgetdiv.js'; import {Workspace} from './workspace.js'; import {WorkspaceAudio} from './workspace_audio.js'; -import {WorkspaceComment} from './workspace_comment.js'; -import {WorkspaceCommentSvg} from './workspace_comment_svg.js'; import {WorkspaceDragger} from './workspace_dragger.js'; import {WorkspaceSvg} from './workspace_svg.js'; import * as Xml from './xml.js'; @@ -581,8 +579,6 @@ export {VariableModel}; export {VerticalFlyout}; export {Workspace}; export {WorkspaceAudio}; -export {WorkspaceComment}; -export {WorkspaceCommentSvg}; export {WorkspaceDragger}; export {WorkspaceSvg}; export {ZoomControls}; diff --git a/core/bump_objects.ts b/core/bump_objects.ts index 7085f78d5..3ceae2dbc 100644 --- a/core/bump_objects.ts +++ b/core/bump_objects.ts @@ -6,7 +6,7 @@ // Former goog.module ID: Blockly.bumpObjects -import {RenderedWorkspaceComment} from './comments.js'; +import {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js'; import type {Abstract} from './events/events_abstract.js'; import type {BlockCreate} from './events/events_block_create.js'; import type {BlockMove} from './events/events_block_move.js'; @@ -165,7 +165,7 @@ function extractObjectFromEvent( case eventUtils.COMMENT_MOVE: object = workspace.getCommentById( (e as CommentCreate | CommentMove).commentId!, - ) as AnyDuringMigration as RenderedWorkspaceComment; + ) as RenderedWorkspaceComment; break; } return object; diff --git a/core/comments/workspace_comment.ts b/core/comments/workspace_comment.ts index faa167a3e..3c23aba86 100644 --- a/core/comments/workspace_comment.ts +++ b/core/comments/workspace_comment.ts @@ -55,8 +55,7 @@ export class WorkspaceComment { ) { this.id = id && !workspace.getCommentById(id) ? id : idGenerator.genUid(); - // TODO: File an issue to remove this once everything is migrated. - workspace.addTopComment(this as AnyDuringMigration); + workspace.addTopComment(this); this.fireCreateEvent(); } @@ -209,7 +208,7 @@ export class WorkspaceComment { dispose() { this.disposing = true; this.fireDeleteEvent(); - this.workspace.removeTopComment(this as AnyDuringMigration); + this.workspace.removeTopComment(this); this.disposed = true; } diff --git a/core/workspace.ts b/core/workspace.ts index 424d9efb7..2e9b2b0a1 100644 --- a/core/workspace.ts +++ b/core/workspace.ts @@ -30,7 +30,6 @@ import * as math from './utils/math.js'; import type * as toolbox from './utils/toolbox.js'; import {VariableMap} from './variable_map.js'; import type {VariableModel} from './variable_model.js'; -import type {WorkspaceComment as OldWorkspaceComment} from './workspace_comment.js'; import {WorkspaceComment} from './comments/workspace_comment.js'; import {IProcedureMap} from './interfaces/i_procedure_map.js'; import {ObservableProcedureMap} from './observable_procedure_map.js'; @@ -101,8 +100,8 @@ export class Workspace implements IASTNodeLocation { connectionChecker: IConnectionChecker; private readonly topBlocks: Block[] = []; - private readonly topComments: OldWorkspaceComment[] = []; - private readonly commentDB = new Map(); + private readonly topComments: WorkspaceComment[] = []; + private readonly commentDB = new Map(); private readonly listeners: Function[] = []; protected undoStack_: Abstract[] = []; protected redoStack_: Abstract[] = []; @@ -169,8 +168,8 @@ export class Workspace implements IASTNodeLocation { * a's index. */ private sortObjects_( - a: Block | OldWorkspaceComment, - b: Block | OldWorkspaceComment, + a: Block | WorkspaceComment, + b: Block | WorkspaceComment, ): number { const offset = Math.sin(math.toRadians(Workspace.SCAN_ANGLE)) * (this.RTL ? -1 : 1); @@ -267,7 +266,7 @@ export class Workspace implements IASTNodeLocation { * @param comment comment to add. * @internal */ - addTopComment(comment: OldWorkspaceComment) { + addTopComment(comment: WorkspaceComment) { this.topComments.push(comment); // Note: If the comment database starts to hold block comments, this may @@ -288,7 +287,7 @@ export class Workspace implements IASTNodeLocation { * @param comment comment to remove. * @internal */ - removeTopComment(comment: OldWorkspaceComment) { + removeTopComment(comment: WorkspaceComment) { if (!arrayUtils.removeElem(this.topComments, comment)) { throw Error( "Comment not present in workspace's list of top-most " + 'comments.', @@ -307,9 +306,9 @@ export class Workspace implements IASTNodeLocation { * @returns The top-level comment objects. * @internal */ - getTopComments(ordered = false): OldWorkspaceComment[] { + getTopComments(ordered = false): WorkspaceComment[] { // Copy the topComments list. - const comments = new Array().concat(this.topComments); + const comments = new Array().concat(this.topComments); if (ordered && comments.length > 1) { comments.sort(this.sortObjects_.bind(this)); } @@ -751,7 +750,7 @@ export class Workspace implements IASTNodeLocation { * @returns The sought after comment, or null if not found. * @internal */ - getCommentById(id: string): OldWorkspaceComment | null { + getCommentById(id: string): WorkspaceComment | null { return this.commentDB.get(id) ?? null; } diff --git a/core/workspace_comment.ts b/core/workspace_comment.ts deleted file mode 100644 index 4149bcff4..000000000 --- a/core/workspace_comment.ts +++ /dev/null @@ -1,397 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a code comment on the workspace. - * - * @class - */ -// Former goog.module ID: Blockly.WorkspaceComment - -import type {CommentMove} from './events/events_comment_move.js'; -import * as eventUtils from './events/utils.js'; -import {Coordinate} from './utils/coordinate.js'; -import * as idGenerator from './utils/idgenerator.js'; -import * as xml from './utils/xml.js'; -import type {Workspace} from './workspace.js'; - -/** - * Class for a workspace comment. - */ -export class WorkspaceComment { - id: string; - protected xy_: Coordinate; - protected height_: number; - protected width_: number; - protected RTL: boolean; - - private deletable = true; - - private movable = true; - - private editable = true; - protected content_: string; - - /** Whether this comment has been disposed. */ - protected disposed_ = false; - /** @internal */ - isComment = true; - - /** - * @param workspace The block's workspace. - * @param content The content of this workspace comment. - * @param height Height of the comment. - * @param width Width of the comment. - * @param opt_id Optional ID. Use this ID if provided, otherwise create a new - * ID. - */ - constructor( - public workspace: Workspace, - content: string, - height: number, - width: number, - opt_id?: string, - ) { - this.id = - opt_id && !workspace.getCommentById(opt_id) - ? opt_id - : idGenerator.genUid(); - - workspace.addTopComment(this); - - /** - * The comment's position in workspace units. (0, 0) is at the workspace's - * origin; scale does not change this value. - */ - this.xy_ = new Coordinate(0, 0); - - /** - * The comment's height in workspace units. Scale does not change this - * value. - */ - this.height_ = height; - - /** - * The comment's width in workspace units. Scale does not change this - * value. - */ - this.width_ = width; - - this.RTL = workspace.RTL; - - this.content_ = content; - - WorkspaceComment.fireCreateEvent(this); - } - - /** - * Dispose of this comment. - * - * @internal - */ - dispose() { - if (this.disposed_) { - return; - } - - if (eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_DELETE))(this)); - } - // Remove from the list of top comments and the comment database. - this.workspace.removeTopComment(this); - this.disposed_ = true; - } - - // Height, width, x, and y are all stored on even non-rendered comments, to - // preserve state if you pass the contents through a headless workspace. - - /** - * Get comment height. - * - * @returns Comment height. - * @internal - */ - getHeight(): number { - return this.height_; - } - - /** - * Set comment height. - * - * @param height Comment height. - * @internal - */ - setHeight(height: number) { - this.height_ = height; - } - - /** - * Get comment width. - * - * @returns Comment width. - * @internal - */ - getWidth(): number { - return this.width_; - } - - /** - * Set comment width. - * - * @param width comment width. - * @internal - */ - setWidth(width: number) { - this.width_ = width; - } - - /** - * Get stored location. - * - * @returns The comment's stored location. - * This is not valid if the comment is currently being dragged. - * @internal - */ - getRelativeToSurfaceXY(): Coordinate { - return new Coordinate(this.xy_.x, this.xy_.y); - } - - /** - * Move a comment by a relative offset. - * - * @param dx Horizontal offset, in workspace units. - * @param dy Vertical offset, in workspace units. - * @internal - */ - moveBy(dx: number, dy: number) { - const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( - this, - ) as CommentMove; - this.xy_.translate(dx, dy); - event.recordNew(); - eventUtils.fire(event); - } - - /** - * Get whether this comment is deletable or not. - * - * @returns True if deletable. - * @internal - */ - isDeletable(): boolean { - return ( - this.deletable && !(this.workspace && this.workspace.options.readOnly) - ); - } - - /** - * Set whether this comment is deletable or not. - * - * @param deletable True if deletable. - * @internal - */ - setDeletable(deletable: boolean) { - this.deletable = deletable; - } - - /** - * Get whether this comment is movable or not. - * - * @returns True if movable. - * @internal - */ - isMovable(): boolean { - return this.movable && !(this.workspace && this.workspace.options.readOnly); - } - - /** - * Set whether this comment is movable or not. - * - * @param movable True if movable. - * @internal - */ - setMovable(movable: boolean) { - this.movable = movable; - } - - /** - * Get whether this comment is editable or not. - * - * @returns True if editable. - */ - isEditable(): boolean { - return ( - this.editable && !(this.workspace && this.workspace.options.readOnly) - ); - } - - /** - * Set whether this comment is editable or not. - * - * @param editable True if editable. - */ - setEditable(editable: boolean) { - this.editable = editable; - } - - /** - * Returns this comment's text. - * - * @returns Comment text. - * @internal - */ - getContent(): string { - return this.content_; - } - - /** - * Set this comment's content. - * - * @param content Comment content. - * @internal - */ - setContent(content: string) { - if (this.content_ !== content) { - eventUtils.fire( - new (eventUtils.get(eventUtils.COMMENT_CHANGE))( - this, - this.content_, - content, - ), - ); - this.content_ = content; - } - } - - /** - * Encode a comment subtree as XML with XY coordinates. - * - * @param opt_noId True if the encoder should skip the comment ID. - * @returns Tree of XML elements. - * @internal - */ - toXmlWithXY(opt_noId?: boolean): Element { - const element = this.toXml(opt_noId); - element.setAttribute('x', String(Math.round(this.xy_.x))); - element.setAttribute('y', String(Math.round(this.xy_.y))); - element.setAttribute('h', String(this.height_)); - element.setAttribute('w', String(this.width_)); - return element; - } - - /** - * Encode a comment subtree as XML, but don't serialize the XY coordinates. - * This method avoids some expensive metrics-related calls that are made in - * toXmlWithXY(). - * - * @param opt_noId True if the encoder should skip the comment ID. - * @returns Tree of XML elements. - * @internal - */ - toXml(opt_noId?: boolean): Element { - const commentElement = xml.createElement('comment'); - if (!opt_noId) { - commentElement.id = this.id; - } - commentElement.textContent = this.getContent(); - return commentElement; - } - - /** - * Fire a create event for the given workspace comment, if comments are - * enabled. - * - * @param comment The comment that was just created. - * @internal - */ - static fireCreateEvent(comment: WorkspaceComment) { - if (eventUtils.isEnabled()) { - const existingGroup = eventUtils.getGroup(); - if (!existingGroup) { - eventUtils.setGroup(true); - } - try { - eventUtils.fire( - new (eventUtils.get(eventUtils.COMMENT_CREATE))(comment), - ); - } finally { - eventUtils.setGroup(existingGroup); - } - } - } - - /** - * Decode an XML comment tag and create a comment on the workspace. - * - * @param xmlComment XML comment element. - * @param workspace The workspace. - * @returns The created workspace comment. - * @internal - */ - static fromXml(xmlComment: Element, workspace: Workspace): WorkspaceComment { - const info = WorkspaceComment.parseAttributes(xmlComment); - - const comment = new WorkspaceComment( - workspace, - info.content, - info.h, - info.w, - info.id, - ); - - const xmlX = xmlComment.getAttribute('x'); - const xmlY = xmlComment.getAttribute('y'); - const commentX = xmlX ? parseInt(xmlX, 10) : NaN; - const commentY = xmlY ? parseInt(xmlY, 10) : NaN; - if (!isNaN(commentX) && !isNaN(commentY)) { - comment.moveBy(commentX, commentY); - } - - WorkspaceComment.fireCreateEvent(comment); - return comment; - } - - /** - * Decode an XML comment tag and return the results in an object. - * - * @param xml XML comment element. - * @returns An object containing the id, size, position, and comment string. - * @internal - */ - static parseAttributes(xml: Element): { - id: string; - w: number; - h: number; - x: number; - y: number; - content: string; - } { - const xmlH = xml.getAttribute('h'); - const xmlW = xml.getAttribute('w'); - const xmlX = xml.getAttribute('x'); - const xmlY = xml.getAttribute('y'); - const xmlId = xml.getAttribute('id'); - - if (!xmlId) { - throw new Error('No ID present in XML comment definition.'); - } - - return { - id: xmlId, - // The height of the comment in workspace units, or 100 if not specified. - h: xmlH ? parseInt(xmlH) : 100, - // The width of the comment in workspace units, or 100 if not specified. - w: xmlW ? parseInt(xmlW) : 100, - // The x position of the comment in workspace coordinates, or NaN if not - // specified in the XML. - x: xmlX ? parseInt(xmlX) : NaN, - // The y position of the comment in workspace coordinates, or NaN if not - // specified in the XML. - y: xmlY ? parseInt(xmlY) : NaN, - content: xml.textContent ?? '', - }; - } -} diff --git a/core/workspace_comment_svg.ts b/core/workspace_comment_svg.ts deleted file mode 100644 index 4c15ec7d7..000000000 --- a/core/workspace_comment_svg.ts +++ /dev/null @@ -1,1152 +0,0 @@ -/** - * @license - * Copyright 2017 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Object representing a code comment on a rendered workspace. - * - * @class - */ -// Former goog.module ID: Blockly.WorkspaceCommentSvg - -// Unused import preserved for side-effects. Remove if unneeded. -import './events/events_selected.js'; - -import * as browserEvents from './browser_events.js'; -import * as common from './common.js'; -// import * as ContextMenu from './contextmenu.js'; -import * as Css from './css.js'; -import type {CommentMove} from './events/events_comment_move.js'; -import * as eventUtils from './events/utils.js'; -import type {IBoundedElement} from './interfaces/i_bounded_element.js'; -import type {ICopyable} from './interfaces/i_copyable.js'; -import * as Touch from './touch.js'; -import {Coordinate} from './utils/coordinate.js'; -import * as dom from './utils/dom.js'; -import {Rect} from './utils/rect.js'; -import {Svg} from './utils/svg.js'; -import * as svgMath from './utils/svg_math.js'; -import {WorkspaceComment} from './workspace_comment.js'; -import type {WorkspaceSvg} from './workspace_svg.js'; -import { - WorkspaceCommentCopyData, - WorkspaceCommentPaster, -} from './clipboard/workspace_comment_paster.js'; - -/** Size of the resize icon. */ -const RESIZE_SIZE = 8; - -/** Radius of the border around the comment. */ -const BORDER_RADIUS = 3; - -/** Offset from the foreignobject edge to the textarea edge. */ -const TEXTAREA_OFFSET = 2; - -/** - * Class for a workspace comment's SVG representation. - */ -export class WorkspaceCommentSvg - extends WorkspaceComment - implements IBoundedElement, ICopyable -{ - /** - * The width and height to use to size a workspace comment when it is first - * added, before it has been edited by the user. - * - * @internal - */ - static DEFAULT_SIZE = 100; - - /** Offset from the top to make room for a top bar. */ - private static readonly TOP_OFFSET = 10; - override workspace: WorkspaceSvg; - - /** Mouse up event data. */ - private onMouseUpWrapper: browserEvents.Data | null = null; - - /** Mouse move event data. */ - private onMouseMoveWrapper: browserEvents.Data | null = null; - - /** Whether event handlers have been initialized. */ - private eventsInit = false; - private textarea: HTMLTextAreaElement | null = null; - - private svgRectTarget: SVGRectElement | null = null; - - private svgHandleTarget: SVGRectElement | null = null; - - private foreignObject: SVGForeignObjectElement | null = null; - - private resizeGroup: SVGGElement | null = null; - - private deleteGroup: SVGGElement | null = null; - - private deleteIconBorder: SVGCircleElement | null = null; - - private focused = false; - private autoLayout = false; - - // Create core elements for the block. - private readonly svgGroup: SVGElement; - svgRect_: SVGRectElement; - - /** Whether the comment is rendered onscreen and is a part of the DOM. */ - private rendered = false; - - /** - * @param workspace The block's workspace. - * @param content The content of this workspace comment. - * @param height Height of the comment. - * @param width Width of the comment. - * @param opt_id Optional ID. Use this ID if provided, otherwise create a new - * ID. - */ - constructor( - workspace: WorkspaceSvg, - content: string, - height: number, - width: number, - opt_id?: string, - ) { - super(workspace, content, height, width, opt_id); - this.svgGroup = dom.createSvgElement(Svg.G, {'class': 'blocklyComment'}); - this.workspace = workspace; - - this.svgRect_ = dom.createSvgElement(Svg.RECT, { - 'class': 'blocklyCommentRect', - 'x': 0, - 'y': 0, - 'rx': BORDER_RADIUS, - 'ry': BORDER_RADIUS, - }); - this.svgGroup.appendChild(this.svgRect_); - - this.render(); - } - - /** - * Dispose of this comment. - * - * @internal - */ - override dispose() { - if (this.disposed_) { - return; - } - // If this comment is being dragged, unlink the mouse events. - if (common.getSelected() === this) { - this.unselect(); - this.workspace.cancelCurrentGesture(); - } - - if (eventUtils.isEnabled()) { - eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_DELETE))(this)); - } - - dom.removeNode(this.svgGroup); - - eventUtils.disable(); - super.dispose(); - eventUtils.enable(); - } - - /** - * Create and initialize the SVG representation of a workspace comment. - * May be called more than once. - * - * @param opt_noSelect Text inside text area will be selected if false - * @internal - */ - initSvg(opt_noSelect?: boolean) { - if (!this.workspace.rendered) { - throw TypeError('Workspace is headless.'); - } - if (!this.workspace.options.readOnly && !this.eventsInit) { - browserEvents.conditionalBind( - this.svgRectTarget as SVGRectElement, - 'pointerdown', - this, - this.pathMouseDown, - ); - browserEvents.conditionalBind( - this.svgHandleTarget as SVGRectElement, - 'pointerdown', - this, - this.pathMouseDown, - ); - } - this.eventsInit = true; - - this.updateMovable(); - if (!this.getSvgRoot().parentNode) { - this.workspace.getBubbleCanvas().appendChild(this.getSvgRoot()); - } - - if (!opt_noSelect && this.textarea) { - this.textarea.select(); - } - } - - /** - * Handle a pointerdown on an SVG comment. - * - * @param e Pointer down event. - */ - private pathMouseDown(e: PointerEvent) { - const gesture = this.workspace.getGesture(e); - if (gesture) { - gesture.handleBubbleStart(e, this as AnyDuringMigration); - } - } - - /** - * Show the context menu for this workspace comment. - * - * @param e Pointer event. - * @internal - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - showContextMenu(e: PointerEvent) { - throw new Error( - 'The implementation of showContextMenu should be ' + - 'monkey-patched in by blockly.ts', - ); - } - - /** - * Select this comment. Highlight it visually. - * - * @internal - */ - select() { - if (common.getSelected() === this) { - return; - } - let oldId = null; - if (common.getSelected()) { - oldId = common.getSelected()!.id; - // Unselect any previously selected block. - eventUtils.disable(); - try { - common.getSelected()!.unselect(); - } finally { - eventUtils.enable(); - } - } - const event = new (eventUtils.get(eventUtils.SELECTED))( - oldId, - this.id, - this.workspace.id, - ); - eventUtils.fire(event); - common.setSelected(this); - this.addSelect(); - } - - /** - * Unselect this comment. Remove its highlighting. - * - * @internal - */ - unselect() { - if (common.getSelected() !== this) { - return; - } - const event = new (eventUtils.get(eventUtils.SELECTED))( - this.id, - null, - this.workspace.id, - ); - eventUtils.fire(event); - common.setSelected(null); - this.removeSelect(); - this.blurFocus(); - } - - /** - * Select this comment. Highlight it visually. - * - * @internal - */ - addSelect() { - dom.addClass(this.svgGroup, 'blocklySelected'); - this.setFocus(); - } - - /** - * Unselect this comment. Remove its highlighting. - * - * @internal - */ - removeSelect() { - dom.addClass(this.svgGroup, 'blocklySelected'); - this.blurFocus(); - } - - /** - * Focus this comment. Highlight it visually. - * - * @internal - */ - addFocus() { - dom.addClass(this.svgGroup, 'blocklyFocused'); - } - - /** - * Unfocus this comment. Remove its highlighting. - * - * @internal - */ - removeFocus() { - dom.removeClass(this.svgGroup, 'blocklyFocused'); - } - - /** - * Return the coordinates of the top-left corner of this comment relative to - * the drawing surface's origin (0,0), in workspace units. - * If the comment is on the workspace, (0, 0) is the origin of the workspace - * coordinate system. - * This does not change with workspace scale. - * - * @returns Object with .x and .y properties in workspace coordinates. - * @internal - */ - override getRelativeToSurfaceXY(): Coordinate { - const layerManger = this.workspace.getLayerManager(); - if (!layerManger) { - throw new Error( - 'Cannot calculate position because the workspace has not been appended', - ); - } - - let x = 0; - let y = 0; - - let element: SVGElement | null = this.getSvgRoot(); - if (element) { - do { - // Loop through this comment and every parent. - const xy = svgMath.getRelativeXY(element); - x += xy.x; - y += xy.y; - element = element.parentNode as SVGElement; - } while (element && !layerManger.hasLayer(element) && element !== null); - } - this.xy_ = new Coordinate(x, y); - return this.xy_; - } - - /** - * Move a comment by a relative offset. - * - * @param dx Horizontal offset, in workspace units. - * @param dy Vertical offset, in workspace units. - * @internal - */ - override moveBy(dx: number, dy: number) { - const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( - this, - ) as CommentMove; - // TODO: Do I need to look up the relative to surface XY position here? - const xy = this.getRelativeToSurfaceXY(); - this.translate(xy.x + dx, xy.y + dy); - this.xy_ = new Coordinate(xy.x + dx, xy.y + dy); - event.recordNew(); - eventUtils.fire(event); - this.workspace.resizeContents(); - } - - /** - * Transforms a comment by setting the translation on the transform attribute - * of the block's SVG. - * - * @param x The x coordinate of the translation in workspace units. - * @param y The y coordinate of the translation in workspace units. - * @internal - */ - translate(x: number, y: number) { - this.xy_ = new Coordinate(x, y); - this.getSvgRoot().setAttribute( - 'transform', - 'translate(' + x + ',' + y + ')', - ); - } - - /** - * Move this comment during a drag. - * - * @param newLoc The location to translate to, in workspace coordinates. - * @internal - */ - moveDuringDrag(newLoc: Coordinate) { - const translation = `translate(${newLoc.x}, ${newLoc.y})`; - this.getSvgRoot().setAttribute('transform', translation); - } - - /** - * Move the bubble group to the specified location in workspace coordinates. - * - * @param x The x position to move to. - * @param y The y position to move to. - * @internal - */ - moveTo(x: number, y: number) { - this.translate(x, y); - } - - /** - * Clear the comment of transform="..." attributes. - * Used when the comment is switching from 3d to 2d transform or vice versa. - */ - private clearTransformAttributes() { - this.getSvgRoot().removeAttribute('transform'); - } - - /** - * Returns the coordinates of a bounding box describing the dimensions of this - * comment. - * Coordinate system: workspace coordinates. - * - * @returns Object with coordinates of the bounding box. - * @internal - */ - getBoundingRectangle(): Rect { - const blockXY = this.getRelativeToSurfaceXY(); - const commentBounds = this.getHeightWidth(); - const top = blockXY.y; - const bottom = blockXY.y + commentBounds.height; - let left; - let right; - if (this.RTL) { - left = blockXY.x - commentBounds.width; - // Add the width of the tab/puzzle piece knob to the x coordinate - // since X is the corner of the rectangle, not the whole puzzle piece. - right = blockXY.x; - } else { - // Subtract the width of the tab/puzzle piece knob to the x coordinate - // since X is the corner of the rectangle, not the whole puzzle piece. - left = blockXY.x; - right = blockXY.x + commentBounds.width; - } - return new Rect(top, bottom, left, right); - } - - /** - * Add or remove the UI indicating if this comment is movable or not. - * - * @internal - */ - updateMovable() { - if (this.isMovable()) { - dom.addClass(this.svgGroup, 'blocklyDraggable'); - } else { - dom.removeClass(this.svgGroup, 'blocklyDraggable'); - } - } - - /** - * Set whether this comment is movable or not. - * - * @param movable True if movable. - * @internal - */ - override setMovable(movable: boolean) { - super.setMovable(movable); - this.updateMovable(); - } - - /** - * Set whether this comment is editable or not. - * - * @param editable True if editable. - */ - override setEditable(editable: boolean) { - super.setEditable(editable); - if (this.textarea) { - this.textarea.readOnly = !editable; - } - } - - /** - * Recursively adds or removes the dragging class to this node and its - * children. - * - * @param adding True if adding, false if removing. - * @internal - */ - setDragging(adding: boolean) { - if (adding) { - dom.addClass(this.getSvgRoot(), 'blocklyDragging'); - } else { - dom.removeClass(this.getSvgRoot(), 'blocklyDragging'); - } - } - - /** - * Return the root node of the SVG or null if none exists. - * - * @returns The root SVG node (probably a group). - * @internal - */ - getSvgRoot(): SVGElement { - return this.svgGroup; - } - - /** - * Returns this comment's text. - * - * @returns Comment text. - * @internal - */ - override getContent(): string { - return this.textarea ? this.textarea.value : this.content_; - } - - /** - * Set this comment's content. - * - * @param content Comment content. - * @internal - */ - override setContent(content: string) { - super.setContent(content); - if (this.textarea) { - this.textarea.value = content; - } - } - - /** - * Update the cursor over this comment by adding or removing a class. - * - * @param enable True if the delete cursor should be shown, false otherwise. - * @internal - */ - setDeleteStyle(enable: boolean) { - if (enable) { - dom.addClass(this.svgGroup, 'blocklyDraggingDelete'); - } else { - dom.removeClass(this.svgGroup, 'blocklyDraggingDelete'); - } - } - - /** - * Set whether auto-layout of this bubble is enabled. The first time a bubble - * is shown it positions itself to not cover any blocks. Once a user has - * dragged it to reposition, it renders where the user put it. - * - * @param _enable True if auto-layout should be enabled, false otherwise. - * @internal - */ - setAutoLayout(_enable: boolean) {} - // NOP for compatibility with the bubble dragger. - - /** - * Encode a comment subtree as XML with XY coordinates. - * - * @param opt_noId True if the encoder should skip the comment ID. - * @returns Tree of XML elements. - * @internal - */ - override toXmlWithXY(opt_noId?: boolean): Element { - let width = 0; // Not used in LTR. - if (this.workspace.RTL) { - // Here be performance dragons: This calls getMetrics(). - width = this.workspace.getWidth(); - } - const element = this.toXml(opt_noId); - const xy = this.getRelativeToSurfaceXY(); - element.setAttribute( - 'x', - String(Math.round(this.workspace.RTL ? width - xy.x : xy.x)), - ); - element.setAttribute('y', String(Math.round(xy.y))); - element.setAttribute('h', String(this.getHeight())); - element.setAttribute('w', String(this.getWidth())); - return element; - } - - /** - * Encode a comment for copying. - * - * @returns Copy metadata. - */ - toCopyData(): WorkspaceCommentCopyData { - return { - paster: WorkspaceCommentPaster.TYPE, - commentState: this.toXmlWithXY(), - }; - } - - /** - * Returns a bounding box describing the dimensions of this comment. - * - * @returns Object with height and width properties in workspace units. - * @internal - */ - getHeightWidth(): {height: number; width: number} { - return {width: this.getWidth(), height: this.getHeight()}; - } - - /** - * Renders the workspace comment. - * - * @internal - */ - render() { - if (this.rendered) { - return; - } - - const size = this.getHeightWidth(); - - // Add text area - const foreignObject = this.createEditor(); - this.svgGroup.appendChild(foreignObject); - - this.svgHandleTarget = dom.createSvgElement(Svg.RECT, { - 'class': 'blocklyCommentHandleTarget', - 'x': 0, - 'y': 0, - }); - this.svgGroup.appendChild(this.svgHandleTarget); - this.svgRectTarget = dom.createSvgElement(Svg.RECT, { - 'class': 'blocklyCommentTarget', - 'x': 0, - 'y': 0, - 'rx': BORDER_RADIUS, - 'ry': BORDER_RADIUS, - }); - this.svgGroup.appendChild(this.svgRectTarget); - - // Add the resize icon - this.addResizeDom(); - if (this.isDeletable()) { - // Add the delete icon - this.addDeleteDom(); - } - - this.setSize(size.width, size.height); - - // Set the content - this.textarea!.value = this.content_; - - this.rendered = true; - - if (this.resizeGroup) { - browserEvents.conditionalBind( - this.resizeGroup, - 'pointerdown', - this, - this.resizeMouseDown, - ); - } - - if (this.isDeletable()) { - browserEvents.conditionalBind( - this.deleteGroup as SVGGElement, - 'pointerdown', - this, - this.deleteMouseDown, - ); - browserEvents.conditionalBind( - this.deleteGroup as SVGGElement, - 'pointerout', - this, - this.deleteMouseOut, - ); - browserEvents.conditionalBind( - this.deleteGroup as SVGGElement, - 'pointerup', - this, - this.deleteMouseUp, - ); - } - } - - /** - * Create the text area for the comment. - * - * @returns The top-level node of the editor. - */ - private createEditor(): Element { - /* Create the editor. Here's the markup that will be generated: - - - - */ - this.foreignObject = dom.createSvgElement(Svg.FOREIGNOBJECT, { - 'x': 0, - 'y': WorkspaceCommentSvg.TOP_OFFSET, - 'class': 'blocklyCommentForeignObject', - }); - const body = document.createElementNS(dom.HTML_NS, 'body'); - body.setAttribute('xmlns', dom.HTML_NS); - body.className = 'blocklyMinimalBody'; - const textarea = document.createElementNS( - dom.HTML_NS, - 'textarea', - ) as HTMLTextAreaElement; - textarea.className = 'blocklyCommentTextarea'; - textarea.setAttribute('dir', this.RTL ? 'RTL' : 'LTR'); - textarea.readOnly = !this.isEditable(); - body.appendChild(textarea); - this.textarea = textarea; - this.foreignObject.appendChild(body); - // Don't zoom with mousewheel. - browserEvents.conditionalBind( - textarea, - 'wheel', - this, - function (e: WheelEvent) { - e.stopPropagation(); - }, - ); - browserEvents.conditionalBind( - textarea, - 'change', - this, - function (this: WorkspaceCommentSvg, _e: Event) { - this.setContent(textarea.value); - }, - ); - return this.foreignObject; - } - - /** Add the resize icon to the DOM */ - private addResizeDom() { - this.resizeGroup = dom.createSvgElement( - Svg.G, - {'class': this.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE'}, - this.svgGroup, - ); - dom.createSvgElement( - Svg.POLYGON, - { - 'points': `0,${RESIZE_SIZE} ${RESIZE_SIZE},${RESIZE_SIZE} ${RESIZE_SIZE},0`, - }, - this.resizeGroup, - ); - dom.createSvgElement( - Svg.LINE, - { - 'class': 'blocklyResizeLine', - 'x1': RESIZE_SIZE / 3, - 'y1': RESIZE_SIZE - 1, - 'x2': RESIZE_SIZE - 1, - 'y2': RESIZE_SIZE / 3, - }, - this.resizeGroup, - ); - dom.createSvgElement( - Svg.LINE, - { - 'class': 'blocklyResizeLine', - 'x1': (RESIZE_SIZE * 2) / 3, - 'y1': RESIZE_SIZE - 1, - 'x2': RESIZE_SIZE - 1, - 'y2': (RESIZE_SIZE * 2) / 3, - }, - this.resizeGroup, - ); - } - - /** Add the delete icon to the DOM */ - private addDeleteDom() { - this.deleteGroup = dom.createSvgElement( - Svg.G, - {'class': 'blocklyCommentDeleteIcon'}, - this.svgGroup, - ); - this.deleteIconBorder = dom.createSvgElement( - Svg.CIRCLE, - {'class': 'blocklyDeleteIconShape', 'r': '7', 'cx': '7.5', 'cy': '7.5'}, - this.deleteGroup, - ); - // x icon. - dom.createSvgElement( - Svg.LINE, - { - 'x1': '5', - 'y1': '10', - 'x2': '10', - 'y2': '5', - 'stroke': '#fff', - 'stroke-width': '2', - }, - this.deleteGroup, - ); - dom.createSvgElement( - Svg.LINE, - { - 'x1': '5', - 'y1': '5', - 'x2': '10', - 'y2': '10', - 'stroke': '#fff', - 'stroke-width': '2', - }, - this.deleteGroup, - ); - } - - /** - * Handle a pointerdown on comment's resize corner. - * - * @param e Pointer down event. - */ - private resizeMouseDown(e: PointerEvent) { - this.unbindDragEvents(); - if (browserEvents.isRightButton(e)) { - // No right-click. - e.stopPropagation(); - return; - } - // Left-click (or middle click) - this.workspace.startDrag( - e, - new Coordinate( - this.workspace.RTL ? -this.width_ : this.width_, - this.height_, - ), - ); - - this.onMouseUpWrapper = browserEvents.conditionalBind( - document, - 'pointerup', - this, - this.resizeMouseUp, - ); - this.onMouseMoveWrapper = browserEvents.conditionalBind( - document, - 'pointermove', - this, - this.resizeMouseMove, - ); - this.workspace.hideChaff(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - } - - /** - * Handle a pointerdown on comment's delete icon. - * - * @param e Pointer down event. - */ - private deleteMouseDown(e: PointerEvent) { - // Highlight the delete icon. - if (this.deleteIconBorder) { - dom.addClass(this.deleteIconBorder, 'blocklyDeleteIconHighlighted'); - } - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - } - - /** - * Handle a pointerout on comment's delete icon. - * - * @param _e Pointer out event. - */ - private deleteMouseOut(_e: PointerEvent) { - // Restore highlight on the delete icon. - if (this.deleteIconBorder) { - dom.removeClass(this.deleteIconBorder, 'blocklyDeleteIconHighlighted'); - } - } - - /** - * Handle a pointerup on comment's delete icon. - * - * @param e Pointer up event. - */ - private deleteMouseUp(e: PointerEvent) { - // Delete this comment. - this.dispose(); - // This event has been handled. No need to bubble up to the document. - e.stopPropagation(); - } - - /** Stop binding to the global pointerup and pointermove events. */ - private unbindDragEvents() { - if (this.onMouseUpWrapper) { - browserEvents.unbind(this.onMouseUpWrapper); - this.onMouseUpWrapper = null; - } - if (this.onMouseMoveWrapper) { - browserEvents.unbind(this.onMouseMoveWrapper); - this.onMouseMoveWrapper = null; - } - } - - /** - * Handle a pointerup event while dragging a comment's border or resize - * handle. - * - * @param _e Pointer up event. - */ - private resizeMouseUp(_e: PointerEvent) { - Touch.clearTouchIdentifier(); - this.unbindDragEvents(); - } - - /** - * Resize this comment to follow the pointer. - * - * @param e Pointer move event. - */ - private resizeMouseMove(e: PointerEvent) { - this.autoLayout = false; - const newXY = this.workspace.moveDrag(e); - this.setSize(this.RTL ? -newXY.x : newXY.x, newXY.y); - } - - /** - * Callback function triggered when the comment has resized. - * Resize the text area accordingly. - */ - private resizeComment() { - const size = this.getHeightWidth(); - const topOffset = WorkspaceCommentSvg.TOP_OFFSET; - const textOffset = TEXTAREA_OFFSET * 2; - - this.foreignObject?.setAttribute('width', String(size.width)); - this.foreignObject?.setAttribute('height', String(size.height - topOffset)); - if (this.RTL) { - this.foreignObject?.setAttribute('x', String(-size.width)); - } - - if (!this.textarea) return; - this.textarea.style.width = size.width - textOffset + 'px'; - this.textarea.style.height = size.height - textOffset - topOffset + 'px'; - } - - /** - * Set size - * - * @param width width of the container - * @param height height of the container - */ - private setSize(width: number, height: number) { - // Minimum size of a comment. - width = Math.max(width, 45); - height = Math.max(height, 20 + WorkspaceCommentSvg.TOP_OFFSET); - this.width_ = width; - this.height_ = height; - this.svgRect_.setAttribute('width', `${width}`); - this.svgRect_.setAttribute('height', `${height}`); - this.svgRectTarget?.setAttribute('width', `${width}`); - this.svgRectTarget?.setAttribute('height', `${height}`); - this.svgHandleTarget?.setAttribute('width', `${width}`); - this.svgHandleTarget?.setAttribute( - 'height', - String(WorkspaceCommentSvg.TOP_OFFSET), - ); - if (this.RTL) { - this.svgRect_.setAttribute('transform', 'scale(-1 1)'); - this.svgRectTarget?.setAttribute('transform', 'scale(-1 1)'); - } - - if (this.resizeGroup) { - if (this.RTL) { - // Mirror the resize group. - this.resizeGroup.setAttribute( - 'transform', - 'translate(' + - (-width + RESIZE_SIZE) + - ',' + - (height - RESIZE_SIZE) + - ') scale(-1 1)', - ); - this.deleteGroup?.setAttribute( - 'transform', - 'translate(' + - (-width + RESIZE_SIZE) + - ',' + - -RESIZE_SIZE + - ') scale(-1 1)', - ); - } else { - this.resizeGroup.setAttribute( - 'transform', - 'translate(' + - (width - RESIZE_SIZE) + - ',' + - (height - RESIZE_SIZE) + - ')', - ); - this.deleteGroup?.setAttribute( - 'transform', - 'translate(' + (width - RESIZE_SIZE) + ',' + -RESIZE_SIZE + ')', - ); - } - } - - // Allow the contents to resize. - this.resizeComment(); - } - - /** - * Set the focus on the text area. - * - * @internal - */ - setFocus() { - this.focused = true; - // Defer CSS changes. - setTimeout(() => { - if (this.disposed_) { - return; - } - this.textarea!.focus(); - this.addFocus(); - if (this.svgRectTarget) { - dom.addClass(this.svgRectTarget, 'blocklyCommentTargetFocused'); - } - if (this.svgHandleTarget) { - dom.addClass(this.svgHandleTarget, 'blocklyCommentHandleTargetFocused'); - } - }, 0); - } - - /** - * Remove focus from the text area. - * - * @internal - */ - blurFocus() { - this.focused = false; - // Defer CSS changes. - setTimeout(() => { - if (this.disposed_) { - return; - } - - this.textarea!.blur(); - this.removeFocus(); - if (this.svgRectTarget) { - dom.removeClass(this.svgRectTarget, 'blocklyCommentTargetFocused'); - } - if (this.svgHandleTarget) { - dom.removeClass( - this.svgHandleTarget, - 'blocklyCommentHandleTargetFocused', - ); - } - }, 0); - } - - /** - * Decode an XML comment tag and create a rendered comment on the workspace. - * - * @param xmlComment XML comment element. - * @param workspace The workspace. - * @param opt_wsWidth The width of the workspace, which is used to position - * comments correctly in RTL. - * @returns The created workspace comment. - * @internal - */ - static fromXmlRendered( - xmlComment: Element, - workspace: WorkspaceSvg, - opt_wsWidth?: number, - ): WorkspaceCommentSvg { - eventUtils.disable(); - let comment; - try { - const info = WorkspaceComment.parseAttributes(xmlComment); - - comment = new WorkspaceCommentSvg( - workspace, - info.content, - info.h, - info.w, - info.id, - ); - if (workspace.rendered) { - comment.initSvg(true); - comment.render(); - } - // Position the comment correctly, taking into account the width of a - // rendered RTL workspace. - if (!isNaN(info.x) && !isNaN(info.y)) { - if (workspace.RTL) { - const wsWidth = opt_wsWidth || workspace.getWidth(); - comment.moveBy(wsWidth - info.x, info.y); - } else { - comment.moveBy(info.x, info.y); - } - } - } finally { - eventUtils.enable(); - } - - WorkspaceComment.fireCreateEvent(comment); - return comment; - } -} - -/** CSS for workspace comment. See css.js for use. */ -Css.register(` -.blocklyCommentForeignObject { - position: relative; - z-index: 0; -} - -.blocklyCommentRect { - fill: #E7DE8E; - stroke: #bcA903; - stroke-width: 1px; -} - -.blocklyCommentTarget { - fill: transparent; - stroke: #bcA903; -} - -.blocklyCommentTargetFocused { - fill: none; -} - -.blocklyCommentHandleTarget { - fill: none; -} - -.blocklyCommentHandleTargetFocused { - fill: transparent; -} - -.blocklyFocused>.blocklyCommentRect { - fill: #B9B272; - stroke: #B9B272; -} - -.blocklySelected>.blocklyCommentTarget { - stroke: #fc3; - stroke-width: 3px; -} - -.blocklyCommentDeleteIcon { - cursor: pointer; - fill: #000; - display: none; -} - -.blocklySelected > .blocklyCommentDeleteIcon { - display: block; -} - -.blocklyDeleteIconShape { - fill: #000; - stroke: #000; - stroke-width: 1px; -} - -.blocklyDeleteIconShape.blocklyDeleteIconHighlighted { - stroke: #fc3; -} -`); diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 589062876..14cc1101f 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -68,13 +68,12 @@ import * as VariablesDynamic from './variables_dynamic.js'; import * as WidgetDiv from './widgetdiv.js'; import {Workspace} from './workspace.js'; import {WorkspaceAudio} from './workspace_audio.js'; -import {WorkspaceComment as OldWorkspaceComment} from './workspace_comment.js'; -import {WorkspaceCommentSvg as OldWorkspaceCommentSvg} from './workspace_comment_svg.js'; import {WorkspaceComment} from './comments/workspace_comment.js'; import {ZoomControls} from './zoom_controls.js'; import {ContextMenuOption} from './contextmenu_registry.js'; import * as renderManagement from './render_management.js'; import {LayerManager} from './layer_manager.js'; +import {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js'; /** Margin around the top/bottom/left/right after a zoomToFit call. */ const ZOOM_TO_FIT_MARGIN = 20; @@ -2143,8 +2142,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @param comment comment to add. */ - override addTopComment(comment: OldWorkspaceComment) { - this.addTopBoundedElement(comment as OldWorkspaceCommentSvg); + override addTopComment(comment: WorkspaceComment) { + this.addTopBoundedElement(comment as RenderedWorkspaceComment); super.addTopComment(comment); } @@ -2153,8 +2152,8 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg { * * @param comment comment to remove. */ - override removeTopComment(comment: OldWorkspaceComment) { - this.removeTopBoundedElement(comment as OldWorkspaceCommentSvg); + override removeTopComment(comment: WorkspaceComment) { + this.removeTopBoundedElement(comment as RenderedWorkspaceComment); super.removeTopComment(comment); } diff --git a/tests/mocha/event_test.js b/tests/mocha/event_test.js index 518118c5e..75688c654 100644 --- a/tests/mocha/event_test.js +++ b/tests/mocha/event_test.js @@ -19,7 +19,6 @@ import { workspaceTeardown, } from './test_helpers/setup_teardown.js'; import * as eventUtils from '../../build/src/core/events/utils.js'; -import {WorkspaceComment} from '../../build/src/core/workspace_comment.js'; suite('Events', function () { setup(function () {