chore: use prettier instead of clang-format (#7014)

* chore: add and configure prettier

* chore: remove clang-format

* chore: remove clang-format config

* chore: lint additional ts files

* chore: fix lint errors in blocks

* chore: add prettier-ignore where needed

* chore: ignore js blocks when formatting

* chore: fix playground html syntax

* chore: fix yaml spacing from merge

* chore: convert text blocks to use arrow functions

* chore: format everything with prettier

* chore: fix lint unused imports in blocks
This commit is contained in:
Maribeth Bottorff
2023-05-10 16:01:39 -07:00
committed by GitHub
parent af991f5e1b
commit 88ff901a72
425 changed files with 29170 additions and 21169 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@ import type {BlockSvg} from './block_svg.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
/** A bounding box for a cloned block. */
interface CloneRect {
x: number;
@@ -21,11 +20,10 @@ interface CloneRect {
}
/** PID of disconnect UI animation. There can only be one at a time. */
let disconnectPid: ReturnType<typeof setTimeout>|null = null;
let disconnectPid: ReturnType<typeof setTimeout> | null = null;
/** The wobbling block. There can only be one at a time. */
let wobblingBlock: BlockSvg|null = null;
let wobblingBlock: BlockSvg | null = null;
/**
* Play some UI effects (sound, animation) when disposing of a block.
@@ -46,8 +44,12 @@ export function disposeUiEffect(block: BlockSvg) {
const clone: SVGGElement = svgGroup.cloneNode(true) as SVGGElement;
clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
workspace.getParentSvg().appendChild(clone);
const cloneRect =
{'x': xy.x, 'y': xy.y, 'width': block.width, 'height': block.height};
const cloneRect = {
'x': xy.x,
'y': xy.y,
'width': block.width,
'height': block.height,
};
disposeUiStep(clone, cloneRect, workspace.RTL, new Date(), workspace.scale);
}
/**
@@ -62,21 +64,25 @@ export function disposeUiEffect(block: BlockSvg) {
* @param workspaceScale Scale of workspace.
*/
function disposeUiStep(
clone: Element, rect: CloneRect, rtl: boolean, start: Date,
workspaceScale: number) {
clone: Element,
rect: CloneRect,
rtl: boolean,
start: Date,
workspaceScale: number
) {
const ms = new Date().getTime() - start.getTime();
const percent = ms / 150;
if (percent > 1) {
dom.removeNode(clone);
} else {
const x =
rect.x + (rtl ? -1 : 1) * rect.width * workspaceScale / 2 * percent;
rect.x + (((rtl ? -1 : 1) * rect.width * workspaceScale) / 2) * percent;
const y = rect.y + rect.height * workspaceScale * percent;
const scale = (1 - percent) * workspaceScale;
clone.setAttribute(
'transform',
'translate(' + x + ',' + y + ')' +
' scale(' + scale + ')');
'transform',
'translate(' + x + ',' + y + ')' + ' scale(' + scale + ')'
);
setTimeout(disposeUiStep, 10, clone, rect, rtl, start, workspaceScale);
}
}
@@ -92,7 +98,7 @@ export function connectionUiEffect(block: BlockSvg) {
const scale = workspace.scale;
workspace.getAudioManager().play('click');
if (scale < 1) {
return; // Too small to care about visual effects.
return; // Too small to care about visual effects.
}
// Determine the absolute coordinates of the inferior block.
const xy = workspace.getSvgXY(block.getSvgRoot());
@@ -105,15 +111,17 @@ export function connectionUiEffect(block: BlockSvg) {
xy.y += 3 * scale;
}
const ripple = dom.createSvgElement(
Svg.CIRCLE, {
'cx': xy.x,
'cy': xy.y,
'r': 0,
'fill': 'none',
'stroke': '#888',
'stroke-width': 10,
},
workspace.getParentSvg());
Svg.CIRCLE,
{
'cx': xy.x,
'cy': xy.y,
'r': 0,
'fill': 'none',
'stroke': '#888',
'stroke-width': 10,
},
workspace.getParentSvg()
);
// Start the animation.
connectionUiStep(ripple, new Date(), scale);
}
@@ -147,13 +155,13 @@ export function disconnectUiEffect(block: BlockSvg) {
disconnectUiStop();
block.workspace.getAudioManager().play('disconnect');
if (block.workspace.scale < 1) {
return; // Too small to care about visual effects.
return; // Too small to care about visual effects.
}
// Horizontal distance for bottom of block to wiggle.
const DISPLACEMENT = 10;
// Scale magnitude of skew to height of block.
const height = block.getHeightWidth().height;
let magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
let magnitude = (Math.atan(DISPLACEMENT / height) / Math.PI) * 180;
if (!block.RTL) {
magnitude *= -1;
}
@@ -170,8 +178,8 @@ export function disconnectUiEffect(block: BlockSvg) {
* @param start Date of animation's start.
*/
function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) {
const DURATION = 200; // Milliseconds.
const WIGGLES = 3; // Half oscillations.
const DURATION = 200; // Milliseconds.
const WIGGLES = 3; // Half oscillations.
const ms = new Date().getTime() - start.getTime();
const percent = ms / DURATION;
@@ -179,13 +187,15 @@ function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) {
let skew = '';
if (percent <= 1) {
const val = Math.round(
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude
);
skew = `skewX(${val})`;
disconnectPid = setTimeout(disconnectUiStep, 10, block, magnitude, start);
}
block.getSvgRoot().setAttribute(
'transform', `${block.getTranslation()} ${skew}`);
block
.getSvgRoot()
.setAttribute('transform', `${block.getTranslation()} ${skew}`);
}
/**
@@ -199,7 +209,8 @@ export function disconnectUiStop() {
clearTimeout(disconnectPid);
disconnectPid = null;
}
wobblingBlock.getSvgRoot().setAttribute(
'transform', wobblingBlock.getTranslation());
wobblingBlock
.getSvgRoot()
.setAttribute('transform', wobblingBlock.getTranslation());
wobblingBlock = null;
}

View File

@@ -23,7 +23,6 @@ import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
import * as svgMath from './utils/svg_math.js';
/**
* Class for a drag surface for the currently dragged block. This is a separate
* SVG that contains only the currently moving block, or nothing.
@@ -65,14 +64,16 @@ export class BlockDragSurfaceSvg {
/** @param container Containing element. */
constructor(private readonly container: Element) {
this.svg = dom.createSvgElement(
Svg.SVG, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklyBlockDragSurface',
},
this.container);
Svg.SVG,
{
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklyBlockDragSurface',
},
this.container
);
this.dragGroup = dom.createSvgElement(Svg.G, {}, this.svg);
}
@@ -120,8 +121,9 @@ export class BlockDragSurfaceSvg {
this.childSurfaceXY.x = roundX;
this.childSurfaceXY.y = roundY;
this.dragGroup.setAttribute(
'transform',
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
'transform',
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')'
);
}
/**
@@ -200,7 +202,7 @@ export class BlockDragSurfaceSvg {
*
* @returns Drag surface block DOM element, or null if no blocks exist.
*/
getCurrentBlock(): Element|null {
getCurrentBlock(): Element | null {
return this.dragGroup.firstChild as Element;
}

View File

@@ -30,7 +30,6 @@ import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for a block dragger. It moves blocks around the workspace when they
* are being dragged by a mouse or touch.
@@ -44,7 +43,7 @@ export class BlockDragger implements IBlockDragger {
protected workspace_: WorkspaceSvg;
/** Which drag area the mouse pointer is over, if any. */
private dragTarget_: IDragTarget|null = null;
private dragTarget_: IDragTarget | null = null;
/** Whether the block would be deleted if dropped immediately. */
protected wouldDeleteBlock_ = false;
@@ -59,8 +58,9 @@ export class BlockDragger implements IBlockDragger {
this.draggingBlock_ = block;
/** Object that keeps track of connections on dragged blocks. */
this.draggedConnectionManager_ =
new InsertionMarkerManager(this.draggingBlock_);
this.draggedConnectionManager_ = new InsertionMarkerManager(
this.draggingBlock_
);
this.workspace_ = workspace;
@@ -136,9 +136,11 @@ export class BlockDragger implements IBlockDragger {
*/
protected shouldDisconnect_(healStack: boolean): boolean {
return !!(
this.draggingBlock_.getParent() ||
healStack && this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock());
this.draggingBlock_.getParent() ||
(healStack &&
this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock())
);
}
/**
@@ -149,7 +151,9 @@ export class BlockDragger implements IBlockDragger {
* at mouse down, in pixel units.
*/
protected disconnectBlock_(
healStack: boolean, currentDragDeltaXY: Coordinate) {
healStack: boolean,
currentDragDeltaXY: Coordinate
) {
this.draggingBlock_.unplug(healStack);
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
@@ -162,7 +166,10 @@ export class BlockDragger implements IBlockDragger {
/** Fire a UI event at the start of a block drag. */
protected fireDragStartEvent_() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, true, this.draggingBlock_.getDescendants(false));
this.draggingBlock_,
true,
this.draggingBlock_.getDescendants(false)
);
eventUtils.fire(event);
}
@@ -217,10 +224,11 @@ export class BlockDragger implements IBlockDragger {
blockAnimation.disconnectUiStop();
const preventMove = !!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
const preventMove =
!!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
let newLoc: Coordinate;
let delta: Coordinate|null = null;
let delta: Coordinate | null = null;
if (preventMove) {
newLoc = this.startXY_;
} else {
@@ -238,15 +246,17 @@ export class BlockDragger implements IBlockDragger {
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.setDragging(false);
if (delta) { // !preventMove
if (delta) {
// !preventMove
this.updateBlockAfterMove_();
} else {
// Blocks dragged directly from a flyout may need to be bumped into
// bounds.
bumpObjects.bumpIntoBounds(
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_);
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_
);
}
}
this.workspace_.setResizesEnabled(true);
@@ -262,8 +272,10 @@ export class BlockDragger implements IBlockDragger {
* @returns New location after drag. delta is in workspace units. newLocation
* is the new coordinate where the block should end up.
*/
protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate):
{delta: Coordinate, newLocation: Coordinate} {
protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate): {
delta: Coordinate;
newLocation: Coordinate;
} {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLocation = Coordinate.sum(this.startXY_, delta);
return {
@@ -307,7 +319,10 @@ export class BlockDragger implements IBlockDragger {
/** Fire a UI event at the end of a block drag. */
protected fireDragEndEvent_() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
this.draggingBlock_, false, this.draggingBlock_.getDescendants(false));
this.draggingBlock_,
false,
this.draggingBlock_.getDescendants(false)
);
eventUtils.fire(event);
}
@@ -322,21 +337,25 @@ export class BlockDragger implements IBlockDragger {
const toolbox = this.workspace_.getToolbox();
if (toolbox) {
const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
const style = this.draggingBlock_.isDeletable()
? 'blocklyToolboxDelete'
: 'blocklyToolboxGrab';
// AnyDuringMigration because: Property 'removeStyle' does not exist on
// type 'IToolbox'.
if (isEnd &&
typeof (toolbox as AnyDuringMigration).removeStyle === 'function') {
if (
isEnd &&
typeof (toolbox as AnyDuringMigration).removeStyle === 'function'
) {
// AnyDuringMigration because: Property 'removeStyle' does not exist on
// type 'IToolbox'.
(toolbox as AnyDuringMigration).removeStyle(style);
// AnyDuringMigration because: Property 'addStyle' does not exist on
// type 'IToolbox'.
} else if (
!isEnd &&
typeof (toolbox as AnyDuringMigration).addStyle === 'function') {
!isEnd &&
typeof (toolbox as AnyDuringMigration).addStyle === 'function'
) {
// AnyDuringMigration because: Property 'addStyle' does not exist on
// type 'IToolbox'.
(toolbox as AnyDuringMigration).addStyle(style);
@@ -348,7 +367,8 @@ export class BlockDragger implements IBlockDragger {
protected fireMoveEvent_() {
if (this.draggingBlock_.isDeadOrDying()) return;
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
this.draggingBlock_) as BlockMove;
this.draggingBlock_
) as BlockMove;
event.setReason(['drag']);
event.oldCoordinate = this.startXY_;
event.recordNew();
@@ -374,8 +394,9 @@ export class BlockDragger implements IBlockDragger {
*/
protected pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale
);
if (this.workspace_.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same
@@ -408,8 +429,10 @@ export class BlockDragger implements IBlockDragger {
*/
getInsertionMarkers(): BlockSvg[] {
// No insertion markers with the old style of dragged connection managers.
if (this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers) {
if (
this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers
) {
return this.draggedConnectionManager_.getInsertionMarkers();
}
return [];
@@ -433,15 +456,15 @@ export interface IconPositionData {
function initIconData(block: BlockSvg): IconPositionData[] {
// Build a list of icons that need to be moved and where they started.
const dragIconData = [];
const descendants = (block.getDescendants(false));
const descendants = block.getDescendants(false);
for (let i = 0, descendant; descendant = descendants[i]; i++) {
for (let i = 0, descendant; (descendant = descendants[i]); i++) {
const icons = descendant.getIcons();
for (let j = 0; j < icons.length; j++) {
const data = {
// Coordinate with x and y properties (workspace
// coordinates).
location: icons[j].getIconLocation(), // Blockly.Icon
location: icons[j].getIconLocation(), // Blockly.Icon
icon: icons[j],
};
dragIconData.push(data);

View File

@@ -25,7 +25,11 @@ import type {Connection} from './connection.js';
import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import * as ContextMenu from './contextmenu.js';
import {ContextMenuOption, ContextMenuRegistry, LegacyContextMenuOption} from './contextmenu_registry.js';
import {
ContextMenuOption,
ContextMenuRegistry,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import type {BlockMove} from './events/events_block_move.js';
import * as eventUtils from './events/utils.js';
import type {Field} from './field.js';
@@ -58,14 +62,14 @@ import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import {queueRender} from './render_management.js';
/**
* Class for a block's SVG representation.
* Not normally called directly, workspace.newBlock() is preferred.
*/
export class BlockSvg extends Block implements IASTNodeLocationSvg,
IBoundedElement, ICopyable,
IDraggable {
export class BlockSvg
extends Block
implements IASTNodeLocationSvg, IBoundedElement, ICopyable, IDraggable
{
/**
* Constant for identifying rows that are to be rendered inline.
* Don't collide with Blockly.inputTypes.
@@ -81,15 +85,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
override decompose?: (p1: Workspace) => BlockSvg;
// override compose?: ((p1: BlockSvg) => void)|null;
saveConnections?: (p1: BlockSvg) => void;
customContextMenu?:
(p1: Array<ContextMenuOption|LegacyContextMenuOption>) => void;
customContextMenu?: (
p1: Array<ContextMenuOption | LegacyContextMenuOption>
) => void;
/**
* An property used internally to reference the block's rendering debugger.
*
* @internal
*/
renderingDebugger: BlockRenderingDebug|null = null;
renderingDebugger: BlockRenderingDebug | null = null;
/**
* Height of this block, not including any statement blocks above or below.
@@ -110,13 +115,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
private warningTextDb = new Map<string, ReturnType<typeof setTimeout>>();
/** Block's mutator icon (if any). */
mutator: Mutator|null = null;
mutator: Mutator | null = null;
/** Block's comment icon (if any). */
private commentIcon_: Comment|null = null;
private commentIcon_: Comment | null = null;
/** Block's warning icon (if any). */
warning: Warning|null = null;
warning: Warning | null = null;
private svgGroup_: SVGGElement;
style: BlockStyle;
@@ -160,7 +165,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
relativeCoords = new Coordinate(0, 0);
/**
* @param workspace The block's workspace.
* @param prototypeName Name of the language object containing type-specific
@@ -177,8 +181,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
/** The renderer's path object. */
this.pathObject =
workspace.getRenderer().makePathObject(this.svgGroup_, this.style);
this.pathObject = workspace
.getRenderer()
.makePathObject(this.svgGroup_, this.style);
/**
* Whether to move the block to the drag surface when it is dragged.
@@ -204,7 +209,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
if (!this.workspace.rendered) {
throw TypeError('Workspace is headless.');
}
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
input.init();
}
const icons = this.getIcons();
@@ -216,7 +221,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
const svg = this.getSvgRoot();
if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
browserEvents.conditionalBind(
svg, 'pointerdown', this, this.onMouseDown_);
svg,
'pointerdown',
this,
this.onMouseDown_
);
}
this.eventsInit_ = true;
@@ -230,7 +239,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns #RRGGBB string.
*/
getColourSecondary(): string|undefined {
getColourSecondary(): string | undefined {
return this.style.colourSecondary;
}
@@ -239,7 +248,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns #RRGGBB string.
*/
getColourTertiary(): string|undefined {
getColourTertiary(): string | undefined {
return this.style.colourTertiary;
}
@@ -268,7 +277,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
}
const event = new (eventUtils.get(eventUtils.SELECTED))(
oldId, this.id, this.workspace.id);
oldId,
this.id,
this.workspace.id
);
eventUtils.fire(event);
common.setSelected(this);
this.addSelect();
@@ -283,7 +295,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
return;
}
const event = new (eventUtils.get(eventUtils.SELECTED))(
this.id, null, this.workspace.id);
this.id,
null,
this.workspace.id
);
event.workspaceId = this.workspace.id;
eventUtils.fire(event);
common.setSelected(null);
@@ -315,7 +330,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param newParent New parent block.
* @internal
*/
override setParent(newParent: this|null) {
override setParent(newParent: this | null) {
const oldParent = this.parentBlock_;
if (newParent === oldParent) {
return;
@@ -359,9 +374,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
let x = 0;
let y = 0;
const dragSurfaceGroup = this.useDragSurface_ ?
this.workspace.getBlockDragSurface()!.getGroup() :
null;
const dragSurfaceGroup = this.useDragSurface_
? this.workspace.getBlockDragSurface()!.getGroup()
: null;
let element: SVGElement = this.getSvgRoot();
if (element) {
@@ -372,17 +387,22 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
y += xy.y;
// If this element is the current element on the drag surface, include
// the translation of the drag surface itself.
if (this.useDragSurface_ &&
this.workspace.getBlockDragSurface()!.getCurrentBlock() ===
element) {
const surfaceTranslation =
this.workspace.getBlockDragSurface()!.getSurfaceTranslation();
if (
this.useDragSurface_ &&
this.workspace.getBlockDragSurface()!.getCurrentBlock() === element
) {
const surfaceTranslation = this.workspace
.getBlockDragSurface()!
.getSurfaceTranslation();
x += surfaceTranslation.x;
y += surfaceTranslation.y;
}
element = element.parentNode as SVGElement;
} while (element && element !== this.workspace.getCanvas() &&
element !== dragSurfaceGroup);
} while (
element &&
element !== this.workspace.getCanvas() &&
element !== dragSurfaceGroup
);
}
return new Coordinate(x, y);
}
@@ -399,9 +419,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
throw Error('Block has parent');
}
const eventsEnabled = eventUtils.isEnabled();
let event: BlockMove|null = null;
let event: BlockMove | null = null;
if (eventsEnabled) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))!(this) as BlockMove;
event = new (eventUtils.get(eventUtils.BLOCK_MOVE)!)(this) as BlockMove;
reason && event.setReason(reason);
}
const xy = this.getRelativeToSurfaceXY();
@@ -487,8 +507,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
// Translate to current position, turning off 3d.
this.translate(newXY.x, newXY.y);
this.workspace.getBlockDragSurface()!.clearAndHide(
this.workspace.getCanvas());
this.workspace
.getBlockDragSurface()!
.clearAndHide(this.workspace.getCanvas());
}
/**
@@ -501,8 +522,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
moveDuringDrag(newLoc: Coordinate) {
if (this.useDragSurface_) {
this.workspace.getBlockDragSurface()!.translateSurface(
newLoc.x, newLoc.y);
this.workspace
.getBlockDragSurface()!
.translateSurface(newLoc.x, newLoc.y);
} else {
this.translate(newLoc.x, newLoc.y);
this.getSvgRoot().setAttribute('transform', this.getTranslation());
@@ -520,29 +542,31 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
/** Snap this block to the nearest grid point. */
snapToGrid() {
if (this.isDeadOrDying()) {
return; // Deleted block.
return; // Deleted block.
}
if (this.workspace.isDragging()) {
return; // Don't bump blocks during a drag.;
return; // Don't bump blocks during a drag.;
}
if (this.getParent()) {
return; // Only snap top-level blocks.
return; // Only snap top-level blocks.
}
if (this.isInFlyout) {
return; // Don't move blocks around in a flyout.
return; // Don't move blocks around in a flyout.
}
const grid = this.workspace.getGrid();
if (!grid || !grid.shouldSnap()) {
return; // Config says no snapping.
return; // Config says no snapping.
}
const spacing = grid.getSpacing();
const half = spacing / 2;
const xy = this.getRelativeToSurfaceXY();
const dx =
Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x);
const dy =
Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y);
const dx = Math.round(
Math.round((xy.x - half) / spacing) * spacing + half - xy.x
);
const dy = Math.round(
Math.round((xy.y - half) / spacing) * spacing + half - xy.y
);
if (dx || dy) {
this.moveBy(dx, dy, ['snap']);
}
@@ -576,7 +600,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
markDirty() {
this.pathObject.constants = this.workspace.getRenderer().getConstants();
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
input.markDirty();
}
}
@@ -603,7 +627,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
if (input.name !== collapsedInputName) {
input.setVisible(!collapsed);
}
@@ -616,7 +640,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
const icons = this.getIcons();
for (let i = 0, icon; icon = icons[i]; i++) {
for (let i = 0, icon; (icon = icons[i]); i++) {
icon.setVisible(false);
}
@@ -626,8 +650,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
field.setValue(text);
return;
}
const input = this.getInput(collapsedInputName) ||
this.appendDummyInput(collapsedInputName);
const input =
this.getInput(collapsedInputName) ||
this.appendDummyInput(collapsedInputName);
input.appendField(new FieldLabel(text), collapsedFieldName);
}
@@ -679,7 +704,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
showHelp() {
const url =
typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl;
typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl;
if (url) {
window.open(url);
}
@@ -690,13 +715,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns Context menu options or null if no menu.
*/
protected generateContextMenu():
Array<ContextMenuOption|LegacyContextMenuOption>|null {
protected generateContextMenu(): Array<
ContextMenuOption | LegacyContextMenuOption
> | null {
if (this.workspace.options.readOnly || !this.contextMenu) {
return null;
}
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
ContextMenuRegistry.ScopeType.BLOCK, {block: this});
ContextMenuRegistry.ScopeType.BLOCK,
{block: this}
);
// Allow the block to add or modify menuOptions.
if (this.customContextMenu) {
@@ -815,12 +843,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
override setInsertionMarker(insertionMarker: boolean) {
if (this.isInsertionMarker_ === insertionMarker) {
return; // No change.
return; // No change.
}
this.isInsertionMarker_ = insertionMarker;
if (this.isInsertionMarker_) {
this.setColour(
this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR);
this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR
);
this.pathObject.updateInsertionMarker(true);
}
}
@@ -897,8 +926,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
// (https://github.com/google/blockly/issues/4832)
this.dispose(false, true);
} else {
this.dispose(/* heal */
true, true);
this.dispose(/* heal */ true, true);
}
eventUtils.setGroup(false);
}
@@ -909,14 +937,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns Copy metadata, or null if the block is an insertion marker.
* @internal
*/
toCopyData(): CopyData|null {
toCopyData(): CopyData | null {
if (this.isInsertionMarker_) {
return null;
}
return {
saveInfo:
blocks.save(this, {addCoordinates: true, addNextBlocks: false}) as
blocks.State,
saveInfo: blocks.save(this, {
addCoordinates: true,
addNextBlocks: false,
}) as blocks.State,
source: this.workspace,
typeCounts: common.getBlockTypeCounts(this, true),
};
@@ -935,8 +964,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
icons[i].applyColour();
}
for (let x = 0, input; input = this.inputList[x]; x++) {
for (let y = 0, field; field = input.fieldRow[y]; y++) {
for (let x = 0, input; (input = this.inputList[x]); x++) {
for (let y = 0, field; (field = input.fieldRow[y]); y++) {
field.applyColour();
}
}
@@ -969,7 +998,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The comment icon attached to this block, or null.
*/
getCommentIcon(): Comment|null {
getCommentIcon(): Comment | null {
return this.commentIcon_;
}
@@ -978,7 +1007,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param text The text, or null to delete.
*/
override setCommentText(text: string|null) {
override setCommentText(text: string | null) {
if (this.commentModel.text === text) {
return;
}
@@ -993,11 +1022,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
if (shouldHaveComment) {
this.commentIcon_ = new Comment(this);
this.comment = this.commentIcon_; // For backwards compatibility.
this.comment = this.commentIcon_; // For backwards compatibility.
} else {
this.commentIcon_!.dispose();
this.commentIcon_ = null;
this.comment = null; // For backwards compatibility.
this.comment = null; // For backwards compatibility.
}
if (this.rendered) {
// Icons must force an immediate render so that bubbles can be opened
@@ -1015,7 +1044,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param opt_id An optional ID for the warning text to be able to maintain
* multiple warnings.
*/
override setWarningText(text: string|null, opt_id?: string) {
override setWarningText(text: string | null, opt_id?: string) {
const id = opt_id || '';
if (!id) {
// Kill all previous pending processes, this edit supersedes them all.
@@ -1031,12 +1060,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
if (this.workspace.isDragging()) {
// Don't change the warning text during a drag.
// Wait until the drag finishes.
this.warningTextDb.set(id, setTimeout(() => {
if (!this.isDeadOrDying()) {
this.warningTextDb.delete(id);
this.setWarningText(text, id);
}
}, 100));
this.warningTextDb.set(
id,
setTimeout(() => {
if (!this.isDeadOrDying()) {
this.warningTextDb.delete(id);
this.setWarningText(text, id);
}
}, 100)
);
return;
}
if (this.isInFlyout) {
@@ -1056,14 +1088,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
if (collapsedParent) {
collapsedParent.setWarningText(
Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID);
Msg['COLLAPSED_WARNINGS_WARNING'],
BlockSvg.COLLAPSED_WARNING_ID
);
}
if (!this.warning) {
this.warning = new Warning(this);
changedState = true;
}
this.warning!.setText((text), id);
this.warning!.setText(text, id);
} else {
// Dispose all warnings if no ID is given.
if (this.warning && !id) {
@@ -1093,7 +1127,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param mutator A mutator dialog instance or null to remove.
*/
override setMutator(mutator: Mutator|null) {
override setMutator(mutator: Mutator | null) {
if (this.mutator && this.mutator !== mutator) {
this.mutator.dispose();
}
@@ -1186,11 +1220,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param colour HSV hue value, or #RRGGBB string.
*/
override setColour(colour: number|string) {
override setColour(colour: number | string) {
super.setColour(colour);
const styleObj =
this.workspace.getRenderer().getConstants().getBlockStyleForColour(
this.colour_);
const styleObj = this.workspace
.getRenderer()
.getConstants()
.getBlockStyleForColour(this.colour_);
this.pathObject.setStyle(styleObj.style);
this.style = styleObj.style;
@@ -1206,9 +1241,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @throws {Error} if the block style does not exist.
*/
override setStyle(blockStyleName: string) {
const blockStyle =
this.workspace.getRenderer().getConstants().getBlockStyle(
blockStyleName);
const blockStyle = this.workspace
.getRenderer()
.getConstants()
.getBlockStyle(blockStyleName);
this.styleName_ = blockStyleName;
if (blockStyle) {
@@ -1234,7 +1270,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
bringToFront() {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
let block: this|null = this;
let block: this | null = this;
do {
const root = block.getSvgRoot();
const parent = root.parentNode;
@@ -1255,7 +1291,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* if any type could be connected.
*/
override setPreviousStatement(
newBoolean: boolean, opt_check?: string|string[]|null) {
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setPreviousStatement(newBoolean, opt_check);
if (this.rendered) {
@@ -1272,7 +1310,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* if any type could be connected.
*/
override setNextStatement(
newBoolean: boolean, opt_check?: string|string[]|null) {
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setNextStatement(newBoolean, opt_check);
if (this.rendered) {
@@ -1288,7 +1328,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param opt_check Returned type or list of returned types. Null or
* undefined if any type could be returned (e.g. variable get).
*/
override setOutput(newBoolean: boolean, opt_check?: string|string[]|null) {
override setOutput(
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setOutput(newBoolean, opt_check);
if (this.rendered) {
@@ -1372,14 +1415,14 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
setConnectionTracking(track: boolean) {
if (this.previousConnection) {
(this.previousConnection).setTracking(track);
this.previousConnection.setTracking(track);
}
if (this.outputConnection) {
(this.outputConnection).setTracking(track);
this.outputConnection.setTracking(track);
}
if (this.nextConnection) {
(this.nextConnection).setTracking(track);
const child = (this.nextConnection).targetBlock();
this.nextConnection.setTracking(track);
const child = this.nextConnection.targetBlock();
if (child) {
child.setConnectionTracking(track);
}
@@ -1428,7 +1471,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
myConnections.push(this.nextConnection);
}
if (all || !this.collapsed_) {
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection) {
myConnections.push(input.connection as RenderedConnection);
}
@@ -1448,8 +1491,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The last next connection on the stack, or null.
* @internal
*/
override lastConnectionInStack(ignoreShadows: boolean): RenderedConnection
|null {
override lastConnectionInStack(
ignoreShadows: boolean
): RenderedConnection | null {
return super.lastConnectionInStack(ignoreShadows) as RenderedConnection;
}
@@ -1463,8 +1507,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The matching connection on this block, or null.
* @internal
*/
override getMatchingConnection(otherBlock: Block, conn: Connection):
RenderedConnection|null {
override getMatchingConnection(
otherBlock: Block,
conn: Connection
): RenderedConnection | null {
return super.getMatchingConnection(otherBlock, conn) as RenderedConnection;
}
@@ -1483,7 +1529,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The next statement block or null.
*/
override getNextBlock(): BlockSvg|null {
override getNextBlock(): BlockSvg | null {
return super.getNextBlock() as BlockSvg;
}
@@ -1492,7 +1538,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The previous statement block or null.
*/
override getPreviousBlock(): BlockSvg|null {
override getPreviousBlock(): BlockSvg | null {
return super.getPreviousBlock() as BlockSvg;
}
@@ -1520,8 +1566,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
private bumpNeighboursInternal() {
const root = this.getRootBlock();
if (this.isDeadOrDying() || this.workspace.isDragging() ||
root.isInFlyout) {
if (
this.isDeadOrDying() ||
this.workspace.isDragging() ||
root.isInFlyout
) {
return;
}
@@ -1581,12 +1630,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @internal
*/
positionNearConnection(
sourceConnection: RenderedConnection,
targetConnection: RenderedConnection) {
sourceConnection: RenderedConnection,
targetConnection: RenderedConnection
) {
// We only need to position the new block if it's before the existing one,
// otherwise its position is set by the previous block.
if (sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
sourceConnection.type === ConnectionType.INPUT_VALUE) {
if (
sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
sourceConnection.type === ConnectionType.INPUT_VALUE
) {
const dx = targetConnection.x - sourceConnection.x;
const dy = targetConnection.y - sourceConnection.y;
@@ -1598,7 +1650,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The first statement connection or null.
* @internal
*/
override getFirstStatementConnection(): RenderedConnection|null {
override getFirstStatementConnection(): RenderedConnection | null {
return super.getFirstStatementConnection() as RenderedConnection | null;
}
@@ -1636,7 +1688,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
render(opt_bubble?: boolean) {
if (this.renderIsInProgress_) {
return; // Don't allow recursive renders.
return; // Don't allow recursive renders.
}
this.renderIsInProgress_ = true;
try {
@@ -1784,15 +1836,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns Object with height and width properties in workspace units.
* @internal
*/
getHeightWidth(): {height: number, width: number} {
getHeightWidth(): {height: number; width: number} {
let height = this.height;
let width = this.width;
// Recursively add size of subsequent blocks.
const nextBlock = this.getNextBlock();
if (nextBlock) {
const nextHeightWidth = nextBlock.getHeightWidth();
const tabHeight =
this.workspace.getRenderer().getConstants().NOTCH_HEIGHT;
const tabHeight = this.workspace
.getRenderer()
.getConstants().NOTCH_HEIGHT;
height += nextHeightWidth.height - tabHeight;
width = Math.max(width, nextHeightWidth.width);
}

View File

@@ -48,19 +48,75 @@ import {DragTarget} from './drag_target.js';
import * as dropDownDiv from './dropdowndiv.js';
import * as Events from './events/events.js';
import * as Extensions from './extensions.js';
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {FieldAngle, FieldAngleConfig, FieldAngleFromJsonConfig, FieldAngleValidator} from './field_angle.js';
import {FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator} from './field_checkbox.js';
import {FieldColour, FieldColourConfig, FieldColourFromJsonConfig, FieldColourValidator} from './field_colour.js';
import {FieldDropdown, FieldDropdownConfig, FieldDropdownFromJsonConfig, FieldDropdownValidator, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js';
import {FieldImage, FieldImageConfig, FieldImageFromJsonConfig} from './field_image.js';
import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import {
FieldAngle,
FieldAngleConfig,
FieldAngleFromJsonConfig,
FieldAngleValidator,
} from './field_angle.js';
import {
FieldCheckbox,
FieldCheckboxConfig,
FieldCheckboxFromJsonConfig,
FieldCheckboxValidator,
} from './field_checkbox.js';
import {
FieldColour,
FieldColourConfig,
FieldColourFromJsonConfig,
FieldColourValidator,
} from './field_colour.js';
import {
FieldDropdown,
FieldDropdownConfig,
FieldDropdownFromJsonConfig,
FieldDropdownValidator,
MenuGenerator,
MenuGeneratorFunction,
MenuOption,
} from './field_dropdown.js';
import {
FieldImage,
FieldImageConfig,
FieldImageFromJsonConfig,
} from './field_image.js';
import {
FieldLabel,
FieldLabelConfig,
FieldLabelFromJsonConfig,
} from './field_label.js';
import {FieldLabelSerializable} from './field_label_serializable.js';
import {FieldMultilineInput, FieldMultilineInputConfig, FieldMultilineInputFromJsonConfig, FieldMultilineInputValidator} from './field_multilineinput.js';
import {FieldNumber, FieldNumberConfig, FieldNumberFromJsonConfig, FieldNumberValidator} from './field_number.js';
import {
FieldMultilineInput,
FieldMultilineInputConfig,
FieldMultilineInputFromJsonConfig,
FieldMultilineInputValidator,
} from './field_multilineinput.js';
import {
FieldNumber,
FieldNumberConfig,
FieldNumberFromJsonConfig,
FieldNumberValidator,
} from './field_number.js';
import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputFromJsonConfig, FieldTextInputValidator} from './field_textinput.js';
import {FieldVariable, FieldVariableConfig, FieldVariableFromJsonConfig, FieldVariableValidator} from './field_variable.js';
import {
FieldTextInput,
FieldTextInputConfig,
FieldTextInputFromJsonConfig,
FieldTextInputValidator,
} from './field_textinput.js';
import {
FieldVariable,
FieldVariableConfig,
FieldVariableFromJsonConfig,
FieldVariableValidator,
} from './field_variable.js';
import {Flyout} from './flyout_base.js';
import {FlyoutButton} from './flyout_button.js';
import {HorizontalFlyout} from './flyout_horizontal.js';
@@ -105,7 +161,10 @@ import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js'
import {IStyleable} from './interfaces/i_styleable.js';
import {IToolbox} from './interfaces/i_toolbox.js';
import {IToolboxItem} from './interfaces/i_toolbox_item.js';
import {IVariableBackedParameterModel, isVariableBackedParameterModel} from './interfaces/i_variable_backed_parameter_model.js';
import {
IVariableBackedParameterModel,
isVariableBackedParameterModel,
} from './interfaces/i_variable_backed_parameter_model.js';
import * as internalConstants from './internal_constants.js';
import {ASTNode} from './keyboard_nav/ast_node.js';
import {BasicCursor} from './keyboard_nav/basic_cursor.js';
@@ -163,11 +222,13 @@ import {WorkspaceComment} from './workspace_comment.js';
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
import {WorkspaceDragger} from './workspace_dragger.js';
import {resizeSvgContents as realResizeSvgContents, WorkspaceSvg} from './workspace_svg.js';
import {
resizeSvgContents as realResizeSvgContents,
WorkspaceSvg,
} from './workspace_svg.js';
import * as Xml from './xml.js';
import {ZoomControls} from './zoom_controls.js';
/**
* Blockly core version.
* This constant is overridden by the build script (npm run build) to the value
@@ -327,8 +388,11 @@ export const setParentContainer = common.setParentContainer;
*/
function resizeSvgContentsLocal(workspace: WorkspaceSvg) {
deprecation.warn(
'Blockly.resizeSvgContents', 'December 2021', 'December 2022',
'Blockly.WorkspaceSvg.resizeSvgContents');
'Blockly.resizeSvgContents',
'December 2021',
'December 2022',
'Blockly.WorkspaceSvg.resizeSvgContents'
);
realResizeSvgContents(workspace);
}
export const resizeSvgContents = resizeSvgContentsLocal;
@@ -342,8 +406,11 @@ export const resizeSvgContents = resizeSvgContentsLocal;
*/
export function copy(toCopy: ICopyable) {
deprecation.warn(
'Blockly.copy', 'December 2021', 'December 2022',
'Blockly.clipboard.copy');
'Blockly.copy',
'December 2021',
'December 2022',
'Blockly.clipboard.copy'
);
clipboard.copy(toCopy);
}
@@ -356,8 +423,11 @@ export function copy(toCopy: ICopyable) {
*/
export function paste(): boolean {
deprecation.warn(
'Blockly.paste', 'December 2021', 'December 2022',
'Blockly.clipboard.paste');
'Blockly.paste',
'December 2021',
'December 2022',
'Blockly.clipboard.paste'
);
return !!clipboard.paste();
}
@@ -370,8 +440,11 @@ export function paste(): boolean {
*/
export function duplicate(toDuplicate: ICopyable) {
deprecation.warn(
'Blockly.duplicate', 'December 2021', 'December 2022',
'Blockly.clipboard.duplicate');
'Blockly.duplicate',
'December 2021',
'December 2022',
'Blockly.clipboard.duplicate'
);
clipboard.duplicate(toDuplicate);
}
@@ -385,8 +458,11 @@ export function duplicate(toDuplicate: ICopyable) {
*/
export function isNumber(str: string): boolean {
deprecation.warn(
'Blockly.isNumber', 'December 2021', 'December 2022',
'Blockly.utils.string.isNumber');
'Blockly.isNumber',
'December 2021',
'December 2022',
'Blockly.utils.string.isNumber'
);
return utils.string.isNumber(str);
}
@@ -400,8 +476,11 @@ export function isNumber(str: string): boolean {
*/
export function hueToHex(hue: number): string {
deprecation.warn(
'Blockly.hueToHex', 'December 2021', 'December 2022',
'Blockly.utils.colour.hueToHex');
'Blockly.hueToHex',
'December 2021',
'December 2022',
'Blockly.utils.colour.hueToHex'
);
return colour.hueToHex(hue);
}
@@ -420,11 +499,17 @@ export function hueToHex(hue: number): string {
* @see Blockly.browserEvents.bind
*/
export function bindEvent_(
node: EventTarget, name: string, thisObject: Object|null,
func: Function): browserEvents.Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function
): browserEvents.Data {
deprecation.warn(
'Blockly.bindEvent_', 'December 2021', 'December 2022',
'Blockly.browserEvents.bind');
'Blockly.bindEvent_',
'December 2021',
'December 2022',
'Blockly.browserEvents.bind'
);
return browserEvents.bind(node, name, thisObject, func);
}
@@ -439,8 +524,11 @@ export function bindEvent_(
*/
export function unbindEvent_(bindData: browserEvents.Data): Function {
deprecation.warn(
'Blockly.unbindEvent_', 'December 2021', 'December 2022',
'Blockly.browserEvents.unbind');
'Blockly.unbindEvent_',
'December 2021',
'December 2022',
'Blockly.browserEvents.unbind'
);
return browserEvents.unbind(bindData);
}
@@ -463,14 +551,26 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
* @see browserEvents.conditionalBind
*/
export function bindEventWithChecks_(
node: EventTarget, name: string, thisObject: Object|null, func: Function,
opt_noCaptureIdentifier?: boolean,
_opt_noPreventDefault?: boolean): browserEvents.Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function,
opt_noCaptureIdentifier?: boolean,
_opt_noPreventDefault?: boolean
): browserEvents.Data {
deprecation.warn(
'Blockly.bindEventWithChecks_', 'December 2021', 'December 2022',
'Blockly.browserEvents.conditionalBind');
'Blockly.bindEventWithChecks_',
'December 2021',
'December 2022',
'Blockly.browserEvents.conditionalBind'
);
return browserEvents.conditionalBind(
node, name, thisObject, func, opt_noCaptureIdentifier);
node,
name,
thisObject,
func,
opt_noCaptureIdentifier
);
}
// Aliases to allow external code to access these values for legacy reasons.
@@ -495,7 +595,7 @@ export const VARIABLE_CATEGORY_NAME: string = Variables.CATEGORY_NAME;
* variable blocks.
*/
export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
VariablesDynamic.CATEGORY_NAME;
VariablesDynamic.CATEGORY_NAME;
/**
* String for use in the "custom" attribute of a category in toolbox XML.
* This string indicates that the category should be dynamically populated with
@@ -503,58 +603,62 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
*/
export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME;
// Context for why we need to monkey-patch in these functions (internal):
// https://docs.google.com/document/d/1MbO0LEA-pAyx1ErGLJnyUqTLrcYTo-5zga9qplnxeXo/edit?usp=sharing&resourcekey=0-5h_32-i-dHwHjf_9KYEVKg
// clang-format off
Workspace.prototype.newBlock =
function(prototypeName: string, opt_id?: string): Block {
return new Block(this, prototypeName, opt_id);
};
Workspace.prototype.newBlock = function (
prototypeName: string,
opt_id?: string
): Block {
return new Block(this, prototypeName, opt_id);
};
WorkspaceSvg.prototype.newBlock =
function(prototypeName: string, opt_id?: string): BlockSvg {
return new BlockSvg(this, prototypeName, opt_id);
};
WorkspaceSvg.prototype.newBlock = function (
prototypeName: string,
opt_id?: string
): BlockSvg {
return new BlockSvg(this, prototypeName, opt_id);
};
WorkspaceSvg.newTrashcan = function(workspace: WorkspaceSvg): Trashcan {
WorkspaceSvg.newTrashcan = function (workspace: WorkspaceSvg): Trashcan {
return new Trashcan(workspace);
};
WorkspaceCommentSvg.prototype.showContextMenu =
function(this: WorkspaceCommentSvg, e: Event) {
if (this.workspace.options.readOnly) {
return;
}
const menuOptions = [];
WorkspaceCommentSvg.prototype.showContextMenu = function (
this: WorkspaceCommentSvg,
e: Event
) {
if (this.workspace.options.readOnly) {
return;
}
const menuOptions = [];
if (this.isDeletable() && this.isMovable()) {
menuOptions.push(ContextMenu.commentDuplicateOption(this));
menuOptions.push(ContextMenu.commentDeleteOption(this));
}
if (this.isDeletable() && this.isMovable()) {
menuOptions.push(ContextMenu.commentDuplicateOption(this));
menuOptions.push(ContextMenu.commentDeleteOption(this));
}
ContextMenu.show(e, menuOptions, this.RTL);
};
ContextMenu.show(e, menuOptions, this.RTL);
};
Mutator.prototype.newWorkspaceSvg =
function(options: Options): WorkspaceSvg {
return new WorkspaceSvg(options);
};
Mutator.prototype.newWorkspaceSvg = function (options: Options): WorkspaceSvg {
return new WorkspaceSvg(options);
};
Names.prototype.populateProcedures =
function(this: Names, workspace: Workspace) {
const procedures = Procedures.allProcedures(workspace);
// Flatten the return vs no-return procedure lists.
const flattenedProcedures =
procedures[0].concat(procedures[1]);
for (let i = 0; i < flattenedProcedures.length; i++) {
this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE);
}
};
Names.prototype.populateProcedures = function (
this: Names,
workspace: Workspace
) {
const procedures = Procedures.allProcedures(workspace);
// Flatten the return vs no-return procedure lists.
const flattenedProcedures = procedures[0].concat(procedures[1]);
for (let i = 0; i < flattenedProcedures.length; i++) {
this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE);
}
};
// clang-format on
// Re-export submodules that no longer declareLegacyNamespace.
export {browserEvents};
export {ContextMenu};
@@ -668,9 +772,9 @@ export {Flyout};
export {FlyoutButton};
export {FlyoutMetricsManager};
export {CodeGenerator};
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
export {Gesture};
export {Gesture as TouchGesture}; // Remove in v10.
export {Gesture as TouchGesture}; // Remove in v10.
export {Grid};
export {HorizontalFlyout};
export {IASTNodeLocation};

View File

@@ -11,7 +11,6 @@ import type {Theme, ITheme} from './theme.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import type {ToolboxDefinition} from './utils/toolbox.js';
/**
* Blockly options.
*/
@@ -32,14 +31,14 @@ export interface BlocklyOptions {
renderer?: string;
rendererOverrides?: {[rendererConstant: string]: any};
rtl?: boolean;
scrollbars?: ScrollbarOptions|boolean;
scrollbars?: ScrollbarOptions | boolean;
sounds?: boolean;
theme?: Theme|string|ITheme;
toolbox?: string|ToolboxDefinition|Element;
theme?: Theme | string | ITheme;
toolbox?: string | ToolboxDefinition | Element;
toolboxPosition?: string;
trashcan?: boolean;
maxTrashcanContents?: number;
plugins?: {[key: string]: (new(...p1: any[]) => any)|string};
plugins?: {[key: string]: (new (...p1: any[]) => any) | string};
zoom?: ZoomOptions;
parentWorkspace?: WorkspaceSvg;
}
@@ -53,7 +52,7 @@ export interface GridOptions {
export interface MoveOptions {
drag?: boolean;
scrollbars?: boolean|ScrollbarOptions;
scrollbars?: boolean | ScrollbarOptions;
wheel?: boolean;
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.blocks');
/**
* A block definition. For now this very loose, but it can potentially
* be refined e.g. by replacing this typedef with a class definition.

View File

@@ -11,7 +11,6 @@ import * as Touch from './touch.js';
import * as deprecation from './utils/deprecation.js';
import * as userAgent from './utils/useragent.js';
/**
* Blockly opaque event data used to unbind events when using
* `bind` and `conditionalBind`.
@@ -49,12 +48,19 @@ const PAGE_MODE_MULTIPLIER = 125;
* @returns Opaque data that can be passed to unbindEvent_.
*/
export function conditionalBind(
node: EventTarget, name: string, thisObject: Object|null, func: Function,
opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function,
opt_noCaptureIdentifier?: boolean,
opt_noPreventDefault?: boolean
): Data {
if (opt_noPreventDefault !== undefined) {
deprecation.warn(
'The opt_noPreventDefault argument of conditionalBind', 'version 9',
'version 10');
'The opt_noPreventDefault argument of conditionalBind',
'version 9',
'version 10'
);
}
/**
*
@@ -99,8 +105,11 @@ export function conditionalBind(
* @returns Opaque data that can be passed to unbindEvent_.
*/
export function bind(
node: EventTarget, name: string, thisObject: Object|null,
func: Function): Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function
): Data {
/**
*
* @param e
@@ -154,17 +163,24 @@ export function unbind(bindData: Data): (e: Event) => void {
*/
export function isTargetInput(e: Event): boolean {
if (e.target instanceof HTMLElement) {
if (e.target.isContentEditable ||
e.target.getAttribute('data-is-text-input') === 'true') {
if (
e.target.isContentEditable ||
e.target.getAttribute('data-is-text-input') === 'true'
) {
return true;
}
if (e.target instanceof HTMLInputElement) {
const target = e.target;
return target.type === 'text' || target.type === 'number' ||
target.type === 'email' || target.type === 'password' ||
target.type === 'search' || target.type === 'tel' ||
target.type === 'url';
return (
target.type === 'text' ||
target.type === 'number' ||
target.type === 'email' ||
target.type === 'password' ||
target.type === 'search' ||
target.type === 'tel' ||
target.type === 'url'
);
}
if (e.target instanceof HTMLTextAreaElement) {
@@ -200,7 +216,10 @@ export function isRightButton(e: MouseEvent): boolean {
* @returns Object with .x and .y properties.
*/
export function mouseToSvg(
e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint {
e: MouseEvent,
svg: SVGSVGElement,
matrix: SVGMatrix | null
): SVGPoint {
const svgPoint = svg.createSVGPoint();
svgPoint.x = e.clientX;
svgPoint.y = e.clientY;
@@ -217,17 +236,17 @@ export function mouseToSvg(
* @param e Mouse event.
* @returns Scroll delta object with .x and .y properties.
*/
export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} {
export function getScrollDeltaPixels(e: WheelEvent): {x: number; y: number} {
switch (e.deltaMode) {
case 0x00: // Pixel mode.
case 0x00: // Pixel mode.
default:
return {x: e.deltaX, y: e.deltaY};
case 0x01: // Line mode.
case 0x01: // Line mode.
return {
x: e.deltaX * LINE_MODE_MULTIPLIER,
y: e.deltaY * LINE_MODE_MULTIPLIER,
};
case 0x02: // Page mode.
case 0x02: // Page mode.
return {
x: e.deltaX * PAGE_MODE_MULTIPLIER,
y: e.deltaY * PAGE_MODE_MULTIPLIER,

View File

@@ -27,7 +27,6 @@ import {Svg} from './utils/svg.js';
import * as userAgent from './utils/useragent.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for UI bubble.
*/
@@ -54,10 +53,10 @@ export class Bubble implements IBubble {
static ANCHOR_RADIUS = 8;
/** Mouse up event data. */
private static onMouseUpWrapper: browserEvents.Data|null = null;
private static onMouseUpWrapper: browserEvents.Data | null = null;
/** Mouse move event data. */
private static onMouseMoveWrapper: browserEvents.Data|null = null;
private static onMouseMoveWrapper: browserEvents.Data | null = null;
workspace_: WorkspaceSvg;
content_: SVGElement;
@@ -67,18 +66,18 @@ export class Bubble implements IBubble {
private readonly rendered: boolean;
/** The SVG group containing all parts of the bubble. */
private bubbleGroup: SVGGElement|null = null;
private bubbleGroup: SVGGElement | null = null;
/**
* The SVG path for the arrow from the bubble to the icon on the block.
*/
private bubbleArrow: SVGPathElement|null = null;
private bubbleArrow: SVGPathElement | null = null;
/** The SVG rect for the main body of the bubble. */
private bubbleBack: SVGRectElement|null = null;
private bubbleBack: SVGRectElement | null = null;
/** The SVG group for the resize hash marks on some bubbles. */
private resizeGroup: SVGGElement|null = null;
private resizeGroup: SVGGElement | null = null;
/** Absolute coordinate of anchor point, in workspace coordinates. */
private anchorXY!: Coordinate;
@@ -106,16 +105,16 @@ export class Bubble implements IBubble {
private autoLayout = true;
/** Method to call on resize of bubble. */
private resizeCallback: (() => void)|null = null;
private resizeCallback: (() => void) | null = null;
/** Method to call on move of bubble. */
private moveCallback: (() => void)|null = null;
private moveCallback: (() => void) | null = null;
/** Mouse down on bubbleBack event data. */
private onMouseDownBubbleWrapper: browserEvents.Data|null = null;
private onMouseDownBubbleWrapper: browserEvents.Data | null = null;
/** Mouse down on resizeGroup event data. */
private onMouseDownResizeWrapper: browserEvents.Data|null = null;
private onMouseDownResizeWrapper: browserEvents.Data | null = null;
/**
* Describes whether this bubble has been disposed of (nodes and event
@@ -135,9 +134,13 @@ export class Bubble implements IBubble {
* @param bubbleHeight Height of bubble, or null if not resizable.
*/
constructor(
workspace: WorkspaceSvg, content: SVGElement, shape: SVGElement,
anchorXY: Coordinate, bubbleWidth: number|null,
bubbleHeight: number|null) {
workspace: WorkspaceSvg,
content: SVGElement,
shape: SVGElement,
anchorXY: Coordinate,
bubbleWidth: number | null,
bubbleHeight: number | null
) {
this.rendered = false;
this.workspace_ = workspace;
this.content_ = content;
@@ -151,7 +154,8 @@ export class Bubble implements IBubble {
const canvas = workspace.getBubbleCanvas();
canvas.appendChild(
this.createDom(content, !!(bubbleWidth && bubbleHeight)));
this.createDom(content, !!(bubbleWidth && bubbleHeight))
);
this.setAnchorLocation(anchorXY);
if (!bubbleWidth || !bubbleHeight) {
@@ -192,8 +196,10 @@ export class Bubble implements IBubble {
*/
this.bubbleGroup = dom.createSvgElement(Svg.G, {});
let filter: {filter?: string} = {
'filter': 'url(#' +
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
'filter':
'url(#' +
this.workspace_.getRenderer().getConstants().embossFilterId +
')',
};
if (userAgent.JavaFx) {
// Multiple reports that JavaFX can't handle filters.
@@ -203,53 +209,70 @@ export class Bubble implements IBubble {
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup);
this.bubbleArrow = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
this.bubbleBack = dom.createSvgElement(
Svg.RECT, {
'class': 'blocklyDraggable',
'x': 0,
'y': 0,
'rx': Bubble.BORDER_WIDTH,
'ry': Bubble.BORDER_WIDTH,
},
bubbleEmboss);
Svg.RECT,
{
'class': 'blocklyDraggable',
'x': 0,
'y': 0,
'rx': Bubble.BORDER_WIDTH,
'ry': Bubble.BORDER_WIDTH,
},
bubbleEmboss
);
if (hasResize) {
this.resizeGroup = dom.createSvgElement(
Svg.G, {
'class': this.workspace_.RTL ? 'blocklyResizeSW' :
'blocklyResizeSE',
},
this.bubbleGroup);
Svg.G,
{
'class': this.workspace_.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE',
},
this.bubbleGroup
);
const size = 2 * Bubble.BORDER_WIDTH;
dom.createSvgElement(
Svg.POLYGON, {'points': `0,${size} ${size},${size} ${size},0`},
this.resizeGroup);
Svg.POLYGON,
{'points': `0,${size} ${size},${size} ${size},0`},
this.resizeGroup
);
dom.createSvgElement(
Svg.LINE, {
'class': 'blocklyResizeLine',
'x1': size / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size / 3,
},
this.resizeGroup);
Svg.LINE,
{
'class': 'blocklyResizeLine',
'x1': size / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size / 3,
},
this.resizeGroup
);
dom.createSvgElement(
Svg.LINE, {
'class': 'blocklyResizeLine',
'x1': size * 2 / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size * 2 / 3,
},
this.resizeGroup);
Svg.LINE,
{
'class': 'blocklyResizeLine',
'x1': (size * 2) / 3,
'y1': size - 1,
'x2': size - 1,
'y2': (size * 2) / 3,
},
this.resizeGroup
);
} else {
this.resizeGroup = null;
}
if (!this.workspace_.options.readOnly) {
this.onMouseDownBubbleWrapper = browserEvents.conditionalBind(
this.bubbleBack, 'pointerdown', this, this.bubbleMouseDown);
this.bubbleBack,
'pointerdown',
this,
this.bubbleMouseDown
);
if (this.resizeGroup) {
this.onMouseDownResizeWrapper = browserEvents.conditionalBind(
this.resizeGroup, 'pointerdown', this, this.resizeMouseDown);
this.resizeGroup,
'pointerdown',
this,
this.resizeMouseDown
);
}
}
this.bubbleGroup.appendChild(content);
@@ -329,14 +352,25 @@ export class Bubble implements IBubble {
}
// Left-click (or middle click)
this.workspace_.startDrag(
e,
new Coordinate(
this.workspace_.RTL ? -this.width : this.width, this.height));
e,
new Coordinate(
this.workspace_.RTL ? -this.width : this.width,
this.height
)
);
Bubble.onMouseUpWrapper = browserEvents.conditionalBind(
document, 'pointerup', this, Bubble.bubbleMouseUp);
document,
'pointerup',
this,
Bubble.bubbleMouseUp
);
Bubble.onMouseMoveWrapper = browserEvents.conditionalBind(
document, 'pointermove', this, this.resizeMouseMove);
document,
'pointermove',
this,
this.resizeMouseMove
);
this.workspace_.hideChaff();
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
@@ -406,8 +440,9 @@ export class Bubble implements IBubble {
/** Position the bubble so that it does not fall off-screen. */
private layoutBubble() {
// Get the metrics in workspace units.
const viewMetrics =
this.workspace_.getMetricsManager().getViewMetrics(true);
const viewMetrics = this.workspace_
.getMetricsManager()
.getViewMetrics(true);
const optimalLeft = this.getOptimalRelativeLeft(viewMetrics);
const optimalTop = this.getOptimalRelativeTop(viewMetrics);
@@ -415,30 +450,35 @@ export class Bubble implements IBubble {
const topPosition = {
x: optimalLeft,
y: -this.height -
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
number,
y: (-this.height -
this.workspace_.getRenderer().getConstants()
.MIN_BLOCK_HEIGHT) as number,
};
const startPosition = {x: -this.width - 30, y: optimalTop};
const endPosition = {x: bbox.width, y: optimalTop};
const bottomPosition = {x: optimalLeft, y: bbox.height};
const closerPosition =
bbox.width < bbox.height ? endPosition : bottomPosition;
bbox.width < bbox.height ? endPosition : bottomPosition;
const fartherPosition =
bbox.width < bbox.height ? bottomPosition : endPosition;
bbox.width < bbox.height ? bottomPosition : endPosition;
const topPositionOverlap = this.getOverlap(topPosition, viewMetrics);
const startPositionOverlap = this.getOverlap(startPosition, viewMetrics);
const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics);
const fartherPositionOverlap =
this.getOverlap(fartherPosition, viewMetrics);
const fartherPositionOverlap = this.getOverlap(
fartherPosition,
viewMetrics
);
// Set the position to whichever position shows the most of the bubble,
// with tiebreaks going in the order: top > start > close > far.
const mostOverlap = Math.max(
topPositionOverlap, startPositionOverlap, closerPositionOverlap,
fartherPositionOverlap);
topPositionOverlap,
startPositionOverlap,
closerPositionOverlap,
fartherPositionOverlap
);
if (topPositionOverlap === mostOverlap) {
this.relativeLeft = topPosition.x;
this.relativeTop = topPosition.y;
@@ -472,12 +512,14 @@ export class Bubble implements IBubble {
* @returns The percentage of the bubble that is visible.
*/
private getOverlap(
relativeMin: {x: number, y: number},
viewMetrics: ContainerRegion): number {
relativeMin: {x: number; y: number},
viewMetrics: ContainerRegion
): number {
// The position of the top-left corner of the bubble in workspace units.
const bubbleMin = {
x: this.workspace_.RTL ? this.anchorXY.x - relativeMin.x - this.width :
relativeMin.x + this.anchorXY.x,
x: this.workspace_.RTL
? this.anchorXY.x - relativeMin.x - this.width
: relativeMin.x + this.anchorXY.x,
y: relativeMin.y + this.anchorXY.y,
};
// The position of the bottom-right corner of the bubble in workspace units.
@@ -499,13 +541,16 @@ export class Bubble implements IBubble {
y: viewMetrics.top + viewMetrics.height,
};
const overlapWidth = Math.min(bubbleMax.x, workspaceMax.x) -
Math.max(bubbleMin.x, workspaceMin.x);
const overlapHeight = Math.min(bubbleMax.y, workspaceMax.y) -
Math.max(bubbleMin.y, workspaceMin.y);
const overlapWidth =
Math.min(bubbleMax.x, workspaceMax.x) -
Math.max(bubbleMin.x, workspaceMin.x);
const overlapHeight =
Math.min(bubbleMax.y, workspaceMax.y) -
Math.max(bubbleMin.y, workspaceMin.y);
return Math.max(
0,
Math.min(1, overlapWidth * overlapHeight / (this.width * this.height)));
0,
Math.min(1, (overlapWidth * overlapHeight) / (this.width * this.height))
);
}
/**
@@ -532,9 +577,10 @@ export class Bubble implements IBubble {
const bubbleLeft = bubbleRight - this.width;
const workspaceRight = viewMetrics.left + viewMetrics.width;
const workspaceLeft = viewMetrics.left +
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const workspaceLeft =
viewMetrics.left +
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
if (bubbleLeft < workspaceLeft) {
// Slide the bubble right until it is onscreen.
@@ -548,9 +594,11 @@ export class Bubble implements IBubble {
const bubbleRight = bubbleLeft + this.width;
const workspaceLeft = viewMetrics.left;
const workspaceRight = viewMetrics.left + viewMetrics.width -
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const workspaceRight =
viewMetrics.left +
viewMetrics.width -
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
if (bubbleLeft < workspaceLeft) {
// Slide the bubble right until it is onscreen.
@@ -585,9 +633,10 @@ export class Bubble implements IBubble {
const bubbleTop = this.anchorXY.y + relativeTop;
const bubbleBottom = bubbleTop + this.height;
const workspaceTop = viewMetrics.top;
const workspaceBottom = viewMetrics.top +
viewMetrics.height - // Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const workspaceBottom =
viewMetrics.top +
viewMetrics.height - // Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const anchorY = this.anchorXY.y;
if (bubbleTop < workspaceTop) {
@@ -622,7 +671,9 @@ export class Bubble implements IBubble {
*/
moveTo(x: number, y: number) {
this.bubbleGroup?.setAttribute(
'transform', 'translate(' + x + ',' + y + ')');
'transform',
'translate(' + x + ',' + y + ')'
);
}
/**
@@ -666,14 +717,22 @@ export class Bubble implements IBubble {
// Mirror the resize group.
const resizeSize = 2 * Bubble.BORDER_WIDTH;
this.resizeGroup.setAttribute(
'transform',
'translate(' + resizeSize + ',' + (height - doubleBorderWidth) +
') scale(-1 1)');
'transform',
'translate(' +
resizeSize +
',' +
(height - doubleBorderWidth) +
') scale(-1 1)'
);
} else {
this.resizeGroup.setAttribute(
'transform',
'translate(' + (width - doubleBorderWidth) + ',' +
(height - doubleBorderWidth) + ')');
'transform',
'translate(' +
(width - doubleBorderWidth) +
',' +
(height - doubleBorderWidth) +
')'
);
}
}
if (this.autoLayout) {
@@ -724,7 +783,7 @@ export class Bubble implements IBubble {
// Calculate the thickness of the base of the arrow.
const bubbleSize = this.getBubbleSize();
let thickness =
(bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS;
(bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS;
thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4;
// Back the tip of the arrow off of the anchor.
@@ -743,16 +802,38 @@ export class Bubble implements IBubble {
if (swirlAngle > Math.PI * 2) {
swirlAngle -= Math.PI * 2;
}
const swirlRise = Math.sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND;
const swirlRun = Math.cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND;
const swirlRise = (Math.sin(swirlAngle) * hypotenuse) / Bubble.ARROW_BEND;
const swirlRun = (Math.cos(swirlAngle) * hypotenuse) / Bubble.ARROW_BEND;
steps.push('M' + baseX1 + ',' + baseY1);
steps.push(
'C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + ' ' +
relAnchorX + ',' + relAnchorY + ' ' + relAnchorX + ',' + relAnchorY);
'C' +
(baseX1 + swirlRun) +
',' +
(baseY1 + swirlRise) +
' ' +
relAnchorX +
',' +
relAnchorY +
' ' +
relAnchorX +
',' +
relAnchorY
);
steps.push(
'C' + relAnchorX + ',' + relAnchorY + ' ' + (baseX2 + swirlRun) +
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
'C' +
relAnchorX +
',' +
relAnchorY +
' ' +
(baseX2 + swirlRun) +
',' +
(baseY2 + swirlRise) +
' ' +
baseX2 +
',' +
baseY2
);
}
steps.push('z');
this.bubbleArrow?.setAttribute('d', steps.join(' '));
@@ -813,10 +894,11 @@ export class Bubble implements IBubble {
*/
getRelativeToSurfaceXY(): Coordinate {
return new Coordinate(
this.workspace_.RTL ?
-this.relativeLeft + this.anchorXY.x - this.width :
this.anchorXY.x + this.relativeLeft,
this.anchorXY.y + this.relativeTop);
this.workspace_.RTL
? -this.relativeLeft + this.anchorXY.x - this.width
: this.anchorXY.x + this.relativeLeft,
this.anchorXY.y + this.relativeTop
);
}
/**
@@ -868,7 +950,10 @@ export class Bubble implements IBubble {
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const tspanElement = dom.createSvgElement(
Svg.TSPAN, {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, paragraph);
Svg.TSPAN,
{'dy': '1em', 'x': Bubble.BORDER_WIDTH},
paragraph
);
const textNode = document.createTextNode(lines[i]);
tspanElement.appendChild(textNode);
}
@@ -885,20 +970,29 @@ export class Bubble implements IBubble {
* @internal
*/
static createNonEditableBubble(
paragraphElement: SVGTextElement, block: BlockSvg,
iconXY: Coordinate): Bubble {
paragraphElement: SVGTextElement,
block: BlockSvg,
iconXY: Coordinate
): Bubble {
const bubble = new Bubble(
block.workspace!, paragraphElement, block.pathObject.svgPath, (iconXY),
null, null);
block.workspace!,
paragraphElement,
block.pathObject.svgPath,
iconXY,
null,
null
);
// Expose this bubble's block's ID on its top-level SVG group.
bubble.setSvgId(block.id);
if (block.RTL) {
// Right-align the paragraph.
// This cannot be done until the bubble is rendered on screen.
const maxWidth = paragraphElement.getBBox().width;
for (let i = 0, textElement;
textElement = paragraphElement.childNodes[i] as SVGTSpanElement;
i++) {
for (
let i = 0, textElement;
(textElement = paragraphElement.childNodes[i] as SVGTSpanElement);
i++
) {
textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute('x', String(maxWidth + Bubble.BORDER_WIDTH));
}

View File

@@ -23,7 +23,6 @@ import {Coordinate} from './utils/coordinate.js';
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for a bubble dragger. It moves things on the bubble canvas around the
* workspace when they are being dragged by a mouse or touch. These can be
@@ -31,12 +30,12 @@ import type {WorkspaceSvg} from './workspace_svg.js';
*/
export class BubbleDragger {
/** Which drag target the mouse pointer is over, if any. */
private dragTarget_: IDragTarget|null = null;
private dragTarget_: IDragTarget | null = null;
/** Whether the bubble would be deleted if dropped immediately. */
private wouldDeleteBubble_ = false;
private readonly startXY_: Coordinate;
private dragSurface_: BlockDragSurfaceSvg|null;
private dragSurface_: BlockDragSurfaceSvg | null;
/**
* @param bubble The item on the bubble canvas to drag.
@@ -116,11 +115,13 @@ export class BubbleDragger {
* @param dragTarget The drag target that the bubblee is currently over.
* @returns Whether dropping the bubble immediately would delete the block.
*/
private shouldDelete_(dragTarget: IDragTarget|null): boolean {
private shouldDelete_(dragTarget: IDragTarget | null): boolean {
if (dragTarget) {
const componentManager = this.workspace.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
dragTarget.id,
ComponentManager.Capability.DELETE_AREA
);
if (isDeleteArea) {
return (dragTarget as IDeleteArea).wouldDelete(this.bubble, false);
}
@@ -149,7 +150,7 @@ export class BubbleDragger {
this.dragBubble(e, currentDragDeltaXY);
const preventMove =
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
let newLoc;
if (preventMove) {
newLoc = this.startXY_;
@@ -187,7 +188,8 @@ export class BubbleDragger {
private fireMoveEvent_() {
if (this.bubble instanceof WorkspaceCommentSvg) {
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
this.bubble) as CommentMove;
this.bubble
) as CommentMove;
event.setOldCoordinate(this.startXY_);
event.recordNew();
eventUtils.fire(event);
@@ -207,8 +209,9 @@ export class BubbleDragger {
*/
private pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
const result = new Coordinate(
pixelCoord.x / this.workspace.scale,
pixelCoord.y / this.workspace.scale);
pixelCoord.x / this.workspace.scale,
pixelCoord.y / this.workspace.scale
);
if (this.workspace.isMutator) {
// If we're in a mutator, its scale is always 1, purely because of some
// oddities in our rendering optimizations. The actual scale is the same

View File

@@ -21,7 +21,6 @@ import * as mathUtils from './utils/math.js';
import type {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Bumps the given object that has passed out of bounds.
*
@@ -34,8 +33,10 @@ import type {WorkspaceSvg} from './workspace_svg.js';
* @returns True if object was bumped.
*/
function bumpObjectIntoBounds(
workspace: WorkspaceSvg, bounds: ContainerRegion,
object: IBoundedElement): boolean {
workspace: WorkspaceSvg,
bounds: ContainerRegion,
object: IBoundedElement
): boolean {
// Compute new top/left position for object.
const objectMetrics = object.getBoundingRectangle();
const height = objectMetrics.bottom - objectMetrics.top;
@@ -46,8 +47,11 @@ function bumpObjectIntoBounds(
const bottomClamp = boundsBottom - height;
// If the object is taller than the workspace we want to
// top-align the block
const newYPosition =
mathUtils.clamp(topClamp, objectMetrics.top, bottomClamp);
const newYPosition = mathUtils.clamp(
topClamp,
objectMetrics.top,
bottomClamp
);
const deltaY = newYPosition - objectMetrics.top;
// Note: Even in RTL mode the "anchor" of the object is the
@@ -66,8 +70,11 @@ function bumpObjectIntoBounds(
// the right clamp to match.
rightClamp = Math.max(leftClamp, rightClamp);
}
const newXPosition =
mathUtils.clamp(leftClamp, objectMetrics.left, rightClamp);
const newXPosition = mathUtils.clamp(
leftClamp,
objectMetrics.left,
rightClamp
);
const deltaX = newXPosition - objectMetrics.left;
if (deltaX || deltaY) {
@@ -84,8 +91,9 @@ export const bumpIntoBounds = bumpObjectIntoBounds;
* @param workspace The workspace to handle.
* @returns The event handler.
*/
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
(p1: Abstract) => void {
export function bumpIntoBoundsHandler(
workspace: WorkspaceSvg
): (p1: Abstract) => void {
return (e) => {
const metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
@@ -96,8 +104,10 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event
const object =
extractObjectFromEvent(workspace, e as eventUtils.BumpEvent);
const object = extractObjectFromEvent(
workspace,
e as eventUtils.BumpEvent
);
if (!object) {
return;
}
@@ -106,18 +116,25 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
eventUtils.setGroup(e.group);
const wasBumped = bumpObjectIntoBounds(
workspace, scrollMetricsInWsCoords, (object as IBoundedElement));
workspace,
scrollMetricsInWsCoords,
object as IBoundedElement
);
if (wasBumped && !e.group) {
console.warn(
'Moved object in bounds but there was no' +
' event group. This may break undo.');
'Moved object in bounds but there was no' +
' event group. This may break undo.'
);
}
eventUtils.setGroup(existingGroup);
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
const viewportEvent = (e as ViewportChange);
if (viewportEvent.scale && viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale) {
const viewportEvent = e as ViewportChange;
if (
viewportEvent.scale &&
viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale
) {
bumpTopObjectsIntoBounds(workspace);
}
}
@@ -134,8 +151,9 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
* object.
*/
function extractObjectFromEvent(
workspace: WorkspaceSvg, e: eventUtils.BumpEvent): BlockSvg|null|
WorkspaceCommentSvg {
workspace: WorkspaceSvg,
e: eventUtils.BumpEvent
): BlockSvg | null | WorkspaceCommentSvg {
let object = null;
switch (e.type) {
case eventUtils.BLOCK_CREATE:
@@ -147,10 +165,9 @@ function extractObjectFromEvent(
break;
case eventUtils.COMMENT_CREATE:
case eventUtils.COMMENT_MOVE:
object =
workspace.getCommentById((e as CommentCreate | CommentMove).commentId!
) as WorkspaceCommentSvg |
null;
object = workspace.getCommentById(
(e as CommentCreate | CommentMove).commentId!
) as WorkspaceCommentSvg | null;
break;
}
return object;
@@ -169,7 +186,7 @@ export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) {
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
const topBlocks = workspace.getTopBoundedElements();
for (let i = 0, block; block = topBlocks[i]; i++) {
for (let i = 0, block; (block = topBlocks[i]); i++) {
bumpObjectIntoBounds(workspace, scrollMetricsInWsCoords, block);
}
}

View File

@@ -9,9 +9,8 @@ goog.declareModuleId('Blockly.clipboard');
import type {CopyData, ICopyable} from './interfaces/i_copyable.js';
/** Metadata about the object that is currently on the clipboard. */
let copyData: CopyData|null = null;
let copyData: CopyData | null = null;
/**
* Copy a block or workspace comment onto the local clipboard.
@@ -36,7 +35,7 @@ function copyInternal(toCopy: ICopyable) {
* @returns The pasted thing if the paste was successful, null otherwise.
* @internal
*/
export function paste(): ICopyable|null {
export function paste(): ICopyable | null {
if (!copyData) {
return null;
}
@@ -46,8 +45,10 @@ export function paste(): ICopyable|null {
if (workspace.isFlyout) {
workspace = workspace.targetWorkspace!;
}
if (copyData.typeCounts &&
workspace.isCapacityAvailable(copyData.typeCounts)) {
if (
copyData.typeCounts &&
workspace.isCapacityAvailable(copyData.typeCounts)
) {
return workspace.paste(copyData.saveInfo);
}
return null;
@@ -61,18 +62,18 @@ export function paste(): ICopyable|null {
* duplication failed.
* @internal
*/
export function duplicate(toDuplicate: ICopyable): ICopyable|null {
export function duplicate(toDuplicate: ICopyable): ICopyable | null {
return TEST_ONLY.duplicateInternal(toDuplicate);
}
/**
* Private version of duplicate for stubbing in tests.
*/
function duplicateInternal(toDuplicate: ICopyable): ICopyable|null {
function duplicateInternal(toDuplicate: ICopyable): ICopyable | null {
const oldCopyData = copyData;
copy(toDuplicate);
const pastedThing =
toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null;
toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null;
copyData = oldCopyData;
return pastedThing;
}

View File

@@ -29,7 +29,6 @@ import * as dom from './utils/dom.js';
import type {Size} from './utils/size.js';
import {Svg} from './utils/svg.js';
/**
* Class for a comment.
*/
@@ -40,7 +39,7 @@ export class Comment extends Icon {
* 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.
*/
private cachedText: string|null = '';
private cachedText: string | null = '';
/**
* Array holding info needed to unbind events.
@@ -52,13 +51,13 @@ export class Comment extends Icon {
/**
* The SVG element that contains the text edit area, or null if not created.
*/
private foreignObject: SVGForeignObjectElement|null = null;
private foreignObject: SVGForeignObjectElement | null = null;
/** The editable text area, or null if not created. */
private textarea_: HTMLTextAreaElement|null = null;
private textarea_: HTMLTextAreaElement | null = null;
/** The top-level node of the comment text, or null if not created. */
private paragraphElement_: SVGTextElement|null = null;
private paragraphElement_: SVGTextElement | null = null;
/** @param block The block associated with this comment. */
constructor(block: BlockSvg) {
@@ -81,28 +80,35 @@ export class Comment extends Icon {
protected override drawIcon_(group: Element) {
// Circle.
dom.createSvgElement(
Svg.CIRCLE,
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, group);
Svg.CIRCLE,
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
group
);
// Can't use a real '?' text character since different browsers and
// operating systems render it differently. Body of question mark.
dom.createSvgElement(
Svg.PATH, {
'class': 'blocklyIconSymbol',
'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
'0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
'-1.201,0.998 -1.201,1.528 -1.204,2.19z',
},
group);
Svg.PATH,
{
'class': 'blocklyIconSymbol',
'd':
'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
'0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
'-1.201,0.998 -1.201,1.528 -1.204,2.19z',
},
group
);
// Dot of question mark.
dom.createSvgElement(
Svg.RECT, {
'class': 'blocklyIconSymbol',
'x': '6.8',
'y': '10.78',
'height': '2',
'width': '2',
},
group);
Svg.RECT,
{
'class': 'blocklyIconSymbol',
'x': '6.8',
'y': '10.78',
'height': '2',
'width': '2',
},
group
);
}
/**
@@ -123,16 +129,19 @@ export class Comment extends Icon {
* For non-editable mode see Warning.textToDom_.
*/
this.foreignObject = dom.createSvgElement(
Svg.FOREIGNOBJECT,
{'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH});
this.foreignObject = dom.createSvgElement(Svg.FOREIGNOBJECT, {
'x': Bubble.BORDER_WIDTH,
'y': Bubble.BORDER_WIDTH,
});
const body = document.createElementNS(dom.HTML_NS, 'body');
body.setAttribute('xmlns', dom.HTML_NS);
body.className = 'blocklyMinimalBody';
this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea') as
HTMLTextAreaElement;
this.textarea_ = document.createElementNS(
dom.HTML_NS,
'textarea'
) as HTMLTextAreaElement;
const textarea = this.textarea_;
textarea.className = 'blocklyCommentTextarea';
textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR');
@@ -142,33 +151,62 @@ export class Comment extends Icon {
body.appendChild(textarea);
this.foreignObject!.appendChild(body);
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'focus', this, this.startEdit, true));
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'focus',
this,
this.startEdit,
true
)
);
// Don't zoom with mousewheel.
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'wheel', this, function(e: Event) {
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'wheel',
this,
function (e: Event) {
e.stopPropagation();
}));
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'change', this,
}
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'change',
this,
/**
* @param _e Unused event parameter.
*/
function(this: Comment, _e: Event) {
function (this: Comment, _e: Event) {
if (this.cachedText !== this.model.text) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.getBlock(), 'comment', null, this.cachedText,
this.model.text));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.getBlock(),
'comment',
null,
this.cachedText,
this.model.text
)
);
}
}));
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'input', this,
}
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'input',
this,
/**
* @param _e Unused event parameter.
*/
function(this: Comment, _e: Event) {
function (this: Comment, _e: Event) {
this.model.text = textarea.value;
}));
}
)
);
setTimeout(textarea.focus.bind(textarea), 0);
@@ -225,8 +263,13 @@ export class Comment extends Icon {
if (visible === this.isVisible()) {
return;
}
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
this.getBlock(), visible, 'comment'));
eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
this.getBlock(),
visible,
'comment'
)
);
this.model.pinned = visible;
if (visible) {
this.createBubble();
@@ -248,9 +291,13 @@ export class Comment extends Icon {
private createEditableBubble() {
const block = this.getBlock();
this.bubble_ = new Bubble(
block.workspace, this.createEditor(), block.pathObject.svgPath,
(this.iconXY_ as Coordinate), this.model.size.width,
this.model.size.height);
block.workspace,
this.createEditor(),
block.pathObject.svgPath,
this.iconXY_ as Coordinate,
this.model.size.width,
this.model.size.height
);
// Expose this comment's block's ID on its top-level SVG group.
this.bubble_.setSvgId(block.id);
this.bubble_.registerResizeEvent(this.onBubbleResize.bind(this));
@@ -264,7 +311,10 @@ export class Comment extends Icon {
// TODO (#2917): It would be great if the comment could support line breaks.
this.paragraphElement_ = Bubble.textToDom(this.model.text ?? '');
this.bubble_ = Bubble.createNonEditableBubble(
this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate);
this.paragraphElement_,
this.getBlock(),
this.iconXY_ as Coordinate
);
this.applyColour();
}

View File

@@ -15,18 +15,16 @@ import type {ICopyable} from './interfaces/i_copyable.js';
import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/** Database of all workspaces. */
const WorkspaceDB_ = Object.create(null);
/**
* Find the workspace with the specified ID.
*
* @param id ID of workspace to find.
* @returns The sought after workspace or null if not found.
*/
export function getWorkspaceById(id: string): Workspace|null {
export function getWorkspaceById(id: string): Workspace | null {
return WorkspaceDB_[id] || null;
}
@@ -90,12 +88,12 @@ export function setMainWorkspace(workspace: Workspace) {
/**
* Currently selected copyable object.
*/
let selected: ICopyable|null = null;
let selected: ICopyable | null = null;
/**
* Returns the currently selected copyable object.
*/
export function getSelected(): ICopyable|null {
export function getSelected(): ICopyable | null {
return selected;
}
@@ -107,14 +105,14 @@ export function getSelected(): ICopyable|null {
* @param newSelection The newly selected block.
* @internal
*/
export function setSelected(newSelection: ICopyable|null) {
export function setSelected(newSelection: ICopyable | null) {
selected = newSelection;
}
/**
* Container element in which to render the WidgetDiv, DropDownDiv and Tooltip.
*/
let parentContainer: Element|null;
let parentContainer: Element | null;
/**
* Get the container element in which to render the WidgetDiv, DropDownDiv and
@@ -122,7 +120,7 @@ let parentContainer: Element|null;
*
* @returns The parent container.
*/
export function getParentContainer(): Element|null {
export function getParentContainer(): Element | null {
return parentContainer;
}
@@ -189,7 +187,9 @@ export const draggingConnections: Connection[] = [];
* @returns Map of types to type counts for descendants of the bock.
*/
export function getBlockTypeCounts(
block: Block, opt_stripFollowing?: boolean): {[key: string]: number} {
block: Block,
opt_stripFollowing?: boolean
): {[key: string]: number} {
const typeCountsMap = Object.create(null);
const descendants = block.getDescendants(true);
if (opt_stripFollowing) {
@@ -199,7 +199,7 @@ export function getBlockTypeCounts(
descendants.splice(index, descendants.length - index);
}
}
for (let i = 0, checkBlock; checkBlock = descendants[i]; i++) {
for (let i = 0, checkBlock; (checkBlock = descendants[i]); i++) {
if (typeCountsMap[checkBlock.type]) {
typeCountsMap[checkBlock.type]++;
} else {
@@ -218,7 +218,7 @@ export function getBlockTypeCounts(
* of jsonDef.
*/
function jsonInitFactory(jsonDef: AnyDuringMigration): () => void {
return function(this: Block) {
return function (this: Block) {
this.jsonInit(jsonDef);
};
}
@@ -249,7 +249,8 @@ function defineBlocksWithJsonArrayInternal(jsonArray: AnyDuringMigration[]) {
* definitions created.
*/
export function createBlockDefinitionsFromJsonArray(
jsonArray: AnyDuringMigration[]): {[key: string]: BlockDefinition} {
jsonArray: AnyDuringMigration[]
): {[key: string]: BlockDefinition} {
const blocks: {[key: string]: BlockDefinition} = {};
for (let i = 0; i < jsonArray.length; i++) {
const elem = jsonArray[i];
@@ -260,8 +261,9 @@ export function createBlockDefinitionsFromJsonArray(
const type = elem['type'];
if (!type) {
console.warn(
`Block definition #${i} in JSON array is missing a type attribute. ` +
'Skipping.');
`Block definition #${i} in JSON array is missing a type attribute. ` +
'Skipping.'
);
continue;
}
blocks[type] = {init: jsonInitFactory(elem)};

View File

@@ -19,7 +19,6 @@ import type {IDragTarget} from './interfaces/i_drag_target.js';
import type {IPositionable} from './interfaces/i_positionable.js';
import * as arrayUtils from './utils/array.js';
class Capability<_T> {
static POSITIONABLE = new Capability<IPositionable>('positionable');
static DRAG_TARGET = new Capability<IDragTarget>('drag_target');
@@ -67,8 +66,12 @@ export class ComponentManager {
const id = componentInfo.component.id;
if (!opt_allowOverrides && this.componentData.has(id)) {
throw Error(
'Plugin "' + id + '" with capabilities "' +
this.componentData.get(id)?.capabilities + '" already added.');
'Plugin "' +
id +
'" with capabilities "' +
this.componentData.get(id)?.capabilities +
'" already added.'
);
}
this.componentData.set(id, componentInfo);
const stringCapabilities = [];
@@ -107,15 +110,20 @@ export class ComponentManager {
* @param id The ID of the component to add the capability to.
* @param capability The capability to add.
*/
addCapability<T>(id: string, capability: string|Capability<T>) {
addCapability<T>(id: string, capability: string | Capability<T>) {
if (!this.getComponent(id)) {
throw Error(
'Cannot add capability, "' + capability + '". Plugin "' + id +
'" has not been added to the ComponentManager');
'Cannot add capability, "' +
capability +
'". Plugin "' +
id +
'" has not been added to the ComponentManager'
);
}
if (this.hasCapability(id, capability)) {
console.warn(
'Plugin "' + id + 'already has capability "' + capability + '"');
'Plugin "' + id + 'already has capability "' + capability + '"'
);
return;
}
capability = `${capability}`.toLowerCase();
@@ -129,16 +137,24 @@ export class ComponentManager {
* @param id The ID of the component to remove the capability from.
* @param capability The capability to remove.
*/
removeCapability<T>(id: string, capability: string|Capability<T>) {
removeCapability<T>(id: string, capability: string | Capability<T>) {
if (!this.getComponent(id)) {
throw Error(
'Cannot remove capability, "' + capability + '". Plugin "' + id +
'" has not been added to the ComponentManager');
'Cannot remove capability, "' +
capability +
'". Plugin "' +
id +
'" has not been added to the ComponentManager'
);
}
if (!this.hasCapability(id, capability)) {
console.warn(
'Plugin "' + id + 'doesn\'t have capability "' + capability +
'" to remove');
'Plugin "' +
id +
'doesn\'t have capability "' +
capability +
'" to remove'
);
return;
}
capability = `${capability}`.toLowerCase();
@@ -153,10 +169,12 @@ export class ComponentManager {
* @param capability The capability to check for.
* @returns Whether the component has the capability.
*/
hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
hasCapability<T>(id: string, capability: string | Capability<T>): boolean {
capability = `${capability}`.toLowerCase();
return this.componentData.has(id) &&
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
return (
this.componentData.has(id) &&
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1
);
}
/**
@@ -165,7 +183,7 @@ export class ComponentManager {
* @param id The ID of the component to get.
* @returns The component with the given name or undefined if not found.
*/
getComponent(id: string): IComponent|undefined {
getComponent(id: string): IComponent | undefined {
return this.componentData.get(id)?.component;
}
@@ -177,7 +195,9 @@ export class ComponentManager {
* @returns The components that match the specified capability.
*/
getComponents<T extends IComponent>(
capability: string|Capability<T>, sorted: boolean): T[] {
capability: string | Capability<T>,
sorted: boolean
): T[] {
capability = `${capability}`.toLowerCase();
const componentIds = this.capabilityToComponentIds.get(capability);
if (!componentIds) {
@@ -189,10 +209,10 @@ export class ComponentManager {
componentIds.forEach((id) => {
componentDataList.push(this.componentData.get(id)!);
});
componentDataList.sort(function(a, b) {
componentDataList.sort(function (a, b) {
return a.weight - b.weight;
});
componentDataList.forEach(function(componentDatum) {
componentDataList.forEach(function (componentDatum) {
components.push(componentDatum.component as T);
});
} else {
@@ -208,7 +228,7 @@ export namespace ComponentManager {
/** An object storing component information. */
export interface ComponentDatum {
component: IComponent;
capabilities: Array<string|Capability<IComponent>>;
capabilities: Array<string | Capability<IComponent>>;
weight: number;
}
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.config');
/**
* All the values that we expect developers to be able to change
* before injecting Blockly.

View File

@@ -22,7 +22,6 @@ import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
import * as blocks from './serialization/blocks.js';
import * as Xml from './xml.js';
/**
* Class for a connection between blocks.
*/
@@ -41,7 +40,7 @@ export class Connection implements IASTNodeLocationWithBlock {
protected sourceBlock_: Block;
/** Connection this connection connects to. Null if not connected. */
targetConnection: Connection|null = null;
targetConnection: Connection | null = null;
/**
* Has this connection been disposed of?
@@ -51,10 +50,10 @@ export class Connection implements IASTNodeLocationWithBlock {
disposed = false;
/** List of compatible value types. Null if all types are compatible. */
private check: string[]|null = null;
private check: string[] | null = null;
/** DOM representation of a shadow block, or null if none. */
private shadowDom: Element|null = null;
private shadowDom: Element | null = null;
/**
* Horizontal location of this connection.
@@ -70,7 +69,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
y = 0;
private shadowState: blocks.State|null = null;
private shadowState: blocks.State | null = null;
/**
* @param source The block establishing this connection.
@@ -113,8 +112,9 @@ export class Connection implements IASTNodeLocationWithBlock {
// Connect the new connection to the parent.
let event;
if (eventUtils.isEnabled()) {
event =
new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove;
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
childBlock
) as BlockMove;
event.setReason(['connect']);
}
connectReciprocally(this, childConnection);
@@ -126,11 +126,15 @@ export class Connection implements IASTNodeLocationWithBlock {
// Deal with the orphan if it exists.
if (orphan) {
const orphanConnection = this.type === INPUT ? orphan.outputConnection :
orphan.previousConnection;
const orphanConnection =
this.type === INPUT
? orphan.outputConnection
: orphan.previousConnection;
if (!orphanConnection) return;
const connection = Connection.getConnectionForOrphanedConnection(
childBlock, orphanConnection);
childBlock,
orphanConnection
);
if (connection) {
orphanConnection.connect(connection);
} else {
@@ -176,8 +180,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns True if connection faces down or right.
*/
isSuperior(): boolean {
return this.type === ConnectionType.INPUT_VALUE ||
this.type === ConnectionType.NEXT_STATEMENT;
return (
this.type === ConnectionType.INPUT_VALUE ||
this.type === ConnectionType.NEXT_STATEMENT
);
}
/**
@@ -259,7 +265,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
protected disconnectInternal(setParent = true) {
const {parentConnection, childConnection} =
this.getParentAndChildConnections();
this.getParentAndChildConnections();
if (!parentConnection || !childConnection) {
throw Error('Source connection not connected.');
}
@@ -272,7 +278,8 @@ export class Connection implements IASTNodeLocationWithBlock {
let event;
if (eventUtils.isEnabled()) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
childConnection.getSourceBlock()) as BlockMove;
childConnection.getSourceBlock()
) as BlockMove;
event.setReason(['disconnect']);
}
const otherConnection = this.targetConnection;
@@ -301,8 +308,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The parent connection and child connection, given this connection
* and the connection it is connected to.
*/
protected getParentAndChildConnections():
{parentConnection?: Connection, childConnection?: Connection} {
protected getParentAndChildConnections(): {
parentConnection?: Connection;
childConnection?: Connection;
} {
if (!this.targetConnection) return {};
if (this.isSuperior()) {
return {
@@ -329,7 +338,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @returns The connected block or null if none is connected.
*/
targetBlock(): Block|null {
targetBlock(): Block | null {
if (this.isConnected()) {
return this.targetConnection?.getSourceBlock() ?? null;
}
@@ -341,10 +350,15 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
protected onCheckChanged_() {
// The new value type may not be compatible with the existing connection.
if (this.isConnected() &&
(!this.targetConnection ||
!this.getConnectionChecker().canConnect(
this, this.targetConnection, false))) {
if (
this.isConnected() &&
(!this.targetConnection ||
!this.getConnectionChecker().canConnect(
this,
this.targetConnection,
false
))
) {
const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child!.unplug();
}
@@ -357,7 +371,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* types are compatible.
* @returns The connection being modified (to allow chaining).
*/
setCheck(check: string|string[]|null): Connection {
setCheck(check: string | string[] | null): Connection {
if (check) {
if (!Array.isArray(check)) {
check = [check];
@@ -376,7 +390,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns List of compatible value types.
* Null if all types are compatible.
*/
getCheck(): string[]|null {
getCheck(): string[] | null {
return this.check;
}
@@ -385,7 +399,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadowDom DOM representation of a block or null.
*/
setShadowDom(shadowDom: Element|null) {
setShadowDom(shadowDom: Element | null) {
this.setShadowStateInternal({shadowDom});
}
@@ -398,10 +412,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* just returned.
* @returns Shadow DOM representation of a block or null.
*/
getShadowDom(returnCurrent?: boolean): Element|null {
return returnCurrent && this.targetBlock()!.isShadow() ?
Xml.blockToDom((this.targetBlock() as Block)) as Element :
this.shadowDom;
getShadowDom(returnCurrent?: boolean): Element | null {
return returnCurrent && this.targetBlock()!.isShadow()
? (Xml.blockToDom(this.targetBlock() as Block) as Element)
: this.shadowDom;
}
/**
@@ -409,7 +423,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadowState An state represetation of the block or null.
*/
setShadowState(shadowState: blocks.State|null) {
setShadowState(shadowState: blocks.State | null) {
this.setShadowStateInternal({shadowState});
}
@@ -423,7 +437,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* returned.
* @returns Serialized object representation of the block, or null.
*/
getShadowState(returnCurrent?: boolean): blocks.State|null {
getShadowState(returnCurrent?: boolean): blocks.State | null {
if (returnCurrent && this.targetBlock() && this.targetBlock()!.isShadow()) {
return blocks.save(this.targetBlock() as Block);
}
@@ -454,7 +468,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* exists.
* @internal
*/
getParentInput(): Input|null {
getParentInput(): Input | null {
let parentInput = null;
const inputs = this.sourceBlock_.inputList;
for (let i = 0; i < inputs.length; i++) {
@@ -486,7 +500,7 @@ export class Connection implements IASTNodeLocationWithBlock {
msg = 'Next Connection of ';
} else {
let parentInput = null;
for (let i = 0, input; input = block.inputList[i]; i++) {
for (let i = 0, input; (input = block.inputList[i]); i++) {
if (input.connection === this) {
parentInput = input;
break;
@@ -508,8 +522,10 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @returns The state of both the shadowDom_ and shadowState_ properties.
*/
private stashShadowState():
{shadowDom: Element|null, shadowState: blocks.State|null} {
private stashShadowState(): {
shadowDom: Element | null;
shadowState: blocks.State | null;
} {
const shadowDom = this.getShadowDom(true);
const shadowState = this.getShadowState(true);
// Set to null so it doesn't respawn.
@@ -524,9 +540,12 @@ export class Connection implements IASTNodeLocationWithBlock {
* @param param0 The state to reapply to the shadowDom_ and shadowState_
* properties.
*/
private applyShadowState({shadowDom, shadowState}: {
shadowDom: Element|null,
shadowState: blocks.State|null
private applyShadowState({
shadowDom,
shadowState,
}: {
shadowDom: Element | null;
shadowState: blocks.State | null;
}) {
this.shadowDom = shadowDom;
this.shadowState = shadowState;
@@ -537,9 +556,12 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param param0 The state to set the shadow of this connection to.
*/
private setShadowStateInternal({shadowDom = null, shadowState = null}: {
shadowDom?: Element|null,
shadowState?: blocks.State|null
private setShadowStateInternal({
shadowDom = null,
shadowState = null,
}: {
shadowDom?: Element | null;
shadowState?: blocks.State | null;
} = {}) {
// One or both of these should always be null.
// If neither is null, the shadowState will get priority.
@@ -577,11 +599,11 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The shadow block that was created, or null if both the
* shadowState_ and shadowDom_ are null.
*/
private createShadowBlock(attemptToConnect: boolean): Block|null {
private createShadowBlock(attemptToConnect: boolean): Block | null {
const parentBlock = this.getSourceBlock();
const shadowState = this.getShadowState();
const shadowDom = this.getShadowDom();
if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) {
if (parentBlock.isDeadOrDying() || (!shadowState && !shadowDom)) {
return null;
}
@@ -614,7 +636,8 @@ export class Connection implements IASTNodeLocationWithBlock {
}
} else {
throw new Error(
'Cannot connect a shadow block to a previous/output connection');
'Cannot connect a shadow block to a previous/output connection'
);
}
}
return blockShadow;
@@ -628,7 +651,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadow The shadow to serialize, or null.
*/
private serializeShadow(shadow: Block|null) {
private serializeShadow(shadow: Block | null) {
if (!shadow) {
return;
}
@@ -646,10 +669,14 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The suitable connection point on the chain of blocks, or null.
*/
static getConnectionForOrphanedConnection(
startBlock: Block, orphanConnection: Connection): Connection|null {
startBlock: Block,
orphanConnection: Connection
): Connection | null {
if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) {
return getConnectionForOrphanedOutput(
startBlock, orphanConnection.getSourceBlock());
startBlock,
orphanConnection.getSourceBlock()
);
}
// Otherwise we're dealing with a stack.
const connection = startBlock.lastConnectionInStack(true);
@@ -684,17 +711,19 @@ function connectReciprocally(first: Connection, second: Connection) {
* @param orphanBlock The inferior block.
* @returns The suitable connection point on 'block', or null.
*/
function getSingleConnection(block: Block, orphanBlock: Block): Connection|
null {
function getSingleConnection(
block: Block,
orphanBlock: Block
): Connection | null {
let foundConnection = null;
const output = orphanBlock.outputConnection;
const typeChecker = output?.getConnectionChecker();
for (let i = 0, input; input = block.inputList[i]; i++) {
for (let i = 0, input; (input = block.inputList[i]); i++) {
const connection = input.connection;
if (connection && typeChecker?.canConnect(output, connection, false)) {
if (foundConnection) {
return null; // More than one connection.
return null; // More than one connection.
}
foundConnection = connection;
}
@@ -714,10 +743,12 @@ function getSingleConnection(block: Block, orphanBlock: Block): Connection|
* @returns The suitable connection point on the chain of blocks, or null.
*/
function getConnectionForOrphanedOutput(
startBlock: Block, orphanBlock: Block): Connection|null {
let newBlock: Block|null = startBlock;
startBlock: Block,
orphanBlock: Block
): Connection | null {
let newBlock: Block | null = startBlock;
let connection;
while (connection = getSingleConnection(newBlock, orphanBlock)) {
while ((connection = getSingleConnection(newBlock, orphanBlock))) {
newBlock = connection.targetBlock();
if (!newBlock || newBlock.isShadow()) {
return connection;

View File

@@ -21,7 +21,6 @@ import * as internalConstants from './internal_constants.js';
import * as registry from './registry.js';
import type {RenderedConnection} from './rendered_connection.js';
/**
* Class for connection type checking logic.
*/
@@ -38,10 +37,15 @@ export class ConnectionChecker implements IConnectionChecker {
* @returns Whether the connection is legal.
*/
canConnect(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): boolean {
return this.canConnectWithReason(a, b, isDragging, opt_distance) ===
Connection.CAN_CONNECT;
a: Connection | null,
b: Connection | null,
isDragging: boolean,
opt_distance?: number
): boolean {
return (
this.canConnectWithReason(a, b, isDragging, opt_distance) ===
Connection.CAN_CONNECT
);
}
/**
@@ -57,8 +61,11 @@ export class ConnectionChecker implements IConnectionChecker {
* otherwise.
*/
canConnectWithReason(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): number {
a: Connection | null,
b: Connection | null,
isDragging: boolean,
opt_distance?: number
): number {
const safety = this.doSafetyChecks(a, b);
if (safety !== Connection.CAN_CONNECT) {
return safety;
@@ -71,10 +78,14 @@ export class ConnectionChecker implements IConnectionChecker {
return Connection.REASON_CHECKS_FAILED;
}
if (isDragging &&
!this.doDragChecks(
a as RenderedConnection, b as RenderedConnection,
opt_distance || 0)) {
if (
isDragging &&
!this.doDragChecks(
a as RenderedConnection,
b as RenderedConnection,
opt_distance || 0
)
) {
return Connection.REASON_DRAG_CHECKS_FAILED;
}
@@ -89,8 +100,11 @@ export class ConnectionChecker implements IConnectionChecker {
* @param b The second of the two connections being checked.
* @returns A developer-readable error string.
*/
getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null):
string {
getErrorMessage(
errorCode: number,
a: Connection | null,
b: Connection | null
): string {
switch (errorCode) {
case Connection.REASON_SELF_CONNECTION:
return 'Attempted to connect a block to itself.';
@@ -105,8 +119,12 @@ export class ConnectionChecker implements IConnectionChecker {
const connOne = a!;
const connTwo = b!;
let msg = 'Connection checks failed. ';
msg += connOne + ' expected ' + connOne.getCheck() + ', found ' +
connTwo.getCheck();
msg +=
connOne +
' expected ' +
connOne.getCheck() +
', found ' +
connTwo.getCheck();
return msg;
}
case Connection.REASON_SHADOW_PARENT:
@@ -128,7 +146,7 @@ export class ConnectionChecker implements IConnectionChecker {
* @param b The second of the connections to check.
* @returns An enum with the reason this connection is safe or unsafe.
*/
doSafetyChecks(a: Connection|null, b: Connection|null): number {
doSafetyChecks(a: Connection | null, b: Connection | null): number {
if (!a || !b) {
return Connection.REASON_TARGET_NULL;
}
@@ -150,22 +168,25 @@ export class ConnectionChecker implements IConnectionChecker {
if (superiorBlock === inferiorBlock) {
return Connection.REASON_SELF_CONNECTION;
} else if (
inferiorConnection.type !==
internalConstants.OPPOSITE_TYPE[superiorConnection.type]) {
inferiorConnection.type !==
internalConstants.OPPOSITE_TYPE[superiorConnection.type]
) {
return Connection.REASON_WRONG_TYPE;
} else if (superiorBlock.workspace !== inferiorBlock.workspace) {
return Connection.REASON_DIFFERENT_WORKSPACES;
} else if (superiorBlock.isShadow() && !inferiorBlock.isShadow()) {
return Connection.REASON_SHADOW_PARENT;
} else if (
inferiorConnection.type === ConnectionType.OUTPUT_VALUE &&
inferiorBlock.previousConnection &&
inferiorBlock.previousConnection.isConnected()) {
inferiorConnection.type === ConnectionType.OUTPUT_VALUE &&
inferiorBlock.previousConnection &&
inferiorBlock.previousConnection.isConnected()
) {
return Connection.REASON_PREVIOUS_AND_OUTPUT;
} else if (
inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT &&
inferiorBlock.outputConnection &&
inferiorBlock.outputConnection.isConnected()) {
inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT &&
inferiorBlock.outputConnection &&
inferiorBlock.outputConnection.isConnected()
) {
return Connection.REASON_PREVIOUS_AND_OUTPUT;
}
return Connection.CAN_CONNECT;
@@ -206,8 +227,11 @@ export class ConnectionChecker implements IConnectionChecker {
* @param distance The maximum allowable distance between connections.
* @returns True if the connection is allowed during a drag.
*/
doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number):
boolean {
doDragChecks(
a: RenderedConnection,
b: RenderedConnection,
distance: number
): boolean {
if (a.distanceFrom(b) > distance) {
return false;
}
@@ -223,8 +247,10 @@ export class ConnectionChecker implements IConnectionChecker {
case ConnectionType.OUTPUT_VALUE: {
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug.
if (b.isConnected() && !b.targetBlock()!.isInsertionMarker() ||
a.isConnected()) {
if (
(b.isConnected() && !b.targetBlock()!.isInsertionMarker()) ||
a.isConnected()
) {
return false;
}
break;
@@ -233,8 +259,11 @@ export class ConnectionChecker implements IConnectionChecker {
// Offering to connect the left (male) of a value block to an already
// connected value pair is ok, we'll splice it in.
// However, don't offer to splice into an immovable block.
if (b.isConnected() && !b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()) {
if (
b.isConnected() &&
!b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()
) {
return false;
}
break;
@@ -244,15 +273,22 @@ export class ConnectionChecker implements IConnectionChecker {
// the stack. But covering up a shadow block or stack of shadow blocks
// is fine. Similarly, replacing a terminal statement with another
// terminal statement is allowed.
if (b.isConnected() && !a.getSourceBlock().nextConnection &&
!b.targetBlock()!.isShadow() && b.targetBlock()!.nextConnection) {
if (
b.isConnected() &&
!a.getSourceBlock().nextConnection &&
!b.targetBlock()!.isShadow() &&
b.targetBlock()!.nextConnection
) {
return false;
}
// Don't offer to splice into a stack where the connected block is
// immovable, unless the block is a shadow block.
if (b.targetBlock() && !b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()) {
if (
b.targetBlock() &&
!b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()
) {
return false;
}
break;
@@ -307,4 +343,7 @@ export class ConnectionChecker implements IConnectionChecker {
}
registry.register(
registry.Type.CONNECTION_CHECKER, registry.DEFAULT, ConnectionChecker);
registry.Type.CONNECTION_CHECKER,
registry.DEFAULT,
ConnectionChecker
);

View File

@@ -19,7 +19,6 @@ import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
import type {RenderedConnection} from './rendered_connection.js';
import type {Coordinate} from './utils/coordinate.js';
/**
* Database of connections.
* Connections are stored in order of their vertical component. This way
@@ -58,8 +57,10 @@ export class ConnectionDB {
* @returns The index of the connection, or -1 if the connection was not
* found.
*/
private findIndexOfConnection(conn: RenderedConnection, yPos: number):
number {
private findIndexOfConnection(
conn: RenderedConnection,
yPos: number
): number {
if (!this.connections.length) {
return -1;
}
@@ -81,8 +82,10 @@ export class ConnectionDB {
}
pointer = bestGuess;
while (pointer < this.connections.length &&
this.connections[pointer].y === yPos) {
while (
pointer < this.connections.length &&
this.connections[pointer].y === yPos
) {
if (this.connections[pointer] === conn) {
return pointer;
}
@@ -140,8 +143,10 @@ export class ConnectionDB {
* @param maxRadius The maximum radius to another connection.
* @returns List of connections.
*/
getNeighbours(connection: RenderedConnection, maxRadius: number):
RenderedConnection[] {
getNeighbours(
connection: RenderedConnection,
maxRadius: number
): RenderedConnection[] {
const db = this.connections;
const currentX = connection.x;
const currentY = connection.y;
@@ -218,8 +223,10 @@ export class ConnectionDB {
* connection or null, and 'radius' which is the distance.
*/
searchForClosest(
conn: RenderedConnection, maxRadius: number,
dxy: Coordinate): {connection: RenderedConnection|null, radius: number} {
conn: RenderedConnection,
maxRadius: number,
dxy: Coordinate
): {connection: RenderedConnection | null; radius: number} {
if (!this.connections.length) {
// Don't bother.
return {connection: null, radius: maxRadius};
@@ -253,8 +260,10 @@ export class ConnectionDB {
}
let pointerMax = closestIndex;
while (pointerMax < this.connections.length &&
this.isInYRange(pointerMax, conn.y, maxRadius)) {
while (
pointerMax < this.connections.length &&
this.isInYRange(pointerMax, conn.y, maxRadius)
) {
temp = this.connections[pointerMax];
if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) {
bestConnection = temp;

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.ConnectionType');
/**
* Enum for the type of a connection or input.
*/
@@ -19,5 +18,5 @@ export enum ConnectionType {
// A down-facing block stack. E.g. 'if-do' or 'else'.
NEXT_STATEMENT,
// An up-facing block stack. E.g. 'break out of loop'.
PREVIOUS_STATEMENT
PREVIOUS_STATEMENT,
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.constants');
/**
* The language-neutral ID given to the collapsed input.
*/

View File

@@ -13,7 +13,10 @@ import * as browserEvents from './browser_events.js';
import * as clipboard from './clipboard.js';
import {config} from './config.js';
import * as dom from './utils/dom.js';
import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import * as eventUtils from './events/utils.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
@@ -27,11 +30,10 @@ import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import * as Xml from './xml.js';
/**
* Which block is the context menu attached to?
*/
let currentBlock: Block|null = null;
let currentBlock: Block | null = null;
const dummyOwner = {};
@@ -40,7 +42,7 @@ const dummyOwner = {};
*
* @returns The block the context menu is attached to.
*/
export function getCurrentBlock(): Block|null {
export function getCurrentBlock(): Block | null {
return currentBlock;
}
@@ -49,14 +51,14 @@ export function getCurrentBlock(): Block|null {
*
* @param block The block the context menu is attached to.
*/
export function setCurrentBlock(block: Block|null) {
export function setCurrentBlock(block: Block | null) {
currentBlock = block;
}
/**
* Menu object.
*/
let menu_: Menu|null = null;
let menu_: Menu | null = null;
/**
* Construct the menu based on the list of options and show the menu.
@@ -66,8 +68,10 @@ let menu_: Menu|null = null;
* @param rtl True if RTL, false if LTR.
*/
export function show(
e: Event, options: (ContextMenuOption|LegacyContextMenuOption)[],
rtl: boolean) {
e: Event,
options: (ContextMenuOption | LegacyContextMenuOption)[],
rtl: boolean
) {
WidgetDiv.show(dummyOwner, rtl, dispose);
if (!options.length) {
hide();
@@ -79,10 +83,10 @@ export function show(
position_(menu, e, rtl);
// 1ms delay is required for focusing on context menus because some other
// mouse event is still waiting in the queue and clears focus.
setTimeout(function() {
setTimeout(function () {
menu.focus();
}, 1);
currentBlock = null; // May be set by Blockly.Block.
currentBlock = null; // May be set by Blockly.Block.
}
/**
@@ -93,8 +97,9 @@ export function show(
* @returns The menu that will be shown on right click.
*/
function populate_(
options: (ContextMenuOption|LegacyContextMenuOption)[],
rtl: boolean): Menu {
options: (ContextMenuOption | LegacyContextMenuOption)[],
rtl: boolean
): Menu {
/* Here's what one option object looks like:
{text: 'Make It So',
enabled: true,
@@ -110,7 +115,7 @@ function populate_(
menu.addChild(menuItem);
menuItem.setEnabled(option.enabled);
if (option.enabled) {
const actionHandler = function() {
const actionHandler = function () {
hide();
requestAnimationFrame(() => {
setTimeout(() => {
@@ -143,10 +148,11 @@ function position_(menu: Menu, e: Event, rtl: boolean) {
// This one is just a point, but we'll pretend that it's a rect so we can use
// some helper functions.
const anchorBBox = new Rect(
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientX + viewportBBox.left,
mouseEvent.clientX + viewportBBox.left);
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientX + viewportBBox.left,
mouseEvent.clientX + viewportBBox.left
);
createWidget_(menu);
const menuSize = menu.getSize();
@@ -179,7 +185,11 @@ function createWidget_(menu: Menu) {
dom.addClass(menuDom, 'blocklyContextMenu');
// Prevent system context menu when right-clicking a Blockly context menu.
browserEvents.conditionalBind(
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
menuDom as EventTarget,
'contextmenu',
null,
haltPropagation
);
// Focus only after the initial render to avoid issue #1329.
menu.focus();
}
@@ -256,12 +266,13 @@ export function callbackFactory(block: Block, xml: Element): () => void {
* containing text, enabled, and a callback.
* @internal
*/
export function commentDeleteOption(comment: WorkspaceCommentSvg):
LegacyContextMenuOption {
export function commentDeleteOption(
comment: WorkspaceCommentSvg
): LegacyContextMenuOption {
const deleteOption = {
text: Msg['REMOVE_COMMENT'],
enabled: true,
callback: function() {
callback: function () {
eventUtils.setGroup(true);
comment.dispose();
eventUtils.setGroup(false);
@@ -279,12 +290,13 @@ export function commentDeleteOption(comment: WorkspaceCommentSvg):
* containing text, enabled, and a callback.
* @internal
*/
export function commentDuplicateOption(comment: WorkspaceCommentSvg):
LegacyContextMenuOption {
export function commentDuplicateOption(
comment: WorkspaceCommentSvg
): LegacyContextMenuOption {
const duplicateOption = {
text: Msg['DUPLICATE_COMMENT'],
enabled: true,
callback: function() {
callback: function () {
clipboard.duplicate(comment);
},
};
@@ -303,15 +315,20 @@ export function commentDuplicateOption(comment: WorkspaceCommentSvg):
* @internal
*/
export function workspaceCommentOption(
ws: WorkspaceSvg, e: Event): ContextMenuOption {
ws: WorkspaceSvg,
e: Event
): ContextMenuOption {
/**
* Helper function to create and position a comment correctly based on the
* location of the mouse event.
*/
function addWsComment() {
const comment = new WorkspaceCommentSvg(
ws, Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'],
WorkspaceCommentSvg.DEFAULT_SIZE, WorkspaceCommentSvg.DEFAULT_SIZE);
ws,
Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'],
WorkspaceCommentSvg.DEFAULT_SIZE,
WorkspaceCommentSvg.DEFAULT_SIZE
);
const injectionDiv = ws.getInjectionDiv();
// Bounding rect coordinates are in client coordinates, meaning that they
@@ -322,8 +339,9 @@ export function workspaceCommentOption(
// The client coordinates offset by the injection div's upper left corner.
const mouseEvent = e as MouseEvent;
const clientOffsetPixels = new Coordinate(
mouseEvent.clientX - boundingRect.left,
mouseEvent.clientY - boundingRect.top);
mouseEvent.clientX - boundingRect.left,
mouseEvent.clientY - boundingRect.top
);
// The offset in pixels between the main workspace's origin and the upper
// left corner of the injection div.
@@ -331,8 +349,10 @@ export function workspaceCommentOption(
// The position of the new comment in pixels relative to the origin of the
// main workspace.
const finalOffset =
Coordinate.difference(clientOffsetPixels, mainOffsetPixels);
const finalOffset = Coordinate.difference(
clientOffsetPixels,
mainOffsetPixels
);
// The position of the new comment in main workspace coordinates.
finalOffset.scale(1 / ws.scale);
@@ -350,7 +370,7 @@ export function workspaceCommentOption(
enabled: true,
} as ContextMenuOption;
wsCommentOption.text = Msg['ADD_COMMENT'];
wsCommentOption.callback = function() {
wsCommentOption.callback = function () {
addWsComment();
};
return wsCommentOption;

View File

@@ -9,7 +9,11 @@ goog.declareModuleId('Blockly.ContextMenuItems');
import type {BlockSvg} from './block_svg.js';
import * as clipboard from './clipboard.js';
import {ContextMenuRegistry, RegistryItem, Scope} from './contextmenu_registry.js';
import {
ContextMenuRegistry,
RegistryItem,
Scope,
} from './contextmenu_registry.js';
import * as dialog from './dialog.js';
import * as Events from './events/events.js';
import * as eventUtils from './events/utils.js';
@@ -17,7 +21,6 @@ import {Msg} from './msg.js';
import {StatementInput} from './renderers/zelos/zelos.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Option to undo previous action.
*/
@@ -111,7 +114,7 @@ function toggleOption_(shouldCollapse: boolean, topBlocks: BlockSvg[]) {
}
Events.setGroup(true);
for (let i = 0; i < topBlocks.length; i++) {
let block: BlockSvg|null = topBlocks[i];
let block: BlockSvg | null = topBlocks[i];
while (block) {
timeoutCounter++;
setTimeout(timeoutFn.bind(null, block), ms);
@@ -133,7 +136,7 @@ export function registerCollapse() {
if (scope.workspace!.options.collapse) {
const topBlocks = scope.workspace!.getTopBlocks(false);
for (let i = 0; i < topBlocks.length; i++) {
let block: BlockSvg|null = topBlocks[i];
let block: BlockSvg | null = topBlocks[i];
while (block) {
if (!block.isCollapsed()) {
return 'enabled';
@@ -167,7 +170,7 @@ export function registerExpand() {
if (scope.workspace!.options.collapse) {
const topBlocks = scope.workspace!.getTopBlocks(false);
for (let i = 0; i < topBlocks.length; i++) {
let block: BlockSvg|null = topBlocks[i];
let block: BlockSvg | null = topBlocks[i];
while (block) {
if (block.isCollapsed()) {
return 'enabled';
@@ -280,13 +283,16 @@ export function registerDeleteAll() {
deleteNext_(deletableBlocks);
} else {
dialog.confirm(
Msg['DELETE_ALL_BLOCKS'].replace(
'%1', String(deletableBlocks.length)),
function(ok) {
if (ok) {
deleteNext_(deletableBlocks);
}
});
Msg['DELETE_ALL_BLOCKS'].replace(
'%1',
String(deletableBlocks.length)
),
function (ok) {
if (ok) {
deleteNext_(deletableBlocks);
}
}
);
}
},
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
@@ -350,8 +356,12 @@ export function registerComment() {
},
preconditionFn(scope: Scope) {
const block = scope.block;
if (!block!.isInFlyout && block!.workspace.options.comments &&
!block!.isCollapsed() && block!.isEditable()) {
if (
!block!.isInFlyout &&
block!.workspace.options.comments &&
!block!.isCollapsed() &&
block!.isEditable()
) {
return 'enabled';
}
return 'hidden';
@@ -377,8 +387,9 @@ export function registerComment() {
export function registerInline() {
const inlineOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.getInputsInline() ? Msg['EXTERNAL_INPUTS'] :
Msg['INLINE_INPUTS'];
return scope.block!.getInputsInline()
? Msg['EXTERNAL_INPUTS']
: Msg['INLINE_INPUTS'];
},
preconditionFn(scope: Scope) {
const block = scope.block;
@@ -386,8 +397,10 @@ export function registerInline() {
for (let i = 1; i < block!.inputList.length; i++) {
// Only display this option if there are two value or dummy inputs
// next to each other.
if (!(block!.inputList[i - 1] instanceof StatementInput) &&
!(block!.inputList[i] instanceof StatementInput)) {
if (
!(block!.inputList[i - 1] instanceof StatementInput) &&
!(block!.inputList[i] instanceof StatementInput)
) {
return 'enabled';
}
}
@@ -410,13 +423,17 @@ export function registerInline() {
export function registerCollapseExpandBlock() {
const collapseExpandOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.isCollapsed() ? Msg['EXPAND_BLOCK'] :
Msg['COLLAPSE_BLOCK'];
return scope.block!.isCollapsed()
? Msg['EXPAND_BLOCK']
: Msg['COLLAPSE_BLOCK'];
},
preconditionFn(scope: Scope) {
const block = scope.block;
if (!block!.isInFlyout && block!.isMovable() &&
block!.workspace.options.collapse) {
if (
!block!.isInFlyout &&
block!.isMovable() &&
block!.workspace.options.collapse
) {
return 'enabled';
}
return 'hidden';
@@ -437,13 +454,17 @@ export function registerCollapseExpandBlock() {
export function registerDisable() {
const disableOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.isEnabled() ? Msg['DISABLE_BLOCK'] :
Msg['ENABLE_BLOCK'];
return scope.block!.isEnabled()
? Msg['DISABLE_BLOCK']
: Msg['ENABLE_BLOCK'];
},
preconditionFn(scope: Scope) {
const block = scope.block;
if (!block!.isInFlyout && block!.workspace.options.disable &&
block!.isEditable()) {
if (
!block!.isInFlyout &&
block!.workspace.options.disable &&
block!.isEditable()
) {
if (block!.getInheritedDisabled()) {
return 'disabled';
}
@@ -481,9 +502,9 @@ export function registerDelete() {
// Blocks in the current stack would survive this block's deletion.
descendantCount -= nextBlock.getDescendants(false).length;
}
return descendantCount === 1 ?
Msg['DELETE_BLOCK'] :
Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
return descendantCount === 1
? Msg['DELETE_BLOCK']
: Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
},
preconditionFn(scope: Scope) {
if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
@@ -513,8 +534,10 @@ export function registerHelp() {
},
preconditionFn(scope: Scope) {
const block = scope.block;
const url = typeof block!.helpUrl === 'function' ? block!.helpUrl() :
block!.helpUrl;
const url =
typeof block!.helpUrl === 'function'
? block!.helpUrl()
: block!.helpUrl;
if (url) {
return 'enabled';
}

View File

@@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.ContextMenuRegistry');
import type {BlockSvg} from './block_svg.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for the registry of context menu items. This is intended to be a
* singleton. You should not create a new instance, and only access this class
@@ -66,7 +65,7 @@ export class ContextMenuRegistry {
* @param id The ID of the RegistryItem to get.
* @returns RegistryItem or null if not found
*/
getItem(id: string): RegistryItem|null {
getItem(id: string): RegistryItem | null {
return this.registry_.get(id) ?? null;
}
@@ -81,16 +80,19 @@ export class ContextMenuRegistry {
* block being clicked on)
* @returns the list of ContextMenuOptions
*/
getContextMenuOptions(scopeType: ScopeType, scope: Scope):
ContextMenuOption[] {
getContextMenuOptions(
scopeType: ScopeType,
scope: Scope
): ContextMenuOption[] {
const menuOptions: ContextMenuOption[] = [];
for (const item of this.registry_.values()) {
if (scopeType === item.scopeType) {
const precondition = item.preconditionFn(scope);
if (precondition !== 'hidden') {
const displayText = typeof item.displayText === 'function' ?
item.displayText(scope) :
item.displayText;
const displayText =
typeof item.displayText === 'function'
? item.displayText(scope)
: item.displayText;
const menuOption: ContextMenuOption = {
text: displayText,
enabled: precondition === 'enabled',
@@ -102,7 +104,7 @@ export class ContextMenuRegistry {
}
}
}
menuOptions.sort(function(a, b) {
menuOptions.sort(function (a, b) {
return a.weight - b.weight;
});
return menuOptions;
@@ -135,7 +137,7 @@ export namespace ContextMenuRegistry {
export interface RegistryItem {
callback: (p1: Scope) => void;
scopeType: ScopeType;
displayText: ((p1: Scope) => string)|string;
displayText: ((p1: Scope) => string) | string;
preconditionFn: (p1: Scope) => string;
weight: number;
id: string;
@@ -175,4 +177,4 @@ export type Scope = ContextMenuRegistry.Scope;
export type RegistryItem = ContextMenuRegistry.RegistryItem;
export type ContextMenuOption = ContextMenuRegistry.ContextMenuOption;
export type LegacyContextMenuOption =
ContextMenuRegistry.LegacyContextMenuOption;
ContextMenuRegistry.LegacyContextMenuOption;

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.Css');
/** Has CSS already been injected? */
let injected = false;

View File

@@ -18,7 +18,6 @@ import {DragTarget} from './drag_target.js';
import type {IDeleteArea} from './interfaces/i_delete_area.js';
import type {IDraggable} from './interfaces/i_draggable.js';
/**
* Abstract class for a component that can delete a block or bubble that is
* dropped on top of it.
@@ -59,7 +58,7 @@ export class DeleteArea extends DragTarget implements IDeleteArea {
*/
wouldDelete(element: IDraggable, couldConnect: boolean): boolean {
if (element instanceof BlockSvg) {
const block = (element);
const block = element;
const couldDeleteBlock = !block.getParent() && block.isDeletable();
this.updateWouldDelete_(couldDeleteBlock && !couldConnect);
} else {

View File

@@ -7,22 +7,28 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.dialog');
let alertImplementation = function(message: string, opt_callback?: () => void) {
let alertImplementation = function (
message: string,
opt_callback?: () => void
) {
window.alert(message);
if (opt_callback) {
opt_callback();
}
};
let confirmImplementation = function(
message: string, callback: (result: boolean) => void) {
let confirmImplementation = function (
message: string,
callback: (result: boolean) => void
) {
callback(window.confirm(message));
};
let promptImplementation = function(
message: string, defaultValue: string,
callback: (result: string|null) => void) {
let promptImplementation = function (
message: string,
defaultValue: string,
callback: (result: string | null) => void
) {
callback(window.prompt(message, defaultValue));
};
@@ -65,7 +71,6 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) {
confirmImplementation(message, callback);
}
/**
* Sets the function to be run when Blockly.dialog.confirm() is called.
*
@@ -73,7 +78,8 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) {
* @see Blockly.dialog.confirm
*/
export function setConfirm(
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void) {
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void
) {
confirmImplementation = confirmFunction;
}
@@ -88,8 +94,10 @@ export function setConfirm(
* @param callback The callback for handling user response.
*/
export function prompt(
message: string, defaultValue: string,
callback: (p1: string|null) => void) {
message: string,
defaultValue: string,
callback: (p1: string | null) => void
) {
promptImplementation(message, defaultValue, callback);
}
@@ -100,8 +108,12 @@ export function prompt(
* @see Blockly.dialog.prompt
*/
export function setPrompt(
promptFunction: (p1: string, p2: string, p3: (p1: string|null) => void) =>
void) {
promptFunction: (
p1: string,
p2: string,
p3: (p1: string | null) => void
) => void
) {
promptImplementation = promptFunction;
}

View File

@@ -17,7 +17,6 @@ import type {IDragTarget} from './interfaces/i_drag_target.js';
import type {IDraggable} from './interfaces/i_draggable.js';
import type {Rect} from './utils/rect.js';
/**
* Abstract class for a component with custom behaviour when a block or bubble
* is dragged over or dropped on top of it.
@@ -76,7 +75,7 @@ export class DragTarget implements IDragTarget {
* @returns The component's bounding box. Null if drag target area should be
* ignored.
*/
getClientRect(): Rect|null {
getClientRect(): Rect | null {
return null;
}

View File

@@ -23,7 +23,6 @@ import type {Size} from './utils/size.js';
import * as style from './utils/style.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Arrow size in px. Should match the value in CSS
* (need to position pre-render).
@@ -52,10 +51,10 @@ export const ANIMATION_TIME = 0.25;
* Timer for animation out, to be cleared if we need to immediately hide
* without disrupting new shows.
*/
let animateOutTimer: ReturnType<typeof setTimeout>|null = null;
let animateOutTimer: ReturnType<typeof setTimeout> | null = null;
/** Callback for when the drop-down is hidden. */
let onHide: Function|null = null;
let onHide: Function | null = null;
/** A class name representing the current owner's workspace renderer. */
let renderedClassName = '';
@@ -76,13 +75,13 @@ let arrow: HTMLDivElement;
* Drop-downs will appear within the bounds of this element if possible.
* Set in setBoundsElement.
*/
let boundsElement: Element|null = null;
let boundsElement: Element | null = null;
/** The object currently using the drop-down. */
let owner: Field|null = null;
let owner: Field | null = null;
/** Whether the dropdown was positioned to a field or the source block. */
let positionToField: boolean|null = null;
let positionToField: boolean | null = null;
/**
* Dropdown bounds info object used to encapsulate sizing information about a
@@ -103,9 +102,9 @@ export interface PositionMetrics {
initialY: number;
finalX: number;
finalY: number;
arrowX: number|null;
arrowY: number|null;
arrowAtTop: boolean|null;
arrowX: number | null;
arrowY: number | null;
arrowAtTop: boolean | null;
arrowVisible: boolean;
}
@@ -116,7 +115,7 @@ export interface PositionMetrics {
*/
export function createDom() {
if (div) {
return; // Already created.
return; // Already created.
}
div = document.createElement('div');
div.className = 'blocklyDropDownDiv';
@@ -133,15 +132,15 @@ export function createDom() {
div.style.opacity = '0';
// Transition animation for transform: translate() and opacity.
div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' +
'opacity ' + ANIMATION_TIME + 's';
div.style.transition =
'transform ' + ANIMATION_TIME + 's, ' + 'opacity ' + ANIMATION_TIME + 's';
// Handle focusin/out events to add a visual indicator when
// a child is focused or blurred.
div.addEventListener('focusin', function() {
div.addEventListener('focusin', function () {
dom.addClass(div, 'blocklyFocused');
});
div.addEventListener('focusout', function() {
div.addEventListener('focusout', function () {
dom.removeClass(div, 'blocklyFocused');
});
}
@@ -152,14 +151,14 @@ export function createDom() {
*
* @param boundsElem Element to bind drop-down to.
*/
export function setBoundsElement(boundsElem: Element|null) {
export function setBoundsElement(boundsElem: Element | null) {
boundsElement = boundsElem;
}
/**
* @returns The field that currently owns this, or null.
*/
export function getOwner(): Field|null {
export function getOwner(): Field | null {
return owner;
}
@@ -202,11 +201,17 @@ export function setColour(backgroundColour: string, borderColour: string) {
* @returns True if the menu rendered below block; false if above.
*/
export function showPositionedByBlock<T>(
field: Field<T>, block: BlockSvg, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
field: Field<T>,
block: BlockSvg,
opt_onHide?: Function,
opt_secondaryYOffset?: number
): boolean {
return showPositionedByRect(
getScaledBboxOfBlock(block), field as Field, opt_onHide,
opt_secondaryYOffset);
getScaledBboxOfBlock(block),
field as Field,
opt_onHide,
opt_secondaryYOffset
);
}
/**
@@ -221,12 +226,17 @@ export function showPositionedByBlock<T>(
* @returns True if the menu rendered below block; false if above.
*/
export function showPositionedByField<T>(
field: Field<T>, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
field: Field<T>,
opt_onHide?: Function,
opt_secondaryYOffset?: number
): boolean {
positionToField = true;
return showPositionedByRect(
getScaledBboxOfField(field as Field), field as Field, opt_onHide,
opt_secondaryYOffset);
getScaledBboxOfField(field as Field),
field as Field,
opt_onHide,
opt_secondaryYOffset
);
}
/**
* Get the scaled bounding box of a block.
@@ -267,8 +277,11 @@ function getScaledBboxOfField(field: Field): Rect {
* @returns True if the menu rendered below block; false if above.
*/
function showPositionedByRect(
bBox: Rect, field: Field, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
bBox: Rect,
field: Field,
opt_onHide?: Function,
opt_secondaryYOffset?: number
): boolean {
// If we can fit it, render below the block.
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
const primaryY = bBox.bottom;
@@ -286,8 +299,14 @@ function showPositionedByRect(
}
setBoundsElement(workspace.getParentSvg().parentNode as Element | null);
return show(
field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY,
opt_onHide);
field,
sourceBlock.RTL,
primaryX,
primaryY,
secondaryX,
secondaryY,
opt_onHide
);
}
/**
@@ -310,8 +329,14 @@ function showPositionedByRect(
* @internal
*/
export function show<T>(
newOwner: Field<T>, rtl: boolean, primaryX: number, primaryY: number,
secondaryX: number, secondaryY: number, opt_onHide?: Function): boolean {
newOwner: Field<T>,
rtl: boolean,
primaryX: number,
primaryY: number,
secondaryX: number,
secondaryY: number,
opt_onHide?: Function
): boolean {
owner = newOwner as Field;
onHide = opt_onHide || null;
// Set direction.
@@ -345,7 +370,7 @@ const internal = {
* @returns An object containing size information about the bounding element
* (bounding box and width/height).
*/
getBoundsInfo: function(): BoundsInfo {
getBoundsInfo: function (): BoundsInfo {
const boundPosition = style.getPageOffset(boundsElement as Element);
const boundSize = style.getSize(boundsElement as Element);
@@ -370,9 +395,12 @@ const internal = {
* @returns Various final metrics, including rendered positions for drop-down
* and arrow.
*/
getPositionMetrics: function(
primaryX: number, primaryY: number, secondaryX: number,
secondaryY: number): PositionMetrics {
getPositionMetrics: function (
primaryX: number,
primaryY: number,
secondaryX: number,
secondaryY: number
): PositionMetrics {
const boundsInfo = internal.getBoundsInfo();
const divSize = style.getSize(div as Element);
@@ -383,7 +411,11 @@ const internal = {
// Can we fit in-bounds above the target?
if (secondaryY - divSize.height > boundsInfo.top) {
return getPositionAboveMetrics(
secondaryX, secondaryY, boundsInfo, divSize);
secondaryX,
secondaryY,
boundsInfo,
divSize
);
}
// Can we fit outside the workspace bounds (but inside the window)
// below?
@@ -394,7 +426,11 @@ const internal = {
// above?
if (secondaryY - divSize.height > document.documentElement.clientTop) {
return getPositionAboveMetrics(
secondaryX, secondaryY, boundsInfo, divSize);
secondaryX,
secondaryY,
boundsInfo,
divSize
);
}
// Last resort, render at top of page.
@@ -415,10 +451,17 @@ const internal = {
* and arrow.
*/
function getPositionBelowMetrics(
primaryX: number, primaryY: number, boundsInfo: BoundsInfo,
divSize: Size): PositionMetrics {
const xCoords =
getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width);
primaryX: number,
primaryY: number,
boundsInfo: BoundsInfo,
divSize: Size
): PositionMetrics {
const xCoords = getPositionX(
primaryX,
boundsInfo.left,
boundsInfo.right,
divSize.width
);
const arrowY = -(ARROW_SIZE / 2 + BORDER_SIZE);
const finalY = primaryY + PADDING_Y;
@@ -426,7 +469,7 @@ function getPositionBelowMetrics(
return {
initialX: xCoords.divX,
initialY: primaryY,
finalX: xCoords.divX, // X position remains constant during animation.
finalX: xCoords.divX, // X position remains constant during animation.
finalY,
arrowX: xCoords.arrowX,
arrowY,
@@ -448,19 +491,26 @@ function getPositionBelowMetrics(
* and arrow.
*/
function getPositionAboveMetrics(
secondaryX: number, secondaryY: number, boundsInfo: BoundsInfo,
divSize: Size): PositionMetrics {
secondaryX: number,
secondaryY: number,
boundsInfo: BoundsInfo,
divSize: Size
): PositionMetrics {
const xCoords = getPositionX(
secondaryX, boundsInfo.left, boundsInfo.right, divSize.width);
secondaryX,
boundsInfo.left,
boundsInfo.right,
divSize.width
);
const arrowY = divSize.height - BORDER_SIZE * 2 - ARROW_SIZE / 2;
const finalY = secondaryY - divSize.height - PADDING_Y;
const initialY = secondaryY - divSize.height; // No padding on Y.
const initialY = secondaryY - divSize.height; // No padding on Y.
return {
initialX: xCoords.divX,
initialY,
finalX: xCoords.divX, // X position remains constant during animation.
finalX: xCoords.divX, // X position remains constant during animation.
finalY,
arrowX: xCoords.arrowX,
arrowY,
@@ -481,16 +531,23 @@ function getPositionAboveMetrics(
* and arrow.
*/
function getPositionTopOfPageMetrics(
sourceX: number, boundsInfo: BoundsInfo, divSize: Size): PositionMetrics {
const xCoords =
getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width);
sourceX: number,
boundsInfo: BoundsInfo,
divSize: Size
): PositionMetrics {
const xCoords = getPositionX(
sourceX,
boundsInfo.left,
boundsInfo.right,
divSize.width
);
// No need to provide arrow-specific information because it won't be visible.
return {
initialX: xCoords.divX,
initialY: 0,
finalX: xCoords.divX, // X position remains constant during animation.
finalY: 0, // Y position remains constant during animation.
finalX: xCoords.divX, // X position remains constant during animation.
finalY: 0, // Y position remains constant during animation.
arrowAtTop: null,
arrowX: null,
arrowY: null,
@@ -511,8 +568,11 @@ function getPositionTopOfPageMetrics(
* @internal
*/
export function getPositionX(
sourceX: number, boundsLeft: number, boundsRight: number,
divWidth: number): {divX: number, arrowX: number} {
sourceX: number,
boundsLeft: number,
boundsRight: number,
divWidth: number
): {divX: number; arrowX: number} {
let divX = sourceX;
// Offset the topLeft coord so that the dropdowndiv is centered.
divX -= divWidth / 2;
@@ -527,7 +587,10 @@ export function getPositionX(
const horizPadding = ARROW_HORIZONTAL_PADDING;
// Clamp the arrow position so that it stays attached to the dropdowndiv.
relativeArrowX = math.clamp(
horizPadding, relativeArrowX, divWidth - horizPadding - ARROW_SIZE);
horizPadding,
relativeArrowX,
divWidth - horizPadding - ARROW_SIZE
);
return {arrowX: relativeArrowX, divX};
}
@@ -550,7 +613,9 @@ export function isVisible(): boolean {
* @returns True if hidden.
*/
export function hideIfOwner<T>(
divOwner: Field<T>, opt_withoutAnimation?: boolean): boolean {
divOwner: Field<T>,
opt_withoutAnimation?: boolean
): boolean {
if (owner === divOwner) {
if (opt_withoutAnimation) {
hideWithoutAnimation();
@@ -569,7 +634,7 @@ export function hide() {
div.style.transform = 'translate(0, 0)';
div.style.opacity = '0';
// Finish animation - reset all values to default.
animateOutTimer = setTimeout(function() {
animateOutTimer = setTimeout(function () {
hideWithoutAnimation();
}, ANIMATION_TIME * 1000);
if (onHide) {
@@ -625,20 +690,33 @@ export function hideWithoutAnimation() {
* @returns True if the menu rendered at the primary origin point.
*/
function positionInternal(
primaryX: number, primaryY: number, secondaryX: number,
secondaryY: number): boolean {
const metrics =
internal.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY);
primaryX: number,
primaryY: number,
secondaryX: number,
secondaryY: number
): boolean {
const metrics = internal.getPositionMetrics(
primaryX,
primaryY,
secondaryX,
secondaryY
);
// Update arrow CSS.
if (metrics.arrowVisible) {
arrow.style.display = '';
arrow.style.transform = 'translate(' + metrics.arrowX + 'px,' +
metrics.arrowY + 'px) rotate(45deg)';
arrow.style.transform =
'translate(' +
metrics.arrowX +
'px,' +
metrics.arrowY +
'px) rotate(45deg)';
arrow.setAttribute(
'class',
metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' :
'blocklyDropDownArrow blocklyArrowBottom');
'class',
metrics.arrowAtTop
? 'blocklyDropDownArrow blocklyArrowTop'
: 'blocklyDropDownArrow blocklyArrowBottom'
);
} else {
arrow.style.display = 'none';
}
@@ -679,8 +757,9 @@ export function repositionForWindowResize() {
// it.
if (owner) {
const block = owner.getSourceBlock() as BlockSvg;
const bBox = positionToField ? getScaledBboxOfField(owner) :
getScaledBboxOfBlock(block);
const bBox = positionToField
? getScaledBboxOfField(owner)
: getScaledBboxOfBlock(block);
// If we can fit it, render below the block.
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
const primaryY = bBox.bottom;

View File

@@ -7,7 +7,6 @@
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.Events');
import {Abstract, AbstractEventJson} from './events_abstract.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {BlockChange, BlockChangeJson} from './events_block_change.js';
@@ -25,7 +24,10 @@ import {CommentMove, CommentMoveJson} from './events_comment_move.js';
import {MarkerMove, MarkerMoveJson} from './events_marker_move.js';
import {Selected, SelectedJson} from './events_selected.js';
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js';
import {
ToolboxItemSelect,
ToolboxItemSelectJson,
} from './events_toolbox_item_select.js';
import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
import {Ui} from './events_ui.js';
import {UiBase} from './events_ui_base.js';
@@ -37,7 +39,6 @@ import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
import * as eventUtils from './utils.js';
import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
// Events.
export {Abstract};
export {AbstractEventJson};

View File

@@ -19,7 +19,6 @@ import type {Workspace} from '../workspace.js';
import * as eventUtils from './utils.js';
/**
* Abstract class for an event.
*/
@@ -74,8 +73,11 @@ export abstract class Abstract {
*/
fromJson(json: AbstractEventJson) {
deprecation.warn(
'Blockly.Events.Abstract.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Abstract.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
this.isBlank = false;
this.group = json['group'] || '';
}
@@ -89,8 +91,11 @@ export abstract class Abstract {
* supertypes of parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: AbstractEventJson, workspace: Workspace, event: any):
Abstract {
static fromJson(
json: AbstractEventJson,
workspace: Workspace,
event: any
): Abstract {
event.isBlank = false;
event.group = json['group'] || '';
event.workspaceId = workspace.id;
@@ -130,8 +135,9 @@ export abstract class Abstract {
}
if (!workspace) {
throw Error(
'Workspace is null. Event must have been generated from real' +
' Blockly events.');
'Workspace is null. Event must have been generated from real' +
' Blockly events.'
);
}
return workspace;
}

View File

@@ -16,8 +16,10 @@ import type {Block} from '../block.js';
import * as deprecation from '../utils/deprecation.js';
import type {Workspace} from '../workspace.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
/**
* Abstract class for any event related to blocks.
@@ -51,8 +53,9 @@ export class BlockBase extends AbstractEvent {
const json = super.toJson() as BlockBaseJson;
if (!this.blockId) {
throw new Error(
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['blockId'] = this.blockId;
return json;
@@ -65,8 +68,11 @@ export class BlockBase extends AbstractEvent {
*/
override fromJson(json: BlockBaseJson) {
deprecation.warn(
'Blockly.Events.BlockBase.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.blockId = json['blockId'];
}
@@ -80,10 +86,16 @@ export class BlockBase extends AbstractEvent {
* static methods in superclasses.
* @internal
*/
static fromJson(json: BlockBaseJson, workspace: Workspace, event?: any):
BlockBase {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockBase()) as BlockBase;
static fromJson(
json: BlockBaseJson,
workspace: Workspace,
event?: any
): BlockBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockBase()
) as BlockBase;
newEvent.blockId = json['blockId'];
return newEvent;
}

View File

@@ -23,7 +23,6 @@ import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners when some element of a block has changed (e.g.
* field values, comments, etc).
@@ -53,12 +52,16 @@ export class BlockChange extends BlockBase {
* @param opt_newValue New value of element.
*/
constructor(
opt_block?: Block, opt_element?: string, opt_name?: string|null,
opt_oldValue?: unknown, opt_newValue?: unknown) {
opt_block?: Block,
opt_element?: string,
opt_name?: string | null,
opt_oldValue?: unknown,
opt_newValue?: unknown
) {
super(opt_block);
if (!opt_block) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.element = opt_element;
this.name = opt_name || undefined;
@@ -75,8 +78,9 @@ export class BlockChange extends BlockBase {
const json = super.toJson() as BlockChangeJson;
if (!this.element) {
throw new Error(
'The changed element is undefined. Either pass an ' +
'element to the constructor, or call fromJson');
'The changed element is undefined. Either pass an ' +
'element to the constructor, or call fromJson'
);
}
json['element'] = this.element;
json['name'] = this.name;
@@ -92,8 +96,11 @@ export class BlockChange extends BlockBase {
*/
override fromJson(json: BlockChangeJson) {
deprecation.warn(
'Blockly.Events.BlockChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.element = json['element'];
this.name = json['name'];
@@ -110,11 +117,16 @@ export class BlockChange extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockChangeJson, workspace: Workspace, event?: any):
BlockChange {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockChange()) as
BlockChange;
static fromJson(
json: BlockChangeJson,
workspace: Workspace,
event?: any
): BlockChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockChange()
) as BlockChange;
newEvent.element = json['element'];
newEvent.name = json['name'];
newEvent.oldValue = json['oldValue'];
@@ -140,14 +152,16 @@ export class BlockChange extends BlockBase {
const workspace = this.getEventWorkspace_();
if (!this.blockId) {
throw new Error(
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
const block = workspace.getBlockById(this.blockId);
if (!block) {
throw new Error(
'The associated block is undefined. Either pass a ' +
'block to the constructor, or call fromJson');
'The associated block is undefined. Either pass a ' +
'block to the constructor, or call fromJson'
);
}
// Assume the block is rendered so that then we can check.
const blockSvg = block as BlockSvg;
@@ -162,12 +176,12 @@ export class BlockChange extends BlockBase {
if (field) {
field.setValue(value);
} else {
console.warn('Can\'t set non-existent field: ' + this.name);
console.warn("Can't set non-existent field: " + this.name);
}
break;
}
case 'comment':
block.setCommentText(value as string || null);
block.setCommentText((value as string) || null);
break;
case 'collapsed':
block.setCollapsed(!!value);
@@ -181,13 +195,15 @@ export class BlockChange extends BlockBase {
case 'mutation': {
const oldState = BlockChange.getExtraBlockState_(block as BlockSvg);
if (block.loadExtraState) {
block.loadExtraState(JSON.parse(value as string || '{}'));
block.loadExtraState(JSON.parse((value as string) || '{}'));
} else if (block.domToMutation) {
block.domToMutation(
utilsXml.textToDom(value as string || '<mutation/>'));
utilsXml.textToDom((value as string) || '<mutation/>')
);
}
eventUtils.fire(
new BlockChange(block, 'mutation', null, oldState, value));
new BlockChange(block, 'mutation', null, oldState, value)
);
break;
}
default:

View File

@@ -23,7 +23,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block (or connected stack of blocks) is
* created.
@@ -32,7 +31,7 @@ export class BlockCreate extends BlockBase {
override type = eventUtils.BLOCK_CREATE;
/** The XML representation of the created block(s). */
xml?: Element|DocumentFragment;
xml?: Element | DocumentFragment;
/** The JSON respresentation of the created block(s). */
json?: blocks.State;
@@ -45,7 +44,7 @@ export class BlockCreate extends BlockBase {
super(opt_block);
if (!opt_block) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
if (opt_block.isShadow()) {
@@ -68,18 +67,21 @@ export class BlockCreate extends BlockBase {
const json = super.toJson() as BlockCreateJson;
if (!this.xml) {
throw new Error(
'The block XML is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block XML is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (!this.ids) {
throw new Error(
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (!this.json) {
throw new Error(
'The block JSON is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block JSON is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['xml'] = Xml.domToText(this.xml);
json['ids'] = this.ids;
@@ -97,8 +99,11 @@ export class BlockCreate extends BlockBase {
*/
override fromJson(json: BlockCreateJson) {
deprecation.warn(
'Blockly.Events.BlockCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.xml = utilsXml.textToDom(json['xml']);
this.ids = json['ids'];
@@ -117,11 +122,16 @@ export class BlockCreate extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockCreateJson, workspace: Workspace, event?: any):
BlockCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockCreate()) as
BlockCreate;
static fromJson(
json: BlockCreateJson,
workspace: Workspace,
event?: any
): BlockCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockCreate()
) as BlockCreate;
newEvent.xml = utilsXml.textToDom(json['xml']);
newEvent.ids = json['ids'];
newEvent.json = json['json'] as blocks.State;
@@ -140,13 +150,15 @@ export class BlockCreate extends BlockBase {
const workspace = this.getEventWorkspace_();
if (!this.json) {
throw new Error(
'The block JSON is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block JSON is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (!this.ids) {
throw new Error(
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
blocks.append(this.json, workspace);
@@ -158,7 +170,7 @@ export class BlockCreate extends BlockBase {
block.dispose(false);
} else if (id === this.blockId) {
// Only complain about root-level block.
console.warn('Can\'t uncreate non-existent block: ' + id);
console.warn("Can't uncreate non-existent block: " + id);
}
}
}

View File

@@ -23,14 +23,13 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block (or connected stack of blocks) is
* deleted.
*/
export class BlockDelete extends BlockBase {
/** The XML representation of the deleted block(s). */
oldXml?: Element|DocumentFragment;
oldXml?: Element | DocumentFragment;
/** The JSON respresentation of the deleted block(s). */
oldJson?: blocks.State;
@@ -48,7 +47,7 @@ export class BlockDelete extends BlockBase {
super(opt_block);
if (!opt_block) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
if (opt_block.getParent()) {
@@ -62,8 +61,9 @@ export class BlockDelete extends BlockBase {
this.oldXml = Xml.blockToDomWithXY(opt_block);
this.ids = eventUtils.getDescendantIds(opt_block);
this.wasShadow = opt_block.isShadow();
this.oldJson =
blocks.save(opt_block, {addCoordinates: true}) as blocks.State;
this.oldJson = blocks.save(opt_block, {
addCoordinates: true,
}) as blocks.State;
}
/**
@@ -75,23 +75,27 @@ export class BlockDelete extends BlockBase {
const json = super.toJson() as BlockDeleteJson;
if (!this.oldXml) {
throw new Error(
'The old block XML is undefined. Either pass a block ' +
'to the constructor, or call fromJson');
'The old block XML is undefined. Either pass a block ' +
'to the constructor, or call fromJson'
);
}
if (!this.ids) {
throw new Error(
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (this.wasShadow === undefined) {
throw new Error(
'Whether the block was a shadow is undefined. Either ' +
'pass a block to the constructor, or call fromJson');
'Whether the block was a shadow is undefined. Either ' +
'pass a block to the constructor, or call fromJson'
);
}
if (!this.oldJson) {
throw new Error(
'The old block JSON is undefined. Either pass a block ' +
'to the constructor, or call fromJson');
'The old block JSON is undefined. Either pass a block ' +
'to the constructor, or call fromJson'
);
}
json['oldXml'] = Xml.domToText(this.oldXml);
json['ids'] = this.ids;
@@ -110,13 +114,16 @@ export class BlockDelete extends BlockBase {
*/
override fromJson(json: BlockDeleteJson) {
deprecation.warn(
'Blockly.Events.BlockDelete.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockDelete.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldXml = utilsXml.textToDom(json['oldXml']);
this.ids = json['ids'];
this.wasShadow =
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
this.oldJson = json['oldJson'];
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
@@ -132,15 +139,20 @@ export class BlockDelete extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockDeleteJson, workspace: Workspace, event?: any):
BlockDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockDelete()) as
BlockDelete;
static fromJson(
json: BlockDeleteJson,
workspace: Workspace,
event?: any
): BlockDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockDelete()
) as BlockDelete;
newEvent.oldXml = utilsXml.textToDom(json['oldXml']);
newEvent.ids = json['ids'];
newEvent.wasShadow =
json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow';
json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow';
newEvent.oldJson = json['oldJson'];
if (json['recordUndo'] !== undefined) {
newEvent.recordUndo = json['recordUndo'];
@@ -157,13 +169,15 @@ export class BlockDelete extends BlockBase {
const workspace = this.getEventWorkspace_();
if (!this.ids) {
throw new Error(
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (!this.oldJson) {
throw new Error(
'The old block JSON is undefined. Either pass a block ' +
'to the constructor, or call fromJson');
'The old block JSON is undefined. Either pass a block ' +
'to the constructor, or call fromJson'
);
}
if (forward) {
for (let i = 0; i < this.ids.length; i++) {
@@ -173,7 +187,7 @@ export class BlockDelete extends BlockBase {
block.dispose(false);
} else if (id === this.blockId) {
// Only complain about root-level block.
console.warn('Can\'t delete non-existent block: ' + id);
console.warn("Can't delete non-existent block: " + id);
}
}
} else {

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block is being manually dragged/dropped.
*/
@@ -66,13 +65,15 @@ export class BlockDrag extends UiBase {
const json = super.toJson() as BlockDragJson;
if (this.isStart === undefined) {
throw new Error(
'Whether this event is the start of a drag is undefined. ' +
'Either pass the value to the constructor, or call fromJson');
'Whether this event is the start of a drag is undefined. ' +
'Either pass the value to the constructor, or call fromJson'
);
}
if (this.blockId === undefined) {
throw new Error(
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['isStart'] = this.isStart;
json['blockId'] = this.blockId;
@@ -89,8 +90,11 @@ export class BlockDrag extends UiBase {
*/
override fromJson(json: BlockDragJson) {
deprecation.warn(
'Blockly.Events.BlockDrag.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockDrag.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isStart = json['isStart'];
this.blockId = json['blockId'];
@@ -106,10 +110,16 @@ export class BlockDrag extends UiBase {
* static methods in superclasses..
* @internal
*/
static fromJson(json: BlockDragJson, workspace: Workspace, event?: any):
BlockDrag {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockDrag()) as BlockDrag;
static fromJson(
json: BlockDragJson,
workspace: Workspace,
event?: any
): BlockDrag {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockDrag()
) as BlockDrag;
newEvent.isStart = json['isStart'];
newEvent.blockId = json['blockId'];
newEvent.blocks = json['blocks'];

View File

@@ -22,7 +22,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
interface BlockLocation {
parentId?: string;
inputName?: string;
@@ -109,14 +108,16 @@ export class BlockMove extends BlockBase {
json['oldParentId'] = this.oldParentId;
json['oldInputName'] = this.oldInputName;
if (this.oldCoordinate) {
json['oldCoordinate'] = `${Math.round(this.oldCoordinate.x)}, ` +
`${Math.round(this.oldCoordinate.y)}`;
json['oldCoordinate'] =
`${Math.round(this.oldCoordinate.x)}, ` +
`${Math.round(this.oldCoordinate.y)}`;
}
json['newParentId'] = this.newParentId;
json['newInputName'] = this.newInputName;
if (this.newCoordinate) {
json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` +
`${Math.round(this.newCoordinate.y)}`;
json['newCoordinate'] =
`${Math.round(this.newCoordinate.x)}, ` +
`${Math.round(this.newCoordinate.y)}`;
}
if (this.reason) {
json['reason'] = this.reason;
@@ -134,8 +135,11 @@ export class BlockMove extends BlockBase {
*/
override fromJson(json: BlockMoveJson) {
deprecation.warn(
'Blockly.Events.BlockMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldParentId = json['oldParentId'];
this.oldInputName = json['oldInputName'];
@@ -166,10 +170,16 @@ export class BlockMove extends BlockBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: BlockMoveJson, workspace: Workspace, event?: any):
BlockMove {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockMove()) as BlockMove;
static fromJson(
json: BlockMoveJson,
workspace: Workspace,
event?: any
): BlockMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockMove()
) as BlockMove;
newEvent.oldParentId = json['oldParentId'];
newEvent.oldInputName = json['oldInputName'];
if (json['oldCoordinate']) {
@@ -218,14 +228,15 @@ export class BlockMove extends BlockBase {
const workspace = this.getEventWorkspace_();
if (!this.blockId) {
throw new Error(
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
const block = workspace.getBlockById(this.blockId);
if (!block) {
throw new Error(
'The block associated with the block move event ' +
'could not be found');
'The block associated with the block move event ' + 'could not be found'
);
}
const location = {} as BlockLocation;
const parent = block.getParent();
@@ -247,9 +258,11 @@ export class BlockMove extends BlockBase {
* @returns False if something changed.
*/
override isNull(): boolean {
return this.oldParentId === this.newParentId &&
this.oldInputName === this.newInputName &&
Coordinate.equals(this.oldCoordinate, this.newCoordinate);
return (
this.oldParentId === this.newParentId &&
this.oldInputName === this.newInputName &&
Coordinate.equals(this.oldCoordinate, this.newCoordinate)
);
}
/**
@@ -261,22 +274,23 @@ export class BlockMove extends BlockBase {
const workspace = this.getEventWorkspace_();
if (!this.blockId) {
throw new Error(
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
const block = workspace.getBlockById(this.blockId);
if (!block) {
console.warn('Can\'t move non-existent block: ' + this.blockId);
console.warn("Can't move non-existent block: " + this.blockId);
return;
}
const parentId = forward ? this.newParentId : this.oldParentId;
const inputName = forward ? this.newInputName : this.oldInputName;
const coordinate = forward ? this.newCoordinate : this.oldCoordinate;
let parentBlock: Block|null;
let parentBlock: Block | null;
if (parentId) {
parentBlock = workspace.getBlockById(parentId);
if (!parentBlock) {
console.warn('Can\'t connect to non-existent block: ' + parentId);
console.warn("Can't connect to non-existent block: " + parentId);
return;
}
}
@@ -288,8 +302,10 @@ export class BlockMove extends BlockBase {
block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y, this.reason);
} else {
let blockConnection = block.outputConnection;
if (!blockConnection ||
block.previousConnection && block.previousConnection.isConnected()) {
if (
!blockConnection ||
(block.previousConnection && block.previousConnection.isConnected())
) {
blockConnection = block.previousConnection;
}
let parentConnection;
@@ -305,7 +321,7 @@ export class BlockMove extends BlockBase {
if (parentConnection && blockConnection) {
blockConnection.connect(parentConnection);
} else {
console.warn('Can\'t connect to non-existent input: ' + inputName);
console.warn("Can't connect to non-existent input: " + inputName);
}
}
}

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Class for a bubble open event.
*/
@@ -44,7 +43,10 @@ export class BubbleOpen extends UiBase {
* 'warning'. Undefined for a blank event.
*/
constructor(
opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) {
opt_block?: BlockSvg,
opt_isOpen?: boolean,
opt_bubbleType?: BubbleType
) {
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
super(workspaceId);
if (!opt_block) return;
@@ -63,13 +65,15 @@ export class BubbleOpen extends UiBase {
const json = super.toJson() as BubbleOpenJson;
if (this.isOpen === undefined) {
throw new Error(
'Whether this event is for opening the bubble is undefined. ' +
'Either pass the value to the constructor, or call fromJson');
'Whether this event is for opening the bubble is undefined. ' +
'Either pass the value to the constructor, or call fromJson'
);
}
if (!this.bubbleType) {
throw new Error(
'The type of bubble is undefined. Either pass the ' +
'value to the constructor, or call fromJson');
'The type of bubble is undefined. Either pass the ' +
'value to the constructor, or call fromJson'
);
}
json['isOpen'] = this.isOpen;
json['bubbleType'] = this.bubbleType;
@@ -84,8 +88,11 @@ export class BubbleOpen extends UiBase {
*/
override fromJson(json: BubbleOpenJson) {
deprecation.warn(
'Blockly.Events.BubbleOpen.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BubbleOpen.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isOpen = json['isOpen'];
this.bubbleType = json['bubbleType'];
@@ -101,11 +108,16 @@ export class BubbleOpen extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BubbleOpenJson, workspace: Workspace, event?: any):
BubbleOpen {
const newEvent =
super.fromJson(json, workspace, event ?? new BubbleOpen()) as
BubbleOpen;
static fromJson(
json: BubbleOpenJson,
workspace: Workspace,
event?: any
): BubbleOpen {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BubbleOpen()
) as BubbleOpen;
newEvent.isOpen = json['isOpen'];
newEvent.bubbleType = json['bubbleType'];
newEvent.blockId = json['blockId'];

View File

@@ -21,7 +21,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners that ome blockly element was clicked.
*/
@@ -46,8 +45,10 @@ export class Click extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_block?: Block|null, opt_workspaceId?: string|null,
opt_targetType?: ClickTarget) {
opt_block?: Block | null,
opt_workspaceId?: string | null,
opt_targetType?: ClickTarget
) {
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
if (workspaceId === null) {
workspaceId = undefined;
@@ -67,8 +68,9 @@ export class Click extends UiBase {
const json = super.toJson() as ClickJson;
if (!this.targetType) {
throw new Error(
'The click target type is undefined. Either pass a block to ' +
'the constructor, or call fromJson');
'The click target type is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['targetType'] = this.targetType;
json['blockId'] = this.blockId;
@@ -82,8 +84,11 @@ export class Click extends UiBase {
*/
override fromJson(json: ClickJson) {
deprecation.warn(
'Blockly.Events.Click.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Click.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.targetType = json['targetType'];
this.blockId = json['blockId'];
@@ -99,8 +104,11 @@ export class Click extends UiBase {
* @internal
*/
static fromJson(json: ClickJson, workspace: Workspace, event?: any): Click {
const newEvent =
super.fromJson(json, workspace, event ?? new Click()) as Click;
const newEvent = super.fromJson(
json,
workspace,
event ?? new Click()
) as Click;
newEvent.targetType = json['targetType'];
newEvent.blockId = json['blockId'];
return newEvent;

View File

@@ -17,13 +17,15 @@ import * as utilsXml from '../utils/xml.js';
import type {WorkspaceComment} from '../workspace_comment.js';
import * as Xml from '../xml.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import type {CommentCreate} from './events_comment_create.js';
import type {CommentDelete} from './events_comment_delete.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Abstract class for a comment event.
*/
@@ -59,8 +61,9 @@ export class CommentBase extends AbstractEvent {
const json = super.toJson() as CommentBaseJson;
if (!this.commentId) {
throw new Error(
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['commentId'] = this.commentId;
return json;
@@ -73,8 +76,11 @@ export class CommentBase extends AbstractEvent {
*/
override fromJson(json: CommentBaseJson) {
deprecation.warn(
'Blockly.Events.CommentBase.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.commentId = json['commentId'];
}
@@ -88,11 +94,16 @@ export class CommentBase extends AbstractEvent {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentBaseJson, workspace: Workspace, event?: any):
CommentBase {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentBase()) as
CommentBase;
static fromJson(
json: CommentBaseJson,
workspace: Workspace,
event?: any
): CommentBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentBase()
) as CommentBase;
newEvent.commentId = json['commentId'];
return newEvent;
}
@@ -104,7 +115,9 @@ export class CommentBase extends AbstractEvent {
* @param create if True then Create, if False then Delete
*/
static CommentCreateDeleteHelper(
event: CommentCreate|CommentDelete, create: boolean) {
event: CommentCreate | CommentDelete,
create: boolean
) {
const workspace = event.getEventWorkspace_();
if (create) {
const xmlElement = utilsXml.createElement('xml');
@@ -116,16 +129,16 @@ export class CommentBase extends AbstractEvent {
} else {
if (!event.commentId) {
throw new Error(
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
const comment = workspace.getCommentById(event.commentId);
if (comment) {
comment.dispose();
} else {
// Only complain about root-level block.
console.warn(
'Can\'t uncreate non-existent comment: ' + event.commentId);
console.warn("Can't uncreate non-existent comment: " + event.commentId);
}
}
}

View File

@@ -20,7 +20,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the contents of a workspace comment has changed.
*/
@@ -41,18 +40,20 @@ export class CommentChange extends CommentBase {
* @param opt_newContents New contents of the comment.
*/
constructor(
opt_comment?: WorkspaceComment, opt_oldContents?: string,
opt_newContents?: string) {
opt_comment?: WorkspaceComment,
opt_oldContents?: string,
opt_newContents?: string
) {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.oldContents_ =
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
this.newContents_ =
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
}
/**
@@ -64,13 +65,15 @@ export class CommentChange extends CommentBase {
const json = super.toJson() as CommentChangeJson;
if (!this.oldContents_) {
throw new Error(
'The old contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The old contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (!this.newContents_) {
throw new Error(
'The new contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['oldContents'] = this.oldContents_;
json['newContents'] = this.newContents_;
@@ -84,8 +87,11 @@ export class CommentChange extends CommentBase {
*/
override fromJson(json: CommentChangeJson) {
deprecation.warn(
'Blockly.Events.CommentChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldContents_ = json['oldContents'];
this.newContents_ = json['newContents'];
@@ -100,11 +106,16 @@ export class CommentChange extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentChangeJson, workspace: Workspace, event?: any):
CommentChange {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentChange()) as
CommentChange;
static fromJson(
json: CommentChangeJson,
workspace: Workspace,
event?: any
): CommentChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentChange()
) as CommentChange;
newEvent.oldContents_ = json['oldContents'];
newEvent.newContents_ = json['newContents'];
return newEvent;
@@ -128,24 +139,27 @@ export class CommentChange extends CommentBase {
const workspace = this.getEventWorkspace_();
if (!this.commentId) {
throw new Error(
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
const comment = workspace.getCommentById(this.commentId);
if (!comment) {
console.warn('Can\'t change non-existent comment: ' + this.commentId);
console.warn("Can't change non-existent comment: " + this.commentId);
return;
}
const contents = forward ? this.newContents_ : this.oldContents_;
if (!contents) {
if (forward) {
throw new Error(
'The new contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
throw new Error(
'The old contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The old contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
comment.setContent(contents);
}
@@ -157,4 +171,7 @@ export interface CommentChangeJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
registry.Type.EVENT,
eventUtils.COMMENT_CHANGE,
CommentChange
);

View File

@@ -22,7 +22,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment was created.
*/
@@ -30,7 +29,7 @@ export class CommentCreate extends CommentBase {
override type = eventUtils.COMMENT_CREATE;
/** The XML representation of the created workspace comment. */
xml?: Element|DocumentFragment;
xml?: Element | DocumentFragment;
/**
* @param opt_comment The created comment.
@@ -56,8 +55,9 @@ export class CommentCreate extends CommentBase {
const json = super.toJson() as CommentCreateJson;
if (!this.xml) {
throw new Error(
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['xml'] = Xml.domToText(this.xml);
return json;
@@ -70,8 +70,11 @@ export class CommentCreate extends CommentBase {
*/
override fromJson(json: CommentCreateJson) {
deprecation.warn(
'Blockly.Events.CommentCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.xml = utilsXml.textToDom(json['xml']);
}
@@ -85,11 +88,16 @@ export class CommentCreate extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentCreateJson, workspace: Workspace, event?: any):
CommentCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentCreate()) as
CommentCreate;
static fromJson(
json: CommentCreateJson,
workspace: Workspace,
event?: any
): CommentCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentCreate()
) as CommentCreate;
newEvent.xml = utilsXml.textToDom(json['xml']);
return newEvent;
}
@@ -109,4 +117,7 @@ export interface CommentCreateJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
registry.Type.EVENT,
eventUtils.COMMENT_CREATE,
CommentCreate
);

View File

@@ -21,7 +21,6 @@ import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment has been deleted.
*/
@@ -39,7 +38,7 @@ export class CommentDelete extends CommentBase {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.xml = opt_comment.toXmlWithXY();
@@ -63,8 +62,9 @@ export class CommentDelete extends CommentBase {
const json = super.toJson() as CommentDeleteJson;
if (!this.xml) {
throw new Error(
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['xml'] = Xml.domToText(this.xml);
return json;
@@ -79,11 +79,16 @@ export class CommentDelete extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentDeleteJson, workspace: Workspace, event?: any):
CommentDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentDelete()) as
CommentDelete;
static fromJson(
json: CommentDeleteJson,
workspace: Workspace,
event?: any
): CommentDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentDelete()
) as CommentDelete;
newEvent.xml = utilsXml.textToDom(json['xml']);
return newEvent;
}
@@ -94,4 +99,7 @@ export interface CommentDeleteJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_DELETE, CommentDelete);
registry.Type.EVENT,
eventUtils.COMMENT_DELETE,
CommentDelete
);

View File

@@ -21,7 +21,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment has moved.
*/
@@ -46,7 +45,7 @@ export class CommentMove extends CommentBase {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.comment_ = opt_comment;
@@ -60,13 +59,15 @@ export class CommentMove extends CommentBase {
recordNew() {
if (this.newCoordinate_) {
throw Error(
'Tried to record the new position of a comment on the ' +
'same event twice.');
'Tried to record the new position of a comment on the ' +
'same event twice.'
);
}
if (!this.comment_) {
throw new Error(
'The comment is undefined. Pass a comment to ' +
'the constructor if you want to use the record functionality');
'The comment is undefined. Pass a comment to ' +
'the constructor if you want to use the record functionality'
);
}
this.newCoordinate_ = this.comment_.getRelativeToSurfaceXY();
}
@@ -91,18 +92,23 @@ export class CommentMove extends CommentBase {
const json = super.toJson() as CommentMoveJson;
if (!this.oldCoordinate_) {
throw new Error(
'The old comment position is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The old comment position is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
if (!this.newCoordinate_) {
throw new Error(
'The new comment position is undefined. Either call recordNew, or ' +
'call fromJson');
'The new comment position is undefined. Either call recordNew, or ' +
'call fromJson'
);
}
json['oldCoordinate'] = `${Math.round(this.oldCoordinate_.x)}, ` +
`${Math.round(this.oldCoordinate_.y)}`;
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
Math.round(this.newCoordinate_.y);
json['oldCoordinate'] =
`${Math.round(this.oldCoordinate_.x)}, ` +
`${Math.round(this.oldCoordinate_.y)}`;
json['newCoordinate'] =
Math.round(this.newCoordinate_.x) +
',' +
Math.round(this.newCoordinate_.y);
return json;
}
@@ -113,8 +119,11 @@ export class CommentMove extends CommentBase {
*/
override fromJson(json: CommentMoveJson) {
deprecation.warn(
'Blockly.Events.CommentMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
let xy = json['oldCoordinate'].split(',');
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
@@ -131,11 +140,16 @@ export class CommentMove extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentMoveJson, workspace: Workspace, event?: any):
CommentMove {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentMove()) as
CommentMove;
static fromJson(
json: CommentMoveJson,
workspace: Workspace,
event?: any
): CommentMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentMove()
) as CommentMove;
let xy = json['oldCoordinate'].split(',');
newEvent.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
xy = json['newCoordinate'].split(',');
@@ -161,21 +175,23 @@ export class CommentMove extends CommentBase {
const workspace = this.getEventWorkspace_();
if (!this.commentId) {
throw new Error(
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
const comment = workspace.getCommentById(this.commentId);
if (!comment) {
console.warn('Can\'t move non-existent comment: ' + this.commentId);
console.warn("Can't move non-existent comment: " + this.commentId);
return;
}
const target = forward ? this.newCoordinate_ : this.oldCoordinate_;
if (!target) {
throw new Error(
'Either oldCoordinate_ or newCoordinate_ is undefined. ' +
'Either oldCoordinate_ or newCoordinate_ is undefined. ' +
'Either pass a comment to the constructor and call recordNew, ' +
'or call fromJson');
'or call fromJson'
);
}
// TODO: Check if the comment is being dragged, and give up if so.
const current = comment.getRelativeToSurfaceXY();

View File

@@ -22,7 +22,6 @@ import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners that a marker (used for keyboard navigation) has
* moved.
@@ -57,8 +56,11 @@ export class MarkerMove extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_block?: Block|null, isCursor?: boolean, opt_oldNode?: ASTNode|null,
opt_newNode?: ASTNode) {
opt_block?: Block | null,
isCursor?: boolean,
opt_oldNode?: ASTNode | null,
opt_newNode?: ASTNode
) {
let workspaceId = opt_block ? opt_block.workspace.id : undefined;
if (opt_newNode && opt_newNode.getType() === ASTNode.types.WORKSPACE) {
workspaceId = (opt_newNode.getLocation() as Workspace).id;
@@ -80,13 +82,15 @@ export class MarkerMove extends UiBase {
const json = super.toJson() as MarkerMoveJson;
if (this.isCursor === undefined) {
throw new Error(
'Whether this is a cursor event or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson');
'Whether this is a cursor event or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson'
);
}
if (!this.newNode) {
throw new Error(
'The new node is undefined. Either pass a node to ' +
'the constructor, or call fromJson');
'The new node is undefined. Either pass a node to ' +
'the constructor, or call fromJson'
);
}
json['isCursor'] = this.isCursor;
json['blockId'] = this.blockId;
@@ -102,8 +106,11 @@ export class MarkerMove extends UiBase {
*/
override fromJson(json: MarkerMoveJson) {
deprecation.warn(
'Blockly.Events.MarkerMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.MarkerMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isCursor = json['isCursor'];
this.blockId = json['blockId'];
@@ -120,11 +127,16 @@ export class MarkerMove extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: MarkerMoveJson, workspace: Workspace, event?: any):
MarkerMove {
const newEvent =
super.fromJson(json, workspace, event ?? new MarkerMove()) as
MarkerMove;
static fromJson(
json: MarkerMoveJson,
workspace: Workspace,
event?: any
): MarkerMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new MarkerMove()
) as MarkerMove;
newEvent.isCursor = json['isCursor'];
newEvent.blockId = json['blockId'];
newEvent.oldNode = json['oldNode'];

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Class for a selected event.
* Notifies listeners that a new element has been selected.
@@ -46,8 +45,10 @@ export class Selected extends UiBase {
* Null if no element previously selected. Undefined for a blank event.
*/
constructor(
opt_oldElementId?: string|null, opt_newElementId?: string|null,
opt_workspaceId?: string) {
opt_oldElementId?: string | null,
opt_newElementId?: string | null,
opt_workspaceId?: string
) {
super(opt_workspaceId);
this.oldElementId = opt_oldElementId ?? undefined;
@@ -73,8 +74,11 @@ export class Selected extends UiBase {
*/
override fromJson(json: SelectedJson) {
deprecation.warn(
'Blockly.Events.Selected.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Selected.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldElementId = json['oldElementId'];
this.newElementId = json['newElementId'];
@@ -89,10 +93,16 @@ export class Selected extends UiBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: SelectedJson, workspace: Workspace, event?: any):
Selected {
const newEvent =
super.fromJson(json, workspace, event ?? new Selected()) as Selected;
static fromJson(
json: SelectedJson,
workspace: Workspace,
event?: any
): Selected {
const newEvent = super.fromJson(
json,
workspace,
event ?? new Selected()
) as Selected;
newEvent.oldElementId = json['oldElementId'];
newEvent.newElementId = json['newElementId'];
return newEvent;

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the workspace theme has changed.
*/
@@ -48,8 +47,9 @@ export class ThemeChange extends UiBase {
const json = super.toJson() as ThemeChangeJson;
if (!this.themeName) {
throw new Error(
'The theme name is undefined. Either pass a theme name to ' +
'the constructor, or call fromJson');
'The theme name is undefined. Either pass a theme name to ' +
'the constructor, or call fromJson'
);
}
json['themeName'] = this.themeName;
return json;
@@ -62,8 +62,11 @@ export class ThemeChange extends UiBase {
*/
override fromJson(json: ThemeChangeJson) {
deprecation.warn(
'Blockly.Events.ThemeChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.ThemeChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.themeName = json['themeName'];
}
@@ -77,11 +80,16 @@ export class ThemeChange extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: ThemeChangeJson, workspace: Workspace, event?: any):
ThemeChange {
const newEvent =
super.fromJson(json, workspace, event ?? new ThemeChange()) as
ThemeChange;
static fromJson(
json: ThemeChangeJson,
workspace: Workspace,
event?: any
): ThemeChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ThemeChange()
) as ThemeChange;
newEvent.themeName = json['themeName'];
return newEvent;
}

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a toolbox item has been selected.
*/
@@ -41,8 +40,10 @@ export class ToolboxItemSelect extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_oldItem?: string|null, opt_newItem?: string|null,
opt_workspaceId?: string) {
opt_oldItem?: string | null,
opt_newItem?: string | null,
opt_workspaceId?: string
) {
super(opt_workspaceId);
this.oldItem = opt_oldItem ?? undefined;
this.newItem = opt_newItem ?? undefined;
@@ -67,8 +68,11 @@ export class ToolboxItemSelect extends UiBase {
*/
override fromJson(json: ToolboxItemSelectJson) {
deprecation.warn(
'Blockly.Events.ToolboxItemSelect.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.ToolboxItemSelect.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldItem = json['oldItem'];
this.newItem = json['newItem'];
@@ -84,11 +88,15 @@ export class ToolboxItemSelect extends UiBase {
* @internal
*/
static fromJson(
json: ToolboxItemSelectJson, workspace: Workspace,
event?: any): ToolboxItemSelect {
const newEvent =
super.fromJson(json, workspace, event ?? new ToolboxItemSelect()) as
ToolboxItemSelect;
json: ToolboxItemSelectJson,
workspace: Workspace,
event?: any
): ToolboxItemSelect {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ToolboxItemSelect()
) as ToolboxItemSelect;
newEvent.oldItem = json['oldItem'];
newEvent.newItem = json['newItem'];
return newEvent;
@@ -101,4 +109,7 @@ export interface ToolboxItemSelectJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect);
registry.Type.EVENT,
eventUtils.TOOLBOX_ITEM_SELECT,
ToolboxItemSelect
);

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners when the trashcan is opening or closing.
*/
@@ -52,8 +51,9 @@ export class TrashcanOpen extends UiBase {
const json = super.toJson() as TrashcanOpenJson;
if (this.isOpen === undefined) {
throw new Error(
'Whether this is already open or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson');
'Whether this is already open or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson'
);
}
json['isOpen'] = this.isOpen;
return json;
@@ -66,8 +66,11 @@ export class TrashcanOpen extends UiBase {
*/
override fromJson(json: TrashcanOpenJson) {
deprecation.warn(
'Blockly.Events.TrashcanOpen.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.TrashcanOpen.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isOpen = json['isOpen'];
}
@@ -81,11 +84,16 @@ export class TrashcanOpen extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: TrashcanOpenJson, workspace: Workspace, event?: any):
TrashcanOpen {
const newEvent =
super.fromJson(json, workspace, event ?? new TrashcanOpen()) as
TrashcanOpen;
static fromJson(
json: TrashcanOpenJson,
workspace: Workspace,
event?: any
): TrashcanOpen {
const newEvent = super.fromJson(
json,
workspace,
event ?? new TrashcanOpen()
) as TrashcanOpen;
newEvent.isOpen = json['isOpen'];
return newEvent;
}

View File

@@ -18,7 +18,6 @@ import * as registry from '../registry.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
/**
* Class for a UI event.
*
@@ -39,8 +38,11 @@ export class Ui extends UiBase {
* @param opt_newValue New value of element.
*/
constructor(
opt_block?: Block|null, opt_element?: string,
opt_oldValue?: AnyDuringMigration, opt_newValue?: AnyDuringMigration) {
opt_block?: Block | null,
opt_element?: string,
opt_oldValue?: AnyDuringMigration,
opt_newValue?: AnyDuringMigration
) {
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
super(workspaceId);

View File

@@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.Events.UiBase');
import {Abstract as AbstractEvent} from './events_abstract.js';
/**
* Base class for a UI event.
* UI events are events that don't need to be sent over the wire for multi-user

View File

@@ -15,10 +15,12 @@ goog.declareModuleId('Blockly.Events.VarBase');
import * as deprecation from '../utils/deprecation.js';
import type {VariableModel} from '../variable_model.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import type {Workspace} from '../workspace.js';
/**
* Abstract class for a variable event.
*/
@@ -49,8 +51,9 @@ export class VarBase extends AbstractEvent {
const json = super.toJson() as VarBaseJson;
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varId'] = this.varId;
return json;
@@ -63,8 +66,11 @@ export class VarBase extends AbstractEvent {
*/
override fromJson(json: VarBaseJson) {
deprecation.warn(
'Blockly.Events.VarBase.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.VarBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varId = json['varId'];
}
@@ -78,10 +84,16 @@ export class VarBase extends AbstractEvent {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarBaseJson, workspace: Workspace, event?: any):
VarBase {
const newEvent =
super.fromJson(json, workspace, event ?? new VarBase()) as VarBase;
static fromJson(
json: VarBaseJson,
workspace: Workspace,
event?: any
): VarBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarBase()
) as VarBase;
newEvent.varId = json['varId'];
return newEvent;
}

View File

@@ -20,7 +20,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model has been created.
*/
@@ -40,7 +39,7 @@ export class VarCreate extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.varType = opt_variable.type;
this.varName = opt_variable.name;
@@ -55,13 +54,15 @@ export class VarCreate extends VarBase {
const json = super.toJson() as VarCreateJson;
if (this.varType === undefined) {
throw new Error(
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varType'] = this.varType;
json['varName'] = this.varName;
@@ -75,8 +76,11 @@ export class VarCreate extends VarBase {
*/
override fromJson(json: VarCreateJson) {
deprecation.warn(
'Blockly.Events.VarCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varType = json['varType'];
this.varName = json['varName'];
@@ -91,10 +95,16 @@ export class VarCreate extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarCreateJson, workspace: Workspace, event?: any):
VarCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new VarCreate()) as VarCreate;
static fromJson(
json: VarCreateJson,
workspace: Workspace,
event?: any
): VarCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarCreate()
) as VarCreate;
newEvent.varType = json['varType'];
newEvent.varName = json['varName'];
return newEvent;
@@ -109,13 +119,15 @@ export class VarCreate extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.createVariable(this.varName, this.varType, this.varId);

View File

@@ -15,7 +15,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model has been deleted.
*
@@ -35,7 +34,7 @@ export class VarDelete extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.varType = opt_variable.type;
this.varName = opt_variable.name;
@@ -50,13 +49,15 @@ export class VarDelete extends VarBase {
const json = super.toJson() as VarDeleteJson;
if (this.varType === undefined) {
throw new Error(
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varType'] = this.varType;
json['varName'] = this.varName;
@@ -70,8 +71,11 @@ export class VarDelete extends VarBase {
*/
override fromJson(json: VarDeleteJson) {
deprecation.warn(
'Blockly.Events.VarDelete.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarDelete.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varType = json['varType'];
this.varName = json['varName'];
@@ -86,10 +90,16 @@ export class VarDelete extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarDeleteJson, workspace: Workspace, event?: any):
VarDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new VarDelete()) as VarDelete;
static fromJson(
json: VarDeleteJson,
workspace: Workspace,
event?: any
): VarDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarDelete()
) as VarDelete;
newEvent.varType = json['varType'];
newEvent.varName = json['varName'];
return newEvent;
@@ -104,13 +114,15 @@ export class VarDelete extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.deleteVariableById(this.varId);

View File

@@ -15,7 +15,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model was renamed.
*
@@ -38,7 +37,7 @@ export class VarRename extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.oldName = opt_variable.name;
this.newName = typeof newName === 'undefined' ? '' : newName;
@@ -53,13 +52,15 @@ export class VarRename extends VarBase {
const json = super.toJson() as VarRenameJson;
if (!this.oldName) {
throw new Error(
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.newName) {
throw new Error(
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['oldName'] = this.oldName;
json['newName'] = this.newName;
@@ -73,8 +74,11 @@ export class VarRename extends VarBase {
*/
override fromJson(json: VarRenameJson) {
deprecation.warn(
'Blockly.Events.VarRename.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarRename.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldName = json['oldName'];
this.newName = json['newName'];
@@ -89,10 +93,16 @@ export class VarRename extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarRenameJson, workspace: Workspace, event?: any):
VarRename {
const newEvent =
super.fromJson(json, workspace, event ?? new VarRename()) as VarRename;
static fromJson(
json: VarRenameJson,
workspace: Workspace,
event?: any
): VarRename {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarRename()
) as VarRename;
newEvent.oldName = json['oldName'];
newEvent.newName = json['newName'];
return newEvent;
@@ -107,18 +117,21 @@ export class VarRename extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.oldName) {
throw new Error(
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.newName) {
throw new Error(
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.renameVariableById(this.varId, this.newName);

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the workspace surface's position or scale has
* changed.
@@ -59,8 +58,12 @@ export class ViewportChange extends UiBase {
* event.
*/
constructor(
opt_top?: number, opt_left?: number, opt_scale?: number,
opt_workspaceId?: string, opt_oldScale?: number) {
opt_top?: number,
opt_left?: number,
opt_scale?: number,
opt_workspaceId?: string,
opt_oldScale?: number
) {
super(opt_workspaceId);
this.viewTop = opt_top;
@@ -78,23 +81,27 @@ export class ViewportChange extends UiBase {
const json = super.toJson() as ViewportChangeJson;
if (this.viewTop === undefined) {
throw new Error(
'The view top is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The view top is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.viewLeft === undefined) {
throw new Error(
'The view left is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The view left is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.scale === undefined) {
throw new Error(
'The scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.oldScale === undefined) {
throw new Error(
'The old scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The old scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['viewTop'] = this.viewTop;
json['viewLeft'] = this.viewLeft;
@@ -110,8 +117,11 @@ export class ViewportChange extends UiBase {
*/
override fromJson(json: ViewportChangeJson) {
deprecation.warn(
'Blockly.Events.Viewport.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Viewport.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.viewTop = json['viewTop'];
this.viewLeft = json['viewLeft'];
@@ -128,11 +138,16 @@ export class ViewportChange extends UiBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: ViewportChangeJson, workspace: Workspace, event?: any):
ViewportChange {
const newEvent =
super.fromJson(json, workspace, event ?? new ViewportChange()) as
ViewportChange;
static fromJson(
json: ViewportChangeJson,
workspace: Workspace,
event?: any
): ViewportChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ViewportChange()
) as ViewportChange;
newEvent.viewTop = json['viewTop'];
newEvent.viewLeft = json['viewLeft'];
newEvent.scale = json['scale'];
@@ -149,4 +164,7 @@ export interface ViewportChangeJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange);
registry.Type.EVENT,
eventUtils.VIEWPORT_CHANGE,
ViewportChange
);

View File

@@ -22,7 +22,6 @@ import type {CommentCreate} from './events_comment_create.js';
import type {CommentMove} from './events_comment_move.js';
import type {ViewportChange} from './events_viewport.js';
/** Group ID for new events. Grouped events are indivisible. */
let group = '';
@@ -187,7 +186,7 @@ export const FINISHED_LOADING = 'finished_loading';
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
export type BumpEvent = BlockCreate | BlockMove | CommentCreate | CommentMove;
/**
* List of events that cause objects to be bumped back into the visible
@@ -196,8 +195,12 @@ export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export const BUMP_EVENTS: string[] =
[BLOCK_CREATE, BLOCK_MOVE, COMMENT_CREATE, COMMENT_MOVE];
export const BUMP_EVENTS: string[] = [
BLOCK_CREATE,
BLOCK_MOVE,
COMMENT_CREATE,
COMMENT_MOVE,
];
/** List of events queued for firing. */
const FIRE_QUEUE: Abstract[] = [];
@@ -235,12 +238,11 @@ function fireInternal(event: Abstract) {
FIRE_QUEUE.push(event);
}
/** Fire all queued events. */
function fireNow() {
const queue = filter(FIRE_QUEUE, true);
FIRE_QUEUE.length = 0;
for (let i = 0, event; event = queue[i]; i++) {
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.workspaceId) {
continue;
}
@@ -268,7 +270,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
const mergedQueue = [];
const hash = Object.create(null);
// Merge duplicates.
for (let i = 0, event; event = queue[i]; i++) {
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.isNull()) {
// Treat all UI events as the same type in hash table.
const eventType = event.isUiEvent ? UI : event.type;
@@ -293,8 +295,9 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
if (moveEvent.reason) {
if (lastEvent.reason) {
// Concatenate reasons without duplicates.
const reasonSet =
new Set(moveEvent.reason.concat(lastEvent.reason));
const reasonSet = new Set(
moveEvent.reason.concat(lastEvent.reason)
);
lastEvent.reason = Array.from(reasonSet);
} else {
lastEvent.reason = moveEvent.reason;
@@ -302,9 +305,10 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
lastEntry.index = i;
} else if (
event.type === CHANGE &&
(event as BlockChange).element === lastEvent.element &&
(event as BlockChange).name === lastEvent.name) {
event.type === CHANGE &&
(event as BlockChange).element === lastEvent.element &&
(event as BlockChange).name === lastEvent.name
) {
const changeEvent = event as BlockChange;
// Merge change events.
lastEvent.newValue = changeEvent.newValue;
@@ -326,7 +330,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
}
// Filter out any events that have become null due to merging.
queue = mergedQueue.filter(function(e) {
queue = mergedQueue.filter(function (e) {
return !e.isNull();
});
if (!forward) {
@@ -335,11 +339,13 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
// Move mutation events to the top of the queue.
// Intentionally skip first event.
for (let i = 1, event; event = queue[i]; i++) {
for (let i = 1, event; (event = queue[i]); i++) {
// AnyDuringMigration because: Property 'element' does not exist on type
// 'Abstract'.
if (event.type === CHANGE &&
(event as AnyDuringMigration).element === 'mutation') {
if (
event.type === CHANGE &&
(event as AnyDuringMigration).element === 'mutation'
) {
queue.unshift(queue.splice(i, 1)[0]);
}
}
@@ -351,7 +357,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
* in the undo stack. Called by Workspace.clearUndo.
*/
export function clearPendingUndo() {
for (let i = 0, event; event = FIRE_QUEUE[i]; i++) {
for (let i = 0, event; (event = FIRE_QUEUE[i]); i++) {
event.recordUndo = false;
}
}
@@ -395,14 +401,14 @@ export function getGroup(): string {
* @param state True to start new group, false to end group.
* String to set group explicitly.
*/
export function setGroup(state: boolean|string) {
export function setGroup(state: boolean | string) {
TEST_ONLY.setGroupInternal(state);
}
/**
* Private version of setGroup for stubbing in tests.
*/
function setGroupInternal(state: boolean|string) {
function setGroupInternal(state: boolean | string) {
if (typeof state === 'boolean') {
group = state ? idGenerator.genUid() : '';
} else {
@@ -420,7 +426,7 @@ function setGroupInternal(state: boolean|string) {
export function getDescendantIds(block: Block): string[] {
const ids = [];
const descendants = block.getDescendants(false);
for (let i = 0, descendant; descendant = descendants[i]; i++) {
for (let i = 0, descendant; (descendant = descendants[i]); i++) {
ids[i] = descendant.id;
}
return ids;
@@ -435,7 +441,9 @@ export function getDescendantIds(block: Block): string[] {
* @throws {Error} if an event type is not found in the registry.
*/
export function fromJson(
json: AnyDuringMigration, workspace: Workspace): Abstract {
json: AnyDuringMigration,
workspace: Workspace
): Abstract {
const eventClass = get(json['type']);
if (!eventClass) throw Error('Unknown event type.');
@@ -457,11 +465,14 @@ export function fromJson(
* Returns false if no static fromJson method exists on the contructor, or if
* the static fromJson method is inheritted.
*/
function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract):
boolean {
function eventClassHasStaticFromJson(
eventClass: new (...p: any[]) => Abstract
): boolean {
const untypedEventClass = eventClass as any;
return Object.getOwnPropertyDescriptors(untypedEventClass).fromJson &&
typeof untypedEventClass.fromJson === 'function';
return (
Object.getOwnPropertyDescriptors(untypedEventClass).fromJson &&
typeof untypedEventClass.fromJson === 'function'
);
}
/**
@@ -470,8 +481,9 @@ function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract):
* @param eventType The type of the event to get.
* @returns The event class with the given type.
*/
export function get(eventType: string):
(new (...p1: AnyDuringMigration[]) => Abstract) {
export function get(
eventType: string
): new (...p1: AnyDuringMigration[]) => Abstract {
const event = registry.getClass(registry.Type.EVENT, eventType);
if (!event) {
throw new Error(`Event type ${eventType} not found in registry.`);
@@ -493,8 +505,9 @@ export function disableOrphans(event: Abstract) {
if (!blockEvent.workspaceId) {
return;
}
const eventWorkspace =
common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg;
const eventWorkspace = common.getWorkspaceById(
blockEvent.workspaceId
) as WorkspaceSvg;
if (!blockEvent.blockId) {
throw new Error('Encountered a blockEvent without a proper blockId');
}
@@ -507,12 +520,13 @@ export function disableOrphans(event: Abstract) {
const parent = block.getParent();
if (parent && parent.isEnabled()) {
const children = block.getDescendants(false);
for (let i = 0, child; child = children[i]; i++) {
for (let i = 0, child; (child = children[i]); i++) {
child.setEnabled(true);
}
} else if (
(block.outputConnection || block.previousConnection) &&
!eventWorkspace.isDragging()) {
(block.outputConnection || block.previousConnection) &&
!eventWorkspace.isDragging()
) {
do {
block.setEnabled(false);
block = block.getNextBlock();

View File

@@ -14,10 +14,12 @@ goog.declareModuleId('Blockly.Events.FinishedLoading');
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners when the workspace has finished deserializing from
* JSON/XML.
@@ -49,8 +51,9 @@ export class FinishedLoading extends AbstractEvent {
const json = super.toJson() as FinishedLoadingJson;
if (!this.workspaceId) {
throw new Error(
'The workspace ID is undefined. Either pass a workspace to ' +
'the constructor, or call fromJson');
'The workspace ID is undefined. Either pass a workspace to ' +
'the constructor, or call fromJson'
);
}
json['workspaceId'] = this.workspaceId;
return json;
@@ -72,4 +75,7 @@ export interface FinishedLoadingJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
registry.Type.EVENT,
eventUtils.FINISHED_LOADING,
FinishedLoading
);

View File

@@ -13,7 +13,6 @@ import {FieldDropdown} from './field_dropdown.js';
import {Mutator} from './mutator.js';
import * as parsing from './utils/parsing.js';
/** The set of all registered extensions, keyed by extension name/id. */
const allExtensions = Object.create(null);
export const TEST_ONLY = {allExtensions};
@@ -54,7 +53,7 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
if (!mixinObj || typeof mixinObj !== 'object') {
throw Error('Error: Mixin "' + name + '" must be a object');
}
register(name, function(this: Block) {
register(name, function (this: Block) {
this.mixin(mixinObj);
});
}
@@ -73,8 +72,11 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
* @throws {Error} if the mutation is invalid or can't be applied to the block.
*/
export function registerMutator(
name: string, mixinObj: AnyDuringMigration,
opt_helperFn?: () => AnyDuringMigration, opt_blockList?: string[]) {
name: string,
mixinObj: AnyDuringMigration,
opt_helperFn?: () => AnyDuringMigration,
opt_blockList?: string[]
) {
const errorPrefix = 'Error when registering mutator "' + name + '": ';
checkHasMutatorProperties(errorPrefix, mixinObj);
@@ -85,7 +87,7 @@ export function registerMutator(
}
// Sanity checks passed.
register(name, function(this: Block) {
register(name, function (this: Block) {
if (hasMutatorDialog) {
this.setMutator(new Mutator(opt_blockList || [], this as BlockSvg));
}
@@ -108,7 +110,8 @@ export function unregister(name: string) {
delete allExtensions[name];
} else {
console.warn(
'No extension mapping for name "' + name + '" found to unregister');
'No extension mapping for name "' + name + '" found to unregister'
);
}
}
@@ -151,11 +154,15 @@ export function apply(name: string, block: Block, isMutator: boolean) {
const errorPrefix = 'Error after applying mutator "' + name + '": ';
checkHasMutatorProperties(errorPrefix, block);
} else {
if (!mutatorPropertiesMatch(
mutatorProperties as AnyDuringMigration[], block)) {
if (
!mutatorPropertiesMatch(mutatorProperties as AnyDuringMigration[], block)
) {
throw Error(
'Error when applying extension "' + name + '": ' +
'mutation properties changed when applying a non-mutator extension.');
'Error when applying extension "' +
name +
'": ' +
'mutation properties changed when applying a non-mutator extension.'
);
}
}
}
@@ -173,9 +180,12 @@ function checkNoMutatorProperties(mutationName: string, block: Block) {
const properties = getMutatorProperties(block);
if (properties.length) {
throw Error(
'Error: tried to apply mutation "' + mutationName +
'Error: tried to apply mutation "' +
mutationName +
'" to a block that already has mutator functions.' +
' Block id: ' + block.id);
' Block id: ' +
block.id
);
}
}
@@ -191,10 +201,14 @@ function checkNoMutatorProperties(mutationName: string, block: Block) {
* actually a function.
*/
function checkXmlHooks(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.mutationToDom, object.domToMutation,
errorPrefix + ' mutationToDom/domToMutation');
object.mutationToDom,
object.domToMutation,
errorPrefix + ' mutationToDom/domToMutation'
);
}
/**
* Checks if the given object has both the 'saveExtraState' and 'loadExtraState'
@@ -208,10 +222,14 @@ function checkXmlHooks(
* actually a function.
*/
function checkJsonHooks(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.saveExtraState, object.loadExtraState,
errorPrefix + ' saveExtraState/loadExtraState');
object.saveExtraState,
object.loadExtraState,
errorPrefix + ' saveExtraState/loadExtraState'
);
}
/**
@@ -225,9 +243,14 @@ function checkJsonHooks(
* actually a function.
*/
function checkMutatorDialog(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.compose, object.decompose, errorPrefix + ' compose/decompose');
object.compose,
object.decompose,
errorPrefix + ' compose/decompose'
);
}
/**
@@ -243,8 +266,10 @@ function checkMutatorDialog(
* actually a function.
*/
function checkHasFunctionPair(
func1: AnyDuringMigration, func2: AnyDuringMigration,
errorPrefix: string): boolean {
func1: AnyDuringMigration,
func2: AnyDuringMigration,
errorPrefix: string
): boolean {
if (func1 && func2) {
if (typeof func1 !== 'function' || typeof func2 !== 'function') {
throw Error(errorPrefix + ' must be a function');
@@ -263,13 +288,16 @@ function checkHasFunctionPair(
* @param object The object to inspect.
*/
function checkHasMutatorProperties(
errorPrefix: string, object: AnyDuringMigration) {
errorPrefix: string,
object: AnyDuringMigration
) {
const hasXmlHooks = checkXmlHooks(object, errorPrefix);
const hasJsonHooks = checkJsonHooks(object, errorPrefix);
if (!hasXmlHooks && !hasJsonHooks) {
throw Error(
errorPrefix +
'Mutations must contain either XML hooks, or JSON hooks, or both');
errorPrefix +
'Mutations must contain either XML hooks, or JSON hooks, or both'
);
}
// A block with a mutator isn't required to have a mutation dialog, but
// it should still have both or neither of compose and decompose.
@@ -318,7 +346,9 @@ function getMutatorProperties(block: Block): AnyDuringMigration[] {
* @returns True if the property lists match.
*/
function mutatorPropertiesMatch(
oldProperties: AnyDuringMigration[], block: Block): boolean {
oldProperties: AnyDuringMigration[],
block: Block
): boolean {
const newProperties = getMutatorProperties(block);
if (newProperties.length !== oldProperties.length) {
return false;
@@ -343,10 +373,10 @@ export function runAfterPageLoad(fn: () => void) {
throw Error('runAfterPageLoad() requires browser document.');
}
if (document.readyState === 'complete') {
fn(); // Page has already loaded. Call immediately.
fn(); // Page has already loaded. Call immediately.
} else {
// Poll readyState.
const readyStateCheckInterval = setInterval(function() {
const readyStateCheckInterval = setInterval(function () {
if (document.readyState === 'complete') {
clearInterval(readyStateCheckInterval);
fn();
@@ -375,7 +405,9 @@ export function runAfterPageLoad(fn: () => void) {
* @returns The extension function.
*/
export function buildTooltipForDropdown(
dropdownName: string, lookupTable: {[key: string]: string}): Function {
dropdownName: string,
lookupTable: {[key: string]: string}
): Function {
// List of block types already validated, to minimize duplicate warnings.
const blockTypesChecked: AnyDuringMigration[] = [];
@@ -383,8 +415,9 @@ export function buildTooltipForDropdown(
// Wait for load, in case Blockly.Msg is not yet populated.
// runAfterPageLoad() does not run in a Node.js environment due to lack
// of document object, in which case skip the validation.
if (typeof document === 'object') { // Relies on document.readyState
runAfterPageLoad(function() {
if (typeof document === 'object') {
// Relies on document.readyState
runAfterPageLoad(function () {
for (const key in lookupTable) {
// Will print warnings if reference is missing.
parsing.checkMessageReferences(lookupTable[key]);
@@ -399,24 +432,29 @@ export function buildTooltipForDropdown(
blockTypesChecked.push(this.type);
}
this.setTooltip(function(this: Block) {
const value = String(this.getFieldValue(dropdownName));
let tooltip = lookupTable[value];
if (tooltip === null) {
if (blockTypesChecked.indexOf(this.type) === -1) {
// Warn for missing values on generated tooltips.
let warning = 'No tooltip mapping for value ' + value + ' of field ' +
this.setTooltip(
function (this: Block) {
const value = String(this.getFieldValue(dropdownName));
let tooltip = lookupTable[value];
if (tooltip === null) {
if (blockTypesChecked.indexOf(this.type) === -1) {
// Warn for missing values on generated tooltips.
let warning =
'No tooltip mapping for value ' +
value +
' of field ' +
dropdownName;
if (this.type !== null) {
warning += ' of block type ' + this.type;
if (this.type !== null) {
warning += ' of block type ' + this.type;
}
console.warn(warning + '.');
}
console.warn(warning + '.');
} else {
tooltip = parsing.replaceMessageReferences(tooltip);
}
} else {
tooltip = parsing.replaceMessageReferences(tooltip);
}
return tooltip;
}.bind(this));
return tooltip;
}.bind(this)
);
}
return extensionFn;
}
@@ -430,17 +468,25 @@ export function buildTooltipForDropdown(
* @param lookupTable The string lookup table
*/
function checkDropdownOptionsInTable(
block: Block, dropdownName: string, lookupTable: {[key: string]: string}) {
block: Block,
dropdownName: string,
lookupTable: {[key: string]: string}
) {
// Validate all dropdown options have values.
const dropdown = block.getField(dropdownName);
if (dropdown instanceof FieldDropdown && !dropdown.isOptionListDynamic()) {
const options = dropdown.getOptions();
for (let i = 0; i < options.length; i++) {
const optionKey = options[i][1]; // label, then value
const optionKey = options[i][1]; // label, then value
if (lookupTable[optionKey] === null) {
console.warn(
'No tooltip mapping for value ' + optionKey + ' of field ' +
dropdownName + ' of block type ' + block.type);
'No tooltip mapping for value ' +
optionKey +
' of field ' +
dropdownName +
' of block type ' +
block.type
);
}
}
}
@@ -457,13 +503,16 @@ function checkDropdownOptionsInTable(
* @returns The extension function.
*/
export function buildTooltipWithFieldText(
msgTemplate: string, fieldName: string): Function {
msgTemplate: string,
fieldName: string
): Function {
// Check the tooltip string messages for invalid references.
// Wait for load, in case Blockly.Msg is not yet populated.
// runAfterPageLoad() does not run in a Node.js environment due to lack
// of document object, in which case skip the validation.
if (typeof document === 'object') { // Relies on document.readyState
runAfterPageLoad(function() {
if (typeof document === 'object') {
// Relies on document.readyState
runAfterPageLoad(function () {
// Will print warnings if reference is missing.
parsing.checkMessageReferences(msgTemplate);
});
@@ -471,11 +520,14 @@ export function buildTooltipWithFieldText(
/** The actual extension. */
function extensionFn(this: Block) {
this.setTooltip(function(this: Block) {
const field = this.getField(fieldName);
return parsing.replaceMessageReferences(msgTemplate)
this.setTooltip(
function (this: Block) {
const field = this.getField(fieldName);
return parsing
.replaceMessageReferences(msgTemplate)
.replace('%1', field ? field.getText() : '');
}.bind(this));
}.bind(this)
);
}
return extensionFn;
}
@@ -488,10 +540,14 @@ export function buildTooltipWithFieldText(
*/
function extensionParentTooltip(this: Block) {
const tooltipWhenNotConnected = this.tooltip;
this.setTooltip(function(this: Block) {
const parent = this.getParent();
return parent && parent.getInputsInline() && parent.tooltip ||
tooltipWhenNotConnected;
}.bind(this));
this.setTooltip(
function (this: Block) {
const parent = this.getParent();
return (
(parent && parent.getInputsInline() && parent.tooltip) ||
tooltipWhenNotConnected
);
}.bind(this)
);
}
register('parent_tooltip_when_inline', extensionParentTooltip);

View File

@@ -59,17 +59,21 @@ import {ISerializable} from './interfaces/i_serializable.js';
*
* - `undefined` to set `newValue` as is.
*/
export type FieldValidator<T = any> = (newValue: T) => T|null|undefined;
export type FieldValidator<T = any> = (newValue: T) => T | null | undefined;
/**
* Abstract class for an editable field.
*
* @typeParam T - The value stored on the field.
*/
export abstract class Field<T = any> implements IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
IKeyboardAccessible,
IRegistrable, ISerializable {
export abstract class Field<T = any>
implements
IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
IKeyboardAccessible,
IRegistrable,
ISerializable
{
/**
* To overwrite the default value which is set in **Field**, directly update
* the prototype.
@@ -79,7 +83,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* FieldImage.prototype.DEFAULT_VALUE = null;
* ```
*/
DEFAULT_VALUE: T|null = null;
DEFAULT_VALUE: T | null = null;
/** Non-breaking space. */
static readonly NBSP = '\u00A0';
@@ -96,47 +100,47 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Static labels are usually unnamed.
*/
name?: string = undefined;
protected value_: T|null;
protected value_: T | null;
/** Validation function called when user edits an editable field. */
protected validator_: FieldValidator<T>|null = null;
protected validator_: FieldValidator<T> | null = null;
/**
* Used to cache the field's tooltip value if setTooltip is called when the
* field is not yet initialized. Is *not* guaranteed to be accurate.
*/
private tooltip_: Tooltip.TipInfo|null = null;
private tooltip_: Tooltip.TipInfo | null = null;
protected size_: Size;
/**
* Holds the cursors svg element when the cursor is attached to the field.
* This is null if there is no cursor on the field.
*/
private cursorSvg_: SVGElement|null = null;
private cursorSvg_: SVGElement | null = null;
/**
* Holds the markers svg element when the marker is attached to the field.
* This is null if there is no marker on the field.
*/
private markerSvg_: SVGElement|null = null;
private markerSvg_: SVGElement | null = null;
/** The rendered field's SVG group element. */
protected fieldGroup_: SVGGElement|null = null;
protected fieldGroup_: SVGGElement | null = null;
/** The rendered field's SVG border element. */
protected borderRect_: SVGRectElement|null = null;
protected borderRect_: SVGRectElement | null = null;
/** The rendered field's SVG text element. */
protected textElement_: SVGTextElement|null = null;
protected textElement_: SVGTextElement | null = null;
/** The rendered field's text content element. */
protected textContent_: Text|null = null;
protected textContent_: Text | null = null;
/** Mouse down event listener data. */
private mouseDownWrapper_: browserEvents.Data|null = null;
private mouseDownWrapper_: browserEvents.Data | null = null;
/** Constants associated with the source block's renderer. */
protected constants_: ConstantProvider|null = null;
protected constants_: ConstantProvider | null = null;
/**
* Has this field been disposed of?
@@ -149,7 +153,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
maxDisplayLength = 50;
/** Block this field is attached to. Starts as null, then set in init. */
protected sourceBlock_: Block|null = null;
protected sourceBlock_: Block | null = null;
/** Does this block need to be re-rendered? */
protected isDirty_ = true;
@@ -163,21 +167,21 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
protected enabled_ = true;
/** The element the click handler is bound to. */
protected clickTarget_: Element|null = null;
protected clickTarget_: Element | null = null;
/**
* The prefix field.
*
* @internal
*/
prefixField: string|null = null;
prefixField: string | null = null;
/**
* The suffix field.
*
* @internal
*/
suffixField: string|null = null;
suffixField: string | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -208,15 +212,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* this parameter supports.
*/
constructor(
value: T|typeof Field.SKIP_SETUP, validator?: FieldValidator<T>|null,
config?: FieldConfig) {
value: T | typeof Field.SKIP_SETUP,
validator?: FieldValidator<T> | null,
config?: FieldConfig
) {
/**
* A generic value possessed by the field.
* Should generally be non-null, only null when the field is created.
*/
this.value_ = 'DEFAULT_VALUE' in new.target.prototype ?
new.target.prototype.DEFAULT_VALUE :
this.DEFAULT_VALUE;
this.value_ =
'DEFAULT_VALUE' in new.target.prototype
? new.target.prototype.DEFAULT_VALUE
: this.DEFAULT_VALUE;
/** The size of the area rendered by the field. */
this.size_ = new Size(0, 0);
@@ -263,13 +270,16 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns The renderer constant provider.
*/
getConstants(): ConstantProvider|null {
if (!this.constants_ && this.sourceBlock_ &&
!this.sourceBlock_.isDeadOrDying() &&
this.sourceBlock_.workspace.rendered) {
getConstants(): ConstantProvider | null {
if (
!this.constants_ &&
this.sourceBlock_ &&
!this.sourceBlock_.isDeadOrDying() &&
this.sourceBlock_.workspace.rendered
) {
this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg)
.getRenderer()
.getConstants();
.getRenderer()
.getConstants();
}
return this.constants_;
}
@@ -280,7 +290,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns The block containing this field.
* @throws An error if the source block is not defined.
*/
getSourceBlock(): Block|null {
getSourceBlock(): Block | null {
return this.sourceBlock_;
}
@@ -334,16 +344,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected createBorderRect_() {
this.borderRect_ = dom.createSvgElement(
Svg.RECT, {
'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width,
'class': 'blocklyFieldRect',
},
this.fieldGroup_);
Svg.RECT,
{
'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width,
'class': 'blocklyFieldRect',
},
this.fieldGroup_
);
}
/**
@@ -353,10 +365,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected createTextElement_() {
this.textElement_ = dom.createSvgElement(
Svg.TEXT, {
'class': 'blocklyText',
},
this.fieldGroup_);
Svg.TEXT,
{
'class': 'blocklyText',
},
this.fieldGroup_
);
if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dominant-baseline', 'central');
}
@@ -373,7 +387,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
if (!clickTarget) throw new Error('A click target has not been set.');
Tooltip.bindMouseEvents(clickTarget);
this.mouseDownWrapper_ = browserEvents.conditionalBind(
clickTarget, 'pointerdown', this, this.onMouseDown_);
clickTarget,
'pointerdown',
this,
this.onMouseDown_
);
}
/**
@@ -443,14 +461,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Used to see if `this` has overridden any relevant hooks.
* @returns The stringified version of the XML state, or null.
*/
protected saveLegacyState(callingClass: FieldProto): string|null {
if (callingClass.prototype.saveState === this.saveState &&
callingClass.prototype.toXml !== this.toXml) {
protected saveLegacyState(callingClass: FieldProto): string | null {
if (
callingClass.prototype.saveState === this.saveState &&
callingClass.prototype.toXml !== this.toXml
) {
const elem = utilsXml.createElement('field');
elem.setAttribute('name', this.name || '');
const text = utilsXml.domToText(this.toXml(elem));
return text.replace(
' xmlns="https://developers.google.com/blockly/xml"', '');
' xmlns="https://developers.google.com/blockly/xml"',
''
);
}
// Either they called this on purpose from their saveState, or they have
// no implementations of either hook. Just do our thing.
@@ -466,10 +488,14 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @param state The state to apply to the field.
* @returns Whether the state was applied or not.
*/
loadLegacyState(callingClass: FieldProto, state: AnyDuringMigration):
boolean {
if (callingClass.prototype.loadState === this.loadState &&
callingClass.prototype.fromXml !== this.fromXml) {
loadLegacyState(
callingClass: FieldProto,
state: AnyDuringMigration
): boolean {
if (
callingClass.prototype.loadState === this.loadState &&
callingClass.prototype.fromXml !== this.fromXml
) {
this.fromXml(utilsXml.textToDom(state as string));
return true;
}
@@ -539,9 +565,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns Whether this field is clickable.
*/
isClickable(): boolean {
return this.enabled_ && !!this.sourceBlock_ &&
this.sourceBlock_.isEditable() &&
this.showEditor_ !== Field.prototype.showEditor_;
return (
this.enabled_ &&
!!this.sourceBlock_ &&
this.sourceBlock_.isEditable() &&
this.showEditor_ !== Field.prototype.showEditor_
);
}
/**
@@ -553,8 +582,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* editable block.
*/
isCurrentlyEditable(): boolean {
return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ &&
this.sourceBlock_.isEditable();
return (
this.enabled_ &&
this.EDITABLE &&
!!this.sourceBlock_ &&
this.sourceBlock_.isEditable()
);
}
/**
@@ -570,9 +603,10 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
isSerializable = true;
} else if (this.EDITABLE) {
console.warn(
'Detected an editable field that was not serializable.' +
'Detected an editable field that was not serializable.' +
' Please define SERIALIZABLE property as true on all editable custom' +
' fields. Proceeding with serialization.');
' fields. Proceeding with serialization.'
);
isSerializable = true;
}
}
@@ -630,7 +664,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Validation function, or null.
*/
getValidator(): FieldValidator<T>|null {
getValidator(): FieldValidator<T> | null {
return this.validator_;
}
@@ -640,7 +674,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns The group element.
*/
getSvgRoot(): SVGGElement|null {
getSvgRoot(): SVGGElement | null {
return this.fieldGroup_;
}
@@ -764,17 +798,23 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected updateSize_(margin?: number) {
const constants = this.getConstants();
const xOffset = margin !== undefined ? margin :
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
0;
const xOffset =
margin !== undefined
? margin
: this.borderRect_
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let totalWidth = xOffset * 2;
let totalHeight = constants!.FIELD_TEXT_HEIGHT;
let contentWidth = 0;
if (this.textElement_) {
contentWidth = dom.getFastTextWidth(
this.textElement_, constants!.FIELD_TEXT_FONTSIZE,
constants!.FIELD_TEXT_FONTWEIGHT, constants!.FIELD_TEXT_FONTFAMILY);
this.textElement_,
constants!.FIELD_TEXT_FONTSIZE,
constants!.FIELD_TEXT_FONTWEIGHT,
constants!.FIELD_TEXT_FONTFAMILY
);
totalWidth += contentWidth;
}
if (this.borderRect_) {
@@ -803,18 +843,23 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
const halfHeight = this.size_.height / 2;
this.textElement_.setAttribute(
'x',
String(
this.getSourceBlock()?.RTL ?
this.size_.width - contentWidth - xOffset :
xOffset));
'x',
String(
this.getSourceBlock()?.RTL
? this.size_.width - contentWidth - xOffset
: xOffset
)
);
this.textElement_.setAttribute(
'y',
String(
constants!.FIELD_TEXT_BASELINE_CENTER ?
halfHeight :
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE));
'y',
String(
constants!.FIELD_TEXT_BASELINE_CENTER
? halfHeight
: halfHeight -
constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE
)
);
}
/** Position a field's border rect after a size change. */
@@ -825,9 +870,13 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.borderRect_.setAttribute('width', String(this.size_.width));
this.borderRect_.setAttribute('height', String(this.size_.height));
this.borderRect_.setAttribute(
'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
'rx',
String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)
);
this.borderRect_.setAttribute(
'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
'ry',
String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)
);
}
/**
@@ -852,8 +901,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
// Don't issue a warning if the field is actually zero width.
if (this.size_.width !== 0) {
console.warn(
'Deprecated use of setting size_.width to 0 to rerender a' +
' field. Set field.isDirty_ to true instead.');
'Deprecated use of setting size_.width to 0 to rerender a' +
' field. Set field.isDirty_ to true instead.'
);
}
}
return this.size_;
@@ -953,7 +1003,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Current text or null.
*/
protected getText_(): string|null {
protected getText_(): string | null {
return null;
}
@@ -1031,8 +1081,15 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.doValueUpdate_(localValue);
if (source && eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source, 'field', this.name || null, oldValue, localValue));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source,
'field',
this.name || null,
oldValue,
localValue
)
);
}
if (this.isDirty_) {
this.forceRerender();
@@ -1048,7 +1105,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns New value, or an Error object.
*/
private processValidation_(
newValue: AnyDuringMigration, validatedValue: T|null|undefined): T|Error {
newValue: AnyDuringMigration,
validatedValue: T | null | undefined
): T | Error {
if (validatedValue === null) {
this.doValueInvalid_(newValue);
if (this.isDirty_) {
@@ -1056,7 +1115,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
}
return Error();
}
return validatedValue === undefined ? newValue as T : validatedValue;
return validatedValue === undefined ? (newValue as T) : validatedValue;
}
/**
@@ -1064,7 +1123,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Current value.
*/
getValue(): T|null {
getValue(): T | null {
return this.value_;
}
@@ -1088,10 +1147,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* - `undefined` to set `newValue` as is.
*/
protected doClassValidation_(newValue: T): T|null|undefined;
protected doClassValidation_(newValue?: AnyDuringMigration): T|null;
protected doClassValidation_(newValue?: T|AnyDuringMigration): T|null
|undefined {
protected doClassValidation_(newValue: T): T | null | undefined;
protected doClassValidation_(newValue?: AnyDuringMigration): T | null;
protected doClassValidation_(
newValue?: T | AnyDuringMigration
): T | null | undefined {
if (newValue === null || newValue === undefined) {
return null;
}
@@ -1143,8 +1203,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* display the tooltip of the parent block. To not display a tooltip pass
* the empty string.
*/
setTooltip(newTip: Tooltip.TipInfo|null) {
if (!newTip && newTip !== '') { // If null or undefined.
setTooltip(newTip: Tooltip.TipInfo | null) {
if (!newTip && newTip !== '') {
// If null or undefined.
newTip = this.sourceBlock_;
}
const clickTarget = this.getClickTarget_();
@@ -1177,7 +1238,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Element to bind click handler to.
*/
protected getClickTarget_(): Element|null {
protected getClickTarget_(): Element | null {
return this.clickTarget_ || this.getSvgRoot();
}
@@ -1351,7 +1412,8 @@ export class UnattachedFieldError extends Error {
/** @internal */
constructor() {
super(
'The field has not yet been attached to its input. ' +
'Call appendField to attach it.');
'The field has not yet been attached to its input. ' +
'Call appendField to attach it.'
);
}
}

View File

@@ -18,7 +18,11 @@ import * as Css from './css.js';
import * as dropDownDiv from './dropdowndiv.js';
import {Field, UnattachedFieldError} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as dom from './utils/dom.js';
import * as math from './utils/math.js';
import {Svg} from './utils/svg.js';
@@ -92,13 +96,13 @@ export class FieldAngle extends FieldInput<number> {
private boundEvents: browserEvents.Data[] = [];
/** Dynamic red line pointing at the value's angle. */
private line: SVGLineElement|null = null;
private line: SVGLineElement | null = null;
/** Dynamic pink area extending from 0 to the value's angle. */
private gauge: SVGPathElement|null = null;
private gauge: SVGPathElement | null = null;
/** The degree symbol for this field. */
protected symbol_: SVGTSpanElement|null = null;
protected symbol_: SVGTSpanElement | null = null;
/**
* @param value The initial value of the field. Should cast to a number.
@@ -114,8 +118,10 @@ export class FieldAngle extends FieldInput<number> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|number|typeof Field.SKIP_SETUP,
validator?: FieldAngleValidator, config?: FieldAngleConfig) {
value?: string | number | typeof Field.SKIP_SETUP,
validator?: FieldAngleValidator,
config?: FieldAngleConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -192,8 +198,9 @@ export class FieldAngle extends FieldInput<number> {
if (this.sourceBlock_ instanceof BlockSvg) {
dropDownDiv.setColour(
this.sourceBlock_.style.colourPrimary,
this.sourceBlock_.style.colourTertiary);
this.sourceBlock_.style.colourPrimary,
this.sourceBlock_.style.colourTertiary
);
}
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
@@ -217,50 +224,80 @@ export class FieldAngle extends FieldInput<number> {
'style': 'touch-action: none',
});
const circle = dom.createSvgElement(
Svg.CIRCLE, {
'cx': FieldAngle.HALF,
'cy': FieldAngle.HALF,
'r': FieldAngle.RADIUS,
'class': 'blocklyAngleCircle',
},
svg);
this.gauge =
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
Svg.CIRCLE,
{
'cx': FieldAngle.HALF,
'cy': FieldAngle.HALF,
'r': FieldAngle.RADIUS,
'class': 'blocklyAngleCircle',
},
svg
);
this.gauge = dom.createSvgElement(
Svg.PATH,
{'class': 'blocklyAngleGauge'},
svg
);
this.line = dom.createSvgElement(
Svg.LINE, {
'x1': FieldAngle.HALF,
'y1': FieldAngle.HALF,
'class': 'blocklyAngleLine',
},
svg);
Svg.LINE,
{
'x1': FieldAngle.HALF,
'y1': FieldAngle.HALF,
'class': 'blocklyAngleLine',
},
svg
);
// Draw markers around the edge.
for (let angle = 0; angle < 360; angle += 15) {
dom.createSvgElement(
Svg.LINE, {
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
'y1': FieldAngle.HALF,
'x2': FieldAngle.HALF + FieldAngle.RADIUS -
(angle % 45 === 0 ? 10 : 5),
'y2': FieldAngle.HALF,
'class': 'blocklyAngleMarks',
'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
FieldAngle.HALF + ')',
},
svg);
Svg.LINE,
{
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
'y1': FieldAngle.HALF,
'x2':
FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5),
'y2': FieldAngle.HALF,
'class': 'blocklyAngleMarks',
'transform':
'rotate(' +
angle +
',' +
FieldAngle.HALF +
',' +
FieldAngle.HALF +
')',
},
svg
);
}
// The angle picker is different from other fields in that it updates on
// mousemove even if it's not in the middle of a drag. In future we may
// change this behaviour.
this.boundEvents.push(
browserEvents.conditionalBind(svg, 'click', this, this.hide));
browserEvents.conditionalBind(svg, 'click', this, this.hide)
);
// On touch devices, the picker's value is only updated with a drag. Add
// a click handler on the drag surface to update the value if the surface
// is clicked.
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointerdown', this, this.onMouseMove_, true));
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointermove', this, this.onMouseMove_, true));
this.boundEvents.push(
browserEvents.conditionalBind(
circle,
'pointerdown',
this,
this.onMouseMove_,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
circle,
'pointermove',
this,
this.onMouseMove_,
true
)
);
return svg;
}
@@ -353,14 +390,31 @@ export class FieldAngle extends FieldInput<number> {
x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
// Don't ask how the flag calculations work. They just do.
let largeFlag =
Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
let largeFlag = Math.abs(
Math.floor((angleRadians - angle1) / Math.PI) % 2
);
if (clockwiseFlag) {
largeFlag = 1 - largeFlag;
}
path.push(
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
' l ',
x1,
',',
y1,
' A ',
FieldAngle.RADIUS,
',',
FieldAngle.RADIUS,
' 0 ',
largeFlag,
' ',
clockwiseFlag,
' ',
x2,
',',
y2,
' z'
);
}
this.gauge.setAttribute('d', path.join(''));
this.line.setAttribute('x2', `${x2}`);
@@ -412,7 +466,7 @@ export class FieldAngle extends FieldInput<number> {
* @param newValue The input value.
* @returns A valid angle, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): number|null {
protected override doClassValidation_(newValue?: any): number | null {
const value = Number(newValue);
if (isNaN(value) || !isFinite(value)) {
return null;
@@ -456,7 +510,6 @@ fieldRegistry.register('field_angle', FieldAngle);
FieldAngle.prototype.DEFAULT_VALUE = 0;
/**
* CSS for angle field.
*/

View File

@@ -19,8 +19,8 @@ import * as dom from './utils/dom.js';
import {Field, FieldConfig, FieldValidator} from './field.js';
import * as fieldRegistry from './field_registry.js';
type BoolString = 'TRUE'|'FALSE';
type CheckboxBool = BoolString|boolean;
type BoolString = 'TRUE' | 'FALSE';
type CheckboxBool = BoolString | boolean;
/**
* Class for a checkbox field.
@@ -45,7 +45,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* NOTE: The default value is set in `Field`, so maintain that value instead
* of overwriting it here or in the constructor.
*/
override value_: boolean|null = this.value_;
override value_: boolean | null = this.value_;
/**
* @param value The initial value of the field. Should either be 'TRUE',
@@ -62,8 +62,10 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* for a list of properties this parameter supports.
*/
constructor(
value?: CheckboxBool|typeof Field.SKIP_SETUP,
validator?: FieldCheckboxValidator, config?: FieldCheckboxConfig) {
value?: CheckboxBool | typeof Field.SKIP_SETUP,
validator?: FieldCheckboxValidator,
config?: FieldCheckboxConfig
) {
super(Field.SKIP_SETUP);
/**
@@ -136,7 +138,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param character The character to use for the check mark, or null to use
* the default.
*/
setCheckCharacter(character: string|null) {
setCheckCharacter(character: string | null) {
this.checkChar = character || FieldCheckbox.CHECK_CHAR;
this.forceRerender();
}
@@ -152,8 +154,9 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param newValue The input value.
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration):
BoolString|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): BoolString | null {
if (newValue === true || newValue === 'TRUE') {
return 'TRUE';
}
@@ -191,7 +194,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
*
* @returns The boolean value of this field.
*/
getValueBoolean(): boolean|null {
getValueBoolean(): boolean | null {
return this.value_;
}
@@ -213,7 +216,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param value The value to convert.
* @returns The converted value.
*/
private convertValueToBool_(value: CheckboxBool|null): boolean {
private convertValueToBool_(value: CheckboxBool | null): boolean {
if (typeof value === 'string') return value === 'TRUE';
return !!value;
}

View File

@@ -36,6 +36,7 @@ export class FieldColour extends Field<string> {
* Copied from goog.ui.ColorPicker.SIMPLE_GRID_COLORS
* All colour pickers use this unless overridden with setColours.
*/
// prettier-ignore
static COLOURS: string[] = [
// grays
'#ffffff', '#cccccc', '#c0c0c0', '#999999',
@@ -74,10 +75,10 @@ export class FieldColour extends Field<string> {
static COLUMNS = 7;
/** The field's colour picker element. */
private picker: HTMLElement|null = null;
private picker: HTMLElement | null = null;
/** Index of the currently highlighted element. */
private highlightedIndex: number|null = null;
private highlightedIndex: number | null = null;
/**
* Array holding info needed to unbind events.
@@ -103,13 +104,13 @@ export class FieldColour extends Field<string> {
protected override isDirty_ = false;
/** Array of colours used by this field. If null, use the global list. */
private colours: string[]|null = null;
private colours: string[] | null = null;
/**
* Array of colour tooltips used by this field. If null, use the global
* list.
*/
private titles: string[]|null = null;
private titles: string[] | null = null;
/**
* Number of colour columns used by this field. If 0, use the global
@@ -132,8 +133,10 @@ export class FieldColour extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP, validator?: FieldColourValidator,
config?: FieldColourConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldColourValidator,
config?: FieldColourConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -165,8 +168,9 @@ export class FieldColour extends Field<string> {
*/
override initView() {
this.size_ = new Size(
this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH,
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT);
this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH,
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT
);
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
this.createBorderRect_();
this.getBorderRect().style['fillOpacity'] = '1';
@@ -185,7 +189,9 @@ export class FieldColour extends Field<string> {
}
} else if (this.sourceBlock_ instanceof BlockSvg) {
this.sourceBlock_.pathObject.svgPath.setAttribute(
'fill', this.getValue() as string);
'fill',
this.getValue() as string
);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
}
@@ -196,7 +202,7 @@ export class FieldColour extends Field<string> {
* @param newValue The input value.
* @returns A valid colour, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): string|null {
protected override doClassValidation_(newValue?: any): string | null {
if (typeof newValue !== 'string') {
return null;
}
@@ -214,8 +220,10 @@ export class FieldColour extends Field<string> {
if (this.borderRect_) {
this.borderRect_.style.fill = newValue;
} else if (
this.sourceBlock_ && this.sourceBlock_.rendered &&
this.sourceBlock_ instanceof BlockSvg) {
this.sourceBlock_ &&
this.sourceBlock_.rendered &&
this.sourceBlock_ instanceof BlockSvg
) {
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
@@ -297,7 +305,7 @@ export class FieldColour extends Field<string> {
*/
private onKeyDown(e: KeyboardEvent) {
let handled = true;
let highlighted: HTMLElement|null;
let highlighted: HTMLElement | null;
switch (e.key) {
case 'ArrowUp':
this.moveHighlightBy(0, -1);
@@ -423,7 +431,7 @@ export class FieldColour extends Field<string> {
*
* @returns Highlighted item (null if none).
*/
private getHighlighted(): HTMLElement|null {
private getHighlighted(): HTMLElement | null {
if (!this.highlightedIndex) {
return null;
}
@@ -476,7 +484,10 @@ export class FieldColour extends Field<string> {
aria.setRole(table, aria.Role.GRID);
aria.setState(table, aria.State.EXPANDED, true);
aria.setState(
table, aria.State.ROWCOUNT, Math.floor(colours.length / columns));
table,
aria.State.ROWCOUNT,
Math.floor(colours.length / columns)
);
aria.setState(table, aria.State.COLCOUNT, columns);
let row: Element;
for (let i = 0; i < colours.length; i++) {
@@ -485,7 +496,7 @@ export class FieldColour extends Field<string> {
aria.setRole(row, aria.Role.ROW);
table.appendChild(row);
}
const cell = (document.createElement('td'));
const cell = document.createElement('td');
row!.appendChild(cell);
// This becomes the value, if clicked.
cell.setAttribute('data-colour', colours[i]);
@@ -503,16 +514,51 @@ export class FieldColour extends Field<string> {
}
// Configure event handler on the table to listen for any event in a cell.
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerdown', this, this.onClick, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointermove', this, this.onMouseMove, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerenter', this, this.onMouseEnter, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerleave', this, this.onMouseLeave, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'keydown', this, this.onKeyDown, false));
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerdown',
this,
this.onClick,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointermove',
this,
this.onMouseMove,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerenter',
this,
this.onMouseEnter,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerleave',
this,
this.onMouseLeave,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'keydown',
this,
this.onKeyDown,
false
)
);
this.picker = table;
}
@@ -547,7 +593,6 @@ FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0];
fieldRegistry.register('field_colour', FieldColour);
/**
* CSS for colour picker.
*/

View File

@@ -16,7 +16,12 @@ goog.declareModuleId('Blockly.FieldDropdown');
import type {BlockSvg} from './block_svg.js';
import * as dropDownDiv from './dropdowndiv.js';
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
@@ -44,21 +49,21 @@ export class FieldDropdown extends Field<string> {
static ARROW_CHAR = '▾';
/** A reference to the currently selected menu item. */
private selectedMenuItem: MenuItem|null = null;
private selectedMenuItem: MenuItem | null = null;
/** The dropdown menu. */
protected menu_: Menu|null = null;
protected menu_: Menu | null = null;
/**
* SVG image element if currently selected option is an image, or null.
*/
private imageElement: SVGImageElement|null = null;
private imageElement: SVGImageElement | null = null;
/** Tspan based arrow element. */
private arrow: SVGTSpanElement|null = null;
private arrow: SVGTSpanElement | null = null;
/** SVG based arrow element. */
private svgArrow: SVGElement|null = null;
private svgArrow: SVGElement | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -72,24 +77,24 @@ export class FieldDropdown extends Field<string> {
protected menuGenerator_?: MenuGenerator;
/** A cache of the most recently generated options. */
private generatedOptions: MenuOption[]|null = null;
private generatedOptions: MenuOption[] | null = null;
/**
* The prefix field label, of common words set after options are trimmed.
*
* @internal
*/
override prefixField: string|null = null;
override prefixField: string | null = null;
/**
* The suffix field label, of common words set after options are trimmed.
*
* @internal
*/
override suffixField: string|null = null;
override suffixField: string | null = null;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private selectedOption!: MenuOption;
override clickTarget_: SVGElement|null = null;
override clickTarget_: SVGElement | null = null;
/**
* @param menuGenerator A non-empty array of options for a dropdown list, or a
@@ -108,15 +113,15 @@ export class FieldDropdown extends Field<string> {
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
*/
constructor(
menuGenerator: MenuGenerator,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
menuGenerator: MenuGenerator,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig
);
constructor(menuGenerator: typeof Field.SKIP_SETUP);
constructor(
menuGenerator: MenuGenerator|typeof Field.SKIP_SETUP,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
menuGenerator: MenuGenerator | typeof Field.SKIP_SETUP,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig
) {
super(Field.SKIP_SETUP);
@@ -210,17 +215,23 @@ export class FieldDropdown extends Field<string> {
* @returns True if the dropdown field should add a border rect.
*/
protected shouldAddBorderRect_(): boolean {
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
!this.getSourceBlock()?.isShadow();
return (
!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
(this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
!this.getSourceBlock()?.isShadow())
);
}
/** Create a tspan based arrow. */
protected createTextArrow_() {
this.arrow = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
this.arrow!.appendChild(document.createTextNode(
this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
' ' + FieldDropdown.ARROW_CHAR));
this.arrow!.appendChild(
document.createTextNode(
this.getSourceBlock()?.RTL
? FieldDropdown.ARROW_CHAR + ' '
: ' ' + FieldDropdown.ARROW_CHAR
)
);
if (this.getSourceBlock()?.RTL) {
this.getTextElement().insertBefore(this.arrow, this.textContent_);
} else {
@@ -231,14 +242,18 @@ export class FieldDropdown extends Field<string> {
/** Create an SVG based arrow. */
protected createSVGArrow_() {
this.svgArrow = dom.createSvgElement(
Svg.IMAGE, {
'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
},
this.fieldGroup_);
Svg.IMAGE,
{
'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
},
this.fieldGroup_
);
this.svgArrow!.setAttributeNS(
dom.XLINK_NS, 'xlink:href',
this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI);
dom.XLINK_NS,
'xlink:href',
this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI
);
}
/**
@@ -266,11 +281,12 @@ export class FieldDropdown extends Field<string> {
dom.addClass(menuElement, 'blocklyDropdownMenu');
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
const primaryColour =
block.isShadow() ? block.getParent()!.getColour() : block.getColour();
const borderColour = block.isShadow() ?
(block.getParent() as BlockSvg).style.colourTertiary :
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
const primaryColour = block.isShadow()
? block.getParent()!.getColour()
: block.getColour();
const borderColour = block.isShadow()
? (block.getParent() as BlockSvg).style.colourTertiary
: (this.sourceBlock_ as BlockSvg).style.colourTertiary;
dropDownDiv.setColour(primaryColour, borderColour);
}
@@ -284,8 +300,10 @@ export class FieldDropdown extends Field<string> {
if (this.selectedMenuItem) {
this.menu_!.setHighlighted(this.selectedMenuItem);
style.scrollIntoContainerView(
this.selectedMenuItem.getElement()!, dropDownDiv.getContentDiv(),
true);
this.selectedMenuItem.getElement()!,
dropDownDiv.getContentDiv(),
true
);
}
this.applyColour();
@@ -397,16 +415,21 @@ export class FieldDropdown extends Field<string> {
* @param newValue The input value.
* @returns A valid language-neutral option, or null if invalid.
*/
protected override doClassValidation_(newValue?: string): string|null {
protected override doClassValidation_(newValue?: string): string | null {
const options = this.getOptions(true);
const isValueValid = options.some((option) => option[1] === newValue);
if (!isValueValid) {
if (this.sourceBlock_) {
console.warn(
'Cannot set the dropdown\'s value to an unavailable option.' +
' Block type: ' + this.sourceBlock_.type +
', Field name: ' + this.name + ', Value: ' + newValue);
"Cannot set the dropdown's value to an unavailable option." +
' Block type: ' +
this.sourceBlock_.type +
', Field name: ' +
this.name +
', Value: ' +
newValue
);
}
return null;
}
@@ -422,7 +445,7 @@ export class FieldDropdown extends Field<string> {
protected override doValueUpdate_(newValue: string) {
super.doValueUpdate_(newValue);
const options = this.getOptions(true);
for (let i = 0, option; option = options[i]; i++) {
for (let i = 0, option; (option = options[i]); i++) {
if (option[1] === this.value_) {
this.selectedOption = option;
}
@@ -481,7 +504,10 @@ export class FieldDropdown extends Field<string> {
}
this.imageElement!.style.display = '';
this.imageElement!.setAttributeNS(
dom.XLINK_NS, 'xlink:href', imageJson.src);
dom.XLINK_NS,
'xlink:href',
imageJson.src
);
this.imageElement!.setAttribute('height', String(imageJson.height));
this.imageElement!.setAttribute('width', String(imageJson.width));
@@ -491,21 +517,25 @@ export class FieldDropdown extends Field<string> {
// Height and width include the border rect.
const hasBorder = !!this.borderRect_;
const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING);
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING
);
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let arrowWidth = 0;
if (this.svgArrow) {
arrowWidth = this.positionSVGArrow(
imageWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
imageWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2
);
} else {
arrowWidth = dom.getFastTextWidth(
this.arrow as SVGTSpanElement,
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
this.arrow as SVGTSpanElement,
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY
);
}
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
@@ -535,19 +565,24 @@ export class FieldDropdown extends Field<string> {
// Height and width include the border rect.
const hasBorder = !!this.borderRect_;
const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.getConstants()!.FIELD_TEXT_HEIGHT);
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.getConstants()!.FIELD_TEXT_HEIGHT
);
const textWidth = dom.getFastTextWidth(
this.getTextElement(), this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
this.getTextElement(),
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY
);
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let arrowWidth = 0;
if (this.svgArrow) {
arrowWidth = this.positionSVGArrow(
textWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
textWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2
);
}
this.size_.width = textWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
@@ -571,13 +606,16 @@ export class FieldDropdown extends Field<string> {
throw new UnattachedFieldError();
}
const hasBorder = !!this.borderRect_;
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
const arrowX = block.RTL ? xPadding : x + textPadding;
this.svgArrow.setAttribute(
'transform', 'translate(' + arrowX + ',' + y + ')');
'transform',
'translate(' + arrowX + ',' + y + ')'
);
return svgArrowSize + textPadding;
}
@@ -588,7 +626,7 @@ export class FieldDropdown extends Field<string> {
*
* @returns Selected option text.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
if (!this.selectedOption) {
return null;
}
@@ -610,9 +648,10 @@ export class FieldDropdown extends Field<string> {
static fromJson(options: FieldDropdownFromJsonConfig): FieldDropdown {
if (!options.options) {
throw new Error(
'options are required for the dropdown field. The ' +
'options are required for the dropdown field. The ' +
'options property must be assigned an array of ' +
'[humanReadableValue, languageNeutralValue] tuples.');
'[humanReadableValue, languageNeutralValue] tuples.'
);
}
// `this` might be a subclass of FieldDropdown if that class doesn't
// override the static fromJson method.
@@ -647,7 +686,7 @@ export type MenuGeneratorFunction = (this: FieldDropdown) => MenuOption[];
* Either an array of menu options or a function that generates an array of
* menu options for FieldDropdown or its descendants.
*/
export type MenuGenerator = MenuOption[]|MenuGeneratorFunction;
export type MenuGenerator = MenuOption[] | MenuGeneratorFunction;
/**
* Config options for the dropdown field.
@@ -691,8 +730,11 @@ const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2;
* Factor out common words in statically defined options.
* Create prefix and/or suffix labels.
*/
function trimOptions(options: MenuOption[]):
{options: MenuOption[]; prefix?: string; suffix?: string;} {
function trimOptions(options: MenuOption[]): {
options: MenuOption[];
prefix?: string;
suffix?: string;
} {
let hasImages = false;
const trimmedOptions = options.map(([label, value]): MenuOption => {
if (typeof label === 'string') {
@@ -702,9 +744,10 @@ function trimOptions(options: MenuOption[]):
hasImages = true;
// Copy the image properties so they're not influenced by the original.
// NOTE: No need to deep copy since image properties are only 1 level deep.
const imageLabel = label.alt !== null ?
{...label, alt: parsing.replaceMessageReferences(label.alt)} :
{...label};
const imageLabel =
label.alt !== null
? {...label, alt: parsing.replaceMessageReferences(label.alt)}
: {...label};
return [imageLabel, value];
});
@@ -717,16 +760,20 @@ function trimOptions(options: MenuOption[]):
const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest);
const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest);
if ((!prefixLength && !suffixLength) ||
(shortest <= prefixLength + suffixLength)) {
if (
(!prefixLength && !suffixLength) ||
shortest <= prefixLength + suffixLength
) {
// One or more strings will entirely vanish if we proceed. Abort.
return {options: stringOptions};
}
const prefix =
prefixLength ? stringLabels[0].substring(0, prefixLength - 1) : undefined;
const suffix =
suffixLength ? stringLabels[0].substr(1 - suffixLength) : undefined;
const prefix = prefixLength
? stringLabels[0].substring(0, prefixLength - 1)
: undefined;
const suffix = suffixLength
? stringLabels[0].substr(1 - suffixLength)
: undefined;
return {
options: applyTrim(stringOptions, prefixLength, suffixLength),
prefix,
@@ -745,12 +792,13 @@ function trimOptions(options: MenuOption[]):
* @returns A new array with all of the option text trimmed.
*/
function applyTrim(
options: [string, string][], prefixLength: number,
suffixLength: number): MenuOption[] {
return options.map(
([text, value]) =>
[text.substring(prefixLength, text.length - suffixLength),
value,
options: [string, string][],
prefixLength: number,
suffixLength: number
): MenuOption[] {
return options.map(([text, value]) => [
text.substring(prefixLength, text.length - suffixLength),
value,
]);
}
@@ -773,23 +821,38 @@ function validateOptions(options: MenuOption[]) {
if (!Array.isArray(tuple)) {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option must be an ' +
'array. Found: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option must be an ' +
'array. Found: ',
tuple
);
} else if (typeof tuple[1] !== 'string') {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option id must be ' +
'a string. Found ' + tuple[1] + ' in: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option id must be ' +
'a string. Found ' +
tuple[1] +
' in: ',
tuple
);
} else if (
tuple[0] && typeof tuple[0] !== 'string' &&
typeof tuple[0].src !== 'string') {
tuple[0] &&
typeof tuple[0] !== 'string' &&
typeof tuple[0].src !== 'string'
) {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option must have a ' +
'string label or image description. Found' + tuple[0] + ' in: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option must have a ' +
'string label or image description. Found' +
tuple[0] +
' in: ',
tuple
);
}
}
if (foundError) {

View File

@@ -32,10 +32,10 @@ export class FieldImage extends Field<string> {
private readonly imageHeight: number;
/** The function to be called when this field is clicked. */
private clickHandler: ((p1: FieldImage) => void)|null = null;
private clickHandler: ((p1: FieldImage) => void) | null = null;
/** The rendered field's image element. */
private imageElement: SVGImageElement|null = null;
private imageElement: SVGImageElement | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -73,22 +73,27 @@ export class FieldImage extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
src: string|typeof Field.SKIP_SETUP, width: string|number,
height: string|number, alt?: string, onClick?: (p1: FieldImage) => void,
flipRtl?: boolean, config?: FieldImageConfig) {
src: string | typeof Field.SKIP_SETUP,
width: string | number,
height: string | number,
alt?: string,
onClick?: (p1: FieldImage) => void,
flipRtl?: boolean,
config?: FieldImageConfig
) {
super(Field.SKIP_SETUP);
const imageHeight = Number(parsing.replaceMessageReferences(height));
const imageWidth = Number(parsing.replaceMessageReferences(width));
if (isNaN(imageHeight) || isNaN(imageWidth)) {
throw Error(
'Height and width values of an image field must cast to' +
' numbers.');
'Height and width values of an image field must cast to' + ' numbers.'
);
}
if (imageHeight <= 0 || imageWidth <= 0) {
throw Error(
'Height and width values of an image field must be greater' +
' than 0.');
'Height and width values of an image field must be greater' + ' than 0.'
);
}
/** The size of the area rendered by the field. */
@@ -134,14 +139,19 @@ export class FieldImage extends Field<string> {
*/
override initView() {
this.imageElement = dom.createSvgElement(
Svg.IMAGE, {
'height': this.imageHeight + 'px',
'width': this.size_.width + 'px',
'alt': this.altText,
},
this.fieldGroup_);
Svg.IMAGE,
{
'height': this.imageHeight + 'px',
'width': this.size_.width + 'px',
'alt': this.altText,
},
this.fieldGroup_
);
this.imageElement.setAttributeNS(
dom.XLINK_NS, 'xlink:href', this.value_ as string);
dom.XLINK_NS,
'xlink:href',
this.value_ as string
);
if (this.clickHandler) {
this.imageElement.style.cursor = 'pointer';
@@ -157,7 +167,7 @@ export class FieldImage extends Field<string> {
* @param newValue The input value.
* @returns A string, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): string|null {
protected override doClassValidation_(newValue?: any): string | null {
if (typeof newValue !== 'string') {
return null;
}
@@ -191,7 +201,7 @@ export class FieldImage extends Field<string> {
*
* @param alt New alt text.
*/
setAlt(alt: string|null) {
setAlt(alt: string | null) {
if (alt === this.altText) {
return;
}
@@ -217,7 +227,7 @@ export class FieldImage extends Field<string> {
* @param func The function that is called when the image is clicked, or null
* to remove.
*/
setOnClickHandler(func: ((p1: FieldImage) => void)|null) {
setOnClickHandler(func: ((p1: FieldImage) => void) | null) {
this.clickHandler = func;
}
@@ -228,7 +238,7 @@ export class FieldImage extends Field<string> {
*
* @returns The image alt text.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
return this.altText;
}
@@ -245,14 +255,21 @@ export class FieldImage extends Field<string> {
static fromJson(options: FieldImageFromJsonConfig): FieldImage {
if (!options.src || !options.width || !options.height) {
throw new Error(
'src, width, and height values for an image field are' +
'required. The width and height must be non-zero.');
'src, width, and height values for an image field are' +
'required. The width and height must be non-zero.'
);
}
// `this` might be a subclass of FieldImage if that class doesn't override
// the static fromJson method.
return new this(
options.src, options.width, options.height, undefined, undefined,
undefined, options);
options.src,
options.width,
options.height,
undefined,
undefined,
undefined,
options
);
}
}

View File

@@ -22,7 +22,12 @@ import * as dialog from './dialog.js';
import * as dom from './utils/dom.js';
import * as dropDownDiv from './dropdowndiv.js';
import * as eventUtils from './events/utils.js';
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import {Msg} from './msg.js';
import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
@@ -36,7 +41,7 @@ import * as renderManagement from './render_management.js';
*
* @internal
*/
type InputTypes = string|number;
type InputTypes = string | number;
/**
* Abstract class for an editable input field.
@@ -44,7 +49,9 @@ type InputTypes = string|number;
* @typeParam T - The value stored on the field.
* @internal
*/
export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
export abstract class FieldInput<T extends InputTypes> extends Field<
string | T
> {
/**
* Pixel size of input border radius.
* Should match blocklyText's border-radius in CSS.
@@ -55,7 +62,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected spellcheck_ = true;
/** The HTML input element. */
protected htmlInput_: HTMLInputElement|null = null;
protected htmlInput_: HTMLInputElement | null = null;
/** True if the field's value is currently being edited via the UI. */
protected isBeingEdited_ = false;
@@ -66,19 +73,19 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected isTextValid_ = false;
/** Key down event data. */
private onKeyDownWrapper_: browserEvents.Data|null = null;
private onKeyDownWrapper_: browserEvents.Data | null = null;
/** Key input event data. */
private onKeyInputWrapper_: browserEvents.Data|null = null;
private onKeyInputWrapper_: browserEvents.Data | null = null;
/**
* Whether the field should consider the whole parent block to be its click
* target.
*/
fullBlockClickTarget_: boolean|null = false;
fullBlockClickTarget_: boolean | null = false;
/** The workspace that this field belongs to. */
protected workspace_: WorkspaceSvg|null = null;
protected workspace_: WorkspaceSvg | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -104,8 +111,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldInputValidator<T>|null, config?: FieldInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldInputValidator<T> | null,
config?: FieldInputConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -137,7 +146,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
let nFields = 0;
let nConnections = 0;
// Count the number of fields, excluding text fields
for (let i = 0, input; input = block.inputList[i]; i++) {
for (let i = 0, input; (input = block.inputList[i]); i++) {
for (let j = 0; input.fieldRow[j]; j++) {
nFields++;
}
@@ -148,7 +157,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// The special case is when this is the only non-label field on the block
// and it has an output but no inputs.
this.fullBlockClickTarget_ =
nFields <= 1 && block.outputConnection && !nConnections;
nFields <= 1 && block.outputConnection && !nConnections;
} else {
this.fullBlockClickTarget_ = false;
}
@@ -178,9 +187,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// Revert value when the text becomes invalid.
this.value_ = this.htmlInput_!.getAttribute('data-untyped-default-value');
if (this.sourceBlock_ && eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.sourceBlock_, 'field', this.name || null, oldValue,
this.value_));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.sourceBlock_,
'field',
this.name || null,
oldValue,
this.value_
)
);
}
}
}
@@ -193,7 +208,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a string.
*/
protected override doValueUpdate_(newValue: string|T) {
protected override doValueUpdate_(newValue: string | T) {
this.isDirty_ = true;
this.isTextValid_ = true;
this.value_ = newValue;
@@ -211,7 +226,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
this.borderRect_.setAttribute('stroke', source.style.colourTertiary);
} else {
source.pathObject.svgPath.setAttribute(
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
'fill',
this.getConstants()!.FIELD_BORDER_RECT_COLOUR
);
}
}
@@ -250,7 +267,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// AnyDuringMigration because: Argument of type 'boolean' is not
// assignable to parameter of type 'string'.
this.htmlInput_.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
'spellcheck',
this.spellcheck_ as AnyDuringMigration
);
}
}
@@ -267,8 +286,11 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*/
protected override showEditor_(_e?: Event, quietInput = false) {
this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace;
if (!quietInput && this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
if (
!quietInput &&
this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)
) {
this.showPromptEditor_();
} else {
this.showInlineEditor_(quietInput);
@@ -282,12 +304,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*/
private showPromptEditor_() {
dialog.prompt(
Msg['CHANGE_VALUE_TITLE'], this.getText(), (text: string|null) => {
// Text is null if user pressed cancel button.
if (text !== null) {
this.setValue(this.getValueFromEditorText_(text));
}
});
Msg['CHANGE_VALUE_TITLE'],
this.getText(),
(text: string | null) => {
// Text is null if user pressed cancel button.
if (text !== null) {
this.setValue(this.getValueFromEditorText_(text));
}
}
);
}
/**
@@ -329,12 +354,14 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
if (!clickTarget) throw new Error('A click target has not been set.');
dom.addClass(clickTarget, 'editing');
const htmlInput = (document.createElement('input'));
const htmlInput = document.createElement('input');
htmlInput.className = 'blocklyHtmlInput';
// AnyDuringMigration because: Argument of type 'boolean' is not assignable
// to parameter of type 'string'.
htmlInput.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
'spellcheck',
this.spellcheck_ as AnyDuringMigration
);
const scale = this.workspace_!.getScale();
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
div!.style.fontSize = fontSize;
@@ -347,15 +374,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// Override border radius.
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
// Pull stroke colour from the existing shadow block
const strokeColour = block.getParent() ?
(block.getParent() as BlockSvg).style.colourTertiary :
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
const strokeColour = block.getParent()
? (block.getParent() as BlockSvg).style.colourTertiary
: (this.sourceBlock_ as BlockSvg).style.colourTertiary;
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
div!.style.borderRadius = borderRadius;
div!.style.transition = 'box-shadow 0.25s ease 0s';
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
div!.style.boxShadow =
'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px';
}
}
htmlInput.style.borderRadius = borderRadius;
@@ -417,10 +444,18 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected bindInputEvents_(htmlInput: HTMLElement) {
// Trap Enter without IME and Esc to hide.
this.onKeyDownWrapper_ = browserEvents.conditionalBind(
htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
htmlInput,
'keydown',
this,
this.onHtmlInputKeyDown_
);
// Resize after every input change.
this.onKeyInputWrapper_ = browserEvents.conditionalBind(
htmlInput, 'input', this, this.onHtmlInputChange_);
htmlInput,
'input',
this,
this.onHtmlInputChange_
);
}
/** Unbind handlers for user input and workspace size changes. */
@@ -446,7 +481,8 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
dropDownDiv.hideWithoutAnimation();
} else if (e.key === 'Escape') {
this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value'));
this.htmlInput_!.getAttribute('data-untyped-default-value')
);
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
} else if (e.key === 'Tab') {
@@ -525,8 +561,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
if (!(block instanceof BlockSvg)) return false;
bumpObjects.bumpIntoBounds(
this.workspace_!,
this.workspace_!.getMetricsManager().getViewMetrics(true), block);
this.workspace_!,
this.workspace_!.getMetricsManager().getViewMetrics(true),
block
);
this.resizeEditor_();
@@ -550,7 +588,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*
* @returns The HTML value if we're editing, otherwise null.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
if (this.isBeingEdited_ && this.htmlInput_) {
// We are currently editing, return the HTML input value instead.
return this.htmlInput_.value;
@@ -611,5 +649,6 @@ export interface FieldInputConfig extends FieldConfig {
* - `undefined` to set `newValue` as is.
* @internal
*/
export type FieldInputValidator<T extends InputTypes> =
FieldValidator<string|T>;
export type FieldInputValidator<T extends InputTypes> = FieldValidator<
string | T
>;

View File

@@ -23,7 +23,7 @@ import * as parsing from './utils/parsing.js';
*/
export class FieldLabel extends Field<string> {
/** The HTML class name to use for this field. */
private class: string|null = null;
private class: string | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -47,8 +47,10 @@ export class FieldLabel extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP, textClass?: string,
config?: FieldLabelConfig) {
value?: string | typeof Field.SKIP_SETUP,
textClass?: string,
config?: FieldLabelConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -83,8 +85,9 @@ export class FieldLabel extends Field<string> {
* @param newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === null || newValue === undefined) {
return null;
}
@@ -96,7 +99,7 @@ export class FieldLabel extends Field<string> {
*
* @param cssClass The new CSS class name, or null to remove.
*/
setClass(cssClass: string|null) {
setClass(cssClass: string | null) {
if (this.textElement_) {
if (this.class) {
dom.removeClass(this.textElement_, this.class);
@@ -139,7 +142,6 @@ export interface FieldLabelConfig extends FieldConfig {
}
// clang-format on
/**
* fromJson config options for the label field.
*/

View File

@@ -14,7 +14,11 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.FieldLabelSerializable');
import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js';
import {
FieldLabel,
FieldLabelConfig,
FieldLabelFromJsonConfig,
} from './field_label.js';
import * as fieldRegistry from './field_registry.js';
import * as parsing from './utils/parsing.js';
@@ -57,8 +61,9 @@ export class FieldLabelSerializable extends FieldLabel {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldLabelFromJsonConfig):
FieldLabelSerializable {
static override fromJson(
options: FieldLabelFromJsonConfig
): FieldLabelSerializable {
const text = parsing.replaceMessageReferences(options.text);
// `this` might be a subclass of FieldLabelSerializable if that class
// doesn't override the static fromJson method.

View File

@@ -15,7 +15,11 @@ goog.declareModuleId('Blockly.FieldMultilineInput');
import * as Css from './css.js';
import {Field, UnattachedFieldError} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js';
import {
FieldTextInput,
FieldTextInputConfig,
FieldTextInputValidator,
} from './field_textinput.js';
import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js';
@@ -31,7 +35,7 @@ export class FieldMultilineInput extends FieldTextInput {
* The SVG group element that will contain a text element for each text row
* when initialized.
*/
textGroup: SVGGElement|null = null;
textGroup: SVGGElement | null = null;
/**
* Defines the maximum number of lines of field.
@@ -58,9 +62,10 @@ export class FieldMultilineInput extends FieldTextInput {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldMultilineInputValidator,
config?: FieldMultilineInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldMultilineInputValidator,
config?: FieldMultilineInputConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -96,8 +101,10 @@ export class FieldMultilineInput extends FieldTextInput {
// needed so the plain-text representation of the XML produced by
// `Blockly.Xml.domToText` will appear on a single line (this is a
// limitation of the plain-text format).
fieldElement.textContent =
(this.getValue() as string).replace(/\n/g, '&#10;');
fieldElement.textContent = (this.getValue() as string).replace(
/\n/g,
'&#10;'
);
return fieldElement;
}
@@ -151,10 +158,12 @@ export class FieldMultilineInput extends FieldTextInput {
override initView() {
this.createBorderRect_();
this.textGroup = dom.createSvgElement(
Svg.G, {
'class': 'blocklyEditableText',
},
this.fieldGroup_);
Svg.G,
{
'class': 'blocklyEditableText',
},
this.fieldGroup_
);
}
/**
@@ -175,8 +184,9 @@ export class FieldMultilineInput extends FieldTextInput {
}
const lines = textLines.split('\n');
textLines = '';
const displayLinesNumber =
this.isOverflowedY_ ? this.maxLines_ : lines.length;
const displayLinesNumber = this.isOverflowedY_
? this.maxLines_
: lines.length;
for (let i = 0; i < displayLinesNumber; i++) {
let text = lines[i];
if (text.length > this.maxDisplayLength) {
@@ -226,7 +236,7 @@ export class FieldMultilineInput extends FieldTextInput {
// Remove all text group children.
let currentChild;
const textGroup = this.textGroup;
while (currentChild = textGroup!.firstChild) {
while ((currentChild = textGroup!.firstChild)) {
textGroup!.removeChild(currentChild);
}
@@ -234,16 +244,19 @@ export class FieldMultilineInput extends FieldTextInput {
const lines = this.getDisplayText_().split('\n');
let y = 0;
for (let i = 0; i < lines.length; i++) {
const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
const lineHeight =
this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
const span = dom.createSvgElement(
Svg.TEXT, {
'class': 'blocklyText blocklyMultilineText',
'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING,
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
},
textGroup);
Svg.TEXT,
{
'class': 'blocklyText blocklyMultilineText',
'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING,
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
},
textGroup
);
span.appendChild(document.createTextNode(lines[i]));
y += lineHeight;
}
@@ -289,13 +302,18 @@ export class FieldMultilineInput extends FieldTextInput {
let totalHeight = 0;
for (let i = 0; i < nodes.length; i++) {
const tspan = nodes[i] as SVGTextElement;
const textWidth =
dom.getFastTextWidth(tspan, fontSize, fontWeight, fontFamily);
const textWidth = dom.getFastTextWidth(
tspan,
fontSize,
fontWeight,
fontFamily
);
if (textWidth > totalWidth) {
totalWidth = textWidth;
}
totalHeight += this.getConstants()!.FIELD_TEXT_HEIGHT +
(i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0);
totalHeight +=
this.getConstants()!.FIELD_TEXT_HEIGHT +
(i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0);
}
if (this.isBeingEdited_) {
// The default width is based on the longest line in the display text,
@@ -304,24 +322,31 @@ export class FieldMultilineInput extends FieldTextInput {
// Otherwise we would get wrong editor width when there are more
// lines than this.maxLines_.
const actualEditorLines = String(this.value_).split('\n');
const dummyTextElement = dom.createSvgElement(
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
const dummyTextElement = dom.createSvgElement(Svg.TEXT, {
'class': 'blocklyText blocklyMultilineText',
});
for (let i = 0; i < actualEditorLines.length; i++) {
if (actualEditorLines[i].length > this.maxDisplayLength) {
actualEditorLines[i] =
actualEditorLines[i].substring(0, this.maxDisplayLength);
actualEditorLines[i] = actualEditorLines[i].substring(
0,
this.maxDisplayLength
);
}
dummyTextElement.textContent = actualEditorLines[i];
const lineWidth = dom.getFastTextWidth(
dummyTextElement, fontSize, fontWeight, fontFamily);
dummyTextElement,
fontSize,
fontWeight,
fontFamily
);
if (lineWidth > totalWidth) {
totalWidth = lineWidth;
}
}
const scrollbarWidth =
this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth;
this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth;
totalWidth += scrollbarWidth;
}
if (this.borderRect_) {
@@ -360,7 +385,7 @@ export class FieldMultilineInput extends FieldTextInput {
const div = WidgetDiv.getDiv();
const scale = this.workspace_!.getScale();
const htmlInput = (document.createElement('textarea'));
const htmlInput = document.createElement('textarea');
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
htmlInput.setAttribute('spellcheck', String(this.spellcheck_));
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
@@ -370,11 +395,12 @@ export class FieldMultilineInput extends FieldTextInput {
htmlInput.style.borderRadius = borderRadius;
const paddingX = this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * scale;
const paddingY =
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale / 2;
htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY +
'px ' + paddingX + 'px';
const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
(this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale) / 2;
htmlInput.style.padding =
paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px';
const lineHeight =
this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
htmlInput.style.lineHeight = lineHeight * scale + 'px';
div!.appendChild(htmlInput);
@@ -401,8 +427,11 @@ export class FieldMultilineInput extends FieldTextInput {
* scrolling functionality is enabled.
*/
setMaxLines(maxLines: number) {
if (typeof maxLines === 'number' && maxLines > 0 &&
maxLines !== this.maxLines_) {
if (
typeof maxLines === 'number' &&
maxLines > 0 &&
maxLines !== this.maxLines_
) {
this.maxLines_ = maxLines;
this.forceRerender();
}
@@ -438,8 +467,9 @@ export class FieldMultilineInput extends FieldTextInput {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldMultilineInputFromJsonConfig):
FieldMultilineInput {
static override fromJson(
options: FieldMultilineInputFromJsonConfig
): FieldMultilineInput {
const text = parsing.replaceMessageReferences(options.text);
// `this` might be a subclass of FieldMultilineInput if that class doesn't
// override the static fromJson method.
@@ -449,7 +479,6 @@ export class FieldMultilineInput extends FieldTextInput {
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
/**
* CSS for multiline field.
*/
@@ -477,8 +506,8 @@ export interface FieldMultilineInputConfig extends FieldTextInputConfig {
/**
* fromJson config options for the multiline input field.
*/
export interface FieldMultilineInputFromJsonConfig extends
FieldMultilineInputConfig {
export interface FieldMultilineInputFromJsonConfig
extends FieldMultilineInputConfig {
text?: string;
}

View File

@@ -14,7 +14,11 @@ goog.declareModuleId('Blockly.FieldNumber');
import {Field} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as aria from './utils/aria.js';
/**
@@ -34,7 +38,7 @@ export class FieldNumber extends FieldInput<number> {
* The number of decimal places to allow, or null to allow any number of
* decimal digits.
*/
private decimalPlaces: number|null = null;
private decimalPlaces: number | null = null;
/** Don't spellcheck numbers. Our validator does a better job. */
protected override spellcheck_ = false;
@@ -59,9 +63,13 @@ export class FieldNumber extends FieldInput<number> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|number|typeof Field.SKIP_SETUP, min?: string|number|null,
max?: string|number|null, precision?: string|number|null,
validator?: FieldNumberValidator|null, config?: FieldNumberConfig) {
value?: string | number | typeof Field.SKIP_SETUP,
min?: string | number | null,
max?: string | number | null,
precision?: string | number | null,
validator?: FieldNumberValidator | null,
config?: FieldNumberConfig
) {
// Pass SENTINEL so that we can define properties before value validation.
super(Field.SKIP_SETUP);
@@ -103,8 +111,10 @@ export class FieldNumber extends FieldInput<number> {
* @param precision Precision for value.
*/
setConstraints(
min: number|string|undefined|null, max: number|string|undefined|null,
precision: number|string|undefined|null) {
min: number | string | undefined | null,
max: number | string | undefined | null,
precision: number | string | undefined | null
) {
this.setMinInternal(min);
this.setMaxInternal(max);
this.setPrecisionInternal(precision);
@@ -117,7 +127,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param min Minimum value.
*/
setMin(min: number|string|undefined|null) {
setMin(min: number | string | undefined | null) {
this.setMinInternal(min);
this.setValue(this.getValue());
}
@@ -128,7 +138,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param min Minimum value.
*/
private setMinInternal(min: number|string|undefined|null) {
private setMinInternal(min: number | string | undefined | null) {
if (min == null) {
this.min_ = -Infinity;
} else {
@@ -155,7 +165,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param max Maximum value.
*/
setMax(max: number|string|undefined|null) {
setMax(max: number | string | undefined | null) {
this.setMaxInternal(max);
this.setValue(this.getValue());
}
@@ -166,7 +176,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param max Maximum value.
*/
private setMaxInternal(max: number|string|undefined|null) {
private setMaxInternal(max: number | string | undefined | null) {
if (max == null) {
this.max_ = Infinity;
} else {
@@ -193,7 +203,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param precision The number to which the field's value is rounded.
*/
setPrecision(precision: number|string|undefined|null) {
setPrecision(precision: number | string | undefined | null) {
this.setPrecisionInternal(precision);
this.setValue(this.getValue());
}
@@ -204,14 +214,15 @@ export class FieldNumber extends FieldInput<number> {
*
* @param precision The number to which the field's value is rounded.
*/
private setPrecisionInternal(precision: number|string|undefined|null) {
private setPrecisionInternal(precision: number | string | undefined | null) {
this.precision_ = Number(precision) || 0;
let precisionString = String(this.precision_);
if (precisionString.indexOf('e') !== -1) {
// String() is fast. But it turns .0000001 into '1e-7'.
// Use the much slower toLocaleString to access all the digits.
precisionString =
this.precision_.toLocaleString('en-US', {maximumFractionDigits: 20});
precisionString = this.precision_.toLocaleString('en-US', {
maximumFractionDigits: 20,
});
}
const decimalIndex = precisionString.indexOf('.');
if (decimalIndex === -1) {
@@ -241,8 +252,9 @@ export class FieldNumber extends FieldInput<number> {
* @param newValue The input value.
* @returns A valid number, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): number
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): number | null {
if (newValue === null) {
return null;
}
@@ -251,7 +263,7 @@ export class FieldNumber extends FieldInput<number> {
newValue = `${newValue}`;
// TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users.
newValue = newValue.replace(/O/ig, '0');
newValue = newValue.replace(/O/gi, '0');
// Strip out thousands separators.
newValue = newValue.replace(/,/g, '');
// Ignore case of 'Infinity'.
@@ -309,7 +321,13 @@ export class FieldNumber extends FieldInput<number> {
// `this` might be a subclass of FieldNumber if that class doesn't override
// the static fromJson method.
return new this(
options.value, undefined, undefined, undefined, undefined, options);
options.value,
undefined,
undefined,
undefined,
undefined,
options
);
}
}

View File

@@ -50,7 +50,7 @@ export function unregister(type: string) {
* given type name
* @internal
*/
export function fromJson<T>(options: RegistryOptions): Field<T>|null {
export function fromJson<T>(options: RegistryOptions): Field<T> | null {
return TEST_ONLY.fromJsonInternal(options);
}
@@ -59,14 +59,16 @@ export function fromJson<T>(options: RegistryOptions): Field<T>|null {
*
* @param options
*/
function fromJsonInternal<T>(options: RegistryOptions): Field<T>|null {
function fromJsonInternal<T>(options: RegistryOptions): Field<T> | null {
const fieldObject = registry.getObject(registry.Type.FIELD, options.type);
if (!fieldObject) {
console.warn(
'Blockly could not create a field of type ' + options['type'] +
'Blockly could not create a field of type ' +
options['type'] +
'. The field is probably not being registered. This could be because' +
' the file is not loaded, the field does not register itself (Issue' +
' #1584), or the registration is not being reached.');
' #1584), or the registration is not being reached.'
);
return null;
} else if (typeof (fieldObject as any).fromJson !== 'function') {
throw new TypeError('returned Field was not a IRegistrableField');

View File

@@ -16,7 +16,11 @@ goog.declareModuleId('Blockly.FieldTextInput');
import './events/events_block_change.js';
import {Field} from './field.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as fieldRegistry from './field_registry.js';
import * as parsing from './utils/parsing.js';
@@ -39,8 +43,10 @@ export class FieldTextInput extends FieldInput<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldTextInputValidator|null, config?: FieldTextInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldTextInputValidator | null,
config?: FieldTextInputConfig
) {
super(value, validator, config);
}
@@ -50,8 +56,9 @@ export class FieldTextInput extends FieldInput<string> {
* @param newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === undefined) {
return null;
}

View File

@@ -17,7 +17,12 @@ import './events/events_block_change.js';
import type {Block} from './block.js';
import {Field, FieldConfig, UnattachedFieldError} from './field.js';
import {FieldDropdown, FieldDropdownValidator, MenuGenerator, MenuOption} from './field_dropdown.js';
import {
FieldDropdown,
FieldDropdownValidator,
MenuGenerator,
MenuOption,
} from './field_dropdown.js';
import * as fieldRegistry from './field_registry.js';
import * as internalConstants from './internal_constants.js';
import type {Menu} from './menu.js';
@@ -33,7 +38,7 @@ import * as Xml from './xml.js';
* Class for a variable's dropdown field.
*/
export class FieldVariable extends FieldDropdown {
protected override menuGenerator_: MenuGenerator|undefined;
protected override menuGenerator_: MenuGenerator | undefined;
defaultVariableName: string;
/** The type of the default variable for this field. */
@@ -43,11 +48,11 @@ export class FieldVariable extends FieldDropdown {
* All of the types of variables that will be available in this field's
* dropdown.
*/
variableTypes: string[]|null = [];
variableTypes: string[] | null = [];
protected override size_: Size;
/** The variable model associated with this field. */
private variable: VariableModel|null = null;
private variable: VariableModel | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -75,9 +80,12 @@ export class FieldVariable extends FieldDropdown {
* for a list of properties this parameter supports.
*/
constructor(
varName: string|null|typeof Field.SKIP_SETUP,
validator?: FieldVariableValidator, variableTypes?: string[],
defaultType?: string, config?: FieldVariableConfig) {
varName: string | null | typeof Field.SKIP_SETUP,
validator?: FieldVariableValidator,
variableTypes?: string[],
defaultType?: string,
config?: FieldVariableConfig
) {
super(Field.SKIP_SETUP);
/**
@@ -131,10 +139,14 @@ export class FieldVariable extends FieldDropdown {
throw new UnattachedFieldError();
}
if (this.variable) {
return; // Initialization already happened.
return; // Initialization already happened.
}
const variable = Variables.getOrCreateVariablePackage(
block.workspace, null, this.defaultVariableName, this.defaultType);
block.workspace,
null,
this.defaultVariableName,
this.defaultType
);
// Don't call setValue because we don't want to cause a rerender.
this.doValueUpdate_(variable.getId());
}
@@ -144,9 +156,11 @@ export class FieldVariable extends FieldDropdown {
if (!block) {
throw new UnattachedFieldError();
}
return super.shouldAddBorderRect_() &&
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
block.type !== 'variables_get');
return (
super.shouldAddBorderRect_() &&
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
block.type !== 'variables_get')
);
}
/**
@@ -164,21 +178,32 @@ export class FieldVariable extends FieldDropdown {
const variableName = fieldElement.textContent;
// 'variabletype' should be lowercase, but until July 2019 it was sometimes
// recorded as 'variableType'. Thus we need to check for both.
const variableType = fieldElement.getAttribute('variabletype') ||
fieldElement.getAttribute('variableType') || '';
const variableType =
fieldElement.getAttribute('variabletype') ||
fieldElement.getAttribute('variableType') ||
'';
// AnyDuringMigration because: Argument of type 'string | null' is not
// assignable to parameter of type 'string | undefined'.
const variable = Variables.getOrCreateVariablePackage(
block.workspace, id, variableName as AnyDuringMigration, variableType);
block.workspace,
id,
variableName as AnyDuringMigration,
variableType
);
// This should never happen :)
if (variableType !== null && variableType !== variable.type) {
throw Error(
'Serialized variable type with id \'' + variable.getId() +
'\' had type ' + variable.type + ', and ' +
"Serialized variable type with id '" +
variable.getId() +
"' had type " +
variable.type +
', and ' +
'does not match variable field that references it: ' +
Xml.domToText(fieldElement) + '.');
Xml.domToText(fieldElement) +
'.'
);
}
this.setValue(variable.getId());
@@ -243,8 +268,11 @@ export class FieldVariable extends FieldDropdown {
}
// This is necessary so that blocks in the flyout can have custom var names.
const variable = Variables.getOrCreateVariablePackage(
block.workspace, state['id'] || null, state['name'],
state['type'] || '');
block.workspace,
state['id'] || null,
state['name'],
state['type'] || ''
);
this.setValue(variable.getId());
}
@@ -265,7 +293,7 @@ export class FieldVariable extends FieldDropdown {
*
* @returns Current variable's ID.
*/
override getValue(): string|null {
override getValue(): string | null {
return this.variable ? this.variable.getId() : null;
}
@@ -287,7 +315,7 @@ export class FieldVariable extends FieldDropdown {
* @returns The selected variable, or null if none was selected.
* @internal
*/
getVariable(): VariableModel|null {
getVariable(): VariableModel | null {
return this.variable;
}
@@ -299,7 +327,7 @@ export class FieldVariable extends FieldDropdown {
*
* @returns Validation function, or null.
*/
override getValidator(): FieldVariableValidator|null {
override getValidator(): FieldVariableValidator | null {
// Validators shouldn't operate on the initial setValue call.
// Normally this is achieved by calling setValidator after setValue, but
// this is not a possibility with variable fields.
@@ -315,8 +343,9 @@ export class FieldVariable extends FieldDropdown {
* @param newValue The ID of the new variable to set.
* @returns The validated ID, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === null) {
return null;
}
@@ -328,15 +357,14 @@ export class FieldVariable extends FieldDropdown {
const variable = Variables.getVariable(block.workspace, newId);
if (!variable) {
console.warn(
'Variable id doesn\'t point to a real variable! ' +
'ID was ' + newId);
"Variable id doesn't point to a real variable! " + 'ID was ' + newId
);
return null;
}
// Type Checks.
const type = variable.type;
if (!this.typeIsAllowed(type)) {
console.warn(
'Variable type doesn\'t match this field! Type was ' + type);
console.warn("Variable type doesn't match this field! Type was " + type);
return null;
}
return newId;
@@ -368,7 +396,7 @@ export class FieldVariable extends FieldDropdown {
private typeIsAllowed(type: string): boolean {
const typeList = this.getVariableTypes();
if (!typeList) {
return true; // If it's null, all types are valid.
return true; // If it's null, all types are valid.
}
for (let i = 0; i < typeList.length; i++) {
if (type === typeList[i]) {
@@ -397,7 +425,8 @@ export class FieldVariable extends FieldDropdown {
// Throw an error if variableTypes is an empty list.
const name = this.getText();
throw Error(
'\'variableTypes\' of field variable ' + name + ' was an empty list');
"'variableTypes' of field variable " + name + ' was an empty list'
);
}
return variableTypes;
}
@@ -412,7 +441,7 @@ export class FieldVariable extends FieldDropdown {
* @param defaultType The type of the variable to create if this field's
* value is not explicitly set. Defaults to ''.
*/
private setTypes(variableTypes: string[]|null = null, defaultType = '') {
private setTypes(variableTypes: string[] | null = null, defaultType = '') {
// If you expected that the default type would be the same as the only entry
// in the variable types array, tell the Blockly team by commenting on
// #1499.
@@ -428,13 +457,17 @@ export class FieldVariable extends FieldDropdown {
}
if (!isInArray) {
throw Error(
'Invalid default type \'' + defaultType + '\' in ' +
'the definition of a FieldVariable');
"Invalid default type '" +
defaultType +
"' in " +
'the definition of a FieldVariable'
);
}
} else if (variableTypes !== null) {
throw Error(
'\'variableTypes\' was not an array in the definition of ' +
'a FieldVariable');
"'variableTypes' was not an array in the definition of " +
'a FieldVariable'
);
}
// Only update the field once all checks pass.
this.defaultType = defaultType;
@@ -467,7 +500,9 @@ export class FieldVariable extends FieldDropdown {
if (id === internalConstants.RENAME_VARIABLE_ID) {
// Rename variable.
Variables.renameVariable(
this.sourceBlock_.workspace, this.variable as VariableModel);
this.sourceBlock_.workspace,
this.variable as VariableModel
);
return;
} else if (id === internalConstants.DELETE_VARIABLE_ID) {
// Delete variable.
@@ -500,8 +535,9 @@ export class FieldVariable extends FieldDropdown {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldVariableFromJsonConfig):
FieldVariable {
static override fromJson(
options: FieldVariableFromJsonConfig
): FieldVariable {
const varName = parsing.replaceMessageReferences(options.variable);
// `this` might be a subclass of FieldVariable if that class doesn't
// override the static fromJson method.
@@ -517,8 +553,9 @@ export class FieldVariable extends FieldDropdown {
static dropdownCreate(this: FieldVariable): MenuOption[] {
if (!this.variable) {
throw Error(
'Tried to call dropdownCreate on a variable field with no' +
' variable selected.');
'Tried to call dropdownCreate on a variable field with no' +
' variable selected.'
);
}
const name = this.getText();
let variableModelList: VariableModel[] = [];
@@ -529,7 +566,7 @@ export class FieldVariable extends FieldDropdown {
for (let i = 0; i < variableTypes.length; i++) {
const variableType = variableTypes[i];
const variables =
this.sourceBlock_.workspace.getVariablesOfType(variableType);
this.sourceBlock_.workspace.getVariablesOfType(variableType);
variableModelList = variableModelList.concat(variables);
}
}
@@ -540,8 +577,10 @@ export class FieldVariable extends FieldDropdown {
// Set the UUID as the internal representation of the variable.
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
}
options.push(
[Msg['RENAME_VARIABLE'], internalConstants.RENAME_VARIABLE_ID]);
options.push([
Msg['RENAME_VARIABLE'],
internalConstants.RENAME_VARIABLE_ID,
]);
if (Msg['DELETE_VARIABLE']) {
options.push([
Msg['DELETE_VARIABLE'].replace('%1', name),

View File

@@ -36,7 +36,6 @@ import {WorkspaceSvg} from './workspace_svg.js';
import * as utilsXml from './utils/xml.js';
import * as Xml from './xml.js';
enum FlyoutItemType {
BLOCK = 'block',
BUTTON = 'button',
@@ -69,7 +68,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* between 0 and 1 specifying the degree of scrolling and a
* similar x property.
*/
protected abstract setMetrics_(xyRatio: {x?: number, y?: number}): void;
protected abstract setMetrics_(xyRatio: {x?: number; y?: number}): void;
/**
* Lay out the blocks in the flyout.
@@ -137,14 +136,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* Function that will be registered as a change listener on the workspace
* to reflow when blocks in the flyout workspace change.
*/
private reflowWrapper: Function|null = null;
private reflowWrapper: Function | null = null;
/**
* Function that disables blocks in the flyout based on max block counts
* allowed in the target workspace. Registered as a change listener on the
* target workspace.
*/
private filterWrapper: Function|null = null;
private filterWrapper: Function | null = null;
/**
* List of background mats that lurk behind each block to catch clicks
@@ -247,12 +246,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* The path around the background of the flyout, which will be filled with a
* background colour.
*/
protected svgBackground_: SVGPathElement|null = null;
protected svgBackground_: SVGPathElement | null = null;
/**
* The root SVG group for the button or label.
*/
protected svgGroup_: SVGGElement|null = null;
protected svgGroup_: SVGGElement | null = null;
/**
* @param workspaceOptions Dictionary of options for the
* workspace.
@@ -263,7 +262,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.workspace_ = new WorkspaceSvg(workspaceOptions);
this.workspace_.setMetricsManager(
new FlyoutMetricsManager(this.workspace_, this));
new FlyoutMetricsManager(this.workspace_, this)
);
this.workspace_.internalIsFlyout = true;
// Keep the workspace visibility consistent with the flyout's visibility.
@@ -326,7 +326,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* put the flyout in. This should be <svg> or <g>.
* @returns The flyout's SVG group.
*/
createDom(tagName: string|Svg<SVGSVGElement>|Svg<SVGGElement>): SVGElement {
createDom(
tagName: string | Svg<SVGSVGElement> | Svg<SVGGElement>
): SVGElement {
/*
<svg | g>
<path class="blocklyFlyoutBackground"/>
@@ -335,15 +337,22 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
// Setting style to display:none to start. The toolbox and flyout
// hide/show code will set up proper visibility and size later.
this.svgGroup_ = dom.createSvgElement(
tagName, {'class': 'blocklyFlyout', 'style': 'display: none'});
this.svgGroup_ = dom.createSvgElement(tagName, {
'class': 'blocklyFlyout',
'style': 'display: none',
});
this.svgBackground_ = dom.createSvgElement(
Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
Svg.PATH,
{'class': 'blocklyFlyoutBackground'},
this.svgGroup_
);
this.svgGroup_.appendChild(this.workspace_.createDom());
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
this.workspace_
.getThemeManager()
.subscribe(this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_
.getThemeManager()
.subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
return this.svgGroup_;
}
@@ -358,26 +367,42 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.workspace_.targetWorkspace = targetWorkspace;
this.workspace_.scrollbar = new ScrollbarPair(
this.workspace_, this.horizontalLayout, !this.horizontalLayout,
'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN);
this.workspace_,
this.horizontalLayout,
!this.horizontalLayout,
'blocklyFlyoutScrollbar',
this.SCROLLBAR_MARGIN
);
this.hide();
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
this.boundEvents.push(
browserEvents.conditionalBind(
this.svgGroup_ as SVGGElement,
'wheel',
this,
this.wheel_
)
);
if (!this.autoClose) {
this.filterWrapper = this.filterForCapacity.bind(this);
this.targetWorkspace.addChangeListener(this.filterWrapper);
}
// Dragging the flyout up and down.
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
this.onMouseDown));
this.boundEvents.push(
browserEvents.conditionalBind(
this.svgBackground_ as SVGPathElement,
'pointerdown',
this,
this.onMouseDown
)
);
// A flyout connected to a workspace doesn't have its own current gesture.
this.workspace_.getGesture =
this.targetWorkspace.getGesture.bind(this.targetWorkspace);
this.workspace_.getGesture = this.targetWorkspace.getGesture.bind(
this.targetWorkspace
);
// Get variables from the main workspace rather than the target workspace.
this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap());
@@ -546,11 +571,15 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// reposition in resize, we need to call setPosition. See issue #4692.
if (scrollbar.hScroll) {
scrollbar.hScroll.setPosition(
scrollbar.hScroll.position.x, scrollbar.hScroll.position.y);
scrollbar.hScroll.position.x,
scrollbar.hScroll.position.y
);
}
if (scrollbar.vScroll) {
scrollbar.vScroll.setPosition(
scrollbar.vScroll.position.x, scrollbar.vScroll.position.y);
scrollbar.vScroll.position.x,
scrollbar.vScroll.position.y
);
}
}
}
@@ -583,7 +612,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* in the flyout. This is either an array of Nodes, a NodeList, a
* toolbox definition, or a string with the name of the dynamic category.
*/
show(flyoutDef: toolbox.FlyoutDefinition|string) {
show(flyoutDef: toolbox.FlyoutDefinition | string) {
this.workspace_.setResizesEnabled(false);
this.hide();
this.clearOldBlocks();
@@ -626,40 +655,42 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* of objects to show in the flyout.
* @returns The list of contents and gaps needed to lay out the flyout.
*/
private createFlyoutInfo(parsedContent: toolbox.FlyoutItemInfoArray):
{contents: FlyoutItem[], gaps: number[]} {
private createFlyoutInfo(parsedContent: toolbox.FlyoutItemInfoArray): {
contents: FlyoutItem[];
gaps: number[];
} {
const contents: FlyoutItem[] = [];
const gaps: number[] = [];
this.permanentlyDisabled.length = 0;
const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
for (const info of parsedContent) {
if ('custom' in info) {
const customInfo = (info as toolbox.DynamicCategoryInfo);
const customInfo = info as toolbox.DynamicCategoryInfo;
const categoryName = customInfo['custom'];
const flyoutDef = this.getDynamicCategoryContents(categoryName);
const parsedDynamicContent =
toolbox.convertFlyoutDefToJsonArray(flyoutDef);
toolbox.convertFlyoutDefToJsonArray(flyoutDef);
const {contents: dynamicContents, gaps: dynamicGaps} =
this.createFlyoutInfo(parsedDynamicContent);
this.createFlyoutInfo(parsedDynamicContent);
contents.push(...dynamicContents);
gaps.push(...dynamicGaps);
}
switch (info['kind'].toUpperCase()) {
case 'BLOCK': {
const blockInfo = (info as toolbox.BlockInfo);
const blockInfo = info as toolbox.BlockInfo;
const block = this.createFlyoutBlock(blockInfo);
contents.push({type: FlyoutItemType.BLOCK, block: block});
this.addBlockGap(blockInfo, gaps, defaultGap);
break;
}
case 'SEP': {
const sepInfo = (info as toolbox.SeparatorInfo);
const sepInfo = info as toolbox.SeparatorInfo;
this.addSeparatorGap(sepInfo, gaps, defaultGap);
break;
}
case 'LABEL': {
const labelInfo = (info as toolbox.LabelInfo);
const labelInfo = info as toolbox.LabelInfo;
// A label is a button with different styling.
const label = this.createButton(labelInfo, /** isLabel */ true);
contents.push({type: FlyoutItemType.BUTTON, button: label});
@@ -667,7 +698,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
break;
}
case 'BUTTON': {
const buttonInfo = (info as toolbox.ButtonInfo);
const buttonInfo = info as toolbox.ButtonInfo;
const button = this.createButton(buttonInfo, /** isLabel */ false);
contents.push({type: FlyoutItemType.BUTTON, button: button});
gaps.push(defaultGap);
@@ -686,17 +717,18 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The definition of the
* flyout in one of its many forms.
*/
private getDynamicCategoryContents(categoryName: string):
toolbox.FlyoutDefinition {
private getDynamicCategoryContents(
categoryName: string
): toolbox.FlyoutDefinition {
// Look up the correct category generation function and call that to get a
// valid XML list.
const fnToApply =
this.workspace_.targetWorkspace!.getToolboxCategoryCallback(
categoryName);
this.workspace_.targetWorkspace!.getToolboxCategoryCallback(categoryName);
if (typeof fnToApply !== 'function') {
throw TypeError(
'Couldn\'t find a callback function when opening' +
' a toolbox category.');
"Couldn't find a callback function when opening" +
' a toolbox category.'
);
}
return fnToApply(this.workspace_.targetWorkspace!);
}
@@ -709,11 +741,16 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The object used to display the button in the
* flyout.
*/
private createButton(btnInfo: toolbox.ButtonOrLabelInfo, isLabel: boolean):
FlyoutButton {
private createButton(
btnInfo: toolbox.ButtonOrLabelInfo,
isLabel: boolean
): FlyoutButton {
const curButton = new FlyoutButton(
this.workspace_, (this.targetWorkspace as WorkspaceSvg), btnInfo,
isLabel);
this.workspace_,
this.targetWorkspace as WorkspaceSvg,
btnInfo,
isLabel
);
return curButton;
}
@@ -727,9 +764,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
private createFlyoutBlock(blockInfo: toolbox.BlockInfo): BlockSvg {
let block;
if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) :
blockInfo['blockxml']) as Element;
const xml = (
typeof blockInfo['blockxml'] === 'string'
? utilsXml.textToDom(blockInfo['blockxml'])
: blockInfo['blockxml']
) as Element;
block = this.getRecycledBlock(xml.getAttribute('type')!);
if (!block) {
block = Xml.domToBlock(xml, this.workspace_);
@@ -738,10 +777,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
block = this.getRecycledBlock(blockInfo['type']!);
if (!block) {
if (blockInfo['enabled'] === undefined) {
blockInfo['enabled'] = blockInfo['disabled'] !== 'true' &&
blockInfo['disabled'] !== true;
blockInfo['enabled'] =
blockInfo['disabled'] !== 'true' && blockInfo['disabled'] !== true;
}
block = blocks.append((blockInfo as blocks.State), this.workspace_);
block = blocks.append(blockInfo as blocks.State, this.workspace_);
}
}
@@ -750,7 +789,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// Do not enable these blocks as a result of capacity filtering.
this.permanentlyDisabled.push(block);
}
return (block as BlockSvg);
return block as BlockSvg;
}
/**
@@ -761,7 +800,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The recycled block, or undefined if
* one could not be recycled.
*/
private getRecycledBlock(blockType: string): BlockSvg|undefined {
private getRecycledBlock(blockType: string): BlockSvg | undefined {
let index = -1;
for (let i = 0; i < this.recycledBlocks.length; i++) {
if (this.recycledBlocks[i].type === blockType) {
@@ -781,14 +820,19 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* next.
*/
private addBlockGap(
blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) {
blockInfo: toolbox.BlockInfo,
gaps: number[],
defaultGap: number
) {
let gap;
if (blockInfo['gap']) {
gap = parseInt(String(blockInfo['gap']));
} else if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) :
blockInfo['blockxml']) as Element;
const xml = (
typeof blockInfo['blockxml'] === 'string'
? utilsXml.textToDom(blockInfo['blockxml'])
: blockInfo['blockxml']
) as Element;
gap = parseInt(xml.getAttribute('gap')!);
}
gaps.push(!gap || isNaN(gap) ? defaultGap : gap);
@@ -804,7 +848,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* element.
*/
private addSeparatorGap(
sepInfo: toolbox.SeparatorInfo, gaps: number[], defaultGap: number) {
sepInfo: toolbox.SeparatorInfo,
gaps: number[],
defaultGap: number
) {
// Change the gap between two toolbox elements.
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
@@ -824,7 +871,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
private clearOldBlocks() {
// Delete any blocks from a previous showing.
const oldBlocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = oldBlocks[i]; i++) {
for (let i = 0, block; (block = oldBlocks[i]); i++) {
if (this.blockIsRecyclable_(block)) {
this.recycleBlock(block);
} else {
@@ -841,7 +888,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
this.mats.length = 0;
// Delete any buttons from a previous showing.
for (let i = 0, button; button = this.buttons_[i]; i++) {
for (let i = 0, button; (button = this.buttons_[i]); i++) {
button.dispose();
}
this.buttons_.length = 0;
@@ -893,19 +940,38 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* as a mat for that block.
*/
protected addBlockListeners_(
root: SVGElement, block: BlockSvg, rect: SVGElement) {
this.listeners.push(browserEvents.conditionalBind(
root, 'pointerdown', null, this.blockMouseDown(block)));
this.listeners.push(browserEvents.conditionalBind(
rect, 'pointerdown', null, this.blockMouseDown(block)));
root: SVGElement,
block: BlockSvg,
rect: SVGElement
) {
this.listeners.push(
browserEvents.bind(root, 'pointerenter', block, block.addSelect));
browserEvents.conditionalBind(
root,
'pointerdown',
null,
this.blockMouseDown(block)
)
);
this.listeners.push(
browserEvents.bind(root, 'pointerleave', block, block.removeSelect));
browserEvents.conditionalBind(
rect,
'pointerdown',
null,
this.blockMouseDown(block)
)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerenter', block, block.addSelect));
browserEvents.bind(root, 'pointerenter', block, block.addSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerleave', block, block.removeSelect));
browserEvents.bind(root, 'pointerleave', block, block.removeSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerenter', block, block.addSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerleave', block, block.removeSelect)
);
}
/**
@@ -972,7 +1038,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.targetWorkspace.hideChaff();
const newVariables = Variables.getAddedVariables(
this.targetWorkspace, variablesBeforeCreation);
this.targetWorkspace,
variablesBeforeCreation
);
if (eventUtils.isEnabled()) {
eventUtils.setGroup(true);
@@ -980,7 +1048,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
for (let i = 0; i < newVariables.length; i++) {
const thisVariable = newVariables[i];
eventUtils.fire(
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable));
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)
);
}
// Block events come after var events, in case they refer to newly created
@@ -1009,8 +1078,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
button.show();
// Clicking on a flyout button or label is a lot like clicking on the
// flyout background.
this.listeners.push(browserEvents.conditionalBind(
buttonSvg, 'pointerdown', this, this.onMouseDown));
this.listeners.push(
browserEvents.conditionalBind(
buttonSvg,
'pointerdown',
this,
this.onMouseDown
)
);
this.buttons_.push(button);
}
@@ -1029,8 +1104,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* the block.
*/
protected createRect_(
block: BlockSvg, x: number, y: number,
blockHW: {height: number, width: number}, index: number): SVGElement {
block: BlockSvg,
x: number,
y: number,
blockHW: {height: number; width: number},
index: number
): SVGElement {
// Create an invisible rectangle under the block to act as a button. Just
// using the block as a button is poor, since blocks have holes in them.
const rect = dom.createSvgElement(Svg.RECT, {
@@ -1065,7 +1144,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
const blockXY = block.getRelativeToSurfaceXY();
rect.setAttribute('y', String(blockXY.y));
rect.setAttribute(
'x', String(this.RTL ? blockXY.x - blockHW.width : blockXY.x));
'x',
String(this.RTL ? blockXY.x - blockHW.width : blockXY.x)
);
}
/**
@@ -1076,10 +1157,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
private filterForCapacity() {
const blocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
if (this.permanentlyDisabled.indexOf(block) === -1) {
const enable = this.targetWorkspace.isCapacityAvailable(
common.getBlockTypeCounts(block));
common.getBlockTypeCounts(block)
);
while (block) {
block.setEnabled(enable);
block = block.getNextBlock();
@@ -1107,8 +1189,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @internal
*/
isScrollable(): boolean {
return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() :
false;
return this.workspace_.scrollbar
? this.workspace_.scrollbar.isVisible()
: false;
}
/**
@@ -1125,10 +1208,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
// Clone the block.
const json = (blocks.save(oldBlock) as blocks.State);
const json = blocks.save(oldBlock) as blocks.State;
// Normallly this resizes leading to weird jumps. Save it for terminateDrag.
targetWorkspace.setResizesEnabled(false);
const block = (blocks.append(json, targetWorkspace) as BlockSvg);
const block = blocks.append(json, targetWorkspace) as BlockSvg;
this.positionNewBlock(oldBlock, block);
@@ -1160,13 +1243,17 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// The position of the old block in pixels relative to the upper left corner
// of the injection div.
const oldBlockOffsetPixels =
Coordinate.sum(flyoutOffsetPixels, oldBlockPos);
const oldBlockOffsetPixels = Coordinate.sum(
flyoutOffsetPixels,
oldBlockPos
);
// The position of the old block in pixels relative to the origin of the
// main workspace.
const finalOffset =
Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels);
const finalOffset = Coordinate.difference(
oldBlockOffsetPixels,
mainOffsetPixels
);
// The position of the old block in main workspace coordinates.
finalOffset.scale(1 / targetWorkspace.scale);
@@ -1180,6 +1267,6 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
export interface FlyoutItem {
type: FlyoutItemType;
button?: FlyoutButton|undefined;
block?: BlockSvg|undefined;
button?: FlyoutButton | undefined;
block?: BlockSvg | undefined;
}

View File

@@ -22,7 +22,6 @@ import {Svg} from './utils/svg.js';
import type * as toolbox from './utils/toolbox.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for a button or label in the flyout.
*/
@@ -39,10 +38,10 @@ export class FlyoutButton {
private readonly text: string;
private readonly position: Coordinate;
private readonly callbackKey: string;
private readonly cssClass: string|null;
private readonly cssClass: string | null;
/** Mouse up event data. */
private onMouseUpWrapper: browserEvents.Data|null = null;
private onMouseUpWrapper: browserEvents.Data | null = null;
info: toolbox.ButtonOrLabelInfo;
/** The width of the button's rect. */
@@ -52,10 +51,10 @@ export class FlyoutButton {
height = 0;
/** The root SVG group for the button or label. */
private svgGroup: SVGGElement|null = null;
private svgGroup: SVGGElement | null = null;
/** The SVG element with the text of the label or button. */
private svgText: SVGTextElement|null = null;
private svgText: SVGTextElement | null = null;
/**
* @param workspace The workspace in which to place this button.
@@ -65,19 +64,22 @@ export class FlyoutButton {
* @internal
*/
constructor(
private readonly workspace: WorkspaceSvg,
private readonly targetWorkspace: WorkspaceSvg,
json: toolbox.ButtonOrLabelInfo, private readonly isLabel_: boolean) {
private readonly workspace: WorkspaceSvg,
private readonly targetWorkspace: WorkspaceSvg,
json: toolbox.ButtonOrLabelInfo,
private readonly isLabel_: boolean
) {
this.text = json['text'];
this.position = new Coordinate(0, 0);
/** The key to the function called when this button is clicked. */
this.callbackKey =
(json as
AnyDuringMigration)['callbackKey'] || /* Check the lower case version
too to satisfy IE */
(json as AnyDuringMigration)['callbackkey'];
(json as AnyDuringMigration)[
'callbackKey'
] /* Check the lower case version
too to satisfy IE */ ||
(json as AnyDuringMigration)['callbackkey'];
/** If specified, a CSS class to add to this button. */
this.cssClass = (json as AnyDuringMigration)['web-class'] || null;
@@ -98,39 +100,49 @@ export class FlyoutButton {
}
this.svgGroup = dom.createSvgElement(
Svg.G, {'class': cssClass}, this.workspace.getCanvas());
Svg.G,
{'class': cssClass},
this.workspace.getCanvas()
);
let shadow;
if (!this.isLabel_) {
// Shadow rectangle (light source does not mirror in RTL).
shadow = dom.createSvgElement(
Svg.RECT, {
'class': 'blocklyFlyoutButtonShadow',
'rx': FlyoutButton.BORDER_RADIUS,
'ry': FlyoutButton.BORDER_RADIUS,
'x': 1,
'y': 1,
},
this.svgGroup!);
Svg.RECT,
{
'class': 'blocklyFlyoutButtonShadow',
'rx': FlyoutButton.BORDER_RADIUS,
'ry': FlyoutButton.BORDER_RADIUS,
'x': 1,
'y': 1,
},
this.svgGroup!
);
}
// Background rectangle.
const rect = dom.createSvgElement(
Svg.RECT, {
'class': this.isLabel_ ? 'blocklyFlyoutLabelBackground' :
'blocklyFlyoutButtonBackground',
'rx': FlyoutButton.BORDER_RADIUS,
'ry': FlyoutButton.BORDER_RADIUS,
},
this.svgGroup!);
Svg.RECT,
{
'class': this.isLabel_
? 'blocklyFlyoutLabelBackground'
: 'blocklyFlyoutButtonBackground',
'rx': FlyoutButton.BORDER_RADIUS,
'ry': FlyoutButton.BORDER_RADIUS,
},
this.svgGroup!
);
const svgText = dom.createSvgElement(
Svg.TEXT, {
'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText',
'x': 0,
'y': 0,
'text-anchor': 'middle',
},
this.svgGroup!);
Svg.TEXT,
{
'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText',
'x': 0,
'y': 0,
'text-anchor': 'middle',
},
this.svgGroup!
);
let text = parsing.replaceMessageReferences(this.text);
if (this.workspace.RTL) {
// Force text to be RTL by adding an RLM.
@@ -139,17 +151,26 @@ export class FlyoutButton {
svgText.textContent = text;
if (this.isLabel_) {
this.svgText = svgText;
this.workspace.getThemeManager().subscribe(
this.svgText, 'flyoutForegroundColour', 'fill');
this.workspace
.getThemeManager()
.subscribe(this.svgText, 'flyoutForegroundColour', 'fill');
}
const fontSize = style.getComputedStyle(svgText, 'fontSize');
const fontWeight = style.getComputedStyle(svgText, 'fontWeight');
const fontFamily = style.getComputedStyle(svgText, 'fontFamily');
this.width = dom.getFastTextWidthWithSizeString(
svgText, fontSize, fontWeight, fontFamily);
const fontMetrics =
dom.measureFontMetrics(text, fontSize, fontWeight, fontFamily);
svgText,
fontSize,
fontWeight,
fontFamily
);
const fontMetrics = dom.measureFontMetrics(
text,
fontSize,
fontWeight,
fontFamily
);
this.height = fontMetrics.height;
if (!this.isLabel_) {
@@ -163,16 +184,20 @@ export class FlyoutButton {
svgText.setAttribute('x', String(this.width / 2));
svgText.setAttribute(
'y',
String(
this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline));
'y',
String(this.height / 2 - fontMetrics.height / 2 + fontMetrics.baseline)
);
this.updateTransform();
// AnyDuringMigration because: Argument of type 'SVGGElement | null' is not
// assignable to parameter of type 'EventTarget'.
this.onMouseUpWrapper = browserEvents.conditionalBind(
this.svgGroup as AnyDuringMigration, 'pointerup', this, this.onMouseUp);
this.svgGroup as AnyDuringMigration,
'pointerup',
this,
this.onMouseUp
);
return this.svgGroup!;
}
@@ -185,8 +210,9 @@ export class FlyoutButton {
/** Update SVG attributes to match internal state. */
private updateTransform() {
this.svgGroup!.setAttribute(
'transform',
'translate(' + this.position.x + ',' + this.position.y + ')');
'transform',
'translate(' + this.position.x + ',' + this.position.y + ')'
);
}
/**
@@ -256,11 +282,15 @@ export class FlyoutButton {
if (this.isLabel_ && this.callbackKey) {
console.warn(
'Labels should not have callbacks. Label text: ' + this.text);
'Labels should not have callbacks. Label text: ' + this.text
);
} else if (
!this.isLabel_ &&
!(this.callbackKey &&
this.targetWorkspace.getButtonCallback(this.callbackKey))) {
!this.isLabel_ &&
!(
this.callbackKey &&
this.targetWorkspace.getButtonCallback(this.callbackKey)
)
) {
console.warn('Buttons should have callbacks. Button text: ' + this.text);
} else if (!this.isLabel_) {
const callback = this.targetWorkspace.getButtonCallback(this.callbackKey);

View File

@@ -24,7 +24,6 @@ import {Rect} from './utils/rect.js';
import * as toolbox from './utils/toolbox.js';
import * as WidgetDiv from './widgetdiv.js';
/**
* Class for a flyout.
*/
@@ -42,7 +41,7 @@ export class HorizontalFlyout extends Flyout {
* @param xyRatio Contains a y property which is a float between 0 and 1
* specifying the degree of scrolling and a similar x property.
*/
protected override setMetrics_(xyRatio: {x: number, y: number}) {
protected override setMetrics_(xyRatio: {x: number; y: number}) {
if (!this.isVisible()) {
return;
}
@@ -53,14 +52,16 @@ export class HorizontalFlyout extends Flyout {
const absoluteMetrics = metricsManager.getAbsoluteMetrics();
if (typeof xyRatio.x === 'number') {
this.workspace_.scrollX =
-(scrollMetrics.left +
(scrollMetrics.width - viewMetrics.width) * xyRatio.x);
this.workspace_.scrollX = -(
scrollMetrics.left +
(scrollMetrics.width - viewMetrics.width) * xyRatio.x
);
}
this.workspace_.translate(
this.workspace_.scrollX + absoluteMetrics.left,
this.workspace_.scrollY + absoluteMetrics.top);
this.workspace_.scrollX + absoluteMetrics.left,
this.workspace_.scrollY + absoluteMetrics.top
);
}
/**
@@ -151,7 +152,9 @@ export class HorizontalFlyout extends Flyout {
private setBackgroundPath(width: number, height: number) {
const atTop = this.toolboxPosition_ === toolbox.Position.TOP;
// Start at top left.
const path: (string|number)[] = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)];
const path: (string | number)[] = [
'M 0,' + (atTop ? 0 : this.CORNER_RADIUS),
];
if (atTop) {
// Top.
@@ -160,24 +163,52 @@ export class HorizontalFlyout extends Flyout {
path.push('v', height);
// Bottom.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
-this.CORNER_RADIUS, this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
1,
-this.CORNER_RADIUS,
this.CORNER_RADIUS
);
path.push('h', -width);
// Left.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
-this.CORNER_RADIUS, -this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
1,
-this.CORNER_RADIUS,
-this.CORNER_RADIUS
);
path.push('z');
} else {
// Top.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
this.CORNER_RADIUS, -this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
1,
this.CORNER_RADIUS,
-this.CORNER_RADIUS
);
path.push('h', width);
// Right.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
this.CORNER_RADIUS, this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
1,
this.CORNER_RADIUS,
this.CORNER_RADIUS
);
path.push('v', height);
// Bottom.
path.push('h', -width - 2 * this.CORNER_RADIUS);
@@ -234,11 +265,11 @@ export class HorizontalFlyout extends Flyout {
contents = contents.reverse();
}
for (let i = 0, item; item = contents[i]; i++) {
for (let i = 0, item; (item = contents[i]); i++) {
if (item.type === 'block') {
const block = item.block;
const allBlocks = block!.getDescendants(false);
for (let j = 0, child; child = allBlocks[j]; j++) {
for (let j = 0, child; (child = allBlocks[j]); j++) {
// Mark blocks as being inside a flyout. This is used to detect and
// prevent the closure of the flyout if the user right-clicks on such
// a block.
@@ -282,12 +313,14 @@ export class HorizontalFlyout extends Flyout {
const dx = currentDragDeltaXY.x;
const dy = currentDragDeltaXY.y;
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
const dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
const dragDirection = (Math.atan2(dy, dx) / Math.PI) * 180;
const range = this.dragAngleRange_;
// Check for up or down dragging.
if (dragDirection < 90 + range && dragDirection > 90 - range ||
dragDirection > -90 - range && dragDirection < -90 + range) {
if (
(dragDirection < 90 + range && dragDirection > 90 - range) ||
(dragDirection > -90 - range && dragDirection < -90 + range)
) {
return true;
}
return false;
@@ -300,7 +333,7 @@ export class HorizontalFlyout extends Flyout {
* @returns The component's bounding box. Null if drag target area should be
* ignored.
*/
override getClientRect(): Rect|null {
override getClientRect(): Rect | null {
if (!this.svgGroup_ || this.autoClose || !this.isVisible()) {
// The bounding rectangle won't compute correctly if the flyout is closed
// and auto-close flyouts aren't valid drag targets (or delete areas).
@@ -318,7 +351,8 @@ export class HorizontalFlyout extends Flyout {
if (this.toolboxPosition_ === toolbox.Position.TOP) {
const height = flyoutRect.height;
return new Rect(-BIG_NUM, top + height, -BIG_NUM, BIG_NUM);
} else { // Bottom.
} else {
// Bottom.
return new Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
}
}
@@ -331,11 +365,11 @@ export class HorizontalFlyout extends Flyout {
this.workspace_.scale = this.getFlyoutScale();
let flyoutHeight = 0;
const blocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
flyoutHeight = Math.max(flyoutHeight, block.getHeightWidth().height);
}
const buttons = this.buttons_;
for (let i = 0, button; button = buttons[i]; i++) {
for (let i = 0, button; (button = buttons[i]); i++) {
flyoutHeight = Math.max(flyoutHeight, button.height);
}
flyoutHeight += this.MARGIN * 1.5;
@@ -343,21 +377,24 @@ export class HorizontalFlyout extends Flyout {
flyoutHeight += Scrollbar.scrollbarThickness;
if (this.height_ !== flyoutHeight) {
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
if (this.rectMap_.has(block)) {
this.moveRectToBlock_(this.rectMap_.get(block)!, block);
}
}
if (this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ &&
this.toolboxPosition_ === toolbox.Position.TOP &&
!this.targetWorkspace!.getToolbox()) {
if (
this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ &&
this.toolboxPosition_ === toolbox.Position.TOP &&
!this.targetWorkspace!.getToolbox()
) {
// This flyout is a simple toolbox. Reposition the workspace so that
// (0,0) is in the correct position relative to the new absolute edge
// (ie toolbox edge).
this.targetWorkspace!.translate(
this.targetWorkspace!.scrollX,
this.targetWorkspace!.scrollY + flyoutHeight);
this.targetWorkspace!.scrollX,
this.targetWorkspace!.scrollY + flyoutHeight
);
}
this.height_ = flyoutHeight;
this.position();
@@ -367,5 +404,7 @@ export class HorizontalFlyout extends Flyout {
}
registry.register(
registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, registry.DEFAULT,
HorizontalFlyout);
registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX,
registry.DEFAULT,
HorizontalFlyout
);

View File

@@ -16,7 +16,6 @@ import type {IFlyout} from './interfaces/i_flyout.js';
import {ContainerRegion, MetricsManager} from './metrics_manager.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Calculates metrics for a flyout's workspace.
* The metrics are mainly used to size scrollbars for the flyout.
@@ -40,8 +39,9 @@ export class FlyoutMetricsManager extends MetricsManager {
*
* @returns The bounding box of the blocks on the workspace.
*/
private getBoundingBox_(): SVGRect|
{height: number, y: number, width: number, x: number} {
private getBoundingBox_():
| SVGRect
| {height: number; y: number; width: number; x: number} {
let blockBoundingBox;
try {
blockBoundingBox = this.workspace_.getCanvas().getBBox();
@@ -68,8 +68,10 @@ export class FlyoutMetricsManager extends MetricsManager {
}
override getScrollMetrics(
opt_getWorkspaceCoordinates?: boolean, opt_viewMetrics?: ContainerRegion,
opt_contentMetrics?: ContainerRegion) {
opt_getWorkspaceCoordinates?: boolean,
opt_viewMetrics?: ContainerRegion,
opt_contentMetrics?: ContainerRegion
) {
const contentMetrics = opt_contentMetrics || this.getContentMetrics();
const margin = this.flyout_.MARGIN * this.workspace_.scale;
const scale = opt_getWorkspaceCoordinates ? this.workspace_.scale : 1;

View File

@@ -24,7 +24,6 @@ import {Rect} from './utils/rect.js';
import * as toolbox from './utils/toolbox.js';
import * as WidgetDiv from './widgetdiv.js';
/**
* Class for a flyout.
*/
@@ -43,7 +42,7 @@ export class VerticalFlyout extends Flyout {
* @param xyRatio Contains a y property which is a float between 0 and 1
* specifying the degree of scrolling and a similar x property.
*/
protected override setMetrics_(xyRatio: {x: number, y: number}) {
protected override setMetrics_(xyRatio: {x: number; y: number}) {
if (!this.isVisible()) {
return;
}
@@ -53,13 +52,15 @@ export class VerticalFlyout extends Flyout {
const absoluteMetrics = metricsManager.getAbsoluteMetrics();
if (typeof xyRatio.y === 'number') {
this.workspace_.scrollY =
-(scrollMetrics.top +
(scrollMetrics.height - viewMetrics.height) * xyRatio.y);
this.workspace_.scrollY = -(
scrollMetrics.top +
(scrollMetrics.height - viewMetrics.height) * xyRatio.y
);
}
this.workspace_.translate(
this.workspace_.scrollX + absoluteMetrics.left,
this.workspace_.scrollY + absoluteMetrics.top);
this.workspace_.scrollX + absoluteMetrics.left,
this.workspace_.scrollY + absoluteMetrics.top
);
}
/**
@@ -132,7 +133,7 @@ export class VerticalFlyout extends Flyout {
const edgeWidth = this.width_ - this.CORNER_RADIUS;
const edgeHeight =
targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS;
targetWorkspaceViewMetrics.height - 2 * this.CORNER_RADIUS;
this.setBackgroundPath(edgeWidth, edgeHeight);
const x = this.getX();
@@ -152,22 +153,37 @@ export class VerticalFlyout extends Flyout {
const totalWidth = width + this.CORNER_RADIUS;
// Decide whether to start on the left or right.
const path: Array<string|number> =
['M ' + (atRight ? totalWidth : 0) + ',0'];
const path: Array<string | number> = [
'M ' + (atRight ? totalWidth : 0) + ',0',
];
// Top.
path.push('h', (atRight ? -width : width));
path.push('h', atRight ? -width : width);
// Rounded corner.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1,
atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS, this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
atRight ? 0 : 1,
atRight ? -this.CORNER_RADIUS : this.CORNER_RADIUS,
this.CORNER_RADIUS
);
// Side closest to workspace.
path.push('v', Math.max(0, height));
// Rounded corner.
path.push(
'a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, atRight ? 0 : 1,
atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS, this.CORNER_RADIUS);
'a',
this.CORNER_RADIUS,
this.CORNER_RADIUS,
0,
0,
atRight ? 0 : 1,
atRight ? this.CORNER_RADIUS : -this.CORNER_RADIUS,
this.CORNER_RADIUS
);
// Bottom.
path.push('h', (atRight ? width : -width));
path.push('h', atRight ? width : -width);
path.push('z');
this.svgBackground_!.setAttribute('d', path.join(' '));
}
@@ -215,11 +231,11 @@ export class VerticalFlyout extends Flyout {
const cursorX = this.RTL ? margin : margin + this.tabWidth_;
let cursorY = margin;
for (let i = 0, item; item = contents[i]; i++) {
for (let i = 0, item; (item = contents[i]); i++) {
if (item.type === 'block') {
const block = item.block;
const allBlocks = block!.getDescendants(false);
for (let j = 0, child; child = allBlocks[j]; j++) {
for (let j = 0, child; (child = allBlocks[j]); j++) {
// Mark blocks as being inside a flyout. This is used to detect and
// prevent the closure of the flyout if the user right-clicks on such
// a block.
@@ -227,13 +243,18 @@ export class VerticalFlyout extends Flyout {
}
const root = block!.getSvgRoot();
const blockHW = block!.getHeightWidth();
const moveX =
block!.outputConnection ? cursorX - this.tabWidth_ : cursorX;
const moveX = block!.outputConnection
? cursorX - this.tabWidth_
: cursorX;
block!.moveBy(moveX, cursorY);
const rect = this.createRect_(
block!, this.RTL ? moveX - blockHW.width : moveX, cursorY, blockHW,
i);
block!,
this.RTL ? moveX - blockHW.width : moveX,
cursorY,
blockHW,
i
);
this.addBlockListeners_(root, block!, rect);
@@ -260,12 +281,15 @@ export class VerticalFlyout extends Flyout {
const dx = currentDragDeltaXY.x;
const dy = currentDragDeltaXY.y;
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
const dragDirection = Math.atan2(dy, dx) / Math.PI * 180;
const dragDirection = (Math.atan2(dy, dx) / Math.PI) * 180;
const range = this.dragAngleRange_;
// Check for left or right dragging.
if (dragDirection < range && dragDirection > -range ||
(dragDirection < -180 + range || dragDirection > 180 - range)) {
if (
(dragDirection < range && dragDirection > -range) ||
dragDirection < -180 + range ||
dragDirection > 180 - range
) {
return true;
}
return false;
@@ -278,7 +302,7 @@ export class VerticalFlyout extends Flyout {
* @returns The component's bounding box. Null if drag target area should be
* ignored.
*/
override getClientRect(): Rect|null {
override getClientRect(): Rect | null {
if (!this.svgGroup_ || this.autoClose || !this.isVisible()) {
// The bounding rectangle won't compute correctly if the flyout is closed
// and auto-close flyouts aren't valid drag targets (or delete areas).
@@ -296,7 +320,8 @@ export class VerticalFlyout extends Flyout {
if (this.toolboxPosition_ === toolbox.Position.LEFT) {
const width = flyoutRect.width;
return new Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, left + width);
} else { // Right
} else {
// Right
return new Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
}
}
@@ -309,14 +334,14 @@ export class VerticalFlyout extends Flyout {
this.workspace_.scale = this.getFlyoutScale();
let flyoutWidth = 0;
const blocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
let width = block.getHeightWidth().width;
if (block.outputConnection) {
width -= this.tabWidth_;
}
flyoutWidth = Math.max(flyoutWidth, width);
}
for (let i = 0, button; button = this.buttons_[i]; i++) {
for (let i = 0, button; (button = this.buttons_[i]); i++) {
flyoutWidth = Math.max(flyoutWidth, button.width);
}
flyoutWidth += this.MARGIN * 1.5 + this.tabWidth_;
@@ -324,7 +349,7 @@ export class VerticalFlyout extends Flyout {
flyoutWidth += Scrollbar.scrollbarThickness;
if (this.width_ !== flyoutWidth) {
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
if (this.RTL) {
// With the flyoutWidth known, right-align the blocks.
const oldX = block.getRelativeToSurfaceXY().x;
@@ -340,23 +365,29 @@ export class VerticalFlyout extends Flyout {
}
if (this.RTL) {
// With the flyoutWidth known, right-align the buttons.
for (let i = 0, button; button = this.buttons_[i]; i++) {
for (let i = 0, button; (button = this.buttons_[i]); i++) {
const y = button.getPosition().y;
const x = flyoutWidth / this.workspace_.scale - button.width -
this.MARGIN - this.tabWidth_;
const x =
flyoutWidth / this.workspace_.scale -
button.width -
this.MARGIN -
this.tabWidth_;
button.moveTo(x, y);
}
}
if (this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ &&
this.toolboxPosition_ === toolbox.Position.LEFT &&
!this.targetWorkspace!.getToolbox()) {
if (
this.targetWorkspace!.toolboxPosition === this.toolboxPosition_ &&
this.toolboxPosition_ === toolbox.Position.LEFT &&
!this.targetWorkspace!.getToolbox()
) {
// This flyout is a simple toolbox. Reposition the workspace so that
// (0,0) is in the correct position relative to the new absolute edge
// (ie toolbox edge).
this.targetWorkspace!.translate(
this.targetWorkspace!.scrollX + flyoutWidth,
this.targetWorkspace!.scrollY);
this.targetWorkspace!.scrollX + flyoutWidth,
this.targetWorkspace!.scrollY
);
}
this.width_ = flyoutWidth;
this.position();
@@ -366,4 +397,7 @@ export class VerticalFlyout extends Flyout {
}
registry.register(
registry.Type.FLYOUTS_VERTICAL_TOOLBOX, registry.DEFAULT, VerticalFlyout);
registry.Type.FLYOUTS_VERTICAL_TOOLBOX,
registry.DEFAULT,
VerticalFlyout
);

View File

@@ -19,7 +19,6 @@ import {Names, NameType} from './names.js';
import * as deprecation from './utils/deprecation.js';
import type {Workspace} from './workspace.js';
/**
* Class for a code generator that translates the blocks into a language.
*
@@ -42,21 +41,21 @@ export class CodeGenerator {
* Any instances of '%1' will be replaced by the block ID that failed.
* E.g. ` checkTimeout(%1);\n`
*/
INFINITE_LOOP_TRAP: string|null = null;
INFINITE_LOOP_TRAP: string | null = null;
/**
* Arbitrary code to inject before every statement.
* Any instances of '%1' will be replaced by the block ID of the statement.
* E.g. `highlight(%1);\n`
*/
STATEMENT_PREFIX: string|null = null;
STATEMENT_PREFIX: string | null = null;
/**
* Arbitrary code to inject after every statement.
* Any instances of '%1' will be replaced by the block ID of the statement.
* E.g. `highlight(%1);\n`
*/
STATEMENT_SUFFIX: string|null = null;
STATEMENT_SUFFIX: string | null = null;
/**
* The method of indenting. Defaults to two spaces, but language generators
@@ -79,7 +78,7 @@ export class CodeGenerator {
* will cause blockToCode to emit a warning if the generator has not been
* initialized. If this flag is untouched, it will have no effect.
*/
isInitialized: boolean|null = null;
isInitialized: boolean | null = null;
/** Comma-separated list of reserved words. */
protected RESERVED_WORDS_ = '';
@@ -100,8 +99,10 @@ export class CodeGenerator {
constructor(name: string) {
this.name_ = name;
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ = new RegExp(
this.FUNCTION_NAME_PLACEHOLDER_,
'g'
);
}
/**
@@ -114,13 +115,14 @@ export class CodeGenerator {
if (!workspace) {
// Backwards compatibility from before there could be multiple workspaces.
console.warn(
'No workspace specified in workspaceToCode call. Guessing.');
'No workspace specified in workspaceToCode call. Guessing.'
);
workspace = common.getMainWorkspace();
}
const code = [];
this.init(workspace);
const blocks = workspace.getTopBlocks(true);
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
let line = this.blockToCode(block);
if (Array.isArray(line)) {
// Value blocks return tuples of code and operator order.
@@ -200,11 +202,14 @@ export class CodeGenerator {
* For value blocks, an array containing the generated code and an
* operator order value. Returns '' if block is null.
*/
blockToCode(block: Block|null, opt_thisOnly?: boolean): string
|[string, number] {
blockToCode(
block: Block | null,
opt_thisOnly?: boolean
): string | [string, number] {
if (this.isInitialized === false) {
console.warn(
'CodeGenerator init was not called before blockToCode was called.');
'CodeGenerator init was not called before blockToCode was called.'
);
}
if (!block) {
return '';
@@ -221,8 +226,13 @@ export class CodeGenerator {
const func = (this as any)[block.type];
if (typeof func !== 'function') {
throw Error(
'Language "' + this.name_ + '" does not know how to generate ' +
'code for block type "' + block.type + '".');
'Language "' +
this.name_ +
'" does not know how to generate ' +
'code for block type "' +
block.type +
'".'
);
}
// First argument to func.call is the value of 'this' in the generator.
// Prior to 24 September 2013 'this' was the only way to access the block.
@@ -278,15 +288,17 @@ export class CodeGenerator {
// Statement blocks must only return code.
if (!Array.isArray(tuple)) {
throw TypeError(
`Expecting tuple from value block: ${targetBlock.type} See ` +
`Expecting tuple from value block: ${targetBlock.type} See ` +
`developers.google.com/blockly/guides/create-custom-blocks/generating-code ` +
`for more information`);
`for more information`
);
}
let code = tuple[0];
const innerOrder = tuple[1];
if (isNaN(innerOrder)) {
throw TypeError(
'Expecting valid order from value block: ' + targetBlock.type);
'Expecting valid order from value block: ' + targetBlock.type
);
}
if (!code) {
return '';
@@ -297,8 +309,10 @@ export class CodeGenerator {
const outerOrderClass = Math.floor(outerOrder);
const innerOrderClass = Math.floor(innerOrder);
if (outerOrderClass <= innerOrderClass) {
if (outerOrderClass === innerOrderClass &&
(outerOrderClass === 0 || outerOrderClass === 99)) {
if (
outerOrderClass === innerOrderClass &&
(outerOrderClass === 0 || outerOrderClass === 99)
) {
// Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
// 0 is the atomic order, 99 is the none order. No parentheses needed.
// In all known languages multiple such code blocks are not order
@@ -310,8 +324,10 @@ export class CodeGenerator {
parensNeeded = true;
// Check for special exceptions.
for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) {
if (this.ORDER_OVERRIDES[i][0] === outerOrder &&
this.ORDER_OVERRIDES[i][1] === innerOrder) {
if (
this.ORDER_OVERRIDES[i][0] === outerOrder &&
this.ORDER_OVERRIDES[i][1] === innerOrder
) {
parensNeeded = false;
break;
}
@@ -343,11 +359,12 @@ export class CodeGenerator {
// Statement blocks must only return code.
if (typeof code !== 'string') {
throw TypeError(
'Expecting code from statement block: ' +
(targetBlock && targetBlock.type));
'Expecting code from statement block: ' +
(targetBlock && targetBlock.type)
);
}
if (code) {
code = this.prefixLines((code), this.INDENT);
code = this.prefixLines(code, this.INDENT);
}
return code;
}
@@ -364,19 +381,26 @@ export class CodeGenerator {
*/
addLoopTrap(branch: string, block: Block): string {
if (this.INFINITE_LOOP_TRAP) {
branch = this.prefixLines(
this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) +
branch;
branch =
this.prefixLines(
this.injectId(this.INFINITE_LOOP_TRAP, block),
this.INDENT
) + branch;
}
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
branch = this.prefixLines(
this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) +
branch;
branch =
this.prefixLines(
this.injectId(this.STATEMENT_SUFFIX, block),
this.INDENT
) + branch;
}
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
branch = branch +
this.prefixLines(
this.injectId(this.STATEMENT_PREFIX, block), this.INDENT);
branch =
branch +
this.prefixLines(
this.injectId(this.STATEMENT_PREFIX, block),
this.INDENT
);
}
return branch;
}
@@ -390,8 +414,8 @@ export class CodeGenerator {
* @returns Code snippet with ID.
*/
injectId(msg: string, block: Block): string {
const id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
return msg.replace(/%1/g, '\'' + id + '\'');
const id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
return msg.replace(/%1/g, "'" + id + "'");
}
/**
@@ -424,17 +448,22 @@ export class CodeGenerator {
* @returns The actual name of the new function. This may differ from
* desiredName if the former has already been taken by the user.
*/
protected provideFunction_(desiredName: string, code: string[]|string):
string {
protected provideFunction_(
desiredName: string,
code: string[] | string
): string {
if (!this.definitions_[desiredName]) {
const functionName =
this.nameDB_!.getDistinctName(desiredName, NameType.PROCEDURE);
const functionName = this.nameDB_!.getDistinctName(
desiredName,
NameType.PROCEDURE
);
this.functionNames_[desiredName] = functionName;
if (Array.isArray(code)) {
code = code.join('\n');
}
let codeText = code.trim().replace(
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
let codeText = code
.trim()
.replace(this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
// Change all ' ' indents into the desired indent.
// To avoid an infinite loop of replacements, change all indents to '\0'
// character first, then replace them all with the indent.
@@ -479,8 +508,11 @@ export class CodeGenerator {
* @param _opt_thisOnly True to generate code for only this statement.
* @returns Code with comments and subsequent blocks added.
*/
protected scrub_(_block: Block, code: string, _opt_thisOnly?: boolean):
string {
protected scrub_(
_block: Block,
code: string,
_opt_thisOnly?: boolean
): string {
// Optionally override
return code;
}
@@ -524,17 +556,16 @@ Object.defineProperties(CodeGenerator.prototype, {
* @deprecated 'variableDB_' was renamed to 'nameDB_' (May 2021).
* @suppress {checkTypes}
*/
variableDB_: ({
variableDB_: {
/** @returns Name database. */
get(this: CodeGenerator): Names |
undefined {
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
return this.nameDB_;
},
get(this: CodeGenerator): Names | undefined {
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
return this.nameDB_;
},
/** @param nameDb New name database. */
set(this: CodeGenerator, nameDb: Names|undefined) {
set(this: CodeGenerator, nameDb: Names | undefined) {
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
this.nameDB_ = nameDb;
},
}),
},
});

View File

@@ -37,7 +37,6 @@ import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
import {WorkspaceDragger} from './workspace_dragger.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Note: In this file "start" refers to pointerdown
* events. "End" refers to pointerup events.
@@ -65,19 +64,19 @@ export class Gesture {
* The bubble that the gesture started on, or null if it did not start on a
* bubble.
*/
private startBubble: IBubble|null = null;
private startBubble: IBubble | null = null;
/**
* The field that the gesture started on, or null if it did not start on a
* field.
*/
private startField: Field|null = null;
private startField: Field | null = null;
/**
* The block that the gesture started on, or null if it did not start on a
* block.
*/
private startBlock: BlockSvg|null = null;
private startBlock: BlockSvg | null = null;
/**
* The block that this gesture targets. If the gesture started on a
@@ -85,14 +84,14 @@ export class Gesture {
* gesture started in the flyout, this is the root block of the block group
* that was clicked or dragged.
*/
private targetBlock: BlockSvg|null = null;
private targetBlock: BlockSvg | null = null;
/**
* The workspace that the gesture started on. There may be multiple
* workspaces on a page; this is more accurate than using
* Blockly.common.getMainWorkspace().
*/
protected startWorkspace_: WorkspaceSvg|null = null;
protected startWorkspace_: WorkspaceSvg | null = null;
/**
* Whether the pointer has at any point moved out of the drag radius.
@@ -109,19 +108,19 @@ export class Gesture {
private boundEvents: browserEvents.Data[] = [];
/** The object tracking a bubble drag, or null if none is in progress. */
private bubbleDragger: BubbleDragger|null = null;
private bubbleDragger: BubbleDragger | null = null;
/** The object tracking a block drag, or null if none is in progress. */
private blockDragger: IBlockDragger|null = null;
private blockDragger: IBlockDragger | null = null;
/**
* The object tracking a workspace or flyout workspace drag, or null if none
* is in progress.
*/
private workspaceDragger: WorkspaceDragger|null = null;
private workspaceDragger: WorkspaceDragger | null = null;
/** The flyout a gesture started in, if any. */
private flyout: IFlyout|null = null;
private flyout: IFlyout | null = null;
/** Boolean for sanity-checking that some code is only called once. */
private calledUpdateIsDragging = false;
@@ -140,7 +139,7 @@ export class Gesture {
private isMultiTouch_ = false;
/** A map of cached points used for tracking multi-touch gestures. */
private cachedPoints = new Map<string, Coordinate|null>();
private cachedPoints = new Map<string, Coordinate | null>();
/**
* This is the ratio between the starting distance between the touch points
@@ -154,7 +153,7 @@ export class Gesture {
private startDistance = 0;
/** Boolean for whether or not the workspace supports pinch-zoom. */
private isPinchZoomEnabled: boolean|null = null;
private isPinchZoomEnabled: boolean | null = null;
/**
* The owner of the dropdownDiv when this gesture first starts.
@@ -162,7 +161,7 @@ export class Gesture {
* act on their events, and some fields care about who owns
* the dropdown.
*/
currentDropdownOwner: Field|null = null;
currentDropdownOwner: Field | null = null;
/**
* @param e The event that kicked off this gesture.
@@ -170,7 +169,9 @@ export class Gesture {
* reference to it.
*/
constructor(
e: PointerEvent, private readonly creatorWorkspace: WorkspaceSvg) {
e: PointerEvent,
private readonly creatorWorkspace: WorkspaceSvg
) {
this.mostRecentEvent = e;
/**
@@ -235,15 +236,18 @@ export class Gesture {
* @returns True if the drag just exceeded the drag radius for the first time.
*/
private updateDragDelta(currentXY: Coordinate): boolean {
this.currentDragDeltaXY =
Coordinate.difference(currentXY, (this.mouseDownXY));
this.currentDragDeltaXY = Coordinate.difference(
currentXY,
this.mouseDownXY
);
if (!this.hasExceededDragRadius) {
const currentDragDelta = Coordinate.magnitude(this.currentDragDeltaXY);
// The flyout has a different drag radius from the rest of Blockly.
const limitRadius =
this.flyout ? config.flyoutDragRadius : config.dragRadius;
const limitRadius = this.flyout
? config.flyoutDragRadius
: config.dragRadius;
this.hasExceededDragRadius = currentDragDelta > limitRadius;
return this.hasExceededDragRadius;
@@ -270,8 +274,10 @@ export class Gesture {
throw new Error(`Cannot update dragging from the flyout because the ' +
'flyout's target workspace is undefined`);
}
if (!this.flyout.isScrollable() ||
this.flyout.isDragTowardWorkspace(this.currentDragDeltaXY)) {
if (
!this.flyout.isScrollable() ||
this.flyout.isDragTowardWorkspace(this.currentDragDeltaXY)
) {
this.startWorkspace_ = this.flyout.targetWorkspace;
this.startWorkspace_.updateScreenCalculationsIfScrolled();
// Start the event group now, so that the same event group is used for
@@ -347,13 +353,14 @@ export class Gesture {
private updateIsDraggingWorkspace() {
if (!this.startWorkspace_) {
throw new Error(
'Cannot update dragging the workspace because the ' +
'start workspace is undefined');
'Cannot update dragging the workspace because the ' +
'start workspace is undefined'
);
}
const wsMovable = this.flyout ?
this.flyout.isScrollable() :
this.startWorkspace_ && this.startWorkspace_.isDraggable();
const wsMovable = this.flyout
? this.flyout.isScrollable()
: this.startWorkspace_ && this.startWorkspace_.isDraggable();
if (!wsMovable) return;
this.workspaceDragger = new WorkspaceDragger(this.startWorkspace_);
@@ -390,10 +397,15 @@ export class Gesture {
/** Create a block dragger and start dragging the selected block. */
private startDraggingBlock() {
const BlockDraggerClass = registry.getClassFromOptions(
registry.Type.BLOCK_DRAGGER, this.creatorWorkspace.options, true);
registry.Type.BLOCK_DRAGGER,
this.creatorWorkspace.options,
true
);
this.blockDragger =
new BlockDraggerClass!((this.targetBlock), (this.startWorkspace_));
this.blockDragger = new BlockDraggerClass!(
this.targetBlock,
this.startWorkspace_
);
this.blockDragger!.startDrag(this.currentDragDeltaXY, this.healStack);
this.blockDragger!.drag(this.mostRecentEvent, this.currentDragDeltaXY);
}
@@ -402,20 +414,26 @@ export class Gesture {
private startDraggingBubble() {
if (!this.startBubble) {
throw new Error(
'Cannot update dragging the bubble because the start ' +
'bubble is undefined');
'Cannot update dragging the bubble because the start ' +
'bubble is undefined'
);
}
if (!this.startWorkspace_) {
throw new Error(
'Cannot update dragging the bubble because the start ' +
'workspace is undefined');
'Cannot update dragging the bubble because the start ' +
'workspace is undefined'
);
}
this.bubbleDragger =
new BubbleDragger(this.startBubble, this.startWorkspace_);
this.bubbleDragger = new BubbleDragger(
this.startBubble,
this.startWorkspace_
);
this.bubbleDragger.startBubbleDrag();
this.bubbleDragger.dragBubble(
this.mostRecentEvent, this.currentDragDeltaXY);
this.mostRecentEvent,
this.currentDragDeltaXY
);
}
/**
@@ -428,11 +446,13 @@ export class Gesture {
doStart(e: PointerEvent) {
if (!this.startWorkspace_) {
throw new Error(
'Cannot start the touch gesture becauase the start ' +
'workspace is undefined');
'Cannot start the touch gesture becauase the start ' +
'workspace is undefined'
);
}
this.isPinchZoomEnabled = this.startWorkspace_.options.zoomOptions &&
this.startWorkspace_.options.zoomOptions.pinch;
this.isPinchZoomEnabled =
this.startWorkspace_.options.zoomOptions &&
this.startWorkspace_.options.zoomOptions.pinch;
if (browserEvents.isTargetInput(e)) {
this.cancel();
@@ -491,15 +511,33 @@ export class Gesture {
* @internal
*/
bindMouseEvents(e: PointerEvent) {
this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointerdown', null, this.handleStart.bind(this),
/* opt_noCaptureIdentifier */ true));
this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointermove', null, this.handleMove.bind(this),
/* opt_noCaptureIdentifier */ true));
this.boundEvents.push(browserEvents.conditionalBind(
document, 'pointerup', null, this.handleUp.bind(this),
/* opt_noCaptureIdentifier */ true));
this.boundEvents.push(
browserEvents.conditionalBind(
document,
'pointerdown',
null,
this.handleStart.bind(this),
/* opt_noCaptureIdentifier */ true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
document,
'pointermove',
null,
this.handleMove.bind(this),
/* opt_noCaptureIdentifier */ true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
document,
'pointerup',
null,
this.handleUp.bind(this),
/* opt_noCaptureIdentifier */ true
)
);
e.preventDefault();
e.stopPropagation();
@@ -530,8 +568,10 @@ export class Gesture {
* @internal
*/
handleMove(e: PointerEvent) {
if ((this.isDragging() && Touch.shouldHandleEvent(e)) ||
!this.isMultiTouch()) {
if (
(this.isDragging() && Touch.shouldHandleEvent(e)) ||
!this.isMultiTouch()
) {
this.updateFromEvent(e);
if (this.workspaceDragger) {
this.workspaceDragger.drag(this.currentDragDeltaXY);
@@ -539,7 +579,9 @@ export class Gesture {
this.blockDragger.drag(this.mostRecentEvent, this.currentDragDeltaXY);
} else if (this.bubbleDragger) {
this.bubbleDragger.dragBubble(
this.mostRecentEvent, this.currentDragDeltaXY);
this.mostRecentEvent,
this.currentDragDeltaXY
);
}
e.preventDefault();
e.stopPropagation();
@@ -618,8 +660,8 @@ export class Gesture {
const pointers = Array.from(this.cachedPoints.keys());
// If two pointers are down, store info
if (pointers.length === 2) {
const point0 = (this.cachedPoints.get(pointers[0]))!;
const point1 = (this.cachedPoints.get(pointers[1]))!;
const point0 = this.cachedPoints.get(pointers[0])!;
const point1 = this.cachedPoints.get(pointers[1])!;
this.startDistance = Coordinate.distance(point0, point1);
this.isMultiTouch_ = true;
e.preventDefault();
@@ -653,23 +695,28 @@ export class Gesture {
private handlePinch(e: PointerEvent) {
const pointers = Array.from(this.cachedPoints.keys());
// Calculate the distance between the two pointers
const point0 = (this.cachedPoints.get(pointers[0]))!;
const point1 = (this.cachedPoints.get(pointers[1]))!;
const point0 = this.cachedPoints.get(pointers[0])!;
const point1 = this.cachedPoints.get(pointers[1])!;
const moveDistance = Coordinate.distance(point0, point1);
const scale = moveDistance / this.startDistance;
if (this.previousScale > 0 && this.previousScale < Infinity) {
const gestureScale = scale - this.previousScale;
const delta = gestureScale > 0 ? gestureScale * ZOOM_IN_MULTIPLIER :
gestureScale * ZOOM_OUT_MULTIPLIER;
const delta =
gestureScale > 0
? gestureScale * ZOOM_IN_MULTIPLIER
: gestureScale * ZOOM_OUT_MULTIPLIER;
if (!this.startWorkspace_) {
throw new Error(
'Cannot handle a pinch because the start workspace ' +
'is undefined');
'Cannot handle a pinch because the start workspace ' + 'is undefined'
);
}
const workspace = this.startWorkspace_;
const position = browserEvents.mouseToSvg(
e, workspace.getParentSvg(), workspace.getInverseScreenCTM());
e,
workspace.getParentSvg(),
workspace.getInverseScreenCTM()
);
workspace.zoom(position.x, position.y, delta);
}
this.previousScale = scale;
@@ -700,7 +747,7 @@ export class Gesture {
* @returns The current touch point coordinate
* @internal
*/
getTouchPoint(e: PointerEvent): Coordinate|null {
getTouchPoint(e: PointerEvent): Coordinate | null {
if (!this.startWorkspace_) {
return null;
}
@@ -733,7 +780,9 @@ export class Gesture {
Touch.longStop();
if (this.bubbleDragger) {
this.bubbleDragger.endBubbleDrag(
this.mostRecentEvent, this.currentDragDeltaXY);
this.mostRecentEvent,
this.currentDragDeltaXY
);
} else if (this.blockDragger) {
this.blockDragger.endDrag(this.mostRecentEvent, this.currentDragDeltaXY);
} else if (this.workspaceDragger) {
@@ -777,8 +826,9 @@ export class Gesture {
handleWsStart(e: PointerEvent, ws: WorkspaceSvg) {
if (this.gestureHasStarted) {
throw Error(
'Tried to call gesture.handleWsStart, ' +
'but the gesture had already been started.');
'Tried to call gesture.handleWsStart, ' +
'but the gesture had already been started.'
);
}
this.setStartWorkspace(ws);
this.mostRecentEvent = e;
@@ -792,7 +842,8 @@ export class Gesture {
*/
private fireWorkspaceClick(ws: WorkspaceSvg) {
eventUtils.fire(
new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace'));
new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace')
);
}
/**
@@ -805,8 +856,9 @@ export class Gesture {
handleFlyoutStart(e: PointerEvent, flyout: IFlyout) {
if (this.gestureHasStarted) {
throw Error(
'Tried to call gesture.handleFlyoutStart, ' +
'but the gesture had already been started.');
'Tried to call gesture.handleFlyoutStart, ' +
'but the gesture had already been started.'
);
}
this.setStartFlyout(flyout);
this.handleWsStart(e, flyout.getWorkspace());
@@ -822,8 +874,9 @@ export class Gesture {
handleBlockStart(e: PointerEvent, block: BlockSvg) {
if (this.gestureHasStarted) {
throw Error(
'Tried to call gesture.handleBlockStart, ' +
'but the gesture had already been started.');
'Tried to call gesture.handleBlockStart, ' +
'but the gesture had already been started.'
);
}
this.setStartBlock(block);
this.mostRecentEvent = e;
@@ -839,8 +892,9 @@ export class Gesture {
handleBubbleStart(e: PointerEvent, bubble: IBubble) {
if (this.gestureHasStarted) {
throw Error(
'Tried to call gesture.handleBubbleStart, ' +
'but the gesture had already been started.');
'Tried to call gesture.handleBubbleStart, ' +
'but the gesture had already been started.'
);
}
this.setStartBubble(bubble);
this.mostRecentEvent = e;
@@ -863,8 +917,8 @@ export class Gesture {
private doFieldClick() {
if (!this.startField) {
throw new Error(
'Cannot do a field click because the start field is ' +
'undefined');
'Cannot do a field click because the start field is ' + 'undefined'
);
}
// Only show the editor if the field's editor wasn't already open
@@ -882,8 +936,8 @@ export class Gesture {
if (this.flyout && this.flyout.autoClose) {
if (!this.targetBlock) {
throw new Error(
'Cannot do a block click because the target block is ' +
'undefined');
'Cannot do a block click because the target block is ' + 'undefined'
);
}
if (this.targetBlock.isEnabled()) {
if (!eventUtils.getGroup()) {
@@ -895,12 +949,16 @@ export class Gesture {
} else {
if (!this.startWorkspace_) {
throw new Error(
'Cannot do a block click because the start workspace ' +
'is undefined');
'Cannot do a block click because the start workspace ' +
'is undefined'
);
}
// Clicks events are on the start block, even if it was a shadow.
const event = new (eventUtils.get(eventUtils.CLICK))(
this.startBlock, this.startWorkspace_.id, 'block');
this.startBlock,
this.startWorkspace_.id,
'block'
);
eventUtils.fire(event);
}
this.bringBlockToFront();
@@ -948,8 +1006,9 @@ export class Gesture {
setStartField<T>(field: Field<T>) {
if (this.gestureHasStarted) {
throw Error(
'Tried to call gesture.setStartField, ' +
'but the gesture had already been started.');
'Tried to call gesture.setStartField, ' +
'but the gesture had already been started.'
);
}
if (!this.startField) {
this.startField = field as Field;
@@ -1063,10 +1122,14 @@ export class Gesture {
* @returns Whether this gesture was a click on a field.
*/
private isFieldClick(): boolean {
const fieldClickable =
this.startField ? this.startField.isClickable() : false;
return fieldClickable && !this.hasExceededDragRadius &&
(!this.flyout || !this.flyout.autoClose);
const fieldClickable = this.startField
? this.startField.isClickable()
: false;
return (
fieldClickable &&
!this.hasExceededDragRadius &&
(!this.flyout || !this.flyout.autoClose)
);
}
/**
@@ -1077,7 +1140,7 @@ export class Gesture {
*/
private isWorkspaceClick(): boolean {
const onlyTouchedWorkspace =
!this.startBlock && !this.startBubble && !this.startField;
!this.startBlock && !this.startBubble && !this.startField;
return onlyTouchedWorkspace && !this.hasExceededDragRadius;
}
@@ -1092,8 +1155,9 @@ export class Gesture {
* @internal
*/
isDragging(): boolean {
return !!this.workspaceDragger || !!this.blockDragger ||
!!this.bubbleDragger;
return (
!!this.workspaceDragger || !!this.blockDragger || !!this.bubbleDragger
);
}
/**
@@ -1129,7 +1193,7 @@ export class Gesture {
* @returns The dragger that is currently in use or null if no drag is in
* progress.
*/
getCurrentDragger(): WorkspaceDragger|BubbleDragger|IBlockDragger|null {
getCurrentDragger(): WorkspaceDragger | BubbleDragger | IBlockDragger | null {
return this.blockDragger ?? this.workspaceDragger ?? this.bubbleDragger;
}
@@ -1140,7 +1204,7 @@ export class Gesture {
*/
static inProgress(): boolean {
const workspaces = common.getAllWorkspaces();
for (let i = 0, workspace; workspace = workspaces[i]; i++) {
for (let i = 0, workspace; (workspace = workspaces[i]); i++) {
// Not actually necessarily a WorkspaceSvg, but it doesn't matter b/c
// we're just checking if the property exists. Theoretically we would
// want to use instanceof, but that causes a circular dependency.

View File

@@ -17,7 +17,6 @@ import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
import {GridOptions} from './options.js';
/**
* Class for a workspace's grid.
*/
@@ -45,7 +44,7 @@ export class Grid {
this.line1 = pattern.firstChild as SVGElement;
/** The vertical grid line, if it exists. */
this.line2 = this.line1 && this.line1.nextSibling as SVGElement;
this.line2 = this.line1 && (this.line1.nextSibling as SVGElement);
/** Whether blocks should snap to the grid. */
this.snapToGrid = options['snap'] ?? false;
@@ -118,8 +117,13 @@ export class Grid {
* @param y2 The new y end position of the line (in px).
*/
private setLineAttributes(
line: SVGElement, width: number, x1: number, x2: number, y1: number,
y2: number) {
line: SVGElement,
width: number,
x1: number,
x2: number,
y1: number,
y2: number
) {
if (line) {
line.setAttribute('stroke-width', `${width}`);
line.setAttribute('x1', `${x1}`);
@@ -151,8 +155,11 @@ export class Grid {
* @returns The SVG element for the grid pattern.
* @internal
*/
static createDom(rnd: string, gridOptions: GridOptions, defs: SVGElement):
SVGElement {
static createDom(
rnd: string,
gridOptions: GridOptions,
defs: SVGElement
): SVGElement {
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
<rect stroke="#888" />
@@ -160,16 +167,23 @@ export class Grid {
</pattern>
*/
const gridPattern = dom.createSvgElement(
Svg.PATTERN,
{'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'},
defs);
Svg.PATTERN,
{'id': 'blocklyGridPattern' + rnd, 'patternUnits': 'userSpaceOnUse'},
defs
);
// x1, y1, x1, x2 properties will be set later in update.
if ((gridOptions['length'] ?? 1) > 0 && (gridOptions['spacing'] ?? 0) > 0) {
dom.createSvgElement(
Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern);
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern
);
if (gridOptions['length'] ?? 1 > 1) {
dom.createSvgElement(
Svg.LINE, {'stroke': gridOptions['colour']}, gridPattern);
Svg.LINE,
{'stroke': gridOptions['colour']},
gridPattern
);
}
} else {
// Edge 16 doesn't handle empty patterns

View File

@@ -22,14 +22,13 @@ import {Svg} from './utils/svg.js';
import * as svgMath from './utils/svg_math.js';
import * as deprecation from './utils/deprecation.js';
/**
* Class for an icon.
*/
export abstract class Icon {
protected block_: BlockSvg|null;
protected block_: BlockSvg | null;
/** The icon SVG group. */
iconGroup_: SVGGElement|null = null;
iconGroup_: SVGGElement | null = null;
/** Whether this icon gets hidden when the block is collapsed. */
collapseHidden = true;
@@ -38,17 +37,20 @@ export abstract class Icon {
readonly SIZE = 17;
/** Bubble UI (if visible). */
protected bubble_: Bubble|null = null;
protected bubble_: Bubble | null = null;
/** Absolute coordinate of icon's center. */
protected iconXY_: Coordinate|null = null;
protected iconXY_: Coordinate | null = null;
/** @param block The block associated with this icon. */
constructor(block: BlockSvg|null) {
constructor(block: BlockSvg | null) {
if (!block) {
deprecation.warn(
'Calling the Icon constructor with a null block', 'version 9',
'version 10', 'a non-null block');
'Calling the Icon constructor with a null block',
'version 9',
'version 10',
'a non-null block'
);
}
this.block_ = block;
}
@@ -64,8 +66,9 @@ export abstract class Icon {
...
</g>
*/
this.iconGroup_ =
dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'});
this.iconGroup_ = dom.createSvgElement(Svg.G, {
'class': 'blocklyIconGroup',
});
if (this.getBlock().isInFlyout) {
dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly');
}
@@ -73,7 +76,11 @@ export abstract class Icon {
this.getBlock().getSvgRoot().appendChild(this.iconGroup_);
browserEvents.conditionalBind(
this.iconGroup_, 'pointerup', this, this.iconClick_);
this.iconGroup_,
'pointerup',
this,
this.iconClick_
);
this.updateEditable();
}
@@ -82,7 +89,7 @@ export abstract class Icon {
if (!this.getBlock().isDeadOrDying()) {
dom.removeNode(this.iconGroup_);
}
this.setVisible(false); // Dispose of and unlink the bubble.
this.setVisible(false); // Dispose of and unlink the bubble.
}
/** Add or remove the UI indicating if this icon may be clicked or not. */
@@ -142,8 +149,9 @@ export abstract class Icon {
const blockXY = this.getBlock().getRelativeToSurfaceXY();
const iconXY = svgMath.getRelativeXY(this.iconGroup_ as SVGElement);
const newXY = new Coordinate(
blockXY.x + iconXY.x + this.SIZE / 2,
blockXY.y + iconXY.y + this.SIZE / 2);
blockXY.x + iconXY.x + this.SIZE / 2,
blockXY.y + iconXY.y + this.SIZE / 2
);
if (!Coordinate.equals(this.getIconLocation(), newXY)) {
this.setIconLocation(newXY);
}
@@ -154,7 +162,7 @@ export abstract class Icon {
*
* @returns Object with x and y properties in workspace coordinates.
*/
getIconLocation(): Coordinate|null {
getIconLocation(): Coordinate | null {
return this.iconXY_;
}

View File

@@ -24,7 +24,7 @@ export abstract class Icon implements IIcon {
protected workspaceLocation: Coordinate = new Coordinate(0, 0);
/** The root svg element visually representing this icon. */
protected svgRoot: SVGGElement|null = null;
protected svgRoot: SVGGElement | null = null;
constructor(protected sourceBlock: Block) {}
@@ -33,13 +33,17 @@ export abstract class Icon implements IIcon {
}
initView(pointerdownListener: (e: PointerEvent) => void): void {
if (this.svgRoot) return; // The icon has already been initialized.
if (this.svgRoot) return; // The icon has already been initialized.
const svgBlock = this.sourceBlock as BlockSvg;
this.svgRoot = dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'});
svgBlock.getSvgRoot().appendChild(this.svgRoot);
browserEvents.conditionalBind(
this.svgRoot, 'pointerdown', this, pointerdownListener);
this.svgRoot,
'pointerdown',
this,
pointerdownListener
);
}
dispose(): void {}
@@ -59,8 +63,9 @@ export abstract class Icon implements IIcon {
updateCollapsed(): void {
if (!this.svgRoot) {
throw new Error(
'Attempt to update the collapsed-ness of an icon before its ' +
'view has been initialized.');
'Attempt to update the collapsed-ness of an icon before its ' +
'view has been initialized.'
);
}
if (this.sourceBlock.isCollapsed()) {
this.svgRoot.style.display = 'none';

View File

@@ -29,7 +29,6 @@ import * as WidgetDiv from './widgetdiv.js';
import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
import {WorkspaceSvg} from './workspace_svg.js';
/**
* Inject a Blockly editor into the specified container element (usually a div).
*
@@ -38,11 +37,13 @@ import {WorkspaceSvg} from './workspace_svg.js';
* @returns Newly created main workspace.
*/
export function inject(
container: Element|string, opt_options?: BlocklyOptions): WorkspaceSvg {
let containerElement: Element|null = null;
container: Element | string,
opt_options?: BlocklyOptions
): WorkspaceSvg {
let containerElement: Element | null = null;
if (typeof container === 'string') {
containerElement =
document.getElementById(container) || document.querySelector(container);
document.getElementById(container) || document.querySelector(container);
} else {
containerElement = container;
}
@@ -50,8 +51,8 @@ export function inject(
if (!document.contains(containerElement)) {
throw Error('Error: container is not in current document');
}
const options = new Options(opt_options || {} as BlocklyOptions);
const subContainer = (document.createElement('div'));
const options = new Options(opt_options || ({} as BlocklyOptions));
const subContainer = document.createElement('div');
subContainer.className = 'injectionDiv';
subContainer.tabIndex = 0;
aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']);
@@ -65,8 +66,12 @@ export function inject(
const workspaceDragSurface = new WorkspaceDragSurfaceSvg(subContainer);
const workspace =
createMainWorkspace(svg, options, blockDragSurface, workspaceDragSurface);
const workspace = createMainWorkspace(
svg,
options,
blockDragSurface,
workspaceDragSurface
);
init(workspace);
@@ -76,7 +81,7 @@ export function inject(
common.svgResize(workspace);
subContainer.addEventListener('focusin', function() {
subContainer.addEventListener('focusin', function () {
common.setMainWorkspace(workspace);
});
@@ -111,15 +116,17 @@ function createDom(container: Element, options: Options): SVGElement {
</svg>
*/
const svg = dom.createSvgElement(
Svg.SVG, {
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklySvg',
'tabindex': '0',
},
container);
Svg.SVG,
{
'xmlns': dom.SVG_NS,
'xmlns:html': dom.HTML_NS,
'xmlns:xlink': dom.XLINK_NS,
'version': '1.1',
'class': 'blocklySvg',
'tabindex': '0',
},
container
);
/*
<defs>
... filters go here ...
@@ -145,11 +152,17 @@ function createDom(container: Element, options: Options): SVGElement {
* @returns Newly created main workspace.
*/
function createMainWorkspace(
svg: SVGElement, options: Options, blockDragSurface: BlockDragSurfaceSvg,
workspaceDragSurface: WorkspaceDragSurfaceSvg): WorkspaceSvg {
svg: SVGElement,
options: Options,
blockDragSurface: BlockDragSurfaceSvg,
workspaceDragSurface: WorkspaceDragSurfaceSvg
): WorkspaceSvg {
options.parentWorkspace = null;
const mainWorkspace =
new WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
const mainWorkspace = new WorkspaceSvg(
options,
blockDragSurface,
workspaceDragSurface
);
const wsOptions = mainWorkspace.options;
mainWorkspace.scale = wsOptions.zoomOptions.startScale;
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
@@ -177,14 +190,16 @@ function createMainWorkspace(
mainWorkspace.addZoomControls();
}
// Register the workspace svg as a UI component.
mainWorkspace.getThemeManager().subscribe(
svg, 'workspaceBackgroundColour', 'background-color');
mainWorkspace
.getThemeManager()
.subscribe(svg, 'workspaceBackgroundColour', 'background-color');
// A null translation will also apply the correct initial scale.
mainWorkspace.translate(0, 0);
mainWorkspace.addChangeListener(
bumpObjects.bumpIntoBoundsHandler(mainWorkspace));
bumpObjects.bumpIntoBoundsHandler(mainWorkspace)
);
// The SVG is now fully assembled.
common.svgResize(mainWorkspace);
@@ -205,23 +220,31 @@ function init(mainWorkspace: WorkspaceSvg) {
// Suppress the browser's context menu.
browserEvents.conditionalBind(
svg.parentNode as Element, 'contextmenu', null, function(e: Event) {
if (!browserEvents.isTargetInput(e)) {
e.preventDefault();
}
});
svg.parentNode as Element,
'contextmenu',
null,
function (e: Event) {
if (!browserEvents.isTargetInput(e)) {
e.preventDefault();
}
}
);
const workspaceResizeHandler =
browserEvents.conditionalBind(window, 'resize', null, function() {
// Don't hide all the chaff. Leave the dropdown and widget divs open if
// possible.
Tooltip.hide();
mainWorkspace.hideComponents(true);
dropDownDiv.repositionForWindowResize();
WidgetDiv.repositionForWindowResize();
common.svgResize(mainWorkspace);
bumpObjects.bumpTopObjectsIntoBounds(mainWorkspace);
});
const workspaceResizeHandler = browserEvents.conditionalBind(
window,
'resize',
null,
function () {
// Don't hide all the chaff. Leave the dropdown and widget divs open if
// possible.
Tooltip.hide();
mainWorkspace.hideComponents(true);
dropDownDiv.repositionForWindowResize();
WidgetDiv.repositionForWindowResize();
common.svgResize(mainWorkspace);
bumpObjects.bumpTopObjectsIntoBounds(mainWorkspace);
}
);
mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);
bindDocumentEvents();
@@ -249,13 +272,18 @@ function init(mainWorkspace: WorkspaceSvg) {
}
if (options.moveOptions && options.moveOptions.scrollbars) {
const horizontalScroll = options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.horizontal;
const verticalScroll = options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.vertical;
const horizontalScroll =
options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.horizontal;
const verticalScroll =
options.moveOptions.scrollbars === true ||
!!options.moveOptions.scrollbars.vertical;
mainWorkspace.scrollbar = new ScrollbarPair(
mainWorkspace, horizontalScroll, verticalScroll,
'blocklyMainWorkspaceScrollbar');
mainWorkspace,
horizontalScroll,
verticalScroll,
'blocklyMainWorkspaceScrollbar'
);
mainWorkspace.scrollbar.resize();
} else {
mainWorkspace.setMetrics({x: 0.5, y: 0.5});
@@ -281,8 +309,10 @@ function onKeyDown(e: KeyboardEvent) {
return;
}
if (browserEvents.isTargetInput(e) ||
mainWorkspace.rendered && !mainWorkspace.isVisible()) {
if (
browserEvents.isTargetInput(e) ||
(mainWorkspace.rendered && !mainWorkspace.isVisible())
) {
// When focused on an HTML text input widget, don't trap any keys.
// Ignore keypresses on rendered workspaces that have been explicitly
// hidden.
@@ -309,9 +339,9 @@ let documentEventsBound = false;
*/
function bindDocumentEvents() {
if (!documentEventsBound) {
browserEvents.conditionalBind(document, 'scroll', null, function() {
browserEvents.conditionalBind(document, 'scroll', null, function () {
const workspaces = common.getAllWorkspaces();
for (let i = 0, workspace; workspace = workspaces[i]; i++) {
for (let i = 0, workspace; (workspace = workspaces[i]); i++) {
if (workspace instanceof WorkspaceSvg) {
workspace.updateInverseScreenCTM();
}
@@ -325,10 +355,14 @@ function bindDocumentEvents() {
// Some iPad versions don't fire resize after portrait to landscape change.
if (userAgent.IPAD) {
browserEvents.conditionalBind(
window, 'orientationchange', document, function() {
// TODO (#397): Fix for multiple Blockly workspaces.
common.svgResize(common.getMainWorkspace() as WorkspaceSvg);
});
window,
'orientationchange',
document,
function () {
// TODO (#397): Fix for multiple Blockly workspaces.
common.svgResize(common.getMainWorkspace() as WorkspaceSvg);
}
);
}
}
documentEventsBound = true;
@@ -343,26 +377,29 @@ function bindDocumentEvents() {
function loadSounds(pathToMedia: string, workspace: WorkspaceSvg) {
const audioMgr = workspace.getAudioManager();
audioMgr.load(
[
pathToMedia + 'click.mp3',
pathToMedia + 'click.wav',
pathToMedia + 'click.ogg',
],
'click');
[
pathToMedia + 'click.mp3',
pathToMedia + 'click.wav',
pathToMedia + 'click.ogg',
],
'click'
);
audioMgr.load(
[
pathToMedia + 'disconnect.wav',
pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.ogg',
],
'disconnect');
[
pathToMedia + 'disconnect.wav',
pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.ogg',
],
'disconnect'
);
audioMgr.load(
[
pathToMedia + 'delete.mp3',
pathToMedia + 'delete.ogg',
pathToMedia + 'delete.wav',
],
'delete');
[
pathToMedia + 'delete.mp3',
pathToMedia + 'delete.ogg',
pathToMedia + 'delete.wav',
],
'delete'
);
// Bind temporary hooks that preload the sounds.
const soundBinds: browserEvents.Data[] = [];
@@ -385,8 +422,22 @@ function loadSounds(pathToMedia: string, workspace: WorkspaceSvg) {
// happens on a click, not a drag, so that's not necessary.
// Android ignores any sound not loaded as a result of a user action.
soundBinds.push(browserEvents.conditionalBind(
document, 'pointermove', null, unbindSounds, true));
soundBinds.push(browserEvents.conditionalBind(
document, 'touchstart', null, unbindSounds, true));
soundBinds.push(
browserEvents.conditionalBind(
document,
'pointermove',
null,
unbindSounds,
true
)
);
soundBinds.push(
browserEvents.conditionalBind(
document,
'touchstart',
null,
unbindSounds,
true
)
);
}

View File

@@ -8,7 +8,6 @@ import type {Block} from '../block.js';
import {Input} from './input.js';
import {inputTypes} from './input_types.js';
/** Represents an input on a block with no connection. */
export class DummyInput extends Input {
readonly type = inputTypes.DUMMY;

View File

@@ -23,7 +23,6 @@ import * as fieldRegistry from '../field_registry.js';
import type {RenderedConnection} from '../rendered_connection.js';
import {inputTypes} from './input_types.js';
/**
* Class for an input with an optional field.
*/
@@ -46,8 +45,10 @@ export class Input {
* optionally construct a connection.
*/
constructor(
public name: string, private sourceBlock: Block,
public connection: Connection|null) {}
public name: string,
private sourceBlock: Block,
public connection: Connection | null
) {}
/**
* Get the source block for this input.
@@ -67,7 +68,7 @@ export class Input {
* field again. Should be unique to the host block.
* @returns The input being append to (to allow chaining).
*/
appendField<T>(field: string|Field<T>, opt_name?: string): Input {
appendField<T>(field: string | Field<T>, opt_name?: string): Input {
this.insertFieldAt(this.fieldRow.length, field, opt_name);
return this;
}
@@ -82,8 +83,11 @@ export class Input {
* field again. Should be unique to the host block.
* @returns The index following the last inserted field.
*/
insertFieldAt<T>(index: number, field: string|Field<T>, opt_name?: string):
number {
insertFieldAt<T>(
index: number,
field: string | Field<T>,
opt_name?: string
): number {
if (index < 0 || index > this.fieldRow.length) {
throw Error('index ' + index + ' out of bounds.');
}
@@ -139,7 +143,7 @@ export class Input {
* @throws {Error} if the field is not present and opt_quiet is false.
*/
removeField(name: string, opt_quiet?: boolean): boolean {
for (let i = 0, field; field = this.fieldRow[i]; i++) {
for (let i = 0, field; (field = this.fieldRow[i]); i++) {
if (field.name === name) {
field.dispose();
this.fieldRow.splice(i, 1);
@@ -184,7 +188,7 @@ export class Input {
}
this.visible = visible;
for (let y = 0, field; field = this.fieldRow[y]; y++) {
for (let y = 0, field; (field = this.fieldRow[y]); y++) {
field.setVisible(visible);
}
if (this.connection) {
@@ -209,7 +213,7 @@ export class Input {
* @internal
*/
markDirty() {
for (let y = 0, field; field = this.fieldRow[y]; y++) {
for (let y = 0, field; (field = this.fieldRow[y]); y++) {
field.markDirty();
}
}
@@ -221,7 +225,7 @@ export class Input {
* types are compatible.
* @returns The input being modified (to allow chaining).
*/
setCheck(check: string|string[]|null): Input {
setCheck(check: string | string[] | null): Input {
if (!this.connection) {
throw Error('This input does not have a connection.');
}
@@ -251,7 +255,7 @@ export class Input {
* @param shadow DOM representation of a block or null.
* @returns The input being modified (to allow chaining).
*/
setShadowDom(shadow: Element|null): Input {
setShadowDom(shadow: Element | null): Input {
if (!this.connection) {
throw Error('This input does not have a connection.');
}
@@ -264,7 +268,7 @@ export class Input {
*
* @returns Shadow DOM representation of a block or null.
*/
getShadowDom(): Element|null {
getShadowDom(): Element | null {
if (!this.connection) {
throw Error('This input does not have a connection.');
}
@@ -274,7 +278,7 @@ export class Input {
/** Initialize the fields on this input. */
init() {
if (!this.sourceBlock.workspace.rendered) {
return; // Headless blocks don't need fields initialized.
return; // Headless blocks don't need fields initialized.
}
for (let i = 0; i < this.fieldRow.length; i++) {
this.fieldRow[i].init();
@@ -287,7 +291,7 @@ export class Input {
* @suppress {checkTypes}
*/
dispose() {
for (let i = 0, field; field = this.fieldRow[i]; i++) {
for (let i = 0, field; (field = this.fieldRow[i]); i++) {
field.dispose();
}
if (this.connection) {

View File

@@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.inputTypes');
import {ConnectionType} from '../connection_type.js';
/**
* Enum for the type of a connection or input.
*/

View File

@@ -9,7 +9,6 @@ import type {Connection} from '../connection.js';
import {Input} from './input.js';
import {inputTypes} from './input_types.js';
/** Represents an input on a block with a statement connection. */
export class StatementInput extends Input {
readonly type = inputTypes.STATEMENT;
@@ -21,7 +20,10 @@ export class StatementInput extends Input {
* @param connection The statement connection for this input.
*/
constructor(
public name: string, block: Block, public connection: Connection) {
public name: string,
block: Block,
public connection: Connection
) {
// Errors are maintained for people not using typescript.
if (!name) throw new Error('Statement inputs must have a non-empty name');
if (!connection) throw new Error('Value inputs must have a connection');

View File

@@ -9,7 +9,6 @@ import type {Connection} from '../connection.js';
import {Input} from './input.js';
import {inputTypes} from './input_types.js';
/** Represents an input on a block with a value connection. */
export class ValueInput extends Input {
readonly type = inputTypes.VALUE;
@@ -21,7 +20,10 @@ export class ValueInput extends Input {
* @param connection The value connection for this input.
*/
constructor(
public name: string, block: Block, public connection: Connection) {
public name: string,
block: Block,
public connection: Connection
) {
// Errors are maintained for people not using typescript.
if (!name) throw new Error('Value inputs must have a non-empty name');
if (!connection) throw new Error('Value inputs must have a connection');

View File

@@ -26,7 +26,6 @@ import type {RenderedConnection} from './rendered_connection.js';
import type {Coordinate} from './utils/coordinate.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/** Represents a nearby valid connection. */
interface CandidateConnection {
/**
@@ -47,10 +46,11 @@ interface CandidateConnection {
* An error message to throw if the block created by createMarkerBlock_ is
* missing any components.
*/
const DUPLICATE_BLOCK_ERROR = 'The insertion marker ' +
'manager tried to create a marker but the result is missing %1. If ' +
'you are using a mutator, make sure your domToMutation method is ' +
'properly defined.';
const DUPLICATE_BLOCK_ERROR =
'The insertion marker ' +
'manager tried to create a marker but the result is missing %1. If ' +
'you are using a mutator, make sure your domToMutation method is ' +
'properly defined.';
/**
* Class that controls updates to connections during drags. It is primarily
@@ -75,14 +75,14 @@ export class InsertionMarkerManager {
* first block.
* Set in initAvailableConnections, if at all.
*/
private lastOnStack: RenderedConnection|null = null;
private lastOnStack: RenderedConnection | null = null;
/**
* The insertion marker corresponding to the last block in the stack, if
* that's not the same as the first block in the stack.
* Set in initAvailableConnections, if at all
*/
private lastMarker: BlockSvg|null = null;
private lastMarker: BlockSvg | null = null;
/**
* The insertion marker that shows up between blocks to show where a block
@@ -94,7 +94,7 @@ export class InsertionMarkerManager {
* Information about the connection that would be made if the dragging block
* were released immediately. Updated on every mouse move.
*/
private activeCandidate: CandidateConnection|null = null;
private activeCandidate: CandidateConnection | null = null;
/**
* Whether the block would be deleted if it were dropped immediately.
@@ -108,13 +108,13 @@ export class InsertionMarkerManager {
* Connection on the insertion marker block that corresponds to
* the active candidate's local connection on the currently dragged block.
*/
private markerConnection: RenderedConnection|null = null;
private markerConnection: RenderedConnection | null = null;
/** The block that currently has an input being highlighted, or null. */
private highlightedBlock: BlockSvg|null = null;
private highlightedBlock: BlockSvg | null = null;
/** The block being faded to indicate replacement, or null. */
private fadedBlock: BlockSvg|null = null;
private fadedBlock: BlockSvg | null = null;
/**
* The connections on the dragging blocks that are available to connect to
@@ -135,8 +135,9 @@ export class InsertionMarkerManager {
this.availableConnections = this.initAvailableConnections();
if (this.lastOnStack) {
this.lastMarker =
this.createMarkerBlock(this.lastOnStack.getSourceBlock());
this.lastMarker = this.createMarkerBlock(
this.lastOnStack.getSourceBlock()
);
}
}
@@ -206,13 +207,13 @@ export class InsertionMarkerManager {
* @param dragTarget The drag target that the block is currently over.
* @internal
*/
update(dxy: Coordinate, dragTarget: IDragTarget|null) {
update(dxy: Coordinate, dragTarget: IDragTarget | null) {
const newCandidate = this.getCandidate(dxy);
this.wouldDeleteBlock = this.shouldDelete(!!newCandidate, dragTarget);
const shouldUpdate =
this.wouldDeleteBlock || this.shouldUpdatePreviews(newCandidate, dxy);
this.wouldDeleteBlock || this.shouldUpdatePreviews(newCandidate, dxy);
if (shouldUpdate) {
// Don't fire events for insertion marker creation or movement.
@@ -254,7 +255,7 @@ export class InsertionMarkerManager {
for (let i = 0; i < sourceBlock.inputList.length; i++) {
const sourceInput = sourceBlock.inputList[i];
if (sourceInput.name === constants.COLLAPSED_INPUT_NAME) {
continue; // Ignore the collapsed input.
continue; // Ignore the collapsed input.
}
const resultInput = result.inputList[i];
if (!resultInput) {
@@ -309,7 +310,9 @@ export class InsertionMarkerManager {
* @returns Whether the preview should be updated.
*/
private shouldUpdatePreviews(
newCandidate: CandidateConnection|null, dxy: Coordinate): boolean {
newCandidate: CandidateConnection | null,
dxy: Coordinate
): boolean {
// Only need to update if we were showing a preview before.
if (!newCandidate) return !!this.activeCandidate;
@@ -319,8 +322,10 @@ export class InsertionMarkerManager {
// We're already showing an insertion marker.
// Decide whether the new connection has higher priority.
const {local: activeLocal, closest: activeClosest} = this.activeCandidate;
if (activeClosest === newCandidate.closest &&
activeLocal === newCandidate.local) {
if (
activeClosest === newCandidate.closest &&
activeLocal === newCandidate.local
) {
// The connection was the same as the current connection.
return false;
}
@@ -330,7 +335,8 @@ export class InsertionMarkerManager {
const curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
// Slightly prefer the existing preview over a new preview.
return (
newCandidate.radius < curDistance - config.currentConnectionPreference);
newCandidate.radius < curDistance - config.currentConnectionPreference
);
}
/**
@@ -341,7 +347,7 @@ export class InsertionMarkerManager {
* @returns An object containing a local connection, a closest connection, and
* a radius.
*/
private getCandidate(dxy: Coordinate): CandidateConnection|null {
private getCandidate(dxy: Coordinate): CandidateConnection | null {
// It's possible that a block has added or removed connections during a
// drag, (e.g. in a drag/move event handler), so let's update the available
// connections. Note that this will be called on every move while dragging,
@@ -383,8 +389,9 @@ export class InsertionMarkerManager {
// insertion marker is created, which could cause the connection became out
// of range. By increasing radiusConnection when a connection already
// exists, we never "lose" the connection from the offset.
return this.activeCandidate ? config.connectingSnapRadius :
config.snapRadius;
return this.activeCandidate
? config.connectingSnapRadius
: config.snapRadius;
}
/**
@@ -395,15 +402,21 @@ export class InsertionMarkerManager {
* @param dragTarget The drag target that the block is currently over.
* @returns Whether dropping the block immediately would delete the block.
*/
private shouldDelete(newCandidate: boolean, dragTarget: IDragTarget|null):
boolean {
private shouldDelete(
newCandidate: boolean,
dragTarget: IDragTarget | null
): boolean {
if (dragTarget) {
const componentManager = this.workspace.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
dragTarget.id,
ComponentManager.Capability.DELETE_AREA
);
if (isDeleteArea) {
return (dragTarget as IDeleteArea)
.wouldDelete(this.topBlock, newCandidate);
return (dragTarget as IDeleteArea).wouldDelete(
this.topBlock,
newCandidate
);
}
}
return false;
@@ -417,16 +430,18 @@ export class InsertionMarkerManager {
* @param newCandidate A new candidate connection that may replace the current
* best candidate.
*/
private maybeShowPreview(newCandidate: CandidateConnection|null) {
if (this.wouldDeleteBlock) return; // Nope, don't add a marker.
if (!newCandidate) return; // Nothing to connect to.
private maybeShowPreview(newCandidate: CandidateConnection | null) {
if (this.wouldDeleteBlock) return; // Nope, don't add a marker.
if (!newCandidate) return; // Nothing to connect to.
const closest = newCandidate.closest;
// Something went wrong and we're trying to connect to an invalid
// connection.
if (closest === this.activeCandidate?.closest ||
closest.getSourceBlock().isInsertionMarker()) {
if (
closest === this.activeCandidate?.closest ||
closest.getSourceBlock().isInsertionMarker()
) {
console.log('Trying to connect to an insertion marker');
return;
}
@@ -445,7 +460,10 @@ export class InsertionMarkerManager {
private showPreview(activeCandidate: CandidateConnection) {
const renderer = this.workspace.getRenderer();
const method = renderer.getConnectionPreviewMethod(
activeCandidate.closest, activeCandidate.local, this.topBlock);
activeCandidate.closest,
activeCandidate.local,
this.topBlock
);
switch (method) {
case InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE:
@@ -474,7 +492,7 @@ export class InsertionMarkerManager {
* @param newCandidate A new candidate connection that may replace the current
* best candidate.
*/
private maybeHidePreview(newCandidate: CandidateConnection|null) {
private maybeHidePreview(newCandidate: CandidateConnection | null) {
// If there's no new preview, remove the old one but don't bother deleting
// it. We might need it later, and this saves disposing of it and recreating
// it.
@@ -483,14 +501,14 @@ export class InsertionMarkerManager {
} else {
if (this.activeCandidate) {
const closestChanged =
this.activeCandidate.closest !== newCandidate.closest;
this.activeCandidate.closest !== newCandidate.closest;
const localChanged = this.activeCandidate.local !== newCandidate.local;
// If there's a new preview and there was a preview before, and either
// connection has changed, remove the old preview.
// Also hide if we had a preview before but now we're going to delete
// instead.
if ((closestChanged || localChanged || this.wouldDeleteBlock)) {
if (closestChanged || localChanged || this.wouldDeleteBlock) {
this.hidePreview();
}
}
@@ -507,8 +525,11 @@ export class InsertionMarkerManager {
*/
private hidePreview() {
const closest = this.activeCandidate?.closest;
if (closest && closest.targetBlock() &&
this.workspace.getRenderer().shouldHighlightConnection(closest)) {
if (
closest &&
closest.targetBlock() &&
this.workspace.getRenderer().shouldHighlightConnection(closest)
) {
closest.unhighlight();
}
this.hideReplacementFade();
@@ -530,13 +551,16 @@ export class InsertionMarkerManager {
let insertionMarker = isLastInStack ? this.lastMarker : this.firstMarker;
if (!insertionMarker) {
throw new Error(
'Cannot show the insertion marker because there is no insertion ' +
'marker block');
'Cannot show the insertion marker because there is no insertion ' +
'marker block'
);
}
let imConn;
try {
imConn =
insertionMarker.getMatchingConnection(local.getSourceBlock(), local);
imConn = insertionMarker.getMatchingConnection(
local.getSourceBlock(),
local
);
} catch (e) {
// It's possible that the number of connections on the local block has
// changed since the insertion marker was originally created. Let's
@@ -546,8 +570,9 @@ export class InsertionMarkerManager {
// might be too slow, so we only do it if necessary.
if (isLastInStack && this.lastOnStack) {
this.disposeInsertionMarker(this.lastMarker);
this.lastMarker =
this.createMarkerBlock(this.lastOnStack.getSourceBlock());
this.lastMarker = this.createMarkerBlock(
this.lastOnStack.getSourceBlock()
);
insertionMarker = this.lastMarker;
} else {
this.disposeInsertionMarker(this.firstMarker);
@@ -557,23 +582,28 @@ export class InsertionMarkerManager {
if (!insertionMarker) {
throw new Error(
'Cannot show the insertion marker because there is no insertion ' +
'marker block');
'Cannot show the insertion marker because there is no insertion ' +
'marker block'
);
}
imConn =
insertionMarker.getMatchingConnection(local.getSourceBlock(), local);
imConn = insertionMarker.getMatchingConnection(
local.getSourceBlock(),
local
);
}
if (!imConn) {
throw new Error(
'Cannot show the insertion marker because there is no ' +
'associated connection');
'Cannot show the insertion marker because there is no ' +
'associated connection'
);
}
if (imConn === this.markerConnection) {
throw new Error(
'Made it to showInsertionMarker_ even though the marker isn\'t ' +
'changing');
"Made it to showInsertionMarker_ even though the marker isn't " +
'changing'
);
}
// Render disconnected from everything else so that we have a valid
@@ -617,8 +647,9 @@ export class InsertionMarkerManager {
if (markerConn.targetConnection) {
throw Error(
'markerConnection still connected at the end of ' +
'disconnectInsertionMarker');
'markerConnection still connected at the end of ' +
'disconnectInsertionMarker'
);
}
this.markerConnection = null;
@@ -646,11 +677,14 @@ export class InsertionMarkerManager {
if (!this.activeCandidate) {
throw new Error(
'Cannot hide the insertion marker outline because ' +
'there is no active candidate');
'Cannot hide the insertion marker outline because ' +
'there is no active candidate'
);
}
this.highlightedBlock.highlightShapeForInput(
this.activeCandidate.closest, false);
this.activeCandidate.closest,
false
);
this.highlightedBlock = null;
}
@@ -665,8 +699,9 @@ export class InsertionMarkerManager {
this.fadedBlock = activeCandidate.closest.targetBlock();
if (!this.fadedBlock) {
throw new Error(
'Cannot show the replacement fade because the ' +
'closest connection does not have a target block');
'Cannot show the replacement fade because the ' +
'closest connection does not have a target block'
);
}
this.fadedBlock.fadeForReplacement(true);
}
@@ -702,7 +737,7 @@ export class InsertionMarkerManager {
/**
* Safely disposes of an insertion marker.
*/
private disposeInsertionMarker(marker: BlockSvg|null) {
private disposeInsertionMarker(marker: BlockSvg | null) {
if (marker) {
eventUtils.disable();
try {

View File

@@ -7,7 +7,6 @@
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.IASTNodeLocation');
/**
* An AST node location interface.
*/

View File

@@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IASTNodeLocationSvg');
import type {IASTNodeLocation} from './i_ast_node_location.js';
/**
* An AST node location SVG interface.
*/
@@ -19,12 +18,12 @@ export interface IASTNodeLocationSvg extends IASTNodeLocation {
*
* @param markerSvg The SVG root of the marker to be added to the SVG group.
*/
setMarkerSvg(markerSvg: SVGElement|null): void;
setMarkerSvg(markerSvg: SVGElement | null): void;
/**
* Add the cursor SVG to this node's SVG group.
*
* @param cursorSvg The SVG root of the cursor to be added to the SVG group.
*/
setCursorSvg(cursorSvg: SVGElement|null): void;
setCursorSvg(cursorSvg: SVGElement | null): void;
}

View File

@@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.IASTNodeLocationWithBlock');
import type {IASTNodeLocation} from './i_ast_node_location.js';
import type {Block} from '../block.js';
/**
* An AST node location that has an associated block.
*/
@@ -20,5 +19,5 @@ export interface IASTNodeLocationWithBlock extends IASTNodeLocation {
*
* @returns The source block.
*/
getSourceBlock(): Block|null;
getSourceBlock(): Block | null;
}

View File

@@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IAutoHideable');
import type {IComponent} from './i_component.js';
/**
* Interface for a component that can be automatically hidden.
*/

View File

@@ -12,7 +12,6 @@ goog.declareModuleId('Blockly.IBubble');
import type {IContextMenu} from './i_contextmenu.js';
import type {IDraggable} from './i_draggable.js';
/**
* A bubble interface.
*/
@@ -56,8 +55,10 @@ export interface IBubble extends IDraggable, IContextMenu {
* or null if no drag surface is in use.
* @param newLoc The location to translate to, in workspace coordinates.
*/
moveDuringDrag(dragSurface: BlockDragSurfaceSvg|null, newLoc: Coordinate):
void;
moveDuringDrag(
dragSurface: BlockDragSurfaceSvg | null,
newLoc: Coordinate
): void;
/**
* Move the bubble to the specified location in workspace coordinates.

View File

@@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.ICollapsibleToolboxItem');
import type {ISelectableToolboxItem} from './i_selectable_toolbox_item.js';
import type {IToolboxItem} from './i_toolbox_item.js';
/**
* Interface for an item in the toolbox that can be collapsed.
*/

View File

@@ -7,7 +7,6 @@
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.IComponent');
/**
* The interface for a workspace component that can be registered with the
* ComponentManager.

View File

@@ -25,8 +25,11 @@ export interface IConnectionChecker {
* @returns Whether the connection is legal.
*/
canConnect(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): boolean;
a: Connection | null,
b: Connection | null,
isDragging: boolean,
opt_distance?: number
): boolean;
/**
* Checks whether the current connection can connect with the target
@@ -41,8 +44,11 @@ export interface IConnectionChecker {
* otherwise.
*/
canConnectWithReason(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): number;
a: Connection | null,
b: Connection | null,
isDragging: boolean,
opt_distance?: number
): number;
/**
* Helper method that translates a connection error code into a string.
@@ -52,8 +58,11 @@ export interface IConnectionChecker {
* @param b The second of the two connections being checked.
* @returns A developer-readable error string.
*/
getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null):
string;
getErrorMessage(
errorCode: number,
a: Connection | null,
b: Connection | null
): string;
/**
* Check that connecting the given connections is safe, meaning that it would
@@ -63,7 +72,7 @@ export interface IConnectionChecker {
* @param b The second of the connections to check.
* @returns An enum with the reason this connection is safe or unsafe.
*/
doSafetyChecks(a: Connection|null, b: Connection|null): number;
doSafetyChecks(a: Connection | null, b: Connection | null): number;
/**
* Check whether this connection is compatible with another connection with
@@ -84,6 +93,9 @@ export interface IConnectionChecker {
* @param distance The maximum allowable distance between connections.
* @returns True if the connection is allowed during a drag.
*/
doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number):
boolean;
doDragChecks(
a: RenderedConnection,
b: RenderedConnection,
distance: number
): boolean;
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.IContextMenu');
export interface IContextMenu {
/**
* Show the context menu for this object.

Some files were not shown because too many files have changed in this diff Show More