diff --git a/core/flyout_base.ts b/core/flyout_base.ts index 76d2e8f8c..77eae8cca 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -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); @@ -1235,8 +1235,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; diff --git a/core/render_management.ts b/core/render_management.ts index 541459860..f0ec2a133 100644 --- a/core/render_management.ts +++ b/core/render_management.ts @@ -6,12 +6,13 @@ import {BlockSvg} from './block_svg.js'; import * as userAgent from './utils/useragent.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(); /** The set of all blocks in need of rendering. */ -let dirtyBlocks = new WeakSet(); +const dirtyBlocks = new WeakSet(); /** * The promise which resolves after the current set of renders is completed. Or @@ -75,12 +76,14 @@ export function finishQueuedRenders(): Promise { * 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(); } /** @@ -110,10 +113,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); } @@ -125,9 +134,19 @@ function doRenders() { block.updateComponentLocations(blockOrigin); } - rootBlocks.clear(); - dirtyBlocks = new Set(); - 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); + for (const child of block.getChildren(false)) { + dequeueBlock(child); + } } /** diff --git a/package-lock.json b/package-lock.json index 63fa0b775..d9fdbeded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "blockly", - "version": "10.3.0", + "version": "10.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "blockly", - "version": "10.3.0", + "version": "10.3.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index aaf3fad2a..8c0b05750 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blockly", - "version": "10.3.0", + "version": "10.3.1", "description": "Blockly is a library for building visual programming editors.", "keywords": [ "blockly" diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index d5d957df4..4a69d2bab 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -57,4 +57,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', + ); + }); + }); });