mirror of
https://github.com/google/blockly.git
synced 2026-01-10 02:17:09 +01:00
refactor: convert some classes to ES6 classes (#5928)
* refactor: move optional function declarations into the constructor * refactor: convert options.js to es6 class * refactor: convert generator.js to es6 class * refactor: convert workspace_events.js to es6 class * refactor: convert events_abstract.js to an es6 class * chore: format * chore: fix lint * chore: rebuild * chore: respond to PR feedback
This commit is contained in:
@@ -17,7 +17,7 @@ goog.module('Blockly.Events');
|
||||
|
||||
const deprecation = goog.require('Blockly.utils.deprecation');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
const {BlockChange} = goog.require('Blockly.Events.BlockChange');
|
||||
const {BlockCreate} = goog.require('Blockly.Events.BlockCreate');
|
||||
@@ -47,7 +47,7 @@ const {ViewportChange} = goog.require('Blockly.Events.ViewportChange');
|
||||
|
||||
|
||||
// Events.
|
||||
exports.Abstract = Abstract;
|
||||
exports.Abstract = AbstractEvent;
|
||||
exports.BubbleOpen = BubbleOpen;
|
||||
exports.BlockBase = BlockBase;
|
||||
exports.BlockChange = BlockChange;
|
||||
|
||||
@@ -24,98 +24,109 @@ const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
* @constructor
|
||||
* @alias Blockly.Events.Abstract
|
||||
* @abstract
|
||||
*/
|
||||
const Abstract = function() {
|
||||
class Abstract {
|
||||
/**
|
||||
* Whether or not the event is blank (to be populated by fromJson).
|
||||
* @type {?boolean}
|
||||
* @alias Blockly.Events.Abstract
|
||||
*/
|
||||
this.isBlank = null;
|
||||
constructor() {
|
||||
/**
|
||||
* Whether or not the event is blank (to be populated by fromJson).
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.isBlank = null;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.workspaceId = undefined;
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.workspaceId = undefined;
|
||||
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
|
||||
/**
|
||||
* Whether or not the event is a UI event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isUiEvent = false;
|
||||
};
|
||||
/**
|
||||
* Whether or not the event is a UI event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isUiEvent = false;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Abstract.prototype.toJson = function() {
|
||||
const json = {'type': this.type};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.type = undefined;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Abstract.prototype.fromJson = function(json) {
|
||||
this.isBlank = false;
|
||||
this.group = json['group'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} True if null, false if something changed.
|
||||
*/
|
||||
Abstract.prototype.isNull = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run an event.
|
||||
* @param {boolean} _forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Abstract.prototype.run = function(_forward) {
|
||||
// Defined by subclasses.
|
||||
};
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
* @return {!Workspace} The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @protected
|
||||
*/
|
||||
Abstract.prototype.getEventWorkspace_ = function() {
|
||||
let workspace;
|
||||
if (this.workspaceId) {
|
||||
const {Workspace} = goog.module.get('Blockly.Workspace');
|
||||
workspace = Workspace.getById(this.workspaceId);
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = {'type': this.type};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
if (!workspace) {
|
||||
throw Error(
|
||||
'Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
this.isBlank = false;
|
||||
this.group = json['group'];
|
||||
}
|
||||
return workspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} True if null, false if something changed.
|
||||
*/
|
||||
isNull() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an event.
|
||||
* @param {boolean} _forward True if run forward, false if run backward
|
||||
* (undo).
|
||||
*/
|
||||
run(_forward) {
|
||||
// Defined by subclasses.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
* @return {!Workspace} The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @protected
|
||||
*/
|
||||
getEventWorkspace_() {
|
||||
let workspace;
|
||||
if (this.workspaceId) {
|
||||
const {Workspace} = goog.module.get('Blockly.Workspace');
|
||||
workspace = Workspace.getById(this.workspaceId);
|
||||
}
|
||||
if (!workspace) {
|
||||
throw Error(
|
||||
'Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
|
||||
exports.Abstract = Abstract;
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockBase');
|
||||
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a block event.
|
||||
* @extends {Abstract}
|
||||
* @extends {AbstractEvent}
|
||||
*/
|
||||
class BlockBase extends Abstract {
|
||||
class BlockBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {!Block=} opt_block The block this event corresponds to.
|
||||
* Undefined for a blank event.
|
||||
|
||||
@@ -18,7 +18,7 @@ goog.module('Blockly.Events.CommentBase');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const utilsXml = goog.require('Blockly.utils.xml');
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentCreate} = goog.requireType('Blockly.Events.CommentCreate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
@@ -29,9 +29,9 @@ const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment');
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
* @extends {Abstract}
|
||||
* @extends {AbstractEvent}
|
||||
*/
|
||||
class CommentBase extends Abstract {
|
||||
class CommentBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {!WorkspaceComment=} opt_comment The comment this event
|
||||
* corresponds to. Undefined for a blank event.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
goog.module('Blockly.Events.UiBase');
|
||||
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
|
||||
|
||||
/**
|
||||
@@ -26,9 +26,9 @@ const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
* editing to work (e.g. scrolling the workspace, zooming, opening toolbox
|
||||
* categories).
|
||||
* UI events do not undo or redo.
|
||||
* @extends {Abstract}
|
||||
* @extends {AbstractEvent}
|
||||
*/
|
||||
class UiBase extends Abstract {
|
||||
class UiBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {string=} opt_workspaceId The workspace identifier for this event.
|
||||
* Undefined for a blank event.
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
*/
|
||||
goog.module('Blockly.Events.VarBase');
|
||||
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {VariableModel} = goog.requireType('Blockly.VariableModel');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a variable event.
|
||||
* @extends {Abstract}
|
||||
* @extends {AbstractEvent}
|
||||
*/
|
||||
class VarBase extends Abstract {
|
||||
class VarBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {!VariableModel=} opt_variable The variable this event
|
||||
* corresponds to. Undefined for a blank event.
|
||||
|
||||
@@ -22,6 +22,8 @@ const registry = goog.require('Blockly.registry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Abstract} = goog.requireType('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockChange} = goog.requireType('Blockly.Events.BlockChange');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockCreate} = goog.requireType('Blockly.Events.BlockCreate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockMove} = goog.requireType('Blockly.Events.BlockMove');
|
||||
@@ -32,6 +34,8 @@ const {CommentCreate} = goog.requireType('Blockly.Events.CommentCreate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentMove} = goog.requireType('Blockly.Events.CommentMove');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {ViewportChange} = goog.requireType('Blockly.Events.ViewportChange');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
|
||||
|
||||
@@ -307,6 +311,7 @@ exports.BUMP_EVENTS = BUMP_EVENTS;
|
||||
|
||||
/**
|
||||
* List of events queued for firing.
|
||||
* @type {!Array<!Abstract>}
|
||||
*/
|
||||
const FIRE_QUEUE = [];
|
||||
|
||||
@@ -365,7 +370,9 @@ const filter = function(queueIn, forward) {
|
||||
if (!event.isNull()) {
|
||||
// Treat all UI events as the same type in hash table.
|
||||
const eventType = event.isUiEvent ? UI : event.type;
|
||||
const key = [eventType, event.blockId, event.workspaceId].join(' ');
|
||||
// TODO(#5927): Ceck whether `blockId` exists before accessing it.
|
||||
const blockId = /** @type {*} */ (event).blockId;
|
||||
const key = [eventType, blockId, event.workspaceId].join(' ');
|
||||
|
||||
const lastEntry = hash[key];
|
||||
const lastEvent = lastEntry ? lastEntry.event : null;
|
||||
@@ -376,22 +383,25 @@ const filter = function(queueIn, forward) {
|
||||
hash[key] = {event: event, index: i};
|
||||
mergedQueue.push(event);
|
||||
} else if (event.type === MOVE && lastEntry.index === i - 1) {
|
||||
const moveEvent = /** @type {!BlockMove} */ (event);
|
||||
// Merge move events.
|
||||
lastEvent.newParentId = event.newParentId;
|
||||
lastEvent.newInputName = event.newInputName;
|
||||
lastEvent.newCoordinate = event.newCoordinate;
|
||||
lastEvent.newParentId = moveEvent.newParentId;
|
||||
lastEvent.newInputName = moveEvent.newInputName;
|
||||
lastEvent.newCoordinate = moveEvent.newCoordinate;
|
||||
lastEntry.index = i;
|
||||
} else if (
|
||||
event.type === CHANGE && event.element === lastEvent.element &&
|
||||
event.name === lastEvent.name) {
|
||||
const changeEvent = /** @type {!BlockChange} */ (event);
|
||||
// Merge change events.
|
||||
lastEvent.newValue = event.newValue;
|
||||
lastEvent.newValue = changeEvent.newValue;
|
||||
} else if (event.type === VIEWPORT_CHANGE) {
|
||||
const viewportEvent = /** @type {!ViewportChange} */ (event);
|
||||
// Merge viewport change events.
|
||||
lastEvent.viewTop = event.viewTop;
|
||||
lastEvent.viewLeft = event.viewLeft;
|
||||
lastEvent.scale = event.scale;
|
||||
lastEvent.oldScale = event.oldScale;
|
||||
lastEvent.viewTop = viewportEvent.viewTop;
|
||||
lastEvent.viewLeft = viewportEvent.viewLeft;
|
||||
lastEvent.scale = viewportEvent.scale;
|
||||
lastEvent.oldScale = viewportEvent.oldScale;
|
||||
} else if (event.type === CLICK && lastEvent.type === BUBBLE_OPEN) {
|
||||
// Drop click events caused by opening/closing bubbles.
|
||||
} else {
|
||||
@@ -546,12 +556,13 @@ exports.get = get;
|
||||
*/
|
||||
const disableOrphans = function(event) {
|
||||
if (event.type === MOVE || event.type === CREATE) {
|
||||
if (!event.workspaceId) {
|
||||
const blockEvent = /** @type {!BlockMove|!BlockCreate} */ (event);
|
||||
if (!blockEvent.workspaceId) {
|
||||
return;
|
||||
}
|
||||
const {Workspace} = goog.module.get('Blockly.Workspace');
|
||||
const eventWorkspace = Workspace.getById(event.workspaceId);
|
||||
let block = eventWorkspace.getBlockById(event.blockId);
|
||||
const eventWorkspace = Workspace.getById(blockEvent.workspaceId);
|
||||
let block = eventWorkspace.getBlockById(blockEvent.blockId);
|
||||
if (block) {
|
||||
// Changing blocks as part of this event shouldn't be undoable.
|
||||
const initialUndoFlag = recordUndo;
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
goog.module('Blockly.Events.FinishedLoading');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const object = goog.require('Blockly.utils.object');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {Abstract} = goog.require('Blockly.Events.Abstract');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
|
||||
@@ -28,70 +27,65 @@ const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
* Used to notify the developer when the workspace has finished loading (i.e
|
||||
* domToWorkspace).
|
||||
* Finished loading events do not record undo or redo.
|
||||
* @param {!Workspace=} opt_workspace The workspace that has finished
|
||||
* loading. Undefined for a blank event.
|
||||
* @extends {Abstract}
|
||||
* @constructor
|
||||
* @alias Blockly.Events.FinishedLoading
|
||||
* @extends {AbstractEvent}
|
||||
*/
|
||||
const FinishedLoading = function(opt_workspace) {
|
||||
class FinishedLoading extends AbstractEvent {
|
||||
/**
|
||||
* Whether or not the event is blank (to be populated by fromJson).
|
||||
* @type {boolean}
|
||||
* @param {!Workspace=} opt_workspace The workspace that has finished
|
||||
* loading. Undefined for a blank event.
|
||||
* @alias Blockly.Events.FinishedLoading
|
||||
*/
|
||||
this.isBlank = typeof opt_workspace === 'undefined';
|
||||
constructor(opt_workspace) {
|
||||
super();
|
||||
/**
|
||||
* Whether or not the event is blank (to be populated by fromJson).
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isBlank = typeof opt_workspace === 'undefined';
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = opt_workspace ? opt_workspace.id : '';
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = opt_workspace ? opt_workspace.id : '';
|
||||
|
||||
/**
|
||||
* The event group ID for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
// Workspace events do not undo or redo.
|
||||
this.recordUndo = false;
|
||||
|
||||
// Workspace events do not undo or redo.
|
||||
this.recordUndo = false;
|
||||
};
|
||||
object.inherits(FinishedLoading, Abstract);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
FinishedLoading.prototype.type = eventUtils.FINISHED_LOADING;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
FinishedLoading.prototype.toJson = function() {
|
||||
const json = {
|
||||
'type': this.type,
|
||||
};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.FINISHED_LOADING;
|
||||
}
|
||||
if (this.workspaceId) {
|
||||
json['workspaceId'] = this.workspaceId;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
FinishedLoading.prototype.fromJson = function(json) {
|
||||
this.isBlank = false;
|
||||
this.workspaceId = json['workspaceId'];
|
||||
this.group = json['group'];
|
||||
};
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = {
|
||||
'type': this.type,
|
||||
};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
if (this.workspaceId) {
|
||||
json['workspaceId'] = this.workspaceId;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
this.isBlank = false;
|
||||
this.workspaceId = json['workspaceId'];
|
||||
this.group = json['group'];
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
|
||||
|
||||
@@ -29,390 +29,508 @@ const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
|
||||
/**
|
||||
* Class for a code generator that translates the blocks into a language.
|
||||
* @param {string} name Language name of this generator.
|
||||
* @constructor
|
||||
* @alias Blockly.Generator
|
||||
* @unrestricted
|
||||
*/
|
||||
const Generator = function(name) {
|
||||
this.name_ = name;
|
||||
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
|
||||
new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
|
||||
|
||||
class Generator {
|
||||
/**
|
||||
* Arbitrary code to inject into locations that risk causing infinite loops.
|
||||
* Any instances of '%1' will be replaced by the block ID that failed.
|
||||
* E.g. ' checkTimeout(%1);\n'
|
||||
* @type {?string}
|
||||
* @param {string} name Language name of this generator.
|
||||
* @alias Blockly.Generator
|
||||
*/
|
||||
this.INFINITE_LOOP_TRAP = null;
|
||||
constructor(name) {
|
||||
this.name_ = name;
|
||||
|
||||
/**
|
||||
* Arbitrary code to inject before every statement.
|
||||
* Any instances of '%1' will be replaced by the block ID of the statement.
|
||||
* E.g. 'highlight(%1);\n'
|
||||
* @type {?string}
|
||||
*/
|
||||
this.STATEMENT_PREFIX = null;
|
||||
/**
|
||||
* This is used as a placeholder in functions defined using
|
||||
* Generator.provideFunction_. It must not be legal code that could
|
||||
* legitimately appear in a function definition (or comment), and it must
|
||||
* not confuse the regular expression parser.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
|
||||
|
||||
/**
|
||||
* Arbitrary code to inject after every statement.
|
||||
* Any instances of '%1' will be replaced by the block ID of the statement.
|
||||
* E.g. 'highlight(%1);\n'
|
||||
* @type {?string}
|
||||
*/
|
||||
this.STATEMENT_SUFFIX = null;
|
||||
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
|
||||
new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
|
||||
|
||||
/**
|
||||
* The method of indenting. Defaults to two spaces, but language generators
|
||||
* may override this to increase indent or change to tabs.
|
||||
* @type {string}
|
||||
*/
|
||||
this.INDENT = ' ';
|
||||
/**
|
||||
* Arbitrary code to inject into locations that risk causing infinite loops.
|
||||
* Any instances of '%1' will be replaced by the block ID that failed.
|
||||
* E.g. ' checkTimeout(%1);\n'
|
||||
* @type {?string}
|
||||
*/
|
||||
this.INFINITE_LOOP_TRAP = null;
|
||||
|
||||
/**
|
||||
* Maximum length for a comment before wrapping. Does not account for
|
||||
* indenting level.
|
||||
* @type {number}
|
||||
*/
|
||||
this.COMMENT_WRAP = 60;
|
||||
/**
|
||||
* Arbitrary code to inject before every statement.
|
||||
* Any instances of '%1' will be replaced by the block ID of the statement.
|
||||
* E.g. 'highlight(%1);\n'
|
||||
* @type {?string}
|
||||
*/
|
||||
this.STATEMENT_PREFIX = null;
|
||||
|
||||
/**
|
||||
* List of outer-inner pairings that do NOT require parentheses.
|
||||
* @type {!Array<!Array<number>>}
|
||||
*/
|
||||
this.ORDER_OVERRIDES = [];
|
||||
/**
|
||||
* Arbitrary code to inject after every statement.
|
||||
* Any instances of '%1' will be replaced by the block ID of the statement.
|
||||
* E.g. 'highlight(%1);\n'
|
||||
* @type {?string}
|
||||
*/
|
||||
this.STATEMENT_SUFFIX = null;
|
||||
|
||||
/**
|
||||
* Whether the init method has been called.
|
||||
* Generators that set this flag to false after creation and true in init
|
||||
* will cause blockToCode to emit a warning if the generator has not been
|
||||
* initialized. If this flag is untouched, it will have no effect.
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.isInitialized = null;
|
||||
};
|
||||
/**
|
||||
* The method of indenting. Defaults to two spaces, but language generators
|
||||
* may override this to increase indent or change to tabs.
|
||||
* @type {string}
|
||||
*/
|
||||
this.INDENT = ' ';
|
||||
|
||||
/**
|
||||
* Generate code for all blocks in the workspace to the specified language.
|
||||
* @param {!Workspace=} workspace Workspace to generate code from.
|
||||
* @return {string} Generated code.
|
||||
*/
|
||||
Generator.prototype.workspaceToCode = function(workspace) {
|
||||
if (!workspace) {
|
||||
// Backwards compatibility from before there could be multiple workspaces.
|
||||
console.warn('No workspace specified in workspaceToCode call. Guessing.');
|
||||
workspace = common.getMainWorkspace();
|
||||
/**
|
||||
* Maximum length for a comment before wrapping. Does not account for
|
||||
* indenting level.
|
||||
* @type {number}
|
||||
*/
|
||||
this.COMMENT_WRAP = 60;
|
||||
|
||||
/**
|
||||
* List of outer-inner pairings that do NOT require parentheses.
|
||||
* @type {!Array<!Array<number>>}
|
||||
*/
|
||||
this.ORDER_OVERRIDES = [];
|
||||
|
||||
/**
|
||||
* Whether the init method has been called.
|
||||
* Generators that set this flag to false after creation and true in init
|
||||
* will cause blockToCode to emit a warning if the generator has not been
|
||||
* initialized. If this flag is untouched, it will have no effect.
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.isInitialized = null;
|
||||
|
||||
/**
|
||||
* Comma-separated list of reserved words.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.RESERVED_WORDS_ = '';
|
||||
|
||||
/**
|
||||
* A dictionary of definitions to be printed before the code.
|
||||
* @type {!Object|undefined}
|
||||
* @protected
|
||||
*/
|
||||
this.definitions_ = undefined;
|
||||
|
||||
/**
|
||||
* A dictionary mapping desired function names in definitions_ to actual
|
||||
* function names (to avoid collisions with user functions).
|
||||
* @type {!Object|undefined}
|
||||
* @protected
|
||||
*/
|
||||
this.functionNames_ = undefined;
|
||||
|
||||
/**
|
||||
* A database of variable and procedure names.
|
||||
* @type {!Names|undefined}
|
||||
* @protected
|
||||
*/
|
||||
this.nameDB_ = undefined;
|
||||
}
|
||||
let code = [];
|
||||
this.init(workspace);
|
||||
const blocks = workspace.getTopBlocks(true);
|
||||
for (let i = 0, block; (block = blocks[i]); i++) {
|
||||
let line = this.blockToCode(block);
|
||||
if (Array.isArray(line)) {
|
||||
// Value blocks return tuples of code and operator order.
|
||||
// Top-level blocks don't care about operator order.
|
||||
line = line[0];
|
||||
|
||||
/**
|
||||
* Generate code for all blocks in the workspace to the specified language.
|
||||
* @param {!Workspace=} workspace Workspace to generate code from.
|
||||
* @return {string} Generated code.
|
||||
*/
|
||||
workspaceToCode(workspace) {
|
||||
if (!workspace) {
|
||||
// Backwards compatibility from before there could be multiple workspaces.
|
||||
console.warn(
|
||||
'No workspace specified in workspaceToCode call. Guessing.');
|
||||
workspace = common.getMainWorkspace();
|
||||
}
|
||||
if (line) {
|
||||
if (block.outputConnection) {
|
||||
// This block is a naked value. Ask the language's code generator if
|
||||
// it wants to append a semicolon, or something.
|
||||
line = this.scrubNakedValue(line);
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
line = this.injectId(this.STATEMENT_PREFIX, block) + line;
|
||||
let code = [];
|
||||
this.init(workspace);
|
||||
const blocks = workspace.getTopBlocks(true);
|
||||
for (let i = 0, block; (block = blocks[i]); i++) {
|
||||
let line = this.blockToCode(block);
|
||||
if (Array.isArray(line)) {
|
||||
// Value blocks return tuples of code and operator order.
|
||||
// Top-level blocks don't care about operator order.
|
||||
line = line[0];
|
||||
}
|
||||
if (line) {
|
||||
if (block.outputConnection) {
|
||||
// This block is a naked value. Ask the language's code generator if
|
||||
// it wants to append a semicolon, or something.
|
||||
line = this.scrubNakedValue(line);
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
line = this.injectId(this.STATEMENT_PREFIX, block) + line;
|
||||
}
|
||||
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
|
||||
line = line + this.injectId(this.STATEMENT_SUFFIX, block);
|
||||
}
|
||||
}
|
||||
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
|
||||
line = line + this.injectId(this.STATEMENT_SUFFIX, block);
|
||||
code.push(line);
|
||||
}
|
||||
}
|
||||
code = code.join('\n'); // Blank line between each section.
|
||||
code = this.finish(code);
|
||||
// Final scrubbing of whitespace.
|
||||
code = code.replace(/^\s+\n/, '');
|
||||
code = code.replace(/\n\s+$/, '\n');
|
||||
code = code.replace(/[ \t]+\n/g, '\n');
|
||||
return code;
|
||||
}
|
||||
|
||||
// The following are some helpful functions which can be used by multiple
|
||||
|
||||
// languages.
|
||||
|
||||
/**
|
||||
* Prepend a common prefix onto each line of code.
|
||||
* Intended for indenting code or adding comment markers.
|
||||
* @param {string} text The lines of code.
|
||||
* @param {string} prefix The common prefix.
|
||||
* @return {string} The prefixed lines of code.
|
||||
*/
|
||||
prefixLines(text, prefix) {
|
||||
return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively spider a tree of blocks, returning all their comments.
|
||||
* @param {!Block} block The block from which to start spidering.
|
||||
* @return {string} Concatenated list of comments.
|
||||
*/
|
||||
allNestedComments(block) {
|
||||
const comments = [];
|
||||
const blocks = block.getDescendants(true);
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
const comment = blocks[i].getCommentText();
|
||||
if (comment) {
|
||||
comments.push(comment);
|
||||
}
|
||||
}
|
||||
// Append an empty string to create a trailing line break when joined.
|
||||
if (comments.length) {
|
||||
comments.push('');
|
||||
}
|
||||
return comments.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate code for the specified block (and attached blocks).
|
||||
* The generator must be initialized before calling this function.
|
||||
* @param {?Block} block The block to generate code for.
|
||||
* @param {boolean=} opt_thisOnly True to generate code for only this
|
||||
* statement.
|
||||
* @return {string|!Array} For statement blocks, the generated code.
|
||||
* For value blocks, an array containing the generated code and an
|
||||
* operator order value. Returns '' if block is null.
|
||||
*/
|
||||
blockToCode(block, opt_thisOnly) {
|
||||
if (this.isInitialized === false) {
|
||||
console.warn(
|
||||
'Generator init was not called before blockToCode was called.');
|
||||
}
|
||||
if (!block) {
|
||||
return '';
|
||||
}
|
||||
if (!block.isEnabled()) {
|
||||
// Skip past this block if it is disabled.
|
||||
return opt_thisOnly ? '' : this.blockToCode(block.getNextBlock());
|
||||
}
|
||||
if (block.isInsertionMarker()) {
|
||||
// Skip past insertion markers.
|
||||
return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]);
|
||||
}
|
||||
|
||||
const func = this[block.type];
|
||||
if (typeof func !== 'function') {
|
||||
throw Error(
|
||||
'Language "' + this.name_ + '" does not know how to generate ' +
|
||||
'code for block type "' + block.type + '".');
|
||||
}
|
||||
// First argument to func.call is the value of 'this' in the generator.
|
||||
// Prior to 24 September 2013 'this' was the only way to access the block.
|
||||
// The current preferred method of accessing the block is through the second
|
||||
// argument to func.call, which becomes the first parameter to the
|
||||
// generator.
|
||||
let code = func.call(block, block);
|
||||
if (Array.isArray(code)) {
|
||||
// Value blocks return tuples of code and operator order.
|
||||
if (!block.outputConnection) {
|
||||
throw TypeError('Expecting string from statement block: ' + block.type);
|
||||
}
|
||||
return [this.scrub_(block, code[0], opt_thisOnly), code[1]];
|
||||
} else if (typeof code === 'string') {
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
code = this.injectId(this.STATEMENT_PREFIX, block) + code;
|
||||
}
|
||||
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
|
||||
code = code + this.injectId(this.STATEMENT_SUFFIX, block);
|
||||
}
|
||||
return this.scrub_(block, code, opt_thisOnly);
|
||||
} else if (code === null) {
|
||||
// Block has handled code generation itself.
|
||||
return '';
|
||||
}
|
||||
throw SyntaxError('Invalid code generated: ' + code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate code representing the specified value input.
|
||||
* @param {!Block} block The block containing the input.
|
||||
* @param {string} name The name of the input.
|
||||
* @param {number} outerOrder The maximum binding strength (minimum order
|
||||
* value) of any operators adjacent to "block".
|
||||
* @return {string} Generated code or '' if no blocks are connected or the
|
||||
* specified input does not exist.
|
||||
*/
|
||||
valueToCode(block, name, outerOrder) {
|
||||
if (isNaN(outerOrder)) {
|
||||
throw TypeError('Expecting valid order from block: ' + block.type);
|
||||
}
|
||||
const targetBlock = block.getInputTargetBlock(name);
|
||||
if (!targetBlock) {
|
||||
return '';
|
||||
}
|
||||
const tuple = this.blockToCode(targetBlock);
|
||||
if (tuple === '') {
|
||||
// Disabled block.
|
||||
return '';
|
||||
}
|
||||
// Value blocks must return code and order of operations info.
|
||||
// Statement blocks must only return code.
|
||||
if (!Array.isArray(tuple)) {
|
||||
throw TypeError('Expecting tuple from value block: ' + targetBlock.type);
|
||||
}
|
||||
let code = tuple[0];
|
||||
const innerOrder = tuple[1];
|
||||
if (isNaN(innerOrder)) {
|
||||
throw TypeError(
|
||||
'Expecting valid order from value block: ' + targetBlock.type);
|
||||
}
|
||||
if (!code) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Add parentheses if needed.
|
||||
let parensNeeded = false;
|
||||
const outerOrderClass = Math.floor(outerOrder);
|
||||
const innerOrderClass = Math.floor(innerOrder);
|
||||
if (outerOrderClass <= innerOrderClass) {
|
||||
if (outerOrderClass === innerOrderClass &&
|
||||
(outerOrderClass === 0 || outerOrderClass === 99)) {
|
||||
// Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
|
||||
// 0 is the atomic order, 99 is the none order. No parentheses needed.
|
||||
// In all known languages multiple such code blocks are not order
|
||||
// sensitive. In fact in Python ('a' 'b') 'c' would fail.
|
||||
} else {
|
||||
// The operators outside this code are stronger than the operators
|
||||
// inside this code. To prevent the code from being pulled apart,
|
||||
// wrap the code in parentheses.
|
||||
parensNeeded = true;
|
||||
// Check for special exceptions.
|
||||
for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) {
|
||||
if (this.ORDER_OVERRIDES[i][0] === outerOrder &&
|
||||
this.ORDER_OVERRIDES[i][1] === innerOrder) {
|
||||
parensNeeded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
code.push(line);
|
||||
}
|
||||
}
|
||||
code = code.join('\n'); // Blank line between each section.
|
||||
code = this.finish(code);
|
||||
// Final scrubbing of whitespace.
|
||||
code = code.replace(/^\s+\n/, '');
|
||||
code = code.replace(/\n\s+$/, '\n');
|
||||
code = code.replace(/[ \t]+\n/g, '\n');
|
||||
return code;
|
||||
};
|
||||
|
||||
// The following are some helpful functions which can be used by multiple
|
||||
// languages.
|
||||
|
||||
/**
|
||||
* Prepend a common prefix onto each line of code.
|
||||
* Intended for indenting code or adding comment markers.
|
||||
* @param {string} text The lines of code.
|
||||
* @param {string} prefix The common prefix.
|
||||
* @return {string} The prefixed lines of code.
|
||||
*/
|
||||
Generator.prototype.prefixLines = function(text, prefix) {
|
||||
return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively spider a tree of blocks, returning all their comments.
|
||||
* @param {!Block} block The block from which to start spidering.
|
||||
* @return {string} Concatenated list of comments.
|
||||
*/
|
||||
Generator.prototype.allNestedComments = function(block) {
|
||||
const comments = [];
|
||||
const blocks = block.getDescendants(true);
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
const comment = blocks[i].getCommentText();
|
||||
if (comment) {
|
||||
comments.push(comment);
|
||||
if (parensNeeded) {
|
||||
// Technically, this should be handled on a language-by-language basis.
|
||||
// However all known (sane) languages use parentheses for grouping.
|
||||
code = '(' + code + ')';
|
||||
}
|
||||
}
|
||||
// Append an empty string to create a trailing line break when joined.
|
||||
if (comments.length) {
|
||||
comments.push('');
|
||||
}
|
||||
return comments.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate code for the specified block (and attached blocks).
|
||||
* The generator must be initialized before calling this function.
|
||||
* @param {Block} block The block to generate code for.
|
||||
* @param {boolean=} opt_thisOnly True to generate code for only this statement.
|
||||
* @return {string|!Array} For statement blocks, the generated code.
|
||||
* For value blocks, an array containing the generated code and an
|
||||
* operator order value. Returns '' if block is null.
|
||||
*/
|
||||
Generator.prototype.blockToCode = function(block, opt_thisOnly) {
|
||||
if (this.isInitialized === false) {
|
||||
console.warn(
|
||||
'Generator init was not called before blockToCode was called.');
|
||||
}
|
||||
if (!block) {
|
||||
return '';
|
||||
}
|
||||
if (!block.isEnabled()) {
|
||||
// Skip past this block if it is disabled.
|
||||
return opt_thisOnly ? '' : this.blockToCode(block.getNextBlock());
|
||||
}
|
||||
if (block.isInsertionMarker()) {
|
||||
// Skip past insertion markers.
|
||||
return opt_thisOnly ? '' : this.blockToCode(block.getChildren(false)[0]);
|
||||
return code;
|
||||
}
|
||||
|
||||
const func = this[block.type];
|
||||
if (typeof func !== 'function') {
|
||||
throw Error(
|
||||
'Language "' + this.name_ + '" does not know how to generate ' +
|
||||
'code for block type "' + block.type + '".');
|
||||
}
|
||||
// First argument to func.call is the value of 'this' in the generator.
|
||||
// Prior to 24 September 2013 'this' was the only way to access the block.
|
||||
// The current preferred method of accessing the block is through the second
|
||||
// argument to func.call, which becomes the first parameter to the generator.
|
||||
let code = func.call(block, block);
|
||||
if (Array.isArray(code)) {
|
||||
// Value blocks return tuples of code and operator order.
|
||||
if (!block.outputConnection) {
|
||||
throw TypeError('Expecting string from statement block: ' + block.type);
|
||||
/**
|
||||
* Generate a code string representing the blocks attached to the named
|
||||
* statement input. Indent the code.
|
||||
* This is mainly used in generators. When trying to generate code to evaluate
|
||||
* look at using workspaceToCode or blockToCode.
|
||||
* @param {!Block} block The block containing the input.
|
||||
* @param {string} name The name of the input.
|
||||
* @return {string} Generated code or '' if no blocks are connected.
|
||||
*/
|
||||
statementToCode(block, name) {
|
||||
const targetBlock = block.getInputTargetBlock(name);
|
||||
let code = this.blockToCode(targetBlock);
|
||||
// Value blocks must return code and order of operations info.
|
||||
// Statement blocks must only return code.
|
||||
if (typeof code !== 'string') {
|
||||
throw TypeError(
|
||||
'Expecting code from statement block: ' +
|
||||
(targetBlock && targetBlock.type));
|
||||
}
|
||||
return [this.scrub_(block, code[0], opt_thisOnly), code[1]];
|
||||
} else if (typeof code === 'string') {
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
code = this.injectId(this.STATEMENT_PREFIX, block) + code;
|
||||
if (code) {
|
||||
code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an infinite loop trap to the contents of a loop.
|
||||
* Add statement suffix at the start of the loop block (right after the loop
|
||||
* statement executes), and a statement prefix to the end of the loop block
|
||||
* (right before the loop statement executes).
|
||||
* @param {string} branch Code for loop contents.
|
||||
* @param {!Block} block Enclosing block.
|
||||
* @return {string} Loop contents, with infinite loop trap added.
|
||||
*/
|
||||
addLoopTrap(branch, block) {
|
||||
if (this.INFINITE_LOOP_TRAP) {
|
||||
branch = this.prefixLines(
|
||||
this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) +
|
||||
branch;
|
||||
}
|
||||
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
|
||||
code = code + this.injectId(this.STATEMENT_SUFFIX, block);
|
||||
branch = this.prefixLines(
|
||||
this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) +
|
||||
branch;
|
||||
}
|
||||
return this.scrub_(block, code, opt_thisOnly);
|
||||
} else if (code === null) {
|
||||
// Block has handled code generation itself.
|
||||
return '';
|
||||
}
|
||||
throw SyntaxError('Invalid code generated: ' + code);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate code representing the specified value input.
|
||||
* @param {!Block} block The block containing the input.
|
||||
* @param {string} name The name of the input.
|
||||
* @param {number} outerOrder The maximum binding strength (minimum order value)
|
||||
* of any operators adjacent to "block".
|
||||
* @return {string} Generated code or '' if no blocks are connected or the
|
||||
* specified input does not exist.
|
||||
*/
|
||||
Generator.prototype.valueToCode = function(block, name, outerOrder) {
|
||||
if (isNaN(outerOrder)) {
|
||||
throw TypeError('Expecting valid order from block: ' + block.type);
|
||||
}
|
||||
const targetBlock = block.getInputTargetBlock(name);
|
||||
if (!targetBlock) {
|
||||
return '';
|
||||
}
|
||||
const tuple = this.blockToCode(targetBlock);
|
||||
if (tuple === '') {
|
||||
// Disabled block.
|
||||
return '';
|
||||
}
|
||||
// Value blocks must return code and order of operations info.
|
||||
// Statement blocks must only return code.
|
||||
if (!Array.isArray(tuple)) {
|
||||
throw TypeError('Expecting tuple from value block: ' + targetBlock.type);
|
||||
}
|
||||
let code = tuple[0];
|
||||
const innerOrder = tuple[1];
|
||||
if (isNaN(innerOrder)) {
|
||||
throw TypeError(
|
||||
'Expecting valid order from value block: ' + targetBlock.type);
|
||||
}
|
||||
if (!code) {
|
||||
return '';
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
branch = branch +
|
||||
this.prefixLines(
|
||||
this.injectId(this.STATEMENT_PREFIX, block), this.INDENT);
|
||||
}
|
||||
return branch;
|
||||
}
|
||||
|
||||
// Add parentheses if needed.
|
||||
let parensNeeded = false;
|
||||
const outerOrderClass = Math.floor(outerOrder);
|
||||
const innerOrderClass = Math.floor(innerOrder);
|
||||
if (outerOrderClass <= innerOrderClass) {
|
||||
if (outerOrderClass === innerOrderClass &&
|
||||
(outerOrderClass === 0 || outerOrderClass === 99)) {
|
||||
// Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
|
||||
// 0 is the atomic order, 99 is the none order. No parentheses needed.
|
||||
// In all known languages multiple such code blocks are not order
|
||||
// sensitive. In fact in Python ('a' 'b') 'c' would fail.
|
||||
} else {
|
||||
// The operators outside this code are stronger than the operators
|
||||
// inside this code. To prevent the code from being pulled apart,
|
||||
// wrap the code in parentheses.
|
||||
parensNeeded = true;
|
||||
// Check for special exceptions.
|
||||
for (let i = 0; i < this.ORDER_OVERRIDES.length; i++) {
|
||||
if (this.ORDER_OVERRIDES[i][0] === outerOrder &&
|
||||
this.ORDER_OVERRIDES[i][1] === innerOrder) {
|
||||
parensNeeded = false;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Inject a block ID into a message to replace '%1'.
|
||||
* Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP.
|
||||
* @param {string} msg Code snippet with '%1'.
|
||||
* @param {!Block} block Block which has an ID.
|
||||
* @return {string} Code snippet with ID.
|
||||
*/
|
||||
injectId(msg, block) {
|
||||
const id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
|
||||
return msg.replace(/%1/g, '\'' + id + '\'');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more words to the list of reserved words for this language.
|
||||
* @param {string} words Comma-separated list of words to add to the list.
|
||||
* No spaces. Duplicates are ok.
|
||||
*/
|
||||
addReservedWords(words) {
|
||||
this.RESERVED_WORDS_ += words + ',';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a developer-defined function (not a user-defined procedure) to be
|
||||
* included in the generated code. Used for creating private helper
|
||||
* functions. The first time this is called with a given desiredName, the code
|
||||
* is saved and an actual name is generated. Subsequent calls with the same
|
||||
* desiredName have no effect but have the same return value.
|
||||
*
|
||||
* It is up to the caller to make sure the same desiredName is not
|
||||
* used for different helper functions (e.g. use "colourRandom" and
|
||||
* "listRandom", not "random"). There is no danger of colliding with reserved
|
||||
* words, or user-defined variable or procedure names.
|
||||
*
|
||||
* The code gets output when Generator.finish() is called.
|
||||
*
|
||||
* @param {string} desiredName The desired name of the function
|
||||
* (e.g. mathIsPrime).
|
||||
* @param {!Array<string>|string} code A list of statements or one multi-line
|
||||
* code string. Use ' ' for indents (they will be replaced).
|
||||
* @return {string} The actual name of the new function. This may differ
|
||||
* from desiredName if the former has already been taken by the user.
|
||||
* @protected
|
||||
*/
|
||||
provideFunction_(desiredName, code) {
|
||||
if (!this.definitions_[desiredName]) {
|
||||
const functionName =
|
||||
this.nameDB_.getDistinctName(desiredName, NameType.PROCEDURE);
|
||||
this.functionNames_[desiredName] = functionName;
|
||||
if (Array.isArray(code)) {
|
||||
code = code.join('\n');
|
||||
}
|
||||
let codeText = code.trim().replace(
|
||||
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
|
||||
// Change all ' ' indents into the desired indent.
|
||||
// To avoid an infinite loop of replacements, change all indents to '\0'
|
||||
// character first, then replace them all with the indent.
|
||||
// We are assuming that no provided functions contain a literal null char.
|
||||
let oldCodeText;
|
||||
while (oldCodeText !== codeText) {
|
||||
oldCodeText = codeText;
|
||||
codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0');
|
||||
}
|
||||
codeText = codeText.replace(/\0/g, this.INDENT);
|
||||
this.definitions_[desiredName] = codeText;
|
||||
}
|
||||
return this.functionNames_[desiredName];
|
||||
}
|
||||
if (parensNeeded) {
|
||||
// Technically, this should be handled on a language-by-language basis.
|
||||
// However all known (sane) languages use parentheses for grouping.
|
||||
code = '(' + code + ')';
|
||||
|
||||
/**
|
||||
* Hook for code to run before code generation starts.
|
||||
* Subclasses may override this, e.g. to initialise the database of variable
|
||||
* names.
|
||||
* @param {!Workspace} _workspace Workspace to generate code from.
|
||||
*/
|
||||
init(_workspace) {
|
||||
// Optionally override
|
||||
// Create a dictionary of definitions to be printed before the code.
|
||||
this.definitions_ = Object.create(null);
|
||||
// Create a dictionary mapping desired developer-defined function names in
|
||||
// definitions_ to actual function names (to avoid collisions with
|
||||
// user-defined procedures).
|
||||
this.functionNames_ = Object.create(null);
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a code string representing the blocks attached to the named
|
||||
* statement input. Indent the code.
|
||||
* This is mainly used in generators. When trying to generate code to evaluate
|
||||
* look at using workspaceToCode or blockToCode.
|
||||
* @param {!Block} block The block containing the input.
|
||||
* @param {string} name The name of the input.
|
||||
* @return {string} Generated code or '' if no blocks are connected.
|
||||
*/
|
||||
Generator.prototype.statementToCode = function(block, name) {
|
||||
const targetBlock = block.getInputTargetBlock(name);
|
||||
let code = this.blockToCode(targetBlock);
|
||||
// Value blocks must return code and order of operations info.
|
||||
// Statement blocks must only return code.
|
||||
if (typeof code !== 'string') {
|
||||
throw TypeError(
|
||||
'Expecting code from statement block: ' +
|
||||
(targetBlock && targetBlock.type));
|
||||
/**
|
||||
* Common tasks for generating code from blocks. This is called from
|
||||
* blockToCode and is called on every block, not just top level blocks.
|
||||
* Subclasses may override this, e.g. to generate code for statements
|
||||
* following the block, or to handle comments for the specified block and any
|
||||
* connected value blocks.
|
||||
* @param {!Block} _block The current block.
|
||||
* @param {string} code The code created for this block.
|
||||
* @param {boolean=} _opt_thisOnly True to generate code for only this
|
||||
* statement.
|
||||
* @return {string} Code with comments and subsequent blocks added.
|
||||
* @protected
|
||||
*/
|
||||
scrub_(_block, code, _opt_thisOnly) {
|
||||
// Optionally override
|
||||
return code;
|
||||
}
|
||||
if (code) {
|
||||
code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
|
||||
|
||||
/**
|
||||
* Hook for code to run at end of code generation.
|
||||
* Subclasses may override this, e.g. to prepend the generated code with
|
||||
* import statements or variable definitions.
|
||||
* @param {string} code Generated code.
|
||||
* @return {string} Completed code.
|
||||
*/
|
||||
finish(code) {
|
||||
// Optionally override
|
||||
// Clean up temporary data.
|
||||
delete this.definitions_;
|
||||
delete this.functionNames_;
|
||||
return code;
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an infinite loop trap to the contents of a loop.
|
||||
* Add statement suffix at the start of the loop block (right after the loop
|
||||
* statement executes), and a statement prefix to the end of the loop block
|
||||
* (right before the loop statement executes).
|
||||
* @param {string} branch Code for loop contents.
|
||||
* @param {!Block} block Enclosing block.
|
||||
* @return {string} Loop contents, with infinite loop trap added.
|
||||
*/
|
||||
Generator.prototype.addLoopTrap = function(branch, block) {
|
||||
if (this.INFINITE_LOOP_TRAP) {
|
||||
branch = this.prefixLines(
|
||||
this.injectId(this.INFINITE_LOOP_TRAP, block), this.INDENT) +
|
||||
branch;
|
||||
/**
|
||||
* Naked values are top-level blocks with outputs that aren't plugged into
|
||||
* anything.
|
||||
* Subclasses may override this, e.g. if their language does not allow
|
||||
* naked values.
|
||||
* @param {string} line Line of generated code.
|
||||
* @return {string} Legal line of code.
|
||||
*/
|
||||
scrubNakedValue(line) {
|
||||
// Optionally override
|
||||
return line;
|
||||
}
|
||||
if (this.STATEMENT_SUFFIX && !block.suppressPrefixSuffix) {
|
||||
branch = this.prefixLines(
|
||||
this.injectId(this.STATEMENT_SUFFIX, block), this.INDENT) +
|
||||
branch;
|
||||
}
|
||||
if (this.STATEMENT_PREFIX && !block.suppressPrefixSuffix) {
|
||||
branch = branch +
|
||||
this.prefixLines(
|
||||
this.injectId(this.STATEMENT_PREFIX, block), this.INDENT);
|
||||
}
|
||||
return branch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject a block ID into a message to replace '%1'.
|
||||
* Used for STATEMENT_PREFIX, STATEMENT_SUFFIX, and INFINITE_LOOP_TRAP.
|
||||
* @param {string} msg Code snippet with '%1'.
|
||||
* @param {!Block} block Block which has an ID.
|
||||
* @return {string} Code snippet with ID.
|
||||
*/
|
||||
Generator.prototype.injectId = function(msg, block) {
|
||||
const id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
|
||||
return msg.replace(/%1/g, '\'' + id + '\'');
|
||||
};
|
||||
|
||||
/**
|
||||
* Comma-separated list of reserved words.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.RESERVED_WORDS_ = '';
|
||||
|
||||
/**
|
||||
* Add one or more words to the list of reserved words for this language.
|
||||
* @param {string} words Comma-separated list of words to add to the list.
|
||||
* No spaces. Duplicates are ok.
|
||||
*/
|
||||
Generator.prototype.addReservedWords = function(words) {
|
||||
this.RESERVED_WORDS_ += words + ',';
|
||||
};
|
||||
|
||||
/**
|
||||
* This is used as a placeholder in functions defined using
|
||||
* Generator.provideFunction_. It must not be legal code that could
|
||||
* legitimately appear in a function definition (or comment), and it must
|
||||
* not confuse the regular expression parser.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
|
||||
|
||||
/**
|
||||
* A dictionary of definitions to be printed before the code.
|
||||
* @type {!Object|undefined}
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.definitions_;
|
||||
|
||||
/**
|
||||
* A dictionary mapping desired function names in definitions_ to actual
|
||||
* function names (to avoid collisions with user functions).
|
||||
* @type {!Object|undefined}
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.functionNames_;
|
||||
|
||||
/**
|
||||
* A database of variable and procedure names.
|
||||
* @type {!Names|undefined}
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.nameDB_;
|
||||
}
|
||||
|
||||
Object.defineProperties(Generator.prototype, {
|
||||
/**
|
||||
@@ -443,113 +561,4 @@ Object.defineProperties(Generator.prototype, {
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Define a developer-defined function (not a user-defined procedure) to be
|
||||
* included in the generated code. Used for creating private helper functions.
|
||||
* The first time this is called with a given desiredName, the code is
|
||||
* saved and an actual name is generated. Subsequent calls with the
|
||||
* same desiredName have no effect but have the same return value.
|
||||
*
|
||||
* It is up to the caller to make sure the same desiredName is not
|
||||
* used for different helper functions (e.g. use "colourRandom" and
|
||||
* "listRandom", not "random"). There is no danger of colliding with reserved
|
||||
* words, or user-defined variable or procedure names.
|
||||
*
|
||||
* The code gets output when Generator.finish() is called.
|
||||
*
|
||||
* @param {string} desiredName The desired name of the function
|
||||
* (e.g. mathIsPrime).
|
||||
* @param {!Array<string>|string} code A list of statements or one multi-line
|
||||
* code string. Use ' ' for indents (they will be replaced).
|
||||
* @return {string} The actual name of the new function. This may differ
|
||||
* from desiredName if the former has already been taken by the user.
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.provideFunction_ = function(desiredName, code) {
|
||||
if (!this.definitions_[desiredName]) {
|
||||
const functionName =
|
||||
this.nameDB_.getDistinctName(desiredName, NameType.PROCEDURE);
|
||||
this.functionNames_[desiredName] = functionName;
|
||||
if (Array.isArray(code)) {
|
||||
code = code.join('\n');
|
||||
}
|
||||
let codeText = code.trim().replace(
|
||||
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
|
||||
// Change all ' ' indents into the desired indent.
|
||||
// To avoid an infinite loop of replacements, change all indents to '\0'
|
||||
// character first, then replace them all with the indent.
|
||||
// We are assuming that no provided functions contain a literal null char.
|
||||
let oldCodeText;
|
||||
while (oldCodeText !== codeText) {
|
||||
oldCodeText = codeText;
|
||||
codeText = codeText.replace(/^(( {2})*) {2}/gm, '$1\0');
|
||||
}
|
||||
codeText = codeText.replace(/\0/g, this.INDENT);
|
||||
this.definitions_[desiredName] = codeText;
|
||||
}
|
||||
return this.functionNames_[desiredName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for code to run before code generation starts.
|
||||
* Subclasses may override this, e.g. to initialise the database of variable
|
||||
* names.
|
||||
* @param {!Workspace} _workspace Workspace to generate code from.
|
||||
*/
|
||||
Generator.prototype.init = function(_workspace) {
|
||||
// Optionally override
|
||||
// Create a dictionary of definitions to be printed before the code.
|
||||
this.definitions_ = Object.create(null);
|
||||
// Create a dictionary mapping desired developer-defined function names in
|
||||
// definitions_ to actual function names (to avoid collisions with
|
||||
// user-defined procedures).
|
||||
this.functionNames_ = Object.create(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Common tasks for generating code from blocks. This is called from
|
||||
* blockToCode and is called on every block, not just top level blocks.
|
||||
* Subclasses may override this, e.g. to generate code for statements following
|
||||
* the block, or to handle comments for the specified block and any connected
|
||||
* value blocks.
|
||||
* @param {!Block} _block The current block.
|
||||
* @param {string} code The code created for this block.
|
||||
* @param {boolean=} _opt_thisOnly True to generate code for only this
|
||||
* statement.
|
||||
* @return {string} Code with comments and subsequent blocks added.
|
||||
* @protected
|
||||
*/
|
||||
Generator.prototype.scrub_ = function(_block, code, _opt_thisOnly) {
|
||||
// Optionally override
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for code to run at end of code generation.
|
||||
* Subclasses may override this, e.g. to prepend the generated code with import
|
||||
* statements or variable definitions.
|
||||
* @param {string} code Generated code.
|
||||
* @return {string} Completed code.
|
||||
*/
|
||||
Generator.prototype.finish = function(code) {
|
||||
// Optionally override
|
||||
// Clean up temporary data.
|
||||
delete this.definitions_;
|
||||
delete this.functionNames_;
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Naked values are top-level blocks with outputs that aren't plugged into
|
||||
* anything.
|
||||
* Subclasses may override this, e.g. if their language does not allow
|
||||
* naked values.
|
||||
* @param {string} line Line of generated code.
|
||||
* @return {string} Legal line of code.
|
||||
*/
|
||||
Generator.prototype.scrubNakedValue = function(line) {
|
||||
// Optionally override
|
||||
return line;
|
||||
};
|
||||
|
||||
exports.Generator = Generator;
|
||||
|
||||
@@ -405,7 +405,8 @@ class Mutator extends Icon {
|
||||
*/
|
||||
workspaceChanged_(e) {
|
||||
if (!(e.isUiEvent ||
|
||||
(e.type === eventUtils.CHANGE && e.element === 'disabled') ||
|
||||
(e.type === eventUtils.CHANGE &&
|
||||
/** @type {!BlockChange} */ (e).element === 'disabled') ||
|
||||
e.type === eventUtils.CREATE)) {
|
||||
this.updateWorkspace_();
|
||||
}
|
||||
|
||||
584
core/options.js
584
core/options.js
@@ -31,163 +31,318 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/**
|
||||
* Parse the user-specified options, using reasonable defaults where behaviour
|
||||
* is unspecified.
|
||||
* @param {!BlocklyOptions} options Dictionary of options.
|
||||
* Specification:
|
||||
* https://developers.google.com/blockly/guides/get-started/web#configuration
|
||||
* @constructor
|
||||
* @alias Blockly.Options
|
||||
*/
|
||||
const Options = function(options) {
|
||||
let toolboxJsonDef = null;
|
||||
let hasCategories = false;
|
||||
let hasTrashcan = false;
|
||||
let hasCollapse = false;
|
||||
let hasComments = false;
|
||||
let hasDisable = false;
|
||||
let hasSounds = false;
|
||||
const readOnly = !!options['readOnly'];
|
||||
if (!readOnly) {
|
||||
toolboxJsonDef = toolbox.convertToolboxDefToJson(options['toolbox']);
|
||||
hasCategories = toolbox.hasCategories(toolboxJsonDef);
|
||||
hasTrashcan = options['trashcan'];
|
||||
if (hasTrashcan === undefined) {
|
||||
hasTrashcan = hasCategories;
|
||||
class Options {
|
||||
/**
|
||||
* @param {!BlocklyOptions} options Dictionary of options.
|
||||
* Specification:
|
||||
* https://developers.google.com/blockly/guides/get-started/web#configuration
|
||||
* @alias Blockly.Options
|
||||
*/
|
||||
constructor(options) {
|
||||
let toolboxJsonDef = null;
|
||||
let hasCategories = false;
|
||||
let hasTrashcan = false;
|
||||
let hasCollapse = false;
|
||||
let hasComments = false;
|
||||
let hasDisable = false;
|
||||
let hasSounds = false;
|
||||
const readOnly = !!options['readOnly'];
|
||||
if (!readOnly) {
|
||||
toolboxJsonDef = toolbox.convertToolboxDefToJson(options['toolbox']);
|
||||
hasCategories = toolbox.hasCategories(toolboxJsonDef);
|
||||
hasTrashcan = options['trashcan'];
|
||||
if (hasTrashcan === undefined) {
|
||||
hasTrashcan = hasCategories;
|
||||
}
|
||||
hasCollapse = options['collapse'];
|
||||
if (hasCollapse === undefined) {
|
||||
hasCollapse = hasCategories;
|
||||
}
|
||||
hasComments = options['comments'];
|
||||
if (hasComments === undefined) {
|
||||
hasComments = hasCategories;
|
||||
}
|
||||
hasDisable = options['disable'];
|
||||
if (hasDisable === undefined) {
|
||||
hasDisable = hasCategories;
|
||||
}
|
||||
hasSounds = options['sounds'];
|
||||
if (hasSounds === undefined) {
|
||||
hasSounds = true;
|
||||
}
|
||||
}
|
||||
hasCollapse = options['collapse'];
|
||||
if (hasCollapse === undefined) {
|
||||
hasCollapse = hasCategories;
|
||||
}
|
||||
hasComments = options['comments'];
|
||||
if (hasComments === undefined) {
|
||||
hasComments = hasCategories;
|
||||
}
|
||||
hasDisable = options['disable'];
|
||||
if (hasDisable === undefined) {
|
||||
hasDisable = hasCategories;
|
||||
}
|
||||
hasSounds = options['sounds'];
|
||||
if (hasSounds === undefined) {
|
||||
hasSounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
let maxTrashcanContents = options['maxTrashcanContents'];
|
||||
if (hasTrashcan) {
|
||||
if (maxTrashcanContents === undefined) {
|
||||
maxTrashcanContents = 32;
|
||||
let maxTrashcanContents = options['maxTrashcanContents'];
|
||||
if (hasTrashcan) {
|
||||
if (maxTrashcanContents === undefined) {
|
||||
maxTrashcanContents = 32;
|
||||
}
|
||||
} else {
|
||||
maxTrashcanContents = 0;
|
||||
}
|
||||
} else {
|
||||
maxTrashcanContents = 0;
|
||||
}
|
||||
const rtl = !!options['rtl'];
|
||||
let horizontalLayout = options['horizontalLayout'];
|
||||
if (horizontalLayout === undefined) {
|
||||
horizontalLayout = false;
|
||||
}
|
||||
let toolboxAtStart = options['toolboxPosition'];
|
||||
toolboxAtStart = toolboxAtStart !== 'end';
|
||||
const rtl = !!options['rtl'];
|
||||
let horizontalLayout = options['horizontalLayout'];
|
||||
if (horizontalLayout === undefined) {
|
||||
horizontalLayout = false;
|
||||
}
|
||||
let toolboxAtStart = options['toolboxPosition'];
|
||||
toolboxAtStart = toolboxAtStart !== 'end';
|
||||
|
||||
/** @type {!toolbox.Position} */
|
||||
let toolboxPosition;
|
||||
if (horizontalLayout) {
|
||||
toolboxPosition =
|
||||
toolboxAtStart ? toolbox.Position.TOP : toolbox.Position.BOTTOM;
|
||||
} else {
|
||||
toolboxPosition = (toolboxAtStart === rtl) ? toolbox.Position.RIGHT :
|
||||
toolbox.Position.LEFT;
|
||||
}
|
||||
/** @type {!toolbox.Position} */
|
||||
let toolboxPosition;
|
||||
if (horizontalLayout) {
|
||||
toolboxPosition =
|
||||
toolboxAtStart ? toolbox.Position.TOP : toolbox.Position.BOTTOM;
|
||||
} else {
|
||||
toolboxPosition = (toolboxAtStart === rtl) ? toolbox.Position.RIGHT :
|
||||
toolbox.Position.LEFT;
|
||||
}
|
||||
|
||||
let hasCss = options['css'];
|
||||
if (hasCss === undefined) {
|
||||
hasCss = true;
|
||||
}
|
||||
let pathToMedia = 'https://blockly-demo.appspot.com/static/media/';
|
||||
if (options['media']) {
|
||||
pathToMedia = options['media'];
|
||||
} else if (options['path']) {
|
||||
// 'path' is a deprecated option which has been replaced by 'media'.
|
||||
pathToMedia = options['path'] + 'media/';
|
||||
}
|
||||
let oneBasedIndex;
|
||||
if (options['oneBasedIndex'] === undefined) {
|
||||
oneBasedIndex = true;
|
||||
} else {
|
||||
oneBasedIndex = !!options['oneBasedIndex'];
|
||||
}
|
||||
const renderer = options['renderer'] || 'geras';
|
||||
let hasCss = options['css'];
|
||||
if (hasCss === undefined) {
|
||||
hasCss = true;
|
||||
}
|
||||
let pathToMedia = 'https://blockly-demo.appspot.com/static/media/';
|
||||
if (options['media']) {
|
||||
pathToMedia = options['media'];
|
||||
} else if (options['path']) {
|
||||
// 'path' is a deprecated option which has been replaced by 'media'.
|
||||
pathToMedia = options['path'] + 'media/';
|
||||
}
|
||||
let oneBasedIndex;
|
||||
if (options['oneBasedIndex'] === undefined) {
|
||||
oneBasedIndex = true;
|
||||
} else {
|
||||
oneBasedIndex = !!options['oneBasedIndex'];
|
||||
}
|
||||
const renderer = options['renderer'] || 'geras';
|
||||
|
||||
const plugins = options['plugins'] || {};
|
||||
const plugins = options['plugins'] || {};
|
||||
|
||||
/** @type {boolean} */
|
||||
this.RTL = rtl;
|
||||
/** @type {boolean} */
|
||||
this.oneBasedIndex = oneBasedIndex;
|
||||
/** @type {boolean} */
|
||||
this.collapse = hasCollapse;
|
||||
/** @type {boolean} */
|
||||
this.comments = hasComments;
|
||||
/** @type {boolean} */
|
||||
this.disable = hasDisable;
|
||||
/** @type {boolean} */
|
||||
this.readOnly = readOnly;
|
||||
/** @type {number} */
|
||||
this.maxBlocks = options['maxBlocks'] || Infinity;
|
||||
/** @type {?Object<string, number>} */
|
||||
this.maxInstances = options['maxInstances'];
|
||||
/** @type {string} */
|
||||
this.pathToMedia = pathToMedia;
|
||||
/** @type {boolean} */
|
||||
this.hasCategories = hasCategories;
|
||||
/** @type {!Options.MoveOptions} */
|
||||
this.moveOptions = Options.parseMoveOptions_(options, hasCategories);
|
||||
/** @deprecated January 2019 */
|
||||
this.hasScrollbars = !!this.moveOptions.scrollbars;
|
||||
/** @type {boolean} */
|
||||
this.hasTrashcan = hasTrashcan;
|
||||
/** @type {number} */
|
||||
this.maxTrashcanContents = maxTrashcanContents;
|
||||
/** @type {boolean} */
|
||||
this.hasSounds = hasSounds;
|
||||
/** @type {boolean} */
|
||||
this.hasCss = hasCss;
|
||||
/** @type {boolean} */
|
||||
this.horizontalLayout = horizontalLayout;
|
||||
/** @type {?toolbox.ToolboxInfo} */
|
||||
this.languageTree = toolboxJsonDef;
|
||||
/** @type {!Options.GridOptions} */
|
||||
this.gridOptions = Options.parseGridOptions_(options);
|
||||
/** @type {!Options.ZoomOptions} */
|
||||
this.zoomOptions = Options.parseZoomOptions_(options);
|
||||
/** @type {!toolbox.Position} */
|
||||
this.toolboxPosition = toolboxPosition;
|
||||
/** @type {!Theme} */
|
||||
this.theme = Options.parseThemeOptions_(options);
|
||||
/** @type {string} */
|
||||
this.renderer = renderer;
|
||||
/** @type {?Object} */
|
||||
this.rendererOverrides = options['rendererOverrides'];
|
||||
/** @type {boolean} */
|
||||
this.RTL = rtl;
|
||||
/** @type {boolean} */
|
||||
this.oneBasedIndex = oneBasedIndex;
|
||||
/** @type {boolean} */
|
||||
this.collapse = hasCollapse;
|
||||
/** @type {boolean} */
|
||||
this.comments = hasComments;
|
||||
/** @type {boolean} */
|
||||
this.disable = hasDisable;
|
||||
/** @type {boolean} */
|
||||
this.readOnly = readOnly;
|
||||
/** @type {number} */
|
||||
this.maxBlocks = options['maxBlocks'] || Infinity;
|
||||
/** @type {?Object<string, number>} */
|
||||
this.maxInstances = options['maxInstances'];
|
||||
/** @type {string} */
|
||||
this.pathToMedia = pathToMedia;
|
||||
/** @type {boolean} */
|
||||
this.hasCategories = hasCategories;
|
||||
/** @type {!Options.MoveOptions} */
|
||||
this.moveOptions = Options.parseMoveOptions_(options, hasCategories);
|
||||
/** @deprecated January 2019 */
|
||||
this.hasScrollbars = !!this.moveOptions.scrollbars;
|
||||
/** @type {boolean} */
|
||||
this.hasTrashcan = hasTrashcan;
|
||||
/** @type {number} */
|
||||
this.maxTrashcanContents = maxTrashcanContents;
|
||||
/** @type {boolean} */
|
||||
this.hasSounds = hasSounds;
|
||||
/** @type {boolean} */
|
||||
this.hasCss = hasCss;
|
||||
/** @type {boolean} */
|
||||
this.horizontalLayout = horizontalLayout;
|
||||
/** @type {?toolbox.ToolboxInfo} */
|
||||
this.languageTree = toolboxJsonDef;
|
||||
/** @type {!Options.GridOptions} */
|
||||
this.gridOptions = Options.parseGridOptions_(options);
|
||||
/** @type {!Options.ZoomOptions} */
|
||||
this.zoomOptions = Options.parseZoomOptions_(options);
|
||||
/** @type {!toolbox.Position} */
|
||||
this.toolboxPosition = toolboxPosition;
|
||||
/** @type {!Theme} */
|
||||
this.theme = Options.parseThemeOptions_(options);
|
||||
/** @type {string} */
|
||||
this.renderer = renderer;
|
||||
/** @type {?Object} */
|
||||
this.rendererOverrides = options['rendererOverrides'];
|
||||
|
||||
/**
|
||||
* The SVG element for the grid pattern.
|
||||
* Created during injection.
|
||||
* @type {?SVGElement}
|
||||
*/
|
||||
this.gridPattern = null;
|
||||
|
||||
/**
|
||||
* The parent of the current workspace, or null if there is no parent
|
||||
* workspace. We can assert that this is of type WorkspaceSvg as opposed to
|
||||
* Workspace as this is only used in a rendered workspace.
|
||||
* @type {?WorkspaceSvg}
|
||||
*/
|
||||
this.parentWorkspace = options['parentWorkspace'];
|
||||
|
||||
/**
|
||||
* Map of plugin type to name of registered plugin or plugin class.
|
||||
* @type {!Object<string, (function(new:?, ...?)|string)>}
|
||||
*/
|
||||
this.plugins = plugins;
|
||||
|
||||
/**
|
||||
* If set, sets the translation of the workspace to match the scrollbars.
|
||||
* @type {undefined|function(!{x:number,y:number}):void} A function that
|
||||
* sets the translation of the workspace to match the scrollbars. The
|
||||
* argument Contains an x and/or y property which is a float between 0
|
||||
* and 1 specifying the degree of scrolling.
|
||||
*/
|
||||
this.setMetrics = undefined;
|
||||
|
||||
/**
|
||||
* @type {undefined|function():!Metrics} A function that returns a metrics
|
||||
* object that describes the current workspace.
|
||||
*/
|
||||
this.getMetrics = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SVG element for the grid pattern.
|
||||
* Created during injection.
|
||||
* @type {?SVGElement}
|
||||
* Parse the user-specified move options, using reasonable defaults where
|
||||
* behaviour is unspecified.
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @param {boolean} hasCategories Whether the workspace has categories or not.
|
||||
* @return {!Options.MoveOptions} Normalized move options.
|
||||
* @private
|
||||
*/
|
||||
this.gridPattern = null;
|
||||
static parseMoveOptions_(options, hasCategories) {
|
||||
const move = options['move'] || {};
|
||||
const moveOptions = {};
|
||||
if (move['scrollbars'] === undefined &&
|
||||
options['scrollbars'] === undefined) {
|
||||
moveOptions.scrollbars = hasCategories;
|
||||
} else if (typeof move['scrollbars'] === 'object') {
|
||||
moveOptions.scrollbars = {};
|
||||
moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal'];
|
||||
moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical'];
|
||||
// Convert scrollbars object to boolean if they have the same value.
|
||||
// This allows us to easily check for whether any scrollbars exist using
|
||||
// !!moveOptions.scrollbars.
|
||||
if (moveOptions.scrollbars.horizontal &&
|
||||
moveOptions.scrollbars.vertical) {
|
||||
moveOptions.scrollbars = true;
|
||||
} else if (
|
||||
!moveOptions.scrollbars.horizontal &&
|
||||
!moveOptions.scrollbars.vertical) {
|
||||
moveOptions.scrollbars = false;
|
||||
}
|
||||
} else {
|
||||
moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars'];
|
||||
}
|
||||
|
||||
if (!moveOptions.scrollbars || move['wheel'] === undefined) {
|
||||
// Defaults to true if single-direction scroll is enabled.
|
||||
moveOptions.wheel = typeof moveOptions.scrollbars === 'object';
|
||||
} else {
|
||||
moveOptions.wheel = !!move['wheel'];
|
||||
}
|
||||
if (!moveOptions.scrollbars) {
|
||||
moveOptions.drag = false;
|
||||
} else if (move['drag'] === undefined) {
|
||||
// Defaults to true if scrollbars is true.
|
||||
moveOptions.drag = true;
|
||||
} else {
|
||||
moveOptions.drag = !!move['drag'];
|
||||
}
|
||||
return moveOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent of the current workspace, or null if there is no parent
|
||||
* workspace. We can assert that this is of type WorkspaceSvg as opposed to
|
||||
* Workspace as this is only used in a rendered workspace.
|
||||
* @type {WorkspaceSvg}
|
||||
* Parse the user-specified zoom options, using reasonable defaults where
|
||||
* behaviour is unspecified. See zoom documentation:
|
||||
* https://developers.google.com/blockly/guides/configure/web/zoom
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Options.ZoomOptions} Normalized zoom options.
|
||||
* @private
|
||||
*/
|
||||
this.parentWorkspace = options['parentWorkspace'];
|
||||
static parseZoomOptions_(options) {
|
||||
const zoom = options['zoom'] || {};
|
||||
const zoomOptions = {};
|
||||
if (zoom['controls'] === undefined) {
|
||||
zoomOptions.controls = false;
|
||||
} else {
|
||||
zoomOptions.controls = !!zoom['controls'];
|
||||
}
|
||||
if (zoom['wheel'] === undefined) {
|
||||
zoomOptions.wheel = false;
|
||||
} else {
|
||||
zoomOptions.wheel = !!zoom['wheel'];
|
||||
}
|
||||
if (zoom['startScale'] === undefined) {
|
||||
zoomOptions.startScale = 1;
|
||||
} else {
|
||||
zoomOptions.startScale = Number(zoom['startScale']);
|
||||
}
|
||||
if (zoom['maxScale'] === undefined) {
|
||||
zoomOptions.maxScale = 3;
|
||||
} else {
|
||||
zoomOptions.maxScale = Number(zoom['maxScale']);
|
||||
}
|
||||
if (zoom['minScale'] === undefined) {
|
||||
zoomOptions.minScale = 0.3;
|
||||
} else {
|
||||
zoomOptions.minScale = Number(zoom['minScale']);
|
||||
}
|
||||
if (zoom['scaleSpeed'] === undefined) {
|
||||
zoomOptions.scaleSpeed = 1.2;
|
||||
} else {
|
||||
zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']);
|
||||
}
|
||||
if (zoom['pinch'] === undefined) {
|
||||
zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls;
|
||||
} else {
|
||||
zoomOptions.pinch = !!zoom['pinch'];
|
||||
}
|
||||
return zoomOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of plugin type to name of registered plugin or plugin class.
|
||||
* @type {!Object<string, (function(new:?, ...?)|string)>}
|
||||
* Parse the user-specified grid options, using reasonable defaults where
|
||||
* behaviour is unspecified. See grid documentation:
|
||||
* https://developers.google.com/blockly/guides/configure/web/grid
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Options.GridOptions} Normalized grid options.
|
||||
* @private
|
||||
*/
|
||||
this.plugins = plugins;
|
||||
};
|
||||
static parseGridOptions_(options) {
|
||||
const grid = options['grid'] || {};
|
||||
const gridOptions = {};
|
||||
gridOptions.spacing = Number(grid['spacing']) || 0;
|
||||
gridOptions.colour = grid['colour'] || '#888';
|
||||
gridOptions.length =
|
||||
(grid['length'] === undefined) ? 1 : Number(grid['length']);
|
||||
gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
|
||||
return gridOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the user-specified theme options, using the classic theme as a
|
||||
* default. https://developers.google.com/blockly/guides/configure/web/themes
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Theme} A Blockly Theme.
|
||||
* @private
|
||||
*/
|
||||
static parseThemeOptions_(options) {
|
||||
const theme = options['theme'] || Classic;
|
||||
if (typeof theme === 'string') {
|
||||
return /** @type {!Theme} */ (
|
||||
registry.getObject(registry.Type.THEME, theme));
|
||||
} else if (theme instanceof Theme) {
|
||||
return /** @type {!Theme} */ (theme);
|
||||
}
|
||||
return Theme.defineTheme(
|
||||
theme.name || ('builtin' + idGenerator.getNextUniqueId()), theme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grid Options.
|
||||
@@ -233,153 +388,4 @@ Options.ScrollbarOptions;
|
||||
*/
|
||||
Options.ZoomOptions;
|
||||
|
||||
/**
|
||||
* If set, sets the translation of the workspace to match the scrollbars.
|
||||
* @param {!{x:number,y:number}} xyRatio Contains an x and/or y property which
|
||||
* is a float between 0 and 1 specifying the degree of scrolling.
|
||||
* @return {void}
|
||||
*/
|
||||
Options.prototype.setMetrics;
|
||||
|
||||
/**
|
||||
* Return an object with the metrics required to size the workspace.
|
||||
* @return {!Metrics} Contains size and position metrics.
|
||||
*/
|
||||
Options.prototype.getMetrics;
|
||||
|
||||
/**
|
||||
* Parse the user-specified move options, using reasonable defaults where
|
||||
* behaviour is unspecified.
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @param {boolean} hasCategories Whether the workspace has categories or not.
|
||||
* @return {!Options.MoveOptions} Normalized move options.
|
||||
* @private
|
||||
*/
|
||||
Options.parseMoveOptions_ = function(options, hasCategories) {
|
||||
const move = options['move'] || {};
|
||||
const moveOptions = {};
|
||||
if (move['scrollbars'] === undefined && options['scrollbars'] === undefined) {
|
||||
moveOptions.scrollbars = hasCategories;
|
||||
} else if (typeof move['scrollbars'] === 'object') {
|
||||
moveOptions.scrollbars = {};
|
||||
moveOptions.scrollbars.horizontal = !!move['scrollbars']['horizontal'];
|
||||
moveOptions.scrollbars.vertical = !!move['scrollbars']['vertical'];
|
||||
// Convert scrollbars object to boolean if they have the same value.
|
||||
// This allows us to easily check for whether any scrollbars exist using
|
||||
// !!moveOptions.scrollbars.
|
||||
if (moveOptions.scrollbars.horizontal && moveOptions.scrollbars.vertical) {
|
||||
moveOptions.scrollbars = true;
|
||||
} else if (
|
||||
!moveOptions.scrollbars.horizontal &&
|
||||
!moveOptions.scrollbars.vertical) {
|
||||
moveOptions.scrollbars = false;
|
||||
}
|
||||
} else {
|
||||
moveOptions.scrollbars = !!move['scrollbars'] || !!options['scrollbars'];
|
||||
}
|
||||
|
||||
if (!moveOptions.scrollbars || move['wheel'] === undefined) {
|
||||
// Defaults to true if single-direction scroll is enabled.
|
||||
moveOptions.wheel = typeof moveOptions.scrollbars === 'object';
|
||||
} else {
|
||||
moveOptions.wheel = !!move['wheel'];
|
||||
}
|
||||
if (!moveOptions.scrollbars) {
|
||||
moveOptions.drag = false;
|
||||
} else if (move['drag'] === undefined) {
|
||||
// Defaults to true if scrollbars is true.
|
||||
moveOptions.drag = true;
|
||||
} else {
|
||||
moveOptions.drag = !!move['drag'];
|
||||
}
|
||||
return moveOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the user-specified zoom options, using reasonable defaults where
|
||||
* behaviour is unspecified. See zoom documentation:
|
||||
* https://developers.google.com/blockly/guides/configure/web/zoom
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Options.ZoomOptions} Normalized zoom options.
|
||||
* @private
|
||||
*/
|
||||
Options.parseZoomOptions_ = function(options) {
|
||||
const zoom = options['zoom'] || {};
|
||||
const zoomOptions = {};
|
||||
if (zoom['controls'] === undefined) {
|
||||
zoomOptions.controls = false;
|
||||
} else {
|
||||
zoomOptions.controls = !!zoom['controls'];
|
||||
}
|
||||
if (zoom['wheel'] === undefined) {
|
||||
zoomOptions.wheel = false;
|
||||
} else {
|
||||
zoomOptions.wheel = !!zoom['wheel'];
|
||||
}
|
||||
if (zoom['startScale'] === undefined) {
|
||||
zoomOptions.startScale = 1;
|
||||
} else {
|
||||
zoomOptions.startScale = Number(zoom['startScale']);
|
||||
}
|
||||
if (zoom['maxScale'] === undefined) {
|
||||
zoomOptions.maxScale = 3;
|
||||
} else {
|
||||
zoomOptions.maxScale = Number(zoom['maxScale']);
|
||||
}
|
||||
if (zoom['minScale'] === undefined) {
|
||||
zoomOptions.minScale = 0.3;
|
||||
} else {
|
||||
zoomOptions.minScale = Number(zoom['minScale']);
|
||||
}
|
||||
if (zoom['scaleSpeed'] === undefined) {
|
||||
zoomOptions.scaleSpeed = 1.2;
|
||||
} else {
|
||||
zoomOptions.scaleSpeed = Number(zoom['scaleSpeed']);
|
||||
}
|
||||
if (zoom['pinch'] === undefined) {
|
||||
zoomOptions.pinch = zoomOptions.wheel || zoomOptions.controls;
|
||||
} else {
|
||||
zoomOptions.pinch = !!zoom['pinch'];
|
||||
}
|
||||
return zoomOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the user-specified grid options, using reasonable defaults where
|
||||
* behaviour is unspecified. See grid documentation:
|
||||
* https://developers.google.com/blockly/guides/configure/web/grid
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Options.GridOptions} Normalized grid options.
|
||||
* @private
|
||||
*/
|
||||
Options.parseGridOptions_ = function(options) {
|
||||
const grid = options['grid'] || {};
|
||||
const gridOptions = {};
|
||||
gridOptions.spacing = Number(grid['spacing']) || 0;
|
||||
gridOptions.colour = grid['colour'] || '#888';
|
||||
gridOptions.length =
|
||||
(grid['length'] === undefined) ? 1 : Number(grid['length']);
|
||||
gridOptions.snap = gridOptions.spacing > 0 && !!grid['snap'];
|
||||
return gridOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the user-specified theme options, using the classic theme as a default.
|
||||
* https://developers.google.com/blockly/guides/configure/web/themes
|
||||
* @param {!Object} options Dictionary of options.
|
||||
* @return {!Theme} A Blockly Theme.
|
||||
* @private
|
||||
*/
|
||||
Options.parseThemeOptions_ = function(options) {
|
||||
const theme = options['theme'] || Classic;
|
||||
if (typeof theme === 'string') {
|
||||
return /** @type {!Theme} */ (
|
||||
registry.getObject(registry.Type.THEME, theme));
|
||||
} else if (theme instanceof Theme) {
|
||||
return /** @type {!Theme} */ (theme);
|
||||
}
|
||||
return Theme.defineTheme(
|
||||
theme.name || ('builtin' + idGenerator.getNextUniqueId()), theme);
|
||||
};
|
||||
|
||||
exports.Options = Options;
|
||||
|
||||
@@ -25,6 +25,8 @@ const {Blocks} = goog.require('Blockly.blocks');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BubbleOpen} = goog.requireType('Blockly.Events.BubbleOpen');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Field} = goog.requireType('Blockly.Field');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Names} = goog.require('Blockly.Names');
|
||||
@@ -325,12 +327,16 @@ const updateMutatorFlyout = function(workspace) {
|
||||
* @package
|
||||
*/
|
||||
const mutatorOpenListener = function(e) {
|
||||
if (!(e.type === eventUtils.BUBBLE_OPEN && e.bubbleType === 'mutator' &&
|
||||
e.isOpen)) {
|
||||
if (e.type !== eventUtils.BUBBLE_OPEN) {
|
||||
return;
|
||||
}
|
||||
const workspaceId = /** @type {string} */ (e.workspaceId);
|
||||
const block = Workspace.getById(workspaceId).getBlockById(e.blockId);
|
||||
const bubbleEvent = /** @type {!BubbleOpen} */ (e);
|
||||
if (!(bubbleEvent.bubbleType === 'mutator' && bubbleEvent.isOpen)) {
|
||||
return;
|
||||
}
|
||||
const workspaceId = /** @type {string} */ (bubbleEvent.workspaceId);
|
||||
const block =
|
||||
Workspace.getById(workspaceId).getBlockById(bubbleEvent.blockId);
|
||||
const type = block.type;
|
||||
if (type !== 'procedures_defnoreturn' && type !== 'procedures_defreturn') {
|
||||
return;
|
||||
|
||||
@@ -26,6 +26,8 @@ const uiPosition = goog.require('Blockly.uiPosition');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Abstract} = goog.requireType('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDelete} = goog.requireType('Blockly.Events.BlockDelete');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions');
|
||||
const {ComponentManager} = goog.require('Blockly.ComponentManager');
|
||||
const {DeleteArea} = goog.require('Blockly.DeleteArea');
|
||||
@@ -607,11 +609,13 @@ class Trashcan extends DeleteArea {
|
||||
* @private
|
||||
*/
|
||||
onDelete_(event) {
|
||||
if (this.workspace_.options.maxTrashcanContents <= 0) {
|
||||
if (this.workspace_.options.maxTrashcanContents <= 0 ||
|
||||
event.type !== eventUtils.BLOCK_DELETE) {
|
||||
return;
|
||||
}
|
||||
if (event.type === eventUtils.BLOCK_DELETE && !event.wasShadow) {
|
||||
const cleanedJson = this.cleanBlockJson_(event.oldJson);
|
||||
const deleteEvent = /** @type {!BlockDelete} */ (event);
|
||||
if (event.type === eventUtils.BLOCK_DELETE && !deleteEvent.wasShadow) {
|
||||
const cleanedJson = this.cleanBlockJson_(deleteEvent.oldJson);
|
||||
if (this.contents_.indexOf(cleanedJson) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ const idGenerator = goog.require('Blockly.utils.idGenerator');
|
||||
const xml = goog.require('Blockly.utils.xml');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentMove} = goog.require('Blockly.Events.CommentMove');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentChange');
|
||||
@@ -27,8 +29,6 @@ goog.require('Blockly.Events.CommentChange');
|
||||
goog.require('Blockly.Events.CommentCreate');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentDelete');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
|
||||
|
||||
/**
|
||||
@@ -201,7 +201,8 @@ class WorkspaceComment {
|
||||
* @package
|
||||
*/
|
||||
moveBy(dx, dy) {
|
||||
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(this);
|
||||
const event = /** @type {!CommentMove} */ (
|
||||
new (eventUtils.get(eventUtils.COMMENT_MOVE))(this));
|
||||
this.xy_.translate(dx, dy);
|
||||
event.recordNew();
|
||||
eventUtils.fire(event);
|
||||
|
||||
@@ -25,6 +25,8 @@ const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const svgMath = goog.require('Blockly.utils.svgMath');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentMove} = goog.require('Blockly.Events.CommentMove');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IBoundedElement} = goog.require('Blockly.IBoundedElement');
|
||||
@@ -42,8 +44,6 @@ goog.require('Blockly.Events.CommentCreate');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentDelete');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.Selected');
|
||||
|
||||
|
||||
@@ -427,7 +427,8 @@ class WorkspaceCommentSvg extends WorkspaceComment {
|
||||
* @package
|
||||
*/
|
||||
moveBy(dx, dy) {
|
||||
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(this);
|
||||
const event = /** @type {!CommentMove} */ (
|
||||
new (eventUtils.get(eventUtils.COMMENT_MOVE))(this));
|
||||
// TODO: Do I need to look up the relative to surface XY position here?
|
||||
const xy = this.getRelativeToSurfaceXY();
|
||||
this.translate(xy.x + dx, xy.y + dy);
|
||||
|
||||
@@ -66,7 +66,7 @@ goog.addDependency('../../core/events/events_var_delete.js', ['Blockly.Events.Va
|
||||
goog.addDependency('../../core/events/events_var_rename.js', ['Blockly.Events.VarRename'], ['Blockly.Events.VarBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/events/events_viewport.js', ['Blockly.Events.ViewportChange'], ['Blockly.Events.UiBase', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/events/utils.js', ['Blockly.Events.utils'], ['Blockly.registry', 'Blockly.utils.idGenerator'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/events/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events.Abstract', 'Blockly.Events.utils', 'Blockly.registry'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils.parsing'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.DropDownDiv', 'Blockly.Events.BlockChange', 'Blockly.Events.utils', 'Blockly.Gesture', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IKeyboardAccessible', 'Blockly.IRegistrable', 'Blockly.MarkerManager', 'Blockly.Tooltip', 'Blockly.WidgetDiv', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.parsing', 'Blockly.utils.style', 'Blockly.utils.userAgent', 'Blockly.utils.xml'], {'lang': 'es6', 'module': 'goog'});
|
||||
goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.WidgetDiv', 'Blockly.browserEvents', 'Blockly.fieldRegistry', 'Blockly.utils.KeyCodes', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {'lang': 'es6', 'module': 'goog'});
|
||||
|
||||
Reference in New Issue
Block a user