Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: change the block-dynamic-connections plugin to use a connection previewer instead of monkey patching #2190

Merged
merged 14 commits into from
Feb 5, 2024
Merged
12 changes: 12 additions & 0 deletions plugins/block-dynamic-connection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ npm install @blockly/block-dynamic-connection --save
```js
import * as Blockly from 'blockly';
import * as BlockDynamicConnection from '@blockly/block-dynamic-connection';

const ws = Blockly.inject({
// options...
plugins: {
connectionPreviewer:
BlockDynamicConnection.decoratePreviewerWithDynamicConnections(
// Replace with a custom connection previewer, or remove to decorate
// the default one.
Blockly.InsertionMarkerPreviewer,
),
},
};
```

## API
Expand Down
20 changes: 10 additions & 10 deletions plugins/block-dynamic-connection/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions plugins/block-dynamic-connection/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blockly/block-dynamic-connection",
"version": "0.5.6",
"version": "1.0.0",
"description": "A group of blocks that add connections dynamically.",
"scripts": {
"audit:fix": "blockly-scripts auditFix",
Expand Down Expand Up @@ -42,13 +42,13 @@
"devDependencies": {
"@blockly/dev-scripts": "^3.1.1",
"@blockly/dev-tools": "^7.1.5",
"blockly": "^10.2.0",
"blockly": "^10.4.0-beta.1",
"chai": "^4.2.0",
"mocha": "^10.2.0",
"typescript": "^5.2.2"
},
"peerDependencies": {
"blockly": "^10.2.0"
"blockly": "^10.4.0-beta.1"
},
"publishConfig": {
"access": "public",
Expand Down
96 changes: 96 additions & 0 deletions plugins/block-dynamic-connection/src/connection_previewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from 'blockly/core';

interface ConnectionPreviewerConstructor {
new (draggedBlock: Blockly.BlockSvg): Blockly.IConnectionPreviewer;
}

interface DynamicBlock extends Blockly.BlockSvg {
onPendingConnection(connection: Blockly.RenderedConnection): void;
finalizeConnections(): void;
}

function blockIsDynamic(block: Blockly.BlockSvg): block is DynamicBlock {
return (
(block as DynamicBlock)['onPendingConnection'] !== undefined &&
(block as DynamicBlock)['finalizeConnections'] !== undefined
);
}

/**
* Returns a connection previewer constructor that decorates the passed
* constructor to add connection previewing.
*
* @param BasePreviewerConstructor The constructor for the base connection
* previewer class being decorated. If not provided, the default
* InsertionMarkerPreviewer will be used.
* @return A decorated connection previewer constructor.
*/
export function decoratePreviewer(
BasePreviewerConstructor?: ConnectionPreviewerConstructor,
): ConnectionPreviewerConstructor {
return class implements Blockly.IConnectionPreviewer {
private basePreviewer: Blockly.IConnectionPreviewer;

private pendingBlocks: Set<DynamicBlock> = new Set();

constructor(draggedBlock: Blockly.BlockSvg) {
const BaseConstructor =
BasePreviewerConstructor ?? Blockly.InsertionMarkerPreviewer;
this.basePreviewer = new BaseConstructor(draggedBlock);
}

previewReplacement(
draggedConn: Blockly.RenderedConnection,
staticConn: Blockly.RenderedConnection,
replacedBlock: Blockly.BlockSvg,
): void {
this.previewDynamism(staticConn);
this.basePreviewer.previewReplacement(
draggedConn,
staticConn,
replacedBlock,
);
}

previewConnection(
draggedConn: Blockly.RenderedConnection,
staticConn: Blockly.RenderedConnection,
): void {
this.previewDynamism(staticConn);
this.basePreviewer.previewConnection(draggedConn, staticConn);
}

hidePreview(): void {
this.basePreviewer.hidePreview();
}

dispose(): void {
for (const block of this.pendingBlocks) {
if (block.isDeadOrDying()) return;
block.finalizeConnections();
}
this.pendingBlocks.clear();
this.basePreviewer.dispose();
}

/**
* If the block is a dynamic block, calls onPendingConnection and
* stores the block to be finalized later.
*
* @param conn The block to trigger onPendingConnection on.
*/
private previewDynamism(conn: Blockly.RenderedConnection) {
const block = conn.getSourceBlock();
if (blockIsDynamic(block)) {
block.onPendingConnection(conn);
this.pendingBlocks.add(block);
}
}
};
}
35 changes: 28 additions & 7 deletions plugins/block-dynamic-connection/src/dynamic_if.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,14 @@ const DYNAMIC_IF_MIXIN = {
* @returns The state of this block, ie the else if count and else state.
*/
saveExtraState: function (this: DynamicIfBlock): IfExtraState | null {
// If we call finalizeConnections here without disabling events, we get into
// an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
if (!this.isCorrectlyFormatted()) {
// If we call finalizeConnections here without disabling events, we get into
// an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
}

if (!this.elseifCount && !this.elseCount) {
return null;
Expand Down Expand Up @@ -218,6 +220,13 @@ const DYNAMIC_IF_MIXIN = {
this: DynamicIfBlock,
connection: Blockly.Connection,
): number | null {
if (
!connection.targetConnection ||
connection.targetBlock()?.isInsertionMarker()
) {
// This connection is available.
return null;
}
for (let i = 0; i < this.inputList.length; i++) {
const input = this.inputList[i];
if (input.connection == connection) {
Expand Down Expand Up @@ -270,7 +279,7 @@ const DYNAMIC_IF_MIXIN = {
return;
}
const input = this.inputList[inputIndex];
if (connection.targetConnection && input.name.includes('IF')) {
if (input.name.includes('IF')) {
const nextIfInput = this.inputList[inputIndex + 2];
if (!nextIfInput || nextIfInput.name == 'ELSE') {
this.insertElseIf(inputIndex + 2, Blockly.utils.idGenerator.genUid());
Expand Down Expand Up @@ -381,6 +390,18 @@ const DYNAMIC_IF_MIXIN = {
Blockly.Msg['CONTROLS_IF_MSG_THEN'],
);
},

/**
* Returns true if all of the inputs on this block are in order.
* False otherwise.
*/
isCorrectlyFormatted(this: DynamicIfBlock): boolean {
for (let i = 0; i < this.inputList.length - 1; i += 2) {
if (this.inputList[i].name !== `IF${i}`) return false;
if (this.inputList[i + 1].name !== `DO${i}`) return false;
}
return true;
},
};

Blockly.Blocks['dynamic_if'] = DYNAMIC_IF_MIXIN;
36 changes: 26 additions & 10 deletions plugins/block-dynamic-connection/src/dynamic_list_create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,14 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
* @returns The state of this block, ie the item count.
*/
saveExtraState: function (this: DynamicListCreateBlock): {itemCount: number} {
// If we call finalizeConnections here without disabling events, we get into
// an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
if (!this.isCorrectlyFormatted()) {
// If we call finalizeConnections here without disabling events, we get
// into an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
}

return {
itemCount: this.itemCount,
Expand Down Expand Up @@ -156,8 +158,11 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
this: DynamicListCreateBlock,
connection: Blockly.Connection,
): number | null {
if (!connection.targetConnection) {
// this connection is available
if (
!connection.targetConnection ||
connection.targetBlock()?.isInsertionMarker()
) {
// This connection is available.
return null;
}

Expand All @@ -169,7 +174,7 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
}

if (connectionIndex == this.inputList.length - 1) {
// this connection is the last one and already has a block in it, so
// This connection is the last one and already has a block in it, so
// we should add a new connection at the end.
return this.inputList.length + 1;
}
Expand All @@ -183,7 +188,7 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
return connectionIndex + 1;
}

// Don't add new connection
// Don't add new connection.
return null;
},

Expand Down Expand Up @@ -281,6 +286,17 @@ const DYNAMIC_LIST_CREATE_MIXIN = {
Blockly.Msg['LISTS_CREATE_WITH_INPUT_WITH'],
);
},

/**
* Returns true if all of the inputs on this block are in order.
* False otherwise.
*/
isCorrectlyFormatted(this: DynamicListCreateBlock): boolean {
for (let i = 0; i < this.inputList.length; i++) {
if (this.inputList[i].name !== `ADD${i}`) return false;
}
return true;
},
};

Blockly.Blocks['dynamic_list_create'] = DYNAMIC_LIST_CREATE_MIXIN;
36 changes: 26 additions & 10 deletions plugins/block-dynamic-connection/src/dynamic_text_join.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,14 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
* @returns The state of this block, ie the item count.
*/
saveExtraState: function (this: DynamicTextJoinBlock): {itemCount: number} {
// If we call finalizeConnections here without disabling events, we get into
// an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
if (!this.isCorrectlyFormatted()) {
// If we call finalizeConnections here without disabling events, we get into
// an event loop.
Blockly.Events.disable();
this.finalizeConnections();
if (this instanceof Blockly.BlockSvg) this.initSvg();
Blockly.Events.enable();
}

return {
itemCount: this.itemCount,
Expand Down Expand Up @@ -152,8 +154,11 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
this: DynamicTextJoinBlock,
connection: Blockly.Connection,
): number | null {
if (!connection.targetConnection) {
// this connection is available
if (
!connection.targetConnection ||
connection.targetBlock()?.isInsertionMarker()
) {
// This connection is available.
return null;
}

Expand All @@ -165,7 +170,7 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
}

if (connectionIndex == this.inputList.length - 1) {
// this connection is the last one and already has a block in it, so
// This connection is the last one and already has a block in it, so
// we should add a new connection at the end.
return this.inputList.length + 1;
}
Expand All @@ -179,7 +184,7 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
return connectionIndex + 1;
}

// Don't add new connection
// Don't add new connection.
return null;
},

Expand Down Expand Up @@ -278,6 +283,17 @@ const DYNAMIC_TEXT_JOIN_MIXIN = {
Blockly.Msg['TEXT_JOIN_TITLE_CREATEWITH'],
);
},

/**
* Returns true if all of the inputs on this block are in order.
* False otherwise.
*/
isCorrectlyFormatted(this: DynamicTextJoinBlock): boolean {
for (let i = 0; i < this.inputList.length; i++) {
if (this.inputList[i].name !== `ADD${i}`) return false;
}
return true;
},
};

Blockly.Blocks['dynamic_text_join'] = DYNAMIC_TEXT_JOIN_MIXIN;
Loading
Loading