mirror of
https://github.com/google/blockly.git
synced 2026-01-04 07:30:08 +01:00
* chore(deps): Add pretter-plugin-organize-imports * chore: Remove insignificant blank lines in import sections Since prettier-plugin-organize-imports sorts imports within sections separated by blank lines, but preserves the section divisions, remove any blank lines that are not dividing imports into meaningful sections. Do not remove blank lines separating side-effect-only imports from main imports. * chore: Remove unneded eslint-disable directives * chore: Organise imports
194 lines
5.5 KiB
TypeScript
194 lines
5.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2023 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {BlockSvg} from './block_svg.js';
|
|
import * as eventUtils from './events/utils.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<BlockSvg>();
|
|
|
|
/** The set of all blocks in need of rendering. */
|
|
const dirtyBlocks = new WeakSet<BlockSvg>();
|
|
|
|
/** A map from queued blocks to the event group from when they were queued. */
|
|
const eventGroups = new WeakMap<BlockSvg, string>();
|
|
|
|
/**
|
|
* The promise which resolves after the current set of renders is completed. Or
|
|
* null if there are no queued renders.
|
|
*
|
|
* Stored so that we can return it from afterQueuedRenders.
|
|
*/
|
|
let afterRendersPromise: Promise<void> | null = null;
|
|
|
|
/** The function to call to resolve the `afterRendersPromise`. */
|
|
let afterRendersResolver: (() => void) | null = null;
|
|
|
|
/**
|
|
* The ID of the current animation frame request. Used to cancel the request
|
|
* if necessary.
|
|
*/
|
|
let animationRequestId = 0;
|
|
|
|
/**
|
|
* Registers that the given block and all of its parents need to be rerendered,
|
|
* and registers a callback to do so after a delay, to allowf or batching.
|
|
*
|
|
* @param block The block to rerender.
|
|
* @returns A promise that resolves after the currently queued renders have been
|
|
* completed. Used for triggering other behavior that relies on updated
|
|
* size/position location for the block.
|
|
* @internal
|
|
*/
|
|
export function queueRender(block: BlockSvg): Promise<void> {
|
|
queueBlock(block);
|
|
|
|
if (alwaysImmediatelyRender()) {
|
|
doRenders();
|
|
return Promise.resolve();
|
|
}
|
|
|
|
if (!afterRendersPromise) {
|
|
afterRendersPromise = new Promise((resolve) => {
|
|
afterRendersResolver = resolve;
|
|
animationRequestId = window.requestAnimationFrame(() => {
|
|
doRenders();
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
return afterRendersPromise;
|
|
}
|
|
|
|
/**
|
|
* @returns A promise that resolves after the currently queued renders have
|
|
* been completed.
|
|
*/
|
|
export function finishQueuedRenders(): Promise<void> {
|
|
// If there are no queued renders, return a resolved promise so `then`
|
|
// callbacks trigger immediately.
|
|
return afterRendersPromise ? afterRendersPromise : Promise.resolve();
|
|
}
|
|
|
|
/**
|
|
* Triggers an immediate render of all queued renders. Should only be used in
|
|
* 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(workspace?: WorkspaceSvg) {
|
|
if (!workspace) window.cancelAnimationFrame(animationRequestId);
|
|
doRenders(workspace);
|
|
if (!workspace && afterRendersResolver) afterRendersResolver();
|
|
}
|
|
|
|
/**
|
|
* @returns True if we should always trigger an immediate render.
|
|
* Some platforms don't properly support `requestAnimationFrame`, so to
|
|
* avoid glitchiness, we give up the performance improvements.
|
|
*/
|
|
function alwaysImmediatelyRender() {
|
|
return userAgent.JavaFx;
|
|
}
|
|
|
|
/**
|
|
* Adds the given block and its parents to the render queue. Adds the root block
|
|
* to the list of root blocks.
|
|
*
|
|
* @param block The block to queue.
|
|
*/
|
|
function queueBlock(block: BlockSvg) {
|
|
dirtyBlocks.add(block);
|
|
eventGroups.set(block, eventUtils.getGroup());
|
|
const parent = block.getParent();
|
|
if (parent) {
|
|
queueBlock(parent);
|
|
} else {
|
|
rootBlocks.add(block);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rerenders all of the blocks in the queue.
|
|
*
|
|
* @param workspace If provided, only rerender blocks in this workspace.
|
|
*/
|
|
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);
|
|
}
|
|
for (const workspace of workspaces) {
|
|
workspace.resizeContents();
|
|
}
|
|
for (const block of blocks) {
|
|
const blockOrigin = block.getRelativeToSurfaceXY();
|
|
block.updateComponentLocations(blockOrigin);
|
|
}
|
|
for (const block of blocks) {
|
|
const oldGroup = eventUtils.getGroup();
|
|
const newGroup = eventGroups.get(block);
|
|
if (newGroup) eventUtils.setGroup(newGroup);
|
|
|
|
block.bumpNeighbours();
|
|
|
|
eventUtils.setGroup(oldGroup);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the block should be rendered.
|
|
*
|
|
* No need to render dead blocks.
|
|
*
|
|
* No need to render blocks with parents. A render for the block may have been
|
|
* queued, and the block was connected to a parent, so it is no longer a
|
|
* root block. Rendering will be triggered through the real root block.
|
|
*/
|
|
function shouldRenderRootBlock(block: BlockSvg): boolean {
|
|
return !block.isDisposed() && !block.getParent();
|
|
}
|
|
|
|
/**
|
|
* Recursively renders all of the dirty children of the given block, and
|
|
* then renders the block.
|
|
*
|
|
* @param block The block to rerender.
|
|
*/
|
|
function renderBlock(block: BlockSvg) {
|
|
if (!dirtyBlocks.has(block)) return;
|
|
if (!block.initialized) return;
|
|
for (const child of block.getChildren(false)) {
|
|
renderBlock(child);
|
|
}
|
|
block.renderEfficiently();
|
|
}
|