Files
blockly/core/connection_db.ts
Beka Westberg 29e1f0cb03 fix: tsc errors picked up from develop (#6224)
* fix: relative path for deprecation utils

* fix: checking if properties exist in svg_math

* fix: set all timeout PIDs to AnyDuringMigration

* fix: make nullability errors explicity in block drag surface

* fix: make null check in events_block_change explicit

* fix: make getEventWorkspace_ internal so we can access it from CommentCreateDeleteHelper

* fix: rename DIV -> containerDiv in tooltip

* fix: ignore backwards compat check in category

* fix: set block styles to AnyDuringMigration

* fix: type typo in KeyboardShortcut

* fix: constants name in row measurables

* fix: typecast in mutator

* fix: populateProcedures type of flattened array

* fix: ignore errors related to workspace comment deserialization

* chore: format files

* fix: renaming imports missing file extensions

* fix: remove check for sound.play

* fix: temporarily remove bad requireType.

All `export type` statements are stripped when tsc is run. This means
that when we attempt to require BlockDefinition from the block files, we
get an error because it does not exist.

We decided to temporarily remove the require, because this will no
longer be a problem when we conver the blocks to typescript, and
everything gets compiled together.

* fix: bad jsdoc in array

* fix: silence missing property errors

Closure was complaining about inexistant properties, but they actually
do exist, they're just not being transpiled by tsc in a way that closure
understands.

I.E. if things are initialized in a function called by the constructor,
rather than in a class field or in the custructor itself, closure would
error.

It would also error on enums, because they are transpiled to a weird
IIFE.

* fix: context menu action handler not knowing the type of this.

this: TypeX information gets stripped when tsc is run, so closure could
not know that this was not global. Fixed this by reorganizing to use the
option object directly instead of passing it to onAction to be bound to
this.

* fix: readd getDeveloperVars checks (should not be part of migration)

This was found because ALL_DEVELOPER_VARS_WARNINGS_BY_BLOCK_TYPE was no
longer being accessed.

* fix: silence closure errors about overriding supertype props

We propertly define the overrides in typescript, but these get removed
from the compiled output, so closure doesn't know they exist.

* fix: silence globalThis errors

this: TypeX annotations get stripped from the compiled output, so
closure can't know that we're accessing the correct things. However,
typescript makes sure that this always has the correct properties, so
silencing this should be fine.

* fix: bad jsdoc name

* chore: attempt compiling with blockly.js

* fix: attempt moving the import statement above the namespace line

* chore: add todo comments to block def files

* chore: remove todo from context menu

* chore: add comments abotu disabled errors
2022-06-27 09:25:56 -07:00

304 lines
10 KiB
TypeScript

/**
* @license
* Copyright 2011 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview A database of all the rendered connections that could
* possibly be connected to (i.e. not collapsed, etc).
* Sorted by y coordinate.
*/
/**
* A database of all the rendered connections that could
* possibly be connected to (i.e. not collapsed, etc).
* Sorted by y coordinate.
* @class
*/
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.ConnectionDB');
// Unused import preserved for side-effects. Remove if unneeded.
import './constants.js';
import {ConnectionType} from './connection_type.js';
/* eslint-disable-next-line no-unused-vars */
import {IConnectionChecker} from './interfaces/i_connection_checker.js';
/* eslint-disable-next-line no-unused-vars */
import {RenderedConnection} from './rendered_connection.js';
/* eslint-disable-next-line no-unused-vars */
import {Coordinate} from './utils/coordinate.js';
/**
* Database of connections.
* Connections are stored in order of their vertical component. This way
* connections in an area may be looked up quickly using a binary search.
* @alias Blockly.ConnectionDB
*/
export class ConnectionDB {
/** Array of connections sorted by y position in workspace units. */
private readonly connections_: RenderedConnection[] = [];
/**
* @param checker The workspace's connection type checker, used to decide if
* connections are valid during a drag.
*/
constructor(private readonly checker: IConnectionChecker) {}
/**
* Add a connection to the database. Should not already exist in the database.
* @param connection The connection to be added.
* @param yPos The y position used to decide where to insert the connection.
* @internal
*/
addConnection(connection: RenderedConnection, yPos: number) {
const index = this.calculateIndexForYPos_(yPos);
this.connections_.splice(index, 0, connection);
}
/**
* Finds the index of the given connection.
*
* Starts by doing a binary search to find the approximate location, then
* linearly searches nearby for the exact connection.
* @param conn The connection to find.
* @param yPos The y position used to find the index of the connection.
* @return The index of the connection, or -1 if the connection was not found.
*/
private findIndexOfConnection_(conn: RenderedConnection, yPos: number):
number {
if (!this.connections_.length) {
return -1;
}
const bestGuess = this.calculateIndexForYPos_(yPos);
if (bestGuess >= this.connections_.length) {
// Not in list
return -1;
}
yPos = conn.y;
// Walk forward and back on the y axis looking for the connection.
let pointer = bestGuess;
while (pointer >= 0 && this.connections_[pointer].y === yPos) {
if (this.connections_[pointer] === conn) {
return pointer;
}
pointer--;
}
pointer = bestGuess;
while (pointer < this.connections_.length &&
this.connections_[pointer].y === yPos) {
if (this.connections_[pointer] === conn) {
return pointer;
}
pointer++;
}
return -1;
}
/**
* Finds the correct index for the given y position.
* @param yPos The y position used to decide where to insert the connection.
* @return The candidate index.
*/
private calculateIndexForYPos_(yPos: number): number {
if (!this.connections_.length) {
return 0;
}
let pointerMin = 0;
let pointerMax = this.connections_.length;
while (pointerMin < pointerMax) {
const pointerMid = Math.floor((pointerMin + pointerMax) / 2);
if (this.connections_[pointerMid].y < yPos) {
pointerMin = pointerMid + 1;
} else if (this.connections_[pointerMid].y > yPos) {
pointerMax = pointerMid;
} else {
pointerMin = pointerMid;
break;
}
}
return pointerMin;
}
/**
* Remove a connection from the database. Must already exist in DB.
* @param connection The connection to be removed.
* @param yPos The y position used to find the index of the connection.
* @throws {Error} If the connection cannot be found in the database.
*/
removeConnection(connection: RenderedConnection, yPos: number) {
const index = this.findIndexOfConnection_(connection, yPos);
if (index === -1) {
throw Error('Unable to find connection in connectionDB.');
}
this.connections_.splice(index, 1);
}
/**
* Find all nearby connections to the given connection.
* Type checking does not apply, since this function is used for bumping.
* @param connection The connection whose neighbours should be returned.
* @param maxRadius The maximum radius to another connection.
* @return List of connections.
*/
getNeighbours(connection: RenderedConnection, maxRadius: number):
RenderedConnection[] {
const db = this.connections_;
const currentX = connection.x;
const currentY = connection.y;
// Binary search to find the closest y location.
let pointerMin = 0;
let pointerMax = db.length - 2;
let pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (db[pointerMid].y < currentY) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
}
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
}
const neighbours: AnyDuringMigration[] = [];
/**
* Computes if the current connection is within the allowed radius of
* another connection. This function is a closure and has access to outside
* variables.
* @param yIndex The other connection's index in the database.
* @return True if the current connection's vertical distance from the other
* connection is less than the allowed radius.
*/
function checkConnection_(yIndex: number): boolean {
const dx = currentX - db[yIndex].x;
const dy = currentY - db[yIndex].y;
const r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxRadius) {
neighbours.push(db[yIndex]);
}
return dy < maxRadius;
}
// Walk forward and back on the y axis looking for the closest x,y point.
pointerMin = pointerMid;
pointerMax = pointerMid;
if (db.length) {
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
pointerMin--;
}
do {
pointerMax++;
} while (pointerMax < db.length && checkConnection_(pointerMax));
}
return neighbours;
}
/**
* Is the candidate connection close to the reference connection.
* Extremely fast; only looks at Y distance.
* @param index Index in database of candidate connection.
* @param baseY Reference connection's Y value.
* @param maxRadius The maximum radius to another connection.
* @return True if connection is in range.
*/
private isInYRange_(index: number, baseY: number, maxRadius: number):
boolean {
return Math.abs(this.connections_[index].y - baseY) <= maxRadius;
}
/**
* Find the closest compatible connection to this connection.
* @param conn The connection searching for a compatible mate.
* @param maxRadius The maximum radius to another connection.
* @param dxy Offset between this connection's location in the database and
* the current location (as a result of dragging).
* @return Contains two properties: 'connection' which is either another
* connection or null, and 'radius' which is the distance.
*/
searchForClosest(
conn: RenderedConnection, maxRadius: number,
dxy: Coordinate): {connection: RenderedConnection, radius: number} {
if (!this.connections_.length) {
// Don't bother.
// AnyDuringMigration because: Type 'null' is not assignable to type
// 'RenderedConnection'.
return {connection: null as AnyDuringMigration, radius: maxRadius};
}
// Stash the values of x and y from before the drag.
const baseY = conn.y;
const baseX = conn.x;
conn.x = baseX + dxy.x;
conn.y = baseY + dxy.y;
// calculateIndexForYPos_ finds an index for insertion, which is always
// after any block with the same y index. We want to search both forward
// and back, so search on both sides of the index.
const closestIndex = this.calculateIndexForYPos_(conn.y);
let bestConnection = null;
let bestRadius = maxRadius;
let temp;
// Walk forward and back on the y axis looking for the closest x,y point.
let pointerMin = closestIndex - 1;
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) {
temp = this.connections_[pointerMin];
if (this.checker.canConnect(conn, temp, true, bestRadius)) {
bestConnection = temp;
// AnyDuringMigration because: Argument of type 'RenderedConnection' is
// not assignable to parameter of type 'Connection'.
bestRadius = temp.distanceFrom(conn as AnyDuringMigration);
}
pointerMin--;
}
let pointerMax = closestIndex;
while (pointerMax < this.connections_.length &&
this.isInYRange_(pointerMax, conn.y, maxRadius)) {
temp = this.connections_[pointerMax];
if (this.checker.canConnect(conn, temp, true, bestRadius)) {
bestConnection = temp;
// AnyDuringMigration because: Argument of type 'RenderedConnection' is
// not assignable to parameter of type 'Connection'.
bestRadius = temp.distanceFrom(conn as AnyDuringMigration);
}
pointerMax++;
}
// Reset the values of x and y.
conn.x = baseX;
conn.y = baseY;
// If there were no valid connections, bestConnection will be null.
// AnyDuringMigration because: Type 'RenderedConnection | null' is not
// assignable to type 'RenderedConnection'.
return {
connection: bestConnection as AnyDuringMigration,
radius: bestRadius
};
}
/**
* Initialize a set of connection DBs for a workspace.
* @param checker The workspace's connection checker, used to decide if
* connections are valid during a drag.
* @return Array of databases.
*/
static init(checker: IConnectionChecker): ConnectionDB[] {
// Create four databases, one for each connection type.
const dbList = [];
dbList[ConnectionType.INPUT_VALUE] = new ConnectionDB(checker);
dbList[ConnectionType.OUTPUT_VALUE] = new ConnectionDB(checker);
dbList[ConnectionType.NEXT_STATEMENT] = new ConnectionDB(checker);
dbList[ConnectionType.PREVIOUS_STATEMENT] = new ConnectionDB(checker);
return dbList;
}
}