Files
blockly/core/comments/rendered_workspace_comment.ts
Christopher Allen ce22f42868 chore: Organise imports (#8527)
* chore(deps): Add pretter-plugin-organize-imports

* chore: Remove insignificant blank lines in import sections

  Since prettier-plugin-organize-imports sorts imports within
  sections separated by blank lines, but preserves the section
  divisions, remove any blank lines that are not dividing imports
  into meaningful sections.

  Do not remove blank lines separating side-effect-only imports
  from main imports.

* chore: Remove unneded eslint-disable directives

* chore: Organise imports
2024-08-15 03:16:14 +01:00

295 lines
8.5 KiB
TypeScript

/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as browserEvents from '../browser_events.js';
import {
WorkspaceCommentCopyData,
WorkspaceCommentPaster,
} from '../clipboard/workspace_comment_paster.js';
import * as common from '../common.js';
import * as contextMenu from '../contextmenu.js';
import {ContextMenuRegistry} from '../contextmenu_registry.js';
import {CommentDragStrategy} from '../dragging/comment_drag_strategy.js';
import {IBoundedElement} from '../interfaces/i_bounded_element.js';
import {IContextMenu} from '../interfaces/i_contextmenu.js';
import {ICopyable} from '../interfaces/i_copyable.js';
import {IDeletable} from '../interfaces/i_deletable.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import {ISelectable} from '../interfaces/i_selectable.js';
import * as layers from '../layers.js';
import * as commentSerialization from '../serialization/workspace_comments.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import {CommentView} from './comment_view.js';
import {WorkspaceComment} from './workspace_comment.js';
export class RenderedWorkspaceComment
extends WorkspaceComment
implements
IBoundedElement,
IRenderedElement,
IDraggable,
ISelectable,
IDeletable,
ICopyable<WorkspaceCommentCopyData>,
IContextMenu
{
/** The class encompassing the svg elements making up the workspace comment. */
private view: CommentView;
public readonly workspace: WorkspaceSvg;
private dragStrategy = new CommentDragStrategy(this);
/** Constructs the workspace comment, including the view. */
constructor(workspace: WorkspaceSvg, id?: string) {
super(workspace, id);
this.workspace = workspace;
this.view = new CommentView(workspace);
// Set the size to the default size as defined in the superclass.
this.view.setSize(this.getSize());
this.view.setEditable(this.isEditable());
this.view.getSvgRoot().setAttribute('data-id', this.id);
this.addModelUpdateBindings();
browserEvents.conditionalBind(
this.view.getSvgRoot(),
'pointerdown',
this,
this.startGesture,
);
// Don't zoom with mousewheel; let it scroll instead.
browserEvents.conditionalBind(
this.view.getSvgRoot(),
'wheel',
this,
(e: Event) => {
e.stopPropagation();
},
);
}
/**
* Adds listeners to the view that updates the model (i.e. the superclass)
* when changes are made to the view.
*/
private addModelUpdateBindings() {
this.view.addTextChangeListener(
(_, newText: string) => void super.setText(newText),
);
this.view.addSizeChangeListener(
(_, newSize: Size) => void super.setSize(newSize),
);
this.view.addOnCollapseListener(
() => void super.setCollapsed(this.view.isCollapsed()),
);
this.view.addDisposeListener(() => {
if (!this.isDeadOrDying()) this.dispose();
});
}
/** Sets the text of the comment. */
override setText(text: string): void {
// setText will trigger the change listener that updates
// the model aka superclass.
this.view.setText(text);
}
/** Sets the size of the comment. */
override setSize(size: Size) {
// setSize will trigger the change listener that updates
// the model aka superclass.
this.view.setSize(size);
}
/** Sets whether the comment is collapsed or not. */
override setCollapsed(collapsed: boolean) {
// setCollapsed will trigger the change listener that updates
// the model aka superclass.
this.view.setCollapsed(collapsed);
}
/** Sets whether the comment is editable or not. */
override setEditable(editable: boolean): void {
super.setEditable(editable);
// Use isEditable rather than isOwnEditable to account for workspace state.
this.view.setEditable(this.isEditable());
}
/** Returns the root SVG element of this comment. */
getSvgRoot(): SVGElement {
return this.view.getSvgRoot();
}
/**
* Returns the comment's size in workspace units.
* Does not respect collapsing.
*/
getSize(): Size {
return super.getSize();
}
/**
* Returns the bounding rectangle of this comment in workspace coordinates.
* Respects collapsing.
*/
getBoundingRectangle(): Rect {
const loc = this.getRelativeToSurfaceXY();
const size = this.view?.getSize() ?? this.getSize();
let left;
let right;
if (this.workspace.RTL) {
left = loc.x - size.width;
right = loc.x;
} else {
left = loc.x;
right = loc.x + size.width;
}
return new Rect(loc.y, loc.y + size.height, left, right);
}
/** Move the comment by the given amounts in workspace coordinates. */
moveBy(dx: number, dy: number, reason?: string[] | undefined): void {
const loc = this.getRelativeToSurfaceXY();
const newLoc = new Coordinate(loc.x + dx, loc.y + dy);
this.moveTo(newLoc, reason);
}
/** Moves the comment to the given location in workspace coordinates. */
override moveTo(location: Coordinate, reason?: string[] | undefined): void {
super.moveTo(location, reason);
this.view.moveTo(location);
}
/**
* Moves the comment during a drag. Doesn't fire move events.
*
* @internal
*/
moveDuringDrag(location: Coordinate): void {
this.location = location;
this.view.moveTo(location);
}
/**
* Adds the dragging CSS class to this comment.
*
* @internal
*/
setDragging(dragging: boolean): void {
if (dragging) {
dom.addClass(this.getSvgRoot(), 'blocklyDragging');
} else {
dom.removeClass(this.getSvgRoot(), 'blocklyDragging');
}
}
/** Disposes of the view. */
override dispose() {
this.disposing = true;
if (!this.view.isDeadOrDying()) this.view.dispose();
super.dispose();
}
/**
* Starts a gesture because we detected a pointer down on the comment
* (that wasn't otherwise gobbled up, e.g. by resizing).
*/
private startGesture(e: PointerEvent) {
const gesture = this.workspace.getGesture(e);
if (gesture) {
gesture.handleCommentStart(e, this);
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
common.setSelected(this);
}
}
/** Visually indicates that this comment would be deleted if dropped. */
setDeleteStyle(wouldDelete: boolean): void {
if (wouldDelete) {
dom.addClass(this.getSvgRoot(), 'blocklyDraggingDelete');
} else {
dom.removeClass(this.getSvgRoot(), 'blocklyDraggingDelete');
}
}
/** Returns whether this comment is movable or not. */
isMovable(): boolean {
return this.dragStrategy.isMovable();
}
/** Starts a drag on the comment. */
startDrag(): void {
this.dragStrategy.startDrag();
}
/** Drags the comment to the given location. */
drag(newLoc: Coordinate): void {
this.dragStrategy.drag(newLoc);
}
/** Ends the drag on the comment. */
endDrag(): void {
this.dragStrategy.endDrag();
}
/** Moves the comment back to where it was at the start of a drag. */
revertDrag(): void {
this.dragStrategy.revertDrag();
}
/** Visually highlights the comment. */
select(): void {
dom.addClass(this.getSvgRoot(), 'blocklySelected');
}
/** Visually unhighlights the comment. */
unselect(): void {
dom.removeClass(this.getSvgRoot(), 'blocklySelected');
}
/**
* Returns a JSON serializable representation of this comment's state that
* can be used for pasting.
*/
toCopyData(): WorkspaceCommentCopyData | null {
return {
paster: WorkspaceCommentPaster.TYPE,
commentState: commentSerialization.save(this, {
addCoordinates: true,
}),
};
}
/** Show a context menu for this comment. */
showContextMenu(e: PointerEvent): void {
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
ContextMenuRegistry.ScopeType.COMMENT,
{comment: this},
);
contextMenu.show(e, menuOptions, this.workspace.RTL, this.workspace);
}
/** Snap this comment to the nearest grid point. */
snapToGrid(): void {
if (this.isDeadOrDying()) return;
const grid = this.workspace.getGrid();
if (!grid?.shouldSnap()) return;
const currentXY = this.getRelativeToSurfaceXY();
const alignedXY = grid.alignXY(currentXY);
if (alignedXY !== currentXY) {
this.moveTo(alignedXY, ['snap']);
}
}
}