Skip to content

Commit abf081f

Browse files
authored
new strategy for duplicate on drag blocks (#10343)
1 parent 327ab19 commit abf081f

File tree

6 files changed

+84
-8
lines changed

6 files changed

+84
-8
lines changed

pxtblocks/builtins/loops.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import * as Blockly from "blockly";
44
import { installBuiltinHelpInfo, setBuiltinHelpInfo, setHelpResources } from "../help";
5+
import { setDuplicateOnDrag } from "../plugins/duplicateOnDrag";
56

67
export function initLoops() {
78
const msg = Blockly.Msg;
@@ -104,6 +105,7 @@ export function initLoops() {
104105
}
105106
}
106107
};
108+
setDuplicateOnDrag(pxtControlsForId, "VAR");
107109

108110
// controls_simple_for
109111
const controlsSimpleForId = "controls_simple_for";
@@ -313,6 +315,7 @@ export function initLoops() {
313315
);
314316
}
315317
};
318+
setDuplicateOnDrag(pxtControlsForOfId, "VAR");
316319

317320
// controls_for_of
318321
const controlsForOfId = "controls_for_of";

pxtblocks/loader.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { initOnStart } from "./builtins/misc";
2323
import { initContextMenu } from "./contextMenu";
2424
import { renderCodeCard } from "./codecardRenderer";
2525
import { FieldDropdown } from "./fields/field_dropdown";
26-
import { setDraggableShadowBlocks, setDuplicateOnDragStrategy } from "./plugins/duplicateOnDrag";
26+
import { setDraggableShadowBlocks, setDuplicateOnDrag, setDuplicateOnDragStrategy } from "./plugins/duplicateOnDrag";
2727
import { applyPolyfills } from "./polyfills";
2828

2929

@@ -259,6 +259,11 @@ function initBlock(block: Blockly.Block, info: pxtc.BlocksInfo, fn: pxtc.SymbolI
259259
} else {
260260
i.setCheck("Variable");
261261
}
262+
263+
});
264+
265+
comp.handlerArgs.forEach(arg => {
266+
setDuplicateOnDrag(block.type, "HANDLER_DRAG_PARAM_" + arg.name);
262267
});
263268
}
264269
else {

pxtblocks/plugins/duplicateOnDrag/connectionChecker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as Blockly from "blockly";
2-
import { isDuplicateOnDragBlock } from "./duplicateOnDrag";
2+
import { shouldDuplicateOnDrag } from "./duplicateOnDrag";
33

44

55
const OPPOSITE_TYPE: number[] = [];
@@ -16,7 +16,7 @@ export class DuplicateOnDragConnectionChecker extends Blockly.ConnectionChecker
1616

1717
const replacedBlock = b.targetBlock();
1818

19-
if (replacedBlock && isDuplicateOnDragBlock(replacedBlock)) return false;
19+
if (replacedBlock && shouldDuplicateOnDrag(replacedBlock)) return false;
2020

2121
return true;
2222
}

pxtblocks/plugins/duplicateOnDrag/dragStrategy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import * as Blockly from "blockly";
88

9-
import { DUPLICATE_ON_DRAG_MUTATION_KEY, isAllowlistedShadow } from "./duplicateOnDrag";
9+
import { DUPLICATE_ON_DRAG_MUTATION_KEY, isAllowlistedShadow, shouldDuplicateOnDrag } from "./duplicateOnDrag";
1010
import eventUtils = Blockly.Events;
1111
import Coordinate = Blockly.utils.Coordinate;
1212
import dom = Blockly.utils.dom;
@@ -160,7 +160,7 @@ export class DuplicateOnDragStrategy implements Blockly.IDragStrategy {
160160

161161
const mutation = this.block.mutationToDom?.();
162162

163-
if (mutation?.getAttribute(DUPLICATE_ON_DRAG_MUTATION_KEY)?.toLowerCase() === "true" || (isAllowlistedShadow(this.block) && isShadow)) {
163+
if (shouldDuplicateOnDrag(this.block)) {
164164
const output = this.block.outputConnection;
165165

166166
if (!output?.targetConnection) return;

pxtblocks/plugins/duplicateOnDrag/duplicateOnDrag.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,87 @@ import * as Blockly from "blockly";
33
export const DUPLICATE_ON_DRAG_MUTATION_KEY = "duplicateondrag";
44

55
let draggableShadowAllowlist: string[];
6+
let duplicateRefs: DuplicateOnDragRef[];
67

7-
export function isDuplicateOnDragBlock(block: Blockly.Block) {
8-
return block.mutationToDom?.()?.getAttribute(DUPLICATE_ON_DRAG_MUTATION_KEY)?.toLowerCase() === "true";
8+
interface DuplicateOnDragRef {
9+
parentBlockType: string;
10+
inputName?: string;
11+
childBlockType?: string;
912
}
1013

1114
export function setDraggableShadowBlocks(ids: string[]) {
1215
draggableShadowAllowlist = ids;
1316
}
1417

18+
/**
19+
* Configures duplicate on drag for a block's child inputs
20+
*
21+
* @param parentBlockType The type of the parent block
22+
* @param inputName The value input to duplicate blocks on when dragged. If not
23+
* specified, all child value inputs will be duplicated
24+
* @param childBlockType The type of the child block to be duplicated. If not specified,
25+
* any block attached to the input will be duplicated on drag
26+
* regardless of type
27+
*/
28+
export function setDuplicateOnDrag(parentBlockType: string, inputName?: string, childBlockType?: string) {
29+
if (!duplicateRefs) {
30+
duplicateRefs = [];
31+
}
32+
33+
const existing = duplicateRefs.some(ref => ref.parentBlockType === parentBlockType && ref.inputName === inputName && ref.childBlockType === childBlockType);
34+
if (existing) {
35+
return;
36+
}
37+
38+
duplicateRefs.push({
39+
parentBlockType,
40+
inputName,
41+
childBlockType
42+
});
43+
}
44+
1545
export function isAllowlistedShadow(block: Blockly.Block) {
1646
if (draggableShadowAllowlist) {
1747
if (draggableShadowAllowlist.indexOf(block.type) !== -1) {
1848
return true;
1949
}
2050
}
51+
return false;
52+
}
53+
54+
export function shouldDuplicateOnDrag(block: Blockly.Block) {
55+
if (block.isShadow() && isAllowlistedShadow(block)) {
56+
return true;
57+
}
58+
59+
if (duplicateRefs) {
60+
const parent = block.outputConnection?.targetBlock();
61+
62+
if (parent) {
63+
const refs = duplicateRefs.filter(r => r.parentBlockType === parent.type);
64+
65+
for (const ref of refs) {
66+
if (ref && (!ref.childBlockType || ref.childBlockType === block.type)) {
67+
if (ref.inputName) {
68+
const targetConnection = block.outputConnection.targetConnection;
69+
if (targetConnection.getParentInput().name === ref.inputName) {
70+
return true;
71+
}
72+
}
73+
else {
74+
return true;
75+
}
76+
}
77+
}
78+
}
79+
}
80+
81+
if (block.mutationToDom) {
82+
const mutation = block.mutationToDom();
83+
if (mutation?.getAttribute(DUPLICATE_ON_DRAG_MUTATION_KEY)?.toLowerCase() === "true") {
84+
return true;
85+
}
86+
}
2187

2288
return false;
2389
}

pxtblocks/plugins/functions/blocks/functionDefinitionBlock.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { MsgKey } from "../msg";
2121
import { FunctionManager } from "../functionManager";
2222
import { COLLAPSE_IMAGE_DATAURI } from "../svgs";
2323
import { ArgumentReporterBlock } from "./argumentReporterBlocks";
24-
import { DUPLICATE_ON_DRAG_MUTATION_KEY } from "../../duplicateOnDrag";
24+
import { DUPLICATE_ON_DRAG_MUTATION_KEY, setDuplicateOnDrag } from "../../duplicateOnDrag";
2525

2626
interface FunctionDefinitionMixin extends CommonFunctionMixin {
2727
createArgumentReporter_(arg: FunctionArgument): ArgumentReporterBlock;
@@ -205,6 +205,8 @@ Blockly.Blocks[FUNCTION_DEFINITION_BLOCK_TYPE] = {
205205
},
206206
};
207207

208+
setDuplicateOnDrag(FUNCTION_DEFINITION_BLOCK_TYPE);
209+
208210

209211
function editFunctionCallback(block: CommonFunctionBlock) {
210212
// Edit can come from either the function definition or a function call.

0 commit comments

Comments
 (0)