From 8a8c633fec899154910986a2240427d0ab44be78 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 22 Apr 2024 10:35:29 -0700 Subject: [PATCH 01/76] fix: run stacks in response to click events (#1) * fix: run stacks in response to stack click events * refactor: listen for regular click events to run block stacks --- packages/scratch-vm/src/engine/blocks.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index cf1b2b7c4f..f9d0138c80 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -308,12 +308,6 @@ class Blocks { const stage = this.runtime.getTargetForStage(); const editingTarget = this.runtime.getEditingTarget(); - // UI event: clicked scripts toggle in the runtime. - if (e.element === 'stackclick') { - this.runtime.toggleScript(e.blockId, {stackClick: true}); - return; - } - // Block create/update/destroy switch (e.type) { case 'create': { @@ -503,6 +497,12 @@ class Blocks { this.emitProjectChanged(); } break; + case 'click': + // UI event: clicked scripts toggle in the runtime. + if (e.targetType === 'block') { + this.runtime.toggleScript(this.getTopLevelScript(e.blockId), {stackClick: true}); + } + break; } } From 28ea54ac8720e39cda1c274af60b3807e1282cec Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 30 Apr 2024 15:49:07 -0700 Subject: [PATCH 02/76] fix: use toolboxitemid instead of id as the identifier attribute for extensions toolbox categories (#2) --- packages/scratch-vm/src/engine/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-vm/src/engine/runtime.js b/packages/scratch-vm/src/engine/runtime.js index 452ff51c20..fd0fa688a4 100644 --- a/packages/scratch-vm/src/engine/runtime.js +++ b/packages/scratch-vm/src/engine/runtime.js @@ -1429,7 +1429,7 @@ class Runtime extends EventEmitter { return { id: categoryInfo.id, - xml: `${ + xml: `${ paletteBlocks.map(block => block.xml).join('')}` }; }); From ac4280e1664a23e53bd188460bf657f17621d8ad Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 12 Jun 2024 09:27:45 -0700 Subject: [PATCH 03/76] fix: handle modern workspace comment events (#3) * fix: handle modern workspace comment events * fix: correctly access coordinates on events --- packages/scratch-vm/src/engine/blocks.js | 33 ++++++++++++----------- packages/scratch-vm/src/engine/comment.js | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index f9d0138c80..ebee6427cf 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -419,8 +419,8 @@ class Blocks { case 'comment_create': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - currTarget.createComment(e.commentId, e.blockId, e.text, - e.xy.x, e.xy.y, e.width, e.height, e.minimized); + currTarget.createComment(e.commentId, null, '', + e.json.x, e.json.y, e.json.width, e.json.height, false); if (currTarget.comments[e.commentId].x === null && currTarget.comments[e.commentId].y === null) { @@ -430,8 +430,8 @@ class Blocks { // comments, then the auto positioning should have taken place. // Update the x and y position of these comments to match the // one from the event. - currTarget.comments[e.commentId].x = e.xy.x; - currTarget.comments[e.commentId].y = e.xy.y; + currTarget.comments[e.commentId].x = e.json.x; + currTarget.comments[e.commentId].y = e.json.y; } } this.emitProjectChanged(); @@ -444,18 +444,7 @@ class Blocks { return; } const comment = currTarget.comments[e.commentId]; - const change = e.newContents_; - if (Object.prototype.hasOwnProperty.call(change, 'minimized')) { - comment.minimized = change.minimized; - } - if (Object.prototype.hasOwnProperty.call(change, 'width') && - Object.prototype.hasOwnProperty.call(change, 'height')) { - comment.width = change.width; - comment.height = change.height; - } - if (Object.prototype.hasOwnProperty.call(change, 'text')) { - comment.text = change.text; - } + comment.text = e.newContents_; this.emitProjectChanged(); } break; @@ -474,6 +463,18 @@ class Blocks { this.emitProjectChanged(); } break; + case 'comment_collapse': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { + log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); + return; + } + const comment = currTarget.comments[e.commentId]; + comment.minimized = e.newCollapsed; + this.emitProjectChanged(); + } + break; case 'comment_delete': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); diff --git a/packages/scratch-vm/src/engine/comment.js b/packages/scratch-vm/src/engine/comment.js index ac644e770e..bdf4e8f893 100644 --- a/packages/scratch-vm/src/engine/comment.js +++ b/packages/scratch-vm/src/engine/comment.js @@ -31,7 +31,7 @@ class Comment { toXML () { return `${xmlEscape(this.text)}`; + this.blockId !== null}" collapsed="${this.minimized}">${xmlEscape(this.text)}`; } // TODO choose min and defaults for width and height From 29bdbd1fef6164884fd6fd64698802c8cc1c7b8d Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 9 Jul 2024 09:31:31 -0700 Subject: [PATCH 04/76] fix: handle new custom block comment events (#4) --- packages/scratch-vm/src/engine/blocks.js | 25 ++++++++++++++++++++--- packages/scratch-vm/src/engine/comment.js | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index ebee6427cf..2796d9ed0d 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -416,10 +416,11 @@ class Blocks { this.emitProjectChanged(); break; } + case 'block_comment_create': case 'comment_create': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); - currTarget.createComment(e.commentId, null, '', + currTarget.createComment(e.commentId, e.blockId, '', e.json.x, e.json.y, e.json.width, e.json.height, false); if (currTarget.comments[e.commentId].x === null && @@ -436,6 +437,7 @@ class Blocks { } this.emitProjectChanged(); break; + case 'block_comment_change': case 'comment_change': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); @@ -448,11 +450,12 @@ class Blocks { this.emitProjectChanged(); } break; + case 'block_comment_move': case 'comment_move': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); + log.warn(`Cannot move comment with id ${e.commentId} because it does not exist.`); return; } const comment = currTarget.comments[e.commentId]; @@ -463,11 +466,12 @@ class Blocks { this.emitProjectChanged(); } break; + case 'block_comment_collapse': case 'comment_collapse': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); + log.warn(`Cannot collapse comment with id ${e.commentId} because it does not exist.`); return; } const comment = currTarget.comments[e.commentId]; @@ -475,6 +479,21 @@ class Blocks { this.emitProjectChanged(); } break; + case 'block_comment_resize': + case 'comment_resize': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { + log.warn(`Cannot resize comment with id ${e.commentId} because it does not exist.`); + return; + } + const comment = currTarget.comments[e.commentId]; + comment.width = e.newSize.width; + comment.height = e.newSize.height; + this.emitProjectChanged(); + } + break; + case 'block_comment_delete': case 'comment_delete': if (this.runtime.getEditingTarget()) { const currTarget = this.runtime.getEditingTarget(); diff --git a/packages/scratch-vm/src/engine/comment.js b/packages/scratch-vm/src/engine/comment.js index bdf4e8f893..a71c8dfcf8 100644 --- a/packages/scratch-vm/src/engine/comment.js +++ b/packages/scratch-vm/src/engine/comment.js @@ -31,7 +31,7 @@ class Comment { toXML () { return `${xmlEscape(this.text)}`; + !this.minimized}" collapsed="${this.minimized}">${xmlEscape(this.text)}`; } // TODO choose min and defaults for width and height From 8ba4f9e6324d7e6a4ce4c571a4633540580c98b6 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 3 Sep 2024 13:24:50 -0700 Subject: [PATCH 05/76] fix: add the hat extension to extension hat blocks (#5) * chore: format runtime.js * fix: add the hat extension to extension hat blocks --- packages/scratch-vm/src/engine/runtime.js | 1271 ++++++++++++--------- 1 file changed, 747 insertions(+), 524 deletions(-) diff --git a/packages/scratch-vm/src/engine/runtime.js b/packages/scratch-vm/src/engine/runtime.js index fd0fa688a4..58d20485e6 100644 --- a/packages/scratch-vm/src/engine/runtime.js +++ b/packages/scratch-vm/src/engine/runtime.js @@ -1,50 +1,50 @@ -const EventEmitter = require('events'); -const {OrderedMap} = require('immutable'); -const uuid = require('uuid'); - -const ArgumentType = require('../extension-support/argument-type'); -const Blocks = require('./blocks'); -const BlocksRuntimeCache = require('./blocks-runtime-cache'); -const BlockType = require('../extension-support/block-type'); -const Profiler = require('./profiler'); -const Sequencer = require('./sequencer'); -const execute = require('./execute.js'); -const ScratchBlocksConstants = require('./scratch-blocks-constants'); -const TargetType = require('../extension-support/target-type'); -const Thread = require('./thread'); -const log = require('../util/log'); -const maybeFormatMessage = require('../util/maybe-format-message'); -const StageLayering = require('./stage-layering'); -const Variable = require('./variable'); -const xmlEscape = require('../util/xml-escape'); -const ScratchLinkWebSocket = require('../util/scratch-link-websocket'); -const fetchWithTimeout = require('../util/fetch-with-timeout'); +const EventEmitter = require("events"); +const { OrderedMap } = require("immutable"); +const uuid = require("uuid"); + +const ArgumentType = require("../extension-support/argument-type"); +const Blocks = require("./blocks"); +const BlocksRuntimeCache = require("./blocks-runtime-cache"); +const BlockType = require("../extension-support/block-type"); +const Profiler = require("./profiler"); +const Sequencer = require("./sequencer"); +const execute = require("./execute.js"); +const ScratchBlocksConstants = require("./scratch-blocks-constants"); +const TargetType = require("../extension-support/target-type"); +const Thread = require("./thread"); +const log = require("../util/log"); +const maybeFormatMessage = require("../util/maybe-format-message"); +const StageLayering = require("./stage-layering"); +const Variable = require("./variable"); +const xmlEscape = require("../util/xml-escape"); +const ScratchLinkWebSocket = require("../util/scratch-link-websocket"); +const fetchWithTimeout = require("../util/fetch-with-timeout"); // Virtual I/O devices. -const Clock = require('../io/clock'); -const Cloud = require('../io/cloud'); -const Keyboard = require('../io/keyboard'); -const Mouse = require('../io/mouse'); -const MouseWheel = require('../io/mouseWheel'); -const UserData = require('../io/userData'); -const Video = require('../io/video'); +const Clock = require("../io/clock"); +const Cloud = require("../io/cloud"); +const Keyboard = require("../io/keyboard"); +const Mouse = require("../io/mouse"); +const MouseWheel = require("../io/mouseWheel"); +const UserData = require("../io/userData"); +const Video = require("../io/video"); -const StringUtil = require('../util/string-util'); -const uid = require('../util/uid'); +const StringUtil = require("../util/string-util"); +const uid = require("../util/uid"); const defaultBlockPackages = { - scratch3_control: require('../blocks/scratch3_control'), - scratch3_event: require('../blocks/scratch3_event'), - scratch3_looks: require('../blocks/scratch3_looks'), - scratch3_motion: require('../blocks/scratch3_motion'), - scratch3_operators: require('../blocks/scratch3_operators'), - scratch3_sound: require('../blocks/scratch3_sound'), - scratch3_sensing: require('../blocks/scratch3_sensing'), - scratch3_data: require('../blocks/scratch3_data'), - scratch3_procedures: require('../blocks/scratch3_procedures') + scratch3_control: require("../blocks/scratch3_control"), + scratch3_event: require("../blocks/scratch3_event"), + scratch3_looks: require("../blocks/scratch3_looks"), + scratch3_motion: require("../blocks/scratch3_motion"), + scratch3_operators: require("../blocks/scratch3_operators"), + scratch3_sound: require("../blocks/scratch3_sound"), + scratch3_sensing: require("../blocks/scratch3_sensing"), + scratch3_data: require("../blocks/scratch3_data"), + scratch3_procedures: require("../blocks/scratch3_procedures"), }; -const defaultExtensionColors = ['#0FBD8C', '#0DA57A', '#0B8E69']; +const defaultExtensionColors = ["#0FBD8C", "#0DA57A", "#0B8E69"]; /** * Information used for converting Scratch argument types into scratch-blocks data. @@ -54,7 +54,7 @@ const ArgumentTypeMap = (() => { const map = {}; map[ArgumentType.ANGLE] = { shadow: { - type: 'math_angle', + type: "math_angle", // We specify fieldNames here so that we can pick // create and populate a field with the defaultValue // specified in the extension. @@ -62,46 +62,46 @@ const ArgumentTypeMap = (() => { // the will be left out of the XML and // the scratch-blocks defaults for that field will be // used instead (e.g. default of 0 for number fields) - fieldName: 'NUM' - } + fieldName: "NUM", + }, }; map[ArgumentType.COLOR] = { shadow: { - type: 'colour_picker', - fieldName: 'COLOUR' - } + type: "colour_picker", + fieldName: "COLOUR", + }, }; map[ArgumentType.NUMBER] = { shadow: { - type: 'math_number', - fieldName: 'NUM' - } + type: "math_number", + fieldName: "NUM", + }, }; map[ArgumentType.STRING] = { shadow: { - type: 'text', - fieldName: 'TEXT' - } + type: "text", + fieldName: "TEXT", + }, }; map[ArgumentType.BOOLEAN] = { - check: 'Boolean' + check: "Boolean", }; map[ArgumentType.MATRIX] = { shadow: { - type: 'matrix', - fieldName: 'MATRIX' - } + type: "matrix", + fieldName: "MATRIX", + }, }; map[ArgumentType.NOTE] = { shadow: { - type: 'note', - fieldName: 'NOTE' - } + type: "note", + fieldName: "NOTE", + }, }; map[ArgumentType.IMAGE] = { // Inline images are weird because they're not actually "arguments". // They are more analagous to the label on a block. - fieldType: 'field_image' + fieldType: "field_image", }; return map; })(); @@ -150,7 +150,7 @@ const cloudDataManager = () => { canAddCloudVariable, addCloudVariable, removeCloudVariable, - hasCloudVariables + hasCloudVariables, }; }; @@ -177,7 +177,7 @@ let rendererDrawProfilerId = -1; * @constructor */ class Runtime extends EventEmitter { - constructor () { + constructor() { super(); /** @@ -344,7 +344,7 @@ class Runtime extends EventEmitter { mouse: new Mouse(this), mouseWheel: new MouseWheel(this), userData: new UserData(), - video: new Video(this) + video: new Video(this), }; /** @@ -385,7 +385,8 @@ class Runtime extends EventEmitter { * being added. * @type {function} */ - this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager); + this.addCloudVariable = + this._initializeAddCloudVariable(newCloudDataManager); /** * A function which updates the runtime's cloud variable limit @@ -393,7 +394,8 @@ class Runtime extends EventEmitter { * if the last of the cloud variables is being removed. * @type {function} */ - this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager); + this.removeCloudVariable = + this._initializeRemoveCloudVariable(newCloudDataManager); /** * A string representing the origin of the current project from outside of the @@ -411,7 +413,7 @@ class Runtime extends EventEmitter { * Width of the stage, in pixels. * @const {number} */ - static get STAGE_WIDTH () { + static get STAGE_WIDTH() { return 480; } @@ -419,7 +421,7 @@ class Runtime extends EventEmitter { * Height of the stage, in pixels. * @const {number} */ - static get STAGE_HEIGHT () { + static get STAGE_HEIGHT() { return 360; } @@ -427,32 +429,32 @@ class Runtime extends EventEmitter { * Event name for glowing a script. * @const {string} */ - static get SCRIPT_GLOW_ON () { - return 'SCRIPT_GLOW_ON'; + static get SCRIPT_GLOW_ON() { + return "SCRIPT_GLOW_ON"; } /** * Event name for unglowing a script. * @const {string} */ - static get SCRIPT_GLOW_OFF () { - return 'SCRIPT_GLOW_OFF'; + static get SCRIPT_GLOW_OFF() { + return "SCRIPT_GLOW_OFF"; } /** * Event name for glowing a block. * @const {string} */ - static get BLOCK_GLOW_ON () { - return 'BLOCK_GLOW_ON'; + static get BLOCK_GLOW_ON() { + return "BLOCK_GLOW_ON"; } /** * Event name for unglowing a block. * @const {string} */ - static get BLOCK_GLOW_OFF () { - return 'BLOCK_GLOW_OFF'; + static get BLOCK_GLOW_OFF() { + return "BLOCK_GLOW_OFF"; } /** @@ -460,24 +462,24 @@ class Runtime extends EventEmitter { * to this project. * @const {string} */ - static get HAS_CLOUD_DATA_UPDATE () { - return 'HAS_CLOUD_DATA_UPDATE'; + static get HAS_CLOUD_DATA_UPDATE() { + return "HAS_CLOUD_DATA_UPDATE"; } /** * Event name for turning on turbo mode. * @const {string} */ - static get TURBO_MODE_ON () { - return 'TURBO_MODE_ON'; + static get TURBO_MODE_ON() { + return "TURBO_MODE_ON"; } /** * Event name for turning off turbo mode. * @const {string} */ - static get TURBO_MODE_OFF () { - return 'TURBO_MODE_OFF'; + static get TURBO_MODE_OFF() { + return "TURBO_MODE_OFF"; } /** @@ -485,8 +487,8 @@ class Runtime extends EventEmitter { * running). * @const {string} */ - static get PROJECT_START () { - return 'PROJECT_START'; + static get PROJECT_START() { + return "PROJECT_START"; } /** @@ -494,8 +496,8 @@ class Runtime extends EventEmitter { * Used by the UI to indicate running status. * @const {string} */ - static get PROJECT_RUN_START () { - return 'PROJECT_RUN_START'; + static get PROJECT_RUN_START() { + return "PROJECT_RUN_START"; } /** @@ -503,8 +505,8 @@ class Runtime extends EventEmitter { * Used by the UI to indicate not-running status. * @const {string} */ - static get PROJECT_RUN_STOP () { - return 'PROJECT_RUN_STOP'; + static get PROJECT_RUN_STOP() { + return "PROJECT_RUN_STOP"; } /** @@ -512,8 +514,8 @@ class Runtime extends EventEmitter { * Used by blocks that need to reset state. * @const {string} */ - static get PROJECT_STOP_ALL () { - return 'PROJECT_STOP_ALL'; + static get PROJECT_STOP_ALL() { + return "PROJECT_STOP_ALL"; } /** @@ -521,88 +523,88 @@ class Runtime extends EventEmitter { * Used by blocks that need to stop individual targets. * @const {string} */ - static get STOP_FOR_TARGET () { - return 'STOP_FOR_TARGET'; + static get STOP_FOR_TARGET() { + return "STOP_FOR_TARGET"; } /** * Event name for visual value report. * @const {string} */ - static get VISUAL_REPORT () { - return 'VISUAL_REPORT'; + static get VISUAL_REPORT() { + return "VISUAL_REPORT"; } /** * Event name for project loaded report. * @const {string} */ - static get PROJECT_LOADED () { - return 'PROJECT_LOADED'; + static get PROJECT_LOADED() { + return "PROJECT_LOADED"; } /** * Event name for report that a change was made that can be saved * @const {string} */ - static get PROJECT_CHANGED () { - return 'PROJECT_CHANGED'; + static get PROJECT_CHANGED() { + return "PROJECT_CHANGED"; } /** * Event name for report that a change was made to an extension in the toolbox. * @const {string} */ - static get TOOLBOX_EXTENSIONS_NEED_UPDATE () { - return 'TOOLBOX_EXTENSIONS_NEED_UPDATE'; + static get TOOLBOX_EXTENSIONS_NEED_UPDATE() { + return "TOOLBOX_EXTENSIONS_NEED_UPDATE"; } /** * Event name for targets update report. * @const {string} */ - static get TARGETS_UPDATE () { - return 'TARGETS_UPDATE'; + static get TARGETS_UPDATE() { + return "TARGETS_UPDATE"; } /** * Event name for monitors update. * @const {string} */ - static get MONITORS_UPDATE () { - return 'MONITORS_UPDATE'; + static get MONITORS_UPDATE() { + return "MONITORS_UPDATE"; } /** * Event name for block drag update. * @const {string} */ - static get BLOCK_DRAG_UPDATE () { - return 'BLOCK_DRAG_UPDATE'; + static get BLOCK_DRAG_UPDATE() { + return "BLOCK_DRAG_UPDATE"; } /** * Event name for block drag end. * @const {string} */ - static get BLOCK_DRAG_END () { - return 'BLOCK_DRAG_END'; + static get BLOCK_DRAG_END() { + return "BLOCK_DRAG_END"; } /** * Event name for reporting that an extension was added. * @const {string} */ - static get EXTENSION_ADDED () { - return 'EXTENSION_ADDED'; + static get EXTENSION_ADDED() { + return "EXTENSION_ADDED"; } /** * Event name for reporting that an extension as asked for a custom field to be added * @const {string} */ - static get EXTENSION_FIELD_ADDED () { - return 'EXTENSION_FIELD_ADDED'; + static get EXTENSION_FIELD_ADDED() { + return "EXTENSION_FIELD_ADDED"; } /** @@ -611,8 +613,8 @@ class Runtime extends EventEmitter { * available peripherals. * @const {string} */ - static get PERIPHERAL_LIST_UPDATE () { - return 'PERIPHERAL_LIST_UPDATE'; + static get PERIPHERAL_LIST_UPDATE() { + return "PERIPHERAL_LIST_UPDATE"; } /** @@ -620,8 +622,8 @@ class Runtime extends EventEmitter { * via Companion Device Manager (CDM) * @const {string} */ - static get USER_PICKED_PERIPHERAL () { - return 'USER_PICKED_PERIPHERAL'; + static get USER_PICKED_PERIPHERAL() { + return "USER_PICKED_PERIPHERAL"; } /** @@ -629,8 +631,8 @@ class Runtime extends EventEmitter { * This causes the status button in the blocks menu to indicate 'connected'. * @const {string} */ - static get PERIPHERAL_CONNECTED () { - return 'PERIPHERAL_CONNECTED'; + static get PERIPHERAL_CONNECTED() { + return "PERIPHERAL_CONNECTED"; } /** @@ -638,8 +640,8 @@ class Runtime extends EventEmitter { * This causes the status button in the blocks menu to indicate 'disconnected'. * @const {string} */ - static get PERIPHERAL_DISCONNECTED () { - return 'PERIPHERAL_DISCONNECTED'; + static get PERIPHERAL_DISCONNECTED() { + return "PERIPHERAL_DISCONNECTED"; } /** @@ -647,8 +649,8 @@ class Runtime extends EventEmitter { * This causes the peripheral connection modal to switch to an error state. * @const {string} */ - static get PERIPHERAL_REQUEST_ERROR () { - return 'PERIPHERAL_REQUEST_ERROR'; + static get PERIPHERAL_REQUEST_ERROR() { + return "PERIPHERAL_REQUEST_ERROR"; } /** @@ -656,8 +658,8 @@ class Runtime extends EventEmitter { * This causes a 'peripheral connection lost' error alert to display. * @const {string} */ - static get PERIPHERAL_CONNECTION_LOST_ERROR () { - return 'PERIPHERAL_CONNECTION_LOST_ERROR'; + static get PERIPHERAL_CONNECTION_LOST_ERROR() { + return "PERIPHERAL_CONNECTION_LOST_ERROR"; } /** @@ -665,61 +667,61 @@ class Runtime extends EventEmitter { * This causes the peripheral connection modal to show a timeout state. * @const {string} */ - static get PERIPHERAL_SCAN_TIMEOUT () { - return 'PERIPHERAL_SCAN_TIMEOUT'; + static get PERIPHERAL_SCAN_TIMEOUT() { + return "PERIPHERAL_SCAN_TIMEOUT"; } /** * Event name to indicate that the microphone is being used to stream audio. * @const {string} */ - static get MIC_LISTENING () { - return 'MIC_LISTENING'; + static get MIC_LISTENING() { + return "MIC_LISTENING"; } /** * Event name for reporting that blocksInfo was updated. * @const {string} */ - static get BLOCKSINFO_UPDATE () { - return 'BLOCKSINFO_UPDATE'; + static get BLOCKSINFO_UPDATE() { + return "BLOCKSINFO_UPDATE"; } /** * Event name when the runtime tick loop has been started. * @const {string} */ - static get RUNTIME_STARTED () { - return 'RUNTIME_STARTED'; + static get RUNTIME_STARTED() { + return "RUNTIME_STARTED"; } /** * Event name when the runtime dispose has been called. * @const {string} */ - static get RUNTIME_DISPOSED () { - return 'RUNTIME_DISPOSED'; + static get RUNTIME_DISPOSED() { + return "RUNTIME_DISPOSED"; } /** * Event name for reporting that a block was updated and needs to be rerendered. * @const {string} */ - static get BLOCKS_NEED_UPDATE () { - return 'BLOCKS_NEED_UPDATE'; + static get BLOCKS_NEED_UPDATE() { + return "BLOCKS_NEED_UPDATE"; } /** * How rapidly we try to step threads by default, in ms. */ - static get THREAD_STEP_INTERVAL () { + static get THREAD_STEP_INTERVAL() { return 1000 / 60; } /** * In compatibility mode, how rapidly we try to step threads, in ms. */ - static get THREAD_STEP_INTERVAL_COMPATIBILITY () { + static get THREAD_STEP_INTERVAL_COMPATIBILITY() { return 1000 / 30; } @@ -727,7 +729,7 @@ class Runtime extends EventEmitter { * How many clones can be created at a time. * @const {number} */ - static get MAX_CLONES () { + static get MAX_CLONES() { return 300; } @@ -735,26 +737,26 @@ class Runtime extends EventEmitter { // ----------------------------------------------------------------------------- // Helper function for initializing the addCloudVariable function - _initializeAddCloudVariable (newCloudDataManager) { + _initializeAddCloudVariable(newCloudDataManager) { // The addCloudVariable function - return (() => { + return () => { const hadCloudVarsBefore = this.hasCloudData(); newCloudDataManager.addCloudVariable(); if (!hadCloudVarsBefore && this.hasCloudData()) { this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, true); } - }); + }; } // Helper function for initializing the removeCloudVariable function - _initializeRemoveCloudVariable (newCloudDataManager) { - return (() => { + _initializeRemoveCloudVariable(newCloudDataManager) { + return () => { const hadCloudVarsBefore = this.hasCloudData(); newCloudDataManager.removeCloudVariable(); if (hadCloudVarsBefore && !this.hasCloudData()) { this.emit(Runtime.HAS_CLOUD_DATA_UPDATE, false); } - }); + }; } /** @@ -762,16 +764,28 @@ class Runtime extends EventEmitter { * @todo Prefix opcodes with package name. * @private */ - _registerBlockPackages () { + _registerBlockPackages() { for (const packageName in defaultBlockPackages) { - if (Object.prototype.hasOwnProperty.call(defaultBlockPackages, packageName)) { + if ( + Object.prototype.hasOwnProperty.call( + defaultBlockPackages, + packageName + ) + ) { // @todo pass a different runtime depending on package privilege? - const packageObject = new (defaultBlockPackages[packageName])(this); + const packageObject = new defaultBlockPackages[packageName]( + this + ); // Collect primitives from package. if (packageObject.getPrimitives) { const packagePrimitives = packageObject.getPrimitives(); for (const op in packagePrimitives) { - if (Object.prototype.hasOwnProperty.call(packagePrimitives, op)) { + if ( + Object.prototype.hasOwnProperty.call( + packagePrimitives, + op + ) + ) { this._primitives[op] = packagePrimitives[op].bind(packageObject); } @@ -781,20 +795,29 @@ class Runtime extends EventEmitter { if (packageObject.getHats) { const packageHats = packageObject.getHats(); for (const hatName in packageHats) { - if (Object.prototype.hasOwnProperty.call(packageHats, hatName)) { + if ( + Object.prototype.hasOwnProperty.call( + packageHats, + hatName + ) + ) { this._hats[hatName] = packageHats[hatName]; } } } // Collect monitored from package. if (packageObject.getMonitored) { - this.monitorBlockInfo = Object.assign({}, this.monitorBlockInfo, packageObject.getMonitored()); + this.monitorBlockInfo = Object.assign( + {}, + this.monitorBlockInfo, + packageObject.getMonitored() + ); } } } } - getMonitorState () { + getMonitorState() { return this._monitorState; } @@ -805,7 +828,7 @@ class Runtime extends EventEmitter { * @returns {string} - the constructed ID. * @private */ - _makeExtensionMenuId (menuName, extensionId) { + _makeExtensionMenuId(menuName, extensionId) { return `${extensionId}_menu_${xmlEscape(menuName)}`; } @@ -814,11 +837,13 @@ class Runtime extends EventEmitter { * @param {Target} [target] - the target to use as context. If a target is not provided, default to the current * editing target or the stage. */ - makeMessageContextForTarget (target) { + makeMessageContextForTarget(target) { const context = {}; target = target || this.getEditingTarget() || this.getTargetForStage(); if (target) { - context.targetType = (target.isStage ? TargetType.STAGE : TargetType.SPRITE); + context.targetType = target.isStage + ? TargetType.STAGE + : TargetType.SPRITE; } } @@ -827,13 +852,13 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - information about the extension (id, blocks, etc.) * @private */ - _registerExtensionPrimitives (extensionInfo) { + _registerExtensionPrimitives(extensionInfo) { const categoryInfo = { id: extensionInfo.id, name: maybeFormatMessage(extensionInfo.name), showStatusButton: extensionInfo.showStatusButton, blockIconURI: extensionInfo.blockIconURI, - menuIconURI: extensionInfo.menuIconURI + menuIconURI: extensionInfo.menuIconURI, }; if (extensionInfo.color1) { @@ -851,13 +876,19 @@ class Runtime extends EventEmitter { this._fillExtensionCategory(categoryInfo, extensionInfo); for (const fieldTypeName in categoryInfo.customFieldTypes) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) { - const fieldTypeInfo = categoryInfo.customFieldTypes[fieldTypeName]; + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.customFieldTypes, + fieldTypeName + ) + ) { + const fieldTypeInfo = + categoryInfo.customFieldTypes[fieldTypeName]; // Emit events for custom field types from extension this.emit(Runtime.EXTENSION_FIELD_ADDED, { name: `field_${fieldTypeInfo.extendedName}`, - implementation: fieldTypeInfo.fieldImplementation + implementation: fieldTypeInfo.fieldImplementation, }); } } @@ -870,8 +901,10 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - new info (results of running getInfo) for an extension * @private */ - _refreshExtensionPrimitives (extensionInfo) { - const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id); + _refreshExtensionPrimitives(extensionInfo) { + const categoryInfo = this._blockInfo.find( + (info) => info.id === extensionInfo.id + ); if (categoryInfo) { categoryInfo.name = maybeFormatMessage(extensionInfo.name); this._fillExtensionCategory(categoryInfo, extensionInfo); @@ -887,22 +920,36 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - the extension metadata to read * @private */ - _fillExtensionCategory (categoryInfo, extensionInfo) { + _fillExtensionCategory(categoryInfo, extensionInfo) { categoryInfo.blocks = []; categoryInfo.customFieldTypes = {}; categoryInfo.menus = []; categoryInfo.menuInfo = {}; for (const menuName in extensionInfo.menus) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.menus, menuName)) { + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.menus, + menuName + ) + ) { const menuInfo = extensionInfo.menus[menuName]; - const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo); + const convertedMenu = this._buildMenuForScratchBlocks( + menuName, + menuInfo, + categoryInfo + ); categoryInfo.menus.push(convertedMenu); categoryInfo.menuInfo[menuName] = menuInfo; } } for (const fieldTypeName in extensionInfo.customFieldTypes) { - if (Object.prototype.hasOwnProperty.call(extensionInfo.customFieldTypes, fieldTypeName)) { + if ( + Object.prototype.hasOwnProperty.call( + extensionInfo.customFieldTypes, + fieldTypeName + ) + ) { const fieldType = extensionInfo.customFieldTypes[fieldTypeName]; const fieldTypeInfo = this._buildCustomFieldInfo( fieldTypeName, @@ -917,22 +964,32 @@ class Runtime extends EventEmitter { for (const blockInfo of extensionInfo.blocks) { try { - const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo); + const convertedBlock = this._convertForScratchBlocks( + blockInfo, + categoryInfo + ); categoryInfo.blocks.push(convertedBlock); if (convertedBlock.json) { const opcode = convertedBlock.json.type; if (blockInfo.blockType !== BlockType.EVENT) { this._primitives[opcode] = convertedBlock.info.func; } - if (blockInfo.blockType === BlockType.EVENT || blockInfo.blockType === BlockType.HAT) { + if ( + blockInfo.blockType === BlockType.EVENT || + blockInfo.blockType === BlockType.HAT + ) { this._hats[opcode] = { edgeActivated: blockInfo.isEdgeActivated, - restartExistingThreads: blockInfo.shouldRestartExistingThreads + restartExistingThreads: + blockInfo.shouldRestartExistingThreads, }; } } } catch (e) { - log.error('Error parsing block: ', {block: blockInfo, error: e}); + log.error("Error parsing block: ", { + block: blockInfo, + error: e, + }); } } } @@ -944,18 +1001,29 @@ class Runtime extends EventEmitter { * @returns {object} - an array of 2 element arrays or the original input function * @private */ - _convertMenuItems (menuItems) { - if (typeof menuItems !== 'function') { + _convertMenuItems(menuItems) { + if (typeof menuItems !== "function") { const extensionMessageContext = this.makeMessageContextForTarget(); - return menuItems.map(item => { - const formattedItem = maybeFormatMessage(item, extensionMessageContext); + return menuItems.map((item) => { + const formattedItem = maybeFormatMessage( + item, + extensionMessageContext + ); switch (typeof formattedItem) { - case 'string': - return [formattedItem, formattedItem]; - case 'object': - return [maybeFormatMessage(item.text, extensionMessageContext), item.value]; - default: - throw new Error(`Can't interpret menu item: ${JSON.stringify(item)}`); + case "string": + return [formattedItem, formattedItem]; + case "object": + return [ + maybeFormatMessage( + item.text, + extensionMessageContext + ), + item.value, + ]; + default: + throw new Error( + `Can't interpret menu item: ${JSON.stringify(item)}` + ); } }); } @@ -972,32 +1040,33 @@ class Runtime extends EventEmitter { * @returns {object} - a JSON-esque object ready for scratch-blocks' consumption * @private */ - _buildMenuForScratchBlocks (menuName, menuInfo, categoryInfo) { + _buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo) { const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id); const menuItems = this._convertMenuItems(menuInfo.items); return { json: { - message0: '%1', + message0: "%1", type: menuId, inputsInline: true, - output: 'String', + output: "String", colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, colourTertiary: categoryInfo.color3, - outputShape: menuInfo.acceptReporters ? - ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, + outputShape: menuInfo.acceptReporters + ? ScratchBlocksConstants.OUTPUT_SHAPE_ROUND + : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, args0: [ { - type: 'field_dropdown', + type: "field_dropdown", name: menuName, - options: menuItems - } - ] - } + options: menuItems, + }, + ], + }, }; } - _buildCustomFieldInfo (fieldName, fieldInfo, extensionId, categoryInfo) { + _buildCustomFieldInfo(fieldName, fieldInfo, extensionId, categoryInfo) { const extendedName = `${extensionId}_${fieldName}`; return { fieldName: fieldName, @@ -1005,8 +1074,8 @@ class Runtime extends EventEmitter { argumentTypeInfo: { shadow: { type: extendedName, - fieldName: `field_${extendedName}` - } + fieldName: `field_${extendedName}`, + }, }, scratchBlocksDefinition: this._buildCustomFieldTypeForScratchBlocks( extendedName, @@ -1014,7 +1083,7 @@ class Runtime extends EventEmitter { fieldInfo.outputShape, categoryInfo ), - fieldImplementation: fieldInfo.implementation + fieldImplementation: fieldInfo.implementation, }; } @@ -1027,11 +1096,16 @@ class Runtime extends EventEmitter { * @param {object} categoryInfo - The category the field belongs to (Used to set its colors) * @returns {object} - Object to be inserted into scratch-blocks */ - _buildCustomFieldTypeForScratchBlocks (fieldName, output, outputShape, categoryInfo) { + _buildCustomFieldTypeForScratchBlocks( + fieldName, + output, + outputShape, + categoryInfo + ) { return { json: { type: fieldName, - message0: '%1', + message0: "%1", inputsInline: true, output: output, colour: categoryInfo.color1, @@ -1041,10 +1115,10 @@ class Runtime extends EventEmitter { args0: [ { name: `field_${fieldName}`, - type: `field_${fieldName}` - } - ] - } + type: `field_${fieldName}`, + }, + ], + }, }; } @@ -1055,8 +1129,8 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertForScratchBlocks (blockInfo, categoryInfo) { - if (blockInfo === '---') { + _convertForScratchBlocks(blockInfo, categoryInfo) { + if (blockInfo === "---") { return this._convertSeparatorForScratchBlocks(blockInfo); } @@ -1074,7 +1148,7 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertBlockForScratchBlocks (blockInfo, categoryInfo) { + _convertBlockForScratchBlocks(blockInfo, categoryInfo) { const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`; const blockJSON = { @@ -1083,7 +1157,7 @@ class Runtime extends EventEmitter { category: categoryInfo.name, colour: categoryInfo.color1, colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3 + colourTertiary: categoryInfo.color3, }; const context = { // TODO: store this somewhere so that we can map args appropriately after translation. @@ -1094,7 +1168,7 @@ class Runtime extends EventEmitter { blockJSON, categoryInfo, blockInfo, - inputList: [] + inputList: [], }; // If an icon for the extension exists, prepend it to each block, with a vertical separator. @@ -1103,72 +1177,97 @@ class Runtime extends EventEmitter { const iconURI = blockInfo.blockIconURI || categoryInfo.blockIconURI; if (iconURI) { - blockJSON.extensions = ['scratch_extension']; - blockJSON.message0 = '%1 %2'; + blockJSON.extensions = ["scratch_extension"]; + blockJSON.message0 = "%1 %2"; const iconJSON = { - type: 'field_image', + type: "field_image", src: iconURI, width: 40, - height: 40 + height: 40, }; const separatorJSON = { - type: 'field_vertical_separator' + type: "field_vertical_separator", }; - blockJSON.args0 = [ - iconJSON, - separatorJSON - ]; + blockJSON.args0 = [iconJSON, separatorJSON]; } switch (blockInfo.blockType) { - case BlockType.COMMAND: - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.previousStatement = null; // null = available connection; undefined = hat - if (!blockInfo.isTerminal) { - blockJSON.nextStatement = null; // null = available connection; undefined = terminal - } - break; - case BlockType.REPORTER: - blockJSON.output = 'String'; // TODO: distinguish number & string here? - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND; - break; - case BlockType.BOOLEAN: - blockJSON.output = 'Boolean'; - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL; - break; - case BlockType.HAT: - case BlockType.EVENT: - if (!Object.prototype.hasOwnProperty.call(blockInfo, 'isEdgeActivated')) { - // if absent, this property defaults to true - blockInfo.isEdgeActivated = true; - } - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.nextStatement = null; // null = available connection; undefined = terminal - break; - case BlockType.CONDITIONAL: - case BlockType.LOOP: - blockInfo.branchCount = blockInfo.branchCount || 1; - blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.previousStatement = null; // null = available connection; undefined = hat - if (!blockInfo.isTerminal) { + case BlockType.COMMAND: + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; + blockJSON.previousStatement = null; // null = available connection; undefined = hat + if (!blockInfo.isTerminal) { + blockJSON.nextStatement = null; // null = available connection; undefined = terminal + } + break; + case BlockType.REPORTER: + blockJSON.output = "String"; // TODO: distinguish number & string here? + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_ROUND; + break; + case BlockType.BOOLEAN: + blockJSON.output = "Boolean"; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL; + break; + case BlockType.HAT: + case BlockType.EVENT: + if ( + !Object.prototype.hasOwnProperty.call( + blockInfo, + "isEdgeActivated" + ) + ) { + // if absent, this property defaults to true + blockInfo.isEdgeActivated = true; + } + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; blockJSON.nextStatement = null; // null = available connection; undefined = terminal - } - break; + if (!blockJSON.extensions) { + blockJSON.extensions = []; + } + blockJSON.extensions.push("shape_hat"); + break; + case BlockType.CONDITIONAL: + case BlockType.LOOP: + blockInfo.branchCount = blockInfo.branchCount || 1; + blockJSON.outputShape = + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; + blockJSON.previousStatement = null; // null = available connection; undefined = hat + if (!blockInfo.isTerminal) { + blockJSON.nextStatement = null; // null = available connection; undefined = terminal + } + break; } - const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text]; + const blockText = Array.isArray(blockInfo.text) + ? blockInfo.text + : [blockInfo.text]; let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum] let inBranchNum = 0; // how many branches have we placed into the JSON so far? let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}` - const convertPlaceholders = this._convertPlaceholders.bind(this, context); + const convertPlaceholders = this._convertPlaceholders.bind( + this, + context + ); const extensionMessageContext = this.makeMessageContextForTarget(); // alternate between a block "arm" with text on it and an open slot for a substack - while (inTextNum < blockText.length || inBranchNum < blockInfo.branchCount) { + while ( + inTextNum < blockText.length || + inBranchNum < blockInfo.branchCount + ) { if (inTextNum < blockText.length) { context.outLineNum = outLineNum; - const lineText = maybeFormatMessage(blockText[inTextNum], extensionMessageContext); - const convertedText = lineText.replace(/\[(.+?)]/g, convertPlaceholders); + const lineText = maybeFormatMessage( + blockText[inTextNum], + extensionMessageContext + ); + const convertedText = lineText.replace( + /\[(.+?)]/g, + convertPlaceholders + ); if (blockJSON[`message${outLineNum}`]) { blockJSON[`message${outLineNum}`] += convertedText; } else { @@ -1178,11 +1277,15 @@ class Runtime extends EventEmitter { ++outLineNum; } if (inBranchNum < blockInfo.branchCount) { - blockJSON[`message${outLineNum}`] = '%1'; - blockJSON[`args${outLineNum}`] = [{ - type: 'input_statement', - name: `SUBSTACK${inBranchNum > 0 ? inBranchNum + 1 : ''}` - }]; + blockJSON[`message${outLineNum}`] = "%1"; + blockJSON[`args${outLineNum}`] = [ + { + type: "input_statement", + name: `SUBSTACK${ + inBranchNum > 0 ? inBranchNum + 1 : "" + }`, + }, + ]; ++inBranchNum; ++outLineNum; } @@ -1194,27 +1297,31 @@ class Runtime extends EventEmitter { } } else if (blockInfo.blockType === BlockType.LOOP) { // Add icon to the bottom right of a loop block - blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT'; - blockJSON[`message${outLineNum}`] = '%1'; - blockJSON[`args${outLineNum}`] = [{ - type: 'field_image', - src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable? - width: 24, - height: 24, - alt: '*', // TODO remove this since we don't use collapsed blocks in scratch - flip_rtl: true - }]; + blockJSON[`lastDummyAlign${outLineNum}`] = "RIGHT"; + blockJSON[`message${outLineNum}`] = "%1"; + blockJSON[`args${outLineNum}`] = [ + { + type: "field_image", + src: "./static/blocks-media/repeat.svg", // TODO: use a constant or make this configurable? + width: 24, + height: 24, + alt: "*", // TODO remove this since we don't use collapsed blocks in scratch + flip_rtl: true, + }, + ]; ++outLineNum; } - const mutation = blockInfo.isDynamic ? `` : ''; - const inputs = context.inputList.join(''); + const mutation = blockInfo.isDynamic + ? `` + : ""; + const inputs = context.inputList.join(""); const blockXML = `${mutation}${inputs}`; return { info: context.blockInfo, json: context.blockJSON, - xml: blockXML + xml: blockXML, }; } @@ -1225,10 +1332,10 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertSeparatorForScratchBlocks (blockInfo) { + _convertSeparatorForScratchBlocks(blockInfo) { return { info: blockInfo, - xml: '' + xml: '', }; } @@ -1240,18 +1347,27 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original button information * @private */ - _convertButtonForScratchBlocks (buttonInfo) { + _convertButtonForScratchBlocks(buttonInfo) { // for now we only support these pre-defined callbacks handled in scratch-blocks - const supportedCallbackKeys = ['MAKE_A_LIST', 'MAKE_A_PROCEDURE', 'MAKE_A_VARIABLE']; + const supportedCallbackKeys = [ + "MAKE_A_LIST", + "MAKE_A_PROCEDURE", + "MAKE_A_VARIABLE", + ]; if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) { - log.error(`Custom button callbacks not supported yet: ${buttonInfo.func}`); + log.error( + `Custom button callbacks not supported yet: ${buttonInfo.func}` + ); } const extensionMessageContext = this.makeMessageContextForTarget(); - const buttonText = maybeFormatMessage(buttonInfo.text, extensionMessageContext); + const buttonText = maybeFormatMessage( + buttonInfo.text, + extensionMessageContext + ); return { info: buttonInfo, - xml: `` + xml: ``, }; } @@ -1261,20 +1377,22 @@ class Runtime extends EventEmitter { * @return {object} JSON blob for a scratch-blocks image field. * @private */ - _constructInlineImageJson (argInfo) { + _constructInlineImageJson(argInfo) { if (!argInfo.dataURI) { - log.warn('Missing data URI in extension block with argument type IMAGE'); + log.warn( + "Missing data URI in extension block with argument type IMAGE" + ); } return { - type: 'field_image', - src: argInfo.dataURI || '', + type: "field_image", + src: argInfo.dataURI || "", // TODO these probably shouldn't be hardcoded...? width: 24, height: 24, // Whether or not the inline image should be flipped horizontally // in RTL languages. Defaults to false, indicating that the // image will not be flipped. - flip_rtl: argInfo.flipRTL || false + flip_rtl: argInfo.flipRTL || false, }; } @@ -1287,17 +1405,22 @@ class Runtime extends EventEmitter { * @return {string} scratch-blocks placeholder for the argument: '%1'. * @private */ - _convertPlaceholders (context, match, placeholder) { + _convertPlaceholders(context, match, placeholder) { // Sanitize the placeholder to ensure valid XML - placeholder = placeholder.replace(/[<"&]/, '_'); + placeholder = placeholder.replace(/[<"&]/, "_"); // Determine whether the argument type is one of the known standard field types const argInfo = context.blockInfo.arguments[placeholder] || {}; let argTypeInfo = ArgumentTypeMap[argInfo.type] || {}; // Field type not a standard field type, see if extension has registered custom field type - if (!ArgumentTypeMap[argInfo.type] && context.categoryInfo.customFieldTypes[argInfo.type]) { - argTypeInfo = context.categoryInfo.customFieldTypes[argInfo.type].argumentTypeInfo; + if ( + !ArgumentTypeMap[argInfo.type] && + context.categoryInfo.customFieldTypes[argInfo.type] + ) { + argTypeInfo = + context.categoryInfo.customFieldTypes[argInfo.type] + .argumentTypeInfo; } // Start to construct the scratch-blocks style JSON defining how the block should be @@ -1306,20 +1429,26 @@ class Runtime extends EventEmitter { // Most field types are inputs (slots on the block that can have other blocks plugged into them) // check if this is not one of those cases. E.g. an inline image on a block. - if (argTypeInfo.fieldType === 'field_image') { + if (argTypeInfo.fieldType === "field_image") { argJSON = this._constructInlineImageJson(argInfo); } else { // Construct input value // Layout a block argument (e.g. an input slot on the block) argJSON = { - type: 'input_value', - name: placeholder + type: "input_value", + name: placeholder, }; const defaultValue = - typeof argInfo.defaultValue === 'undefined' ? '' : - xmlEscape(maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString()); + typeof argInfo.defaultValue === "undefined" + ? "" + : xmlEscape( + maybeFormatMessage( + argInfo.defaultValue, + this.makeMessageContextForTarget() + ).toString() + ); if (argTypeInfo.check) { // Right now the only type of 'check' we have specifies that the @@ -1335,10 +1464,13 @@ class Runtime extends EventEmitter { const menuInfo = context.categoryInfo.menuInfo[argInfo.menu]; if (menuInfo.acceptReporters) { valueName = placeholder; - shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id); + shadowType = this._makeExtensionMenuId( + argInfo.menu, + context.categoryInfo.id + ); fieldName = argInfo.menu; } else { - argJSON.type = 'field_dropdown'; + argJSON.type = "field_dropdown"; argJSON.options = this._convertMenuItems(menuInfo.items); valueName = null; shadowType = null; @@ -1346,8 +1478,11 @@ class Runtime extends EventEmitter { } } else { valueName = placeholder; - shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null; - fieldName = (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || null; + shadowType = + (argTypeInfo.shadow && argTypeInfo.shadow.type) || null; + fieldName = + (argTypeInfo.shadow && argTypeInfo.shadow.fieldName) || + null; } // is the ScratchBlocks name for a block input. @@ -1364,20 +1499,23 @@ class Runtime extends EventEmitter { // A displays a dynamic value: a user-editable text field, a drop-down menu, etc. // Leave out the field if defaultValue or fieldName are not specified if (defaultValue && fieldName) { - context.inputList.push(`${defaultValue}`); + context.inputList.push( + `${defaultValue}` + ); } if (shadowType) { - context.inputList.push(''); + context.inputList.push(""); } if (valueName) { - context.inputList.push(''); + context.inputList.push(""); } } const argsName = `args${context.outLineNum}`; - const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []); + const blockArgs = (context.blockJSON[argsName] = + context.blockJSON[argsName] || []); if (argJSON) blockArgs.push(argJSON); const argNum = blockArgs.length; context.argsMap[placeholder] = argNum; @@ -1391,12 +1529,12 @@ class Runtime extends EventEmitter { * @property {string} id - the category / extension ID * @property {string} xml - the XML text for this category, starting with `` and ending with `` */ - getBlocksXML (target) { - return this._blockInfo.map(categoryInfo => { - const {name, color1, color2} = categoryInfo; + getBlocksXML(target) { + return this._blockInfo.map((categoryInfo) => { + const { name, color1, color2 } = categoryInfo; // Filter out blocks that aren't supposed to be shown on this target, as determined by the block info's // `hideFromPalette` and `filter` properties. - const paletteBlocks = categoryInfo.blocks.filter(block => { + const paletteBlocks = categoryInfo.blocks.filter((block) => { let blockFilterIncludesTarget = true; // If an editing target is not passed, include all blocks // If the block info doesn't include a `filter` property, always include it @@ -1413,24 +1551,26 @@ class Runtime extends EventEmitter { // Use a menu icon if there is one. Otherwise, use the block icon. If there's no icon, // the category menu will show its default colored circle. - let menuIconURI = ''; + let menuIconURI = ""; if (categoryInfo.menuIconURI) { menuIconURI = categoryInfo.menuIconURI; } else if (categoryInfo.blockIconURI) { menuIconURI = categoryInfo.blockIconURI; } - const menuIconXML = menuIconURI ? - `iconURI="${menuIconURI}"` : ''; + const menuIconXML = menuIconURI ? `iconURI="${menuIconURI}"` : ""; - let statusButtonXML = ''; + let statusButtonXML = ""; if (categoryInfo.showStatusButton) { statusButtonXML = 'showStatusButton="true"'; } return { id: categoryInfo.id, - xml: `${ - paletteBlocks.map(block => block.xml).join('')}` + xml: `${paletteBlocks + .map((block) => block.xml) + .join("")}`, }; }); } @@ -1438,39 +1578,47 @@ class Runtime extends EventEmitter { /** * @returns {Array.} - an array containing the scratch-blocks JSON information for each dynamic block. */ - getBlocksJSON () { + getBlocksJSON() { return this._blockInfo.reduce( - (result, categoryInfo) => result.concat(categoryInfo.blocks.map(blockInfo => blockInfo.json)), []); + (result, categoryInfo) => + result.concat( + categoryInfo.blocks.map((blockInfo) => blockInfo.json) + ), + [] + ); } /** * One-time initialization for Scratch Link support. */ - _initScratchLink () { + _initScratchLink() { // Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin. // note that `if (self?....)` will throw if `self` is undefined, so check for that first! - if (typeof self !== 'undefined' && - typeof document !== 'undefined' && + if ( + typeof self !== "undefined" && + typeof document !== "undefined" && document.getElementById && self.origin && - self.origin !== 'null' && // note this is a string comparison, not a null check + self.origin !== "null" && // note this is a string comparison, not a null check self.navigator && self.navigator.userAgent && !( - self.navigator.userAgent.includes('Node.js') || - self.navigator.userAgent.includes('jsdom') + self.navigator.userAgent.includes("Node.js") || + self.navigator.userAgent.includes("jsdom") ) ) { // Create a script tag for the Scratch Link browser extension, unless one already exists - const scriptElement = document.getElementById('scratch-link-extension-script'); + const scriptElement = document.getElementById( + "scratch-link-extension-script" + ); if (!scriptElement) { - const script = document.createElement('script'); - script.id = 'scratch-link-extension-script'; + const script = document.createElement("script"); + script.id = "scratch-link-extension-script"; document.body.appendChild(script); // Tell the browser extension to inject its script. // If the extension isn't present or isn't active, this will do nothing. - self.postMessage('inject-scratch-link-script', self.origin); + self.postMessage("inject-scratch-link-script", self.origin); } } } @@ -1480,8 +1628,9 @@ class Runtime extends EventEmitter { * @param {string} type Either BLE or BT * @returns {ScratchLinkSocket} The scratch link socket. */ - getScratchLinkSocket (type) { - const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory; + getScratchLinkSocket(type) { + const factory = + this._linkSocketFactory || this._defaultScratchLinkSocketFactory; return factory(type); } @@ -1490,7 +1639,7 @@ class Runtime extends EventEmitter { * either BT or BLE. * @param {Function} factory The new factory for creating ScratchLink sockets. */ - configureScratchLinkSocketFactory (factory) { + configureScratchLinkSocketFactory(factory) { this._linkSocketFactory = factory; } @@ -1499,12 +1648,17 @@ class Runtime extends EventEmitter { * @param {string} type Either BLE or BT * @returns {ScratchLinkSocket} The new scratch link socket (a WebSocket object) */ - _defaultScratchLinkSocketFactory (type) { + _defaultScratchLinkSocketFactory(type) { const Scratch = self.Scratch; - const ScratchLinkSafariSocket = Scratch && Scratch.ScratchLinkSafariSocket; + const ScratchLinkSafariSocket = + Scratch && Scratch.ScratchLinkSafariSocket; // detect this every time in case the user turns on the extension after loading the page - const useSafariSocket = ScratchLinkSafariSocket && ScratchLinkSafariSocket.isSafariHelperCompatible(); - return useSafariSocket ? new ScratchLinkSafariSocket(type) : new ScratchLinkWebSocket(type); + const useSafariSocket = + ScratchLinkSafariSocket && + ScratchLinkSafariSocket.isSafariHelperCompatible(); + return useSafariSocket + ? new ScratchLinkSafariSocket(type) + : new ScratchLinkWebSocket(type); } /** @@ -1513,7 +1667,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @param {object} extension - the extension to register. */ - registerPeripheralExtension (extensionId, extension) { + registerPeripheralExtension(extensionId, extension) { this.peripheralExtensions[extensionId] = extension; } @@ -1521,7 +1675,7 @@ class Runtime extends EventEmitter { * Tell the specified extension to scan for a peripheral. * @param {string} extensionId - the id of the extension. */ - scanForPeripheral (extensionId) { + scanForPeripheral(extensionId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].scan(); } @@ -1532,7 +1686,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @param {number} peripheralId - the id of the peripheral. */ - connectPeripheral (extensionId, peripheralId) { + connectPeripheral(extensionId, peripheralId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].connect(peripheralId); } @@ -1542,7 +1696,7 @@ class Runtime extends EventEmitter { * Disconnect from the extension's connected peripheral. * @param {string} extensionId - the id of the extension. */ - disconnectPeripheral (extensionId) { + disconnectPeripheral(extensionId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].disconnect(); } @@ -1553,7 +1707,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @return {boolean} - whether the extension has a connected peripheral. */ - getPeripheralIsConnected (extensionId) { + getPeripheralIsConnected(extensionId) { let isConnected = false; if (this.peripheralExtensions[extensionId]) { isConnected = this.peripheralExtensions[extensionId].isConnected(); @@ -1565,7 +1719,7 @@ class Runtime extends EventEmitter { * Emit an event to indicate that the microphone is being used to stream audio. * @param {boolean} listening - true if the microphone is currently listening. */ - emitMicListening (listening) { + emitMicListening(listening) { this.emit(Runtime.MIC_LISTENING, listening); } @@ -1574,7 +1728,7 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {Function} The function which implements the opcode. */ - getOpcodeFunction (opcode) { + getOpcodeFunction(opcode) { return this._primitives[opcode]; } @@ -1583,7 +1737,7 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {boolean} True if the op is known to be a hat. */ - getIsHat (opcode) { + getIsHat(opcode) { return Object.prototype.hasOwnProperty.call(this._hats, opcode); } @@ -1592,17 +1746,18 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {boolean} True if the op is known to be a edge-activated hat. */ - getIsEdgeActivatedHat (opcode) { - return Object.prototype.hasOwnProperty.call(this._hats, opcode) && - this._hats[opcode].edgeActivated; + getIsEdgeActivatedHat(opcode) { + return ( + Object.prototype.hasOwnProperty.call(this._hats, opcode) && + this._hats[opcode].edgeActivated + ); } - /** * Attach the audio engine * @param {!AudioEngine} audioEngine The audio engine to attach */ - attachAudioEngine (audioEngine) { + attachAudioEngine(audioEngine) { this.audioEngine = audioEngine; } @@ -1610,7 +1765,7 @@ class Runtime extends EventEmitter { * Attach the renderer * @param {!RenderWebGL} renderer The renderer to attach */ - attachRenderer (renderer) { + attachRenderer(renderer) { this.renderer = renderer; this.renderer.setLayerGroupOrdering(StageLayering.LAYER_GROUPS); } @@ -1620,7 +1775,7 @@ class Runtime extends EventEmitter { * bitmaps to scratch 3 bitmaps. (Scratch 3 bitmaps are all bitmap resolution 2) * @param {!function} bitmapAdapter The adapter to attach */ - attachV2BitmapAdapter (bitmapAdapter) { + attachV2BitmapAdapter(bitmapAdapter) { this.v2BitmapAdapter = bitmapAdapter; } @@ -1628,7 +1783,7 @@ class Runtime extends EventEmitter { * Attach the storage module * @param {!ScratchStorage} storage The storage module to attach */ - attachStorage (storage) { + attachStorage(storage) { this.storage = storage; fetchWithTimeout.setFetch(storage.scratchFetch.scratchFetch); this.resetRunId(); @@ -1646,14 +1801,14 @@ class Runtime extends EventEmitter { * @param {?boolean} opts.updateMonitor true if the script should update a monitor value * @return {!Thread} The newly created thread. */ - _pushThread (id, target, opts) { + _pushThread(id, target, opts) { const thread = new Thread(id); thread.target = target; thread.stackClick = Boolean(opts && opts.stackClick); thread.updateMonitor = Boolean(opts && opts.updateMonitor); - thread.blockContainer = thread.updateMonitor ? - this.monitorBlocks : - target.blocks; + thread.blockContainer = thread.updateMonitor + ? this.monitorBlocks + : target.blocks; thread.pushStack(id); this.threads.push(thread); @@ -1664,7 +1819,7 @@ class Runtime extends EventEmitter { * Stop a thread: stop running it immediately, and remove it from the thread list later. * @param {!Thread} thread Thread object to remove from actives */ - _stopThread (thread) { + _stopThread(thread) { // Mark the thread for later removal thread.isKilled = true; // Inform sequencer to stop executing that thread. @@ -1678,7 +1833,7 @@ class Runtime extends EventEmitter { * @param {!Thread} thread Thread object to restart. * @return {Thread} The restarted thread. */ - _restartThread (thread) { + _restartThread(thread) { const newThread = new Thread(thread.topBlock); newThread.target = thread.target; newThread.stackClick = thread.stackClick; @@ -1699,12 +1854,12 @@ class Runtime extends EventEmitter { * @param {?Thread} thread Thread object to check. * @return {boolean} True if the thread is active/running. */ - isActiveThread (thread) { + isActiveThread(thread) { return ( - ( - thread.stack.length > 0 && - thread.status !== Thread.STATUS_DONE) && - this.threads.indexOf(thread) > -1); + thread.stack.length > 0 && + thread.status !== Thread.STATUS_DONE && + this.threads.indexOf(thread) > -1 + ); } /** @@ -1712,7 +1867,7 @@ class Runtime extends EventEmitter { * @param {?Thread} thread Thread object to check. * @return {boolean} True if the thread is waiting */ - isWaitingThread (thread) { + isWaitingThread(thread) { return ( thread.status === Thread.STATUS_PROMISE_WAIT || thread.status === Thread.STATUS_YIELD_TICK || @@ -1728,19 +1883,30 @@ class Runtime extends EventEmitter { * @param {?boolean} opts.stackClick true if the user activated the stack by clicking, false if not. This * determines whether we show a visual report when turning on the script. */ - toggleScript (topBlockId, opts) { - opts = Object.assign({ - target: this._editingTarget, - stackClick: false - }, opts); + toggleScript(topBlockId, opts) { + opts = Object.assign( + { + target: this._editingTarget, + stackClick: false, + }, + opts + ); // Remove any existing thread. for (let i = 0; i < this.threads.length; i++) { // Toggling a script that's already running turns it off - if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE) { + if ( + this.threads[i].topBlock === topBlockId && + this.threads[i].status !== Thread.STATUS_DONE + ) { const blockContainer = opts.target.blocks; - const opcode = blockContainer.getOpcode(blockContainer.getBlock(topBlockId)); + const opcode = blockContainer.getOpcode( + blockContainer.getBlock(topBlockId) + ); - if (this.getIsEdgeActivatedHat(opcode) && this.threads[i].stackClick !== opts.stackClick) { + if ( + this.getIsEdgeActivatedHat(opcode) && + this.threads[i].stackClick !== opts.stackClick + ) { // Allow edge activated hat thread stack click to coexist with // edge activated hat thread that runs every frame continue; @@ -1758,17 +1924,20 @@ class Runtime extends EventEmitter { * @param {!string} topBlockId ID of block that starts the script. * @param {?Target} optTarget target Target to run script on. If not supplied, uses editing target. */ - addMonitorScript (topBlockId, optTarget) { + addMonitorScript(topBlockId, optTarget) { if (!optTarget) optTarget = this._editingTarget; for (let i = 0; i < this.threads.length; i++) { // Don't re-add the script if it's already running - if (this.threads[i].topBlock === topBlockId && this.threads[i].status !== Thread.STATUS_DONE && - this.threads[i].updateMonitor) { + if ( + this.threads[i].topBlock === topBlockId && + this.threads[i].status !== Thread.STATUS_DONE && + this.threads[i].updateMonitor + ) { return; } } // Otherwise add it. - this._pushThread(topBlockId, optTarget, {updateMonitor: true}); + this._pushThread(topBlockId, optTarget, { updateMonitor: true }); } /** @@ -1779,7 +1948,7 @@ class Runtime extends EventEmitter { * @param {!Function} f Function to call for each script. * @param {Target=} optTarget Optionally, a target to restrict to. */ - allScriptsDo (f, optTarget) { + allScriptsDo(f, optTarget) { let targets = this.executableTargets; if (optTarget) { targets = [optTarget]; @@ -1794,14 +1963,17 @@ class Runtime extends EventEmitter { } } - allScriptsByOpcodeDo (opcode, f, optTarget) { + allScriptsByOpcodeDo(opcode, f, optTarget) { let targets = this.executableTargets; if (optTarget) { targets = [optTarget]; } for (let t = targets.length - 1; t >= 0; t--) { const target = targets[t]; - const scripts = BlocksRuntimeCache.getScripts(target.blocks, opcode); + const scripts = BlocksRuntimeCache.getScripts( + target.blocks, + opcode + ); for (let j = 0; j < scripts.length; j++) { f(scripts[j], target); } @@ -1815,9 +1987,13 @@ class Runtime extends EventEmitter { * @param {Target=} optTarget Optionally, a target to restrict to. * @return {Array.} List of threads started by this function. */ - startHats (requestedHatOpcode, - optMatchFields, optTarget) { - if (!Object.prototype.hasOwnProperty.call(this._hats, requestedHatOpcode)) { + startHats(requestedHatOpcode, optMatchFields, optTarget) { + if ( + !Object.prototype.hasOwnProperty.call( + this._hats, + requestedHatOpcode + ) + ) { // No known hat with this opcode. return; } @@ -1827,75 +2003,86 @@ class Runtime extends EventEmitter { const hatMeta = instance._hats[requestedHatOpcode]; for (const opts in optMatchFields) { - if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) continue; + if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) + continue; optMatchFields[opts] = optMatchFields[opts].toUpperCase(); } // Consider all scripts, looking for hats with opcode `requestedHatOpcode`. - this.allScriptsByOpcodeDo(requestedHatOpcode, (script, target) => { - const { - blockId: topBlockId, - fieldsOfInputs: hatFields - } = script; - - // Match any requested fields. - // For example: ensures that broadcasts match. - // This needs to happen before the block is evaluated - // (i.e., before the predicate can be run) because "broadcast and wait" - // needs to have a precise collection of started threads. - for (const matchField in optMatchFields) { - if (hatFields[matchField].value !== optMatchFields[matchField]) { - // Field mismatch. - return; - } - } - - if (hatMeta.restartExistingThreads) { - // If `restartExistingThreads` is true, we should stop - // any existing threads starting with the top block. - for (let i = 0; i < this.threads.length; i++) { - if (this.threads[i].target === target && - this.threads[i].topBlock === topBlockId && - // stack click threads and hat threads can coexist - !this.threads[i].stackClick) { - newThreads.push(this._restartThread(this.threads[i])); + this.allScriptsByOpcodeDo( + requestedHatOpcode, + (script, target) => { + const { blockId: topBlockId, fieldsOfInputs: hatFields } = + script; + + // Match any requested fields. + // For example: ensures that broadcasts match. + // This needs to happen before the block is evaluated + // (i.e., before the predicate can be run) because "broadcast and wait" + // needs to have a precise collection of started threads. + for (const matchField in optMatchFields) { + if ( + hatFields[matchField].value !== + optMatchFields[matchField] + ) { + // Field mismatch. return; } } - } else { - // If `restartExistingThreads` is false, we should - // give up if any threads with the top block are running. - for (let j = 0; j < this.threads.length; j++) { - if (this.threads[j].target === target && - this.threads[j].topBlock === topBlockId && - // stack click threads and hat threads can coexist - !this.threads[j].stackClick && - this.threads[j].status !== Thread.STATUS_DONE) { - // Some thread is already running. - return; + + if (hatMeta.restartExistingThreads) { + // If `restartExistingThreads` is true, we should stop + // any existing threads starting with the top block. + for (let i = 0; i < this.threads.length; i++) { + if ( + this.threads[i].target === target && + this.threads[i].topBlock === topBlockId && + // stack click threads and hat threads can coexist + !this.threads[i].stackClick + ) { + newThreads.push( + this._restartThread(this.threads[i]) + ); + return; + } + } + } else { + // If `restartExistingThreads` is false, we should + // give up if any threads with the top block are running. + for (let j = 0; j < this.threads.length; j++) { + if ( + this.threads[j].target === target && + this.threads[j].topBlock === topBlockId && + // stack click threads and hat threads can coexist + !this.threads[j].stackClick && + this.threads[j].status !== Thread.STATUS_DONE + ) { + // Some thread is already running. + return; + } } } - } - // Start the thread with this top block. - newThreads.push(this._pushThread(topBlockId, target)); - }, optTarget); + // Start the thread with this top block. + newThreads.push(this._pushThread(topBlockId, target)); + }, + optTarget + ); // For compatibility with Scratch 2, edge triggered hats need to be processed before // threads are stepped. See ScratchRuntime.as for original implementation - newThreads.forEach(thread => { + newThreads.forEach((thread) => { execute(this.sequencer, thread); thread.goToNextBlock(); }); return newThreads; } - /** * Dispose all targets. Return to clean state. */ - dispose () { + dispose() { this.stopAll(); // Deleting each target's variable's monitors. - this.targets.forEach(target => { + this.targets.forEach((target) => { if (target.isOriginal) target.deleteMonitors(); }); @@ -1920,8 +2107,10 @@ class Runtime extends EventEmitter { const newCloudDataManager = cloudDataManager(); this.hasCloudData = newCloudDataManager.hasCloudVariables; this.canAddCloudVariable = newCloudDataManager.canAddCloudVariable; - this.addCloudVariable = this._initializeAddCloudVariable(newCloudDataManager); - this.removeCloudVariable = this._initializeRemoveCloudVariable(newCloudDataManager); + this.addCloudVariable = + this._initializeAddCloudVariable(newCloudDataManager); + this.removeCloudVariable = + this._initializeRemoveCloudVariable(newCloudDataManager); } /** @@ -1930,7 +2119,7 @@ class Runtime extends EventEmitter { * into the correct execution order after calling this function. * @param {Target} target target to add */ - addTarget (target) { + addTarget(target) { this.targets.push(target); this.executableTargets.push(target); } @@ -1945,7 +2134,7 @@ class Runtime extends EventEmitter { * @param {number} delta number of positions to move target by * @returns {number} new position in execution order */ - moveExecutable (executableTarget, delta) { + moveExecutable(executableTarget, delta) { const oldIndex = this.executableTargets.indexOf(executableTarget); this.executableTargets.splice(oldIndex, 1); let newIndex = oldIndex + delta; @@ -1953,7 +2142,10 @@ class Runtime extends EventEmitter { newIndex = this.executableTargets.length; } if (newIndex <= 0) { - if (this.executableTargets.length > 0 && this.executableTargets[0].isStage) { + if ( + this.executableTargets.length > 0 && + this.executableTargets[0].isStage + ) { newIndex = 1; } else { newIndex = 0; @@ -1973,7 +2165,7 @@ class Runtime extends EventEmitter { * @param {number} newIndex position in execution order to place the target * @returns {number} new position in the execution order */ - setExecutablePosition (executableTarget, newIndex) { + setExecutablePosition(executableTarget, newIndex) { const oldIndex = this.executableTargets.indexOf(executableTarget); return this.moveExecutable(executableTarget, newIndex - oldIndex); } @@ -1982,7 +2174,7 @@ class Runtime extends EventEmitter { * Remove a target from the execution set. * @param {Target} executableTarget target to remove */ - removeExecutable (executableTarget) { + removeExecutable(executableTarget) { const oldIndex = this.executableTargets.indexOf(executableTarget); if (oldIndex > -1) { this.executableTargets.splice(oldIndex, 1); @@ -1993,8 +2185,8 @@ class Runtime extends EventEmitter { * Dispose of a target. * @param {!Target} disposingTarget Target to dispose of. */ - disposeTarget (disposingTarget) { - this.targets = this.targets.filter(target => { + disposeTarget(disposingTarget) { + this.targets = this.targets.filter((target) => { if (disposingTarget !== target) return true; // Allow target to do dispose actions. target.dispose(); @@ -2008,7 +2200,7 @@ class Runtime extends EventEmitter { * @param {!Target} target Target to stop threads for. * @param {Thread=} optThreadException Optional thread to skip. */ - stopForTarget (target, optThreadException) { + stopForTarget(target, optThreadException) { // Emit stop event to allow blocks to clean up any state. this.emit(Runtime.STOP_FOR_TARGET, target, optThreadException); @@ -2026,35 +2218,38 @@ class Runtime extends EventEmitter { /** * Reset the Run ID. Call this any time the project logically starts, stops, or changes identity. */ - resetRunId () { + resetRunId() { if (!this.storage) { // see also: attachStorage return; } const newRunId = uuid.v1(); - this.storage.scratchFetch.setMetadata(this.storage.scratchFetch.RequestMetadata.RunId, newRunId); + this.storage.scratchFetch.setMetadata( + this.storage.scratchFetch.RequestMetadata.RunId, + newRunId + ); } /** * Start all threads that start with the green flag. */ - greenFlag () { + greenFlag() { this.stopAll(); this.emit(Runtime.PROJECT_START); this.ioDevices.clock.resetProjectTimer(); - this.targets.forEach(target => target.clearEdgeActivatedValues()); + this.targets.forEach((target) => target.clearEdgeActivatedValues()); // Inform all targets of the green flag. for (let i = 0; i < this.targets.length; i++) { this.targets[i].onGreenFlag(); } - this.startHats('event_whenflagclicked'); + this.startHats("event_whenflagclicked"); } /** * Stop "everything." */ - stopAll () { + stopAll() { // Emit stop event to allow blocks to clean up any state. this.emit(Runtime.PROJECT_STOP_ALL); @@ -2062,8 +2257,13 @@ class Runtime extends EventEmitter { const newTargets = []; for (let i = 0; i < this.targets.length; i++) { this.targets[i].onStopAll(); - if (Object.prototype.hasOwnProperty.call(this.targets[i], 'isOriginal') && - !this.targets[i].isOriginal) { + if ( + Object.prototype.hasOwnProperty.call( + this.targets[i], + "isOriginal" + ) && + !this.targets[i].isOriginal + ) { this.targets[i].dispose(); } else { newTargets.push(this.targets[i]); @@ -2084,20 +2284,21 @@ class Runtime extends EventEmitter { * Repeatedly run `sequencer.stepThreads` and filter out * inactive threads after each iteration. */ - _step () { + _step() { if (this.profiler !== null) { if (stepProfilerId === -1) { - stepProfilerId = this.profiler.idByName('Runtime._step'); + stepProfilerId = this.profiler.idByName("Runtime._step"); } this.profiler.start(stepProfilerId); } // Clean up threads that were told to stop during or since the last step - this.threads = this.threads.filter(thread => !thread.isKilled); + this.threads = this.threads.filter((thread) => !thread.isKilled); // Find all edge-activated hats, and add them to threads to be evaluated. for (const hatType in this._hats) { - if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) continue; + if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) + continue; const hat = this._hats[hatType]; if (hat.edgeActivated) { this.startHats(hatType); @@ -2107,7 +2308,9 @@ class Runtime extends EventEmitter { this._pushMonitors(); if (this.profiler !== null) { if (stepThreadsProfilerId === -1) { - stepThreadsProfilerId = this.profiler.idByName('Sequencer.stepThreads'); + stepThreadsProfilerId = this.profiler.idByName( + "Sequencer.stepThreads" + ); } this.profiler.start(stepThreadsProfilerId); } @@ -2119,8 +2322,10 @@ class Runtime extends EventEmitter { // Add done threads so that even if a thread finishes within 1 frame, the green // flag will still indicate that a script ran. this._emitProjectRunStatus( - this.threads.length + doneThreads.length - - this._getMonitorThreadCount([...this.threads, ...doneThreads])); + this.threads.length + + doneThreads.length - + this._getMonitorThreadCount([...this.threads, ...doneThreads]) + ); // Store threads that completed this iteration for testing and other // internal purposes. this._lastStepDoneThreads = doneThreads; @@ -2128,7 +2333,8 @@ class Runtime extends EventEmitter { // @todo: Only render when this.redrawRequested or clones rendered. if (this.profiler !== null) { if (rendererDrawProfilerId === -1) { - rendererDrawProfilerId = this.profiler.idByName('RenderWebGL.draw'); + rendererDrawProfilerId = + this.profiler.idByName("RenderWebGL.draw"); } this.profiler.start(rendererDrawProfilerId); } @@ -2139,7 +2345,10 @@ class Runtime extends EventEmitter { } if (this._refreshTargets) { - this.emit(Runtime.TARGETS_UPDATE, false /* Don't emit project changed */); + this.emit( + Runtime.TARGETS_UPDATE, + false /* Don't emit project changed */ + ); this._refreshTargets = false; } @@ -2160,9 +2369,9 @@ class Runtime extends EventEmitter { * @param {!Array.} threads The set of threads to look through. * @return {number} The number of monitor threads in threads. */ - _getMonitorThreadCount (threads) { + _getMonitorThreadCount(threads) { let count = 0; - threads.forEach(thread => { + threads.forEach((thread) => { if (thread.updateMonitor) count++; }); return count; @@ -2171,7 +2380,7 @@ class Runtime extends EventEmitter { /** * Queue monitor blocks to sequencer to be run. */ - _pushMonitors () { + _pushMonitors() { this.monitorBlocks.runAllMonitored(this); } @@ -2179,7 +2388,7 @@ class Runtime extends EventEmitter { * Set the current editing target known by the runtime. * @param {!Target} editingTarget New editing target. */ - setEditingTarget (editingTarget) { + setEditingTarget(editingTarget) { const oldEditingTarget = this._editingTarget; this._editingTarget = editingTarget; // Script glows must be cleared. @@ -2195,7 +2404,7 @@ class Runtime extends EventEmitter { * Set whether we are in 30 TPS compatibility mode. * @param {boolean} compatibilityModeOn True iff in compatibility mode. */ - setCompatibilityMode (compatibilityModeOn) { + setCompatibilityMode(compatibilityModeOn) { this.compatibilityMode = compatibilityModeOn; if (this._steppingInterval) { clearInterval(this._steppingInterval); @@ -2209,7 +2418,7 @@ class Runtime extends EventEmitter { * Looks at `this.threads` and notices which have turned on/off new glows. * @param {Array.=} optExtraThreads Optional list of inactive threads. */ - _updateGlows (optExtraThreads) { + _updateGlows(optExtraThreads) { const searchThreads = []; searchThreads.push(...this.threads); if (optExtraThreads) { @@ -2226,12 +2435,12 @@ class Runtime extends EventEmitter { if (target === this._editingTarget) { const blockForThread = thread.blockGlowInFrame; if (thread.requestScriptGlowInFrame || thread.stackClick) { - let script = target.blocks.getTopLevelScript(blockForThread); + let script = + target.blocks.getTopLevelScript(blockForThread); if (!script) { // Attempt to find in flyout blocks. - script = this.flyoutBlocks.getTopLevelScript( - blockForThread - ); + script = + this.flyoutBlocks.getTopLevelScript(blockForThread); } if (script) { requestedGlowsThisFrame.push(script); @@ -2267,7 +2476,7 @@ class Runtime extends EventEmitter { * * @param {number} nonMonitorThreadCount The new nonMonitorThreadCount */ - _emitProjectRunStatus (nonMonitorThreadCount) { + _emitProjectRunStatus(nonMonitorThreadCount) { if (this._nonMonitorThreadCount === 0 && nonMonitorThreadCount > 0) { this.emit(Runtime.PROJECT_RUN_START); } @@ -2283,7 +2492,7 @@ class Runtime extends EventEmitter { * still be tracking glow data about it. * @param {!string} scriptBlockId Id of top-level block in script to quiet. */ - quietGlow (scriptBlockId) { + quietGlow(scriptBlockId) { const index = this._scriptGlowsPreviousFrame.indexOf(scriptBlockId); if (index > -1) { this._scriptGlowsPreviousFrame.splice(index, 1); @@ -2295,11 +2504,11 @@ class Runtime extends EventEmitter { * @param {?string} blockId ID for the block to update glow * @param {boolean} isGlowing True to turn on glow; false to turn off. */ - glowBlock (blockId, isGlowing) { + glowBlock(blockId, isGlowing) { if (isGlowing) { - this.emit(Runtime.BLOCK_GLOW_ON, {id: blockId}); + this.emit(Runtime.BLOCK_GLOW_ON, { id: blockId }); } else { - this.emit(Runtime.BLOCK_GLOW_OFF, {id: blockId}); + this.emit(Runtime.BLOCK_GLOW_OFF, { id: blockId }); } } @@ -2308,11 +2517,11 @@ class Runtime extends EventEmitter { * @param {?string} topBlockId ID for the top block to update glow * @param {boolean} isGlowing True to turn on glow; false to turn off. */ - glowScript (topBlockId, isGlowing) { + glowScript(topBlockId, isGlowing) { if (isGlowing) { - this.emit(Runtime.SCRIPT_GLOW_ON, {id: topBlockId}); + this.emit(Runtime.SCRIPT_GLOW_ON, { id: topBlockId }); } else { - this.emit(Runtime.SCRIPT_GLOW_OFF, {id: topBlockId}); + this.emit(Runtime.SCRIPT_GLOW_OFF, { id: topBlockId }); } } @@ -2320,7 +2529,7 @@ class Runtime extends EventEmitter { * Emit whether blocks are being dragged over gui * @param {boolean} areBlocksOverGui True if blocks are dragged out of blocks workspace, false otherwise */ - emitBlockDragUpdate (areBlocksOverGui) { + emitBlockDragUpdate(areBlocksOverGui) { this.emit(Runtime.BLOCK_DRAG_UPDATE, areBlocksOverGui); } @@ -2329,7 +2538,7 @@ class Runtime extends EventEmitter { * @param {Array.} blocks The set of blocks dragged to the GUI * @param {string} topBlockId The original id of the top block being dragged */ - emitBlockEndDrag (blocks, topBlockId) { + emitBlockEndDrag(blocks, topBlockId) { this.emit(Runtime.BLOCK_DRAG_END, blocks, topBlockId); } @@ -2338,8 +2547,8 @@ class Runtime extends EventEmitter { * @param {string} blockId ID for the block. * @param {string} value Value to show associated with the block. */ - visualReport (blockId, value) { - this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value)}); + visualReport(blockId, value) { + this.emit(Runtime.VISUAL_REPORT, { id: blockId, value: String(value) }); } /** @@ -2347,9 +2556,10 @@ class Runtime extends EventEmitter { * updates those properties that are defined in the given monitor record. * @param {!MonitorRecord} monitor Monitor to add. */ - requestAddMonitor (monitor) { - const id = monitor.get('id'); - if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state + requestAddMonitor(monitor) { + const id = monitor.get("id"); + if (!this.requestUpdateMonitor(monitor)) { + // update monitor if it exists in the state // if the monitor did not exist in the state, add it this._monitorState = this._monitorState.set(id, monitor); } @@ -2362,17 +2572,20 @@ class Runtime extends EventEmitter { * the old monitor will keep its old value. * @return {boolean} true if monitor exists in the state and was updated, false if it did not exist. */ - requestUpdateMonitor (monitor) { - const id = monitor.get('id'); + requestUpdateMonitor(monitor) { + const id = monitor.get("id"); if (this._monitorState.has(id)) { this._monitorState = // Use mergeWith here to prevent undefined values from overwriting existing ones - this._monitorState.set(id, this._monitorState.get(id).mergeWith((prev, next) => { - if (typeof next === 'undefined' || next === null) { - return prev; - } - return next; - }, monitor)); + this._monitorState.set( + id, + this._monitorState.get(id).mergeWith((prev, next) => { + if (typeof next === "undefined" || next === null) { + return prev; + } + return next; + }, monitor) + ); return true; } return false; @@ -2383,7 +2596,7 @@ class Runtime extends EventEmitter { * not exist in the state. * @param {!string} monitorId ID of the monitor to remove. */ - requestRemoveMonitor (monitorId) { + requestRemoveMonitor(monitorId) { this._monitorState = this._monitorState.delete(monitorId); } @@ -2392,11 +2605,13 @@ class Runtime extends EventEmitter { * @param {!string} monitorId ID of the monitor to hide. * @return {boolean} true if monitor exists and was updated, false otherwise */ - requestHideMonitor (monitorId) { - return this.requestUpdateMonitor(new Map([ - ['id', monitorId], - ['visible', false] - ])); + requestHideMonitor(monitorId) { + return this.requestUpdateMonitor( + new Map([ + ["id", monitorId], + ["visible", false], + ]) + ); } /** @@ -2405,11 +2620,13 @@ class Runtime extends EventEmitter { * @param {!string} monitorId ID of the monitor to show. * @return {boolean} true if monitor exists and was updated, false otherwise */ - requestShowMonitor (monitorId) { - return this.requestUpdateMonitor(new Map([ - ['id', monitorId], - ['visible', true] - ])); + requestShowMonitor(monitorId) { + return this.requestUpdateMonitor( + new Map([ + ["id", monitorId], + ["visible", true], + ]) + ); } /** @@ -2417,8 +2634,10 @@ class Runtime extends EventEmitter { * the monitor already does not exist in the state. * @param {!string} targetId Remove all monitors with given target ID. */ - requestRemoveMonitorByTargetId (targetId) { - this._monitorState = this._monitorState.filterNot(value => value.targetId === targetId); + requestRemoveMonitorByTargetId(targetId) { + this._monitorState = this._monitorState.filterNot( + (value) => value.targetId === targetId + ); } /** @@ -2426,7 +2645,7 @@ class Runtime extends EventEmitter { * @param {string} targetId Id of target to find. * @return {?Target} The target, if found. */ - getTargetById (targetId) { + getTargetById(targetId) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.id === targetId) { @@ -2440,7 +2659,7 @@ class Runtime extends EventEmitter { * @param {string} spriteName Name of sprite to look for. * @return {?Target} Target representing a sprite of the given name. */ - getSpriteTargetByName (spriteName) { + getSpriteTargetByName(spriteName) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.isStage) { @@ -2457,7 +2676,7 @@ class Runtime extends EventEmitter { * @param {number} drawableID drawable id of target to find * @return {?Target} The target, if found */ - getTargetByDrawableId (drawableID) { + getTargetByDrawableId(drawableID) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.drawableID === drawableID) return target; @@ -2468,7 +2687,7 @@ class Runtime extends EventEmitter { * Update the clone counter to track how many clones are created. * @param {number} changeAmount How many clones have been created/destroyed. */ - changeCloneCounter (changeAmount) { + changeCloneCounter(changeAmount) { this._cloneCounter += changeAmount; } @@ -2476,14 +2695,14 @@ class Runtime extends EventEmitter { * Return whether there are clones available. * @return {boolean} True until the number of clones hits Runtime.MAX_CLONES. */ - clonesAvailable () { + clonesAvailable() { return this._cloneCounter < Runtime.MAX_CLONES; } /** * Handle that the project has loaded in the Virtual Machine. */ - handleProjectLoaded () { + handleProjectLoaded() { this.emit(Runtime.PROJECT_LOADED); this.resetRunId(); } @@ -2491,7 +2710,7 @@ class Runtime extends EventEmitter { /** * Report that the project has changed in a way that would affect serialization */ - emitProjectChanged () { + emitProjectChanged() { this.emit(Runtime.PROJECT_CHANGED); } @@ -2501,8 +2720,8 @@ class Runtime extends EventEmitter { * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any. * @fires Runtime#targetWasCreated */ - fireTargetWasCreated (newTarget, sourceTarget) { - this.emit('targetWasCreated', newTarget, sourceTarget); + fireTargetWasCreated(newTarget, sourceTarget) { + this.emit("targetWasCreated", newTarget, sourceTarget); } /** @@ -2510,15 +2729,15 @@ class Runtime extends EventEmitter { * @param {Target} target - the target being removed * @fires Runtime#targetWasRemoved */ - fireTargetWasRemoved (target) { - this.emit('targetWasRemoved', target); + fireTargetWasRemoved(target) { + this.emit("targetWasRemoved", target); } /** * Get a target representing the Scratch stage, if one exists. * @return {?Target} The target, if found. */ - getTargetForStage () { + getTargetForStage() { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.isStage) { @@ -2531,14 +2750,17 @@ class Runtime extends EventEmitter { * Get the editing target. * @return {?Target} The editing target. */ - getEditingTarget () { + getEditingTarget() { return this._editingTarget; } - getAllVarNamesOfType (varType) { + getAllVarNamesOfType(varType) { let varNames = []; for (const target of this.targets) { - const targetVarNames = target.getAllVariableNamesInScopeByType(varType, true); + const targetVarNames = target.getAllVariableNamesInScopeByType( + varType, + true + ); varNames = varNames.concat(targetVarNames); } return varNames; @@ -2552,20 +2774,20 @@ class Runtime extends EventEmitter { * @property {Function} [labelFn] - function to generate the label for this opcode * @property {string} [label] - the label for this opcode if `labelFn` is absent */ - getLabelForOpcode (extendedOpcode) { - const [category, opcode] = StringUtil.splitFirst(extendedOpcode, '_'); + getLabelForOpcode(extendedOpcode) { + const [category, opcode] = StringUtil.splitFirst(extendedOpcode, "_"); if (!(category && opcode)) return; - const categoryInfo = this._blockInfo.find(ci => ci.id === category); + const categoryInfo = this._blockInfo.find((ci) => ci.id === category); if (!categoryInfo) return; - const block = categoryInfo.blocks.find(b => b.info.opcode === opcode); + const block = categoryInfo.blocks.find((b) => b.info.opcode === opcode); if (!block) return; // TODO: we may want to format the label in a locale-specific way. return { - category: 'extension', // This assumes that all extensions have the same monitor color. - label: `${categoryInfo.name}: ${block.info.text}` + category: "extension", // This assumes that all extensions have the same monitor color. + label: `${categoryInfo.name}: ${block.info.text}`, }; } @@ -2578,8 +2800,9 @@ class Runtime extends EventEmitter { * @param {string} optVarType The type of the variable to create. Defaults to Variable.SCALAR_TYPE. * @return {Variable} The new variable that was created. */ - createNewGlobalVariable (variableName, optVarId, optVarType) { - const varType = (typeof optVarType === 'string') ? optVarType : Variable.SCALAR_TYPE; + createNewGlobalVariable(variableName, optVarId, optVarType) { + const varType = + typeof optVarType === "string" ? optVarType : Variable.SCALAR_TYPE; const allVariableNames = this.getAllVarNamesOfType(varType); const newName = StringUtil.unusedName(variableName, allVariableNames); const variable = new Variable(optVarId || uid(), newName, varType); @@ -2592,7 +2815,7 @@ class Runtime extends EventEmitter { * Tell the runtime to request a redraw. * Use after a clone/sprite has completed some visible operation on the stage. */ - requestRedraw () { + requestRedraw() { this.redrawRequested = true; } @@ -2601,7 +2824,7 @@ class Runtime extends EventEmitter { * the original sprite * @param {!Target} target Target requesting the targets update */ - requestTargetsUpdate (target) { + requestTargetsUpdate(target) { if (!target.isOriginal) return; this._refreshTargets = true; } @@ -2609,21 +2832,21 @@ class Runtime extends EventEmitter { /** * Emit an event that indicates that the blocks on the workspace need updating. */ - requestBlocksUpdate () { + requestBlocksUpdate() { this.emit(Runtime.BLOCKS_NEED_UPDATE); } /** * Emit an event that indicates that the toolbox extension blocks need updating. */ - requestToolboxExtensionsUpdate () { + requestToolboxExtensionsUpdate() { this.emit(Runtime.TOOLBOX_EXTENSIONS_NEED_UPDATE); } /** * Set up timers to repeatedly step in a browser. */ - start () { + start() { // Do not start if we are already running if (this._steppingInterval) return; @@ -2642,7 +2865,7 @@ class Runtime extends EventEmitter { * Quit the Runtime, clearing any handles which might keep the process alive. * Do not use the runtime after calling this method. This method is meant for test shutdown. */ - quit () { + quit() { clearInterval(this._steppingInterval); this._steppingInterval = null; } @@ -2652,7 +2875,7 @@ class Runtime extends EventEmitter { * @param {Profiler/FrameCallback} onFrame A callback handle passed a * profiling frame when the profiler reports its collected data. */ - enableProfiling (onFrame) { + enableProfiling(onFrame) { if (Profiler.available()) { this.profiler = new Profiler(onFrame); } @@ -2661,7 +2884,7 @@ class Runtime extends EventEmitter { /** * Turn off profiling. */ - disableProfiling () { + disableProfiling() { this.profiler = null; } @@ -2670,7 +2893,7 @@ class Runtime extends EventEmitter { * This value is helpful in certain instances for compatibility with Scratch 2, * which sometimes uses a `currentMSecs` timestamp value in Interpreter.as */ - updateCurrentMSecs () { + updateCurrentMSecs() { this.currentMSecs = Date.now(); } } From 42efd32514549dcb1ff3158edb182aebdd2271fb Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 3 Sep 2024 13:33:20 -0700 Subject: [PATCH 06/76] fix: add the monitor block extension to extension monitor blocks (#6) --- packages/scratch-vm/src/engine/runtime.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/scratch-vm/src/engine/runtime.js b/packages/scratch-vm/src/engine/runtime.js index 58d20485e6..2d3aefd499 100644 --- a/packages/scratch-vm/src/engine/runtime.js +++ b/packages/scratch-vm/src/engine/runtime.js @@ -1293,7 +1293,10 @@ class Runtime extends EventEmitter { if (blockInfo.blockType === BlockType.REPORTER) { if (!blockInfo.disableMonitor && context.inputList.length === 0) { - blockJSON.checkboxInFlyout = true; + if (!blockJSON.extensions) { + blockJSON.extensions = []; + } + blockJSON.extensions.push("monitor_block"); } } else if (blockInfo.blockType === BlockType.LOOP) { // Add icon to the bottom right of a loop block From 686996af76c712215fb6e23b960e2fc59d94d3c9 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 5 Sep 2024 15:53:02 -0700 Subject: [PATCH 07/76] refactor: use block styles instead of colors (#7) --- packages/scratch-vm/src/engine/runtime.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/scratch-vm/src/engine/runtime.js b/packages/scratch-vm/src/engine/runtime.js index 2d3aefd499..59a1405b16 100644 --- a/packages/scratch-vm/src/engine/runtime.js +++ b/packages/scratch-vm/src/engine/runtime.js @@ -1049,9 +1049,7 @@ class Runtime extends EventEmitter { type: menuId, inputsInline: true, output: "String", - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, outputShape: menuInfo.acceptReporters ? ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, @@ -1108,9 +1106,7 @@ class Runtime extends EventEmitter { message0: "%1", inputsInline: true, output: output, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, outputShape: outputShape, args0: [ { @@ -1155,9 +1151,8 @@ class Runtime extends EventEmitter { type: extendedOpcode, inputsInline: true, category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, + extensions: [], }; const context = { // TODO: store this somewhere so that we can map args appropriately after translation. @@ -1177,7 +1172,7 @@ class Runtime extends EventEmitter { const iconURI = blockInfo.blockIconURI || categoryInfo.blockIconURI; if (iconURI) { - blockJSON.extensions = ["scratch_extension"]; + blockJSON.extensions.push("scratch_extension"); blockJSON.message0 = "%1 %2"; const iconJSON = { type: "field_image", @@ -1224,9 +1219,6 @@ class Runtime extends EventEmitter { blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; blockJSON.nextStatement = null; // null = available connection; undefined = terminal - if (!blockJSON.extensions) { - blockJSON.extensions = []; - } blockJSON.extensions.push("shape_hat"); break; case BlockType.CONDITIONAL: @@ -1293,9 +1285,6 @@ class Runtime extends EventEmitter { if (blockInfo.blockType === BlockType.REPORTER) { if (!blockInfo.disableMonitor && context.inputList.length === 0) { - if (!blockJSON.extensions) { - blockJSON.extensions = []; - } blockJSON.extensions.push("monitor_block"); } } else if (blockInfo.blockType === BlockType.LOOP) { From ced972e9a9d4c9bdb063bc47edb8144fa4ae8731 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 16 Sep 2024 09:49:20 -0700 Subject: [PATCH 08/76] fix: update VM state when blocks are removed from an input previously occupied by a shadow block (#8) * chore: format engine/blocks.js * fix: restore shadow blocks when removing a block from an input --- packages/scratch-vm/src/engine/blocks.js | 1096 +++++++++++++--------- 1 file changed, 651 insertions(+), 445 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index 2796d9ed0d..fecd0cb92d 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -1,14 +1,14 @@ -const adapter = require('./adapter'); -const mutationAdapter = require('./mutation-adapter'); -const xmlEscape = require('../util/xml-escape'); -const MonitorRecord = require('./monitor-record'); -const Clone = require('../util/clone'); -const {Map} = require('immutable'); -const BlocksExecuteCache = require('./blocks-execute-cache'); -const BlocksRuntimeCache = require('./blocks-runtime-cache'); -const log = require('../util/log'); -const Variable = require('./variable'); -const getMonitorIdForBlockWithArgs = require('../util/get-monitor-id'); +const adapter = require("./adapter"); +const mutationAdapter = require("./mutation-adapter"); +const xmlEscape = require("../util/xml-escape"); +const MonitorRecord = require("./monitor-record"); +const Clone = require("../util/clone"); +const { Map } = require("immutable"); +const BlocksExecuteCache = require("./blocks-execute-cache"); +const BlocksRuntimeCache = require("./blocks-runtime-cache"); +const log = require("../util/log"); +const Variable = require("./variable"); +const getMonitorIdForBlockWithArgs = require("../util/get-monitor-id"); /** * @fileoverview @@ -23,7 +23,7 @@ const getMonitorIdForBlockWithArgs = require('../util/get-monitor-id'); * should not request glows. This does not affect glows when clicking on a block to execute it. */ class Blocks { - constructor (runtime, optNoGlow) { + constructor(runtime, optNoGlow) { this.runtime = runtime; /** @@ -45,7 +45,10 @@ class Blocks { * @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}} * @private */ - Object.defineProperty(this, '_cache', {writable: true, enumerable: false}); + Object.defineProperty(this, "_cache", { + writable: true, + enumerable: false, + }); this._cache = { /** * Cache block inputs by block id @@ -81,7 +84,7 @@ class Blocks { * A cache of hat opcodes to collection of theads to execute. * @type {object.} */ - scripts: {} + scripts: {}, }; /** @@ -101,8 +104,8 @@ class Blocks { * are prefixed with this string. * @const{string} */ - static get BRANCH_INPUT_PREFIX () { - return 'SUBSTACK'; + static get BRANCH_INPUT_PREFIX() { + return "SUBSTACK"; } /** @@ -110,7 +113,7 @@ class Blocks { * @param {!string} blockId ID of block we have stored. * @return {?object} Metadata about the block, if it exists. */ - getBlock (blockId) { + getBlock(blockId) { return this._blocks[blockId]; } @@ -118,18 +121,18 @@ class Blocks { * Get all known top-level blocks that start scripts. * @return {Array.} List of block IDs. */ - getScripts () { + getScripts() { return this._scripts; } /** - * Get the next block for a particular block - * @param {?string} id ID of block to get the next block for - * @return {?string} ID of next block in the sequence - */ - getNextBlock (id) { + * Get the next block for a particular block + * @param {?string} id ID of block to get the next block for + * @return {?string} ID of next block in the sequence + */ + getNextBlock(id) { const block = this._blocks[id]; - return (typeof block === 'undefined') ? null : block.next; + return typeof block === "undefined" ? null : block.next; } /** @@ -138,9 +141,9 @@ class Blocks { * @param {?number} branchNum Which branch to select (e.g. for if-else). * @return {?string} ID of block in the branch. */ - getBranch (id, branchNum) { + getBranch(id, branchNum) { const block = this._blocks[id]; - if (typeof block === 'undefined') return null; + if (typeof block === "undefined") return null; if (!branchNum) branchNum = 1; let inputName = Blocks.BRANCH_INPUT_PREFIX; @@ -150,7 +153,7 @@ class Blocks { // Empty C-block? const input = block.inputs[inputName]; - return (typeof input === 'undefined') ? null : input.block; + return typeof input === "undefined" ? null : input.block; } /** @@ -158,8 +161,8 @@ class Blocks { * @param {?object} block The block to query * @return {?string} the opcode corresponding to that block */ - getOpcode (block) { - return (typeof block === 'undefined') ? null : block.opcode; + getOpcode(block) { + return typeof block === "undefined" ? null : block.opcode; } /** @@ -167,8 +170,8 @@ class Blocks { * @param {?object} block The block to query. * @return {?object} All fields and their values. */ - getFields (block) { - return (typeof block === 'undefined') ? null : block.fields; + getFields(block) { + return typeof block === "undefined" ? null : block.fields; } /** @@ -176,18 +179,20 @@ class Blocks { * @param {?object} block the block to query. * @return {?Array.} All non-branch inputs and their associated blocks. */ - getInputs (block) { - if (typeof block === 'undefined') return null; + getInputs(block) { + if (typeof block === "undefined") return null; let inputs = this._cache.inputs[block.id]; - if (typeof inputs !== 'undefined') { + if (typeof inputs !== "undefined") { return inputs; } inputs = {}; for (const input in block.inputs) { // Ignore blocks prefixed with branch prefix. - if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !== - Blocks.BRANCH_INPUT_PREFIX) { + if ( + input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !== + Blocks.BRANCH_INPUT_PREFIX + ) { inputs[input] = block.inputs[input]; } } @@ -201,8 +206,8 @@ class Blocks { * @param {?object} block The block to query. * @return {?object} Mutation for the block. */ - getMutation (block) { - return (typeof block === 'undefined') ? null : block.mutation; + getMutation(block) { + return typeof block === "undefined" ? null : block.mutation; } /** @@ -210,9 +215,9 @@ class Blocks { * @param {?string} id ID of block to query. * @return {?string} ID of top-level script block. */ - getTopLevelScript (id) { + getTopLevelScript(id) { let block = this._blocks[id]; - if (typeof block === 'undefined') return null; + if (typeof block === "undefined") return null; while (block.parent !== null) { block = this._blocks[block.parent]; } @@ -224,16 +229,17 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?string} ID of procedure definition. */ - getProcedureDefinition (name) { + getProcedureDefinition(name) { const blockID = this._cache.procedureDefinitions[name]; - if (typeof blockID !== 'undefined') { + if (typeof blockID !== "undefined") { return blockID; } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue; + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) + continue; const block = this._blocks[id]; - if (block.opcode === 'procedures_definition') { + if (block.opcode === "procedures_definition") { const internal = this._getCustomBlockInternal(block); if (internal && internal.mutation.proccode === name) { this._cache.procedureDefinitions[name] = id; // The outer define block id @@ -251,7 +257,7 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?Array.} List of param names for a procedure. */ - getProcedureParamNamesAndIds (name) { + getProcedureParamNamesAndIds(name) { return this.getProcedureParamNamesIdsAndDefaults(name).slice(0, 2); } @@ -260,17 +266,20 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?Array.} List of param names for a procedure. */ - getProcedureParamNamesIdsAndDefaults (name) { + getProcedureParamNamesIdsAndDefaults(name) { const cachedNames = this._cache.procedureParamNames[name]; - if (typeof cachedNames !== 'undefined') { + if (typeof cachedNames !== "undefined") { return cachedNames; } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) continue; + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) + continue; const block = this._blocks[id]; - if (block.opcode === 'procedures_prototype' && - block.mutation.proccode === name) { + if ( + block.opcode === "procedures_prototype" && + block.mutation.proccode === name + ) { const names = JSON.parse(block.mutation.argumentnames); const ids = JSON.parse(block.mutation.argumentids); const defaults = JSON.parse(block.mutation.argumentdefaults); @@ -284,7 +293,7 @@ class Blocks { return null; } - duplicate () { + duplicate() { const newBlocks = new Blocks(this.runtime, this.forceNoGlow); newBlocks._blocks = Clone.simple(this._blocks); newBlocks._scripts = Clone.simple(this._scripts); @@ -298,11 +307,14 @@ class Blocks { * runtime interface. * @param {object} e Blockly "block" or "variable" event */ - blocklyListen (e) { + blocklyListen(e) { // Validate event - if (typeof e !== 'object') return; - if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' && - typeof e.commentId !== 'string') { + if (typeof e !== "object") return; + if ( + typeof e.blockId !== "string" && + typeof e.varId !== "string" && + typeof e.commentId !== "string" + ) { return; } const stage = this.runtime.getTargetForStage(); @@ -310,219 +322,315 @@ class Blocks { // Block create/update/destroy switch (e.type) { - case 'create': { - const newBlocks = adapter(e); - // A create event can create many blocks. Add them all. - for (let i = 0; i < newBlocks.length; i++) { - this.createBlock(newBlocks[i]); - } - break; - } - case 'change': - this.changeBlock({ - id: e.blockId, - element: e.element, - name: e.name, - value: e.newValue - }); - break; - case 'move': - this.moveBlock({ - id: e.blockId, - oldParent: e.oldParentId, - oldInput: e.oldInputName, - newParent: e.newParentId, - newInput: e.newInputName, - newCoordinate: e.newCoordinate - }); - break; - case 'dragOutside': - this.runtime.emitBlockDragUpdate(e.isOutside); - break; - case 'endDrag': - this.runtime.emitBlockDragUpdate(false /* areBlocksOverGui */); - - // Drag blocks onto another sprite - if (e.isOutside) { + case "create": { const newBlocks = adapter(e); - this.runtime.emitBlockEndDrag(newBlocks, e.blockId); - } - break; - case 'delete': - // Don't accept delete events for missing blocks, - // or shadow blocks being obscured. - if (!Object.prototype.hasOwnProperty.call(this._blocks, e.blockId) || - this._blocks[e.blockId].shadow) { - return; - } - // Inform any runtime to forget about glows on this script. - if (this._blocks[e.blockId].topLevel) { - this.runtime.quietGlow(e.blockId); + // A create event can create many blocks. Add them all. + for (let i = 0; i < newBlocks.length; i++) { + this.createBlock(newBlocks[i]); + } + break; } - this.deleteBlock(e.blockId); - break; - case 'var_create': - // Check if the variable being created is global or local - // If local, create a local var on the current editing target, as long - // as there are no conflicts, and the current target is actually a sprite - // If global or if the editing target is not present or we somehow got - // into a state where a local var was requested for the stage, - // create a stage (global) var after checking for name conflicts - // on all the sprites. - if (e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud) { - if (!editingTarget.lookupVariableById(e.varId)) { - editingTarget.createVariable(e.varId, e.varName, e.varType); - this.emitProjectChanged(); + case "change": + this.changeBlock({ + id: e.blockId, + element: e.element, + name: e.name, + value: e.newValue, + }); + break; + case "move": + this.moveBlock({ + id: e.blockId, + oldParent: e.oldParentId, + oldInput: e.oldInputName, + newParent: e.newParentId, + newInput: e.newInputName, + newCoordinate: e.newCoordinate, + }); + break; + case "dragOutside": + this.runtime.emitBlockDragUpdate(e.isOutside); + break; + case "endDrag": + this.runtime.emitBlockDragUpdate(false /* areBlocksOverGui */); + + // Drag blocks onto another sprite + if (e.isOutside) { + const newBlocks = adapter(e); + this.runtime.emitBlockEndDrag(newBlocks, e.blockId); } - } else { - if (stage.lookupVariableById(e.varId)) { - // Do not re-create a variable if it already exists + break; + case "delete": + // Don't accept delete events for missing blocks, + // or shadow blocks being obscured. + if ( + !Object.prototype.hasOwnProperty.call( + this._blocks, + e.blockId + ) || + this._blocks[e.blockId].shadow + ) { return; } - // Check for name conflicts in all of the targets - const allTargets = this.runtime.targets.filter(t => t.isOriginal); - for (const target of allTargets) { - if (target.lookupVariableByNameAndType(e.varName, e.varType, true)) { + // Inform any runtime to forget about glows on this script. + if (this._blocks[e.blockId].topLevel) { + this.runtime.quietGlow(e.blockId); + } + this.deleteBlock(e.blockId); + break; + case "var_create": + // Check if the variable being created is global or local + // If local, create a local var on the current editing target, as long + // as there are no conflicts, and the current target is actually a sprite + // If global or if the editing target is not present or we somehow got + // into a state where a local var was requested for the stage, + // create a stage (global) var after checking for name conflicts + // on all the sprites. + if ( + e.isLocal && + editingTarget && + !editingTarget.isStage && + !e.isCloud + ) { + if (!editingTarget.lookupVariableById(e.varId)) { + editingTarget.createVariable( + e.varId, + e.varName, + e.varType + ); + this.emitProjectChanged(); + } + } else { + if (stage.lookupVariableById(e.varId)) { + // Do not re-create a variable if it already exists return; } + // Check for name conflicts in all of the targets + const allTargets = this.runtime.targets.filter( + (t) => t.isOriginal + ); + for (const target of allTargets) { + if ( + target.lookupVariableByNameAndType( + e.varName, + e.varType, + true + ) + ) { + return; + } + } + stage.createVariable( + e.varId, + e.varName, + e.varType, + e.isCloud + ); + this.emitProjectChanged(); } - stage.createVariable(e.varId, e.varName, e.varType, e.isCloud); - this.emitProjectChanged(); - } - break; - case 'var_rename': - if (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) { - // This is a local variable, rename on the current target - editingTarget.renameVariable(e.varId, e.newName); - // Update all the blocks on the current target that use - // this variable - editingTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName); - } else { - // This is a global variable - stage.renameVariable(e.varId, e.newName); - // Update all blocks on all targets that use the renamed variable - const targets = this.runtime.targets; - for (let i = 0; i < targets.length; i++) { - const currTarget = targets[i]; - currTarget.blocks.updateBlocksAfterVarRename(e.varId, e.newName); - } - } - this.emitProjectChanged(); - break; - case 'var_delete': { - const target = (editingTarget && Object.prototype.hasOwnProperty.call(editingTarget.variables, e.varId)) ? - editingTarget : stage; - target.deleteVariable(e.varId); - this.emitProjectChanged(); - break; - } - case 'block_comment_create': - case 'comment_create': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - currTarget.createComment(e.commentId, e.blockId, '', - e.json.x, e.json.y, e.json.width, e.json.height, false); - - if (currTarget.comments[e.commentId].x === null && - currTarget.comments[e.commentId].y === null) { - // Block comments imported from 2.0 projects are imported with their - // x and y coordinates set to null so that scratch-blocks can - // auto-position them. If we are receiving a create event for these - // comments, then the auto positioning should have taken place. - // Update the x and y position of these comments to match the - // one from the event. - currTarget.comments[e.commentId].x = e.json.x; - currTarget.comments[e.commentId].y = e.json.y; - } - } - this.emitProjectChanged(); - break; - case 'block_comment_change': - case 'comment_change': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot change comment with id ${e.commentId} because it does not exist.`); - return; + break; + case "var_rename": + if ( + editingTarget && + Object.prototype.hasOwnProperty.call( + editingTarget.variables, + e.varId + ) + ) { + // This is a local variable, rename on the current target + editingTarget.renameVariable(e.varId, e.newName); + // Update all the blocks on the current target that use + // this variable + editingTarget.blocks.updateBlocksAfterVarRename( + e.varId, + e.newName + ); + } else { + // This is a global variable + stage.renameVariable(e.varId, e.newName); + // Update all blocks on all targets that use the renamed variable + const targets = this.runtime.targets; + for (let i = 0; i < targets.length; i++) { + const currTarget = targets[i]; + currTarget.blocks.updateBlocksAfterVarRename( + e.varId, + e.newName + ); + } } - const comment = currTarget.comments[e.commentId]; - comment.text = e.newContents_; this.emitProjectChanged(); - } - break; - case 'block_comment_move': - case 'comment_move': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot move comment with id ${e.commentId} because it does not exist.`); - return; - } - const comment = currTarget.comments[e.commentId]; - const newCoord = e.newCoordinate_; - comment.x = newCoord.x; - comment.y = newCoord.y; - + break; + case "var_delete": { + const target = + editingTarget && + Object.prototype.hasOwnProperty.call( + editingTarget.variables, + e.varId + ) + ? editingTarget + : stage; + target.deleteVariable(e.varId); this.emitProjectChanged(); + break; } - break; - case 'block_comment_collapse': - case 'comment_collapse': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot collapse comment with id ${e.commentId} because it does not exist.`); - return; + case "block_comment_create": + case "comment_create": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + currTarget.createComment( + e.commentId, + e.blockId, + "", + e.json.x, + e.json.y, + e.json.width, + e.json.height, + false + ); + + if ( + currTarget.comments[e.commentId].x === null && + currTarget.comments[e.commentId].y === null + ) { + // Block comments imported from 2.0 projects are imported with their + // x and y coordinates set to null so that scratch-blocks can + // auto-position them. If we are receiving a create event for these + // comments, then the auto positioning should have taken place. + // Update the x and y position of these comments to match the + // one from the event. + currTarget.comments[e.commentId].x = e.json.x; + currTarget.comments[e.commentId].y = e.json.y; + } } - const comment = currTarget.comments[e.commentId]; - comment.minimized = e.newCollapsed; this.emitProjectChanged(); - } - break; - case 'block_comment_resize': - case 'comment_resize': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if (currTarget && !Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - log.warn(`Cannot resize comment with id ${e.commentId} because it does not exist.`); - return; + break; + case "block_comment_change": + case "comment_change": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot change comment with id ${e.commentId} because it does not exist.` + ); + return; + } + const comment = currTarget.comments[e.commentId]; + comment.text = e.newContents_; + this.emitProjectChanged(); } - const comment = currTarget.comments[e.commentId]; - comment.width = e.newSize.width; - comment.height = e.newSize.height; - this.emitProjectChanged(); - } - break; - case 'block_comment_delete': - case 'comment_delete': - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if (!Object.prototype.hasOwnProperty.call(currTarget.comments, e.commentId)) { - // If we're in this state, we have probably received - // a delete event from a workspace that we switched from - // (e.g. a delete event for a comment on sprite a's workspace - // when switching from sprite a to sprite b) - return; + break; + case "block_comment_move": + case "comment_move": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot move comment with id ${e.commentId} because it does not exist.` + ); + return; + } + const comment = currTarget.comments[e.commentId]; + const newCoord = e.newCoordinate_; + comment.x = newCoord.x; + comment.y = newCoord.y; + + this.emitProjectChanged(); } - delete currTarget.comments[e.commentId]; - if (e.blockId) { - const block = currTarget.blocks.getBlock(e.blockId); - if (!block) { - log.warn(`Could not find block referenced by comment with id: ${e.commentId}`); + break; + case "block_comment_collapse": + case "comment_collapse": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot collapse comment with id ${e.commentId} because it does not exist.` + ); return; } - delete block.comment; + const comment = currTarget.comments[e.commentId]; + comment.minimized = e.newCollapsed; + this.emitProjectChanged(); } + break; + case "block_comment_resize": + case "comment_resize": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot resize comment with id ${e.commentId} because it does not exist.` + ); + return; + } + const comment = currTarget.comments[e.commentId]; + comment.width = e.newSize.width; + comment.height = e.newSize.height; + this.emitProjectChanged(); + } + break; + case "block_comment_delete": + case "comment_delete": + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + // If we're in this state, we have probably received + // a delete event from a workspace that we switched from + // (e.g. a delete event for a comment on sprite a's workspace + // when switching from sprite a to sprite b) + return; + } + delete currTarget.comments[e.commentId]; + if (e.blockId) { + const block = currTarget.blocks.getBlock(e.blockId); + if (!block) { + log.warn( + `Could not find block referenced by comment with id: ${e.commentId}` + ); + return; + } + delete block.comment; + } - this.emitProjectChanged(); - } - break; - case 'click': - // UI event: clicked scripts toggle in the runtime. - if (e.targetType === 'block') { - this.runtime.toggleScript(this.getTopLevelScript(e.blockId), {stackClick: true}); - } - break; + this.emitProjectChanged(); + } + break; + case "click": + // UI event: clicked scripts toggle in the runtime. + if (e.targetType === "block") { + this.runtime.toggleScript( + this.getTopLevelScript(e.blockId), + { stackClick: true } + ); + } + break; } } @@ -531,7 +639,7 @@ class Blocks { /** * Reset all runtime caches. */ - resetCache () { + resetCache() { this._cache.inputs = {}; this._cache.procedureParamNames = {}; this._cache.procedureDefinitions = {}; @@ -544,7 +652,7 @@ class Blocks { * Emit a project changed event if this is a block container * that can affect the project state. */ - emitProjectChanged () { + emitProjectChanged() { if (!this.forceNoGlow) { this.runtime.emitProjectChanged(); } @@ -554,7 +662,7 @@ class Blocks { * Block management: create blocks and scripts from a `create` event * @param {!object} block Blockly create event to be processed */ - createBlock (block) { + createBlock(block) { // Does the block already exist? // Could happen, e.g., for an unobscured shadow. if (Object.prototype.hasOwnProperty.call(this._blocks, block.id)) { @@ -580,127 +688,166 @@ class Blocks { * Block management: change block field values * @param {!object} args Blockly change event to be processed */ - changeBlock (args) { + changeBlock(args) { // Validate - if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return; + if (["field", "mutation", "checkbox"].indexOf(args.element) === -1) + return; let block = this._blocks[args.id]; - if (typeof block === 'undefined') return; + if (typeof block === "undefined") return; switch (args.element) { - case 'field': - // TODO when the field of a monitored block changes, - // update the checkbox in the flyout based on whether - // a monitor for that current combination of selected parameters exists - // e.g. - // 1. check (current [v year]) - // 2. switch dropdown in flyout block to (current [v minute]) - // 3. the checkbox should become unchecked if we're not already - // monitoring current minute - - - // Update block value - if (!block.fields[args.name]) return; - if (args.name === 'VARIABLE' || args.name === 'LIST' || - args.name === 'BROADCAST_OPTION') { - // Get variable name using the id in args.value. - const variable = this.runtime.getEditingTarget().lookupVariableById(args.value); - if (variable) { - block.fields[args.name].value = variable.name; - block.fields[args.name].id = args.value; - } - } else { - // Changing the value in a dropdown - block.fields[args.name].value = args.value; - - // The selected item in the sensing of block menu needs to change based on the - // selected target. Set it to the first item in the menu list. - // TODO: (#1787) - if (block.opcode === 'sensing_of_object_menu') { - if (block.fields.OBJECT.value === '_stage_') { - this._blocks[block.parent].fields.PROPERTY.value = 'backdrop #'; - } else { - this._blocks[block.parent].fields.PROPERTY.value = 'x position'; + case "field": + // TODO when the field of a monitored block changes, + // update the checkbox in the flyout based on whether + // a monitor for that current combination of selected parameters exists + // e.g. + // 1. check (current [v year]) + // 2. switch dropdown in flyout block to (current [v minute]) + // 3. the checkbox should become unchecked if we're not already + // monitoring current minute + + // Update block value + if (!block.fields[args.name]) return; + if ( + args.name === "VARIABLE" || + args.name === "LIST" || + args.name === "BROADCAST_OPTION" + ) { + // Get variable name using the id in args.value. + const variable = this.runtime + .getEditingTarget() + .lookupVariableById(args.value); + if (variable) { + block.fields[args.name].value = variable.name; + block.fields[args.name].id = args.value; + } + } else { + // Changing the value in a dropdown + block.fields[args.name].value = args.value; + + // The selected item in the sensing of block menu needs to change based on the + // selected target. Set it to the first item in the menu list. + // TODO: (#1787) + if (block.opcode === "sensing_of_object_menu") { + if (block.fields.OBJECT.value === "_stage_") { + this._blocks[block.parent].fields.PROPERTY.value = + "backdrop #"; + } else { + this._blocks[block.parent].fields.PROPERTY.value = + "x position"; + } + this.runtime.requestBlocksUpdate(); } - this.runtime.requestBlocksUpdate(); - } - const flyoutBlock = block.shadow && block.parent ? this._blocks[block.parent] : block; - if (flyoutBlock.isMonitored) { - this.runtime.requestUpdateMonitor(Map({ - id: flyoutBlock.id, - params: this._getBlockParams(flyoutBlock) - })); - } - } - break; - case 'mutation': - block.mutation = mutationAdapter(args.value); - break; - case 'checkbox': { - // A checkbox usually has a one to one correspondence with the monitor - // block but in the case of monitored reporters that have arguments, - // map the old id to a new id, creating a new monitor block if necessary - if (block.fields && Object.keys(block.fields).length > 0 && - block.opcode !== 'data_variable' && block.opcode !== 'data_listcontents') { - - // This block has an argument which needs to get separated out into - // multiple monitor blocks with ids based on the selected argument - const newId = getMonitorIdForBlockWithArgs(block.id, block.fields); - // Note: we're not just constantly creating a longer and longer id everytime we check - // the checkbox because we're using the id of the block in the flyout as the base - - // check if a block with the new id already exists, otherwise create - let newBlock = this.runtime.monitorBlocks.getBlock(newId); - if (!newBlock) { - newBlock = JSON.parse(JSON.stringify(block)); - newBlock.id = newId; - this.runtime.monitorBlocks.createBlock(newBlock); + const flyoutBlock = + block.shadow && block.parent + ? this._blocks[block.parent] + : block; + if (flyoutBlock.isMonitored) { + this.runtime.requestUpdateMonitor( + Map({ + id: flyoutBlock.id, + params: this._getBlockParams(flyoutBlock), + }) + ); + } } + break; + case "mutation": + block.mutation = mutationAdapter(args.value); + break; + case "checkbox": { + // A checkbox usually has a one to one correspondence with the monitor + // block but in the case of monitored reporters that have arguments, + // map the old id to a new id, creating a new monitor block if necessary + if ( + block.fields && + Object.keys(block.fields).length > 0 && + block.opcode !== "data_variable" && + block.opcode !== "data_listcontents" + ) { + // This block has an argument which needs to get separated out into + // multiple monitor blocks with ids based on the selected argument + const newId = getMonitorIdForBlockWithArgs( + block.id, + block.fields + ); + // Note: we're not just constantly creating a longer and longer id everytime we check + // the checkbox because we're using the id of the block in the flyout as the base + + // check if a block with the new id already exists, otherwise create + let newBlock = this.runtime.monitorBlocks.getBlock(newId); + if (!newBlock) { + newBlock = JSON.parse(JSON.stringify(block)); + newBlock.id = newId; + this.runtime.monitorBlocks.createBlock(newBlock); + } - block = newBlock; // Carry on through the rest of this code with newBlock - } - - const wasMonitored = block.isMonitored; - block.isMonitored = args.value; + block = newBlock; // Carry on through the rest of this code with newBlock + } - // Variable blocks may be sprite specific depending on the owner of the variable - let isSpriteLocalVariable = false; - if (block.opcode === 'data_variable') { - isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.VARIABLE.id]); - } else if (block.opcode === 'data_listcontents') { - isSpriteLocalVariable = !(this.runtime.getTargetForStage().variables[block.fields.LIST.id]); - } + const wasMonitored = block.isMonitored; + block.isMonitored = args.value; + + // Variable blocks may be sprite specific depending on the owner of the variable + let isSpriteLocalVariable = false; + if (block.opcode === "data_variable") { + isSpriteLocalVariable = + !this.runtime.getTargetForStage().variables[ + block.fields.VARIABLE.id + ]; + } else if (block.opcode === "data_listcontents") { + isSpriteLocalVariable = + !this.runtime.getTargetForStage().variables[ + block.fields.LIST.id + ]; + } - const isSpriteSpecific = isSpriteLocalVariable || - (Object.prototype.hasOwnProperty.call(this.runtime.monitorBlockInfo, block.opcode) && - this.runtime.monitorBlockInfo[block.opcode].isSpriteSpecific); - if (isSpriteSpecific) { - // If creating a new sprite specific monitor, the only possible target is - // the current editing one b/c you cannot dynamically create monitors. - // Also, do not change the targetId if it has already been assigned - block.targetId = block.targetId || this.runtime.getEditingTarget().id; - } else { - block.targetId = null; - } + const isSpriteSpecific = + isSpriteLocalVariable || + (Object.prototype.hasOwnProperty.call( + this.runtime.monitorBlockInfo, + block.opcode + ) && + this.runtime.monitorBlockInfo[block.opcode] + .isSpriteSpecific); + if (isSpriteSpecific) { + // If creating a new sprite specific monitor, the only possible target is + // the current editing one b/c you cannot dynamically create monitors. + // Also, do not change the targetId if it has already been assigned + block.targetId = + block.targetId || this.runtime.getEditingTarget().id; + } else { + block.targetId = null; + } - if (wasMonitored && !block.isMonitored) { - this.runtime.requestHideMonitor(block.id); - } else if (!wasMonitored && block.isMonitored) { - // Tries to show the monitor for specified block. If it doesn't exist, add the monitor. - if (!this.runtime.requestShowMonitor(block.id)) { - this.runtime.requestAddMonitor(MonitorRecord({ - id: block.id, - targetId: block.targetId, - spriteName: block.targetId ? this.runtime.getTargetById(block.targetId).getName() : null, - opcode: block.opcode, - params: this._getBlockParams(block), - // @todo(vm#565) for numerical values with decimals, some countries use comma - value: '', - mode: block.opcode === 'data_listcontents' ? 'list' : 'default' - })); + if (wasMonitored && !block.isMonitored) { + this.runtime.requestHideMonitor(block.id); + } else if (!wasMonitored && block.isMonitored) { + // Tries to show the monitor for specified block. If it doesn't exist, add the monitor. + if (!this.runtime.requestShowMonitor(block.id)) { + this.runtime.requestAddMonitor( + MonitorRecord({ + id: block.id, + targetId: block.targetId, + spriteName: block.targetId + ? this.runtime + .getTargetById(block.targetId) + .getName() + : null, + opcode: block.opcode, + params: this._getBlockParams(block), + // @todo(vm#565) for numerical values with decimals, some countries use comma + value: "", + mode: + block.opcode === "data_listcontents" + ? "list" + : "default", + }) + ); + } } + break; } - break; - } } this.emitProjectChanged(); @@ -712,7 +859,7 @@ class Blocks { * Block management: move blocks from parent to parent * @param {!object} e Blockly move event to be processed */ - moveBlock (e) { + moveBlock(e) { if (!Object.prototype.hasOwnProperty.call(this._blocks, e.id)) { return; } @@ -725,20 +872,26 @@ class Blocks { // Move coordinate changes. if (e.newCoordinate) { - - didChange = (block.x !== e.newCoordinate.x) || (block.y !== e.newCoordinate.y); + didChange = + block.x !== e.newCoordinate.x || block.y !== e.newCoordinate.y; block.x = e.newCoordinate.x; block.y = e.newCoordinate.y; } // Remove from any old parent. - if (typeof e.oldParent !== 'undefined') { + if (typeof e.oldParent !== "undefined") { const oldParent = this._blocks[e.oldParent]; - if (typeof e.oldInput !== 'undefined' && - oldParent.inputs[e.oldInput].block === e.id) { - // This block was connected to the old parent's input. - oldParent.inputs[e.oldInput].block = null; + if ( + typeof e.oldInput !== "undefined" && + oldParent.inputs[e.oldInput].block === e.id + ) { + // This block was connected to the old parent's input. We either + // want to restore the shadow block that previously occupied + // this input, or set it to null (which `.shadow` will be if + // there was no shadow previously) + oldParent.inputs[e.oldInput].block = + oldParent.inputs[e.oldInput].shadow; } else if (oldParent.next === e.id) { // This block was connected to the old parent's next connection. oldParent.next = null; @@ -748,21 +901,27 @@ class Blocks { } // Is this block a top-level block? - if (typeof e.newParent === 'undefined') { + if (typeof e.newParent === "undefined") { this._addScript(e.id); } else { // Remove script, if one exists. this._deleteScript(e.id); // Otherwise, try to connect it in its new place. - if (typeof e.newInput === 'undefined') { + if (typeof e.newInput === "undefined") { // Moved to the new parent's next connection. this._blocks[e.newParent].next = e.id; } else { // Moved to the new parent's input. // Don't obscure the shadow block. let oldShadow = null; - if (Object.prototype.hasOwnProperty.call(this._blocks[e.newParent].inputs, e.newInput)) { - oldShadow = this._blocks[e.newParent].inputs[e.newInput].shadow; + if ( + Object.prototype.hasOwnProperty.call( + this._blocks[e.newParent].inputs, + e.newInput + ) + ) { + oldShadow = + this._blocks[e.newParent].inputs[e.newInput].shadow; } // If the block being attached is itself a shadow, make sure to set @@ -773,7 +932,7 @@ class Blocks { this._blocks[e.newParent].inputs[e.newInput] = { name: e.newInput, block: e.id, - shadow: oldShadow + shadow: oldShadow, }; } this._blocks[e.id].parent = e.newParent; @@ -784,27 +943,28 @@ class Blocks { if (didChange) this.emitProjectChanged(); } - /** * Block management: run all blocks. * @param {!object} runtime Runtime to run all blocks in. */ - runAllMonitored (runtime) { + runAllMonitored(runtime) { if (this._cache._monitored === null) { this._cache._monitored = Object.keys(this._blocks) - .filter(blockId => this.getBlock(blockId).isMonitored) - .map(blockId => { + .filter((blockId) => this.getBlock(blockId).isMonitored) + .map((blockId) => { const targetId = this.getBlock(blockId).targetId; return { blockId, - target: targetId ? runtime.getTargetById(targetId) : null + target: targetId + ? runtime.getTargetById(targetId) + : null, }; }); } const monitored = this._cache._monitored; for (let i = 0; i < monitored.length; i++) { - const {blockId, target} = monitored[i]; + const { blockId, target } = monitored[i]; runtime.addMonitorScript(blockId, target); } } @@ -814,7 +974,7 @@ class Blocks { * with the given ID does not exist. * @param {!string} blockId Id of block to delete */ - deleteBlock (blockId) { + deleteBlock(blockId) { // @todo In runtime, stop threads running on this script. // Get block @@ -836,8 +996,10 @@ class Blocks { this.deleteBlock(block.inputs[input].block); } // Delete obscured shadow blocks. - if (block.inputs[input].shadow !== null && - block.inputs[input].shadow !== block.inputs[input].block) { + if ( + block.inputs[input].shadow !== null && + block.inputs[input].shadow !== block.inputs[input].block + ) { this.deleteBlock(block.inputs[input].shadow); } } @@ -855,9 +1017,9 @@ class Blocks { /** * Delete all blocks and their associated scripts. */ - deleteAllBlocks () { + deleteAllBlocks() { const blockIds = Object.keys(this._blocks); - blockIds.forEach(blockId => this.deleteBlock(blockId)); + blockIds.forEach((blockId) => this.deleteBlock(blockId)); } /** @@ -871,7 +1033,7 @@ class Blocks { * for that ID. A variable reference contains the field referencing that variable * and also the type of the variable being referenced. */ - getAllVariableAndListReferences (optBlocks, optIncludeBroadcast) { + getAllVariableAndListReferences(optBlocks, optIncludeBroadcast) { const blocks = optBlocks ? optBlocks : this._blocks; const allReferences = Object.create(null); for (const blockId in blocks) { @@ -883,7 +1045,10 @@ class Blocks { } else if (blocks[blockId].fields.LIST) { varOrListField = blocks[blockId].fields.LIST; varType = Variable.LIST_TYPE; - } else if (optIncludeBroadcast && blocks[blockId].fields.BROADCAST_OPTION) { + } else if ( + optIncludeBroadcast && + blocks[blockId].fields.BROADCAST_OPTION + ) { varOrListField = blocks[blockId].fields.BROADCAST_OPTION; varType = Variable.BROADCAST_MESSAGE_TYPE; } @@ -892,13 +1057,15 @@ class Blocks { if (allReferences[currVarId]) { allReferences[currVarId].push({ referencingField: varOrListField, - type: varType + type: varType, }); } else { - allReferences[currVarId] = [{ - referencingField: varOrListField, - type: varType - }]; + allReferences[currVarId] = [ + { + referencingField: varOrListField, + type: varType, + }, + ]; } } } @@ -910,7 +1077,7 @@ class Blocks { * @param {string} varId The id of the variable that was renamed * @param {string} newName The new name of the variable that was renamed */ - updateBlocksAfterVarRename (varId, newName) { + updateBlocksAfterVarRename(varId, newName) { const blocks = this._blocks; for (const blockId in blocks) { let varOrListField = null; @@ -932,13 +1099,19 @@ class Blocks { * Keep blocks up to date after they are shared between targets. * @param {boolean} isStage If the new target is a stage. */ - updateTargetSpecificBlocks (isStage) { + updateTargetSpecificBlocks(isStage) { const blocks = this._blocks; for (const blockId in blocks) { - if (isStage && blocks[blockId].opcode === 'event_whenthisspriteclicked') { - blocks[blockId].opcode = 'event_whenstageclicked'; - } else if (!isStage && blocks[blockId].opcode === 'event_whenstageclicked') { - blocks[blockId].opcode = 'event_whenthisspriteclicked'; + if ( + isStage && + blocks[blockId].opcode === "event_whenthisspriteclicked" + ) { + blocks[blockId].opcode = "event_whenstageclicked"; + } else if ( + !isStage && + blocks[blockId].opcode === "event_whenstageclicked" + ) { + blocks[blockId].opcode = "event_whenthisspriteclicked"; } } } @@ -953,15 +1126,15 @@ class Blocks { * that was renamed. This can be one of 'sprite','costume', 'sound', or * 'backdrop'. */ - updateAssetName (oldName, newName, assetType) { + updateAssetName(oldName, newName, assetType) { let getAssetField; - if (assetType === 'costume') { + if (assetType === "costume") { getAssetField = this._getCostumeField.bind(this); - } else if (assetType === 'sound') { + } else if (assetType === "sound") { getAssetField = this._getSoundField.bind(this); - } else if (assetType === 'backdrop') { + } else if (assetType === "backdrop") { getAssetField = this._getBackdropField.bind(this); - } else if (assetType === 'sprite') { + } else if (assetType === "sprite") { getAssetField = this._getSpriteField.bind(this); } else { return; @@ -982,15 +1155,17 @@ class Blocks { * @param {string} targetName The name of the target the variable belongs to. * @return {boolean} Returns true if any of the blocks were updated. */ - updateSensingOfReference (oldName, newName, targetName) { + updateSensingOfReference(oldName, newName, targetName) { const blocks = this._blocks; let blockUpdated = false; for (const blockId in blocks) { const block = blocks[blockId]; - if (block.opcode === 'sensing_of' && + if ( + block.opcode === "sensing_of" && block.fields.PROPERTY.value === oldName && // If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored. - block.inputs.OBJECT.block === block.inputs.OBJECT.shadow) { + block.inputs.OBJECT.block === block.inputs.OBJECT.shadow + ) { const inputBlock = this.getBlock(block.inputs.OBJECT.block); if (inputBlock.fields.OBJECT.value === targetName) { block.fields.PROPERTY.value = newName; @@ -1009,9 +1184,12 @@ class Blocks { * Null if either a block with the given id doesn't exist or if a costume menu field * does not exist on the block with the given id. */ - _getCostumeField (blockId) { + _getCostumeField(blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, "COSTUME") + ) { return block.fields.COSTUME; } return null; @@ -1024,9 +1202,12 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a sound menu field * does not exist on the block with the given id. */ - _getSoundField (blockId) { + _getSoundField(blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, "SOUND_MENU") + ) { return block.fields.SOUND_MENU; } return null; @@ -1039,9 +1220,12 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a backdrop menu field * does not exist on the block with the given id. */ - _getBackdropField (blockId) { + _getBackdropField(blockId) { const block = this.getBlock(blockId); - if (block && Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP')) { + if ( + block && + Object.prototype.hasOwnProperty.call(block.fields, "BACKDROP") + ) { return block.fields.BACKDROP; } return null; @@ -1054,13 +1238,20 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a sprite menu field * does not exist on the block with the given id. */ - _getSpriteField (blockId) { + _getSpriteField(blockId) { const block = this.getBlock(blockId); if (!block) { return null; } - const spriteMenuNames = ['TOWARDS', 'TO', 'OBJECT', 'VIDEOONMENU2', - 'DISTANCETOMENU', 'TOUCHINGOBJECTMENU', 'CLONE_OPTION']; + const spriteMenuNames = [ + "TOWARDS", + "TO", + "OBJECT", + "VIDEOONMENU2", + "DISTANCETOMENU", + "TOUCHINGOBJECTMENU", + "CLONE_OPTION", + ]; for (let i = 0; i < spriteMenuNames.length; i++) { const menuName = spriteMenuNames[i]; if (Object.prototype.hasOwnProperty.call(block.fields, menuName)) { @@ -1078,8 +1269,10 @@ class Blocks { * @param {object} comments Map of comments referenced by id * @return {string} String of XML representing this object's blocks. */ - toXML (comments) { - return this._scripts.map(script => this.blockToXML(script, comments)).join(); + toXML(comments) { + return this._scripts + .map((script) => this.blockToXML(script, comments)) + .join(); } /** @@ -1089,19 +1282,18 @@ class Blocks { * @param {object} comments Map of comments referenced by id * @return {string} String of XML representing this block and any children. */ - blockToXML (blockId, comments) { + blockToXML(blockId, comments) { const block = this._blocks[blockId]; // block should exist, but currently some blocks' next property point // to a blockId for non-existent blocks. Until we track down that behavior, // this early exit allows the project to load. if (!block) return; // Encode properties of this block. - const tagName = (block.shadow) ? 'shadow' : 'block'; - let xmlString = - `<${tagName} + const tagName = block.shadow ? "shadow" : "block"; + let xmlString = `<${tagName} id="${block.id}" type="${block.opcode}" - ${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''} + ${block.topLevel ? `x="${block.x}" y="${block.y}"` : ""} >`; const commentId = block.comment; if (commentId) { @@ -1109,10 +1301,14 @@ class Blocks { if (Object.prototype.hasOwnProperty.call(comments, commentId)) { xmlString += comments[commentId].toXML(); } else { - log.warn(`Could not find comment with id: ${commentId} in provided comment descriptions.`); + log.warn( + `Could not find comment with id: ${commentId} in provided comment descriptions.` + ); } } else { - log.warn(`Cannot serialize comment with id: ${commentId}; no comment descriptions provided.`); + log.warn( + `Cannot serialize comment with id: ${commentId}; no comment descriptions provided.` + ); } } // Add any mutation. Must come before inputs. @@ -1121,7 +1317,8 @@ class Blocks { } // Add any inputs on this block. for (const input in block.inputs) { - if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) continue; + if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) + continue; const blockInput = block.inputs[input]; // Only encode a value tag if the value input is occupied. if (blockInput.block || blockInput.shadow) { @@ -1129,16 +1326,20 @@ class Blocks { if (blockInput.block) { xmlString += this.blockToXML(blockInput.block, comments); } - if (blockInput.shadow && blockInput.shadow !== blockInput.block) { + if ( + blockInput.shadow && + blockInput.shadow !== blockInput.block + ) { // Obscured shadow. xmlString += this.blockToXML(blockInput.shadow, comments); } - xmlString += ''; + xmlString += ""; } } // Add any fields on this block. for (const field in block.fields) { - if (!Object.prototype.hasOwnProperty.call(block.fields, field)) continue; + if (!Object.prototype.hasOwnProperty.call(block.fields, field)) + continue; const blockField = block.fields[field]; xmlString += `${value}`; } // Add blocks connected to the next connection. if (block.next) { - xmlString += `${this.blockToXML(block.next, comments)}`; + xmlString += `${this.blockToXML( + block.next, + comments + )}`; } xmlString += ``; return xmlString; @@ -1168,21 +1372,23 @@ class Blocks { * @param {!object} mutation Object representing a mutation. * @return {string} XML string representing a mutation. */ - mutationToXML (mutation) { + mutationToXML(mutation) { let mutationString = `<${mutation.tagName}`; for (const prop in mutation) { - if (prop === 'children' || prop === 'tagName') continue; - let mutationValue = (typeof mutation[prop] === 'string') ? - xmlEscape(mutation[prop]) : mutation[prop]; + if (prop === "children" || prop === "tagName") continue; + let mutationValue = + typeof mutation[prop] === "string" + ? xmlEscape(mutation[prop]) + : mutation[prop]; // Handle dynamic extension blocks - if (prop === 'blockInfo') { + if (prop === "blockInfo") { mutationValue = xmlEscape(JSON.stringify(mutation[prop])); } mutationString += ` ${prop}="${mutationValue}"`; } - mutationString += '>'; + mutationString += ">"; for (let i = 0; i < mutation.children.length; i++) { mutationString += this.mutationToXML(mutation.children[i]); } @@ -1196,7 +1402,7 @@ class Blocks { * @param {!object} block Block to be paramified. * @return {!object} object of param key/values. */ - _getBlockParams (block) { + _getBlockParams(block) { const params = {}; for (const key in block.fields) { params[key] = block.fields[key].value; @@ -1215,7 +1421,7 @@ class Blocks { * @param {!object} defineBlock Outer define block. * @return {!object} internal definition block which has the mutation. */ - _getCustomBlockInternal (defineBlock) { + _getCustomBlockInternal(defineBlock) { if (defineBlock.inputs && defineBlock.inputs.custom_block) { return this._blocks[defineBlock.inputs.custom_block.block]; } @@ -1225,7 +1431,7 @@ class Blocks { * Helper to add a stack to `this._scripts`. * @param {?string} topBlockId ID of block that starts the script. */ - _addScript (topBlockId) { + _addScript(topBlockId) { const i = this._scripts.indexOf(topBlockId); if (i > -1) return; // Already in scripts. this._scripts.push(topBlockId); @@ -1237,7 +1443,7 @@ class Blocks { * Helper to remove a script from `this._scripts`. * @param {?string} topBlockId ID of block that starts the script. */ - _deleteScript (topBlockId) { + _deleteScript(topBlockId) { const i = this._scripts.indexOf(topBlockId); if (i > -1) this._scripts.splice(i, 1); // Update `topLevel` property on the top block. @@ -1256,20 +1462,20 @@ class Blocks { */ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) { let cached = blocks._cache._executeCached[blockId]; - if (typeof cached !== 'undefined') { + if (typeof cached !== "undefined") { return cached; } const block = blocks.getBlock(blockId); - if (typeof block === 'undefined') return null; + if (typeof block === "undefined") return null; - if (typeof CacheType === 'undefined') { + if (typeof CacheType === "undefined") { cached = { id: blockId, opcode: blocks.getOpcode(block), fields: blocks.getFields(block), inputs: blocks.getInputs(block), - mutation: blocks.getMutation(block) + mutation: blocks.getMutation(block), }; } else { cached = new CacheType(blocks, { @@ -1277,7 +1483,7 @@ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) { opcode: blocks.getOpcode(block), fields: blocks.getFields(block), inputs: blocks.getInputs(block), - mutation: blocks.getMutation(block) + mutation: blocks.getMutation(block), }); } From ae67b9bfef7a95e856beb3396dbafd964adc791d Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 8 Oct 2024 08:43:13 -0700 Subject: [PATCH 09/76] fix: fix bug that could result in the VM's representation of shadow blocks getting into a bad state (#9) --- packages/scratch-vm/src/engine/blocks.js | 25 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index fecd0cb92d..a0a833a46f 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -886,23 +886,32 @@ class Blocks { typeof e.oldInput !== "undefined" && oldParent.inputs[e.oldInput].block === e.id ) { - // This block was connected to the old parent's input. We either - // want to restore the shadow block that previously occupied - // this input, or set it to null (which `.shadow` will be if - // there was no shadow previously) - oldParent.inputs[e.oldInput].block = - oldParent.inputs[e.oldInput].shadow; + // This block was connected to an input. We either want to + // restore the shadow block that previously occupied + // this input, or null out the input's block. + const shadow = oldParent.inputs[e.oldInput].shadow; + if (shadow && e.id !== shadow) { + oldParent.inputs[e.oldInput].block = shadow; + this._blocks[shadow].parent = oldParent.id; + } else { + oldParent.inputs[e.oldInput].block = null; + if (e.id !== shadow) { + this._blocks[e.id].parent = null; + } + } } else if (oldParent.next === e.id) { // This block was connected to the old parent's next connection. oldParent.next = null; + this._blocks[e.id].parent = null; } - this._blocks[e.id].parent = null; didChange = true; } // Is this block a top-level block? if (typeof e.newParent === "undefined") { - this._addScript(e.id); + if (!this._blocks[e.id].shadow) { + this._addScript(e.id); + } } else { // Remove script, if one exists. this._deleteScript(e.id); From a135fb51198b8987221de3bb64a2a8e5088a11fc Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:15:16 -0700 Subject: [PATCH 10/76] style: run "eslint --fix ." --- packages/scratch-vm/src/engine/blocks.js | 1035 +++++++++++---------- packages/scratch-vm/src/engine/runtime.js | 866 ++++++++--------- 2 files changed, 954 insertions(+), 947 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index a0a833a46f..6b646dc1b7 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -1,14 +1,14 @@ -const adapter = require("./adapter"); -const mutationAdapter = require("./mutation-adapter"); -const xmlEscape = require("../util/xml-escape"); -const MonitorRecord = require("./monitor-record"); -const Clone = require("../util/clone"); -const { Map } = require("immutable"); -const BlocksExecuteCache = require("./blocks-execute-cache"); -const BlocksRuntimeCache = require("./blocks-runtime-cache"); -const log = require("../util/log"); -const Variable = require("./variable"); -const getMonitorIdForBlockWithArgs = require("../util/get-monitor-id"); +const adapter = require('./adapter'); +const mutationAdapter = require('./mutation-adapter'); +const xmlEscape = require('../util/xml-escape'); +const MonitorRecord = require('./monitor-record'); +const Clone = require('../util/clone'); +const {Map} = require('immutable'); +const BlocksExecuteCache = require('./blocks-execute-cache'); +const BlocksRuntimeCache = require('./blocks-runtime-cache'); +const log = require('../util/log'); +const Variable = require('./variable'); +const getMonitorIdForBlockWithArgs = require('../util/get-monitor-id'); /** * @fileoverview @@ -23,7 +23,7 @@ const getMonitorIdForBlockWithArgs = require("../util/get-monitor-id"); * should not request glows. This does not affect glows when clicking on a block to execute it. */ class Blocks { - constructor(runtime, optNoGlow) { + constructor (runtime, optNoGlow) { this.runtime = runtime; /** @@ -45,9 +45,9 @@ class Blocks { * @type {{inputs: {}, procedureParamNames: {}, procedureDefinitions: {}}} * @private */ - Object.defineProperty(this, "_cache", { + Object.defineProperty(this, '_cache', { writable: true, - enumerable: false, + enumerable: false }); this._cache = { /** @@ -84,7 +84,7 @@ class Blocks { * A cache of hat opcodes to collection of theads to execute. * @type {object.} */ - scripts: {}, + scripts: {} }; /** @@ -104,8 +104,8 @@ class Blocks { * are prefixed with this string. * @const{string} */ - static get BRANCH_INPUT_PREFIX() { - return "SUBSTACK"; + static get BRANCH_INPUT_PREFIX () { + return 'SUBSTACK'; } /** @@ -113,7 +113,7 @@ class Blocks { * @param {!string} blockId ID of block we have stored. * @return {?object} Metadata about the block, if it exists. */ - getBlock(blockId) { + getBlock (blockId) { return this._blocks[blockId]; } @@ -121,7 +121,7 @@ class Blocks { * Get all known top-level blocks that start scripts. * @return {Array.} List of block IDs. */ - getScripts() { + getScripts () { return this._scripts; } @@ -130,9 +130,9 @@ class Blocks { * @param {?string} id ID of block to get the next block for * @return {?string} ID of next block in the sequence */ - getNextBlock(id) { + getNextBlock (id) { const block = this._blocks[id]; - return typeof block === "undefined" ? null : block.next; + return typeof block === 'undefined' ? null : block.next; } /** @@ -141,9 +141,9 @@ class Blocks { * @param {?number} branchNum Which branch to select (e.g. for if-else). * @return {?string} ID of block in the branch. */ - getBranch(id, branchNum) { + getBranch (id, branchNum) { const block = this._blocks[id]; - if (typeof block === "undefined") return null; + if (typeof block === 'undefined') return null; if (!branchNum) branchNum = 1; let inputName = Blocks.BRANCH_INPUT_PREFIX; @@ -153,7 +153,7 @@ class Blocks { // Empty C-block? const input = block.inputs[inputName]; - return typeof input === "undefined" ? null : input.block; + return typeof input === 'undefined' ? null : input.block; } /** @@ -161,8 +161,8 @@ class Blocks { * @param {?object} block The block to query * @return {?string} the opcode corresponding to that block */ - getOpcode(block) { - return typeof block === "undefined" ? null : block.opcode; + getOpcode (block) { + return typeof block === 'undefined' ? null : block.opcode; } /** @@ -170,8 +170,8 @@ class Blocks { * @param {?object} block The block to query. * @return {?object} All fields and their values. */ - getFields(block) { - return typeof block === "undefined" ? null : block.fields; + getFields (block) { + return typeof block === 'undefined' ? null : block.fields; } /** @@ -179,10 +179,10 @@ class Blocks { * @param {?object} block the block to query. * @return {?Array.} All non-branch inputs and their associated blocks. */ - getInputs(block) { - if (typeof block === "undefined") return null; + getInputs (block) { + if (typeof block === 'undefined') return null; let inputs = this._cache.inputs[block.id]; - if (typeof inputs !== "undefined") { + if (typeof inputs !== 'undefined') { return inputs; } @@ -206,8 +206,8 @@ class Blocks { * @param {?object} block The block to query. * @return {?object} Mutation for the block. */ - getMutation(block) { - return typeof block === "undefined" ? null : block.mutation; + getMutation (block) { + return typeof block === 'undefined' ? null : block.mutation; } /** @@ -215,9 +215,9 @@ class Blocks { * @param {?string} id ID of block to query. * @return {?string} ID of top-level script block. */ - getTopLevelScript(id) { + getTopLevelScript (id) { let block = this._blocks[id]; - if (typeof block === "undefined") return null; + if (typeof block === 'undefined') return null; while (block.parent !== null) { block = this._blocks[block.parent]; } @@ -229,17 +229,18 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?string} ID of procedure definition. */ - getProcedureDefinition(name) { + getProcedureDefinition (name) { const blockID = this._cache.procedureDefinitions[name]; - if (typeof blockID !== "undefined") { + if (typeof blockID !== 'undefined') { return blockID; } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) { continue; + } const block = this._blocks[id]; - if (block.opcode === "procedures_definition") { + if (block.opcode === 'procedures_definition') { const internal = this._getCustomBlockInternal(block); if (internal && internal.mutation.proccode === name) { this._cache.procedureDefinitions[name] = id; // The outer define block id @@ -257,7 +258,7 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?Array.} List of param names for a procedure. */ - getProcedureParamNamesAndIds(name) { + getProcedureParamNamesAndIds (name) { return this.getProcedureParamNamesIdsAndDefaults(name).slice(0, 2); } @@ -266,18 +267,19 @@ class Blocks { * @param {?string} name Name of procedure to query. * @return {?Array.} List of param names for a procedure. */ - getProcedureParamNamesIdsAndDefaults(name) { + getProcedureParamNamesIdsAndDefaults (name) { const cachedNames = this._cache.procedureParamNames[name]; - if (typeof cachedNames !== "undefined") { + if (typeof cachedNames !== 'undefined') { return cachedNames; } for (const id in this._blocks) { - if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) + if (!Object.prototype.hasOwnProperty.call(this._blocks, id)) { continue; + } const block = this._blocks[id]; if ( - block.opcode === "procedures_prototype" && + block.opcode === 'procedures_prototype' && block.mutation.proccode === name ) { const names = JSON.parse(block.mutation.argumentnames); @@ -293,7 +295,7 @@ class Blocks { return null; } - duplicate() { + duplicate () { const newBlocks = new Blocks(this.runtime, this.forceNoGlow); newBlocks._blocks = Clone.simple(this._blocks); newBlocks._scripts = Clone.simple(this._scripts); @@ -307,13 +309,13 @@ class Blocks { * runtime interface. * @param {object} e Blockly "block" or "variable" event */ - blocklyListen(e) { + blocklyListen (e) { // Validate event - if (typeof e !== "object") return; + if (typeof e !== 'object') return; if ( - typeof e.blockId !== "string" && - typeof e.varId !== "string" && - typeof e.commentId !== "string" + typeof e.blockId !== 'string' && + typeof e.varId !== 'string' && + typeof e.commentId !== 'string' ) { return; } @@ -322,315 +324,315 @@ class Blocks { // Block create/update/destroy switch (e.type) { - case "create": { + case 'create': { + const newBlocks = adapter(e); + // A create event can create many blocks. Add them all. + for (let i = 0; i < newBlocks.length; i++) { + this.createBlock(newBlocks[i]); + } + break; + } + case 'change': + this.changeBlock({ + id: e.blockId, + element: e.element, + name: e.name, + value: e.newValue + }); + break; + case 'move': + this.moveBlock({ + id: e.blockId, + oldParent: e.oldParentId, + oldInput: e.oldInputName, + newParent: e.newParentId, + newInput: e.newInputName, + newCoordinate: e.newCoordinate + }); + break; + case 'dragOutside': + this.runtime.emitBlockDragUpdate(e.isOutside); + break; + case 'endDrag': + this.runtime.emitBlockDragUpdate(false /* areBlocksOverGui */); + + // Drag blocks onto another sprite + if (e.isOutside) { const newBlocks = adapter(e); - // A create event can create many blocks. Add them all. - for (let i = 0; i < newBlocks.length; i++) { - this.createBlock(newBlocks[i]); - } - break; + this.runtime.emitBlockEndDrag(newBlocks, e.blockId); } - case "change": - this.changeBlock({ - id: e.blockId, - element: e.element, - name: e.name, - value: e.newValue, - }); - break; - case "move": - this.moveBlock({ - id: e.blockId, - oldParent: e.oldParentId, - oldInput: e.oldInputName, - newParent: e.newParentId, - newInput: e.newInputName, - newCoordinate: e.newCoordinate, - }); - break; - case "dragOutside": - this.runtime.emitBlockDragUpdate(e.isOutside); - break; - case "endDrag": - this.runtime.emitBlockDragUpdate(false /* areBlocksOverGui */); - - // Drag blocks onto another sprite - if (e.isOutside) { - const newBlocks = adapter(e); - this.runtime.emitBlockEndDrag(newBlocks, e.blockId); - } - break; - case "delete": - // Don't accept delete events for missing blocks, - // or shadow blocks being obscured. - if ( - !Object.prototype.hasOwnProperty.call( - this._blocks, - e.blockId - ) || + break; + case 'delete': + // Don't accept delete events for missing blocks, + // or shadow blocks being obscured. + if ( + !Object.prototype.hasOwnProperty.call( + this._blocks, + e.blockId + ) || this._blocks[e.blockId].shadow - ) { - return; - } - // Inform any runtime to forget about glows on this script. - if (this._blocks[e.blockId].topLevel) { - this.runtime.quietGlow(e.blockId); - } - this.deleteBlock(e.blockId); - break; - case "var_create": - // Check if the variable being created is global or local - // If local, create a local var on the current editing target, as long - // as there are no conflicts, and the current target is actually a sprite - // If global or if the editing target is not present or we somehow got - // into a state where a local var was requested for the stage, - // create a stage (global) var after checking for name conflicts - // on all the sprites. - if ( - e.isLocal && + ) { + return; + } + // Inform any runtime to forget about glows on this script. + if (this._blocks[e.blockId].topLevel) { + this.runtime.quietGlow(e.blockId); + } + this.deleteBlock(e.blockId); + break; + case 'var_create': + // Check if the variable being created is global or local + // If local, create a local var on the current editing target, as long + // as there are no conflicts, and the current target is actually a sprite + // If global or if the editing target is not present or we somehow got + // into a state where a local var was requested for the stage, + // create a stage (global) var after checking for name conflicts + // on all the sprites. + if ( + e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud - ) { - if (!editingTarget.lookupVariableById(e.varId)) { - editingTarget.createVariable( - e.varId, - e.varName, - e.varType - ); - this.emitProjectChanged(); - } - } else { - if (stage.lookupVariableById(e.varId)) { - // Do not re-create a variable if it already exists - return; - } - // Check for name conflicts in all of the targets - const allTargets = this.runtime.targets.filter( - (t) => t.isOriginal - ); - for (const target of allTargets) { - if ( - target.lookupVariableByNameAndType( - e.varName, - e.varType, - true - ) - ) { - return; - } - } - stage.createVariable( + ) { + if (!editingTarget.lookupVariableById(e.varId)) { + editingTarget.createVariable( e.varId, e.varName, - e.varType, - e.isCloud + e.varType ); this.emitProjectChanged(); } - break; - case "var_rename": - if ( - editingTarget && + } else { + if (stage.lookupVariableById(e.varId)) { + // Do not re-create a variable if it already exists + return; + } + // Check for name conflicts in all of the targets + const allTargets = this.runtime.targets.filter( + t => t.isOriginal + ); + for (const target of allTargets) { + if ( + target.lookupVariableByNameAndType( + e.varName, + e.varType, + true + ) + ) { + return; + } + } + stage.createVariable( + e.varId, + e.varName, + e.varType, + e.isCloud + ); + this.emitProjectChanged(); + } + break; + case 'var_rename': + if ( + editingTarget && Object.prototype.hasOwnProperty.call( editingTarget.variables, e.varId ) - ) { - // This is a local variable, rename on the current target - editingTarget.renameVariable(e.varId, e.newName); - // Update all the blocks on the current target that use - // this variable - editingTarget.blocks.updateBlocksAfterVarRename( + ) { + // This is a local variable, rename on the current target + editingTarget.renameVariable(e.varId, e.newName); + // Update all the blocks on the current target that use + // this variable + editingTarget.blocks.updateBlocksAfterVarRename( + e.varId, + e.newName + ); + } else { + // This is a global variable + stage.renameVariable(e.varId, e.newName); + // Update all blocks on all targets that use the renamed variable + const targets = this.runtime.targets; + for (let i = 0; i < targets.length; i++) { + const currTarget = targets[i]; + currTarget.blocks.updateBlocksAfterVarRename( e.varId, e.newName ); - } else { - // This is a global variable - stage.renameVariable(e.varId, e.newName); - // Update all blocks on all targets that use the renamed variable - const targets = this.runtime.targets; - for (let i = 0; i < targets.length; i++) { - const currTarget = targets[i]; - currTarget.blocks.updateBlocksAfterVarRename( - e.varId, - e.newName - ); - } } - this.emitProjectChanged(); - break; - case "var_delete": { - const target = + } + this.emitProjectChanged(); + break; + case 'var_delete': { + const target = editingTarget && Object.prototype.hasOwnProperty.call( editingTarget.variables, e.varId - ) - ? editingTarget - : stage; - target.deleteVariable(e.varId); - this.emitProjectChanged(); - break; - } - case "block_comment_create": - case "comment_create": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - currTarget.createComment( - e.commentId, - e.blockId, - "", - e.json.x, - e.json.y, - e.json.width, - e.json.height, - false - ); + ) ? + editingTarget : + stage; + target.deleteVariable(e.varId); + this.emitProjectChanged(); + break; + } + case 'block_comment_create': + case 'comment_create': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + currTarget.createComment( + e.commentId, + e.blockId, + '', + e.json.x, + e.json.y, + e.json.width, + e.json.height, + false + ); - if ( - currTarget.comments[e.commentId].x === null && + if ( + currTarget.comments[e.commentId].x === null && currTarget.comments[e.commentId].y === null - ) { - // Block comments imported from 2.0 projects are imported with their - // x and y coordinates set to null so that scratch-blocks can - // auto-position them. If we are receiving a create event for these - // comments, then the auto positioning should have taken place. - // Update the x and y position of these comments to match the - // one from the event. - currTarget.comments[e.commentId].x = e.json.x; - currTarget.comments[e.commentId].y = e.json.y; - } + ) { + // Block comments imported from 2.0 projects are imported with their + // x and y coordinates set to null so that scratch-blocks can + // auto-position them. If we are receiving a create event for these + // comments, then the auto positioning should have taken place. + // Update the x and y position of these comments to match the + // one from the event. + currTarget.comments[e.commentId].x = e.json.x; + currTarget.comments[e.commentId].y = e.json.y; + } + } + this.emitProjectChanged(); + break; + case 'block_comment_change': + case 'comment_change': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + log.warn( + `Cannot change comment with id ${e.commentId} because it does not exist.` + ); + return; } + const comment = currTarget.comments[e.commentId]; + comment.text = e.newContents_; this.emitProjectChanged(); - break; - case "block_comment_change": - case "comment_change": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if ( + } + break; + case 'block_comment_move': + case 'comment_move': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && !Object.prototype.hasOwnProperty.call( currTarget.comments, e.commentId ) - ) { - log.warn( - `Cannot change comment with id ${e.commentId} because it does not exist.` - ); - return; - } - const comment = currTarget.comments[e.commentId]; - comment.text = e.newContents_; - this.emitProjectChanged(); + ) { + log.warn( + `Cannot move comment with id ${e.commentId} because it does not exist.` + ); + return; } - break; - case "block_comment_move": - case "comment_move": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if ( - currTarget && - !Object.prototype.hasOwnProperty.call( - currTarget.comments, - e.commentId - ) - ) { - log.warn( - `Cannot move comment with id ${e.commentId} because it does not exist.` - ); - return; - } - const comment = currTarget.comments[e.commentId]; - const newCoord = e.newCoordinate_; - comment.x = newCoord.x; - comment.y = newCoord.y; + const comment = currTarget.comments[e.commentId]; + const newCoord = e.newCoordinate_; + comment.x = newCoord.x; + comment.y = newCoord.y; - this.emitProjectChanged(); - } - break; - case "block_comment_collapse": - case "comment_collapse": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if ( - currTarget && + this.emitProjectChanged(); + } + break; + case 'block_comment_collapse': + case 'comment_collapse': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && !Object.prototype.hasOwnProperty.call( currTarget.comments, e.commentId ) - ) { - log.warn( - `Cannot collapse comment with id ${e.commentId} because it does not exist.` - ); - return; - } - const comment = currTarget.comments[e.commentId]; - comment.minimized = e.newCollapsed; - this.emitProjectChanged(); + ) { + log.warn( + `Cannot collapse comment with id ${e.commentId} because it does not exist.` + ); + return; } - break; - case "block_comment_resize": - case "comment_resize": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if ( - currTarget && + const comment = currTarget.comments[e.commentId]; + comment.minimized = e.newCollapsed; + this.emitProjectChanged(); + } + break; + case 'block_comment_resize': + case 'comment_resize': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + currTarget && !Object.prototype.hasOwnProperty.call( currTarget.comments, e.commentId ) - ) { + ) { + log.warn( + `Cannot resize comment with id ${e.commentId} because it does not exist.` + ); + return; + } + const comment = currTarget.comments[e.commentId]; + comment.width = e.newSize.width; + comment.height = e.newSize.height; + this.emitProjectChanged(); + } + break; + case 'block_comment_delete': + case 'comment_delete': + if (this.runtime.getEditingTarget()) { + const currTarget = this.runtime.getEditingTarget(); + if ( + !Object.prototype.hasOwnProperty.call( + currTarget.comments, + e.commentId + ) + ) { + // If we're in this state, we have probably received + // a delete event from a workspace that we switched from + // (e.g. a delete event for a comment on sprite a's workspace + // when switching from sprite a to sprite b) + return; + } + delete currTarget.comments[e.commentId]; + if (e.blockId) { + const block = currTarget.blocks.getBlock(e.blockId); + if (!block) { log.warn( - `Cannot resize comment with id ${e.commentId} because it does not exist.` + `Could not find block referenced by comment with id: ${e.commentId}` ); return; } - const comment = currTarget.comments[e.commentId]; - comment.width = e.newSize.width; - comment.height = e.newSize.height; - this.emitProjectChanged(); + delete block.comment; } - break; - case "block_comment_delete": - case "comment_delete": - if (this.runtime.getEditingTarget()) { - const currTarget = this.runtime.getEditingTarget(); - if ( - !Object.prototype.hasOwnProperty.call( - currTarget.comments, - e.commentId - ) - ) { - // If we're in this state, we have probably received - // a delete event from a workspace that we switched from - // (e.g. a delete event for a comment on sprite a's workspace - // when switching from sprite a to sprite b) - return; - } - delete currTarget.comments[e.commentId]; - if (e.blockId) { - const block = currTarget.blocks.getBlock(e.blockId); - if (!block) { - log.warn( - `Could not find block referenced by comment with id: ${e.commentId}` - ); - return; - } - delete block.comment; - } - this.emitProjectChanged(); - } - break; - case "click": - // UI event: clicked scripts toggle in the runtime. - if (e.targetType === "block") { - this.runtime.toggleScript( - this.getTopLevelScript(e.blockId), - { stackClick: true } - ); - } - break; + this.emitProjectChanged(); + } + break; + case 'click': + // UI event: clicked scripts toggle in the runtime. + if (e.targetType === 'block') { + this.runtime.toggleScript( + this.getTopLevelScript(e.blockId), + {stackClick: true} + ); + } + break; } } @@ -639,7 +641,7 @@ class Blocks { /** * Reset all runtime caches. */ - resetCache() { + resetCache () { this._cache.inputs = {}; this._cache.procedureParamNames = {}; this._cache.procedureDefinitions = {}; @@ -652,7 +654,7 @@ class Blocks { * Emit a project changed event if this is a block container * that can affect the project state. */ - emitProjectChanged() { + emitProjectChanged () { if (!this.forceNoGlow) { this.runtime.emitProjectChanged(); } @@ -662,7 +664,7 @@ class Blocks { * Block management: create blocks and scripts from a `create` event * @param {!object} block Blockly create event to be processed */ - createBlock(block) { + createBlock (block) { // Does the block already exist? // Could happen, e.g., for an unobscured shadow. if (Object.prototype.hasOwnProperty.call(this._blocks, block.id)) { @@ -688,121 +690,122 @@ class Blocks { * Block management: change block field values * @param {!object} args Blockly change event to be processed */ - changeBlock(args) { + changeBlock (args) { // Validate - if (["field", "mutation", "checkbox"].indexOf(args.element) === -1) + if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) { return; + } let block = this._blocks[args.id]; - if (typeof block === "undefined") return; + if (typeof block === 'undefined') return; switch (args.element) { - case "field": - // TODO when the field of a monitored block changes, - // update the checkbox in the flyout based on whether - // a monitor for that current combination of selected parameters exists - // e.g. - // 1. check (current [v year]) - // 2. switch dropdown in flyout block to (current [v minute]) - // 3. the checkbox should become unchecked if we're not already - // monitoring current minute - - // Update block value - if (!block.fields[args.name]) return; - if ( - args.name === "VARIABLE" || - args.name === "LIST" || - args.name === "BROADCAST_OPTION" - ) { - // Get variable name using the id in args.value. - const variable = this.runtime - .getEditingTarget() - .lookupVariableById(args.value); - if (variable) { - block.fields[args.name].value = variable.name; - block.fields[args.name].id = args.value; - } - } else { - // Changing the value in a dropdown - block.fields[args.name].value = args.value; - - // The selected item in the sensing of block menu needs to change based on the - // selected target. Set it to the first item in the menu list. - // TODO: (#1787) - if (block.opcode === "sensing_of_object_menu") { - if (block.fields.OBJECT.value === "_stage_") { - this._blocks[block.parent].fields.PROPERTY.value = - "backdrop #"; - } else { - this._blocks[block.parent].fields.PROPERTY.value = - "x position"; - } - this.runtime.requestBlocksUpdate(); + case 'field': + // TODO when the field of a monitored block changes, + // update the checkbox in the flyout based on whether + // a monitor for that current combination of selected parameters exists + // e.g. + // 1. check (current [v year]) + // 2. switch dropdown in flyout block to (current [v minute]) + // 3. the checkbox should become unchecked if we're not already + // monitoring current minute + + // Update block value + if (!block.fields[args.name]) return; + if ( + args.name === 'VARIABLE' || + args.name === 'LIST' || + args.name === 'BROADCAST_OPTION' + ) { + // Get variable name using the id in args.value. + const variable = this.runtime + .getEditingTarget() + .lookupVariableById(args.value); + if (variable) { + block.fields[args.name].value = variable.name; + block.fields[args.name].id = args.value; + } + } else { + // Changing the value in a dropdown + block.fields[args.name].value = args.value; + + // The selected item in the sensing of block menu needs to change based on the + // selected target. Set it to the first item in the menu list. + // TODO: (#1787) + if (block.opcode === 'sensing_of_object_menu') { + if (block.fields.OBJECT.value === '_stage_') { + this._blocks[block.parent].fields.PROPERTY.value = + 'backdrop #'; + } else { + this._blocks[block.parent].fields.PROPERTY.value = + 'x position'; } + this.runtime.requestBlocksUpdate(); + } - const flyoutBlock = - block.shadow && block.parent - ? this._blocks[block.parent] - : block; - if (flyoutBlock.isMonitored) { - this.runtime.requestUpdateMonitor( - Map({ - id: flyoutBlock.id, - params: this._getBlockParams(flyoutBlock), - }) - ); - } + const flyoutBlock = + block.shadow && block.parent ? + this._blocks[block.parent] : + block; + if (flyoutBlock.isMonitored) { + this.runtime.requestUpdateMonitor( + Map({ + id: flyoutBlock.id, + params: this._getBlockParams(flyoutBlock) + }) + ); } - break; - case "mutation": - block.mutation = mutationAdapter(args.value); - break; - case "checkbox": { - // A checkbox usually has a one to one correspondence with the monitor - // block but in the case of monitored reporters that have arguments, - // map the old id to a new id, creating a new monitor block if necessary - if ( - block.fields && + } + break; + case 'mutation': + block.mutation = mutationAdapter(args.value); + break; + case 'checkbox': { + // A checkbox usually has a one to one correspondence with the monitor + // block but in the case of monitored reporters that have arguments, + // map the old id to a new id, creating a new monitor block if necessary + if ( + block.fields && Object.keys(block.fields).length > 0 && - block.opcode !== "data_variable" && - block.opcode !== "data_listcontents" - ) { - // This block has an argument which needs to get separated out into - // multiple monitor blocks with ids based on the selected argument - const newId = getMonitorIdForBlockWithArgs( - block.id, - block.fields - ); + block.opcode !== 'data_variable' && + block.opcode !== 'data_listcontents' + ) { + // This block has an argument which needs to get separated out into + // multiple monitor blocks with ids based on the selected argument + const newId = getMonitorIdForBlockWithArgs( + block.id, + block.fields + ); // Note: we're not just constantly creating a longer and longer id everytime we check // the checkbox because we're using the id of the block in the flyout as the base - // check if a block with the new id already exists, otherwise create - let newBlock = this.runtime.monitorBlocks.getBlock(newId); - if (!newBlock) { - newBlock = JSON.parse(JSON.stringify(block)); - newBlock.id = newId; - this.runtime.monitorBlocks.createBlock(newBlock); - } - - block = newBlock; // Carry on through the rest of this code with newBlock + // check if a block with the new id already exists, otherwise create + let newBlock = this.runtime.monitorBlocks.getBlock(newId); + if (!newBlock) { + newBlock = JSON.parse(JSON.stringify(block)); + newBlock.id = newId; + this.runtime.monitorBlocks.createBlock(newBlock); } - const wasMonitored = block.isMonitored; - block.isMonitored = args.value; + block = newBlock; // Carry on through the rest of this code with newBlock + } + + const wasMonitored = block.isMonitored; + block.isMonitored = args.value; - // Variable blocks may be sprite specific depending on the owner of the variable - let isSpriteLocalVariable = false; - if (block.opcode === "data_variable") { - isSpriteLocalVariable = + // Variable blocks may be sprite specific depending on the owner of the variable + let isSpriteLocalVariable = false; + if (block.opcode === 'data_variable') { + isSpriteLocalVariable = !this.runtime.getTargetForStage().variables[ block.fields.VARIABLE.id ]; - } else if (block.opcode === "data_listcontents") { - isSpriteLocalVariable = + } else if (block.opcode === 'data_listcontents') { + isSpriteLocalVariable = !this.runtime.getTargetForStage().variables[ block.fields.LIST.id ]; - } + } - const isSpriteSpecific = + const isSpriteSpecific = isSpriteLocalVariable || (Object.prototype.hasOwnProperty.call( this.runtime.monitorBlockInfo, @@ -810,44 +813,44 @@ class Blocks { ) && this.runtime.monitorBlockInfo[block.opcode] .isSpriteSpecific); - if (isSpriteSpecific) { - // If creating a new sprite specific monitor, the only possible target is - // the current editing one b/c you cannot dynamically create monitors. - // Also, do not change the targetId if it has already been assigned - block.targetId = + if (isSpriteSpecific) { + // If creating a new sprite specific monitor, the only possible target is + // the current editing one b/c you cannot dynamically create monitors. + // Also, do not change the targetId if it has already been assigned + block.targetId = block.targetId || this.runtime.getEditingTarget().id; - } else { - block.targetId = null; - } + } else { + block.targetId = null; + } - if (wasMonitored && !block.isMonitored) { - this.runtime.requestHideMonitor(block.id); - } else if (!wasMonitored && block.isMonitored) { - // Tries to show the monitor for specified block. If it doesn't exist, add the monitor. - if (!this.runtime.requestShowMonitor(block.id)) { - this.runtime.requestAddMonitor( - MonitorRecord({ - id: block.id, - targetId: block.targetId, - spriteName: block.targetId - ? this.runtime - .getTargetById(block.targetId) - .getName() - : null, - opcode: block.opcode, - params: this._getBlockParams(block), - // @todo(vm#565) for numerical values with decimals, some countries use comma - value: "", - mode: - block.opcode === "data_listcontents" - ? "list" - : "default", - }) - ); - } + if (wasMonitored && !block.isMonitored) { + this.runtime.requestHideMonitor(block.id); + } else if (!wasMonitored && block.isMonitored) { + // Tries to show the monitor for specified block. If it doesn't exist, add the monitor. + if (!this.runtime.requestShowMonitor(block.id)) { + this.runtime.requestAddMonitor( + MonitorRecord({ + id: block.id, + targetId: block.targetId, + spriteName: block.targetId ? + this.runtime + .getTargetById(block.targetId) + .getName() : + null, + opcode: block.opcode, + params: this._getBlockParams(block), + // @todo(vm#565) for numerical values with decimals, some countries use comma + value: '', + mode: + block.opcode === 'data_listcontents' ? + 'list' : + 'default' + }) + ); } - break; } + break; + } } this.emitProjectChanged(); @@ -859,7 +862,7 @@ class Blocks { * Block management: move blocks from parent to parent * @param {!object} e Blockly move event to be processed */ - moveBlock(e) { + moveBlock (e) { if (!Object.prototype.hasOwnProperty.call(this._blocks, e.id)) { return; } @@ -880,10 +883,10 @@ class Blocks { } // Remove from any old parent. - if (typeof e.oldParent !== "undefined") { + if (typeof e.oldParent !== 'undefined') { const oldParent = this._blocks[e.oldParent]; if ( - typeof e.oldInput !== "undefined" && + typeof e.oldInput !== 'undefined' && oldParent.inputs[e.oldInput].block === e.id ) { // This block was connected to an input. We either want to @@ -908,7 +911,7 @@ class Blocks { } // Is this block a top-level block? - if (typeof e.newParent === "undefined") { + if (typeof e.newParent === 'undefined') { if (!this._blocks[e.id].shadow) { this._addScript(e.id); } @@ -916,7 +919,7 @@ class Blocks { // Remove script, if one exists. this._deleteScript(e.id); // Otherwise, try to connect it in its new place. - if (typeof e.newInput === "undefined") { + if (typeof e.newInput === 'undefined') { // Moved to the new parent's next connection. this._blocks[e.newParent].next = e.id; } else { @@ -941,7 +944,7 @@ class Blocks { this._blocks[e.newParent].inputs[e.newInput] = { name: e.newInput, block: e.id, - shadow: oldShadow, + shadow: oldShadow }; } this._blocks[e.id].parent = e.newParent; @@ -956,24 +959,24 @@ class Blocks { * Block management: run all blocks. * @param {!object} runtime Runtime to run all blocks in. */ - runAllMonitored(runtime) { + runAllMonitored (runtime) { if (this._cache._monitored === null) { this._cache._monitored = Object.keys(this._blocks) - .filter((blockId) => this.getBlock(blockId).isMonitored) - .map((blockId) => { + .filter(blockId => this.getBlock(blockId).isMonitored) + .map(blockId => { const targetId = this.getBlock(blockId).targetId; return { blockId, - target: targetId - ? runtime.getTargetById(targetId) - : null, + target: targetId ? + runtime.getTargetById(targetId) : + null }; }); } const monitored = this._cache._monitored; for (let i = 0; i < monitored.length; i++) { - const { blockId, target } = monitored[i]; + const {blockId, target} = monitored[i]; runtime.addMonitorScript(blockId, target); } } @@ -983,7 +986,7 @@ class Blocks { * with the given ID does not exist. * @param {!string} blockId Id of block to delete */ - deleteBlock(blockId) { + deleteBlock (blockId) { // @todo In runtime, stop threads running on this script. // Get block @@ -1026,9 +1029,9 @@ class Blocks { /** * Delete all blocks and their associated scripts. */ - deleteAllBlocks() { + deleteAllBlocks () { const blockIds = Object.keys(this._blocks); - blockIds.forEach((blockId) => this.deleteBlock(blockId)); + blockIds.forEach(blockId => this.deleteBlock(blockId)); } /** @@ -1042,7 +1045,7 @@ class Blocks { * for that ID. A variable reference contains the field referencing that variable * and also the type of the variable being referenced. */ - getAllVariableAndListReferences(optBlocks, optIncludeBroadcast) { + getAllVariableAndListReferences (optBlocks, optIncludeBroadcast) { const blocks = optBlocks ? optBlocks : this._blocks; const allReferences = Object.create(null); for (const blockId in blocks) { @@ -1066,14 +1069,14 @@ class Blocks { if (allReferences[currVarId]) { allReferences[currVarId].push({ referencingField: varOrListField, - type: varType, + type: varType }); } else { allReferences[currVarId] = [ { referencingField: varOrListField, - type: varType, - }, + type: varType + } ]; } } @@ -1086,7 +1089,7 @@ class Blocks { * @param {string} varId The id of the variable that was renamed * @param {string} newName The new name of the variable that was renamed */ - updateBlocksAfterVarRename(varId, newName) { + updateBlocksAfterVarRename (varId, newName) { const blocks = this._blocks; for (const blockId in blocks) { let varOrListField = null; @@ -1108,19 +1111,19 @@ class Blocks { * Keep blocks up to date after they are shared between targets. * @param {boolean} isStage If the new target is a stage. */ - updateTargetSpecificBlocks(isStage) { + updateTargetSpecificBlocks (isStage) { const blocks = this._blocks; for (const blockId in blocks) { if ( isStage && - blocks[blockId].opcode === "event_whenthisspriteclicked" + blocks[blockId].opcode === 'event_whenthisspriteclicked' ) { - blocks[blockId].opcode = "event_whenstageclicked"; + blocks[blockId].opcode = 'event_whenstageclicked'; } else if ( !isStage && - blocks[blockId].opcode === "event_whenstageclicked" + blocks[blockId].opcode === 'event_whenstageclicked' ) { - blocks[blockId].opcode = "event_whenthisspriteclicked"; + blocks[blockId].opcode = 'event_whenthisspriteclicked'; } } } @@ -1135,15 +1138,15 @@ class Blocks { * that was renamed. This can be one of 'sprite','costume', 'sound', or * 'backdrop'. */ - updateAssetName(oldName, newName, assetType) { + updateAssetName (oldName, newName, assetType) { let getAssetField; - if (assetType === "costume") { + if (assetType === 'costume') { getAssetField = this._getCostumeField.bind(this); - } else if (assetType === "sound") { + } else if (assetType === 'sound') { getAssetField = this._getSoundField.bind(this); - } else if (assetType === "backdrop") { + } else if (assetType === 'backdrop') { getAssetField = this._getBackdropField.bind(this); - } else if (assetType === "sprite") { + } else if (assetType === 'sprite') { getAssetField = this._getSpriteField.bind(this); } else { return; @@ -1164,13 +1167,13 @@ class Blocks { * @param {string} targetName The name of the target the variable belongs to. * @return {boolean} Returns true if any of the blocks were updated. */ - updateSensingOfReference(oldName, newName, targetName) { + updateSensingOfReference (oldName, newName, targetName) { const blocks = this._blocks; let blockUpdated = false; for (const blockId in blocks) { const block = blocks[blockId]; if ( - block.opcode === "sensing_of" && + block.opcode === 'sensing_of' && block.fields.PROPERTY.value === oldName && // If block and shadow are different, it means a block is inserted to OBJECT, and should be ignored. block.inputs.OBJECT.block === block.inputs.OBJECT.shadow @@ -1193,11 +1196,11 @@ class Blocks { * Null if either a block with the given id doesn't exist or if a costume menu field * does not exist on the block with the given id. */ - _getCostumeField(blockId) { + _getCostumeField (blockId) { const block = this.getBlock(blockId); if ( block && - Object.prototype.hasOwnProperty.call(block.fields, "COSTUME") + Object.prototype.hasOwnProperty.call(block.fields, 'COSTUME') ) { return block.fields.COSTUME; } @@ -1211,11 +1214,11 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a sound menu field * does not exist on the block with the given id. */ - _getSoundField(blockId) { + _getSoundField (blockId) { const block = this.getBlock(blockId); if ( block && - Object.prototype.hasOwnProperty.call(block.fields, "SOUND_MENU") + Object.prototype.hasOwnProperty.call(block.fields, 'SOUND_MENU') ) { return block.fields.SOUND_MENU; } @@ -1229,11 +1232,11 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a backdrop menu field * does not exist on the block with the given id. */ - _getBackdropField(blockId) { + _getBackdropField (blockId) { const block = this.getBlock(blockId); if ( block && - Object.prototype.hasOwnProperty.call(block.fields, "BACKDROP") + Object.prototype.hasOwnProperty.call(block.fields, 'BACKDROP') ) { return block.fields.BACKDROP; } @@ -1247,19 +1250,19 @@ class Blocks { * Null, if either a block with the given id doesn't exist or if a sprite menu field * does not exist on the block with the given id. */ - _getSpriteField(blockId) { + _getSpriteField (blockId) { const block = this.getBlock(blockId); if (!block) { return null; } const spriteMenuNames = [ - "TOWARDS", - "TO", - "OBJECT", - "VIDEOONMENU2", - "DISTANCETOMENU", - "TOUCHINGOBJECTMENU", - "CLONE_OPTION", + 'TOWARDS', + 'TO', + 'OBJECT', + 'VIDEOONMENU2', + 'DISTANCETOMENU', + 'TOUCHINGOBJECTMENU', + 'CLONE_OPTION' ]; for (let i = 0; i < spriteMenuNames.length; i++) { const menuName = spriteMenuNames[i]; @@ -1278,9 +1281,9 @@ class Blocks { * @param {object} comments Map of comments referenced by id * @return {string} String of XML representing this object's blocks. */ - toXML(comments) { + toXML (comments) { return this._scripts - .map((script) => this.blockToXML(script, comments)) + .map(script => this.blockToXML(script, comments)) .join(); } @@ -1291,18 +1294,18 @@ class Blocks { * @param {object} comments Map of comments referenced by id * @return {string} String of XML representing this block and any children. */ - blockToXML(blockId, comments) { + blockToXML (blockId, comments) { const block = this._blocks[blockId]; // block should exist, but currently some blocks' next property point // to a blockId for non-existent blocks. Until we track down that behavior, // this early exit allows the project to load. if (!block) return; // Encode properties of this block. - const tagName = block.shadow ? "shadow" : "block"; + const tagName = block.shadow ? 'shadow' : 'block'; let xmlString = `<${tagName} id="${block.id}" type="${block.opcode}" - ${block.topLevel ? `x="${block.x}" y="${block.y}"` : ""} + ${block.topLevel ? `x="${block.x}" y="${block.y}"` : ''} >`; const commentId = block.comment; if (commentId) { @@ -1326,8 +1329,9 @@ class Blocks { } // Add any inputs on this block. for (const input in block.inputs) { - if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) + if (!Object.prototype.hasOwnProperty.call(block.inputs, input)) { continue; + } const blockInput = block.inputs[input]; // Only encode a value tag if the value input is occupied. if (blockInput.block || blockInput.shadow) { @@ -1342,13 +1346,14 @@ class Blocks { // Obscured shadow. xmlString += this.blockToXML(blockInput.shadow, comments); } - xmlString += ""; + xmlString += ''; } } // Add any fields on this block. for (const field in block.fields) { - if (!Object.prototype.hasOwnProperty.call(block.fields, field)) + if (!Object.prototype.hasOwnProperty.call(block.fields, field)) { continue; + } const blockField = block.fields[field]; xmlString += `${value}`; @@ -1381,23 +1386,23 @@ class Blocks { * @param {!object} mutation Object representing a mutation. * @return {string} XML string representing a mutation. */ - mutationToXML(mutation) { + mutationToXML (mutation) { let mutationString = `<${mutation.tagName}`; for (const prop in mutation) { - if (prop === "children" || prop === "tagName") continue; + if (prop === 'children' || prop === 'tagName') continue; let mutationValue = - typeof mutation[prop] === "string" - ? xmlEscape(mutation[prop]) - : mutation[prop]; + typeof mutation[prop] === 'string' ? + xmlEscape(mutation[prop]) : + mutation[prop]; // Handle dynamic extension blocks - if (prop === "blockInfo") { + if (prop === 'blockInfo') { mutationValue = xmlEscape(JSON.stringify(mutation[prop])); } mutationString += ` ${prop}="${mutationValue}"`; } - mutationString += ">"; + mutationString += '>'; for (let i = 0; i < mutation.children.length; i++) { mutationString += this.mutationToXML(mutation.children[i]); } @@ -1411,7 +1416,7 @@ class Blocks { * @param {!object} block Block to be paramified. * @return {!object} object of param key/values. */ - _getBlockParams(block) { + _getBlockParams (block) { const params = {}; for (const key in block.fields) { params[key] = block.fields[key].value; @@ -1430,7 +1435,7 @@ class Blocks { * @param {!object} defineBlock Outer define block. * @return {!object} internal definition block which has the mutation. */ - _getCustomBlockInternal(defineBlock) { + _getCustomBlockInternal (defineBlock) { if (defineBlock.inputs && defineBlock.inputs.custom_block) { return this._blocks[defineBlock.inputs.custom_block.block]; } @@ -1440,7 +1445,7 @@ class Blocks { * Helper to add a stack to `this._scripts`. * @param {?string} topBlockId ID of block that starts the script. */ - _addScript(topBlockId) { + _addScript (topBlockId) { const i = this._scripts.indexOf(topBlockId); if (i > -1) return; // Already in scripts. this._scripts.push(topBlockId); @@ -1452,7 +1457,7 @@ class Blocks { * Helper to remove a script from `this._scripts`. * @param {?string} topBlockId ID of block that starts the script. */ - _deleteScript(topBlockId) { + _deleteScript (topBlockId) { const i = this._scripts.indexOf(topBlockId); if (i > -1) this._scripts.splice(i, 1); // Update `topLevel` property on the top block. @@ -1471,20 +1476,20 @@ class Blocks { */ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) { let cached = blocks._cache._executeCached[blockId]; - if (typeof cached !== "undefined") { + if (typeof cached !== 'undefined') { return cached; } const block = blocks.getBlock(blockId); - if (typeof block === "undefined") return null; + if (typeof block === 'undefined') return null; - if (typeof CacheType === "undefined") { + if (typeof CacheType === 'undefined') { cached = { id: blockId, opcode: blocks.getOpcode(block), fields: blocks.getFields(block), inputs: blocks.getInputs(block), - mutation: blocks.getMutation(block), + mutation: blocks.getMutation(block) }; } else { cached = new CacheType(blocks, { @@ -1492,7 +1497,7 @@ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) { opcode: blocks.getOpcode(block), fields: blocks.getFields(block), inputs: blocks.getInputs(block), - mutation: blocks.getMutation(block), + mutation: blocks.getMutation(block) }); } diff --git a/packages/scratch-vm/src/engine/runtime.js b/packages/scratch-vm/src/engine/runtime.js index 59a1405b16..06518d493b 100644 --- a/packages/scratch-vm/src/engine/runtime.js +++ b/packages/scratch-vm/src/engine/runtime.js @@ -1,50 +1,50 @@ -const EventEmitter = require("events"); -const { OrderedMap } = require("immutable"); -const uuid = require("uuid"); - -const ArgumentType = require("../extension-support/argument-type"); -const Blocks = require("./blocks"); -const BlocksRuntimeCache = require("./blocks-runtime-cache"); -const BlockType = require("../extension-support/block-type"); -const Profiler = require("./profiler"); -const Sequencer = require("./sequencer"); -const execute = require("./execute.js"); -const ScratchBlocksConstants = require("./scratch-blocks-constants"); -const TargetType = require("../extension-support/target-type"); -const Thread = require("./thread"); -const log = require("../util/log"); -const maybeFormatMessage = require("../util/maybe-format-message"); -const StageLayering = require("./stage-layering"); -const Variable = require("./variable"); -const xmlEscape = require("../util/xml-escape"); -const ScratchLinkWebSocket = require("../util/scratch-link-websocket"); -const fetchWithTimeout = require("../util/fetch-with-timeout"); +const EventEmitter = require('events'); +const {OrderedMap} = require('immutable'); +const uuid = require('uuid'); + +const ArgumentType = require('../extension-support/argument-type'); +const Blocks = require('./blocks'); +const BlocksRuntimeCache = require('./blocks-runtime-cache'); +const BlockType = require('../extension-support/block-type'); +const Profiler = require('./profiler'); +const Sequencer = require('./sequencer'); +const execute = require('./execute.js'); +const ScratchBlocksConstants = require('./scratch-blocks-constants'); +const TargetType = require('../extension-support/target-type'); +const Thread = require('./thread'); +const log = require('../util/log'); +const maybeFormatMessage = require('../util/maybe-format-message'); +const StageLayering = require('./stage-layering'); +const Variable = require('./variable'); +const xmlEscape = require('../util/xml-escape'); +const ScratchLinkWebSocket = require('../util/scratch-link-websocket'); +const fetchWithTimeout = require('../util/fetch-with-timeout'); // Virtual I/O devices. -const Clock = require("../io/clock"); -const Cloud = require("../io/cloud"); -const Keyboard = require("../io/keyboard"); -const Mouse = require("../io/mouse"); -const MouseWheel = require("../io/mouseWheel"); -const UserData = require("../io/userData"); -const Video = require("../io/video"); +const Clock = require('../io/clock'); +const Cloud = require('../io/cloud'); +const Keyboard = require('../io/keyboard'); +const Mouse = require('../io/mouse'); +const MouseWheel = require('../io/mouseWheel'); +const UserData = require('../io/userData'); +const Video = require('../io/video'); -const StringUtil = require("../util/string-util"); -const uid = require("../util/uid"); +const StringUtil = require('../util/string-util'); +const uid = require('../util/uid'); const defaultBlockPackages = { - scratch3_control: require("../blocks/scratch3_control"), - scratch3_event: require("../blocks/scratch3_event"), - scratch3_looks: require("../blocks/scratch3_looks"), - scratch3_motion: require("../blocks/scratch3_motion"), - scratch3_operators: require("../blocks/scratch3_operators"), - scratch3_sound: require("../blocks/scratch3_sound"), - scratch3_sensing: require("../blocks/scratch3_sensing"), - scratch3_data: require("../blocks/scratch3_data"), - scratch3_procedures: require("../blocks/scratch3_procedures"), + scratch3_control: require('../blocks/scratch3_control'), + scratch3_event: require('../blocks/scratch3_event'), + scratch3_looks: require('../blocks/scratch3_looks'), + scratch3_motion: require('../blocks/scratch3_motion'), + scratch3_operators: require('../blocks/scratch3_operators'), + scratch3_sound: require('../blocks/scratch3_sound'), + scratch3_sensing: require('../blocks/scratch3_sensing'), + scratch3_data: require('../blocks/scratch3_data'), + scratch3_procedures: require('../blocks/scratch3_procedures') }; -const defaultExtensionColors = ["#0FBD8C", "#0DA57A", "#0B8E69"]; +const defaultExtensionColors = ['#0FBD8C', '#0DA57A', '#0B8E69']; /** * Information used for converting Scratch argument types into scratch-blocks data. @@ -54,7 +54,7 @@ const ArgumentTypeMap = (() => { const map = {}; map[ArgumentType.ANGLE] = { shadow: { - type: "math_angle", + type: 'math_angle', // We specify fieldNames here so that we can pick // create and populate a field with the defaultValue // specified in the extension. @@ -62,46 +62,46 @@ const ArgumentTypeMap = (() => { // the will be left out of the XML and // the scratch-blocks defaults for that field will be // used instead (e.g. default of 0 for number fields) - fieldName: "NUM", - }, + fieldName: 'NUM' + } }; map[ArgumentType.COLOR] = { shadow: { - type: "colour_picker", - fieldName: "COLOUR", - }, + type: 'colour_picker', + fieldName: 'COLOUR' + } }; map[ArgumentType.NUMBER] = { shadow: { - type: "math_number", - fieldName: "NUM", - }, + type: 'math_number', + fieldName: 'NUM' + } }; map[ArgumentType.STRING] = { shadow: { - type: "text", - fieldName: "TEXT", - }, + type: 'text', + fieldName: 'TEXT' + } }; map[ArgumentType.BOOLEAN] = { - check: "Boolean", + check: 'Boolean' }; map[ArgumentType.MATRIX] = { shadow: { - type: "matrix", - fieldName: "MATRIX", - }, + type: 'matrix', + fieldName: 'MATRIX' + } }; map[ArgumentType.NOTE] = { shadow: { - type: "note", - fieldName: "NOTE", - }, + type: 'note', + fieldName: 'NOTE' + } }; map[ArgumentType.IMAGE] = { // Inline images are weird because they're not actually "arguments". // They are more analagous to the label on a block. - fieldType: "field_image", + fieldType: 'field_image' }; return map; })(); @@ -150,7 +150,7 @@ const cloudDataManager = () => { canAddCloudVariable, addCloudVariable, removeCloudVariable, - hasCloudVariables, + hasCloudVariables }; }; @@ -177,7 +177,7 @@ let rendererDrawProfilerId = -1; * @constructor */ class Runtime extends EventEmitter { - constructor() { + constructor () { super(); /** @@ -344,7 +344,7 @@ class Runtime extends EventEmitter { mouse: new Mouse(this), mouseWheel: new MouseWheel(this), userData: new UserData(), - video: new Video(this), + video: new Video(this) }; /** @@ -413,7 +413,7 @@ class Runtime extends EventEmitter { * Width of the stage, in pixels. * @const {number} */ - static get STAGE_WIDTH() { + static get STAGE_WIDTH () { return 480; } @@ -421,7 +421,7 @@ class Runtime extends EventEmitter { * Height of the stage, in pixels. * @const {number} */ - static get STAGE_HEIGHT() { + static get STAGE_HEIGHT () { return 360; } @@ -429,32 +429,32 @@ class Runtime extends EventEmitter { * Event name for glowing a script. * @const {string} */ - static get SCRIPT_GLOW_ON() { - return "SCRIPT_GLOW_ON"; + static get SCRIPT_GLOW_ON () { + return 'SCRIPT_GLOW_ON'; } /** * Event name for unglowing a script. * @const {string} */ - static get SCRIPT_GLOW_OFF() { - return "SCRIPT_GLOW_OFF"; + static get SCRIPT_GLOW_OFF () { + return 'SCRIPT_GLOW_OFF'; } /** * Event name for glowing a block. * @const {string} */ - static get BLOCK_GLOW_ON() { - return "BLOCK_GLOW_ON"; + static get BLOCK_GLOW_ON () { + return 'BLOCK_GLOW_ON'; } /** * Event name for unglowing a block. * @const {string} */ - static get BLOCK_GLOW_OFF() { - return "BLOCK_GLOW_OFF"; + static get BLOCK_GLOW_OFF () { + return 'BLOCK_GLOW_OFF'; } /** @@ -462,24 +462,24 @@ class Runtime extends EventEmitter { * to this project. * @const {string} */ - static get HAS_CLOUD_DATA_UPDATE() { - return "HAS_CLOUD_DATA_UPDATE"; + static get HAS_CLOUD_DATA_UPDATE () { + return 'HAS_CLOUD_DATA_UPDATE'; } /** * Event name for turning on turbo mode. * @const {string} */ - static get TURBO_MODE_ON() { - return "TURBO_MODE_ON"; + static get TURBO_MODE_ON () { + return 'TURBO_MODE_ON'; } /** * Event name for turning off turbo mode. * @const {string} */ - static get TURBO_MODE_OFF() { - return "TURBO_MODE_OFF"; + static get TURBO_MODE_OFF () { + return 'TURBO_MODE_OFF'; } /** @@ -487,8 +487,8 @@ class Runtime extends EventEmitter { * running). * @const {string} */ - static get PROJECT_START() { - return "PROJECT_START"; + static get PROJECT_START () { + return 'PROJECT_START'; } /** @@ -496,8 +496,8 @@ class Runtime extends EventEmitter { * Used by the UI to indicate running status. * @const {string} */ - static get PROJECT_RUN_START() { - return "PROJECT_RUN_START"; + static get PROJECT_RUN_START () { + return 'PROJECT_RUN_START'; } /** @@ -505,8 +505,8 @@ class Runtime extends EventEmitter { * Used by the UI to indicate not-running status. * @const {string} */ - static get PROJECT_RUN_STOP() { - return "PROJECT_RUN_STOP"; + static get PROJECT_RUN_STOP () { + return 'PROJECT_RUN_STOP'; } /** @@ -514,8 +514,8 @@ class Runtime extends EventEmitter { * Used by blocks that need to reset state. * @const {string} */ - static get PROJECT_STOP_ALL() { - return "PROJECT_STOP_ALL"; + static get PROJECT_STOP_ALL () { + return 'PROJECT_STOP_ALL'; } /** @@ -523,88 +523,88 @@ class Runtime extends EventEmitter { * Used by blocks that need to stop individual targets. * @const {string} */ - static get STOP_FOR_TARGET() { - return "STOP_FOR_TARGET"; + static get STOP_FOR_TARGET () { + return 'STOP_FOR_TARGET'; } /** * Event name for visual value report. * @const {string} */ - static get VISUAL_REPORT() { - return "VISUAL_REPORT"; + static get VISUAL_REPORT () { + return 'VISUAL_REPORT'; } /** * Event name for project loaded report. * @const {string} */ - static get PROJECT_LOADED() { - return "PROJECT_LOADED"; + static get PROJECT_LOADED () { + return 'PROJECT_LOADED'; } /** * Event name for report that a change was made that can be saved * @const {string} */ - static get PROJECT_CHANGED() { - return "PROJECT_CHANGED"; + static get PROJECT_CHANGED () { + return 'PROJECT_CHANGED'; } /** * Event name for report that a change was made to an extension in the toolbox. * @const {string} */ - static get TOOLBOX_EXTENSIONS_NEED_UPDATE() { - return "TOOLBOX_EXTENSIONS_NEED_UPDATE"; + static get TOOLBOX_EXTENSIONS_NEED_UPDATE () { + return 'TOOLBOX_EXTENSIONS_NEED_UPDATE'; } /** * Event name for targets update report. * @const {string} */ - static get TARGETS_UPDATE() { - return "TARGETS_UPDATE"; + static get TARGETS_UPDATE () { + return 'TARGETS_UPDATE'; } /** * Event name for monitors update. * @const {string} */ - static get MONITORS_UPDATE() { - return "MONITORS_UPDATE"; + static get MONITORS_UPDATE () { + return 'MONITORS_UPDATE'; } /** * Event name for block drag update. * @const {string} */ - static get BLOCK_DRAG_UPDATE() { - return "BLOCK_DRAG_UPDATE"; + static get BLOCK_DRAG_UPDATE () { + return 'BLOCK_DRAG_UPDATE'; } /** * Event name for block drag end. * @const {string} */ - static get BLOCK_DRAG_END() { - return "BLOCK_DRAG_END"; + static get BLOCK_DRAG_END () { + return 'BLOCK_DRAG_END'; } /** * Event name for reporting that an extension was added. * @const {string} */ - static get EXTENSION_ADDED() { - return "EXTENSION_ADDED"; + static get EXTENSION_ADDED () { + return 'EXTENSION_ADDED'; } /** * Event name for reporting that an extension as asked for a custom field to be added * @const {string} */ - static get EXTENSION_FIELD_ADDED() { - return "EXTENSION_FIELD_ADDED"; + static get EXTENSION_FIELD_ADDED () { + return 'EXTENSION_FIELD_ADDED'; } /** @@ -613,8 +613,8 @@ class Runtime extends EventEmitter { * available peripherals. * @const {string} */ - static get PERIPHERAL_LIST_UPDATE() { - return "PERIPHERAL_LIST_UPDATE"; + static get PERIPHERAL_LIST_UPDATE () { + return 'PERIPHERAL_LIST_UPDATE'; } /** @@ -622,8 +622,8 @@ class Runtime extends EventEmitter { * via Companion Device Manager (CDM) * @const {string} */ - static get USER_PICKED_PERIPHERAL() { - return "USER_PICKED_PERIPHERAL"; + static get USER_PICKED_PERIPHERAL () { + return 'USER_PICKED_PERIPHERAL'; } /** @@ -631,8 +631,8 @@ class Runtime extends EventEmitter { * This causes the status button in the blocks menu to indicate 'connected'. * @const {string} */ - static get PERIPHERAL_CONNECTED() { - return "PERIPHERAL_CONNECTED"; + static get PERIPHERAL_CONNECTED () { + return 'PERIPHERAL_CONNECTED'; } /** @@ -640,8 +640,8 @@ class Runtime extends EventEmitter { * This causes the status button in the blocks menu to indicate 'disconnected'. * @const {string} */ - static get PERIPHERAL_DISCONNECTED() { - return "PERIPHERAL_DISCONNECTED"; + static get PERIPHERAL_DISCONNECTED () { + return 'PERIPHERAL_DISCONNECTED'; } /** @@ -649,8 +649,8 @@ class Runtime extends EventEmitter { * This causes the peripheral connection modal to switch to an error state. * @const {string} */ - static get PERIPHERAL_REQUEST_ERROR() { - return "PERIPHERAL_REQUEST_ERROR"; + static get PERIPHERAL_REQUEST_ERROR () { + return 'PERIPHERAL_REQUEST_ERROR'; } /** @@ -658,8 +658,8 @@ class Runtime extends EventEmitter { * This causes a 'peripheral connection lost' error alert to display. * @const {string} */ - static get PERIPHERAL_CONNECTION_LOST_ERROR() { - return "PERIPHERAL_CONNECTION_LOST_ERROR"; + static get PERIPHERAL_CONNECTION_LOST_ERROR () { + return 'PERIPHERAL_CONNECTION_LOST_ERROR'; } /** @@ -667,61 +667,61 @@ class Runtime extends EventEmitter { * This causes the peripheral connection modal to show a timeout state. * @const {string} */ - static get PERIPHERAL_SCAN_TIMEOUT() { - return "PERIPHERAL_SCAN_TIMEOUT"; + static get PERIPHERAL_SCAN_TIMEOUT () { + return 'PERIPHERAL_SCAN_TIMEOUT'; } /** * Event name to indicate that the microphone is being used to stream audio. * @const {string} */ - static get MIC_LISTENING() { - return "MIC_LISTENING"; + static get MIC_LISTENING () { + return 'MIC_LISTENING'; } /** * Event name for reporting that blocksInfo was updated. * @const {string} */ - static get BLOCKSINFO_UPDATE() { - return "BLOCKSINFO_UPDATE"; + static get BLOCKSINFO_UPDATE () { + return 'BLOCKSINFO_UPDATE'; } /** * Event name when the runtime tick loop has been started. * @const {string} */ - static get RUNTIME_STARTED() { - return "RUNTIME_STARTED"; + static get RUNTIME_STARTED () { + return 'RUNTIME_STARTED'; } /** * Event name when the runtime dispose has been called. * @const {string} */ - static get RUNTIME_DISPOSED() { - return "RUNTIME_DISPOSED"; + static get RUNTIME_DISPOSED () { + return 'RUNTIME_DISPOSED'; } /** * Event name for reporting that a block was updated and needs to be rerendered. * @const {string} */ - static get BLOCKS_NEED_UPDATE() { - return "BLOCKS_NEED_UPDATE"; + static get BLOCKS_NEED_UPDATE () { + return 'BLOCKS_NEED_UPDATE'; } /** * How rapidly we try to step threads by default, in ms. */ - static get THREAD_STEP_INTERVAL() { + static get THREAD_STEP_INTERVAL () { return 1000 / 60; } /** * In compatibility mode, how rapidly we try to step threads, in ms. */ - static get THREAD_STEP_INTERVAL_COMPATIBILITY() { + static get THREAD_STEP_INTERVAL_COMPATIBILITY () { return 1000 / 30; } @@ -729,7 +729,7 @@ class Runtime extends EventEmitter { * How many clones can be created at a time. * @const {number} */ - static get MAX_CLONES() { + static get MAX_CLONES () { return 300; } @@ -737,7 +737,7 @@ class Runtime extends EventEmitter { // ----------------------------------------------------------------------------- // Helper function for initializing the addCloudVariable function - _initializeAddCloudVariable(newCloudDataManager) { + _initializeAddCloudVariable (newCloudDataManager) { // The addCloudVariable function return () => { const hadCloudVarsBefore = this.hasCloudData(); @@ -749,7 +749,7 @@ class Runtime extends EventEmitter { } // Helper function for initializing the removeCloudVariable function - _initializeRemoveCloudVariable(newCloudDataManager) { + _initializeRemoveCloudVariable (newCloudDataManager) { return () => { const hadCloudVarsBefore = this.hasCloudData(); newCloudDataManager.removeCloudVariable(); @@ -764,7 +764,7 @@ class Runtime extends EventEmitter { * @todo Prefix opcodes with package name. * @private */ - _registerBlockPackages() { + _registerBlockPackages () { for (const packageName in defaultBlockPackages) { if ( Object.prototype.hasOwnProperty.call( @@ -817,7 +817,7 @@ class Runtime extends EventEmitter { } } - getMonitorState() { + getMonitorState () { return this._monitorState; } @@ -828,7 +828,7 @@ class Runtime extends EventEmitter { * @returns {string} - the constructed ID. * @private */ - _makeExtensionMenuId(menuName, extensionId) { + _makeExtensionMenuId (menuName, extensionId) { return `${extensionId}_menu_${xmlEscape(menuName)}`; } @@ -837,13 +837,13 @@ class Runtime extends EventEmitter { * @param {Target} [target] - the target to use as context. If a target is not provided, default to the current * editing target or the stage. */ - makeMessageContextForTarget(target) { + makeMessageContextForTarget (target) { const context = {}; target = target || this.getEditingTarget() || this.getTargetForStage(); if (target) { - context.targetType = target.isStage - ? TargetType.STAGE - : TargetType.SPRITE; + context.targetType = target.isStage ? + TargetType.STAGE : + TargetType.SPRITE; } } @@ -852,13 +852,13 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - information about the extension (id, blocks, etc.) * @private */ - _registerExtensionPrimitives(extensionInfo) { + _registerExtensionPrimitives (extensionInfo) { const categoryInfo = { id: extensionInfo.id, name: maybeFormatMessage(extensionInfo.name), showStatusButton: extensionInfo.showStatusButton, blockIconURI: extensionInfo.blockIconURI, - menuIconURI: extensionInfo.menuIconURI, + menuIconURI: extensionInfo.menuIconURI }; if (extensionInfo.color1) { @@ -888,7 +888,7 @@ class Runtime extends EventEmitter { // Emit events for custom field types from extension this.emit(Runtime.EXTENSION_FIELD_ADDED, { name: `field_${fieldTypeInfo.extendedName}`, - implementation: fieldTypeInfo.fieldImplementation, + implementation: fieldTypeInfo.fieldImplementation }); } } @@ -901,9 +901,9 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - new info (results of running getInfo) for an extension * @private */ - _refreshExtensionPrimitives(extensionInfo) { + _refreshExtensionPrimitives (extensionInfo) { const categoryInfo = this._blockInfo.find( - (info) => info.id === extensionInfo.id + info => info.id === extensionInfo.id ); if (categoryInfo) { categoryInfo.name = maybeFormatMessage(extensionInfo.name); @@ -920,7 +920,7 @@ class Runtime extends EventEmitter { * @param {ExtensionMetadata} extensionInfo - the extension metadata to read * @private */ - _fillExtensionCategory(categoryInfo, extensionInfo) { + _fillExtensionCategory (categoryInfo, extensionInfo) { categoryInfo.blocks = []; categoryInfo.customFieldTypes = {}; categoryInfo.menus = []; @@ -981,14 +981,14 @@ class Runtime extends EventEmitter { this._hats[opcode] = { edgeActivated: blockInfo.isEdgeActivated, restartExistingThreads: - blockInfo.shouldRestartExistingThreads, + blockInfo.shouldRestartExistingThreads }; } } } catch (e) { - log.error("Error parsing block: ", { + log.error('Error parsing block: ', { block: blockInfo, - error: e, + error: e }); } } @@ -1001,29 +1001,29 @@ class Runtime extends EventEmitter { * @returns {object} - an array of 2 element arrays or the original input function * @private */ - _convertMenuItems(menuItems) { - if (typeof menuItems !== "function") { + _convertMenuItems (menuItems) { + if (typeof menuItems !== 'function') { const extensionMessageContext = this.makeMessageContextForTarget(); - return menuItems.map((item) => { + return menuItems.map(item => { const formattedItem = maybeFormatMessage( item, extensionMessageContext ); switch (typeof formattedItem) { - case "string": - return [formattedItem, formattedItem]; - case "object": - return [ - maybeFormatMessage( - item.text, - extensionMessageContext - ), - item.value, - ]; - default: - throw new Error( - `Can't interpret menu item: ${JSON.stringify(item)}` - ); + case 'string': + return [formattedItem, formattedItem]; + case 'object': + return [ + maybeFormatMessage( + item.text, + extensionMessageContext + ), + item.value + ]; + default: + throw new Error( + `Can't interpret menu item: ${JSON.stringify(item)}` + ); } }); } @@ -1040,31 +1040,31 @@ class Runtime extends EventEmitter { * @returns {object} - a JSON-esque object ready for scratch-blocks' consumption * @private */ - _buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo) { + _buildMenuForScratchBlocks (menuName, menuInfo, categoryInfo) { const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id); const menuItems = this._convertMenuItems(menuInfo.items); return { json: { - message0: "%1", + message0: '%1', type: menuId, inputsInline: true, - output: "String", + output: 'String', style: categoryInfo.id, - outputShape: menuInfo.acceptReporters - ? ScratchBlocksConstants.OUTPUT_SHAPE_ROUND - : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, + outputShape: menuInfo.acceptReporters ? + ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : + ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE, args0: [ { - type: "field_dropdown", + type: 'field_dropdown', name: menuName, - options: menuItems, - }, - ], - }, + options: menuItems + } + ] + } }; } - _buildCustomFieldInfo(fieldName, fieldInfo, extensionId, categoryInfo) { + _buildCustomFieldInfo (fieldName, fieldInfo, extensionId, categoryInfo) { const extendedName = `${extensionId}_${fieldName}`; return { fieldName: fieldName, @@ -1072,8 +1072,8 @@ class Runtime extends EventEmitter { argumentTypeInfo: { shadow: { type: extendedName, - fieldName: `field_${extendedName}`, - }, + fieldName: `field_${extendedName}` + } }, scratchBlocksDefinition: this._buildCustomFieldTypeForScratchBlocks( extendedName, @@ -1081,7 +1081,7 @@ class Runtime extends EventEmitter { fieldInfo.outputShape, categoryInfo ), - fieldImplementation: fieldInfo.implementation, + fieldImplementation: fieldInfo.implementation }; } @@ -1094,7 +1094,7 @@ class Runtime extends EventEmitter { * @param {object} categoryInfo - The category the field belongs to (Used to set its colors) * @returns {object} - Object to be inserted into scratch-blocks */ - _buildCustomFieldTypeForScratchBlocks( + _buildCustomFieldTypeForScratchBlocks ( fieldName, output, outputShape, @@ -1103,7 +1103,7 @@ class Runtime extends EventEmitter { return { json: { type: fieldName, - message0: "%1", + message0: '%1', inputsInline: true, output: output, style: categoryInfo.id, @@ -1111,10 +1111,10 @@ class Runtime extends EventEmitter { args0: [ { name: `field_${fieldName}`, - type: `field_${fieldName}`, - }, - ], - }, + type: `field_${fieldName}` + } + ] + } }; } @@ -1125,8 +1125,8 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertForScratchBlocks(blockInfo, categoryInfo) { - if (blockInfo === "---") { + _convertForScratchBlocks (blockInfo, categoryInfo) { + if (blockInfo === '---') { return this._convertSeparatorForScratchBlocks(blockInfo); } @@ -1144,7 +1144,7 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertBlockForScratchBlocks(blockInfo, categoryInfo) { + _convertBlockForScratchBlocks (blockInfo, categoryInfo) { const extendedOpcode = `${categoryInfo.id}_${blockInfo.opcode}`; const blockJSON = { @@ -1152,7 +1152,7 @@ class Runtime extends EventEmitter { inputsInline: true, category: categoryInfo.name, style: categoryInfo.id, - extensions: [], + extensions: [] }; const context = { // TODO: store this somewhere so that we can map args appropriately after translation. @@ -1163,7 +1163,7 @@ class Runtime extends EventEmitter { blockJSON, categoryInfo, blockInfo, - inputList: [], + inputList: [] }; // If an icon for the extension exists, prepend it to each block, with a vertical separator. @@ -1172,70 +1172,70 @@ class Runtime extends EventEmitter { const iconURI = blockInfo.blockIconURI || categoryInfo.blockIconURI; if (iconURI) { - blockJSON.extensions.push("scratch_extension"); - blockJSON.message0 = "%1 %2"; + blockJSON.extensions.push('scratch_extension'); + blockJSON.message0 = '%1 %2'; const iconJSON = { - type: "field_image", + type: 'field_image', src: iconURI, width: 40, - height: 40, + height: 40 }; const separatorJSON = { - type: "field_vertical_separator", + type: 'field_vertical_separator' }; blockJSON.args0 = [iconJSON, separatorJSON]; } switch (blockInfo.blockType) { - case BlockType.COMMAND: - blockJSON.outputShape = + case BlockType.COMMAND: + blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.previousStatement = null; // null = available connection; undefined = hat - if (!blockInfo.isTerminal) { - blockJSON.nextStatement = null; // null = available connection; undefined = terminal - } - break; - case BlockType.REPORTER: - blockJSON.output = "String"; // TODO: distinguish number & string here? - blockJSON.outputShape = + blockJSON.previousStatement = null; // null = available connection; undefined = hat + if (!blockInfo.isTerminal) { + blockJSON.nextStatement = null; // null = available connection; undefined = terminal + } + break; + case BlockType.REPORTER: + blockJSON.output = 'String'; // TODO: distinguish number & string here? + blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_ROUND; - break; - case BlockType.BOOLEAN: - blockJSON.output = "Boolean"; - blockJSON.outputShape = + break; + case BlockType.BOOLEAN: + blockJSON.output = 'Boolean'; + blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL; - break; - case BlockType.HAT: - case BlockType.EVENT: - if ( - !Object.prototype.hasOwnProperty.call( - blockInfo, - "isEdgeActivated" - ) - ) { - // if absent, this property defaults to true - blockInfo.isEdgeActivated = true; - } - blockJSON.outputShape = + break; + case BlockType.HAT: + case BlockType.EVENT: + if ( + !Object.prototype.hasOwnProperty.call( + blockInfo, + 'isEdgeActivated' + ) + ) { + // if absent, this property defaults to true + blockInfo.isEdgeActivated = true; + } + blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.nextStatement = null; // null = available connection; undefined = terminal - blockJSON.extensions.push("shape_hat"); - break; - case BlockType.CONDITIONAL: - case BlockType.LOOP: - blockInfo.branchCount = blockInfo.branchCount || 1; - blockJSON.outputShape = + blockJSON.nextStatement = null; // null = available connection; undefined = terminal + blockJSON.extensions.push('shape_hat'); + break; + case BlockType.CONDITIONAL: + case BlockType.LOOP: + blockInfo.branchCount = blockInfo.branchCount || 1; + blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE; - blockJSON.previousStatement = null; // null = available connection; undefined = hat - if (!blockInfo.isTerminal) { - blockJSON.nextStatement = null; // null = available connection; undefined = terminal - } - break; + blockJSON.previousStatement = null; // null = available connection; undefined = hat + if (!blockInfo.isTerminal) { + blockJSON.nextStatement = null; // null = available connection; undefined = terminal + } + break; } - const blockText = Array.isArray(blockInfo.text) - ? blockInfo.text - : [blockInfo.text]; + const blockText = Array.isArray(blockInfo.text) ? + blockInfo.text : + [blockInfo.text]; let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum] let inBranchNum = 0; // how many branches have we placed into the JSON so far? let outLineNum = 0; // used for scratch-blocks `message${outLineNum}` and `args${outLineNum}` @@ -1269,14 +1269,14 @@ class Runtime extends EventEmitter { ++outLineNum; } if (inBranchNum < blockInfo.branchCount) { - blockJSON[`message${outLineNum}`] = "%1"; + blockJSON[`message${outLineNum}`] = '%1'; blockJSON[`args${outLineNum}`] = [ { - type: "input_statement", + type: 'input_statement', name: `SUBSTACK${ - inBranchNum > 0 ? inBranchNum + 1 : "" - }`, - }, + inBranchNum > 0 ? inBranchNum + 1 : '' + }` + } ]; ++inBranchNum; ++outLineNum; @@ -1285,35 +1285,35 @@ class Runtime extends EventEmitter { if (blockInfo.blockType === BlockType.REPORTER) { if (!blockInfo.disableMonitor && context.inputList.length === 0) { - blockJSON.extensions.push("monitor_block"); + blockJSON.extensions.push('monitor_block'); } } else if (blockInfo.blockType === BlockType.LOOP) { // Add icon to the bottom right of a loop block - blockJSON[`lastDummyAlign${outLineNum}`] = "RIGHT"; - blockJSON[`message${outLineNum}`] = "%1"; + blockJSON[`lastDummyAlign${outLineNum}`] = 'RIGHT'; + blockJSON[`message${outLineNum}`] = '%1'; blockJSON[`args${outLineNum}`] = [ { - type: "field_image", - src: "./static/blocks-media/repeat.svg", // TODO: use a constant or make this configurable? + type: 'field_image', + src: './static/blocks-media/repeat.svg', // TODO: use a constant or make this configurable? width: 24, height: 24, - alt: "*", // TODO remove this since we don't use collapsed blocks in scratch - flip_rtl: true, - }, + alt: '*', // TODO remove this since we don't use collapsed blocks in scratch + flip_rtl: true + } ]; ++outLineNum; } - const mutation = blockInfo.isDynamic - ? `` - : ""; - const inputs = context.inputList.join(""); + const mutation = blockInfo.isDynamic ? + `` : + ''; + const inputs = context.inputList.join(''); const blockXML = `${mutation}${inputs}`; return { info: context.blockInfo, json: context.blockJSON, - xml: blockXML, + xml: blockXML }; } @@ -1324,10 +1324,10 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original block information * @private */ - _convertSeparatorForScratchBlocks(blockInfo) { + _convertSeparatorForScratchBlocks (blockInfo) { return { info: blockInfo, - xml: '', + xml: '' }; } @@ -1339,12 +1339,12 @@ class Runtime extends EventEmitter { * @returns {ConvertedBlockInfo} - the converted & original button information * @private */ - _convertButtonForScratchBlocks(buttonInfo) { + _convertButtonForScratchBlocks (buttonInfo) { // for now we only support these pre-defined callbacks handled in scratch-blocks const supportedCallbackKeys = [ - "MAKE_A_LIST", - "MAKE_A_PROCEDURE", - "MAKE_A_VARIABLE", + 'MAKE_A_LIST', + 'MAKE_A_PROCEDURE', + 'MAKE_A_VARIABLE' ]; if (supportedCallbackKeys.indexOf(buttonInfo.func) < 0) { log.error( @@ -1359,7 +1359,7 @@ class Runtime extends EventEmitter { ); return { info: buttonInfo, - xml: ``, + xml: `` }; } @@ -1369,22 +1369,22 @@ class Runtime extends EventEmitter { * @return {object} JSON blob for a scratch-blocks image field. * @private */ - _constructInlineImageJson(argInfo) { + _constructInlineImageJson (argInfo) { if (!argInfo.dataURI) { log.warn( - "Missing data URI in extension block with argument type IMAGE" + 'Missing data URI in extension block with argument type IMAGE' ); } return { - type: "field_image", - src: argInfo.dataURI || "", + type: 'field_image', + src: argInfo.dataURI || '', // TODO these probably shouldn't be hardcoded...? width: 24, height: 24, // Whether or not the inline image should be flipped horizontally // in RTL languages. Defaults to false, indicating that the // image will not be flipped. - flip_rtl: argInfo.flipRTL || false, + flip_rtl: argInfo.flipRTL || false }; } @@ -1397,9 +1397,9 @@ class Runtime extends EventEmitter { * @return {string} scratch-blocks placeholder for the argument: '%1'. * @private */ - _convertPlaceholders(context, match, placeholder) { + _convertPlaceholders (context, match, placeholder) { // Sanitize the placeholder to ensure valid XML - placeholder = placeholder.replace(/[<"&]/, "_"); + placeholder = placeholder.replace(/[<"&]/, '_'); // Determine whether the argument type is one of the known standard field types const argInfo = context.blockInfo.arguments[placeholder] || {}; @@ -1421,26 +1421,26 @@ class Runtime extends EventEmitter { // Most field types are inputs (slots on the block that can have other blocks plugged into them) // check if this is not one of those cases. E.g. an inline image on a block. - if (argTypeInfo.fieldType === "field_image") { + if (argTypeInfo.fieldType === 'field_image') { argJSON = this._constructInlineImageJson(argInfo); } else { // Construct input value // Layout a block argument (e.g. an input slot on the block) argJSON = { - type: "input_value", - name: placeholder, + type: 'input_value', + name: placeholder }; const defaultValue = - typeof argInfo.defaultValue === "undefined" - ? "" - : xmlEscape( - maybeFormatMessage( - argInfo.defaultValue, - this.makeMessageContextForTarget() - ).toString() - ); + typeof argInfo.defaultValue === 'undefined' ? + '' : + xmlEscape( + maybeFormatMessage( + argInfo.defaultValue, + this.makeMessageContextForTarget() + ).toString() + ); if (argTypeInfo.check) { // Right now the only type of 'check' we have specifies that the @@ -1462,7 +1462,7 @@ class Runtime extends EventEmitter { ); fieldName = argInfo.menu; } else { - argJSON.type = "field_dropdown"; + argJSON.type = 'field_dropdown'; argJSON.options = this._convertMenuItems(menuInfo.items); valueName = null; shadowType = null; @@ -1497,11 +1497,11 @@ class Runtime extends EventEmitter { } if (shadowType) { - context.inputList.push(""); + context.inputList.push(''); } if (valueName) { - context.inputList.push(""); + context.inputList.push(''); } } @@ -1521,12 +1521,12 @@ class Runtime extends EventEmitter { * @property {string} id - the category / extension ID * @property {string} xml - the XML text for this category, starting with `` and ending with `` */ - getBlocksXML(target) { - return this._blockInfo.map((categoryInfo) => { - const { name, color1, color2 } = categoryInfo; + getBlocksXML (target) { + return this._blockInfo.map(categoryInfo => { + const {name, color1, color2} = categoryInfo; // Filter out blocks that aren't supposed to be shown on this target, as determined by the block info's // `hideFromPalette` and `filter` properties. - const paletteBlocks = categoryInfo.blocks.filter((block) => { + const paletteBlocks = categoryInfo.blocks.filter(block => { let blockFilterIncludesTarget = true; // If an editing target is not passed, include all blocks // If the block info doesn't include a `filter` property, always include it @@ -1543,15 +1543,15 @@ class Runtime extends EventEmitter { // Use a menu icon if there is one. Otherwise, use the block icon. If there's no icon, // the category menu will show its default colored circle. - let menuIconURI = ""; + let menuIconURI = ''; if (categoryInfo.menuIconURI) { menuIconURI = categoryInfo.menuIconURI; } else if (categoryInfo.blockIconURI) { menuIconURI = categoryInfo.blockIconURI; } - const menuIconXML = menuIconURI ? `iconURI="${menuIconURI}"` : ""; + const menuIconXML = menuIconURI ? `iconURI="${menuIconURI}"` : ''; - let statusButtonXML = ""; + let statusButtonXML = ''; if (categoryInfo.showStatusButton) { statusButtonXML = 'showStatusButton="true"'; } @@ -1561,8 +1561,8 @@ class Runtime extends EventEmitter { xml: `${paletteBlocks - .map((block) => block.xml) - .join("")}`, + .map(block => block.xml) + .join('')}` }; }); } @@ -1570,11 +1570,11 @@ class Runtime extends EventEmitter { /** * @returns {Array.} - an array containing the scratch-blocks JSON information for each dynamic block. */ - getBlocksJSON() { + getBlocksJSON () { return this._blockInfo.reduce( (result, categoryInfo) => result.concat( - categoryInfo.blocks.map((blockInfo) => blockInfo.json) + categoryInfo.blocks.map(blockInfo => blockInfo.json) ), [] ); @@ -1583,34 +1583,34 @@ class Runtime extends EventEmitter { /** * One-time initialization for Scratch Link support. */ - _initScratchLink() { + _initScratchLink () { // Check that we're actually in a real browser, not Node.js or JSDOM, and we have a valid-looking origin. // note that `if (self?....)` will throw if `self` is undefined, so check for that first! if ( - typeof self !== "undefined" && - typeof document !== "undefined" && + typeof self !== 'undefined' && + typeof document !== 'undefined' && document.getElementById && self.origin && - self.origin !== "null" && // note this is a string comparison, not a null check + self.origin !== 'null' && // note this is a string comparison, not a null check self.navigator && self.navigator.userAgent && !( - self.navigator.userAgent.includes("Node.js") || - self.navigator.userAgent.includes("jsdom") + self.navigator.userAgent.includes('Node.js') || + self.navigator.userAgent.includes('jsdom') ) ) { // Create a script tag for the Scratch Link browser extension, unless one already exists const scriptElement = document.getElementById( - "scratch-link-extension-script" + 'scratch-link-extension-script' ); if (!scriptElement) { - const script = document.createElement("script"); - script.id = "scratch-link-extension-script"; + const script = document.createElement('script'); + script.id = 'scratch-link-extension-script'; document.body.appendChild(script); // Tell the browser extension to inject its script. // If the extension isn't present or isn't active, this will do nothing. - self.postMessage("inject-scratch-link-script", self.origin); + self.postMessage('inject-scratch-link-script', self.origin); } } } @@ -1620,7 +1620,7 @@ class Runtime extends EventEmitter { * @param {string} type Either BLE or BT * @returns {ScratchLinkSocket} The scratch link socket. */ - getScratchLinkSocket(type) { + getScratchLinkSocket (type) { const factory = this._linkSocketFactory || this._defaultScratchLinkSocketFactory; return factory(type); @@ -1631,7 +1631,7 @@ class Runtime extends EventEmitter { * either BT or BLE. * @param {Function} factory The new factory for creating ScratchLink sockets. */ - configureScratchLinkSocketFactory(factory) { + configureScratchLinkSocketFactory (factory) { this._linkSocketFactory = factory; } @@ -1640,7 +1640,7 @@ class Runtime extends EventEmitter { * @param {string} type Either BLE or BT * @returns {ScratchLinkSocket} The new scratch link socket (a WebSocket object) */ - _defaultScratchLinkSocketFactory(type) { + _defaultScratchLinkSocketFactory (type) { const Scratch = self.Scratch; const ScratchLinkSafariSocket = Scratch && Scratch.ScratchLinkSafariSocket; @@ -1648,9 +1648,9 @@ class Runtime extends EventEmitter { const useSafariSocket = ScratchLinkSafariSocket && ScratchLinkSafariSocket.isSafariHelperCompatible(); - return useSafariSocket - ? new ScratchLinkSafariSocket(type) - : new ScratchLinkWebSocket(type); + return useSafariSocket ? + new ScratchLinkSafariSocket(type) : + new ScratchLinkWebSocket(type); } /** @@ -1659,7 +1659,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @param {object} extension - the extension to register. */ - registerPeripheralExtension(extensionId, extension) { + registerPeripheralExtension (extensionId, extension) { this.peripheralExtensions[extensionId] = extension; } @@ -1667,7 +1667,7 @@ class Runtime extends EventEmitter { * Tell the specified extension to scan for a peripheral. * @param {string} extensionId - the id of the extension. */ - scanForPeripheral(extensionId) { + scanForPeripheral (extensionId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].scan(); } @@ -1678,7 +1678,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @param {number} peripheralId - the id of the peripheral. */ - connectPeripheral(extensionId, peripheralId) { + connectPeripheral (extensionId, peripheralId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].connect(peripheralId); } @@ -1688,7 +1688,7 @@ class Runtime extends EventEmitter { * Disconnect from the extension's connected peripheral. * @param {string} extensionId - the id of the extension. */ - disconnectPeripheral(extensionId) { + disconnectPeripheral (extensionId) { if (this.peripheralExtensions[extensionId]) { this.peripheralExtensions[extensionId].disconnect(); } @@ -1699,7 +1699,7 @@ class Runtime extends EventEmitter { * @param {string} extensionId - the id of the extension. * @return {boolean} - whether the extension has a connected peripheral. */ - getPeripheralIsConnected(extensionId) { + getPeripheralIsConnected (extensionId) { let isConnected = false; if (this.peripheralExtensions[extensionId]) { isConnected = this.peripheralExtensions[extensionId].isConnected(); @@ -1711,7 +1711,7 @@ class Runtime extends EventEmitter { * Emit an event to indicate that the microphone is being used to stream audio. * @param {boolean} listening - true if the microphone is currently listening. */ - emitMicListening(listening) { + emitMicListening (listening) { this.emit(Runtime.MIC_LISTENING, listening); } @@ -1720,7 +1720,7 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {Function} The function which implements the opcode. */ - getOpcodeFunction(opcode) { + getOpcodeFunction (opcode) { return this._primitives[opcode]; } @@ -1729,7 +1729,7 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {boolean} True if the op is known to be a hat. */ - getIsHat(opcode) { + getIsHat (opcode) { return Object.prototype.hasOwnProperty.call(this._hats, opcode); } @@ -1738,7 +1738,7 @@ class Runtime extends EventEmitter { * @param {!string} opcode The opcode to look up. * @return {boolean} True if the op is known to be a edge-activated hat. */ - getIsEdgeActivatedHat(opcode) { + getIsEdgeActivatedHat (opcode) { return ( Object.prototype.hasOwnProperty.call(this._hats, opcode) && this._hats[opcode].edgeActivated @@ -1749,7 +1749,7 @@ class Runtime extends EventEmitter { * Attach the audio engine * @param {!AudioEngine} audioEngine The audio engine to attach */ - attachAudioEngine(audioEngine) { + attachAudioEngine (audioEngine) { this.audioEngine = audioEngine; } @@ -1757,7 +1757,7 @@ class Runtime extends EventEmitter { * Attach the renderer * @param {!RenderWebGL} renderer The renderer to attach */ - attachRenderer(renderer) { + attachRenderer (renderer) { this.renderer = renderer; this.renderer.setLayerGroupOrdering(StageLayering.LAYER_GROUPS); } @@ -1767,7 +1767,7 @@ class Runtime extends EventEmitter { * bitmaps to scratch 3 bitmaps. (Scratch 3 bitmaps are all bitmap resolution 2) * @param {!function} bitmapAdapter The adapter to attach */ - attachV2BitmapAdapter(bitmapAdapter) { + attachV2BitmapAdapter (bitmapAdapter) { this.v2BitmapAdapter = bitmapAdapter; } @@ -1775,7 +1775,7 @@ class Runtime extends EventEmitter { * Attach the storage module * @param {!ScratchStorage} storage The storage module to attach */ - attachStorage(storage) { + attachStorage (storage) { this.storage = storage; fetchWithTimeout.setFetch(storage.scratchFetch.scratchFetch); this.resetRunId(); @@ -1793,14 +1793,14 @@ class Runtime extends EventEmitter { * @param {?boolean} opts.updateMonitor true if the script should update a monitor value * @return {!Thread} The newly created thread. */ - _pushThread(id, target, opts) { + _pushThread (id, target, opts) { const thread = new Thread(id); thread.target = target; thread.stackClick = Boolean(opts && opts.stackClick); thread.updateMonitor = Boolean(opts && opts.updateMonitor); - thread.blockContainer = thread.updateMonitor - ? this.monitorBlocks - : target.blocks; + thread.blockContainer = thread.updateMonitor ? + this.monitorBlocks : + target.blocks; thread.pushStack(id); this.threads.push(thread); @@ -1811,7 +1811,7 @@ class Runtime extends EventEmitter { * Stop a thread: stop running it immediately, and remove it from the thread list later. * @param {!Thread} thread Thread object to remove from actives */ - _stopThread(thread) { + _stopThread (thread) { // Mark the thread for later removal thread.isKilled = true; // Inform sequencer to stop executing that thread. @@ -1825,7 +1825,7 @@ class Runtime extends EventEmitter { * @param {!Thread} thread Thread object to restart. * @return {Thread} The restarted thread. */ - _restartThread(thread) { + _restartThread (thread) { const newThread = new Thread(thread.topBlock); newThread.target = thread.target; newThread.stackClick = thread.stackClick; @@ -1846,7 +1846,7 @@ class Runtime extends EventEmitter { * @param {?Thread} thread Thread object to check. * @return {boolean} True if the thread is active/running. */ - isActiveThread(thread) { + isActiveThread (thread) { return ( thread.stack.length > 0 && thread.status !== Thread.STATUS_DONE && @@ -1859,7 +1859,7 @@ class Runtime extends EventEmitter { * @param {?Thread} thread Thread object to check. * @return {boolean} True if the thread is waiting */ - isWaitingThread(thread) { + isWaitingThread (thread) { return ( thread.status === Thread.STATUS_PROMISE_WAIT || thread.status === Thread.STATUS_YIELD_TICK || @@ -1875,11 +1875,11 @@ class Runtime extends EventEmitter { * @param {?boolean} opts.stackClick true if the user activated the stack by clicking, false if not. This * determines whether we show a visual report when turning on the script. */ - toggleScript(topBlockId, opts) { + toggleScript (topBlockId, opts) { opts = Object.assign( { target: this._editingTarget, - stackClick: false, + stackClick: false }, opts ); @@ -1916,7 +1916,7 @@ class Runtime extends EventEmitter { * @param {!string} topBlockId ID of block that starts the script. * @param {?Target} optTarget target Target to run script on. If not supplied, uses editing target. */ - addMonitorScript(topBlockId, optTarget) { + addMonitorScript (topBlockId, optTarget) { if (!optTarget) optTarget = this._editingTarget; for (let i = 0; i < this.threads.length; i++) { // Don't re-add the script if it's already running @@ -1929,7 +1929,7 @@ class Runtime extends EventEmitter { } } // Otherwise add it. - this._pushThread(topBlockId, optTarget, { updateMonitor: true }); + this._pushThread(topBlockId, optTarget, {updateMonitor: true}); } /** @@ -1940,7 +1940,7 @@ class Runtime extends EventEmitter { * @param {!Function} f Function to call for each script. * @param {Target=} optTarget Optionally, a target to restrict to. */ - allScriptsDo(f, optTarget) { + allScriptsDo (f, optTarget) { let targets = this.executableTargets; if (optTarget) { targets = [optTarget]; @@ -1955,7 +1955,7 @@ class Runtime extends EventEmitter { } } - allScriptsByOpcodeDo(opcode, f, optTarget) { + allScriptsByOpcodeDo (opcode, f, optTarget) { let targets = this.executableTargets; if (optTarget) { targets = [optTarget]; @@ -1979,7 +1979,7 @@ class Runtime extends EventEmitter { * @param {Target=} optTarget Optionally, a target to restrict to. * @return {Array.} List of threads started by this function. */ - startHats(requestedHatOpcode, optMatchFields, optTarget) { + startHats (requestedHatOpcode, optMatchFields, optTarget) { if ( !Object.prototype.hasOwnProperty.call( this._hats, @@ -1995,8 +1995,9 @@ class Runtime extends EventEmitter { const hatMeta = instance._hats[requestedHatOpcode]; for (const opts in optMatchFields) { - if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) + if (!Object.prototype.hasOwnProperty.call(optMatchFields, opts)) { continue; + } optMatchFields[opts] = optMatchFields[opts].toUpperCase(); } @@ -2004,7 +2005,7 @@ class Runtime extends EventEmitter { this.allScriptsByOpcodeDo( requestedHatOpcode, (script, target) => { - const { blockId: topBlockId, fieldsOfInputs: hatFields } = + const {blockId: topBlockId, fieldsOfInputs: hatFields} = script; // Match any requested fields. @@ -2061,7 +2062,7 @@ class Runtime extends EventEmitter { ); // For compatibility with Scratch 2, edge triggered hats need to be processed before // threads are stepped. See ScratchRuntime.as for original implementation - newThreads.forEach((thread) => { + newThreads.forEach(thread => { execute(this.sequencer, thread); thread.goToNextBlock(); }); @@ -2071,10 +2072,10 @@ class Runtime extends EventEmitter { /** * Dispose all targets. Return to clean state. */ - dispose() { + dispose () { this.stopAll(); // Deleting each target's variable's monitors. - this.targets.forEach((target) => { + this.targets.forEach(target => { if (target.isOriginal) target.deleteMonitors(); }); @@ -2111,7 +2112,7 @@ class Runtime extends EventEmitter { * into the correct execution order after calling this function. * @param {Target} target target to add */ - addTarget(target) { + addTarget (target) { this.targets.push(target); this.executableTargets.push(target); } @@ -2126,7 +2127,7 @@ class Runtime extends EventEmitter { * @param {number} delta number of positions to move target by * @returns {number} new position in execution order */ - moveExecutable(executableTarget, delta) { + moveExecutable (executableTarget, delta) { const oldIndex = this.executableTargets.indexOf(executableTarget); this.executableTargets.splice(oldIndex, 1); let newIndex = oldIndex + delta; @@ -2157,7 +2158,7 @@ class Runtime extends EventEmitter { * @param {number} newIndex position in execution order to place the target * @returns {number} new position in the execution order */ - setExecutablePosition(executableTarget, newIndex) { + setExecutablePosition (executableTarget, newIndex) { const oldIndex = this.executableTargets.indexOf(executableTarget); return this.moveExecutable(executableTarget, newIndex - oldIndex); } @@ -2166,7 +2167,7 @@ class Runtime extends EventEmitter { * Remove a target from the execution set. * @param {Target} executableTarget target to remove */ - removeExecutable(executableTarget) { + removeExecutable (executableTarget) { const oldIndex = this.executableTargets.indexOf(executableTarget); if (oldIndex > -1) { this.executableTargets.splice(oldIndex, 1); @@ -2177,8 +2178,8 @@ class Runtime extends EventEmitter { * Dispose of a target. * @param {!Target} disposingTarget Target to dispose of. */ - disposeTarget(disposingTarget) { - this.targets = this.targets.filter((target) => { + disposeTarget (disposingTarget) { + this.targets = this.targets.filter(target => { if (disposingTarget !== target) return true; // Allow target to do dispose actions. target.dispose(); @@ -2192,7 +2193,7 @@ class Runtime extends EventEmitter { * @param {!Target} target Target to stop threads for. * @param {Thread=} optThreadException Optional thread to skip. */ - stopForTarget(target, optThreadException) { + stopForTarget (target, optThreadException) { // Emit stop event to allow blocks to clean up any state. this.emit(Runtime.STOP_FOR_TARGET, target, optThreadException); @@ -2210,7 +2211,7 @@ class Runtime extends EventEmitter { /** * Reset the Run ID. Call this any time the project logically starts, stops, or changes identity. */ - resetRunId() { + resetRunId () { if (!this.storage) { // see also: attachStorage return; @@ -2226,22 +2227,22 @@ class Runtime extends EventEmitter { /** * Start all threads that start with the green flag. */ - greenFlag() { + greenFlag () { this.stopAll(); this.emit(Runtime.PROJECT_START); this.ioDevices.clock.resetProjectTimer(); - this.targets.forEach((target) => target.clearEdgeActivatedValues()); + this.targets.forEach(target => target.clearEdgeActivatedValues()); // Inform all targets of the green flag. for (let i = 0; i < this.targets.length; i++) { this.targets[i].onGreenFlag(); } - this.startHats("event_whenflagclicked"); + this.startHats('event_whenflagclicked'); } /** * Stop "everything." */ - stopAll() { + stopAll () { // Emit stop event to allow blocks to clean up any state. this.emit(Runtime.PROJECT_STOP_ALL); @@ -2252,7 +2253,7 @@ class Runtime extends EventEmitter { if ( Object.prototype.hasOwnProperty.call( this.targets[i], - "isOriginal" + 'isOriginal' ) && !this.targets[i].isOriginal ) { @@ -2276,21 +2277,22 @@ class Runtime extends EventEmitter { * Repeatedly run `sequencer.stepThreads` and filter out * inactive threads after each iteration. */ - _step() { + _step () { if (this.profiler !== null) { if (stepProfilerId === -1) { - stepProfilerId = this.profiler.idByName("Runtime._step"); + stepProfilerId = this.profiler.idByName('Runtime._step'); } this.profiler.start(stepProfilerId); } // Clean up threads that were told to stop during or since the last step - this.threads = this.threads.filter((thread) => !thread.isKilled); + this.threads = this.threads.filter(thread => !thread.isKilled); // Find all edge-activated hats, and add them to threads to be evaluated. for (const hatType in this._hats) { - if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) + if (!Object.prototype.hasOwnProperty.call(this._hats, hatType)) { continue; + } const hat = this._hats[hatType]; if (hat.edgeActivated) { this.startHats(hatType); @@ -2301,7 +2303,7 @@ class Runtime extends EventEmitter { if (this.profiler !== null) { if (stepThreadsProfilerId === -1) { stepThreadsProfilerId = this.profiler.idByName( - "Sequencer.stepThreads" + 'Sequencer.stepThreads' ); } this.profiler.start(stepThreadsProfilerId); @@ -2326,7 +2328,7 @@ class Runtime extends EventEmitter { if (this.profiler !== null) { if (rendererDrawProfilerId === -1) { rendererDrawProfilerId = - this.profiler.idByName("RenderWebGL.draw"); + this.profiler.idByName('RenderWebGL.draw'); } this.profiler.start(rendererDrawProfilerId); } @@ -2361,9 +2363,9 @@ class Runtime extends EventEmitter { * @param {!Array.} threads The set of threads to look through. * @return {number} The number of monitor threads in threads. */ - _getMonitorThreadCount(threads) { + _getMonitorThreadCount (threads) { let count = 0; - threads.forEach((thread) => { + threads.forEach(thread => { if (thread.updateMonitor) count++; }); return count; @@ -2372,7 +2374,7 @@ class Runtime extends EventEmitter { /** * Queue monitor blocks to sequencer to be run. */ - _pushMonitors() { + _pushMonitors () { this.monitorBlocks.runAllMonitored(this); } @@ -2380,7 +2382,7 @@ class Runtime extends EventEmitter { * Set the current editing target known by the runtime. * @param {!Target} editingTarget New editing target. */ - setEditingTarget(editingTarget) { + setEditingTarget (editingTarget) { const oldEditingTarget = this._editingTarget; this._editingTarget = editingTarget; // Script glows must be cleared. @@ -2396,7 +2398,7 @@ class Runtime extends EventEmitter { * Set whether we are in 30 TPS compatibility mode. * @param {boolean} compatibilityModeOn True iff in compatibility mode. */ - setCompatibilityMode(compatibilityModeOn) { + setCompatibilityMode (compatibilityModeOn) { this.compatibilityMode = compatibilityModeOn; if (this._steppingInterval) { clearInterval(this._steppingInterval); @@ -2410,7 +2412,7 @@ class Runtime extends EventEmitter { * Looks at `this.threads` and notices which have turned on/off new glows. * @param {Array.=} optExtraThreads Optional list of inactive threads. */ - _updateGlows(optExtraThreads) { + _updateGlows (optExtraThreads) { const searchThreads = []; searchThreads.push(...this.threads); if (optExtraThreads) { @@ -2468,7 +2470,7 @@ class Runtime extends EventEmitter { * * @param {number} nonMonitorThreadCount The new nonMonitorThreadCount */ - _emitProjectRunStatus(nonMonitorThreadCount) { + _emitProjectRunStatus (nonMonitorThreadCount) { if (this._nonMonitorThreadCount === 0 && nonMonitorThreadCount > 0) { this.emit(Runtime.PROJECT_RUN_START); } @@ -2484,7 +2486,7 @@ class Runtime extends EventEmitter { * still be tracking glow data about it. * @param {!string} scriptBlockId Id of top-level block in script to quiet. */ - quietGlow(scriptBlockId) { + quietGlow (scriptBlockId) { const index = this._scriptGlowsPreviousFrame.indexOf(scriptBlockId); if (index > -1) { this._scriptGlowsPreviousFrame.splice(index, 1); @@ -2496,11 +2498,11 @@ class Runtime extends EventEmitter { * @param {?string} blockId ID for the block to update glow * @param {boolean} isGlowing True to turn on glow; false to turn off. */ - glowBlock(blockId, isGlowing) { + glowBlock (blockId, isGlowing) { if (isGlowing) { - this.emit(Runtime.BLOCK_GLOW_ON, { id: blockId }); + this.emit(Runtime.BLOCK_GLOW_ON, {id: blockId}); } else { - this.emit(Runtime.BLOCK_GLOW_OFF, { id: blockId }); + this.emit(Runtime.BLOCK_GLOW_OFF, {id: blockId}); } } @@ -2509,11 +2511,11 @@ class Runtime extends EventEmitter { * @param {?string} topBlockId ID for the top block to update glow * @param {boolean} isGlowing True to turn on glow; false to turn off. */ - glowScript(topBlockId, isGlowing) { + glowScript (topBlockId, isGlowing) { if (isGlowing) { - this.emit(Runtime.SCRIPT_GLOW_ON, { id: topBlockId }); + this.emit(Runtime.SCRIPT_GLOW_ON, {id: topBlockId}); } else { - this.emit(Runtime.SCRIPT_GLOW_OFF, { id: topBlockId }); + this.emit(Runtime.SCRIPT_GLOW_OFF, {id: topBlockId}); } } @@ -2521,7 +2523,7 @@ class Runtime extends EventEmitter { * Emit whether blocks are being dragged over gui * @param {boolean} areBlocksOverGui True if blocks are dragged out of blocks workspace, false otherwise */ - emitBlockDragUpdate(areBlocksOverGui) { + emitBlockDragUpdate (areBlocksOverGui) { this.emit(Runtime.BLOCK_DRAG_UPDATE, areBlocksOverGui); } @@ -2530,7 +2532,7 @@ class Runtime extends EventEmitter { * @param {Array.} blocks The set of blocks dragged to the GUI * @param {string} topBlockId The original id of the top block being dragged */ - emitBlockEndDrag(blocks, topBlockId) { + emitBlockEndDrag (blocks, topBlockId) { this.emit(Runtime.BLOCK_DRAG_END, blocks, topBlockId); } @@ -2539,8 +2541,8 @@ class Runtime extends EventEmitter { * @param {string} blockId ID for the block. * @param {string} value Value to show associated with the block. */ - visualReport(blockId, value) { - this.emit(Runtime.VISUAL_REPORT, { id: blockId, value: String(value) }); + visualReport (blockId, value) { + this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value)}); } /** @@ -2548,8 +2550,8 @@ class Runtime extends EventEmitter { * updates those properties that are defined in the given monitor record. * @param {!MonitorRecord} monitor Monitor to add. */ - requestAddMonitor(monitor) { - const id = monitor.get("id"); + requestAddMonitor (monitor) { + const id = monitor.get('id'); if (!this.requestUpdateMonitor(monitor)) { // update monitor if it exists in the state // if the monitor did not exist in the state, add it @@ -2564,15 +2566,15 @@ class Runtime extends EventEmitter { * the old monitor will keep its old value. * @return {boolean} true if monitor exists in the state and was updated, false if it did not exist. */ - requestUpdateMonitor(monitor) { - const id = monitor.get("id"); + requestUpdateMonitor (monitor) { + const id = monitor.get('id'); if (this._monitorState.has(id)) { this._monitorState = // Use mergeWith here to prevent undefined values from overwriting existing ones this._monitorState.set( id, this._monitorState.get(id).mergeWith((prev, next) => { - if (typeof next === "undefined" || next === null) { + if (typeof next === 'undefined' || next === null) { return prev; } return next; @@ -2588,7 +2590,7 @@ class Runtime extends EventEmitter { * not exist in the state. * @param {!string} monitorId ID of the monitor to remove. */ - requestRemoveMonitor(monitorId) { + requestRemoveMonitor (monitorId) { this._monitorState = this._monitorState.delete(monitorId); } @@ -2597,11 +2599,11 @@ class Runtime extends EventEmitter { * @param {!string} monitorId ID of the monitor to hide. * @return {boolean} true if monitor exists and was updated, false otherwise */ - requestHideMonitor(monitorId) { + requestHideMonitor (monitorId) { return this.requestUpdateMonitor( new Map([ - ["id", monitorId], - ["visible", false], + ['id', monitorId], + ['visible', false] ]) ); } @@ -2612,11 +2614,11 @@ class Runtime extends EventEmitter { * @param {!string} monitorId ID of the monitor to show. * @return {boolean} true if monitor exists and was updated, false otherwise */ - requestShowMonitor(monitorId) { + requestShowMonitor (monitorId) { return this.requestUpdateMonitor( new Map([ - ["id", monitorId], - ["visible", true], + ['id', monitorId], + ['visible', true] ]) ); } @@ -2626,9 +2628,9 @@ class Runtime extends EventEmitter { * the monitor already does not exist in the state. * @param {!string} targetId Remove all monitors with given target ID. */ - requestRemoveMonitorByTargetId(targetId) { + requestRemoveMonitorByTargetId (targetId) { this._monitorState = this._monitorState.filterNot( - (value) => value.targetId === targetId + value => value.targetId === targetId ); } @@ -2637,7 +2639,7 @@ class Runtime extends EventEmitter { * @param {string} targetId Id of target to find. * @return {?Target} The target, if found. */ - getTargetById(targetId) { + getTargetById (targetId) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.id === targetId) { @@ -2651,7 +2653,7 @@ class Runtime extends EventEmitter { * @param {string} spriteName Name of sprite to look for. * @return {?Target} Target representing a sprite of the given name. */ - getSpriteTargetByName(spriteName) { + getSpriteTargetByName (spriteName) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.isStage) { @@ -2668,7 +2670,7 @@ class Runtime extends EventEmitter { * @param {number} drawableID drawable id of target to find * @return {?Target} The target, if found */ - getTargetByDrawableId(drawableID) { + getTargetByDrawableId (drawableID) { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.drawableID === drawableID) return target; @@ -2679,7 +2681,7 @@ class Runtime extends EventEmitter { * Update the clone counter to track how many clones are created. * @param {number} changeAmount How many clones have been created/destroyed. */ - changeCloneCounter(changeAmount) { + changeCloneCounter (changeAmount) { this._cloneCounter += changeAmount; } @@ -2687,14 +2689,14 @@ class Runtime extends EventEmitter { * Return whether there are clones available. * @return {boolean} True until the number of clones hits Runtime.MAX_CLONES. */ - clonesAvailable() { + clonesAvailable () { return this._cloneCounter < Runtime.MAX_CLONES; } /** * Handle that the project has loaded in the Virtual Machine. */ - handleProjectLoaded() { + handleProjectLoaded () { this.emit(Runtime.PROJECT_LOADED); this.resetRunId(); } @@ -2702,7 +2704,7 @@ class Runtime extends EventEmitter { /** * Report that the project has changed in a way that would affect serialization */ - emitProjectChanged() { + emitProjectChanged () { this.emit(Runtime.PROJECT_CHANGED); } @@ -2712,8 +2714,8 @@ class Runtime extends EventEmitter { * @param {Target} [sourceTarget] - the target used as a source for the new clone, if any. * @fires Runtime#targetWasCreated */ - fireTargetWasCreated(newTarget, sourceTarget) { - this.emit("targetWasCreated", newTarget, sourceTarget); + fireTargetWasCreated (newTarget, sourceTarget) { + this.emit('targetWasCreated', newTarget, sourceTarget); } /** @@ -2721,15 +2723,15 @@ class Runtime extends EventEmitter { * @param {Target} target - the target being removed * @fires Runtime#targetWasRemoved */ - fireTargetWasRemoved(target) { - this.emit("targetWasRemoved", target); + fireTargetWasRemoved (target) { + this.emit('targetWasRemoved', target); } /** * Get a target representing the Scratch stage, if one exists. * @return {?Target} The target, if found. */ - getTargetForStage() { + getTargetForStage () { for (let i = 0; i < this.targets.length; i++) { const target = this.targets[i]; if (target.isStage) { @@ -2742,11 +2744,11 @@ class Runtime extends EventEmitter { * Get the editing target. * @return {?Target} The editing target. */ - getEditingTarget() { + getEditingTarget () { return this._editingTarget; } - getAllVarNamesOfType(varType) { + getAllVarNamesOfType (varType) { let varNames = []; for (const target of this.targets) { const targetVarNames = target.getAllVariableNamesInScopeByType( @@ -2766,20 +2768,20 @@ class Runtime extends EventEmitter { * @property {Function} [labelFn] - function to generate the label for this opcode * @property {string} [label] - the label for this opcode if `labelFn` is absent */ - getLabelForOpcode(extendedOpcode) { - const [category, opcode] = StringUtil.splitFirst(extendedOpcode, "_"); + getLabelForOpcode (extendedOpcode) { + const [category, opcode] = StringUtil.splitFirst(extendedOpcode, '_'); if (!(category && opcode)) return; - const categoryInfo = this._blockInfo.find((ci) => ci.id === category); + const categoryInfo = this._blockInfo.find(ci => ci.id === category); if (!categoryInfo) return; - const block = categoryInfo.blocks.find((b) => b.info.opcode === opcode); + const block = categoryInfo.blocks.find(b => b.info.opcode === opcode); if (!block) return; // TODO: we may want to format the label in a locale-specific way. return { - category: "extension", // This assumes that all extensions have the same monitor color. - label: `${categoryInfo.name}: ${block.info.text}`, + category: 'extension', // This assumes that all extensions have the same monitor color. + label: `${categoryInfo.name}: ${block.info.text}` }; } @@ -2792,9 +2794,9 @@ class Runtime extends EventEmitter { * @param {string} optVarType The type of the variable to create. Defaults to Variable.SCALAR_TYPE. * @return {Variable} The new variable that was created. */ - createNewGlobalVariable(variableName, optVarId, optVarType) { + createNewGlobalVariable (variableName, optVarId, optVarType) { const varType = - typeof optVarType === "string" ? optVarType : Variable.SCALAR_TYPE; + typeof optVarType === 'string' ? optVarType : Variable.SCALAR_TYPE; const allVariableNames = this.getAllVarNamesOfType(varType); const newName = StringUtil.unusedName(variableName, allVariableNames); const variable = new Variable(optVarId || uid(), newName, varType); @@ -2807,7 +2809,7 @@ class Runtime extends EventEmitter { * Tell the runtime to request a redraw. * Use after a clone/sprite has completed some visible operation on the stage. */ - requestRedraw() { + requestRedraw () { this.redrawRequested = true; } @@ -2816,7 +2818,7 @@ class Runtime extends EventEmitter { * the original sprite * @param {!Target} target Target requesting the targets update */ - requestTargetsUpdate(target) { + requestTargetsUpdate (target) { if (!target.isOriginal) return; this._refreshTargets = true; } @@ -2824,21 +2826,21 @@ class Runtime extends EventEmitter { /** * Emit an event that indicates that the blocks on the workspace need updating. */ - requestBlocksUpdate() { + requestBlocksUpdate () { this.emit(Runtime.BLOCKS_NEED_UPDATE); } /** * Emit an event that indicates that the toolbox extension blocks need updating. */ - requestToolboxExtensionsUpdate() { + requestToolboxExtensionsUpdate () { this.emit(Runtime.TOOLBOX_EXTENSIONS_NEED_UPDATE); } /** * Set up timers to repeatedly step in a browser. */ - start() { + start () { // Do not start if we are already running if (this._steppingInterval) return; @@ -2857,7 +2859,7 @@ class Runtime extends EventEmitter { * Quit the Runtime, clearing any handles which might keep the process alive. * Do not use the runtime after calling this method. This method is meant for test shutdown. */ - quit() { + quit () { clearInterval(this._steppingInterval); this._steppingInterval = null; } @@ -2867,7 +2869,7 @@ class Runtime extends EventEmitter { * @param {Profiler/FrameCallback} onFrame A callback handle passed a * profiling frame when the profiler reports its collected data. */ - enableProfiling(onFrame) { + enableProfiling (onFrame) { if (Profiler.available()) { this.profiler = new Profiler(onFrame); } @@ -2876,7 +2878,7 @@ class Runtime extends EventEmitter { /** * Turn off profiling. */ - disableProfiling() { + disableProfiling () { this.profiler = null; } @@ -2885,7 +2887,7 @@ class Runtime extends EventEmitter { * This value is helpful in certain instances for compatibility with Scratch 2, * which sometimes uses a `currentMSecs` timestamp value in Interpreter.as */ - updateCurrentMSecs() { + updateCurrentMSecs () { this.currentMSecs = Date.now(); } } From 9136cd0b530e2255f221b31df398a13bb2b50af2 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:29:25 -0800 Subject: [PATCH 11/76] style: copy style changes from gonfunko/modern-blockly --- packages/scratch-vm/src/engine/blocks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scratch-vm/src/engine/blocks.js b/packages/scratch-vm/src/engine/blocks.js index 6b646dc1b7..881b345d6d 100644 --- a/packages/scratch-vm/src/engine/blocks.js +++ b/packages/scratch-vm/src/engine/blocks.js @@ -770,12 +770,12 @@ class Blocks { ) { // This block has an argument which needs to get separated out into // multiple monitor blocks with ids based on the selected argument + // Note: we're not just constantly creating a longer and longer id everytime we check + // the checkbox because we're using the id of the block in the flyout as the base const newId = getMonitorIdForBlockWithArgs( block.id, block.fields ); - // Note: we're not just constantly creating a longer and longer id everytime we check - // the checkbox because we're using the id of the block in the flyout as the base // check if a block with the new id already exists, otherwise create let newBlock = this.runtime.monitorBlocks.getBlock(newId); From e8e96034bf05fea59f8ef7dcaea6287694e3a7c5 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:22:05 -0700 Subject: [PATCH 12/76] ci: enable alpha and beta releases --- packages/scratch-vm/release.config.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/scratch-vm/release.config.js b/packages/scratch-vm/release.config.js index 87af40a04c..1276a673d0 100644 --- a/packages/scratch-vm/release.config.js +++ b/packages/scratch-vm/release.config.js @@ -5,6 +5,14 @@ module.exports = { name: 'develop' // default channel }, + { + name: 'alpha', + prerelease: true + }, + { + name: 'beta', + prerelease: true + }, { name: 'hotfix/*', channel: 'hotfix' From 8263b70edf13a615e02f1c1aae795c99bd23a088 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:33:18 -0700 Subject: [PATCH 13/76] fix: use scratch-blocks@^2.0.0-beta --- package-lock.json | 333 +++++++++++++++++++++++++- packages/scratch-vm/package.json | 2 +- packages/scratch-vm/webpack.config.js | 2 +- 3 files changed, 334 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d30d96494a..744f464452 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2037,6 +2037,30 @@ "node": ">=6.9.0" } }, + "node_modules/@blockly/continuous-toolbox": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-6.0.12.tgz", + "integrity": "sha512-I2jsxN/f+wytrzUQkSrxOKLWHPgsjiIz/w0Tpdo9PcDB5mwoBnG8l0ldh5Yj55CRMZbY/q9tANPWR8V8acgMIw==", + "dev": true, + "engines": { + "node": ">=8.17.0" + }, + "peerDependencies": { + "blockly": "^11.0.0" + } + }, + "node_modules/@blockly/field-colour": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-5.0.12.tgz", + "integrity": "sha512-vNw6L/B0cpf+j0S6pShX31bOI16KJu+eACpsfHGOBZbb7+LT3bYKcGHe6+VRe+KtIE3jGlY7vYfnaJdOCrYlfQ==", + "dev": true, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "blockly": "^11.0.0" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -8760,6 +8784,283 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "node_modules/blockly": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-11.2.1.tgz", + "integrity": "sha512-20sCwSwX2Z6UxR/er0B5y6wRFukuIdvOjc7jMuIwyCO/yT35+UbAqYueMga3JFA9NoWPwQc+3s6/XnLkyceAww==", + "dev": true, + "dependencies": { + "jsdom": "25.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dev": true, + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true + }, + "node_modules/blockly/node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/blockly/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockly/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/blockly/node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/blockly/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/blockly/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/blockly/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/blockly/node_modules/tough-cookie": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", + "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "dev": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/blockly/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/blockly/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/blockly/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -35590,6 +35891,24 @@ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" }, + "node_modules/tldts": { + "version": "6.1.77", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.77.tgz", + "integrity": "sha512-lBpoWgy+kYmuXWQ83+R7LlJCnsd9YW8DGpZSHhrMl4b8Ly/1vzOie3OdtmUJDkKxcgRGOehDu5btKkty+JEe+g==", + "dev": true, + "dependencies": { + "tldts-core": "^6.1.77" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.77", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.77.tgz", + "integrity": "sha512-bCaqm24FPk8OgBkM0u/SrEWJgHnhBWYqeBo6yUmcZJDCHt/IfyWBb+14CXdGi4RInMv4v7eUAin15W0DoA+Ytg==", + "dev": true + }, "node_modules/tmp": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz", @@ -40370,7 +40689,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "1.1.206", + "scratch-blocks": "^2.0.0-beta", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", @@ -41322,6 +41641,18 @@ "node": ">= 4" } }, + "packages/scratch-vm/node_modules/scratch-blocks": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-beta.2.tgz", + "integrity": "sha512-j/2F1/tIGoPMeQHVIDXzEh21fdQTwhLDSCv9Qo8J0wIq+v2EYipHMbV3QvQloheBaXdTpLsn9SJnUjOmiICFUA==", + "deprecated": "Moved Blockly unforking test to 'spork' channel", + "dev": true, + "dependencies": { + "@blockly/continuous-toolbox": "^6.0.9", + "@blockly/field-colour": "^5.0.9", + "blockly": "^11.0.0" + } + }, "packages/scratch-vm/node_modules/selfsigned": { "version": "1.10.14", "dev": true, diff --git a/packages/scratch-vm/package.json b/packages/scratch-vm/package.json index 64eb736c0a..627d6e0fa0 100644 --- a/packages/scratch-vm/package.json +++ b/packages/scratch-vm/package.json @@ -91,7 +91,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "1.1.206", + "scratch-blocks": "^2.0.0-beta", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", diff --git a/packages/scratch-vm/webpack.config.js b/packages/scratch-vm/webpack.config.js index 82e7b787b1..81d841845e 100644 --- a/packages/scratch-vm/webpack.config.js +++ b/packages/scratch-vm/webpack.config.js @@ -81,7 +81,7 @@ const playgroundBuilder = webBuilder.clone() } }) .addModuleRule({ - test: require.resolve('scratch-blocks/dist/vertical.js'), + test: require.resolve('scratch-blocks/dist/main.js'), loader: 'expose-loader', options: { exposes: 'Blockly' From 36652adaba293649313171eceee02dfc1bd50876 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:52:33 -0700 Subject: [PATCH 14/76] ci: make temporary "spork" release channel --- packages/scratch-vm/release.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/scratch-vm/release.config.js b/packages/scratch-vm/release.config.js index 1276a673d0..1becc7a098 100644 --- a/packages/scratch-vm/release.config.js +++ b/packages/scratch-vm/release.config.js @@ -13,6 +13,10 @@ module.exports = { name: 'beta', prerelease: true }, + { + name: 'spork', + prerelease: true + }, { name: 'hotfix/*', channel: 'hotfix' From 603b8526a55558a69b8b8f9d6d131753238e3519 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:58:57 -0700 Subject: [PATCH 15/76] chore(deps): update deps for spork test --- package-lock.json | 9 ++++----- packages/scratch-vm/package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 744f464452..b67914d451 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40689,7 +40689,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "^2.0.0-beta", + "scratch-blocks": "2.0.0-spork.1", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", @@ -41642,10 +41642,9 @@ } }, "packages/scratch-vm/node_modules/scratch-blocks": { - "version": "2.0.0-beta.2", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-beta.2.tgz", - "integrity": "sha512-j/2F1/tIGoPMeQHVIDXzEh21fdQTwhLDSCv9Qo8J0wIq+v2EYipHMbV3QvQloheBaXdTpLsn9SJnUjOmiICFUA==", - "deprecated": "Moved Blockly unforking test to 'spork' channel", + "version": "2.0.0-spork.1", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.1.tgz", + "integrity": "sha512-kA9T2rg1UZk6JVKwxZoPCz8uOZBhZ0eLi7nnFnZ4VZzzfItjCQQpdgPzbeBycEuEzlp3TIE2zP1G3jpthpkW0g==", "dev": true, "dependencies": { "@blockly/continuous-toolbox": "^6.0.9", diff --git a/packages/scratch-vm/package.json b/packages/scratch-vm/package.json index 627d6e0fa0..1e6f9c5b72 100644 --- a/packages/scratch-vm/package.json +++ b/packages/scratch-vm/package.json @@ -91,7 +91,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "^2.0.0-beta", + "scratch-blocks": "2.0.0-spork.1", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", From f6339954aecf43ce94bf876b4f66c5840809800e Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 14 Nov 2024 11:05:45 -0800 Subject: [PATCH 16/76] fix: Fix test failures. (#10) --- packages/scratch-vm/test/fixtures/events.json | 2 +- .../test/integration/stack-click.js | 3 +- .../test/unit/extension_conversion.js | 10 ++-- .../test/unit/project_changed_state_blocks.js | 48 +++++++++---------- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/packages/scratch-vm/test/fixtures/events.json b/packages/scratch-vm/test/fixtures/events.json index a6b42cf818..c1534e67be 100644 --- a/packages/scratch-vm/test/fixtures/events.json +++ b/packages/scratch-vm/test/fixtures/events.json @@ -89,7 +89,7 @@ "name": "comment", "type": "comment_create", "commentId": "a comment", - "xy": {"x": 10, "y": 20} + "json": { "x": 10, "y": 20 } }, "mockVariableBlock": { "name": "block", diff --git a/packages/scratch-vm/test/integration/stack-click.js b/packages/scratch-vm/test/integration/stack-click.js index a6911064ff..b36dcc5582 100644 --- a/packages/scratch-vm/test/integration/stack-click.js +++ b/packages/scratch-vm/test/integration/stack-click.js @@ -44,7 +44,8 @@ test('stack click activates the stack', t => { if (allBlocks[blockId].opcode === 'event_whengreaterthan') { blockContainer.blocklyListen({ blockId: blockId, - element: 'stackclick' + targetType: 'block', + type: 'click' }); } } diff --git a/packages/scratch-vm/test/unit/extension_conversion.js b/packages/scratch-vm/test/unit/extension_conversion.js index 876b01a62c..d2ec0fb3b5 100644 --- a/packages/scratch-vm/test/unit/extension_conversion.js +++ b/packages/scratch-vm/test/unit/extension_conversion.js @@ -125,9 +125,7 @@ const extensionInfoWithCustomFieldTypes = { const testCategoryInfo = function (t, block) { t.equal(block.json.category, 'fake test extension'); - t.equal(block.json.colour, '#111111'); - t.equal(block.json.colourSecondary, '#222222'); - t.equal(block.json.colourTertiary, '#333333'); + t.equal(block.json.style, 'test'); t.equal(block.json.inputsInline, true); }; @@ -139,12 +137,11 @@ const testButton = function (t, button) { const testReporter = function (t, reporter) { t.equal(reporter.json.type, 'test_reporter'); testCategoryInfo(t, reporter); - t.equal(reporter.json.checkboxInFlyout, true); t.equal(reporter.json.outputShape, ScratchBlocksConstants.OUTPUT_SHAPE_ROUND); t.equal(reporter.json.output, 'String'); t.notOk(Object.prototype.hasOwnProperty.call(reporter.json, 'previousStatement')); t.notOk(Object.prototype.hasOwnProperty.call(reporter.json, 'nextStatement')); - t.same(reporter.json.extensions, ['scratch_extension']); + t.same(reporter.json.extensions, ['scratch_extension', 'monitor_block']); t.equal(reporter.json.message0, '%1 %2simple text'); // "%1 %2" from the block icon t.notOk(Object.prototype.hasOwnProperty.call(reporter.json, 'message1')); t.same(reporter.json.args0, [ @@ -167,12 +164,11 @@ const testReporter = function (t, reporter) { const testInlineImage = function (t, inlineImage) { t.equal(inlineImage.json.type, 'test_inlineImage'); testCategoryInfo(t, inlineImage); - t.equal(inlineImage.json.checkboxInFlyout, true); t.equal(inlineImage.json.outputShape, ScratchBlocksConstants.OUTPUT_SHAPE_ROUND); t.equal(inlineImage.json.output, 'String'); t.notOk(Object.prototype.hasOwnProperty.call(inlineImage.json, 'previousStatement')); t.notOk(Object.prototype.hasOwnProperty.call(inlineImage.json, 'nextStatement')); - t.notOk(inlineImage.json.extensions && inlineImage.json.extensions.length); // OK if it's absent or empty + t.same(inlineImage.json.extensions, ['monitor_block']); t.equal(inlineImage.json.message0, 'text and %1'); // block text followed by inline image t.notOk(Object.prototype.hasOwnProperty.call(inlineImage.json, 'message1')); t.same(inlineImage.json.args0, [ diff --git a/packages/scratch-vm/test/unit/project_changed_state_blocks.js b/packages/scratch-vm/test/unit/project_changed_state_blocks.js index 9685cdd298..18b0dc0677 100644 --- a/packages/scratch-vm/test/unit/project_changed_state_blocks.js +++ b/packages/scratch-vm/test/unit/project_changed_state_blocks.js @@ -255,11 +255,11 @@ test('Creating a block comment should emit a project changed event', t => { type: 'comment_create', blockId: 'a new block', commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' @@ -274,11 +274,11 @@ test('Creating a workspace comment should emit a project changed event', t => { type: 'comment_create', blockId: null, commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' @@ -293,11 +293,11 @@ test('Changing a comment should emit a project changed event', t => { type: 'comment_create', blockId: null, commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' @@ -343,11 +343,11 @@ test('Deleting a block comment should emit a project changed event', t => { type: 'comment_create', blockId: 'a new block', commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' @@ -378,11 +378,11 @@ test('Deleting a workspace comment should emit a project changed event', t => { type: 'comment_create', blockId: null, commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' @@ -432,11 +432,11 @@ test('Moving a comment should emit a project changed event', t => { type: 'comment_create', blockId: null, commentId: 'a new comment', - height: 250, - width: 400, - xy: { + json: { x: -40, - y: 27 + y: 27, + height: 250, + width: 400 }, minimized: false, text: 'comment' From c07586b713d7cd7da32ca6bf0cdac07052a52f5f Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:39:15 -0800 Subject: [PATCH 17/76] chore(deps): update deps for spork test --- package-lock.json | 310 ++++++++++++++++++++++++++++++- packages/scratch-vm/package.json | 2 +- 2 files changed, 305 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index b67914d451..f15fceb9b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8789,6 +8789,7 @@ "resolved": "https://registry.npmjs.org/blockly/-/blockly-11.2.1.tgz", "integrity": "sha512-20sCwSwX2Z6UxR/er0B5y6wRFukuIdvOjc7jMuIwyCO/yT35+UbAqYueMga3JFA9NoWPwQc+3s6/XnLkyceAww==", "dev": true, + "peer": true, "dependencies": { "jsdom": "25.0.1" }, @@ -8801,6 +8802,7 @@ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", "dev": true, + "peer": true, "dependencies": { "@asamuzakjp/css-color": "^2.8.2", "rrweb-cssom": "^0.8.0" @@ -8813,13 +8815,15 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/blockly/node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, + "peer": true, "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" @@ -8833,6 +8837,7 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -8845,6 +8850,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dev": true, + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -8860,6 +8866,7 @@ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, + "peer": true, "dependencies": { "whatwg-encoding": "^3.1.1" }, @@ -8872,6 +8879,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8884,6 +8892,7 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, + "peer": true, "dependencies": { "cssstyle": "^4.1.0", "data-urls": "^5.0.0", @@ -8924,6 +8933,7 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "dev": true, + "peer": true, "dependencies": { "entities": "^4.5.0" }, @@ -8936,6 +8946,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -8945,6 +8956,7 @@ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", "dev": true, + "peer": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -8957,6 +8969,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", "dev": true, + "peer": true, "dependencies": { "tldts": "^6.1.32" }, @@ -8969,6 +8982,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", "dev": true, + "peer": true, "dependencies": { "punycode": "^2.3.1" }, @@ -8981,6 +8995,7 @@ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, + "peer": true, "dependencies": { "xml-name-validator": "^5.0.0" }, @@ -8993,6 +9008,7 @@ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, + "peer": true, "engines": { "node": ">=12" } @@ -9002,6 +9018,7 @@ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, + "peer": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -9014,6 +9031,7 @@ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, + "peer": true, "engines": { "node": ">=18" } @@ -9023,6 +9041,7 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", "dev": true, + "peer": true, "dependencies": { "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" @@ -9036,6 +9055,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -9057,6 +9077,7 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, + "peer": true, "engines": { "node": ">=18" } @@ -40689,7 +40710,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "2.0.0-spork.1", + "scratch-blocks": "2.0.0-spork.3", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", @@ -40837,6 +40858,18 @@ "node": ">=0.10.0" } }, + "packages/scratch-vm/node_modules/blockly": { + "version": "12.0.0-beta.1", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-12.0.0-beta.1.tgz", + "integrity": "sha512-lECwZ4K+YuLXMM0yxWTz1lwkmDl424sst7h/dhtSefuCki8afjI/F87byYK/ZIZsMKBEz2+8wEJ1Wlx5cYWIAg==", + "dev": true, + "dependencies": { + "jsdom": "25.0.1" + }, + "engines": { + "node": ">=18" + } + }, "packages/scratch-vm/node_modules/braces": { "version": "2.3.2", "dev": true, @@ -40991,6 +41024,38 @@ "node": ">= 4" } }, + "packages/scratch-vm/node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dev": true, + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/scratch-vm/node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true + }, + "packages/scratch-vm/node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/scratch-vm/node_modules/define-property": { "version": "2.0.2", "dev": true, @@ -41074,6 +41139,18 @@ "dev": true, "license": "MIT" }, + "packages/scratch-vm/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "packages/scratch-vm/node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -41195,6 +41272,21 @@ "node": ">=4" } }, + "packages/scratch-vm/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "packages/scratch-vm/node_modules/glob-parent": { "version": "3.1.0", "dev": true, @@ -41239,6 +41331,18 @@ "node": ">=4" } }, + "packages/scratch-vm/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, "packages/scratch-vm/node_modules/html-entities": { "version": "1.4.0", "dev": true, @@ -41258,6 +41362,18 @@ "node": ">=4.0.0" } }, + "packages/scratch-vm/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "packages/scratch-vm/node_modules/ignore": { "version": "3.3.10", "dev": true, @@ -41353,6 +41469,67 @@ "dev": true, "license": "MIT" }, + "packages/scratch-vm/node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dev": true, + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "packages/scratch-vm/node_modules/jsdom/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "packages/scratch-vm/node_modules/json-schema-traverse": { "version": "0.4.1", "dev": true, @@ -41521,6 +41698,18 @@ "node": ">=4" } }, + "packages/scratch-vm/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "packages/scratch-vm/node_modules/path-exists": { "version": "3.0.0", "dev": true, @@ -41551,6 +41740,15 @@ "node": ">=4" } }, + "packages/scratch-vm/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "packages/scratch-vm/node_modules/readable-stream": { "version": "2.3.8", "dev": true, @@ -41628,6 +41826,18 @@ "dev": true, "license": "MIT" }, + "packages/scratch-vm/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "packages/scratch-vm/node_modules/schema-utils": { "version": "1.0.0", "dev": true, @@ -41642,14 +41852,14 @@ } }, "packages/scratch-vm/node_modules/scratch-blocks": { - "version": "2.0.0-spork.1", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.1.tgz", - "integrity": "sha512-kA9T2rg1UZk6JVKwxZoPCz8uOZBhZ0eLi7nnFnZ4VZzzfItjCQQpdgPzbeBycEuEzlp3TIE2zP1G3jpthpkW0g==", + "version": "2.0.0-spork.3", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.3.tgz", + "integrity": "sha512-luy2QtACBjhHT2rH2Zcvwevjb9zBnMWGwLnp9ydEXzbcFTptmYxFTmC8iFRPm6szxGiCw1pH48Qr3BwTGRKp8Q==", "dev": true, "dependencies": { "@blockly/continuous-toolbox": "^6.0.9", "@blockly/field-colour": "^5.0.9", - "blockly": "^11.0.0" + "blockly": "^12.0.0-beta.0" } }, "packages/scratch-vm/node_modules/selfsigned": { @@ -41763,6 +41973,51 @@ "node": ">=0.10.0" } }, + "packages/scratch-vm/node_modules/tough-cookie": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", + "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "dev": true, + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "packages/scratch-vm/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dev": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "packages/scratch-vm/node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/scratch-vm/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "packages/scratch-vm/node_modules/webpack-cli": { "version": "4.10.0", "dev": true, @@ -41963,6 +42218,40 @@ "node": ">=6" } }, + "packages/scratch-vm/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "packages/scratch-vm/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "packages/scratch-vm/node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "dev": true, + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "packages/scratch-vm/node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -42001,6 +42290,15 @@ "node": ">=6" } }, + "packages/scratch-vm/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "packages/scratch-vm/node_modules/y18n": { "version": "4.0.3", "dev": true, diff --git a/packages/scratch-vm/package.json b/packages/scratch-vm/package.json index 1e6f9c5b72..85d7d2f664 100644 --- a/packages/scratch-vm/package.json +++ b/packages/scratch-vm/package.json @@ -91,7 +91,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "2.0.0-spork.1", + "scratch-blocks": "2.0.0-spork.3", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", From baf68a236c85bdadf5d1a61dfbcf22edc06d4b8c Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:47:58 -0800 Subject: [PATCH 18/76] build: fix webpack-dev-server configuration The playgrounds themselves are still broken, but it's a step... --- packages/scratch-vm/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scratch-vm/webpack.config.js b/packages/scratch-vm/webpack.config.js index 81d841845e..d073541905 100644 --- a/packages/scratch-vm/webpack.config.js +++ b/packages/scratch-vm/webpack.config.js @@ -128,7 +128,7 @@ const playgroundBuilder = webBuilder.clone() ])); module.exports = [ + playgroundBuilder.get(), // webpack-dev-server only looks at the first configuration nodeBuilder.get(), - webBuilder.get(), - playgroundBuilder.get() + webBuilder.get() ]; From f0f1c14b2e7091eb210c661d35dca4004a679776 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:27:06 -0800 Subject: [PATCH 19/76] chore(deps): update deps for spork test --- package-lock.json | 282 ++++++++++++++++++++----------- packages/scratch-vm/package.json | 2 +- 2 files changed, 181 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index f15fceb9b8..6ec88501af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2037,28 +2037,16 @@ "node": ">=6.9.0" } }, - "node_modules/@blockly/continuous-toolbox": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-6.0.12.tgz", - "integrity": "sha512-I2jsxN/f+wytrzUQkSrxOKLWHPgsjiIz/w0Tpdo9PcDB5mwoBnG8l0ldh5Yj55CRMZbY/q9tANPWR8V8acgMIw==", - "dev": true, - "engines": { - "node": ">=8.17.0" - }, - "peerDependencies": { - "blockly": "^11.0.0" - } - }, "node_modules/@blockly/field-colour": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-5.0.12.tgz", - "integrity": "sha512-vNw6L/B0cpf+j0S6pShX31bOI16KJu+eACpsfHGOBZbb7+LT3bYKcGHe6+VRe+KtIE3jGlY7vYfnaJdOCrYlfQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-4.0.4.tgz", + "integrity": "sha512-FUXnlReiyejaaT+tkweW386dFbj9mSHjUCVtHgTP6cP5Wbvh6vJHQmnFxlPUNLsHPDGtri8j+yRKxGH97UkJvA==", "dev": true, "engines": { "node": ">=8.0.0" }, "peerDependencies": { - "blockly": "^11.0.0" + "blockly": "^10.4.3" } }, "node_modules/@colors/colors": { @@ -6094,6 +6082,16 @@ "node": ">=4" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@transifex/api": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@transifex/api/-/api-4.3.0.tgz", @@ -8785,51 +8783,68 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/blockly": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-11.2.1.tgz", - "integrity": "sha512-20sCwSwX2Z6UxR/er0B5y6wRFukuIdvOjc7jMuIwyCO/yT35+UbAqYueMga3JFA9NoWPwQc+3s6/XnLkyceAww==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz", + "integrity": "sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug==", "dev": true, "peer": true, "dependencies": { - "jsdom": "25.0.1" + "jsdom": "22.1.0" + } + }, + "node_modules/blockly/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "4" }, "engines": { - "node": ">=18" + "node": ">= 6.0.0" } }, "node_modules/blockly/node_modules/cssstyle": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", - "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", "dev": true, "peer": true, "dependencies": { - "@asamuzakjp/css-color": "^2.8.2", - "rrweb-cssom": "^0.8.0" + "rrweb-cssom": "^0.6.0" }, "engines": { - "node": ">=18" + "node": ">=14" } }, - "node_modules/blockly/node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "node_modules/blockly/node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", "dev": true, - "peer": true + "peer": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } }, - "node_modules/blockly/node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "node_modules/blockly/node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", "dev": true, "peer": true, "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/blockly/node_modules/entities": { @@ -8862,16 +8877,45 @@ } }, "node_modules/blockly/node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", "dev": true, "peer": true, "dependencies": { - "whatwg-encoding": "^3.1.1" + "whatwg-encoding": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=12" + } + }, + "node_modules/blockly/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blockly/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "peer": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, "node_modules/blockly/node_modules/iconv-lite": { @@ -8888,39 +8932,41 @@ } }, "node_modules/blockly/node_modules/jsdom": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", - "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "peer": true, "dependencies": { - "cssstyle": "^4.1.0", - "data-urls": "^5.0.0", + "abab": "^2.0.6", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", "decimal.js": "^10.4.3", + "domexception": "^4.0.0", "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", + "nwsapi": "^2.2.4", "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", + "rrweb-cssom": "^0.6.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=16" }, "peerDependencies": { - "canvas": "^2.11.2" + "canvas": "^2.5.0" }, "peerDependenciesMeta": { "canvas": { @@ -8951,6 +8997,13 @@ "node": ">=6" } }, + "node_modules/blockly/node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true, + "peer": true + }, "node_modules/blockly/node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -8965,42 +9018,55 @@ } }, "node_modules/blockly/node_modules/tough-cookie": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", - "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", "dev": true, "peer": true, "dependencies": { - "tldts": "^6.1.32" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=16" + "node": ">=6" } }, "node_modules/blockly/node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", "dev": true, "peer": true, "dependencies": { - "punycode": "^2.3.1" + "punycode": "^2.3.0" }, "engines": { - "node": ">=18" + "node": ">=14" + } + }, + "node_modules/blockly/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4.0.0" } }, "node_modules/blockly/node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", "dev": true, "peer": true, "dependencies": { - "xml-name-validator": "^5.0.0" + "xml-name-validator": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=14" } }, "node_modules/blockly/node_modules/webidl-conversions": { @@ -9014,40 +9080,40 @@ } }, "node_modules/blockly/node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", "dev": true, "peer": true, "dependencies": { "iconv-lite": "0.6.3" }, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/blockly/node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/blockly/node_modules/whatwg-url": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", - "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", "dev": true, "peer": true, "dependencies": { - "tr46": "^5.0.0", + "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=14" } }, "node_modules/blockly/node_modules/ws": { @@ -9073,13 +9139,13 @@ } }, "node_modules/blockly/node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", "dev": true, "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/bluebird": { @@ -40710,7 +40776,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "2.0.0-spork.3", + "scratch-blocks": "2.0.0-spork.4", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", @@ -40724,6 +40790,18 @@ "webpack-dev-server": "3.11.3" } }, + "packages/scratch-vm/node_modules/@blockly/continuous-toolbox": { + "version": "7.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-7.0.0-beta.1.tgz", + "integrity": "sha512-IKu0whdtRTmdXSB11efSJ5fCNzQtAp98fv+7KnZk/V7Q/xAhVAutWWQE+FJvr9lKJlkeYqm9m+cxDhOBtlVP1w==", + "dev": true, + "engines": { + "node": ">=8.17.0" + }, + "peerDependencies": { + "blockly": "^12.0.0-beta.0" + } + }, "packages/scratch-vm/node_modules/@webpack-cli/configtest": { "version": "1.2.0", "dev": true, @@ -41852,14 +41930,14 @@ } }, "packages/scratch-vm/node_modules/scratch-blocks": { - "version": "2.0.0-spork.3", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.3.tgz", - "integrity": "sha512-luy2QtACBjhHT2rH2Zcvwevjb9zBnMWGwLnp9ydEXzbcFTptmYxFTmC8iFRPm6szxGiCw1pH48Qr3BwTGRKp8Q==", + "version": "2.0.0-spork.4", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.4.tgz", + "integrity": "sha512-gG/w7a5bAKZAPoYLFbyNgtS7ZIGKpc6MrwmxtZG5oSA2WdJcwZMyYLeV9kMCnCh4P0pKBy+NaQEfQC4fDeXK9A==", "dev": true, "dependencies": { - "@blockly/continuous-toolbox": "^6.0.9", - "@blockly/field-colour": "^5.0.9", - "blockly": "^12.0.0-beta.0" + "@blockly/continuous-toolbox": "^7.0.0-beta.1", + "@blockly/field-colour": "^4.0.2", + "blockly": "^12.0.0-beta.1" } }, "packages/scratch-vm/node_modules/selfsigned": { diff --git a/packages/scratch-vm/package.json b/packages/scratch-vm/package.json index 85d7d2f664..0222610edf 100644 --- a/packages/scratch-vm/package.json +++ b/packages/scratch-vm/package.json @@ -91,7 +91,7 @@ "jsdoc": "3.6.11", "json": "^9.0.4", "pngjs": "3.4.0", - "scratch-blocks": "2.0.0-spork.3", + "scratch-blocks": "2.0.0-spork.4", "scratch-l10n": "5.0.134", "scratch-render-fonts": "1.0.165", "scratch-semantic-release-config": "3.0.0", From aedce8a9080009fde1c423541cc234a8f793367e Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 9 Mar 2021 18:40:11 -0500 Subject: [PATCH 20/76] Replace text-encoding polyfill --- package-lock.json | 5 ++--- packages/scratch-gui/package.json | 2 +- packages/scratch-gui/src/lib/default-project/index.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ec88501af..7e083672a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14550,8 +14550,7 @@ "node_modules/fastestsmallesttextencoderdecoder": { "version": "1.0.22", "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", - "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", - "license": "CC0-1.0" + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==" }, "node_modules/fastq": { "version": "1.17.1", @@ -38225,6 +38224,7 @@ "css-loader": "5.2.7", "dapjs": "2.3.0", "es6-object-assign": "1.1.0", + "fastestsmallesttextencoderdecoder": "^1.0.22", "get-float-time-domain-data": "0.1.0", "get-user-media-promise": "1.1.4", "immutable": "3.8.2", @@ -38265,7 +38265,6 @@ "scratch-storage": "4.0.24", "startaudiocontext": "1.2.1", "style-loader": "4.0.0", - "text-encoding": "0.7.0", "to-style": "1.3.3", "wav-encoder": "1.3.0", "xhr": "2.6.0" diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 0ef4808eda..1fb967bab4 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -60,6 +60,7 @@ "css-loader": "5.2.7", "dapjs": "2.3.0", "es6-object-assign": "1.1.0", + "fastestsmallesttextencoderdecoder": "^1.0.22", "get-float-time-domain-data": "0.1.0", "get-user-media-promise": "1.1.4", "immutable": "3.8.2", @@ -100,7 +101,6 @@ "scratch-storage": "4.0.24", "startaudiocontext": "1.2.1", "style-loader": "4.0.0", - "text-encoding": "0.7.0", "to-style": "1.3.3", "wav-encoder": "1.3.0", "xhr": "2.6.0" diff --git a/packages/scratch-gui/src/lib/default-project/index.ts b/packages/scratch-gui/src/lib/default-project/index.ts index 4808aca037..a1b6c6697d 100644 --- a/packages/scratch-gui/src/lib/default-project/index.ts +++ b/packages/scratch-gui/src/lib/default-project/index.ts @@ -14,7 +14,7 @@ declare function require(path: 'text-encoding'): { TextEncoder: typeof TextEncod const defaultProject = (translator?: TranslatorFunction) => { let _TextEncoder: typeof TextEncoder; if (typeof TextEncoder === 'undefined') { - _TextEncoder = require('text-encoding').TextEncoder; + _TextEncoder = require('fastestsmallesttextencoderdecoder').TextEncoder; } else { _TextEncoder = TextEncoder; } From d67cb26c366a96357e34ff09d8f4b58bc8d1f4c0 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 19 Apr 2024 08:07:58 -0700 Subject: [PATCH 21/76] fix: temporarily disable functionality in scratch-gui for compatibility with patched scratch-blocks (#1) --- .../scratch-gui/src/containers/blocks.jsx | 55 +++++++++++-------- packages/scratch-gui/src/lib/blocks.js | 40 +++++++++----- .../scratch-gui/src/lib/make-toolbox-xml.js | 20 +++---- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 84b1e3f3ce..cdbec8f8ca 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -95,14 +95,24 @@ class Blocks extends React.Component { this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; - this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; + // this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; this.ScratchBlocks.Procedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); + const theme = this.ScratchBlocks.Theme.defineTheme('Scratch', { + 'base': this.ScratchBlocks.Themes.Zelos, + 'startHats': true + }); const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options, - {rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme)} + { + rtl: this.props.isRtl, + toolbox: this.props.toolboxXML, + colours: getColorsForTheme(this.props.theme), + renderer: 'zelos', + theme: theme, + } ); this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); @@ -129,10 +139,11 @@ class Blocks extends React.Component { // we actually never want the workspace to enable "refresh toolbox" - this basically re-renders the // entire toolbox every time we reset the workspace. We call updateToolbox as a part of // componentDidUpdate so the toolbox will still correctly be updated - this.setToolboxRefreshEnabled = this.workspace.setToolboxRefreshEnabled.bind(this.workspace); - this.workspace.setToolboxRefreshEnabled = () => { - this.setToolboxRefreshEnabled(false); - }; + this.setToolboxRefreshEnabled = () => {}; + // this.workspace.setToolboxRefreshEnabled.bind(this.workspace); + // this.workspace.setToolboxRefreshEnabled = () => { + // this.setToolboxRefreshEnabled(false); + // }; // @todo change this when blockly supports UI events addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); @@ -213,11 +224,11 @@ class Blocks extends React.Component { this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); this.props.vm.setLocale(this.props.locale, this.props.messages) .then(() => { - this.workspace.getFlyout().setRecyclingEnabled(false); + // this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); this.requestToolboxUpdate(); this.withToolboxUpdates(() => { - this.workspace.getFlyout().setRecyclingEnabled(true); + // this.workspace.getFlyout().setRecyclingEnabled(true); }); }); } @@ -225,8 +236,8 @@ class Blocks extends React.Component { updateToolbox () { this.toolboxUpdateTimeout = false; - const categoryId = this.workspace.toolbox_.getSelectedCategoryId(); - const offset = this.workspace.toolbox_.getCategoryScrollOffset(); + // const categoryId = this.workspace.toolbox_.getSelectedItem().getId(); + // const offset = this.workspace.toolbox_.getCategoryScrollOffset(); this.workspace.updateToolbox(this.props.toolboxXML); this._renderedToolboxXML = this.props.toolboxXML; @@ -235,13 +246,13 @@ class Blocks extends React.Component { // Using the setter function will rerender the entire toolbox which we just rendered. this.workspace.toolboxRefreshEnabled_ = true; - const currentCategoryPos = this.workspace.toolbox_.getCategoryPositionById(categoryId); - const currentCategoryLen = this.workspace.toolbox_.getCategoryLengthById(categoryId); - if (offset < currentCategoryLen) { - this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos + offset); - } else { - this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos); - } + // const currentCategoryPos = this.workspace.toolbox_.getCategoryPositionById(categoryId); + // const currentCategoryLen = this.workspace.toolbox_.getCategoryLengthById(categoryId); + // if (offset < currentCategoryLen) { + // this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos + offset); + // } else { + // this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos); + // } const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; @@ -329,16 +340,16 @@ class Blocks extends React.Component { } } onScriptGlowOn (data) { - this.workspace.glowStack(data.id, true); + // this.workspace.glowStack(data.id, true); } onScriptGlowOff (data) { - this.workspace.glowStack(data.id, false); + // this.workspace.glowStack(data.id, false); } onBlockGlowOn (data) { - this.workspace.glowBlock(data.id, true); + // this.workspace.glowBlock(data.id, true); } onBlockGlowOff (data) { - this.workspace.glowBlock(data.id, false); + // this.workspace.glowBlock(data.id, false); } onVisualReport (data) { this.workspace.reportValue(data.id, data.value); @@ -382,7 +393,7 @@ class Blocks extends React.Component { // Remove and reattach the workspace listener (but allow flyout events) this.workspace.removeChangeListener(this.props.vm.blockListener); - const dom = this.ScratchBlocks.Xml.textToDom(data.xml); + const dom = this.ScratchBlocks.utils.xml.textToDom(data.xml); try { this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); } catch (error) { diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index f89f5fe0b0..97d5ec30de 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -5,7 +5,7 @@ * @return {ScratchBlocks} ScratchBlocks connected with the vm */ export default function (vm, useCatBlocks) { - const ScratchBlocks = useCatBlocks ? require('cat-blocks') : require('scratch-blocks'); + const {ScratchBlocks} = useCatBlocks ? require('cat-blocks') : require('scratch-blocks'); const jsonForMenuBlock = function (name, menuOptionsFn, colors, start) { return { message0: '%1', @@ -82,7 +82,7 @@ export default function (vm, useCatBlocks) { } menu.push([ ScratchBlocks.ScratchMsgs.translate('SOUND_RECORD', 'record...'), - ScratchBlocks.recordSoundCallback + 'SOUND_RECORD' ]); return menu; }; @@ -158,6 +158,16 @@ export default function (vm, useCatBlocks) { ScratchBlocks.Blocks.sound_sounds_menu.init = function () { const json = jsonForMenuBlock('SOUND_MENU', soundsMenu, soundColors, []); this.jsonInit(json); + this.inputList[0].removeField('SOUND_MENU'); + this.inputList[0].appendField(new ScratchBlocks.FieldDropdown(() => { + return soundsMenu(); + }, (newValue) => { + if (newValue === 'SOUND_RECORD') { + ScratchBlocks.recordSoundCallback(); + return null; + } + return newValue; + }), 'SOUND_MENU'); }; ScratchBlocks.Blocks.looks_costume.init = function () { @@ -323,16 +333,16 @@ export default function (vm, useCatBlocks) { return monitoredBlock ? monitoredBlock.isMonitored : false; }; - ScratchBlocks.FlyoutExtensionCategoryHeader.getExtensionState = function (extensionId) { - if (vm.getPeripheralIsConnected(extensionId)) { - return ScratchBlocks.StatusButtonState.READY; - } - return ScratchBlocks.StatusButtonState.NOT_READY; - }; - - ScratchBlocks.FieldNote.playNote_ = function (noteNum, extensionId) { - vm.runtime.emit('PLAY_NOTE', noteNum, extensionId); - }; + // ScratchBlocks.FlyoutExtensionCategoryHeader.getExtensionState = function (extensionId) { + // if (vm.getPeripheralIsConnected(extensionId)) { + // return ScratchBlocks.StatusButtonState.READY; + // } + // return ScratchBlocks.StatusButtonState.NOT_READY; + // }; + // + // ScratchBlocks.FieldNote.playNote_ = function (noteNum, extensionId) { + // vm.runtime.emit('PLAY_NOTE', noteNum, extensionId); + // }; // Use a collator's compare instead of localeCompare which internally // creates a collator. Using this is a lot faster in browsers that create a @@ -341,9 +351,9 @@ export default function (vm, useCatBlocks) { sensitivity: 'base', numeric: true }); - ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) { - return collator.compare(str1, str2); - }; + // ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) { + // return collator.compare(str1, str2); + // }; // Blocks wants to know if 3D CSS transforms are supported. The cross // section of browsers Scratch supports and browsers that support 3D CSS diff --git a/packages/scratch-gui/src/lib/make-toolbox-xml.js b/packages/scratch-gui/src/lib/make-toolbox-xml.js index 8d356e442d..156f4cd281 100644 --- a/packages/scratch-gui/src/lib/make-toolbox-xml.js +++ b/packages/scratch-gui/src/lib/make-toolbox-xml.js @@ -1,4 +1,4 @@ -import ScratchBlocks from 'scratch-blocks'; +import {ScratchBlocks} from 'scratch-blocks'; import {defaultColors} from './themes'; const categorySeparator = ''; @@ -13,7 +13,7 @@ const motion = function (isInitialSetup, isStage, targetId, colors) { ); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + ${isStage ? ` ` : ` @@ -158,7 +158,7 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...'); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + ${isStage ? '' : ` @@ -294,7 +294,7 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + @@ -350,7 +350,7 @@ const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { const events = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + @@ -391,7 +391,7 @@ const control = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` @@ -444,7 +444,7 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` @@ -526,7 +526,7 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` @@ -715,7 +715,7 @@ const variables = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` Date: Tue, 23 Apr 2024 09:45:13 -0700 Subject: [PATCH 22/76] fix: show the correct toolbox based on sprites or the stage being selected (#4) --- packages/scratch-gui/src/containers/blocks.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index cdbec8f8ca..ad632b6884 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -148,6 +148,7 @@ class Blocks extends React.Component { // @todo change this when blockly supports UI events addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange); + this.workspace.getToolbox().selectItemByPosition(0); this.attachVM(); // Only update blocks/vm locale when visible to avoid sizing issues @@ -224,11 +225,11 @@ class Blocks extends React.Component { this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); this.props.vm.setLocale(this.props.locale, this.props.messages) .then(() => { - // this.workspace.getFlyout().setRecyclingEnabled(false); + this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); this.requestToolboxUpdate(); this.withToolboxUpdates(() => { - // this.workspace.getFlyout().setRecyclingEnabled(true); + this.workspace.getFlyout().setRecyclingEnabled(true); }); }); } @@ -239,6 +240,7 @@ class Blocks extends React.Component { // const categoryId = this.workspace.toolbox_.getSelectedItem().getId(); // const offset = this.workspace.toolbox_.getCategoryScrollOffset(); this.workspace.updateToolbox(this.props.toolboxXML); + this.workspace.refreshToolboxSelection(); this._renderedToolboxXML = this.props.toolboxXML; // In order to catch any changes that mutate the toolbox during "normal runtime" From 818227b7512e46a38d500192e609b692d0be2f9f Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 23 Apr 2024 10:48:35 -0700 Subject: [PATCH 23/76] fix: modify inject options to reflect Scratch behaviors (#5) --- packages/scratch-gui/src/containers/blocks.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index ad632b6884..398bb3e3ee 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -664,9 +664,12 @@ Blocks.propTypes = { Blocks.defaultOptions = { zoom: { controls: true, - wheel: true, + wheel: false, startScale: BLOCKS_DEFAULT_SCALE }, + move: { + wheel: true, + }, grid: { spacing: 40, length: 2, @@ -674,7 +677,8 @@ Blocks.defaultOptions = { }, comments: true, collapse: false, - sounds: false + sounds: false, + trashcan: false, }; Blocks.defaultProps = { From 7c758282ff875b048f15a4925015605961dc3233 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 24 Apr 2024 12:28:10 -0700 Subject: [PATCH 24/76] fix: add support for Scratch-style procedures (#6) * fix: add support for Scratch-style procedures * refactor: remove underscore procedure creation callback --- packages/scratch-gui/src/containers/blocks.jsx | 14 ++++++++------ .../src/containers/custom-procedures.jsx | 12 +++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 398bb3e3ee..9b082eaca4 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -79,7 +79,7 @@ class Blocks extends React.Component { 'setBlocks', 'setLocale' ]); - this.ScratchBlocks.prompt = this.handlePromptStart; + this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; @@ -91,12 +91,12 @@ class Blocks extends React.Component { } componentDidMount () { this.ScratchBlocks = VMScratchBlocks(this.props.vm, this.props.useCatBlocks); - this.ScratchBlocks.prompt = this.handlePromptStart; + this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; // this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; - this.ScratchBlocks.Procedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; + this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); const theme = this.ScratchBlocks.Theme.defineTheme('Scratch', { @@ -115,6 +115,8 @@ class Blocks extends React.Component { } ); this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); + this.workspace.registerToolboxCategoryCallback('PROCEDURE', + this.ScratchBlocks.ScratchProcedures.getProceduresCategory); // Register buttons under new callback keys for creating variables, // lists, and procedures from extensions. @@ -124,7 +126,7 @@ class Blocks extends React.Component { const varListButtonCallback = type => (() => this.ScratchBlocks.Variables.createVariable(this.workspace, null, type)); const procButtonCallback = () => { - this.ScratchBlocks.Procedures.createProcedureDefCallback_(this.workspace); + this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback(this.workspace); }; toolboxWorkspace.registerButtonCallback('MAKE_A_VARIABLE', varListButtonCallback('')); @@ -545,8 +547,8 @@ class Blocks extends React.Component { handleCustomProceduresClose (data) { this.props.onRequestCloseCustomProcedures(data); const ws = this.workspace; - ws.refreshToolboxSelection_(); - ws.toolbox_.scrollToCategoryById('myBlocks'); + this.updateToolbox(); + ws.getToolbox().selectCategoryByName('myBlocks'); } handleDrop (dragInfo) { fetch(dragInfo.payload.bodyUrl) diff --git a/packages/scratch-gui/src/containers/custom-procedures.jsx b/packages/scratch-gui/src/containers/custom-procedures.jsx index e691b0466f..de7ac0a042 100644 --- a/packages/scratch-gui/src/containers/custom-procedures.jsx +++ b/packages/scratch-gui/src/containers/custom-procedures.jsx @@ -3,7 +3,7 @@ import defaultsDeep from 'lodash.defaultsdeep'; import PropTypes from 'prop-types'; import React from 'react'; import CustomProceduresComponent from '../components/custom-procedures/custom-procedures.jsx'; -import ScratchBlocks from 'scratch-blocks'; +import {ScratchBlocks} from 'scratch-blocks'; import {connect} from 'react-redux'; class CustomProcedures extends React.Component { @@ -37,11 +37,13 @@ class CustomProcedures extends React.Component { {rtl: this.props.isRtl} ); - // @todo This is a hack to make there be no toolbox. - const oldDefaultToolbox = ScratchBlocks.Blocks.defaultToolbox; - ScratchBlocks.Blocks.defaultToolbox = null; + const theme = ScratchBlocks.Theme.defineTheme('Scratch', { + 'base': ScratchBlocks.Themes.Zelos, + 'startHats': true + }); + workspaceConfig.theme = theme; + workspaceConfig.renderer = 'zelos'; this.workspace = ScratchBlocks.inject(this.blocks, workspaceConfig); - ScratchBlocks.Blocks.defaultToolbox = oldDefaultToolbox; // Create the procedure declaration block for editing the mutation. this.mutationRoot = this.workspace.newBlock('procedures_declaration'); From fde8293fe73b9f4916ebf98552907d81848d4a22 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 24 Apr 2024 15:40:57 -0700 Subject: [PATCH 25/76] fix: reenable the Scratch colour eyedropper --- packages/scratch-gui/src/containers/blocks.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 9b082eaca4..73ac041465 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -95,7 +95,7 @@ class Blocks extends React.Component { this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; - // this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; + this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); From 3baa9bb1535e7068fe20ac47948cbbaa1c0faec5 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 26 Apr 2024 11:22:41 -0700 Subject: [PATCH 26/76] fix: patch the getCheckboxState method in the new flyout (#7) --- packages/scratch-gui/src/lib/blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 97d5ec30de..3230729c8a 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -328,7 +328,7 @@ export default function (vm, useCatBlocks) { this.jsonInit(json); }; - ScratchBlocks.VerticalFlyout.getCheckboxState = function (blockId) { + ScratchBlocks.CheckableContinuousFlyout.prototype.getCheckboxState = function (blockId) { const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId]; return monitoredBlock ? monitoredBlock.isMonitored : false; }; From fbffc14ceadb80261ad7873c9d0d148bd9332e01 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 30 Apr 2024 15:07:15 -0700 Subject: [PATCH 27/76] fix: select extension categories when added (#8) --- packages/scratch-gui/src/containers/blocks.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 73ac041465..8654acdb86 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -500,7 +500,8 @@ class Blocks extends React.Component { } this.withToolboxUpdates(() => { - this.workspace.toolbox_.setSelectedCategoryById(categoryId); + const toolbox = this.workspace.getToolbox(); + toolbox.setSelectedItem(toolbox.getToolboxItemById(categoryId)); }); } setBlocks (blocks) { From 5de68f1a60fa3909e7f81a2fba1c60718be15581 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 30 Apr 2024 15:48:05 -0700 Subject: [PATCH 28/76] fix: Use toolboxitemid instead of id as the identifier attribute for toolbox categories (#9) --- .../scratch-gui/src/lib/make-toolbox-xml.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/scratch-gui/src/lib/make-toolbox-xml.js b/packages/scratch-gui/src/lib/make-toolbox-xml.js index 156f4cd281..532f9e14c0 100644 --- a/packages/scratch-gui/src/lib/make-toolbox-xml.js +++ b/packages/scratch-gui/src/lib/make-toolbox-xml.js @@ -13,7 +13,7 @@ const motion = function (isInitialSetup, isStage, targetId, colors) { ); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + ${isStage ? ` ` : ` @@ -158,7 +158,7 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...'); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + ${isStage ? '' : ` @@ -294,7 +294,7 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + @@ -350,7 +350,7 @@ const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { const events = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + @@ -392,7 +392,7 @@ const control = function (isInitialSetup, isStage, targetId, colors) { return ` @@ -445,7 +445,7 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { return ` ${isStage ? '' : ` @@ -527,7 +527,7 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { return ` @@ -716,7 +716,7 @@ const variables = function (isInitialSetup, isStage, targetId, colors) { return ` @@ -729,7 +729,7 @@ const myBlocks = function (isInitialSetup, isStage, targetId, colors) { return ` From c0b3d0e9102b26fbd192e6cb5f55cc452cb06e6d Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 1 May 2024 09:21:02 -0700 Subject: [PATCH 29/76] fix: preserve toolbox scroll position when switching between sprites/the stage (#10) --- .../scratch-gui/src/containers/blocks.jsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 8654acdb86..08c697f0cd 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -239,8 +239,13 @@ class Blocks extends React.Component { updateToolbox () { this.toolboxUpdateTimeout = false; - // const categoryId = this.workspace.toolbox_.getSelectedItem().getId(); - // const offset = this.workspace.toolbox_.getCategoryScrollOffset(); + const scale = this.workspace.getFlyout().getWorkspace().scale; + const selectedCategoryName = this.workspace.getToolbox().getSelectedItem().getName(); + const selectedCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition( + selectedCategoryName).y * scale; + const offsetWithinCategory = (this.workspace.getFlyout().getWorkspace().getMetrics().viewTop + - selectedCategoryScrollPosition); + this.workspace.updateToolbox(this.props.toolboxXML); this.workspace.refreshToolboxSelection(); this._renderedToolboxXML = this.props.toolboxXML; @@ -250,13 +255,10 @@ class Blocks extends React.Component { // Using the setter function will rerender the entire toolbox which we just rendered. this.workspace.toolboxRefreshEnabled_ = true; - // const currentCategoryPos = this.workspace.toolbox_.getCategoryPositionById(categoryId); - // const currentCategoryLen = this.workspace.toolbox_.getCategoryLengthById(categoryId); - // if (offset < currentCategoryLen) { - // this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos + offset); - // } else { - // this.workspace.toolbox_.setFlyoutScrollPos(currentCategoryPos); - // } + const newCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition( + selectedCategoryName).y * scale; + this.workspace.getFlyout().getWorkspace().scrollbar.setY( + newCategoryScrollPosition + offsetWithinCategory); const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; From b7e7854aaa1e261cee8e4dd6c6c27886ed487cab Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 3 May 2024 08:58:11 -0700 Subject: [PATCH 30/76] fix: call reportValue on the ScratchBlocks module instead of the workspace (#11) --- packages/scratch-gui/src/containers/blocks.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 08c697f0cd..e8de435c28 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -358,7 +358,7 @@ class Blocks extends React.Component { // this.workspace.glowBlock(data.id, false); } onVisualReport (data) { - this.workspace.reportValue(data.id, data.value); + this.ScratchBlocks.reportValue(data.id, data.value); } getToolboxXML () { // Use try/catch because this requires digging pretty deep into the VM From 0cd556ff3b66814610cfa3249d1d5fea7cdbff2b Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 6 May 2024 13:42:13 -0700 Subject: [PATCH 31/76] fix: call the new glow methods on ScratchBlocks (#12) * fix: call the new glow methods on ScratchBlocks * refactor: remove the glowBlock calls --- packages/scratch-gui/src/containers/blocks.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index e8de435c28..a0373417a8 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -346,16 +346,16 @@ class Blocks extends React.Component { } } onScriptGlowOn (data) { - // this.workspace.glowStack(data.id, true); + this.ScratchBlocks.glowStack(data.id, true); } onScriptGlowOff (data) { - // this.workspace.glowStack(data.id, false); + this.ScratchBlocks.glowStack(data.id, false); } onBlockGlowOn (data) { - // this.workspace.glowBlock(data.id, true); + // No-op, support may be added in the future } onBlockGlowOff (data) { - // this.workspace.glowBlock(data.id, false); + // No-op, support may be added in the future } onVisualReport (data) { this.ScratchBlocks.reportValue(data.id, data.value); From 9ecec906762ecc2cbe3800d8b4357e5b264dc16e Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 7 May 2024 11:57:53 -0700 Subject: [PATCH 32/76] fix: adjust key event filtering to fix the when key pressed block (#13) --- packages/scratch-gui/src/lib/vm-listener-hoc.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx index 87b9dab7c6..6d9c3c5dc1 100644 --- a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx +++ b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx @@ -85,7 +85,7 @@ const vmListenerHOC = function (WrappedComponent) { } handleKeyDown (e) { // Don't capture keys intended for Blockly inputs. - if (e.target !== document && e.target !== document.body) return; + if (e.target instanceof HTMLInputElement) return; const key = (!e.key || e.key === 'Dead') ? e.keyCode : e.key; this.props.vm.postIOData('keyboard', { From 2d6e6768f9f7b03b85e557a8056c485d30fceb1a Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 16 May 2024 15:54:10 -0700 Subject: [PATCH 33/76] refactor: simplify toolbox refreshing behavior (#14) --- packages/scratch-gui/src/containers/blocks.jsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index a0373417a8..e803f3fdb4 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -138,15 +138,6 @@ class Blocks extends React.Component { // the xml can change while e.g. on the costumes tab. this._renderedToolboxXML = this.props.toolboxXML; - // we actually never want the workspace to enable "refresh toolbox" - this basically re-renders the - // entire toolbox every time we reset the workspace. We call updateToolbox as a part of - // componentDidUpdate so the toolbox will still correctly be updated - this.setToolboxRefreshEnabled = () => {}; - // this.workspace.setToolboxRefreshEnabled.bind(this.workspace); - // this.workspace.setToolboxRefreshEnabled = () => { - // this.setToolboxRefreshEnabled(false); - // }; - // @todo change this when blockly supports UI events addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange); @@ -247,14 +238,9 @@ class Blocks extends React.Component { - selectedCategoryScrollPosition); this.workspace.updateToolbox(this.props.toolboxXML); - this.workspace.refreshToolboxSelection(); + this.workspace.getToolbox().forceRerender(); this._renderedToolboxXML = this.props.toolboxXML; - // In order to catch any changes that mutate the toolbox during "normal runtime" - // (variable changes/etc), re-enable toolbox refresh. - // Using the setter function will rerender the entire toolbox which we just rendered. - this.workspace.toolboxRefreshEnabled_ = true; - const newCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition( selectedCategoryName).y * scale; this.workspace.getFlyout().getWorkspace().scrollbar.setY( From 1c691944637fb336d1384eead7ded7f187a0d8ae Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 8 Jul 2024 14:52:42 -0700 Subject: [PATCH 34/76] fix: allow typing into comments (#15) --- packages/scratch-gui/src/lib/vm-listener-hoc.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx index 6d9c3c5dc1..eceac44092 100644 --- a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx +++ b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx @@ -85,7 +85,7 @@ const vmListenerHOC = function (WrappedComponent) { } handleKeyDown (e) { // Don't capture keys intended for Blockly inputs. - if (e.target instanceof HTMLInputElement) return; + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; const key = (!e.key || e.key === 'Dead') ? e.keyCode : e.key; this.props.vm.postIOData('keyboard', { From 2d07d0bb545e517a59773bd588aa20cc7a68092e Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Fri, 2 Aug 2024 13:30:06 -0700 Subject: [PATCH 35/76] feat: plumb Scratch variable support into the UI (#16) * chore: format blocks.jsx * feat: plumb Scratch variable support into the UI --- .../scratch-gui/src/containers/blocks.jsx | 630 +++++++++++------- 1 file changed, 396 insertions(+), 234 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index e803f3fdb4..96dcba775c 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -1,39 +1,49 @@ -import bindAll from 'lodash.bindall'; -import debounce from 'lodash.debounce'; -import defaultsDeep from 'lodash.defaultsdeep'; -import makeToolboxXML from '../lib/make-toolbox-xml'; -import PropTypes from 'prop-types'; -import React from 'react'; -import VMScratchBlocks from '../lib/blocks'; -import VM from '@scratch/scratch-vm'; - -import log from '../lib/log.js'; -import Prompt from './prompt.jsx'; -import BlocksComponent from '../components/blocks/blocks.jsx'; -import ExtensionLibrary from './extension-library.jsx'; -import extensionData from '../lib/libraries/extensions/index.jsx'; -import CustomProcedures from './custom-procedures.jsx'; -import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; -import {BLOCKS_DEFAULT_SCALE, STAGE_DISPLAY_SIZES} from '../lib/layout-constants'; -import DropAreaHOC from '../lib/drop-area-hoc.jsx'; -import DragConstants from '../lib/drag-constants'; -import defineDynamicBlock from '../lib/define-dynamic-block'; -import {DEFAULT_THEME, getColorsForTheme, themeMap} from '../lib/themes'; -import {injectExtensionBlockTheme, injectExtensionCategoryTheme} from '../lib/themes/blockHelpers'; - -import {connect} from 'react-redux'; -import {updateToolbox} from '../reducers/toolbox'; -import {activateColorPicker} from '../reducers/color-picker'; -import {closeExtensionLibrary, openSoundRecorder, openConnectionModal} from '../reducers/modals'; -import {activateCustomProcedures, deactivateCustomProcedures} from '../reducers/custom-procedures'; -import {setConnectionModalExtensionId} from '../reducers/connection-modal'; -import {updateMetrics} from '../reducers/workspace-metrics'; -import {isTimeTravel2020} from '../reducers/time-travel'; +import bindAll from "lodash.bindall"; +import debounce from "lodash.debounce"; +import defaultsDeep from "lodash.defaultsdeep"; +import makeToolboxXML from "../lib/make-toolbox-xml"; +import PropTypes from "prop-types"; +import React from "react"; +import VMScratchBlocks from "../lib/blocks"; +import VM from "@scratch/scratch-vm"; + +import log from "../lib/log.js"; +import Prompt from "./prompt.jsx"; +import BlocksComponent from "../components/blocks/blocks.jsx"; +import ExtensionLibrary from "./extension-library.jsx"; +import extensionData from "../lib/libraries/extensions/index.jsx"; +import CustomProcedures from "./custom-procedures.jsx"; +import errorBoundaryHOC from "../lib/error-boundary-hoc.jsx"; +import { + BLOCKS_DEFAULT_SCALE, + STAGE_DISPLAY_SIZES, +} from "../lib/layout-constants"; +import DropAreaHOC from "../lib/drop-area-hoc.jsx"; +import DragConstants from "../lib/drag-constants"; +import defineDynamicBlock from "../lib/define-dynamic-block"; +import { DEFAULT_THEME, getColorsForTheme, themeMap } from "../lib/themes"; +import { + injectExtensionBlockTheme, + injectExtensionCategoryTheme, +} from "../lib/themes/blockHelpers"; +import { connect } from "react-redux"; +import { updateToolbox } from "../reducers/toolbox"; +import { activateColorPicker } from "../reducers/color-picker"; +import { + closeExtensionLibrary, + openSoundRecorder, + openConnectionModal, +} from "../reducers/modals"; import { - activateTab, - SOUNDS_TAB_INDEX -} from '../reducers/editor-tab'; + activateCustomProcedures, + deactivateCustomProcedures, +} from "../reducers/custom-procedures"; +import { setConnectionModalExtensionId } from "../reducers/connection-modal"; +import { updateMetrics } from "../reducers/workspace-metrics"; +import { isTimeTravel2020 } from "../reducers/time-travel"; + +import { activateTab, SOUNDS_TAB_INDEX } from "../reducers/editor-tab"; const addFunctionListener = (object, property, callback) => { const oldFn = object[property]; @@ -44,94 +54,135 @@ const addFunctionListener = (object, property, callback) => { }; }; -const DroppableBlocks = DropAreaHOC([ - DragConstants.BACKPACK_CODE -])(BlocksComponent); +const DroppableBlocks = DropAreaHOC([DragConstants.BACKPACK_CODE])( + BlocksComponent +); class Blocks extends React.Component { - constructor (props) { + constructor(props) { super(props); this.ScratchBlocks = VMScratchBlocks(props.vm, false); bindAll(this, [ - 'attachVM', - 'detachVM', - 'getToolboxXML', - 'handleCategorySelected', - 'handleConnectionModalStart', - 'handleDrop', - 'handleStatusButtonUpdate', - 'handleOpenSoundRecorder', - 'handlePromptStart', - 'handlePromptCallback', - 'handlePromptClose', - 'handleCustomProceduresClose', - 'onScriptGlowOn', - 'onScriptGlowOff', - 'onBlockGlowOn', - 'onBlockGlowOff', - 'handleMonitorsUpdate', - 'handleExtensionAdded', - 'handleBlocksInfoUpdate', - 'onTargetsUpdate', - 'onVisualReport', - 'onWorkspaceUpdate', - 'onWorkspaceMetricsChange', - 'setBlocks', - 'setLocale' + "attachVM", + "detachVM", + "getToolboxXML", + "handleCategorySelected", + "handleConnectionModalStart", + "handleDrop", + "handleStatusButtonUpdate", + "handleOpenSoundRecorder", + "handlePromptStart", + "handlePromptCallback", + "handlePromptClose", + "handleCustomProceduresClose", + "onScriptGlowOn", + "onScriptGlowOff", + "onBlockGlowOn", + "onBlockGlowOff", + "handleMonitorsUpdate", + "handleExtensionAdded", + "handleBlocksInfoUpdate", + "onTargetsUpdate", + "onVisualReport", + "onWorkspaceUpdate", + "onWorkspaceMetricsChange", + "setBlocks", + "setLocale", ]); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); - this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; + this.ScratchBlocks.statusButtonCallback = + this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; this.state = { - prompt: null + prompt: null, }; this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100); this.toolboxUpdateQueue = []; } - componentDidMount () { - this.ScratchBlocks = VMScratchBlocks(this.props.vm, this.props.useCatBlocks); + componentDidMount() { + this.ScratchBlocks = VMScratchBlocks( + this.props.vm, + this.props.useCatBlocks + ); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); - this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; + this.ScratchBlocks.statusButtonCallback = + this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; - this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; - this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; + this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = + this.props.onActivateColorPicker; + this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = + this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); - const theme = this.ScratchBlocks.Theme.defineTheme('Scratch', { - 'base': this.ScratchBlocks.Themes.Zelos, - 'startHats': true + const theme = this.ScratchBlocks.Theme.defineTheme("Scratch", { + base: this.ScratchBlocks.Themes.Zelos, + startHats: true, }); - const workspaceConfig = defaultsDeep({}, + const workspaceConfig = defaultsDeep( + {}, Blocks.defaultOptions, this.props.options, { rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme), - renderer: 'zelos', + renderer: "zelos", theme: theme, } ); - this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); - this.workspace.registerToolboxCategoryCallback('PROCEDURE', - this.ScratchBlocks.ScratchProcedures.getProceduresCategory); + this.workspace = this.ScratchBlocks.inject( + this.blocks, + workspaceConfig + ); + this.workspace.registerToolboxCategoryCallback( + "VARIABLE", + this.ScratchBlocks.ScratchVariables.getVariablesCategory + ); + this.workspace.registerToolboxCategoryCallback( + "PROCEDURE", + this.ScratchBlocks.ScratchProcedures.getProceduresCategory + ); + this.workspace.addChangeListener((event) => { + if ( + event.type === this.ScratchBlocks.Events.VAR_CREATE || + event.type === this.ScratchBlocks.Events.VAR_RENAME || + event.type === this.ScratchBlocks.Events.VAR_DELETE + ) { + this.requestToolboxUpdate(); + } + }); // Register buttons under new callback keys for creating variables, // lists, and procedures from extensions. const toolboxWorkspace = this.workspace.getFlyout().getWorkspace(); - const varListButtonCallback = type => - (() => this.ScratchBlocks.Variables.createVariable(this.workspace, null, type)); + const varListButtonCallback = (type) => () => + this.ScratchBlocks.ScratchVariables.createVariable( + this.workspace, + null, + type + ); const procButtonCallback = () => { - this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback(this.workspace); + this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback( + this.workspace + ); }; - toolboxWorkspace.registerButtonCallback('MAKE_A_VARIABLE', varListButtonCallback('')); - toolboxWorkspace.registerButtonCallback('MAKE_A_LIST', varListButtonCallback('list')); - toolboxWorkspace.registerButtonCallback('MAKE_A_PROCEDURE', procButtonCallback); + toolboxWorkspace.registerButtonCallback( + "MAKE_A_VARIABLE", + varListButtonCallback("") + ); + toolboxWorkspace.registerButtonCallback( + "MAKE_A_LIST", + varListButtonCallback("list") + ); + toolboxWorkspace.registerButtonCallback( + "MAKE_A_PROCEDURE", + procButtonCallback + ); // Store the xml of the toolbox that is actually rendered. // This is used in componentDidUpdate instead of prevProps, because @@ -139,8 +190,16 @@ class Blocks extends React.Component { this._renderedToolboxXML = this.props.toolboxXML; // @todo change this when blockly supports UI events - addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); - addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange); + addFunctionListener( + this.workspace, + "translate", + this.onWorkspaceMetricsChange + ); + addFunctionListener( + this.workspace, + "zoom", + this.onWorkspaceMetricsChange + ); this.workspace.getToolbox().selectItemByPosition(0); this.attachVM(); @@ -150,19 +209,21 @@ class Blocks extends React.Component { this.setLocale(); } } - shouldComponentUpdate (nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { return ( this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible || this._renderedToolboxXML !== nextProps.toolboxXML || - this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible || - this.props.customProceduresVisible !== nextProps.customProceduresVisible || + this.props.extensionLibraryVisible !== + nextProps.extensionLibraryVisible || + this.props.customProceduresVisible !== + nextProps.customProceduresVisible || this.props.locale !== nextProps.locale || this.props.anyModalVisible !== nextProps.anyModalVisible || this.props.stageSize !== nextProps.stageSize ); } - componentDidUpdate (prevProps) { + componentDidUpdate(prevProps) { // If any modals are open, call hideChaff to close z-indexed field editors if (this.props.anyModalVisible && !prevProps.anyModalVisible) { this.ScratchBlocks.hideChaff(); @@ -171,22 +232,29 @@ class Blocks extends React.Component { // Only rerender the toolbox when the blocks are visible and the xml is // different from the previously rendered toolbox xml. // Do not check against prevProps.toolboxXML because that may not have been rendered. - if (this.props.isVisible && this.props.toolboxXML !== this._renderedToolboxXML) { + if ( + this.props.isVisible && + this.props.toolboxXML !== this._renderedToolboxXML + ) { this.requestToolboxUpdate(); } if (this.props.isVisible === prevProps.isVisible) { if (this.props.stageSize !== prevProps.stageSize) { // force workspace to redraw for the new stage size - window.dispatchEvent(new Event('resize')); + window.dispatchEvent(new Event("resize")); } return; } // @todo hack to resize blockly manually in case resize happened while hidden // @todo hack to reload the workspace due to gui bug #413 - if (this.props.isVisible) { // Scripts tab + if (this.props.isVisible) { + // Scripts tab this.workspace.setVisible(true); - if (prevProps.locale !== this.props.locale || this.props.locale !== this.props.vm.getLocale()) { + if ( + prevProps.locale !== this.props.locale || + this.props.locale !== this.props.vm.getLocale() + ) { // call setLocale if the locale has changed, or changed while the blocks were hidden. // vm.getLocale() will be out of sync if locale was changed while not visible this.setLocale(); @@ -195,12 +263,12 @@ class Blocks extends React.Component { this.requestToolboxUpdate(); } - window.dispatchEvent(new Event('resize')); + window.dispatchEvent(new Event("resize")); } else { this.workspace.setVisible(false); } } - componentWillUnmount () { + componentWillUnmount() { this.detachVM(); this.workspace.dispose(); clearTimeout(this.toolboxUpdateTimeout); @@ -208,15 +276,16 @@ class Blocks extends React.Component { // Clear the flyout blocks so that they can be recreated on mount. this.props.vm.clearFlyoutBlocks(); } - requestToolboxUpdate () { + requestToolboxUpdate() { clearTimeout(this.toolboxUpdateTimeout); this.toolboxUpdateTimeout = setTimeout(() => { this.updateToolbox(); }, 0); } - setLocale () { + setLocale() { this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); - this.props.vm.setLocale(this.props.locale, this.props.messages) + this.props.vm + .setLocale(this.props.locale, this.props.messages) .then(() => { this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); @@ -227,31 +296,41 @@ class Blocks extends React.Component { }); } - updateToolbox () { + updateToolbox() { this.toolboxUpdateTimeout = false; const scale = this.workspace.getFlyout().getWorkspace().scale; - const selectedCategoryName = this.workspace.getToolbox().getSelectedItem().getName(); - const selectedCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition( - selectedCategoryName).y * scale; - const offsetWithinCategory = (this.workspace.getFlyout().getWorkspace().getMetrics().viewTop - - selectedCategoryScrollPosition); + const selectedCategoryName = this.workspace + .getToolbox() + .getSelectedItem() + .getName(); + const selectedCategoryScrollPosition = + this.workspace + .getFlyout() + .getCategoryScrollPosition(selectedCategoryName).y * scale; + const offsetWithinCategory = + this.workspace.getFlyout().getWorkspace().getMetrics().viewTop - + selectedCategoryScrollPosition; this.workspace.updateToolbox(this.props.toolboxXML); this.workspace.getToolbox().forceRerender(); this._renderedToolboxXML = this.props.toolboxXML; - const newCategoryScrollPosition = this.workspace.getFlyout().getCategoryScrollPosition( - selectedCategoryName).y * scale; - this.workspace.getFlyout().getWorkspace().scrollbar.setY( - newCategoryScrollPosition + offsetWithinCategory); + const newCategoryScrollPosition = + this.workspace + .getFlyout() + .getCategoryScrollPosition(selectedCategoryName).y * scale; + this.workspace + .getFlyout() + .getWorkspace() + .scrollbar.setY(newCategoryScrollPosition + offsetWithinCategory); const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; - queue.forEach(fn => fn()); + queue.forEach((fn) => fn()); } - withToolboxUpdates (fn) { + withToolboxUpdates(fn) { // if there is a queued toolbox update, we need to wait if (this.toolboxUpdateTimeout) { this.toolboxUpdateQueue.push(fn); @@ -260,42 +339,68 @@ class Blocks extends React.Component { } } - attachVM () { + attachVM() { this.workspace.addChangeListener(this.props.vm.blockListener); - this.flyoutWorkspace = this.workspace - .getFlyout() - .getWorkspace(); - this.flyoutWorkspace.addChangeListener(this.props.vm.flyoutBlockListener); - this.flyoutWorkspace.addChangeListener(this.props.vm.monitorBlockListener); - this.props.vm.addListener('SCRIPT_GLOW_ON', this.onScriptGlowOn); - this.props.vm.addListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff); - this.props.vm.addListener('BLOCK_GLOW_ON', this.onBlockGlowOn); - this.props.vm.addListener('BLOCK_GLOW_OFF', this.onBlockGlowOff); - this.props.vm.addListener('VISUAL_REPORT', this.onVisualReport); - this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate); - this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate); - this.props.vm.addListener('MONITORS_UPDATE', this.handleMonitorsUpdate); - this.props.vm.addListener('EXTENSION_ADDED', this.handleExtensionAdded); - this.props.vm.addListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); - this.props.vm.addListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); - this.props.vm.addListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); - } - detachVM () { - this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn); - this.props.vm.removeListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff); - this.props.vm.removeListener('BLOCK_GLOW_ON', this.onBlockGlowOn); - this.props.vm.removeListener('BLOCK_GLOW_OFF', this.onBlockGlowOff); - this.props.vm.removeListener('VISUAL_REPORT', this.onVisualReport); - this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate); - this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate); - this.props.vm.removeListener('MONITORS_UPDATE', this.handleMonitorsUpdate); - this.props.vm.removeListener('EXTENSION_ADDED', this.handleExtensionAdded); - this.props.vm.removeListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); - this.props.vm.removeListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); - this.props.vm.removeListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); - } - - updateToolboxBlockValue (id, value) { + this.flyoutWorkspace = this.workspace.getFlyout().getWorkspace(); + this.flyoutWorkspace.addChangeListener( + this.props.vm.flyoutBlockListener + ); + this.flyoutWorkspace.addChangeListener( + this.props.vm.monitorBlockListener + ); + this.props.vm.addListener("SCRIPT_GLOW_ON", this.onScriptGlowOn); + this.props.vm.addListener("SCRIPT_GLOW_OFF", this.onScriptGlowOff); + this.props.vm.addListener("BLOCK_GLOW_ON", this.onBlockGlowOn); + this.props.vm.addListener("BLOCK_GLOW_OFF", this.onBlockGlowOff); + this.props.vm.addListener("VISUAL_REPORT", this.onVisualReport); + this.props.vm.addListener("workspaceUpdate", this.onWorkspaceUpdate); + this.props.vm.addListener("targetsUpdate", this.onTargetsUpdate); + this.props.vm.addListener("MONITORS_UPDATE", this.handleMonitorsUpdate); + this.props.vm.addListener("EXTENSION_ADDED", this.handleExtensionAdded); + this.props.vm.addListener( + "BLOCKSINFO_UPDATE", + this.handleBlocksInfoUpdate + ); + this.props.vm.addListener( + "PERIPHERAL_CONNECTED", + this.handleStatusButtonUpdate + ); + this.props.vm.addListener( + "PERIPHERAL_DISCONNECTED", + this.handleStatusButtonUpdate + ); + } + detachVM() { + this.props.vm.removeListener("SCRIPT_GLOW_ON", this.onScriptGlowOn); + this.props.vm.removeListener("SCRIPT_GLOW_OFF", this.onScriptGlowOff); + this.props.vm.removeListener("BLOCK_GLOW_ON", this.onBlockGlowOn); + this.props.vm.removeListener("BLOCK_GLOW_OFF", this.onBlockGlowOff); + this.props.vm.removeListener("VISUAL_REPORT", this.onVisualReport); + this.props.vm.removeListener("workspaceUpdate", this.onWorkspaceUpdate); + this.props.vm.removeListener("targetsUpdate", this.onTargetsUpdate); + this.props.vm.removeListener( + "MONITORS_UPDATE", + this.handleMonitorsUpdate + ); + this.props.vm.removeListener( + "EXTENSION_ADDED", + this.handleExtensionAdded + ); + this.props.vm.removeListener( + "BLOCKSINFO_UPDATE", + this.handleBlocksInfoUpdate + ); + this.props.vm.removeListener( + "PERIPHERAL_CONNECTED", + this.handleStatusButtonUpdate + ); + this.props.vm.removeListener( + "PERIPHERAL_DISCONNECTED", + this.handleStatusButtonUpdate + ); + } + + updateToolboxBlockValue(id, value) { this.withToolboxUpdates(() => { const block = this.workspace .getFlyout() @@ -307,15 +412,21 @@ class Blocks extends React.Component { }); } - onTargetsUpdate () { + onTargetsUpdate() { if (this.props.vm.editingTarget && this.workspace.getFlyout()) { - ['glide', 'move', 'set'].forEach(prefix => { - this.updateToolboxBlockValue(`${prefix}x`, Math.round(this.props.vm.editingTarget.x).toString()); - this.updateToolboxBlockValue(`${prefix}y`, Math.round(this.props.vm.editingTarget.y).toString()); + ["glide", "move", "set"].forEach((prefix) => { + this.updateToolboxBlockValue( + `${prefix}x`, + Math.round(this.props.vm.editingTarget.x).toString() + ); + this.updateToolboxBlockValue( + `${prefix}y`, + Math.round(this.props.vm.editingTarget.y).toString() + ); }); } } - onWorkspaceMetricsChange () { + onWorkspaceMetricsChange() { const target = this.props.vm.editingTarget; if (target && target.id) { // Dispatch updateMetrics later, since onWorkspaceMetricsChange may be (very indirectly) @@ -326,32 +437,32 @@ class Blocks extends React.Component { targetID: target.id, scrollX: this.workspace.scrollX, scrollY: this.workspace.scrollY, - scale: this.workspace.scale + scale: this.workspace.scale, }); }, 0); } } - onScriptGlowOn (data) { + onScriptGlowOn(data) { this.ScratchBlocks.glowStack(data.id, true); } - onScriptGlowOff (data) { + onScriptGlowOff(data) { this.ScratchBlocks.glowStack(data.id, false); } - onBlockGlowOn (data) { + onBlockGlowOn(data) { // No-op, support may be added in the future } - onBlockGlowOff (data) { + onBlockGlowOff(data) { // No-op, support may be added in the future } - onVisualReport (data) { + onVisualReport(data) { this.ScratchBlocks.reportValue(data.id, data.value); } - getToolboxXML () { + getToolboxXML() { // Use try/catch because this requires digging pretty deep into the VM // Code inside intentionally ignores several error situations (no stage, etc.) // Because they would get caught by this try/catch try { - let {editingTarget: target, runtime} = this.props.vm; + let { editingTarget: target, runtime } = this.props.vm; const stage = runtime.getTargetForStage(); if (!target) target = stage; // If no editingTarget, use the stage @@ -362,24 +473,33 @@ class Blocks extends React.Component { this.props.vm.runtime.getBlocksXML(target), this.props.theme ); - return makeToolboxXML(false, target.isStage, target.id, dynamicBlocksXML, + return makeToolboxXML( + false, + target.isStage, + target.id, + dynamicBlocksXML, targetCostumes[targetCostumes.length - 1].name, stageCostumes[stageCostumes.length - 1].name, - targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '', + targetSounds.length > 0 + ? targetSounds[targetSounds.length - 1].name + : "", getColorsForTheme(this.props.theme) ); } catch { return null; } } - onWorkspaceUpdate (data) { + onWorkspaceUpdate(data) { // When we change sprites, update the toolbox to have the new sprite's blocks const toolboxXML = this.getToolboxXML(); if (toolboxXML) { this.props.updateToolboxState(toolboxXML); } - if (this.props.vm.editingTarget && !this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]) { + if ( + this.props.vm.editingTarget && + !this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id] + ) { this.onWorkspaceMetricsChange(); } @@ -387,7 +507,10 @@ class Blocks extends React.Component { this.workspace.removeChangeListener(this.props.vm.blockListener); const dom = this.ScratchBlocks.utils.xml.textToDom(data.xml); try { - this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); + this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml( + dom, + this.workspace + ); } catch (error) { // The workspace is likely incomplete. What did update should be // functional. @@ -405,8 +528,14 @@ class Blocks extends React.Component { } this.workspace.addChangeListener(this.props.vm.blockListener); - if (this.props.vm.editingTarget && this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]) { - const {scrollX, scrollY, scale} = this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]; + if ( + this.props.vm.editingTarget && + this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id] + ) { + const { scrollX, scrollY, scale } = + this.props.workspaceMetrics.targets[ + this.props.vm.editingTarget.id + ]; this.workspace.scrollX = scrollX; this.workspace.scrollY = scrollY; this.workspace.scale = scale; @@ -418,14 +547,14 @@ class Blocks extends React.Component { // workspace to be 'undone' here. this.workspace.clearUndo(); } - handleMonitorsUpdate (monitors) { + handleMonitorsUpdate(monitors) { // Update the checkboxes of the relevant monitors. // TODO: What about monitors that have fields? See todo in scratch-vm blocks.js changeBlock: // https://github.com/LLK/scratch-vm/blob/2373f9483edaf705f11d62662f7bb2a57fbb5e28/src/engine/blocks.js#L569-L576 const flyout = this.workspace.getFlyout(); for (const monitor of monitors.values()) { - const blockId = monitor.get('id'); - const isVisible = monitor.get('visible'); + const blockId = monitor.get("id"); + const isVisible = monitor.get("visible"); flyout.setCheckboxState(blockId, isVisible); // We also need to update the isMonitored flag for this block on the VM, since it's used to determine // whether the checkbox is activated or not when the checkbox is re-displayed (e.g. local variables/blocks @@ -436,28 +565,37 @@ class Blocks extends React.Component { } } } - handleExtensionAdded (categoryInfo) { - const defineBlocks = blockInfoArray => { + handleExtensionAdded(categoryInfo) { + const defineBlocks = (blockInfoArray) => { if (blockInfoArray && blockInfoArray.length > 0) { const staticBlocksJson = []; const dynamicBlocksInfo = []; - blockInfoArray.forEach(blockInfo => { + blockInfoArray.forEach((blockInfo) => { if (blockInfo.info && blockInfo.info.isDynamic) { dynamicBlocksInfo.push(blockInfo); } else if (blockInfo.json) { - staticBlocksJson.push(injectExtensionBlockTheme(blockInfo.json, this.props.theme)); + staticBlocksJson.push( + injectExtensionBlockTheme( + blockInfo.json, + this.props.theme + ) + ); } // otherwise it's a non-block entry such as '---' }); this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson); - dynamicBlocksInfo.forEach(blockInfo => { + dynamicBlocksInfo.forEach((blockInfo) => { // This is creating the block factory / constructor -- NOT a specific instance of the block. // The factory should only know static info about the block: the category info and the opcode. // Anything else will be picked up from the XML attached to the block instance. const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`; - const blockDefinition = - defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode); + const blockDefinition = defineDynamicBlock( + this.ScratchBlocks, + categoryInfo, + blockInfo, + extendedOpcode + ); this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition; }); } @@ -466,8 +604,12 @@ class Blocks extends React.Component { // scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block) // these actually define blocks and MUST run regardless of the UI state defineBlocks( - Object.getOwnPropertyNames(categoryInfo.customFieldTypes) - .map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition)); + Object.getOwnPropertyNames(categoryInfo.customFieldTypes).map( + (fieldTypeName) => + categoryInfo.customFieldTypes[fieldTypeName] + .scratchBlocksDefinition + ) + ); defineBlocks(categoryInfo.menus); defineBlocks(categoryInfo.blocks); @@ -477,12 +619,14 @@ class Blocks extends React.Component { this.props.updateToolboxState(toolboxXML); } } - handleBlocksInfoUpdate (categoryInfo) { + handleBlocksInfoUpdate(categoryInfo) { // @todo Later we should replace this to avoid all the warnings from redefining blocks. this.handleExtensionAdded(categoryInfo); } - handleCategorySelected (categoryId) { - const extension = extensionData.find(ext => ext.extensionId === categoryId); + handleCategorySelected(categoryId) { + const extension = extensionData.find( + (ext) => ext.extensionId === categoryId + ); if (extension && extension.launchPeripheralConnectionFlow) { this.handleConnectionModalStart(categoryId); } @@ -492,29 +636,35 @@ class Blocks extends React.Component { toolbox.setSelectedItem(toolbox.getToolboxItemById(categoryId)); }); } - setBlocks (blocks) { + setBlocks(blocks) { this.blocks = blocks; } - handlePromptStart (message, defaultValue, callback, optTitle, optVarType) { - const p = {prompt: {callback, message, defaultValue}}; - p.prompt.title = optTitle ? optTitle : - this.ScratchBlocks.Msg.VARIABLE_MODAL_TITLE; - p.prompt.varType = typeof optVarType === 'string' ? - optVarType : this.ScratchBlocks.SCALAR_VARIABLE_TYPE; + handlePromptStart(message, defaultValue, callback, optTitle, optVarType) { + const p = { prompt: { callback, message, defaultValue } }; + p.prompt.title = optTitle + ? optTitle + : this.ScratchBlocks.Msg.VARIABLE_MODAL_TITLE; + p.prompt.varType = + typeof optVarType === "string" + ? optVarType + : this.ScratchBlocks.SCALAR_VARIABLE_TYPE; p.prompt.showVariableOptions = // This flag means that we should show variable/list options about scope optVarType !== this.ScratchBlocks.BROADCAST_MESSAGE_VARIABLE_TYPE && - p.prompt.title !== this.ScratchBlocks.Msg.RENAME_VARIABLE_MODAL_TITLE && + p.prompt.title !== + this.ScratchBlocks.Msg.RENAME_VARIABLE_MODAL_TITLE && p.prompt.title !== this.ScratchBlocks.Msg.RENAME_LIST_MODAL_TITLE; - p.prompt.showCloudOption = (optVarType === this.ScratchBlocks.SCALAR_VARIABLE_TYPE) && this.props.canUseCloud; + p.prompt.showCloudOption = + optVarType === this.ScratchBlocks.SCALAR_VARIABLE_TYPE && + this.props.canUseCloud; this.setState(p); } - handleConnectionModalStart (extensionId) { + handleConnectionModalStart(extensionId) { this.props.onOpenConnectionModal(extensionId); } - handleStatusButtonUpdate () { + handleStatusButtonUpdate() { this.ScratchBlocks.refreshStatusButtons(this.workspace); } - handleOpenSoundRecorder () { + handleOpenSoundRecorder() { this.props.onOpenSoundRecorder(); } @@ -523,32 +673,40 @@ class Blocks extends React.Component { * and additional potentially conflicting variable names from the VM * to the variable validation prompt callback used in scratch-blocks. */ - handlePromptCallback (input, variableOptions) { + handlePromptCallback(input, variableOptions) { this.state.prompt.callback( input, - this.props.vm.runtime.getAllVarNamesOfType(this.state.prompt.varType), - variableOptions); + this.props.vm.runtime.getAllVarNamesOfType( + this.state.prompt.varType + ), + variableOptions + ); this.handlePromptClose(); } - handlePromptClose () { - this.setState({prompt: null}); + handlePromptClose() { + this.setState({ prompt: null }); } - handleCustomProceduresClose (data) { + handleCustomProceduresClose(data) { this.props.onRequestCloseCustomProcedures(data); const ws = this.workspace; this.updateToolbox(); - ws.getToolbox().selectCategoryByName('myBlocks'); + ws.getToolbox().selectCategoryByName("myBlocks"); } - handleDrop (dragInfo) { + handleDrop(dragInfo) { fetch(dragInfo.payload.bodyUrl) - .then(response => response.json()) - .then(blocks => this.props.vm.shareBlocksToTarget(blocks, this.props.vm.editingTarget.id)) + .then((response) => response.json()) + .then((blocks) => + this.props.vm.shareBlocksToTarget( + blocks, + this.props.vm.editingTarget.id + ) + ) .then(() => { this.props.vm.refreshWorkspace(); this.updateToolbox(); // To show new variables/custom blocks }); } - render () { + render() { /* eslint-disable no-unused-vars */ const { anyModalVisible, @@ -585,10 +743,15 @@ class Blocks extends React.Component { @@ -635,10 +798,10 @@ Blocks.propTypes = { zoom: PropTypes.shape({ controls: PropTypes.bool, wheel: PropTypes.bool, - startScale: PropTypes.number + startScale: PropTypes.number, }), comments: PropTypes.bool, - collapse: PropTypes.bool + collapse: PropTypes.bool, }), stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired, theme: PropTypes.oneOf(Object.keys(themeMap)), @@ -648,23 +811,23 @@ Blocks.propTypes = { useCatBlocks: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired, workspaceMetrics: PropTypes.shape({ - targets: PropTypes.objectOf(PropTypes.object) - }) + targets: PropTypes.objectOf(PropTypes.object), + }), }; Blocks.defaultOptions = { zoom: { controls: true, wheel: false, - startScale: BLOCKS_DEFAULT_SCALE + startScale: BLOCKS_DEFAULT_SCALE, }, move: { - wheel: true, + wheel: true, }, grid: { spacing: 40, length: 2, - colour: '#ddd' + colour: "#ddd", }, comments: true, collapse: false, @@ -675,14 +838,14 @@ Blocks.defaultOptions = { Blocks.defaultProps = { isVisible: true, options: Blocks.defaultOptions, - theme: DEFAULT_THEME + theme: DEFAULT_THEME, }; -const mapStateToProps = state => ({ - anyModalVisible: ( - Object.keys(state.scratchGui.modals).some(key => state.scratchGui.modals[key]) || - state.scratchGui.mode.isFullScreen - ), +const mapStateToProps = (state) => ({ + anyModalVisible: + Object.keys(state.scratchGui.modals).some( + (key) => state.scratchGui.modals[key] + ) || state.scratchGui.mode.isFullScreen, extensionLibraryVisible: state.scratchGui.modals.extensionLibrary, isRtl: state.locales.isRtl, locale: state.locales.locale, @@ -690,13 +853,15 @@ const mapStateToProps = state => ({ toolboxXML: state.scratchGui.toolbox.toolboxXML, customProceduresVisible: state.scratchGui.customProcedures.active, workspaceMetrics: state.scratchGui.workspaceMetrics, - useCatBlocks: isTimeTravel2020(state) + useCatBlocks: isTimeTravel2020(state), }); -const mapDispatchToProps = dispatch => ({ - onActivateColorPicker: callback => dispatch(activateColorPicker(callback)), - onActivateCustomProcedures: (data, callback) => dispatch(activateCustomProcedures(data, callback)), - onOpenConnectionModal: id => { +const mapDispatchToProps = (dispatch) => ({ + onActivateColorPicker: (callback) => + dispatch(activateColorPicker(callback)), + onActivateCustomProcedures: (data, callback) => + dispatch(activateCustomProcedures(data, callback)), + onOpenConnectionModal: (id) => { dispatch(setConnectionModalExtensionId(id)); dispatch(openConnectionModal()); }, @@ -707,20 +872,17 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseExtensionLibrary: () => { dispatch(closeExtensionLibrary()); }, - onRequestCloseCustomProcedures: data => { + onRequestCloseCustomProcedures: (data) => { dispatch(deactivateCustomProcedures(data)); }, - updateToolboxState: toolboxXML => { + updateToolboxState: (toolboxXML) => { dispatch(updateToolbox(toolboxXML)); }, - updateMetrics: metrics => { + updateMetrics: (metrics) => { dispatch(updateMetrics(metrics)); - } + }, }); -export default errorBoundaryHOC('Blocks')( - connect( - mapStateToProps, - mapDispatchToProps - )(Blocks) +export default errorBoundaryHOC("Blocks")( + connect(mapStateToProps, mapDispatchToProps)(Blocks) ); From b30a1c3464cff8d5fd6e0e5bde6a1757ed2d60b3 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 5 Aug 2024 13:32:41 -0700 Subject: [PATCH 36/76] fix: specify the function to be used for prompting about variables (#17) --- packages/scratch-gui/src/containers/blocks.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 96dcba775c..7e39c14992 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -90,6 +90,9 @@ class Blocks extends React.Component { "setLocale", ]); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); + this.ScratchBlocks.ScratchVariables.setPromptHandler( + this.handlePromptStart + ); this.ScratchBlocks.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; From f7fc9dc09d8db8fcb8ce9e58b24c16fad4de58aa Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 6 Aug 2024 09:40:36 -0700 Subject: [PATCH 37/76] fix: update the toolbox in response to procedure deletion/creation (#18) --- packages/scratch-gui/src/containers/blocks.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 7e39c14992..c6e7f8977e 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -151,7 +151,11 @@ class Blocks extends React.Component { if ( event.type === this.ScratchBlocks.Events.VAR_CREATE || event.type === this.ScratchBlocks.Events.VAR_RENAME || - event.type === this.ScratchBlocks.Events.VAR_DELETE + event.type === this.ScratchBlocks.Events.VAR_DELETE || + (event.type === this.ScratchBlocks.Events.BLOCK_DELETE && + event.oldJson.type === "procedures_definition") || + (event.type === this.ScratchBlocks.Events.BLOCK_CREATE && + event.json.type === "procedures_definition") ) { this.requestToolboxUpdate(); } From 8bcd420c7b81c995ccc2fffbafc2f575643762d1 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 12 Aug 2024 15:51:00 -0700 Subject: [PATCH 38/76] chore: don't specify the renderer/theme (#20) --- packages/scratch-gui/src/containers/blocks.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index c6e7f8977e..87fa082ed2 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -119,10 +119,6 @@ class Blocks extends React.Component { this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); - const theme = this.ScratchBlocks.Theme.defineTheme("Scratch", { - base: this.ScratchBlocks.Themes.Zelos, - startHats: true, - }); const workspaceConfig = defaultsDeep( {}, Blocks.defaultOptions, @@ -131,8 +127,6 @@ class Blocks extends React.Component { rtl: this.props.isRtl, toolbox: this.props.toolboxXML, colours: getColorsForTheme(this.props.theme), - renderer: "zelos", - theme: theme, } ); this.workspace = this.ScratchBlocks.inject( From 4638b40a6c483634b07aed9b74859c1d5b5320ef Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 12 Aug 2024 15:53:44 -0700 Subject: [PATCH 39/76] fix: only refresh the toolbox when procedures are created via undo (#19) * fix: only refresh the toolbox when procedures are created via undo * chore: add comment clarifying procedure block creation handling --- packages/scratch-gui/src/containers/blocks.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 87fa082ed2..b20a48a1ad 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -148,8 +148,12 @@ class Blocks extends React.Component { event.type === this.ScratchBlocks.Events.VAR_DELETE || (event.type === this.ScratchBlocks.Events.BLOCK_DELETE && event.oldJson.type === "procedures_definition") || + // Only refresh the toolbox when procedure block creations are + // triggered by undoing a deletion (implied by recordUndo being + // false on the event). (event.type === this.ScratchBlocks.Events.BLOCK_CREATE && - event.json.type === "procedures_definition") + event.json.type === "procedures_definition" && + !event.recordUndo) ) { this.requestToolboxUpdate(); } From 64b7d3d4e127aa1dac01db02fd3e605dfe99fcd4 Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Wed, 21 Aug 2024 16:02:42 +0000 Subject: [PATCH 40/76] fix: add pinch to zoom support (#21) --- packages/scratch-gui/src/containers/blocks.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index b20a48a1ad..c2550e90dd 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -823,7 +823,8 @@ Blocks.propTypes = { Blocks.defaultOptions = { zoom: { controls: true, - wheel: false, + wheel: true, + pinch: true, startScale: BLOCKS_DEFAULT_SCALE, }, move: { From 217a3c01b0a1f05d67c86e2fbeeae6e2d6355e68 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 3 Sep 2024 11:42:47 -0700 Subject: [PATCH 41/76] fix: make dropdown menu shadow block colors consistent (#22) * chore: format blocks.js * fix: define shadow block colors consistently with scratch-blocks * refactor: clean up sound menu recording handler --- packages/scratch-gui/src/lib/blocks.js | 337 +++++++++++++++---------- 1 file changed, 203 insertions(+), 134 deletions(-) diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 3230729c8a..149d67897f 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -5,126 +5,154 @@ * @return {ScratchBlocks} ScratchBlocks connected with the vm */ export default function (vm, useCatBlocks) { - const {ScratchBlocks} = useCatBlocks ? require('cat-blocks') : require('scratch-blocks'); - const jsonForMenuBlock = function (name, menuOptionsFn, colors, start) { + const { ScratchBlocks } = useCatBlocks + ? require("cat-blocks") + : require("scratch-blocks"); + const jsonForMenuBlock = function (name, menuOptionsFn, category, start) { return { - message0: '%1', + message0: "%1", args0: [ { - type: 'field_dropdown', + type: "field_dropdown", name: name, options: function () { return start.concat(menuOptionsFn()); - } - } + }, + }, ], inputsInline: true, - output: 'String', - colour: colors.secondary, - colourSecondary: colors.secondary, - colourTertiary: colors.tertiary, - colourQuaternary: colors.quaternary, - outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND + output: "String", + outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND, + extensions: [`colours_${category}`], }; }; - const jsonForHatBlockMenu = function (hatName, name, menuOptionsFn, colors, start) { + const jsonForHatBlockMenu = function ( + hatName, + name, + menuOptionsFn, + category, + start + ) { return { message0: hatName, args0: [ { - type: 'field_dropdown', + type: "field_dropdown", name: name, options: function () { return start.concat(menuOptionsFn()); - } - } + }, + }, ], - colour: colors.primary, - colourSecondary: colors.secondary, - colourTertiary: colors.tertiary, - colourQuaternary: colors.quaternary, - extensions: ['shape_hat'] + extensions: [`colours_${category}`, "shape_hat"], }; }; - const jsonForSensingMenus = function (menuOptionsFn) { return { message0: ScratchBlocks.Msg.SENSING_OF, args0: [ { - type: 'field_dropdown', - name: 'PROPERTY', + type: "field_dropdown", + name: "PROPERTY", options: function () { return menuOptionsFn(); - } - + }, }, { - type: 'input_value', - name: 'OBJECT' - } + type: "input_value", + name: "OBJECT", + }, ], output: true, - colour: ScratchBlocks.Colours.sensing.primary, - colourSecondary: ScratchBlocks.Colours.sensing.secondary, - colourTertiary: ScratchBlocks.Colours.sensing.tertiary, - colourQuaternary: ScratchBlocks.Colours.sensing.quaternary, - outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND + outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND, + extensions: ["colours_sensing"], }; }; const soundsMenu = function () { - let menu = [['', '']]; + let menu = [["", ""]]; if (vm.editingTarget && vm.editingTarget.sprite.sounds.length > 0) { - menu = vm.editingTarget.sprite.sounds.map(sound => [sound.name, sound.name]); + menu = vm.editingTarget.sprite.sounds.map((sound) => [ + sound.name, + sound.name, + ]); } menu.push([ - ScratchBlocks.ScratchMsgs.translate('SOUND_RECORD', 'record...'), - 'SOUND_RECORD' + ScratchBlocks.ScratchMsgs.translate("SOUND_RECORD", "record..."), + "SOUND_RECORD", ]); return menu; }; const costumesMenu = function () { if (vm.editingTarget && vm.editingTarget.getCostumes().length > 0) { - return vm.editingTarget.getCostumes().map(costume => [costume.name, costume.name]); + return vm.editingTarget + .getCostumes() + .map((costume) => [costume.name, costume.name]); } - return [['', '']]; + return [["", ""]]; }; const backdropsMenu = function () { - const next = ScratchBlocks.ScratchMsgs.translate('LOOKS_NEXTBACKDROP', 'next backdrop'); - const previous = ScratchBlocks.ScratchMsgs.translate('LOOKS_PREVIOUSBACKDROP', 'previous backdrop'); - const random = ScratchBlocks.ScratchMsgs.translate('LOOKS_RANDOMBACKDROP', 'random backdrop'); - if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) { - return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name]) - .concat([[next, 'next backdrop'], - [previous, 'previous backdrop'], - [random, 'random backdrop']]); + const next = ScratchBlocks.ScratchMsgs.translate( + "LOOKS_NEXTBACKDROP", + "next backdrop" + ); + const previous = ScratchBlocks.ScratchMsgs.translate( + "LOOKS_PREVIOUSBACKDROP", + "previous backdrop" + ); + const random = ScratchBlocks.ScratchMsgs.translate( + "LOOKS_RANDOMBACKDROP", + "random backdrop" + ); + if ( + vm.runtime.targets[0] && + vm.runtime.targets[0].getCostumes().length > 0 + ) { + return vm.runtime.targets[0] + .getCostumes() + .map((costume) => [costume.name, costume.name]) + .concat([ + [next, "next backdrop"], + [previous, "previous backdrop"], + [random, "random backdrop"], + ]); } - return [['', '']]; + return [["", ""]]; }; const backdropNamesMenu = function () { const stage = vm.runtime.getTargetForStage(); if (stage && stage.getCostumes().length > 0) { - return stage.getCostumes().map(costume => [costume.name, costume.name]); + return stage + .getCostumes() + .map((costume) => [costume.name, costume.name]); } - return [['', '']]; + return [["", ""]]; }; const spriteMenu = function () { const sprites = []; for (const targetId in vm.runtime.targets) { - if (!Object.prototype.hasOwnProperty.call(vm.runtime.targets, targetId)) continue; + if ( + !Object.prototype.hasOwnProperty.call( + vm.runtime.targets, + targetId + ) + ) + continue; if (vm.runtime.targets[targetId].isOriginal) { if (!vm.runtime.targets[targetId].isStage) { if (vm.runtime.targets[targetId] === vm.editingTarget) { continue; } - sprites.push([vm.runtime.targets[targetId].sprite.name, vm.runtime.targets[targetId].sprite.name]); + sprites.push([ + vm.runtime.targets[targetId].sprite.name, + vm.runtime.targets[targetId].sprite.name, + ]); } } } @@ -135,90 +163,100 @@ export default function (vm, useCatBlocks) { if (vm.editingTarget && vm.editingTarget.isStage) { const menu = spriteMenu(); if (menu.length === 0) { - return [['', '']]; // Empty menu matches Scratch 2 behavior + return [["", ""]]; // Empty menu matches Scratch 2 behavior } return menu; } - const myself = ScratchBlocks.ScratchMsgs.translate('CONTROL_CREATECLONEOF_MYSELF', 'myself'); - return [[myself, '_myself_']].concat(spriteMenu()); + const myself = ScratchBlocks.ScratchMsgs.translate( + "CONTROL_CREATECLONEOF_MYSELF", + "myself" + ); + return [[myself, "_myself_"]].concat(spriteMenu()); }; - const soundColors = ScratchBlocks.Colours.sounds; - - const looksColors = ScratchBlocks.Colours.looks; - - const motionColors = ScratchBlocks.Colours.motion; - - const sensingColors = ScratchBlocks.Colours.sensing; - - const controlColors = ScratchBlocks.Colours.control; - - const eventColors = ScratchBlocks.Colours.event; - ScratchBlocks.Blocks.sound_sounds_menu.init = function () { - const json = jsonForMenuBlock('SOUND_MENU', soundsMenu, soundColors, []); + const json = jsonForMenuBlock("SOUND_MENU", soundsMenu, "sounds", []); this.jsonInit(json); - this.inputList[0].removeField('SOUND_MENU'); - this.inputList[0].appendField(new ScratchBlocks.FieldDropdown(() => { - return soundsMenu(); - }, (newValue) => { - if (newValue === 'SOUND_RECORD') { - ScratchBlocks.recordSoundCallback(); - return null; + this.getField("SOUND_MENU").setValidator((newValue) => { + if (newValue === "SOUND_RECORD") { + ScratchBlocks.recordSoundCallback(); + return null; } return newValue; - }), 'SOUND_MENU'); + }); }; ScratchBlocks.Blocks.looks_costume.init = function () { - const json = jsonForMenuBlock('COSTUME', costumesMenu, looksColors, []); + const json = jsonForMenuBlock("COSTUME", costumesMenu, "looks", []); this.jsonInit(json); }; ScratchBlocks.Blocks.looks_backdrops.init = function () { - const json = jsonForMenuBlock('BACKDROP', backdropsMenu, looksColors, []); + const json = jsonForMenuBlock("BACKDROP", backdropsMenu, "looks", []); this.jsonInit(json); }; ScratchBlocks.Blocks.event_whenbackdropswitchesto.init = function () { const json = jsonForHatBlockMenu( ScratchBlocks.Msg.EVENT_WHENBACKDROPSWITCHESTO, - 'BACKDROP', backdropNamesMenu, eventColors, []); + "BACKDROP", + backdropNamesMenu, + "event", + [] + ); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_pointtowards_menu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_POINTTOWARDS_POINTER', 'mouse-pointer'); - const json = jsonForMenuBlock('TOWARDS', spriteMenu, motionColors, [ - [mouse, '_mouse_'] + const mouse = ScratchBlocks.ScratchMsgs.translate( + "MOTION_POINTTOWARDS_POINTER", + "mouse-pointer" + ); + const json = jsonForMenuBlock("TOWARDS", spriteMenu, "motion", [ + [mouse, "_mouse_"], ]); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_goto_menu.init = function () { - const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_RANDOM', 'random position'); - const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_POINTER', 'mouse-pointer'); - const json = jsonForMenuBlock('TO', spriteMenu, motionColors, [ - [random, '_random_'], - [mouse, '_mouse_'] + const random = ScratchBlocks.ScratchMsgs.translate( + "MOTION_GOTO_RANDOM", + "random position" + ); + const mouse = ScratchBlocks.ScratchMsgs.translate( + "MOTION_GOTO_POINTER", + "mouse-pointer" + ); + const json = jsonForMenuBlock("TO", spriteMenu, "motion", [ + [random, "_random_"], + [mouse, "_mouse_"], ]); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_glideto_menu.init = function () { - const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_RANDOM', 'random position'); - const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_POINTER', 'mouse-pointer'); - const json = jsonForMenuBlock('TO', spriteMenu, motionColors, [ - [random, '_random_'], - [mouse, '_mouse_'] + const random = ScratchBlocks.ScratchMsgs.translate( + "MOTION_GLIDETO_RANDOM", + "random position" + ); + const mouse = ScratchBlocks.ScratchMsgs.translate( + "MOTION_GLIDETO_POINTER", + "mouse-pointer" + ); + const json = jsonForMenuBlock("TO", spriteMenu, "motion", [ + [random, "_random_"], + [mouse, "_mouse_"], ]); this.jsonInit(json); }; ScratchBlocks.Blocks.sensing_of_object_menu.init = function () { - const stage = ScratchBlocks.ScratchMsgs.translate('SENSING_OF_STAGE', 'Stage'); - const json = jsonForMenuBlock('OBJECT', spriteMenu, sensingColors, [ - [stage, '_stage_'] + const stage = ScratchBlocks.ScratchMsgs.translate( + "SENSING_OF_STAGE", + "Stage" + ); + const json = jsonForMenuBlock("OBJECT", spriteMenu, "sensing", [ + [stage, "_stage_"], ]); this.jsonInit(json); }; @@ -230,7 +268,7 @@ export default function (vm, useCatBlocks) { // Get the sensing_of block from vm. let defaultSensingOfBlock; const blocks = vm.runtime.flyoutBlocks._blocks; - Object.keys(blocks).forEach(id => { + Object.keys(blocks).forEach((id) => { const block = blocks[id]; if (id === blockType || (block && block.opcode === blockType)) { defaultSensingOfBlock = block; @@ -241,18 +279,18 @@ export default function (vm, useCatBlocks) { // Called every time it opens since it depends on the values in the other block input. const menuFn = function () { const stageOptions = [ - [ScratchBlocks.Msg.SENSING_OF_BACKDROPNUMBER, 'backdrop #'], - [ScratchBlocks.Msg.SENSING_OF_BACKDROPNAME, 'backdrop name'], - [ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume'] + [ScratchBlocks.Msg.SENSING_OF_BACKDROPNUMBER, "backdrop #"], + [ScratchBlocks.Msg.SENSING_OF_BACKDROPNAME, "backdrop name"], + [ScratchBlocks.Msg.SENSING_OF_VOLUME, "volume"], ]; const spriteOptions = [ - [ScratchBlocks.Msg.SENSING_OF_XPOSITION, 'x position'], - [ScratchBlocks.Msg.SENSING_OF_YPOSITION, 'y position'], - [ScratchBlocks.Msg.SENSING_OF_DIRECTION, 'direction'], - [ScratchBlocks.Msg.SENSING_OF_COSTUMENUMBER, 'costume #'], - [ScratchBlocks.Msg.SENSING_OF_COSTUMENAME, 'costume name'], - [ScratchBlocks.Msg.SENSING_OF_SIZE, 'size'], - [ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume'] + [ScratchBlocks.Msg.SENSING_OF_XPOSITION, "x position"], + [ScratchBlocks.Msg.SENSING_OF_YPOSITION, "y position"], + [ScratchBlocks.Msg.SENSING_OF_DIRECTION, "direction"], + [ScratchBlocks.Msg.SENSING_OF_COSTUMENUMBER, "costume #"], + [ScratchBlocks.Msg.SENSING_OF_COSTUMENAME, "costume name"], + [ScratchBlocks.Msg.SENSING_OF_SIZE, "size"], + [ScratchBlocks.Msg.SENSING_OF_VOLUME, "volume"], ]; if (vm.editingTarget) { let lookupBlocks = vm.editingTarget.blocks; @@ -260,31 +298,44 @@ export default function (vm, useCatBlocks) { // The block doesn't exist, but should be in the flyout. Look there. if (!sensingOfBlock) { - sensingOfBlock = vm.runtime.flyoutBlocks.getBlock(blockId) || defaultSensingOfBlock; + sensingOfBlock = + vm.runtime.flyoutBlocks.getBlock(blockId) || + defaultSensingOfBlock; // If we still don't have a block, just return an empty list . This happens during // scratch blocks construction. if (!sensingOfBlock) { - return [['', '']]; + return [["", ""]]; } // The block was in the flyout so look up future block info there. lookupBlocks = vm.runtime.flyoutBlocks; } const sort = function (options) { - options.sort(ScratchBlocks.scratchBlocksUtils.compareStrings); + options.sort( + ScratchBlocks.scratchBlocksUtils.compareStrings + ); }; // Get all the stage variables (no lists) so we can add them to menu when the stage is selected. - const stageVariableOptions = vm.runtime.getTargetForStage().getAllVariableNamesInScopeByType(''); + const stageVariableOptions = vm.runtime + .getTargetForStage() + .getAllVariableNamesInScopeByType(""); sort(stageVariableOptions); - const stageVariableMenuItems = stageVariableOptions.map(variable => [variable, variable]); - if (sensingOfBlock.inputs.OBJECT.shadow !== sensingOfBlock.inputs.OBJECT.block) { + const stageVariableMenuItems = stageVariableOptions.map( + (variable) => [variable, variable] + ); + if ( + sensingOfBlock.inputs.OBJECT.shadow !== + sensingOfBlock.inputs.OBJECT.block + ) { // There's a block dropped on top of the menu. It'd be nice to evaluate it and // return the correct list, but that is tricky. Scratch2 just returns stage options // so just do that here too. return stageOptions.concat(stageVariableMenuItems); } - const menuBlock = lookupBlocks.getBlock(sensingOfBlock.inputs.OBJECT.shadow); + const menuBlock = lookupBlocks.getBlock( + sensingOfBlock.inputs.OBJECT.shadow + ); const selectedItem = menuBlock.fields.OBJECT.value; - if (selectedItem === '_stage_') { + if (selectedItem === "_stage_") { return stageOptions.concat(stageVariableMenuItems); } // Get all the local variables (no lists) and add them to the menu. @@ -292,13 +343,16 @@ export default function (vm, useCatBlocks) { let spriteVariableOptions = []; // The target should exist, but there are ways for it not to (e.g. #4203). if (target) { - spriteVariableOptions = target.getAllVariableNamesInScopeByType('', true); + spriteVariableOptions = + target.getAllVariableNamesInScopeByType("", true); sort(spriteVariableOptions); } - const spriteVariableMenuItems = spriteVariableOptions.map(variable => [variable, variable]); + const spriteVariableMenuItems = spriteVariableOptions.map( + (variable) => [variable, variable] + ); return spriteOptions.concat(spriteVariableMenuItems); } - return [['', '']]; + return [["", ""]]; }; const json = jsonForSensingMenus(menuFn); @@ -306,32 +360,47 @@ export default function (vm, useCatBlocks) { }; ScratchBlocks.Blocks.sensing_distancetomenu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_DISTANCETO_POINTER', 'mouse-pointer'); - const json = jsonForMenuBlock('DISTANCETOMENU', spriteMenu, sensingColors, [ - [mouse, '_mouse_'] + const mouse = ScratchBlocks.ScratchMsgs.translate( + "SENSING_DISTANCETO_POINTER", + "mouse-pointer" + ); + const json = jsonForMenuBlock("DISTANCETOMENU", spriteMenu, "sensing", [ + [mouse, "_mouse_"], ]); this.jsonInit(json); }; ScratchBlocks.Blocks.sensing_touchingobjectmenu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_POINTER', 'mouse-pointer'); - const edge = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_EDGE', 'edge'); - const json = jsonForMenuBlock('TOUCHINGOBJECTMENU', spriteMenu, sensingColors, [ - [mouse, '_mouse_'], - [edge, '_edge_'] - ]); + const mouse = ScratchBlocks.ScratchMsgs.translate( + "SENSING_TOUCHINGOBJECT_POINTER", + "mouse-pointer" + ); + const edge = ScratchBlocks.ScratchMsgs.translate( + "SENSING_TOUCHINGOBJECT_EDGE", + "edge" + ); + const json = jsonForMenuBlock( + "TOUCHINGOBJECTMENU", + spriteMenu, + "sensing", + [ + [mouse, "_mouse_"], + [edge, "_edge_"], + ] + ); this.jsonInit(json); }; ScratchBlocks.Blocks.control_create_clone_of_menu.init = function () { - const json = jsonForMenuBlock('CLONE_OPTION', cloneMenu, controlColors, []); + const json = jsonForMenuBlock("CLONE_OPTION", cloneMenu, "control", []); this.jsonInit(json); }; - ScratchBlocks.CheckableContinuousFlyout.prototype.getCheckboxState = function (blockId) { - const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId]; - return monitoredBlock ? monitoredBlock.isMonitored : false; - }; + ScratchBlocks.CheckableContinuousFlyout.prototype.getCheckboxState = + function (blockId) { + const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId]; + return monitoredBlock ? monitoredBlock.isMonitored : false; + }; // ScratchBlocks.FlyoutExtensionCategoryHeader.getExtensionState = function (extensionId) { // if (vm.getPeripheralIsConnected(extensionId)) { @@ -348,8 +417,8 @@ export default function (vm, useCatBlocks) { // creates a collator. Using this is a lot faster in browsers that create a // collator for every localeCompare call. const collator = new Intl.Collator([], { - sensitivity: 'base', - numeric: true + sensitivity: "base", + numeric: true, }); // ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) { // return collator.compare(str1, str2); From 77c4e2ff7aa17d0eb89f8df8f40d095d251067f7 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 11 Sep 2024 15:11:46 -0700 Subject: [PATCH 42/76] refactor: use block styles instead of directly specifying block colors (#23) * chore: format monitor.jsx * chore: format define-dynamic-block.js * chore: format make-toolbox-xml.js * chore: format blockHelpers.js * chore: format theme definitions * chore: format custom-procedures.jsx * fix: use styles instead of colors for coloring dynamic blocks * refactor: define themes in the format expected by Blockly for BlockStyles * refactor: convert and inject Scratch themes into Blockly * chore: add comments clarifying the color/colour dichotomy --- .../src/components/monitor/monitor.jsx | 128 ++++--- .../scratch-gui/src/containers/blocks.jsx | 46 ++- .../src/containers/custom-procedures.jsx | 120 ++++--- .../src/lib/define-dynamic-block.js | 107 +++--- .../scratch-gui/src/lib/make-toolbox-xml.js | 325 +++++++++++++----- .../src/lib/themes/blockHelpers.js | 83 +++-- .../src/lib/themes/default/index.js | 142 ++++---- .../src/lib/themes/high-contrast/index.js | 134 ++++---- 8 files changed, 650 insertions(+), 435 deletions(-) diff --git a/packages/scratch-gui/src/components/monitor/monitor.jsx b/packages/scratch-gui/src/components/monitor/monitor.jsx index d9ceb92695..e398445708 100644 --- a/packages/scratch-gui/src/components/monitor/monitor.jsx +++ b/packages/scratch-gui/src/components/monitor/monitor.jsx @@ -1,49 +1,56 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import Draggable from 'react-draggable'; -import {FormattedMessage} from 'react-intl'; -import {ContextMenuTrigger} from 'react-contextmenu'; -import {BorderedMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx'; -import Box from '../box/box.jsx'; -import DefaultMonitor from './default-monitor.jsx'; -import LargeMonitor from './large-monitor.jsx'; -import SliderMonitor from '../../containers/slider-monitor.jsx'; -import ListMonitor from '../../containers/list-monitor.jsx'; -import {getColorsForTheme} from '../../lib/themes/index.js'; +import React from "react"; +import ReactDOM from "react-dom"; +import PropTypes from "prop-types"; +import Draggable from "react-draggable"; +import { FormattedMessage } from "react-intl"; +import { ContextMenuTrigger } from "react-contextmenu"; +import { + BorderedMenuItem, + ContextMenu, + MenuItem, +} from "../context-menu/context-menu.jsx"; +import Box from "../box/box.jsx"; +import DefaultMonitor from "./default-monitor.jsx"; +import LargeMonitor from "./large-monitor.jsx"; +import SliderMonitor from "../../containers/slider-monitor.jsx"; +import ListMonitor from "../../containers/list-monitor.jsx"; +import { getColorsForTheme } from "../../lib/themes/index.js"; -import styles from './monitor.css'; +import styles from "./monitor.css"; -// Map category name to color name used in scratch-blocks Blockly.Colours +// Map category name to color name used in scratch-blocks Blockly.Colours. Note +// that Blockly uses the UK spelling of "colour", so fields that interact +// directly with Blockly follow that convention, while Scratch code uses the US +// spelling of "color". const categoryColorMap = { - data: 'data', - sensing: 'sensing', - sound: 'sounds', - looks: 'looks', - motion: 'motion', - list: 'data_lists', - extension: 'pen' + data: "data", + sensing: "sensing", + sound: "sounds", + looks: "looks", + motion: "motion", + list: "data_lists", + extension: "pen", }; const modes = { default: DefaultMonitor, large: LargeMonitor, slider: SliderMonitor, - list: ListMonitor + list: ListMonitor, }; const getCategoryColor = (theme, category) => { const colors = getColorsForTheme(theme); return { - background: colors[categoryColorMap[category]].primary, - text: colors.text + background: colors[categoryColorMap[category]].colourPrimary, + text: colors.text, }; }; -const MonitorComponent = props => ( +const MonitorComponent = (props) => ( ( {React.createElement(modes[props.mode], { - categoryColor: getCategoryColor(props.theme, props.category), - ...props + categoryColor: getCategoryColor( + props.theme, + props.category + ), + ...props, })} - {ReactDOM.createPortal(( + {ReactDOM.createPortal( // Use a portal to render the context menu outside the flow to avoid // positioning conflicts between the monitors `transform: scale` and // the context menus `position: fixed`. For more details, see // http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/ - {props.onSetModeToDefault && + {props.onSetModeToDefault && ( - } - {props.onSetModeToLarge && + + )} + {props.onSetModeToLarge && ( - } - {props.onSetModeToSlider && + + )} + {props.onSetModeToSlider && ( - } - {props.onSliderPromptOpen && props.mode === 'slider' && + + )} + {props.onSliderPromptOpen && props.mode === "slider" && ( - } - {props.onImport && + + )} + {props.onImport && ( - } - {props.onExport && + + )} + {props.onExport && ( - } - {props.onHide && + + )} + {props.onHide && ( - } - - ), document.body)} + + )} + , + document.body + )} - ); const monitorModes = Object.keys(modes); @@ -149,15 +170,12 @@ MonitorComponent.propTypes = { onSetModeToLarge: PropTypes.func, onSetModeToSlider: PropTypes.func, onSliderPromptOpen: PropTypes.func, - theme: PropTypes.string.isRequired + theme: PropTypes.string.isRequired, }; MonitorComponent.defaultProps = { - category: 'extension', - mode: 'default' + category: "extension", + mode: "default", }; -export { - MonitorComponent as default, - monitorModes -}; +export { MonitorComponent as default, monitorModes }; diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index c2550e90dd..ecdc577443 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -23,8 +23,9 @@ import DragConstants from "../lib/drag-constants"; import defineDynamicBlock from "../lib/define-dynamic-block"; import { DEFAULT_THEME, getColorsForTheme, themeMap } from "../lib/themes"; import { - injectExtensionBlockTheme, + injectExtensionBlockIcons, injectExtensionCategoryTheme, + getExtensionColors, } from "../lib/themes/blockHelpers"; import { connect } from "react-redux"; @@ -126,7 +127,10 @@ class Blocks extends React.Component { { rtl: this.props.isRtl, toolbox: this.props.toolboxXML, - colours: getColorsForTheme(this.props.theme), + theme: new this.ScratchBlocks.Theme( + this.props.theme, + getColorsForTheme(this.props.theme) + ), } ); this.workspace = this.ScratchBlocks.inject( @@ -580,7 +584,7 @@ class Blocks extends React.Component { dynamicBlocksInfo.push(blockInfo); } else if (blockInfo.json) { staticBlocksJson.push( - injectExtensionBlockTheme( + injectExtensionBlockIcons( blockInfo.json, this.props.theme ) @@ -617,7 +621,39 @@ class Blocks extends React.Component { ); defineBlocks(categoryInfo.menus); defineBlocks(categoryInfo.blocks); - + // Note that Blockly uses the UK spelling of "colour", so fields that + // interact directly with Blockly follow that convention, while Scratch + // code uses the US spelling of "color". + let colourPrimary = categoryInfo.color1; + let colourSecondary = categoryInfo.color2; + let colourTertiary = categoryInfo.color3; + let colourQuaternary = categoryInfo.color3; + if (this.props.theme !== DEFAULT_THEME) { + const colors = getExtensionColors(this.props.theme); + colourPrimary = colors.colourPrimary; + colourSecondary = colors.colourSecondary; + colourTertiary = colors.colourTertiary; + colourQuaternary = colors.colourQuaternary; + } + this.ScratchBlocks.getMainWorkspace() + .getTheme() + .setBlockStyle(categoryInfo.id, { + colourPrimary, + colourSecondary, + colourTertiary, + colourQuaternary, + }); + this.ScratchBlocks.getMainWorkspace() + .getTheme() + .setBlockStyle(`${categoryInfo.id}_selected`, { + colourPrimary: colourQuaternary, + colourSecondary: colourQuaternary, + colourTertiary: colourQuaternary, + colourQuaternary: colourQuaternary, + }); + this.ScratchBlocks.getMainWorkspace().setTheme( + this.ScratchBlocks.getMainWorkspace().getTheme() + ); // Update the toolbox with new blocks if possible const toolboxXML = this.getToolboxXML(); if (toolboxXML) { @@ -734,6 +770,7 @@ class Blocks extends React.Component { updateMetrics: updateMetricsProp, useCatBlocks, workspaceMetrics, + theme, ...props } = this.props; /* eslint-enable no-unused-vars */ @@ -776,6 +813,7 @@ class Blocks extends React.Component { media: options.media, }} onRequestClose={this.handleCustomProceduresClose} + theme={theme} /> ) : null} diff --git a/packages/scratch-gui/src/containers/custom-procedures.jsx b/packages/scratch-gui/src/containers/custom-procedures.jsx index de7ac0a042..20dd9146e3 100644 --- a/packages/scratch-gui/src/containers/custom-procedures.jsx +++ b/packages/scratch-gui/src/containers/custom-procedures.jsx @@ -1,52 +1,53 @@ -import bindAll from 'lodash.bindall'; -import defaultsDeep from 'lodash.defaultsdeep'; -import PropTypes from 'prop-types'; -import React from 'react'; -import CustomProceduresComponent from '../components/custom-procedures/custom-procedures.jsx'; -import {ScratchBlocks} from 'scratch-blocks'; -import {connect} from 'react-redux'; +import bindAll from "lodash.bindall"; +import defaultsDeep from "lodash.defaultsdeep"; +import PropTypes from "prop-types"; +import React from "react"; +import CustomProceduresComponent from "../components/custom-procedures/custom-procedures.jsx"; +import { getColorsForTheme, themeMap } from "../lib/themes"; +import { ScratchBlocks } from "scratch-blocks"; +import { connect } from "react-redux"; class CustomProcedures extends React.Component { - constructor (props) { + constructor(props) { super(props); bindAll(this, [ - 'handleAddLabel', - 'handleAddBoolean', - 'handleAddTextNumber', - 'handleToggleWarp', - 'handleCancel', - 'handleOk', - 'setBlocks' + "handleAddLabel", + "handleAddBoolean", + "handleAddTextNumber", + "handleToggleWarp", + "handleCancel", + "handleOk", + "setBlocks", ]); this.state = { rtlOffset: 0, - warp: false + warp: false, }; } - componentWillUnmount () { + componentWillUnmount() { if (this.workspace) { this.workspace.dispose(); } } - setBlocks (blocksRef) { + setBlocks(blocksRef) { if (!blocksRef) return; this.blocks = blocksRef; - const workspaceConfig = defaultsDeep({}, + const workspaceConfig = defaultsDeep( + {}, CustomProcedures.defaultOptions, this.props.options, - {rtl: this.props.isRtl} + { rtl: this.props.isRtl } ); - const theme = ScratchBlocks.Theme.defineTheme('Scratch', { - 'base': ScratchBlocks.Themes.Zelos, - 'startHats': true - }); + const theme = new ScratchBlocks.Theme( + this.props.theme, + getColorsForTheme(this.props.theme) + ); workspaceConfig.theme = theme; - workspaceConfig.renderer = 'zelos'; this.workspace = ScratchBlocks.inject(this.blocks, workspaceConfig); // Create the procedure declaration block for editing the mutation. - this.mutationRoot = this.workspace.newBlock('procedures_declaration'); + this.mutationRoot = this.workspace.newBlock("procedures_declaration"); // Make the declaration immovable, undeletable and have no context menu this.mutationRoot.setMovable(false); this.mutationRoot.setDeletable(false); @@ -56,8 +57,9 @@ class CustomProcedures extends React.Component { this.mutationRoot.onChangeFn(); // Keep the block centered on the workspace const metrics = this.workspace.getMetrics(); - const {x, y} = this.mutationRoot.getRelativeToSurfaceXY(); - const dy = (metrics.viewHeight / 2) - (this.mutationRoot.height / 2) - y; + const { x, y } = this.mutationRoot.getRelativeToSurfaceXY(); + const dy = + metrics.viewHeight / 2 - this.mutationRoot.height / 2 - y; let dx; if (this.props.isRtl) { // // TODO: https://github.com/LLK/scratch-gui/issues/2838 @@ -69,8 +71,9 @@ class CustomProcedures extends React.Component { // Calculate a new left postion based on new width // Convert current x position into LTR (mirror) x position (uses original offset) // Use the difference between ltrX and mirrorX as the amount to move - const ltrX = ((metrics.viewWidth / 2) - (this.mutationRoot.width / 2) + 25); - const mirrorX = x - ((x - this.state.rtlOffset) * 2); + const ltrX = + metrics.viewWidth / 2 - this.mutationRoot.width / 2 + 25; + const mirrorX = x - (x - this.state.rtlOffset) * 2; if (mirrorX === ltrX) { return; } @@ -81,19 +84,25 @@ class CustomProcedures extends React.Component { if (this.mutationRoot.width < midPoint) { dx = ltrX; } else if (this.mutationRoot.width < metrics.viewWidth) { - dx = midPoint - ((metrics.viewWidth - this.mutationRoot.width) / 2); + dx = + midPoint - + (metrics.viewWidth - this.mutationRoot.width) / 2; } else { - dx = midPoint + (this.mutationRoot.width - metrics.viewWidth); + dx = + midPoint + + (this.mutationRoot.width - metrics.viewWidth); } this.mutationRoot.moveBy(dx, dy); - this.setState({rtlOffset: this.mutationRoot.getRelativeToSurfaceXY().x}); + this.setState({ + rtlOffset: this.mutationRoot.getRelativeToSurfaceXY().x, + }); return; } if (this.mutationRoot.width > metrics.viewWidth) { dx = dx + this.mutationRoot.width - metrics.viewWidth; } } else { - dx = (metrics.viewWidth / 2) - (this.mutationRoot.width / 2) - x; + dx = metrics.viewWidth / 2 - this.mutationRoot.width / 2 - x; // If the procedure declaration is wider than the view width, // keep the right-hand side of the procedure in view. if (this.mutationRoot.width > metrics.viewWidth) { @@ -105,42 +114,44 @@ class CustomProcedures extends React.Component { this.mutationRoot.domToMutation(this.props.mutator); this.mutationRoot.initSvg(); this.mutationRoot.render(); - this.setState({warp: this.mutationRoot.getWarp()}); + this.setState({ warp: this.mutationRoot.getWarp() }); // Allow the initial events to run to position this block, then focus. setTimeout(() => { this.mutationRoot.focusLastEditor_(); }); } - handleCancel () { + handleCancel() { this.props.onRequestClose(); } - handleOk () { - const newMutation = this.mutationRoot ? this.mutationRoot.mutationToDom(true) : null; + handleOk() { + const newMutation = this.mutationRoot + ? this.mutationRoot.mutationToDom(true) + : null; this.props.onRequestClose(newMutation); } - handleAddLabel () { + handleAddLabel() { if (this.mutationRoot) { this.mutationRoot.addLabelExternal(); } } - handleAddBoolean () { + handleAddBoolean() { if (this.mutationRoot) { this.mutationRoot.addBooleanExternal(); } } - handleAddTextNumber () { + handleAddTextNumber() { if (this.mutationRoot) { this.mutationRoot.addStringNumberExternal(); } } - handleToggleWarp () { + handleToggleWarp() { if (this.mutationRoot) { const newWarp = !this.mutationRoot.getWarp(); this.mutationRoot.setWarp(newWarp); - this.setState({warp: newWarp}); + this.setState({ warp: newWarp }); } } - render () { + render() { return ( ({ +const mapStateToProps = (state) => ({ isRtl: state.locales.isRtl, - mutator: state.scratchGui.customProcedures.mutator + mutator: state.scratchGui.customProcedures.mutator, }); -export default connect( - mapStateToProps -)(CustomProcedures); +export default connect(mapStateToProps)(CustomProcedures); diff --git a/packages/scratch-gui/src/lib/define-dynamic-block.js b/packages/scratch-gui/src/lib/define-dynamic-block.js index 14b0ad9a9a..30727f48e7 100644 --- a/packages/scratch-gui/src/lib/define-dynamic-block.js +++ b/packages/scratch-gui/src/lib/define-dynamic-block.js @@ -1,6 +1,6 @@ // TODO: access `BlockType` and `ArgumentType` without reaching into VM // Should we move these into a new extension support module or something? -import {ArgumentType, BlockType} from '@scratch/scratch-vm'; +import { ArgumentType, BlockType } from "@scratch/scratch-vm"; /** * Define a block using extension info which has the ability to dynamically determine (and update) its layout. @@ -13,74 +13,72 @@ import {ArgumentType, BlockType} from '@scratch/scratch-vm'; * @param {string} extendedOpcode - The opcode for the block (including the extension ID). */ // TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime -const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({ +const defineDynamicBlock = ( + ScratchBlocks, + categoryInfo, + staticBlockInfo, + extendedOpcode +) => ({ init: function () { const blockJson = { type: extendedOpcode, inputsInline: true, category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3 + style: categoryInfo.id, }; // There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of // blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension // blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly // extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon. if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) { - blockJson.extensions = ['scratch_extension']; + blockJson.extensions = ["scratch_extension"]; } // initialize the basics of the block, to be overridden & extended later by `domToMutation` this.jsonInit(blockJson); // initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom` - this.blockInfoText = '{}'; + this.blockInfoText = "{}"; // we need a block info update (through `domToMutation`) before we have a completely initialized block this.needsBlockInfoUpdate = true; }, mutationToDom: function () { - const container = document.createElement('mutation'); - container.setAttribute('blockInfo', this.blockInfoText); + const container = document.createElement("mutation"); + container.setAttribute("blockInfo", this.blockInfoText); return container; }, domToMutation: function (xmlElement) { - const blockInfoText = xmlElement.getAttribute('blockInfo'); + const blockInfoText = xmlElement.getAttribute("blockInfo"); if (!blockInfoText) return; if (!this.needsBlockInfoUpdate) { - throw new Error('Attempted to update block info twice'); + throw new Error("Attempted to update block info twice"); } delete this.needsBlockInfoUpdate; this.blockInfoText = blockInfoText; const blockInfo = JSON.parse(blockInfoText); switch (blockInfo.blockType) { - case BlockType.COMMAND: - case BlockType.CONDITIONAL: - case BlockType.LOOP: - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); - this.setPreviousStatement(true); - this.setNextStatement(!blockInfo.isTerminal); - break; - case BlockType.REPORTER: - this.setOutput(true); - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND); - if (!blockInfo.disableMonitor) { - this.setCheckboxInFlyout(true); - } - break; - case BlockType.BOOLEAN: - this.setOutput(true); - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL); - break; - case BlockType.HAT: - case BlockType.EVENT: - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); - this.setNextStatement(true); - break; - } - - if (blockInfo.color1 || blockInfo.color2 || blockInfo.color3) { - // `setColour` handles undefined parameters by adjusting defined colors - this.setColour(blockInfo.color1, blockInfo.color2, blockInfo.color3); + case BlockType.COMMAND: + case BlockType.CONDITIONAL: + case BlockType.LOOP: + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); + this.setPreviousStatement(true); + this.setNextStatement(!blockInfo.isTerminal); + break; + case BlockType.REPORTER: + this.setOutput(true); + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND); + if (!blockInfo.disableMonitor) { + this.setCheckboxInFlyout(true); + } + break; + case BlockType.BOOLEAN: + this.setOutput(true); + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL); + break; + case BlockType.HAT: + case BlockType.EVENT: + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); + this.setNextStatement(true); + break; } // Layout block arguments @@ -88,20 +86,27 @@ const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extend const blockText = blockInfo.text; const args = []; let argCount = 0; - const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => { - const arg = blockInfo.arguments[argName]; - switch (arg.type) { - case ArgumentType.STRING: - args.push({type: 'input_value', name: argName}); - break; - case ArgumentType.BOOLEAN: - args.push({type: 'input_value', name: argName, check: 'Boolean'}); - break; + const scratchBlocksStyleText = blockText.replace( + /\[(.+?)]/g, + (match, argName) => { + const arg = blockInfo.arguments[argName]; + switch (arg.type) { + case ArgumentType.STRING: + args.push({ type: "input_value", name: argName }); + break; + case ArgumentType.BOOLEAN: + args.push({ + type: "input_value", + name: argName, + check: "Boolean", + }); + break; + } + return `%${++argCount}`; } - return `%${++argCount}`; - }); + ); this.interpolate_(scratchBlocksStyleText, args); - } + }, }); export default defineDynamicBlock; diff --git a/packages/scratch-gui/src/lib/make-toolbox-xml.js b/packages/scratch-gui/src/lib/make-toolbox-xml.js index 532f9e14c0..7d6acc24ab 100644 --- a/packages/scratch-gui/src/lib/make-toolbox-xml.js +++ b/packages/scratch-gui/src/lib/make-toolbox-xml.js @@ -1,5 +1,5 @@ -import {ScratchBlocks} from 'scratch-blocks'; -import {defaultColors} from './themes'; +import { ScratchBlocks } from "scratch-blocks"; +import { defaultColors } from "./themes"; const categorySeparator = ''; @@ -8,15 +8,25 @@ const blockSeparator = ''; // At default scale, about 28px /* eslint-disable no-unused-vars */ const motion = function (isInitialSetup, isStage, targetId, colors) { const stageSelected = ScratchBlocks.ScratchMsgs.translate( - 'MOTION_STAGE_SELECTED', - 'Stage selected: no motion blocks' + "MOTION_STAGE_SELECTED", + "Stage selected: no motion blocks" ); - // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. + // Note: the category's secondaryColour matches up with the blocks' tertiary + // color, both used for border color. Since Blockly uses the UK spelling of + // "colour", certain attributes are named accordingly. return ` - - ${isStage ? ` + + ${ + isStage + ? ` - ` : ` + ` + : ` @@ -135,31 +145,52 @@ const motion = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - `} + ` + } ${categorySeparator} `; }; const xmlEscape = function (unsafe) { - return unsafe.replace(/[<>&'"]/g, c => { + return unsafe.replace(/[<>&'"]/g, (c) => { switch (c) { - case '<': return '<'; - case '>': return '>'; - case '&': return '&'; - case '\'': return '''; - case '"': return '"'; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; } }); }; -const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName, colors) { - const hello = ScratchBlocks.ScratchMsgs.translate('LOOKS_HELLO', 'Hello!'); - const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...'); +const looks = function ( + isInitialSetup, + isStage, + targetId, + costumeName, + backdropName, + colors +) { + const hello = ScratchBlocks.ScratchMsgs.translate("LOOKS_HELLO", "Hello!"); + const hmm = ScratchBlocks.ScratchMsgs.translate("LOOKS_HMM", "Hmm..."); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - - ${isStage ? '' : ` + + ${ + isStage + ? "" + : ` @@ -199,8 +230,11 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop ${blockSeparator} - `} - ${isStage ? ` + ` + } + ${ + isStage + ? ` @@ -216,7 +250,8 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop - ` : ` + ` + : ` @@ -248,7 +283,8 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop - `} + ` + } ${blockSeparator} @@ -266,7 +302,10 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop ${blockSeparator} - ${isStage ? '' : ` + ${ + isStage + ? "" + : ` ${blockSeparator} @@ -278,14 +317,19 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop - `} - ${isStage ? ` + ` + } + ${ + isStage + ? ` - ` : ` + ` + : ` - `} + ` + } ${categorySeparator} `; @@ -294,7 +338,12 @@ const looks = function (isInitialSetup, isStage, targetId, costumeName, backdrop const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + @@ -350,15 +399,24 @@ const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { const events = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - + - ${isStage ? ` + ${ + isStage + ? ` - ` : ` + ` + : ` - `} + ` + } ${blockSeparator} @@ -391,10 +449,13 @@ const control = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` + colour="${colors.colourPrimary}" + secondaryColour="${colors.colourTertiary}"> @@ -419,13 +480,16 @@ const control = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} ${blockSeparator} - ${isStage ? ` + ${ + isStage + ? ` - ` : ` + ` + : ` @@ -433,22 +497,32 @@ const control = function (isInitialSetup, isStage, targetId, colors) { - `} + ` + } ${categorySeparator} `; }; const sensing = function (isInitialSetup, isStage, targetId, colors) { - const name = ScratchBlocks.ScratchMsgs.translate('SENSING_ASK_TEXT', 'What\'s your name?'); + const name = ScratchBlocks.ScratchMsgs.translate( + "SENSING_ASK_TEXT", + "What's your name?" + ); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - ${isStage ? '' : ` + colour="${colors.colourPrimary}" + secondaryColour="${colors.colourTertiary}"> + ${ + isStage + ? "" + : ` @@ -473,8 +547,12 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - `} - ${isInitialSetup ? '' : ` + ` + } + ${ + isInitialSetup + ? "" + : ` @@ -482,7 +560,8 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { - `} + ` + } ${blockSeparator} @@ -493,11 +572,15 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { - ${isStage ? '' : ` + ${ + isStage + ? "" + : ` ${blockSeparator} ''+ ${blockSeparator} - `} + ` + } ${blockSeparator} ${blockSeparator} @@ -520,16 +603,28 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { }; const operators = function (isInitialSetup, isStage, targetId, colors) { - const apple = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_APPLE', 'apple'); - const banana = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_BANANA', 'banana'); - const letter = ScratchBlocks.ScratchMsgs.translate('OPERATORS_LETTEROF_APPLE', 'a'); + const apple = ScratchBlocks.ScratchMsgs.translate( + "OPERATORS_JOIN_APPLE", + "apple" + ); + const banana = ScratchBlocks.ScratchMsgs.translate( + "OPERATORS_JOIN_BANANA", + "banana" + ); + const letter = ScratchBlocks.ScratchMsgs.translate( + "OPERATORS_LETTEROF_APPLE", + "a" + ); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` + colour="${colors.colourPrimary}" + secondaryColour="${colors.colourTertiary}"> @@ -633,7 +728,10 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - ${isInitialSetup ? '' : ` + ${ + isInitialSetup + ? "" + : ` @@ -677,7 +775,8 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { - `} + ` + } ${blockSeparator} @@ -715,10 +814,13 @@ const variables = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` `; @@ -728,10 +830,13 @@ const myBlocks = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` `; @@ -739,7 +844,7 @@ const myBlocks = function (isInitialSetup, isStage, targetId, colors) { /* eslint-enable no-unused-vars */ const xmlOpen = ''; -const xmlClose = ''; +const xmlClose = ""; /** * @param {!boolean} isInitialSetup - Whether the toolbox is for initial setup. If the mode is "initial setup", @@ -757,8 +862,16 @@ const xmlClose = ''; * @param {?object} colors - The colors for the theme. * @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox. */ -const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categoriesXML = [], - costumeName = '', backdropName = '', soundName = '', colors = defaultColors) { +const makeToolboxXML = function ( + isInitialSetup, + isStage = true, + targetId, + categoriesXML = [], + costumeName = "", + backdropName = "", + soundName = "", + colors = defaultColors +) { isStage = isInitialSetup || isStage; const gap = [categorySeparator]; @@ -767,8 +880,10 @@ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categ soundName = xmlEscape(soundName); categoriesXML = categoriesXML.slice(); - const moveCategory = categoryId => { - const index = categoriesXML.findIndex(categoryInfo => categoryInfo.id === categoryId); + const moveCategory = (categoryId) => { + const index = categoriesXML.findIndex( + (categoryInfo) => categoryInfo.id === categoryId + ); if (index >= 0) { // remove the category from categoriesXML and return its XML const [categoryInfo] = categoriesXML.splice(index, 1); @@ -776,28 +891,60 @@ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categ } // return `undefined` }; - const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId, colors.motion); - const looksXML = moveCategory('looks') || - looks(isInitialSetup, isStage, targetId, costumeName, backdropName, colors.looks); - const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName, colors.sounds); - const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId, colors.event); - const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId, colors.control); - const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId, colors.sensing); - const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId, colors.operators); - const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId, colors.data); - const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId, colors.more); + const motionXML = + moveCategory("motion") || + motion(isInitialSetup, isStage, targetId, colors.motion); + const looksXML = + moveCategory("looks") || + looks( + isInitialSetup, + isStage, + targetId, + costumeName, + backdropName, + colors.looks + ); + const soundXML = + moveCategory("sound") || + sound(isInitialSetup, isStage, targetId, soundName, colors.sounds); + const eventsXML = + moveCategory("event") || + events(isInitialSetup, isStage, targetId, colors.event); + const controlXML = + moveCategory("control") || + control(isInitialSetup, isStage, targetId, colors.control); + const sensingXML = + moveCategory("sensing") || + sensing(isInitialSetup, isStage, targetId, colors.sensing); + const operatorsXML = + moveCategory("operators") || + operators(isInitialSetup, isStage, targetId, colors.operators); + const variablesXML = + moveCategory("data") || + variables(isInitialSetup, isStage, targetId, colors.data); + const myBlocksXML = + moveCategory("procedures") || + myBlocks(isInitialSetup, isStage, targetId, colors.more); const everything = [ xmlOpen, - motionXML, gap, - looksXML, gap, - soundXML, gap, - eventsXML, gap, - controlXML, gap, - sensingXML, gap, - operatorsXML, gap, - variablesXML, gap, - myBlocksXML + motionXML, + gap, + looksXML, + gap, + soundXML, + gap, + eventsXML, + gap, + controlXML, + gap, + sensingXML, + gap, + operatorsXML, + gap, + variablesXML, + gap, + myBlocksXML, ]; for (const extensionCategory of categoriesXML) { @@ -805,7 +952,7 @@ const makeToolboxXML = function (isInitialSetup, isStage = true, targetId, categ } everything.push(xmlClose); - return everything.join('\n'); + return everything.join("\n"); }; export default makeToolboxXML; diff --git a/packages/scratch-gui/src/lib/themes/blockHelpers.js b/packages/scratch-gui/src/lib/themes/blockHelpers.js index b201241eeb..696c0b5785 100644 --- a/packages/scratch-gui/src/lib/themes/blockHelpers.js +++ b/packages/scratch-gui/src/lib/themes/blockHelpers.js @@ -1,19 +1,19 @@ -import {DEFAULT_THEME, getColorsForTheme, themeMap} from '.'; +import { DEFAULT_THEME, getColorsForTheme, themeMap } from "."; -const getBlockIconURI = extensionIcons => { +const getBlockIconURI = (extensionIcons) => { if (!extensionIcons) return null; return extensionIcons.blockIconURI || extensionIcons.menuIconURI; }; -const getCategoryIconURI = extensionIcons => { +const getCategoryIconURI = (extensionIcons) => { if (!extensionIcons) return null; return extensionIcons.menuIconURI || extensionIcons.blockIconURI; }; // scratch-blocks colours has a pen property that scratch-gui uses for all extensions -const getExtensionColors = theme => getColorsForTheme(theme).pen; +const getExtensionColors = (theme) => getColorsForTheme(theme).pen; /** * Applies extension color theme to categories. @@ -33,32 +33,52 @@ const injectExtensionCategoryTheme = (dynamicBlockXML, theme) => { const parser = new DOMParser(); const serializer = new XMLSerializer(); - return dynamicBlockXML.map(extension => { - const dom = parser.parseFromString(extension.xml, 'text/xml'); + return dynamicBlockXML.map((extension) => { + const dom = parser.parseFromString(extension.xml, "text/xml"); - dom.documentElement.setAttribute('colour', extensionColors.primary); + // This element is deserialized by Blockly, which uses the UK spelling + // of "colour". + dom.documentElement.setAttribute( + "colour", + extensionColors.colourPrimary + ); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. - dom.documentElement.setAttribute('secondaryColour', extensionColors.tertiary); - - const categoryIconURI = getCategoryIconURI(extensionIcons[extension.id]); + dom.documentElement.setAttribute( + "secondaryColour", + extensionColors.colourTertiary + ); + + const categoryIconURI = getCategoryIconURI( + extensionIcons[extension.id] + ); if (categoryIconURI) { - dom.documentElement.setAttribute('iconURI', categoryIconURI); + dom.documentElement.setAttribute("iconURI", categoryIconURI); } return { ...extension, - xml: serializer.serializeToString(dom) + xml: serializer.serializeToString(dom), }; }); }; -const injectBlockIcons = (blockInfoJson, theme) => { +const injectExtensionBlockIcons = (blockInfoJson, theme) => { + // Don't do any manipulation for the default theme + if (theme === DEFAULT_THEME) return blockInfoJson; + // Block icons are the first element of `args0` - if (!blockInfoJson.args0 || blockInfoJson.args0.length < 1 || - blockInfoJson.args0[0].type !== 'field_image') return blockInfoJson; + if ( + !blockInfoJson.args0 || + blockInfoJson.args0.length < 1 || + blockInfoJson.args0[0].type !== "field_image" + ) + return blockInfoJson; const extensionIcons = themeMap[theme].extensions; - const extensionId = blockInfoJson.type.substring(0, blockInfoJson.type.indexOf('_')); + const extensionId = blockInfoJson.type.substring( + 0, + blockInfoJson.type.indexOf("_") + ); const blockIconURI = getBlockIconURI(extensionIcons[extensionId]); if (!blockIconURI) return blockInfoJson; @@ -70,35 +90,14 @@ const injectBlockIcons = (blockInfoJson, theme) => { return { ...value, - src: blockIconURI + src: blockIconURI, }; - }) - }; -}; - -/** - * Applies extension color theme to static block json. - * No changes are applied if called with the default theme, allowing extensions to provide their own colors. - * @param {object} blockInfoJson - Static block json - * @param {string} theme - Theme name - * @returns {object} Block info json with updated colors. The original blockInfoJson is not modified. - */ -const injectExtensionBlockTheme = (blockInfoJson, theme) => { - // Don't do any manipulation for the default theme - if (theme === DEFAULT_THEME) return blockInfoJson; - - const extensionColors = getExtensionColors(theme); - - return { - ...injectBlockIcons(blockInfoJson, theme), - colour: extensionColors.primary, - colourSecondary: extensionColors.secondary, - colourTertiary: extensionColors.tertiary, - colourQuaternary: extensionColors.quaternary + }), }; }; export { - injectExtensionBlockTheme, - injectExtensionCategoryTheme + injectExtensionBlockIcons, + injectExtensionCategoryTheme, + getExtensionColors, }; diff --git a/packages/scratch-gui/src/lib/themes/default/index.js b/packages/scratch-gui/src/lib/themes/default/index.js index 3a754dca37..7047a1c7fd 100644 --- a/packages/scratch-gui/src/lib/themes/default/index.js +++ b/packages/scratch-gui/src/lib/themes/default/index.js @@ -1,105 +1,105 @@ +// This object is passed directly to Blockly, hence the colour* fields need to +// be named exactly as they are, including the UK spelling of "colour". const blockColors = { motion: { - primary: '#4C97FF', - secondary: '#4280D7', - tertiary: '#3373CC', - quaternary: '#3373CC' + colourPrimary: "#4C97FF", + colourSecondary: "#4280D7", + colourTertiary: "#3373CC", + colourQuaternary: "#3373CC", }, looks: { - primary: '#9966FF', - secondary: '#855CD6', - tertiary: '#774DCB', - quaternary: '#774DCB' + colourPrimary: "#9966FF", + colourSecondary: "#855CD6", + colourTertiary: "#774DCB", + colourQuaternary: "#774DCB", }, sounds: { - primary: '#CF63CF', - secondary: '#C94FC9', - tertiary: '#BD42BD', - quaternary: '#BD42BD' + colourPrimary: "#CF63CF", + colourSecondary: "#C94FC9", + colourTertiary: "#BD42BD", + colourQuaternary: "#BD42BD", }, control: { - primary: '#FFAB19', - secondary: '#EC9C13', - tertiary: '#CF8B17', - quaternary: '#CF8B17' + colourPrimary: "#FFAB19", + colourSecondary: "#EC9C13", + colourTertiary: "#CF8B17", + colourQuaternary: "#CF8B17", }, event: { - primary: '#FFBF00', - secondary: '#E6AC00', - tertiary: '#CC9900', - quaternary: '#CC9900' + colourPrimary: "#FFBF00", + colourSecondary: "#E6AC00", + colourTertiary: "#CC9900", + colourQuaternary: "#CC9900", }, sensing: { - primary: '#5CB1D6', - secondary: '#47A8D1', - tertiary: '#2E8EB8', - quaternary: '#2E8EB8' + colourPrimary: "#5CB1D6", + colourSecondary: "#47A8D1", + colourTertiary: "#2E8EB8", + colourQuaternary: "#2E8EB8", }, pen: { - primary: '#0fBD8C', - secondary: '#0DA57A', - tertiary: '#0B8E69', - quaternary: '#0B8E69' + colourPrimary: "#0fBD8C", + colourSecondary: "#0DA57A", + colourTertiary: "#0B8E69", + colourQuaternary: "#0B8E69", }, operators: { - primary: '#59C059', - secondary: '#46B946', - tertiary: '#389438', - quaternary: '#389438' + colourPrimary: "#59C059", + colourSecondary: "#46B946", + colourTertiary: "#389438", + colourQuaternary: "#389438", }, data: { - primary: '#FF8C1A', - secondary: '#FF8000', - tertiary: '#DB6E00', - quaternary: '#DB6E00' + colourPrimary: "#FF8C1A", + colourSecondary: "#FF8000", + colourTertiary: "#DB6E00", + colourQuaternary: "#DB6E00", }, // This is not a new category, but rather for differentiation // between lists and scalar variables. data_lists: { - primary: '#FF661A', - secondary: '#FF5500', - tertiary: '#E64D00', - quaternary: '#E64D00' + colourPrimary: "#FF661A", + colourSecondary: "#FF5500", + colourTertiary: "#E64D00", + colourQuaternary: "#E64D00", }, more: { - primary: '#FF6680', - secondary: '#FF4D6A', - tertiary: '#FF3355', - quaternary: '#FF3355' + colourPrimary: "#FF6680", + colourSecondary: "#FF4D6A", + colourTertiary: "#FF3355", + colourQuaternary: "#FF3355", }, - text: '#FFFFFF', - workspace: '#F9F9F9', - toolboxHover: '#4C97FF', - toolboxSelected: '#E9EEF2', - toolboxText: '#575E75', - toolbox: '#FFFFFF', - flyout: '#F9F9F9', - scrollbar: '#CECDCE', - scrollbarHover: '#CECDCE', - textField: '#FFFFFF', - textFieldText: '#575E75', - insertionMarker: '#000000', + text: "#FFFFFF", + workspace: "#F9F9F9", + toolboxHover: "#4C97FF", + toolboxSelected: "#E9EEF2", + toolboxText: "#575E75", + toolbox: "#FFFFFF", + flyout: "#F9F9F9", + scrollbar: "#CECDCE", + scrollbarHover: "#CECDCE", + textField: "#FFFFFF", + textFieldText: "#575E75", + insertionMarker: "#000000", insertionMarkerOpacity: 0.2, dragShadowOpacity: 0.6, - stackGlow: '#FFF200', + stackGlow: "#FFF200", stackGlowSize: 4, stackGlowOpacity: 1, - replacementGlow: '#FFFFFF', + replacementGlow: "#FFFFFF", replacementGlowSize: 2, replacementGlowOpacity: 1, - colourPickerStroke: '#FFFFFF', + colourPickerStroke: "#FFFFFF", // CSS colours: support RGBA - fieldShadow: 'rgba(255, 255, 255, 0.3)', - dropDownShadow: 'rgba(0, 0, 0, .3)', - numPadBackground: '#547AB2', - numPadBorder: '#435F91', - numPadActiveBackground: '#435F91', - numPadText: 'white', // Do not use hex here, it cannot be inlined with data-uri SVG - valueReportBackground: '#FFFFFF', - valueReportBorder: '#AAAAAA', - menuHover: 'rgba(0, 0, 0, 0.2)' + fieldShadow: "rgba(255, 255, 255, 0.3)", + dropDownShadow: "rgba(0, 0, 0, .3)", + numPadBackground: "#547AB2", + numPadBorder: "#435F91", + numPadActiveBackground: "#435F91", + numPadText: "white", // Do not use hex here, it cannot be inlined with data-uri SVG + valueReportBackground: "#FFFFFF", + valueReportBorder: "#AAAAAA", + menuHover: "rgba(0, 0, 0, 0.2)", }; -export { - blockColors -}; +export { blockColors }; diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/index.js b/packages/scratch-gui/src/lib/themes/high-contrast/index.js index 8a5dd94155..853ff7947c 100644 --- a/packages/scratch-gui/src/lib/themes/high-contrast/index.js +++ b/packages/scratch-gui/src/lib/themes/high-contrast/index.js @@ -1,110 +1,108 @@ -import musicIcon from './extensions/musicIcon.svg'; -import penIcon from './extensions/penIcon.svg'; -import text2speechIcon from './extensions/text2speechIcon.svg'; -import translateIcon from './extensions/translateIcon.svg'; -import videoSensingIcon from './extensions/videoSensingIcon.svg'; +import musicIcon from "./extensions/musicIcon.svg"; +import penIcon from "./extensions/penIcon.svg"; +import text2speechIcon from "./extensions/text2speechIcon.svg"; +import translateIcon from "./extensions/translateIcon.svg"; +import videoSensingIcon from "./extensions/videoSensingIcon.svg"; +// This object is passed directly to Blockly, hence the colour* fields need to +// be named exactly as they are, including the UK spelling of "colour". const blockColors = { motion: { - primary: '#80B5FF', - secondary: '#B3D2FF', - tertiary: '#3373CC', - quaternary: '#CCE1FF' + colourPrimary: "#80B5FF", + colourSecondary: "#B3D2FF", + colourTertiary: "#3373CC", + colourQuaternary: "#CCE1FF", }, looks: { - primary: '#CCB3FF', - secondary: '#DDCCFF', - tertiary: '#774DCB', - quaternary: '#EEE5FF' + colourPrimary: "#CCB3FF", + colourSecondary: "#DDCCFF", + colourTertiary: "#774DCB", + colourQuaternary: "#EEE5FF", }, sounds: { - primary: '#E19DE1', - secondary: '#FFB3FF', - tertiary: '#BD42BD', - quaternary: '#FFCCFF' - + colourPrimary: "#E19DE1", + colourSecondary: "#FFB3FF", + colourTertiary: "#BD42BD", + colourQuaternary: "#FFCCFF", }, control: { - primary: '#FFBE4C', - secondary: '#FFDA99', - tertiary: '#CF8B17', - quaternary: '#FFE3B3' + colourPrimary: "#FFBE4C", + colourSecondary: "#FFDA99", + colourTertiary: "#CF8B17", + colourQuaternary: "#FFE3B3", }, event: { - primary: '#FFD966', - secondary: '#FFECB3', - tertiary: '#CC9900', - quaternary: '#FFF2CC' + colourPrimary: "#FFD966", + colourSecondary: "#FFECB3", + colourTertiary: "#CC9900", + colourQuaternary: "#FFF2CC", }, sensing: { - primary: '#85C4E0', - secondary: '#AED8EA', - tertiary: '#2E8EB8', - quaternary: '#C2E2F0' + colourPrimary: "#85C4E0", + colourSecondary: "#AED8EA", + colourTertiary: "#2E8EB8", + colourQuaternary: "#C2E2F0", }, pen: { - primary: '#13ECAF', - secondary: '#75F0CD', - tertiary: '#0B8E69', - quaternary: '#A3F5DE' + colourPrimary: "#13ECAF", + colourSecondary: "#75F0CD", + colourTertiary: "#0B8E69", + colourQuaternary: "#A3F5DE", }, operators: { - primary: '#7ECE7E', - secondary: '#B5E3B5', - tertiary: '#389438', - quaternary: '#DAF1DA' + colourPrimary: "#7ECE7E", + colourSecondary: "#B5E3B5", + colourTertiary: "#389438", + colourQuaternary: "#DAF1DA", }, data: { - primary: '#FFA54C', - secondary: '#FFCC99', - tertiary: '#DB6E00', - quaternary: '#FFE5CC' + colourPrimary: "#FFA54C", + colourSecondary: "#FFCC99", + colourTertiary: "#DB6E00", + colourQuaternary: "#FFE5CC", }, // This is not a new category, but rather for differentiation // between lists and scalar variables. data_lists: { - primary: '#FF9966', - secondary: '#FFCAB0', // I don't think this is used, b/c we don't have any droppable fields in list blocks - tertiary: '#E64D00', - quaternary: '#FFDDCC' + colourPrimary: "#FF9966", + colourSecondary: "#FFCAB0", // I don't think this is used, b/c we don't have any droppable fields in list blocks + colourTertiary: "#E64D00", + colourQuaternary: "#FFDDCC", }, more: { - primary: '#FF99AA', - secondary: '#FFCCD5', - tertiary: '#FF3355', - quaternary: '#FFE5EA' - }, - text: '#000000', - textFieldText: '#000000', // Text inside of inputs e.g. 90 in [point in direction (90)] - toolboxText: '#000000', // Toolbox text, color picker text (used to be #575E75) + colourPrimary: "#FF99AA", + colourSecondary: "#FFCCD5", + colourTertiary: "#FF3355", + colourQuaternary: "#FFE5EA", + }, + text: "#000000", + textFieldText: "#000000", // Text inside of inputs e.g. 90 in [point in direction (90)] + toolboxText: "#000000", // Toolbox text, color picker text (used to be #575E75) // The color that the category menu label (e.g. 'motion', 'looks', etc.) changes to on hover - toolboxHover: '#3373CC', - insertionMarker: '#000000', + toolboxHover: "#3373CC", + insertionMarker: "#000000", insertionMarkerOpacity: 0.2, - fieldShadow: 'rgba(255, 255, 255, 0.3)', + fieldShadow: "rgba(255, 255, 255, 0.3)", dragShadowOpacity: 0.6, - menuHover: 'rgba(255, 255, 255, 0.3)' + menuHover: "rgba(255, 255, 255, 0.3)", }; const extensions = { music: { - blockIconURI: musicIcon + blockIconURI: musicIcon, }, pen: { - blockIconURI: penIcon + blockIconURI: penIcon, }, text2speech: { - blockIconURI: text2speechIcon + blockIconURI: text2speechIcon, }, translate: { - blockIconURI: translateIcon + blockIconURI: translateIcon, }, videoSensing: { - blockIconURI: videoSensingIcon - } + blockIconURI: videoSensingIcon, + }, }; -export { - blockColors, - extensions -}; +export { blockColors, extensions }; From 9703bc6531e101d7684ebb394f5afacb287f36fd Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 12 Sep 2024 09:42:07 -0700 Subject: [PATCH 43/76] refactor: improve efficiency of toolbox updates (#24) --- packages/scratch-gui/src/containers/blocks.jsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index ecdc577443..6795150e78 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -238,16 +238,6 @@ class Blocks extends React.Component { this.ScratchBlocks.hideChaff(); } - // Only rerender the toolbox when the blocks are visible and the xml is - // different from the previously rendered toolbox xml. - // Do not check against prevProps.toolboxXML because that may not have been rendered. - if ( - this.props.isVisible && - this.props.toolboxXML !== this._renderedToolboxXML - ) { - this.requestToolboxUpdate(); - } - if (this.props.isVisible === prevProps.isVisible) { if (this.props.stageSize !== prevProps.stageSize) { // force workspace to redraw for the new stage size @@ -269,7 +259,6 @@ class Blocks extends React.Component { this.setLocale(); } else { this.props.vm.refreshWorkspace(); - this.requestToolboxUpdate(); } window.dispatchEvent(new Event("resize")); @@ -298,7 +287,6 @@ class Blocks extends React.Component { .then(() => { this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); - this.requestToolboxUpdate(); this.withToolboxUpdates(() => { this.workspace.getFlyout().setRecyclingEnabled(true); }); @@ -744,7 +732,6 @@ class Blocks extends React.Component { ) .then(() => { this.props.vm.refreshWorkspace(); - this.updateToolbox(); // To show new variables/custom blocks }); } render() { From 52ac75295c487d703baa26fdc05af28bf90837d3 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 16 Sep 2024 09:48:17 -0700 Subject: [PATCH 44/76] fix: partially roll back flyout optimization (#25) --- packages/scratch-gui/src/containers/blocks.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 6795150e78..328733db01 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -238,6 +238,16 @@ class Blocks extends React.Component { this.ScratchBlocks.hideChaff(); } + // Only rerender the toolbox when the blocks are visible and the xml is + // different from the previously rendered toolbox xml. + // Do not check against prevProps.toolboxXML because that may not have been rendered. + if ( + this.props.isVisible && + this.props.toolboxXML !== this._renderedToolboxXML + ) { + this.requestToolboxUpdate(); + } + if (this.props.isVisible === prevProps.isVisible) { if (this.props.stageSize !== prevProps.stageSize) { // force workspace to redraw for the new stage size From 15904a23e1b00ff6d858b2d7f4ddad3eb84411f7 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 18 Sep 2024 12:04:08 -0700 Subject: [PATCH 45/76] fix: prevent exception when switching languages (#27) --- packages/scratch-gui/src/containers/blocks.jsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 328733db01..ca6018fb59 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -323,14 +323,17 @@ class Blocks extends React.Component { this.workspace.getToolbox().forceRerender(); this._renderedToolboxXML = this.props.toolboxXML; - const newCategoryScrollPosition = + const newCategoryScrollPosition = this.workspace + .getFlyout() + .getCategoryScrollPosition(selectedCategoryName); + if (newCategoryScrollPosition) { this.workspace .getFlyout() - .getCategoryScrollPosition(selectedCategoryName).y * scale; - this.workspace - .getFlyout() - .getWorkspace() - .scrollbar.setY(newCategoryScrollPosition + offsetWithinCategory); + .getWorkspace() + .scrollbar.setY( + newCategoryScrollPosition.y * scale + offsetWithinCategory + ); + } const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; From 1955a6fecdb266ef94e514a71651a0fd40eb6092 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 18 Sep 2024 12:12:28 -0700 Subject: [PATCH 46/76] fix: fix bug that prevented displaying the procedure editor modal on mobile (#26) --- packages/scratch-gui/src/containers/blocks.jsx | 1 + packages/scratch-gui/src/containers/custom-procedures.jsx | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index ca6018fb59..f534400db6 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -877,6 +877,7 @@ Blocks.defaultOptions = { collapse: false, sounds: false, trashcan: false, + modalInputs: false, }; Blocks.defaultProps = { diff --git a/packages/scratch-gui/src/containers/custom-procedures.jsx b/packages/scratch-gui/src/containers/custom-procedures.jsx index 20dd9146e3..068b9097b1 100644 --- a/packages/scratch-gui/src/containers/custom-procedures.jsx +++ b/packages/scratch-gui/src/containers/custom-procedures.jsx @@ -193,6 +193,7 @@ CustomProcedures.defaultOptions = { comments: false, collapse: false, scrollbars: true, + modalInputs: false, }; CustomProcedures.defaultProps = { From 565acd676a25d0f17bf622a361feb891277513b7 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 2 Oct 2024 09:21:22 -0700 Subject: [PATCH 47/76] fix: avoid clearing the state of the sensing_of block by refreshing the toolbox (#28) --- packages/scratch-gui/src/containers/blocks.jsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index f534400db6..ea57224495 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -145,7 +145,8 @@ class Blocks extends React.Component { "PROCEDURE", this.ScratchBlocks.ScratchProcedures.getProceduresCategory ); - this.workspace.addChangeListener((event) => { + + this.toolboxUpdateChangeListener = (event) => { if ( event.type === this.ScratchBlocks.Events.VAR_CREATE || event.type === this.ScratchBlocks.Events.VAR_RENAME || @@ -161,7 +162,8 @@ class Blocks extends React.Component { ) { this.requestToolboxUpdate(); } - }); + }; + this.workspace.addChangeListener(this.toolboxUpdateChangeListener); // Register buttons under new callback keys for creating variables, // lists, and procedures from extensions. @@ -515,6 +517,7 @@ class Blocks extends React.Component { // Remove and reattach the workspace listener (but allow flyout events) this.workspace.removeChangeListener(this.props.vm.blockListener); + this.workspace.removeChangeListener(this.toolboxUpdateChangeListener); const dom = this.ScratchBlocks.utils.xml.textToDom(data.xml); try { this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml( @@ -556,6 +559,15 @@ class Blocks extends React.Component { // fresh workspace and we don't want any changes made to another sprites // workspace to be 'undone' here. this.workspace.clearUndo(); + // Let events get flushed before readding the toolbox-updater listener + // to avoid unneeded refreshes. + requestAnimationFrame(() => { + setTimeout(() => { + this.workspace.addChangeListener( + this.toolboxUpdateChangeListener + ); + }); + }); } handleMonitorsUpdate(monitors) { // Update the checkboxes of the relevant monitors. From 3fbb1325a39e463334e406292e6b9f3c1a4d3e97 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 14 Oct 2024 08:23:44 -0700 Subject: [PATCH 48/76] refactor: fix compatibility with checkboxes and category status indicators (#29) * refactor: fix compatibility with checkboxes and category status indicators * chore: remove errant logging --- .../scratch-gui/src/containers/blocks.jsx | 6 ++--- packages/scratch-gui/src/lib/blocks.js | 25 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index ea57224495..1c759423c8 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -94,7 +94,7 @@ class Blocks extends React.Component { this.ScratchBlocks.ScratchVariables.setPromptHandler( this.handlePromptStart ); - this.ScratchBlocks.statusButtonCallback = + this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; @@ -110,7 +110,7 @@ class Blocks extends React.Component { this.props.useCatBlocks ); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); - this.ScratchBlocks.statusButtonCallback = + this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; @@ -716,7 +716,7 @@ class Blocks extends React.Component { this.props.onOpenConnectionModal(extensionId); } handleStatusButtonUpdate() { - this.ScratchBlocks.refreshStatusButtons(this.workspace); + this.workspace.getFlyout().refreshStatusButtons(); } handleOpenSoundRecorder() { this.props.onOpenSoundRecorder(); diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 149d67897f..298307cab3 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -396,19 +396,20 @@ export default function (vm, useCatBlocks) { this.jsonInit(json); }; - ScratchBlocks.CheckableContinuousFlyout.prototype.getCheckboxState = - function (blockId) { - const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId]; - return monitoredBlock ? monitoredBlock.isMonitored : false; - }; + ScratchBlocks.CheckboxBubble.prototype.isChecked = function (blockId) { + const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId]; + return monitoredBlock ? monitoredBlock.isMonitored : false; + }; + + ScratchBlocks.StatusIndicatorLabel.prototype.getExtensionState = function ( + extensionId + ) { + if (vm.getPeripheralIsConnected(extensionId)) { + return ScratchBlocks.StatusButtonState.READY; + } + return ScratchBlocks.StatusButtonState.NOT_READY; + }; - // ScratchBlocks.FlyoutExtensionCategoryHeader.getExtensionState = function (extensionId) { - // if (vm.getPeripheralIsConnected(extensionId)) { - // return ScratchBlocks.StatusButtonState.READY; - // } - // return ScratchBlocks.StatusButtonState.NOT_READY; - // }; - // // ScratchBlocks.FieldNote.playNote_ = function (noteNum, extensionId) { // vm.runtime.emit('PLAY_NOTE', noteNum, extensionId); // }; From 800b185225dcef879eec23041a9a82706bbb85e2 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:35:21 -0700 Subject: [PATCH 49/76] chore(deps): update scratch-blocks for unforking test --- package-lock.json | 313 +++++++++++++++++++++++++----- packages/scratch-gui/package.json | 2 +- 2 files changed, 270 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e083672a8..2fe1ac2e56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31018,65 +31018,292 @@ } }, "node_modules/scratch-blocks": { - "version": "1.1.206", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-1.1.206.tgz", - "integrity": "sha512-pjry8XGFlP2Gm3VJEuz7983oJE3fp2Gd1AzLDGaQSV8hDcAISzEd5GF0PoTZ2qQStp5G4b3eK/Xs9M+LEKmuJw==", - "license": "Apache-2.0", + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-beta.2.tgz", + "integrity": "sha512-j/2F1/tIGoPMeQHVIDXzEh21fdQTwhLDSCv9Qo8J0wIq+v2EYipHMbV3QvQloheBaXdTpLsn9SJnUjOmiICFUA==", + "deprecated": "Moved Blockly unforking test to 'spork' channel", "dependencies": { - "exports-loader": "^0.7.0", - "google-closure-library": "^20190301.0.0", - "imports-loader": "^0.8.0", - "scratch-l10n": "^3.18.3" + "@blockly/continuous-toolbox": "^6.0.9", + "@blockly/field-colour": "^5.0.9", + "blockly": "^11.0.0" } }, - "node_modules/scratch-blocks/node_modules/exports-loader": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.7.0.tgz", - "integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==", - "license": "MIT", + "node_modules/scratch-blocks/node_modules/@blockly/continuous-toolbox": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-6.0.12.tgz", + "integrity": "sha512-I2jsxN/f+wytrzUQkSrxOKLWHPgsjiIz/w0Tpdo9PcDB5mwoBnG8l0ldh5Yj55CRMZbY/q9tANPWR8V8acgMIw==", + "engines": { + "node": ">=8.17.0" + }, + "peerDependencies": { + "blockly": "^11.0.0" + } + }, + "node_modules/scratch-blocks/node_modules/@blockly/field-colour": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-5.0.12.tgz", + "integrity": "sha512-vNw6L/B0cpf+j0S6pShX31bOI16KJu+eACpsfHGOBZbb7+LT3bYKcGHe6+VRe+KtIE3jGlY7vYfnaJdOCrYlfQ==", + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "blockly": "^11.0.0" + } + }, + "node_modules/scratch-blocks/node_modules/blockly": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-11.2.1.tgz", + "integrity": "sha512-20sCwSwX2Z6UxR/er0B5y6wRFukuIdvOjc7jMuIwyCO/yT35+UbAqYueMga3JFA9NoWPwQc+3s6/XnLkyceAww==", "dependencies": { - "loader-utils": "^1.1.0", - "source-map": "0.5.0" + "jsdom": "25.0.1" }, "engines": { - "node": ">= 4" + "node": ">=18" } }, - "node_modules/scratch-blocks/node_modules/exports-loader/node_modules/source-map": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", - "integrity": "sha512-gjGnxNN0K+/Pr4Mi4fs/pOtda10dKB6Wn9QvjOrH6v5TWsI7ghHuJUHoIgyM6DkUL5kr2GtPFGererzKpMBWfA==", - "license": "BSD-3-Clause", + "node_modules/scratch-blocks/node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=18" } }, - "node_modules/scratch-blocks/node_modules/imports-loader": { + "node_modules/scratch-blocks/node_modules/cssstyle/node_modules/rrweb-cssom": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.8.0.tgz", - "integrity": "sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ==", - "license": "MIT", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==" + }, + "node_modules/scratch-blocks/node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dependencies": { - "loader-utils": "^1.0.2", - "source-map": "^0.6.1" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">= 4" + "node": ">=18" } }, - "node_modules/scratch-blocks/node_modules/scratch-l10n": { - "version": "3.18.357", - "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.18.357.tgz", - "integrity": "sha512-Rs3YmUa2dzpYqT1O/YT15g99sIwnC7j9TOOmOhUphVKLeiYUvJWiRPKZCugA7/hbIMYZV5VLkmuDgGXhgfSOBw==", - "license": "BSD-3-Clause", + "node_modules/scratch-blocks/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/scratch-blocks/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dependencies": { - "@transifex/api": "4.3.0", - "download": "8.0.0", - "transifex": "1.6.6" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" }, - "bin": { - "build-i18n-src": "scripts/build-i18n-src.js", - "tx-push-src": "scripts/tx-push-src.js" + "engines": { + "node": ">= 6" + } + }, + "node_modules/scratch-blocks/node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/scratch-blocks/node_modules/jsdom": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", + "dependencies": { + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^2.11.2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/scratch-blocks/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/scratch-blocks/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/scratch-blocks/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scratch-blocks/node_modules/tough-cookie": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", + "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/scratch-blocks/node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/scratch-blocks/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/scratch-blocks/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/scratch-blocks/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" } }, "node_modules/scratch-l10n": { @@ -35981,7 +36208,6 @@ "version": "6.1.77", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.77.tgz", "integrity": "sha512-lBpoWgy+kYmuXWQ83+R7LlJCnsd9YW8DGpZSHhrMl4b8Ly/1vzOie3OdtmUJDkKxcgRGOehDu5btKkty+JEe+g==", - "dev": true, "dependencies": { "tldts-core": "^6.1.77" }, @@ -35992,8 +36218,7 @@ "node_modules/tldts-core": { "version": "6.1.77", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.77.tgz", - "integrity": "sha512-bCaqm24FPk8OgBkM0u/SrEWJgHnhBWYqeBo6yUmcZJDCHt/IfyWBb+14CXdGi4RInMv4v7eUAin15W0DoA+Ytg==", - "dev": true + "integrity": "sha512-bCaqm24FPk8OgBkM0u/SrEWJgHnhBWYqeBo6yUmcZJDCHt/IfyWBb+14CXdGi4RInMv4v7eUAin15W0DoA+Ytg==" }, "node_modules/tmp": { "version": "0.0.30", @@ -38258,7 +38483,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "1.1.206", + "scratch-blocks": "^2.0.0-beta", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 1fb967bab4..15f216db68 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -94,7 +94,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "1.1.206", + "scratch-blocks": "^2.0.0-beta", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", From 50f031c0866ecfbca6e6695c358a782959e4052a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:48:45 -0700 Subject: [PATCH 50/76] fix(release): release beta branch under beta label(?) --- packages/scratch-gui/release.config.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/scratch-gui/release.config.js b/packages/scratch-gui/release.config.js index 4827501092..c09249000b 100644 --- a/packages/scratch-gui/release.config.js +++ b/packages/scratch-gui/release.config.js @@ -6,14 +6,19 @@ module.exports = { // default channel }, { - name: 'hotfix/REPLACE', // replace with actual hotfix branch name - channel: 'hotfix', - prerelease: 'hotfix' + name: 'alpha', + channel: 'alpha', + prerelease: true }, { name: 'beta', channel: 'beta', prerelease: true + }, + { + name: 'hotfix/REPLACE', // replace with actual hotfix branch name + channel: 'hotfix', + prerelease: 'hotfix' } ] }; From 6d07604b3bf445a5a6131270192ca81f57d206a9 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Sun, 20 Oct 2024 19:06:26 -0700 Subject: [PATCH 51/76] chore(deps): update deps for spork test --- package-lock.json | 9 ++++----- packages/scratch-gui/package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2fe1ac2e56..9261f315d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31018,10 +31018,9 @@ } }, "node_modules/scratch-blocks": { - "version": "2.0.0-beta.2", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-beta.2.tgz", - "integrity": "sha512-j/2F1/tIGoPMeQHVIDXzEh21fdQTwhLDSCv9Qo8J0wIq+v2EYipHMbV3QvQloheBaXdTpLsn9SJnUjOmiICFUA==", - "deprecated": "Moved Blockly unforking test to 'spork' channel", + "version": "2.0.0-spork.1", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.1.tgz", + "integrity": "sha512-kA9T2rg1UZk6JVKwxZoPCz8uOZBhZ0eLi7nnFnZ4VZzzfItjCQQpdgPzbeBycEuEzlp3TIE2zP1G3jpthpkW0g==", "dependencies": { "@blockly/continuous-toolbox": "^6.0.9", "@blockly/field-colour": "^5.0.9", @@ -38483,7 +38482,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "^2.0.0-beta", + "scratch-blocks": "2.0.0-spork.1", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 15f216db68..f5d73b6c77 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -94,7 +94,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "^2.0.0-beta", + "scratch-blocks": "2.0.0-spork.1", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", From f28a71de37b3881474ff3446e58df2c7f6bece1c Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:36:58 -0800 Subject: [PATCH 52/76] chore(deps): update deps for spork test --- package-lock.json | 16 ++++++++-------- packages/scratch-gui/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9261f315d0..1d8030deae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31018,13 +31018,13 @@ } }, "node_modules/scratch-blocks": { - "version": "2.0.0-spork.1", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.1.tgz", - "integrity": "sha512-kA9T2rg1UZk6JVKwxZoPCz8uOZBhZ0eLi7nnFnZ4VZzzfItjCQQpdgPzbeBycEuEzlp3TIE2zP1G3jpthpkW0g==", + "version": "2.0.0-spork.3", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.3.tgz", + "integrity": "sha512-luy2QtACBjhHT2rH2Zcvwevjb9zBnMWGwLnp9ydEXzbcFTptmYxFTmC8iFRPm6szxGiCw1pH48Qr3BwTGRKp8Q==", "dependencies": { "@blockly/continuous-toolbox": "^6.0.9", "@blockly/field-colour": "^5.0.9", - "blockly": "^11.0.0" + "blockly": "^12.0.0-beta.0" } }, "node_modules/scratch-blocks/node_modules/@blockly/continuous-toolbox": { @@ -31050,9 +31050,9 @@ } }, "node_modules/scratch-blocks/node_modules/blockly": { - "version": "11.2.1", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-11.2.1.tgz", - "integrity": "sha512-20sCwSwX2Z6UxR/er0B5y6wRFukuIdvOjc7jMuIwyCO/yT35+UbAqYueMga3JFA9NoWPwQc+3s6/XnLkyceAww==", + "version": "12.0.0-beta.1", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-12.0.0-beta.1.tgz", + "integrity": "sha512-lECwZ4K+YuLXMM0yxWTz1lwkmDl424sst7h/dhtSefuCki8afjI/F87byYK/ZIZsMKBEz2+8wEJ1Wlx5cYWIAg==", "dependencies": { "jsdom": "25.0.1" }, @@ -38482,7 +38482,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "2.0.0-spork.1", + "scratch-blocks": "2.0.0-spork.3", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index f5d73b6c77..3c55c42a51 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -94,7 +94,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "2.0.0-spork.1", + "scratch-blocks": "2.0.0-spork.3", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", From c2a5fe2cfae801ae8e5eca43ba2f852ce9f497df Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:50:25 -0800 Subject: [PATCH 53/76] style: re-apply existing lint rules --- .../src/components/monitor/monitor.jsx | 121 ++-- .../scratch-gui/src/containers/blocks.jsx | 597 +++++++----------- .../src/containers/custom-procedures.jsx | 111 ++-- packages/scratch-gui/src/lib/blocks.js | 293 +++------ .../src/lib/define-dynamic-block.js | 100 ++- .../scratch-gui/src/lib/make-toolbox-xml.js | 315 +++------ .../src/lib/themes/blockHelpers.js | 51 +- .../src/lib/themes/default/index.js | 140 ++-- .../src/lib/themes/high-contrast/index.js | 131 ++-- 9 files changed, 736 insertions(+), 1123 deletions(-) diff --git a/packages/scratch-gui/src/components/monitor/monitor.jsx b/packages/scratch-gui/src/components/monitor/monitor.jsx index e398445708..52be964d83 100644 --- a/packages/scratch-gui/src/components/monitor/monitor.jsx +++ b/packages/scratch-gui/src/components/monitor/monitor.jsx @@ -1,56 +1,52 @@ -import React from "react"; -import ReactDOM from "react-dom"; -import PropTypes from "prop-types"; -import Draggable from "react-draggable"; -import { FormattedMessage } from "react-intl"; -import { ContextMenuTrigger } from "react-contextmenu"; -import { - BorderedMenuItem, - ContextMenu, - MenuItem, -} from "../context-menu/context-menu.jsx"; -import Box from "../box/box.jsx"; -import DefaultMonitor from "./default-monitor.jsx"; -import LargeMonitor from "./large-monitor.jsx"; -import SliderMonitor from "../../containers/slider-monitor.jsx"; -import ListMonitor from "../../containers/list-monitor.jsx"; -import { getColorsForTheme } from "../../lib/themes/index.js"; +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import Draggable from 'react-draggable'; +import {FormattedMessage} from 'react-intl'; +import {ContextMenuTrigger} from 'react-contextmenu'; +import {BorderedMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx'; +import Box from '../box/box.jsx'; +import DefaultMonitor from './default-monitor.jsx'; +import LargeMonitor from './large-monitor.jsx'; +import SliderMonitor from '../../containers/slider-monitor.jsx'; +import ListMonitor from '../../containers/list-monitor.jsx'; +import {getColorsForTheme} from '../../lib/themes/index.js'; -import styles from "./monitor.css"; +import styles from './monitor.css'; // Map category name to color name used in scratch-blocks Blockly.Colours. Note // that Blockly uses the UK spelling of "colour", so fields that interact // directly with Blockly follow that convention, while Scratch code uses the US // spelling of "color". const categoryColorMap = { - data: "data", - sensing: "sensing", - sound: "sounds", - looks: "looks", - motion: "motion", - list: "data_lists", - extension: "pen", + data: 'data', + sensing: 'sensing', + sound: 'sounds', + looks: 'looks', + motion: 'motion', + list: 'data_lists', + extension: 'pen' }; const modes = { default: DefaultMonitor, large: LargeMonitor, slider: SliderMonitor, - list: ListMonitor, + list: ListMonitor }; const getCategoryColor = (theme, category) => { const colors = getColorsForTheme(theme); return { background: colors[categoryColorMap[category]].colourPrimary, - text: colors.text, + text: colors.text }; }; -const MonitorComponent = (props) => ( +const MonitorComponent = props => ( ( {React.createElement(modes[props.mode], { - categoryColor: getCategoryColor( - props.theme, - props.category - ), - ...props, + categoryColor: getCategoryColor(props.theme, props.category), + ...props })} - {ReactDOM.createPortal( + {ReactDOM.createPortal(( // Use a portal to render the context menu outside the flow to avoid // positioning conflicts between the monitors `transform: scale` and // the context menus `position: fixed`. For more details, see // http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/ - {props.onSetModeToDefault && ( + {props.onSetModeToDefault && - - )} - {props.onSetModeToLarge && ( + } + {props.onSetModeToLarge && - - )} - {props.onSetModeToSlider && ( + } + {props.onSetModeToSlider && - - )} - {props.onSliderPromptOpen && props.mode === "slider" && ( + } + {props.onSliderPromptOpen && props.mode === 'slider' && - - )} - {props.onImport && ( + } + {props.onImport && - - )} - {props.onExport && ( + } + {props.onExport && - - )} - {props.onHide && ( + } + {props.onHide && - - )} - , - document.body - )} + } + + ), document.body)} + ); const monitorModes = Object.keys(modes); @@ -170,12 +152,15 @@ MonitorComponent.propTypes = { onSetModeToLarge: PropTypes.func, onSetModeToSlider: PropTypes.func, onSliderPromptOpen: PropTypes.func, - theme: PropTypes.string.isRequired, + theme: PropTypes.string.isRequired }; MonitorComponent.defaultProps = { - category: "extension", - mode: "default", + category: 'extension', + mode: 'default' }; -export { MonitorComponent as default, monitorModes }; +export { + MonitorComponent as default, + monitorModes +}; diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 1c759423c8..f1cd7ee618 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -1,50 +1,43 @@ -import bindAll from "lodash.bindall"; -import debounce from "lodash.debounce"; -import defaultsDeep from "lodash.defaultsdeep"; -import makeToolboxXML from "../lib/make-toolbox-xml"; -import PropTypes from "prop-types"; -import React from "react"; -import VMScratchBlocks from "../lib/blocks"; -import VM from "@scratch/scratch-vm"; - -import log from "../lib/log.js"; -import Prompt from "./prompt.jsx"; -import BlocksComponent from "../components/blocks/blocks.jsx"; -import ExtensionLibrary from "./extension-library.jsx"; -import extensionData from "../lib/libraries/extensions/index.jsx"; -import CustomProcedures from "./custom-procedures.jsx"; -import errorBoundaryHOC from "../lib/error-boundary-hoc.jsx"; -import { - BLOCKS_DEFAULT_SCALE, - STAGE_DISPLAY_SIZES, -} from "../lib/layout-constants"; -import DropAreaHOC from "../lib/drop-area-hoc.jsx"; -import DragConstants from "../lib/drag-constants"; -import defineDynamicBlock from "../lib/define-dynamic-block"; -import { DEFAULT_THEME, getColorsForTheme, themeMap } from "../lib/themes"; +import bindAll from 'lodash.bindall'; +import debounce from 'lodash.debounce'; +import defaultsDeep from 'lodash.defaultsdeep'; +import makeToolboxXML from '../lib/make-toolbox-xml'; +import PropTypes from 'prop-types'; +import React from 'react'; +import VMScratchBlocks from '../lib/blocks'; +import VM from '@scratch/scratch-vm'; + +import log from '../lib/log.js'; +import Prompt from './prompt.jsx'; +import BlocksComponent from '../components/blocks/blocks.jsx'; +import ExtensionLibrary from './extension-library.jsx'; +import extensionData from '../lib/libraries/extensions/index.jsx'; +import CustomProcedures from './custom-procedures.jsx'; +import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; +import {BLOCKS_DEFAULT_SCALE, STAGE_DISPLAY_SIZES} from '../lib/layout-constants'; +import DropAreaHOC from '../lib/drop-area-hoc.jsx'; +import DragConstants from '../lib/drag-constants'; +import defineDynamicBlock from '../lib/define-dynamic-block'; +import {DEFAULT_THEME, getColorsForTheme, themeMap} from '../lib/themes'; import { injectExtensionBlockIcons, injectExtensionCategoryTheme, - getExtensionColors, -} from "../lib/themes/blockHelpers"; + getExtensionColors +} from '../lib/themes/blockHelpers'; + +import {connect} from 'react-redux'; +import {updateToolbox} from '../reducers/toolbox'; +import {activateColorPicker} from '../reducers/color-picker'; +import {closeExtensionLibrary, openSoundRecorder, openConnectionModal} from '../reducers/modals'; +import {activateCustomProcedures, deactivateCustomProcedures} from '../reducers/custom-procedures'; +import {setConnectionModalExtensionId} from '../reducers/connection-modal'; +import {updateMetrics} from '../reducers/workspace-metrics'; +import {isTimeTravel2020} from '../reducers/time-travel'; -import { connect } from "react-redux"; -import { updateToolbox } from "../reducers/toolbox"; -import { activateColorPicker } from "../reducers/color-picker"; -import { - closeExtensionLibrary, - openSoundRecorder, - openConnectionModal, -} from "../reducers/modals"; import { - activateCustomProcedures, - deactivateCustomProcedures, -} from "../reducers/custom-procedures"; -import { setConnectionModalExtensionId } from "../reducers/connection-modal"; -import { updateMetrics } from "../reducers/workspace-metrics"; -import { isTimeTravel2020 } from "../reducers/time-travel"; - -import { activateTab, SOUNDS_TAB_INDEX } from "../reducers/editor-tab"; + activateTab, + SOUNDS_TAB_INDEX +} from '../reducers/editor-tab'; const addFunctionListener = (object, property, callback) => { const oldFn = object[property]; @@ -55,73 +48,65 @@ const addFunctionListener = (object, property, callback) => { }; }; -const DroppableBlocks = DropAreaHOC([DragConstants.BACKPACK_CODE])( - BlocksComponent -); +const DroppableBlocks = DropAreaHOC([ + DragConstants.BACKPACK_CODE +])(BlocksComponent); class Blocks extends React.Component { - constructor(props) { + constructor (props) { super(props); this.ScratchBlocks = VMScratchBlocks(props.vm, false); bindAll(this, [ - "attachVM", - "detachVM", - "getToolboxXML", - "handleCategorySelected", - "handleConnectionModalStart", - "handleDrop", - "handleStatusButtonUpdate", - "handleOpenSoundRecorder", - "handlePromptStart", - "handlePromptCallback", - "handlePromptClose", - "handleCustomProceduresClose", - "onScriptGlowOn", - "onScriptGlowOff", - "onBlockGlowOn", - "onBlockGlowOff", - "handleMonitorsUpdate", - "handleExtensionAdded", - "handleBlocksInfoUpdate", - "onTargetsUpdate", - "onVisualReport", - "onWorkspaceUpdate", - "onWorkspaceMetricsChange", - "setBlocks", - "setLocale", + 'attachVM', + 'detachVM', + 'getToolboxXML', + 'handleCategorySelected', + 'handleConnectionModalStart', + 'handleDrop', + 'handleStatusButtonUpdate', + 'handleOpenSoundRecorder', + 'handlePromptStart', + 'handlePromptCallback', + 'handlePromptClose', + 'handleCustomProceduresClose', + 'onScriptGlowOn', + 'onScriptGlowOff', + 'onBlockGlowOn', + 'onBlockGlowOff', + 'handleMonitorsUpdate', + 'handleExtensionAdded', + 'handleBlocksInfoUpdate', + 'onTargetsUpdate', + 'onVisualReport', + 'onWorkspaceUpdate', + 'onWorkspaceMetricsChange', + 'setBlocks', + 'setLocale' ]); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); this.ScratchBlocks.ScratchVariables.setPromptHandler( this.handlePromptStart ); - this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = - this.handleConnectionModalStart; + this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; this.state = { - prompt: null, + prompt: null }; this.onTargetsUpdate = debounce(this.onTargetsUpdate, 100); this.toolboxUpdateQueue = []; } - componentDidMount() { - this.ScratchBlocks = VMScratchBlocks( - this.props.vm, - this.props.useCatBlocks - ); + componentDidMount () { + this.ScratchBlocks = VMScratchBlocks(this.props.vm, this.props.useCatBlocks); this.ScratchBlocks.dialog.setPrompt(this.handlePromptStart); - this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = - this.handleConnectionModalStart; + this.ScratchBlocks.StatusIndicatorLabel.statusButtonCallback = this.handleConnectionModalStart; this.ScratchBlocks.recordSoundCallback = this.handleOpenSoundRecorder; - this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = - this.props.onActivateColorPicker; - this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = - this.props.onActivateCustomProcedures; + this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker; + this.ScratchBlocks.ScratchProcedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); - const workspaceConfig = defaultsDeep( - {}, + const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options, { @@ -130,34 +115,31 @@ class Blocks extends React.Component { theme: new this.ScratchBlocks.Theme( this.props.theme, getColorsForTheme(this.props.theme) - ), + ) } ); - this.workspace = this.ScratchBlocks.inject( - this.blocks, - workspaceConfig - ); + this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig); this.workspace.registerToolboxCategoryCallback( - "VARIABLE", + 'VARIABLE', this.ScratchBlocks.ScratchVariables.getVariablesCategory ); this.workspace.registerToolboxCategoryCallback( - "PROCEDURE", + 'PROCEDURE', this.ScratchBlocks.ScratchProcedures.getProceduresCategory ); - this.toolboxUpdateChangeListener = (event) => { + this.toolboxUpdateChangeListener = event => { if ( event.type === this.ScratchBlocks.Events.VAR_CREATE || event.type === this.ScratchBlocks.Events.VAR_RENAME || event.type === this.ScratchBlocks.Events.VAR_DELETE || (event.type === this.ScratchBlocks.Events.BLOCK_DELETE && - event.oldJson.type === "procedures_definition") || + event.oldJson.type === 'procedures_definition') || // Only refresh the toolbox when procedure block creations are // triggered by undoing a deletion (implied by recordUndo being // false on the event). (event.type === this.ScratchBlocks.Events.BLOCK_CREATE && - event.json.type === "procedures_definition" && + event.json.type === 'procedures_definition' && !event.recordUndo) ) { this.requestToolboxUpdate(); @@ -170,30 +152,15 @@ class Blocks extends React.Component { const toolboxWorkspace = this.workspace.getFlyout().getWorkspace(); - const varListButtonCallback = (type) => () => - this.ScratchBlocks.ScratchVariables.createVariable( - this.workspace, - null, - type - ); + const varListButtonCallback = type => + (() => this.ScratchBlocks.ScratchVariables.createVariable(this.workspace, null, type)); const procButtonCallback = () => { - this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback( - this.workspace - ); + this.ScratchBlocks.ScratchProcedures.createProcedureDefCallback(this.workspace); }; - toolboxWorkspace.registerButtonCallback( - "MAKE_A_VARIABLE", - varListButtonCallback("") - ); - toolboxWorkspace.registerButtonCallback( - "MAKE_A_LIST", - varListButtonCallback("list") - ); - toolboxWorkspace.registerButtonCallback( - "MAKE_A_PROCEDURE", - procButtonCallback - ); + toolboxWorkspace.registerButtonCallback('MAKE_A_VARIABLE', varListButtonCallback('')); + toolboxWorkspace.registerButtonCallback('MAKE_A_LIST', varListButtonCallback('list')); + toolboxWorkspace.registerButtonCallback('MAKE_A_PROCEDURE', procButtonCallback); // Store the xml of the toolbox that is actually rendered. // This is used in componentDidUpdate instead of prevProps, because @@ -201,16 +168,8 @@ class Blocks extends React.Component { this._renderedToolboxXML = this.props.toolboxXML; // @todo change this when blockly supports UI events - addFunctionListener( - this.workspace, - "translate", - this.onWorkspaceMetricsChange - ); - addFunctionListener( - this.workspace, - "zoom", - this.onWorkspaceMetricsChange - ); + addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange); + addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange); this.workspace.getToolbox().selectItemByPosition(0); this.attachVM(); @@ -220,21 +179,19 @@ class Blocks extends React.Component { this.setLocale(); } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate (nextProps, nextState) { return ( this.state.prompt !== nextState.prompt || this.props.isVisible !== nextProps.isVisible || this._renderedToolboxXML !== nextProps.toolboxXML || - this.props.extensionLibraryVisible !== - nextProps.extensionLibraryVisible || - this.props.customProceduresVisible !== - nextProps.customProceduresVisible || + this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible || + this.props.customProceduresVisible !== nextProps.customProceduresVisible || this.props.locale !== nextProps.locale || this.props.anyModalVisible !== nextProps.anyModalVisible || this.props.stageSize !== nextProps.stageSize ); } - componentDidUpdate(prevProps) { + componentDidUpdate (prevProps) { // If any modals are open, call hideChaff to close z-indexed field editors if (this.props.anyModalVisible && !prevProps.anyModalVisible) { this.ScratchBlocks.hideChaff(); @@ -243,29 +200,22 @@ class Blocks extends React.Component { // Only rerender the toolbox when the blocks are visible and the xml is // different from the previously rendered toolbox xml. // Do not check against prevProps.toolboxXML because that may not have been rendered. - if ( - this.props.isVisible && - this.props.toolboxXML !== this._renderedToolboxXML - ) { + if (this.props.isVisible && this.props.toolboxXML !== this._renderedToolboxXML) { this.requestToolboxUpdate(); } if (this.props.isVisible === prevProps.isVisible) { if (this.props.stageSize !== prevProps.stageSize) { // force workspace to redraw for the new stage size - window.dispatchEvent(new Event("resize")); + window.dispatchEvent(new Event('resize')); } return; } // @todo hack to resize blockly manually in case resize happened while hidden // @todo hack to reload the workspace due to gui bug #413 - if (this.props.isVisible) { - // Scripts tab + if (this.props.isVisible) { // Scripts tab this.workspace.setVisible(true); - if ( - prevProps.locale !== this.props.locale || - this.props.locale !== this.props.vm.getLocale() - ) { + if (prevProps.locale !== this.props.locale || this.props.locale !== this.props.vm.getLocale()) { // call setLocale if the locale has changed, or changed while the blocks were hidden. // vm.getLocale() will be out of sync if locale was changed while not visible this.setLocale(); @@ -273,12 +223,12 @@ class Blocks extends React.Component { this.props.vm.refreshWorkspace(); } - window.dispatchEvent(new Event("resize")); + window.dispatchEvent(new Event('resize')); } else { this.workspace.setVisible(false); } } - componentWillUnmount() { + componentWillUnmount () { this.detachVM(); this.workspace.dispose(); clearTimeout(this.toolboxUpdateTimeout); @@ -286,16 +236,15 @@ class Blocks extends React.Component { // Clear the flyout blocks so that they can be recreated on mount. this.props.vm.clearFlyoutBlocks(); } - requestToolboxUpdate() { + requestToolboxUpdate () { clearTimeout(this.toolboxUpdateTimeout); this.toolboxUpdateTimeout = setTimeout(() => { this.updateToolbox(); }, 0); } - setLocale() { + setLocale () { this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); - this.props.vm - .setLocale(this.props.locale, this.props.messages) + this.props.vm.setLocale(this.props.locale, this.props.messages) .then(() => { this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); @@ -305,7 +254,7 @@ class Blocks extends React.Component { }); } - updateToolbox() { + updateToolbox () { this.toolboxUpdateTimeout = false; const scale = this.workspace.getFlyout().getWorkspace().scale; @@ -318,7 +267,8 @@ class Blocks extends React.Component { .getFlyout() .getCategoryScrollPosition(selectedCategoryName).y * scale; const offsetWithinCategory = - this.workspace.getFlyout().getWorkspace().getMetrics().viewTop - + this.workspace.getFlyout().getWorkspace() + .getMetrics().viewTop - selectedCategoryScrollPosition; this.workspace.updateToolbox(this.props.toolboxXML); @@ -333,16 +283,16 @@ class Blocks extends React.Component { .getFlyout() .getWorkspace() .scrollbar.setY( - newCategoryScrollPosition.y * scale + offsetWithinCategory + (newCategoryScrollPosition.y * scale) + offsetWithinCategory ); } const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; - queue.forEach((fn) => fn()); + queue.forEach(fn => fn()); } - withToolboxUpdates(fn) { + withToolboxUpdates (fn) { // if there is a queued toolbox update, we need to wait if (this.toolboxUpdateTimeout) { this.toolboxUpdateQueue.push(fn); @@ -351,68 +301,42 @@ class Blocks extends React.Component { } } - attachVM() { + attachVM () { this.workspace.addChangeListener(this.props.vm.blockListener); - this.flyoutWorkspace = this.workspace.getFlyout().getWorkspace(); - this.flyoutWorkspace.addChangeListener( - this.props.vm.flyoutBlockListener - ); - this.flyoutWorkspace.addChangeListener( - this.props.vm.monitorBlockListener - ); - this.props.vm.addListener("SCRIPT_GLOW_ON", this.onScriptGlowOn); - this.props.vm.addListener("SCRIPT_GLOW_OFF", this.onScriptGlowOff); - this.props.vm.addListener("BLOCK_GLOW_ON", this.onBlockGlowOn); - this.props.vm.addListener("BLOCK_GLOW_OFF", this.onBlockGlowOff); - this.props.vm.addListener("VISUAL_REPORT", this.onVisualReport); - this.props.vm.addListener("workspaceUpdate", this.onWorkspaceUpdate); - this.props.vm.addListener("targetsUpdate", this.onTargetsUpdate); - this.props.vm.addListener("MONITORS_UPDATE", this.handleMonitorsUpdate); - this.props.vm.addListener("EXTENSION_ADDED", this.handleExtensionAdded); - this.props.vm.addListener( - "BLOCKSINFO_UPDATE", - this.handleBlocksInfoUpdate - ); - this.props.vm.addListener( - "PERIPHERAL_CONNECTED", - this.handleStatusButtonUpdate - ); - this.props.vm.addListener( - "PERIPHERAL_DISCONNECTED", - this.handleStatusButtonUpdate - ); - } - detachVM() { - this.props.vm.removeListener("SCRIPT_GLOW_ON", this.onScriptGlowOn); - this.props.vm.removeListener("SCRIPT_GLOW_OFF", this.onScriptGlowOff); - this.props.vm.removeListener("BLOCK_GLOW_ON", this.onBlockGlowOn); - this.props.vm.removeListener("BLOCK_GLOW_OFF", this.onBlockGlowOff); - this.props.vm.removeListener("VISUAL_REPORT", this.onVisualReport); - this.props.vm.removeListener("workspaceUpdate", this.onWorkspaceUpdate); - this.props.vm.removeListener("targetsUpdate", this.onTargetsUpdate); - this.props.vm.removeListener( - "MONITORS_UPDATE", - this.handleMonitorsUpdate - ); - this.props.vm.removeListener( - "EXTENSION_ADDED", - this.handleExtensionAdded - ); - this.props.vm.removeListener( - "BLOCKSINFO_UPDATE", - this.handleBlocksInfoUpdate - ); - this.props.vm.removeListener( - "PERIPHERAL_CONNECTED", - this.handleStatusButtonUpdate - ); - this.props.vm.removeListener( - "PERIPHERAL_DISCONNECTED", - this.handleStatusButtonUpdate - ); - } - - updateToolboxBlockValue(id, value) { + this.flyoutWorkspace = this.workspace + .getFlyout() + .getWorkspace(); + this.flyoutWorkspace.addChangeListener(this.props.vm.flyoutBlockListener); + this.flyoutWorkspace.addChangeListener(this.props.vm.monitorBlockListener); + this.props.vm.addListener('SCRIPT_GLOW_ON', this.onScriptGlowOn); + this.props.vm.addListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff); + this.props.vm.addListener('BLOCK_GLOW_ON', this.onBlockGlowOn); + this.props.vm.addListener('BLOCK_GLOW_OFF', this.onBlockGlowOff); + this.props.vm.addListener('VISUAL_REPORT', this.onVisualReport); + this.props.vm.addListener('workspaceUpdate', this.onWorkspaceUpdate); + this.props.vm.addListener('targetsUpdate', this.onTargetsUpdate); + this.props.vm.addListener('MONITORS_UPDATE', this.handleMonitorsUpdate); + this.props.vm.addListener('EXTENSION_ADDED', this.handleExtensionAdded); + this.props.vm.addListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); + this.props.vm.addListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); + this.props.vm.addListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); + } + detachVM () { + this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn); + this.props.vm.removeListener('SCRIPT_GLOW_OFF', this.onScriptGlowOff); + this.props.vm.removeListener('BLOCK_GLOW_ON', this.onBlockGlowOn); + this.props.vm.removeListener('BLOCK_GLOW_OFF', this.onBlockGlowOff); + this.props.vm.removeListener('VISUAL_REPORT', this.onVisualReport); + this.props.vm.removeListener('workspaceUpdate', this.onWorkspaceUpdate); + this.props.vm.removeListener('targetsUpdate', this.onTargetsUpdate); + this.props.vm.removeListener('MONITORS_UPDATE', this.handleMonitorsUpdate); + this.props.vm.removeListener('EXTENSION_ADDED', this.handleExtensionAdded); + this.props.vm.removeListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); + this.props.vm.removeListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); + this.props.vm.removeListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); + } + + updateToolboxBlockValue (id, value) { this.withToolboxUpdates(() => { const block = this.workspace .getFlyout() @@ -424,21 +348,15 @@ class Blocks extends React.Component { }); } - onTargetsUpdate() { + onTargetsUpdate () { if (this.props.vm.editingTarget && this.workspace.getFlyout()) { - ["glide", "move", "set"].forEach((prefix) => { - this.updateToolboxBlockValue( - `${prefix}x`, - Math.round(this.props.vm.editingTarget.x).toString() - ); - this.updateToolboxBlockValue( - `${prefix}y`, - Math.round(this.props.vm.editingTarget.y).toString() - ); + ['glide', 'move', 'set'].forEach(prefix => { + this.updateToolboxBlockValue(`${prefix}x`, Math.round(this.props.vm.editingTarget.x).toString()); + this.updateToolboxBlockValue(`${prefix}y`, Math.round(this.props.vm.editingTarget.y).toString()); }); } } - onWorkspaceMetricsChange() { + onWorkspaceMetricsChange () { const target = this.props.vm.editingTarget; if (target && target.id) { // Dispatch updateMetrics later, since onWorkspaceMetricsChange may be (very indirectly) @@ -449,32 +367,32 @@ class Blocks extends React.Component { targetID: target.id, scrollX: this.workspace.scrollX, scrollY: this.workspace.scrollY, - scale: this.workspace.scale, + scale: this.workspace.scale }); }, 0); } } - onScriptGlowOn(data) { + onScriptGlowOn (data) { this.ScratchBlocks.glowStack(data.id, true); } - onScriptGlowOff(data) { + onScriptGlowOff (data) { this.ScratchBlocks.glowStack(data.id, false); } - onBlockGlowOn(data) { + onBlockGlowOn (/* data */) { // No-op, support may be added in the future } - onBlockGlowOff(data) { + onBlockGlowOff (/* data */) { // No-op, support may be added in the future } - onVisualReport(data) { + onVisualReport (data) { this.ScratchBlocks.reportValue(data.id, data.value); } - getToolboxXML() { + getToolboxXML () { // Use try/catch because this requires digging pretty deep into the VM // Code inside intentionally ignores several error situations (no stage, etc.) // Because they would get caught by this try/catch try { - let { editingTarget: target, runtime } = this.props.vm; + let {editingTarget: target, runtime} = this.props.vm; const stage = runtime.getTargetForStage(); if (!target) target = stage; // If no editingTarget, use the stage @@ -485,33 +403,24 @@ class Blocks extends React.Component { this.props.vm.runtime.getBlocksXML(target), this.props.theme ); - return makeToolboxXML( - false, - target.isStage, - target.id, - dynamicBlocksXML, + return makeToolboxXML(false, target.isStage, target.id, dynamicBlocksXML, targetCostumes[targetCostumes.length - 1].name, stageCostumes[stageCostumes.length - 1].name, - targetSounds.length > 0 - ? targetSounds[targetSounds.length - 1].name - : "", + targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : '', getColorsForTheme(this.props.theme) ); } catch { return null; } } - onWorkspaceUpdate(data) { + onWorkspaceUpdate (data) { // When we change sprites, update the toolbox to have the new sprite's blocks const toolboxXML = this.getToolboxXML(); if (toolboxXML) { this.props.updateToolboxState(toolboxXML); } - if ( - this.props.vm.editingTarget && - !this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id] - ) { + if (this.props.vm.editingTarget && !this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]) { this.onWorkspaceMetricsChange(); } @@ -520,10 +429,7 @@ class Blocks extends React.Component { this.workspace.removeChangeListener(this.toolboxUpdateChangeListener); const dom = this.ScratchBlocks.utils.xml.textToDom(data.xml); try { - this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml( - dom, - this.workspace - ); + this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); } catch (error) { // The workspace is likely incomplete. What did update should be // functional. @@ -541,14 +447,8 @@ class Blocks extends React.Component { } this.workspace.addChangeListener(this.props.vm.blockListener); - if ( - this.props.vm.editingTarget && - this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id] - ) { - const { scrollX, scrollY, scale } = - this.props.workspaceMetrics.targets[ - this.props.vm.editingTarget.id - ]; + if (this.props.vm.editingTarget && this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]) { + const {scrollX, scrollY, scale} = this.props.workspaceMetrics.targets[this.props.vm.editingTarget.id]; this.workspace.scrollX = scrollX; this.workspace.scrollY = scrollY; this.workspace.scale = scale; @@ -569,14 +469,14 @@ class Blocks extends React.Component { }); }); } - handleMonitorsUpdate(monitors) { + handleMonitorsUpdate (monitors) { // Update the checkboxes of the relevant monitors. // TODO: What about monitors that have fields? See todo in scratch-vm blocks.js changeBlock: // https://github.com/LLK/scratch-vm/blob/2373f9483edaf705f11d62662f7bb2a57fbb5e28/src/engine/blocks.js#L569-L576 const flyout = this.workspace.getFlyout(); for (const monitor of monitors.values()) { - const blockId = monitor.get("id"); - const isVisible = monitor.get("visible"); + const blockId = monitor.get('id'); + const isVisible = monitor.get('visible'); flyout.setCheckboxState(blockId, isVisible); // We also need to update the isMonitored flag for this block on the VM, since it's used to determine // whether the checkbox is activated or not when the checkbox is re-displayed (e.g. local variables/blocks @@ -587,37 +487,28 @@ class Blocks extends React.Component { } } } - handleExtensionAdded(categoryInfo) { - const defineBlocks = (blockInfoArray) => { + handleExtensionAdded (categoryInfo) { + const defineBlocks = blockInfoArray => { if (blockInfoArray && blockInfoArray.length > 0) { const staticBlocksJson = []; const dynamicBlocksInfo = []; - blockInfoArray.forEach((blockInfo) => { + blockInfoArray.forEach(blockInfo => { if (blockInfo.info && blockInfo.info.isDynamic) { dynamicBlocksInfo.push(blockInfo); } else if (blockInfo.json) { - staticBlocksJson.push( - injectExtensionBlockIcons( - blockInfo.json, - this.props.theme - ) - ); + staticBlocksJson.push(injectExtensionBlockIcons(blockInfo.json, this.props.theme)); } // otherwise it's a non-block entry such as '---' }); this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson); - dynamicBlocksInfo.forEach((blockInfo) => { + dynamicBlocksInfo.forEach(blockInfo => { // This is creating the block factory / constructor -- NOT a specific instance of the block. // The factory should only know static info about the block: the category info and the opcode. // Anything else will be picked up from the XML attached to the block instance. const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`; - const blockDefinition = defineDynamicBlock( - this.ScratchBlocks, - categoryInfo, - blockInfo, - extendedOpcode - ); + const blockDefinition = + defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode); this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition; }); } @@ -626,12 +517,8 @@ class Blocks extends React.Component { // scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block) // these actually define blocks and MUST run regardless of the UI state defineBlocks( - Object.getOwnPropertyNames(categoryInfo.customFieldTypes).map( - (fieldTypeName) => - categoryInfo.customFieldTypes[fieldTypeName] - .scratchBlocksDefinition - ) - ); + Object.getOwnPropertyNames(categoryInfo.customFieldTypes) + .map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition)); defineBlocks(categoryInfo.menus); defineBlocks(categoryInfo.blocks); // Note that Blockly uses the UK spelling of "colour", so fields that @@ -654,7 +541,7 @@ class Blocks extends React.Component { colourPrimary, colourSecondary, colourTertiary, - colourQuaternary, + colourQuaternary }); this.ScratchBlocks.getMainWorkspace() .getTheme() @@ -662,7 +549,7 @@ class Blocks extends React.Component { colourPrimary: colourQuaternary, colourSecondary: colourQuaternary, colourTertiary: colourQuaternary, - colourQuaternary: colourQuaternary, + colourQuaternary: colourQuaternary }); this.ScratchBlocks.getMainWorkspace().setTheme( this.ScratchBlocks.getMainWorkspace().getTheme() @@ -673,14 +560,12 @@ class Blocks extends React.Component { this.props.updateToolboxState(toolboxXML); } } - handleBlocksInfoUpdate(categoryInfo) { + handleBlocksInfoUpdate (categoryInfo) { // @todo Later we should replace this to avoid all the warnings from redefining blocks. this.handleExtensionAdded(categoryInfo); } - handleCategorySelected(categoryId) { - const extension = extensionData.find( - (ext) => ext.extensionId === categoryId - ); + handleCategorySelected (categoryId) { + const extension = extensionData.find(ext => ext.extensionId === categoryId); if (extension && extension.launchPeripheralConnectionFlow) { this.handleConnectionModalStart(categoryId); } @@ -690,35 +575,29 @@ class Blocks extends React.Component { toolbox.setSelectedItem(toolbox.getToolboxItemById(categoryId)); }); } - setBlocks(blocks) { + setBlocks (blocks) { this.blocks = blocks; } - handlePromptStart(message, defaultValue, callback, optTitle, optVarType) { - const p = { prompt: { callback, message, defaultValue } }; - p.prompt.title = optTitle - ? optTitle - : this.ScratchBlocks.Msg.VARIABLE_MODAL_TITLE; - p.prompt.varType = - typeof optVarType === "string" - ? optVarType - : this.ScratchBlocks.SCALAR_VARIABLE_TYPE; + handlePromptStart (message, defaultValue, callback, optTitle, optVarType) { + const p = {prompt: {callback, message, defaultValue}}; + p.prompt.title = optTitle ? optTitle : + this.ScratchBlocks.Msg.VARIABLE_MODAL_TITLE; + p.prompt.varType = typeof optVarType === 'string' ? + optVarType : this.ScratchBlocks.SCALAR_VARIABLE_TYPE; p.prompt.showVariableOptions = // This flag means that we should show variable/list options about scope optVarType !== this.ScratchBlocks.BROADCAST_MESSAGE_VARIABLE_TYPE && - p.prompt.title !== - this.ScratchBlocks.Msg.RENAME_VARIABLE_MODAL_TITLE && + p.prompt.title !== this.ScratchBlocks.Msg.RENAME_VARIABLE_MODAL_TITLE && p.prompt.title !== this.ScratchBlocks.Msg.RENAME_LIST_MODAL_TITLE; - p.prompt.showCloudOption = - optVarType === this.ScratchBlocks.SCALAR_VARIABLE_TYPE && - this.props.canUseCloud; + p.prompt.showCloudOption = (optVarType === this.ScratchBlocks.SCALAR_VARIABLE_TYPE) && this.props.canUseCloud; this.setState(p); } - handleConnectionModalStart(extensionId) { + handleConnectionModalStart (extensionId) { this.props.onOpenConnectionModal(extensionId); } - handleStatusButtonUpdate() { + handleStatusButtonUpdate () { this.workspace.getFlyout().refreshStatusButtons(); } - handleOpenSoundRecorder() { + handleOpenSoundRecorder () { this.props.onOpenSoundRecorder(); } @@ -727,39 +606,31 @@ class Blocks extends React.Component { * and additional potentially conflicting variable names from the VM * to the variable validation prompt callback used in scratch-blocks. */ - handlePromptCallback(input, variableOptions) { + handlePromptCallback (input, variableOptions) { this.state.prompt.callback( input, - this.props.vm.runtime.getAllVarNamesOfType( - this.state.prompt.varType - ), - variableOptions - ); + this.props.vm.runtime.getAllVarNamesOfType(this.state.prompt.varType), + variableOptions); this.handlePromptClose(); } - handlePromptClose() { - this.setState({ prompt: null }); + handlePromptClose () { + this.setState({prompt: null}); } - handleCustomProceduresClose(data) { + handleCustomProceduresClose (data) { this.props.onRequestCloseCustomProcedures(data); const ws = this.workspace; this.updateToolbox(); - ws.getToolbox().selectCategoryByName("myBlocks"); + ws.getToolbox().selectCategoryByName('myBlocks'); } - handleDrop(dragInfo) { + handleDrop (dragInfo) { fetch(dragInfo.payload.bodyUrl) - .then((response) => response.json()) - .then((blocks) => - this.props.vm.shareBlocksToTarget( - blocks, - this.props.vm.editingTarget.id - ) - ) + .then(response => response.json()) + .then(blocks => this.props.vm.shareBlocksToTarget(blocks, this.props.vm.editingTarget.id)) .then(() => { this.props.vm.refreshWorkspace(); }); } - render() { + render () { /* eslint-disable no-unused-vars */ const { anyModalVisible, @@ -797,15 +668,10 @@ class Blocks extends React.Component { ({ - anyModalVisible: - Object.keys(state.scratchGui.modals).some( - (key) => state.scratchGui.modals[key] - ) || state.scratchGui.mode.isFullScreen, +const mapStateToProps = state => ({ + anyModalVisible: ( + Object.keys(state.scratchGui.modals).some(key => state.scratchGui.modals[key]) || + state.scratchGui.mode.isFullScreen + ), extensionLibraryVisible: state.scratchGui.modals.extensionLibrary, isRtl: state.locales.isRtl, locale: state.locales.locale, @@ -910,15 +776,13 @@ const mapStateToProps = (state) => ({ toolboxXML: state.scratchGui.toolbox.toolboxXML, customProceduresVisible: state.scratchGui.customProcedures.active, workspaceMetrics: state.scratchGui.workspaceMetrics, - useCatBlocks: isTimeTravel2020(state), + useCatBlocks: isTimeTravel2020(state) }); -const mapDispatchToProps = (dispatch) => ({ - onActivateColorPicker: (callback) => - dispatch(activateColorPicker(callback)), - onActivateCustomProcedures: (data, callback) => - dispatch(activateCustomProcedures(data, callback)), - onOpenConnectionModal: (id) => { +const mapDispatchToProps = dispatch => ({ + onActivateColorPicker: callback => dispatch(activateColorPicker(callback)), + onActivateCustomProcedures: (data, callback) => dispatch(activateCustomProcedures(data, callback)), + onOpenConnectionModal: id => { dispatch(setConnectionModalExtensionId(id)); dispatch(openConnectionModal()); }, @@ -929,17 +793,20 @@ const mapDispatchToProps = (dispatch) => ({ onRequestCloseExtensionLibrary: () => { dispatch(closeExtensionLibrary()); }, - onRequestCloseCustomProcedures: (data) => { + onRequestCloseCustomProcedures: data => { dispatch(deactivateCustomProcedures(data)); }, - updateToolboxState: (toolboxXML) => { + updateToolboxState: toolboxXML => { dispatch(updateToolbox(toolboxXML)); }, - updateMetrics: (metrics) => { + updateMetrics: metrics => { dispatch(updateMetrics(metrics)); - }, + } }); -export default errorBoundaryHOC("Blocks")( - connect(mapStateToProps, mapDispatchToProps)(Blocks) +export default errorBoundaryHOC('Blocks')( + connect( + mapStateToProps, + mapDispatchToProps + )(Blocks) ); diff --git a/packages/scratch-gui/src/containers/custom-procedures.jsx b/packages/scratch-gui/src/containers/custom-procedures.jsx index 068b9097b1..522a8059a0 100644 --- a/packages/scratch-gui/src/containers/custom-procedures.jsx +++ b/packages/scratch-gui/src/containers/custom-procedures.jsx @@ -1,42 +1,41 @@ -import bindAll from "lodash.bindall"; -import defaultsDeep from "lodash.defaultsdeep"; -import PropTypes from "prop-types"; -import React from "react"; -import CustomProceduresComponent from "../components/custom-procedures/custom-procedures.jsx"; -import { getColorsForTheme, themeMap } from "../lib/themes"; -import { ScratchBlocks } from "scratch-blocks"; -import { connect } from "react-redux"; +import bindAll from 'lodash.bindall'; +import defaultsDeep from 'lodash.defaultsdeep'; +import PropTypes from 'prop-types'; +import React from 'react'; +import CustomProceduresComponent from '../components/custom-procedures/custom-procedures.jsx'; +import {getColorsForTheme, themeMap} from '../lib/themes'; +import {ScratchBlocks} from 'scratch-blocks'; +import {connect} from 'react-redux'; class CustomProcedures extends React.Component { - constructor(props) { + constructor (props) { super(props); bindAll(this, [ - "handleAddLabel", - "handleAddBoolean", - "handleAddTextNumber", - "handleToggleWarp", - "handleCancel", - "handleOk", - "setBlocks", + 'handleAddLabel', + 'handleAddBoolean', + 'handleAddTextNumber', + 'handleToggleWarp', + 'handleCancel', + 'handleOk', + 'setBlocks' ]); this.state = { rtlOffset: 0, - warp: false, + warp: false }; } - componentWillUnmount() { + componentWillUnmount () { if (this.workspace) { this.workspace.dispose(); } } - setBlocks(blocksRef) { + setBlocks (blocksRef) { if (!blocksRef) return; this.blocks = blocksRef; - const workspaceConfig = defaultsDeep( - {}, + const workspaceConfig = defaultsDeep({}, CustomProcedures.defaultOptions, this.props.options, - { rtl: this.props.isRtl } + {rtl: this.props.isRtl} ); const theme = new ScratchBlocks.Theme( @@ -47,7 +46,7 @@ class CustomProcedures extends React.Component { this.workspace = ScratchBlocks.inject(this.blocks, workspaceConfig); // Create the procedure declaration block for editing the mutation. - this.mutationRoot = this.workspace.newBlock("procedures_declaration"); + this.mutationRoot = this.workspace.newBlock('procedures_declaration'); // Make the declaration immovable, undeletable and have no context menu this.mutationRoot.setMovable(false); this.mutationRoot.setDeletable(false); @@ -57,9 +56,8 @@ class CustomProcedures extends React.Component { this.mutationRoot.onChangeFn(); // Keep the block centered on the workspace const metrics = this.workspace.getMetrics(); - const { x, y } = this.mutationRoot.getRelativeToSurfaceXY(); - const dy = - metrics.viewHeight / 2 - this.mutationRoot.height / 2 - y; + const {x, y} = this.mutationRoot.getRelativeToSurfaceXY(); + const dy = (metrics.viewHeight / 2) - (this.mutationRoot.height / 2) - y; let dx; if (this.props.isRtl) { // // TODO: https://github.com/LLK/scratch-gui/issues/2838 @@ -71,9 +69,8 @@ class CustomProcedures extends React.Component { // Calculate a new left postion based on new width // Convert current x position into LTR (mirror) x position (uses original offset) // Use the difference between ltrX and mirrorX as the amount to move - const ltrX = - metrics.viewWidth / 2 - this.mutationRoot.width / 2 + 25; - const mirrorX = x - (x - this.state.rtlOffset) * 2; + const ltrX = ((metrics.viewWidth / 2) - (this.mutationRoot.width / 2) + 25); + const mirrorX = x - ((x - this.state.rtlOffset) * 2); if (mirrorX === ltrX) { return; } @@ -84,25 +81,19 @@ class CustomProcedures extends React.Component { if (this.mutationRoot.width < midPoint) { dx = ltrX; } else if (this.mutationRoot.width < metrics.viewWidth) { - dx = - midPoint - - (metrics.viewWidth - this.mutationRoot.width) / 2; + dx = midPoint - ((metrics.viewWidth - this.mutationRoot.width) / 2); } else { - dx = - midPoint + - (this.mutationRoot.width - metrics.viewWidth); + dx = midPoint + (this.mutationRoot.width - metrics.viewWidth); } this.mutationRoot.moveBy(dx, dy); - this.setState({ - rtlOffset: this.mutationRoot.getRelativeToSurfaceXY().x, - }); + this.setState({rtlOffset: this.mutationRoot.getRelativeToSurfaceXY().x}); return; } if (this.mutationRoot.width > metrics.viewWidth) { dx = dx + this.mutationRoot.width - metrics.viewWidth; } } else { - dx = metrics.viewWidth / 2 - this.mutationRoot.width / 2 - x; + dx = (metrics.viewWidth / 2) - (this.mutationRoot.width / 2) - x; // If the procedure declaration is wider than the view width, // keep the right-hand side of the procedure in view. if (this.mutationRoot.width > metrics.viewWidth) { @@ -114,44 +105,42 @@ class CustomProcedures extends React.Component { this.mutationRoot.domToMutation(this.props.mutator); this.mutationRoot.initSvg(); this.mutationRoot.render(); - this.setState({ warp: this.mutationRoot.getWarp() }); + this.setState({warp: this.mutationRoot.getWarp()}); // Allow the initial events to run to position this block, then focus. setTimeout(() => { this.mutationRoot.focusLastEditor_(); }); } - handleCancel() { + handleCancel () { this.props.onRequestClose(); } - handleOk() { - const newMutation = this.mutationRoot - ? this.mutationRoot.mutationToDom(true) - : null; + handleOk () { + const newMutation = this.mutationRoot ? this.mutationRoot.mutationToDom(true) : null; this.props.onRequestClose(newMutation); } - handleAddLabel() { + handleAddLabel () { if (this.mutationRoot) { this.mutationRoot.addLabelExternal(); } } - handleAddBoolean() { + handleAddBoolean () { if (this.mutationRoot) { this.mutationRoot.addBooleanExternal(); } } - handleAddTextNumber() { + handleAddTextNumber () { if (this.mutationRoot) { this.mutationRoot.addStringNumberExternal(); } } - handleToggleWarp() { + handleToggleWarp () { if (this.mutationRoot) { const newWarp = !this.mutationRoot.getWarp(); this.mutationRoot.setWarp(newWarp); - this.setState({ warp: newWarp }); + this.setState({warp: newWarp}); } } - render() { + render () { return ( ({ +const mapStateToProps = state => ({ isRtl: state.locales.isRtl, - mutator: state.scratchGui.customProcedures.mutator, + mutator: state.scratchGui.customProcedures.mutator }); -export default connect(mapStateToProps)(CustomProcedures); +export default connect( + mapStateToProps +)(CustomProcedures); diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 298307cab3..47b7bcd771 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -5,47 +5,39 @@ * @return {ScratchBlocks} ScratchBlocks connected with the vm */ export default function (vm, useCatBlocks) { - const { ScratchBlocks } = useCatBlocks - ? require("cat-blocks") - : require("scratch-blocks"); + const {ScratchBlocks} = useCatBlocks ? require('cat-blocks') : require('scratch-blocks'); const jsonForMenuBlock = function (name, menuOptionsFn, category, start) { return { - message0: "%1", + message0: '%1', args0: [ { - type: "field_dropdown", + type: 'field_dropdown', name: name, options: function () { return start.concat(menuOptionsFn()); - }, - }, + } + } ], inputsInline: true, - output: "String", + output: 'String', outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND, - extensions: [`colours_${category}`], + extensions: [`colours_${category}`] }; }; - const jsonForHatBlockMenu = function ( - hatName, - name, - menuOptionsFn, - category, - start - ) { + const jsonForHatBlockMenu = function (hatName, name, menuOptionsFn, category, start) { return { message0: hatName, args0: [ { - type: "field_dropdown", + type: 'field_dropdown', name: name, options: function () { return start.concat(menuOptionsFn()); - }, - }, + } + } ], - extensions: [`colours_${category}`, "shape_hat"], + extensions: [`colours_${category}`, 'shape_hat'] }; }; @@ -54,105 +46,73 @@ export default function (vm, useCatBlocks) { message0: ScratchBlocks.Msg.SENSING_OF, args0: [ { - type: "field_dropdown", - name: "PROPERTY", + type: 'field_dropdown', + name: 'PROPERTY', options: function () { return menuOptionsFn(); - }, + } }, { - type: "input_value", - name: "OBJECT", - }, + type: 'input_value', + name: 'OBJECT' + } ], output: true, outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND, - extensions: ["colours_sensing"], + extensions: ['colours_sensing'] }; }; const soundsMenu = function () { - let menu = [["", ""]]; + let menu = [['', '']]; if (vm.editingTarget && vm.editingTarget.sprite.sounds.length > 0) { - menu = vm.editingTarget.sprite.sounds.map((sound) => [ - sound.name, - sound.name, - ]); + menu = vm.editingTarget.sprite.sounds.map(sound => [sound.name, sound.name]); } menu.push([ - ScratchBlocks.ScratchMsgs.translate("SOUND_RECORD", "record..."), - "SOUND_RECORD", + ScratchBlocks.ScratchMsgs.translate('SOUND_RECORD', 'record...'), + 'SOUND_RECORD' ]); return menu; }; const costumesMenu = function () { if (vm.editingTarget && vm.editingTarget.getCostumes().length > 0) { - return vm.editingTarget - .getCostumes() - .map((costume) => [costume.name, costume.name]); + return vm.editingTarget.getCostumes().map(costume => [costume.name, costume.name]); } - return [["", ""]]; + return [['', '']]; }; const backdropsMenu = function () { - const next = ScratchBlocks.ScratchMsgs.translate( - "LOOKS_NEXTBACKDROP", - "next backdrop" - ); - const previous = ScratchBlocks.ScratchMsgs.translate( - "LOOKS_PREVIOUSBACKDROP", - "previous backdrop" - ); - const random = ScratchBlocks.ScratchMsgs.translate( - "LOOKS_RANDOMBACKDROP", - "random backdrop" - ); - if ( - vm.runtime.targets[0] && - vm.runtime.targets[0].getCostumes().length > 0 - ) { - return vm.runtime.targets[0] - .getCostumes() - .map((costume) => [costume.name, costume.name]) - .concat([ - [next, "next backdrop"], - [previous, "previous backdrop"], - [random, "random backdrop"], - ]); + const next = ScratchBlocks.ScratchMsgs.translate('LOOKS_NEXTBACKDROP', 'next backdrop'); + const previous = ScratchBlocks.ScratchMsgs.translate('LOOKS_PREVIOUSBACKDROP', 'previous backdrop'); + const random = ScratchBlocks.ScratchMsgs.translate('LOOKS_RANDOMBACKDROP', 'random backdrop'); + if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) { + return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name]) + .concat([[next, 'next backdrop'], + [previous, 'previous backdrop'], + [random, 'random backdrop']]); } - return [["", ""]]; + return [['', '']]; }; const backdropNamesMenu = function () { const stage = vm.runtime.getTargetForStage(); if (stage && stage.getCostumes().length > 0) { - return stage - .getCostumes() - .map((costume) => [costume.name, costume.name]); + return stage.getCostumes().map(costume => [costume.name, costume.name]); } - return [["", ""]]; + return [['', '']]; }; const spriteMenu = function () { const sprites = []; for (const targetId in vm.runtime.targets) { - if ( - !Object.prototype.hasOwnProperty.call( - vm.runtime.targets, - targetId - ) - ) - continue; + if (!Object.prototype.hasOwnProperty.call(vm.runtime.targets, targetId)) continue; if (vm.runtime.targets[targetId].isOriginal) { if (!vm.runtime.targets[targetId].isStage) { if (vm.runtime.targets[targetId] === vm.editingTarget) { continue; } - sprites.push([ - vm.runtime.targets[targetId].sprite.name, - vm.runtime.targets[targetId].sprite.name, - ]); + sprites.push([vm.runtime.targets[targetId].sprite.name, vm.runtime.targets[targetId].sprite.name]); } } } @@ -163,22 +123,19 @@ export default function (vm, useCatBlocks) { if (vm.editingTarget && vm.editingTarget.isStage) { const menu = spriteMenu(); if (menu.length === 0) { - return [["", ""]]; // Empty menu matches Scratch 2 behavior + return [['', '']]; // Empty menu matches Scratch 2 behavior } return menu; } - const myself = ScratchBlocks.ScratchMsgs.translate( - "CONTROL_CREATECLONEOF_MYSELF", - "myself" - ); - return [[myself, "_myself_"]].concat(spriteMenu()); + const myself = ScratchBlocks.ScratchMsgs.translate('CONTROL_CREATECLONEOF_MYSELF', 'myself'); + return [[myself, '_myself_']].concat(spriteMenu()); }; ScratchBlocks.Blocks.sound_sounds_menu.init = function () { - const json = jsonForMenuBlock("SOUND_MENU", soundsMenu, "sounds", []); + const json = jsonForMenuBlock('SOUND_MENU', soundsMenu, 'sounds', []); this.jsonInit(json); - this.getField("SOUND_MENU").setValidator((newValue) => { - if (newValue === "SOUND_RECORD") { + this.getField('SOUND_MENU').setValidator(newValue => { + if (newValue === 'SOUND_RECORD') { ScratchBlocks.recordSoundCallback(); return null; } @@ -187,76 +144,54 @@ export default function (vm, useCatBlocks) { }; ScratchBlocks.Blocks.looks_costume.init = function () { - const json = jsonForMenuBlock("COSTUME", costumesMenu, "looks", []); + const json = jsonForMenuBlock('COSTUME', costumesMenu, 'looks', []); this.jsonInit(json); }; ScratchBlocks.Blocks.looks_backdrops.init = function () { - const json = jsonForMenuBlock("BACKDROP", backdropsMenu, "looks", []); + const json = jsonForMenuBlock('BACKDROP', backdropsMenu, 'looks', []); this.jsonInit(json); }; ScratchBlocks.Blocks.event_whenbackdropswitchesto.init = function () { const json = jsonForHatBlockMenu( ScratchBlocks.Msg.EVENT_WHENBACKDROPSWITCHESTO, - "BACKDROP", - backdropNamesMenu, - "event", - [] - ); + 'BACKDROP', backdropNamesMenu, 'event', []); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_pointtowards_menu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate( - "MOTION_POINTTOWARDS_POINTER", - "mouse-pointer" - ); - const json = jsonForMenuBlock("TOWARDS", spriteMenu, "motion", [ - [mouse, "_mouse_"], + const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_POINTTOWARDS_POINTER', 'mouse-pointer'); + const json = jsonForMenuBlock('TOWARDS', spriteMenu, 'motion', [ + [mouse, '_mouse_'] ]); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_goto_menu.init = function () { - const random = ScratchBlocks.ScratchMsgs.translate( - "MOTION_GOTO_RANDOM", - "random position" - ); - const mouse = ScratchBlocks.ScratchMsgs.translate( - "MOTION_GOTO_POINTER", - "mouse-pointer" - ); - const json = jsonForMenuBlock("TO", spriteMenu, "motion", [ - [random, "_random_"], - [mouse, "_mouse_"], + const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_RANDOM', 'random position'); + const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_POINTER', 'mouse-pointer'); + const json = jsonForMenuBlock('TO', spriteMenu, 'motion', [ + [random, '_random_'], + [mouse, '_mouse_'] ]); this.jsonInit(json); }; ScratchBlocks.Blocks.motion_glideto_menu.init = function () { - const random = ScratchBlocks.ScratchMsgs.translate( - "MOTION_GLIDETO_RANDOM", - "random position" - ); - const mouse = ScratchBlocks.ScratchMsgs.translate( - "MOTION_GLIDETO_POINTER", - "mouse-pointer" - ); - const json = jsonForMenuBlock("TO", spriteMenu, "motion", [ - [random, "_random_"], - [mouse, "_mouse_"], + const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_RANDOM', 'random position'); + const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_POINTER', 'mouse-pointer'); + const json = jsonForMenuBlock('TO', spriteMenu, 'motion', [ + [random, '_random_'], + [mouse, '_mouse_'] ]); this.jsonInit(json); }; ScratchBlocks.Blocks.sensing_of_object_menu.init = function () { - const stage = ScratchBlocks.ScratchMsgs.translate( - "SENSING_OF_STAGE", - "Stage" - ); - const json = jsonForMenuBlock("OBJECT", spriteMenu, "sensing", [ - [stage, "_stage_"], + const stage = ScratchBlocks.ScratchMsgs.translate('SENSING_OF_STAGE', 'Stage'); + const json = jsonForMenuBlock('OBJECT', spriteMenu, 'sensing', [ + [stage, '_stage_'] ]); this.jsonInit(json); }; @@ -268,7 +203,7 @@ export default function (vm, useCatBlocks) { // Get the sensing_of block from vm. let defaultSensingOfBlock; const blocks = vm.runtime.flyoutBlocks._blocks; - Object.keys(blocks).forEach((id) => { + Object.keys(blocks).forEach(id => { const block = blocks[id]; if (id === blockType || (block && block.opcode === blockType)) { defaultSensingOfBlock = block; @@ -279,18 +214,18 @@ export default function (vm, useCatBlocks) { // Called every time it opens since it depends on the values in the other block input. const menuFn = function () { const stageOptions = [ - [ScratchBlocks.Msg.SENSING_OF_BACKDROPNUMBER, "backdrop #"], - [ScratchBlocks.Msg.SENSING_OF_BACKDROPNAME, "backdrop name"], - [ScratchBlocks.Msg.SENSING_OF_VOLUME, "volume"], + [ScratchBlocks.Msg.SENSING_OF_BACKDROPNUMBER, 'backdrop #'], + [ScratchBlocks.Msg.SENSING_OF_BACKDROPNAME, 'backdrop name'], + [ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume'] ]; const spriteOptions = [ - [ScratchBlocks.Msg.SENSING_OF_XPOSITION, "x position"], - [ScratchBlocks.Msg.SENSING_OF_YPOSITION, "y position"], - [ScratchBlocks.Msg.SENSING_OF_DIRECTION, "direction"], - [ScratchBlocks.Msg.SENSING_OF_COSTUMENUMBER, "costume #"], - [ScratchBlocks.Msg.SENSING_OF_COSTUMENAME, "costume name"], - [ScratchBlocks.Msg.SENSING_OF_SIZE, "size"], - [ScratchBlocks.Msg.SENSING_OF_VOLUME, "volume"], + [ScratchBlocks.Msg.SENSING_OF_XPOSITION, 'x position'], + [ScratchBlocks.Msg.SENSING_OF_YPOSITION, 'y position'], + [ScratchBlocks.Msg.SENSING_OF_DIRECTION, 'direction'], + [ScratchBlocks.Msg.SENSING_OF_COSTUMENUMBER, 'costume #'], + [ScratchBlocks.Msg.SENSING_OF_COSTUMENAME, 'costume name'], + [ScratchBlocks.Msg.SENSING_OF_SIZE, 'size'], + [ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume'] ]; if (vm.editingTarget) { let lookupBlocks = vm.editingTarget.blocks; @@ -298,44 +233,31 @@ export default function (vm, useCatBlocks) { // The block doesn't exist, but should be in the flyout. Look there. if (!sensingOfBlock) { - sensingOfBlock = - vm.runtime.flyoutBlocks.getBlock(blockId) || - defaultSensingOfBlock; + sensingOfBlock = vm.runtime.flyoutBlocks.getBlock(blockId) || defaultSensingOfBlock; // If we still don't have a block, just return an empty list . This happens during // scratch blocks construction. if (!sensingOfBlock) { - return [["", ""]]; + return [['', '']]; } // The block was in the flyout so look up future block info there. lookupBlocks = vm.runtime.flyoutBlocks; } const sort = function (options) { - options.sort( - ScratchBlocks.scratchBlocksUtils.compareStrings - ); + options.sort(ScratchBlocks.scratchBlocksUtils.compareStrings); }; // Get all the stage variables (no lists) so we can add them to menu when the stage is selected. - const stageVariableOptions = vm.runtime - .getTargetForStage() - .getAllVariableNamesInScopeByType(""); + const stageVariableOptions = vm.runtime.getTargetForStage().getAllVariableNamesInScopeByType(''); sort(stageVariableOptions); - const stageVariableMenuItems = stageVariableOptions.map( - (variable) => [variable, variable] - ); - if ( - sensingOfBlock.inputs.OBJECT.shadow !== - sensingOfBlock.inputs.OBJECT.block - ) { + const stageVariableMenuItems = stageVariableOptions.map(variable => [variable, variable]); + if (sensingOfBlock.inputs.OBJECT.shadow !== sensingOfBlock.inputs.OBJECT.block) { // There's a block dropped on top of the menu. It'd be nice to evaluate it and // return the correct list, but that is tricky. Scratch2 just returns stage options // so just do that here too. return stageOptions.concat(stageVariableMenuItems); } - const menuBlock = lookupBlocks.getBlock( - sensingOfBlock.inputs.OBJECT.shadow - ); + const menuBlock = lookupBlocks.getBlock(sensingOfBlock.inputs.OBJECT.shadow); const selectedItem = menuBlock.fields.OBJECT.value; - if (selectedItem === "_stage_") { + if (selectedItem === '_stage_') { return stageOptions.concat(stageVariableMenuItems); } // Get all the local variables (no lists) and add them to the menu. @@ -343,16 +265,13 @@ export default function (vm, useCatBlocks) { let spriteVariableOptions = []; // The target should exist, but there are ways for it not to (e.g. #4203). if (target) { - spriteVariableOptions = - target.getAllVariableNamesInScopeByType("", true); + spriteVariableOptions = target.getAllVariableNamesInScopeByType('', true); sort(spriteVariableOptions); } - const spriteVariableMenuItems = spriteVariableOptions.map( - (variable) => [variable, variable] - ); + const spriteVariableMenuItems = spriteVariableOptions.map(variable => [variable, variable]); return spriteOptions.concat(spriteVariableMenuItems); } - return [["", ""]]; + return [['', '']]; }; const json = jsonForSensingMenus(menuFn); @@ -360,39 +279,25 @@ export default function (vm, useCatBlocks) { }; ScratchBlocks.Blocks.sensing_distancetomenu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate( - "SENSING_DISTANCETO_POINTER", - "mouse-pointer" - ); - const json = jsonForMenuBlock("DISTANCETOMENU", spriteMenu, "sensing", [ - [mouse, "_mouse_"], + const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_DISTANCETO_POINTER', 'mouse-pointer'); + const json = jsonForMenuBlock('DISTANCETOMENU', spriteMenu, 'sensing', [ + [mouse, '_mouse_'] ]); this.jsonInit(json); }; ScratchBlocks.Blocks.sensing_touchingobjectmenu.init = function () { - const mouse = ScratchBlocks.ScratchMsgs.translate( - "SENSING_TOUCHINGOBJECT_POINTER", - "mouse-pointer" - ); - const edge = ScratchBlocks.ScratchMsgs.translate( - "SENSING_TOUCHINGOBJECT_EDGE", - "edge" - ); - const json = jsonForMenuBlock( - "TOUCHINGOBJECTMENU", - spriteMenu, - "sensing", - [ - [mouse, "_mouse_"], - [edge, "_edge_"], - ] - ); + const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_POINTER', 'mouse-pointer'); + const edge = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_EDGE', 'edge'); + const json = jsonForMenuBlock('TOUCHINGOBJECTMENU', spriteMenu, 'sensing', [ + [mouse, '_mouse_'], + [edge, '_edge_'] + ]); this.jsonInit(json); }; ScratchBlocks.Blocks.control_create_clone_of_menu.init = function () { - const json = jsonForMenuBlock("CLONE_OPTION", cloneMenu, "control", []); + const json = jsonForMenuBlock('CLONE_OPTION', cloneMenu, 'control', []); this.jsonInit(json); }; @@ -401,9 +306,7 @@ export default function (vm, useCatBlocks) { return monitoredBlock ? monitoredBlock.isMonitored : false; }; - ScratchBlocks.StatusIndicatorLabel.prototype.getExtensionState = function ( - extensionId - ) { + ScratchBlocks.StatusIndicatorLabel.prototype.getExtensionState = function (extensionId) { if (vm.getPeripheralIsConnected(extensionId)) { return ScratchBlocks.StatusButtonState.READY; } @@ -418,8 +321,8 @@ export default function (vm, useCatBlocks) { // creates a collator. Using this is a lot faster in browsers that create a // collator for every localeCompare call. const collator = new Intl.Collator([], { - sensitivity: "base", - numeric: true, + sensitivity: 'base', + numeric: true }); // ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) { // return collator.compare(str1, str2); diff --git a/packages/scratch-gui/src/lib/define-dynamic-block.js b/packages/scratch-gui/src/lib/define-dynamic-block.js index 30727f48e7..943a650ab0 100644 --- a/packages/scratch-gui/src/lib/define-dynamic-block.js +++ b/packages/scratch-gui/src/lib/define-dynamic-block.js @@ -1,6 +1,6 @@ // TODO: access `BlockType` and `ArgumentType` without reaching into VM // Should we move these into a new extension support module or something? -import { ArgumentType, BlockType } from "@scratch/scratch-vm"; +import {ArgumentType, BlockType} from '@scratch/scratch-vm'; /** * Define a block using extension info which has the ability to dynamically determine (and update) its layout. @@ -13,72 +13,67 @@ import { ArgumentType, BlockType } from "@scratch/scratch-vm"; * @param {string} extendedOpcode - The opcode for the block (including the extension ID). */ // TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime -const defineDynamicBlock = ( - ScratchBlocks, - categoryInfo, - staticBlockInfo, - extendedOpcode -) => ({ +const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({ init: function () { const blockJson = { type: extendedOpcode, inputsInline: true, category: categoryInfo.name, - style: categoryInfo.id, + style: categoryInfo.id }; // There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of // blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension // blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly // extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon. if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) { - blockJson.extensions = ["scratch_extension"]; + blockJson.extensions = ['scratch_extension']; } // initialize the basics of the block, to be overridden & extended later by `domToMutation` this.jsonInit(blockJson); // initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom` - this.blockInfoText = "{}"; + this.blockInfoText = '{}'; // we need a block info update (through `domToMutation`) before we have a completely initialized block this.needsBlockInfoUpdate = true; }, mutationToDom: function () { - const container = document.createElement("mutation"); - container.setAttribute("blockInfo", this.blockInfoText); + const container = document.createElement('mutation'); + container.setAttribute('blockInfo', this.blockInfoText); return container; }, domToMutation: function (xmlElement) { - const blockInfoText = xmlElement.getAttribute("blockInfo"); + const blockInfoText = xmlElement.getAttribute('blockInfo'); if (!blockInfoText) return; if (!this.needsBlockInfoUpdate) { - throw new Error("Attempted to update block info twice"); + throw new Error('Attempted to update block info twice'); } delete this.needsBlockInfoUpdate; this.blockInfoText = blockInfoText; const blockInfo = JSON.parse(blockInfoText); switch (blockInfo.blockType) { - case BlockType.COMMAND: - case BlockType.CONDITIONAL: - case BlockType.LOOP: - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); - this.setPreviousStatement(true); - this.setNextStatement(!blockInfo.isTerminal); - break; - case BlockType.REPORTER: - this.setOutput(true); - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND); - if (!blockInfo.disableMonitor) { - this.setCheckboxInFlyout(true); - } - break; - case BlockType.BOOLEAN: - this.setOutput(true); - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL); - break; - case BlockType.HAT: - case BlockType.EVENT: - this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); - this.setNextStatement(true); - break; + case BlockType.COMMAND: + case BlockType.CONDITIONAL: + case BlockType.LOOP: + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); + this.setPreviousStatement(true); + this.setNextStatement(!blockInfo.isTerminal); + break; + case BlockType.REPORTER: + this.setOutput(true); + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND); + if (!blockInfo.disableMonitor) { + this.setCheckboxInFlyout(true); + } + break; + case BlockType.BOOLEAN: + this.setOutput(true); + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL); + break; + case BlockType.HAT: + case BlockType.EVENT: + this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE); + this.setNextStatement(true); + break; } // Layout block arguments @@ -86,27 +81,20 @@ const defineDynamicBlock = ( const blockText = blockInfo.text; const args = []; let argCount = 0; - const scratchBlocksStyleText = blockText.replace( - /\[(.+?)]/g, - (match, argName) => { - const arg = blockInfo.arguments[argName]; - switch (arg.type) { - case ArgumentType.STRING: - args.push({ type: "input_value", name: argName }); - break; - case ArgumentType.BOOLEAN: - args.push({ - type: "input_value", - name: argName, - check: "Boolean", - }); - break; - } - return `%${++argCount}`; + const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => { + const arg = blockInfo.arguments[argName]; + switch (arg.type) { + case ArgumentType.STRING: + args.push({type: 'input_value', name: argName}); + break; + case ArgumentType.BOOLEAN: + args.push({type: 'input_value', name: argName, check: 'Boolean'}); + break; } - ); + return `%${++argCount}`; + }); this.interpolate_(scratchBlocksStyleText, args); - }, + } }); export default defineDynamicBlock; diff --git a/packages/scratch-gui/src/lib/make-toolbox-xml.js b/packages/scratch-gui/src/lib/make-toolbox-xml.js index 7d6acc24ab..ba3f7050c8 100644 --- a/packages/scratch-gui/src/lib/make-toolbox-xml.js +++ b/packages/scratch-gui/src/lib/make-toolbox-xml.js @@ -1,5 +1,5 @@ -import { ScratchBlocks } from "scratch-blocks"; -import { defaultColors } from "./themes"; +import {ScratchBlocks} from 'scratch-blocks'; +import {defaultColors} from './themes'; const categorySeparator = ''; @@ -8,25 +8,21 @@ const blockSeparator = ''; // At default scale, about 28px /* eslint-disable no-unused-vars */ const motion = function (isInitialSetup, isStage, targetId, colors) { const stageSelected = ScratchBlocks.ScratchMsgs.translate( - "MOTION_STAGE_SELECTED", - "Stage selected: no motion blocks" + 'MOTION_STAGE_SELECTED', + 'Stage selected: no motion blocks' ); - // Note: the category's secondaryColour matches up with the blocks' tertiary - // color, both used for border color. Since Blockly uses the UK spelling of - // "colour", certain attributes are named accordingly. + // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. + // Since Blockly uses the UK spelling of "colour", certain attributes are named accordingly. return ` - ${ - isStage - ? ` + colors.colourPrimary +}" secondaryColour="${colors.colourTertiary}"> + ${isStage ? ` - ` - : ` + ` : ` @@ -145,52 +141,36 @@ const motion = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - ` - } + `} ${categorySeparator} `; }; const xmlEscape = function (unsafe) { - return unsafe.replace(/[<>&'"]/g, (c) => { + return unsafe.replace(/[<>&'"]/g, c => { switch (c) { - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - case "'": - return "'"; - case '"': - return """; + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + case '\'': return '''; + case '"': return '"'; } }); }; -const looks = function ( - isInitialSetup, - isStage, - targetId, - costumeName, - backdropName, - colors -) { - const hello = ScratchBlocks.ScratchMsgs.translate("LOOKS_HELLO", "Hello!"); - const hmm = ScratchBlocks.ScratchMsgs.translate("LOOKS_HMM", "Hmm..."); +const looks = function (isInitialSetup, isStage, targetId, costumeName, backdropName, colors) { + const hello = ScratchBlocks.ScratchMsgs.translate('LOOKS_HELLO', 'Hello!'); + const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...'); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - ${ - isStage - ? "" - : ` + colors.colourPrimary +}" secondaryColour="${colors.colourTertiary}"> + ${isStage ? '' : ` @@ -230,11 +210,8 @@ const looks = function ( ${blockSeparator} - ` - } - ${ - isStage - ? ` + `} + ${isStage ? ` @@ -250,8 +227,7 @@ const looks = function ( - ` - : ` + ` : ` @@ -283,8 +259,7 @@ const looks = function ( - ` - } + `} ${blockSeparator} @@ -302,10 +277,7 @@ const looks = function ( ${blockSeparator} - ${ - isStage - ? "" - : ` + ${isStage ? '' : ` ${blockSeparator} @@ -317,19 +289,14 @@ const looks = function ( - ` - } - ${ - isStage - ? ` + `} + ${isStage ? ` - ` - : ` + ` : ` - ` - } + `} ${categorySeparator} `; @@ -339,11 +306,11 @@ const sound = function (isInitialSetup, isStage, targetId, soundName, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` + colors.colourPrimary +}" secondaryColour="${colors.colourTertiary}"> @@ -400,23 +367,19 @@ const events = function (isInitialSetup, isStage, targetId, colors) { // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` + colors.colourPrimary +}" secondaryColour="${colors.colourTertiary}"> - ${ - isStage - ? ` + ${isStage ? ` - ` - : ` + ` : ` - ` - } + `} ${blockSeparator} @@ -450,9 +413,9 @@ const control = function (isInitialSetup, isStage, targetId, colors) { return ` @@ -480,16 +443,13 @@ const control = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} ${blockSeparator} - ${ - isStage - ? ` + ${isStage ? ` - ` - : ` + ` : ` @@ -497,32 +457,25 @@ const control = function (isInitialSetup, isStage, targetId, colors) { - ` - } + `} ${categorySeparator} `; }; const sensing = function (isInitialSetup, isStage, targetId, colors) { - const name = ScratchBlocks.ScratchMsgs.translate( - "SENSING_ASK_TEXT", - "What's your name?" - ); + const name = ScratchBlocks.ScratchMsgs.translate('SENSING_ASK_TEXT', 'What\'s your name?'); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` - ${ - isStage - ? "" - : ` + ${isStage ? '' : ` @@ -547,12 +500,8 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - ` - } - ${ - isInitialSetup - ? "" - : ` + `} + ${isInitialSetup ? '' : ` @@ -560,8 +509,7 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { - ` - } + `} ${blockSeparator} @@ -572,15 +520,11 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { - ${ - isStage - ? "" - : ` + ${isStage ? '' : ` ${blockSeparator} ''+ ${blockSeparator} - ` - } + `} ${blockSeparator} ${blockSeparator} @@ -603,25 +547,16 @@ const sensing = function (isInitialSetup, isStage, targetId, colors) { }; const operators = function (isInitialSetup, isStage, targetId, colors) { - const apple = ScratchBlocks.ScratchMsgs.translate( - "OPERATORS_JOIN_APPLE", - "apple" - ); - const banana = ScratchBlocks.ScratchMsgs.translate( - "OPERATORS_JOIN_BANANA", - "banana" - ); - const letter = ScratchBlocks.ScratchMsgs.translate( - "OPERATORS_LETTEROF_APPLE", - "a" - ); + const apple = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_APPLE', 'apple'); + const banana = ScratchBlocks.ScratchMsgs.translate('OPERATORS_JOIN_BANANA', 'banana'); + const letter = ScratchBlocks.ScratchMsgs.translate('OPERATORS_LETTEROF_APPLE', 'a'); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. return ` @@ -728,10 +663,7 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { ${blockSeparator} - ${ - isInitialSetup - ? "" - : ` + ${isInitialSetup ? '' : ` @@ -775,8 +707,7 @@ const operators = function (isInitialSetup, isStage, targetId, colors) { - ` - } + `} ${blockSeparator} @@ -815,9 +746,9 @@ const variables = function (isInitialSetup, isStage, targetId, colors) { return ` { - const index = categoriesXML.findIndex( - (categoryInfo) => categoryInfo.id === categoryId - ); + const moveCategory = categoryId => { + const index = categoriesXML.findIndex(categoryInfo => categoryInfo.id === categoryId); if (index >= 0) { // remove the category from categoriesXML and return its XML const [categoryInfo] = categoriesXML.splice(index, 1); @@ -891,60 +812,28 @@ const makeToolboxXML = function ( } // return `undefined` }; - const motionXML = - moveCategory("motion") || - motion(isInitialSetup, isStage, targetId, colors.motion); - const looksXML = - moveCategory("looks") || - looks( - isInitialSetup, - isStage, - targetId, - costumeName, - backdropName, - colors.looks - ); - const soundXML = - moveCategory("sound") || - sound(isInitialSetup, isStage, targetId, soundName, colors.sounds); - const eventsXML = - moveCategory("event") || - events(isInitialSetup, isStage, targetId, colors.event); - const controlXML = - moveCategory("control") || - control(isInitialSetup, isStage, targetId, colors.control); - const sensingXML = - moveCategory("sensing") || - sensing(isInitialSetup, isStage, targetId, colors.sensing); - const operatorsXML = - moveCategory("operators") || - operators(isInitialSetup, isStage, targetId, colors.operators); - const variablesXML = - moveCategory("data") || - variables(isInitialSetup, isStage, targetId, colors.data); - const myBlocksXML = - moveCategory("procedures") || - myBlocks(isInitialSetup, isStage, targetId, colors.more); + const motionXML = moveCategory('motion') || motion(isInitialSetup, isStage, targetId, colors.motion); + const looksXML = moveCategory('looks') || + looks(isInitialSetup, isStage, targetId, costumeName, backdropName, colors.looks); + const soundXML = moveCategory('sound') || sound(isInitialSetup, isStage, targetId, soundName, colors.sounds); + const eventsXML = moveCategory('event') || events(isInitialSetup, isStage, targetId, colors.event); + const controlXML = moveCategory('control') || control(isInitialSetup, isStage, targetId, colors.control); + const sensingXML = moveCategory('sensing') || sensing(isInitialSetup, isStage, targetId, colors.sensing); + const operatorsXML = moveCategory('operators') || operators(isInitialSetup, isStage, targetId, colors.operators); + const variablesXML = moveCategory('data') || variables(isInitialSetup, isStage, targetId, colors.data); + const myBlocksXML = moveCategory('procedures') || myBlocks(isInitialSetup, isStage, targetId, colors.more); const everything = [ xmlOpen, - motionXML, - gap, - looksXML, - gap, - soundXML, - gap, - eventsXML, - gap, - controlXML, - gap, - sensingXML, - gap, - operatorsXML, - gap, - variablesXML, - gap, - myBlocksXML, + motionXML, gap, + looksXML, gap, + soundXML, gap, + eventsXML, gap, + controlXML, gap, + sensingXML, gap, + operatorsXML, gap, + variablesXML, gap, + myBlocksXML ]; for (const extensionCategory of categoriesXML) { @@ -952,7 +841,7 @@ const makeToolboxXML = function ( } everything.push(xmlClose); - return everything.join("\n"); + return everything.join('\n'); }; export default makeToolboxXML; diff --git a/packages/scratch-gui/src/lib/themes/blockHelpers.js b/packages/scratch-gui/src/lib/themes/blockHelpers.js index 696c0b5785..1a0db3e5ad 100644 --- a/packages/scratch-gui/src/lib/themes/blockHelpers.js +++ b/packages/scratch-gui/src/lib/themes/blockHelpers.js @@ -1,19 +1,19 @@ -import { DEFAULT_THEME, getColorsForTheme, themeMap } from "."; +import {DEFAULT_THEME, getColorsForTheme, themeMap} from '.'; -const getBlockIconURI = (extensionIcons) => { +const getBlockIconURI = extensionIcons => { if (!extensionIcons) return null; return extensionIcons.blockIconURI || extensionIcons.menuIconURI; }; -const getCategoryIconURI = (extensionIcons) => { +const getCategoryIconURI = extensionIcons => { if (!extensionIcons) return null; return extensionIcons.menuIconURI || extensionIcons.blockIconURI; }; // scratch-blocks colours has a pen property that scratch-gui uses for all extensions -const getExtensionColors = (theme) => getColorsForTheme(theme).pen; +const getExtensionColors = theme => getColorsForTheme(theme).pen; /** * Applies extension color theme to categories. @@ -33,31 +33,23 @@ const injectExtensionCategoryTheme = (dynamicBlockXML, theme) => { const parser = new DOMParser(); const serializer = new XMLSerializer(); - return dynamicBlockXML.map((extension) => { - const dom = parser.parseFromString(extension.xml, "text/xml"); + return dynamicBlockXML.map(extension => { + const dom = parser.parseFromString(extension.xml, 'text/xml'); // This element is deserialized by Blockly, which uses the UK spelling // of "colour". - dom.documentElement.setAttribute( - "colour", - extensionColors.colourPrimary - ); + dom.documentElement.setAttribute('colour', extensionColors.colourPrimary); // Note: the category's secondaryColour matches up with the blocks' tertiary color, both used for border color. - dom.documentElement.setAttribute( - "secondaryColour", - extensionColors.colourTertiary - ); - - const categoryIconURI = getCategoryIconURI( - extensionIcons[extension.id] - ); + dom.documentElement.setAttribute('secondaryColour', extensionColors.colourTertiary); + + const categoryIconURI = getCategoryIconURI(extensionIcons[extension.id]); if (categoryIconURI) { - dom.documentElement.setAttribute("iconURI", categoryIconURI); + dom.documentElement.setAttribute('iconURI', categoryIconURI); } return { ...extension, - xml: serializer.serializeToString(dom), + xml: serializer.serializeToString(dom) }; }); }; @@ -67,18 +59,11 @@ const injectExtensionBlockIcons = (blockInfoJson, theme) => { if (theme === DEFAULT_THEME) return blockInfoJson; // Block icons are the first element of `args0` - if ( - !blockInfoJson.args0 || - blockInfoJson.args0.length < 1 || - blockInfoJson.args0[0].type !== "field_image" - ) - return blockInfoJson; + if (!blockInfoJson.args0 || blockInfoJson.args0.length < 1 || + blockInfoJson.args0[0].type !== 'field_image') return blockInfoJson; const extensionIcons = themeMap[theme].extensions; - const extensionId = blockInfoJson.type.substring( - 0, - blockInfoJson.type.indexOf("_") - ); + const extensionId = blockInfoJson.type.substring(0, blockInfoJson.type.indexOf('_')); const blockIconURI = getBlockIconURI(extensionIcons[extensionId]); if (!blockIconURI) return blockInfoJson; @@ -90,14 +75,14 @@ const injectExtensionBlockIcons = (blockInfoJson, theme) => { return { ...value, - src: blockIconURI, + src: blockIconURI }; - }), + }) }; }; export { injectExtensionBlockIcons, injectExtensionCategoryTheme, - getExtensionColors, + getExtensionColors }; diff --git a/packages/scratch-gui/src/lib/themes/default/index.js b/packages/scratch-gui/src/lib/themes/default/index.js index 7047a1c7fd..bc033fbf8e 100644 --- a/packages/scratch-gui/src/lib/themes/default/index.js +++ b/packages/scratch-gui/src/lib/themes/default/index.js @@ -2,104 +2,106 @@ // be named exactly as they are, including the UK spelling of "colour". const blockColors = { motion: { - colourPrimary: "#4C97FF", - colourSecondary: "#4280D7", - colourTertiary: "#3373CC", - colourQuaternary: "#3373CC", + colourPrimary: '#4C97FF', + colourSecondary: '#4280D7', + colourTertiary: '#3373CC', + colourQuaternary: '#3373CC' }, looks: { - colourPrimary: "#9966FF", - colourSecondary: "#855CD6", - colourTertiary: "#774DCB", - colourQuaternary: "#774DCB", + colourPrimary: '#9966FF', + colourSecondary: '#855CD6', + colourTertiary: '#774DCB', + colourQuaternary: '#774DCB' }, sounds: { - colourPrimary: "#CF63CF", - colourSecondary: "#C94FC9", - colourTertiary: "#BD42BD", - colourQuaternary: "#BD42BD", + colourPrimary: '#CF63CF', + colourSecondary: '#C94FC9', + colourTertiary: '#BD42BD', + colourQuaternary: '#BD42BD' }, control: { - colourPrimary: "#FFAB19", - colourSecondary: "#EC9C13", - colourTertiary: "#CF8B17", - colourQuaternary: "#CF8B17", + colourPrimary: '#FFAB19', + colourSecondary: '#EC9C13', + colourTertiary: '#CF8B17', + colourQuaternary: '#CF8B17' }, event: { - colourPrimary: "#FFBF00", - colourSecondary: "#E6AC00", - colourTertiary: "#CC9900", - colourQuaternary: "#CC9900", + colourPrimary: '#FFBF00', + colourSecondary: '#E6AC00', + colourTertiary: '#CC9900', + colourQuaternary: '#CC9900' }, sensing: { - colourPrimary: "#5CB1D6", - colourSecondary: "#47A8D1", - colourTertiary: "#2E8EB8", - colourQuaternary: "#2E8EB8", + colourPrimary: '#5CB1D6', + colourSecondary: '#47A8D1', + colourTertiary: '#2E8EB8', + colourQuaternary: '#2E8EB8' }, pen: { - colourPrimary: "#0fBD8C", - colourSecondary: "#0DA57A", - colourTertiary: "#0B8E69", - colourQuaternary: "#0B8E69", + colourPrimary: '#0fBD8C', + colourSecondary: '#0DA57A', + colourTertiary: '#0B8E69', + colourQuaternary: '#0B8E69' }, operators: { - colourPrimary: "#59C059", - colourSecondary: "#46B946", - colourTertiary: "#389438", - colourQuaternary: "#389438", + colourPrimary: '#59C059', + colourSecondary: '#46B946', + colourTertiary: '#389438', + colourQuaternary: '#389438' }, data: { - colourPrimary: "#FF8C1A", - colourSecondary: "#FF8000", - colourTertiary: "#DB6E00", - colourQuaternary: "#DB6E00", + colourPrimary: '#FF8C1A', + colourSecondary: '#FF8000', + colourTertiary: '#DB6E00', + colourQuaternary: '#DB6E00' }, // This is not a new category, but rather for differentiation // between lists and scalar variables. data_lists: { - colourPrimary: "#FF661A", - colourSecondary: "#FF5500", - colourTertiary: "#E64D00", - colourQuaternary: "#E64D00", + colourPrimary: '#FF661A', + colourSecondary: '#FF5500', + colourTertiary: '#E64D00', + colourQuaternary: '#E64D00' }, more: { - colourPrimary: "#FF6680", - colourSecondary: "#FF4D6A", - colourTertiary: "#FF3355", - colourQuaternary: "#FF3355", + colourPrimary: '#FF6680', + colourSecondary: '#FF4D6A', + colourTertiary: '#FF3355', + colourQuaternary: '#FF3355' }, - text: "#FFFFFF", - workspace: "#F9F9F9", - toolboxHover: "#4C97FF", - toolboxSelected: "#E9EEF2", - toolboxText: "#575E75", - toolbox: "#FFFFFF", - flyout: "#F9F9F9", - scrollbar: "#CECDCE", - scrollbarHover: "#CECDCE", - textField: "#FFFFFF", - textFieldText: "#575E75", - insertionMarker: "#000000", + text: '#FFFFFF', + workspace: '#F9F9F9', + toolboxHover: '#4C97FF', + toolboxSelected: '#E9EEF2', + toolboxText: '#575E75', + toolbox: '#FFFFFF', + flyout: '#F9F9F9', + scrollbar: '#CECDCE', + scrollbarHover: '#CECDCE', + textField: '#FFFFFF', + textFieldText: '#575E75', + insertionMarker: '#000000', insertionMarkerOpacity: 0.2, dragShadowOpacity: 0.6, - stackGlow: "#FFF200", + stackGlow: '#FFF200', stackGlowSize: 4, stackGlowOpacity: 1, - replacementGlow: "#FFFFFF", + replacementGlow: '#FFFFFF', replacementGlowSize: 2, replacementGlowOpacity: 1, - colourPickerStroke: "#FFFFFF", + colourPickerStroke: '#FFFFFF', // CSS colours: support RGBA - fieldShadow: "rgba(255, 255, 255, 0.3)", - dropDownShadow: "rgba(0, 0, 0, .3)", - numPadBackground: "#547AB2", - numPadBorder: "#435F91", - numPadActiveBackground: "#435F91", - numPadText: "white", // Do not use hex here, it cannot be inlined with data-uri SVG - valueReportBackground: "#FFFFFF", - valueReportBorder: "#AAAAAA", - menuHover: "rgba(0, 0, 0, 0.2)", + fieldShadow: 'rgba(255, 255, 255, 0.3)', + dropDownShadow: 'rgba(0, 0, 0, .3)', + numPadBackground: '#547AB2', + numPadBorder: '#435F91', + numPadActiveBackground: '#435F91', + numPadText: 'white', // Do not use hex here, it cannot be inlined with data-uri SVG + valueReportBackground: '#FFFFFF', + valueReportBorder: '#AAAAAA', + menuHover: 'rgba(0, 0, 0, 0.2)' }; -export { blockColors }; +export { + blockColors +}; diff --git a/packages/scratch-gui/src/lib/themes/high-contrast/index.js b/packages/scratch-gui/src/lib/themes/high-contrast/index.js index 853ff7947c..b5e7fe3cc9 100644 --- a/packages/scratch-gui/src/lib/themes/high-contrast/index.js +++ b/packages/scratch-gui/src/lib/themes/high-contrast/index.js @@ -1,108 +1,111 @@ -import musicIcon from "./extensions/musicIcon.svg"; -import penIcon from "./extensions/penIcon.svg"; -import text2speechIcon from "./extensions/text2speechIcon.svg"; -import translateIcon from "./extensions/translateIcon.svg"; -import videoSensingIcon from "./extensions/videoSensingIcon.svg"; +import musicIcon from './extensions/musicIcon.svg'; +import penIcon from './extensions/penIcon.svg'; +import text2speechIcon from './extensions/text2speechIcon.svg'; +import translateIcon from './extensions/translateIcon.svg'; +import videoSensingIcon from './extensions/videoSensingIcon.svg'; // This object is passed directly to Blockly, hence the colour* fields need to // be named exactly as they are, including the UK spelling of "colour". const blockColors = { motion: { - colourPrimary: "#80B5FF", - colourSecondary: "#B3D2FF", - colourTertiary: "#3373CC", - colourQuaternary: "#CCE1FF", + colourPrimary: '#80B5FF', + colourSecondary: '#B3D2FF', + colourTertiary: '#3373CC', + colourQuaternary: '#CCE1FF' }, looks: { - colourPrimary: "#CCB3FF", - colourSecondary: "#DDCCFF", - colourTertiary: "#774DCB", - colourQuaternary: "#EEE5FF", + colourPrimary: '#CCB3FF', + colourSecondary: '#DDCCFF', + colourTertiary: '#774DCB', + colourQuaternary: '#EEE5FF' }, sounds: { - colourPrimary: "#E19DE1", - colourSecondary: "#FFB3FF", - colourTertiary: "#BD42BD", - colourQuaternary: "#FFCCFF", + colourPrimary: '#E19DE1', + colourSecondary: '#FFB3FF', + colourTertiary: '#BD42BD', + colourQuaternary: '#FFCCFF' }, control: { - colourPrimary: "#FFBE4C", - colourSecondary: "#FFDA99", - colourTertiary: "#CF8B17", - colourQuaternary: "#FFE3B3", + colourPrimary: '#FFBE4C', + colourSecondary: '#FFDA99', + colourTertiary: '#CF8B17', + colourQuaternary: '#FFE3B3' }, event: { - colourPrimary: "#FFD966", - colourSecondary: "#FFECB3", - colourTertiary: "#CC9900", - colourQuaternary: "#FFF2CC", + colourPrimary: '#FFD966', + colourSecondary: '#FFECB3', + colourTertiary: '#CC9900', + colourQuaternary: '#FFF2CC' }, sensing: { - colourPrimary: "#85C4E0", - colourSecondary: "#AED8EA", - colourTertiary: "#2E8EB8", - colourQuaternary: "#C2E2F0", + colourPrimary: '#85C4E0', + colourSecondary: '#AED8EA', + colourTertiary: '#2E8EB8', + colourQuaternary: '#C2E2F0' }, pen: { - colourPrimary: "#13ECAF", - colourSecondary: "#75F0CD", - colourTertiary: "#0B8E69", - colourQuaternary: "#A3F5DE", + colourPrimary: '#13ECAF', + colourSecondary: '#75F0CD', + colourTertiary: '#0B8E69', + colourQuaternary: '#A3F5DE' }, operators: { - colourPrimary: "#7ECE7E", - colourSecondary: "#B5E3B5", - colourTertiary: "#389438", - colourQuaternary: "#DAF1DA", + colourPrimary: '#7ECE7E', + colourSecondary: '#B5E3B5', + colourTertiary: '#389438', + colourQuaternary: '#DAF1DA' }, data: { - colourPrimary: "#FFA54C", - colourSecondary: "#FFCC99", - colourTertiary: "#DB6E00", - colourQuaternary: "#FFE5CC", + colourPrimary: '#FFA54C', + colourSecondary: '#FFCC99', + colourTertiary: '#DB6E00', + colourQuaternary: '#FFE5CC' }, // This is not a new category, but rather for differentiation // between lists and scalar variables. data_lists: { - colourPrimary: "#FF9966", - colourSecondary: "#FFCAB0", // I don't think this is used, b/c we don't have any droppable fields in list blocks - colourTertiary: "#E64D00", - colourQuaternary: "#FFDDCC", + colourPrimary: '#FF9966', + colourSecondary: '#FFCAB0', // I don't think this is used, b/c we don't have any droppable fields in list blocks + colourTertiary: '#E64D00', + colourQuaternary: '#FFDDCC' }, more: { - colourPrimary: "#FF99AA", - colourSecondary: "#FFCCD5", - colourTertiary: "#FF3355", - colourQuaternary: "#FFE5EA", - }, - text: "#000000", - textFieldText: "#000000", // Text inside of inputs e.g. 90 in [point in direction (90)] - toolboxText: "#000000", // Toolbox text, color picker text (used to be #575E75) + colourPrimary: '#FF99AA', + colourSecondary: '#FFCCD5', + colourTertiary: '#FF3355', + colourQuaternary: '#FFE5EA' + }, + text: '#000000', + textFieldText: '#000000', // Text inside of inputs e.g. 90 in [point in direction (90)] + toolboxText: '#000000', // Toolbox text, color picker text (used to be #575E75) // The color that the category menu label (e.g. 'motion', 'looks', etc.) changes to on hover - toolboxHover: "#3373CC", - insertionMarker: "#000000", + toolboxHover: '#3373CC', + insertionMarker: '#000000', insertionMarkerOpacity: 0.2, - fieldShadow: "rgba(255, 255, 255, 0.3)", + fieldShadow: 'rgba(255, 255, 255, 0.3)', dragShadowOpacity: 0.6, - menuHover: "rgba(255, 255, 255, 0.3)", + menuHover: 'rgba(255, 255, 255, 0.3)' }; const extensions = { music: { - blockIconURI: musicIcon, + blockIconURI: musicIcon }, pen: { - blockIconURI: penIcon, + blockIconURI: penIcon }, text2speech: { - blockIconURI: text2speechIcon, + blockIconURI: text2speechIcon }, translate: { - blockIconURI: translateIcon, + blockIconURI: translateIcon }, videoSensing: { - blockIconURI: videoSensingIcon, - }, + blockIconURI: videoSensingIcon + } }; -export { blockColors, extensions }; +export { + blockColors, + extensions +}; From e017b95095c18e9440284ce02b28f1374c55011e Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:01:29 -0800 Subject: [PATCH 54/76] fix: update "colour" property names for dark theme and mocks --- .../src/lib/themes/dark/__mocks__/index.js | 8 +- .../scratch-gui/src/lib/themes/dark/index.js | 88 +++++++++---------- .../src/lib/themes/default/__mocks__/index.js | 12 +-- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js b/packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js index 6d1b817db6..c8770c5b45 100644 --- a/packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js +++ b/packages/scratch-gui/src/lib/themes/dark/__mocks__/index.js @@ -1,11 +1,11 @@ const blockColors = { motion: { - primary: '#AAAAAA' + colourPrimary: '#AAAAAA' }, pen: { - primary: '#FFFFFF', - secondary: '#EEEEEE', - tertiary: '#DDDDDD' + colourPrimary: '#FFFFFF', + colourSecondary: '#EEEEEE', + colourTertiary: '#DDDDDD' }, text: '#BBBBBB' }; diff --git a/packages/scratch-gui/src/lib/themes/dark/index.js b/packages/scratch-gui/src/lib/themes/dark/index.js index d1e8651633..b3f9f36d39 100644 --- a/packages/scratch-gui/src/lib/themes/dark/index.js +++ b/packages/scratch-gui/src/lib/themes/dark/index.js @@ -1,69 +1,69 @@ const blockColors = { motion: { - primary: '#0F1E33', - secondary: '#4C4C4C', - tertiary: '#4C97FF', - quaternary: '#4C97FF' + colourPrimary: '#0F1E33', + colourSecondary: '#4C4C4C', + colourTertiary: '#4C97FF', + colorQuaternary: '#4C97FF' }, looks: { - primary: '#1E1433', - secondary: '#4C4C4C', - tertiary: '#9966FF', - quaternary: '#9966FF' + colourPrimary: '#1E1433', + colourSecondary: '#4C4C4C', + colourTertiary: '#9966FF', + colorQuaternary: '#9966FF' }, sounds: { - primary: '#291329', - secondary: '#4C4C4C', - tertiary: '#CF63CF', - quaternary: '#CF63CF' + colourPrimary: '#291329', + colourSecondary: '#4C4C4C', + colourTertiary: '#CF63CF', + colorQuaternary: '#CF63CF' }, control: { - primary: '#332205', - secondary: '#4C4C4C', - tertiary: '#FFAB19', - quaternary: '#FFAB19' + colourPrimary: '#332205', + colourSecondary: '#4C4C4C', + colourTertiary: '#FFAB19', + colorQuaternary: '#FFAB19' }, event: { - primary: '#332600', - secondary: '#4C4C4C', - tertiary: '#FFBF00', - quaternary: '#FFBF00' + colourPrimary: '#332600', + colourSecondary: '#4C4C4C', + colourTertiary: '#FFBF00', + colorQuaternary: '#FFBF00' }, sensing: { - primary: '#12232A', - secondary: '#4C4C4C', - tertiary: '#5CB1D6', - quaternary: '#5CB1D6' + colourPrimary: '#12232A', + colourSecondary: '#4C4C4C', + colourTertiary: '#5CB1D6', + colorQuaternary: '#5CB1D6' }, pen: { - primary: '#03251C', - secondary: '#4C4C4C', - tertiary: '#0fBD8C', - quaternary: '#0fBD8C' + colourPrimary: '#03251C', + colourSecondary: '#4C4C4C', + colourTertiary: '#0fBD8C', + colorQuaternary: '#0fBD8C' }, operators: { - primary: '#112611', - secondary: '#4C4C4C', - tertiary: '#59C059', - quaternary: '#59C059' + colourPrimary: '#112611', + colourSecondary: '#4C4C4C', + colourTertiary: '#59C059', + colorQuaternary: '#59C059' }, data: { - primary: '#331C05', - secondary: '#4C4C4C', - tertiary: '#FF8C1A', - quaternary: '#FF8C1A' + colourPrimary: '#331C05', + colourSecondary: '#4C4C4C', + colourTertiary: '#FF8C1A', + colorQuaternary: '#FF8C1A' }, data_lists: { - primary: '#331405', - secondary: '#4C4C4C', - tertiary: '#FF661A', - quaternary: '#FF661A' + colourPrimary: '#331405', + colourSecondary: '#4C4C4C', + colourTertiary: '#FF661A', + colorQuaternary: '#FF661A' }, more: { - primary: '#331419', - secondary: '#4C4C4C', - tertiary: '#FF6680', - quaternary: '#FF6680' + colourPrimary: '#331419', + colourSecondary: '#4C4C4C', + colourTertiary: '#FF6680', + colorQuaternary: '#FF6680' }, text: 'rgba(255, 255, 255, .7)', textFieldText: '#E5E5E5', diff --git a/packages/scratch-gui/src/lib/themes/default/__mocks__/index.js b/packages/scratch-gui/src/lib/themes/default/__mocks__/index.js index d3b3bc94b1..7e0d4efa1d 100644 --- a/packages/scratch-gui/src/lib/themes/default/__mocks__/index.js +++ b/packages/scratch-gui/src/lib/themes/default/__mocks__/index.js @@ -1,13 +1,13 @@ const blockColors = { motion: { - primary: '#111111', - secondary: '#222222', - tertiary: '#333333' + colourPrimary: '#111111', + colourSecondary: '#222222', + colourTertiary: '#333333' }, pen: { - primary: '#121212', - secondary: '#232323', - tertiary: '#343434' + colourPrimary: '#121212', + colourSecondary: '#232323', + colourTertiary: '#343434' }, text: '#444444', workspace: '#555555' From 6177c0ee86dbe19ea6bc05b1e5d0e2fbb7235a73 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:10:36 -0800 Subject: [PATCH 55/76] test: update dynamic block tests for colour -> style --- .../unit/util/define-dynamic-block.test.js | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/packages/scratch-gui/test/unit/util/define-dynamic-block.test.js b/packages/scratch-gui/test/unit/util/define-dynamic-block.test.js index b755b0a1da..365159af14 100644 --- a/packages/scratch-gui/test/unit/util/define-dynamic-block.test.js +++ b/packages/scratch-gui/test/unit/util/define-dynamic-block.test.js @@ -9,10 +9,8 @@ const MockScratchBlocks = { }; const categoryInfo = { - name: 'test category', - color1: '#111', - color2: '#222', - color3: '#333' + name: 'motion category', + id: 'motion' }; const penIconURI = 'data:image/svg+xml;base64,fake_pen_icon_svg_base64_data'; @@ -101,9 +99,7 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.commandWithIcon, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, extensions: ['scratch_extension'], inputsInline: true, nextConnection: true, @@ -117,9 +113,7 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.commandWithoutIcon, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: true, @@ -133,9 +127,7 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.terminalCommand, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: false, // terminal @@ -149,10 +141,8 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.reporter, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, + style: categoryInfo.id, checkboxInFlyout_: true, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, // nextConnection: undefined, // reporter @@ -167,10 +157,8 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.boolean, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, + style: categoryInfo.id, // checkboxInFlyout_: undefined, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, // extensions: undefined, // no icon means no extension inputsInline: true, // nextConnection: undefined, // reporter @@ -185,9 +173,7 @@ describe('defineDynamicBlock', () => { const block = new MockBlock(testBlockInfo.hat, extendedOpcode); expect(block.result).toEqual({ category: categoryInfo.name, - colour: categoryInfo.color1, - colourSecondary: categoryInfo.color2, - colourTertiary: categoryInfo.color3, + style: categoryInfo.id, // extensions: undefined, // no icon means no extension inputsInline: true, nextConnection: true, From 2f08990510a94dac5fe2953cedc4ac652479c390 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:02:08 -0800 Subject: [PATCH 56/76] test: fix theme injection tests --- .../scratch-gui/test/unit/util/themes.test.js | 60 +++++++------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/packages/scratch-gui/test/unit/util/themes.test.js b/packages/scratch-gui/test/unit/util/themes.test.js index 35d8fa25b9..23f40bf8f3 100644 --- a/packages/scratch-gui/test/unit/util/themes.test.js +++ b/packages/scratch-gui/test/unit/util/themes.test.js @@ -5,7 +5,7 @@ import { getColorsForTheme, HIGH_CONTRAST_THEME } from '../../../src/lib/themes'; -import {injectExtensionBlockTheme, injectExtensionCategoryTheme} from '../../../src/lib/themes/blockHelpers'; +import {injectExtensionBlockIcons, injectExtensionCategoryTheme} from '../../../src/lib/themes/blockHelpers'; import {detectTheme, persistTheme} from '../../../src/lib/themes/themePersistance'; jest.mock('../../../src/lib/themes/default'); @@ -16,19 +16,19 @@ describe('themes', () => { describe('core functionality', () => { test('provides the default theme colors', () => { - expect(defaultColors.motion.primary).toEqual('#111111'); + expect(defaultColors.motion.colourPrimary).toEqual('#111111'); }); test('returns the dark mode', () => { const colors = getColorsForTheme(DARK_THEME); - expect(colors.motion.primary).toEqual('#AAAAAA'); + expect(colors.motion.colourPrimary).toEqual('#AAAAAA'); }); test('uses default theme colors when not specified', () => { const colors = getColorsForTheme(DARK_THEME); - expect(colors.motion.secondary).toEqual('#222222'); + expect(colors.motion.colourSecondary).toEqual('#222222'); }); }); @@ -45,26 +45,6 @@ describe('themes', () => { global.XMLSerializer = XMLSerializer; }); - test('updates extension block colors based on theme', () => { - const blockInfoJson = { - type: 'dummy_block', - colour: '#0FBD8C', - colourSecondary: '#0DA57A', - colourTertiary: '#0B8E69' - }; - - const updated = injectExtensionBlockTheme(blockInfoJson, DARK_THEME); - - expect(updated).toEqual({ - type: 'dummy_block', - colour: '#FFFFFF', - colourSecondary: '#EEEEEE', - colourTertiary: '#DDDDDD' - }); - // The original value was not modified - expect(blockInfoJson.colour).toBe('#0FBD8C'); - }); - test('updates extension block icon based on theme', () => { const blockInfoJson = { type: 'pen_block', @@ -73,13 +53,10 @@ describe('themes', () => { type: 'field_image', src: 'original' } - ], - colour: '#0FBD8C', - colourSecondary: '#0DA57A', - colourTertiary: '#0B8E69' + ] }; - const updated = injectExtensionBlockTheme(blockInfoJson, DARK_THEME); + const updated = injectExtensionBlockIcons(blockInfoJson, DARK_THEME); expect(updated).toEqual({ type: 'pen_block', @@ -88,10 +65,7 @@ describe('themes', () => { type: 'field_image', src: 'darkPenIcon' } - ], - colour: '#FFFFFF', - colourSecondary: '#EEEEEE', - colourTertiary: '#DDDDDD' + ] }); // The original value was not modified expect(blockInfoJson.args0[0].src).toBe('original'); @@ -100,18 +74,24 @@ describe('themes', () => { test('bypasses updates if using the default theme', () => { const blockInfoJson = { type: 'dummy_block', - colour: '#0FBD8C', - colourSecondary: '#0DA57A', - colourTertiary: '#0B8E69' + args0: [ + { + type: 'field_image', + src: 'original' + } + ] }; - const updated = injectExtensionBlockTheme(blockInfoJson, DEFAULT_THEME); + const updated = injectExtensionBlockIcons(blockInfoJson, DEFAULT_THEME); expect(updated).toEqual({ type: 'dummy_block', - colour: '#0FBD8C', - colourSecondary: '#0DA57A', - colourTertiary: '#0B8E69' + args0: [ + { + type: 'field_image', + src: 'original' + } + ] }); }); From fbae21e7642d1dff2cba66cfcd41382eb6bc75c0 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 4 Feb 2025 15:51:39 -0800 Subject: [PATCH 57/76] fix: fix preserving toolbox scroll position for new continuous toolbox plugin (#30) --- .../scratch-gui/src/containers/blocks.jsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index f1cd7ee618..6093981f1e 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -265,28 +265,29 @@ class Blocks extends React.Component { const selectedCategoryScrollPosition = this.workspace .getFlyout() - .getCategoryScrollPosition(selectedCategoryName).y * scale; + .getCategoryScrollPosition(selectedCategoryName) * scale; const offsetWithinCategory = this.workspace.getFlyout().getWorkspace() .getMetrics().viewTop - selectedCategoryScrollPosition; this.workspace.updateToolbox(this.props.toolboxXML); + this.workspace.getToolbox().runAfterRerender(() => { + const newCategoryScrollPosition = this.workspace + .getFlyout() + .getCategoryScrollPosition(selectedCategoryName); + if (newCategoryScrollPosition) { + this.workspace + .getFlyout() + .getWorkspace() + .scrollbar.setY( + (newCategoryScrollPosition * scale) + offsetWithinCategory + ); + } + }); this.workspace.getToolbox().forceRerender(); this._renderedToolboxXML = this.props.toolboxXML; - const newCategoryScrollPosition = this.workspace - .getFlyout() - .getCategoryScrollPosition(selectedCategoryName); - if (newCategoryScrollPosition) { - this.workspace - .getFlyout() - .getWorkspace() - .scrollbar.setY( - (newCategoryScrollPosition.y * scale) + offsetWithinCategory - ); - } - const queue = this.toolboxUpdateQueue; this.toolboxUpdateQueue = []; queue.forEach(fn => fn()); From fcdb0c383287a09aa3fca866ca95055e7e9555db Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:52:11 -0800 Subject: [PATCH 58/76] chore(deps): update deps for spork test --- package-lock.json | 364 +----------------------------- packages/scratch-gui/package.json | 2 +- 2 files changed, 13 insertions(+), 353 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d8030deae..44c4c83093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2041,7 +2041,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-4.0.4.tgz", "integrity": "sha512-FUXnlReiyejaaT+tkweW386dFbj9mSHjUCVtHgTP6cP5Wbvh6vJHQmnFxlPUNLsHPDGtri8j+yRKxGH97UkJvA==", - "dev": true, "engines": { "node": ">=8.0.0" }, @@ -6086,7 +6085,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, "peer": true, "engines": { "node": ">= 10" @@ -7147,8 +7145,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true + "deprecated": "Use your platform's native atob() and btoa() methods instead" }, "node_modules/accepts": { "version": "1.3.8", @@ -8786,7 +8783,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/blockly/-/blockly-10.4.3.tgz", "integrity": "sha512-+opfBmQnSiv7vTiY/TkDEBOslxUyfj8luS3S+qs1NnQKjInC+Waf2l9cNsMh9J8BMkmiCIT+Ed/3mmjIaL9wug==", - "dev": true, "peer": true, "dependencies": { "jsdom": "22.1.0" @@ -8796,7 +8792,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "peer": true, "dependencies": { "debug": "4" @@ -8809,7 +8804,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", - "dev": true, "peer": true, "dependencies": { "rrweb-cssom": "^0.6.0" @@ -8822,7 +8816,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", - "dev": true, "peer": true, "dependencies": { "abab": "^2.0.6", @@ -8838,7 +8831,6 @@ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "deprecated": "Use your platform's native DOMException instead", - "dev": true, "peer": true, "dependencies": { "webidl-conversions": "^7.0.0" @@ -8851,7 +8843,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "peer": true, "engines": { "node": ">=0.12" @@ -8864,7 +8855,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, "peer": true, "dependencies": { "asynckit": "^0.4.0", @@ -8880,7 +8870,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, "peer": true, "dependencies": { "whatwg-encoding": "^2.0.0" @@ -8893,7 +8882,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, "peer": true, "dependencies": { "@tootallnate/once": "2", @@ -8908,7 +8896,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "peer": true, "dependencies": { "agent-base": "6", @@ -8922,7 +8909,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8935,7 +8921,6 @@ "version": "22.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", - "dev": true, "peer": true, "dependencies": { "abab": "^2.0.6", @@ -8978,7 +8963,6 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, "peer": true, "dependencies": { "entities": "^4.5.0" @@ -8991,7 +8975,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "peer": true, "engines": { "node": ">=6" @@ -9001,14 +8984,12 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", - "dev": true, "peer": true }, "node_modules/blockly/node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, "peer": true, "dependencies": { "xmlchars": "^2.2.0" @@ -9021,7 +9002,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, "peer": true, "dependencies": { "psl": "^1.1.33", @@ -9037,7 +9017,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dev": true, "peer": true, "dependencies": { "punycode": "^2.3.0" @@ -9050,7 +9029,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, "peer": true, "engines": { "node": ">= 4.0.0" @@ -9060,7 +9038,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, "peer": true, "dependencies": { "xml-name-validator": "^4.0.0" @@ -9073,7 +9050,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, "peer": true, "engines": { "node": ">=12" @@ -9083,7 +9059,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, "peer": true, "dependencies": { "iconv-lite": "0.6.3" @@ -9096,7 +9071,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, "peer": true, "engines": { "node": ">=12" @@ -9106,7 +9080,6 @@ "version": "12.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", - "dev": true, "peer": true, "dependencies": { "tr46": "^4.1.1", @@ -9120,7 +9093,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, "peer": true, "engines": { "node": ">=10.0.0" @@ -9142,7 +9114,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, "peer": true, "engines": { "node": ">=12" @@ -31018,35 +30989,24 @@ } }, "node_modules/scratch-blocks": { - "version": "2.0.0-spork.3", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.3.tgz", - "integrity": "sha512-luy2QtACBjhHT2rH2Zcvwevjb9zBnMWGwLnp9ydEXzbcFTptmYxFTmC8iFRPm6szxGiCw1pH48Qr3BwTGRKp8Q==", + "version": "2.0.0-spork.4", + "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.4.tgz", + "integrity": "sha512-gG/w7a5bAKZAPoYLFbyNgtS7ZIGKpc6MrwmxtZG5oSA2WdJcwZMyYLeV9kMCnCh4P0pKBy+NaQEfQC4fDeXK9A==", "dependencies": { - "@blockly/continuous-toolbox": "^6.0.9", - "@blockly/field-colour": "^5.0.9", - "blockly": "^12.0.0-beta.0" + "@blockly/continuous-toolbox": "^7.0.0-beta.1", + "@blockly/field-colour": "^4.0.2", + "blockly": "^12.0.0-beta.1" } }, "node_modules/scratch-blocks/node_modules/@blockly/continuous-toolbox": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-6.0.12.tgz", - "integrity": "sha512-I2jsxN/f+wytrzUQkSrxOKLWHPgsjiIz/w0Tpdo9PcDB5mwoBnG8l0ldh5Yj55CRMZbY/q9tANPWR8V8acgMIw==", + "version": "7.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-7.0.0-beta.1.tgz", + "integrity": "sha512-IKu0whdtRTmdXSB11efSJ5fCNzQtAp98fv+7KnZk/V7Q/xAhVAutWWQE+FJvr9lKJlkeYqm9m+cxDhOBtlVP1w==", "engines": { "node": ">=8.17.0" }, "peerDependencies": { - "blockly": "^11.0.0" - } - }, - "node_modules/scratch-blocks/node_modules/@blockly/field-colour": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@blockly/field-colour/-/field-colour-5.0.12.tgz", - "integrity": "sha512-vNw6L/B0cpf+j0S6pShX31bOI16KJu+eACpsfHGOBZbb7+LT3bYKcGHe6+VRe+KtIE3jGlY7vYfnaJdOCrYlfQ==", - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "blockly": "^11.0.0" + "blockly": "^12.0.0-beta.0" } }, "node_modules/scratch-blocks/node_modules/blockly": { @@ -38482,7 +38442,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "2.0.0-spork.3", + "scratch-blocks": "2.0.0-spork.4", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", @@ -41013,18 +40973,6 @@ "webpack-dev-server": "3.11.3" } }, - "packages/scratch-vm/node_modules/@blockly/continuous-toolbox": { - "version": "7.0.0-beta.1", - "resolved": "https://registry.npmjs.org/@blockly/continuous-toolbox/-/continuous-toolbox-7.0.0-beta.1.tgz", - "integrity": "sha512-IKu0whdtRTmdXSB11efSJ5fCNzQtAp98fv+7KnZk/V7Q/xAhVAutWWQE+FJvr9lKJlkeYqm9m+cxDhOBtlVP1w==", - "dev": true, - "engines": { - "node": ">=8.17.0" - }, - "peerDependencies": { - "blockly": "^12.0.0-beta.0" - } - }, "packages/scratch-vm/node_modules/@webpack-cli/configtest": { "version": "1.2.0", "dev": true, @@ -41159,18 +41107,6 @@ "node": ">=0.10.0" } }, - "packages/scratch-vm/node_modules/blockly": { - "version": "12.0.0-beta.1", - "resolved": "https://registry.npmjs.org/blockly/-/blockly-12.0.0-beta.1.tgz", - "integrity": "sha512-lECwZ4K+YuLXMM0yxWTz1lwkmDl424sst7h/dhtSefuCki8afjI/F87byYK/ZIZsMKBEz2+8wEJ1Wlx5cYWIAg==", - "dev": true, - "dependencies": { - "jsdom": "25.0.1" - }, - "engines": { - "node": ">=18" - } - }, "packages/scratch-vm/node_modules/braces": { "version": "2.3.2", "dev": true, @@ -41325,38 +41261,6 @@ "node": ">= 4" } }, - "packages/scratch-vm/node_modules/cssstyle": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", - "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", - "dev": true, - "dependencies": { - "@asamuzakjp/css-color": "^2.8.2", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "packages/scratch-vm/node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true - }, - "packages/scratch-vm/node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18" - } - }, "packages/scratch-vm/node_modules/define-property": { "version": "2.0.2", "dev": true, @@ -41440,18 +41344,6 @@ "dev": true, "license": "MIT" }, - "packages/scratch-vm/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "packages/scratch-vm/node_modules/expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -41573,21 +41465,6 @@ "node": ">=4" } }, - "packages/scratch-vm/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "packages/scratch-vm/node_modules/glob-parent": { "version": "3.1.0", "dev": true, @@ -41632,18 +41509,6 @@ "node": ">=4" } }, - "packages/scratch-vm/node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, "packages/scratch-vm/node_modules/html-entities": { "version": "1.4.0", "dev": true, @@ -41663,18 +41528,6 @@ "node": ">=4.0.0" } }, - "packages/scratch-vm/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "packages/scratch-vm/node_modules/ignore": { "version": "3.3.10", "dev": true, @@ -41770,67 +41623,6 @@ "dev": true, "license": "MIT" }, - "packages/scratch-vm/node_modules/jsdom": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", - "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", - "dev": true, - "dependencies": { - "cssstyle": "^4.1.0", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "packages/scratch-vm/node_modules/jsdom/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "packages/scratch-vm/node_modules/json-schema-traverse": { "version": "0.4.1", "dev": true, @@ -41999,18 +41791,6 @@ "node": ">=4" } }, - "packages/scratch-vm/node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "dev": true, - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "packages/scratch-vm/node_modules/path-exists": { "version": "3.0.0", "dev": true, @@ -42041,15 +41821,6 @@ "node": ">=4" } }, - "packages/scratch-vm/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "packages/scratch-vm/node_modules/readable-stream": { "version": "2.3.8", "dev": true, @@ -42127,18 +41898,6 @@ "dev": true, "license": "MIT" }, - "packages/scratch-vm/node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "packages/scratch-vm/node_modules/schema-utils": { "version": "1.0.0", "dev": true, @@ -42152,17 +41911,6 @@ "node": ">= 4" } }, - "packages/scratch-vm/node_modules/scratch-blocks": { - "version": "2.0.0-spork.4", - "resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-2.0.0-spork.4.tgz", - "integrity": "sha512-gG/w7a5bAKZAPoYLFbyNgtS7ZIGKpc6MrwmxtZG5oSA2WdJcwZMyYLeV9kMCnCh4P0pKBy+NaQEfQC4fDeXK9A==", - "dev": true, - "dependencies": { - "@blockly/continuous-toolbox": "^7.0.0-beta.1", - "@blockly/field-colour": "^4.0.2", - "blockly": "^12.0.0-beta.1" - } - }, "packages/scratch-vm/node_modules/selfsigned": { "version": "1.10.14", "dev": true, @@ -42274,51 +42022,6 @@ "node": ">=0.10.0" } }, - "packages/scratch-vm/node_modules/tough-cookie": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.1.tgz", - "integrity": "sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==", - "dev": true, - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "packages/scratch-vm/node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "dev": true, - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "packages/scratch-vm/node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "packages/scratch-vm/node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "packages/scratch-vm/node_modules/webpack-cli": { "version": "4.10.0", "dev": true, @@ -42519,40 +42222,6 @@ "node": ">=6" } }, - "packages/scratch-vm/node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "packages/scratch-vm/node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "engines": { - "node": ">=18" - } - }, - "packages/scratch-vm/node_modules/whatwg-url": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", - "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", - "dev": true, - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "packages/scratch-vm/node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -42591,15 +42260,6 @@ "node": ">=6" } }, - "packages/scratch-vm/node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "packages/scratch-vm/node_modules/y18n": { "version": "4.0.3", "dev": true, diff --git a/packages/scratch-gui/package.json b/packages/scratch-gui/package.json index 3c55c42a51..89ef38bf9e 100644 --- a/packages/scratch-gui/package.json +++ b/packages/scratch-gui/package.json @@ -94,7 +94,7 @@ "react-virtualized": "9.22.5", "redux-throttle": "0.1.1", "scratch-audio": "2.0.73", - "scratch-blocks": "2.0.0-spork.3", + "scratch-blocks": "2.0.0-spork.4", "scratch-l10n": "5.0.134", "scratch-paint": "3.0.144", "scratch-render-fonts": "1.0.165", From 4d1e43a177fae8e1b7ea7fe5e7f0fe5e6cf71b16 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 11 Feb 2025 09:55:21 -0800 Subject: [PATCH 59/76] style: lint fixes --- packages/scratch-gui/src/lib/blocks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/scratch-gui/src/lib/blocks.js b/packages/scratch-gui/src/lib/blocks.js index 47b7bcd771..28db2767ed 100644 --- a/packages/scratch-gui/src/lib/blocks.js +++ b/packages/scratch-gui/src/lib/blocks.js @@ -320,10 +320,10 @@ export default function (vm, useCatBlocks) { // Use a collator's compare instead of localeCompare which internally // creates a collator. Using this is a lot faster in browsers that create a // collator for every localeCompare call. - const collator = new Intl.Collator([], { - sensitivity: 'base', - numeric: true - }); + // const collator = new Intl.Collator([], { + // sensitivity: 'base', + // numeric: true + // }); // ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) { // return collator.compare(str1, str2); // }; From 199055e557d74862d515046713728a6ad7dfd697 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:18:03 -0800 Subject: [PATCH 60/76] Revert "fix: adjust key event filtering to fix the when key pressed block (#13)" This reverts commit 87a9787cf5e977dd36bf77354ec4ca397bcc2969. --- packages/scratch-gui/src/lib/vm-listener-hoc.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx index eceac44092..87b9dab7c6 100644 --- a/packages/scratch-gui/src/lib/vm-listener-hoc.jsx +++ b/packages/scratch-gui/src/lib/vm-listener-hoc.jsx @@ -85,7 +85,7 @@ const vmListenerHOC = function (WrappedComponent) { } handleKeyDown (e) { // Don't capture keys intended for Blockly inputs. - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; + if (e.target !== document && e.target !== document.body) return; const key = (!e.key || e.key === 'Dead') ? e.keyCode : e.key; this.props.vm.postIOData('keyboard', { From fd6deb2fc982466f5a57247290a83ab7a2e9b331 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:22:05 -0800 Subject: [PATCH 61/76] test: move Selenium debugging info to files in test-results/ Port 554c82efdaf0bb93fd82730ba0260c863c9f1d70 from the monorepo and apply a minor supporting `package.json` change --- packages/scratch-gui/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/scratch-gui/.gitignore b/packages/scratch-gui/.gitignore index c851a6e0cf..eeb5311cb1 100644 --- a/packages/scratch-gui/.gitignore +++ b/packages/scratch-gui/.gitignore @@ -8,6 +8,7 @@ npm-* # Testing /.nyc_output /coverage +/test-results # Build /build From 5127c736f85e10db6efaac756fee4e2b53cfde7a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:52:15 -0800 Subject: [PATCH 62/76] test: update XPath expressions for Blockly v12 beta --- packages/scratch-gui/test/helpers/selenium-helper.js | 9 ++++++--- packages/scratch-gui/test/integration/blocks.test.js | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/scratch-gui/test/helpers/selenium-helper.js b/packages/scratch-gui/test/helpers/selenium-helper.js index 40be5f1fce..4c76a69d88 100644 --- a/packages/scratch-gui/test/helpers/selenium-helper.js +++ b/packages/scratch-gui/test/helpers/selenium-helper.js @@ -343,9 +343,12 @@ class SeleniumHelper { // then finally click the toolbox text. try { await this.setTitle(`clickBlocksCategory ${categoryText}`); - await this.findByXpath('//div[contains(@class, "blocks_blocks")]'); - await this.driver.sleep(100); - await this.clickText(categoryText, 'div[contains(@class, "blocks_blocks")]'); + + const desiredCategoryLabel = `*[contains(text(), "${categoryText}")]`; + const anyCategoryContainer = '*[contains(@class, "blocklyToolboxCategoryContainer")]'; + const desiredCategoryContainer = `//${desiredCategoryLabel}/ancestor::${anyCategoryContainer}`; + await this.clickXpath(desiredCategoryContainer); + await this.driver.sleep(500); // Wait for scroll to finish } catch (cause) { throw await enhanceError(outerError, cause); diff --git a/packages/scratch-gui/test/integration/blocks.test.js b/packages/scratch-gui/test/integration/blocks.test.js index 2521100788..dd44fcb17f 100644 --- a/packages/scratch-gui/test/integration/blocks.test.js +++ b/packages/scratch-gui/test/integration/blocks.test.js @@ -297,7 +297,7 @@ describe('Working with the blocks', () => { test('Use variable blocks after switching languages', async () => { const myVariable = 'my\u00A0variable'; - const changeVariableByScope = "*[@data-id='data_changevariableby']"; + const changeVariableByScope = '*[contains(@class, "blocklyFlyout")]//*[contains(@class, "data_changevariableby")]'; await loadUri(uri); From f2e9e2b8920fc9d7e65c38252cef9dfcf46a654e Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:11:58 -0800 Subject: [PATCH 63/76] test: update more XPath expressions for Blockly v12 --- .../test/helpers/selenium-helper.js | 61 ++++++++++++++++--- .../integration/blocks-standalone.test.js | 3 +- .../test/integration/localization.test.js | 14 ++--- .../test/integration/menu-bar.test.js | 11 ++-- .../test/integration/project-loading.test.js | 10 ++- .../test/integration/sprites.test.js | 3 +- 6 files changed, 73 insertions(+), 29 deletions(-) diff --git a/packages/scratch-gui/test/helpers/selenium-helper.js b/packages/scratch-gui/test/helpers/selenium-helper.js index 4c76a69d88..274fa4b33e 100644 --- a/packages/scratch-gui/test/helpers/selenium-helper.js +++ b/packages/scratch-gui/test/helpers/selenium-helper.js @@ -98,6 +98,10 @@ class SeleniumHelper { 'clickText', 'clickButton', 'clickXpath', + 'scopeForBlockId', + 'scopeForBlockText', + 'scopeForCategoryId', + 'scopeForCategoryText', 'clickBlocksCategory', 'elementIsVisible', 'findByText', @@ -149,14 +153,15 @@ class SeleniumHelper { get scope () { return { blocksTab: "*[@id='react-tabs-1']", + categoryContainer: '*[contains(@class, "blocklyToolboxCategoryContainer")]', // matches any category + contextMenu: '*[starts-with(@class,"react-contextmenu")]', costumesTab: "*[@id='react-tabs-3']", + menuBar: '*[contains(@class,"menu-bar_menu-bar_")]', modal: '*[@class="ReactModalPortal"]', + monitors: '*[starts-with(@class,"stage_monitor-wrapper")]', reportedValue: '*[@class="blocklyDropDownContent"]', soundsTab: "*[@id='react-tabs-5']", - spriteTile: '*[starts-with(@class,"react-contextmenu-wrapper")]', - menuBar: '*[contains(@class,"menu-bar_menu-bar_")]', - monitors: '*[starts-with(@class,"stage_monitor-wrapper")]', - contextMenu: '*[starts-with(@class,"react-contextmenu")]' + spriteTile: '*[starts-with(@class,"react-contextmenu-wrapper")]' }; } @@ -332,7 +337,47 @@ class SeleniumHelper { } /** - * Click a category in the blocks pane. + * Calculate an XPath expression to find a block in the blocks panel. + * Clicking this the element at this XPath should run the block. + * @param {string} blockId The identifier (opcode) of the block to find. Example: 'motion_movesteps'. + * @returns {string} An XPath expression that finds the block. + */ + scopeForBlockId (blockId) { + return `*[contains(@class, "blocklyBlock") and contains(@class, "${blockId}")]`; + } + + /** + * Calculate an XPath expression to find a block in the blocks panel. + * Clicking this the element at this XPath should run the block. + * @param {string} blockText The text of the block to find. Depends on the current language! + * @returns {string} An XPath expression that finds the block. + */ + scopeForBlockText (blockText) { + return `*[contains(text(), "${blockText}")]/ancestor::*[contains(@class, "blocklyBlock")]`; + } + + /** + * Calculate an XPath expression to find a category in the blocks panel. + * Clicking this the element at this XPath should scroll the category into view. + * @param {string} categoryId The ID of the category to find. Example: 'motion'. + * @returns {string} An XPath expression that finds the category. + */ + scopeForCategoryId (categoryId) { + return `*[@id="${categoryId}"]/ancestor::${this.scope.categoryContainer}`; + } + + /** + * Calculate an XPath expression to find a category in the blocks panel. + * Clicking this the element at this XPath should scroll the category into view. + * @param {string} categoryText The text of the category to find. Depends on the current language! + * @returns {string} An XPath expression that finds the category. + */ + scopeForCategoryText (categoryText) { + return `*[contains(text(), "${categoryText}")]/ancestor::${this.scope.categoryContainer}`; + } + + /** + * Click a category in the blocks pane, then wait to allow scroll time. * @param {string} categoryText The text of the category to click. * @returns {Promise} A promise that resolves when the category is clicked. */ @@ -344,10 +389,8 @@ class SeleniumHelper { try { await this.setTitle(`clickBlocksCategory ${categoryText}`); - const desiredCategoryLabel = `*[contains(text(), "${categoryText}")]`; - const anyCategoryContainer = '*[contains(@class, "blocklyToolboxCategoryContainer")]'; - const desiredCategoryContainer = `//${desiredCategoryLabel}/ancestor::${anyCategoryContainer}`; - await this.clickXpath(desiredCategoryContainer); + const desiredCategoryContainer = this.scopeForCategoryText(categoryText); + await this.clickXpath(`//${desiredCategoryContainer}`); await this.driver.sleep(500); // Wait for scroll to finish } catch (cause) { diff --git a/packages/scratch-gui/test/integration/blocks-standalone.test.js b/packages/scratch-gui/test/integration/blocks-standalone.test.js index 35afe3ed54..4a8611d612 100644 --- a/packages/scratch-gui/test/integration/blocks-standalone.test.js +++ b/packages/scratch-gui/test/integration/blocks-standalone.test.js @@ -3,6 +3,7 @@ import SeleniumHelper from '../helpers/selenium-helper'; const { clickText, + scopeForBlockId, clickBlocksCategory, clickButton, clickXpath, @@ -299,7 +300,7 @@ describe('Working with the blocks', () => { test('Use variable blocks after switching languages', async () => { const myVariable = 'my\u00A0variable'; - const changeVariableByScope = "*[@data-id='data_changevariableby']"; + const changeVariableByScope = scopeForBlockId('data_changevariableby'); await loadUri(uri); diff --git a/packages/scratch-gui/test/integration/localization.test.js b/packages/scratch-gui/test/integration/localization.test.js index 00cae10ba4..d2aa3babc1 100644 --- a/packages/scratch-gui/test/integration/localization.test.js +++ b/packages/scratch-gui/test/integration/localization.test.js @@ -2,6 +2,7 @@ import path from 'path'; import SeleniumHelper from '../helpers/selenium-helper'; const { + clickBlocksCategory, clickText, clickXpath, findByText, @@ -10,7 +11,8 @@ const { getLogs, loadUri, rightClickText, - scope + scope, + scopeForBlockText } = new SeleniumHelper(); const uri = path.resolve(__dirname, '../../build/index.html'); @@ -41,12 +43,10 @@ describe('Localization', () => { await clickXpath(SETTINGS_MENU_XPATH); await clickText('Language', scope.menuBar); await clickText('Deutsch'); - await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks refresh // Make sure the blocks are translating - await clickText('Fühlen'); // Sensing category in German - await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks to scroll - await clickText('Antwort'); // Find the "answer" block in German + await clickBlocksCategory('Fühlen'); // Sensing category in German & wait for scroll + await clickXpath(`//${scopeForBlockText('Antwort')}`); // Find the "answer" block in German // Change to the costumes tab to confirm other parts of the GUI are translating await clickText('Kostüme'); @@ -64,7 +64,7 @@ describe('Localization', () => { // Regression test for #4476, blocks in wrong language when loaded with locale test('Loading with locale shows correct blocks', async () => { await loadUri(`${uri}?locale=de`); - await clickText('Fühlen'); // Sensing category in German + await clickBlocksCategory('Fühlen'); // Sensing category in German await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks to scroll await clickText('Antwort'); // Find the "answer" block in German const logs = await getLogs(); @@ -74,7 +74,7 @@ describe('Localization', () => { // test for #5445 test('Loading with locale shows correct translation for string length block parameter', async () => { await loadUri(`${uri}?locale=ja`); - await clickText('演算'); // Operators category in Japanese + await clickBlocksCategory('演算'); // Operators category in Japanese await new Promise(resolve => setTimeout(resolve, 1000)); // wait for blocks to scroll await clickText('の長さ', scope.blocksTab); // Click "length " block await findByText('3', scope.reportedValue); // Tooltip with result diff --git a/packages/scratch-gui/test/integration/menu-bar.test.js b/packages/scratch-gui/test/integration/menu-bar.test.js index 33a6353e61..0494eb62e4 100644 --- a/packages/scratch-gui/test/integration/menu-bar.test.js +++ b/packages/scratch-gui/test/integration/menu-bar.test.js @@ -9,7 +9,8 @@ const { getDriver, loadUri, rightClickText, - scope + scope, + scopeForCategoryId } = new SeleniumHelper(); const uri = path.resolve(__dirname, '../../build/index.html'); @@ -110,12 +111,12 @@ describe('Menu bar settings', () => { await clickText('Color Mode', scope.menuBar); await clickText('High Contrast', scope.menuBar); + const motionBubblePath = `//${scopeForCategoryId('motion')}//*[contains(@class, "categoryBubble")]`; + // There is a tiny delay for the color theme to be applied to the categories. await driver.wait(async () => { - const motionCategoryDiv = await findByXpath( - '//div[contains(@class, "scratchCategoryMenuItem") and ' + - 'contains(@class, "scratchCategoryId-motion")]/*[1]'); - const color = await motionCategoryDiv.getCssValue('background-color'); + const motionCategoryBubble = await findByXpath(motionBubblePath); + const color = await motionCategoryBubble.getCssValue('background-color'); // Documentation for getCssValue says it depends on how the browser // returns the value. Locally I am seeing 'rgba(128, 181, 255, 1)', diff --git a/packages/scratch-gui/test/integration/project-loading.test.js b/packages/scratch-gui/test/integration/project-loading.test.js index 697b136ef8..dc4c052337 100644 --- a/packages/scratch-gui/test/integration/project-loading.test.js +++ b/packages/scratch-gui/test/integration/project-loading.test.js @@ -2,6 +2,7 @@ import path from 'path'; import SeleniumHelper from '../helpers/selenium-helper'; const { + clickBlocksCategory, clickText, clickXpath, findByText, @@ -87,16 +88,14 @@ describe('Loading scratch gui', () => { await clickText('Costumes'); await clickXpath(FILE_MENU_XPATH); await clickXpath('//li[span[text()="New"]]'); - await findByXpath('//div[@class="scratchCategoryMenu"]'); - await clickText('Operators', scope.blocksTab); + await clickBlocksCategory('Operators'); }); test('Not logged in->made no changes to project->create new project should not show alert', async () => { await loadUri(uri); await clickXpath(FILE_MENU_XPATH); await clickXpath('//li[span[text()="New"]]'); - await findByXpath('//*[div[@class="scratchCategoryMenu"]]'); - await clickText('Operators', scope.blocksTab); + await clickBlocksCategory('Operators'); }); test.skip('Not logged in->made a change to project->create new project should show alert', async () => { @@ -110,8 +109,7 @@ describe('Loading scratch gui', () => { driver.switchTo() .alert() .accept(); - await findByXpath('//*[div[@class="scratchCategoryMenu"]]'); - await clickText('Operators', scope.blocksTab); + await clickBlocksCategory('Operators'); }); }); }); diff --git a/packages/scratch-gui/test/integration/sprites.test.js b/packages/scratch-gui/test/integration/sprites.test.js index c798e0a179..d8f1a6d8c8 100644 --- a/packages/scratch-gui/test/integration/sprites.test.js +++ b/packages/scratch-gui/test/integration/sprites.test.js @@ -4,6 +4,7 @@ import {StaleElementReferenceError} from 'selenium-webdriver/lib/error'; import until from 'selenium-webdriver/lib/until'; const { + clickBlocksCategory, clickText, clickXpath, elementIsVisible, @@ -35,7 +36,7 @@ describe('Working with sprites', () => { await clickXpath('//button[@aria-label="Choose a Sprite"]'); await clickText('Apple', scope.modal); // Closes modal await rightClickText('Apple', scope.spriteTile); // Make sure it is there - await clickText('Motion'); // Make sure we are back to the code tab + await clickBlocksCategory('Motion'); // Make sure we are back to the code tab const logs = await getLogs(); await expect(logs).toEqual([]); }); From d985234ab6cbf3f2aec161b4d32372a525c65ff1 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:24:11 -0800 Subject: [PATCH 64/76] fix: variables got shy when flipping between code and project view Reverts one line of 9703bc6531e101d7684ebb394f5afacb287f36fd --- packages/scratch-gui/src/containers/blocks.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 6093981f1e..65ed4f99a0 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -248,6 +248,7 @@ class Blocks extends React.Component { .then(() => { this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); + this.requestToolboxUpdate(); this.withToolboxUpdates(() => { this.workspace.getFlyout().setRecyclingEnabled(true); }); From c5054bad7c4a71788b62e124b39b9eae6de40cc1 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 5 Mar 2025 06:36:47 -0800 Subject: [PATCH 65/76] test: avoid false positives in xpath @class tests --- .../test/helpers/selenium-helper.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/scratch-gui/test/helpers/selenium-helper.js b/packages/scratch-gui/test/helpers/selenium-helper.js index 274fa4b33e..b6eb2e5657 100644 --- a/packages/scratch-gui/test/helpers/selenium-helper.js +++ b/packages/scratch-gui/test/helpers/selenium-helper.js @@ -149,19 +149,24 @@ class SeleniumHelper { /** * List of useful xpath scopes for finding elements. * @returns {object} An object mapping names to xpath strings. + * @note Do not check for an exact class name with `contains(@class, "foo")`: that will match `class="foo2"`. + * Instead, use `contains(concat(" ", @class, " "), " foo ")`, which in this example will correctly report that + * " foo2 " does not contain " foo ". Similarly, to check if an element has any class starting with "foo", use + * `contains(concat(" ", @class), " foo")`. Checking with `starts-with(@class, "foo")`, for example, will only + * work if the whole "class" attribute starts with "foo" -- it will fail if another class is listed first. */ get scope () { return { blocksTab: "*[@id='react-tabs-1']", - categoryContainer: '*[contains(@class, "blocklyToolboxCategoryContainer")]', // matches any category - contextMenu: '*[starts-with(@class,"react-contextmenu")]', + categoryContainer: '*[contains(concat(" ", @class, " "), " blocklyToolboxCategoryContainer ")]', costumesTab: "*[@id='react-tabs-3']", - menuBar: '*[contains(@class,"menu-bar_menu-bar_")]', - modal: '*[@class="ReactModalPortal"]', - monitors: '*[starts-with(@class,"stage_monitor-wrapper")]', - reportedValue: '*[@class="blocklyDropDownContent"]', + modal: '*[contains(concat(" ", @class, " "), " ReactModalPortal ")]', + reportedValue: '*[contains(concat(" ", @class, " "), " blocklyDropDownContent ")]', soundsTab: "*[@id='react-tabs-5']", - spriteTile: '*[starts-with(@class,"react-contextmenu-wrapper")]' + spriteTile: '*[contains(concat(" ", @class, " "), " react-contextmenu-wrapper ")]', + menuBar: '*[contains(concat(" ", @class), " menu-bar_menu-bar_")]', + monitors: '*[contains(concat(" ", @class), " stage_monitor-wrapper_")]', + contextMenu: '*[contains(concat(" ", @class, " "), " react-contextmenu ")]' }; } @@ -343,7 +348,8 @@ class SeleniumHelper { * @returns {string} An XPath expression that finds the block. */ scopeForBlockId (blockId) { - return `*[contains(@class, "blocklyBlock") and contains(@class, "${blockId}")]`; + return `*[contains(concat(" ", @class, " "), " blocklyBlock ") and contains(concat(" ", @class, " "), " ${ + blockId} ")]`; } /** @@ -353,7 +359,7 @@ class SeleniumHelper { * @returns {string} An XPath expression that finds the block. */ scopeForBlockText (blockText) { - return `*[contains(text(), "${blockText}")]/ancestor::*[contains(@class, "blocklyBlock")]`; + return `*[contains(text(), "${blockText}")]/ancestor::*[contains(concat(" ", @class, " "), " blocklyBlock ")]`; } /** From 08891cef317c07146f0a5fe2bb90d7d0a456c4fc Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:00:54 -0800 Subject: [PATCH 66/76] ci: fix(?) pushing from the publish workflow --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d8e4b353ec..ee3c337efa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + token: ${{ secrets.PAT_RELEASE_PUSH }} # persists the token for pushing to the repo later - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 with: cache: 'npm' From cd8f57aa336bc6efe01958240f9bac5877745deb Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:08:56 -0800 Subject: [PATCH 67/76] ci: move commitlint into its own separate workflow This way it doesn't prevent other tests from running --- .github/workflows/ci.yml | 1 - .github/workflows/commitlint.yml | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/commitlint.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eadc3978b4..afba1bc5c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,6 @@ jobs: with: cache: 'npm' node-version-file: '.nvmrc' - - uses: wagoid/commitlint-github-action@9763196e10f27aef304c9b8b660d31d97fce0f99 # v5 - name: Debug info run: | cat < Date: Wed, 5 Mar 2025 15:31:51 -0800 Subject: [PATCH 68/76] ci: try again to get the push to work --- .github/workflows/publish.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee3c337efa..9ecf7b7b6d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,21 +10,22 @@ jobs: cd: runs-on: ubuntu-latest steps: + - name: Config GitHub user + shell: bash + run: | + git config --global user.name 'GitHub Actions' + git config --global user.email 'github-actions@localhost' + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: token: ${{ secrets.PAT_RELEASE_PUSH }} # persists the token for pushing to the repo later + - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 with: cache: 'npm' node-version-file: '.nvmrc' registry-url: 'https://registry.npmjs.org' - - name: Config GitHub user - shell: bash - run: | - git config --global user.name 'GitHub Actions' - git config --global user.email 'github-actions@localhost' - - uses: ./.github/actions/install-dependencies - name: Update the version in the package files From 9030d5cc939a1a0fa2c615db944d23128109082f Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Wed, 5 Mar 2025 19:00:06 -0800 Subject: [PATCH 69/76] ci: temporarily disable publishing to NPM --- .github/workflows/publish.yml | 38 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9ecf7b7b6d..165e7433a3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -40,25 +40,25 @@ jobs: - name: Build packages run: npm run build - - name: Publish scratch-svg-renderer - run: npm publish --access=public --workspace=@scratch/scratch-svg-renderer - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish scratch-render - run: npm publish --access=public --workspace=@scratch/scratch-render - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish scratch-vm - run: npm publish --access=public --workspace=@scratch/scratch-vm - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - - name: Publish scratch-gui - run: npm publish --access=public --workspace=@scratch/scratch-gui - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + # - name: Publish scratch-svg-renderer + # run: npm publish --access=public --workspace=@scratch/scratch-svg-renderer + # env: + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + # - name: Publish scratch-render + # run: npm publish --access=public --workspace=@scratch/scratch-render + # env: + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + # - name: Publish scratch-vm + # run: npm publish --access=public --workspace=@scratch/scratch-vm + # env: + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + # - name: Publish scratch-gui + # run: npm publish --access=public --workspace=@scratch/scratch-gui + # env: + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - name: Push to develop shell: bash From a2338604dfa11150135a164e8c48ad30a33249d8 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:14:52 -0800 Subject: [PATCH 70/76] ci: temporarily disable pre-build/test in publish workflow --- .github/workflows/publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 165e7433a3..8a93ec7dd8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,9 +5,11 @@ on: types: [published] jobs: - ci: - uses: ./.github/workflows/ci.yml + # ci: + # uses: ./.github/workflows/ci.yml cd: + # needs: + # - ci runs-on: ubuntu-latest steps: - name: Config GitHub user @@ -113,5 +115,3 @@ jobs: publish_dir: ./packages/scratch-gui/build destination_dir: scratch-gui full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" - needs: - - ci From c34518ba1a8c99386f40af79c2ec0dc7ddaf1418 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 6 Mar 2025 07:19:07 -0800 Subject: [PATCH 71/76] ci: determine tag for "npm publish" from branch name --- .github/workflows/publish.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8a93ec7dd8..49a5a95e0b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,6 +12,39 @@ jobs: # - ci runs-on: ubuntu-latest steps: + - name: Debug info + run: | + cat <> "$GITHUB_OUTPUT" + - name: Check NPM tag + run: | + if [ -z "${{ steps.npm_tag.outputs.npm_tag }}" ]; then + echo "Refusing to publish with empty NPM tag." + exit 1 + fi + - name: Config GitHub user shell: bash run: | From 8408a2abcb232ccc8fa1fdc9dbba318ca4b804cd Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:32:20 -0800 Subject: [PATCH 72/76] ci: make release commit message pass commitlint --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 49a5a95e0b..3c4975ad85 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -70,7 +70,7 @@ jobs: NEW_VERSION="${GIT_TAG/v/}" npm version "$NEW_VERSION" --no-git-tag-version - git add package* && git commit -m "Release $NEW_VERSION" + git add package* && git commit -m "chore(release): $NEW_VERSION [skip ci]" - name: Build packages run: npm run build From 5aa2727885e7cc91ad55621a6daca2b82da4eb9a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:47:32 -0800 Subject: [PATCH 73/76] ci: re-enable publish steps that were disabled for testing purposes --- .github/workflows/publish.yml | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3c4975ad85..6ffc9ab523 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,11 +5,11 @@ on: types: [published] jobs: - # ci: - # uses: ./.github/workflows/ci.yml + ci: + uses: ./.github/workflows/ci.yml cd: - # needs: - # - ci + needs: + - ci runs-on: ubuntu-latest steps: - name: Debug info @@ -75,25 +75,25 @@ jobs: - name: Build packages run: npm run build - # - name: Publish scratch-svg-renderer - # run: npm publish --access=public --workspace=@scratch/scratch-svg-renderer - # env: - # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - # - name: Publish scratch-render - # run: npm publish --access=public --workspace=@scratch/scratch-render - # env: - # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - # - name: Publish scratch-vm - # run: npm publish --access=public --workspace=@scratch/scratch-vm - # env: - # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - # - name: Publish scratch-gui - # run: npm publish --access=public --workspace=@scratch/scratch-gui - # env: - # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + - name: Publish scratch-svg-renderer + run: npm publish --access=public --workspace=@scratch/scratch-svg-renderer + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + - name: Publish scratch-render + run: npm publish --access=public --workspace=@scratch/scratch-render + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + - name: Publish scratch-vm + run: npm publish --access=public --workspace=@scratch/scratch-vm + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + + - name: Publish scratch-gui + run: npm publish --access=public --workspace=@scratch/scratch-gui + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - name: Push to develop shell: bash From 8942bddd73043015cc4c8e86051954f5415db725 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:48:39 -0800 Subject: [PATCH 74/76] ci: publish to NPM under a tag determined by branch --- .github/workflows/publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6ffc9ab523..8a1cc701b2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -76,22 +76,22 @@ jobs: run: npm run build - name: Publish scratch-svg-renderer - run: npm publish --access=public --workspace=@scratch/scratch-svg-renderer + run: npm publish --access=public --tag="${{steps.npm_tag.outputs.npm_tag}}" --workspace=@scratch/scratch-svg-renderer env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - name: Publish scratch-render - run: npm publish --access=public --workspace=@scratch/scratch-render + run: npm publish --access=public --tag="${{steps.npm_tag.outputs.npm_tag}}" --workspace=@scratch/scratch-render env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - name: Publish scratch-vm - run: npm publish --access=public --workspace=@scratch/scratch-vm + run: npm publish --access=public --tag="${{steps.npm_tag.outputs.npm_tag}}" --workspace=@scratch/scratch-vm env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - name: Publish scratch-gui - run: npm publish --access=public --workspace=@scratch/scratch-gui + run: npm publish --access=public --tag="${{steps.npm_tag.outputs.npm_tag}}" --workspace=@scratch/scratch-gui env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} From d19570db5615baa2b6c3d82bd3477c860e3b366f Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 2 May 2025 14:24:26 -0700 Subject: [PATCH 75/76] ci: publish all builds to GH Pages --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 32 --------------------------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afba1bc5c9..26bb8799c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,3 +86,44 @@ jobs: uses: ./.github/actions/test-package with: package_name: scratch-gui + + - name: Determine GitHub Pages directory name + id: branch_dir_name + run: | + if [ "$GITHUB_REF_NAME" == "develop" ]; then + echo "result=." + else + echo "result=${GITHUB_REF_NAME//[^A-Za-z0-9._-]/_}" + fi | tee --append "$GITHUB_OUTPUT" + + - name: Deploy scratch-svg-renderer to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packages/scratch-svg-renderer/playground + destination_dir: "${{steps.branch_dir_name.outputs.result}}/scratch-svg-renderer" + full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" + + - name: Deploy scratch-render to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packages/scratch-render/playground + destination_dir: "${{steps.branch_dir_name.outputs.result}}/scratch-render" + full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" + + - name: Deploy scratch-vm to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packages/scratch-vm/playground + destination_dir: "${{steps.branch_dir_name.outputs.result}}/scratch-vm" + full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" + + - name: Deploy scratch-gui to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./packages/scratch-gui/build + destination_dir: "${{steps.branch_dir_name.outputs.result}}/scratch-gui" + full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8a1cc701b2..9305250f48 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -116,35 +116,3 @@ jobs: run: | git tag -f "${{github.event.release.tag_name}}" HEAD git push -f origin "refs/tags/${{github.event.release.tag_name}}" - - - name: Deploy scratch-svg-renderer to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packages/scratch-svg-renderer/playground - destination_dir: scratch-svg-renderer - full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" - - - name: Deploy scratch-render to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packages/scratch-render/playground - destination_dir: scratch-render - full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" - - - name: Deploy scratch-vm to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packages/scratch-vm/playground - destination_dir: scratch-vm - full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" - - - name: Deploy scratch-gui to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./packages/scratch-gui/build - destination_dir: scratch-gui - full_commit_message: "Build for ${{ github.sha }} ${{ github.event.head_commit.message }}" From 4b31377da72e188cc79a86bc671e712282c80c23 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Fri, 2 May 2025 14:44:44 -0700 Subject: [PATCH 76/76] ci: clean up stale GH Pages subdirectories --- .github/workflows/ghpages-cleanup.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/ghpages-cleanup.yml diff --git a/.github/workflows/ghpages-cleanup.yml b/.github/workflows/ghpages-cleanup.yml new file mode 100644 index 0000000000..75f6efd380 --- /dev/null +++ b/.github/workflows/ghpages-cleanup.yml @@ -0,0 +1,27 @@ +on: + schedule: + - cron: 0 0 * * 6 # midnight on Saturdays + workflow_dispatch: + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Check out GH Pages branch + uses: actions/checkout + with: + # replace `fetch-depth` with `shallow-since` if and when actions/checkout#619 (or an equivalent) gets merged + fetch-depth: 0 + ref: gh-pages + - name: Mark stale directories for removal + run: | + for dir in */; do + if [ -z "$(git log -n 1 --since "1 month ago" -- "$dir")" ]; then + git rm -r "$dir" + fi + done + git status + - name: Commit + run: "git commit -m 'chore: remove stale GH Pages branches'" + - name: Push + run: "git push"