mirror of
https://github.com/google/blockly.git
synced 2026-01-04 15:40: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
255 lines
7.6 KiB
TypeScript
255 lines
7.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2024 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {BlockSvg} from './block_svg.js';
|
|
import {ConnectionType} from './connection_type.js';
|
|
import * as eventUtils from './events/utils.js';
|
|
import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
|
|
import * as registry from './registry.js';
|
|
import * as renderManagement from './render_management.js';
|
|
import {RenderedConnection} from './rendered_connection.js';
|
|
import {Renderer as ZelosRenderer} from './renderers/zelos/renderer.js';
|
|
import * as blocks from './serialization/blocks.js';
|
|
import {WorkspaceSvg} from './workspace_svg.js';
|
|
|
|
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();
|
|
|
|
// TODO(7898): Instead of special casing, we should change the dragger to
|
|
// track the change in distance between the dragged connection and the
|
|
// static connection, so that it doesn't disconnect unless that
|
|
// (+ a bit) has been exceeded.
|
|
if (this.shouldUseMarkerPreview(draggedConn, staticConn)) {
|
|
this.markerConn = this.previewMarker(draggedConn, staticConn);
|
|
}
|
|
|
|
if (this.workspace.getRenderer().shouldHighlightConnection(staticConn)) {
|
|
staticConn.highlight();
|
|
}
|
|
|
|
this.draggedConn = draggedConn;
|
|
this.staticConn = staticConn;
|
|
} finally {
|
|
eventUtils.enable();
|
|
}
|
|
}
|
|
|
|
private shouldUseMarkerPreview(
|
|
_draggedConn: RenderedConnection,
|
|
staticConn: RenderedConnection,
|
|
): boolean {
|
|
return (
|
|
staticConn.type === ConnectionType.PREVIOUS_STATEMENT ||
|
|
staticConn.type === ConnectionType.NEXT_STATEMENT ||
|
|
!(this.workspace.getRenderer() instanceof ZelosRenderer)
|
|
);
|
|
}
|
|
|
|
private previewMarker(
|
|
draggedConn: RenderedConnection,
|
|
staticConn: RenderedConnection,
|
|
): RenderedConnection | null {
|
|
const dragged = draggedConn.getSourceBlock();
|
|
const marker = this.createInsertionMarker(dragged);
|
|
const markerConn = this.getMatchingConnection(dragged, marker, draggedConn);
|
|
if (!markerConn) return null;
|
|
|
|
// 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(() => {
|
|
if (marker.isDeadOrDying()) return;
|
|
eventUtils.disable();
|
|
try {
|
|
// Position so that the existing block doesn't move.
|
|
marker?.positionNearConnection(
|
|
markerConn,
|
|
originalOffsetToTarget,
|
|
originalOffsetInBlock,
|
|
);
|
|
marker?.getSvgRoot().setAttribute('visibility', 'visible');
|
|
} finally {
|
|
eventUtils.enable();
|
|
}
|
|
});
|
|
return markerConn;
|
|
}
|
|
|
|
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,
|
|
): RenderedConnection | null {
|
|
const origConns = orig.getConnections_(true);
|
|
const markerConns = marker.getConnections_(true);
|
|
if (origConns.length !== markerConns.length) return null;
|
|
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,
|
|
);
|