scratch-blocks 2.0.0-spork.4 → 2.0.0-spork.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scratch-blocks",
3
- "version": "2.0.0-spork.4",
3
+ "version": "2.0.0-spork.6",
4
4
  "description": "Scratch Blocks is a library for building creative computing interfaces.",
5
5
  "author": "Massachusetts Institute of Technology",
6
6
  "license": "Apache-2.0",
@@ -17,12 +17,17 @@
17
17
  "test": "echo \"Error: no test specified\" && exit 1",
18
18
  "test:lint": "eslint ."
19
19
  },
20
+ "dependencies": {
21
+ "@blockly/continuous-toolbox": "^7.0.1",
22
+ "@blockly/field-colour": "^6.0.3",
23
+ "blockly": "^12.3.0-beta.0"
24
+ },
20
25
  "devDependencies": {
21
- "@commitlint/cli": "^17.8.1",
22
- "@commitlint/config-conventional": "^17.8.1",
23
- "eslint": "^4.19.1",
26
+ "@commitlint/cli": "17.8.1",
27
+ "@commitlint/config-conventional": "17.8.1",
28
+ "eslint": "4.19.1",
24
29
  "husky": "9.1.6",
25
- "scratch-semantic-release-config": "1.0.16",
30
+ "scratch-semantic-release-config": "4.0.0",
26
31
  "semantic-release": "22.0.12",
27
32
  "source-map-loader": "^4.0.1",
28
33
  "ts-loader": "^9.5.1",
@@ -31,11 +36,6 @@
31
36
  "webpack-cli": "^4.10.0",
32
37
  "webpack-dev-server": "^4.11.1"
33
38
  },
34
- "dependencies": {
35
- "@blockly/continuous-toolbox": "^7.0.0-beta.1",
36
- "@blockly/field-colour": "^4.0.2",
37
- "blockly": "^12.0.0-beta.1"
38
- },
39
39
  "config": {
40
40
  "commitizen": {
41
41
  "path": "cz-conventional-changelog"
@@ -224,7 +224,7 @@ function disconnectOldBlocks_(this: ProcedureBlock): ConnectionMap {
224
224
  if (input.connection) {
225
225
  const target = input.connection.targetBlock() as Blockly.BlockSvg;
226
226
  const saveInfo = {
227
- shadow: input.connection.getShadowDom(),
227
+ shadow: input.connection.getShadowDom(true),
228
228
  block: target,
229
229
  };
230
230
  connectionMap[input.name] = saveInfo;
@@ -374,6 +374,18 @@ Blockly.Blocks["sensing_dayssince2000"] = {
374
374
  },
375
375
  };
376
376
 
377
+ Blockly.Blocks["sensing_online"] = {
378
+ /**
379
+ * Block to report whether or not the system is online
380
+ */
381
+ init: function(this: Blockly.Block) {
382
+ this.jsonInit({
383
+ message0: Blockly.Msg.SENSING_ONLINE,
384
+ extensions: ["colours_sensing", "output_boolean", "monitor_block"],
385
+ });
386
+ },
387
+ };
388
+
377
389
  Blockly.Blocks["sensing_username"] = {
378
390
  /**
379
391
  * Block to report user's username
@@ -109,4 +109,8 @@ export class CheckableContinuousFlyout extends ContinuousFlyout {
109
109
  }
110
110
  }
111
111
  }
112
+
113
+ scrollTo(position: number) {
114
+ super.scrollTo(Math.ceil(position));
115
+ }
112
116
  }
@@ -243,6 +243,27 @@ export class CheckboxBubble
243
243
  Blockly.browserEvents.unbind(this.clickListener);
244
244
  }
245
245
 
246
+ /** See IFocusableNode.getFocusableElement. */
247
+ getFocusableElement(): HTMLElement | SVGElement {
248
+ return this.svgRoot;
249
+ }
250
+
251
+ /** See IFocusableNode.getFocusableTree. */
252
+ getFocusableTree(): Blockly.IFocusableTree {
253
+ return this.sourceBlock.workspace;
254
+ }
255
+
256
+ /** See IFocusableNode.onNodeFocus. */
257
+ onNodeFocus(): void {}
258
+
259
+ /** See IFocusableNode.onNodeBlur. */
260
+ onNodeBlur(): void {}
261
+
262
+ /** See IFocusableNode.canBeFocused. */
263
+ canBeFocused(): boolean {
264
+ return true;
265
+ }
266
+
246
267
  // These methods are required by the interfaces, but intentionally have no
247
268
  // implementation, largely because this bubble's location is fixed relative
248
269
  // to its block and is not draggable by the user.
package/src/colours.ts CHANGED
@@ -53,6 +53,7 @@ const Colours = {
53
53
  valueReportBackground: "#FFFFFF",
54
54
  valueReportBorder: "#AAAAAA",
55
55
  contextualMenuHover: "rgba(77, 151, 255, .25)",
56
+ menuHover: "rgba(0, 0, 0, .2)",
56
57
  };
57
58
 
58
59
  /**
@@ -154,3 +154,27 @@ function deleteNext(deleteList: Blockly.BlockSvg[], eventGroup?: string) {
154
154
  }
155
155
  Blockly.Events.setGroup(false);
156
156
  }
157
+
158
+ /**
159
+ * Registers a block duplicate option that duplicates the selected block and
160
+ * all subsequent blocks in the stack.
161
+ */
162
+ export function registerDuplicateBlock() {
163
+ const original =
164
+ Blockly.ContextMenuRegistry.registry.getItem("blockDuplicate");
165
+ const duplicateOption = {
166
+ displayText: original.displayText,
167
+ preconditionFn: original.preconditionFn,
168
+ callback(scope: Blockly.ContextMenuRegistry.Scope) {
169
+ if (!scope.block) return;
170
+ const data = scope.block.toCopyData(true);
171
+ if (!data) return;
172
+ Blockly.clipboard.paste(data, scope.block.workspace);
173
+ },
174
+ scopeType: original.scopeType,
175
+ id: original.id,
176
+ weight: original.weight,
177
+ };
178
+ Blockly.ContextMenuRegistry.registry.unregister(duplicateOption.id);
179
+ Blockly.ContextMenuRegistry.registry.register(duplicateOption);
180
+ }
package/src/css.ts CHANGED
@@ -702,16 +702,16 @@ const styles = `
702
702
  }
703
703
 
704
704
  .blocklyAngleCenterPoint {
705
- stroke: #fff;
705
+ stroke: var(--colour-text);
706
706
  stroke-width: 1;
707
- fill: #fff;
707
+ fill: var(--colour-text);
708
708
  }
709
709
 
710
710
  .blocklyAngleDragHandle {
711
- stroke: #fff;
711
+ stroke: var(--colour-text);
712
712
  stroke-width: 5;
713
713
  stroke-opacity: 0.25;
714
- fill: #fff;
714
+ fill: var(--colour-text);
715
715
  cursor: pointer;
716
716
  }
717
717
 
@@ -720,18 +720,18 @@ const styles = `
720
720
  }
721
721
 
722
722
  .blocklyAngleMarks {
723
- stroke: #fff;
723
+ stroke: var(--colour-text);
724
724
  stroke-width: 1;
725
725
  stroke-opacity: 0.5;
726
726
  }
727
727
 
728
728
  .blocklyAngleGauge {
729
- fill: #fff;
729
+ fill: var(--colour-text);
730
730
  fill-opacity: 0.20;
731
731
  }
732
732
 
733
733
  .blocklyAngleLine {
734
- stroke: #fff;
734
+ stroke: var(--colour-text);
735
735
  stroke-width: 1;
736
736
  stroke-linecap: round;
737
737
  pointer-events: none;
@@ -1011,12 +1011,12 @@ const styles = `
1011
1011
  z-index: 20000; /* Arbitrary, but some apps depend on it... */
1012
1012
  }
1013
1013
 
1014
- .blocklyDropDownDiv .blocklyMenu .blocklyMenuItem:hover {
1015
- background: var(--colour-menuHover);
1014
+ .blocklyDropDownDiv .blocklyMenu .blocklyMenuItem.blocklyMenuItemHighlight {
1015
+ background-color: var(--colour-menuHover);
1016
1016
  }
1017
1017
 
1018
- .blocklyWidgetDiv .blocklyMenu .blocklyMenuItem:hover {
1019
- background: var(--colour-contextualMenuHover);
1018
+ .blocklyWidgetDiv .blocklyMenu .blocklyMenuItem.blocklyMenuItemHighlight {
1019
+ background-color: var(--colour-contextualMenuHover);
1020
1020
  }
1021
1021
 
1022
1022
  .blocklyWidgetDiv .blocklyMenu .blocklyMenuItemDisabled.blocklyMenuItem:hover {
@@ -1168,13 +1168,12 @@ const styles = `
1168
1168
  color: #4c97ff;
1169
1169
  }
1170
1170
  .blocklyDropDownDiv .blocklyMenuItem {
1171
- color: #fff;
1172
1171
  font-weight: bold;
1173
1172
  min-height: 32px;
1174
1173
  padding: 4px 7em 4px 28px;
1175
1174
  }
1176
- .high-contrast-theme.blocklyDropDownDiv .blocklyMenuItem {
1177
- color: #000;
1175
+ .scratch-renderer.blocklyDropDownDiv .blocklyMenuItem .blocklyMenuItemContent {
1176
+ color: var(--colour-text);
1178
1177
  }
1179
1178
  .blocklyToolboxSelected .blocklyTreeLabel {
1180
1179
  color: var(--colour-toolboxText);
@@ -1198,6 +1197,10 @@ const styles = `
1198
1197
  stroke: revert-layer;
1199
1198
  stroke-width: 1;
1200
1199
  }
1200
+
1201
+ .blocklyInsertionMarker > g:not(:last-child) {
1202
+ visibility: hidden;
1203
+ }
1201
1204
  `;
1202
1205
 
1203
1206
  Blockly.Css.register(styles);
@@ -215,7 +215,7 @@ class FieldMatrix extends Blockly.Field<string> {
215
215
  this.arrow_.setAttributeNS(
216
216
  "http://www.w3.org/1999/xlink",
217
217
  "xlink:href",
218
- Blockly.getMainWorkspace().options.pathToMedia + "dropdown-arrow.svg"
218
+ this.getConstants().FIELD_DROPDOWN_SVG_ARROW_DATAURI
219
219
  );
220
220
  this.arrow_.style.cursor = "default";
221
221
  }
@@ -238,6 +238,25 @@ class FieldMatrix extends Blockly.Field<string> {
238
238
  * Show the drop-down menu for editing this field.
239
239
  */
240
240
  showEditor_() {
241
+ const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg;
242
+ Blockly.DropDownDiv.setColour(
243
+ sourceBlock.getColour(),
244
+ sourceBlock.getColourTertiary()
245
+ );
246
+
247
+ const style = sourceBlock.style;
248
+ if (sourceBlock.isShadow()) {
249
+ this.originalStyle = sourceBlock.getStyleName();
250
+ sourceBlock.setStyle(`${this.originalStyle}_selected`);
251
+ } else if (this.borderRect_) {
252
+ this.borderRect_.setAttribute(
253
+ "fill",
254
+ "colourQuaternary" in style
255
+ ? `${style.colourQuaternary}`
256
+ : style.colourTertiary
257
+ );
258
+ }
259
+
241
260
  const div = Blockly.DropDownDiv.getContentDiv();
242
261
  // Build the SVG DOM.
243
262
  const matrixSize =
@@ -286,23 +305,19 @@ class FieldMatrix extends Blockly.Field<string> {
286
305
  // Button to clear matrix
287
306
  const clearButtonDiv = document.createElement("div");
288
307
  clearButtonDiv.className = "scratchMatrixButtonDiv";
289
- const sourceBlock = this.getSourceBlock() as Blockly.BlockSvg;
308
+
290
309
  const clearButton = this.createButton_(sourceBlock.getColourSecondary());
291
310
  clearButtonDiv.appendChild(clearButton);
292
311
  // Button to fill matrix
293
312
  const fillButtonDiv = document.createElement("div");
294
313
  fillButtonDiv.className = "scratchMatrixButtonDiv";
295
- const fillButton = this.createButton_("#FFFFFF");
314
+ const fillButton = this.createButton_("var(--colour-text)");
296
315
  fillButtonDiv.appendChild(fillButton);
297
316
 
298
317
  buttonDiv.appendChild(clearButtonDiv);
299
318
  buttonDiv.appendChild(fillButtonDiv);
300
319
  div.appendChild(buttonDiv);
301
320
 
302
- Blockly.DropDownDiv.setColour(
303
- sourceBlock.getColour(),
304
- sourceBlock.getColourTertiary()
305
- );
306
321
  Blockly.DropDownDiv.showPositionedByBlock(
307
322
  this,
308
323
  sourceBlock,
@@ -328,19 +343,6 @@ class FieldMatrix extends Blockly.Field<string> {
328
343
  this.fillMatrix_
329
344
  );
330
345
 
331
- const style = sourceBlock.style;
332
- if (sourceBlock.isShadow()) {
333
- this.originalStyle = sourceBlock.getStyleName();
334
- sourceBlock.setStyle(`${this.originalStyle}_selected`);
335
- } else if (this.borderRect_) {
336
- this.borderRect_.setAttribute(
337
- "fill",
338
- "colourQuaternary" in style
339
- ? `${style.colourQuaternary}`
340
- : style.colourTertiary
341
- );
342
- }
343
-
344
346
  // Update the matrix for the current value
345
347
  this.updateMatrix_();
346
348
  }
@@ -350,6 +352,7 @@ class FieldMatrix extends Blockly.Field<string> {
350
352
  if (sourceBlock.isShadow()) {
351
353
  sourceBlock.setStyle(this.originalStyle);
352
354
  }
355
+ this.updateMatrix_();
353
356
  }
354
357
 
355
358
  /**
@@ -400,12 +403,16 @@ class FieldMatrix extends Blockly.Field<string> {
400
403
  this.fillMatrixNode_(
401
404
  this.ledButtons_,
402
405
  i,
406
+ sourceBlock.getColourTertiary()
407
+ );
408
+ this.fillMatrixNode_(
409
+ this.ledThumbNodes_,
410
+ i,
403
411
  sourceBlock.getColourSecondary()
404
412
  );
405
- this.fillMatrixNode_(this.ledThumbNodes_, i, sourceBlock.getColour());
406
413
  } else {
407
- this.fillMatrixNode_(this.ledButtons_, i, "#FFFFFF");
408
- this.fillMatrixNode_(this.ledThumbNodes_, i, "#FFFFFF");
414
+ this.fillMatrixNode_(this.ledButtons_, i, "var(--colour-text)");
415
+ this.fillMatrixNode_(this.ledThumbNodes_, i, "var(--colour-text)");
409
416
  }
410
417
  }
411
418
  }
@@ -505,10 +512,14 @@ class FieldMatrix extends Blockly.Field<string> {
505
512
  * Unbind mouse move event and clear the paint style.
506
513
  */
507
514
  onMouseUp() {
508
- Blockly.browserEvents.unbind(this.matrixMoveWrapper_);
509
- this.matrixMoveWrapper_ = null;
510
- Blockly.browserEvents.unbind(this.matrixReleaseWrapper_);
511
- this.matrixReleaseWrapper_ = null;
515
+ if (this.matrixMoveWrapper_) {
516
+ Blockly.browserEvents.unbind(this.matrixMoveWrapper_);
517
+ this.matrixMoveWrapper_ = null;
518
+ }
519
+ if (this.matrixReleaseWrapper_) {
520
+ Blockly.browserEvents.unbind(this.matrixReleaseWrapper_);
521
+ this.matrixReleaseWrapper_ = null;
522
+ }
512
523
  this.paintStyle_ = null;
513
524
  }
514
525
 
@@ -286,7 +286,7 @@ export class FieldNote extends Blockly.FieldTextInput {
286
286
  * Show a field with piano keys.
287
287
  */
288
288
  showEditor_(event: PointerEvent, quietInput = false) {
289
- super.showEditor_(event, quietInput);
289
+ super.showEditor_(event, quietInput, false);
290
290
 
291
291
  // Build the SVG DOM.
292
292
  const div = Blockly.DropDownDiv.getContentDiv();
@@ -157,7 +157,15 @@ class ScratchFieldAngle extends Blockly.FieldNumber {
157
157
  * Show the inline free-text editor on top of the text.
158
158
  */
159
159
  showEditor_(event: PointerEvent) {
160
- super.showEditor_(event);
160
+ // Mobile browsers have issues with in-line textareas (focus & keyboards).
161
+ // Also, don't let the parent take ephemeral focus since the drop-down div
162
+ // below will handle it, instead.
163
+ const noFocus =
164
+ Blockly.utils.userAgent.MOBILE ||
165
+ Blockly.utils.userAgent.ANDROID ||
166
+ Blockly.utils.userAgent.IPAD;
167
+ super.showEditor_(event, noFocus, false);
168
+
161
169
  // If there is an existing drop-down someone else owns, hide it immediately and clear it.
162
170
  Blockly.DropDownDiv.hideWithoutAnimation();
163
171
  Blockly.DropDownDiv.clearContent();
@@ -94,7 +94,7 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
94
94
  * @returns Array of variable names.
95
95
  */
96
96
  static dropdownCreate(this: ScratchFieldVariable): Blockly.MenuOption[] {
97
- const options = super.dropdownCreate();
97
+ let options = super.dropdownCreate();
98
98
  const type = this.getDefaultType();
99
99
  if (type === Constants.BROADCAST_MESSAGE_VARIABLE_TYPE) {
100
100
  options.splice(-2, 2, [
@@ -102,16 +102,17 @@ export class ScratchFieldVariable extends Blockly.FieldVariable {
102
102
  Constants.NEW_BROADCAST_MESSAGE_ID,
103
103
  ]);
104
104
  } else if (type === Constants.LIST_VARIABLE_TYPE) {
105
- for (const option of options) {
105
+ options = options.map((option) => {
106
106
  if (option[1] === Blockly.RENAME_VARIABLE_ID) {
107
- option[0] = ScratchMsgs.translate("RENAME_LIST");
107
+ return [ScratchMsgs.translate("RENAME_LIST"), option[1]];
108
108
  } else if (option[1] === Blockly.DELETE_VARIABLE_ID) {
109
- option[0] = ScratchMsgs.translate("DELETE_LIST").replace(
110
- "%1",
111
- this.getText()
112
- );
109
+ return [
110
+ ScratchMsgs.translate("DELETE_LIST").replace("%1", this.getText()),
111
+ option[1],
112
+ ];
113
113
  }
114
- }
114
+ return option;
115
+ });
115
116
  }
116
117
 
117
118
  return options;
@@ -10,11 +10,15 @@ import { CheckboxBubble } from "./checkbox_bubble";
10
10
  /**
11
11
  * Invisible icon that exists solely to host the corresponding checkbox bubble.
12
12
  */
13
- export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
13
+ export class FlyoutCheckboxIcon
14
+ extends Blockly.icons.Icon
15
+ implements Blockly.IHasBubble
16
+ {
14
17
  private checkboxBubble: CheckboxBubble;
15
18
  private type = new Blockly.icons.IconType("checkbox");
16
19
 
17
- constructor(private sourceBlock: Blockly.BlockSvg) {
20
+ constructor(protected override sourceBlock: Blockly.BlockSvg) {
21
+ super(sourceBlock);
18
22
  if (this.sourceBlock.workspace.isFlyout) {
19
23
  this.checkboxBubble = new CheckboxBubble(this.sourceBlock);
20
24
  }
@@ -24,19 +28,11 @@ export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
24
28
  return this.type;
25
29
  }
26
30
 
27
- getWeight(): number {
28
- return -1;
29
- }
30
-
31
31
  getSize(): Blockly.utils.Size {
32
32
  // Awful hack to cancel out the default padding added to icons.
33
33
  return new Blockly.utils.Size(-8, 0);
34
34
  }
35
35
 
36
- isShownWhenCollapsed(): boolean {
37
- return false;
38
- }
39
-
40
36
  isClickableInFlyout(): boolean {
41
37
  return false;
42
38
  }
@@ -55,25 +51,23 @@ export class FlyoutCheckboxIcon implements Blockly.IIcon, Blockly.IHasBubble {
55
51
 
56
52
  dispose() {
57
53
  this.checkboxBubble?.dispose();
54
+ super.dispose();
58
55
  }
59
56
 
60
57
  // These methods are required by the interfaces, but intentionally have no
61
58
  // implementation, largely because this icon has no visual representation.
62
- applyColour() {}
63
-
64
- hideForInsertionMarker() {}
65
-
66
- updateEditable() {}
67
-
68
- updateCollapsed() {}
69
-
70
- setOffsetInBlock() {}
71
-
72
- onClick() {}
73
59
 
74
60
  async setBubbleVisible(visible: boolean) {}
75
61
 
76
62
  initView(pointerDownListener: (e: PointerEvent) => void) {}
63
+
64
+ canBeFocused() {
65
+ return false;
66
+ }
67
+
68
+ getBubble() {
69
+ return this.checkboxBubble;
70
+ }
77
71
  }
78
72
 
79
73
  Blockly.registry.register(
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import "./scratch_dragger";
37
37
  import "./scratch_variable_map";
38
38
  import "./scratch_variable_model";
39
39
  import "./scratch_connection_checker";
40
+ import "./scratch_insertion_marker_previewer";
40
41
  import "./flyout_checkbox_icon";
41
42
  import "./events/events_block_comment_change";
42
43
  import "./events/events_block_comment_collapse";
@@ -80,6 +81,7 @@ export {
80
81
  StatusIndicatorLabel,
81
82
  StatusButtonState,
82
83
  } from "./status_indicator_label";
84
+ export * from "./xml";
83
85
 
84
86
  export function inject(container: Element, options: Blockly.BlocklyOptions) {
85
87
  registerScratchFieldAngle();
@@ -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(
@@ -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
  }