mirror of
https://github.com/google/blockly.git
synced 2026-01-11 10:57:07 +01:00
chore: Merge branch develop into no-checkin-built
Files that have had manual merge conflict resolution performed: - gulpfile.js Files where a merge conflic was resolved by deleting the file, because it had previously been deleted in no-checkin-built: - *_compresed.js* - msg/js/*.js - tests/run_all_tests.sh Changes made (in no-checkin-built) to the following files that have been deleted or obsoleted will need to be dealt with manually; this will be done in a separate commit for clarity: - tests/run_all_tests.sh - tests/scripts/check_metadata.sh - tests/scripts/run_generators.sh
This commit is contained in:
@@ -160,7 +160,6 @@
|
||||
"jsdoc/check-param-names": ["off", {"checkDestructured": false}],
|
||||
// Allow any text in the license tag. Other checks are not relevant.
|
||||
"jsdoc/check-values": ["off"]
|
||||
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@@ -29,9 +29,9 @@ All submissions, including submissions by project members, require review. We
|
||||
use Github pull requests for this purpose.
|
||||
|
||||
### Browser compatibility
|
||||
We care strongly about making Blockly work on all browsers. As of 2022 we
|
||||
We care strongly about making Blockly work on all browsers. As of 2022 we
|
||||
support Edge, Chrome, Safari, and Firefox. We will not accept changes that only
|
||||
work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
|
||||
work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
|
||||
for compatibility information.
|
||||
|
||||
### The small print
|
||||
|
||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -35,11 +35,11 @@
|
||||
|
||||
### Test Coverage
|
||||
|
||||
<!-- TODO: Please show how you have added tests to cover your changes,
|
||||
- or tell us how you tested it. If your change includes
|
||||
- browser-specific behaviour, include information about the
|
||||
- browser and device that you used for testing.
|
||||
-->
|
||||
<!-- TODO: Please create unit tests, and explain here how they cover
|
||||
your changes, or tell us how you tested it manually. If
|
||||
your changes include browser-specific behaviour, include
|
||||
information about the browser and device that you used for
|
||||
testing. -->
|
||||
|
||||
### Documentation
|
||||
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -22,7 +22,7 @@ updates:
|
||||
- "PR: chore"
|
||||
- "PR: dependencies"
|
||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||
directory: "/"
|
||||
directory: "/"
|
||||
target-branch: "develop"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
7
.github/release-please.yml
vendored
7
.github/release-please.yml
vendored
@@ -1,7 +0,0 @@
|
||||
primaryBranch: develop
|
||||
releaseType: node
|
||||
packageName: blockly
|
||||
manifest: true
|
||||
manifestConfig: release-please-config.json
|
||||
manifestFile: .release-please-manifest.json
|
||||
handleGHRelease: true
|
||||
2
.github/workflows/appengine_deploy.yml
vendored
2
.github/workflows/appengine_deploy.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
path: _deploy/
|
||||
|
||||
- name: Deploy to App Engine
|
||||
uses: google-github-actions/deploy-appengine@v0.8.0
|
||||
uses: google-github-actions/deploy-appengine@v0.8.2
|
||||
# For parameters see:
|
||||
# https://github.com/google-github-actions/deploy-appengine#inputs
|
||||
with:
|
||||
|
||||
12
.github/workflows/conventional-label.yml
vendored
Normal file
12
.github/workflows/conventional-label.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, edited ]
|
||||
name: conventional-release-labels
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: bcoe/conventional-release-labels@v1
|
||||
with:
|
||||
type_labels: '{"feat": "PR: feature", "fix": "PR: fix", "breaking": "breaking change", "chore": "PR: chore", "docs": "PR: docs", "refactor": "PR: refactor"}'
|
||||
ignored_types: '[]'
|
||||
4
.github/workflows/tag_module_cleanup.yml
vendored
4
.github/workflows/tag_module_cleanup.yml
vendored
@@ -5,14 +5,14 @@ name: Tag module cleanup
|
||||
|
||||
# Trigger on pull requests against goog_module branch only
|
||||
# Uses pull_request_target to get write permissions so that it can write labels.
|
||||
on:
|
||||
on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- goog_module
|
||||
|
||||
jobs:
|
||||
tag-module-cleanup:
|
||||
|
||||
|
||||
# Add the type: cleanup label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
4
.github/workflows/update_metadata.yml
vendored
4
.github/workflows/update_metadata.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write # for peter-evans/create-pull-request to create branch
|
||||
pull-requests: write # for peter-evans/create-pull-request to create a PR
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check Out Blockly
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: source ./tests/scripts/update_metadata.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d
|
||||
uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7
|
||||
with:
|
||||
commit-message: Update build artifact sizes in check_metadata.sh
|
||||
delete-branch: true
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
".": "8.0.0"
|
||||
}
|
||||
@@ -379,11 +379,6 @@
|
||||
},
|
||||
"tsdoc-escape-right-brace": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
|
||||
// Needs to be fixed
|
||||
"tsdoc-missing-deprecation-message": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ blocks['lists_create_with'] = {
|
||||
this.itemCount_ = 3;
|
||||
this.updateShape_();
|
||||
this.setOutput(true, 'Array');
|
||||
this.setMutator(new Mutator(['lists_create_with_item']));
|
||||
this.setMutator(new Mutator(['lists_create_with_item'], this));
|
||||
this.setTooltip(Msg['LISTS_CREATE_WITH_TOOLTIP']);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -150,8 +150,7 @@ const PROCEDURE_DEF_COMMON = {
|
||||
this.argumentVarModels_.push(variable);
|
||||
} else {
|
||||
console.log(
|
||||
'Failed to create a variable with name ' + varName +
|
||||
', ignoring.');
|
||||
`Failed to create a variable named "${varName}", ignoring.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,7 +460,7 @@ blocks['procedures_defnoreturn'] = {
|
||||
.appendField(Msg['PROCEDURES_DEFNORETURN_TITLE'])
|
||||
.appendField(nameField, 'NAME')
|
||||
.appendField('', 'PARAMS');
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
@@ -507,7 +506,7 @@ blocks['procedures_defreturn'] = {
|
||||
this.appendValueInput('RETURN')
|
||||
.setAlign(Align.RIGHT)
|
||||
.appendField(Msg['PROCEDURES_DEFRETURN_RETURN']);
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
|
||||
@@ -864,7 +864,7 @@ const TEXT_JOIN_EXTENSION = function() {
|
||||
this.itemCount_ = 2;
|
||||
this.updateShape_();
|
||||
// Configure the mutator UI.
|
||||
this.setMutator(new Mutator(['text_create_join_item']));
|
||||
this.setMutator(new Mutator(['text_create_join_item'], this));
|
||||
};
|
||||
|
||||
// Update the tooltip of 'text_append' block to reference the variable.
|
||||
|
||||
@@ -23,7 +23,7 @@ var goog = goog || {};
|
||||
/**
|
||||
* Reference to the global object. This is provided as 'root' by the
|
||||
* UMD wrapper, but prefer globalThis if it is defined.
|
||||
*
|
||||
*
|
||||
* https://www.ecma-international.org/ecma-262/9.0/index.html#sec-global-object
|
||||
*
|
||||
* @const
|
||||
|
||||
@@ -180,6 +180,11 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
protected collapsed_ = false;
|
||||
protected outputShape_: number|null = null;
|
||||
|
||||
/**
|
||||
* Is the current block currently in the process of being disposed?
|
||||
*/
|
||||
private disposing = false;
|
||||
|
||||
/**
|
||||
* A string representing the comment attached to this block.
|
||||
*
|
||||
@@ -316,7 +321,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
dispose(healStack: boolean) {
|
||||
if (this.disposed) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,6 +343,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
this.workspace.removeTypedBlock(this);
|
||||
// Remove from block database.
|
||||
this.workspace.removeBlockById(this.id);
|
||||
this.disposing = true;
|
||||
|
||||
// First, dispose of all my children.
|
||||
for (let i = this.childBlocks_.length - 1; i >= 0; i--) {
|
||||
@@ -360,6 +366,16 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the block is either in the process of being disposed, or
|
||||
* is disposed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
isDeadOrDying(): boolean {
|
||||
return this.disposing || this.disposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call initModel on all fields on the block.
|
||||
* May be called more than once.
|
||||
@@ -772,7 +788,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @returns True if deletable.
|
||||
*/
|
||||
isDeletable(): boolean {
|
||||
return this.deletable_ && !this.isShadow_ && !this.disposed &&
|
||||
return this.deletable_ && !this.isShadow_ && !this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
@@ -791,7 +807,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @returns True if movable.
|
||||
*/
|
||||
isMovable(): boolean {
|
||||
return this.movable_ && !this.isShadow_ && !this.disposed &&
|
||||
return this.movable_ && !this.isShadow_ && !this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
@@ -865,7 +881,8 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @returns True if editable.
|
||||
*/
|
||||
isEditable(): boolean {
|
||||
return this.editable_ && !this.disposed && !this.workspace.options.readOnly;
|
||||
return this.editable_ && !this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -188,7 +188,11 @@ function disconnectUiStep(group: SVGElement, magnitude: number, start: Date) {
|
||||
skew = `skewX(${val})`;
|
||||
disconnectPid = setTimeout(disconnectUiStep, 10, group, magnitude, start);
|
||||
}
|
||||
group.setAttribute('transform', skew);
|
||||
(group as AnyDuringMigration).skew_ = skew;
|
||||
group.setAttribute(
|
||||
'transform',
|
||||
(group as AnyDuringMigration).translate_ +
|
||||
(group as AnyDuringMigration).skew_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,7 +206,9 @@ export function disconnectUiStop() {
|
||||
if (disconnectPid) {
|
||||
clearTimeout(disconnectPid);
|
||||
}
|
||||
disconnectGroup.setAttribute('transform', '');
|
||||
const group = disconnectGroup;
|
||||
(group as AnyDuringMigration).skew_ = '';
|
||||
group.setAttribute('transform', (group as AnyDuringMigration).translate_);
|
||||
disconnectGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,39 +31,40 @@ import * as svgMath from './utils/svg_math.js';
|
||||
* @alias Blockly.BlockDragSurfaceSvg
|
||||
*/
|
||||
export class BlockDragSurfaceSvg {
|
||||
/** The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom. */
|
||||
private svg_: SVGElement;
|
||||
/**
|
||||
* The root element of the drag surface.
|
||||
*/
|
||||
private svg: SVGElement;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag
|
||||
* surface is enabled.
|
||||
*/
|
||||
private dragGroup_: SVGElement;
|
||||
private dragGroup: SVGElement;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
*/
|
||||
private scale_ = 1;
|
||||
private scale = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
*/
|
||||
private surfaceXY_: Coordinate = new Coordinate(0, 0);
|
||||
private readonly childSurfaceXY_: Coordinate;
|
||||
private surfaceXY = new Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the child drag surface in pixel
|
||||
* units. Since the child drag surface tracks the translation of the
|
||||
* workspace this is ultimately the translation of the workspace.
|
||||
*/
|
||||
private readonly childSurfaceXY = new Coordinate(0, 0);
|
||||
|
||||
/** @param container Containing element. */
|
||||
constructor(private readonly container: Element) {
|
||||
/**
|
||||
* Cached value for the translation of the child drag surface in pixel
|
||||
* units. Since the child drag surface tracks the translation of the
|
||||
* workspace this is ultimately the translation of the workspace.
|
||||
*/
|
||||
this.childSurfaceXY_ = new Coordinate(0, 0);
|
||||
|
||||
this.svg_ = dom.createSvgElement(
|
||||
this.svg = dom.createSvgElement(
|
||||
Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
@@ -72,10 +73,15 @@ export class BlockDragSurfaceSvg {
|
||||
'class': 'blocklyBlockDragSurface',
|
||||
},
|
||||
this.container);
|
||||
this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.svg_ as SVGElement);
|
||||
|
||||
this.dragGroup = dom.createSvgElement(Svg.G, {}, this.svg);
|
||||
}
|
||||
|
||||
/** Create the drag surface and inject it into the container. */
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*
|
||||
* @deprecated The DOM is automatically created by the constructor.
|
||||
*/
|
||||
createDom() {
|
||||
// No alternative provided, because now the dom is just automatically
|
||||
// created in the constructor now.
|
||||
@@ -89,13 +95,13 @@ export class BlockDragSurfaceSvg {
|
||||
* @param blocks Block or group of blocks to place on the drag surface.
|
||||
*/
|
||||
setBlocksAndShow(blocks: SVGElement) {
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
if (this.dragGroup.childNodes.length) {
|
||||
throw Error('Already dragging a block.');
|
||||
}
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.svg_.style.display = 'block';
|
||||
this.surfaceXY_ = new Coordinate(0, 0);
|
||||
this.dragGroup.appendChild(blocks);
|
||||
this.svg.style.display = 'block';
|
||||
this.surfaceXY = new Coordinate(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,13 +113,13 @@ export class BlockDragSurfaceSvg {
|
||||
* @param scale Scale of the group.
|
||||
*/
|
||||
translateAndScaleGroup(x: number, y: number, scale: number) {
|
||||
this.scale_ = scale;
|
||||
this.scale = scale;
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
const roundX = Math.round(x);
|
||||
const roundY = Math.round(y);
|
||||
this.childSurfaceXY_.x = roundX;
|
||||
this.childSurfaceXY_.y = roundY;
|
||||
this.dragGroup_!.setAttribute(
|
||||
this.childSurfaceXY.x = roundX;
|
||||
this.childSurfaceXY.y = roundY;
|
||||
this.dragGroup.setAttribute(
|
||||
'transform',
|
||||
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
|
||||
}
|
||||
@@ -124,13 +130,11 @@ export class BlockDragSurfaceSvg {
|
||||
* @internal
|
||||
*/
|
||||
translateSurfaceInternal_() {
|
||||
let x = this.surfaceXY_!.x;
|
||||
let y = this.surfaceXY_!.y;
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
this.svg_.style.display = 'block';
|
||||
dom.setCssTransform(this.svg_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||
const x = Math.round(this.surfaceXY.x);
|
||||
const y = Math.round(this.surfaceXY.y);
|
||||
this.svg.style.display = 'block';
|
||||
dom.setCssTransform(this.svg, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,9 +144,9 @@ export class BlockDragSurfaceSvg {
|
||||
* @param deltaY Vertical offset in pixel units.
|
||||
*/
|
||||
translateBy(deltaX: number, deltaY: number) {
|
||||
const x = this.surfaceXY_.x + deltaX;
|
||||
const y = this.surfaceXY_.y + deltaY;
|
||||
this.surfaceXY_ = new Coordinate(x, y);
|
||||
const x = this.surfaceXY.x + deltaX;
|
||||
const y = this.surfaceXY.y + deltaY;
|
||||
this.surfaceXY = new Coordinate(x, y);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
@@ -156,7 +160,7 @@ export class BlockDragSurfaceSvg {
|
||||
* @param y Y translation for the entire surface.
|
||||
*/
|
||||
translateSurface(x: number, y: number) {
|
||||
this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.surfaceXY = new Coordinate(x * this.scale, y * this.scale);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
@@ -167,8 +171,8 @@ export class BlockDragSurfaceSvg {
|
||||
* @returns Current translation of the surface.
|
||||
*/
|
||||
getSurfaceTranslation(): Coordinate {
|
||||
const xy = svgMath.getRelativeXY(this.svg_ as SVGElement);
|
||||
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
const xy = svgMath.getRelativeXY(this.svg);
|
||||
return new Coordinate(xy.x / this.scale, xy.y / this.scale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,8 +181,8 @@ export class BlockDragSurfaceSvg {
|
||||
*
|
||||
* @returns Drag surface group element.
|
||||
*/
|
||||
getGroup(): SVGElement|null {
|
||||
return this.dragGroup_;
|
||||
getGroup(): SVGElement {
|
||||
return this.dragGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,8 +190,8 @@ export class BlockDragSurfaceSvg {
|
||||
*
|
||||
* @returns The SVG drag surface.
|
||||
*/
|
||||
getSvgRoot(): SVGElement|null {
|
||||
return this.svg_;
|
||||
getSvgRoot(): SVGElement {
|
||||
return this.svg;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +201,7 @@ export class BlockDragSurfaceSvg {
|
||||
* @returns Drag surface block DOM element, or null if no blocks exist.
|
||||
*/
|
||||
getCurrentBlock(): Element|null {
|
||||
return this.dragGroup_.firstChild as Element;
|
||||
return this.dragGroup.firstChild as Element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +213,7 @@ export class BlockDragSurfaceSvg {
|
||||
*/
|
||||
getWsTranslation(): Coordinate {
|
||||
// Returning a copy so the coordinate can not be changed outside this class.
|
||||
return this.childSurfaceXY_.clone();
|
||||
return this.childSurfaceXY.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,16 +230,16 @@ export class BlockDragSurfaceSvg {
|
||||
const currentBlockElement = this.getCurrentBlock();
|
||||
if (currentBlockElement) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
// appendChild removes the node from this.dragGroup
|
||||
opt_newSurface.appendChild(currentBlockElement);
|
||||
} else {
|
||||
this.dragGroup_.removeChild(currentBlockElement);
|
||||
this.dragGroup.removeChild(currentBlockElement);
|
||||
}
|
||||
}
|
||||
this.svg_.style.display = 'none';
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
this.svg.style.display = 'none';
|
||||
if (this.dragGroup.childNodes.length) {
|
||||
throw Error('Drag group was not cleared.');
|
||||
}
|
||||
this.surfaceXY_ = new Coordinate(0, 0);
|
||||
this.surfaceXY = new Coordinate(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns #RRGGBB string.
|
||||
*/
|
||||
getColourSecondary(): string|null {
|
||||
getColourSecondary(): string|undefined {
|
||||
return this.style.colourSecondary;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns #RRGGBB string.
|
||||
*/
|
||||
getColourTertiary(): string|null {
|
||||
getColourTertiary(): string|undefined {
|
||||
return this.style.colourTertiary;
|
||||
}
|
||||
|
||||
@@ -525,7 +525,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
|
||||
/** Snap this block to the nearest grid point. */
|
||||
snapToGrid() {
|
||||
if (this.disposed) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (this.workspace.isDragging()) {
|
||||
@@ -778,10 +778,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
(group as AnyDuringMigration).translate_ = '';
|
||||
(group as AnyDuringMigration).skew_ = '';
|
||||
common.draggingConnections.push(...this.getConnections_(true));
|
||||
this.svgGroup_.classList.add('blocklyDragging');
|
||||
dom.addClass(this.svgGroup_, 'blocklyDragging');
|
||||
} else {
|
||||
common.draggingConnections.length = 0;
|
||||
this.svgGroup_.classList.remove('blocklyDragging');
|
||||
dom.removeClass(this.svgGroup_, 'blocklyDragging');
|
||||
}
|
||||
// Recurse through all blocks attached under this one.
|
||||
for (let i = 0; i < this.childBlocks_.length; i++) {
|
||||
@@ -861,8 +861,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
override dispose(healStack?: boolean, animate?: boolean) {
|
||||
if (this.disposed) {
|
||||
// The block has already been deleted.
|
||||
if (this.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
Tooltip.dispose();
|
||||
@@ -981,8 +980,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the color of the block (and children) to match the current disabled
|
||||
* state.
|
||||
* Updates the colour of the block (and children) to match the current
|
||||
* disabled state.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@@ -1067,13 +1066,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
if (this.workspace.isDragging()) {
|
||||
// Don't change the warning text during a drag.
|
||||
// Wait until the drag finishes.
|
||||
this.warningTextDb.set(
|
||||
id, setTimeout(() => {
|
||||
if (!this.disposed) { // Check block wasn't deleted.
|
||||
this.warningTextDb.delete(id);
|
||||
this.setWarningText(text, id);
|
||||
}
|
||||
}, 100));
|
||||
this.warningTextDb.set(id, setTimeout(() => {
|
||||
if (!this.isDeadOrDying()) {
|
||||
this.warningTextDb.delete(id);
|
||||
this.setWarningText(text, id);
|
||||
}
|
||||
}, 100));
|
||||
return;
|
||||
}
|
||||
if (this.isInFlyout) {
|
||||
@@ -1537,45 +1535,46 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Bump unconnected blocks out of alignment. Two blocks which aren't actually
|
||||
* connected should not coincidentally line up on screen.
|
||||
* Bumps unconnected blocks out of alignment.
|
||||
*
|
||||
* Two blocks which aren't actually connected should not coincidentally line
|
||||
* up on screen, because that creates confusion for end-users.
|
||||
*/
|
||||
override bumpNeighbours() {
|
||||
if (this.disposed) {
|
||||
return; // Deleted block.
|
||||
}
|
||||
if (this.workspace.isDragging()) {
|
||||
return; // Don't bump blocks during a drag.
|
||||
}
|
||||
const rootBlock = this.getRootBlock();
|
||||
if (rootBlock.isInFlyout) {
|
||||
this.getRootBlock().bumpNeighboursInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bumps unconnected blocks out of alignment.
|
||||
*/
|
||||
private bumpNeighboursInternal() {
|
||||
const root = this.getRootBlock();
|
||||
if (this.isDeadOrDying() || this.workspace.isDragging() ||
|
||||
root.isInFlyout) {
|
||||
return;
|
||||
}
|
||||
// Don't move blocks around in a flyout.
|
||||
// Loop through every connection on this block.
|
||||
const myConnections = this.getConnections_(false);
|
||||
for (let i = 0, connection; connection = myConnections[i]; i++) {
|
||||
const renderedConn = (connection);
|
||||
// Spider down from this block bumping all sub-blocks.
|
||||
if (renderedConn.isConnected() && renderedConn.isSuperior()) {
|
||||
renderedConn.targetBlock()!.bumpNeighbours();
|
||||
|
||||
function neighbourIsInStack(neighbour: RenderedConnection) {
|
||||
return neighbour.getSourceBlock().getRootBlock() === root;
|
||||
}
|
||||
|
||||
for (const conn of this.getConnections_(false)) {
|
||||
if (conn.isSuperior()) {
|
||||
// Recurse down the block stack.
|
||||
conn.targetBlock()?.bumpNeighboursInternal();
|
||||
}
|
||||
|
||||
const neighbours = connection.neighbours(config.snapRadius);
|
||||
for (let j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
|
||||
const renderedOther = otherConnection as RenderedConnection;
|
||||
// If both connections are connected, that's probably fine. But if
|
||||
// either one of them is unconnected, then there could be confusion.
|
||||
if (!renderedConn.isConnected() || !renderedOther.isConnected()) {
|
||||
// Only bump blocks if they are from different tree structures.
|
||||
if (renderedOther.getSourceBlock().getRootBlock() !== rootBlock) {
|
||||
// Always bump the inferior block.
|
||||
if (renderedConn.isSuperior()) {
|
||||
renderedOther.bumpAwayFrom(renderedConn);
|
||||
} else {
|
||||
renderedConn.bumpAwayFrom(renderedOther);
|
||||
}
|
||||
}
|
||||
for (const neighbour of conn.neighbours(config.snapRadius)) {
|
||||
// Don't bump away from things that are in our stack.
|
||||
if (neighbourIsInStack(neighbour)) continue;
|
||||
// If both connections are connected, that's fine.
|
||||
if (conn.isConnected() && neighbour.isConnected()) continue;
|
||||
|
||||
// Always bump the inferior connection.
|
||||
if (conn.isSuperior()) {
|
||||
neighbour.bumpAwayFrom(conn);
|
||||
} else {
|
||||
conn.bumpAwayFrom(neighbour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ import {Field} from './field.js';
|
||||
import {FieldAngle} from './field_angle.js';
|
||||
import {FieldCheckbox} from './field_checkbox.js';
|
||||
import {FieldColour} from './field_colour.js';
|
||||
import {FieldDropdown} from './field_dropdown.js';
|
||||
import {FieldDropdown, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js';
|
||||
import {FieldImage} from './field_image.js';
|
||||
import {FieldLabel} from './field_label.js';
|
||||
import {FieldLabelSerializable} from './field_label_serializable.js';
|
||||
@@ -71,7 +71,7 @@ import {FlyoutButton} from './flyout_button.js';
|
||||
import {HorizontalFlyout} from './flyout_horizontal.js';
|
||||
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
||||
import {VerticalFlyout} from './flyout_vertical.js';
|
||||
import {Generator} from './generator.js';
|
||||
import {CodeGenerator} from './generator.js';
|
||||
import {Gesture} from './gesture.js';
|
||||
import {Grid} from './grid.js';
|
||||
import {Icon} from './icon.js';
|
||||
@@ -104,7 +104,6 @@ import {IRegistrable} from './interfaces/i_registrable.js';
|
||||
import {IRegistrableField} from './interfaces/i_registrable_field.js';
|
||||
import {ISelectable} from './interfaces/i_selectable.js';
|
||||
import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js';
|
||||
import {ISerializer as SerializerInterface} from './interfaces/i_serializer.js';
|
||||
import {IStyleable} from './interfaces/i_styleable.js';
|
||||
import {IToolbox} from './interfaces/i_toolbox.js';
|
||||
import {IToolboxItem} from './interfaces/i_toolbox_item.js';
|
||||
@@ -118,7 +117,7 @@ import {MarkerManager} from './marker_manager.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
import {MetricsManager} from './metrics_manager.js';
|
||||
import {Msg} from './msg.js';
|
||||
import {Msg, setLocale} from './msg.js';
|
||||
import {Mutator} from './mutator.js';
|
||||
import {Names} from './names.js';
|
||||
import {Options} from './options.js';
|
||||
@@ -134,12 +133,7 @@ import * as thrasos from './renderers/thrasos/thrasos.js';
|
||||
import * as zelos from './renderers/zelos/zelos.js';
|
||||
import {Scrollbar} from './scrollbar.js';
|
||||
import {ScrollbarPair} from './scrollbar_pair.js';
|
||||
import * as serializationBlocks from './serialization/blocks.js';
|
||||
import * as serializationExceptions from './serialization/exceptions.js';
|
||||
import * as serializationPriorities from './serialization/priorities.js';
|
||||
import * as serializationRegistry from './serialization/registry.js';
|
||||
import * as serializationVariables from './serialization/variables.js';
|
||||
import * as serializationWorkspaces from './serialization/workspaces.js';
|
||||
import * as serialization from './serialization.js';
|
||||
import * as ShortcutItems from './shortcut_items.js';
|
||||
import {ShortcutRegistry} from './shortcut_registry.js';
|
||||
import {Theme} from './theme.js';
|
||||
@@ -157,7 +151,6 @@ import {Trashcan} from './trashcan.js';
|
||||
import * as utils from './utils.js';
|
||||
import * as colour from './utils/colour.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
import * as toolbox from './utils/toolbox.js';
|
||||
import {VariableMap} from './variable_map.js';
|
||||
import {VariableModel} from './variable_model.js';
|
||||
@@ -345,23 +338,12 @@ export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
||||
*/
|
||||
export const setParentContainer = common.setParentContainer;
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the specified SVG image.
|
||||
*
|
||||
* @param svg SVG image.
|
||||
* @returns Contains width and height properties.
|
||||
* @deprecated Use workspace.setCachedParentSvgSize. (2021 March 5)
|
||||
* @see Blockly.WorkspaceSvg.setCachedParentSvgSize
|
||||
* @alias Blockly.svgSize
|
||||
*/
|
||||
export const svgSize = svgMath.svgSize;
|
||||
|
||||
/**
|
||||
* Size the workspace when the contents change. This also updates
|
||||
* scrollbars accordingly.
|
||||
*
|
||||
* @param workspace The workspace to resize.
|
||||
* @deprecated Use workspace.resizeContents. (2021 December)
|
||||
* @deprecated Use **workspace.resizeContents** instead.
|
||||
* @see Blockly.WorkspaceSvg.resizeContents
|
||||
* @alias Blockly.resizeSvgContents
|
||||
*/
|
||||
@@ -377,7 +359,7 @@ export const resizeSvgContents = resizeSvgContentsLocal;
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
*
|
||||
* @param toCopy Block or Workspace Comment to be copied.
|
||||
* @deprecated Use Blockly.clipboard.copy(). (2021 December)
|
||||
* @deprecated Use **Blockly.clipboard.copy** instead.
|
||||
* @see Blockly.clipboard.copy
|
||||
* @alias Blockly.copy
|
||||
*/
|
||||
@@ -392,7 +374,7 @@ export function copy(toCopy: ICopyable) {
|
||||
* Paste a block or workspace comment on to the main workspace.
|
||||
*
|
||||
* @returns True if the paste was successful, false otherwise.
|
||||
* @deprecated Use Blockly.clipboard.paste(). (2021 December)
|
||||
* @deprecated Use **Blockly.clipboard.paste** instead.
|
||||
* @see Blockly.clipboard.paste
|
||||
* @alias Blockly.paste
|
||||
*/
|
||||
@@ -407,7 +389,7 @@ export function paste(): boolean {
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
*
|
||||
* @param toDuplicate Block or Workspace Comment to be copied.
|
||||
* @deprecated Use Blockly.clipboard.duplicate(). (2021 December)
|
||||
* @deprecated Use **Blockly.clipboard.duplicate** instead.
|
||||
* @see Blockly.clipboard.duplicate
|
||||
* @alias Blockly.duplicate
|
||||
*/
|
||||
@@ -423,7 +405,7 @@ export function duplicate(toDuplicate: ICopyable) {
|
||||
*
|
||||
* @param str Input string.
|
||||
* @returns True if number, false otherwise.
|
||||
* @deprecated Use Blockly.utils.string.isNumber(str). (2021 December)
|
||||
* @deprecated Use **Blockly.utils.string.isNumber** instead.
|
||||
* @see Blockly.utils.string.isNumber
|
||||
* @alias Blockly.isNumber
|
||||
*/
|
||||
@@ -439,7 +421,7 @@ export function isNumber(str: string): boolean {
|
||||
*
|
||||
* @param hue Hue on a colour wheel (0-360).
|
||||
* @returns RGB code, e.g. '#5ba65b'.
|
||||
* @deprecated Use Blockly.utils.colour.hueToHex(). (2021 December)
|
||||
* @deprecated Use **Blockly.utils.colour.hueToHex** instead.
|
||||
* @see Blockly.utils.colour.hueToHex
|
||||
* @alias Blockly.hueToHex
|
||||
*/
|
||||
@@ -461,7 +443,7 @@ export function hueToHex(hue: number): string {
|
||||
* @param thisObject The value of 'this' in the function.
|
||||
* @param func Function to call when event is triggered.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @deprecated Use Blockly.browserEvents.bind(). (December 2021)
|
||||
* @deprecated Use **Blockly.browserEvents.bind** instead.
|
||||
* @see Blockly.browserEvents.bind
|
||||
* @alias Blockly.bindEvent_
|
||||
*/
|
||||
@@ -480,7 +462,7 @@ export function bindEvent_(
|
||||
* @param bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @returns The function call.
|
||||
* @deprecated Use Blockly.browserEvents.unbind(). (December 2021)
|
||||
* @deprecated Use **Blockly.browserEvents.unbind** instead.
|
||||
* @see browserEvents.unbind
|
||||
* @alias Blockly.unbindEvent_
|
||||
*/
|
||||
@@ -508,7 +490,7 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
|
||||
* the default handler. False by default. If opt_noPreventDefault is
|
||||
* provided, opt_noCaptureIdentifier must also be provided.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @deprecated Use Blockly.browserEvents.conditionalBind(). (December 2021)
|
||||
* @deprecated Use **Blockly.browserEvents.conditionalBind** instead.
|
||||
* @see browserEvents.conditionalBind
|
||||
* @alias Blockly.bindEventWithChecks_
|
||||
*/
|
||||
@@ -560,6 +542,7 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
|
||||
*/
|
||||
export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME;
|
||||
|
||||
|
||||
// Context for why we need to monkey-patch in these functions (internal):
|
||||
// https://docs.google.com/document/d/1MbO0LEA-pAyx1ErGLJnyUqTLrcYTo-5zga9qplnxeXo/edit?usp=sharing&resourcekey=0-5h_32-i-dHwHjf_9KYEVKg
|
||||
|
||||
@@ -584,12 +567,12 @@ WorkspaceCommentSvg.prototype.showContextMenu =
|
||||
return;
|
||||
}
|
||||
const menuOptions = [];
|
||||
|
||||
|
||||
if (this.isDeletable() && this.isMovable()) {
|
||||
menuOptions.push(ContextMenu.commentDuplicateOption(this));
|
||||
menuOptions.push(ContextMenu.commentDeleteOption(this));
|
||||
}
|
||||
|
||||
|
||||
ContextMenu.show(e, menuOptions, this.RTL);
|
||||
};
|
||||
|
||||
@@ -619,6 +602,7 @@ export {Css};
|
||||
export {Events};
|
||||
export {Extensions};
|
||||
export {Procedures};
|
||||
export {Procedures as procedures};
|
||||
export {ShortcutItems};
|
||||
export {Themes};
|
||||
export {Tooltip};
|
||||
@@ -668,7 +652,7 @@ export {Field};
|
||||
export {FieldAngle};
|
||||
export {FieldCheckbox};
|
||||
export {FieldColour};
|
||||
export {FieldDropdown};
|
||||
export {FieldDropdown, MenuGenerator, MenuGeneratorFunction, MenuOption};
|
||||
export {FieldImage};
|
||||
export {FieldLabel};
|
||||
export {FieldLabelSerializable};
|
||||
@@ -679,7 +663,8 @@ export {FieldVariable};
|
||||
export {Flyout};
|
||||
export {FlyoutButton};
|
||||
export {FlyoutMetricsManager};
|
||||
export {Generator};
|
||||
export {CodeGenerator};
|
||||
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
|
||||
export {Gesture};
|
||||
export {Grid};
|
||||
export {HorizontalFlyout};
|
||||
@@ -720,7 +705,7 @@ export {Menu};
|
||||
export {MenuItem};
|
||||
export {MetricsManager};
|
||||
export {Mutator};
|
||||
export {Msg};
|
||||
export {Msg, setLocale};
|
||||
export {Names};
|
||||
export {Options};
|
||||
export {RenderedConnection};
|
||||
@@ -753,12 +738,4 @@ export {config};
|
||||
export const connectionTypes = ConnectionType;
|
||||
export {inject};
|
||||
export {inputTypes};
|
||||
export namespace serialization {
|
||||
export const blocks = serializationBlocks;
|
||||
export const exceptions = serializationExceptions;
|
||||
export const priorities = serializationPriorities;
|
||||
export const registry = serializationRegistry;
|
||||
export const variables = serializationVariables;
|
||||
export const workspaces = serializationWorkspaces;
|
||||
export type ISerializer = SerializerInterface;
|
||||
}
|
||||
export {serialization};
|
||||
|
||||
324
core/bubble.ts
324
core/bubble.ts
@@ -56,67 +56,68 @@ export class Bubble implements IBubble {
|
||||
static ANCHOR_RADIUS = 8;
|
||||
|
||||
/** Mouse up event data. */
|
||||
private static onMouseUpWrapper_: browserEvents.Data|null = null;
|
||||
private static onMouseUpWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse move event data. */
|
||||
private static onMouseMoveWrapper_: browserEvents.Data|null = null;
|
||||
private static onMouseMoveWrapper: browserEvents.Data|null = null;
|
||||
|
||||
workspace_: WorkspaceSvg;
|
||||
content_: SVGElement;
|
||||
shape_: SVGElement;
|
||||
|
||||
/** Flag to stop incremental rendering during construction. */
|
||||
private readonly rendered_: boolean;
|
||||
private readonly rendered: boolean;
|
||||
|
||||
/** The SVG group containing all parts of the bubble. */
|
||||
private bubbleGroup_: SVGGElement|null = null;
|
||||
private bubbleGroup: SVGGElement|null = null;
|
||||
|
||||
/**
|
||||
* The SVG path for the arrow from the bubble to the icon on the block.
|
||||
*/
|
||||
private bubbleArrow_: SVGPathElement|null = null;
|
||||
private bubbleArrow: SVGPathElement|null = null;
|
||||
|
||||
/** The SVG rect for the main body of the bubble. */
|
||||
private bubbleBack_: SVGRectElement|null = null;
|
||||
private bubbleBack: SVGRectElement|null = null;
|
||||
|
||||
/** The SVG group for the resize hash marks on some bubbles. */
|
||||
private resizeGroup_: SVGGElement|null = null;
|
||||
private resizeGroup: SVGGElement|null = null;
|
||||
|
||||
/** Absolute coordinate of anchor point, in workspace coordinates. */
|
||||
private anchorXY_!: Coordinate;
|
||||
private anchorXY!: Coordinate;
|
||||
|
||||
/**
|
||||
* Relative X coordinate of bubble with respect to the anchor's centre,
|
||||
* in workspace units.
|
||||
* In RTL mode the initial value is negated.
|
||||
*/
|
||||
private relativeLeft_ = 0;
|
||||
private relativeLeft = 0;
|
||||
|
||||
/**
|
||||
* Relative Y coordinate of bubble with respect to the anchor's centre, in
|
||||
* workspace units.
|
||||
*/
|
||||
private relativeTop_ = 0;
|
||||
private relativeTop = 0;
|
||||
|
||||
/** Width of bubble, in workspace units. */
|
||||
private width_ = 0;
|
||||
private width = 0;
|
||||
|
||||
/** Height of bubble, in workspace units. */
|
||||
private height_ = 0;
|
||||
private height = 0;
|
||||
|
||||
/** Automatically position and reposition the bubble. */
|
||||
private autoLayout_ = true;
|
||||
private autoLayout = true;
|
||||
|
||||
/** Method to call on resize of bubble. */
|
||||
private resizeCallback_: (() => void)|null = null;
|
||||
private resizeCallback: (() => void)|null = null;
|
||||
|
||||
/** Method to call on move of bubble. */
|
||||
private moveCallback_: (() => void)|null = null;
|
||||
private moveCallback: (() => void)|null = null;
|
||||
|
||||
/** Mouse down on bubbleBack_ event data. */
|
||||
private onMouseDownBubbleWrapper_: browserEvents.Data|null = null;
|
||||
/** Mouse down on bubbleBack event data. */
|
||||
private onMouseDownBubbleWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse down on resizeGroup_ event data. */
|
||||
private onMouseDownResizeWrapper_: browserEvents.Data|null = null;
|
||||
/** Mouse down on resizeGroup event data. */
|
||||
private onMouseDownResizeWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* Describes whether this bubble has been disposed of (nodes and event
|
||||
@@ -125,7 +126,7 @@ export class Bubble implements IBubble {
|
||||
* @internal
|
||||
*/
|
||||
disposed = false;
|
||||
private arrow_radians_: number;
|
||||
private arrowRadians: number;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace on which to draw the bubble.
|
||||
@@ -139,7 +140,7 @@ export class Bubble implements IBubble {
|
||||
workspace: WorkspaceSvg, content: SVGElement, shape: SVGElement,
|
||||
anchorXY: Coordinate, bubbleWidth: number|null,
|
||||
bubbleHeight: number|null) {
|
||||
this.rendered_ = false;
|
||||
this.rendered = false;
|
||||
this.workspace_ = workspace;
|
||||
this.content_ = content;
|
||||
this.shape_ = shape;
|
||||
@@ -148,11 +149,11 @@ export class Bubble implements IBubble {
|
||||
if (this.workspace_.RTL) {
|
||||
angle = -angle;
|
||||
}
|
||||
this.arrow_radians_ = math.toRadians(angle);
|
||||
this.arrowRadians = math.toRadians(angle);
|
||||
|
||||
const canvas = workspace.getBubbleCanvas();
|
||||
canvas.appendChild(
|
||||
this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
|
||||
this.createDom(content, !!(bubbleWidth && bubbleHeight)));
|
||||
|
||||
this.setAnchorLocation(anchorXY);
|
||||
if (!bubbleWidth || !bubbleHeight) {
|
||||
@@ -163,9 +164,9 @@ export class Bubble implements IBubble {
|
||||
this.setBubbleSize(bubbleWidth, bubbleHeight);
|
||||
|
||||
// Render the bubble.
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
this.rendered_ = true;
|
||||
this.positionBubble();
|
||||
this.renderArrow();
|
||||
this.rendered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +176,7 @@ export class Bubble implements IBubble {
|
||||
* @param hasResize Add diagonal resize gripper if true.
|
||||
* @returns The bubble's SVG group.
|
||||
*/
|
||||
private createDom_(content: Element, hasResize: boolean): SVGElement {
|
||||
private createDom(content: Element, hasResize: boolean): SVGElement {
|
||||
/* Create the bubble. Here's the markup that will be generated:
|
||||
<g>
|
||||
<g filter="url(#blocklyEmbossFilter837493)">
|
||||
@@ -191,7 +192,7 @@ export class Bubble implements IBubble {
|
||||
[...content goes here...]
|
||||
</g>
|
||||
*/
|
||||
this.bubbleGroup_ = dom.createSvgElement(Svg.G, {});
|
||||
this.bubbleGroup = dom.createSvgElement(Svg.G, {});
|
||||
let filter: {filter?: string} = {
|
||||
'filter': 'url(#' +
|
||||
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
|
||||
@@ -201,9 +202,9 @@ export class Bubble implements IBubble {
|
||||
// https://github.com/google/blockly/issues/99
|
||||
filter = {};
|
||||
}
|
||||
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup_);
|
||||
this.bubbleArrow_ = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
|
||||
this.bubbleBack_ = dom.createSvgElement(
|
||||
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup);
|
||||
this.bubbleArrow = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
|
||||
this.bubbleBack = dom.createSvgElement(
|
||||
Svg.RECT, {
|
||||
'class': 'blocklyDraggable',
|
||||
'x': 0,
|
||||
@@ -213,17 +214,17 @@ export class Bubble implements IBubble {
|
||||
},
|
||||
bubbleEmboss);
|
||||
if (hasResize) {
|
||||
this.resizeGroup_ = dom.createSvgElement(
|
||||
this.resizeGroup = dom.createSvgElement(
|
||||
Svg.G, {
|
||||
'class': this.workspace_.RTL ? 'blocklyResizeSW' :
|
||||
'blocklyResizeSE',
|
||||
},
|
||||
this.bubbleGroup_);
|
||||
this.bubbleGroup);
|
||||
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
||||
dom.createSvgElement(
|
||||
Svg.POLYGON,
|
||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||
this.resizeGroup_);
|
||||
this.resizeGroup);
|
||||
dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'class': 'blocklyResizeLine',
|
||||
@@ -232,7 +233,7 @@ export class Bubble implements IBubble {
|
||||
'x2': resizeSize - 1,
|
||||
'y2': resizeSize / 3,
|
||||
},
|
||||
this.resizeGroup_);
|
||||
this.resizeGroup);
|
||||
dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'class': 'blocklyResizeLine',
|
||||
@@ -241,21 +242,21 @@ export class Bubble implements IBubble {
|
||||
'x2': resizeSize - 1,
|
||||
'y2': resizeSize * 2 / 3,
|
||||
},
|
||||
this.resizeGroup_);
|
||||
this.resizeGroup);
|
||||
} else {
|
||||
this.resizeGroup_ = null;
|
||||
this.resizeGroup = null;
|
||||
}
|
||||
|
||||
if (!this.workspace_.options.readOnly) {
|
||||
this.onMouseDownBubbleWrapper_ = browserEvents.conditionalBind(
|
||||
this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_);
|
||||
if (this.resizeGroup_) {
|
||||
this.onMouseDownResizeWrapper_ = browserEvents.conditionalBind(
|
||||
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
|
||||
this.onMouseDownBubbleWrapper = browserEvents.conditionalBind(
|
||||
this.bubbleBack, 'mousedown', this, this.bubbleMouseDown);
|
||||
if (this.resizeGroup) {
|
||||
this.onMouseDownResizeWrapper = browserEvents.conditionalBind(
|
||||
this.resizeGroup, 'mousedown', this, this.resizeMouseDown);
|
||||
}
|
||||
}
|
||||
this.bubbleGroup_.appendChild(content);
|
||||
return this.bubbleGroup_;
|
||||
this.bubbleGroup.appendChild(content);
|
||||
return this.bubbleGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +265,7 @@ export class Bubble implements IBubble {
|
||||
* @returns The root SVG node of the bubble's group.
|
||||
*/
|
||||
getSvgRoot(): SVGElement {
|
||||
return this.bubbleGroup_ as SVGElement;
|
||||
return this.bubbleGroup as SVGElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,7 +274,7 @@ export class Bubble implements IBubble {
|
||||
* @param id ID of block.
|
||||
*/
|
||||
setSvgId(id: string) {
|
||||
this.bubbleGroup_?.setAttribute('data-block-id', id);
|
||||
this.bubbleGroup?.setAttribute('data-block-id', id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,7 +282,7 @@ export class Bubble implements IBubble {
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
private bubbleMouseDown_(e: Event) {
|
||||
private bubbleMouseDown(e: Event) {
|
||||
const gesture = this.workspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
@@ -321,9 +322,9 @@ export class Bubble implements IBubble {
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
private resizeMouseDown_(e: MouseEvent) {
|
||||
private resizeMouseDown(e: MouseEvent) {
|
||||
this.promote();
|
||||
Bubble.unbindDragEvents_();
|
||||
Bubble.unbindDragEvents();
|
||||
if (browserEvents.isRightButton(e)) {
|
||||
// No right-click.
|
||||
e.stopPropagation();
|
||||
@@ -333,12 +334,12 @@ export class Bubble implements IBubble {
|
||||
this.workspace_.startDrag(
|
||||
e,
|
||||
new Coordinate(
|
||||
this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
|
||||
this.workspace_.RTL ? -this.width : this.width, this.height));
|
||||
|
||||
Bubble.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mouseup', this, Bubble.bubbleMouseUp_);
|
||||
Bubble.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousemove', this, this.resizeMouseMove_);
|
||||
Bubble.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||
document, 'mouseup', this, Bubble.bubbleMouseUp);
|
||||
Bubble.onMouseMoveWrapper = browserEvents.conditionalBind(
|
||||
document, 'mousemove', this, this.resizeMouseMove);
|
||||
this.workspace_.hideChaff();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
@@ -349,13 +350,13 @@ export class Bubble implements IBubble {
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
*/
|
||||
private resizeMouseMove_(e: MouseEvent) {
|
||||
this.autoLayout_ = false;
|
||||
private resizeMouseMove(e: MouseEvent) {
|
||||
this.autoLayout = false;
|
||||
const newXY = this.workspace_.moveDrag(e);
|
||||
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
if (this.workspace_.RTL) {
|
||||
// RTL requires the bubble to move its left edge.
|
||||
this.positionBubble_();
|
||||
this.positionBubble();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,7 +366,7 @@ export class Bubble implements IBubble {
|
||||
* @param callback The function to call on resize.
|
||||
*/
|
||||
registerResizeEvent(callback: () => void) {
|
||||
this.resizeCallback_ = callback;
|
||||
this.resizeCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +375,7 @@ export class Bubble implements IBubble {
|
||||
* @param callback The function to call on move.
|
||||
*/
|
||||
registerMoveEvent(callback: () => void) {
|
||||
this.moveCallback_ = callback;
|
||||
this.moveCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,9 +385,9 @@ export class Bubble implements IBubble {
|
||||
* @internal
|
||||
*/
|
||||
promote(): boolean {
|
||||
const svgGroup = this.bubbleGroup_?.parentNode;
|
||||
if (svgGroup?.lastChild !== this.bubbleGroup_ && this.bubbleGroup_) {
|
||||
svgGroup?.appendChild(this.bubbleGroup_);
|
||||
const svgGroup = this.bubbleGroup?.parentNode;
|
||||
if (svgGroup?.lastChild !== this.bubbleGroup && this.bubbleGroup) {
|
||||
svgGroup?.appendChild(this.bubbleGroup);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -399,29 +400,29 @@ export class Bubble implements IBubble {
|
||||
* @param xy Absolute location.
|
||||
*/
|
||||
setAnchorLocation(xy: Coordinate) {
|
||||
this.anchorXY_ = xy;
|
||||
if (this.rendered_) {
|
||||
this.positionBubble_();
|
||||
this.anchorXY = xy;
|
||||
if (this.rendered) {
|
||||
this.positionBubble();
|
||||
}
|
||||
}
|
||||
|
||||
/** Position the bubble so that it does not fall off-screen. */
|
||||
private layoutBubble_() {
|
||||
private layoutBubble() {
|
||||
// Get the metrics in workspace units.
|
||||
const viewMetrics =
|
||||
this.workspace_.getMetricsManager().getViewMetrics(true);
|
||||
|
||||
const optimalLeft = this.getOptimalRelativeLeft_(viewMetrics);
|
||||
const optimalTop = this.getOptimalRelativeTop_(viewMetrics);
|
||||
const optimalLeft = this.getOptimalRelativeLeft(viewMetrics);
|
||||
const optimalTop = this.getOptimalRelativeTop(viewMetrics);
|
||||
const bbox = (this.shape_ as SVGGraphicsElement).getBBox();
|
||||
|
||||
const topPosition = {
|
||||
x: optimalLeft,
|
||||
y: -this.height_ -
|
||||
y: -this.height -
|
||||
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
|
||||
number,
|
||||
};
|
||||
const startPosition = {x: -this.width_ - 30, y: optimalTop};
|
||||
const startPosition = {x: -this.width - 30, y: optimalTop};
|
||||
const endPosition = {x: bbox.width, y: optimalTop};
|
||||
const bottomPosition = {x: optimalLeft, y: bbox.height};
|
||||
|
||||
@@ -430,11 +431,11 @@ export class Bubble implements IBubble {
|
||||
const fartherPosition =
|
||||
bbox.width < bbox.height ? bottomPosition : endPosition;
|
||||
|
||||
const topPositionOverlap = this.getOverlap_(topPosition, viewMetrics);
|
||||
const startPositionOverlap = this.getOverlap_(startPosition, viewMetrics);
|
||||
const closerPositionOverlap = this.getOverlap_(closerPosition, viewMetrics);
|
||||
const topPositionOverlap = this.getOverlap(topPosition, viewMetrics);
|
||||
const startPositionOverlap = this.getOverlap(startPosition, viewMetrics);
|
||||
const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics);
|
||||
const fartherPositionOverlap =
|
||||
this.getOverlap_(fartherPosition, viewMetrics);
|
||||
this.getOverlap(fartherPosition, viewMetrics);
|
||||
|
||||
// Set the position to whichever position shows the most of the bubble,
|
||||
// with tiebreaks going in the order: top > start > close > far.
|
||||
@@ -442,25 +443,25 @@ export class Bubble implements IBubble {
|
||||
topPositionOverlap, startPositionOverlap, closerPositionOverlap,
|
||||
fartherPositionOverlap);
|
||||
if (topPositionOverlap === mostOverlap) {
|
||||
this.relativeLeft_ = topPosition.x;
|
||||
this.relativeTop_ = topPosition.y;
|
||||
this.relativeLeft = topPosition.x;
|
||||
this.relativeTop = topPosition.y;
|
||||
return;
|
||||
}
|
||||
if (startPositionOverlap === mostOverlap) {
|
||||
this.relativeLeft_ = startPosition.x;
|
||||
this.relativeTop_ = startPosition.y;
|
||||
this.relativeLeft = startPosition.x;
|
||||
this.relativeTop = startPosition.y;
|
||||
return;
|
||||
}
|
||||
if (closerPositionOverlap === mostOverlap) {
|
||||
this.relativeLeft_ = closerPosition.x;
|
||||
this.relativeTop_ = closerPosition.y;
|
||||
this.relativeLeft = closerPosition.x;
|
||||
this.relativeTop = closerPosition.y;
|
||||
return;
|
||||
}
|
||||
// TODO: I believe relativeLeft_ should actually be called relativeStart_
|
||||
// and then the math should be fixed to reflect this. (hopefully it'll
|
||||
// make it look simpler)
|
||||
this.relativeLeft_ = fartherPosition.x;
|
||||
this.relativeTop_ = fartherPosition.y;
|
||||
this.relativeLeft = fartherPosition.x;
|
||||
this.relativeTop = fartherPosition.y;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,19 +474,19 @@ export class Bubble implements IBubble {
|
||||
* in.
|
||||
* @returns The percentage of the bubble that is visible.
|
||||
*/
|
||||
private getOverlap_(
|
||||
private getOverlap(
|
||||
relativeMin: {x: number, y: number},
|
||||
viewMetrics: ContainerRegion): number {
|
||||
// The position of the top-left corner of the bubble in workspace units.
|
||||
const bubbleMin = {
|
||||
x: this.workspace_.RTL ? this.anchorXY_.x - relativeMin.x - this.width_ :
|
||||
relativeMin.x + this.anchorXY_.x,
|
||||
y: relativeMin.y + this.anchorXY_.y,
|
||||
x: this.workspace_.RTL ? this.anchorXY.x - relativeMin.x - this.width :
|
||||
relativeMin.x + this.anchorXY.x,
|
||||
y: relativeMin.y + this.anchorXY.y,
|
||||
};
|
||||
// The position of the bottom-right corner of the bubble in workspace units.
|
||||
const bubbleMax = {
|
||||
x: bubbleMin.x + this.width_,
|
||||
y: bubbleMin.y + this.height_,
|
||||
x: bubbleMin.x + this.width,
|
||||
y: bubbleMin.y + this.height,
|
||||
};
|
||||
|
||||
// We could adjust these values to account for the scrollbars, but the
|
||||
@@ -507,8 +508,7 @@ export class Bubble implements IBubble {
|
||||
Math.max(bubbleMin.y, workspaceMin.y);
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
1, overlapWidth * overlapHeight / (this.width_ * this.height_)));
|
||||
Math.min(1, overlapWidth * overlapHeight / (this.width * this.height)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -521,18 +521,18 @@ export class Bubble implements IBubble {
|
||||
* @returns The optimal horizontal position of the top-left corner of the
|
||||
* bubble.
|
||||
*/
|
||||
private getOptimalRelativeLeft_(viewMetrics: ContainerRegion): number {
|
||||
let relativeLeft = -this.width_ / 4;
|
||||
private getOptimalRelativeLeft(viewMetrics: ContainerRegion): number {
|
||||
let relativeLeft = -this.width / 4;
|
||||
|
||||
// No amount of sliding left or right will give us a better overlap.
|
||||
if (this.width_ > viewMetrics.width) {
|
||||
if (this.width > viewMetrics.width) {
|
||||
return relativeLeft;
|
||||
}
|
||||
|
||||
if (this.workspace_.RTL) {
|
||||
// Bubble coordinates are flipped in RTL.
|
||||
const bubbleRight = this.anchorXY_.x - relativeLeft;
|
||||
const bubbleLeft = bubbleRight - this.width_;
|
||||
const bubbleRight = this.anchorXY.x - relativeLeft;
|
||||
const bubbleLeft = bubbleRight - this.width;
|
||||
|
||||
const workspaceRight = viewMetrics.left + viewMetrics.width;
|
||||
const workspaceLeft = viewMetrics.left +
|
||||
@@ -541,14 +541,14 @@ export class Bubble implements IBubble {
|
||||
|
||||
if (bubbleLeft < workspaceLeft) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
relativeLeft = -(workspaceLeft - this.anchorXY_.x + this.width_);
|
||||
relativeLeft = -(workspaceLeft - this.anchorXY.x + this.width);
|
||||
} else if (bubbleRight > workspaceRight) {
|
||||
// Slide the bubble left until it is onscreen.
|
||||
relativeLeft = -(workspaceRight - this.anchorXY_.x);
|
||||
relativeLeft = -(workspaceRight - this.anchorXY.x);
|
||||
}
|
||||
} else {
|
||||
const bubbleLeft = relativeLeft + this.anchorXY_.x;
|
||||
const bubbleRight = bubbleLeft + this.width_;
|
||||
const bubbleLeft = relativeLeft + this.anchorXY.x;
|
||||
const bubbleRight = bubbleLeft + this.width;
|
||||
|
||||
const workspaceLeft = viewMetrics.left;
|
||||
const workspaceRight = viewMetrics.left + viewMetrics.width -
|
||||
@@ -557,10 +557,10 @@ export class Bubble implements IBubble {
|
||||
|
||||
if (bubbleLeft < workspaceLeft) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
relativeLeft = workspaceLeft - this.anchorXY_.x;
|
||||
relativeLeft = workspaceLeft - this.anchorXY.x;
|
||||
} else if (bubbleRight > workspaceRight) {
|
||||
// Slide the bubble left until it is onscreen.
|
||||
relativeLeft = workspaceRight - this.anchorXY_.x - this.width_;
|
||||
relativeLeft = workspaceRight - this.anchorXY.x - this.width;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,42 +577,42 @@ export class Bubble implements IBubble {
|
||||
* @returns The optimal vertical position of the top-left corner of the
|
||||
* bubble.
|
||||
*/
|
||||
private getOptimalRelativeTop_(viewMetrics: ContainerRegion): number {
|
||||
let relativeTop = -this.height_ / 4;
|
||||
private getOptimalRelativeTop(viewMetrics: ContainerRegion): number {
|
||||
let relativeTop = -this.height / 4;
|
||||
|
||||
// No amount of sliding up or down will give us a better overlap.
|
||||
if (this.height_ > viewMetrics.height) {
|
||||
if (this.height > viewMetrics.height) {
|
||||
return relativeTop;
|
||||
}
|
||||
|
||||
const bubbleTop = this.anchorXY_.y + relativeTop;
|
||||
const bubbleBottom = bubbleTop + this.height_;
|
||||
const bubbleTop = this.anchorXY.y + relativeTop;
|
||||
const bubbleBottom = bubbleTop + this.height;
|
||||
const workspaceTop = viewMetrics.top;
|
||||
const workspaceBottom = viewMetrics.top +
|
||||
viewMetrics.height - // Thickness in workspace units.
|
||||
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
||||
|
||||
const anchorY = this.anchorXY_.y;
|
||||
const anchorY = this.anchorXY.y;
|
||||
if (bubbleTop < workspaceTop) {
|
||||
// Slide the bubble down until it is onscreen.
|
||||
relativeTop = workspaceTop - anchorY;
|
||||
} else if (bubbleBottom > workspaceBottom) {
|
||||
// Slide the bubble up until it is onscreen.
|
||||
relativeTop = workspaceBottom - anchorY - this.height_;
|
||||
relativeTop = workspaceBottom - anchorY - this.height;
|
||||
}
|
||||
|
||||
return relativeTop;
|
||||
}
|
||||
|
||||
/** Move the bubble to a location relative to the anchor's centre. */
|
||||
private positionBubble_() {
|
||||
let left = this.anchorXY_.x;
|
||||
private positionBubble() {
|
||||
let left = this.anchorXY.x;
|
||||
if (this.workspace_.RTL) {
|
||||
left -= this.relativeLeft_ + this.width_;
|
||||
left -= this.relativeLeft + this.width;
|
||||
} else {
|
||||
left += this.relativeLeft_;
|
||||
left += this.relativeLeft;
|
||||
}
|
||||
const top = this.relativeTop_ + this.anchorXY_.y;
|
||||
const top = this.relativeTop + this.anchorXY.y;
|
||||
this.moveTo(left, top);
|
||||
}
|
||||
|
||||
@@ -624,7 +624,7 @@ export class Bubble implements IBubble {
|
||||
* @internal
|
||||
*/
|
||||
moveTo(x: number, y: number) {
|
||||
this.bubbleGroup_?.setAttribute(
|
||||
this.bubbleGroup?.setAttribute(
|
||||
'transform', 'translate(' + x + ',' + y + ')');
|
||||
}
|
||||
|
||||
@@ -635,8 +635,8 @@ export class Bubble implements IBubble {
|
||||
* @internal
|
||||
*/
|
||||
setDragging(adding: boolean) {
|
||||
if (!adding && this.moveCallback_) {
|
||||
this.moveCallback_();
|
||||
if (!adding && this.moveCallback) {
|
||||
this.moveCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -646,7 +646,7 @@ export class Bubble implements IBubble {
|
||||
* @returns The height and width of the bubble.
|
||||
*/
|
||||
getBubbleSize(): Size {
|
||||
return new Size(this.width_, this.height_);
|
||||
return new Size(this.width, this.height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,46 +660,46 @@ export class Bubble implements IBubble {
|
||||
// Minimum size of a bubble.
|
||||
width = Math.max(width, doubleBorderWidth + 45);
|
||||
height = Math.max(height, doubleBorderWidth + 20);
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
this.bubbleBack_?.setAttribute('width', width.toString());
|
||||
this.bubbleBack_?.setAttribute('height', height.toString());
|
||||
if (this.resizeGroup_) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.bubbleBack?.setAttribute('width', width.toString());
|
||||
this.bubbleBack?.setAttribute('height', height.toString());
|
||||
if (this.resizeGroup) {
|
||||
if (this.workspace_.RTL) {
|
||||
// Mirror the resize group.
|
||||
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
||||
this.resizeGroup_.setAttribute(
|
||||
this.resizeGroup.setAttribute(
|
||||
'transform',
|
||||
'translate(' + resizeSize + ',' + (height - doubleBorderWidth) +
|
||||
') scale(-1 1)');
|
||||
} else {
|
||||
this.resizeGroup_.setAttribute(
|
||||
this.resizeGroup.setAttribute(
|
||||
'transform',
|
||||
'translate(' + (width - doubleBorderWidth) + ',' +
|
||||
(height - doubleBorderWidth) + ')');
|
||||
}
|
||||
}
|
||||
if (this.autoLayout_) {
|
||||
this.layoutBubble_();
|
||||
if (this.autoLayout) {
|
||||
this.layoutBubble();
|
||||
}
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
this.positionBubble();
|
||||
this.renderArrow();
|
||||
|
||||
// Allow the contents to resize.
|
||||
if (this.resizeCallback_) {
|
||||
this.resizeCallback_();
|
||||
if (this.resizeCallback) {
|
||||
this.resizeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
/** Draw the arrow between the bubble and the origin. */
|
||||
private renderArrow_() {
|
||||
private renderArrow() {
|
||||
const steps = [];
|
||||
// Find the relative coordinates of the center of the bubble.
|
||||
const relBubbleX = this.width_ / 2;
|
||||
const relBubbleY = this.height_ / 2;
|
||||
const relBubbleX = this.width / 2;
|
||||
const relBubbleY = this.height / 2;
|
||||
// Find the relative coordinates of the center of the anchor.
|
||||
let relAnchorX = -this.relativeLeft_;
|
||||
let relAnchorY = -this.relativeTop_;
|
||||
let relAnchorX = -this.relativeLeft;
|
||||
let relAnchorY = -this.relativeTop;
|
||||
if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) {
|
||||
// Null case. Bubble is directly on top of the anchor.
|
||||
// Short circuit this rather than wade through divide by zeros.
|
||||
@@ -742,7 +742,7 @@ export class Bubble implements IBubble {
|
||||
const baseY2 = relBubbleY - thickness * rightRise;
|
||||
|
||||
// Distortion to curve the arrow.
|
||||
let swirlAngle = angle + this.arrow_radians_;
|
||||
let swirlAngle = angle + this.arrowRadians;
|
||||
if (swirlAngle > Math.PI * 2) {
|
||||
swirlAngle -= Math.PI * 2;
|
||||
}
|
||||
@@ -758,7 +758,7 @@ export class Bubble implements IBubble {
|
||||
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
|
||||
}
|
||||
steps.push('z');
|
||||
this.bubbleArrow_?.setAttribute('d', steps.join(' '));
|
||||
this.bubbleArrow?.setAttribute('d', steps.join(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -767,20 +767,20 @@ export class Bubble implements IBubble {
|
||||
* @param hexColour Hex code of colour.
|
||||
*/
|
||||
setColour(hexColour: string) {
|
||||
this.bubbleBack_?.setAttribute('fill', hexColour);
|
||||
this.bubbleArrow_?.setAttribute('fill', hexColour);
|
||||
this.bubbleBack?.setAttribute('fill', hexColour);
|
||||
this.bubbleArrow?.setAttribute('fill', hexColour);
|
||||
}
|
||||
|
||||
/** Dispose of this bubble. */
|
||||
dispose() {
|
||||
if (this.onMouseDownBubbleWrapper_) {
|
||||
browserEvents.unbind(this.onMouseDownBubbleWrapper_);
|
||||
if (this.onMouseDownBubbleWrapper) {
|
||||
browserEvents.unbind(this.onMouseDownBubbleWrapper);
|
||||
}
|
||||
if (this.onMouseDownResizeWrapper_) {
|
||||
browserEvents.unbind(this.onMouseDownResizeWrapper_);
|
||||
if (this.onMouseDownResizeWrapper) {
|
||||
browserEvents.unbind(this.onMouseDownResizeWrapper);
|
||||
}
|
||||
Bubble.unbindDragEvents_();
|
||||
dom.removeNode(this.bubbleGroup_);
|
||||
Bubble.unbindDragEvents();
|
||||
dom.removeNode(this.bubbleGroup);
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
@@ -800,12 +800,12 @@ export class Bubble implements IBubble {
|
||||
this.moveTo(newLoc.x, newLoc.y);
|
||||
}
|
||||
if (this.workspace_.RTL) {
|
||||
this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_;
|
||||
this.relativeLeft = this.anchorXY.x - newLoc.x - this.width;
|
||||
} else {
|
||||
this.relativeLeft_ = newLoc.x - this.anchorXY_.x;
|
||||
this.relativeLeft = newLoc.x - this.anchorXY.x;
|
||||
}
|
||||
this.relativeTop_ = newLoc.y - this.anchorXY_.y;
|
||||
this.renderArrow_();
|
||||
this.relativeTop = newLoc.y - this.anchorXY.y;
|
||||
this.renderArrow();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -817,9 +817,9 @@ export class Bubble implements IBubble {
|
||||
getRelativeToSurfaceXY(): Coordinate {
|
||||
return new Coordinate(
|
||||
this.workspace_.RTL ?
|
||||
-this.relativeLeft_ + this.anchorXY_.x - this.width_ :
|
||||
this.anchorXY_.x + this.relativeLeft_,
|
||||
this.anchorXY_.y + this.relativeTop_);
|
||||
-this.relativeLeft + this.anchorXY.x - this.width :
|
||||
this.anchorXY.x + this.relativeLeft,
|
||||
this.anchorXY.y + this.relativeTop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -831,18 +831,18 @@ export class Bubble implements IBubble {
|
||||
* @internal
|
||||
*/
|
||||
setAutoLayout(enable: boolean) {
|
||||
this.autoLayout_ = enable;
|
||||
this.autoLayout = enable;
|
||||
}
|
||||
|
||||
/** Stop binding to the global mouseup and mousemove events. */
|
||||
private static unbindDragEvents_() {
|
||||
if (Bubble.onMouseUpWrapper_) {
|
||||
browserEvents.unbind(Bubble.onMouseUpWrapper_);
|
||||
Bubble.onMouseUpWrapper_ = null;
|
||||
private static unbindDragEvents() {
|
||||
if (Bubble.onMouseUpWrapper) {
|
||||
browserEvents.unbind(Bubble.onMouseUpWrapper);
|
||||
Bubble.onMouseUpWrapper = null;
|
||||
}
|
||||
if (Bubble.onMouseMoveWrapper_) {
|
||||
browserEvents.unbind(Bubble.onMouseMoveWrapper_);
|
||||
Bubble.onMouseMoveWrapper_ = null;
|
||||
if (Bubble.onMouseMoveWrapper) {
|
||||
browserEvents.unbind(Bubble.onMouseMoveWrapper);
|
||||
Bubble.onMouseMoveWrapper = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -851,9 +851,9 @@ export class Bubble implements IBubble {
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
*/
|
||||
private static bubbleMouseUp_(_e: MouseEvent) {
|
||||
private static bubbleMouseUp(_e: MouseEvent) {
|
||||
Touch.clearTouchIdentifier();
|
||||
Bubble.unbindDragEvents_();
|
||||
Bubble.unbindDragEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -147,16 +147,16 @@ function extractObjectFromEvent(
|
||||
switch (e.type) {
|
||||
case eventUtils.BLOCK_CREATE:
|
||||
case eventUtils.BLOCK_MOVE:
|
||||
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId);
|
||||
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!);
|
||||
if (object) {
|
||||
object = object.getRootBlock();
|
||||
}
|
||||
break;
|
||||
case eventUtils.COMMENT_CREATE:
|
||||
case eventUtils.COMMENT_MOVE:
|
||||
object = workspace.getCommentById(
|
||||
(e as CommentCreate | CommentMove).commentId) as
|
||||
WorkspaceCommentSvg |
|
||||
object =
|
||||
workspace.getCommentById((e as CommentCreate | CommentMove).commentId!
|
||||
) as WorkspaceCommentSvg |
|
||||
null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ export class Comment extends Icon {
|
||||
HTMLTextAreaElement;
|
||||
const textarea = this.textarea_;
|
||||
textarea.className = 'blocklyCommentTextarea';
|
||||
textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
|
||||
textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR');
|
||||
textarea.value = this.model_.text ?? '';
|
||||
this.resizeTextarea_();
|
||||
|
||||
@@ -167,7 +167,7 @@ export class Comment extends Icon {
|
||||
function(this: Comment, _e: Event) {
|
||||
if (this.cachedText_ !== this.model_.text) {
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
||||
this.block_, 'comment', null, this.cachedText_,
|
||||
this.getBlock(), 'comment', null, this.cachedText_,
|
||||
this.model_.text));
|
||||
}
|
||||
});
|
||||
@@ -233,7 +233,7 @@ export class Comment extends Icon {
|
||||
return;
|
||||
}
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
||||
this.block_, visible, 'comment'));
|
||||
this.getBlock(), visible, 'comment'));
|
||||
this.model_.pinned = visible;
|
||||
if (visible) {
|
||||
this.createBubble_();
|
||||
@@ -244,7 +244,7 @@ export class Comment extends Icon {
|
||||
|
||||
/** Show the bubble. Handles deciding if it should be editable or not. */
|
||||
private createBubble_() {
|
||||
if (!this.block_.isEditable()) {
|
||||
if (!this.getBlock().isEditable()) {
|
||||
this.createNonEditableBubble_();
|
||||
} else {
|
||||
this.createEditableBubble_();
|
||||
@@ -253,12 +253,13 @@ export class Comment extends Icon {
|
||||
|
||||
/** Show an editable bubble. */
|
||||
private createEditableBubble_() {
|
||||
const block = this.getBlock();
|
||||
this.bubble_ = new Bubble(
|
||||
this.block_.workspace, this.createEditor_(),
|
||||
this.block_.pathObject.svgPath, (this.iconXY_ as Coordinate),
|
||||
this.model_.size.width, this.model_.size.height);
|
||||
block.workspace, this.createEditor_(), block.pathObject.svgPath,
|
||||
(this.iconXY_ as Coordinate), this.model_.size.width,
|
||||
this.model_.size.height);
|
||||
// Expose this comment's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.setSvgId(block.id);
|
||||
this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
|
||||
this.applyColour();
|
||||
}
|
||||
@@ -272,7 +273,7 @@ export class Comment extends Icon {
|
||||
// TODO (#2917): It would be great if the comment could support line breaks.
|
||||
this.paragraphElement_ = Bubble.textToDom(this.model_.text ?? '');
|
||||
this.bubble_ = Bubble.createNonEditableBubble(
|
||||
this.paragraphElement_, (this.block_), this.iconXY_ as Coordinate);
|
||||
this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate);
|
||||
this.applyColour();
|
||||
}
|
||||
|
||||
@@ -371,7 +372,7 @@ export class Comment extends Icon {
|
||||
* should not be called directly. Instead call block.setCommentText(null);
|
||||
*/
|
||||
override dispose() {
|
||||
this.block_.comment = null;
|
||||
this.getBlock().comment = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,7 +571,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
const parentBlock = this.getSourceBlock();
|
||||
const shadowState = this.getShadowState();
|
||||
const shadowDom = this.getShadowDom();
|
||||
if (parentBlock.disposed || !shadowState && !shadowDom) {
|
||||
if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as clipboard from './clipboard.js';
|
||||
import {config} from './config.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {Menu} from './menu.js';
|
||||
@@ -179,7 +180,7 @@ function createWidget_(menu: Menu) {
|
||||
throw Error('Attempting to create a context menu when widget div is null');
|
||||
}
|
||||
const menuDom = menu.render(div);
|
||||
menuDom.classList.add('blocklyContextMenu');
|
||||
dom.addClass(menuDom, 'blocklyContextMenu');
|
||||
// Prevent system context menu when right-clicking a Blockly context menu.
|
||||
browserEvents.conditionalBind(
|
||||
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
|
||||
|
||||
@@ -249,7 +249,7 @@ function deleteNext_(deleteList: BlockSvg[], eventGroup: string) {
|
||||
eventUtils.setGroup(eventGroup);
|
||||
const block = deleteList.shift();
|
||||
if (block) {
|
||||
if (!block.disposed) {
|
||||
if (!block.isDeadOrDying()) {
|
||||
block.dispose(false, true);
|
||||
setTimeout(deleteNext_, DELAY, deleteList, eventGroup);
|
||||
} else {
|
||||
|
||||
17
core/css.ts
17
core/css.ts
@@ -12,8 +12,6 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Css');
|
||||
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
|
||||
|
||||
/** Has CSS already been injected? */
|
||||
let injected = false;
|
||||
@@ -25,20 +23,11 @@ let injected = false;
|
||||
* @param cssContent Multiline CSS string or an array of single lines of CSS.
|
||||
* @alias Blockly.Css.register
|
||||
*/
|
||||
export function register(cssContent: string|string[]) {
|
||||
export function register(cssContent: string) {
|
||||
if (injected) {
|
||||
throw Error('CSS already injected');
|
||||
}
|
||||
|
||||
if (Array.isArray(cssContent)) {
|
||||
deprecation.warn(
|
||||
'Registering CSS by passing an array of strings', 'September 2021',
|
||||
'September 2022', 'css.register passing a multiline string');
|
||||
content += '\n' + cssContent.join('\n');
|
||||
} else {
|
||||
// Add new cssContent in the global content.
|
||||
content += '\n' + cssContent;
|
||||
}
|
||||
content += '\n' + cssContent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +160,7 @@ let content = `
|
||||
}
|
||||
|
||||
.blocklyDropDownContent {
|
||||
max-height: 300px; // @todo: spec for maximum height.
|
||||
max-height: 300px; /* @todo: spec for maximum height. */
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
|
||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.dropDownDiv');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as common from './common.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {Field} from './field.js';
|
||||
import * as math from './utils/math.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
@@ -138,10 +139,10 @@ export function createDom() {
|
||||
// Handle focusin/out events to add a visual indicator when
|
||||
// a child is focused or blurred.
|
||||
div.addEventListener('focusin', function() {
|
||||
div.classList.add('blocklyFocused');
|
||||
dom.addClass(div, 'blocklyFocused');
|
||||
});
|
||||
div.addEventListener('focusout', function() {
|
||||
div.classList.remove('blocklyFocused');
|
||||
dom.removeClass(div, 'blocklyFocused');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -311,10 +312,10 @@ export function show(
|
||||
renderedClassName = mainWorkspace.getRenderer().getClassName();
|
||||
themeClassName = mainWorkspace.getTheme().getClassName();
|
||||
if (renderedClassName) {
|
||||
div.classList.add(renderedClassName);
|
||||
dom.addClass(div, renderedClassName);
|
||||
}
|
||||
if (themeClassName) {
|
||||
div.classList.add(themeClassName);
|
||||
dom.addClass(div, themeClassName);
|
||||
}
|
||||
|
||||
// When we change `translate` multiple times in close succession,
|
||||
@@ -595,11 +596,11 @@ export function hideWithoutAnimation() {
|
||||
owner = null;
|
||||
|
||||
if (renderedClassName) {
|
||||
div.classList.remove(renderedClassName);
|
||||
dom.removeClass(div, renderedClassName);
|
||||
renderedClassName = '';
|
||||
}
|
||||
if (themeClassName) {
|
||||
div.classList.remove(themeClassName);
|
||||
dom.removeClass(div, themeClassName);
|
||||
themeClassName = '';
|
||||
}
|
||||
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
||||
|
||||
@@ -13,64 +13,90 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events');
|
||||
|
||||
|
||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
import {BlockBase} from './events_block_base.js';
|
||||
import {BlockChange} from './events_block_change.js';
|
||||
import {BlockCreate} from './events_block_create.js';
|
||||
import {BlockDelete} from './events_block_delete.js';
|
||||
import {BlockDrag} from './events_block_drag.js';
|
||||
import {BlockMove} from './events_block_move.js';
|
||||
import {BubbleOpen} from './events_bubble_open.js';
|
||||
import {Click} from './events_click.js';
|
||||
import {CommentBase} from './events_comment_base.js';
|
||||
import {CommentChange} from './events_comment_change.js';
|
||||
import {CommentCreate} from './events_comment_create.js';
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import {BlockChange, BlockChangeJson} from './events_block_change.js';
|
||||
import {BlockCreate, BlockCreateJson} from './events_block_create.js';
|
||||
import {BlockDelete, BlockDeleteJson} from './events_block_delete.js';
|
||||
import {BlockDrag, BlockDragJson} from './events_block_drag.js';
|
||||
import {BlockMove, BlockMoveJson} from './events_block_move.js';
|
||||
import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js';
|
||||
import {Click, ClickJson, ClickTarget} from './events_click.js';
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import {CommentChange, CommentChangeJson} from './events_comment_change.js';
|
||||
import {CommentCreate, CommentCreateJson} from './events_comment_create.js';
|
||||
import {CommentDelete} from './events_comment_delete.js';
|
||||
import {CommentMove} from './events_comment_move.js';
|
||||
import {MarkerMove} from './events_marker_move.js';
|
||||
import {Selected} from './events_selected.js';
|
||||
import {ThemeChange} from './events_theme_change.js';
|
||||
import {ToolboxItemSelect} from './events_toolbox_item_select.js';
|
||||
import {TrashcanOpen} from './events_trashcan_open.js';
|
||||
import {CommentMove, CommentMoveJson} from './events_comment_move.js';
|
||||
import {MarkerMove, MarkerMoveJson} from './events_marker_move.js';
|
||||
import {Selected, SelectedJson} from './events_selected.js';
|
||||
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
|
||||
import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js';
|
||||
import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
|
||||
import {Ui} from './events_ui.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import {VarBase} from './events_var_base.js';
|
||||
import {VarCreate} from './events_var_create.js';
|
||||
import {VarDelete} from './events_var_delete.js';
|
||||
import {VarRename} from './events_var_rename.js';
|
||||
import {ViewportChange} from './events_viewport.js';
|
||||
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||
import {VarCreate, VarCreateJson} from './events_var_create.js';
|
||||
import {VarDelete, VarDeleteJson} from './events_var_delete.js';
|
||||
import {VarRename, VarRenameJson} from './events_var_rename.js';
|
||||
import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
import {FinishedLoading} from './workspace_events.js';
|
||||
import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
|
||||
|
||||
|
||||
// Events.
|
||||
export const Abstract = AbstractEvent;
|
||||
export {AbstractEventJson};
|
||||
export {BubbleOpen};
|
||||
export {BubbleOpenJson};
|
||||
export {BubbleType};
|
||||
export {BlockBase};
|
||||
export {BlockBaseJson};
|
||||
export {BlockChange};
|
||||
export {BlockChangeJson};
|
||||
export {BlockCreate};
|
||||
export {BlockCreateJson};
|
||||
export {BlockDelete};
|
||||
export {BlockDeleteJson};
|
||||
export {BlockDrag};
|
||||
export {BlockDragJson};
|
||||
export {BlockMove};
|
||||
export {BlockMoveJson};
|
||||
export {Click};
|
||||
export {ClickJson};
|
||||
export {ClickTarget};
|
||||
export {CommentBase};
|
||||
export {CommentBaseJson};
|
||||
export {CommentChange};
|
||||
export {CommentChangeJson};
|
||||
export {CommentCreate};
|
||||
export {CommentCreateJson};
|
||||
export {CommentDelete};
|
||||
export {CommentMove};
|
||||
export {CommentMoveJson};
|
||||
export {FinishedLoading};
|
||||
export {FinishedLoadingJson};
|
||||
export {MarkerMove};
|
||||
export {MarkerMoveJson};
|
||||
export {Selected};
|
||||
export {SelectedJson};
|
||||
export {ThemeChange};
|
||||
export {ThemeChangeJson};
|
||||
export {ToolboxItemSelect};
|
||||
export {ToolboxItemSelectJson};
|
||||
export {TrashcanOpen};
|
||||
export {TrashcanOpenJson};
|
||||
export {Ui};
|
||||
export {UiBase};
|
||||
export {VarBase};
|
||||
export {VarBaseJson};
|
||||
export {VarCreate};
|
||||
export {VarCreateJson};
|
||||
export {VarDelete};
|
||||
export {VarDeleteJson};
|
||||
export {VarRename};
|
||||
export {VarRenameJson};
|
||||
export {ViewportChange};
|
||||
export {ViewportChangeJson};
|
||||
|
||||
// Event types.
|
||||
export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE;
|
||||
|
||||
@@ -26,7 +26,7 @@ import * as eventUtils from './utils.js';
|
||||
*/
|
||||
export abstract class Abstract {
|
||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
||||
isBlank: boolean|null = null;
|
||||
abstract isBlank: boolean;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
workspaceId?: string = undefined;
|
||||
@@ -37,7 +37,7 @@ export abstract class Abstract {
|
||||
isUiEvent = false;
|
||||
|
||||
/** Type of this event. */
|
||||
type?: string = undefined;
|
||||
type = '';
|
||||
|
||||
/** @alias Blockly.Events.Abstract */
|
||||
constructor() {
|
||||
@@ -57,12 +57,11 @@ export abstract class Abstract {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): AnyDuringMigration {
|
||||
const json = {'type': this.type};
|
||||
if (this.group) {
|
||||
(json as AnyDuringMigration)['group'] = this.group;
|
||||
}
|
||||
return json;
|
||||
toJson(): AbstractEventJson {
|
||||
return {
|
||||
'type': this.type,
|
||||
'group': this.group,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,9 +69,9 @@ export abstract class Abstract {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
fromJson(json: AnyDuringMigration) {
|
||||
fromJson(json: AbstractEventJson) {
|
||||
this.isBlank = false;
|
||||
this.group = json['group'];
|
||||
this.group = json['group'] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,3 +111,8 @@ export abstract class Abstract {
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AbstractEventJson {
|
||||
type: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.BlockBase');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
|
||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -23,9 +23,8 @@ import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
* @alias Blockly.Events.BlockBase
|
||||
*/
|
||||
export class BlockBase extends AbstractEvent {
|
||||
override isBlank: AnyDuringMigration;
|
||||
blockId: string;
|
||||
override workspaceId: string;
|
||||
override isBlank = true;
|
||||
blockId?: string;
|
||||
|
||||
/**
|
||||
* @param opt_block The block this event corresponds to.
|
||||
@@ -33,13 +32,15 @@ export class BlockBase extends AbstractEvent {
|
||||
*/
|
||||
constructor(opt_block?: Block) {
|
||||
super();
|
||||
this.isBlank = typeof opt_block === 'undefined';
|
||||
this.isBlank = !!opt_block;
|
||||
|
||||
if (!opt_block) return;
|
||||
|
||||
/** The block ID for the block this event pertains to */
|
||||
this.blockId = this.isBlank ? '' : opt_block!.id;
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = this.isBlank ? '' : opt_block!.workspace.id;
|
||||
this.workspaceId = opt_block.workspace.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,8 +48,13 @@ export class BlockBase extends AbstractEvent {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): AbstractEventJson {
|
||||
const json = super.toJson() as BlockBaseJson;
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
@@ -58,8 +64,12 @@ export class BlockBase extends AbstractEvent {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockBaseJson) {
|
||||
super.fromJson(json);
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockBaseJson extends AbstractEventJson {
|
||||
blockId: string;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {BlockSvg} from '../block_svg.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase} from './events_block_base.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -27,13 +27,11 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.BlockChange
|
||||
*/
|
||||
export class BlockChange extends BlockBase {
|
||||
override type: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
element!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
name!: string|null;
|
||||
oldValue: AnyDuringMigration;
|
||||
newValue: AnyDuringMigration;
|
||||
override type = eventUtils.BLOCK_CHANGE;
|
||||
element?: string;
|
||||
name?: string;
|
||||
oldValue: unknown;
|
||||
newValue: unknown;
|
||||
|
||||
/**
|
||||
* @param opt_block The changed block. Undefined for a blank event.
|
||||
@@ -44,19 +42,16 @@ export class BlockChange extends BlockBase {
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: Block, opt_element?: string, opt_name?: string|null,
|
||||
opt_oldValue?: AnyDuringMigration, opt_newValue?: AnyDuringMigration) {
|
||||
opt_oldValue?: unknown, opt_newValue?: unknown) {
|
||||
super(opt_block);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BLOCK_CHANGE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
this.element = typeof opt_element === 'undefined' ? '' : opt_element;
|
||||
this.name = typeof opt_name === 'undefined' ? '' : opt_name;
|
||||
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
||||
this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue;
|
||||
this.element = opt_element;
|
||||
this.name = opt_name || undefined;
|
||||
this.oldValue = opt_oldValue;
|
||||
this.newValue = opt_newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,12 +59,15 @@ export class BlockChange extends BlockBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
json['element'] = this.element;
|
||||
if (this.name) {
|
||||
json['name'] = this.name;
|
||||
override toJson(): BlockChangeJson {
|
||||
const json = super.toJson() as BlockChangeJson;
|
||||
if (!this.element) {
|
||||
throw new Error(
|
||||
'The changed element is undefined. Either pass an ' +
|
||||
'element to the constructor, or call fromJson');
|
||||
}
|
||||
json['element'] = this.element;
|
||||
json['name'] = this.name;
|
||||
json['oldValue'] = this.oldValue;
|
||||
json['newValue'] = this.newValue;
|
||||
return json;
|
||||
@@ -80,7 +78,7 @@ export class BlockChange extends BlockBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.element = json['element'];
|
||||
this.name = json['name'];
|
||||
@@ -104,10 +102,16 @@ export class BlockChange extends BlockBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn('Can\'t change non-existent block: ' + this.blockId);
|
||||
return;
|
||||
throw new Error(
|
||||
'The associated block is undefined. Either pass a ' +
|
||||
'block to the constructor, or call fromJson');
|
||||
}
|
||||
// Assume the block is rendered so that then we can check.
|
||||
const blockSvg = block as BlockSvg;
|
||||
@@ -176,4 +180,11 @@ export class BlockChange extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockChangeJson extends BlockBaseJson {
|
||||
element: string;
|
||||
name?: string;
|
||||
newValue: unknown;
|
||||
oldValue: unknown;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange);
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase} from './events_block_base.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -27,20 +27,15 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.BlockCreate
|
||||
*/
|
||||
export class BlockCreate extends BlockBase {
|
||||
override type: string;
|
||||
xml: AnyDuringMigration;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
ids!: string[];
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
json!: blocks.State;
|
||||
override type = eventUtils.BLOCK_CREATE;
|
||||
xml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
json?: blocks.State;
|
||||
|
||||
/** @param opt_block The created block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BLOCK_CREATE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -62,8 +57,23 @@ export class BlockCreate extends BlockBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): BlockCreateJson {
|
||||
const json = super.toJson() as BlockCreateJson;
|
||||
if (!this.xml) {
|
||||
throw new Error(
|
||||
'The block XML is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.json) {
|
||||
throw new Error(
|
||||
'The block JSON is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
json['ids'] = this.ids;
|
||||
json['json'] = this.json;
|
||||
@@ -78,7 +88,7 @@ export class BlockCreate extends BlockBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockCreateJson) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
this.ids = json['ids'];
|
||||
@@ -95,6 +105,16 @@ export class BlockCreate extends BlockBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.json) {
|
||||
throw new Error(
|
||||
'The block JSON is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
blocks.append(this.json, workspace);
|
||||
} else {
|
||||
@@ -112,4 +132,11 @@ export class BlockCreate extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockCreateJson extends BlockBaseJson {
|
||||
xml: string;
|
||||
ids: string[];
|
||||
json: object;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate);
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase} from './events_block_base.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -27,22 +27,16 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.BlockDelete
|
||||
*/
|
||||
export class BlockDelete extends BlockBase {
|
||||
override type: string;
|
||||
oldXml: AnyDuringMigration;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
ids!: string[];
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
wasShadow!: boolean;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldJson!: blocks.State;
|
||||
oldXml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
wasShadow?: boolean;
|
||||
oldJson?: blocks.State;
|
||||
override type = eventUtils.BLOCK_DELETE;
|
||||
|
||||
/** @param opt_block The deleted block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BLOCK_DELETE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -71,8 +65,28 @@ export class BlockDelete extends BlockBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): BlockDeleteJson {
|
||||
const json = super.toJson() as BlockDeleteJson;
|
||||
if (!this.oldXml) {
|
||||
throw new Error(
|
||||
'The old block XML is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.wasShadow === undefined) {
|
||||
throw new Error(
|
||||
'Whether the block was a shadow is undefined. Either ' +
|
||||
'pass a block to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.oldJson) {
|
||||
throw new Error(
|
||||
'The old block JSON is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
json['oldXml'] = Xml.domToText(this.oldXml);
|
||||
json['ids'] = this.ids;
|
||||
json['wasShadow'] = this.wasShadow;
|
||||
@@ -88,13 +102,13 @@ export class BlockDelete extends BlockBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockDeleteJson) {
|
||||
super.fromJson(json);
|
||||
this.oldXml = Xml.textToDom(json['oldXml']);
|
||||
this.ids = json['ids'];
|
||||
this.wasShadow =
|
||||
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
||||
this.oldJson = json['oldJson'] as blocks.State;
|
||||
this.oldJson = json['oldJson'];
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
this.recordUndo = json['recordUndo'];
|
||||
}
|
||||
@@ -107,6 +121,16 @@ export class BlockDelete extends BlockBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.oldJson) {
|
||||
throw new Error(
|
||||
'The old block JSON is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
for (let i = 0; i < this.ids.length; i++) {
|
||||
const id = this.ids[i];
|
||||
@@ -124,4 +148,12 @@ export class BlockDelete extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockDeleteJson extends BlockBaseJson {
|
||||
oldXml: string;
|
||||
ids: string[];
|
||||
wasShadow: boolean;
|
||||
oldJson: blocks.State;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete);
|
||||
|
||||
@@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.BlockDrag');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -25,10 +25,10 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.BlockDrag
|
||||
*/
|
||||
export class BlockDrag extends UiBase {
|
||||
blockId: AnyDuringMigration;
|
||||
blockId?: string;
|
||||
isStart?: boolean;
|
||||
blocks?: Block[];
|
||||
override type: string;
|
||||
override type = eventUtils.BLOCK_DRAG;
|
||||
|
||||
/**
|
||||
* @param opt_block The top block in the stack that is being dragged.
|
||||
@@ -41,16 +41,15 @@ export class BlockDrag extends UiBase {
|
||||
constructor(opt_block?: Block, opt_isStart?: boolean, opt_blocks?: Block[]) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
if (!opt_block) return;
|
||||
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** Whether this is the start of a block drag. */
|
||||
this.isStart = opt_isStart;
|
||||
|
||||
/** The blocks affected by this drag event. */
|
||||
this.blocks = opt_blocks;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BLOCK_DRAG;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,10 +57,22 @@ export class BlockDrag extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): BlockDragJson {
|
||||
const json = super.toJson() as BlockDragJson;
|
||||
if (this.isStart === undefined) {
|
||||
throw new Error(
|
||||
'Whether this event is the start of a drag is undefined. ' +
|
||||
'Either pass the value to the constructor, or call fromJson');
|
||||
}
|
||||
if (this.blockId === undefined) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['isStart'] = this.isStart;
|
||||
json['blockId'] = this.blockId;
|
||||
// TODO: I don't think we should actually apply the blocks array to the JSON
|
||||
// object b/c they have functions and aren't actually serializable.
|
||||
json['blocks'] = this.blocks;
|
||||
return json;
|
||||
}
|
||||
@@ -71,7 +82,7 @@ export class BlockDrag extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockDragJson) {
|
||||
super.fromJson(json);
|
||||
this.isStart = json['isStart'];
|
||||
this.blockId = json['blockId'];
|
||||
@@ -79,4 +90,10 @@ export class BlockDrag extends UiBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockDragJson extends AbstractEventJson {
|
||||
isStart: boolean;
|
||||
blockId: string;
|
||||
blocks?: Block[];
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag);
|
||||
|
||||
@@ -17,15 +17,15 @@ import {ConnectionType} from '../connection_type.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
|
||||
import {BlockBase} from './events_block_base.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
interface BlockLocation {
|
||||
parentId: string;
|
||||
inputName: string;
|
||||
coordinate: Coordinate|null;
|
||||
} // eslint-disable-line no-unused-vars
|
||||
parentId?: string;
|
||||
inputName?: string;
|
||||
coordinate?: Coordinate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
@@ -33,25 +33,19 @@ interface BlockLocation {
|
||||
* @alias Blockly.Events.BlockMove
|
||||
*/
|
||||
export class BlockMove extends BlockBase {
|
||||
override type: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldParentId!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldInputName!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldCoordinate!: Coordinate|null;
|
||||
override type = eventUtils.BLOCK_MOVE;
|
||||
oldParentId?: string;
|
||||
oldInputName?: string;
|
||||
oldCoordinate?: Coordinate;
|
||||
|
||||
newParentId: string|null = null;
|
||||
newInputName: string|null = null;
|
||||
newCoordinate: Coordinate|null = null;
|
||||
newParentId?: string;
|
||||
newInputName?: string;
|
||||
newCoordinate?: Coordinate;
|
||||
|
||||
/** @param opt_block The moved block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BLOCK_MOVE;
|
||||
|
||||
if (!opt_block) {
|
||||
return;
|
||||
}
|
||||
@@ -72,17 +66,13 @@ export class BlockMove extends BlockBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
if (this.newParentId) {
|
||||
json['newParentId'] = this.newParentId;
|
||||
}
|
||||
if (this.newInputName) {
|
||||
json['newInputName'] = this.newInputName;
|
||||
}
|
||||
override toJson(): BlockMoveJson {
|
||||
const json = super.toJson() as BlockMoveJson;
|
||||
json['newParentId'] = this.newParentId;
|
||||
json['newInputName'] = this.newInputName;
|
||||
if (this.newCoordinate) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
|
||||
Math.round(this.newCoordinate.y);
|
||||
json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` +
|
||||
`${Math.round(this.newCoordinate.y)}`;
|
||||
}
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
@@ -95,7 +85,7 @@ export class BlockMove extends BlockBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BlockMoveJson) {
|
||||
super.fromJson(json);
|
||||
this.newParentId = json['newParentId'];
|
||||
this.newInputName = json['newInputName'];
|
||||
@@ -124,19 +114,27 @@ export class BlockMove extends BlockBase {
|
||||
*/
|
||||
private currentLocation_(): BlockLocation {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The block associated with the block move event ' +
|
||||
'could not be found');
|
||||
}
|
||||
const location = {} as BlockLocation;
|
||||
const parent = block!.getParent();
|
||||
const parent = block.getParent();
|
||||
if (parent) {
|
||||
location.parentId = parent.id;
|
||||
// AnyDuringMigration because: Argument of type 'Block | null' is not
|
||||
// assignable to parameter of type 'Block'.
|
||||
const input = parent.getInputWithBlock(block as AnyDuringMigration);
|
||||
const input = parent.getInputWithBlock(block);
|
||||
if (input) {
|
||||
location.inputName = input.name;
|
||||
}
|
||||
} else {
|
||||
location.coordinate = block!.getRelativeToSurfaceXY();
|
||||
location.coordinate = block.getRelativeToSurfaceXY();
|
||||
}
|
||||
return location;
|
||||
}
|
||||
@@ -159,6 +157,11 @@ export class BlockMove extends BlockBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn('Can\'t move non-existent block: ' + this.blockId);
|
||||
@@ -206,4 +209,11 @@ export class BlockMove extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockMoveJson extends BlockBaseJson {
|
||||
newParentId?: string;
|
||||
newInputName?: string;
|
||||
newCoordinate?: string;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove);
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BubbleOpen');
|
||||
|
||||
import type {AbstractEventJson} from './events_abstract.js';
|
||||
import type {BlockSvg} from '../block_svg.js';
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -25,10 +25,10 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.BubbleOpen
|
||||
*/
|
||||
export class BubbleOpen extends UiBase {
|
||||
blockId: string|null;
|
||||
blockId?: string;
|
||||
isOpen?: boolean;
|
||||
bubbleType?: string;
|
||||
override type: string;
|
||||
bubbleType?: BubbleType;
|
||||
override type = eventUtils.BUBBLE_OPEN;
|
||||
|
||||
/**
|
||||
* @param opt_block The associated block. Undefined for a blank event.
|
||||
@@ -38,19 +38,18 @@ export class BubbleOpen extends UiBase {
|
||||
* 'warning'. Undefined for a blank event.
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: string) {
|
||||
opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
if (!opt_block) return;
|
||||
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** Whether the bubble is opening (false if closing). */
|
||||
this.isOpen = opt_isOpen;
|
||||
|
||||
/** The type of bubble. One of 'mutator', 'comment', or 'warning'. */
|
||||
this.bubbleType = opt_bubbleType;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.BUBBLE_OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,11 +57,21 @@ export class BubbleOpen extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): BubbleOpenJson {
|
||||
const json = super.toJson() as BubbleOpenJson;
|
||||
if (this.isOpen === undefined) {
|
||||
throw new Error(
|
||||
'Whether this event is for opening the bubble is undefined. ' +
|
||||
'Either pass the value to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.bubbleType) {
|
||||
throw new Error(
|
||||
'The type of bubble is undefined. Either pass the ' +
|
||||
'value to the constructor, or call fromJson');
|
||||
}
|
||||
json['isOpen'] = this.isOpen;
|
||||
json['bubbleType'] = this.bubbleType;
|
||||
json['blockId'] = this.blockId;
|
||||
json['blockId'] = this.blockId || '';
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -71,7 +80,7 @@ export class BubbleOpen extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: BubbleOpenJson) {
|
||||
super.fromJson(json);
|
||||
this.isOpen = json['isOpen'];
|
||||
this.bubbleType = json['bubbleType'];
|
||||
@@ -79,4 +88,16 @@ export class BubbleOpen extends UiBase {
|
||||
}
|
||||
}
|
||||
|
||||
export enum BubbleType {
|
||||
MUTATOR = 'mutator',
|
||||
COMMENT = 'comment',
|
||||
WARNING = 'warning',
|
||||
}
|
||||
|
||||
export interface BubbleOpenJson extends AbstractEventJson {
|
||||
isOpen: boolean;
|
||||
bubbleType: BubbleType;
|
||||
blockId: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen);
|
||||
|
||||
@@ -14,6 +14,7 @@ goog.declareModuleId('Blockly.Events.Click');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
@@ -25,9 +26,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.Click
|
||||
*/
|
||||
export class Click extends UiBase {
|
||||
blockId: AnyDuringMigration;
|
||||
targetType?: string;
|
||||
override type: string;
|
||||
blockId?: string;
|
||||
targetType?: ClickTarget;
|
||||
override type = eventUtils.CLICK;
|
||||
|
||||
/**
|
||||
* @param opt_block The affected block. Null for click events that do not have
|
||||
@@ -40,19 +41,17 @@ export class Click extends UiBase {
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: Block|null, opt_workspaceId?: string|null,
|
||||
opt_targetType?: string) {
|
||||
opt_targetType?: ClickTarget) {
|
||||
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
|
||||
if (workspaceId === null) {
|
||||
workspaceId = undefined;
|
||||
}
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
|
||||
this.blockId = opt_block ? opt_block.id : undefined;
|
||||
|
||||
/** The type of element targeted by this click event. */
|
||||
this.targetType = opt_targetType;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.CLICK;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,12 +59,15 @@ export class Click extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
json['targetType'] = this.targetType;
|
||||
if (this.blockId) {
|
||||
json['blockId'] = this.blockId;
|
||||
override toJson(): ClickJson {
|
||||
const json = super.toJson() as ClickJson;
|
||||
if (!this.targetType) {
|
||||
throw new Error(
|
||||
'The click target type is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['targetType'] = this.targetType;
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -74,11 +76,22 @@ export class Click extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: ClickJson) {
|
||||
super.fromJson(json);
|
||||
this.targetType = json['targetType'];
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
export enum ClickTarget {
|
||||
BLOCK = 'block',
|
||||
WORKSPACE = 'workspace',
|
||||
ZOOM_CONTROLS = 'zoom_controls',
|
||||
}
|
||||
|
||||
export interface ClickJson extends AbstractEventJson {
|
||||
targetType: ClickTarget;
|
||||
blockId?: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CLICK, Click);
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as utilsXml from '../utils/xml.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import type {CommentCreate} from './events_comment_create.js';
|
||||
import type {CommentDelete} from './events_comment_delete.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
@@ -28,9 +28,8 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.CommentBase
|
||||
*/
|
||||
export class CommentBase extends AbstractEvent {
|
||||
override isBlank: boolean;
|
||||
commentId: string;
|
||||
override workspaceId: string;
|
||||
override isBlank = true;
|
||||
commentId?: string;
|
||||
|
||||
/**
|
||||
* @param opt_comment The comment this event corresponds to. Undefined for a
|
||||
@@ -39,13 +38,15 @@ export class CommentBase extends AbstractEvent {
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super();
|
||||
/** Whether or not an event is blank. */
|
||||
this.isBlank = typeof opt_comment === 'undefined';
|
||||
this.isBlank = !opt_comment;
|
||||
|
||||
if (!opt_comment) return;
|
||||
|
||||
/** The ID of the comment this event pertains to. */
|
||||
this.commentId = this.isBlank ? '' : opt_comment!.id;
|
||||
this.commentId = opt_comment.id;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = this.isBlank ? '' : opt_comment!.workspace.id;
|
||||
this.workspaceId = opt_comment.workspace.id;
|
||||
|
||||
/**
|
||||
* The event group ID for the group this event belongs to. Groups define
|
||||
@@ -63,11 +64,14 @@ export class CommentBase extends AbstractEvent {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
if (this.commentId) {
|
||||
json['commentId'] = this.commentId;
|
||||
override toJson(): CommentBaseJson {
|
||||
const json = super.toJson() as CommentBaseJson;
|
||||
if (!this.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['commentId'] = this.commentId;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -76,7 +80,7 @@ export class CommentBase extends AbstractEvent {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: CommentBaseJson) {
|
||||
super.fromJson(json);
|
||||
this.commentId = json['commentId'];
|
||||
}
|
||||
@@ -92,9 +96,17 @@ export class CommentBase extends AbstractEvent {
|
||||
const workspace = event.getEventWorkspace_();
|
||||
if (create) {
|
||||
const xmlElement = utilsXml.createElement('xml');
|
||||
if (!event.xml) {
|
||||
throw new Error('Ecountered a comment event without proper xml');
|
||||
}
|
||||
xmlElement.appendChild(event.xml);
|
||||
Xml.domToWorkspace(xmlElement, workspace);
|
||||
} else {
|
||||
if (!event.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const comment = workspace.getCommentById(event.commentId);
|
||||
if (comment) {
|
||||
comment.dispose();
|
||||
@@ -106,3 +118,7 @@ export class CommentBase extends AbstractEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentBaseJson extends AbstractEventJson {
|
||||
commentId: string;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.CommentChange');
|
||||
import * as registry from '../registry.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
|
||||
import {CommentBase} from './events_comment_base.js';
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.CommentChange
|
||||
*/
|
||||
export class CommentChange extends CommentBase {
|
||||
override type: string;
|
||||
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldContents_!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
newContents_!: string;
|
||||
override type = eventUtils.COMMENT_CHANGE;
|
||||
oldContents_?: string;
|
||||
newContents_?: string;
|
||||
|
||||
/**
|
||||
* @param opt_comment The comment that is being changed. Undefined for a
|
||||
@@ -43,9 +40,6 @@ export class CommentChange extends CommentBase {
|
||||
opt_newContents?: string) {
|
||||
super(opt_comment);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.COMMENT_CHANGE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -61,8 +55,18 @@ export class CommentChange extends CommentBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): CommentChangeJson {
|
||||
const json = super.toJson() as CommentChangeJson;
|
||||
if (!this.oldContents_) {
|
||||
throw new Error(
|
||||
'The old contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.newContents_) {
|
||||
throw new Error(
|
||||
'The new contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['oldContents'] = this.oldContents_;
|
||||
json['newContents'] = this.newContents_;
|
||||
return json;
|
||||
@@ -73,7 +77,7 @@ export class CommentChange extends CommentBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: CommentChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.oldContents_ = json['oldContents'];
|
||||
this.newContents_ = json['newContents'];
|
||||
@@ -95,16 +99,35 @@ export class CommentChange extends CommentBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
const contents = forward ? this.newContents_ : this.oldContents_;
|
||||
|
||||
if (!contents) {
|
||||
if (forward) {
|
||||
throw new Error(
|
||||
'The new contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
throw new Error(
|
||||
'The old contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
comment.setContent(contents);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentChangeJson extends CommentBaseJson {
|
||||
oldContents: string;
|
||||
newContents: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as registry from '../registry.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {CommentBase} from './events_comment_base.js';
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.CommentCreate
|
||||
*/
|
||||
export class CommentCreate extends CommentBase {
|
||||
override type: string;
|
||||
override type = eventUtils.COMMENT_CREATE;
|
||||
|
||||
xml: AnyDuringMigration;
|
||||
xml?: Element|DocumentFragment;
|
||||
|
||||
/**
|
||||
* @param opt_comment The created comment.
|
||||
@@ -37,9 +37,6 @@ export class CommentCreate extends CommentBase {
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super(opt_comment);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.COMMENT_CREATE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return;
|
||||
}
|
||||
@@ -53,8 +50,13 @@ export class CommentCreate extends CommentBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): CommentCreateJson {
|
||||
const json = super.toJson() as CommentCreateJson;
|
||||
if (!this.xml) {
|
||||
throw new Error(
|
||||
'The comment XML is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
return json;
|
||||
}
|
||||
@@ -64,7 +66,7 @@ export class CommentCreate extends CommentBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: CommentCreateJson) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
}
|
||||
@@ -79,5 +81,9 @@ export class CommentCreate extends CommentBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentCreateJson extends CommentBaseJson {
|
||||
xml: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
|
||||
|
||||
@@ -25,8 +25,8 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.CommentDelete
|
||||
*/
|
||||
export class CommentDelete extends CommentBase {
|
||||
override type: string;
|
||||
xml: AnyDuringMigration;
|
||||
override type = eventUtils.COMMENT_DELETE;
|
||||
xml?: Element;
|
||||
|
||||
/**
|
||||
* @param opt_comment The deleted comment.
|
||||
@@ -35,34 +35,12 @@ export class CommentDelete extends CommentBase {
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super(opt_comment);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.COMMENT_DELETE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
this.xml = opt_comment.toXmlWithXY();
|
||||
}
|
||||
// TODO (#1266): "Full" and "minimal" serialization.
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
super.fromJson(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as registry from '../registry.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
|
||||
import {CommentBase} from './events_comment_base.js';
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -26,17 +26,11 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.CommentMove
|
||||
*/
|
||||
export class CommentMove extends CommentBase {
|
||||
override type: string;
|
||||
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
comment_!: WorkspaceComment;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldCoordinate_!: Coordinate;
|
||||
|
||||
override type = eventUtils.COMMENT_MOVE;
|
||||
comment_?: WorkspaceComment;
|
||||
oldCoordinate_?: Coordinate;
|
||||
/** The location after the move, in workspace coordinates. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Coordinate'.
|
||||
newCoordinate_: Coordinate = null as AnyDuringMigration;
|
||||
newCoordinate_?: Coordinate;
|
||||
|
||||
/**
|
||||
* @param opt_comment The comment that is being moved. Undefined for a blank
|
||||
@@ -45,16 +39,12 @@ export class CommentMove extends CommentBase {
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super(opt_comment);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.COMMENT_MOVE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
/**
|
||||
* The comment that is being moved. Will be cleared after recording the new
|
||||
* location.
|
||||
* The comment that is being moved.
|
||||
*/
|
||||
this.comment_ = opt_comment;
|
||||
|
||||
@@ -67,15 +57,17 @@ export class CommentMove extends CommentBase {
|
||||
* called once.
|
||||
*/
|
||||
recordNew() {
|
||||
if (!this.comment_) {
|
||||
if (this.newCoordinate_) {
|
||||
throw Error(
|
||||
'Tried to record the new position of a comment on the ' +
|
||||
'same event twice.');
|
||||
}
|
||||
if (!this.comment_) {
|
||||
throw new Error(
|
||||
'The comment is undefined. Pass a comment to ' +
|
||||
'the constructor if you want to use the record functionality');
|
||||
}
|
||||
this.newCoordinate_ = this.comment_.getXY();
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'WorkspaceComment'.
|
||||
this.comment_ = null as AnyDuringMigration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,16 +86,22 @@ export class CommentMove extends CommentBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
if (this.oldCoordinate_) {
|
||||
json['oldCoordinate'] = Math.round(this.oldCoordinate_.x) + ',' +
|
||||
Math.round(this.oldCoordinate_.y);
|
||||
override toJson(): CommentMoveJson {
|
||||
const json = super.toJson() as CommentMoveJson;
|
||||
if (!this.oldCoordinate_) {
|
||||
throw new Error(
|
||||
'The old comment position is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.newCoordinate_) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
|
||||
Math.round(this.newCoordinate_.y);
|
||||
if (!this.newCoordinate_) {
|
||||
throw new Error(
|
||||
'The new comment position is undefined. Either call recordNew, or ' +
|
||||
'call fromJson');
|
||||
}
|
||||
json['oldCoordinate'] = `${Math.round(this.oldCoordinate_.x)}, ` +
|
||||
`${Math.round(this.oldCoordinate_.y)}`;
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
|
||||
Math.round(this.newCoordinate_.y);
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -112,17 +110,12 @@ export class CommentMove extends CommentBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: CommentMoveJson) {
|
||||
super.fromJson(json);
|
||||
|
||||
if (json['oldCoordinate']) {
|
||||
const xy = json['oldCoordinate'].split(',');
|
||||
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
||||
}
|
||||
if (json['newCoordinate']) {
|
||||
const xy = json['newCoordinate'].split(',');
|
||||
this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
||||
}
|
||||
let xy = json['oldCoordinate'].split(',');
|
||||
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
||||
xy = json['newCoordinate'].split(',');
|
||||
this.newCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,6 +134,11 @@ export class CommentMove extends CommentBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t move non-existent comment: ' + this.commentId);
|
||||
@@ -148,10 +146,21 @@ export class CommentMove extends CommentBase {
|
||||
}
|
||||
|
||||
const target = forward ? this.newCoordinate_ : this.oldCoordinate_;
|
||||
if (!target) {
|
||||
throw new Error(
|
||||
'Either oldCoordinate_ or newCoordinate_ is undefined. ' +
|
||||
'Either pass a comment to the constructor and call recordNew, ' +
|
||||
'or call fromJson');
|
||||
}
|
||||
// TODO: Check if the comment is being dragged, and give up if so.
|
||||
const current = comment.getXY();
|
||||
comment.moveBy(target.x - current.x, target.y - current.y);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentMoveJson extends CommentBaseJson {
|
||||
oldCoordinate: string;
|
||||
newCoordinate: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.COMMENT_MOVE, CommentMove);
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {Block} from '../block.js';
|
||||
import {ASTNode} from '../keyboard_nav/ast_node.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
@@ -27,11 +28,11 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.MarkerMove
|
||||
*/
|
||||
export class MarkerMove extends UiBase {
|
||||
blockId: string|null;
|
||||
oldNode?: ASTNode|null;
|
||||
blockId?: string;
|
||||
oldNode?: ASTNode;
|
||||
newNode?: ASTNode;
|
||||
isCursor?: boolean;
|
||||
override type: string;
|
||||
override type = eventUtils.MARKER_MOVE;
|
||||
|
||||
/**
|
||||
* @param opt_block The affected block. Null if current node is of type
|
||||
@@ -52,20 +53,17 @@ export class MarkerMove extends UiBase {
|
||||
}
|
||||
super(workspaceId);
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
/** The block identifier for this event. */
|
||||
this.blockId = opt_block?.id;
|
||||
|
||||
/** The old node the marker used to be on. */
|
||||
this.oldNode = opt_oldNode;
|
||||
this.oldNode = opt_oldNode || undefined;
|
||||
|
||||
/** The new node the marker is now on. */
|
||||
this.newNode = opt_newNode;
|
||||
|
||||
/** Whether this is a cursor event. */
|
||||
this.isCursor = isCursor;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.MARKER_MOVE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +71,18 @@ export class MarkerMove extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): MarkerMoveJson {
|
||||
const json = super.toJson() as MarkerMoveJson;
|
||||
if (this.isCursor === undefined) {
|
||||
throw new Error(
|
||||
'Whether this is a cursor event or not is undefined. Either pass ' +
|
||||
'a value to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.newNode) {
|
||||
throw new Error(
|
||||
'The new node is undefined. Either pass a node to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['isCursor'] = this.isCursor;
|
||||
json['blockId'] = this.blockId;
|
||||
json['oldNode'] = this.oldNode;
|
||||
@@ -87,7 +95,7 @@ export class MarkerMove extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: MarkerMoveJson) {
|
||||
super.fromJson(json);
|
||||
this.isCursor = json['isCursor'];
|
||||
this.blockId = json['blockId'];
|
||||
@@ -96,4 +104,11 @@ export class MarkerMove extends UiBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MarkerMoveJson extends AbstractEventJson {
|
||||
isCursor: boolean;
|
||||
blockId?: string;
|
||||
oldNode?: ASTNode;
|
||||
newNode: ASTNode;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.MARKER_MOVE, MarkerMove);
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.Selected');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
@@ -24,9 +25,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.Selected
|
||||
*/
|
||||
export class Selected extends UiBase {
|
||||
oldElementId?: string|null;
|
||||
newElementId?: string|null;
|
||||
override type: string;
|
||||
oldElementId?: string;
|
||||
newElementId?: string;
|
||||
override type = eventUtils.SELECTED;
|
||||
|
||||
/**
|
||||
* @param opt_oldElementId The ID of the previously selected element. Null if
|
||||
@@ -41,14 +42,11 @@ export class Selected extends UiBase {
|
||||
opt_workspaceId?: string) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** The ID of the last selected element. */
|
||||
this.oldElementId = opt_oldElementId;
|
||||
/** The id of the last selected element. */
|
||||
this.oldElementId = opt_oldElementId ?? undefined;
|
||||
|
||||
/** The ID of the selected element. */
|
||||
this.newElementId = opt_newElementId;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.SELECTED;
|
||||
/** The id of the selected element. */
|
||||
this.newElementId = opt_newElementId ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,8 +54,8 @@ export class Selected extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): SelectedJson {
|
||||
const json = super.toJson() as SelectedJson;
|
||||
json['oldElementId'] = this.oldElementId;
|
||||
json['newElementId'] = this.newElementId;
|
||||
return json;
|
||||
@@ -68,11 +66,16 @@ export class Selected extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: SelectedJson) {
|
||||
super.fromJson(json);
|
||||
this.oldElementId = json['oldElementId'];
|
||||
this.newElementId = json['newElementId'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface SelectedJson extends AbstractEventJson {
|
||||
oldElementId?: string;
|
||||
newElementId?: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.SELECTED, Selected);
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.ThemeChange');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -25,7 +25,7 @@ import * as eventUtils from './utils.js';
|
||||
*/
|
||||
export class ThemeChange extends UiBase {
|
||||
themeName?: string;
|
||||
override type: string;
|
||||
override type = eventUtils.THEME_CHANGE;
|
||||
|
||||
/**
|
||||
* @param opt_themeName The theme name. Undefined for a blank event.
|
||||
@@ -37,9 +37,6 @@ export class ThemeChange extends UiBase {
|
||||
|
||||
/** The theme name. */
|
||||
this.themeName = opt_themeName;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.THEME_CHANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,8 +44,13 @@ export class ThemeChange extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): ThemeChangeJson {
|
||||
const json = super.toJson() as ThemeChangeJson;
|
||||
if (!this.themeName) {
|
||||
throw new Error(
|
||||
'The theme name is undefined. Either pass a theme name to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['themeName'] = this.themeName;
|
||||
return json;
|
||||
}
|
||||
@@ -58,10 +60,14 @@ export class ThemeChange extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: ThemeChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.themeName = json['themeName'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface ThemeChangeJson extends AbstractEventJson {
|
||||
themeName: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.THEME_CHANGE, ThemeChange);
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.ToolboxItemSelect');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -24,9 +24,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.ToolboxItemSelect
|
||||
*/
|
||||
export class ToolboxItemSelect extends UiBase {
|
||||
oldItem?: string|null;
|
||||
newItem?: string|null;
|
||||
override type: string;
|
||||
oldItem?: string;
|
||||
newItem?: string;
|
||||
override type = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||
|
||||
/**
|
||||
* @param opt_oldItem The previously selected toolbox item.
|
||||
@@ -42,13 +42,10 @@ export class ToolboxItemSelect extends UiBase {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** The previously selected toolbox item. */
|
||||
this.oldItem = opt_oldItem;
|
||||
this.oldItem = opt_oldItem ?? undefined;
|
||||
|
||||
/** The newly selected toolbox item. */
|
||||
this.newItem = opt_newItem;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||
this.newItem = opt_newItem ?? undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,8 +53,8 @@ export class ToolboxItemSelect extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): ToolboxItemSelectJson {
|
||||
const json = super.toJson() as ToolboxItemSelectJson;
|
||||
json['oldItem'] = this.oldItem;
|
||||
json['newItem'] = this.newItem;
|
||||
return json;
|
||||
@@ -68,12 +65,17 @@ export class ToolboxItemSelect extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: ToolboxItemSelectJson) {
|
||||
super.fromJson(json);
|
||||
this.oldItem = json['oldItem'];
|
||||
this.newItem = json['newItem'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface ToolboxItemSelectJson extends AbstractEventJson {
|
||||
oldItem?: string;
|
||||
newItem?: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect);
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.TrashcanOpen');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
@@ -25,7 +26,7 @@ import * as eventUtils from './utils.js';
|
||||
*/
|
||||
export class TrashcanOpen extends UiBase {
|
||||
isOpen?: boolean;
|
||||
override type: string;
|
||||
override type = eventUtils.TRASHCAN_OPEN;
|
||||
|
||||
/**
|
||||
* @param opt_isOpen Whether the trashcan flyout is opening (false if
|
||||
@@ -38,9 +39,6 @@ export class TrashcanOpen extends UiBase {
|
||||
|
||||
/** Whether the trashcan flyout is opening (false if closing). */
|
||||
this.isOpen = opt_isOpen;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.TRASHCAN_OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,8 +46,13 @@ export class TrashcanOpen extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): TrashcanOpenJson {
|
||||
const json = super.toJson() as TrashcanOpenJson;
|
||||
if (this.isOpen === undefined) {
|
||||
throw new Error(
|
||||
'Whether this is already open or not is undefined. Either pass ' +
|
||||
'a value to the constructor, or call fromJson');
|
||||
}
|
||||
json['isOpen'] = this.isOpen;
|
||||
return json;
|
||||
}
|
||||
@@ -59,10 +62,14 @@ export class TrashcanOpen extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: TrashcanOpenJson) {
|
||||
super.fromJson(json);
|
||||
this.isOpen = json['isOpen'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface TrashcanOpenJson extends AbstractEventJson {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.TRASHCAN_OPEN, TrashcanOpen);
|
||||
|
||||
@@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.Events.Ui');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -31,7 +30,7 @@ export class Ui extends UiBase {
|
||||
element: AnyDuringMigration;
|
||||
oldValue: AnyDuringMigration;
|
||||
newValue: AnyDuringMigration;
|
||||
override type: string;
|
||||
override type = eventUtils.UI;
|
||||
|
||||
/**
|
||||
* @param opt_block The affected block. Null for UI events that do not have
|
||||
@@ -50,9 +49,6 @@ export class Ui extends UiBase {
|
||||
this.element = typeof opt_element === 'undefined' ? '' : opt_element;
|
||||
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
||||
this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.UI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +57,7 @@ export class Ui extends UiBase {
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
const json = super.toJson() as AnyDuringMigration;
|
||||
json['element'] = this.element;
|
||||
if (this.newValue !== undefined) {
|
||||
json['newValue'] = this.newValue;
|
||||
|
||||
@@ -26,7 +26,7 @@ import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
* @alias Blockly.Events.UiBase
|
||||
*/
|
||||
export class UiBase extends AbstractEvent {
|
||||
override isBlank: boolean;
|
||||
override isBlank = true;
|
||||
override workspaceId: string;
|
||||
|
||||
// UI events do not undo or redo.
|
||||
|
||||
@@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.VarBase');
|
||||
|
||||
import type {VariableModel} from '../variable_model.js';
|
||||
|
||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -23,9 +23,8 @@ import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
* @alias Blockly.Events.VarBase
|
||||
*/
|
||||
export class VarBase extends AbstractEvent {
|
||||
override isBlank: AnyDuringMigration;
|
||||
varId: string;
|
||||
override workspaceId: string;
|
||||
override isBlank = true;
|
||||
varId?: string;
|
||||
|
||||
/**
|
||||
* @param opt_variable The variable this event corresponds to. Undefined for
|
||||
@@ -34,12 +33,13 @@ export class VarBase extends AbstractEvent {
|
||||
constructor(opt_variable?: VariableModel) {
|
||||
super();
|
||||
this.isBlank = typeof opt_variable === 'undefined';
|
||||
if (!opt_variable) return;
|
||||
|
||||
/** The variable ID for the variable this event pertains to. */
|
||||
this.varId = this.isBlank ? '' : opt_variable!.getId();
|
||||
/** The variable id for the variable this event pertains to. */
|
||||
this.varId = opt_variable.getId();
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = this.isBlank ? '' : opt_variable!.workspace.id;
|
||||
this.workspaceId = opt_variable.workspace.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,8 +47,13 @@ export class VarBase extends AbstractEvent {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): VarBaseJson {
|
||||
const json = super.toJson() as VarBaseJson;
|
||||
if (!this.varId) {
|
||||
throw new Error(
|
||||
'The var ID is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['varId'] = this.varId;
|
||||
return json;
|
||||
}
|
||||
@@ -58,8 +63,12 @@ export class VarBase extends AbstractEvent {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: VarBaseJson) {
|
||||
super.fromJson(json);
|
||||
this.varId = json['varId'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface VarBaseJson extends AbstractEventJson {
|
||||
varId: string;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarCreate');
|
||||
import * as registry from '../registry.js';
|
||||
import type {VariableModel} from '../variable_model.js';
|
||||
|
||||
import {VarBase} from './events_var_base.js';
|
||||
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.VarCreate
|
||||
*/
|
||||
export class VarCreate extends VarBase {
|
||||
override type: string;
|
||||
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
varType!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
varName!: string;
|
||||
override type = eventUtils.VAR_CREATE;
|
||||
varType?: string;
|
||||
varName?: string;
|
||||
|
||||
/**
|
||||
* @param opt_variable The created variable. Undefined for a blank event.
|
||||
@@ -38,9 +35,6 @@ export class VarCreate extends VarBase {
|
||||
constructor(opt_variable?: VariableModel) {
|
||||
super(opt_variable);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.VAR_CREATE;
|
||||
|
||||
if (!opt_variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -53,8 +47,18 @@ export class VarCreate extends VarBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): VarCreateJson {
|
||||
const json = super.toJson() as VarCreateJson;
|
||||
if (!this.varType) {
|
||||
throw new Error(
|
||||
'The var type is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.varName) {
|
||||
throw new Error(
|
||||
'The var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['varType'] = this.varType;
|
||||
json['varName'] = this.varName;
|
||||
return json;
|
||||
@@ -65,7 +69,7 @@ export class VarCreate extends VarBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: VarCreateJson) {
|
||||
super.fromJson(json);
|
||||
this.varType = json['varType'];
|
||||
this.varName = json['varName'];
|
||||
@@ -78,6 +82,16 @@ export class VarCreate extends VarBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.varId) {
|
||||
throw new Error(
|
||||
'The var ID is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.varName) {
|
||||
throw new Error(
|
||||
'The var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
workspace.createVariable(this.varName, this.varType, this.varId);
|
||||
} else {
|
||||
@@ -86,4 +100,9 @@ export class VarCreate extends VarBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface VarCreateJson extends VarBaseJson {
|
||||
varType: string;
|
||||
varName: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.VAR_CREATE, VarCreate);
|
||||
|
||||
@@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarDelete');
|
||||
import * as registry from '../registry.js';
|
||||
import type {VariableModel} from '../variable_model.js';
|
||||
|
||||
import {VarBase} from './events_var_base.js';
|
||||
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.VarDelete
|
||||
*/
|
||||
export class VarDelete extends VarBase {
|
||||
override type: string;
|
||||
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
varType!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
varName!: string;
|
||||
override type = eventUtils.VAR_DELETE;
|
||||
varType?: string;
|
||||
varName?: string;
|
||||
|
||||
/**
|
||||
* @param opt_variable The deleted variable. Undefined for a blank event.
|
||||
@@ -38,9 +35,6 @@ export class VarDelete extends VarBase {
|
||||
constructor(opt_variable?: VariableModel) {
|
||||
super(opt_variable);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.VAR_DELETE;
|
||||
|
||||
if (!opt_variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -53,8 +47,18 @@ export class VarDelete extends VarBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): VarDeleteJson {
|
||||
const json = super.toJson() as VarDeleteJson;
|
||||
if (!this.varType) {
|
||||
throw new Error(
|
||||
'The var type is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.varName) {
|
||||
throw new Error(
|
||||
'The var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['varType'] = this.varType;
|
||||
json['varName'] = this.varName;
|
||||
return json;
|
||||
@@ -65,7 +69,7 @@ export class VarDelete extends VarBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: VarDeleteJson) {
|
||||
super.fromJson(json);
|
||||
this.varType = json['varType'];
|
||||
this.varName = json['varName'];
|
||||
@@ -78,6 +82,16 @@ export class VarDelete extends VarBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.varId) {
|
||||
throw new Error(
|
||||
'The var ID is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.varName) {
|
||||
throw new Error(
|
||||
'The var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
workspace.deleteVariableById(this.varId);
|
||||
} else {
|
||||
@@ -86,4 +100,9 @@ export class VarDelete extends VarBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface VarDeleteJson extends VarBaseJson {
|
||||
varType: string;
|
||||
varName: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.VAR_DELETE, VarDelete);
|
||||
|
||||
@@ -15,7 +15,7 @@ goog.declareModuleId('Blockly.Events.VarRename');
|
||||
import * as registry from '../registry.js';
|
||||
import type {VariableModel} from '../variable_model.js';
|
||||
|
||||
import {VarBase} from './events_var_base.js';
|
||||
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.VarRename
|
||||
*/
|
||||
export class VarRename extends VarBase {
|
||||
override type: string;
|
||||
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
oldName!: string;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
newName!: string;
|
||||
override type = eventUtils.VAR_RENAME;
|
||||
oldName?: string;
|
||||
newName?: string;
|
||||
|
||||
/**
|
||||
* @param opt_variable The renamed variable. Undefined for a blank event.
|
||||
@@ -39,9 +36,6 @@ export class VarRename extends VarBase {
|
||||
constructor(opt_variable?: VariableModel, newName?: string) {
|
||||
super(opt_variable);
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.VAR_RENAME;
|
||||
|
||||
if (!opt_variable) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
@@ -54,8 +48,18 @@ export class VarRename extends VarBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): VarRenameJson {
|
||||
const json = super.toJson() as VarRenameJson;
|
||||
if (!this.oldName) {
|
||||
throw new Error(
|
||||
'The old var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.newName) {
|
||||
throw new Error(
|
||||
'The new var name is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['oldName'] = this.oldName;
|
||||
json['newName'] = this.newName;
|
||||
return json;
|
||||
@@ -66,7 +70,7 @@ export class VarRename extends VarBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: VarRenameJson) {
|
||||
super.fromJson(json);
|
||||
this.oldName = json['oldName'];
|
||||
this.newName = json['newName'];
|
||||
@@ -79,6 +83,21 @@ export class VarRename extends VarBase {
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.varId) {
|
||||
throw new Error(
|
||||
'The var ID is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.oldName) {
|
||||
throw new Error(
|
||||
'The old var name is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.newName) {
|
||||
throw new Error(
|
||||
'The new var name is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
workspace.renameVariableById(this.varId, this.newName);
|
||||
} else {
|
||||
@@ -87,4 +106,9 @@ export class VarRename extends VarBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface VarRenameJson extends VarBaseJson {
|
||||
oldName: string;
|
||||
newName: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.VAR_RENAME, VarRename);
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.ViewportChange');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ViewportChange extends UiBase {
|
||||
viewLeft?: number;
|
||||
scale?: number;
|
||||
oldScale?: number;
|
||||
override type: string;
|
||||
override type = eventUtils.VIEWPORT_CHANGE;
|
||||
|
||||
/**
|
||||
* @param opt_top Top-edge of the visible portion of the workspace, relative
|
||||
@@ -63,9 +63,6 @@ export class ViewportChange extends UiBase {
|
||||
|
||||
/** The old scale of the workspace. */
|
||||
this.oldScale = opt_oldScale;
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.VIEWPORT_CHANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +70,28 @@ export class ViewportChange extends UiBase {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = super.toJson();
|
||||
override toJson(): ViewportChangeJson {
|
||||
const json = super.toJson() as ViewportChangeJson;
|
||||
if (this.viewTop === undefined) {
|
||||
throw new Error(
|
||||
'The view top is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.viewLeft === undefined) {
|
||||
throw new Error(
|
||||
'The view left is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.scale === undefined) {
|
||||
throw new Error(
|
||||
'The scale is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.oldScale === undefined) {
|
||||
throw new Error(
|
||||
'The old scale is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['viewTop'] = this.viewTop;
|
||||
json['viewLeft'] = this.viewLeft;
|
||||
json['scale'] = this.scale;
|
||||
@@ -87,7 +104,7 @@ export class ViewportChange extends UiBase {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
override fromJson(json: ViewportChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.viewTop = json['viewTop'];
|
||||
this.viewLeft = json['viewLeft'];
|
||||
@@ -96,5 +113,12 @@ export class ViewportChange extends UiBase {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ViewportChangeJson extends AbstractEventJson {
|
||||
viewTop: number;
|
||||
viewLeft: number;
|
||||
scale: number;
|
||||
oldScale: number;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange);
|
||||
|
||||
@@ -536,6 +536,9 @@ export function disableOrphans(event: Abstract) {
|
||||
}
|
||||
const eventWorkspace =
|
||||
common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg;
|
||||
if (!blockEvent.blockId) {
|
||||
throw new Error('Encountered a blockEvent without a proper blockId');
|
||||
}
|
||||
let block = eventWorkspace.getBlockById(blockEvent.blockId);
|
||||
if (block) {
|
||||
// Changing blocks as part of this event shouldn't be undoable.
|
||||
|
||||
@@ -14,8 +14,7 @@ goog.declareModuleId('Blockly.Events.FinishedLoading');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
@@ -28,13 +27,9 @@ import * as eventUtils from './utils.js';
|
||||
* @alias Blockly.Events.FinishedLoading
|
||||
*/
|
||||
export class FinishedLoading extends AbstractEvent {
|
||||
override isBlank: boolean;
|
||||
override workspaceId: string;
|
||||
|
||||
// Workspace events do not undo or redo.
|
||||
override isBlank = true;
|
||||
override recordUndo = false;
|
||||
override type: string;
|
||||
override group: AnyDuringMigration;
|
||||
override type = eventUtils.FINISHED_LOADING;
|
||||
|
||||
/**
|
||||
* @param opt_workspace The workspace that has finished loading. Undefined
|
||||
@@ -43,13 +38,12 @@ export class FinishedLoading extends AbstractEvent {
|
||||
constructor(opt_workspace?: Workspace) {
|
||||
super();
|
||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
||||
this.isBlank = typeof opt_workspace === 'undefined';
|
||||
this.isBlank = !!opt_workspace;
|
||||
|
||||
if (!opt_workspace) return;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = opt_workspace ? opt_workspace.id : '';
|
||||
|
||||
/** Type of this event. */
|
||||
this.type = eventUtils.FINISHED_LOADING;
|
||||
this.workspaceId = opt_workspace.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,16 +51,14 @@ export class FinishedLoading extends AbstractEvent {
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AnyDuringMigration {
|
||||
const json = {
|
||||
'type': this.type,
|
||||
};
|
||||
if (this.group) {
|
||||
(json as AnyDuringMigration)['group'] = this.group;
|
||||
}
|
||||
if (this.workspaceId) {
|
||||
(json as AnyDuringMigration)['workspaceId'] = this.workspaceId;
|
||||
override toJson(): FinishedLoadingJson {
|
||||
const json = super.toJson() as FinishedLoadingJson;
|
||||
if (!this.workspaceId) {
|
||||
throw new Error(
|
||||
'The workspace ID is undefined. Either pass a workspace to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['workspaceId'] = this.workspaceId;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -75,12 +67,15 @@ export class FinishedLoading extends AbstractEvent {
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: AnyDuringMigration) {
|
||||
this.isBlank = false;
|
||||
override fromJson(json: FinishedLoadingJson) {
|
||||
super.fromJson(json);
|
||||
this.workspaceId = json['workspaceId'];
|
||||
this.group = json['group'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface FinishedLoadingJson extends AbstractEventJson {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
|
||||
|
||||
@@ -98,7 +98,7 @@ export function registerMutator(
|
||||
// Sanity checks passed.
|
||||
register(name, function(this: Block) {
|
||||
if (hasMutatorDialog) {
|
||||
this.setMutator(new Mutator(this as BlockSvg, opt_blockList || []));
|
||||
this.setMutator(new Mutator(opt_blockList || [], this as BlockSvg));
|
||||
}
|
||||
// Mixin the object.
|
||||
this.mixin(mixinObj);
|
||||
|
||||
229
core/field.ts
229
core/field.ts
@@ -54,9 +54,6 @@ import * as Xml from './xml.js';
|
||||
export abstract class Field implements IASTNodeLocationSvg,
|
||||
IASTNodeLocationWithBlock,
|
||||
IKeyboardAccessible, IRegistrable {
|
||||
/** The default value for this field. */
|
||||
protected DEFAULT_VALUE: any = null;
|
||||
|
||||
/** Non-breaking space. */
|
||||
static readonly NBSP = '\u00A0';
|
||||
|
||||
@@ -75,9 +72,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
protected value_: AnyDuringMigration;
|
||||
|
||||
/** Validation function called when user edits an editable field. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Function'.
|
||||
protected validator_: Function = null as AnyDuringMigration;
|
||||
protected validator_: Function|null = null;
|
||||
|
||||
/**
|
||||
* Used to cache the field's tooltip value if setTooltip is called when the
|
||||
@@ -90,44 +85,31 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* Holds the cursors svg element when the cursor is attached to the field.
|
||||
* This is null if there is no cursor on the field.
|
||||
*/
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGElement'.
|
||||
private cursorSvg_: SVGElement = null as AnyDuringMigration;
|
||||
private cursorSvg_: SVGElement|null = null;
|
||||
|
||||
/**
|
||||
* Holds the markers svg element when the marker is attached to the field.
|
||||
* This is null if there is no marker on the field.
|
||||
*/
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGElement'.
|
||||
private markerSvg_: SVGElement = null as AnyDuringMigration;
|
||||
private markerSvg_: SVGElement|null = null;
|
||||
|
||||
/** The rendered field's SVG group element. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGGElement'.
|
||||
protected fieldGroup_: SVGGElement = null as AnyDuringMigration;
|
||||
protected fieldGroup_: SVGGElement|null = null;
|
||||
|
||||
/** The rendered field's SVG border element. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGRectElement'.
|
||||
protected borderRect_: SVGRectElement = null as AnyDuringMigration;
|
||||
protected borderRect_: SVGRectElement|null = null;
|
||||
|
||||
/** The rendered field's SVG text element. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGTextElement'.
|
||||
protected textElement_: SVGTextElement = null as AnyDuringMigration;
|
||||
protected textElement_: SVGTextElement|null = null;
|
||||
|
||||
/** The rendered field's text content element. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type 'Text'.
|
||||
protected textContent_: Text = null as AnyDuringMigration;
|
||||
protected textContent_: Text|null = null;
|
||||
|
||||
/** Mouse down event listener data. */
|
||||
private mouseDownWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Constants associated with the source block's renderer. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'ConstantProvider'.
|
||||
protected constants_: ConstantProvider = null as AnyDuringMigration;
|
||||
protected constants_: ConstantProvider|null = null;
|
||||
|
||||
/**
|
||||
* Has this field been disposed of?
|
||||
@@ -140,8 +122,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
maxDisplayLength = 50;
|
||||
|
||||
/** Block this field is attached to. Starts as null, then set in init. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type 'Block'.
|
||||
protected sourceBlock_: Block = null as AnyDuringMigration;
|
||||
protected sourceBlock_: Block|null = null;
|
||||
|
||||
/** Does this block need to be re-rendered? */
|
||||
protected isDirty_ = true;
|
||||
@@ -155,9 +136,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
protected enabled_ = true;
|
||||
|
||||
/** The element the click handler is bound to. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Element'.
|
||||
protected clickTarget_: Element = null as AnyDuringMigration;
|
||||
protected clickTarget_: Element|null = null;
|
||||
|
||||
/**
|
||||
* The prefix field.
|
||||
@@ -208,7 +187,9 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* A generic value possessed by the field.
|
||||
* Should generally be non-null, only null when the field is created.
|
||||
*/
|
||||
this.value_ = (new.target).prototype.DEFAULT_VALUE;
|
||||
this.value_ = ('DEFAULT_VALUE' in (new.target).prototype) ?
|
||||
((new.target).prototype as AnyDuringMigration).DEFAULT_VALUE :
|
||||
null;
|
||||
|
||||
/** The size of the area rendered by the field. */
|
||||
this.size_ = new Size(0, 0);
|
||||
@@ -258,7 +239,8 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* @returns The renderer constant provider.
|
||||
*/
|
||||
getConstants(): ConstantProvider|null {
|
||||
if (!this.constants_ && this.sourceBlock_ && !this.sourceBlock_.disposed &&
|
||||
if (!this.constants_ && this.sourceBlock_ &&
|
||||
!this.sourceBlock_.isDeadOrDying() &&
|
||||
this.sourceBlock_.workspace.rendered) {
|
||||
this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg)
|
||||
.getRenderer()
|
||||
@@ -271,8 +253,9 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* Get the block this field is attached to.
|
||||
*
|
||||
* @returns The block containing this field.
|
||||
* @throws An error if the source block is not defined.
|
||||
*/
|
||||
getSourceBlock(): Block {
|
||||
getSourceBlock(): Block|null {
|
||||
return this.sourceBlock_;
|
||||
}
|
||||
|
||||
@@ -361,9 +344,11 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* do custom input handling.
|
||||
*/
|
||||
protected bindEvents_() {
|
||||
Tooltip.bindMouseEvents(this.getClickTarget_());
|
||||
const clickTarget = this.getClickTarget_();
|
||||
if (!clickTarget) throw new Error('A click target has not been set.');
|
||||
Tooltip.bindMouseEvents(clickTarget);
|
||||
this.mouseDownWrapper_ = browserEvents.conditionalBind(
|
||||
this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
||||
clickTarget, 'mousedown', this, this.onMouseDown_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -431,7 +416,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* Used to see if `this` has overridden any relevant hooks.
|
||||
* @returns The stringified version of the XML state, or null.
|
||||
*/
|
||||
protected saveLegacyState(callingClass: AnyDuringMigration): string|null {
|
||||
protected saveLegacyState(callingClass: FieldProto): string|null {
|
||||
if (callingClass.prototype.saveState === this.saveState &&
|
||||
callingClass.prototype.toXml !== this.toXml) {
|
||||
const elem = utilsXml.createElement('field');
|
||||
@@ -454,7 +439,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* @param state The state to apply to the field.
|
||||
* @returns Whether the state was applied or not.
|
||||
*/
|
||||
loadLegacyState(callingClass: AnyDuringMigration, state: AnyDuringMigration):
|
||||
loadLegacyState(callingClass: FieldProto, state: AnyDuringMigration):
|
||||
boolean {
|
||||
if (callingClass.prototype.loadState === this.loadState &&
|
||||
callingClass.prototype.fromXml !== this.fromXml) {
|
||||
@@ -488,16 +473,17 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
/** Add or remove the UI indicating if this field is editable or not. */
|
||||
updateEditable() {
|
||||
const group = this.fieldGroup_;
|
||||
if (!this.EDITABLE || !group) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!this.EDITABLE || !group || !block) {
|
||||
return;
|
||||
}
|
||||
if (this.enabled_ && this.sourceBlock_.isEditable()) {
|
||||
group.classList.add('blocklyEditableText');
|
||||
group.classList.remove('blocklyNonEditableText');
|
||||
if (this.enabled_ && block.isEditable()) {
|
||||
dom.addClass(group, 'blocklyEditableText');
|
||||
dom.removeClass(group, 'blocklyNonEditableText');
|
||||
group.style.cursor = this.CURSOR;
|
||||
} else {
|
||||
group.classList.add('blocklyNonEditableText');
|
||||
group.classList.remove('blocklyEditableText');
|
||||
dom.addClass(group, 'blocklyNonEditableText');
|
||||
dom.removeClass(group, 'blocklyEditableText');
|
||||
group.style.cursor = '';
|
||||
}
|
||||
}
|
||||
@@ -590,7 +576,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
return;
|
||||
}
|
||||
this.visible_ = visible;
|
||||
const root = this.getSvgRoot();
|
||||
const root = this.fieldGroup_;
|
||||
if (root) {
|
||||
root.style.display = visible ? 'block' : 'none';
|
||||
}
|
||||
@@ -630,10 +616,49 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns The group element.
|
||||
*/
|
||||
getSvgRoot(): SVGGElement {
|
||||
getSvgRoot(): SVGGElement|null {
|
||||
return this.fieldGroup_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the border rectangle element.
|
||||
*
|
||||
* @returns The border rectangle element.
|
||||
* @throws An error if the border rectangle element is not defined.
|
||||
*/
|
||||
protected getBorderRect(): SVGRectElement {
|
||||
if (!this.borderRect_) {
|
||||
throw new Error(`The border rectangle is ${this.borderRect_}.`);
|
||||
}
|
||||
return this.borderRect_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text element.
|
||||
*
|
||||
* @returns The text element.
|
||||
* @throws An error if the text element is not defined.
|
||||
*/
|
||||
protected getTextElement(): SVGTextElement {
|
||||
if (!this.textElement_) {
|
||||
throw new Error(`The text element is ${this.textElement_}.`);
|
||||
}
|
||||
return this.textElement_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text content.
|
||||
*
|
||||
* @returns The text content.
|
||||
* @throws An error if the text content is not defined.
|
||||
*/
|
||||
protected getTextContent(): Text {
|
||||
if (!this.textContent_) {
|
||||
throw new Error(`The text content is ${this.textContent_}.`);
|
||||
}
|
||||
return this.textContent_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the field to match the colour/style of the block. Should only be
|
||||
* called by BlockSvg.applyColour().
|
||||
@@ -726,20 +751,19 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
const constants = this.getConstants();
|
||||
const halfHeight = this.size_.height / 2;
|
||||
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.textElement_.setAttribute(
|
||||
'x',
|
||||
(this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset :
|
||||
xOffset) as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
`${
|
||||
this.getSourceBlock()?.RTL ?
|
||||
this.size_.width - contentWidth - xOffset :
|
||||
xOffset}`);
|
||||
this.textElement_.setAttribute(
|
||||
'y',
|
||||
(constants!.FIELD_TEXT_BASELINE_CENTER ?
|
||||
halfHeight :
|
||||
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
||||
constants!.FIELD_TEXT_BASELINE) as AnyDuringMigration);
|
||||
`${
|
||||
constants!.FIELD_TEXT_BASELINE_CENTER ?
|
||||
halfHeight :
|
||||
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
||||
constants!.FIELD_TEXT_BASELINE}`);
|
||||
}
|
||||
|
||||
/** Position a field's border rect after a size change. */
|
||||
@@ -747,24 +771,12 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
if (!this.borderRect_) {
|
||||
return;
|
||||
}
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.borderRect_.setAttribute('width', `${this.size_.width}`);
|
||||
this.borderRect_.setAttribute('height', `${this.size_.height}`);
|
||||
this.borderRect_.setAttribute(
|
||||
'width', this.size_.width as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
this.borderRect_.setAttribute(
|
||||
'height', this.size_.height as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.borderRect_.setAttribute(
|
||||
'rx',
|
||||
this.getConstants()!.FIELD_BORDER_RECT_RADIUS as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.borderRect_.setAttribute(
|
||||
'ry',
|
||||
this.getConstants()!.FIELD_BORDER_RECT_RADIUS as AnyDuringMigration);
|
||||
'ry', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,10 +797,13 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
} else if (this.visible_ && this.size_.width === 0) {
|
||||
// If the field is not visible the width will be 0 as well, one of the
|
||||
// problems with the old system.
|
||||
console.warn(
|
||||
'Deprecated use of setting size_.width to 0 to rerender a' +
|
||||
' field. Set field.isDirty_ to true instead.');
|
||||
this.render_();
|
||||
// Don't issue a warning if the field is actually zero width.
|
||||
if (this.size_.width !== 0) {
|
||||
console.warn(
|
||||
'Deprecated use of setting size_.width to 0 to rerender a' +
|
||||
' field. Set field.isDirty_ to true instead.');
|
||||
}
|
||||
}
|
||||
return this.size_;
|
||||
}
|
||||
@@ -805,12 +820,17 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
let scaledWidth;
|
||||
let scaledHeight;
|
||||
let xy;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
if (!this.borderRect_) {
|
||||
// Browsers are inconsistent in what they return for a bounding box.
|
||||
// - Webkit / Blink: fill-box / object bounding box
|
||||
// - Gecko: stroke-box
|
||||
const bBox = (this.sourceBlock_ as BlockSvg).getHeightWidth();
|
||||
const scale = (this.sourceBlock_.workspace as WorkspaceSvg).scale;
|
||||
const scale = (block.workspace as WorkspaceSvg).scale;
|
||||
xy = this.getAbsoluteXY_();
|
||||
scaledWidth = (bBox.width + 1) * scale;
|
||||
scaledHeight = (bBox.height + 1) * scale;
|
||||
@@ -896,9 +916,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*/
|
||||
markDirty() {
|
||||
this.isDirty_ = true;
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'ConstantProvider'.
|
||||
this.constants_ = null as AnyDuringMigration;
|
||||
this.constants_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1050,7 +1068,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
protected onMouseDown_(e: Event) {
|
||||
if (!this.sourceBlock_ || this.sourceBlock_.disposed) {
|
||||
if (!this.sourceBlock_ || this.sourceBlock_.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
const gesture = (this.sourceBlock_.workspace as WorkspaceSvg).getGesture(e);
|
||||
@@ -1101,7 +1119,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns Element to bind click handler to.
|
||||
*/
|
||||
protected getClickTarget_(): Element {
|
||||
protected getClickTarget_(): Element|null {
|
||||
return this.clickTarget_ || this.getSvgRoot();
|
||||
}
|
||||
|
||||
@@ -1145,7 +1163,10 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*/
|
||||
getParentInput(): Input {
|
||||
let parentInput = null;
|
||||
const block = this.sourceBlock_;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const inputs = block.inputList;
|
||||
|
||||
for (let idx = 0; idx < block.inputList.length; idx++) {
|
||||
@@ -1158,9 +1179,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
}
|
||||
}
|
||||
}
|
||||
// AnyDuringMigration because: Type 'Input | null' is not assignable to
|
||||
// type 'Input'.
|
||||
return parentInput as AnyDuringMigration;
|
||||
return parentInput!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1199,12 +1218,13 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*/
|
||||
setCursorSvg(cursorSvg: SVGElement) {
|
||||
if (!cursorSvg) {
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGElement'.
|
||||
this.cursorSvg_ = null as AnyDuringMigration;
|
||||
this.cursorSvg_ = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fieldGroup_) {
|
||||
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||
}
|
||||
this.fieldGroup_.appendChild(cursorSvg);
|
||||
this.cursorSvg_ = cursorSvg;
|
||||
}
|
||||
@@ -1217,19 +1237,24 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
*/
|
||||
setMarkerSvg(markerSvg: SVGElement) {
|
||||
if (!markerSvg) {
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGElement'.
|
||||
this.markerSvg_ = null as AnyDuringMigration;
|
||||
this.markerSvg_ = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.fieldGroup_) {
|
||||
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||
}
|
||||
this.fieldGroup_.appendChild(markerSvg);
|
||||
this.markerSvg_ = markerSvg;
|
||||
}
|
||||
|
||||
/** Redraw any attached marker or cursor svgs if needed. */
|
||||
protected updateMarkers_() {
|
||||
const workspace = this.sourceBlock_.workspace as WorkspaceSvg;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const workspace = block.workspace as WorkspaceSvg;
|
||||
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
|
||||
workspace.getCursor()!.draw();
|
||||
}
|
||||
@@ -1246,3 +1271,23 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
export interface FieldConfig {
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* For use by Field and descendants of Field. Constructors can change
|
||||
* in descendants, though they should contain all of Field's prototype methods.
|
||||
*/
|
||||
export type FieldProto = Pick<typeof Field, 'prototype'>;
|
||||
|
||||
/**
|
||||
* Represents an error where the field is trying to access its block or
|
||||
* information about its block before it has actually been attached to said
|
||||
* block.
|
||||
*/
|
||||
export class UnattachedFieldError extends Error {
|
||||
/** @internal */
|
||||
constructor() {
|
||||
super(
|
||||
'The field has not yet been attached to its input. ' +
|
||||
'Call appendField to attach it.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as Css from './css.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
@@ -70,19 +70,36 @@ export class FieldAngle extends FieldTextInput {
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
*/
|
||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||
private clockwise_: boolean;
|
||||
private offset_: number;
|
||||
private wrap_: number;
|
||||
private round_: number;
|
||||
|
||||
/**
|
||||
* Whether the angle should increase as the angle picker is moved clockwise
|
||||
* (true) or counterclockwise (false).
|
||||
*/
|
||||
private clockwise_ = FieldAngle.CLOCKWISE;
|
||||
|
||||
/**
|
||||
* The offset of zero degrees (and all other angles).
|
||||
*/
|
||||
private offset_ = FieldAngle.OFFSET;
|
||||
|
||||
/**
|
||||
* The maximum angle to allow before wrapping.
|
||||
*/
|
||||
private wrap_ = FieldAngle.WRAP;
|
||||
|
||||
/**
|
||||
* The amount to round angles to when using a mouse or keyboard nav input.
|
||||
*/
|
||||
private round_ = FieldAngle.ROUND;
|
||||
|
||||
/** The angle picker's SVG element. */
|
||||
private editor_: SVGElement|null = null;
|
||||
private editor_: SVGSVGElement|null = null;
|
||||
|
||||
/** The angle picker's gauge path depending on the value. */
|
||||
gauge_: SVGElement|null = null;
|
||||
gauge_: SVGPathElement|null = null;
|
||||
|
||||
/** The angle picker's line drawn representing the value's angle. */
|
||||
line_: SVGElement|null = null;
|
||||
line_: SVGLineElement|null = null;
|
||||
|
||||
/** The degree symbol for this field. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
@@ -122,35 +139,6 @@ export class FieldAngle extends FieldTextInput {
|
||||
opt_config?: FieldAngleConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
/**
|
||||
* Should the angle increase as the angle picker is moved clockwise (true)
|
||||
* or counterclockwise (false)
|
||||
*
|
||||
* @see FieldAngle.CLOCKWISE
|
||||
*/
|
||||
this.clockwise_ = FieldAngle.CLOCKWISE;
|
||||
|
||||
/**
|
||||
* The offset of zero degrees (and all other angles).
|
||||
*
|
||||
* @see FieldAngle.OFFSET
|
||||
*/
|
||||
this.offset_ = FieldAngle.OFFSET;
|
||||
|
||||
/**
|
||||
* The maximum angle to allow before wrapping.
|
||||
*
|
||||
* @see FieldAngle.WRAP
|
||||
*/
|
||||
this.wrap_ = FieldAngle.WRAP;
|
||||
|
||||
/**
|
||||
* The amount to round angles to when using a mouse or keyboard nav input.
|
||||
*
|
||||
* @see FieldAngle.ROUND
|
||||
*/
|
||||
this.round_ = FieldAngle.ROUND;
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
@@ -202,7 +190,7 @@ export class FieldAngle extends FieldTextInput {
|
||||
// #2380)
|
||||
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
|
||||
this.symbol_.appendChild(document.createTextNode('°'));
|
||||
this.textElement_.appendChild(this.symbol_);
|
||||
this.getTextElement().appendChild(this.symbol_);
|
||||
}
|
||||
|
||||
/** Updates the graph when the field rerenders. */
|
||||
@@ -223,9 +211,7 @@ export class FieldAngle extends FieldTextInput {
|
||||
super.showEditor_(opt_e, noFocus);
|
||||
|
||||
this.dropdownCreate_();
|
||||
// AnyDuringMigration because: Argument of type 'SVGElement | null' is not
|
||||
// assignable to parameter of type 'Node'.
|
||||
dropDownDiv.getContentDiv().appendChild(this.editor_ as AnyDuringMigration);
|
||||
dropDownDiv.getContentDiv().appendChild(this.editor_!);
|
||||
|
||||
if (this.sourceBlock_ instanceof BlockSvg) {
|
||||
dropDownDiv.setColour(
|
||||
@@ -426,26 +412,23 @@ export class FieldAngle extends FieldTextInput {
|
||||
*/
|
||||
protected override onHtmlInputKeyDown_(e: Event) {
|
||||
super.onHtmlInputKeyDown_(e);
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
const keyboardEvent = e as KeyboardEvent;
|
||||
let multiplier;
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
if ((e as AnyDuringMigration).keyCode === KeyCodes.LEFT) {
|
||||
if (keyboardEvent.keyCode === KeyCodes.LEFT) {
|
||||
// decrement (increment in RTL)
|
||||
multiplier = this.sourceBlock_.RTL ? 1 : -1;
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.RIGHT) {
|
||||
multiplier = block.RTL ? 1 : -1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.RIGHT) {
|
||||
// increment (decrement in RTL)
|
||||
multiplier = this.sourceBlock_.RTL ? -1 : 1;
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.DOWN) {
|
||||
multiplier = block.RTL ? -1 : 1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.DOWN) {
|
||||
// decrement
|
||||
multiplier = -1;
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.UP) {
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.UP) {
|
||||
// increment
|
||||
multiplier = 1;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.FieldCheckbox');
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_block_change.js';
|
||||
|
||||
import * as dom from './utils/dom.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
@@ -111,8 +112,9 @@ export class FieldCheckbox extends Field {
|
||||
override initView() {
|
||||
super.initView();
|
||||
|
||||
this.textElement_.classList.add('blocklyCheckbox');
|
||||
this.textElement_.style.display = this.value_ ? 'block' : 'none';
|
||||
const textElement = this.getTextElement();
|
||||
dom.addClass(textElement, 'blocklyCheckbox');
|
||||
textElement.style.display = this.value_ ? 'block' : 'none';
|
||||
}
|
||||
|
||||
override render_() {
|
||||
|
||||
@@ -18,6 +18,7 @@ import './events/events_block_change.js';
|
||||
import {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as Css from './css.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
@@ -190,7 +191,7 @@ export class FieldColour extends Field {
|
||||
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT);
|
||||
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
|
||||
this.createBorderRect_();
|
||||
this.borderRect_.style['fillOpacity'] = '1';
|
||||
this.getBorderRect().style['fillOpacity'] = '1';
|
||||
} else if (this.sourceBlock_ instanceof BlockSvg) {
|
||||
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
|
||||
}
|
||||
@@ -438,7 +439,7 @@ export class FieldColour extends Field {
|
||||
(this.picker_ as AnyDuringMigration)!.blur();
|
||||
const highlighted = this.getHighlighted_();
|
||||
if (highlighted) {
|
||||
highlighted.classList.remove('blocklyColourHighlighted');
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,10 +474,10 @@ export class FieldColour extends Field {
|
||||
// Unhighlight the current item.
|
||||
const highlighted = this.getHighlighted_();
|
||||
if (highlighted) {
|
||||
highlighted.classList.remove('blocklyColourHighlighted');
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
// Highlight new item.
|
||||
cell.classList.add('blocklyColourHighlighted');
|
||||
dom.addClass(cell, 'blocklyColourHighlighted');
|
||||
// Set new highlighted index.
|
||||
this.highlightedIndex_ = index;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ goog.declareModuleId('Blockly.FieldDropdown');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import {FieldConfig, Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
@@ -27,8 +27,6 @@ import * as parsing from './utils/parsing.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import * as utilsString from './utils/string.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for an editable dropdown field.
|
||||
@@ -44,7 +42,8 @@ export class FieldDropdown extends Field {
|
||||
* height.
|
||||
*/
|
||||
static MAX_MENU_HEIGHT_VH = 0.45;
|
||||
static ARROW_CHAR: AnyDuringMigration;
|
||||
|
||||
static ARROW_CHAR = '▾';
|
||||
|
||||
/** A reference to the currently selected menu item. */
|
||||
private selectedMenuItem_: MenuItem|null = null;
|
||||
@@ -71,14 +70,11 @@ export class FieldDropdown extends Field {
|
||||
|
||||
/** Mouse cursor style when over the hotspot that initiates the editor. */
|
||||
override CURSOR = 'default';
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
protected menuGenerator_!: AnyDuringMigration[][]|
|
||||
((this: FieldDropdown) => AnyDuringMigration[][]);
|
||||
|
||||
protected menuGenerator_?: MenuGenerator;
|
||||
|
||||
/** A cache of the most recently generated options. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'string[][]'.
|
||||
private generatedOptions_: string[][] = null as AnyDuringMigration;
|
||||
private generatedOptions_: MenuOption[]|null = null;
|
||||
|
||||
/**
|
||||
* The prefix field label, of common words set after options are trimmed.
|
||||
@@ -95,7 +91,7 @@ export class FieldDropdown extends Field {
|
||||
override suffixField: string|null = null;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
private selectedOption_!: Array<string|ImageProperties>;
|
||||
override clickTarget_: AnyDuringMigration;
|
||||
override clickTarget_: SVGElement|null = null;
|
||||
|
||||
/**
|
||||
* @param menuGenerator A non-empty array of options for a dropdown list, or a
|
||||
@@ -114,30 +110,31 @@ export class FieldDropdown extends Field {
|
||||
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
|
||||
*/
|
||||
constructor(
|
||||
menuGenerator: AnyDuringMigration[][]|Function|Sentinel,
|
||||
opt_validator?: Function, opt_config?: FieldConfig) {
|
||||
menuGenerator: MenuGenerator,
|
||||
opt_validator?: Function,
|
||||
opt_config?: FieldConfig,
|
||||
);
|
||||
constructor(menuGenerator: Sentinel);
|
||||
constructor(
|
||||
menuGenerator: MenuGenerator|Sentinel,
|
||||
opt_validator?: Function,
|
||||
opt_config?: FieldConfig,
|
||||
) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
// If we pass SKIP_SETUP, don't do *anything* with the menu generator.
|
||||
if (menuGenerator === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
if (!isMenuGenerator(menuGenerator)) return;
|
||||
|
||||
if (Array.isArray(menuGenerator)) {
|
||||
validateOptions(menuGenerator);
|
||||
// Deep copy the option structure so it doesn't change.
|
||||
menuGenerator = JSON.parse(JSON.stringify(menuGenerator));
|
||||
const trimmed = trimOptions(menuGenerator);
|
||||
this.menuGenerator_ = trimmed.options;
|
||||
this.prefixField = trimmed.prefix || null;
|
||||
this.suffixField = trimmed.suffix || null;
|
||||
} else {
|
||||
this.menuGenerator_ = menuGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of options for a dropdown list,
|
||||
* or a function which generates these options.
|
||||
*/
|
||||
this.menuGenerator_ = menuGenerator as AnyDuringMigration[][] |
|
||||
((this: FieldDropdown) => AnyDuringMigration[][]);
|
||||
|
||||
this.trimOptions_();
|
||||
|
||||
/**
|
||||
* The currently selected option. The field is initialized with the
|
||||
* first option selected.
|
||||
@@ -205,7 +202,7 @@ export class FieldDropdown extends Field {
|
||||
}
|
||||
|
||||
if (this.borderRect_) {
|
||||
this.borderRect_.classList.add('blocklyDropdownRect');
|
||||
dom.addClass(this.borderRect_, 'blocklyDropdownRect');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,24 +214,19 @@ export class FieldDropdown extends Field {
|
||||
protected shouldAddBorderRect_(): boolean {
|
||||
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
||||
this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
|
||||
!this.sourceBlock_.isShadow();
|
||||
!this.getSourceBlock()?.isShadow();
|
||||
}
|
||||
|
||||
/** Create a tspan based arrow. */
|
||||
protected createTextArrow_() {
|
||||
this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
|
||||
this.arrow_!.appendChild(document.createTextNode(
|
||||
this.sourceBlock_.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
||||
' ' + FieldDropdown.ARROW_CHAR));
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// AnyDuringMigration because: Argument of type 'SVGTSpanElement | null'
|
||||
// is not assignable to parameter of type 'Node'.
|
||||
this.textElement_.insertBefore(
|
||||
this.arrow_ as AnyDuringMigration, this.textContent_);
|
||||
this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
||||
' ' + FieldDropdown.ARROW_CHAR));
|
||||
if (this.getSourceBlock()?.RTL) {
|
||||
this.getTextElement().insertBefore(this.arrow_, this.textContent_);
|
||||
} else {
|
||||
// AnyDuringMigration because: Argument of type 'SVGTSpanElement | null'
|
||||
// is not assignable to parameter of type 'Node'.
|
||||
this.textElement_.appendChild(this.arrow_ as AnyDuringMigration);
|
||||
this.getTextElement().appendChild(this.arrow_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,17 +249,14 @@ export class FieldDropdown extends Field {
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: Event) {
|
||||
protected override showEditor_(opt_e?: MouseEvent) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.dropdownCreate_();
|
||||
// AnyDuringMigration because: Property 'clientX' does not exist on type
|
||||
// 'Event'.
|
||||
if (opt_e && typeof (opt_e as AnyDuringMigration).clientX === 'number') {
|
||||
// AnyDuringMigration because: Property 'clientY' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'clientX' does not exist
|
||||
// on type 'Event'.
|
||||
this.menu_!.openingCoords = new Coordinate(
|
||||
(opt_e as AnyDuringMigration).clientX,
|
||||
(opt_e as AnyDuringMigration).clientY);
|
||||
if (opt_e && typeof opt_e.clientX === 'number') {
|
||||
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
||||
} else {
|
||||
this.menu_!.openingCoords = null;
|
||||
}
|
||||
@@ -276,14 +265,13 @@ export class FieldDropdown extends Field {
|
||||
dropDownDiv.clearContent();
|
||||
// Element gets created in render.
|
||||
const menuElement = this.menu_!.render(dropDownDiv.getContentDiv());
|
||||
menuElement.classList.add('blocklyDropdownMenu');
|
||||
dom.addClass(menuElement, 'blocklyDropdownMenu');
|
||||
|
||||
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
|
||||
const primaryColour = this.sourceBlock_.isShadow() ?
|
||||
this.sourceBlock_.getParent()!.getColour() :
|
||||
this.sourceBlock_.getColour();
|
||||
const borderColour = this.sourceBlock_.isShadow() ?
|
||||
(this.sourceBlock_.getParent() as BlockSvg).style.colourTertiary :
|
||||
const primaryColour =
|
||||
block.isShadow() ? block.getParent()!.getColour() : block.getColour();
|
||||
const borderColour = block.isShadow() ?
|
||||
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
dropDownDiv.setColour(primaryColour, borderColour);
|
||||
}
|
||||
@@ -304,6 +292,10 @@ export class FieldDropdown extends Field {
|
||||
|
||||
/** Create the dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const menu = new Menu();
|
||||
menu.setRole(aria.Role.LISTBOX);
|
||||
this.menu_ = menu;
|
||||
@@ -311,18 +303,20 @@ export class FieldDropdown extends Field {
|
||||
const options = this.getOptions(false);
|
||||
this.selectedMenuItem_ = null;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let content = options[i][0]; // Human-readable text or image.
|
||||
const value = options[i][1]; // Language-neutral value.
|
||||
if (typeof content === 'object') {
|
||||
// An image, not text.
|
||||
const image = new Image(content['width'], content['height']);
|
||||
image.src = content['src'];
|
||||
image.alt = content['alt'] || '';
|
||||
content = image;
|
||||
}
|
||||
const [label, value] = options[i];
|
||||
const content = (() => {
|
||||
if (typeof label === 'object') {
|
||||
// Convert ImageProperties to an HTMLImageElement.
|
||||
const image = new Image(label['width'], label['height']);
|
||||
image.src = label['src'];
|
||||
image.alt = label['alt'] || '';
|
||||
return image;
|
||||
}
|
||||
return label;
|
||||
})();
|
||||
const menuItem = new MenuItem(content, value);
|
||||
menuItem.setRole(aria.Role.OPTION);
|
||||
menuItem.setRightToLeft(this.sourceBlock_.RTL);
|
||||
menuItem.setRightToLeft(block.RTL);
|
||||
menuItem.setCheckable(true);
|
||||
menu.addChild(menuItem);
|
||||
menuItem.setChecked(value === this.value_);
|
||||
@@ -365,57 +359,6 @@ export class FieldDropdown extends Field {
|
||||
this.setValue(menuItem.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Factor out common words in statically defined options.
|
||||
* Create prefix and/or suffix labels.
|
||||
*/
|
||||
private trimOptions_() {
|
||||
const options = this.menuGenerator_;
|
||||
if (!Array.isArray(options)) {
|
||||
return;
|
||||
}
|
||||
let hasImages = false;
|
||||
|
||||
// Localize label text and image alt text.
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const label = options[i][0];
|
||||
if (typeof label === 'string') {
|
||||
options[i][0] = parsing.replaceMessageReferences(label);
|
||||
} else {
|
||||
if (label.alt !== null) {
|
||||
options[i][0].alt = parsing.replaceMessageReferences(label.alt);
|
||||
}
|
||||
hasImages = true;
|
||||
}
|
||||
}
|
||||
if (hasImages || options.length < 2) {
|
||||
return; // Do nothing if too few items or at least one label is an image.
|
||||
}
|
||||
const strings = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
strings.push(options[i][0]);
|
||||
}
|
||||
const shortest = utilsString.shortestStringLength(strings);
|
||||
const prefixLength = utilsString.commonWordPrefix(strings, shortest);
|
||||
const suffixLength = utilsString.commonWordSuffix(strings, shortest);
|
||||
if (!prefixLength && !suffixLength) {
|
||||
return;
|
||||
}
|
||||
if (shortest <= prefixLength + suffixLength) {
|
||||
// One or more strings will entirely vanish if we proceed. Abort.
|
||||
return;
|
||||
}
|
||||
if (prefixLength) {
|
||||
this.prefixField = strings[0].substring(0, prefixLength - 1);
|
||||
}
|
||||
if (suffixLength) {
|
||||
this.suffixField = strings[0].substr(1 - suffixLength);
|
||||
}
|
||||
|
||||
this.menuGenerator_ =
|
||||
FieldDropdown.applyTrim_(options, prefixLength, suffixLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns True if the option list is generated by a function.
|
||||
* Otherwise false.
|
||||
@@ -433,18 +376,18 @@ export class FieldDropdown extends Field {
|
||||
* (human-readable text or image, language-neutral name).
|
||||
* @throws {TypeError} If generated options are incorrectly structured.
|
||||
*/
|
||||
getOptions(opt_useCache?: boolean): AnyDuringMigration[][] {
|
||||
if (this.isOptionListDynamic()) {
|
||||
if (!this.generatedOptions_ || !opt_useCache) {
|
||||
// AnyDuringMigration because: Property 'call' does not exist on type
|
||||
// 'any[][] | ((this: FieldDropdown) => any[][])'.
|
||||
this.generatedOptions_ =
|
||||
(this.menuGenerator_ as AnyDuringMigration).call(this);
|
||||
validateOptions(this.generatedOptions_);
|
||||
}
|
||||
return this.generatedOptions_;
|
||||
getOptions(opt_useCache?: boolean): MenuOption[] {
|
||||
if (!this.menuGenerator_) {
|
||||
// A subclass improperly skipped setup without defining the menu
|
||||
// generator.
|
||||
throw TypeError('A menu generator was never defined.');
|
||||
}
|
||||
return this.menuGenerator_ as string[][];
|
||||
if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_;
|
||||
if (opt_useCache && this.generatedOptions_) return this.generatedOptions_;
|
||||
|
||||
this.generatedOptions_ = this.menuGenerator_();
|
||||
validateOptions(this.generatedOptions_);
|
||||
return this.generatedOptions_;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,17 +396,11 @@ export class FieldDropdown extends Field {
|
||||
* @param opt_newValue The input value.
|
||||
* @returns A valid language-neutral option, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
let isValueValid = false;
|
||||
protected override doClassValidation_(opt_newValue?: MenuOption[1]): string
|
||||
|null {
|
||||
const options = this.getOptions(true);
|
||||
for (let i = 0, option; option = options[i]; i++) {
|
||||
// Options are tuples of human-readable text and language-neutral values.
|
||||
if (option[1] === opt_newValue) {
|
||||
isValueValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const isValueValid = options.some((option) => option[1] === opt_newValue);
|
||||
|
||||
if (!isValueValid) {
|
||||
if (this.sourceBlock_) {
|
||||
console.warn(
|
||||
@@ -482,7 +419,7 @@ export class FieldDropdown extends Field {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is one of the valid dropdown options.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: MenuOption[1]) {
|
||||
super.doValueUpdate_(newValue);
|
||||
const options = this.getOptions(true);
|
||||
for (let i = 0, option; option = options[i]; i++) {
|
||||
@@ -520,7 +457,7 @@ export class FieldDropdown extends Field {
|
||||
/** Draws the border with the correct width. */
|
||||
protected override render_() {
|
||||
// Hide both elements.
|
||||
this.textContent_.nodeValue = '';
|
||||
this.getTextContent().nodeValue = '';
|
||||
this.imageElement_!.style.display = 'none';
|
||||
|
||||
// Show correct element.
|
||||
@@ -540,17 +477,15 @@ export class FieldDropdown extends Field {
|
||||
* @param imageJson Selected option that must be an image.
|
||||
*/
|
||||
private renderSelectedImage_(imageJson: ImageProperties) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.imageElement_!.style.display = '';
|
||||
this.imageElement_!.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.imageElement_!.setAttribute(
|
||||
'height', imageJson.height as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.imageElement_!.setAttribute(
|
||||
'width', imageJson.width as AnyDuringMigration);
|
||||
this.imageElement_!.setAttribute('height', `${imageJson.height}`);
|
||||
this.imageElement_!.setAttribute('width', `${imageJson.width}`);
|
||||
|
||||
const imageHeight = Number(imageJson.height);
|
||||
const imageWidth = Number(imageJson.width);
|
||||
@@ -578,12 +513,12 @@ export class FieldDropdown extends Field {
|
||||
this.size_.height = height;
|
||||
|
||||
let arrowX = 0;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
if (block.RTL) {
|
||||
const imageX = xPadding + arrowWidth;
|
||||
this.imageElement_!.setAttribute('x', imageX.toString());
|
||||
} else {
|
||||
arrowX = imageWidth + arrowWidth;
|
||||
this.textElement_.setAttribute('text-anchor', 'end');
|
||||
this.getTextElement().setAttribute('text-anchor', 'end');
|
||||
this.imageElement_!.setAttribute('x', xPadding.toString());
|
||||
}
|
||||
this.imageElement_!.setAttribute(
|
||||
@@ -595,9 +530,10 @@ export class FieldDropdown extends Field {
|
||||
/** Renders the selected option, which must be text. */
|
||||
private renderSelectedText_() {
|
||||
// Retrieves the selected option to display through getText_.
|
||||
this.textContent_.nodeValue = this.getDisplayText_();
|
||||
this.textElement_.classList.add('blocklyDropdownText');
|
||||
this.textElement_.setAttribute('text-anchor', 'start');
|
||||
this.getTextContent().nodeValue = this.getDisplayText_();
|
||||
const textElement = this.getTextElement();
|
||||
dom.addClass(textElement, 'blocklyDropdownText');
|
||||
textElement.setAttribute('text-anchor', 'start');
|
||||
|
||||
// Height and width include the border rect.
|
||||
const hasBorder = !!this.borderRect_;
|
||||
@@ -605,7 +541,7 @@ export class FieldDropdown extends Field {
|
||||
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
||||
this.getConstants()!.FIELD_TEXT_HEIGHT);
|
||||
const textWidth = dom.getFastTextWidth(
|
||||
this.textElement_, this.getConstants()!.FIELD_TEXT_FONTSIZE,
|
||||
this.getTextElement(), this.getConstants()!.FIELD_TEXT_FONTSIZE,
|
||||
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
|
||||
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
|
||||
const xPadding =
|
||||
@@ -633,12 +569,16 @@ export class FieldDropdown extends Field {
|
||||
if (!this.svgArrow_) {
|
||||
return 0;
|
||||
}
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const hasBorder = !!this.borderRect_;
|
||||
const xPadding =
|
||||
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
|
||||
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
||||
const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
|
||||
const arrowX = this.sourceBlock_.RTL ? xPadding : x + textPadding;
|
||||
const arrowX = block.RTL ? xPadding : x + textPadding;
|
||||
this.svgArrow_.setAttribute(
|
||||
'transform', 'translate(' + arrowX + ',' + y + ')');
|
||||
return svgArrowSize + textPadding;
|
||||
@@ -681,30 +621,6 @@ export class FieldDropdown extends Field {
|
||||
// override the static fromJson method.
|
||||
return new this(options.options, undefined, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the calculated prefix and suffix lengths to trim all of the options in
|
||||
* the given array.
|
||||
*
|
||||
* @param options Array of option tuples:
|
||||
* (human-readable text or image, language-neutral name).
|
||||
* @param prefixLength The length of the common prefix.
|
||||
* @param suffixLength The length of the common suffix
|
||||
* @returns A new array with all of the option text trimmed.
|
||||
*/
|
||||
static applyTrim_(
|
||||
options: AnyDuringMigration[][], prefixLength: number,
|
||||
suffixLength: number): AnyDuringMigration[][] {
|
||||
const newOptions = [];
|
||||
// Remove the prefix and suffix from the options.
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let text = options[i][0];
|
||||
const value = options[i][1];
|
||||
text = text.substring(prefixLength, text.length - suffixLength);
|
||||
newOptions[i] = [text, value];
|
||||
}
|
||||
return newOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -724,6 +640,18 @@ export interface ImageProperties {
|
||||
*/
|
||||
export type MenuOption = [string | ImageProperties, string];
|
||||
|
||||
/**
|
||||
* A function that generates an array of menu options for FieldDropdown
|
||||
* or its descendants.
|
||||
*/
|
||||
export type MenuGeneratorFunction = (this: FieldDropdown) => MenuOption[];
|
||||
|
||||
/**
|
||||
* Either an array of menu options or a function that generates an array of
|
||||
* menu options for FieldDropdown or its descendants.
|
||||
*/
|
||||
export type MenuGenerator = MenuOption[]|MenuGeneratorFunction;
|
||||
|
||||
/**
|
||||
* fromJson config for the dropdown field.
|
||||
*/
|
||||
@@ -740,8 +668,81 @@ const IMAGE_Y_OFFSET = 5;
|
||||
/** The total vertical padding above and below an image. */
|
||||
const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2;
|
||||
|
||||
/** Android can't (in 2014) display "▾", so use "▼" instead. */
|
||||
FieldDropdown.ARROW_CHAR = userAgent.ANDROID ? '▼' : '▾';
|
||||
/**
|
||||
* NOTE: Because Sentinel is an empty class, proving a value is Sentinel does
|
||||
* not resolve in TS that it isn't a MenuGenerator.
|
||||
*/
|
||||
function isMenuGenerator(menuGenerator: MenuGenerator|
|
||||
Sentinel): menuGenerator is MenuGenerator {
|
||||
return menuGenerator !== Field.SKIP_SETUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factor out common words in statically defined options.
|
||||
* Create prefix and/or suffix labels.
|
||||
*/
|
||||
function trimOptions(options: MenuOption[]):
|
||||
{options: MenuOption[]; prefix?: string; suffix?: string;} {
|
||||
let hasImages = false;
|
||||
const trimmedOptions = options.map(([label, value]): MenuOption => {
|
||||
if (typeof label === 'string') {
|
||||
return [parsing.replaceMessageReferences(label), value];
|
||||
}
|
||||
|
||||
hasImages = true;
|
||||
// Copy the image properties so they're not influenced by the original.
|
||||
// NOTE: No need to deep copy since image properties are only 1 level deep.
|
||||
const imageLabel = label.alt !== null ?
|
||||
{...label, alt: parsing.replaceMessageReferences(label.alt)} :
|
||||
{...label};
|
||||
return [imageLabel, value];
|
||||
});
|
||||
|
||||
if (hasImages || options.length < 2) return {options: trimmedOptions};
|
||||
|
||||
const stringOptions = trimmedOptions as [string, string][];
|
||||
const stringLabels = stringOptions.map(([label]) => label);
|
||||
|
||||
const shortest = utilsString.shortestStringLength(stringLabels);
|
||||
const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest);
|
||||
const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest);
|
||||
|
||||
if ((!prefixLength && !suffixLength) ||
|
||||
(shortest <= prefixLength + suffixLength)) {
|
||||
// One or more strings will entirely vanish if we proceed. Abort.
|
||||
return {options: stringOptions};
|
||||
}
|
||||
|
||||
const prefix =
|
||||
prefixLength ? stringLabels[0].substring(0, prefixLength - 1) : undefined;
|
||||
const suffix =
|
||||
suffixLength ? stringLabels[0].substr(1 - suffixLength) : undefined;
|
||||
return {
|
||||
options: applyTrim(stringOptions, prefixLength, suffixLength),
|
||||
prefix,
|
||||
suffix,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the calculated prefix and suffix lengths to trim all of the options in
|
||||
* the given array.
|
||||
*
|
||||
* @param options Array of option tuples:
|
||||
* (human-readable text or image, language-neutral name).
|
||||
* @param prefixLength The length of the common prefix.
|
||||
* @param suffixLength The length of the common suffix
|
||||
* @returns A new array with all of the option text trimmed.
|
||||
*/
|
||||
function applyTrim(
|
||||
options: [string, string][], prefixLength: number,
|
||||
suffixLength: number): MenuOption[] {
|
||||
return options.map(
|
||||
([text, value]) =>
|
||||
[text.substring(prefixLength, text.length - suffixLength),
|
||||
value,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the data structure to be processed as an options list.
|
||||
@@ -749,7 +750,7 @@ FieldDropdown.ARROW_CHAR = userAgent.ANDROID ? '▼' : '▾';
|
||||
* @param options The proposed dropdown options.
|
||||
* @throws {TypeError} If proposed options are incorrectly structured.
|
||||
*/
|
||||
function validateOptions(options: AnyDuringMigration) {
|
||||
function validateOptions(options: MenuOption[]) {
|
||||
if (!Array.isArray(options)) {
|
||||
throw TypeError('FieldDropdown options must be an array.');
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.FieldLabel');
|
||||
|
||||
import * as dom from './utils/dom.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
@@ -75,7 +76,7 @@ export class FieldLabel extends Field {
|
||||
override initView() {
|
||||
this.createTextElement_();
|
||||
if (this.class_) {
|
||||
this.textElement_.classList.add(this.class_);
|
||||
dom.addClass(this.getTextElement(), this.class_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,10 +102,10 @@ export class FieldLabel extends Field {
|
||||
setClass(cssClass: string|null) {
|
||||
if (this.textElement_) {
|
||||
if (this.class_) {
|
||||
this.textElement_.classList.remove(this.class_);
|
||||
dom.removeClass(this.textElement_, this.class_);
|
||||
}
|
||||
if (cssClass) {
|
||||
this.textElement_.classList.add(cssClass);
|
||||
dom.addClass(this.textElement_, cssClass);
|
||||
}
|
||||
}
|
||||
this.class_ = cssClass;
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.FieldMultilineInput');
|
||||
|
||||
import * as Css from './css.js';
|
||||
import {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
@@ -163,6 +163,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
* @returns Currently displayed text.
|
||||
*/
|
||||
protected override getDisplayText_(): string {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
let textLines = this.getText();
|
||||
if (!textLines) {
|
||||
// Prevent the field from disappearing if empty.
|
||||
@@ -189,8 +193,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
textLines += '\n';
|
||||
}
|
||||
}
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// The SVG is LTR, force value to be RTL by adding an RLM.
|
||||
if (block.RTL) {
|
||||
// The SVG is LTR, force value to be RTL.
|
||||
textLines += '\u200F';
|
||||
}
|
||||
return textLines;
|
||||
@@ -212,6 +216,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
/** Updates the text of the textElement. */
|
||||
protected override render_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
// Remove all text group children.
|
||||
let currentChild;
|
||||
while (currentChild = this.textGroup_.firstChild) {
|
||||
@@ -239,16 +247,16 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
if (this.isBeingEdited_) {
|
||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||
if (this.isOverflowedY_) {
|
||||
htmlInput.classList.add('blocklyHtmlTextAreaInputOverflowedY');
|
||||
dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
|
||||
} else {
|
||||
htmlInput.classList.remove('blocklyHtmlTextAreaInputOverflowedY');
|
||||
dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSize_();
|
||||
|
||||
if (this.isBeingEdited_) {
|
||||
if (this.sourceBlock_.RTL) {
|
||||
if (block.RTL) {
|
||||
// in RTL, we need to let the browser reflow before resizing
|
||||
// in order to get the correct bounding box of the borderRect
|
||||
// avoiding issue #2777.
|
||||
@@ -258,10 +266,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
}
|
||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||
if (!this.isTextValid_) {
|
||||
htmlInput.classList.add('blocklyInvalidInput');
|
||||
dom.addClass(htmlInput, 'blocklyInvalidInput');
|
||||
aria.setState(htmlInput, aria.State.INVALID, true);
|
||||
} else {
|
||||
htmlInput.classList.remove('blocklyInvalidInput');
|
||||
dom.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||
aria.setState(htmlInput, aria.State.INVALID, false);
|
||||
}
|
||||
}
|
||||
@@ -270,11 +278,15 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
/** Updates the size of the field based on the text. */
|
||||
protected override updateSize_() {
|
||||
const nodes = this.textGroup_.childNodes;
|
||||
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE;
|
||||
const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT;
|
||||
const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY;
|
||||
let totalWidth = 0;
|
||||
let totalHeight = 0;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const tspan = nodes[i] as SVGTextElement;
|
||||
const textWidth = dom.getTextWidth(tspan);
|
||||
const textWidth =
|
||||
dom.getFastTextWidth(tspan, fontSize, fontWeight, fontFamily);
|
||||
if (textWidth > totalWidth) {
|
||||
totalWidth = textWidth;
|
||||
}
|
||||
@@ -290,9 +302,6 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
const actualEditorLines = this.value_.split('\n');
|
||||
const dummyTextElement = dom.createSvgElement(
|
||||
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
|
||||
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE;
|
||||
const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT;
|
||||
const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY;
|
||||
|
||||
for (let i = 0; i < actualEditorLines.length; i++) {
|
||||
if (actualEditorLines[i].length > this.maxDisplayLength) {
|
||||
|
||||
@@ -75,7 +75,7 @@ function fromJsonInternal(options: AnyDuringMigration): Field|null {
|
||||
' the file is not loaded, the field does not register itself (Issue' +
|
||||
' #1584), or the registration is not being reached.');
|
||||
return null;
|
||||
} else if (typeof (fieldObject as any)['fromJson'] !== 'function') {
|
||||
} else if (typeof (fieldObject as any).fromJson !== 'function') {
|
||||
throw new TypeError('returned Field was not a IRegistrableField');
|
||||
} else {
|
||||
return (fieldObject as unknown as IRegistrableField).fromJson(options);
|
||||
|
||||
@@ -18,9 +18,10 @@ import './events/events_block_change.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as dialog from './dialog.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import {FieldConfig, Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {Msg} from './msg.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
@@ -126,13 +127,17 @@ export class FieldTextInput extends Field {
|
||||
|
||||
/** @internal */
|
||||
override initView() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.getConstants()!.FULL_BLOCK_FIELDS) {
|
||||
// Step one: figure out if this is the only field on this block.
|
||||
// Rendering is quite different in that case.
|
||||
let nFields = 0;
|
||||
let nConnections = 0;
|
||||
// Count the number of fields, excluding text fields
|
||||
for (let i = 0, input; input = this.sourceBlock_.inputList[i]; i++) {
|
||||
for (let i = 0, input; input = block.inputList[i]; i++) {
|
||||
for (let j = 0; input.fieldRow[j]; j++) {
|
||||
nFields++;
|
||||
}
|
||||
@@ -143,7 +148,7 @@ export class FieldTextInput extends Field {
|
||||
// The special case is when this is the only non-label field on the block
|
||||
// and it has an output but no inputs.
|
||||
this.fullBlockClickTarget_ =
|
||||
nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections;
|
||||
nFields <= 1 && block.outputConnection && !nConnections;
|
||||
} else {
|
||||
this.fullBlockClickTarget_ = false;
|
||||
}
|
||||
@@ -216,15 +221,15 @@ export class FieldTextInput extends Field {
|
||||
* @internal
|
||||
*/
|
||||
override applyColour() {
|
||||
if (this.sourceBlock_ && this.getConstants()!.FULL_BLOCK_FIELDS) {
|
||||
if (this.borderRect_) {
|
||||
this.borderRect_.setAttribute(
|
||||
'stroke', (this.sourceBlock_ as BlockSvg).style.colourTertiary);
|
||||
} else {
|
||||
(this.sourceBlock_ as BlockSvg)
|
||||
.pathObject.svgPath.setAttribute(
|
||||
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
|
||||
}
|
||||
if (!this.sourceBlock_ || !this.getConstants()!.FULL_BLOCK_FIELDS) return;
|
||||
|
||||
const source = this.sourceBlock_ as BlockSvg;
|
||||
|
||||
if (this.borderRect_) {
|
||||
this.borderRect_.setAttribute('stroke', source.style.colourTertiary);
|
||||
} else {
|
||||
source.pathObject.svgPath.setAttribute(
|
||||
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +245,10 @@ export class FieldTextInput extends Field {
|
||||
this.resizeEditor_();
|
||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||
if (!this.isTextValid_) {
|
||||
htmlInput.classList.add('blocklyInvalidInput');
|
||||
dom.addClass(htmlInput, 'blocklyInvalidInput');
|
||||
aria.setState(htmlInput, aria.State.INVALID, true);
|
||||
} else {
|
||||
htmlInput.classList.remove('blocklyInvalidInput');
|
||||
dom.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||
aria.setState(htmlInput, aria.State.INVALID, false);
|
||||
}
|
||||
}
|
||||
@@ -306,7 +311,11 @@ export class FieldTextInput extends Field {
|
||||
* @param quietInput True if editor should be created without focus.
|
||||
*/
|
||||
private showInlineEditor_(quietInput: boolean) {
|
||||
WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_.bind(this));
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
WidgetDiv.show(this, block.RTL, this.widgetDispose_.bind(this));
|
||||
this.htmlInput_ = this.widgetCreate_() as HTMLInputElement;
|
||||
this.isBeingEdited_ = true;
|
||||
|
||||
@@ -324,10 +333,16 @@ export class FieldTextInput extends Field {
|
||||
* @returns The newly created text input editor.
|
||||
*/
|
||||
protected widgetCreate_(): HTMLElement {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
eventUtils.setGroup(true);
|
||||
const div = WidgetDiv.getDiv();
|
||||
|
||||
this.getClickTarget_().classList.add('editing');
|
||||
const clickTarget = this.getClickTarget_();
|
||||
if (!clickTarget) throw new Error('A click target has not been set.');
|
||||
dom.addClass(clickTarget, 'editing');
|
||||
|
||||
const htmlInput = (document.createElement('input'));
|
||||
htmlInput.className = 'blocklyHtmlInput';
|
||||
@@ -347,8 +362,8 @@ export class FieldTextInput extends Field {
|
||||
// Override border radius.
|
||||
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
|
||||
// Pull stroke colour from the existing shadow block
|
||||
const strokeColour = this.sourceBlock_.getParent() ?
|
||||
(this.sourceBlock_.getParent() as BlockSvg).style.colourTertiary :
|
||||
const strokeColour = block.getParent() ?
|
||||
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
|
||||
div!.style.borderRadius = borderRadius;
|
||||
@@ -396,7 +411,9 @@ export class FieldTextInput extends Field {
|
||||
style.boxShadow = '';
|
||||
this.htmlInput_ = null;
|
||||
|
||||
this.getClickTarget_().classList.remove('editing');
|
||||
const clickTarget = this.getClickTarget_();
|
||||
if (!clickTarget) throw new Error('A click target has not been set.');
|
||||
dom.removeClass(clickTarget, 'editing');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,6 +521,10 @@ export class FieldTextInput extends Field {
|
||||
|
||||
/** Resize the editor to fit the text. */
|
||||
protected resizeEditor_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const div = WidgetDiv.getDiv();
|
||||
const bBox = this.getScaledBBox();
|
||||
div!.style.width = bBox.right - bBox.left + 'px';
|
||||
@@ -511,7 +532,7 @@ export class FieldTextInput extends Field {
|
||||
|
||||
// In RTL mode block fields and LTR input fields the left edge moves,
|
||||
// whereas the right edge is fixed. Reposition the editor.
|
||||
const x = this.sourceBlock_.RTL ? bBox.right - div!.offsetWidth : bBox.left;
|
||||
const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left;
|
||||
const xy = new Coordinate(x, bBox.top);
|
||||
|
||||
div!.style.left = xy.x + 'px';
|
||||
|
||||
@@ -16,8 +16,8 @@ goog.declareModuleId('Blockly.FieldVariable');
|
||||
import './events/events_block_change.js';
|
||||
|
||||
import type {Block} from './block.js';
|
||||
import {Field, FieldConfig} from './field.js';
|
||||
import {FieldDropdown} from './field_dropdown.js';
|
||||
import {Field, FieldConfig, UnattachedFieldError} from './field.js';
|
||||
import {FieldDropdown, MenuGenerator, MenuOption} from './field_dropdown.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
import type {Menu} from './menu.js';
|
||||
@@ -37,8 +37,7 @@ import * as Xml from './xml.js';
|
||||
* @alias Blockly.FieldVariable
|
||||
*/
|
||||
export class FieldVariable extends FieldDropdown {
|
||||
protected override menuGenerator_: AnyDuringMigration[][]|
|
||||
((this: FieldDropdown) => AnyDuringMigration[][]);
|
||||
protected override menuGenerator_: MenuGenerator|undefined;
|
||||
defaultVariableName: string;
|
||||
|
||||
/** The type of the default variable for this field. */
|
||||
@@ -89,9 +88,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
* An array of options for a dropdown list,
|
||||
* or a function which generates these options.
|
||||
*/
|
||||
// AnyDuringMigration because: Type '(this: FieldVariable) => any[][]' is
|
||||
// not assignable to type 'any[][] | ((this: FieldDropdown) => any[][])'.
|
||||
this.menuGenerator_ = FieldVariable.dropdownCreate as AnyDuringMigration;
|
||||
this.menuGenerator_ = FieldVariable.dropdownCreate as MenuGenerator;
|
||||
|
||||
/**
|
||||
* The initial variable name passed to this field's constructor, or an
|
||||
@@ -135,20 +132,27 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @internal
|
||||
*/
|
||||
override initModel() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.variable_) {
|
||||
return; // Initialization already happened.
|
||||
}
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.sourceBlock_.workspace, null, this.defaultVariableName,
|
||||
this.defaultType_);
|
||||
block.workspace, null, this.defaultVariableName, this.defaultType_);
|
||||
// Don't call setValue because we don't want to cause a rerender.
|
||||
this.doValueUpdate_(variable.getId());
|
||||
}
|
||||
|
||||
override shouldAddBorderRect_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
return super.shouldAddBorderRect_() &&
|
||||
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
||||
this.sourceBlock_.type !== 'variables_get');
|
||||
block.type !== 'variables_get');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +162,10 @@ export class FieldVariable extends FieldDropdown {
|
||||
* field's state.
|
||||
*/
|
||||
override fromXml(fieldElement: Element) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const id = fieldElement.getAttribute('id');
|
||||
const variableName = fieldElement.textContent;
|
||||
// 'variabletype' should be lowercase, but until July 2019 it was sometimes
|
||||
@@ -168,8 +176,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
// AnyDuringMigration because: Argument of type 'string | null' is not
|
||||
// assignable to parameter of type 'string | undefined'.
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.sourceBlock_.workspace, id, variableName as AnyDuringMigration,
|
||||
variableType);
|
||||
block.workspace, id, variableName as AnyDuringMigration, variableType);
|
||||
|
||||
// This should never happen :)
|
||||
if (variableType !== null && variableType !== variable.type) {
|
||||
@@ -233,12 +240,16 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @internal
|
||||
*/
|
||||
override loadState(state: AnyDuringMigration) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.loadLegacyState(FieldVariable, state)) {
|
||||
return;
|
||||
}
|
||||
// This is necessary so that blocks in the flyout can have custom var names.
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.sourceBlock_.workspace, state['id'] || null, state['name'],
|
||||
block.workspace, state['id'] || null, state['name'],
|
||||
state['type'] || '');
|
||||
this.setValue(variable.getId());
|
||||
}
|
||||
@@ -315,8 +326,12 @@ export class FieldVariable extends FieldDropdown {
|
||||
if (opt_newValue === null) {
|
||||
return null;
|
||||
}
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const newId = opt_newValue as string;
|
||||
const variable = Variables.getVariable(this.sourceBlock_.workspace, newId);
|
||||
const variable = Variables.getVariable(block.workspace, newId);
|
||||
if (!variable) {
|
||||
console.warn(
|
||||
'Variable id doesn\'t point to a real variable! ' +
|
||||
@@ -342,8 +357,11 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @param newId The value to be saved.
|
||||
*/
|
||||
protected override doValueUpdate_(newId: AnyDuringMigration) {
|
||||
this.variable_ =
|
||||
Variables.getVariable(this.sourceBlock_.workspace, newId as string);
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.variable_ = Variables.getVariable(block.workspace, newId as string);
|
||||
super.doValueUpdate_(newId);
|
||||
}
|
||||
|
||||
@@ -377,7 +395,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
let variableTypes = this.variableTypes;
|
||||
if (variableTypes === null) {
|
||||
// If variableTypes is null, return all variable types.
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.disposed) {
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
|
||||
return this.sourceBlock_.workspace.getVariableTypes();
|
||||
}
|
||||
}
|
||||
@@ -456,7 +474,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
protected override onItemSelected_(menu: Menu, menuItem: MenuItem) {
|
||||
const id = menuItem.getValue();
|
||||
// Handle special cases.
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.disposed) {
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
|
||||
if (id === internalConstants.RENAME_VARIABLE_ID) {
|
||||
// Rename variable.
|
||||
Variables.renameVariable(
|
||||
@@ -507,15 +525,15 @@ export class FieldVariable extends FieldDropdown {
|
||||
*
|
||||
* @returns Array of variable names/id tuples.
|
||||
*/
|
||||
static dropdownCreate(this: FieldVariable): AnyDuringMigration[][] {
|
||||
static dropdownCreate(this: FieldVariable): MenuOption[] {
|
||||
if (!this.variable_) {
|
||||
throw Error(
|
||||
'Tried to call dropdownCreate on a variable field with no' +
|
||||
' variable selected.');
|
||||
}
|
||||
const name = this.getText();
|
||||
let variableModelList: AnyDuringMigration[] = [];
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.disposed) {
|
||||
let variableModelList: VariableModel[] = [];
|
||||
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
|
||||
const variableTypes = this.getVariableTypes_();
|
||||
// Get a copy of the list, so that adding rename and new variable options
|
||||
// doesn't modify the workspace's list.
|
||||
@@ -528,7 +546,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
}
|
||||
variableModelList.sort(VariableModel.compareByName);
|
||||
|
||||
const options = [];
|
||||
const options: [string, string][] = [];
|
||||
for (let i = 0; i < variableModelList.length; i++) {
|
||||
// Set the UUID as the internal representation of the variable.
|
||||
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Generator');
|
||||
goog.declareModuleId('Blockly.CodeGenerator');
|
||||
|
||||
import type {Block} from './block.js';
|
||||
import * as common from './common.js';
|
||||
@@ -24,14 +24,14 @@ import type {Workspace} from './workspace.js';
|
||||
* Class for a code generator that translates the blocks into a language.
|
||||
*
|
||||
* @unrestricted
|
||||
* @alias Blockly.Generator
|
||||
* @alias Blockly.CodeGenerator
|
||||
*/
|
||||
export class Generator {
|
||||
export class CodeGenerator {
|
||||
name_: string;
|
||||
|
||||
/**
|
||||
* This is used as a placeholder in functions defined using
|
||||
* Generator.provideFunction_. It must not be legal code that could
|
||||
* CodeGenerator.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.
|
||||
*/
|
||||
@@ -205,7 +205,7 @@ export class Generator {
|
||||
|[string, number] {
|
||||
if (this.isInitialized === false) {
|
||||
console.warn(
|
||||
'Generator init was not called before blockToCode was called.');
|
||||
'CodeGenerator init was not called before blockToCode was called.');
|
||||
}
|
||||
if (!block) {
|
||||
return '';
|
||||
@@ -414,7 +414,7 @@ export class Generator {
|
||||
* "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.
|
||||
* The code gets output when CodeGenerator.finish() is called.
|
||||
*
|
||||
* @param desiredName The desired name of the function (e.g. mathIsPrime).
|
||||
* @param code A list of statements or one multi-line code string. Use ' '
|
||||
@@ -514,25 +514,24 @@ export class Generator {
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(Generator.prototype, {
|
||||
Object.defineProperties(CodeGenerator.prototype, {
|
||||
/**
|
||||
* A database of variable names.
|
||||
*
|
||||
* @name Blockly.Generator.prototype.variableDB_
|
||||
* @name Blockly.CodeGenerator.prototype.variableDB_
|
||||
* @deprecated 'variableDB_' was renamed to 'nameDB_' (May 2021).
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
variableDB_: ({
|
||||
/** @returns Name database. */
|
||||
get(this: Generator): Names |
|
||||
get(this: CodeGenerator): Names |
|
||||
undefined {
|
||||
deprecation.warn(
|
||||
'variableDB_', 'May 2021', 'September 2022', 'nameDB_');
|
||||
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
|
||||
return this.nameDB_;
|
||||
},
|
||||
/** @param nameDb New name database. */
|
||||
set(this: Generator, nameDb: Names|undefined) {
|
||||
deprecation.warn('variableDB_', 'May 2021', 'September2022', 'nameDB_');
|
||||
set(this: CodeGenerator, nameDb: Names|undefined) {
|
||||
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
|
||||
this.nameDB_ = nameDb;
|
||||
},
|
||||
}),
|
||||
|
||||
41
core/icon.ts
41
core/icon.ts
@@ -20,6 +20,7 @@ import * as dom from './utils/dom.js';
|
||||
import {Size} from './utils/size.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,7 @@ import * as svgMath from './utils/svg_math.js';
|
||||
* @alias Blockly.Icon
|
||||
*/
|
||||
export abstract class Icon {
|
||||
protected block_: BlockSvg;
|
||||
protected block_: BlockSvg|null;
|
||||
/** The icon SVG group. */
|
||||
iconGroup_: SVGGElement|null = null;
|
||||
|
||||
@@ -45,7 +46,12 @@ export abstract class Icon {
|
||||
protected iconXY_: Coordinate|null = null;
|
||||
|
||||
/** @param block The block associated with this icon. */
|
||||
constructor(block: BlockSvg) {
|
||||
constructor(block: BlockSvg|null) {
|
||||
if (!block) {
|
||||
deprecation.warn(
|
||||
'Calling the Icon constructor with a null block', 'version 9',
|
||||
'version 10', 'a non-null block');
|
||||
}
|
||||
this.block_ = block;
|
||||
}
|
||||
|
||||
@@ -62,12 +68,12 @@ export abstract class Icon {
|
||||
*/
|
||||
this.iconGroup_ =
|
||||
dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'});
|
||||
if (this.block_.isInFlyout) {
|
||||
this.iconGroup_.classList.add('blocklyIconGroupReadonly');
|
||||
if (this.getBlock().isInFlyout) {
|
||||
dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly');
|
||||
}
|
||||
this.drawIcon_(this.iconGroup_);
|
||||
|
||||
this.block_.getSvgRoot().appendChild(this.iconGroup_);
|
||||
this.getBlock().getSvgRoot().appendChild(this.iconGroup_);
|
||||
browserEvents.conditionalBind(
|
||||
this.iconGroup_, 'mouseup', this, this.iconClick_);
|
||||
this.updateEditable();
|
||||
@@ -99,19 +105,19 @@ export abstract class Icon {
|
||||
* @param e Mouse click event.
|
||||
*/
|
||||
protected iconClick_(e: MouseEvent) {
|
||||
if (this.block_.workspace.isDragging()) {
|
||||
if (this.getBlock().workspace.isDragging()) {
|
||||
// Drag operation is concluding. Don't open the editor.
|
||||
return;
|
||||
}
|
||||
if (!this.block_.isInFlyout && !browserEvents.isRightButton(e)) {
|
||||
if (!this.getBlock().isInFlyout && !browserEvents.isRightButton(e)) {
|
||||
this.setVisible(!this.isVisible());
|
||||
}
|
||||
}
|
||||
|
||||
/** Change the colour of the associated bubble to match its block. */
|
||||
applyColour() {
|
||||
if (this.isVisible()) {
|
||||
this.bubble_!.setColour(this.block_.style.colourPrimary);
|
||||
if (this.bubble_ && this.isVisible()) {
|
||||
this.bubble_.setColour(this.getBlock().style.colourPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +128,8 @@ export abstract class Icon {
|
||||
*/
|
||||
setIconLocation(xy: Coordinate) {
|
||||
this.iconXY_ = xy;
|
||||
if (this.isVisible()) {
|
||||
this.bubble_!.setAnchorLocation(xy);
|
||||
if (this.bubble_ && this.isVisible()) {
|
||||
this.bubble_.setAnchorLocation(xy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +139,7 @@ export abstract class Icon {
|
||||
*/
|
||||
computeIconLocation() {
|
||||
// Find coordinates for the centre of the icon and update the arrow.
|
||||
const blockXY = this.block_.getRelativeToSurfaceXY();
|
||||
const blockXY = this.getBlock().getRelativeToSurfaceXY();
|
||||
const iconXY = svgMath.getRelativeXY(this.iconGroup_ as SVGElement);
|
||||
const newXY = new Coordinate(
|
||||
blockXY.x + iconXY.x + this.SIZE / 2,
|
||||
@@ -178,5 +184,16 @@ export abstract class Icon {
|
||||
* @param _visible True if the icon should be visible.
|
||||
*/
|
||||
setVisible(_visible: boolean) {}
|
||||
|
||||
/**
|
||||
* Returns the block this icon is attached to.
|
||||
*/
|
||||
protected getBlock(): BlockSvg {
|
||||
if (!this.block_) {
|
||||
throw new Error('Block is not set for this icon.');
|
||||
}
|
||||
|
||||
return this.block_;
|
||||
}
|
||||
}
|
||||
// No-op on base class
|
||||
|
||||
@@ -172,11 +172,11 @@ function createMainWorkspace(
|
||||
const injectionDiv = mainWorkspace.getInjectionDiv();
|
||||
const rendererClassName = mainWorkspace.getRenderer().getClassName();
|
||||
if (rendererClassName) {
|
||||
injectionDiv.classList.add(rendererClassName);
|
||||
dom.addClass(injectionDiv, rendererClassName);
|
||||
}
|
||||
const themeClassName = mainWorkspace.getTheme().getClassName();
|
||||
if (themeClassName) {
|
||||
injectionDiv.classList.add(themeClassName);
|
||||
dom.addClass(injectionDiv, themeClassName);
|
||||
}
|
||||
|
||||
if (!wsOptions.hasCategories && wsOptions.languageTree) {
|
||||
|
||||
@@ -28,5 +28,5 @@ export interface IASTNodeLocationWithBlock extends IASTNodeLocation {
|
||||
*
|
||||
* @returns The source block.
|
||||
*/
|
||||
getSourceBlock(): Block;
|
||||
getSourceBlock(): Block|null;
|
||||
}
|
||||
|
||||
45
core/interfaces/i_parameter_model.ts
Normal file
45
core/interfaces/i_parameter_model.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The interface for the data model of a procedure parameter.
|
||||
*
|
||||
* @namespace Blockly.IParameterModel
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A data model for a procedure.
|
||||
*/
|
||||
export interface IParameterModel {
|
||||
/**
|
||||
* Sets the name of this parameter to the given name.
|
||||
*/
|
||||
setName(name: string): this;
|
||||
|
||||
/**
|
||||
* Sets the types of this parameter to the given type.
|
||||
*/
|
||||
setTypes(types: string[]): this;
|
||||
|
||||
/**
|
||||
* Returns the name of this parameter.
|
||||
*/
|
||||
getName(): string;
|
||||
|
||||
/**
|
||||
* Return the types of this parameter.
|
||||
*/
|
||||
getTypes(): string[];
|
||||
|
||||
/**
|
||||
* Returns the unique language-neutral ID for the parameter.
|
||||
*
|
||||
* This represents the identify of the variable model which does not change
|
||||
* over time.
|
||||
*/
|
||||
getId(): string;
|
||||
}
|
||||
19
core/interfaces/i_procedure_block.ts
Normal file
19
core/interfaces/i_procedure_block.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
|
||||
|
||||
/** The interface for a block which models a procedure. */
|
||||
export interface IProcedureBlock {
|
||||
doProcedureUpdate(): void;
|
||||
}
|
||||
|
||||
/** A type guard which checks if the given block is a procedure block. */
|
||||
export function isProcedureBlock(block: Block|
|
||||
IProcedureBlock): block is IProcedureBlock {
|
||||
return (block as IProcedureBlock).doProcedureUpdate !== undefined;
|
||||
}
|
||||
20
core/interfaces/i_procedure_map.ts
Normal file
20
core/interfaces/i_procedure_map.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {IProcedureModel} from './i_procedure_model.js';
|
||||
|
||||
|
||||
export interface IProcedureMap extends Map<string, IProcedureModel> {
|
||||
/**
|
||||
* Adds the given ProcedureModel to the map of procedure models, so that
|
||||
* blocks can find it.
|
||||
*/
|
||||
add(proc: IProcedureModel): this;
|
||||
|
||||
|
||||
/** Returns all of the procedures stored in this map. */
|
||||
getProcedures(): IProcedureModel[];
|
||||
}
|
||||
70
core/interfaces/i_procedure_model.ts
Normal file
70
core/interfaces/i_procedure_model.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The interface for the data model of a procedure.
|
||||
*
|
||||
* @namespace Blockly.IProcedureModel
|
||||
*/
|
||||
|
||||
import {IParameterModel} from './i_parameter_model.js';
|
||||
|
||||
|
||||
/**
|
||||
* A data model for a procedure.
|
||||
*/
|
||||
export interface IProcedureModel {
|
||||
/** Sets the human-readable name of the procedure. */
|
||||
setName(name: string): this;
|
||||
|
||||
/**
|
||||
* Inserts a parameter into the list of parameters.
|
||||
*
|
||||
* To move a parameter, first delete it, and then re-insert.
|
||||
*/
|
||||
insertParameter(parameterModel: IParameterModel, index: number): this;
|
||||
|
||||
/** Removes the parameter at the given index from the parameter list. */
|
||||
deleteParameter(index: number): this;
|
||||
|
||||
/**
|
||||
* Sets the return type(s) of the procedure.
|
||||
*
|
||||
* Pass null to represent a procedure that does not return.
|
||||
*/
|
||||
setReturnTypes(types: string[]|null): this;
|
||||
|
||||
/**
|
||||
* Sets whether this procedure is enabled/disabled. If a procedure is disabled
|
||||
* all procedure caller blocks should be disabled as well.
|
||||
*/
|
||||
setEnabled(enabled: boolean): this;
|
||||
|
||||
/** Returns the unique language-neutral ID for the procedure. */
|
||||
getId(): string;
|
||||
|
||||
/** Returns the human-readable name of the procedure. */
|
||||
getName(): string;
|
||||
|
||||
/** Returns the parameter at the given index in the parameter list. */
|
||||
getParameter(index: number): IParameterModel;
|
||||
|
||||
/** Returns an array of all of the parameters in the parameter list. */
|
||||
getParameters(): IParameterModel[];
|
||||
|
||||
/**
|
||||
* Returns the return type(s) of the procedure.
|
||||
*
|
||||
* Null represents a procedure that does not return a value.
|
||||
*/
|
||||
getReturnTypes(): string[]|null;
|
||||
|
||||
/**
|
||||
* Returns whether the procedure is enabled/disabled. If a procedure is
|
||||
* disabled, all procedure caller blocks should be disabled as well.
|
||||
*/
|
||||
getEnabled(): boolean;
|
||||
}
|
||||
@@ -176,6 +176,10 @@ export class ASTNode {
|
||||
const location = this.location_ as Field;
|
||||
const input = location.getParentInput();
|
||||
const block = location.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
const curIdx = block.inputList.indexOf((input));
|
||||
let fieldIdx = input.fieldRow.indexOf(location) + 1;
|
||||
for (let i = curIdx; i < block.inputList.length; i++) {
|
||||
@@ -235,6 +239,10 @@ export class ASTNode {
|
||||
const location = this.location_ as Field;
|
||||
const parentInput = location.getParentInput();
|
||||
const block = location.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
const curIdx = block.inputList.indexOf((parentInput));
|
||||
let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
|
||||
for (let i = curIdx; i >= 0; i--) {
|
||||
@@ -270,12 +278,15 @@ export class ASTNode {
|
||||
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||
// curLocation that don't make sense.
|
||||
if ((curLocation as IASTNodeLocationWithBlock).getSourceBlock) {
|
||||
curLocation = (curLocation as IASTNodeLocationWithBlock).getSourceBlock();
|
||||
const block = (curLocation as IASTNodeLocationWithBlock).getSourceBlock();
|
||||
if (block) {
|
||||
curLocation = block;
|
||||
}
|
||||
}
|
||||
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||
// curLocation that don't make sense.
|
||||
const curLocationAsBlock = curLocation as Block;
|
||||
if (!curLocationAsBlock || curLocationAsBlock.disposed) {
|
||||
if (!curLocationAsBlock || curLocationAsBlock.isDeadOrDying()) {
|
||||
return null;
|
||||
}
|
||||
const curRoot = curLocationAsBlock.getRootBlock();
|
||||
@@ -531,7 +542,12 @@ export class ASTNode {
|
||||
}
|
||||
case ASTNode.types.FIELD: {
|
||||
const field = this.location_ as Field;
|
||||
return ASTNode.createBlockNode(field.getSourceBlock());
|
||||
const block = field.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
return ASTNode.createBlockNode(block);
|
||||
}
|
||||
case ASTNode.types.INPUT: {
|
||||
const connection = this.location_ as Connection;
|
||||
|
||||
10
core/main.js
10
core/main.js
@@ -86,13 +86,13 @@ Object.defineProperties(Blockly, {
|
||||
mainWorkspace: {
|
||||
set: function(x) {
|
||||
deprecation.warn(
|
||||
'Blockly.mainWorkspace', 'August 2022', 'September 2022',
|
||||
'Blockly.mainWorkspace', 'version 9', 'version 10',
|
||||
'Blockly.getMainWorkspace');
|
||||
common.setMainWorkspace(x);
|
||||
},
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.mainWorkspace', 'August 2022', 'September 2022',
|
||||
'Blockly.mainWorkspace', 'version 9', 'version 10',
|
||||
'Blockly.getMainWorkspace');
|
||||
return common.getMainWorkspace();
|
||||
},
|
||||
@@ -130,14 +130,12 @@ Object.defineProperties(Blockly, {
|
||||
selected: {
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.selected', 'August 2022', 'September 2022',
|
||||
'Blockly.getSelected');
|
||||
'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected');
|
||||
return common.getSelected();
|
||||
},
|
||||
set: function(newSelection) {
|
||||
deprecation.warn(
|
||||
'Blockly.selected', 'August 2022', 'September 2022',
|
||||
'Blockly.getSelected');
|
||||
'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected');
|
||||
common.setSelected(newSelection);
|
||||
},
|
||||
},
|
||||
|
||||
174
core/menu.ts
174
core/menu.ts
@@ -16,6 +16,7 @@ import * as browserEvents from './browser_events.js';
|
||||
import type {MenuItem} from './menuitem.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import type {Size} from './utils/size.js';
|
||||
import * as style from './utils/style.js';
|
||||
@@ -32,7 +33,7 @@ export class Menu {
|
||||
* (Nulls are never in the array, but typing the array as nullable prevents
|
||||
* the compiler from objecting to .indexOf(null))
|
||||
*/
|
||||
private readonly menuItems_: MenuItem[] = [];
|
||||
private readonly menuItems: MenuItem[] = [];
|
||||
|
||||
/**
|
||||
* Coordinates of the mousedown event that caused this menu to open. Used to
|
||||
@@ -45,28 +46,28 @@ export class Menu {
|
||||
* This is the element that we will listen to the real focus events on.
|
||||
* A value of null means no menu item is highlighted.
|
||||
*/
|
||||
private highlightedItem_: MenuItem|null = null;
|
||||
private highlightedItem: MenuItem|null = null;
|
||||
|
||||
/** Mouse over event data. */
|
||||
private mouseOverHandler_: browserEvents.Data|null = null;
|
||||
private mouseOverHandler: browserEvents.Data|null = null;
|
||||
|
||||
/** Click event data. */
|
||||
private clickHandler_: browserEvents.Data|null = null;
|
||||
private clickHandler: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse enter event data. */
|
||||
private mouseEnterHandler_: browserEvents.Data|null = null;
|
||||
private mouseEnterHandler: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse leave event data. */
|
||||
private mouseLeaveHandler_: browserEvents.Data|null = null;
|
||||
private mouseLeaveHandler: browserEvents.Data|null = null;
|
||||
|
||||
/** Key down event data. */
|
||||
private onKeyDownHandler_: browserEvents.Data|null = null;
|
||||
private onKeyDownHandler: browserEvents.Data|null = null;
|
||||
|
||||
/** The menu's root DOM element. */
|
||||
private element_: HTMLDivElement|null = null;
|
||||
private element: HTMLDivElement|null = null;
|
||||
|
||||
/** ARIA name for this menu. */
|
||||
private roleName_: aria.Role|null = null;
|
||||
private roleName: aria.Role|null = null;
|
||||
|
||||
/** Constructs a new Menu instance. */
|
||||
constructor() {}
|
||||
@@ -78,7 +79,7 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
addChild(menuItem: MenuItem) {
|
||||
this.menuItems_.push(menuItem);
|
||||
this.menuItems.push(menuItem);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,27 +93,27 @@ export class Menu {
|
||||
// goog-menu is deprecated, use blocklyMenu. May 2020.
|
||||
element.className = 'blocklyMenu goog-menu blocklyNonSelectable';
|
||||
element.tabIndex = 0;
|
||||
if (this.roleName_) {
|
||||
aria.setRole(element, this.roleName_);
|
||||
if (this.roleName) {
|
||||
aria.setRole(element, this.roleName);
|
||||
}
|
||||
this.element_ = element;
|
||||
this.element = element;
|
||||
|
||||
// Add menu items.
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems_[i]; i++) {
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) {
|
||||
element.appendChild(menuItem.createDom());
|
||||
}
|
||||
|
||||
// Add event handlers.
|
||||
this.mouseOverHandler_ = browserEvents.conditionalBind(
|
||||
element, 'mouseover', this, this.handleMouseOver_, true);
|
||||
this.clickHandler_ = browserEvents.conditionalBind(
|
||||
element, 'click', this, this.handleClick_, true);
|
||||
this.mouseEnterHandler_ = browserEvents.conditionalBind(
|
||||
element, 'mouseenter', this, this.handleMouseEnter_, true);
|
||||
this.mouseLeaveHandler_ = browserEvents.conditionalBind(
|
||||
element, 'mouseleave', this, this.handleMouseLeave_, true);
|
||||
this.onKeyDownHandler_ = browserEvents.conditionalBind(
|
||||
element, 'keydown', this, this.handleKeyEvent_);
|
||||
this.mouseOverHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseover', this, this.handleMouseOver, true);
|
||||
this.clickHandler = browserEvents.conditionalBind(
|
||||
element, 'click', this, this.handleClick, true);
|
||||
this.mouseEnterHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseenter', this, this.handleMouseEnter, true);
|
||||
this.mouseLeaveHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseleave', this, this.handleMouseLeave, true);
|
||||
this.onKeyDownHandler = browserEvents.conditionalBind(
|
||||
element, 'keydown', this, this.handleKeyEvent);
|
||||
|
||||
container.appendChild(element);
|
||||
return element;
|
||||
@@ -125,7 +126,7 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
getElement(): HTMLDivElement|null {
|
||||
return this.element_;
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,16 +138,16 @@ export class Menu {
|
||||
const el = this.getElement();
|
||||
if (el) {
|
||||
el.focus({preventScroll: true});
|
||||
el.classList.add('blocklyFocused');
|
||||
dom.addClass(el, 'blocklyFocused');
|
||||
}
|
||||
}
|
||||
|
||||
/** Blur the menu element. */
|
||||
private blur_() {
|
||||
private blur() {
|
||||
const el = this.getElement();
|
||||
if (el) {
|
||||
el.blur();
|
||||
el.classList.remove('blocklyFocused');
|
||||
dom.removeClass(el, 'blocklyFocused');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,38 +158,38 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
setRole(roleName: aria.Role) {
|
||||
this.roleName_ = roleName;
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
/** Dispose of this menu. */
|
||||
dispose() {
|
||||
// Remove event handlers.
|
||||
if (this.mouseOverHandler_) {
|
||||
browserEvents.unbind(this.mouseOverHandler_);
|
||||
this.mouseOverHandler_ = null;
|
||||
if (this.mouseOverHandler) {
|
||||
browserEvents.unbind(this.mouseOverHandler);
|
||||
this.mouseOverHandler = null;
|
||||
}
|
||||
if (this.clickHandler_) {
|
||||
browserEvents.unbind(this.clickHandler_);
|
||||
this.clickHandler_ = null;
|
||||
if (this.clickHandler) {
|
||||
browserEvents.unbind(this.clickHandler);
|
||||
this.clickHandler = null;
|
||||
}
|
||||
if (this.mouseEnterHandler_) {
|
||||
browserEvents.unbind(this.mouseEnterHandler_);
|
||||
this.mouseEnterHandler_ = null;
|
||||
if (this.mouseEnterHandler) {
|
||||
browserEvents.unbind(this.mouseEnterHandler);
|
||||
this.mouseEnterHandler = null;
|
||||
}
|
||||
if (this.mouseLeaveHandler_) {
|
||||
browserEvents.unbind(this.mouseLeaveHandler_);
|
||||
this.mouseLeaveHandler_ = null;
|
||||
if (this.mouseLeaveHandler) {
|
||||
browserEvents.unbind(this.mouseLeaveHandler);
|
||||
this.mouseLeaveHandler = null;
|
||||
}
|
||||
if (this.onKeyDownHandler_) {
|
||||
browserEvents.unbind(this.onKeyDownHandler_);
|
||||
this.onKeyDownHandler_ = null;
|
||||
if (this.onKeyDownHandler) {
|
||||
browserEvents.unbind(this.onKeyDownHandler);
|
||||
this.onKeyDownHandler = null;
|
||||
}
|
||||
|
||||
// Remove menu items.
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems_[i]; i++) {
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) {
|
||||
menuItem.dispose();
|
||||
}
|
||||
this.element_ = null;
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
// Child component management.
|
||||
@@ -200,7 +201,7 @@ export class Menu {
|
||||
* @param elem DOM element whose owner is to be returned.
|
||||
* @returns Menu item for which the DOM element belongs to.
|
||||
*/
|
||||
private getMenuItem_(elem: Element): MenuItem|null {
|
||||
private getMenuItem(elem: Element): MenuItem|null {
|
||||
const menuElem = this.getElement();
|
||||
// Node might be the menu border (resulting in no associated menu item), or
|
||||
// a menu item's div, or some element within the menu item.
|
||||
@@ -210,7 +211,7 @@ export class Menu {
|
||||
while (currentElement && currentElement !== menuElem) {
|
||||
if (currentElement.classList.contains('blocklyMenuItem')) {
|
||||
// Having found a menu item's div, locate that menu item in this menu.
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems_[i]; i++) {
|
||||
for (let i = 0, menuItem; menuItem = this.menuItems[i]; i++) {
|
||||
if (menuItem.getElement() === currentElement) {
|
||||
return menuItem;
|
||||
}
|
||||
@@ -230,14 +231,14 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
setHighlighted(item: MenuItem|null) {
|
||||
const currentHighlighted = this.highlightedItem_;
|
||||
const currentHighlighted = this.highlightedItem;
|
||||
if (currentHighlighted) {
|
||||
currentHighlighted.setHighlighted(false);
|
||||
this.highlightedItem_ = null;
|
||||
this.highlightedItem = null;
|
||||
}
|
||||
if (item) {
|
||||
item.setHighlighted(true);
|
||||
this.highlightedItem_ = item;
|
||||
this.highlightedItem = item;
|
||||
// Bring the highlighted item into view. This has no effect if the menu is
|
||||
// not scrollable.
|
||||
const el = this.getElement() as Element;
|
||||
@@ -254,10 +255,10 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
highlightNext() {
|
||||
const index = this.highlightedItem_ ?
|
||||
this.menuItems_.indexOf(this.highlightedItem_) :
|
||||
const index = this.highlightedItem ?
|
||||
this.menuItems.indexOf(this.highlightedItem) :
|
||||
-1;
|
||||
this.highlightHelper_(index, 1);
|
||||
this.highlightHelper(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,20 +268,20 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
highlightPrevious() {
|
||||
const index = this.highlightedItem_ ?
|
||||
this.menuItems_.indexOf(this.highlightedItem_) :
|
||||
const index = this.highlightedItem ?
|
||||
this.menuItems.indexOf(this.highlightedItem) :
|
||||
-1;
|
||||
this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1);
|
||||
this.highlightHelper(index < 0 ? this.menuItems.length : index, -1);
|
||||
}
|
||||
|
||||
/** Highlights the first highlightable item. */
|
||||
private highlightFirst_() {
|
||||
this.highlightHelper_(-1, 1);
|
||||
private highlightFirst() {
|
||||
this.highlightHelper(-1, 1);
|
||||
}
|
||||
|
||||
/** Highlights the last highlightable item. */
|
||||
private highlightLast_() {
|
||||
this.highlightHelper_(this.menuItems_.length, -1);
|
||||
private highlightLast() {
|
||||
this.highlightHelper(this.menuItems.length, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,10 +291,10 @@ export class Menu {
|
||||
* @param startIndex Start index.
|
||||
* @param delta Step direction: 1 to go down, -1 to go up.
|
||||
*/
|
||||
private highlightHelper_(startIndex: number, delta: number) {
|
||||
private highlightHelper(startIndex: number, delta: number) {
|
||||
let index = startIndex + delta;
|
||||
let menuItem;
|
||||
while (menuItem = this.menuItems_[index]) {
|
||||
while (menuItem = this.menuItems[index]) {
|
||||
if (menuItem.isEnabled()) {
|
||||
this.setHighlighted(menuItem);
|
||||
break;
|
||||
@@ -309,12 +310,12 @@ export class Menu {
|
||||
*
|
||||
* @param e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseOver_(e: Event) {
|
||||
const menuItem = this.getMenuItem_(e.target as Element);
|
||||
private handleMouseOver(e: Event) {
|
||||
const menuItem = this.getMenuItem(e.target as Element);
|
||||
|
||||
if (menuItem) {
|
||||
if (menuItem.isEnabled()) {
|
||||
if (this.highlightedItem_ !== menuItem) {
|
||||
if (this.highlightedItem !== menuItem) {
|
||||
this.setHighlighted(menuItem);
|
||||
}
|
||||
} else {
|
||||
@@ -328,7 +329,7 @@ export class Menu {
|
||||
*
|
||||
* @param e Click event to handle.
|
||||
*/
|
||||
private handleClick_(e: Event) {
|
||||
private handleClick(e: Event) {
|
||||
const oldCoords = this.openingCoords;
|
||||
// Clear out the saved opening coords immediately so they're not used twice.
|
||||
this.openingCoords = null;
|
||||
@@ -350,7 +351,7 @@ export class Menu {
|
||||
}
|
||||
}
|
||||
|
||||
const menuItem = this.getMenuItem_(e.target as Element);
|
||||
const menuItem = this.getMenuItem(e.target as Element);
|
||||
if (menuItem) {
|
||||
menuItem.performAction();
|
||||
}
|
||||
@@ -361,7 +362,7 @@ export class Menu {
|
||||
*
|
||||
* @param _e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseEnter_(_e: Event) {
|
||||
private handleMouseEnter(_e: Event) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
@@ -370,9 +371,9 @@ export class Menu {
|
||||
*
|
||||
* @param _e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseLeave_(_e: Event) {
|
||||
private handleMouseLeave(_e: Event) {
|
||||
if (this.getElement()) {
|
||||
this.blur_();
|
||||
this.blur();
|
||||
this.setHighlighted(null);
|
||||
}
|
||||
}
|
||||
@@ -386,27 +387,20 @@ export class Menu {
|
||||
*
|
||||
* @param e Key event to handle.
|
||||
*/
|
||||
private handleKeyEvent_(e: Event) {
|
||||
if (!this.menuItems_.length) {
|
||||
private handleKeyEvent(e: Event) {
|
||||
if (!this.menuItems.length) {
|
||||
// Empty menu.
|
||||
return;
|
||||
}
|
||||
// AnyDuringMigration because: Property 'altKey' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'metaKey' does not exist
|
||||
// on type 'Event'. AnyDuringMigration because: Property 'ctrlKey' does not
|
||||
// exist on type 'Event'. AnyDuringMigration because: Property 'shiftKey'
|
||||
// does not exist on type 'Event'.
|
||||
if ((e as AnyDuringMigration).shiftKey ||
|
||||
(e as AnyDuringMigration).ctrlKey ||
|
||||
(e as AnyDuringMigration).metaKey || (e as AnyDuringMigration).altKey) {
|
||||
const keyboardEvent = e as KeyboardEvent;
|
||||
if (keyboardEvent.shiftKey || keyboardEvent.ctrlKey ||
|
||||
keyboardEvent.metaKey || keyboardEvent.altKey) {
|
||||
// Do not handle the key event if any modifier key is pressed.
|
||||
return;
|
||||
}
|
||||
|
||||
const highlighted = this.highlightedItem_;
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
switch ((e as AnyDuringMigration).keyCode) {
|
||||
const highlighted = this.highlightedItem;
|
||||
switch (keyboardEvent.keyCode) {
|
||||
case KeyCodes.ENTER:
|
||||
case KeyCodes.SPACE:
|
||||
if (highlighted) {
|
||||
@@ -424,12 +418,12 @@ export class Menu {
|
||||
|
||||
case KeyCodes.PAGE_UP:
|
||||
case KeyCodes.HOME:
|
||||
this.highlightFirst_();
|
||||
this.highlightFirst();
|
||||
break;
|
||||
|
||||
case KeyCodes.PAGE_DOWN:
|
||||
case KeyCodes.END:
|
||||
this.highlightLast_();
|
||||
this.highlightLast();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -448,10 +442,10 @@ export class Menu {
|
||||
* @internal
|
||||
*/
|
||||
getSize(): Size {
|
||||
const menuDom = this.getElement();
|
||||
const menuSize = style.getSize(menuDom as Element);
|
||||
const menuDom = this.getElement() as HTMLDivElement;
|
||||
const menuSize = style.getSize(menuDom);
|
||||
// Recalculate height for the total content, not only box height.
|
||||
menuSize.height = menuDom!.scrollHeight;
|
||||
menuSize.height = menuDom.scrollHeight;
|
||||
return menuSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.MenuItem');
|
||||
|
||||
import * as aria from './utils/aria.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
|
||||
|
||||
@@ -23,28 +24,28 @@ import * as idGenerator from './utils/idgenerator.js';
|
||||
*/
|
||||
export class MenuItem {
|
||||
/** Is the menu item clickable, as opposed to greyed-out. */
|
||||
private enabled_ = true;
|
||||
private enabled = true;
|
||||
|
||||
/** The DOM element for the menu item. */
|
||||
private element_: HTMLDivElement|null = null;
|
||||
private element: HTMLDivElement|null = null;
|
||||
|
||||
/** Whether the menu item is rendered right-to-left. */
|
||||
private rightToLeft_ = false;
|
||||
private rightToLeft = false;
|
||||
|
||||
/** ARIA name for this menu. */
|
||||
private roleName_: aria.Role|null = null;
|
||||
private roleName: aria.Role|null = null;
|
||||
|
||||
/** Is this menu item checkable. */
|
||||
private checkable_ = false;
|
||||
private checkable = false;
|
||||
|
||||
/** Is this menu item currently checked. */
|
||||
private checked_ = false;
|
||||
private checked = false;
|
||||
|
||||
/** Is this menu item currently highlighted. */
|
||||
private highlight_ = false;
|
||||
private highlight = false;
|
||||
|
||||
/** Bound function to call when this menu item is clicked. */
|
||||
private actionHandler_: Function|null = null;
|
||||
private actionHandler: Function|null = null;
|
||||
|
||||
/**
|
||||
* @param content Text caption to display as the content of the item, or a
|
||||
@@ -63,22 +64,22 @@ export class MenuItem {
|
||||
createDom(): Element {
|
||||
const element = (document.createElement('div'));
|
||||
element.id = idGenerator.getNextUniqueId();
|
||||
this.element_ = element;
|
||||
this.element = element;
|
||||
|
||||
// Set class and style
|
||||
// goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020.
|
||||
element.className = 'blocklyMenuItem goog-menuitem ' +
|
||||
(this.enabled_ ? '' :
|
||||
'blocklyMenuItemDisabled goog-menuitem-disabled ') +
|
||||
(this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') +
|
||||
(this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' :
|
||||
'') +
|
||||
(this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : '');
|
||||
(this.enabled ? '' :
|
||||
'blocklyMenuItemDisabled goog-menuitem-disabled ') +
|
||||
(this.checked ? 'blocklyMenuItemSelected goog-option-selected ' : '') +
|
||||
(this.highlight ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' :
|
||||
'') +
|
||||
(this.rightToLeft ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : '');
|
||||
|
||||
const content = (document.createElement('div'));
|
||||
content.className = 'blocklyMenuItemContent goog-menuitem-content';
|
||||
// Add a checkbox for checkable menu items.
|
||||
if (this.checkable_) {
|
||||
if (this.checkable) {
|
||||
const checkbox = (document.createElement('div'));
|
||||
checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox';
|
||||
content.appendChild(checkbox);
|
||||
@@ -92,20 +93,19 @@ export class MenuItem {
|
||||
element.appendChild(content);
|
||||
|
||||
// Initialize ARIA role and state.
|
||||
if (this.roleName_) {
|
||||
aria.setRole(element, this.roleName_);
|
||||
if (this.roleName) {
|
||||
aria.setRole(element, this.roleName);
|
||||
}
|
||||
aria.setState(
|
||||
element, aria.State.SELECTED,
|
||||
this.checkable_ && this.checked_ || false);
|
||||
aria.setState(element, aria.State.DISABLED, !this.enabled_);
|
||||
element, aria.State.SELECTED, this.checkable && this.checked || false);
|
||||
aria.setState(element, aria.State.DISABLED, !this.enabled);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/** Dispose of this menu item. */
|
||||
dispose() {
|
||||
this.element_ = null;
|
||||
this.element = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +115,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
getElement(): Element|null {
|
||||
return this.element_;
|
||||
return this.element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +125,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
getId(): string {
|
||||
return this.element_!.id;
|
||||
return this.element!.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,7 +145,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setRightToLeft(rtl: boolean) {
|
||||
this.rightToLeft_ = rtl;
|
||||
this.rightToLeft = rtl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +155,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setRole(roleName: aria.Role) {
|
||||
this.roleName_ = roleName;
|
||||
this.roleName = roleName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +166,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setCheckable(checkable: boolean) {
|
||||
this.checkable_ = checkable;
|
||||
this.checkable = checkable;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +176,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setChecked(checked: boolean) {
|
||||
this.checked_ = checked;
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +186,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setHighlighted(highlight: boolean) {
|
||||
this.highlight_ = highlight;
|
||||
this.highlight = highlight;
|
||||
|
||||
const el = this.getElement();
|
||||
if (el && this.isEnabled()) {
|
||||
@@ -195,11 +195,11 @@ export class MenuItem {
|
||||
const name = 'blocklyMenuItemHighlight';
|
||||
const nameDep = 'goog-menuitem-highlight';
|
||||
if (highlight) {
|
||||
el.classList.add(name);
|
||||
el.classList.add(nameDep);
|
||||
dom.addClass(el, name);
|
||||
dom.addClass(el, nameDep);
|
||||
} else {
|
||||
el.classList.remove(name);
|
||||
el.classList.remove(nameDep);
|
||||
dom.removeClass(el, name);
|
||||
dom.removeClass(el, nameDep);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
isEnabled(): boolean {
|
||||
return this.enabled_;
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +221,7 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
setEnabled(enabled: boolean) {
|
||||
this.enabled_ = enabled;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,8 +231,8 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
performAction() {
|
||||
if (this.isEnabled() && this.actionHandler_) {
|
||||
this.actionHandler_(this);
|
||||
if (this.isEnabled() && this.actionHandler) {
|
||||
this.actionHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +245,6 @@ export class MenuItem {
|
||||
* @internal
|
||||
*/
|
||||
onAction(fn: (p1: MenuItem) => void, obj: object) {
|
||||
this.actionHandler_ = fn.bind(obj);
|
||||
this.actionHandler = fn.bind(obj);
|
||||
}
|
||||
}
|
||||
|
||||
17
core/msg.ts
17
core/msg.ts
@@ -15,3 +15,20 @@ goog.declareModuleId('Blockly.Msg');
|
||||
|
||||
/** A dictionary of localised messages. */
|
||||
export const Msg: {[key: string]: string} = Object.create(null);
|
||||
|
||||
/**
|
||||
* Sets the locale (i.e. the localized messages/block-text/etc) to the given
|
||||
* locale.
|
||||
*
|
||||
* This is not useful/necessary when loading from a script tag, because the
|
||||
* messages are automatically cluged into the Blockly.Msg object. But we provide
|
||||
* it in both the script-tag and non-script-tag contexts so that the tscompiler
|
||||
* can properly create our type definition files.
|
||||
*
|
||||
* @param locale An object defining the messages for a given language.
|
||||
*/
|
||||
export const setLocale = function(locale: {[key: string]: string}) {
|
||||
Object.keys(locale).forEach(function(k) {
|
||||
Msg[k] = locale[k];
|
||||
});
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as toolbox from './utils/toolbox.js';
|
||||
import * as xml from './utils/xml.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
@@ -77,8 +78,14 @@ export class Mutator extends Icon {
|
||||
private updateWorkspacePid_: ReturnType<typeof setTimeout>|null = null;
|
||||
|
||||
/** @param quarkNames List of names of sub-blocks for flyout. */
|
||||
constructor(block: BlockSvg, quarkNames: string[]) {
|
||||
super(block);
|
||||
constructor(quarkNames: string[], block?: BlockSvg) {
|
||||
if (!block) {
|
||||
deprecation.warn(
|
||||
'Calling the Mutator constructor without passing the block it is attached to',
|
||||
'version 9', 'version 10',
|
||||
'the constructor by passing the list of subblocks and the block instance to attach the mutator to');
|
||||
}
|
||||
super(block ?? null);
|
||||
this.quarkNames_ = quarkNames;
|
||||
}
|
||||
|
||||
@@ -145,7 +152,7 @@ export class Mutator extends Icon {
|
||||
* @param e Mouse click event.
|
||||
*/
|
||||
protected override iconClick_(e: MouseEvent) {
|
||||
if (this.block_.isEditable()) {
|
||||
if (this.getBlock().isEditable()) {
|
||||
super.iconClick_(e);
|
||||
}
|
||||
}
|
||||
@@ -175,19 +182,20 @@ export class Mutator extends Icon {
|
||||
} else {
|
||||
quarkXml = null;
|
||||
}
|
||||
const block = this.getBlock();
|
||||
const workspaceOptions = new Options(({
|
||||
// If you want to enable disabling, also remove the
|
||||
// event filter from workspaceChanged_ .
|
||||
'disable': false,
|
||||
'parentWorkspace': this.block_.workspace,
|
||||
'media': this.block_.workspace.options.pathToMedia,
|
||||
'rtl': this.block_.RTL,
|
||||
'parentWorkspace': block.workspace,
|
||||
'media': block.workspace.options.pathToMedia,
|
||||
'rtl': block.RTL,
|
||||
'horizontalLayout': false,
|
||||
'renderer': this.block_.workspace.options.renderer,
|
||||
'rendererOverrides': this.block_.workspace.options.rendererOverrides,
|
||||
'renderer': block.workspace.options.renderer,
|
||||
'rendererOverrides': block.workspace.options.rendererOverrides,
|
||||
} as BlocklyOptions));
|
||||
workspaceOptions.toolboxPosition =
|
||||
this.block_.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT;
|
||||
block.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT;
|
||||
const hasFlyout = !!quarkXml;
|
||||
if (hasFlyout) {
|
||||
workspaceOptions.languageTree = toolbox.convertToolboxDefToJson(quarkXml);
|
||||
@@ -227,16 +235,16 @@ export class Mutator extends Icon {
|
||||
/** Add or remove the UI indicating if this icon may be clicked or not. */
|
||||
override updateEditable() {
|
||||
super.updateEditable();
|
||||
if (!this.block_.isInFlyout) {
|
||||
if (this.block_.isEditable()) {
|
||||
if (!this.getBlock().isInFlyout) {
|
||||
if (this.getBlock().isEditable()) {
|
||||
if (this.iconGroup_) {
|
||||
this.iconGroup_.classList.remove('blocklyIconGroupReadonly');
|
||||
dom.removeClass(this.iconGroup_, 'blocklyIconGroupReadonly');
|
||||
}
|
||||
} else {
|
||||
// Close any mutator bubble. Icon is not clickable.
|
||||
this.setVisible(false);
|
||||
if (this.iconGroup_) {
|
||||
this.iconGroup_.classList.add('blocklyIconGroupReadonly');
|
||||
dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -255,7 +263,7 @@ export class Mutator extends Icon {
|
||||
height = Math.max(height, flyoutScrollMetrics.height + 20);
|
||||
width += flyout.getWidth();
|
||||
}
|
||||
if (this.block_.RTL) {
|
||||
if (this.getBlock().RTL) {
|
||||
width = -workspaceSize.x;
|
||||
}
|
||||
width += doubleBorderWidth * 3;
|
||||
@@ -275,7 +283,7 @@ export class Mutator extends Icon {
|
||||
this.workspaceWidth_, this.workspaceHeight_);
|
||||
}
|
||||
|
||||
if (this.block_.RTL) {
|
||||
if (this.getBlock().RTL) {
|
||||
// Scroll the workspace to always left-align.
|
||||
const translation = 'translate(' + this.workspaceWidth_ + ',0)';
|
||||
this.workspace_!.getCanvas().setAttribute('transform', translation);
|
||||
@@ -300,16 +308,16 @@ export class Mutator extends Icon {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
const block = this.getBlock();
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
||||
this.block_, visible, 'mutator'));
|
||||
block, visible, 'mutator'));
|
||||
if (visible) {
|
||||
// Create the bubble.
|
||||
this.bubble_ = new Bubble(
|
||||
(this.block_.workspace as WorkspaceSvg), this.createEditor_(),
|
||||
this.block_.pathObject.svgPath, (this.iconXY_ as Coordinate), null,
|
||||
null);
|
||||
block.workspace, this.createEditor_(), block.pathObject.svgPath,
|
||||
(this.iconXY_ as Coordinate), null, null);
|
||||
// Expose this mutator's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.setSvgId(block.id);
|
||||
this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this));
|
||||
const tree = this.workspace_!.options.languageTree;
|
||||
const flyout = this.workspace_!.getFlyout();
|
||||
@@ -318,7 +326,7 @@ export class Mutator extends Icon {
|
||||
flyout!.show(tree);
|
||||
}
|
||||
|
||||
this.rootBlock_ = this.block_!.decompose!(this.workspace_!)!;
|
||||
this.rootBlock_ = block.decompose!(this.workspace_!)!;
|
||||
const blocks = this.rootBlock_!.getDescendants(false);
|
||||
for (let i = 0, child; child = blocks[i]; i++) {
|
||||
child.render();
|
||||
@@ -335,20 +343,21 @@ export class Mutator extends Icon {
|
||||
margin = 16;
|
||||
x = margin;
|
||||
}
|
||||
if (this.block_.RTL) {
|
||||
if (block.RTL) {
|
||||
x = -x;
|
||||
}
|
||||
this.rootBlock_!.moveBy(x, margin);
|
||||
// Save the initial connections, then listen for further changes.
|
||||
if (this.block_.saveConnections) {
|
||||
if (block.saveConnections) {
|
||||
const thisRootBlock = this.rootBlock_;
|
||||
this.block_.saveConnections(thisRootBlock);
|
||||
block.saveConnections(thisRootBlock);
|
||||
this.sourceListener_ = () => {
|
||||
if (this.block_ && this.block_.saveConnections) {
|
||||
this.block_.saveConnections(thisRootBlock);
|
||||
const currentBlock = this.getBlock();
|
||||
if (currentBlock.saveConnections) {
|
||||
currentBlock.saveConnections(thisRootBlock);
|
||||
}
|
||||
};
|
||||
this.block_.workspace.addChangeListener(this.sourceListener_);
|
||||
block.workspace.addChangeListener(this.sourceListener_);
|
||||
}
|
||||
this.resizeBubble_();
|
||||
// When the mutator's workspace changes, update the source block.
|
||||
@@ -367,7 +376,7 @@ export class Mutator extends Icon {
|
||||
this.workspaceWidth_ = 0;
|
||||
this.workspaceHeight_ = 0;
|
||||
if (this.sourceListener_) {
|
||||
this.block_.workspace.removeChangeListener(this.sourceListener_);
|
||||
block.workspace.removeChangeListener(this.sourceListener_);
|
||||
this.sourceListener_ = null;
|
||||
}
|
||||
}
|
||||
@@ -438,7 +447,7 @@ export class Mutator extends Icon {
|
||||
if (!existingGroup) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
const block = this.block_ as BlockSvg;
|
||||
const block = this.getBlock();
|
||||
const oldExtraState = BlockChange.getExtraBlockState_(block);
|
||||
|
||||
// Switch off rendering while the source block is rebuilt.
|
||||
@@ -482,7 +491,7 @@ export class Mutator extends Icon {
|
||||
|
||||
/** Dispose of this mutator. */
|
||||
override dispose() {
|
||||
this.block_.mutator = null;
|
||||
this.getBlock().mutator = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,12 @@ import * as common from './common.js';
|
||||
import type {Abstract} from './events/events_abstract.js';
|
||||
import type {BubbleOpen} from './events/events_bubble_open.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import {Msg} from './msg.js';
|
||||
import {Names} from './names.js';
|
||||
import {ObservableProcedureMap} from './procedures/observable_procedure_map.js';
|
||||
import {ObservableProcedureModel} from './procedures/observable_procedure_model.js';
|
||||
import {ObservableParameterModel} from './procedures/observable_parameter_model.js';
|
||||
import * as utilsXml from './utils/xml.js';
|
||||
import * as Variables from './variables.js';
|
||||
import type {Workspace} from './workspace.js';
|
||||
@@ -180,14 +183,19 @@ export function isNameUsed(
|
||||
* @alias Blockly.Procedures.rename
|
||||
*/
|
||||
export function rename(this: Field, name: string): string {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
// Strip leading and trailing whitespace. Beyond this, all names are legal.
|
||||
name = name.trim();
|
||||
|
||||
const legalName = findLegalName(name, (this.getSourceBlock()));
|
||||
const legalName = findLegalName(name, block);
|
||||
const oldName = this.getValue();
|
||||
if (oldName !== name && oldName !== legalName) {
|
||||
// Rename any callers.
|
||||
const blocks = this.getSourceBlock().workspace.getAllBlocks(false);
|
||||
const blocks = block.workspace.getAllBlocks(false);
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
// Assume it is a procedure so we can check.
|
||||
const procedureBlock = blocks[i] as unknown as ProcedureBlock;
|
||||
@@ -445,3 +453,9 @@ export function getDefinition(name: string, workspace: Workspace): Block|null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export {
|
||||
ObservableProcedureMap,
|
||||
ObservableProcedureModel,
|
||||
ObservableParameterModel,
|
||||
};
|
||||
|
||||
79
core/procedures/observable_parameter_model.ts
Normal file
79
core/procedures/observable_parameter_model.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {genUid} from '../utils/idgenerator.js';
|
||||
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import {triggerProceduresUpdate} from './update_procedures.js';
|
||||
import type {VariableModel} from '../variable_model.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
export class ObservableParameterModel implements IParameterModel {
|
||||
private id: string;
|
||||
private variable: VariableModel;
|
||||
|
||||
constructor(
|
||||
private readonly workspace: Workspace, name: string, id?: string) {
|
||||
this.id = id ?? genUid();
|
||||
this.variable =
|
||||
this.workspace.getVariable(name) ?? workspace.createVariable(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of this parameter to the given name.
|
||||
*/
|
||||
setName(name: string): this {
|
||||
// TODO(#6516): Fire events.
|
||||
if (name == this.variable.name) return this;
|
||||
this.variable =
|
||||
this.workspace.getVariable(name) ?? this.workspace.createVariable(name);
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unimplemented. The built-in ParameterModel does not support typing.
|
||||
* If you want your procedure blocks to have typed parameters, you need to
|
||||
* implement your own ParameterModel.
|
||||
*
|
||||
* @throws Throws for the ObservableParameterModel specifically because this
|
||||
* method is unimplemented.
|
||||
*/
|
||||
setTypes(_types: string[]): this {
|
||||
throw new Error(
|
||||
'The built-in ParameterModel does not support typing. You need to ' +
|
||||
'implement your own custom ParameterModel.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this parameter.
|
||||
*/
|
||||
getName(): string {
|
||||
return this.variable.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the types of this parameter.
|
||||
*/
|
||||
getTypes(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique language-neutral ID for the parameter.
|
||||
*
|
||||
* This represents the identify of the variable model which does not change
|
||||
* over time.
|
||||
*/
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/** Returns the variable model associated with the parameter model. */
|
||||
getVariableModel(): VariableModel {
|
||||
return this.variable;
|
||||
}
|
||||
}
|
||||
64
core/procedures/observable_procedure_map.ts
Normal file
64
core/procedures/observable_procedure_map.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {triggerProceduresUpdate} from './update_procedures.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
import {IProcedureMap} from '../interfaces/i_procedure_map.js';
|
||||
|
||||
|
||||
export class ObservableProcedureMap extends
|
||||
Map<string, IProcedureModel> implements IProcedureMap {
|
||||
constructor(private readonly workspace: Workspace) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given procedure model to the procedure map.
|
||||
*/
|
||||
override set(id: string, proc: IProcedureModel): this {
|
||||
// TODO(#6516): Fire events.
|
||||
super.set(id, proc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the ProcedureModel with the given ID from the procedure map (if it
|
||||
* exists).
|
||||
*/
|
||||
override delete(id: string): boolean {
|
||||
// TODO(#6516): Fire events.
|
||||
const existed = super.delete(id);
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return existed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all ProcedureModels from the procedure map.
|
||||
*/
|
||||
override clear() {
|
||||
// TODO(#6516): Fire events.
|
||||
super.clear();
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ProcedureModel to the map of procedure models, so that
|
||||
* blocks can find it.
|
||||
*/
|
||||
add(proc: IProcedureModel): this {
|
||||
// TODO(#6516): Fire events.
|
||||
// TODO(#6526): See if this method is actually useful.
|
||||
return this.set(proc.getId(), proc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all of the procedures stored in this map.
|
||||
*/
|
||||
getProcedures(): IProcedureModel[] {
|
||||
return [...this.values()];
|
||||
}
|
||||
}
|
||||
123
core/procedures/observable_procedure_model.ts
Normal file
123
core/procedures/observable_procedure_model.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {genUid} from '../utils/idgenerator.js';
|
||||
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {triggerProceduresUpdate} from './update_procedures.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
export class ObservableProcedureModel implements IProcedureModel {
|
||||
private id: string;
|
||||
private name: string;
|
||||
private parameters: IParameterModel[] = [];
|
||||
private returnTypes: string[]|null = null;
|
||||
private enabled = true;
|
||||
|
||||
constructor(
|
||||
private readonly workspace: Workspace, name: string, id?: string) {
|
||||
this.id = id ?? genUid();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/** Sets the human-readable name of the procedure. */
|
||||
setName(name: string): this {
|
||||
// TODO(#6516): Fire events.
|
||||
this.name = name;
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a parameter into the list of parameters.
|
||||
*
|
||||
* To move a parameter, first delete it, and then re-insert.
|
||||
*/
|
||||
insertParameter(parameterModel: IParameterModel, index: number): this {
|
||||
// TODO(#6516): Fire events.
|
||||
this.parameters.splice(index, 0, parameterModel);
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes the parameter at the given index from the parameter list. */
|
||||
deleteParameter(index: number): this {
|
||||
// TODO(#6516): Fire events.
|
||||
this.parameters.splice(index, 1);
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the procedure has a return value (empty array) or no return
|
||||
* value (null).
|
||||
*
|
||||
* The built-in procedure model does not support procedures that have actual
|
||||
* return types (i.e. non-empty arrays, e.g. ['number']). If you want your
|
||||
* procedure block to have return types, you need to implement your own
|
||||
* procedure model.
|
||||
*/
|
||||
setReturnTypes(types: string[]|null): this {
|
||||
if (types && types.length) {
|
||||
throw new Error(
|
||||
'The built-in ProcedureModel does not support typing. You need to ' +
|
||||
'implement your own custom ProcedureModel.');
|
||||
}
|
||||
this.returnTypes = types;
|
||||
// TODO(#6516): Fire events.
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this procedure is enabled/disabled. If a procedure is disabled
|
||||
* all procedure caller blocks should be disabled as well.
|
||||
*/
|
||||
setEnabled(enabled: boolean): this {
|
||||
// TODO(#6516): Fire events.
|
||||
this.enabled = enabled;
|
||||
triggerProceduresUpdate(this.workspace);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the unique language-neutral ID for the procedure. */
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/** Returns the human-readable name of the procedure. */
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/** Returns the parameter at the given index in the parameter list. */
|
||||
getParameter(index: number): IParameterModel {
|
||||
return this.parameters[index];
|
||||
}
|
||||
|
||||
/** Returns an array of all of the parameters in the parameter list. */
|
||||
getParameters(): IParameterModel[] {
|
||||
return [...this.parameters];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the return type of the procedure.
|
||||
*
|
||||
* Null represents a procedure that does not return a value.
|
||||
*/
|
||||
getReturnTypes(): string[]|null {
|
||||
return this.returnTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the procedure is enabled/disabled. If a procedure is
|
||||
* disabled, all procedure caller blocks should be disabled as well.
|
||||
*/
|
||||
getEnabled(): boolean {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
22
core/procedures/update_procedures.ts
Normal file
22
core/procedures/update_procedures.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {isProcedureBlock} from '../interfaces/i_procedure_block.js';
|
||||
import {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Calls the `doProcedureUpdate` method on all blocks which implement it.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function triggerProceduresUpdate(workspace: Workspace) {
|
||||
for (const block of workspace.getAllBlocks(false)) {
|
||||
if (isProcedureBlock(block)) {
|
||||
block.doProcedureUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,6 +280,10 @@ export class RenderedConnection extends Connection {
|
||||
|
||||
/** Add highlighting around this connection. */
|
||||
highlight() {
|
||||
if (this.highlightPath) {
|
||||
// This connection is already highlighted
|
||||
return;
|
||||
}
|
||||
let steps;
|
||||
const sourceBlockSvg = (this.sourceBlock_);
|
||||
const renderConstants =
|
||||
@@ -495,7 +499,7 @@ export class RenderedConnection extends Connection {
|
||||
* @returns List of connections.
|
||||
* @internal
|
||||
*/
|
||||
override neighbours(maxLimit: number): Connection[] {
|
||||
override neighbours(maxLimit: number): RenderedConnection[] {
|
||||
return this.dbOpposite_.getNeighbours(this, maxLimit);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,23 +48,6 @@ import {MarkerSvg} from './marker_svg.js';
|
||||
import {PathObject} from './path_object.js';
|
||||
import {Renderer} from './renderer.js';
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the debugger is turned on.
|
||||
*
|
||||
* @returns Whether the debugger is turned on.
|
||||
* @alias Blockly.blockRendering.isDebuggerEnabled
|
||||
* @deprecated
|
||||
* @internal
|
||||
*/
|
||||
export function isDebuggerEnabled(): boolean {
|
||||
deprecation.warn(
|
||||
'Blockly.blockRendering.isDebuggerEnabled()', 'September 2021',
|
||||
'September 2022',
|
||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||
return debug.isDebuggerEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new renderer.
|
||||
*
|
||||
@@ -86,26 +69,12 @@ export function unregister(name: string) {
|
||||
registry.unregister(registry.Type.RENDERER, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on the blocks debugger.
|
||||
*
|
||||
* @alias Blockly.blockRendering.startDebugger
|
||||
* @deprecated
|
||||
* @internal
|
||||
*/
|
||||
export function startDebugger() {
|
||||
deprecation.warn(
|
||||
'Blockly.blockRendering.startDebugger()', 'September 2021',
|
||||
'September 2022',
|
||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||
debug.startDebugger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off the blocks debugger.
|
||||
*
|
||||
* @alias Blockly.blockRendering.stopDebugger
|
||||
* @deprecated
|
||||
* @deprecated Use the debug renderer in **\@blockly/dev-tools** (See {@link
|
||||
* https://www.npmjs.com/package/@blockly/dev-tools}.)
|
||||
* @internal
|
||||
*/
|
||||
export function stopDebugger() {
|
||||
|
||||
@@ -563,7 +563,7 @@ export class ConstantProvider {
|
||||
this.setComponentConstants_(theme);
|
||||
|
||||
this.ADD_START_HATS =
|
||||
theme.startHats !== null ? theme.startHats : this.ADD_START_HATS;
|
||||
theme.startHats !== undefined ? theme.startHats : this.ADD_START_HATS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -658,12 +658,7 @@ export class ConstantProvider {
|
||||
* @param blockStyle A full or partial block style object.
|
||||
* @returns A full block style object, with all required properties populated.
|
||||
*/
|
||||
protected validatedBlockStyle_(blockStyle: {
|
||||
colourPrimary: string,
|
||||
colourSecondary?: string,
|
||||
colourTertiary?: string,
|
||||
hat?: string
|
||||
}): BlockStyle {
|
||||
protected validatedBlockStyle_(blockStyle: Partial<BlockStyle>): BlockStyle {
|
||||
// Make a new object with all of the same properties.
|
||||
const valid = {} as BlockStyle;
|
||||
if (blockStyle) {
|
||||
|
||||
@@ -38,8 +38,7 @@ export function isDebuggerEnabled(): boolean {
|
||||
*/
|
||||
export function startDebugger() {
|
||||
deprecation.warn(
|
||||
'Blockly.blockRendering.debug.startDebugger()', 'February 2022',
|
||||
'September 2022',
|
||||
'Blockly.blockRendering.debug.startDebugger()', 'version 8', 'version 10',
|
||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||
useDebugger = true;
|
||||
}
|
||||
@@ -54,8 +53,7 @@ export function startDebugger() {
|
||||
*/
|
||||
export function stopDebugger() {
|
||||
deprecation.warn(
|
||||
'Blockly.blockRendering.debug.stopDebugger()', 'February 2022',
|
||||
'September 2022',
|
||||
'Blockly.blockRendering.debug.stopDebugger()', 'version 8', 'version 10',
|
||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||
useDebugger = false;
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export class Drawer {
|
||||
*/
|
||||
protected layoutField_(fieldInfo: Icon|Field) {
|
||||
const svgGroup = Types.isField(fieldInfo) ?
|
||||
(fieldInfo as Field).field.getSvgRoot() :
|
||||
(fieldInfo as Field).field.getSvgRoot()! :
|
||||
(fieldInfo as Icon).icon.iconGroup_!; // Never null in rendered case.
|
||||
|
||||
const yPos = fieldInfo.centerline - fieldInfo.height / 2;
|
||||
|
||||
@@ -181,7 +181,8 @@ export class MarkerSvg {
|
||||
// Ensures the marker will be visible immediately after the move.
|
||||
const animate = this.currentMarkerSvg!.childNodes[0];
|
||||
if (animate !== undefined) {
|
||||
animate instanceof SVGAnimationElement && animate.beginElement();
|
||||
(animate as SVGAnimationElement).beginElement &&
|
||||
(animate as SVGAnimationElement).beginElement();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -166,9 +166,9 @@ export class PathObject implements IPathObject {
|
||||
return;
|
||||
}
|
||||
if (add) {
|
||||
this.svgRoot.classList.add(className);
|
||||
dom.addClass(this.svgRoot, className);
|
||||
} else {
|
||||
this.svgRoot.classList.remove(className);
|
||||
dom.removeClass(this.svgRoot, className);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,11 @@ export class PathObject extends BasePathObject {
|
||||
override applyColour(block: BlockSvg) {
|
||||
this.svgPathLight.style.display = '';
|
||||
this.svgPathDark.style.display = '';
|
||||
if (!this.style.colourTertiary) {
|
||||
throw new Error(
|
||||
'The renderer did not properly initialize the tertiary colour of ' +
|
||||
'the block style');
|
||||
}
|
||||
this.svgPathLight.setAttribute('stroke', this.style.colourTertiary);
|
||||
this.svgPathDark.setAttribute('fill', this.colourDark);
|
||||
|
||||
@@ -118,6 +123,11 @@ export class PathObject extends BasePathObject {
|
||||
override updateShadow_(shadow: boolean) {
|
||||
if (shadow) {
|
||||
this.svgPathLight.style.display = 'none';
|
||||
if (!this.style.colourSecondary) {
|
||||
throw new Error(
|
||||
'The renderer did not properly initialize the secondary colour ' +
|
||||
'of the block style block style');
|
||||
}
|
||||
this.svgPathDark.setAttribute('fill', this.style.colourSecondary);
|
||||
this.svgPath.setAttribute('stroke', 'none');
|
||||
this.svgPath.setAttribute('fill', this.style.colourSecondary);
|
||||
|
||||
31
core/serialization.ts
Normal file
31
core/serialization.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Serialization methods.
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.serialization');
|
||||
|
||||
import * as blocks from './serialization/blocks.js';
|
||||
import * as exceptions from './serialization/exceptions.js';
|
||||
import * as priorities from './serialization/priorities.js';
|
||||
import * as procedures from './serialization/procedures.js';
|
||||
import * as registry from './serialization/registry.js';
|
||||
import * as variables from './serialization/variables.js';
|
||||
import * as workspaces from './serialization/workspaces.js';
|
||||
import {ISerializer} from './interfaces/i_serializer.js';
|
||||
|
||||
export {
|
||||
blocks,
|
||||
exceptions,
|
||||
priorities,
|
||||
procedures,
|
||||
registry,
|
||||
variables,
|
||||
workspaces,
|
||||
ISerializer,
|
||||
};
|
||||
@@ -21,6 +21,12 @@ goog.declareModuleId('Blockly.serialization.priorities');
|
||||
* @alias Blockly.serialization.priorities.VARIABLES
|
||||
*/
|
||||
export const VARIABLES = 100;
|
||||
|
||||
/**
|
||||
* The priority for deserializing variable data.
|
||||
*/
|
||||
export const PROCEDURES = 75;
|
||||
|
||||
/**
|
||||
* The priority for deserializing blocks.
|
||||
*
|
||||
|
||||
152
core/serialization/procedures.ts
Normal file
152
core/serialization/procedures.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import type {ISerializer} from '../interfaces/i_serializer.js';
|
||||
import {ObservableProcedureModel} from '../procedures/observable_procedure_model.js';
|
||||
import {ObservableParameterModel} from '../procedures/observable_parameter_model.js';
|
||||
import * as priorities from './priorities.js';
|
||||
import * as serializationRegistry from './registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Representation of a procedure data model.
|
||||
*/
|
||||
export interface State {
|
||||
id: string, name: string, returnTypes: string[]|null,
|
||||
parameters?: ParameterState[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of a parameter data model.
|
||||
*/
|
||||
export interface ParameterState {
|
||||
id: string, name: string, types?: string[],
|
||||
}
|
||||
|
||||
/**
|
||||
* A newable signature for an IProcedureModel.
|
||||
*
|
||||
* Refer to
|
||||
* https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics
|
||||
* for what is going on with this.
|
||||
*/
|
||||
type ProcedureModelConstructor<ProcedureModel extends IProcedureModel> =
|
||||
new (workspace: Workspace, name: string, id: string) => ProcedureModel;
|
||||
|
||||
/**
|
||||
* A newable signature for an IParameterModel.
|
||||
*
|
||||
* Refer to
|
||||
* https://www.typescriptlang.org/docs/handbook/2/generics.html#using-class-types-in-generics
|
||||
* for what is going on with this.
|
||||
*/
|
||||
type ParameterModelConstructor<ParameterModel extends IParameterModel> =
|
||||
new (workspace: Workspace, name: string, id: string) => ParameterModel;
|
||||
|
||||
|
||||
/** Serializes the given IProcedureModel to JSON. */
|
||||
function saveProcedure(proc: IProcedureModel): State {
|
||||
const state: State = {
|
||||
id: proc.getId(),
|
||||
name: proc.getName(),
|
||||
returnTypes: proc.getReturnTypes(),
|
||||
};
|
||||
if (!proc.getParameters().length) return state;
|
||||
state.parameters = proc.getParameters().map((param) => saveParameter(param));
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Serializes the given IParameterModel to JSON. */
|
||||
function saveParameter(param: IParameterModel): ParameterState {
|
||||
const state: ParameterState = {
|
||||
id: param.getId(),
|
||||
name: param.getName(),
|
||||
};
|
||||
if (!param.getTypes().length) return state;
|
||||
state.types = param.getTypes();
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Deserializes the given procedure model State from JSON. */
|
||||
function
|
||||
loadProcedure<ProcedureModel extends IProcedureModel,
|
||||
ParameterModel extends IParameterModel>(
|
||||
procedureModelClass: ProcedureModelConstructor<ProcedureModel>,
|
||||
parameterModelClass: ParameterModelConstructor<ParameterModel>,
|
||||
state: State, workspace: Workspace): ProcedureModel {
|
||||
const proc = new procedureModelClass(workspace, state.name, state.id)
|
||||
.setReturnTypes(state.returnTypes);
|
||||
if (!state.parameters) return proc;
|
||||
for (const [index, param] of state.parameters.entries()) {
|
||||
proc.insertParameter(
|
||||
loadParameter(parameterModelClass, param, workspace), index);
|
||||
}
|
||||
return proc;
|
||||
}
|
||||
|
||||
/** Deserializes the given ParameterState from JSON. */
|
||||
function loadParameter<ParameterModel extends IParameterModel>(
|
||||
parameterModelClass: ParameterModelConstructor<ParameterModel>,
|
||||
state: ParameterState, workspace: Workspace): ParameterModel {
|
||||
return new parameterModelClass(workspace, state.name, state.id)
|
||||
.setTypes(state.types || []);
|
||||
}
|
||||
|
||||
/** Serializer for saving and loading procedure state. */
|
||||
export class ProcedureSerializer<ProcedureModel extends IProcedureModel,
|
||||
ParameterModel extends
|
||||
IParameterModel> implements ISerializer {
|
||||
public priority = priorities.PROCEDURES;
|
||||
|
||||
/**
|
||||
* Constructs the procedure serializer.
|
||||
*
|
||||
* Example usage:
|
||||
* new ProcedureSerializer(MyProcedureModelClass, MyParameterModelClass)
|
||||
*
|
||||
* @param procedureModelClass The class (implementing IProcedureModel) that
|
||||
* you want this serializer to deserialize.
|
||||
* @param parameterModelClass The class (implementing IParameterModel) that
|
||||
* you want this serializer to deserialize.
|
||||
*/
|
||||
constructor(
|
||||
private readonly procedureModelClass:
|
||||
ProcedureModelConstructor<ProcedureModel>,
|
||||
private readonly parameterModelClass:
|
||||
ParameterModelConstructor<ParameterModel>) {}
|
||||
|
||||
/** Serializes the procedure models of the given workspace. */
|
||||
save(workspace: Workspace): State[]|null {
|
||||
return workspace.getProcedureMap().getProcedures().map(
|
||||
(proc) => saveProcedure(proc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the procedures models defined by the given state into the
|
||||
* workspace.
|
||||
*/
|
||||
load(state: State[], workspace: Workspace) {
|
||||
const map = workspace.getProcedureMap();
|
||||
for (const procState of state) {
|
||||
map.add(loadProcedure(
|
||||
this.procedureModelClass, this.parameterModelClass, procState,
|
||||
workspace));
|
||||
}
|
||||
}
|
||||
|
||||
/** Disposes of any procedure models that exist on the workspace. */
|
||||
clear(workspace: Workspace) {
|
||||
workspace.getProcedureMap().clear();
|
||||
}
|
||||
}
|
||||
|
||||
serializationRegistry.register(
|
||||
'procedures',
|
||||
new ProcedureSerializer(
|
||||
ObservableProcedureModel, ObservableParameterModel));
|
||||
@@ -34,7 +34,7 @@ export enum names {
|
||||
CUT = 'cut',
|
||||
PASTE = 'paste',
|
||||
UNDO = 'undo',
|
||||
REDO = 'redo'
|
||||
REDO = 'redo',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,8 +56,7 @@ export class ShortcutRegistry {
|
||||
register(shortcut: KeyboardShortcut, opt_allowOverrides?: boolean) {
|
||||
const registeredShortcut = this.shortcuts.get(shortcut.name);
|
||||
if (registeredShortcut && !opt_allowOverrides) {
|
||||
throw new Error(
|
||||
'Shortcut with name "' + shortcut.name + '" already exists.');
|
||||
throw new Error(`Shortcut named "${shortcut.name}" already exists.`);
|
||||
}
|
||||
this.shortcuts.set(shortcut.name, shortcut);
|
||||
|
||||
@@ -81,8 +80,7 @@ export class ShortcutRegistry {
|
||||
const shortcut = this.shortcuts.get(shortcutName);
|
||||
|
||||
if (!shortcut) {
|
||||
console.warn(
|
||||
'Keyboard shortcut with name "' + shortcutName + '" not found.');
|
||||
console.warn(`Keyboard shortcut named "${shortcutName}" not found.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,9 +108,8 @@ export class ShortcutRegistry {
|
||||
keyCode = String(keyCode);
|
||||
const shortcutNames = this.keyMap.get(keyCode);
|
||||
if (shortcutNames && !opt_allowCollision) {
|
||||
throw new Error(
|
||||
'Shortcut with name "' + shortcutName + '" collides with shortcuts ' +
|
||||
shortcutNames.toString());
|
||||
throw new Error(`Shortcut named "${
|
||||
shortcutName}" collides with shortcuts "${shortcutNames}"`);
|
||||
} else if (shortcutNames && opt_allowCollision) {
|
||||
shortcutNames.unshift(shortcutName);
|
||||
} else {
|
||||
@@ -138,9 +135,8 @@ export class ShortcutRegistry {
|
||||
|
||||
if (!shortcutNames) {
|
||||
if (!opt_quiet) {
|
||||
console.warn(
|
||||
'No keyboard shortcut with name "' + shortcutName +
|
||||
'" registered with key code "' + keyCode + '"');
|
||||
console.warn(`No keyboard shortcut named "${
|
||||
shortcutName}" registered with key code "${keyCode}"`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -154,9 +150,8 @@ export class ShortcutRegistry {
|
||||
return true;
|
||||
}
|
||||
if (!opt_quiet) {
|
||||
console.warn(
|
||||
'No keyboard shortcut with name "' + shortcutName +
|
||||
'" registered with key code "' + keyCode + '"');
|
||||
console.warn(`No keyboard shortcut named "${
|
||||
shortcutName}" registered with key code "${keyCode}"`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user