merge develop into rc/v11.0.0

This commit is contained in:
Beka Westberg
2024-02-02 22:14:46 +00:00
31 changed files with 1214 additions and 385 deletions

View File

@@ -1,4 +1,4 @@
runtime: python37
runtime: python312
handlers:
# Redirect obsolete URLs.

View File

@@ -21,7 +21,6 @@ import * as common from './common.js';
import type {BlockMove} from './events/events_block_move.js';
import * as eventUtils from './events/utils.js';
import type {Icon} from './icons/icon.js';
import {InsertionMarkerManager} from './insertion_marker_manager.js';
import type {IBlockDragger} from './interfaces/i_block_dragger.js';
import type {IDragTarget} from './interfaces/i_drag_target.js';
import * as registry from './registry.js';
@@ -29,6 +28,26 @@ import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import * as layers from './layers.js';
import {ConnectionType, IConnectionPreviewer} from './blockly.js';
import {RenderedConnection} from './rendered_connection.js';
import {config} from './config.js';
import {ComponentManager} from './component_manager.js';
import {IDeleteArea} from './interfaces/i_delete_area.js';
import {Connection} from './connection.js';
import {Block} from './block.js';
import {finishQueuedRenders} from './render_management.js';
/** Represents a nearby valid connection. */
interface ConnectionCandidate {
/** A connection on the dragging stack that is compatible with neighbour. */
local: RenderedConnection;
/** A nearby connection that is compatible with local. */
neighbour: RenderedConnection;
/** The distance between the local connection and the neighbour connection. */
distance: number;
}
/**
* Class for a block dragger. It moves blocks around the workspace when they
@@ -37,7 +56,8 @@ import * as layers from './layers.js';
export class BlockDragger implements IBlockDragger {
/** The top block in the stack that is being dragged. */
protected draggingBlock_: BlockSvg;
protected draggedConnectionManager_: InsertionMarkerManager;
protected connectionPreviewer: IConnectionPreviewer;
/** The workspace on which the block is being dragged. */
protected workspace_: WorkspaceSvg;
@@ -45,6 +65,8 @@ export class BlockDragger implements IBlockDragger {
/** Which drag area the mouse pointer is over, if any. */
private dragTarget_: IDragTarget | null = null;
private connectionCandidate: ConnectionCandidate | null = null;
/** Whether the block would be deleted if dropped immediately. */
protected wouldDeleteBlock_ = false;
protected startXY_: Coordinate;
@@ -55,14 +77,14 @@ export class BlockDragger implements IBlockDragger {
*/
constructor(block: BlockSvg, workspace: WorkspaceSvg) {
this.draggingBlock_ = block;
/** Object that keeps track of connections on dragged blocks. */
this.draggedConnectionManager_ = new InsertionMarkerManager(
this.draggingBlock_,
);
this.workspace_ = workspace;
const previewerConstructor = registry.getClassFromOptions(
registry.Type.CONNECTION_PREVIEWER,
this.workspace_.options,
);
this.connectionPreviewer = new previewerConstructor!(block);
/**
* The location of the top left corner of the dragging block at the
* beginning of the drag in workspace coordinates.
@@ -76,9 +98,7 @@ export class BlockDragger implements IBlockDragger {
* @internal
*/
dispose() {
if (this.draggedConnectionManager_) {
this.draggedConnectionManager_.dispose();
}
this.connectionPreviewer.dispose();
}
/**
@@ -144,7 +164,6 @@ export class BlockDragger implements IBlockDragger {
this.draggingBlock_.translate(newLoc.x, newLoc.y);
blockAnimation.disconnectUiEffect(this.draggingBlock_);
this.draggedConnectionManager_.updateAvailableConnections();
}
/** Fire a UI event at the start of a block drag. */
@@ -162,32 +181,178 @@ export class BlockDragger implements IBlockDragger {
* display accordingly.
*
* @param e The most recent move event.
* @param currentDragDeltaXY How far the pointer has moved from the position
* @param delta How far the pointer has moved from the position
* at the start of the drag, in pixel units.
*/
drag(e: PointerEvent, currentDragDeltaXY: Coordinate) {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
drag(e: PointerEvent, delta: Coordinate) {
const block = this.draggingBlock_;
this.moveBlock(block, delta);
this.updateDragTargets(e, block);
this.wouldDeleteBlock_ = this.wouldDeleteBlock(e, block, delta);
this.updateCursorDuringBlockDrag_();
this.updateConnectionPreview(block, delta);
}
private moveBlock(draggingBlock: BlockSvg, dragDelta: Coordinate) {
const delta = this.pixelsToWorkspaceUnits_(dragDelta);
const newLoc = Coordinate.sum(this.startXY_, delta);
this.draggingBlock_.moveDuringDrag(newLoc);
draggingBlock.moveDuringDrag(newLoc);
}
const oldDragTarget = this.dragTarget_;
this.dragTarget_ = this.workspace_.getDragTarget(e);
private updateDragTargets(e: PointerEvent, draggingBlock: BlockSvg) {
const newDragTarget = this.workspace_.getDragTarget(e);
if (this.dragTarget_ !== newDragTarget) {
this.dragTarget_?.onDragExit(draggingBlock);
newDragTarget?.onDragEnter(draggingBlock);
}
newDragTarget?.onDragOver(draggingBlock);
this.dragTarget_ = newDragTarget;
}
this.draggedConnectionManager_.update(delta, this.dragTarget_);
const oldWouldDeleteBlock = this.wouldDeleteBlock_;
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock;
if (oldWouldDeleteBlock !== this.wouldDeleteBlock_) {
// Prevent unnecessary add/remove class calls.
this.updateCursorDuringBlockDrag_();
/**
* Returns true if we would delete the block if it was dropped at this time,
* false otherwise.
*/
private wouldDeleteBlock(
e: PointerEvent,
draggingBlock: BlockSvg,
delta: Coordinate,
): boolean {
const dragTarget = this.workspace_.getDragTarget(e);
if (!dragTarget) return false;
const componentManager = this.workspace_.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id,
ComponentManager.Capability.DELETE_AREA,
);
if (!isDeleteArea) return false;
return (dragTarget as IDeleteArea).wouldDelete(
draggingBlock,
!!this.getConnectionCandidate(draggingBlock, delta),
);
}
private updateConnectionPreview(draggingBlock: BlockSvg, delta: Coordinate) {
const currCandidate = this.connectionCandidate;
const newCandidate = this.getConnectionCandidate(draggingBlock, delta);
if (!newCandidate) {
this.connectionPreviewer.hidePreview();
this.connectionCandidate = null;
return;
}
const candidate =
currCandidate &&
this.currCandidateIsBetter(currCandidate, delta, newCandidate)
? currCandidate
: newCandidate;
this.connectionCandidate = candidate;
const {local, neighbour} = candidate;
if (
(local.type === ConnectionType.OUTPUT_VALUE ||
local.type === ConnectionType.PREVIOUS_STATEMENT) &&
neighbour.isConnected() &&
!neighbour.targetBlock()!.isInsertionMarker() &&
!this.orphanCanConnectAtEnd(
draggingBlock,
neighbour.targetBlock()!,
local.type,
)
) {
this.connectionPreviewer.previewReplacement(
local,
neighbour,
neighbour.targetBlock()!,
);
return;
}
this.connectionPreviewer.previewConnection(local, neighbour);
}
/**
* Returns true if the given orphan block can connect at the end of the
* top block's stack or row, false otherwise.
*/
private orphanCanConnectAtEnd(
topBlock: BlockSvg,
orphanBlock: BlockSvg,
localType: number,
): boolean {
const orphanConnection =
localType === ConnectionType.OUTPUT_VALUE
? orphanBlock.outputConnection
: orphanBlock.previousConnection;
return !!Connection.getConnectionForOrphanedConnection(
topBlock as Block,
orphanConnection as Connection,
);
}
/**
* Returns true if the current candidate is better than the new candidate.
*
* We slightly prefer the current candidate even if it is farther away.
*/
private currCandidateIsBetter(
currCandiate: ConnectionCandidate,
delta: Coordinate,
newCandidate: ConnectionCandidate,
): boolean {
const {local: currLocal, neighbour: currNeighbour} = currCandiate;
const localPos = new Coordinate(currLocal.x, currLocal.y);
const neighbourPos = new Coordinate(currNeighbour.x, currNeighbour.y);
const distance = Coordinate.distance(
Coordinate.sum(localPos, delta),
neighbourPos,
);
return (
newCandidate.distance > distance - config.currentConnectionPreference
);
}
/**
* Returns the closest valid candidate connection, if one can be found.
*
* Valid neighbour connections are within the configured start radius, with a
* compatible type (input, output, etc) and connection check.
*/
private getConnectionCandidate(
draggingBlock: BlockSvg,
delta: Coordinate,
): ConnectionCandidate | null {
const localConns = this.getLocalConnections(draggingBlock);
let radius = config.snapRadius;
let candidate = null;
for (const conn of localConns) {
const {connection: neighbour, radius: rad} = conn.closest(radius, delta);
if (neighbour) {
candidate = {
local: conn,
neighbour: neighbour,
distance: rad,
};
radius = rad;
}
}
// Call drag enter/exit/over after wouldDeleteBlock is called in
// InsertionMarkerManager.update.
if (this.dragTarget_ !== oldDragTarget) {
oldDragTarget && oldDragTarget.onDragExit(this.draggingBlock_);
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBlock_);
return candidate;
}
/**
* Returns all of the connections we might connect to blocks on the workspace.
*
* Includes any connections on the dragging block, and any last next
* connection on the stack (if one exists).
*/
private getLocalConnections(draggingBlock: BlockSvg): RenderedConnection[] {
const available = draggingBlock.getConnections_(false);
const lastOnStack = draggingBlock.lastConnectionInStack(true);
if (lastOnStack && lastOnStack !== draggingBlock.nextConnection) {
available.push(lastOnStack);
}
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBlock_);
return available;
}
/**
@@ -205,6 +370,7 @@ export class BlockDragger implements IBlockDragger {
dom.stopTextWidthCache();
blockAnimation.disconnectUiStop();
this.connectionPreviewer.hidePreview();
const preventMove =
!!this.dragTarget_ &&
@@ -287,15 +453,33 @@ export class BlockDragger implements IBlockDragger {
*/
protected updateBlockAfterMove_() {
this.fireMoveEvent_();
if (this.draggedConnectionManager_.wouldConnectBlock()) {
if (this.connectionCandidate) {
// Applying connections also rerenders the relevant blocks.
this.draggedConnectionManager_.applyConnections();
this.applyConnections(this.connectionCandidate);
} else {
this.draggingBlock_.queueRender();
}
this.draggingBlock_.snapToGrid();
}
private applyConnections(candidate: ConnectionCandidate) {
const {local, neighbour} = candidate;
local.connect(neighbour);
// TODO: We can remove this `rendered` check when we reconcile with v11.
if (this.draggingBlock_.rendered) {
const inferiorConnection = local.isSuperior() ? neighbour : local;
const rootBlock = this.draggingBlock_.getRootBlock();
finishQueuedRenders().then(() => {
blockAnimation.connectionUiEffect(inferiorConnection.getSourceBlock());
// bringToFront is incredibly expensive. Delay until the next frame.
setTimeout(() => {
rootBlock.bringToFront();
}, 0);
});
}
}
/** Fire a UI event at the end of a block drag. */
protected fireDragEndEvent_() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
@@ -394,14 +578,9 @@ export class BlockDragger implements IBlockDragger {
* @returns A possibly empty list of insertion marker blocks.
*/
getInsertionMarkers(): BlockSvg[] {
// No insertion markers with the old style of dragged connection managers.
if (
this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers
) {
return this.draggedConnectionManager_.getInsertionMarkers();
}
return [];
return this.workspace_
.getAllBlocks()
.filter((block) => block.isInsertionMarker());
}
}

View File

@@ -1603,7 +1603,8 @@ export class BlockSvg
* @internal
*/
fadeForReplacement(add: boolean) {
this.pathObject.updateReplacementFade(add);
// TODO (7204): Remove these internal methods.
(this.pathObject as AnyDuringMigration).updateReplacementFade(add);
}
/**
@@ -1615,6 +1616,10 @@ export class BlockSvg
* @internal
*/
highlightShapeForInput(conn: RenderedConnection, add: boolean) {
this.pathObject.updateShapeForInputHighlight(conn, add);
// TODO (7204): Remove these internal methods.
(this.pathObject as AnyDuringMigration).updateShapeForInputHighlight(
conn,
add,
);
}
}

View File

@@ -125,6 +125,7 @@ import {inject} from './inject.js';
import {Input} from './inputs/input.js';
import * as inputs from './inputs.js';
import {InsertionMarkerManager} from './insertion_marker_manager.js';
import {InsertionMarkerPreviewer} from './connection_previewers/insertion_marker_previewer.js';
import {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
import {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
@@ -135,6 +136,7 @@ import {IBubble} from './interfaces/i_bubble.js';
import {ICollapsibleToolboxItem} from './interfaces/i_collapsible_toolbox_item.js';
import {IComponent} from './interfaces/i_component.js';
import {IConnectionChecker} from './interfaces/i_connection_checker.js';
import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
import {IContextMenu} from './interfaces/i_contextmenu.js';
import {ICopyable, isCopyable} from './interfaces/i_copyable.js';
import {IDeletable} from './interfaces/i_deletable.js';
@@ -555,6 +557,7 @@ export {IBubble};
export {ICollapsibleToolboxItem};
export {IComponent};
export {IConnectionChecker};
export {IConnectionPreviewer};
export {IContextMenu};
export {icons};
export {ICopyable, isCopyable};
@@ -571,6 +574,7 @@ export {IMovable};
export {Input};
export {inputs};
export {InsertionMarkerManager};
export {InsertionMarkerPreviewer};
export {IObservable, isObservable};
export {IPaster, isPaster};
export {IPositionable};

View File

@@ -48,6 +48,7 @@ export const config: Config = {
* Maximum misalignment between connections for them to snap together.
* This should be the same as the snap radius.
*
* @deprecated v11 - This is no longer used. Use snapRadius instead.
*/
connectingSnapRadius: DEFAULT_SNAP_RADIUS,
/**

View File

@@ -0,0 +1,235 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {BlockSvg} from '../block_svg.js';
import {IConnectionPreviewer} from '../interfaces/i_connection_previewer.js';
import {RenderedConnection} from '../rendered_connection.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import * as eventUtils from '../events/utils.js';
import * as renderManagement from '../render_management.js';
import * as registry from '../registry.js';
import * as blocks from '../serialization/blocks.js';
/**
* An error message to throw if the block created by createMarkerBlock_ is
* missing any components.
*/
const DUPLICATE_BLOCK_ERROR =
'The insertion marker previewer tried to create a marker but the result ' +
'is missing a connection. If you are using a mutator, make sure your ' +
'domToMutation method is properly defined.';
export class InsertionMarkerPreviewer implements IConnectionPreviewer {
private readonly workspace: WorkspaceSvg;
private fadedBlock: BlockSvg | null = null;
private markerConn: RenderedConnection | null = null;
private draggedConn: RenderedConnection | null = null;
private staticConn: RenderedConnection | null = null;
constructor(draggedBlock: BlockSvg) {
this.workspace = draggedBlock.workspace;
}
/**
* Display a connection preview where the draggedCon connects to the
* staticCon, replacing the replacedBlock (currently connected to the
* staticCon).
*
* @param draggedConn The connection on the block stack being dragged.
* @param staticConn The connection not being dragged that we are
* connecting to.
* @param replacedBlock The block currently connected to the staticCon that
* is being replaced.
*/
previewReplacement(
draggedConn: RenderedConnection,
staticConn: RenderedConnection,
replacedBlock: BlockSvg,
) {
eventUtils.disable();
try {
this.hidePreview();
this.fadedBlock = replacedBlock;
replacedBlock.fadeForReplacement(true);
if (this.workspace.getRenderer().shouldHighlightConnection(staticConn)) {
staticConn.highlight();
this.staticConn = staticConn;
}
} finally {
eventUtils.enable();
}
}
/**
* Display a connection preview where the draggedCon connects to the
* staticCon, and no block is being relaced.
*
* @param draggedConn The connection on the block stack being dragged.
* @param staticConn The connection not being dragged that we are
* connecting to.
*/
previewConnection(
draggedConn: RenderedConnection,
staticConn: RenderedConnection,
) {
if (draggedConn === this.draggedConn && staticConn === this.staticConn) {
return;
}
eventUtils.disable();
try {
this.hidePreview();
const dragged = draggedConn.getSourceBlock();
const marker = this.createInsertionMarker(dragged);
const markerConn = this.getMatchingConnection(
dragged,
marker,
draggedConn,
);
if (!markerConn) {
throw Error(DUPLICATE_BLOCK_ERROR);
}
// Render disconnected from everything else so that we have a valid
// connection location.
marker.queueRender();
renderManagement.triggerQueuedRenders();
// Connect() also renders the insertion marker.
markerConn.connect(staticConn);
const originalOffsetToTarget = {
x: staticConn.x - markerConn.x,
y: staticConn.y - markerConn.y,
};
const originalOffsetInBlock = markerConn.getOffsetInBlock().clone();
renderManagement.finishQueuedRenders().then(() => {
// Position so that the existing block doesn't move.
marker?.positionNearConnection(
markerConn,
originalOffsetToTarget,
originalOffsetInBlock,
);
marker?.getSvgRoot().setAttribute('visibility', 'visible');
});
if (this.workspace.getRenderer().shouldHighlightConnection(staticConn)) {
staticConn.highlight();
}
this.markerConn = markerConn;
this.draggedConn = draggedConn;
this.staticConn = staticConn;
} finally {
eventUtils.enable();
}
}
private createInsertionMarker(origBlock: BlockSvg) {
const blockJson = blocks.save(origBlock, {
addCoordinates: false,
addInputBlocks: false,
addNextBlocks: false,
doFullSerialization: false,
});
if (!blockJson) {
throw new Error(
`Failed to serialize source block. ${origBlock.toDevString()}`,
);
}
const result = blocks.append(blockJson, this.workspace) as BlockSvg;
// Turn shadow blocks that are created programmatically during
// initalization to insertion markers too.
for (const block of result.getDescendants(false)) {
block.setInsertionMarker(true);
}
result.initSvg();
result.getSvgRoot().setAttribute('visibility', 'hidden');
return result;
}
/**
* Gets the connection on the marker block that matches the original
* connection on the original block.
*
* @param orig The original block.
* @param marker The marker block (where we want to find the matching
* connection).
* @param origConn The original connection.
*/
private getMatchingConnection(
orig: BlockSvg,
marker: BlockSvg,
origConn: RenderedConnection,
) {
const origConns = orig.getConnections_(true);
const markerConns = marker.getConnections_(true);
for (let i = 0; i < origConns.length; i++) {
if (origConns[i] === origConn) {
return markerConns[i];
}
}
return null;
}
/** Hide any previews that are currently displayed. */
hidePreview() {
eventUtils.disable();
try {
if (this.staticConn) {
this.staticConn.unhighlight();
this.staticConn = null;
}
if (this.fadedBlock) {
this.fadedBlock.fadeForReplacement(false);
this.fadedBlock = null;
}
if (this.markerConn) {
this.hideInsertionMarker(this.markerConn);
this.markerConn = null;
this.draggedConn = null;
}
} finally {
eventUtils.enable();
}
}
private hideInsertionMarker(markerConn: RenderedConnection) {
const marker = markerConn.getSourceBlock();
const markerPrev = marker.previousConnection;
const markerOutput = marker.outputConnection;
if (!markerPrev?.targetConnection && !markerOutput?.targetConnection) {
// If we are the top block, unplugging doesn't do anything.
// The marker connection may not have a target block if we are hiding
// as part of applying connections.
markerConn.targetBlock()?.unplug(false);
} else {
marker.unplug(true);
}
marker.dispose();
}
/** Dispose of any references held by this connection previewer. */
dispose() {
this.hidePreview();
}
}
registry.register(
registry.Type.CONNECTION_PREVIEWER,
registry.DEFAULT,
InsertionMarkerPreviewer,
);

View File

@@ -542,6 +542,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
} else if (e.key === 'Escape') {
this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value'),
false,
);
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();

View File

@@ -649,7 +649,7 @@ export abstract class Flyout
const parsedContent = toolbox.convertFlyoutDefToJsonArray(flyoutDef);
const flyoutInfo = this.createFlyoutInfo(parsedContent);
renderManagement.triggerQueuedRenders();
renderManagement.triggerQueuedRenders(this.workspace_);
this.layout_(flyoutInfo.contents, flyoutInfo.gaps);
@@ -1251,8 +1251,7 @@ export abstract class Flyout
}
// Clone the block.
// TODO(#7432): Add a saveIds parameter to `save`.
const json = blocks.save(oldBlock, {saveIds: false}) 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;

View File

@@ -46,6 +46,8 @@ interface CandidateConnection {
* Class that controls updates to connections during drags. It is primarily
* responsible for finding the closest eligible connection and highlighting or
* unhighlighting it as needed during a drag.
*
* @deprecated v10 - Use an IConnectionPreviewer instead.
*/
export class InsertionMarkerManager {
/**

View File

@@ -0,0 +1,50 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type {BlockSvg} from '../block_svg';
import type {RenderedConnection} from '../rendered_connection';
/**
* Displays visual "previews" of where a block will be connected if it is
* dropped.
*/
export interface IConnectionPreviewer {
/**
* Display a connection preview where the draggedCon connects to the
* staticCon, replacing the replacedBlock (currently connected to the
* staticCon).
*
* @param draggedCon The connection on the block stack being dragged.
* @param staticCon The connection not being dragged that we are
* connecting to.
* @param replacedBlock The block currently connected to the staticCon that
* is being replaced.
*/
previewReplacement(
draggedConn: RenderedConnection,
staticConn: RenderedConnection,
replacedBlock: BlockSvg,
): void;
/**
* Display a connection preview where the draggedCon connects to the
* staticCon, and no block is being relaced.
*
* @param draggedCon The connection on the block stack being dragged.
* @param staticCon The connection not being dragged that we are
* connecting to.
*/
previewConnection(
draggedConn: RenderedConnection,
staticConn: RenderedConnection,
): void;
/** Hide any previews that are currently displayed. */
hidePreview(): void;
/** Dispose of any references held by this connection previewer. */
dispose(): void;
}

View File

@@ -21,8 +21,9 @@ import type {Options} from './options.js';
import type {Renderer} from './renderers/common/renderer.js';
import type {Theme} from './theme.js';
import type {ToolboxItem} from './toolbox/toolbox_item.js';
import {IPaster} from './interfaces/i_paster.js';
import {ICopyData, ICopyable} from './interfaces/i_copyable.js';
import type {IPaster} from './interfaces/i_paster.js';
import type {ICopyData, ICopyable} from './interfaces/i_copyable.js';
import type {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
/**
* A map of maps. With the keys being the type and name of the class we are
@@ -66,6 +67,10 @@ export class Type<_T> {
static CONNECTION_CHECKER = new Type<IConnectionChecker>('connectionChecker');
static CONNECTION_PREVIEWER = new Type<IConnectionPreviewer>(
'connectionPreviewer',
);
static CURSOR = new Type<Cursor>('cursor');
static EVENT = new Type<Abstract>('event');

View File

@@ -7,12 +7,13 @@
import {BlockSvg} from './block_svg.js';
import * as userAgent from './utils/useragent.js';
import * as eventUtils from './events/utils.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/** The set of all blocks in need of rendering which don't have parents. */
const rootBlocks = new Set<BlockSvg>();
/** The set of all blocks in need of rendering. */
let dirtyBlocks = new WeakSet<BlockSvg>();
const dirtyBlocks = new WeakSet<BlockSvg>();
/** A map from queued blocks to the event group from when they were queued. */
let eventGroups = new WeakMap<BlockSvg, string>();
@@ -79,12 +80,14 @@ export function finishQueuedRenders(): Promise<void> {
* cases where queueing renders breaks functionality + backwards compatibility
* (such as rendering icons).
*
* @param workspace If provided, only rerender blocks in this workspace.
*
* @internal
*/
export function triggerQueuedRenders() {
window.cancelAnimationFrame(animationRequestId);
doRenders();
if (afterRendersResolver) afterRendersResolver();
export function triggerQueuedRenders(workspace?: WorkspaceSvg) {
if (!workspace) window.cancelAnimationFrame(animationRequestId);
doRenders(workspace);
if (!workspace && afterRendersResolver) afterRendersResolver();
}
/**
@@ -115,10 +118,16 @@ function queueBlock(block: BlockSvg) {
/**
* Rerenders all of the blocks in the queue.
*
* @param workspace If provided, only rerender blocks in this workspace.
*/
function doRenders() {
const workspaces = new Set([...rootBlocks].map((block) => block.workspace));
const blocks = [...rootBlocks].filter(shouldRenderRootBlock);
function doRenders(workspace?: WorkspaceSvg) {
const workspaces = workspace
? new Set([workspace])
: new Set([...rootBlocks].map((block) => block.workspace));
const blocks = [...rootBlocks]
.filter(shouldRenderRootBlock)
.filter((b) => workspaces.has(b.workspace));
for (const block of blocks) {
renderBlock(block);
}
@@ -139,10 +148,20 @@ function doRenders() {
eventUtils.setGroup(oldGroup);
}
rootBlocks.clear();
dirtyBlocks = new WeakSet();
eventGroups = new WeakMap();
afterRendersPromise = null;
for (const block of blocks) {
dequeueBlock(block);
}
if (!workspace) afterRendersPromise = null;
}
/** Removes the given block and children from the render queue. */
function dequeueBlock(block: BlockSvg) {
rootBlocks.delete(block);
dirtyBlocks.delete(block);
eventGroups.delete(block);
for (const child of block.getChildren(false)) {
dequeueBlock(child);
}
}
/**

View File

@@ -22,19 +22,6 @@ import * as eventUtils from './events/utils.js';
import {hasBubble} from './interfaces/i_has_bubble.js';
import * as internalConstants from './internal_constants.js';
import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
import * as svgPaths from './utils/svg_paths.js';
/** A shape that has a pathDown property. */
interface PathDownShape {
pathDown: string;
}
/** A shape that has a pathLeft property. */
interface PathLeftShape {
pathLeft: string;
}
/** Maximum randomness in workspace units for bumping a block. */
const BUMP_RANDOMNESS = 10;
@@ -49,7 +36,7 @@ export class RenderedConnection extends Connection {
private readonly dbOpposite: ConnectionDB;
private readonly offsetInBlock: Coordinate;
private trackedState: TrackedState;
private highlightPath: SVGPathElement | null = null;
private highlighted: boolean = false;
/** Connection this connection connects to. Null if not connected. */
override targetConnection: RenderedConnection | null = null;
@@ -92,10 +79,6 @@ export class RenderedConnection extends Connection {
if (this.trackedState === RenderedConnection.TrackedState.TRACKED) {
this.db.removeConnection(this, this.y);
}
if (this.highlightPath) {
dom.removeNode(this.highlightPath);
this.highlightPath = null;
}
}
/**
@@ -305,57 +288,19 @@ export class RenderedConnection extends Connection {
/** Add highlighting around this connection. */
highlight() {
if (this.highlightPath) {
// This connection is already highlighted
return;
}
let steps;
const sourceBlockSvg = this.sourceBlock_;
const renderConstants = sourceBlockSvg.workspace
.getRenderer()
.getConstants();
const shape = renderConstants.shapeFor(this);
if (
this.type === ConnectionType.INPUT_VALUE ||
this.type === ConnectionType.OUTPUT_VALUE
) {
// Vertical line, puzzle tab, vertical line.
const yLen = renderConstants.TAB_OFFSET_FROM_TOP;
steps =
svgPaths.moveBy(0, -yLen) +
svgPaths.lineOnAxis('v', yLen) +
(shape as unknown as PathDownShape).pathDown +
svgPaths.lineOnAxis('v', yLen);
} else {
const xLen =
renderConstants.NOTCH_OFFSET_LEFT - renderConstants.CORNER_RADIUS;
// Horizontal line, notch, horizontal line.
steps =
svgPaths.moveBy(-xLen, 0) +
svgPaths.lineOnAxis('h', xLen) +
(shape as unknown as PathLeftShape).pathLeft +
svgPaths.lineOnAxis('h', xLen);
}
const offset = this.offsetInBlock;
this.highlightPath = dom.createSvgElement(
Svg.PATH,
{
'class': 'blocklyHighlightedConnectionPath',
'd': steps,
'transform':
`translate(${offset.x}, ${offset.y})` +
(this.sourceBlock_.RTL ? ' scale(-1 1)' : ''),
},
this.sourceBlock_.getSvgRoot(),
);
this.highlighted = true;
this.getSourceBlock().queueRender();
}
/** Remove the highlighting around this connection. */
unhighlight() {
if (this.highlightPath) {
dom.removeNode(this.highlightPath);
this.highlightPath = null;
}
this.highlighted = false;
this.getSourceBlock().queueRender();
}
/** Returns true if this connection is highlighted, false otherwise. */
isHighlighted(): boolean {
return this.highlighted;
}
/**

View File

@@ -53,8 +53,8 @@ export interface PuzzleTab {
type: number;
width: number;
height: number;
pathDown: string | ((p1: number) => string);
pathUp: string | ((p1: number) => string);
pathDown: string;
pathUp: string;
}
/**
@@ -100,6 +100,22 @@ export function isDynamicShape(shape: Shape): shape is DynamicShape {
return (shape as DynamicShape).isDynamic;
}
/** Returns whether the shape is a puzzle tab or not. */
export function isPuzzleTab(shape: Shape): shape is PuzzleTab {
return (
(shape as PuzzleTab).pathDown !== undefined &&
(shape as PuzzleTab).pathUp !== undefined
);
}
/** Returns whether the shape is a notch or not. */
export function isNotch(shape: Shape): shape is Notch {
return (
(shape as Notch).pathLeft !== undefined &&
(shape as Notch).pathRight !== undefined
);
}
/**
* An object that provides constants for rendering blocks.
*/

View File

@@ -18,9 +18,10 @@ import type {PreviousConnection} from '../measurables/previous_connection.js';
import type {Row} from '../measurables/row.js';
import {Types} from '../measurables/types.js';
import {isDynamicShape} from './constants.js';
import {isDynamicShape, isNotch, isPuzzleTab} from './constants.js';
import type {ConstantProvider, Notch, PuzzleTab} from './constants.js';
import type {RenderInfo} from './info.js';
import {ConnectionType} from '../../connection_type.js';
/**
* An object that draws a block based on the given rendering information.
@@ -59,6 +60,7 @@ export class Drawer {
draw() {
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
this.block_.pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
@@ -429,4 +431,87 @@ export class Drawer {
);
}
}
/**
* Updates the path object to reflect which connections on the block are
* highlighted.
*/
protected updateConnectionHighlights() {
for (const row of this.info_.rows) {
for (const elem of row.elements) {
if (!(elem instanceof Connection)) continue;
if (elem.highlighted) {
this.drawConnectionHighlightPath(elem);
} else {
this.block_.pathObject.removeConnectionHighlight?.(
elem.connectionModel,
);
}
}
}
}
/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
const conn = measurable.connectionModel;
let path = '';
if (
conn.type === ConnectionType.INPUT_VALUE ||
conn.type === ConnectionType.OUTPUT_VALUE
) {
path = this.getExpressionConnectionHighlightPath(measurable);
} else {
path = this.getStatementConnectionHighlightPath(measurable);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
block.RTL,
);
}
/**
* Returns a path to highlight the given conneciton, assuming it is an
* input or output connection.
*/
private getExpressionConnectionHighlightPath(connection: Connection): string {
let connPath = '';
if (isDynamicShape(connection.shape)) {
connPath = connection.shape.pathDown(connection.height);
} else if (isPuzzleTab(connection.shape)) {
connPath = connection.shape.pathDown;
}
// We are assuming that there is room for the tab offset above and below
// the tab.
const yLen = this.constants_.TAB_OFFSET_FROM_TOP;
return (
svgPaths.moveBy(0, -yLen) +
svgPaths.lineOnAxis('v', yLen) +
connPath +
svgPaths.lineOnAxis('v', yLen)
);
}
/**
* Returns a path to highlight the given conneciton, assuming it is a
* next or previous connection.
*/
private getStatementConnectionHighlightPath(connection: Connection): string {
if (!isNotch(connection.shape)) {
throw new Error('Statement connections should have notch shapes');
}
const xLen =
this.constants_.NOTCH_OFFSET_LEFT - this.constants_.CORNER_RADIUS;
return (
svgPaths.moveBy(-xLen, 0) +
svgPaths.lineOnAxis('h', xLen) +
connection.shape.pathLeft +
svgPaths.lineOnAxis('h', xLen)
);
}
}

View File

@@ -9,7 +9,8 @@
import type {BlockStyle} from '../../theme.js';
import type {BlockSvg} from '../../block_svg.js';
import type {ConstantProvider} from './constants.js';
import {RenderedConnection} from '../../rendered_connection.js';
import type {RenderedConnection} from '../../rendered_connection.js';
import type {Coordinate} from '../../utils/coordinate.js';
/**
* An interface for a block's path object.
@@ -121,21 +122,16 @@ export interface IPathObject {
*/
updateMovable(enabled: boolean): void;
/**
* Add or remove styling that shows that if the dragging block is dropped,
* this block will be replaced. If a shadow block, it will disappear.
* Otherwise it will bump.
*
* @param enable True if styling should be added.
*/
updateReplacementFade(enabled: boolean): void;
/** Adds the given path as a connection highlight for the given connection. */
addConnectionHighlight?(
connection: RenderedConnection,
connectionPath: string,
offset: Coordinate,
rtl: boolean,
): void;
/**
* Add or remove styling that shows that if the dragging block is dropped,
* this block will be connected to the input.
*
* @param conn The connection on the input to highlight.
* @param enable True if styling should be added.
* Removes any highlight associated with the given connection, if it exists.
*/
updateShapeForInputHighlight(conn: RenderedConnection, enable: boolean): void;
removeConnectionHighlight?(connection: RenderedConnection): void;
}

View File

@@ -37,6 +37,7 @@ import {ValueInput} from '../../inputs/value_input.js';
import type {ConstantProvider} from './constants.js';
import type {Renderer} from './renderer.js';
import {Connection} from '../measurables/connection.js';
/**
* An object containing all sizing information needed to draw this block.
@@ -143,8 +144,7 @@ export class RenderInfo {
}
/**
* Populate and return an object containing all sizing information needed to
* draw this block.
* Populate this object with all sizing information needed to draw the block.
*
* This measure pass does not propagate changes to the block (although fields
* may choose to rerender when getSize() is called). However, calling it
@@ -748,4 +748,21 @@ export class RenderInfo {
this.startY = this.topRow.capline;
this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
}
/** Returns the connection measurable associated with the given connection. */
getMeasureableForConnection(conn: RenderedConnection): Connection | null {
if (this.outputConnection?.connectionModel === conn) {
return this.outputConnection;
}
for (const row of this.rows) {
for (const elem of row.elements) {
if (elem instanceof Connection && elem.connectionModel === conn) {
return elem;
}
}
}
return null;
}
}

View File

@@ -8,7 +8,9 @@
import type {BlockSvg} from '../../block_svg.js';
import type {Connection} from '../../connection.js';
import {RenderedConnection} from '../../rendered_connection.js';
import type {BlockStyle} from '../../theme.js';
import {Coordinate} from '../../utils/coordinate.js';
import * as dom from '../../utils/dom.js';
import {Svg} from '../../utils/svg.js';
@@ -38,6 +40,12 @@ export class PathObject implements IPathObject {
constants: ConstantProvider;
style: BlockStyle;
/** Highlight paths associated with connections. */
private connectionHighlights = new WeakMap<RenderedConnection, SVGElement>();
/** Locations of connection highlights. */
private highlightOffsets = new WeakMap<RenderedConnection, Coordinate>();
/**
* @param root The root SVG element.
* @param style The style object to use for colouring.
@@ -256,4 +264,53 @@ export class PathObject implements IPathObject {
updateShapeForInputHighlight(_conn: Connection, _enable: boolean) {
// NOOP
}
/** Adds the given path as a connection highlight for the given connection. */
addConnectionHighlight(
connection: RenderedConnection,
connectionPath: string,
offset: Coordinate,
rtl: boolean,
) {
if (this.connectionHighlights.has(connection)) {
if (this.currentHighlightMatchesNew(connection, connectionPath, offset)) {
return;
}
this.removeConnectionHighlight(connection);
}
const highlight = dom.createSvgElement(
Svg.PATH,
{
'class': 'blocklyHighlightedConnectionPath',
'd': connectionPath,
'transform':
`translate(${offset.x}, ${offset.y})` + (rtl ? ' scale(-1 1)' : ''),
},
this.svgRoot,
);
this.connectionHighlights.set(connection, highlight);
}
private currentHighlightMatchesNew(
connection: RenderedConnection,
newPath: string,
newOffset: Coordinate,
): boolean {
const currPath = this.connectionHighlights
.get(connection)
?.getAttribute('d');
const currOffset = this.highlightOffsets.get(connection);
return currPath === newPath && Coordinate.equals(currOffset, newOffset);
}
/**
* Removes any highlight associated with the given connection, if it exists.
*/
removeConnectionHighlight(connection: RenderedConnection) {
const highlight = this.connectionHighlights.get(connection);
if (!highlight) return;
dom.removeNode(highlight);
this.connectionHighlights.delete(connection);
}
}

View File

@@ -26,6 +26,7 @@ import type {IPathObject} from './i_path_object.js';
import {RenderInfo} from './info.js';
import {MarkerSvg} from './marker_svg.js';
import {PathObject} from './path_object.js';
import * as deprecation from '../../utils/deprecation.js';
/**
* The base class for a block renderer.
@@ -231,12 +232,21 @@ export class Renderer implements IRegistrable {
* @param local The connection currently being dragged.
* @param topBlock The block currently being dragged.
* @returns The preview type to display.
*
* @deprecated v10 - This function is no longer respected. A custom
* IConnectionPreviewer may be able to fulfill the functionality.
*/
getConnectionPreviewMethod(
closest: RenderedConnection,
local: RenderedConnection,
topBlock: BlockSvg,
): PreviewType {
deprecation.warn(
'getConnectionPreviewMethod',
'v10',
'v12',
'an IConnectionPreviewer, if it fulfills your use case.',
);
if (
local.type === ConnectionType.OUTPUT_VALUE ||
local.type === ConnectionType.PREVIOUS_STATEMENT

View File

@@ -40,6 +40,7 @@ export class Drawer extends BaseDrawer {
override draw() {
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
const pathObject = this.block_.pathObject as PathObject;
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);

View File

@@ -20,6 +20,7 @@ import {Types} from './types.js';
export class Connection extends Measurable {
shape: Shape;
isDynamicShape: boolean;
highlighted: boolean;
/**
* @param constants The rendering constants provider.
@@ -32,9 +33,11 @@ export class Connection extends Measurable {
) {
super(constants);
this.shape = this.constants_.shapeFor(connectionModel);
this.isDynamicShape = 'isDynamic' in this.shape && this.shape.isDynamic;
this.type |= Types.CONNECTION;
this.shape = this.constants_.shapeFor(connectionModel);
this.isDynamicShape = 'isDynamic' in this.shape && this.shape.isDynamic;
this.highlighted = connectionModel.isHighlighted();
}
}

View File

@@ -392,19 +392,20 @@ export class ConstantProvider extends BaseConstantProvider {
blockHeight > maxHeight ? blockHeight - maxHeight : 0;
const height = blockHeight > maxHeight ? maxHeight : blockHeight;
const radius = height / 2;
const sweep = right === up ? '0' : '1';
return (
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius),
) +
svgPaths.lineOnAxis('v', (right ? 1 : -1) * remainingHeight) +
svgPaths.lineOnAxis('v', (up ? -1 : 1) * remainingHeight) +
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius),
)
);
}
@@ -465,19 +466,20 @@ export class ConstantProvider extends BaseConstantProvider {
*/
function makeMainPath(height: number, up: boolean, right: boolean): string {
const innerHeight = height - radius * 2;
const sweep = right === up ? '0' : '1';
return (
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? -1 : 1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? 1 : -1) * radius, (up ? -1 : 1) * radius),
) +
svgPaths.lineOnAxis('v', (right ? 1 : -1) * innerHeight) +
svgPaths.lineOnAxis('v', (up ? -1 : 1) * innerHeight) +
svgPaths.arc(
'a',
'0 0,1',
'0 0,' + sweep,
radius,
svgPaths.point((up ? 1 : -1) * radius, (up ? -1 : 1) * radius),
svgPaths.point((right ? -1 : 1) * radius, (up ? -1 : 1) * radius),
)
);
}

View File

@@ -7,10 +7,13 @@
// Former goog.module ID: Blockly.zelos.Drawer
import type {BlockSvg} from '../../block_svg.js';
import {ConnectionType} from '../../connection_type.js';
import * as svgPaths from '../../utils/svg_paths.js';
import type {BaseShape, DynamicShape, Notch} from '../common/constants.js';
import {Drawer as BaseDrawer} from '../common/drawer.js';
import {Connection} from '../measurables/connection.js';
import type {InlineInput} from '../measurables/inline_input.js';
import {OutputConnection} from '../measurables/output_connection.js';
import type {Row} from '../measurables/row.js';
import type {SpacerRow} from '../measurables/spacer_row.js';
import {Types} from '../measurables/types.js';
@@ -41,6 +44,7 @@ export class Drawer extends BaseDrawer {
pathObject.beginDrawing();
this.drawOutline_();
this.drawInternals_();
this.updateConnectionHighlights();
pathObject.setPath(this.outlinePath_ + '\n' + this.inlinePath_);
if (this.info_.RTL) {
@@ -179,21 +183,27 @@ export class Drawer extends BaseDrawer {
return;
}
const width = input.width - input.connectionWidth * 2;
const height = input.height;
const yPos = input.centerline - height / 2;
const yPos = input.centerline - input.height / 2;
const connectionRight = input.xPos + input.connectionWidth;
const outlinePath =
svgPaths.moveTo(connectionRight, yPos) +
svgPaths.lineOnAxis('h', width) +
(input.shape as DynamicShape).pathRightDown(input.height) +
svgPaths.lineOnAxis('h', -width) +
(input.shape as DynamicShape).pathUp(input.height) +
'z';
const path =
svgPaths.moveTo(connectionRight, yPos) + this.getInlineInputPath(input);
const pathObject = this.block_.pathObject as PathObject;
pathObject.setOutlinePath(inputName, outlinePath);
pathObject.setOutlinePath(inputName, path);
}
private getInlineInputPath(input: InlineInput) {
const width = input.width - input.connectionWidth * 2;
const height = input.height;
return (
svgPaths.lineOnAxis('h', width) +
(input.shape as DynamicShape).pathRightDown(height) +
svgPaths.lineOnAxis('h', -width) +
(input.shape as DynamicShape).pathUp(height) +
'z'
);
}
override drawStatementInput_(row: Row) {
@@ -225,4 +235,40 @@ export class Drawer extends BaseDrawer {
this.positionStatementInputConnection_(row);
}
/** Returns a path to highlight the given connection. */
drawConnectionHighlightPath(measurable: Connection) {
const conn = measurable.connectionModel;
if (
conn.type === ConnectionType.NEXT_STATEMENT ||
conn.type === ConnectionType.PREVIOUS_STATEMENT ||
(conn.type === ConnectionType.OUTPUT_VALUE && !measurable.isDynamicShape)
) {
super.drawConnectionHighlightPath(measurable);
return;
}
let path = '';
if (conn.type === ConnectionType.INPUT_VALUE) {
const input = measurable as InlineInput;
const xPos = input.connectionWidth;
const yPos = -input.height / 2;
path = svgPaths.moveTo(xPos, yPos) + this.getInlineInputPath(input);
} else {
// Dynamic output.
const output = measurable as OutputConnection;
const xPos = output.width;
const yPos = -output.height / 2;
path =
svgPaths.moveTo(xPos, yPos) +
(output.shape as DynamicShape).pathDown(output.height);
}
const block = conn.getSourceBlock();
block.pathObject.addConnectionHighlight?.(
conn,
path,
conn.getOffsetInBlock(),
block.RTL,
);
}
}

View File

@@ -7,7 +7,6 @@
// Former goog.module ID: Blockly.zelos.Renderer
import type {BlockSvg} from '../../block_svg.js';
import type {Connection} from '../../connection.js';
import {ConnectionType} from '../../connection_type.js';
import {InsertionMarkerManager} from '../../insertion_marker_manager.js';
import type {Marker} from '../../keyboard_nav/marker.js';
@@ -23,6 +22,7 @@ import {Drawer} from './drawer.js';
import {RenderInfo} from './info.js';
import {MarkerSvg} from './marker_svg.js';
import {PathObject} from './path_object.js';
import * as deprecation from '../../utils/deprecation.js';
/**
* The zelos renderer. This renderer emulates Scratch-style and MakeCode-style
@@ -109,18 +109,21 @@ export class Renderer extends BaseRenderer {
return this.constants_;
}
override shouldHighlightConnection(conn: Connection) {
return (
conn.type !== ConnectionType.INPUT_VALUE &&
conn.type !== ConnectionType.OUTPUT_VALUE
);
}
/**
* @deprecated v10 - This function is no longer respected. A custom
* IConnectionPreviewer may be able to fulfill the functionality.
*/
override getConnectionPreviewMethod(
closest: RenderedConnection,
local: RenderedConnection,
topBlock: BlockSvg,
) {
deprecation.warn(
'getConnectionPreviewMethod',
'v10',
'v12',
'an IConnectionPreviewer, if it fulfills your use case.',
);
if (local.type === ConnectionType.OUTPUT_VALUE) {
if (!closest.isConnected()) {
return InsertionMarkerManager.PREVIEW_TYPE.INPUT_OUTLINE;

View File

@@ -6,6 +6,9 @@
// Former goog.module ID: Blockly.utils.deprecation
// Set of previously-emitted warnings.
const previousWarnings = new Set();
/**
* Warn developers that a function or property is deprecated.
*
@@ -33,5 +36,12 @@ export function warn(
if (opt_use) {
msg += '\nUse ' + opt_use + ' instead.';
}
// Don't log deprecation warnings multiple times.
if (previousWarnings.has(msg)) {
return;
}
previousWarnings.add(msg);
console.warn(msg);
}

View File

@@ -31,6 +31,9 @@ export class WorkspaceAudio {
/** Time that the last sound was played. */
private lastSound_: Date | null = null;
/** Whether the audio is muted or not. */
private muted: boolean = false;
/**
* @param parentWorkspace The parent of the workspace this audio object
* belongs to, or null.
@@ -121,6 +124,9 @@ export class WorkspaceAudio {
* @param opt_volume Volume of sound (0-1).
*/
play(name: string, opt_volume?: number) {
if (this.muted) {
return;
}
const sound = this.sounds.get(name);
if (sound) {
// Don't play one sound on top of another.
@@ -148,4 +154,18 @@ export class WorkspaceAudio {
this.parentWorkspace.getAudioManager().play(name, opt_volume);
}
}
/**
* @param muted If true, mute sounds. Otherwise, play them.
*/
setMuted(muted: boolean) {
this.muted = muted;
}
/**
* @returns Whether the audio is currently muted or not.
*/
getMuted(): boolean {
return this.muted;
}
}

View File

@@ -995,10 +995,7 @@ function domToBlockHeadless(
throw TypeError('Shadow block not allowed non-shadow child.');
}
}
// Ensure this block doesn't have any variable inputs.
if (block.getVarModels().length) {
throw TypeError('Shadow blocks cannot have variable references.');
}
block.setShadow(true);
}
return block;

427
package-lock.json generated
View File

@@ -26,7 +26,7 @@
"eslint": "^8.4.1",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsdoc": "^46.2.6",
"eslint-plugin-jsdoc": "^48.0.2",
"glob": "^10.3.4",
"google-closure-compiler": "^20230802.0.0",
"gulp": "^4.0.2",
@@ -45,12 +45,15 @@
"markdown-tables-to-json": "^0.1.7",
"mocha": "^10.0.0",
"patch-package": "^8.0.0",
"prettier": "3.1.0",
"prettier": "3.1.1",
"readline-sync": "^1.4.10",
"rimraf": "^5.0.0",
"typescript": "^5.0.2",
"webdriverio": "^8.16.7",
"yargs": "^17.2.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -148,9 +151,9 @@
}
},
"node_modules/@blockly/theme-modern": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-5.0.4.tgz",
"integrity": "sha512-AamkRgc5XDvENdEBol8GVUebBooAaHP/yGfixVQ3Oj48xErKisUbLuCpZ4emvahewGghJ55HVXSJKdLQ2n0h8w==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@blockly/theme-modern/-/theme-modern-5.0.5.tgz",
"integrity": "sha512-rbVOGxKHAatzHI6Yhy9lJbIRPzAW2Xgf+N1U1KSkyVmUziLKKaNKwwYvnOSx4MmoDD49SrZMdUgT8G+VBLFhYw==",
"dev": true,
"engines": {
"node": ">=8.17.0"
@@ -984,16 +987,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
"integrity": "sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.19.0.tgz",
"integrity": "sha512-DUCUkQNklCQYnrBSSikjVChdc84/vMPDQSgJTHBZ64G9bA9w0Crc0rd2diujKbTdp6w2J47qkeHQLoi0rpLCdg==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/type-utils": "6.13.2",
"@typescript-eslint/utils": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.2",
"@typescript-eslint/scope-manager": "6.19.0",
"@typescript-eslint/type-utils": "6.19.0",
"@typescript-eslint/utils": "6.19.0",
"@typescript-eslint/visitor-keys": "6.19.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -1019,13 +1022,13 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
"integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz",
"integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.2"
"@typescript-eslint/types": "6.19.0",
"@typescript-eslint/visitor-keys": "6.19.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1036,9 +1039,9 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
"integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz",
"integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1049,12 +1052,12 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
"integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz",
"integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/types": "6.19.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -1113,13 +1116,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.13.2.tgz",
"integrity": "sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.19.0.tgz",
"integrity": "sha512-mcvS6WSWbjiSxKCwBcXtOM5pRkPQ6kcDds/juxcy/727IQr3xMEcwr/YLHW2A2+Fp5ql6khjbKBzOyjuPqGi/w==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/utils": "6.13.2",
"@typescript-eslint/typescript-estree": "6.19.0",
"@typescript-eslint/utils": "6.19.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -1140,9 +1143,9 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
"integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz",
"integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1153,16 +1156,17 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
"integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz",
"integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.2",
"@typescript-eslint/types": "6.19.0",
"@typescript-eslint/visitor-keys": "6.19.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
@@ -1180,12 +1184,12 @@
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
"integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz",
"integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/types": "6.19.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -1196,6 +1200,30 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/type-utils/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/types": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz",
@@ -1239,17 +1267,17 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.13.2.tgz",
"integrity": "sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.19.0.tgz",
"integrity": "sha512-QR41YXySiuN++/dC9UArYOg4X86OAYP83OWTewpVx5ct1IZhjjgTLocj7QNxGhWoTqknsgpl7L+hGygCO+sdYw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.13.2",
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/typescript-estree": "6.13.2",
"@typescript-eslint/scope-manager": "6.19.0",
"@typescript-eslint/types": "6.19.0",
"@typescript-eslint/typescript-estree": "6.19.0",
"semver": "^7.5.4"
},
"engines": {
@@ -1264,13 +1292,13 @@
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.13.2.tgz",
"integrity": "sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.19.0.tgz",
"integrity": "sha512-dO1XMhV2ehBI6QN8Ufi7I10wmUovmLU0Oru3n5LVlM2JuzB4M+dVphCPLkVpKvGij2j/pHBWuJ9piuXx+BhzxQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.2"
"@typescript-eslint/types": "6.19.0",
"@typescript-eslint/visitor-keys": "6.19.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1281,9 +1309,9 @@
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.13.2.tgz",
"integrity": "sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.19.0.tgz",
"integrity": "sha512-lFviGV/vYhOy3m8BJ/nAKoAyNhInTdXpftonhWle66XHAtT1ouBlkjL496b5H5hb8dWXHwtypTqgtb/DEa+j5A==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -1294,16 +1322,17 @@
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.13.2.tgz",
"integrity": "sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.19.0.tgz",
"integrity": "sha512-o/zefXIbbLBZ8YJ51NlkSAt2BamrK6XOmuxSR3hynMIzzyMY33KuJ9vuMdFSXW+H0tVvdF9qBPTHA91HDb4BIQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/visitor-keys": "6.13.2",
"@typescript-eslint/types": "6.19.0",
"@typescript-eslint/visitor-keys": "6.19.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
@@ -1321,12 +1350,12 @@
}
},
"node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.13.2.tgz",
"integrity": "sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.19.0.tgz",
"integrity": "sha512-hZaUCORLgubBvtGpp1JEFEazcuEdfxta9j4iUwdSAr7mEsYYAp3EAUyCZk3VEEqGj6W+AV4uWyrDGtrlawAsgQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.13.2",
"@typescript-eslint/types": "6.19.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -1337,6 +1366,30 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/utils/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/utils/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz",
@@ -1362,18 +1415,18 @@
"dev": true
},
"node_modules/@wdio/config": {
"version": "8.24.6",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.24.6.tgz",
"integrity": "sha512-ZFmd6rB1kgL4k/SjLXbtFTCxvxSf1qzdt/losiTqkqFBYznkTRUBGSoGaVTlkMtHAReiVSK92sICc15JWaCdEA==",
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz",
"integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==",
"dev": true,
"dependencies": {
"@wdio/logger": "8.16.17",
"@wdio/types": "8.24.2",
"@wdio/utils": "8.24.6",
"@wdio/logger": "8.28.0",
"@wdio/types": "8.29.1",
"@wdio/utils": "8.29.1",
"decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0",
"glob": "^10.2.2",
"import-meta-resolve": "^3.0.0"
"import-meta-resolve": "^4.0.0"
},
"engines": {
"node": "^16.13 || >=18"
@@ -1392,9 +1445,9 @@
}
},
"node_modules/@wdio/logger": {
"version": "8.16.17",
"resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.16.17.tgz",
"integrity": "sha512-zeQ41z3T+b4IsrriZZipayXxLNDuGsm7TdExaviNGojPVrIsQUCSd/FvlLHM32b7ZrMyInHenu/zx1cjAZO71g==",
"version": "8.28.0",
"resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz",
"integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==",
"dev": true,
"dependencies": {
"chalk": "^5.1.2",
@@ -1446,15 +1499,15 @@
}
},
"node_modules/@wdio/protocols": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.23.0.tgz",
"integrity": "sha512-2XTzD+lqQP3g8BWn+Bn5BTFzjHqzZNwq7DjlYrb27Bq8nOA+1DEcj3WzQ6V6CktTnKI/LAYKA1IFAF//Azrp/Q==",
"version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz",
"integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==",
"dev": true
},
"node_modules/@wdio/repl": {
"version": "8.23.1",
"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.23.1.tgz",
"integrity": "sha512-u6zG2cgBm67V5/WlQzadWqLGXs3moH8MOsgoljULQncelSBBZGZ5DyLB4p7jKcUAsKtMjgmFQmIvpQoqmyvdfg==",
"version": "8.24.12",
"resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz",
"integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0"
@@ -1464,9 +1517,9 @@
}
},
"node_modules/@wdio/types": {
"version": "8.24.2",
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.24.2.tgz",
"integrity": "sha512-x7iWF5NM8NfVxziGwLdQ+3sstgSxRoqfmmFEDTDps0oFrN5CgkqcoLkqXJ5u166gvpxpEq0gxZwxkbPC/Lp0cw==",
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz",
"integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0"
@@ -1476,21 +1529,20 @@
}
},
"node_modules/@wdio/utils": {
"version": "8.24.6",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.24.6.tgz",
"integrity": "sha512-qwcshLH9iKnhK0jXoXjPw3G02UhyShT0I+ljC0hMybJEBsra92TYFa47Cp6n1fdvM3+/BTuhsgtzRz0anObicQ==",
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz",
"integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==",
"dev": true,
"dependencies": {
"@puppeteer/browsers": "^1.6.0",
"@wdio/logger": "8.16.17",
"@wdio/types": "8.24.2",
"@wdio/logger": "8.28.0",
"@wdio/types": "8.29.1",
"decamelize": "^6.0.0",
"deepmerge-ts": "^5.1.0",
"edgedriver": "^5.3.5",
"geckodriver": "^4.2.0",
"get-port": "^7.0.0",
"got": "^13.0.0",
"import-meta-resolve": "^3.0.0",
"import-meta-resolve": "^4.0.0",
"locate-app": "^2.1.0",
"safaridriver": "^0.1.0",
"split2": "^4.2.0",
@@ -1501,9 +1553,9 @@
}
},
"node_modules/@wdio/utils/node_modules/@puppeteer/browsers": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.8.0.tgz",
"integrity": "sha512-TkRHIV6k2D8OlUe8RtG+5jgOF/H98Myx0M6AOafC8DdNVOFiBSFa5cpRDtpm8LXOa9sVwe0+e6Q3FC56X/DZfg==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz",
"integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==",
"dev": true,
"dependencies": {
"debug": "4.3.4",
@@ -3513,9 +3565,9 @@
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1213968",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1213968.tgz",
"integrity": "sha512-o4n/beY+3CcZwFctYapjGelKptR4AuQT5gXS1Kvgbig+ArwkxK7f8wDVuD1wsoswiJWCwV6OK+Qb7vhNzNmABQ==",
"version": "0.0.1249869",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz",
"integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==",
"dev": true
},
"node_modules/dir-glob": {
@@ -3661,9 +3713,9 @@
}
},
"node_modules/edgedriver": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.8.tgz",
"integrity": "sha512-FWLPDuwJDeGGgtmlqTXb4lQi/HV9yylLo1F9O1g9TLqSemA5T6xH28seUIfyleVirLFtDQyKNUxKsMhMT4IfnA==",
"version": "5.3.9",
"resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz",
"integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -3940,9 +3992,9 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz",
"integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
@@ -3952,9 +4004,9 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "46.9.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.9.0.tgz",
"integrity": "sha512-UQuEtbqLNkPf5Nr/6PPRCtr9xypXY+g8y/Q7gPa0YK7eDhh0y2lWprXRnaYbW7ACgIUvpDKy9X2bZqxtGzBG9Q==",
"version": "48.0.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.0.2.tgz",
"integrity": "sha512-CBFl5Jc7+jlV36RwDm+PQ8Uw5r28pn2/uW/OaB+Gw5bFwn4Py/1eYMZ3hGf9S4meUFZ/sRvS+hVif2mRAp6WqQ==",
"dev": true,
"dependencies": {
"@es-joy/jsdoccomment": "~0.41.0",
@@ -3965,13 +4017,23 @@
"esquery": "^1.5.0",
"is-builtin-module": "^3.2.1",
"semver": "^7.5.4",
"spdx-expression-parse": "^3.0.1"
"spdx-expression-parse": "^4.0.0"
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
"integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
"dev": true,
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/eslint-scope": {
@@ -4666,9 +4728,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true,
"funding": [
{
@@ -4898,17 +4960,17 @@
"dev": true
},
"node_modules/geckodriver": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.2.1.tgz",
"integrity": "sha512-4m/CRk0OI8MaANRuFIahvOxYTSjlNAO2p9JmE14zxueknq6cdtB5M9UGRQ8R9aMV0bLGNVHHDnDXmoXdOwJfWg==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz",
"integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@wdio/logger": "^8.11.0",
"@wdio/logger": "^8.24.12",
"decamelize": "^6.0.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.1",
"https-proxy-agent": "^7.0.2",
"node-fetch": "^3.3.2",
"tar-fs": "^3.0.4",
"unzipper": "^0.10.14",
"which": "^4.0.0"
@@ -5629,9 +5691,9 @@
]
},
"node_modules/got": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
"integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
"version": "12.6.1",
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^5.2.0",
@@ -5647,7 +5709,7 @@
"responselike": "^3.0.0"
},
"engines": {
"node": ">=16"
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
@@ -6544,9 +6606,9 @@
}
},
"node_modules/import-meta-resolve": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz",
"integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz",
"integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==",
"dev": true,
"funding": {
"type": "github",
@@ -7389,12 +7451,12 @@
}
},
"node_modules/locate-app": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.1.0.tgz",
"integrity": "sha512-rcVo/iLUxrd9d0lrmregK/Z5Y5NCpSwf9KlMbPpOHmKmdxdQY1Fj8NDQ5QymJTryCsBLqwmniFv2f3JKbk9Bvg==",
"version": "2.2.14",
"resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.14.tgz",
"integrity": "sha512-fqGE0IHZ3v+9kCjYvhwrP52aTGP1itOfp4TZZuv4dNl2gKN/pHCIlMhDSqPDb3qJ5Rti39y5T+/XrfCsiDRjKw==",
"dev": true,
"dependencies": {
"n12": "0.4.0",
"n12": "1.8.17",
"type-fest": "2.13.0",
"userhome": "1.0.0"
}
@@ -7498,9 +7560,9 @@
}
},
"node_modules/loglevel": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
"integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
@@ -8091,9 +8153,9 @@
}
},
"node_modules/n12": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/n12/-/n12-0.4.0.tgz",
"integrity": "sha512-p/hj4zQ8d3pbbFLQuN1K9honUxiDDhueOWyFLw/XgBv+wZCE44bcLH4CIcsolOceJQduh4Jf7m/LfaTxyGmGtQ==",
"version": "1.8.17",
"resolved": "https://registry.npmjs.org/n12/-/n12-1.8.17.tgz",
"integrity": "sha512-/NdfkU7nyqq70E4RvDa3OrR/wkZrYjDGXjn4JgIZnn+ULcyW1f6BLjNqyFC+ND9FqzyWjcQhvagFJmVJ8k8Lew==",
"dev": true
},
"node_modules/nanoid": {
@@ -9044,9 +9106,9 @@
}
},
"node_modules/prettier": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
@@ -9680,9 +9742,9 @@
"dev": true
},
"node_modules/safaridriver": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.0.tgz",
"integrity": "sha512-azzzIP3gR1TB9bVPv7QO4Zjw0rR1BWEU/s2aFdUMN48gxDjxEB13grAEuXDmkKPgE74cObymDxmAmZnL3clj4w==",
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.2.tgz",
"integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==",
"dev": true
},
"node_modules/safe-buffer": {
@@ -11379,29 +11441,29 @@
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz",
"integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/webdriver": {
"version": "8.24.6",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.24.6.tgz",
"integrity": "sha512-k5XI2/SHd/14h4ElPQH8EzSUXujZIGbBEi+3dTS2H457KFR5Q8QYfIazDs/YnEdooOp8b6Oe9N7qI99LP8K6bQ==",
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz",
"integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0",
"@types/ws": "^8.5.3",
"@wdio/config": "8.24.6",
"@wdio/logger": "8.16.17",
"@wdio/protocols": "8.23.0",
"@wdio/types": "8.24.2",
"@wdio/utils": "8.24.6",
"@wdio/config": "8.29.1",
"@wdio/logger": "8.28.0",
"@wdio/protocols": "8.24.12",
"@wdio/types": "8.29.1",
"@wdio/utils": "8.29.1",
"deepmerge-ts": "^5.1.0",
"got": "^ 12.6.1",
"got": "^12.6.1",
"ky": "^0.33.0",
"ws": "^8.8.0"
},
@@ -11409,63 +11471,26 @@
"node": "^16.13 || >=18"
}
},
"node_modules/webdriver/node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/webdriver/node_modules/got": {
"version": "12.6.1",
"resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz",
"integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^5.2.0",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
"cacheable-request": "^10.2.8",
"decompress-response": "^6.0.0",
"form-data-encoder": "^2.1.2",
"get-stream": "^6.0.1",
"http2-wrapper": "^2.1.10",
"lowercase-keys": "^3.0.0",
"p-cancelable": "^3.0.0",
"responselike": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/webdriverio": {
"version": "8.24.6",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.24.6.tgz",
"integrity": "sha512-gJMAJiErbXe/oFJbV+H9lXp9GPxnUgHrbtxkG6SCKQlk1zPFho9FZ3fQWl/ty84w5n9ZMhAdnQIfZM9aytxIBQ==",
"version": "8.29.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz",
"integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0",
"@wdio/config": "8.24.6",
"@wdio/logger": "8.16.17",
"@wdio/protocols": "8.23.0",
"@wdio/repl": "8.23.1",
"@wdio/types": "8.24.2",
"@wdio/utils": "8.24.6",
"@wdio/config": "8.29.1",
"@wdio/logger": "8.28.0",
"@wdio/protocols": "8.24.12",
"@wdio/repl": "8.24.12",
"@wdio/types": "8.29.1",
"@wdio/utils": "8.29.1",
"archiver": "^6.0.0",
"aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1",
"css-value": "^0.0.1",
"devtools-protocol": "^0.0.1213968",
"devtools-protocol": "^0.0.1249869",
"grapheme-splitter": "^1.0.2",
"import-meta-resolve": "^3.0.0",
"import-meta-resolve": "^4.0.0",
"is-plain-obj": "^4.1.0",
"lodash.clonedeep": "^4.5.0",
"lodash.zip": "^4.2.0",
@@ -11475,7 +11500,7 @@
"resq": "^1.9.1",
"rgb2hex": "0.2.5",
"serialize-error": "^11.0.1",
"webdriver": "8.24.6"
"webdriver": "8.29.1"
},
"engines": {
"node": "^16.13 || >=18"

View File

@@ -78,7 +78,7 @@
"eslint": "^8.4.1",
"eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsdoc": "^46.2.6",
"eslint-plugin-jsdoc": "^48.0.2",
"glob": "^10.3.4",
"google-closure-compiler": "^20230802.0.0",
"gulp": "^4.0.2",
@@ -97,7 +97,7 @@
"markdown-tables-to-json": "^0.1.7",
"mocha": "^10.0.0",
"patch-package": "^8.0.0",
"prettier": "3.1.0",
"prettier": "3.1.1",
"readline-sync": "^1.4.10",
"rimraf": "^5.0.0",
"typescript": "^5.0.2",

View File

@@ -59,4 +59,69 @@ suite('Render Management', function () {
return promise;
});
});
suite('triggering queued renders', function () {
function createMockBlock(ws) {
return {
hasRendered: false,
renderEfficiently: function () {
this.hasRendered = true;
},
// All of the APIs the render management system needs.
getParent: () => null,
getChildren: () => [],
isDisposed: () => false,
getRelativeToSurfaceXY: () => ({x: 0, y: 0}),
updateComponentLocations: () => {},
workspace: ws || createMockWorkspace(),
};
}
function createMockWorkspace() {
return {
resizeContents: () => {},
};
}
test('triggering queued renders rerenders blocks', function () {
const block = createMockBlock();
Blockly.renderManagement.queueRender(block);
Blockly.renderManagement.triggerQueuedRenders();
chai.assert.isTrue(block.hasRendered, 'Expected block to be rendered');
});
test('triggering queued renders rerenders blocks in all workspaces', function () {
const workspace1 = createMockWorkspace();
const workspace2 = createMockWorkspace();
const block1 = createMockBlock(workspace1);
const block2 = createMockBlock(workspace2);
Blockly.renderManagement.queueRender(block1);
Blockly.renderManagement.queueRender(block2);
Blockly.renderManagement.triggerQueuedRenders();
chai.assert.isTrue(block1.hasRendered, 'Expected block1 to be rendered');
chai.assert.isTrue(block2.hasRendered, 'Expected block2 to be rendered');
});
test('triggering queued renders in one workspace does not rerender blocks in another workspace', function () {
const workspace1 = createMockWorkspace();
const workspace2 = createMockWorkspace();
const block1 = createMockBlock(workspace1);
const block2 = createMockBlock(workspace2);
Blockly.renderManagement.queueRender(block1);
Blockly.renderManagement.queueRender(block2);
Blockly.renderManagement.triggerQueuedRenders(workspace1);
chai.assert.isTrue(block1.hasRendered, 'Expected block1 to be rendered');
chai.assert.isFalse(
block2.hasRendered,
'Expected block2 to not be rendered',
);
});
});
});

View File

@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import * as Blockly from 'blockly-test/core';
import {JavascriptGenerator} from 'blockly-test/javascript';
import {PhpGenerator, phpGenerator, Order} from 'blockly-test/php';
import {LuaGenerator} from 'blockly-test/lua';
import {PythonGenerator} from 'blockly-test/python';
import {DartGenerator} from 'blockly-test/dart';
JavascriptGenerator;
PhpGenerator;
LuaGenerator;
PythonGenerator;
DartGenerator;
class TestGenerator extends PhpGenerator {}
const testGenerator = new TestGenerator();
testGenerator.forBlock['test_block'] = function (
_block: Blockly.Block,
_generator: TestGenerator,
) {
return ['a fake code string', Order.ADDITION];
};
phpGenerator.quote_();