scratch-blocks 2.0.0-spork.3 → 2.0.0-spork.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +3 -9
- package/package.json +4 -4
- package/src/blocks/procedures.ts +1 -1
- package/src/checkable_continuous_flyout.ts +116 -0
- package/src/checkbox_bubble.ts +25 -5
- package/src/colours.ts +1 -0
- package/src/context_menu_items.ts +24 -0
- package/src/css.ts +30 -21
- package/src/fields/field_matrix.ts +38 -27
- package/src/fields/field_note.ts +1 -1
- package/src/fields/scratch_field_angle.ts +9 -1
- package/src/fields/scratch_field_variable.ts +9 -8
- package/src/flyout_checkbox_icon.ts +15 -21
- package/src/index.ts +6 -3
- package/src/recyclable_block_flyout_inflater.ts +8 -151
- package/src/scratch_block_paster.ts +10 -0
- package/src/{scratch_comment_bubble.js → scratch_comment_bubble.ts} +52 -19
- package/src/scratch_comment_icon.ts +20 -5
- package/src/scratch_continuous_toolbox.ts +36 -30
- package/src/scratch_insertion_marker_previewer.ts +45 -0
- package/src/status_indicator_label_flyout_inflater.ts +8 -6
- package/src/variables.ts +1 -1
- package/src/xml.ts +57 -0
- package/src/checkable_continuous_flyout.js +0 -138
package/src/index.ts
CHANGED
|
@@ -26,11 +26,10 @@ import "./css";
|
|
|
26
26
|
import "./renderer/renderer";
|
|
27
27
|
import * as contextMenuItems from "./context_menu_items";
|
|
28
28
|
import {
|
|
29
|
-
|
|
30
|
-
ContinuousFlyout,
|
|
29
|
+
registerContinuousToolbox,
|
|
31
30
|
ContinuousMetrics,
|
|
32
31
|
} from "@blockly/continuous-toolbox";
|
|
33
|
-
import { CheckableContinuousFlyout } from "./checkable_continuous_flyout
|
|
32
|
+
import { CheckableContinuousFlyout } from "./checkable_continuous_flyout";
|
|
34
33
|
import { buildGlowFilter, glowStack } from "./glows";
|
|
35
34
|
import { ScratchContinuousToolbox } from "./scratch_continuous_toolbox";
|
|
36
35
|
import "./scratch_comment_icon";
|
|
@@ -38,6 +37,7 @@ import "./scratch_dragger";
|
|
|
38
37
|
import "./scratch_variable_map";
|
|
39
38
|
import "./scratch_variable_model";
|
|
40
39
|
import "./scratch_connection_checker";
|
|
40
|
+
import "./scratch_insertion_marker_previewer";
|
|
41
41
|
import "./flyout_checkbox_icon";
|
|
42
42
|
import "./events/events_block_comment_change";
|
|
43
43
|
import "./events/events_block_comment_collapse";
|
|
@@ -81,6 +81,7 @@ export {
|
|
|
81
81
|
StatusIndicatorLabel,
|
|
82
82
|
StatusButtonState,
|
|
83
83
|
} from "./status_indicator_label";
|
|
84
|
+
export * from "./xml";
|
|
84
85
|
|
|
85
86
|
export function inject(container: Element, options: Blockly.BlocklyOptions) {
|
|
86
87
|
registerScratchFieldAngle();
|
|
@@ -120,6 +121,7 @@ export function inject(container: Element, options: Blockly.BlocklyOptions) {
|
|
|
120
121
|
return workspace;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
registerContinuousToolbox();
|
|
123
125
|
Blockly.Scrollbar.scrollbarThickness = Blockly.Touch.TOUCH_ENABLED ? 14 : 11;
|
|
124
126
|
Blockly.FlyoutButton.TEXT_MARGIN_X = 40;
|
|
125
127
|
Blockly.FlyoutButton.TEXT_MARGIN_Y = 10;
|
|
@@ -128,6 +130,7 @@ Blockly.ContextMenuRegistry.registry.unregister("blockInline");
|
|
|
128
130
|
Blockly.ContextMenuItems.registerCommentOptions();
|
|
129
131
|
Blockly.ContextMenuRegistry.registry.unregister("blockDelete");
|
|
130
132
|
contextMenuItems.registerDeleteBlock();
|
|
133
|
+
contextMenuItems.registerDuplicateBlock();
|
|
131
134
|
Blockly.ContextMenuRegistry.registry.unregister("workspaceDelete");
|
|
132
135
|
contextMenuItems.registerDeleteAll();
|
|
133
136
|
Blockly.comments.CommentView.defaultCommentSize = new Blockly.utils.Size(
|
|
@@ -6,21 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import * as Blockly from "blockly/core";
|
|
8
8
|
import { CheckboxBubble } from "./checkbox_bubble";
|
|
9
|
+
import { RecyclableBlockFlyoutInflater as BlocklyRecyclableBlockFlyoutInflater } from "@blockly/continuous-toolbox";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A block inflater that caches and reuses blocks to improve performance.
|
|
12
13
|
*/
|
|
13
|
-
export class RecyclableBlockFlyoutInflater extends
|
|
14
|
-
/**
|
|
15
|
-
* Whether or not block recycling is enabled.
|
|
16
|
-
*/
|
|
17
|
-
recyclingEnabled = true;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Map from block type to block instance.
|
|
21
|
-
*/
|
|
22
|
-
recycledBlocks = new Map<string, Blockly.BlockSvg>();
|
|
23
|
-
|
|
14
|
+
export class RecyclableBlockFlyoutInflater extends BlocklyRecyclableBlockFlyoutInflater {
|
|
24
15
|
/**
|
|
25
16
|
* Creates a block on the flyout workspace from the given block definition.
|
|
26
17
|
*
|
|
@@ -31,152 +22,18 @@ export class RecyclableBlockFlyoutInflater extends Blockly.BlockFlyoutInflater {
|
|
|
31
22
|
load(
|
|
32
23
|
state: Blockly.utils.toolbox.BlockInfo,
|
|
33
24
|
flyoutWorkspace: Blockly.WorkspaceSvg
|
|
34
|
-
): Blockly.
|
|
35
|
-
const
|
|
25
|
+
): Blockly.FlyoutItem {
|
|
26
|
+
const flyoutItem = super.load(state, flyoutWorkspace);
|
|
27
|
+
const block = flyoutItem.getElement();
|
|
36
28
|
if ("checkboxInFlyout" in block && block.checkboxInFlyout) {
|
|
37
29
|
block.moveBy(
|
|
38
|
-
|
|
30
|
+
(flyoutWorkspace.RTL ? -1 : 1) *
|
|
31
|
+
(CheckboxBubble.CHECKBOX_SIZE + CheckboxBubble.CHECKBOX_MARGIN),
|
|
39
32
|
0
|
|
40
33
|
);
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Toggles whether or not recycling is enabled.
|
|
48
|
-
*
|
|
49
|
-
* @param enabled True if recycling should be enabled.
|
|
50
|
-
*/
|
|
51
|
-
setRecyclingEnabled(enabled: boolean) {
|
|
52
|
-
this.recyclingEnabled = enabled;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Creates a new block from the given block definition.
|
|
57
|
-
*
|
|
58
|
-
* @param blockDefinition The definition to create a block from.
|
|
59
|
-
* @returns The newly created block.
|
|
60
|
-
*/
|
|
61
|
-
createBlock(
|
|
62
|
-
blockDefinition: Blockly.utils.toolbox.BlockInfo
|
|
63
|
-
): Blockly.BlockSvg {
|
|
64
|
-
const blockType = this.getTypeFromDefinition(blockDefinition);
|
|
65
|
-
return (
|
|
66
|
-
this.getRecycledBlock(blockType) ??
|
|
67
|
-
super.createBlock(blockDefinition, this.flyoutWorkspace)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Returns the type of a block from an XML or JSON block definition.
|
|
73
|
-
*
|
|
74
|
-
* @param blockDefinition The block definition to parse.
|
|
75
|
-
* @returns The block type.
|
|
76
|
-
*/
|
|
77
|
-
getTypeFromDefinition(
|
|
78
|
-
blockDefinition: Blockly.utils.toolbox.BlockInfo
|
|
79
|
-
): string {
|
|
80
|
-
if (blockDefinition["blockxml"]) {
|
|
81
|
-
const xml =
|
|
82
|
-
typeof blockDefinition["blockxml"] === "string"
|
|
83
|
-
? Blockly.utils.xml.textToDom(blockDefinition["blockxml"])
|
|
84
|
-
: (blockDefinition["blockxml"] as Element);
|
|
85
|
-
return xml.getAttribute("type");
|
|
86
|
-
} else {
|
|
87
|
-
return blockDefinition["type"];
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Puts a previously created block into the recycle bin and moves it to the
|
|
93
|
-
* top of the workspace. Used during large workspace swaps to limit the number
|
|
94
|
-
* of new DOM elements we need to create.
|
|
95
|
-
*
|
|
96
|
-
* @param block The block to recycle.
|
|
97
|
-
*/
|
|
98
|
-
recycleBlock(block: Blockly.BlockSvg) {
|
|
99
|
-
const xy = block.getRelativeToSurfaceXY();
|
|
100
|
-
block.moveBy(-xy.x, -xy.y);
|
|
101
|
-
this.recycledBlocks.set(block.type, block);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Returns a block from the cache of recycled blocks with the given type, or
|
|
106
|
-
* undefined if one cannot be found.
|
|
107
|
-
*
|
|
108
|
-
* @param blockType The type of the block to try to recycle.
|
|
109
|
-
* @returns The recycled block, or undefined if one could not be recycled.
|
|
110
|
-
*/
|
|
111
|
-
getRecycledBlock(blockType: string): Blockly.BlockSvg | undefined {
|
|
112
|
-
const block = this.recycledBlocks.get(blockType);
|
|
113
|
-
this.recycledBlocks.delete(blockType);
|
|
114
|
-
return block;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Returns whether the given block can be recycled or not.
|
|
119
|
-
*
|
|
120
|
-
* @param block The block to check for recyclability.
|
|
121
|
-
* @returns True if the block can be recycled. False otherwise.
|
|
122
|
-
*/
|
|
123
|
-
blockIsRecyclable(block: Blockly.Block): boolean {
|
|
124
|
-
if (!this.recyclingEnabled) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// If the block needs to parse mutations, never recycle.
|
|
129
|
-
if (block.mutationToDom && block.domToMutation) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!block.isEnabled()) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const input of block.inputList) {
|
|
138
|
-
for (const field of input.fieldRow) {
|
|
139
|
-
// No variables.
|
|
140
|
-
if (field.referencesVariables()) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
if (field instanceof Blockly.FieldDropdown) {
|
|
144
|
-
if (field.isOptionListDynamic()) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// Check children.
|
|
150
|
-
if (input.connection) {
|
|
151
|
-
const targetBlock = input.connection.targetBlock();
|
|
152
|
-
if (targetBlock && !this.blockIsRecyclable(targetBlock)) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Disposes of the provided block.
|
|
162
|
-
*
|
|
163
|
-
* @param element The block to dispose of.
|
|
164
|
-
*/
|
|
165
|
-
disposeElement(element: Blockly.BlockSvg) {
|
|
166
|
-
if (this.blockIsRecyclable(element)) {
|
|
167
|
-
this.removeListeners(element.id);
|
|
168
|
-
this.recycleBlock(element);
|
|
169
|
-
} else {
|
|
170
|
-
super.disposeElement(element);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Clears the cache of recycled blocks.
|
|
176
|
-
*/
|
|
177
|
-
emptyRecycledBlocks() {
|
|
178
|
-
this.recycledBlocks.forEach((block) => block.dispose(false, false));
|
|
179
|
-
this.recycledBlocks.clear();
|
|
36
|
+
return flyoutItem;
|
|
180
37
|
}
|
|
181
38
|
}
|
|
182
39
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as Blockly from "blockly/core";
|
|
8
|
+
import type { ScratchCommentIcon } from "./scratch_comment_icon";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Class responsible for handling the pasting of copied blocks.
|
|
@@ -31,6 +32,15 @@ class ScratchBlockPaster extends Blockly.clipboard.BlockPaster {
|
|
|
31
32
|
block.setDragStrategy(new Blockly.dragging.BlockDragStrategy(block));
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
// Deserialization of blocks suppresses events, so even though this gets
|
|
36
|
+
// fired for blocks with comments, the VM will never receive it, causing its
|
|
37
|
+
// state to get out of sync. Manually fire it here (after suppression has
|
|
38
|
+
// been turned off) if needed.
|
|
39
|
+
const commentIcon = block.getIcon(Blockly.icons.IconType.COMMENT);
|
|
40
|
+
if (commentIcon) {
|
|
41
|
+
(commentIcon as ScratchCommentIcon).fireCreateEvent();
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
return block;
|
|
35
45
|
}
|
|
36
46
|
}
|
|
@@ -11,17 +11,28 @@ import * as Blockly from "blockly/core";
|
|
|
11
11
|
* @implements {IBubble}
|
|
12
12
|
* @implements {ISelectable}
|
|
13
13
|
*/
|
|
14
|
-
export class ScratchCommentBubble
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export class ScratchCommentBubble
|
|
15
|
+
extends Blockly.comments.CommentView
|
|
16
|
+
implements Blockly.IBubble, Blockly.ISelectable
|
|
17
|
+
{
|
|
18
|
+
id: string;
|
|
19
|
+
private sourceBlock: Blockly.BlockSvg;
|
|
20
|
+
private anchor?: Blockly.utils.Coordinate;
|
|
21
|
+
private anchorChain?: SVGLineElement;
|
|
22
|
+
private dragStartLocation?: Blockly.utils.Coordinate;
|
|
23
|
+
|
|
24
|
+
constructor(sourceBlock: Blockly.BlockSvg) {
|
|
25
|
+
const commentId = `${sourceBlock.id}_comment`;
|
|
26
|
+
super(sourceBlock.workspace, commentId);
|
|
17
27
|
this.sourceBlock = sourceBlock;
|
|
18
28
|
this.disposing = false;
|
|
19
|
-
this.id =
|
|
29
|
+
this.id = commentId;
|
|
20
30
|
this.setPlaceholderText(Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT);
|
|
21
31
|
this.getSvgRoot().setAttribute(
|
|
22
32
|
"style",
|
|
23
33
|
`--colour-commentBorder: ${sourceBlock.getColourTertiary()};`
|
|
24
34
|
);
|
|
35
|
+
this.getSvgRoot().setAttribute("id", this.id);
|
|
25
36
|
|
|
26
37
|
Blockly.browserEvents.conditionalBind(
|
|
27
38
|
this.getSvgRoot(),
|
|
@@ -34,15 +45,15 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
34
45
|
this.getSvgRoot(),
|
|
35
46
|
"wheel",
|
|
36
47
|
this,
|
|
37
|
-
(e) => {
|
|
48
|
+
(e: WheelEvent) => {
|
|
38
49
|
e.stopPropagation();
|
|
39
50
|
}
|
|
40
51
|
);
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
setDeleteStyle(enable) {}
|
|
54
|
+
setDeleteStyle(enable: boolean) {}
|
|
44
55
|
showContextMenu() {}
|
|
45
|
-
setDragging(start) {}
|
|
56
|
+
setDragging(start: boolean) {}
|
|
46
57
|
select() {}
|
|
47
58
|
unselect() {}
|
|
48
59
|
|
|
@@ -50,11 +61,13 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
50
61
|
return true;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
|
-
moveDuringDrag(newLocation) {
|
|
64
|
+
moveDuringDrag(newLocation: Blockly.utils.Coordinate) {
|
|
54
65
|
this.moveTo(newLocation);
|
|
55
66
|
}
|
|
56
67
|
|
|
57
|
-
moveTo(xOrCoordinate, y)
|
|
68
|
+
moveTo(xOrCoordinate: number, y: number): void;
|
|
69
|
+
moveTo(xOrCoordinate: Blockly.utils.Coordinate): void;
|
|
70
|
+
moveTo(xOrCoordinate: Blockly.utils.Coordinate | number, y?: number) {
|
|
58
71
|
const destination =
|
|
59
72
|
xOrCoordinate instanceof Blockly.utils.Coordinate
|
|
60
73
|
? xOrCoordinate
|
|
@@ -63,22 +76,22 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
63
76
|
this.redrawAnchorChain();
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
startGesture(e) {
|
|
79
|
+
startGesture(e: PointerEvent) {
|
|
67
80
|
const gesture = this.workspace.getGesture(e);
|
|
68
81
|
if (gesture) {
|
|
69
|
-
gesture.
|
|
82
|
+
gesture.handleBubbleStart(e, this);
|
|
70
83
|
Blockly.common.setSelected(this);
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
|
|
74
|
-
startDrag(event) {
|
|
87
|
+
startDrag(event: PointerEvent) {
|
|
75
88
|
this.dragStartLocation = this.getRelativeToSurfaceXY();
|
|
76
89
|
this.workspace.setResizesEnabled(false);
|
|
77
90
|
this.workspace.getLayerManager()?.moveToDragLayer(this);
|
|
78
91
|
Blockly.utils.dom.addClass(this.getSvgRoot(), "blocklyDragging");
|
|
79
92
|
}
|
|
80
93
|
|
|
81
|
-
drag(newLocation, event) {
|
|
94
|
+
drag(newLocation: Blockly.utils.Coordinate, event: Event) {
|
|
82
95
|
this.moveTo(newLocation);
|
|
83
96
|
}
|
|
84
97
|
|
|
@@ -101,7 +114,7 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
101
114
|
this.moveTo(this.dragStartLocation);
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
setAnchorLocation(newAnchor) {
|
|
117
|
+
setAnchorLocation(newAnchor: Blockly.utils.Coordinate) {
|
|
105
118
|
const oldAnchor = this.anchor;
|
|
106
119
|
const alreadyAnchored = !!this.anchor;
|
|
107
120
|
this.anchor = newAnchor;
|
|
@@ -116,15 +129,19 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
dropAnchor() {
|
|
119
|
-
|
|
132
|
+
const verticalOffset = 16;
|
|
133
|
+
this.moveTo(
|
|
134
|
+
this.anchor.x + 40 * (this.workspace.RTL ? -1 : 1),
|
|
135
|
+
this.anchor.y - verticalOffset
|
|
136
|
+
);
|
|
120
137
|
const location = this.getRelativeToSurfaceXY();
|
|
121
138
|
this.anchorChain = Blockly.utils.dom.createSvgElement(
|
|
122
139
|
Blockly.utils.Svg.LINE,
|
|
123
140
|
{
|
|
124
141
|
x1: this.anchor.x - location.x,
|
|
125
142
|
y1: this.anchor.y - location.y,
|
|
126
|
-
x2: this.getSize().width / 2,
|
|
127
|
-
y2:
|
|
143
|
+
x2: (this.getSize().width / 2) * (this.workspace.RTL ? -1 : 1),
|
|
144
|
+
y2: verticalOffset,
|
|
128
145
|
style: `stroke: ${this.sourceBlock.getColourTertiary()}; stroke-width: 1`,
|
|
129
146
|
},
|
|
130
147
|
this.getSvgRoot()
|
|
@@ -139,8 +156,8 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
139
156
|
if (!this.anchorChain) return;
|
|
140
157
|
|
|
141
158
|
const location = this.getRelativeToSurfaceXY();
|
|
142
|
-
this.anchorChain.setAttribute("x1", this.anchor.x - location.x);
|
|
143
|
-
this.anchorChain.setAttribute("y1", this.anchor.y - location.y);
|
|
159
|
+
this.anchorChain.setAttribute("x1", `${this.anchor.x - location.x}`);
|
|
160
|
+
this.anchorChain.setAttribute("y1", `${this.anchor.y - location.y}`);
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
getId() {
|
|
@@ -166,4 +183,20 @@ export class ScratchCommentBubble extends Blockly.comments.CommentView {
|
|
|
166
183
|
}
|
|
167
184
|
super.dispose();
|
|
168
185
|
}
|
|
186
|
+
|
|
187
|
+
getFocusableElement() {
|
|
188
|
+
return this.getSvgRoot();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getFocusableTree() {
|
|
192
|
+
return this.workspace;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
onNodeFocus() {}
|
|
196
|
+
|
|
197
|
+
onNodeBlur() {}
|
|
198
|
+
|
|
199
|
+
canBeFocused() {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
169
202
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import * as Blockly from "blockly/core";
|
|
8
|
-
import { ScratchCommentBubble } from "./scratch_comment_bubble
|
|
8
|
+
import { ScratchCommentBubble } from "./scratch_comment_bubble";
|
|
9
9
|
|
|
10
10
|
interface CommentState {
|
|
11
11
|
text: string;
|
|
@@ -19,7 +19,7 @@ interface CommentState {
|
|
|
19
19
|
/**
|
|
20
20
|
* Custom comment icon that draws no icon indicator, used for block comments.
|
|
21
21
|
*/
|
|
22
|
-
class ScratchCommentIcon
|
|
22
|
+
export class ScratchCommentIcon
|
|
23
23
|
extends Blockly.icons.Icon
|
|
24
24
|
implements Blockly.ISerializable, Blockly.IHasBubble
|
|
25
25
|
{
|
|
@@ -34,9 +34,7 @@ class ScratchCommentIcon
|
|
|
34
34
|
constructor(protected sourceBlock: Blockly.BlockSvg) {
|
|
35
35
|
super(sourceBlock);
|
|
36
36
|
this.commentBubble = new ScratchCommentBubble(this.sourceBlock);
|
|
37
|
-
|
|
38
|
-
new (Blockly.Events.get("block_comment_create"))(this.commentBubble)
|
|
39
|
-
);
|
|
37
|
+
this.fireCreateEvent();
|
|
40
38
|
this.onTextChangedListener = this.onTextChanged.bind(this);
|
|
41
39
|
this.onSizeChangedListener = this.onSizeChanged.bind(this);
|
|
42
40
|
this.onCollapseListener = this.onCollapsed.bind(this);
|
|
@@ -198,10 +196,27 @@ class ScratchCommentIcon
|
|
|
198
196
|
this.commentBubble.setCollapsed(!visible);
|
|
199
197
|
}
|
|
200
198
|
|
|
199
|
+
getBubble() {
|
|
200
|
+
return this.commentBubble;
|
|
201
|
+
}
|
|
202
|
+
|
|
201
203
|
dispose() {
|
|
202
204
|
this.commentBubble.dispose();
|
|
203
205
|
super.dispose();
|
|
204
206
|
}
|
|
207
|
+
|
|
208
|
+
canBeFocused() {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Fires a block comment create event corresponding to this icon's comment.
|
|
214
|
+
*/
|
|
215
|
+
fireCreateEvent() {
|
|
216
|
+
Blockly.Events.fire(
|
|
217
|
+
new (Blockly.Events.get("block_comment_create"))(this.commentBubble)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
205
220
|
}
|
|
206
221
|
|
|
207
222
|
Blockly.registry.register(
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as Blockly from "blockly/core";
|
|
8
8
|
import { ContinuousToolbox } from "@blockly/continuous-toolbox";
|
|
9
9
|
import { ScratchContinuousCategory } from "./scratch_continuous_category";
|
|
10
|
+
import { STATUS_INDICATOR_LABEL_TYPE } from "./status_indicator_label_flyout_inflater";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* A toolbox that displays items from all categories in one scrolling list.
|
|
@@ -23,36 +24,25 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
*
|
|
27
|
+
* Converts the given toolbox item to a corresponding array of items that
|
|
28
|
+
* should appear in the flyout.
|
|
27
29
|
*
|
|
28
|
-
* @
|
|
30
|
+
* @param toolboxItem The toolbox item to convert.
|
|
31
|
+
* @returns An array of flyout item definitions.
|
|
29
32
|
*/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
contents.push({ kind: "LABEL", text: toolboxItem.getName() });
|
|
44
|
-
}
|
|
45
|
-
let itemContents = toolboxItem.getContents();
|
|
46
|
-
|
|
47
|
-
// Handle custom categories (e.g. variables and functions)
|
|
48
|
-
if (typeof itemContents === "string") {
|
|
49
|
-
itemContents = {
|
|
50
|
-
custom: itemContents,
|
|
51
|
-
kind: "CATEGORY",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
contents = contents.concat(itemContents);
|
|
55
|
-
}
|
|
33
|
+
protected convertToolboxItemToFlyoutItems(
|
|
34
|
+
toolboxItem: Blockly.IToolboxItem
|
|
35
|
+
): Blockly.utils.toolbox.FlyoutItemInfoArray {
|
|
36
|
+
const contents = super.convertToolboxItemToFlyoutItems(toolboxItem);
|
|
37
|
+
if (
|
|
38
|
+
toolboxItem instanceof ScratchContinuousCategory &&
|
|
39
|
+
toolboxItem.shouldShowStatusButton()
|
|
40
|
+
) {
|
|
41
|
+
contents.splice(0, 1, {
|
|
42
|
+
kind: STATUS_INDICATOR_LABEL_TYPE,
|
|
43
|
+
id: toolboxItem.getId(),
|
|
44
|
+
text: toolboxItem.getName(),
|
|
45
|
+
});
|
|
56
46
|
}
|
|
57
47
|
return contents;
|
|
58
48
|
}
|
|
@@ -62,12 +52,12 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
|
|
|
62
52
|
*/
|
|
63
53
|
forceRerender() {
|
|
64
54
|
const selectedCategoryName = this.selectedItem_?.getName();
|
|
65
|
-
|
|
55
|
+
this.getFlyout().show(this.getInitialFlyoutContents());
|
|
56
|
+
this.selectCategoryByName(selectedCategoryName);
|
|
66
57
|
let callback;
|
|
67
58
|
while ((callback = this.postRenderCallbacks.shift())) {
|
|
68
59
|
callback();
|
|
69
60
|
}
|
|
70
|
-
this.selectCategoryByName(selectedCategoryName);
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
/**
|
|
@@ -77,4 +67,20 @@ export class ScratchContinuousToolbox extends ContinuousToolbox {
|
|
|
77
67
|
runAfterRerender(callback: () => void) {
|
|
78
68
|
this.postRenderCallbacks.push(callback);
|
|
79
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns whether or not the given item should be deselected.
|
|
73
|
+
* Prevents items from being deselected without a replacement.
|
|
74
|
+
*
|
|
75
|
+
* @param oldItem The item that was previously selected.
|
|
76
|
+
* @param newItem The item that is proposed to be selected instead.
|
|
77
|
+
* @returns True if the old item should be allowed to be deselected.
|
|
78
|
+
*/
|
|
79
|
+
shouldDeselectItem_(
|
|
80
|
+
oldItem: Blockly.ISelectableToolboxItem | null,
|
|
81
|
+
newItem: Blockly.ISelectableToolboxItem | null
|
|
82
|
+
) {
|
|
83
|
+
if (!newItem) return false;
|
|
84
|
+
return super.shouldDeselectItem_(oldItem, newItem);
|
|
85
|
+
}
|
|
80
86
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Blockly from "blockly/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Displays an indicator of where a block being dragged will be connected.
|
|
11
|
+
*/
|
|
12
|
+
class ScratchInsertionMarkerPreviewer extends Blockly.InsertionMarkerPreviewer {
|
|
13
|
+
/**
|
|
14
|
+
* Transforms the given block into a JSON representation used to construct an
|
|
15
|
+
* insertion marker.
|
|
16
|
+
*
|
|
17
|
+
* @param block The block to serialize and use as an insertion marker.
|
|
18
|
+
* @returns A JSON-formatted string corresponding to a serialized
|
|
19
|
+
* representation of the given block suitable for use as an insertion
|
|
20
|
+
* marker.
|
|
21
|
+
*/
|
|
22
|
+
protected override serializeBlockToInsertionMarker(block: Blockly.BlockSvg) {
|
|
23
|
+
const blockJson = Blockly.serialization.blocks.save(block, {
|
|
24
|
+
addCoordinates: false,
|
|
25
|
+
addInputBlocks: true,
|
|
26
|
+
addNextBlocks: false,
|
|
27
|
+
doFullSerialization: false,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!blockJson) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Failed to serialize source block. ${block.toDevString()}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return blockJson;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Blockly.registry.register(
|
|
41
|
+
Blockly.registry.Type.CONNECTION_PREVIEWER,
|
|
42
|
+
Blockly.registry.DEFAULT,
|
|
43
|
+
ScratchInsertionMarkerPreviewer,
|
|
44
|
+
true
|
|
45
|
+
);
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import * as Blockly from "blockly/core";
|
|
8
8
|
import { StatusIndicatorLabel } from "./status_indicator_label";
|
|
9
9
|
|
|
10
|
+
export const STATUS_INDICATOR_LABEL_TYPE = "status_indicator_label";
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* Flyout inflater responsible for creating status indicator labels.
|
|
12
14
|
*/
|
|
@@ -20,15 +22,15 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
|
|
|
20
22
|
*/
|
|
21
23
|
load(
|
|
22
24
|
state: Blockly.utils.toolbox.LabelInfo,
|
|
23
|
-
|
|
24
|
-
):
|
|
25
|
+
flyout: Blockly.IFlyout
|
|
26
|
+
): Blockly.FlyoutItem {
|
|
25
27
|
const label = new StatusIndicatorLabel(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
flyout.getWorkspace(),
|
|
29
|
+
flyout.targetWorkspace,
|
|
28
30
|
state
|
|
29
31
|
);
|
|
30
32
|
label.show();
|
|
31
|
-
return label;
|
|
33
|
+
return new Blockly.FlyoutItem(label, STATUS_INDICATOR_LABEL_TYPE);
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -38,7 +40,7 @@ class StatusIndicatorLabelFlyoutInflater extends Blockly.LabelFlyoutInflater {
|
|
|
38
40
|
export function registerStatusIndicatorLabelFlyoutInflater() {
|
|
39
41
|
Blockly.registry.register(
|
|
40
42
|
Blockly.registry.Type.FLYOUT_INFLATER,
|
|
41
|
-
|
|
43
|
+
STATUS_INDICATOR_LABEL_TYPE,
|
|
42
44
|
StatusIndicatorLabelFlyoutInflater
|
|
43
45
|
);
|
|
44
46
|
}
|
package/src/variables.ts
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
} from "./constants";
|
|
30
30
|
import { ScratchVariableModel } from "./scratch_variable_model";
|
|
31
31
|
import { ScratchContinuousToolbox } from "./scratch_continuous_toolbox";
|
|
32
|
-
import { CheckableContinuousFlyout } from "./checkable_continuous_flyout
|
|
32
|
+
import { CheckableContinuousFlyout } from "./checkable_continuous_flyout";
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Constant prefix to differentiate cloud variable names from other types of
|