mirror of
https://github.com/google/blockly.git
synced 2026-01-10 02:17:09 +01:00
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:
committed by
GitHub
parent
af991f5e1b
commit
88ff901a72
492
core/block.ts
492
core/block.ts
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
260
core/blockly.ts
260
core/blockly.ts
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
316
core/bubble.ts
316
core/bubble.ts
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
148
core/comment.ts
148
core/comment.ts
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Css');
|
||||
|
||||
|
||||
/** Has CSS already been injected? */
|
||||
let injected = false;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
262
core/field.ts
262
core/field.ts
@@ -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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
>;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ' ');
|
||||
fieldElement.textContent = (this.getValue() as string).replace(
|
||||
/\n/g,
|
||||
' '
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
242
core/gesture.ts
242
core/gesture.ts
@@ -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.
|
||||
|
||||
36
core/grid.ts
36
core/grid.ts
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
205
core/inject.ts
205
core/inject.ts
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.inputTypes');
|
||||
|
||||
import {ConnectionType} from '../connection_type.js';
|
||||
|
||||
|
||||
/**
|
||||
* Enum for the type of a connection or input.
|
||||
*/
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.IASTNodeLocation');
|
||||
|
||||
|
||||
/**
|
||||
* An AST node location interface.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ goog.declareModuleId('Blockly.IAutoHideable');
|
||||
|
||||
import type {IComponent} from './i_component.js';
|
||||
|
||||
|
||||
/**
|
||||
* Interface for a component that can be automatically hidden.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user