mirror of
https://github.com/google/blockly.git
synced 2026-01-12 03:17:09 +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}],
|
"jsdoc/check-param-names": ["off", {"checkDestructured": false}],
|
||||||
// Allow any text in the license tag. Other checks are not relevant.
|
// Allow any text in the license tag. Other checks are not relevant.
|
||||||
"jsdoc/check-values": ["off"]
|
"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.
|
use Github pull requests for this purpose.
|
||||||
|
|
||||||
### Browser compatibility
|
### 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
|
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.
|
for compatibility information.
|
||||||
|
|
||||||
### The small print
|
### The small print
|
||||||
|
|||||||
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -35,11 +35,11 @@
|
|||||||
|
|
||||||
### Test Coverage
|
### Test Coverage
|
||||||
|
|
||||||
<!-- TODO: Please show how you have added tests to cover your changes,
|
<!-- TODO: Please create unit tests, and explain here how they cover
|
||||||
- or tell us how you tested it. If your change includes
|
your changes, or tell us how you tested it manually. If
|
||||||
- browser-specific behaviour, include information about the
|
your changes include browser-specific behaviour, include
|
||||||
- browser and device that you used for testing.
|
information about the browser and device that you used for
|
||||||
-->
|
testing. -->
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -22,7 +22,7 @@ updates:
|
|||||||
- "PR: chore"
|
- "PR: chore"
|
||||||
- "PR: dependencies"
|
- "PR: dependencies"
|
||||||
- package-ecosystem: "github-actions" # See documentation for possible values
|
- package-ecosystem: "github-actions" # See documentation for possible values
|
||||||
directory: "/"
|
directory: "/"
|
||||||
target-branch: "develop"
|
target-branch: "develop"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
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/
|
path: _deploy/
|
||||||
|
|
||||||
- name: Deploy to App Engine
|
- 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:
|
# For parameters see:
|
||||||
# https://github.com/google-github-actions/deploy-appengine#inputs
|
# https://github.com/google-github-actions/deploy-appengine#inputs
|
||||||
with:
|
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
|
# Trigger on pull requests against goog_module branch only
|
||||||
# Uses pull_request_target to get write permissions so that it can write labels.
|
# Uses pull_request_target to get write permissions so that it can write labels.
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
branches:
|
branches:
|
||||||
- goog_module
|
- goog_module
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag-module-cleanup:
|
tag-module-cleanup:
|
||||||
|
|
||||||
# Add the type: cleanup label
|
# Add the type: cleanup label
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
4
.github/workflows/update_metadata.yml
vendored
4
.github/workflows/update_metadata.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write # for peter-evans/create-pull-request to create branch
|
contents: write # for peter-evans/create-pull-request to create branch
|
||||||
pull-requests: write # for peter-evans/create-pull-request to create a PR
|
pull-requests: write # for peter-evans/create-pull-request to create a PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check Out Blockly
|
- name: Check Out Blockly
|
||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: source ./tests/scripts/update_metadata.sh
|
run: source ./tests/scripts/update_metadata.sh
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d
|
uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7
|
||||||
with:
|
with:
|
||||||
commit-message: Update build artifact sizes in check_metadata.sh
|
commit-message: Update build artifact sizes in check_metadata.sh
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
".": "8.0.0"
|
|
||||||
}
|
|
||||||
@@ -379,11 +379,6 @@
|
|||||||
},
|
},
|
||||||
"tsdoc-escape-right-brace": {
|
"tsdoc-escape-right-brace": {
|
||||||
"logLevel": "none"
|
"logLevel": "none"
|
||||||
},
|
|
||||||
|
|
||||||
// Needs to be fixed
|
|
||||||
"tsdoc-missing-deprecation-message": {
|
|
||||||
"logLevel": "none"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ blocks['lists_create_with'] = {
|
|||||||
this.itemCount_ = 3;
|
this.itemCount_ = 3;
|
||||||
this.updateShape_();
|
this.updateShape_();
|
||||||
this.setOutput(true, 'Array');
|
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']);
|
this.setTooltip(Msg['LISTS_CREATE_WITH_TOOLTIP']);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -150,8 +150,7 @@ const PROCEDURE_DEF_COMMON = {
|
|||||||
this.argumentVarModels_.push(variable);
|
this.argumentVarModels_.push(variable);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
'Failed to create a variable with name ' + varName +
|
`Failed to create a variable named "${varName}", ignoring.`);
|
||||||
', ignoring.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,7 +460,7 @@ blocks['procedures_defnoreturn'] = {
|
|||||||
.appendField(Msg['PROCEDURES_DEFNORETURN_TITLE'])
|
.appendField(Msg['PROCEDURES_DEFNORETURN_TITLE'])
|
||||||
.appendField(nameField, 'NAME')
|
.appendField(nameField, 'NAME')
|
||||||
.appendField('', 'PARAMS');
|
.appendField('', 'PARAMS');
|
||||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||||
if ((this.workspace.options.comments ||
|
if ((this.workspace.options.comments ||
|
||||||
(this.workspace.options.parentWorkspace &&
|
(this.workspace.options.parentWorkspace &&
|
||||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||||
@@ -507,7 +506,7 @@ blocks['procedures_defreturn'] = {
|
|||||||
this.appendValueInput('RETURN')
|
this.appendValueInput('RETURN')
|
||||||
.setAlign(Align.RIGHT)
|
.setAlign(Align.RIGHT)
|
||||||
.appendField(Msg['PROCEDURES_DEFRETURN_RETURN']);
|
.appendField(Msg['PROCEDURES_DEFRETURN_RETURN']);
|
||||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||||
if ((this.workspace.options.comments ||
|
if ((this.workspace.options.comments ||
|
||||||
(this.workspace.options.parentWorkspace &&
|
(this.workspace.options.parentWorkspace &&
|
||||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ const TEXT_JOIN_EXTENSION = function() {
|
|||||||
this.itemCount_ = 2;
|
this.itemCount_ = 2;
|
||||||
this.updateShape_();
|
this.updateShape_();
|
||||||
// Configure the mutator UI.
|
// 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.
|
// 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
|
* Reference to the global object. This is provided as 'root' by the
|
||||||
* UMD wrapper, but prefer globalThis if it is defined.
|
* UMD wrapper, but prefer globalThis if it is defined.
|
||||||
*
|
*
|
||||||
* https://www.ecma-international.org/ecma-262/9.0/index.html#sec-global-object
|
* https://www.ecma-international.org/ecma-262/9.0/index.html#sec-global-object
|
||||||
*
|
*
|
||||||
* @const
|
* @const
|
||||||
|
|||||||
@@ -180,6 +180,11 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
protected collapsed_ = false;
|
protected collapsed_ = false;
|
||||||
protected outputShape_: number|null = null;
|
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.
|
* A string representing the comment attached to this block.
|
||||||
*
|
*
|
||||||
@@ -316,7 +321,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
* @suppress {checkTypes}
|
* @suppress {checkTypes}
|
||||||
*/
|
*/
|
||||||
dispose(healStack: boolean) {
|
dispose(healStack: boolean) {
|
||||||
if (this.disposed) {
|
if (this.isDeadOrDying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +343,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
this.workspace.removeTypedBlock(this);
|
this.workspace.removeTypedBlock(this);
|
||||||
// Remove from block database.
|
// Remove from block database.
|
||||||
this.workspace.removeBlockById(this.id);
|
this.workspace.removeBlockById(this.id);
|
||||||
|
this.disposing = true;
|
||||||
|
|
||||||
// First, dispose of all my children.
|
// First, dispose of all my children.
|
||||||
for (let i = this.childBlocks_.length - 1; i >= 0; i--) {
|
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.
|
* Call initModel on all fields on the block.
|
||||||
* May be called more than once.
|
* May be called more than once.
|
||||||
@@ -772,7 +788,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
* @returns True if deletable.
|
* @returns True if deletable.
|
||||||
*/
|
*/
|
||||||
isDeletable(): boolean {
|
isDeletable(): boolean {
|
||||||
return this.deletable_ && !this.isShadow_ && !this.disposed &&
|
return this.deletable_ && !this.isShadow_ && !this.isDeadOrDying() &&
|
||||||
!this.workspace.options.readOnly;
|
!this.workspace.options.readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,7 +807,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
* @returns True if movable.
|
* @returns True if movable.
|
||||||
*/
|
*/
|
||||||
isMovable(): boolean {
|
isMovable(): boolean {
|
||||||
return this.movable_ && !this.isShadow_ && !this.disposed &&
|
return this.movable_ && !this.isShadow_ && !this.isDeadOrDying() &&
|
||||||
!this.workspace.options.readOnly;
|
!this.workspace.options.readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,7 +881,8 @@ export class Block implements IASTNodeLocation, IDeletable {
|
|||||||
* @returns True if editable.
|
* @returns True if editable.
|
||||||
*/
|
*/
|
||||||
isEditable(): boolean {
|
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})`;
|
skew = `skewX(${val})`;
|
||||||
disconnectPid = setTimeout(disconnectUiStep, 10, group, magnitude, start);
|
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) {
|
if (disconnectPid) {
|
||||||
clearTimeout(disconnectPid);
|
clearTimeout(disconnectPid);
|
||||||
}
|
}
|
||||||
disconnectGroup.setAttribute('transform', '');
|
const group = disconnectGroup;
|
||||||
|
(group as AnyDuringMigration).skew_ = '';
|
||||||
|
group.setAttribute('transform', (group as AnyDuringMigration).translate_);
|
||||||
disconnectGroup = null;
|
disconnectGroup = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,39 +31,40 @@ import * as svgMath from './utils/svg_math.js';
|
|||||||
* @alias Blockly.BlockDragSurfaceSvg
|
* @alias Blockly.BlockDragSurfaceSvg
|
||||||
*/
|
*/
|
||||||
export class 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
|
* This is where blocks live while they are being dragged if the drag
|
||||||
* surface is enabled.
|
* surface is enabled.
|
||||||
*/
|
*/
|
||||||
private dragGroup_: SVGElement;
|
private dragGroup: SVGElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cached value for the scale of the drag surface.
|
* Cached value for the scale of the drag surface.
|
||||||
* Used to set/get the correct translation during and after a drag.
|
* 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.
|
* Cached value for the translation of the drag surface.
|
||||||
* This translation is in pixel units, because the scale is applied to the
|
* This translation is in pixel units, because the scale is applied to the
|
||||||
* drag group rather than the top-level SVG.
|
* drag group rather than the top-level SVG.
|
||||||
*/
|
*/
|
||||||
private surfaceXY_: Coordinate = new Coordinate(0, 0);
|
private surfaceXY = new Coordinate(0, 0);
|
||||||
private readonly childSurfaceXY_: Coordinate;
|
|
||||||
|
/**
|
||||||
|
* 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. */
|
/** @param container Containing element. */
|
||||||
constructor(private readonly container: Element) {
|
constructor(private readonly container: Element) {
|
||||||
/**
|
this.svg = dom.createSvgElement(
|
||||||
* 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(
|
|
||||||
Svg.SVG, {
|
Svg.SVG, {
|
||||||
'xmlns': dom.SVG_NS,
|
'xmlns': dom.SVG_NS,
|
||||||
'xmlns:html': dom.HTML_NS,
|
'xmlns:html': dom.HTML_NS,
|
||||||
@@ -72,10 +73,15 @@ export class BlockDragSurfaceSvg {
|
|||||||
'class': 'blocklyBlockDragSurface',
|
'class': 'blocklyBlockDragSurface',
|
||||||
},
|
},
|
||||||
this.container);
|
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() {
|
createDom() {
|
||||||
// No alternative provided, because now the dom is just automatically
|
// No alternative provided, because now the dom is just automatically
|
||||||
// created in the constructor now.
|
// 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.
|
* @param blocks Block or group of blocks to place on the drag surface.
|
||||||
*/
|
*/
|
||||||
setBlocksAndShow(blocks: SVGElement) {
|
setBlocksAndShow(blocks: SVGElement) {
|
||||||
if (this.dragGroup_.childNodes.length) {
|
if (this.dragGroup.childNodes.length) {
|
||||||
throw Error('Already dragging a block.');
|
throw Error('Already dragging a block.');
|
||||||
}
|
}
|
||||||
// appendChild removes the blocks from the previous parent
|
// appendChild removes the blocks from the previous parent
|
||||||
this.dragGroup_.appendChild(blocks);
|
this.dragGroup.appendChild(blocks);
|
||||||
this.svg_.style.display = 'block';
|
this.svg.style.display = 'block';
|
||||||
this.surfaceXY_ = new Coordinate(0, 0);
|
this.surfaceXY = new Coordinate(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,13 +113,13 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @param scale Scale of the group.
|
* @param scale Scale of the group.
|
||||||
*/
|
*/
|
||||||
translateAndScaleGroup(x: number, y: number, scale: number) {
|
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.
|
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||||
const roundX = Math.round(x);
|
const roundX = Math.round(x);
|
||||||
const roundY = Math.round(y);
|
const roundY = Math.round(y);
|
||||||
this.childSurfaceXY_.x = roundX;
|
this.childSurfaceXY.x = roundX;
|
||||||
this.childSurfaceXY_.y = roundY;
|
this.childSurfaceXY.y = roundY;
|
||||||
this.dragGroup_!.setAttribute(
|
this.dragGroup.setAttribute(
|
||||||
'transform',
|
'transform',
|
||||||
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
|
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
|
||||||
}
|
}
|
||||||
@@ -124,13 +130,11 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
translateSurfaceInternal_() {
|
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.
|
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||||
x = Math.round(x);
|
const x = Math.round(this.surfaceXY.x);
|
||||||
y = Math.round(y);
|
const y = Math.round(this.surfaceXY.y);
|
||||||
this.svg_.style.display = 'block';
|
this.svg.style.display = 'block';
|
||||||
dom.setCssTransform(this.svg_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
dom.setCssTransform(this.svg, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,9 +144,9 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @param deltaY Vertical offset in pixel units.
|
* @param deltaY Vertical offset in pixel units.
|
||||||
*/
|
*/
|
||||||
translateBy(deltaX: number, deltaY: number) {
|
translateBy(deltaX: number, deltaY: number) {
|
||||||
const x = this.surfaceXY_.x + deltaX;
|
const x = this.surfaceXY.x + deltaX;
|
||||||
const y = this.surfaceXY_.y + deltaY;
|
const y = this.surfaceXY.y + deltaY;
|
||||||
this.surfaceXY_ = new Coordinate(x, y);
|
this.surfaceXY = new Coordinate(x, y);
|
||||||
this.translateSurfaceInternal_();
|
this.translateSurfaceInternal_();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +160,7 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @param y Y translation for the entire surface.
|
* @param y Y translation for the entire surface.
|
||||||
*/
|
*/
|
||||||
translateSurface(x: number, y: number) {
|
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_();
|
this.translateSurfaceInternal_();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +171,8 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @returns Current translation of the surface.
|
* @returns Current translation of the surface.
|
||||||
*/
|
*/
|
||||||
getSurfaceTranslation(): Coordinate {
|
getSurfaceTranslation(): Coordinate {
|
||||||
const xy = svgMath.getRelativeXY(this.svg_ as SVGElement);
|
const xy = svgMath.getRelativeXY(this.svg);
|
||||||
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
return new Coordinate(xy.x / this.scale, xy.y / this.scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,8 +181,8 @@ export class BlockDragSurfaceSvg {
|
|||||||
*
|
*
|
||||||
* @returns Drag surface group element.
|
* @returns Drag surface group element.
|
||||||
*/
|
*/
|
||||||
getGroup(): SVGElement|null {
|
getGroup(): SVGElement {
|
||||||
return this.dragGroup_;
|
return this.dragGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,8 +190,8 @@ export class BlockDragSurfaceSvg {
|
|||||||
*
|
*
|
||||||
* @returns The SVG drag surface.
|
* @returns The SVG drag surface.
|
||||||
*/
|
*/
|
||||||
getSvgRoot(): SVGElement|null {
|
getSvgRoot(): SVGElement {
|
||||||
return this.svg_;
|
return this.svg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,7 +201,7 @@ export class BlockDragSurfaceSvg {
|
|||||||
* @returns Drag surface block DOM element, or null if no blocks exist.
|
* @returns Drag surface block DOM element, or null if no blocks exist.
|
||||||
*/
|
*/
|
||||||
getCurrentBlock(): Element|null {
|
getCurrentBlock(): Element|null {
|
||||||
return this.dragGroup_.firstChild as Element;
|
return this.dragGroup.firstChild as Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,7 +213,7 @@ export class BlockDragSurfaceSvg {
|
|||||||
*/
|
*/
|
||||||
getWsTranslation(): Coordinate {
|
getWsTranslation(): Coordinate {
|
||||||
// Returning a copy so the coordinate can not be changed outside this class.
|
// 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();
|
const currentBlockElement = this.getCurrentBlock();
|
||||||
if (currentBlockElement) {
|
if (currentBlockElement) {
|
||||||
if (opt_newSurface) {
|
if (opt_newSurface) {
|
||||||
// appendChild removes the node from this.dragGroup_
|
// appendChild removes the node from this.dragGroup
|
||||||
opt_newSurface.appendChild(currentBlockElement);
|
opt_newSurface.appendChild(currentBlockElement);
|
||||||
} else {
|
} else {
|
||||||
this.dragGroup_.removeChild(currentBlockElement);
|
this.dragGroup.removeChild(currentBlockElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.svg_.style.display = 'none';
|
this.svg.style.display = 'none';
|
||||||
if (this.dragGroup_.childNodes.length) {
|
if (this.dragGroup.childNodes.length) {
|
||||||
throw Error('Drag group was not cleared.');
|
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.
|
* @returns #RRGGBB string.
|
||||||
*/
|
*/
|
||||||
getColourSecondary(): string|null {
|
getColourSecondary(): string|undefined {
|
||||||
return this.style.colourSecondary;
|
return this.style.colourSecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
|||||||
*
|
*
|
||||||
* @returns #RRGGBB string.
|
* @returns #RRGGBB string.
|
||||||
*/
|
*/
|
||||||
getColourTertiary(): string|null {
|
getColourTertiary(): string|undefined {
|
||||||
return this.style.colourTertiary;
|
return this.style.colourTertiary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,7 +525,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
|||||||
|
|
||||||
/** Snap this block to the nearest grid point. */
|
/** Snap this block to the nearest grid point. */
|
||||||
snapToGrid() {
|
snapToGrid() {
|
||||||
if (this.disposed) {
|
if (this.isDeadOrDying()) {
|
||||||
return; // Deleted block.
|
return; // Deleted block.
|
||||||
}
|
}
|
||||||
if (this.workspace.isDragging()) {
|
if (this.workspace.isDragging()) {
|
||||||
@@ -778,10 +778,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
|||||||
(group as AnyDuringMigration).translate_ = '';
|
(group as AnyDuringMigration).translate_ = '';
|
||||||
(group as AnyDuringMigration).skew_ = '';
|
(group as AnyDuringMigration).skew_ = '';
|
||||||
common.draggingConnections.push(...this.getConnections_(true));
|
common.draggingConnections.push(...this.getConnections_(true));
|
||||||
this.svgGroup_.classList.add('blocklyDragging');
|
dom.addClass(this.svgGroup_, 'blocklyDragging');
|
||||||
} else {
|
} else {
|
||||||
common.draggingConnections.length = 0;
|
common.draggingConnections.length = 0;
|
||||||
this.svgGroup_.classList.remove('blocklyDragging');
|
dom.removeClass(this.svgGroup_, 'blocklyDragging');
|
||||||
}
|
}
|
||||||
// Recurse through all blocks attached under this one.
|
// Recurse through all blocks attached under this one.
|
||||||
for (let i = 0; i < this.childBlocks_.length; i++) {
|
for (let i = 0; i < this.childBlocks_.length; i++) {
|
||||||
@@ -861,8 +861,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
|||||||
* @suppress {checkTypes}
|
* @suppress {checkTypes}
|
||||||
*/
|
*/
|
||||||
override dispose(healStack?: boolean, animate?: boolean) {
|
override dispose(healStack?: boolean, animate?: boolean) {
|
||||||
if (this.disposed) {
|
if (this.isDeadOrDying()) {
|
||||||
// The block has already been deleted.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Tooltip.dispose();
|
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
|
* Updates the colour of the block (and children) to match the current
|
||||||
* state.
|
* disabled state.
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -1067,13 +1066,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
|||||||
if (this.workspace.isDragging()) {
|
if (this.workspace.isDragging()) {
|
||||||
// Don't change the warning text during a drag.
|
// Don't change the warning text during a drag.
|
||||||
// Wait until the drag finishes.
|
// Wait until the drag finishes.
|
||||||
this.warningTextDb.set(
|
this.warningTextDb.set(id, setTimeout(() => {
|
||||||
id, setTimeout(() => {
|
if (!this.isDeadOrDying()) {
|
||||||
if (!this.disposed) { // Check block wasn't deleted.
|
this.warningTextDb.delete(id);
|
||||||
this.warningTextDb.delete(id);
|
this.setWarningText(text, id);
|
||||||
this.setWarningText(text, id);
|
}
|
||||||
}
|
}, 100));
|
||||||
}, 100));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.isInFlyout) {
|
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
|
* Bumps unconnected blocks out of alignment.
|
||||||
* connected should not coincidentally line up on screen.
|
*
|
||||||
|
* Two blocks which aren't actually connected should not coincidentally line
|
||||||
|
* up on screen, because that creates confusion for end-users.
|
||||||
*/
|
*/
|
||||||
override bumpNeighbours() {
|
override bumpNeighbours() {
|
||||||
if (this.disposed) {
|
this.getRootBlock().bumpNeighboursInternal();
|
||||||
return; // Deleted block.
|
}
|
||||||
}
|
|
||||||
if (this.workspace.isDragging()) {
|
/**
|
||||||
return; // Don't bump blocks during a drag.
|
* Bumps unconnected blocks out of alignment.
|
||||||
}
|
*/
|
||||||
const rootBlock = this.getRootBlock();
|
private bumpNeighboursInternal() {
|
||||||
if (rootBlock.isInFlyout) {
|
const root = this.getRootBlock();
|
||||||
|
if (this.isDeadOrDying() || this.workspace.isDragging() ||
|
||||||
|
root.isInFlyout) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Don't move blocks around in a flyout.
|
|
||||||
// Loop through every connection on this block.
|
function neighbourIsInStack(neighbour: RenderedConnection) {
|
||||||
const myConnections = this.getConnections_(false);
|
return neighbour.getSourceBlock().getRootBlock() === root;
|
||||||
for (let i = 0, connection; connection = myConnections[i]; i++) {
|
}
|
||||||
const renderedConn = (connection);
|
|
||||||
// Spider down from this block bumping all sub-blocks.
|
for (const conn of this.getConnections_(false)) {
|
||||||
if (renderedConn.isConnected() && renderedConn.isSuperior()) {
|
if (conn.isSuperior()) {
|
||||||
renderedConn.targetBlock()!.bumpNeighbours();
|
// Recurse down the block stack.
|
||||||
|
conn.targetBlock()?.bumpNeighboursInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
const neighbours = connection.neighbours(config.snapRadius);
|
for (const neighbour of conn.neighbours(config.snapRadius)) {
|
||||||
for (let j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
|
// Don't bump away from things that are in our stack.
|
||||||
const renderedOther = otherConnection as RenderedConnection;
|
if (neighbourIsInStack(neighbour)) continue;
|
||||||
// If both connections are connected, that's probably fine. But if
|
// If both connections are connected, that's fine.
|
||||||
// either one of them is unconnected, then there could be confusion.
|
if (conn.isConnected() && neighbour.isConnected()) continue;
|
||||||
if (!renderedConn.isConnected() || !renderedOther.isConnected()) {
|
|
||||||
// Only bump blocks if they are from different tree structures.
|
// Always bump the inferior connection.
|
||||||
if (renderedOther.getSourceBlock().getRootBlock() !== rootBlock) {
|
if (conn.isSuperior()) {
|
||||||
// Always bump the inferior block.
|
neighbour.bumpAwayFrom(conn);
|
||||||
if (renderedConn.isSuperior()) {
|
} else {
|
||||||
renderedOther.bumpAwayFrom(renderedConn);
|
conn.bumpAwayFrom(neighbour);
|
||||||
} else {
|
|
||||||
renderedConn.bumpAwayFrom(renderedOther);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ import {Field} from './field.js';
|
|||||||
import {FieldAngle} from './field_angle.js';
|
import {FieldAngle} from './field_angle.js';
|
||||||
import {FieldCheckbox} from './field_checkbox.js';
|
import {FieldCheckbox} from './field_checkbox.js';
|
||||||
import {FieldColour} from './field_colour.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 {FieldImage} from './field_image.js';
|
||||||
import {FieldLabel} from './field_label.js';
|
import {FieldLabel} from './field_label.js';
|
||||||
import {FieldLabelSerializable} from './field_label_serializable.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 {HorizontalFlyout} from './flyout_horizontal.js';
|
||||||
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
||||||
import {VerticalFlyout} from './flyout_vertical.js';
|
import {VerticalFlyout} from './flyout_vertical.js';
|
||||||
import {Generator} from './generator.js';
|
import {CodeGenerator} from './generator.js';
|
||||||
import {Gesture} from './gesture.js';
|
import {Gesture} from './gesture.js';
|
||||||
import {Grid} from './grid.js';
|
import {Grid} from './grid.js';
|
||||||
import {Icon} from './icon.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 {IRegistrableField} from './interfaces/i_registrable_field.js';
|
||||||
import {ISelectable} from './interfaces/i_selectable.js';
|
import {ISelectable} from './interfaces/i_selectable.js';
|
||||||
import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.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 {IStyleable} from './interfaces/i_styleable.js';
|
||||||
import {IToolbox} from './interfaces/i_toolbox.js';
|
import {IToolbox} from './interfaces/i_toolbox.js';
|
||||||
import {IToolboxItem} from './interfaces/i_toolbox_item.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 {Menu} from './menu.js';
|
||||||
import {MenuItem} from './menuitem.js';
|
import {MenuItem} from './menuitem.js';
|
||||||
import {MetricsManager} from './metrics_manager.js';
|
import {MetricsManager} from './metrics_manager.js';
|
||||||
import {Msg} from './msg.js';
|
import {Msg, setLocale} from './msg.js';
|
||||||
import {Mutator} from './mutator.js';
|
import {Mutator} from './mutator.js';
|
||||||
import {Names} from './names.js';
|
import {Names} from './names.js';
|
||||||
import {Options} from './options.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 * as zelos from './renderers/zelos/zelos.js';
|
||||||
import {Scrollbar} from './scrollbar.js';
|
import {Scrollbar} from './scrollbar.js';
|
||||||
import {ScrollbarPair} from './scrollbar_pair.js';
|
import {ScrollbarPair} from './scrollbar_pair.js';
|
||||||
import * as serializationBlocks from './serialization/blocks.js';
|
import * as serialization from './serialization.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 ShortcutItems from './shortcut_items.js';
|
import * as ShortcutItems from './shortcut_items.js';
|
||||||
import {ShortcutRegistry} from './shortcut_registry.js';
|
import {ShortcutRegistry} from './shortcut_registry.js';
|
||||||
import {Theme} from './theme.js';
|
import {Theme} from './theme.js';
|
||||||
@@ -157,7 +151,6 @@ import {Trashcan} from './trashcan.js';
|
|||||||
import * as utils from './utils.js';
|
import * as utils from './utils.js';
|
||||||
import * as colour from './utils/colour.js';
|
import * as colour from './utils/colour.js';
|
||||||
import * as deprecation from './utils/deprecation.js';
|
import * as deprecation from './utils/deprecation.js';
|
||||||
import * as svgMath from './utils/svg_math.js';
|
|
||||||
import * as toolbox from './utils/toolbox.js';
|
import * as toolbox from './utils/toolbox.js';
|
||||||
import {VariableMap} from './variable_map.js';
|
import {VariableMap} from './variable_map.js';
|
||||||
import {VariableModel} from './variable_model.js';
|
import {VariableModel} from './variable_model.js';
|
||||||
@@ -345,23 +338,12 @@ export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
|||||||
*/
|
*/
|
||||||
export const setParentContainer = common.setParentContainer;
|
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
|
* Size the workspace when the contents change. This also updates
|
||||||
* scrollbars accordingly.
|
* scrollbars accordingly.
|
||||||
*
|
*
|
||||||
* @param workspace The workspace to resize.
|
* @param workspace The workspace to resize.
|
||||||
* @deprecated Use workspace.resizeContents. (2021 December)
|
* @deprecated Use **workspace.resizeContents** instead.
|
||||||
* @see Blockly.WorkspaceSvg.resizeContents
|
* @see Blockly.WorkspaceSvg.resizeContents
|
||||||
* @alias Blockly.resizeSvgContents
|
* @alias Blockly.resizeSvgContents
|
||||||
*/
|
*/
|
||||||
@@ -377,7 +359,7 @@ export const resizeSvgContents = resizeSvgContentsLocal;
|
|||||||
* Copy a block or workspace comment onto the local clipboard.
|
* Copy a block or workspace comment onto the local clipboard.
|
||||||
*
|
*
|
||||||
* @param toCopy Block or Workspace Comment to be copied.
|
* @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
|
* @see Blockly.clipboard.copy
|
||||||
* @alias Blockly.copy
|
* @alias Blockly.copy
|
||||||
*/
|
*/
|
||||||
@@ -392,7 +374,7 @@ export function copy(toCopy: ICopyable) {
|
|||||||
* Paste a block or workspace comment on to the main workspace.
|
* Paste a block or workspace comment on to the main workspace.
|
||||||
*
|
*
|
||||||
* @returns True if the paste was successful, false otherwise.
|
* @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
|
* @see Blockly.clipboard.paste
|
||||||
* @alias Blockly.paste
|
* @alias Blockly.paste
|
||||||
*/
|
*/
|
||||||
@@ -407,7 +389,7 @@ export function paste(): boolean {
|
|||||||
* Duplicate this block and its children, or a workspace comment.
|
* Duplicate this block and its children, or a workspace comment.
|
||||||
*
|
*
|
||||||
* @param toDuplicate Block or Workspace Comment to be copied.
|
* @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
|
* @see Blockly.clipboard.duplicate
|
||||||
* @alias Blockly.duplicate
|
* @alias Blockly.duplicate
|
||||||
*/
|
*/
|
||||||
@@ -423,7 +405,7 @@ export function duplicate(toDuplicate: ICopyable) {
|
|||||||
*
|
*
|
||||||
* @param str Input string.
|
* @param str Input string.
|
||||||
* @returns True if number, false otherwise.
|
* @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
|
* @see Blockly.utils.string.isNumber
|
||||||
* @alias Blockly.isNumber
|
* @alias Blockly.isNumber
|
||||||
*/
|
*/
|
||||||
@@ -439,7 +421,7 @@ export function isNumber(str: string): boolean {
|
|||||||
*
|
*
|
||||||
* @param hue Hue on a colour wheel (0-360).
|
* @param hue Hue on a colour wheel (0-360).
|
||||||
* @returns RGB code, e.g. '#5ba65b'.
|
* @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
|
* @see Blockly.utils.colour.hueToHex
|
||||||
* @alias Blockly.hueToHex
|
* @alias Blockly.hueToHex
|
||||||
*/
|
*/
|
||||||
@@ -461,7 +443,7 @@ export function hueToHex(hue: number): string {
|
|||||||
* @param thisObject The value of 'this' in the function.
|
* @param thisObject The value of 'this' in the function.
|
||||||
* @param func Function to call when event is triggered.
|
* @param func Function to call when event is triggered.
|
||||||
* @returns Opaque data that can be passed to unbindEvent_.
|
* @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
|
* @see Blockly.browserEvents.bind
|
||||||
* @alias Blockly.bindEvent_
|
* @alias Blockly.bindEvent_
|
||||||
*/
|
*/
|
||||||
@@ -480,7 +462,7 @@ export function bindEvent_(
|
|||||||
* @param bindData Opaque data from bindEvent_.
|
* @param bindData Opaque data from bindEvent_.
|
||||||
* This list is emptied during the course of calling this function.
|
* This list is emptied during the course of calling this function.
|
||||||
* @returns The function call.
|
* @returns The function call.
|
||||||
* @deprecated Use Blockly.browserEvents.unbind(). (December 2021)
|
* @deprecated Use **Blockly.browserEvents.unbind** instead.
|
||||||
* @see browserEvents.unbind
|
* @see browserEvents.unbind
|
||||||
* @alias Blockly.unbindEvent_
|
* @alias Blockly.unbindEvent_
|
||||||
*/
|
*/
|
||||||
@@ -508,7 +490,7 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
|
|||||||
* the default handler. False by default. If opt_noPreventDefault is
|
* the default handler. False by default. If opt_noPreventDefault is
|
||||||
* provided, opt_noCaptureIdentifier must also be provided.
|
* provided, opt_noCaptureIdentifier must also be provided.
|
||||||
* @returns Opaque data that can be passed to unbindEvent_.
|
* @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
|
* @see browserEvents.conditionalBind
|
||||||
* @alias Blockly.bindEventWithChecks_
|
* @alias Blockly.bindEventWithChecks_
|
||||||
*/
|
*/
|
||||||
@@ -560,6 +542,7 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
|
|||||||
*/
|
*/
|
||||||
export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME;
|
export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME;
|
||||||
|
|
||||||
|
|
||||||
// Context for why we need to monkey-patch in these functions (internal):
|
// 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
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
const menuOptions = [];
|
const menuOptions = [];
|
||||||
|
|
||||||
if (this.isDeletable() && this.isMovable()) {
|
if (this.isDeletable() && this.isMovable()) {
|
||||||
menuOptions.push(ContextMenu.commentDuplicateOption(this));
|
menuOptions.push(ContextMenu.commentDuplicateOption(this));
|
||||||
menuOptions.push(ContextMenu.commentDeleteOption(this));
|
menuOptions.push(ContextMenu.commentDeleteOption(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu.show(e, menuOptions, this.RTL);
|
ContextMenu.show(e, menuOptions, this.RTL);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -619,6 +602,7 @@ export {Css};
|
|||||||
export {Events};
|
export {Events};
|
||||||
export {Extensions};
|
export {Extensions};
|
||||||
export {Procedures};
|
export {Procedures};
|
||||||
|
export {Procedures as procedures};
|
||||||
export {ShortcutItems};
|
export {ShortcutItems};
|
||||||
export {Themes};
|
export {Themes};
|
||||||
export {Tooltip};
|
export {Tooltip};
|
||||||
@@ -668,7 +652,7 @@ export {Field};
|
|||||||
export {FieldAngle};
|
export {FieldAngle};
|
||||||
export {FieldCheckbox};
|
export {FieldCheckbox};
|
||||||
export {FieldColour};
|
export {FieldColour};
|
||||||
export {FieldDropdown};
|
export {FieldDropdown, MenuGenerator, MenuGeneratorFunction, MenuOption};
|
||||||
export {FieldImage};
|
export {FieldImage};
|
||||||
export {FieldLabel};
|
export {FieldLabel};
|
||||||
export {FieldLabelSerializable};
|
export {FieldLabelSerializable};
|
||||||
@@ -679,7 +663,8 @@ export {FieldVariable};
|
|||||||
export {Flyout};
|
export {Flyout};
|
||||||
export {FlyoutButton};
|
export {FlyoutButton};
|
||||||
export {FlyoutMetricsManager};
|
export {FlyoutMetricsManager};
|
||||||
export {Generator};
|
export {CodeGenerator};
|
||||||
|
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
|
||||||
export {Gesture};
|
export {Gesture};
|
||||||
export {Grid};
|
export {Grid};
|
||||||
export {HorizontalFlyout};
|
export {HorizontalFlyout};
|
||||||
@@ -720,7 +705,7 @@ export {Menu};
|
|||||||
export {MenuItem};
|
export {MenuItem};
|
||||||
export {MetricsManager};
|
export {MetricsManager};
|
||||||
export {Mutator};
|
export {Mutator};
|
||||||
export {Msg};
|
export {Msg, setLocale};
|
||||||
export {Names};
|
export {Names};
|
||||||
export {Options};
|
export {Options};
|
||||||
export {RenderedConnection};
|
export {RenderedConnection};
|
||||||
@@ -753,12 +738,4 @@ export {config};
|
|||||||
export const connectionTypes = ConnectionType;
|
export const connectionTypes = ConnectionType;
|
||||||
export {inject};
|
export {inject};
|
||||||
export {inputTypes};
|
export {inputTypes};
|
||||||
export namespace serialization {
|
export {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;
|
|
||||||
}
|
|
||||||
|
|||||||
324
core/bubble.ts
324
core/bubble.ts
@@ -56,67 +56,68 @@ export class Bubble implements IBubble {
|
|||||||
static ANCHOR_RADIUS = 8;
|
static ANCHOR_RADIUS = 8;
|
||||||
|
|
||||||
/** Mouse up event data. */
|
/** Mouse up event data. */
|
||||||
private static onMouseUpWrapper_: browserEvents.Data|null = null;
|
private static onMouseUpWrapper: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Mouse move event data. */
|
/** Mouse move event data. */
|
||||||
private static onMouseMoveWrapper_: browserEvents.Data|null = null;
|
private static onMouseMoveWrapper: browserEvents.Data|null = null;
|
||||||
|
|
||||||
workspace_: WorkspaceSvg;
|
workspace_: WorkspaceSvg;
|
||||||
content_: SVGElement;
|
content_: SVGElement;
|
||||||
shape_: SVGElement;
|
shape_: SVGElement;
|
||||||
|
|
||||||
/** Flag to stop incremental rendering during construction. */
|
/** Flag to stop incremental rendering during construction. */
|
||||||
private readonly rendered_: boolean;
|
private readonly rendered: boolean;
|
||||||
|
|
||||||
/** The SVG group containing all parts of the bubble. */
|
/** 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.
|
* 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. */
|
/** 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. */
|
/** 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. */
|
/** 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,
|
* Relative X coordinate of bubble with respect to the anchor's centre,
|
||||||
* in workspace units.
|
* in workspace units.
|
||||||
* In RTL mode the initial value is negated.
|
* 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
|
* Relative Y coordinate of bubble with respect to the anchor's centre, in
|
||||||
* workspace units.
|
* workspace units.
|
||||||
*/
|
*/
|
||||||
private relativeTop_ = 0;
|
private relativeTop = 0;
|
||||||
|
|
||||||
/** Width of bubble, in workspace units. */
|
/** Width of bubble, in workspace units. */
|
||||||
private width_ = 0;
|
private width = 0;
|
||||||
|
|
||||||
/** Height of bubble, in workspace units. */
|
/** Height of bubble, in workspace units. */
|
||||||
private height_ = 0;
|
private height = 0;
|
||||||
|
|
||||||
/** Automatically position and reposition the bubble. */
|
/** Automatically position and reposition the bubble. */
|
||||||
private autoLayout_ = true;
|
private autoLayout = true;
|
||||||
|
|
||||||
/** Method to call on resize of bubble. */
|
/** Method to call on resize of bubble. */
|
||||||
private resizeCallback_: (() => void)|null = null;
|
private resizeCallback: (() => void)|null = null;
|
||||||
|
|
||||||
/** Method to call on move of bubble. */
|
/** Method to call on move of bubble. */
|
||||||
private moveCallback_: (() => void)|null = null;
|
private moveCallback: (() => void)|null = null;
|
||||||
|
|
||||||
/** Mouse down on bubbleBack_ event data. */
|
/** Mouse down on bubbleBack event data. */
|
||||||
private onMouseDownBubbleWrapper_: browserEvents.Data|null = null;
|
private onMouseDownBubbleWrapper: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Mouse down on resizeGroup_ event data. */
|
/** Mouse down on resizeGroup event data. */
|
||||||
private onMouseDownResizeWrapper_: browserEvents.Data|null = null;
|
private onMouseDownResizeWrapper: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes whether this bubble has been disposed of (nodes and event
|
* Describes whether this bubble has been disposed of (nodes and event
|
||||||
@@ -125,7 +126,7 @@ export class Bubble implements IBubble {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
disposed = false;
|
disposed = false;
|
||||||
private arrow_radians_: number;
|
private arrowRadians: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param workspace The workspace on which to draw the bubble.
|
* @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,
|
workspace: WorkspaceSvg, content: SVGElement, shape: SVGElement,
|
||||||
anchorXY: Coordinate, bubbleWidth: number|null,
|
anchorXY: Coordinate, bubbleWidth: number|null,
|
||||||
bubbleHeight: number|null) {
|
bubbleHeight: number|null) {
|
||||||
this.rendered_ = false;
|
this.rendered = false;
|
||||||
this.workspace_ = workspace;
|
this.workspace_ = workspace;
|
||||||
this.content_ = content;
|
this.content_ = content;
|
||||||
this.shape_ = shape;
|
this.shape_ = shape;
|
||||||
@@ -148,11 +149,11 @@ export class Bubble implements IBubble {
|
|||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
angle = -angle;
|
angle = -angle;
|
||||||
}
|
}
|
||||||
this.arrow_radians_ = math.toRadians(angle);
|
this.arrowRadians = math.toRadians(angle);
|
||||||
|
|
||||||
const canvas = workspace.getBubbleCanvas();
|
const canvas = workspace.getBubbleCanvas();
|
||||||
canvas.appendChild(
|
canvas.appendChild(
|
||||||
this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
|
this.createDom(content, !!(bubbleWidth && bubbleHeight)));
|
||||||
|
|
||||||
this.setAnchorLocation(anchorXY);
|
this.setAnchorLocation(anchorXY);
|
||||||
if (!bubbleWidth || !bubbleHeight) {
|
if (!bubbleWidth || !bubbleHeight) {
|
||||||
@@ -163,9 +164,9 @@ export class Bubble implements IBubble {
|
|||||||
this.setBubbleSize(bubbleWidth, bubbleHeight);
|
this.setBubbleSize(bubbleWidth, bubbleHeight);
|
||||||
|
|
||||||
// Render the bubble.
|
// Render the bubble.
|
||||||
this.positionBubble_();
|
this.positionBubble();
|
||||||
this.renderArrow_();
|
this.renderArrow();
|
||||||
this.rendered_ = true;
|
this.rendered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,7 +176,7 @@ export class Bubble implements IBubble {
|
|||||||
* @param hasResize Add diagonal resize gripper if true.
|
* @param hasResize Add diagonal resize gripper if true.
|
||||||
* @returns The bubble's SVG group.
|
* @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:
|
/* Create the bubble. Here's the markup that will be generated:
|
||||||
<g>
|
<g>
|
||||||
<g filter="url(#blocklyEmbossFilter837493)">
|
<g filter="url(#blocklyEmbossFilter837493)">
|
||||||
@@ -191,7 +192,7 @@ export class Bubble implements IBubble {
|
|||||||
[...content goes here...]
|
[...content goes here...]
|
||||||
</g>
|
</g>
|
||||||
*/
|
*/
|
||||||
this.bubbleGroup_ = dom.createSvgElement(Svg.G, {});
|
this.bubbleGroup = dom.createSvgElement(Svg.G, {});
|
||||||
let filter: {filter?: string} = {
|
let filter: {filter?: string} = {
|
||||||
'filter': 'url(#' +
|
'filter': 'url(#' +
|
||||||
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
|
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
|
||||||
@@ -201,9 +202,9 @@ export class Bubble implements IBubble {
|
|||||||
// https://github.com/google/blockly/issues/99
|
// https://github.com/google/blockly/issues/99
|
||||||
filter = {};
|
filter = {};
|
||||||
}
|
}
|
||||||
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup_);
|
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup);
|
||||||
this.bubbleArrow_ = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
|
this.bubbleArrow = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
|
||||||
this.bubbleBack_ = dom.createSvgElement(
|
this.bubbleBack = dom.createSvgElement(
|
||||||
Svg.RECT, {
|
Svg.RECT, {
|
||||||
'class': 'blocklyDraggable',
|
'class': 'blocklyDraggable',
|
||||||
'x': 0,
|
'x': 0,
|
||||||
@@ -213,17 +214,17 @@ export class Bubble implements IBubble {
|
|||||||
},
|
},
|
||||||
bubbleEmboss);
|
bubbleEmboss);
|
||||||
if (hasResize) {
|
if (hasResize) {
|
||||||
this.resizeGroup_ = dom.createSvgElement(
|
this.resizeGroup = dom.createSvgElement(
|
||||||
Svg.G, {
|
Svg.G, {
|
||||||
'class': this.workspace_.RTL ? 'blocklyResizeSW' :
|
'class': this.workspace_.RTL ? 'blocklyResizeSW' :
|
||||||
'blocklyResizeSE',
|
'blocklyResizeSE',
|
||||||
},
|
},
|
||||||
this.bubbleGroup_);
|
this.bubbleGroup);
|
||||||
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
||||||
dom.createSvgElement(
|
dom.createSvgElement(
|
||||||
Svg.POLYGON,
|
Svg.POLYGON,
|
||||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||||
this.resizeGroup_);
|
this.resizeGroup);
|
||||||
dom.createSvgElement(
|
dom.createSvgElement(
|
||||||
Svg.LINE, {
|
Svg.LINE, {
|
||||||
'class': 'blocklyResizeLine',
|
'class': 'blocklyResizeLine',
|
||||||
@@ -232,7 +233,7 @@ export class Bubble implements IBubble {
|
|||||||
'x2': resizeSize - 1,
|
'x2': resizeSize - 1,
|
||||||
'y2': resizeSize / 3,
|
'y2': resizeSize / 3,
|
||||||
},
|
},
|
||||||
this.resizeGroup_);
|
this.resizeGroup);
|
||||||
dom.createSvgElement(
|
dom.createSvgElement(
|
||||||
Svg.LINE, {
|
Svg.LINE, {
|
||||||
'class': 'blocklyResizeLine',
|
'class': 'blocklyResizeLine',
|
||||||
@@ -241,21 +242,21 @@ export class Bubble implements IBubble {
|
|||||||
'x2': resizeSize - 1,
|
'x2': resizeSize - 1,
|
||||||
'y2': resizeSize * 2 / 3,
|
'y2': resizeSize * 2 / 3,
|
||||||
},
|
},
|
||||||
this.resizeGroup_);
|
this.resizeGroup);
|
||||||
} else {
|
} else {
|
||||||
this.resizeGroup_ = null;
|
this.resizeGroup = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.workspace_.options.readOnly) {
|
if (!this.workspace_.options.readOnly) {
|
||||||
this.onMouseDownBubbleWrapper_ = browserEvents.conditionalBind(
|
this.onMouseDownBubbleWrapper = browserEvents.conditionalBind(
|
||||||
this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_);
|
this.bubbleBack, 'mousedown', this, this.bubbleMouseDown);
|
||||||
if (this.resizeGroup_) {
|
if (this.resizeGroup) {
|
||||||
this.onMouseDownResizeWrapper_ = browserEvents.conditionalBind(
|
this.onMouseDownResizeWrapper = browserEvents.conditionalBind(
|
||||||
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
|
this.resizeGroup, 'mousedown', this, this.resizeMouseDown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.bubbleGroup_.appendChild(content);
|
this.bubbleGroup.appendChild(content);
|
||||||
return this.bubbleGroup_;
|
return this.bubbleGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,7 +265,7 @@ export class Bubble implements IBubble {
|
|||||||
* @returns The root SVG node of the bubble's group.
|
* @returns The root SVG node of the bubble's group.
|
||||||
*/
|
*/
|
||||||
getSvgRoot(): SVGElement {
|
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.
|
* @param id ID of block.
|
||||||
*/
|
*/
|
||||||
setSvgId(id: string) {
|
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.
|
* @param e Mouse down event.
|
||||||
*/
|
*/
|
||||||
private bubbleMouseDown_(e: Event) {
|
private bubbleMouseDown(e: Event) {
|
||||||
const gesture = this.workspace_.getGesture(e);
|
const gesture = this.workspace_.getGesture(e);
|
||||||
if (gesture) {
|
if (gesture) {
|
||||||
gesture.handleBubbleStart(e, this);
|
gesture.handleBubbleStart(e, this);
|
||||||
@@ -321,9 +322,9 @@ export class Bubble implements IBubble {
|
|||||||
*
|
*
|
||||||
* @param e Mouse down event.
|
* @param e Mouse down event.
|
||||||
*/
|
*/
|
||||||
private resizeMouseDown_(e: MouseEvent) {
|
private resizeMouseDown(e: MouseEvent) {
|
||||||
this.promote();
|
this.promote();
|
||||||
Bubble.unbindDragEvents_();
|
Bubble.unbindDragEvents();
|
||||||
if (browserEvents.isRightButton(e)) {
|
if (browserEvents.isRightButton(e)) {
|
||||||
// No right-click.
|
// No right-click.
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -333,12 +334,12 @@ export class Bubble implements IBubble {
|
|||||||
this.workspace_.startDrag(
|
this.workspace_.startDrag(
|
||||||
e,
|
e,
|
||||||
new Coordinate(
|
new Coordinate(
|
||||||
this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
|
this.workspace_.RTL ? -this.width : this.width, this.height));
|
||||||
|
|
||||||
Bubble.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
Bubble.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||||
document, 'mouseup', this, Bubble.bubbleMouseUp_);
|
document, 'mouseup', this, Bubble.bubbleMouseUp);
|
||||||
Bubble.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
Bubble.onMouseMoveWrapper = browserEvents.conditionalBind(
|
||||||
document, 'mousemove', this, this.resizeMouseMove_);
|
document, 'mousemove', this, this.resizeMouseMove);
|
||||||
this.workspace_.hideChaff();
|
this.workspace_.hideChaff();
|
||||||
// This event has been handled. No need to bubble up to the document.
|
// This event has been handled. No need to bubble up to the document.
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -349,13 +350,13 @@ export class Bubble implements IBubble {
|
|||||||
*
|
*
|
||||||
* @param e Mouse move event.
|
* @param e Mouse move event.
|
||||||
*/
|
*/
|
||||||
private resizeMouseMove_(e: MouseEvent) {
|
private resizeMouseMove(e: MouseEvent) {
|
||||||
this.autoLayout_ = false;
|
this.autoLayout = false;
|
||||||
const newXY = this.workspace_.moveDrag(e);
|
const newXY = this.workspace_.moveDrag(e);
|
||||||
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
// RTL requires the bubble to move its left edge.
|
// 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.
|
* @param callback The function to call on resize.
|
||||||
*/
|
*/
|
||||||
registerResizeEvent(callback: () => void) {
|
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.
|
* @param callback The function to call on move.
|
||||||
*/
|
*/
|
||||||
registerMoveEvent(callback: () => void) {
|
registerMoveEvent(callback: () => void) {
|
||||||
this.moveCallback_ = callback;
|
this.moveCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -384,9 +385,9 @@ export class Bubble implements IBubble {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
promote(): boolean {
|
promote(): boolean {
|
||||||
const svgGroup = this.bubbleGroup_?.parentNode;
|
const svgGroup = this.bubbleGroup?.parentNode;
|
||||||
if (svgGroup?.lastChild !== this.bubbleGroup_ && this.bubbleGroup_) {
|
if (svgGroup?.lastChild !== this.bubbleGroup && this.bubbleGroup) {
|
||||||
svgGroup?.appendChild(this.bubbleGroup_);
|
svgGroup?.appendChild(this.bubbleGroup);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -399,29 +400,29 @@ export class Bubble implements IBubble {
|
|||||||
* @param xy Absolute location.
|
* @param xy Absolute location.
|
||||||
*/
|
*/
|
||||||
setAnchorLocation(xy: Coordinate) {
|
setAnchorLocation(xy: Coordinate) {
|
||||||
this.anchorXY_ = xy;
|
this.anchorXY = xy;
|
||||||
if (this.rendered_) {
|
if (this.rendered) {
|
||||||
this.positionBubble_();
|
this.positionBubble();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Position the bubble so that it does not fall off-screen. */
|
/** Position the bubble so that it does not fall off-screen. */
|
||||||
private layoutBubble_() {
|
private layoutBubble() {
|
||||||
// Get the metrics in workspace units.
|
// Get the metrics in workspace units.
|
||||||
const viewMetrics =
|
const viewMetrics =
|
||||||
this.workspace_.getMetricsManager().getViewMetrics(true);
|
this.workspace_.getMetricsManager().getViewMetrics(true);
|
||||||
|
|
||||||
const optimalLeft = this.getOptimalRelativeLeft_(viewMetrics);
|
const optimalLeft = this.getOptimalRelativeLeft(viewMetrics);
|
||||||
const optimalTop = this.getOptimalRelativeTop_(viewMetrics);
|
const optimalTop = this.getOptimalRelativeTop(viewMetrics);
|
||||||
const bbox = (this.shape_ as SVGGraphicsElement).getBBox();
|
const bbox = (this.shape_ as SVGGraphicsElement).getBBox();
|
||||||
|
|
||||||
const topPosition = {
|
const topPosition = {
|
||||||
x: optimalLeft,
|
x: optimalLeft,
|
||||||
y: -this.height_ -
|
y: -this.height -
|
||||||
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
|
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
|
||||||
number,
|
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 endPosition = {x: bbox.width, y: optimalTop};
|
||||||
const bottomPosition = {x: optimalLeft, y: bbox.height};
|
const bottomPosition = {x: optimalLeft, y: bbox.height};
|
||||||
|
|
||||||
@@ -430,11 +431,11 @@ export class Bubble implements IBubble {
|
|||||||
const fartherPosition =
|
const fartherPosition =
|
||||||
bbox.width < bbox.height ? bottomPosition : endPosition;
|
bbox.width < bbox.height ? bottomPosition : endPosition;
|
||||||
|
|
||||||
const topPositionOverlap = this.getOverlap_(topPosition, viewMetrics);
|
const topPositionOverlap = this.getOverlap(topPosition, viewMetrics);
|
||||||
const startPositionOverlap = this.getOverlap_(startPosition, viewMetrics);
|
const startPositionOverlap = this.getOverlap(startPosition, viewMetrics);
|
||||||
const closerPositionOverlap = this.getOverlap_(closerPosition, viewMetrics);
|
const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics);
|
||||||
const fartherPositionOverlap =
|
const fartherPositionOverlap =
|
||||||
this.getOverlap_(fartherPosition, viewMetrics);
|
this.getOverlap(fartherPosition, viewMetrics);
|
||||||
|
|
||||||
// Set the position to whichever position shows the most of the bubble,
|
// Set the position to whichever position shows the most of the bubble,
|
||||||
// with tiebreaks going in the order: top > start > close > far.
|
// with tiebreaks going in the order: top > start > close > far.
|
||||||
@@ -442,25 +443,25 @@ export class Bubble implements IBubble {
|
|||||||
topPositionOverlap, startPositionOverlap, closerPositionOverlap,
|
topPositionOverlap, startPositionOverlap, closerPositionOverlap,
|
||||||
fartherPositionOverlap);
|
fartherPositionOverlap);
|
||||||
if (topPositionOverlap === mostOverlap) {
|
if (topPositionOverlap === mostOverlap) {
|
||||||
this.relativeLeft_ = topPosition.x;
|
this.relativeLeft = topPosition.x;
|
||||||
this.relativeTop_ = topPosition.y;
|
this.relativeTop = topPosition.y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (startPositionOverlap === mostOverlap) {
|
if (startPositionOverlap === mostOverlap) {
|
||||||
this.relativeLeft_ = startPosition.x;
|
this.relativeLeft = startPosition.x;
|
||||||
this.relativeTop_ = startPosition.y;
|
this.relativeTop = startPosition.y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (closerPositionOverlap === mostOverlap) {
|
if (closerPositionOverlap === mostOverlap) {
|
||||||
this.relativeLeft_ = closerPosition.x;
|
this.relativeLeft = closerPosition.x;
|
||||||
this.relativeTop_ = closerPosition.y;
|
this.relativeTop = closerPosition.y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: I believe relativeLeft_ should actually be called relativeStart_
|
// TODO: I believe relativeLeft_ should actually be called relativeStart_
|
||||||
// and then the math should be fixed to reflect this. (hopefully it'll
|
// and then the math should be fixed to reflect this. (hopefully it'll
|
||||||
// make it look simpler)
|
// make it look simpler)
|
||||||
this.relativeLeft_ = fartherPosition.x;
|
this.relativeLeft = fartherPosition.x;
|
||||||
this.relativeTop_ = fartherPosition.y;
|
this.relativeTop = fartherPosition.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -473,19 +474,19 @@ export class Bubble implements IBubble {
|
|||||||
* in.
|
* in.
|
||||||
* @returns The percentage of the bubble that is visible.
|
* @returns The percentage of the bubble that is visible.
|
||||||
*/
|
*/
|
||||||
private getOverlap_(
|
private getOverlap(
|
||||||
relativeMin: {x: number, y: number},
|
relativeMin: {x: number, y: number},
|
||||||
viewMetrics: ContainerRegion): number {
|
viewMetrics: ContainerRegion): number {
|
||||||
// The position of the top-left corner of the bubble in workspace units.
|
// The position of the top-left corner of the bubble in workspace units.
|
||||||
const bubbleMin = {
|
const bubbleMin = {
|
||||||
x: this.workspace_.RTL ? this.anchorXY_.x - relativeMin.x - this.width_ :
|
x: this.workspace_.RTL ? this.anchorXY.x - relativeMin.x - this.width :
|
||||||
relativeMin.x + this.anchorXY_.x,
|
relativeMin.x + this.anchorXY.x,
|
||||||
y: relativeMin.y + this.anchorXY_.y,
|
y: relativeMin.y + this.anchorXY.y,
|
||||||
};
|
};
|
||||||
// The position of the bottom-right corner of the bubble in workspace units.
|
// The position of the bottom-right corner of the bubble in workspace units.
|
||||||
const bubbleMax = {
|
const bubbleMax = {
|
||||||
x: bubbleMin.x + this.width_,
|
x: bubbleMin.x + this.width,
|
||||||
y: bubbleMin.y + this.height_,
|
y: bubbleMin.y + this.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We could adjust these values to account for the scrollbars, but the
|
// 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);
|
Math.max(bubbleMin.y, workspaceMin.y);
|
||||||
return Math.max(
|
return Math.max(
|
||||||
0,
|
0,
|
||||||
Math.min(
|
Math.min(1, overlapWidth * overlapHeight / (this.width * this.height)));
|
||||||
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
|
* @returns The optimal horizontal position of the top-left corner of the
|
||||||
* bubble.
|
* bubble.
|
||||||
*/
|
*/
|
||||||
private getOptimalRelativeLeft_(viewMetrics: ContainerRegion): number {
|
private getOptimalRelativeLeft(viewMetrics: ContainerRegion): number {
|
||||||
let relativeLeft = -this.width_ / 4;
|
let relativeLeft = -this.width / 4;
|
||||||
|
|
||||||
// No amount of sliding left or right will give us a better overlap.
|
// 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;
|
return relativeLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
// Bubble coordinates are flipped in RTL.
|
// Bubble coordinates are flipped in RTL.
|
||||||
const bubbleRight = this.anchorXY_.x - relativeLeft;
|
const bubbleRight = this.anchorXY.x - relativeLeft;
|
||||||
const bubbleLeft = bubbleRight - this.width_;
|
const bubbleLeft = bubbleRight - this.width;
|
||||||
|
|
||||||
const workspaceRight = viewMetrics.left + viewMetrics.width;
|
const workspaceRight = viewMetrics.left + viewMetrics.width;
|
||||||
const workspaceLeft = viewMetrics.left +
|
const workspaceLeft = viewMetrics.left +
|
||||||
@@ -541,14 +541,14 @@ export class Bubble implements IBubble {
|
|||||||
|
|
||||||
if (bubbleLeft < workspaceLeft) {
|
if (bubbleLeft < workspaceLeft) {
|
||||||
// Slide the bubble right until it is onscreen.
|
// 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) {
|
} else if (bubbleRight > workspaceRight) {
|
||||||
// Slide the bubble left until it is onscreen.
|
// Slide the bubble left until it is onscreen.
|
||||||
relativeLeft = -(workspaceRight - this.anchorXY_.x);
|
relativeLeft = -(workspaceRight - this.anchorXY.x);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const bubbleLeft = relativeLeft + this.anchorXY_.x;
|
const bubbleLeft = relativeLeft + this.anchorXY.x;
|
||||||
const bubbleRight = bubbleLeft + this.width_;
|
const bubbleRight = bubbleLeft + this.width;
|
||||||
|
|
||||||
const workspaceLeft = viewMetrics.left;
|
const workspaceLeft = viewMetrics.left;
|
||||||
const workspaceRight = viewMetrics.left + viewMetrics.width -
|
const workspaceRight = viewMetrics.left + viewMetrics.width -
|
||||||
@@ -557,10 +557,10 @@ export class Bubble implements IBubble {
|
|||||||
|
|
||||||
if (bubbleLeft < workspaceLeft) {
|
if (bubbleLeft < workspaceLeft) {
|
||||||
// Slide the bubble right until it is onscreen.
|
// Slide the bubble right until it is onscreen.
|
||||||
relativeLeft = workspaceLeft - this.anchorXY_.x;
|
relativeLeft = workspaceLeft - this.anchorXY.x;
|
||||||
} else if (bubbleRight > workspaceRight) {
|
} else if (bubbleRight > workspaceRight) {
|
||||||
// Slide the bubble left until it is onscreen.
|
// 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
|
* @returns The optimal vertical position of the top-left corner of the
|
||||||
* bubble.
|
* bubble.
|
||||||
*/
|
*/
|
||||||
private getOptimalRelativeTop_(viewMetrics: ContainerRegion): number {
|
private getOptimalRelativeTop(viewMetrics: ContainerRegion): number {
|
||||||
let relativeTop = -this.height_ / 4;
|
let relativeTop = -this.height / 4;
|
||||||
|
|
||||||
// No amount of sliding up or down will give us a better overlap.
|
// 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;
|
return relativeTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bubbleTop = this.anchorXY_.y + relativeTop;
|
const bubbleTop = this.anchorXY.y + relativeTop;
|
||||||
const bubbleBottom = bubbleTop + this.height_;
|
const bubbleBottom = bubbleTop + this.height;
|
||||||
const workspaceTop = viewMetrics.top;
|
const workspaceTop = viewMetrics.top;
|
||||||
const workspaceBottom = viewMetrics.top +
|
const workspaceBottom = viewMetrics.top +
|
||||||
viewMetrics.height - // Thickness in workspace units.
|
viewMetrics.height - // Thickness in workspace units.
|
||||||
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
||||||
|
|
||||||
const anchorY = this.anchorXY_.y;
|
const anchorY = this.anchorXY.y;
|
||||||
if (bubbleTop < workspaceTop) {
|
if (bubbleTop < workspaceTop) {
|
||||||
// Slide the bubble down until it is onscreen.
|
// Slide the bubble down until it is onscreen.
|
||||||
relativeTop = workspaceTop - anchorY;
|
relativeTop = workspaceTop - anchorY;
|
||||||
} else if (bubbleBottom > workspaceBottom) {
|
} else if (bubbleBottom > workspaceBottom) {
|
||||||
// Slide the bubble up until it is onscreen.
|
// Slide the bubble up until it is onscreen.
|
||||||
relativeTop = workspaceBottom - anchorY - this.height_;
|
relativeTop = workspaceBottom - anchorY - this.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativeTop;
|
return relativeTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Move the bubble to a location relative to the anchor's centre. */
|
/** Move the bubble to a location relative to the anchor's centre. */
|
||||||
private positionBubble_() {
|
private positionBubble() {
|
||||||
let left = this.anchorXY_.x;
|
let left = this.anchorXY.x;
|
||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
left -= this.relativeLeft_ + this.width_;
|
left -= this.relativeLeft + this.width;
|
||||||
} else {
|
} 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);
|
this.moveTo(left, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +624,7 @@ export class Bubble implements IBubble {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
moveTo(x: number, y: number) {
|
moveTo(x: number, y: number) {
|
||||||
this.bubbleGroup_?.setAttribute(
|
this.bubbleGroup?.setAttribute(
|
||||||
'transform', 'translate(' + x + ',' + y + ')');
|
'transform', 'translate(' + x + ',' + y + ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,8 +635,8 @@ export class Bubble implements IBubble {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setDragging(adding: boolean) {
|
setDragging(adding: boolean) {
|
||||||
if (!adding && this.moveCallback_) {
|
if (!adding && this.moveCallback) {
|
||||||
this.moveCallback_();
|
this.moveCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@ export class Bubble implements IBubble {
|
|||||||
* @returns The height and width of the bubble.
|
* @returns The height and width of the bubble.
|
||||||
*/
|
*/
|
||||||
getBubbleSize(): Size {
|
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.
|
// Minimum size of a bubble.
|
||||||
width = Math.max(width, doubleBorderWidth + 45);
|
width = Math.max(width, doubleBorderWidth + 45);
|
||||||
height = Math.max(height, doubleBorderWidth + 20);
|
height = Math.max(height, doubleBorderWidth + 20);
|
||||||
this.width_ = width;
|
this.width = width;
|
||||||
this.height_ = height;
|
this.height = height;
|
||||||
this.bubbleBack_?.setAttribute('width', width.toString());
|
this.bubbleBack?.setAttribute('width', width.toString());
|
||||||
this.bubbleBack_?.setAttribute('height', height.toString());
|
this.bubbleBack?.setAttribute('height', height.toString());
|
||||||
if (this.resizeGroup_) {
|
if (this.resizeGroup) {
|
||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
// Mirror the resize group.
|
// Mirror the resize group.
|
||||||
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
||||||
this.resizeGroup_.setAttribute(
|
this.resizeGroup.setAttribute(
|
||||||
'transform',
|
'transform',
|
||||||
'translate(' + resizeSize + ',' + (height - doubleBorderWidth) +
|
'translate(' + resizeSize + ',' + (height - doubleBorderWidth) +
|
||||||
') scale(-1 1)');
|
') scale(-1 1)');
|
||||||
} else {
|
} else {
|
||||||
this.resizeGroup_.setAttribute(
|
this.resizeGroup.setAttribute(
|
||||||
'transform',
|
'transform',
|
||||||
'translate(' + (width - doubleBorderWidth) + ',' +
|
'translate(' + (width - doubleBorderWidth) + ',' +
|
||||||
(height - doubleBorderWidth) + ')');
|
(height - doubleBorderWidth) + ')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.autoLayout_) {
|
if (this.autoLayout) {
|
||||||
this.layoutBubble_();
|
this.layoutBubble();
|
||||||
}
|
}
|
||||||
this.positionBubble_();
|
this.positionBubble();
|
||||||
this.renderArrow_();
|
this.renderArrow();
|
||||||
|
|
||||||
// Allow the contents to resize.
|
// Allow the contents to resize.
|
||||||
if (this.resizeCallback_) {
|
if (this.resizeCallback) {
|
||||||
this.resizeCallback_();
|
this.resizeCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Draw the arrow between the bubble and the origin. */
|
/** Draw the arrow between the bubble and the origin. */
|
||||||
private renderArrow_() {
|
private renderArrow() {
|
||||||
const steps = [];
|
const steps = [];
|
||||||
// Find the relative coordinates of the center of the bubble.
|
// Find the relative coordinates of the center of the bubble.
|
||||||
const relBubbleX = this.width_ / 2;
|
const relBubbleX = this.width / 2;
|
||||||
const relBubbleY = this.height_ / 2;
|
const relBubbleY = this.height / 2;
|
||||||
// Find the relative coordinates of the center of the anchor.
|
// Find the relative coordinates of the center of the anchor.
|
||||||
let relAnchorX = -this.relativeLeft_;
|
let relAnchorX = -this.relativeLeft;
|
||||||
let relAnchorY = -this.relativeTop_;
|
let relAnchorY = -this.relativeTop;
|
||||||
if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) {
|
if (relBubbleX === relAnchorX && relBubbleY === relAnchorY) {
|
||||||
// Null case. Bubble is directly on top of the anchor.
|
// Null case. Bubble is directly on top of the anchor.
|
||||||
// Short circuit this rather than wade through divide by zeros.
|
// Short circuit this rather than wade through divide by zeros.
|
||||||
@@ -742,7 +742,7 @@ export class Bubble implements IBubble {
|
|||||||
const baseY2 = relBubbleY - thickness * rightRise;
|
const baseY2 = relBubbleY - thickness * rightRise;
|
||||||
|
|
||||||
// Distortion to curve the arrow.
|
// Distortion to curve the arrow.
|
||||||
let swirlAngle = angle + this.arrow_radians_;
|
let swirlAngle = angle + this.arrowRadians;
|
||||||
if (swirlAngle > Math.PI * 2) {
|
if (swirlAngle > Math.PI * 2) {
|
||||||
swirlAngle -= Math.PI * 2;
|
swirlAngle -= Math.PI * 2;
|
||||||
}
|
}
|
||||||
@@ -758,7 +758,7 @@ export class Bubble implements IBubble {
|
|||||||
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
|
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
|
||||||
}
|
}
|
||||||
steps.push('z');
|
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.
|
* @param hexColour Hex code of colour.
|
||||||
*/
|
*/
|
||||||
setColour(hexColour: string) {
|
setColour(hexColour: string) {
|
||||||
this.bubbleBack_?.setAttribute('fill', hexColour);
|
this.bubbleBack?.setAttribute('fill', hexColour);
|
||||||
this.bubbleArrow_?.setAttribute('fill', hexColour);
|
this.bubbleArrow?.setAttribute('fill', hexColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dispose of this bubble. */
|
/** Dispose of this bubble. */
|
||||||
dispose() {
|
dispose() {
|
||||||
if (this.onMouseDownBubbleWrapper_) {
|
if (this.onMouseDownBubbleWrapper) {
|
||||||
browserEvents.unbind(this.onMouseDownBubbleWrapper_);
|
browserEvents.unbind(this.onMouseDownBubbleWrapper);
|
||||||
}
|
}
|
||||||
if (this.onMouseDownResizeWrapper_) {
|
if (this.onMouseDownResizeWrapper) {
|
||||||
browserEvents.unbind(this.onMouseDownResizeWrapper_);
|
browserEvents.unbind(this.onMouseDownResizeWrapper);
|
||||||
}
|
}
|
||||||
Bubble.unbindDragEvents_();
|
Bubble.unbindDragEvents();
|
||||||
dom.removeNode(this.bubbleGroup_);
|
dom.removeNode(this.bubbleGroup);
|
||||||
this.disposed = true;
|
this.disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,12 +800,12 @@ export class Bubble implements IBubble {
|
|||||||
this.moveTo(newLoc.x, newLoc.y);
|
this.moveTo(newLoc.x, newLoc.y);
|
||||||
}
|
}
|
||||||
if (this.workspace_.RTL) {
|
if (this.workspace_.RTL) {
|
||||||
this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_;
|
this.relativeLeft = this.anchorXY.x - newLoc.x - this.width;
|
||||||
} else {
|
} else {
|
||||||
this.relativeLeft_ = newLoc.x - this.anchorXY_.x;
|
this.relativeLeft = newLoc.x - this.anchorXY.x;
|
||||||
}
|
}
|
||||||
this.relativeTop_ = newLoc.y - this.anchorXY_.y;
|
this.relativeTop = newLoc.y - this.anchorXY.y;
|
||||||
this.renderArrow_();
|
this.renderArrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -817,9 +817,9 @@ export class Bubble implements IBubble {
|
|||||||
getRelativeToSurfaceXY(): Coordinate {
|
getRelativeToSurfaceXY(): Coordinate {
|
||||||
return new Coordinate(
|
return new Coordinate(
|
||||||
this.workspace_.RTL ?
|
this.workspace_.RTL ?
|
||||||
-this.relativeLeft_ + this.anchorXY_.x - this.width_ :
|
-this.relativeLeft + this.anchorXY.x - this.width :
|
||||||
this.anchorXY_.x + this.relativeLeft_,
|
this.anchorXY.x + this.relativeLeft,
|
||||||
this.anchorXY_.y + this.relativeTop_);
|
this.anchorXY.y + this.relativeTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -831,18 +831,18 @@ export class Bubble implements IBubble {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setAutoLayout(enable: boolean) {
|
setAutoLayout(enable: boolean) {
|
||||||
this.autoLayout_ = enable;
|
this.autoLayout = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stop binding to the global mouseup and mousemove events. */
|
/** Stop binding to the global mouseup and mousemove events. */
|
||||||
private static unbindDragEvents_() {
|
private static unbindDragEvents() {
|
||||||
if (Bubble.onMouseUpWrapper_) {
|
if (Bubble.onMouseUpWrapper) {
|
||||||
browserEvents.unbind(Bubble.onMouseUpWrapper_);
|
browserEvents.unbind(Bubble.onMouseUpWrapper);
|
||||||
Bubble.onMouseUpWrapper_ = null;
|
Bubble.onMouseUpWrapper = null;
|
||||||
}
|
}
|
||||||
if (Bubble.onMouseMoveWrapper_) {
|
if (Bubble.onMouseMoveWrapper) {
|
||||||
browserEvents.unbind(Bubble.onMouseMoveWrapper_);
|
browserEvents.unbind(Bubble.onMouseMoveWrapper);
|
||||||
Bubble.onMouseMoveWrapper_ = null;
|
Bubble.onMouseMoveWrapper = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,9 +851,9 @@ export class Bubble implements IBubble {
|
|||||||
*
|
*
|
||||||
* @param _e Mouse up event.
|
* @param _e Mouse up event.
|
||||||
*/
|
*/
|
||||||
private static bubbleMouseUp_(_e: MouseEvent) {
|
private static bubbleMouseUp(_e: MouseEvent) {
|
||||||
Touch.clearTouchIdentifier();
|
Touch.clearTouchIdentifier();
|
||||||
Bubble.unbindDragEvents_();
|
Bubble.unbindDragEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -147,16 +147,16 @@ function extractObjectFromEvent(
|
|||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case eventUtils.BLOCK_CREATE:
|
case eventUtils.BLOCK_CREATE:
|
||||||
case eventUtils.BLOCK_MOVE:
|
case eventUtils.BLOCK_MOVE:
|
||||||
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId);
|
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!);
|
||||||
if (object) {
|
if (object) {
|
||||||
object = object.getRootBlock();
|
object = object.getRootBlock();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case eventUtils.COMMENT_CREATE:
|
case eventUtils.COMMENT_CREATE:
|
||||||
case eventUtils.COMMENT_MOVE:
|
case eventUtils.COMMENT_MOVE:
|
||||||
object = workspace.getCommentById(
|
object =
|
||||||
(e as CommentCreate | CommentMove).commentId) as
|
workspace.getCommentById((e as CommentCreate | CommentMove).commentId!
|
||||||
WorkspaceCommentSvg |
|
) as WorkspaceCommentSvg |
|
||||||
null;
|
null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export class Comment extends Icon {
|
|||||||
HTMLTextAreaElement;
|
HTMLTextAreaElement;
|
||||||
const textarea = this.textarea_;
|
const textarea = this.textarea_;
|
||||||
textarea.className = 'blocklyCommentTextarea';
|
textarea.className = 'blocklyCommentTextarea';
|
||||||
textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
|
textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR');
|
||||||
textarea.value = this.model_.text ?? '';
|
textarea.value = this.model_.text ?? '';
|
||||||
this.resizeTextarea_();
|
this.resizeTextarea_();
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export class Comment extends Icon {
|
|||||||
function(this: Comment, _e: Event) {
|
function(this: Comment, _e: Event) {
|
||||||
if (this.cachedText_ !== this.model_.text) {
|
if (this.cachedText_ !== this.model_.text) {
|
||||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
||||||
this.block_, 'comment', null, this.cachedText_,
|
this.getBlock(), 'comment', null, this.cachedText_,
|
||||||
this.model_.text));
|
this.model_.text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -233,7 +233,7 @@ export class Comment extends Icon {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
||||||
this.block_, visible, 'comment'));
|
this.getBlock(), visible, 'comment'));
|
||||||
this.model_.pinned = visible;
|
this.model_.pinned = visible;
|
||||||
if (visible) {
|
if (visible) {
|
||||||
this.createBubble_();
|
this.createBubble_();
|
||||||
@@ -244,7 +244,7 @@ export class Comment extends Icon {
|
|||||||
|
|
||||||
/** Show the bubble. Handles deciding if it should be editable or not. */
|
/** Show the bubble. Handles deciding if it should be editable or not. */
|
||||||
private createBubble_() {
|
private createBubble_() {
|
||||||
if (!this.block_.isEditable()) {
|
if (!this.getBlock().isEditable()) {
|
||||||
this.createNonEditableBubble_();
|
this.createNonEditableBubble_();
|
||||||
} else {
|
} else {
|
||||||
this.createEditableBubble_();
|
this.createEditableBubble_();
|
||||||
@@ -253,12 +253,13 @@ export class Comment extends Icon {
|
|||||||
|
|
||||||
/** Show an editable bubble. */
|
/** Show an editable bubble. */
|
||||||
private createEditableBubble_() {
|
private createEditableBubble_() {
|
||||||
|
const block = this.getBlock();
|
||||||
this.bubble_ = new Bubble(
|
this.bubble_ = new Bubble(
|
||||||
this.block_.workspace, this.createEditor_(),
|
block.workspace, this.createEditor_(), block.pathObject.svgPath,
|
||||||
this.block_.pathObject.svgPath, (this.iconXY_ as Coordinate),
|
(this.iconXY_ as Coordinate), this.model_.size.width,
|
||||||
this.model_.size.width, this.model_.size.height);
|
this.model_.size.height);
|
||||||
// Expose this comment's block's ID on its top-level SVG group.
|
// 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.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
|
||||||
this.applyColour();
|
this.applyColour();
|
||||||
}
|
}
|
||||||
@@ -272,7 +273,7 @@ export class Comment extends Icon {
|
|||||||
// TODO (#2917): It would be great if the comment could support line breaks.
|
// TODO (#2917): It would be great if the comment could support line breaks.
|
||||||
this.paragraphElement_ = Bubble.textToDom(this.model_.text ?? '');
|
this.paragraphElement_ = Bubble.textToDom(this.model_.text ?? '');
|
||||||
this.bubble_ = Bubble.createNonEditableBubble(
|
this.bubble_ = Bubble.createNonEditableBubble(
|
||||||
this.paragraphElement_, (this.block_), this.iconXY_ as Coordinate);
|
this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate);
|
||||||
this.applyColour();
|
this.applyColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +372,7 @@ export class Comment extends Icon {
|
|||||||
* should not be called directly. Instead call block.setCommentText(null);
|
* should not be called directly. Instead call block.setCommentText(null);
|
||||||
*/
|
*/
|
||||||
override dispose() {
|
override dispose() {
|
||||||
this.block_.comment = null;
|
this.getBlock().comment = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
|||||||
const parentBlock = this.getSourceBlock();
|
const parentBlock = this.getSourceBlock();
|
||||||
const shadowState = this.getShadowState();
|
const shadowState = this.getShadowState();
|
||||||
const shadowDom = this.getShadowDom();
|
const shadowDom = this.getShadowDom();
|
||||||
if (parentBlock.disposed || !shadowState && !shadowDom) {
|
if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {BlockSvg} from './block_svg.js';
|
|||||||
import * as browserEvents from './browser_events.js';
|
import * as browserEvents from './browser_events.js';
|
||||||
import * as clipboard from './clipboard.js';
|
import * as clipboard from './clipboard.js';
|
||||||
import {config} from './config.js';
|
import {config} from './config.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js';
|
import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js';
|
||||||
import * as eventUtils from './events/utils.js';
|
import * as eventUtils from './events/utils.js';
|
||||||
import {Menu} from './menu.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');
|
throw Error('Attempting to create a context menu when widget div is null');
|
||||||
}
|
}
|
||||||
const menuDom = menu.render(div);
|
const menuDom = menu.render(div);
|
||||||
menuDom.classList.add('blocklyContextMenu');
|
dom.addClass(menuDom, 'blocklyContextMenu');
|
||||||
// Prevent system context menu when right-clicking a Blockly context menu.
|
// Prevent system context menu when right-clicking a Blockly context menu.
|
||||||
browserEvents.conditionalBind(
|
browserEvents.conditionalBind(
|
||||||
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
|
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ function deleteNext_(deleteList: BlockSvg[], eventGroup: string) {
|
|||||||
eventUtils.setGroup(eventGroup);
|
eventUtils.setGroup(eventGroup);
|
||||||
const block = deleteList.shift();
|
const block = deleteList.shift();
|
||||||
if (block) {
|
if (block) {
|
||||||
if (!block.disposed) {
|
if (!block.isDeadOrDying()) {
|
||||||
block.dispose(false, true);
|
block.dispose(false, true);
|
||||||
setTimeout(deleteNext_, DELAY, deleteList, eventGroup);
|
setTimeout(deleteNext_, DELAY, deleteList, eventGroup);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
17
core/css.ts
17
core/css.ts
@@ -12,8 +12,6 @@
|
|||||||
import * as goog from '../closure/goog/goog.js';
|
import * as goog from '../closure/goog/goog.js';
|
||||||
goog.declareModuleId('Blockly.Css');
|
goog.declareModuleId('Blockly.Css');
|
||||||
|
|
||||||
import * as deprecation from './utils/deprecation.js';
|
|
||||||
|
|
||||||
|
|
||||||
/** Has CSS already been injected? */
|
/** Has CSS already been injected? */
|
||||||
let injected = false;
|
let injected = false;
|
||||||
@@ -25,20 +23,11 @@ let injected = false;
|
|||||||
* @param cssContent Multiline CSS string or an array of single lines of CSS.
|
* @param cssContent Multiline CSS string or an array of single lines of CSS.
|
||||||
* @alias Blockly.Css.register
|
* @alias Blockly.Css.register
|
||||||
*/
|
*/
|
||||||
export function register(cssContent: string|string[]) {
|
export function register(cssContent: string) {
|
||||||
if (injected) {
|
if (injected) {
|
||||||
throw Error('CSS already injected');
|
throw Error('CSS already injected');
|
||||||
}
|
}
|
||||||
|
content += '\n' + cssContent;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,7 +160,7 @@ let content = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.blocklyDropDownContent {
|
.blocklyDropDownContent {
|
||||||
max-height: 300px; // @todo: spec for maximum height.
|
max-height: 300px; /* @todo: spec for maximum height. */
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.dropDownDiv');
|
|||||||
|
|
||||||
import type {BlockSvg} from './block_svg.js';
|
import type {BlockSvg} from './block_svg.js';
|
||||||
import * as common from './common.js';
|
import * as common from './common.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import type {Field} from './field.js';
|
import type {Field} from './field.js';
|
||||||
import * as math from './utils/math.js';
|
import * as math from './utils/math.js';
|
||||||
import {Rect} from './utils/rect.js';
|
import {Rect} from './utils/rect.js';
|
||||||
@@ -138,10 +139,10 @@ export function createDom() {
|
|||||||
// Handle focusin/out events to add a visual indicator when
|
// Handle focusin/out events to add a visual indicator when
|
||||||
// a child is focused or blurred.
|
// a child is focused or blurred.
|
||||||
div.addEventListener('focusin', function() {
|
div.addEventListener('focusin', function() {
|
||||||
div.classList.add('blocklyFocused');
|
dom.addClass(div, 'blocklyFocused');
|
||||||
});
|
});
|
||||||
div.addEventListener('focusout', function() {
|
div.addEventListener('focusout', function() {
|
||||||
div.classList.remove('blocklyFocused');
|
dom.removeClass(div, 'blocklyFocused');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,10 +312,10 @@ export function show(
|
|||||||
renderedClassName = mainWorkspace.getRenderer().getClassName();
|
renderedClassName = mainWorkspace.getRenderer().getClassName();
|
||||||
themeClassName = mainWorkspace.getTheme().getClassName();
|
themeClassName = mainWorkspace.getTheme().getClassName();
|
||||||
if (renderedClassName) {
|
if (renderedClassName) {
|
||||||
div.classList.add(renderedClassName);
|
dom.addClass(div, renderedClassName);
|
||||||
}
|
}
|
||||||
if (themeClassName) {
|
if (themeClassName) {
|
||||||
div.classList.add(themeClassName);
|
dom.addClass(div, themeClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we change `translate` multiple times in close succession,
|
// When we change `translate` multiple times in close succession,
|
||||||
@@ -595,11 +596,11 @@ export function hideWithoutAnimation() {
|
|||||||
owner = null;
|
owner = null;
|
||||||
|
|
||||||
if (renderedClassName) {
|
if (renderedClassName) {
|
||||||
div.classList.remove(renderedClassName);
|
dom.removeClass(div, renderedClassName);
|
||||||
renderedClassName = '';
|
renderedClassName = '';
|
||||||
}
|
}
|
||||||
if (themeClassName) {
|
if (themeClassName) {
|
||||||
div.classList.remove(themeClassName);
|
dom.removeClass(div, themeClassName);
|
||||||
themeClassName = '';
|
themeClassName = '';
|
||||||
}
|
}
|
||||||
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
||||||
|
|||||||
@@ -13,64 +13,90 @@ import * as goog from '../../closure/goog/goog.js';
|
|||||||
goog.declareModuleId('Blockly.Events');
|
goog.declareModuleId('Blockly.Events');
|
||||||
|
|
||||||
|
|
||||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||||
import {BlockBase} from './events_block_base.js';
|
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||||
import {BlockChange} from './events_block_change.js';
|
import {BlockChange, BlockChangeJson} from './events_block_change.js';
|
||||||
import {BlockCreate} from './events_block_create.js';
|
import {BlockCreate, BlockCreateJson} from './events_block_create.js';
|
||||||
import {BlockDelete} from './events_block_delete.js';
|
import {BlockDelete, BlockDeleteJson} from './events_block_delete.js';
|
||||||
import {BlockDrag} from './events_block_drag.js';
|
import {BlockDrag, BlockDragJson} from './events_block_drag.js';
|
||||||
import {BlockMove} from './events_block_move.js';
|
import {BlockMove, BlockMoveJson} from './events_block_move.js';
|
||||||
import {BubbleOpen} from './events_bubble_open.js';
|
import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js';
|
||||||
import {Click} from './events_click.js';
|
import {Click, ClickJson, ClickTarget} from './events_click.js';
|
||||||
import {CommentBase} from './events_comment_base.js';
|
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||||
import {CommentChange} from './events_comment_change.js';
|
import {CommentChange, CommentChangeJson} from './events_comment_change.js';
|
||||||
import {CommentCreate} from './events_comment_create.js';
|
import {CommentCreate, CommentCreateJson} from './events_comment_create.js';
|
||||||
import {CommentDelete} from './events_comment_delete.js';
|
import {CommentDelete} from './events_comment_delete.js';
|
||||||
import {CommentMove} from './events_comment_move.js';
|
import {CommentMove, CommentMoveJson} from './events_comment_move.js';
|
||||||
import {MarkerMove} from './events_marker_move.js';
|
import {MarkerMove, MarkerMoveJson} from './events_marker_move.js';
|
||||||
import {Selected} from './events_selected.js';
|
import {Selected, SelectedJson} from './events_selected.js';
|
||||||
import {ThemeChange} from './events_theme_change.js';
|
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
|
||||||
import {ToolboxItemSelect} from './events_toolbox_item_select.js';
|
import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js';
|
||||||
import {TrashcanOpen} from './events_trashcan_open.js';
|
import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
|
||||||
import {Ui} from './events_ui.js';
|
import {Ui} from './events_ui.js';
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import {VarBase} from './events_var_base.js';
|
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||||
import {VarCreate} from './events_var_create.js';
|
import {VarCreate, VarCreateJson} from './events_var_create.js';
|
||||||
import {VarDelete} from './events_var_delete.js';
|
import {VarDelete, VarDeleteJson} from './events_var_delete.js';
|
||||||
import {VarRename} from './events_var_rename.js';
|
import {VarRename, VarRenameJson} from './events_var_rename.js';
|
||||||
import {ViewportChange} from './events_viewport.js';
|
import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
import {FinishedLoading} from './workspace_events.js';
|
import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
|
||||||
|
|
||||||
|
|
||||||
// Events.
|
// Events.
|
||||||
export const Abstract = AbstractEvent;
|
export const Abstract = AbstractEvent;
|
||||||
|
export {AbstractEventJson};
|
||||||
export {BubbleOpen};
|
export {BubbleOpen};
|
||||||
|
export {BubbleOpenJson};
|
||||||
|
export {BubbleType};
|
||||||
export {BlockBase};
|
export {BlockBase};
|
||||||
|
export {BlockBaseJson};
|
||||||
export {BlockChange};
|
export {BlockChange};
|
||||||
|
export {BlockChangeJson};
|
||||||
export {BlockCreate};
|
export {BlockCreate};
|
||||||
|
export {BlockCreateJson};
|
||||||
export {BlockDelete};
|
export {BlockDelete};
|
||||||
|
export {BlockDeleteJson};
|
||||||
export {BlockDrag};
|
export {BlockDrag};
|
||||||
|
export {BlockDragJson};
|
||||||
export {BlockMove};
|
export {BlockMove};
|
||||||
|
export {BlockMoveJson};
|
||||||
export {Click};
|
export {Click};
|
||||||
|
export {ClickJson};
|
||||||
|
export {ClickTarget};
|
||||||
export {CommentBase};
|
export {CommentBase};
|
||||||
|
export {CommentBaseJson};
|
||||||
export {CommentChange};
|
export {CommentChange};
|
||||||
|
export {CommentChangeJson};
|
||||||
export {CommentCreate};
|
export {CommentCreate};
|
||||||
|
export {CommentCreateJson};
|
||||||
export {CommentDelete};
|
export {CommentDelete};
|
||||||
export {CommentMove};
|
export {CommentMove};
|
||||||
|
export {CommentMoveJson};
|
||||||
export {FinishedLoading};
|
export {FinishedLoading};
|
||||||
|
export {FinishedLoadingJson};
|
||||||
export {MarkerMove};
|
export {MarkerMove};
|
||||||
|
export {MarkerMoveJson};
|
||||||
export {Selected};
|
export {Selected};
|
||||||
|
export {SelectedJson};
|
||||||
export {ThemeChange};
|
export {ThemeChange};
|
||||||
|
export {ThemeChangeJson};
|
||||||
export {ToolboxItemSelect};
|
export {ToolboxItemSelect};
|
||||||
|
export {ToolboxItemSelectJson};
|
||||||
export {TrashcanOpen};
|
export {TrashcanOpen};
|
||||||
|
export {TrashcanOpenJson};
|
||||||
export {Ui};
|
export {Ui};
|
||||||
export {UiBase};
|
export {UiBase};
|
||||||
export {VarBase};
|
export {VarBase};
|
||||||
|
export {VarBaseJson};
|
||||||
export {VarCreate};
|
export {VarCreate};
|
||||||
|
export {VarCreateJson};
|
||||||
export {VarDelete};
|
export {VarDelete};
|
||||||
|
export {VarDeleteJson};
|
||||||
export {VarRename};
|
export {VarRename};
|
||||||
|
export {VarRenameJson};
|
||||||
export {ViewportChange};
|
export {ViewportChange};
|
||||||
|
export {ViewportChangeJson};
|
||||||
|
|
||||||
// Event types.
|
// Event types.
|
||||||
export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE;
|
export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import * as eventUtils from './utils.js';
|
|||||||
*/
|
*/
|
||||||
export abstract class Abstract {
|
export abstract class Abstract {
|
||||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
/** 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. */
|
/** The workspace identifier for this event. */
|
||||||
workspaceId?: string = undefined;
|
workspaceId?: string = undefined;
|
||||||
@@ -37,7 +37,7 @@ export abstract class Abstract {
|
|||||||
isUiEvent = false;
|
isUiEvent = false;
|
||||||
|
|
||||||
/** Type of this event. */
|
/** Type of this event. */
|
||||||
type?: string = undefined;
|
type = '';
|
||||||
|
|
||||||
/** @alias Blockly.Events.Abstract */
|
/** @alias Blockly.Events.Abstract */
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -57,12 +57,11 @@ export abstract class Abstract {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
toJson(): AnyDuringMigration {
|
toJson(): AbstractEventJson {
|
||||||
const json = {'type': this.type};
|
return {
|
||||||
if (this.group) {
|
'type': this.type,
|
||||||
(json as AnyDuringMigration)['group'] = this.group;
|
'group': this.group,
|
||||||
}
|
};
|
||||||
return json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,9 +69,9 @@ export abstract class Abstract {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
fromJson(json: AnyDuringMigration) {
|
fromJson(json: AbstractEventJson) {
|
||||||
this.isBlank = false;
|
this.isBlank = false;
|
||||||
this.group = json['group'];
|
this.group = json['group'] || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -112,3 +111,8 @@ export abstract class Abstract {
|
|||||||
return workspace;
|
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 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
|
* @alias Blockly.Events.BlockBase
|
||||||
*/
|
*/
|
||||||
export class BlockBase extends AbstractEvent {
|
export class BlockBase extends AbstractEvent {
|
||||||
override isBlank: AnyDuringMigration;
|
override isBlank = true;
|
||||||
blockId: string;
|
blockId?: string;
|
||||||
override workspaceId: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The block this event corresponds to.
|
* @param opt_block The block this event corresponds to.
|
||||||
@@ -33,13 +32,15 @@ export class BlockBase extends AbstractEvent {
|
|||||||
*/
|
*/
|
||||||
constructor(opt_block?: Block) {
|
constructor(opt_block?: Block) {
|
||||||
super();
|
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 */
|
/** 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. */
|
/** 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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): AbstractEventJson {
|
||||||
const json = super.toJson();
|
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;
|
json['blockId'] = this.blockId;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -58,8 +64,12 @@ export class BlockBase extends AbstractEvent {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockBaseJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.blockId = json['blockId'];
|
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 registry from '../registry.js';
|
||||||
import * as Xml from '../xml.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -27,13 +27,11 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.BlockChange
|
* @alias Blockly.Events.BlockChange
|
||||||
*/
|
*/
|
||||||
export class BlockChange extends BlockBase {
|
export class BlockChange extends BlockBase {
|
||||||
override type: string;
|
override type = eventUtils.BLOCK_CHANGE;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
element?: string;
|
||||||
element!: string;
|
name?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
oldValue: unknown;
|
||||||
name!: string|null;
|
newValue: unknown;
|
||||||
oldValue: AnyDuringMigration;
|
|
||||||
newValue: AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The changed block. Undefined for a blank event.
|
* @param opt_block The changed block. Undefined for a blank event.
|
||||||
@@ -44,19 +42,16 @@ export class BlockChange extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
opt_block?: Block, opt_element?: string, opt_name?: string|null,
|
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);
|
super(opt_block);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.BLOCK_CHANGE;
|
|
||||||
|
|
||||||
if (!opt_block) {
|
if (!opt_block) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
this.element = typeof opt_element === 'undefined' ? '' : opt_element;
|
this.element = opt_element;
|
||||||
this.name = typeof opt_name === 'undefined' ? '' : opt_name;
|
this.name = opt_name || undefined;
|
||||||
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
this.oldValue = opt_oldValue;
|
||||||
this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue;
|
this.newValue = opt_newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,12 +59,15 @@ export class BlockChange extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BlockChangeJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as BlockChangeJson;
|
||||||
json['element'] = this.element;
|
if (!this.element) {
|
||||||
if (this.name) {
|
throw new Error(
|
||||||
json['name'] = this.name;
|
'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['oldValue'] = this.oldValue;
|
||||||
json['newValue'] = this.newValue;
|
json['newValue'] = this.newValue;
|
||||||
return json;
|
return json;
|
||||||
@@ -80,7 +78,7 @@ export class BlockChange extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockChangeJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.element = json['element'];
|
this.element = json['element'];
|
||||||
this.name = json['name'];
|
this.name = json['name'];
|
||||||
@@ -104,10 +102,16 @@ export class BlockChange extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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);
|
const block = workspace.getBlockById(this.blockId);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
console.warn('Can\'t change non-existent block: ' + this.blockId);
|
throw new Error(
|
||||||
return;
|
'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.
|
// Assume the block is rendered so that then we can check.
|
||||||
const blockSvg = block as BlockSvg;
|
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);
|
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 blocks from '../serialization/blocks.js';
|
||||||
import * as Xml from '../xml.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -27,20 +27,15 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.BlockCreate
|
* @alias Blockly.Events.BlockCreate
|
||||||
*/
|
*/
|
||||||
export class BlockCreate extends BlockBase {
|
export class BlockCreate extends BlockBase {
|
||||||
override type: string;
|
override type = eventUtils.BLOCK_CREATE;
|
||||||
xml: AnyDuringMigration;
|
xml?: Element|DocumentFragment;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
ids?: string[];
|
||||||
ids!: string[];
|
json?: blocks.State;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
json!: blocks.State;
|
|
||||||
|
|
||||||
/** @param opt_block The created block. Undefined for a blank event. */
|
/** @param opt_block The created block. Undefined for a blank event. */
|
||||||
constructor(opt_block?: Block) {
|
constructor(opt_block?: Block) {
|
||||||
super(opt_block);
|
super(opt_block);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.BLOCK_CREATE;
|
|
||||||
|
|
||||||
if (!opt_block) {
|
if (!opt_block) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -62,8 +57,23 @@ export class BlockCreate extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BlockCreateJson {
|
||||||
const json = super.toJson();
|
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['xml'] = Xml.domToText(this.xml);
|
||||||
json['ids'] = this.ids;
|
json['ids'] = this.ids;
|
||||||
json['json'] = this.json;
|
json['json'] = this.json;
|
||||||
@@ -78,7 +88,7 @@ export class BlockCreate extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockCreateJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.xml = Xml.textToDom(json['xml']);
|
this.xml = Xml.textToDom(json['xml']);
|
||||||
this.ids = json['ids'];
|
this.ids = json['ids'];
|
||||||
@@ -95,6 +105,16 @@ export class BlockCreate extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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) {
|
if (forward) {
|
||||||
blocks.append(this.json, workspace);
|
blocks.append(this.json, workspace);
|
||||||
} else {
|
} 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);
|
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 blocks from '../serialization/blocks.js';
|
||||||
import * as Xml from '../xml.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -27,22 +27,16 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.BlockDelete
|
* @alias Blockly.Events.BlockDelete
|
||||||
*/
|
*/
|
||||||
export class BlockDelete extends BlockBase {
|
export class BlockDelete extends BlockBase {
|
||||||
override type: string;
|
oldXml?: Element|DocumentFragment;
|
||||||
oldXml: AnyDuringMigration;
|
ids?: string[];
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
wasShadow?: boolean;
|
||||||
ids!: string[];
|
oldJson?: blocks.State;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
override type = eventUtils.BLOCK_DELETE;
|
||||||
wasShadow!: boolean;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
oldJson!: blocks.State;
|
|
||||||
|
|
||||||
/** @param opt_block The deleted block. Undefined for a blank event. */
|
/** @param opt_block The deleted block. Undefined for a blank event. */
|
||||||
constructor(opt_block?: Block) {
|
constructor(opt_block?: Block) {
|
||||||
super(opt_block);
|
super(opt_block);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.BLOCK_DELETE;
|
|
||||||
|
|
||||||
if (!opt_block) {
|
if (!opt_block) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -71,8 +65,28 @@ export class BlockDelete extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BlockDeleteJson {
|
||||||
const json = super.toJson();
|
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['oldXml'] = Xml.domToText(this.oldXml);
|
||||||
json['ids'] = this.ids;
|
json['ids'] = this.ids;
|
||||||
json['wasShadow'] = this.wasShadow;
|
json['wasShadow'] = this.wasShadow;
|
||||||
@@ -88,13 +102,13 @@ export class BlockDelete extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockDeleteJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.oldXml = Xml.textToDom(json['oldXml']);
|
this.oldXml = Xml.textToDom(json['oldXml']);
|
||||||
this.ids = json['ids'];
|
this.ids = json['ids'];
|
||||||
this.wasShadow =
|
this.wasShadow =
|
||||||
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
||||||
this.oldJson = json['oldJson'] as blocks.State;
|
this.oldJson = json['oldJson'];
|
||||||
if (json['recordUndo'] !== undefined) {
|
if (json['recordUndo'] !== undefined) {
|
||||||
this.recordUndo = json['recordUndo'];
|
this.recordUndo = json['recordUndo'];
|
||||||
}
|
}
|
||||||
@@ -107,6 +121,16 @@ export class BlockDelete extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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) {
|
if (forward) {
|
||||||
for (let i = 0; i < this.ids.length; i++) {
|
for (let i = 0; i < this.ids.length; i++) {
|
||||||
const id = this.ids[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);
|
registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.BlockDrag');
|
|||||||
|
|
||||||
import type {Block} from '../block.js';
|
import type {Block} from '../block.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.BlockDrag
|
* @alias Blockly.Events.BlockDrag
|
||||||
*/
|
*/
|
||||||
export class BlockDrag extends UiBase {
|
export class BlockDrag extends UiBase {
|
||||||
blockId: AnyDuringMigration;
|
blockId?: string;
|
||||||
isStart?: boolean;
|
isStart?: boolean;
|
||||||
blocks?: Block[];
|
blocks?: Block[];
|
||||||
override type: string;
|
override type = eventUtils.BLOCK_DRAG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The top block in the stack that is being dragged.
|
* @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[]) {
|
constructor(opt_block?: Block, opt_isStart?: boolean, opt_blocks?: Block[]) {
|
||||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||||
super(workspaceId);
|
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. */
|
/** Whether this is the start of a block drag. */
|
||||||
this.isStart = opt_isStart;
|
this.isStart = opt_isStart;
|
||||||
|
|
||||||
/** The blocks affected by this drag event. */
|
/** The blocks affected by this drag event. */
|
||||||
this.blocks = opt_blocks;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BlockDragJson {
|
||||||
const json = super.toJson();
|
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['isStart'] = this.isStart;
|
||||||
json['blockId'] = this.blockId;
|
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;
|
json['blocks'] = this.blocks;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -71,7 +82,7 @@ export class BlockDrag extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockDragJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.isStart = json['isStart'];
|
this.isStart = json['isStart'];
|
||||||
this.blockId = json['blockId'];
|
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);
|
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 * as registry from '../registry.js';
|
||||||
import {Coordinate} from '../utils/coordinate.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
interface BlockLocation {
|
interface BlockLocation {
|
||||||
parentId: string;
|
parentId?: string;
|
||||||
inputName: string;
|
inputName?: string;
|
||||||
coordinate: Coordinate|null;
|
coordinate?: Coordinate;
|
||||||
} // eslint-disable-line no-unused-vars
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for a block move event. Created before the move.
|
* Class for a block move event. Created before the move.
|
||||||
@@ -33,25 +33,19 @@ interface BlockLocation {
|
|||||||
* @alias Blockly.Events.BlockMove
|
* @alias Blockly.Events.BlockMove
|
||||||
*/
|
*/
|
||||||
export class BlockMove extends BlockBase {
|
export class BlockMove extends BlockBase {
|
||||||
override type: string;
|
override type = eventUtils.BLOCK_MOVE;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
oldParentId?: string;
|
||||||
oldParentId!: string;
|
oldInputName?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
oldCoordinate?: Coordinate;
|
||||||
oldInputName!: string;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
oldCoordinate!: Coordinate|null;
|
|
||||||
|
|
||||||
newParentId: string|null = null;
|
newParentId?: string;
|
||||||
newInputName: string|null = null;
|
newInputName?: string;
|
||||||
newCoordinate: Coordinate|null = null;
|
newCoordinate?: Coordinate;
|
||||||
|
|
||||||
/** @param opt_block The moved block. Undefined for a blank event. */
|
/** @param opt_block The moved block. Undefined for a blank event. */
|
||||||
constructor(opt_block?: Block) {
|
constructor(opt_block?: Block) {
|
||||||
super(opt_block);
|
super(opt_block);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.BLOCK_MOVE;
|
|
||||||
|
|
||||||
if (!opt_block) {
|
if (!opt_block) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -72,17 +66,13 @@ export class BlockMove extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BlockMoveJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as BlockMoveJson;
|
||||||
if (this.newParentId) {
|
json['newParentId'] = this.newParentId;
|
||||||
json['newParentId'] = this.newParentId;
|
json['newInputName'] = this.newInputName;
|
||||||
}
|
|
||||||
if (this.newInputName) {
|
|
||||||
json['newInputName'] = this.newInputName;
|
|
||||||
}
|
|
||||||
if (this.newCoordinate) {
|
if (this.newCoordinate) {
|
||||||
json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
|
json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` +
|
||||||
Math.round(this.newCoordinate.y);
|
`${Math.round(this.newCoordinate.y)}`;
|
||||||
}
|
}
|
||||||
if (!this.recordUndo) {
|
if (!this.recordUndo) {
|
||||||
json['recordUndo'] = this.recordUndo;
|
json['recordUndo'] = this.recordUndo;
|
||||||
@@ -95,7 +85,7 @@ export class BlockMove extends BlockBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BlockMoveJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.newParentId = json['newParentId'];
|
this.newParentId = json['newParentId'];
|
||||||
this.newInputName = json['newInputName'];
|
this.newInputName = json['newInputName'];
|
||||||
@@ -124,19 +114,27 @@ export class BlockMove extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
private currentLocation_(): BlockLocation {
|
private currentLocation_(): BlockLocation {
|
||||||
const workspace = this.getEventWorkspace_();
|
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);
|
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 location = {} as BlockLocation;
|
||||||
const parent = block!.getParent();
|
const parent = block.getParent();
|
||||||
if (parent) {
|
if (parent) {
|
||||||
location.parentId = parent.id;
|
location.parentId = parent.id;
|
||||||
// AnyDuringMigration because: Argument of type 'Block | null' is not
|
const input = parent.getInputWithBlock(block);
|
||||||
// assignable to parameter of type 'Block'.
|
|
||||||
const input = parent.getInputWithBlock(block as AnyDuringMigration);
|
|
||||||
if (input) {
|
if (input) {
|
||||||
location.inputName = input.name;
|
location.inputName = input.name;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
location.coordinate = block!.getRelativeToSurfaceXY();
|
location.coordinate = block.getRelativeToSurfaceXY();
|
||||||
}
|
}
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
@@ -159,6 +157,11 @@ export class BlockMove extends BlockBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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);
|
const block = workspace.getBlockById(this.blockId);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
console.warn('Can\'t move non-existent block: ' + this.blockId);
|
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);
|
registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove);
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
import * as goog from '../../closure/goog/goog.js';
|
import * as goog from '../../closure/goog/goog.js';
|
||||||
goog.declareModuleId('Blockly.Events.BubbleOpen');
|
goog.declareModuleId('Blockly.Events.BubbleOpen');
|
||||||
|
|
||||||
|
import type {AbstractEventJson} from './events_abstract.js';
|
||||||
import type {BlockSvg} from '../block_svg.js';
|
import type {BlockSvg} from '../block_svg.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.BubbleOpen
|
* @alias Blockly.Events.BubbleOpen
|
||||||
*/
|
*/
|
||||||
export class BubbleOpen extends UiBase {
|
export class BubbleOpen extends UiBase {
|
||||||
blockId: string|null;
|
blockId?: string;
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
bubbleType?: string;
|
bubbleType?: BubbleType;
|
||||||
override type: string;
|
override type = eventUtils.BUBBLE_OPEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The associated block. Undefined for a blank event.
|
* @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.
|
* 'warning'. Undefined for a blank event.
|
||||||
*/
|
*/
|
||||||
constructor(
|
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;
|
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||||
super(workspaceId);
|
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). */
|
/** Whether the bubble is opening (false if closing). */
|
||||||
this.isOpen = opt_isOpen;
|
this.isOpen = opt_isOpen;
|
||||||
|
|
||||||
/** The type of bubble. One of 'mutator', 'comment', or 'warning'. */
|
/** The type of bubble. One of 'mutator', 'comment', or 'warning'. */
|
||||||
this.bubbleType = opt_bubbleType;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): BubbleOpenJson {
|
||||||
const json = super.toJson();
|
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['isOpen'] = this.isOpen;
|
||||||
json['bubbleType'] = this.bubbleType;
|
json['bubbleType'] = this.bubbleType;
|
||||||
json['blockId'] = this.blockId;
|
json['blockId'] = this.blockId || '';
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +80,7 @@ export class BubbleOpen extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: BubbleOpenJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.isOpen = json['isOpen'];
|
this.isOpen = json['isOpen'];
|
||||||
this.bubbleType = json['bubbleType'];
|
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);
|
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 type {Block} from '../block.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
@@ -25,9 +26,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.Click
|
* @alias Blockly.Events.Click
|
||||||
*/
|
*/
|
||||||
export class Click extends UiBase {
|
export class Click extends UiBase {
|
||||||
blockId: AnyDuringMigration;
|
blockId?: string;
|
||||||
targetType?: string;
|
targetType?: ClickTarget;
|
||||||
override type: string;
|
override type = eventUtils.CLICK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The affected block. Null for click events that do not have
|
* @param opt_block The affected block. Null for click events that do not have
|
||||||
@@ -40,19 +41,17 @@ export class Click extends UiBase {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
opt_block?: Block|null, opt_workspaceId?: string|null,
|
opt_block?: Block|null, opt_workspaceId?: string|null,
|
||||||
opt_targetType?: string) {
|
opt_targetType?: ClickTarget) {
|
||||||
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
|
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
|
||||||
if (workspaceId === null) {
|
if (workspaceId === null) {
|
||||||
workspaceId = undefined;
|
workspaceId = undefined;
|
||||||
}
|
}
|
||||||
super(workspaceId);
|
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. */
|
/** The type of element targeted by this click event. */
|
||||||
this.targetType = opt_targetType;
|
this.targetType = opt_targetType;
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.CLICK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,12 +59,15 @@ export class Click extends UiBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): ClickJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as ClickJson;
|
||||||
json['targetType'] = this.targetType;
|
if (!this.targetType) {
|
||||||
if (this.blockId) {
|
throw new Error(
|
||||||
json['blockId'] = this.blockId;
|
'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;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +76,22 @@ export class Click extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: ClickJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.targetType = json['targetType'];
|
this.targetType = json['targetType'];
|
||||||
this.blockId = json['blockId'];
|
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);
|
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 type {WorkspaceComment} from '../workspace_comment.js';
|
||||||
import * as Xml from '../xml.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 {CommentCreate} from './events_comment_create.js';
|
||||||
import type {CommentDelete} from './events_comment_delete.js';
|
import type {CommentDelete} from './events_comment_delete.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
@@ -28,9 +28,8 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.CommentBase
|
* @alias Blockly.Events.CommentBase
|
||||||
*/
|
*/
|
||||||
export class CommentBase extends AbstractEvent {
|
export class CommentBase extends AbstractEvent {
|
||||||
override isBlank: boolean;
|
override isBlank = true;
|
||||||
commentId: string;
|
commentId?: string;
|
||||||
override workspaceId: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_comment The comment this event corresponds to. Undefined for a
|
* @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) {
|
constructor(opt_comment?: WorkspaceComment) {
|
||||||
super();
|
super();
|
||||||
/** Whether or not an event is blank. */
|
/** 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. */
|
/** 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. */
|
/** 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
|
* 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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): CommentBaseJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as CommentBaseJson;
|
||||||
if (this.commentId) {
|
if (!this.commentId) {
|
||||||
json['commentId'] = 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;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +80,7 @@ export class CommentBase extends AbstractEvent {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: CommentBaseJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.commentId = json['commentId'];
|
this.commentId = json['commentId'];
|
||||||
}
|
}
|
||||||
@@ -92,9 +96,17 @@ export class CommentBase extends AbstractEvent {
|
|||||||
const workspace = event.getEventWorkspace_();
|
const workspace = event.getEventWorkspace_();
|
||||||
if (create) {
|
if (create) {
|
||||||
const xmlElement = utilsXml.createElement('xml');
|
const xmlElement = utilsXml.createElement('xml');
|
||||||
|
if (!event.xml) {
|
||||||
|
throw new Error('Ecountered a comment event without proper xml');
|
||||||
|
}
|
||||||
xmlElement.appendChild(event.xml);
|
xmlElement.appendChild(event.xml);
|
||||||
Xml.domToWorkspace(xmlElement, workspace);
|
Xml.domToWorkspace(xmlElement, workspace);
|
||||||
} else {
|
} 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);
|
const comment = workspace.getCommentById(event.commentId);
|
||||||
if (comment) {
|
if (comment) {
|
||||||
comment.dispose();
|
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 * as registry from '../registry.js';
|
||||||
import type {WorkspaceComment} from '../workspace_comment.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.CommentChange
|
* @alias Blockly.Events.CommentChange
|
||||||
*/
|
*/
|
||||||
export class CommentChange extends CommentBase {
|
export class CommentChange extends CommentBase {
|
||||||
override type: string;
|
override type = eventUtils.COMMENT_CHANGE;
|
||||||
|
oldContents_?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
newContents_?: string;
|
||||||
oldContents_!: string;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
newContents_!: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_comment The comment that is being changed. Undefined for a
|
* @param opt_comment The comment that is being changed. Undefined for a
|
||||||
@@ -43,9 +40,6 @@ export class CommentChange extends CommentBase {
|
|||||||
opt_newContents?: string) {
|
opt_newContents?: string) {
|
||||||
super(opt_comment);
|
super(opt_comment);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.COMMENT_CHANGE;
|
|
||||||
|
|
||||||
if (!opt_comment) {
|
if (!opt_comment) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -61,8 +55,18 @@ export class CommentChange extends CommentBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): CommentChangeJson {
|
||||||
const json = super.toJson();
|
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['oldContents'] = this.oldContents_;
|
||||||
json['newContents'] = this.newContents_;
|
json['newContents'] = this.newContents_;
|
||||||
return json;
|
return json;
|
||||||
@@ -73,7 +77,7 @@ export class CommentChange extends CommentBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: CommentChangeJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.oldContents_ = json['oldContents'];
|
this.oldContents_ = json['oldContents'];
|
||||||
this.newContents_ = json['newContents'];
|
this.newContents_ = json['newContents'];
|
||||||
@@ -95,16 +99,35 @@ export class CommentChange extends CommentBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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);
|
const comment = workspace.getCommentById(this.commentId);
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const contents = forward ? this.newContents_ : this.oldContents_;
|
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);
|
comment.setContent(contents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommentChangeJson extends CommentBaseJson {
|
||||||
|
oldContents: string;
|
||||||
|
newContents: string;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(
|
registry.register(
|
||||||
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
|
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 type {WorkspaceComment} from '../workspace_comment.js';
|
||||||
import * as Xml from '../xml.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -26,9 +26,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.CommentCreate
|
* @alias Blockly.Events.CommentCreate
|
||||||
*/
|
*/
|
||||||
export class CommentCreate extends CommentBase {
|
export class CommentCreate extends CommentBase {
|
||||||
override type: string;
|
override type = eventUtils.COMMENT_CREATE;
|
||||||
|
|
||||||
xml: AnyDuringMigration;
|
xml?: Element|DocumentFragment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_comment The created comment.
|
* @param opt_comment The created comment.
|
||||||
@@ -37,9 +37,6 @@ export class CommentCreate extends CommentBase {
|
|||||||
constructor(opt_comment?: WorkspaceComment) {
|
constructor(opt_comment?: WorkspaceComment) {
|
||||||
super(opt_comment);
|
super(opt_comment);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.COMMENT_CREATE;
|
|
||||||
|
|
||||||
if (!opt_comment) {
|
if (!opt_comment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,8 +50,13 @@ export class CommentCreate extends CommentBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): CommentCreateJson {
|
||||||
const json = super.toJson();
|
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);
|
json['xml'] = Xml.domToText(this.xml);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -64,7 +66,7 @@ export class CommentCreate extends CommentBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: CommentCreateJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.xml = Xml.textToDom(json['xml']);
|
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.register(
|
||||||
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
|
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.CommentDelete
|
* @alias Blockly.Events.CommentDelete
|
||||||
*/
|
*/
|
||||||
export class CommentDelete extends CommentBase {
|
export class CommentDelete extends CommentBase {
|
||||||
override type: string;
|
override type = eventUtils.COMMENT_DELETE;
|
||||||
xml: AnyDuringMigration;
|
xml?: Element;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_comment The deleted comment.
|
* @param opt_comment The deleted comment.
|
||||||
@@ -35,34 +35,12 @@ export class CommentDelete extends CommentBase {
|
|||||||
constructor(opt_comment?: WorkspaceComment) {
|
constructor(opt_comment?: WorkspaceComment) {
|
||||||
super(opt_comment);
|
super(opt_comment);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.COMMENT_DELETE;
|
|
||||||
|
|
||||||
if (!opt_comment) {
|
if (!opt_comment) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
|
|
||||||
this.xml = opt_comment.toXmlWithXY();
|
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.
|
* Run a creation event.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import * as registry from '../registry.js';
|
|||||||
import {Coordinate} from '../utils/coordinate.js';
|
import {Coordinate} from '../utils/coordinate.js';
|
||||||
import type {WorkspaceComment} from '../workspace_comment.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -26,17 +26,11 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.CommentMove
|
* @alias Blockly.Events.CommentMove
|
||||||
*/
|
*/
|
||||||
export class CommentMove extends CommentBase {
|
export class CommentMove extends CommentBase {
|
||||||
override type: string;
|
override type = eventUtils.COMMENT_MOVE;
|
||||||
|
comment_?: WorkspaceComment;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
oldCoordinate_?: Coordinate;
|
||||||
comment_!: WorkspaceComment;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
oldCoordinate_!: Coordinate;
|
|
||||||
|
|
||||||
/** The location after the move, in workspace coordinates. */
|
/** The location after the move, in workspace coordinates. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
newCoordinate_?: Coordinate;
|
||||||
// 'Coordinate'.
|
|
||||||
newCoordinate_: Coordinate = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_comment The comment that is being moved. Undefined for a blank
|
* @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) {
|
constructor(opt_comment?: WorkspaceComment) {
|
||||||
super(opt_comment);
|
super(opt_comment);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.COMMENT_MOVE;
|
|
||||||
|
|
||||||
if (!opt_comment) {
|
if (!opt_comment) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The comment that is being moved. Will be cleared after recording the new
|
* The comment that is being moved.
|
||||||
* location.
|
|
||||||
*/
|
*/
|
||||||
this.comment_ = opt_comment;
|
this.comment_ = opt_comment;
|
||||||
|
|
||||||
@@ -67,15 +57,17 @@ export class CommentMove extends CommentBase {
|
|||||||
* called once.
|
* called once.
|
||||||
*/
|
*/
|
||||||
recordNew() {
|
recordNew() {
|
||||||
if (!this.comment_) {
|
if (this.newCoordinate_) {
|
||||||
throw Error(
|
throw Error(
|
||||||
'Tried to record the new position of a comment on the ' +
|
'Tried to record the new position of a comment on the ' +
|
||||||
'same event twice.');
|
'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();
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): CommentMoveJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as CommentMoveJson;
|
||||||
if (this.oldCoordinate_) {
|
if (!this.oldCoordinate_) {
|
||||||
json['oldCoordinate'] = Math.round(this.oldCoordinate_.x) + ',' +
|
throw new Error(
|
||||||
Math.round(this.oldCoordinate_.y);
|
'The old comment position is undefined. Either pass a comment to ' +
|
||||||
|
'the constructor, or call fromJson');
|
||||||
}
|
}
|
||||||
if (this.newCoordinate_) {
|
if (!this.newCoordinate_) {
|
||||||
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
|
throw new Error(
|
||||||
Math.round(this.newCoordinate_.y);
|
'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;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,17 +110,12 @@ export class CommentMove extends CommentBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: CommentMoveJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
|
let xy = json['oldCoordinate'].split(',');
|
||||||
if (json['oldCoordinate']) {
|
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
||||||
const xy = json['oldCoordinate'].split(',');
|
xy = json['newCoordinate'].split(',');
|
||||||
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
|
this.newCoordinate_ = 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]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,6 +134,11 @@ export class CommentMove extends CommentBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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);
|
const comment = workspace.getCommentById(this.commentId);
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
console.warn('Can\'t move non-existent comment: ' + this.commentId);
|
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_;
|
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.
|
// TODO: Check if the comment is being dragged, and give up if so.
|
||||||
const current = comment.getXY();
|
const current = comment.getXY();
|
||||||
comment.moveBy(target.x - current.x, target.y - current.y);
|
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);
|
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 {ASTNode} from '../keyboard_nav/ast_node.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
import type {Workspace} from '../workspace.js';
|
import type {Workspace} from '../workspace.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
@@ -27,11 +28,11 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.MarkerMove
|
* @alias Blockly.Events.MarkerMove
|
||||||
*/
|
*/
|
||||||
export class MarkerMove extends UiBase {
|
export class MarkerMove extends UiBase {
|
||||||
blockId: string|null;
|
blockId?: string;
|
||||||
oldNode?: ASTNode|null;
|
oldNode?: ASTNode;
|
||||||
newNode?: ASTNode;
|
newNode?: ASTNode;
|
||||||
isCursor?: boolean;
|
isCursor?: boolean;
|
||||||
override type: string;
|
override type = eventUtils.MARKER_MOVE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The affected block. Null if current node is of type
|
* @param opt_block The affected block. Null if current node is of type
|
||||||
@@ -52,20 +53,17 @@ export class MarkerMove extends UiBase {
|
|||||||
}
|
}
|
||||||
super(workspaceId);
|
super(workspaceId);
|
||||||
|
|
||||||
/** The workspace identifier for this event. */
|
/** The block identifier for this event. */
|
||||||
this.blockId = opt_block ? opt_block.id : null;
|
this.blockId = opt_block?.id;
|
||||||
|
|
||||||
/** The old node the marker used to be on. */
|
/** 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. */
|
/** The new node the marker is now on. */
|
||||||
this.newNode = opt_newNode;
|
this.newNode = opt_newNode;
|
||||||
|
|
||||||
/** Whether this is a cursor event. */
|
/** Whether this is a cursor event. */
|
||||||
this.isCursor = isCursor;
|
this.isCursor = isCursor;
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.MARKER_MOVE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,8 +71,18 @@ export class MarkerMove extends UiBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): MarkerMoveJson {
|
||||||
const json = super.toJson();
|
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['isCursor'] = this.isCursor;
|
||||||
json['blockId'] = this.blockId;
|
json['blockId'] = this.blockId;
|
||||||
json['oldNode'] = this.oldNode;
|
json['oldNode'] = this.oldNode;
|
||||||
@@ -87,7 +95,7 @@ export class MarkerMove extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: MarkerMoveJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.isCursor = json['isCursor'];
|
this.isCursor = json['isCursor'];
|
||||||
this.blockId = json['blockId'];
|
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);
|
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');
|
goog.declareModuleId('Blockly.Events.Selected');
|
||||||
|
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
@@ -24,9 +25,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.Selected
|
* @alias Blockly.Events.Selected
|
||||||
*/
|
*/
|
||||||
export class Selected extends UiBase {
|
export class Selected extends UiBase {
|
||||||
oldElementId?: string|null;
|
oldElementId?: string;
|
||||||
newElementId?: string|null;
|
newElementId?: string;
|
||||||
override type: string;
|
override type = eventUtils.SELECTED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_oldElementId The ID of the previously selected element. Null if
|
* @param opt_oldElementId The ID of the previously selected element. Null if
|
||||||
@@ -41,14 +42,11 @@ export class Selected extends UiBase {
|
|||||||
opt_workspaceId?: string) {
|
opt_workspaceId?: string) {
|
||||||
super(opt_workspaceId);
|
super(opt_workspaceId);
|
||||||
|
|
||||||
/** The ID of the last selected element. */
|
/** The id of the last selected element. */
|
||||||
this.oldElementId = opt_oldElementId;
|
this.oldElementId = opt_oldElementId ?? undefined;
|
||||||
|
|
||||||
/** The ID of the selected element. */
|
/** The id of the selected element. */
|
||||||
this.newElementId = opt_newElementId;
|
this.newElementId = opt_newElementId ?? undefined;
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.SELECTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,8 +54,8 @@ export class Selected extends UiBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): SelectedJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as SelectedJson;
|
||||||
json['oldElementId'] = this.oldElementId;
|
json['oldElementId'] = this.oldElementId;
|
||||||
json['newElementId'] = this.newElementId;
|
json['newElementId'] = this.newElementId;
|
||||||
return json;
|
return json;
|
||||||
@@ -68,11 +66,16 @@ export class Selected extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: SelectedJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.oldElementId = json['oldElementId'];
|
this.oldElementId = json['oldElementId'];
|
||||||
this.newElementId = json['newElementId'];
|
this.newElementId = json['newElementId'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SelectedJson extends AbstractEventJson {
|
||||||
|
oldElementId?: string;
|
||||||
|
newElementId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(registry.Type.EVENT, eventUtils.SELECTED, Selected);
|
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');
|
goog.declareModuleId('Blockly.Events.ThemeChange');
|
||||||
|
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ import * as eventUtils from './utils.js';
|
|||||||
*/
|
*/
|
||||||
export class ThemeChange extends UiBase {
|
export class ThemeChange extends UiBase {
|
||||||
themeName?: string;
|
themeName?: string;
|
||||||
override type: string;
|
override type = eventUtils.THEME_CHANGE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_themeName The theme name. Undefined for a blank event.
|
* @param opt_themeName The theme name. Undefined for a blank event.
|
||||||
@@ -37,9 +37,6 @@ export class ThemeChange extends UiBase {
|
|||||||
|
|
||||||
/** The theme name. */
|
/** The theme name. */
|
||||||
this.themeName = opt_themeName;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): ThemeChangeJson {
|
||||||
const json = super.toJson();
|
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;
|
json['themeName'] = this.themeName;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -58,10 +60,14 @@ export class ThemeChange extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: ThemeChangeJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.themeName = json['themeName'];
|
this.themeName = json['themeName'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThemeChangeJson extends AbstractEventJson {
|
||||||
|
themeName: string;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(registry.Type.EVENT, eventUtils.THEME_CHANGE, ThemeChange);
|
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');
|
goog.declareModuleId('Blockly.Events.ToolboxItemSelect');
|
||||||
|
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -24,9 +24,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.ToolboxItemSelect
|
* @alias Blockly.Events.ToolboxItemSelect
|
||||||
*/
|
*/
|
||||||
export class ToolboxItemSelect extends UiBase {
|
export class ToolboxItemSelect extends UiBase {
|
||||||
oldItem?: string|null;
|
oldItem?: string;
|
||||||
newItem?: string|null;
|
newItem?: string;
|
||||||
override type: string;
|
override type = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_oldItem The previously selected toolbox item.
|
* @param opt_oldItem The previously selected toolbox item.
|
||||||
@@ -42,13 +42,10 @@ export class ToolboxItemSelect extends UiBase {
|
|||||||
super(opt_workspaceId);
|
super(opt_workspaceId);
|
||||||
|
|
||||||
/** The previously selected toolbox item. */
|
/** The previously selected toolbox item. */
|
||||||
this.oldItem = opt_oldItem;
|
this.oldItem = opt_oldItem ?? undefined;
|
||||||
|
|
||||||
/** The newly selected toolbox item. */
|
/** The newly selected toolbox item. */
|
||||||
this.newItem = opt_newItem;
|
this.newItem = opt_newItem ?? undefined;
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.TOOLBOX_ITEM_SELECT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,8 +53,8 @@ export class ToolboxItemSelect extends UiBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): ToolboxItemSelectJson {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as ToolboxItemSelectJson;
|
||||||
json['oldItem'] = this.oldItem;
|
json['oldItem'] = this.oldItem;
|
||||||
json['newItem'] = this.newItem;
|
json['newItem'] = this.newItem;
|
||||||
return json;
|
return json;
|
||||||
@@ -68,12 +65,17 @@ export class ToolboxItemSelect extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: ToolboxItemSelectJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.oldItem = json['oldItem'];
|
this.oldItem = json['oldItem'];
|
||||||
this.newItem = json['newItem'];
|
this.newItem = json['newItem'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ToolboxItemSelectJson extends AbstractEventJson {
|
||||||
|
oldItem?: string;
|
||||||
|
newItem?: string;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(
|
registry.register(
|
||||||
registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect);
|
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');
|
goog.declareModuleId('Blockly.Events.TrashcanOpen');
|
||||||
|
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
@@ -25,7 +26,7 @@ import * as eventUtils from './utils.js';
|
|||||||
*/
|
*/
|
||||||
export class TrashcanOpen extends UiBase {
|
export class TrashcanOpen extends UiBase {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
override type: string;
|
override type = eventUtils.TRASHCAN_OPEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_isOpen Whether the trashcan flyout is opening (false if
|
* @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). */
|
/** Whether the trashcan flyout is opening (false if closing). */
|
||||||
this.isOpen = opt_isOpen;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): TrashcanOpenJson {
|
||||||
const json = super.toJson();
|
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;
|
json['isOpen'] = this.isOpen;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -59,10 +62,14 @@ export class TrashcanOpen extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: TrashcanOpenJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.isOpen = json['isOpen'];
|
this.isOpen = json['isOpen'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TrashcanOpenJson extends AbstractEventJson {
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(registry.Type.EVENT, eventUtils.TRASHCAN_OPEN, TrashcanOpen);
|
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 type {Block} from '../block.js';
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ export class Ui extends UiBase {
|
|||||||
element: AnyDuringMigration;
|
element: AnyDuringMigration;
|
||||||
oldValue: AnyDuringMigration;
|
oldValue: AnyDuringMigration;
|
||||||
newValue: AnyDuringMigration;
|
newValue: AnyDuringMigration;
|
||||||
override type: string;
|
override type = eventUtils.UI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_block The affected block. Null for UI events that do not have
|
* @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.element = typeof opt_element === 'undefined' ? '' : opt_element;
|
||||||
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
||||||
this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): AnyDuringMigration {
|
||||||
const json = super.toJson();
|
const json = super.toJson() as AnyDuringMigration;
|
||||||
json['element'] = this.element;
|
json['element'] = this.element;
|
||||||
if (this.newValue !== undefined) {
|
if (this.newValue !== undefined) {
|
||||||
json['newValue'] = this.newValue;
|
json['newValue'] = this.newValue;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {Abstract as AbstractEvent} from './events_abstract.js';
|
|||||||
* @alias Blockly.Events.UiBase
|
* @alias Blockly.Events.UiBase
|
||||||
*/
|
*/
|
||||||
export class UiBase extends AbstractEvent {
|
export class UiBase extends AbstractEvent {
|
||||||
override isBlank: boolean;
|
override isBlank = true;
|
||||||
override workspaceId: string;
|
override workspaceId: string;
|
||||||
|
|
||||||
// UI events do not undo or redo.
|
// UI events do not undo or redo.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.Events.VarBase');
|
|||||||
|
|
||||||
import type {VariableModel} from '../variable_model.js';
|
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
|
* @alias Blockly.Events.VarBase
|
||||||
*/
|
*/
|
||||||
export class VarBase extends AbstractEvent {
|
export class VarBase extends AbstractEvent {
|
||||||
override isBlank: AnyDuringMigration;
|
override isBlank = true;
|
||||||
varId: string;
|
varId?: string;
|
||||||
override workspaceId: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_variable The variable this event corresponds to. Undefined for
|
* @param opt_variable The variable this event corresponds to. Undefined for
|
||||||
@@ -34,12 +33,13 @@ export class VarBase extends AbstractEvent {
|
|||||||
constructor(opt_variable?: VariableModel) {
|
constructor(opt_variable?: VariableModel) {
|
||||||
super();
|
super();
|
||||||
this.isBlank = typeof opt_variable === 'undefined';
|
this.isBlank = typeof opt_variable === 'undefined';
|
||||||
|
if (!opt_variable) return;
|
||||||
|
|
||||||
/** The variable ID for the variable this event pertains to. */
|
/** The variable id for the variable this event pertains to. */
|
||||||
this.varId = this.isBlank ? '' : opt_variable!.getId();
|
this.varId = opt_variable.getId();
|
||||||
|
|
||||||
/** The workspace identifier for this event. */
|
/** 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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): VarBaseJson {
|
||||||
const json = super.toJson();
|
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;
|
json['varId'] = this.varId;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -58,8 +63,12 @@ export class VarBase extends AbstractEvent {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: VarBaseJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.varId = json['varId'];
|
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 * as registry from '../registry.js';
|
||||||
import type {VariableModel} from '../variable_model.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.VarCreate
|
* @alias Blockly.Events.VarCreate
|
||||||
*/
|
*/
|
||||||
export class VarCreate extends VarBase {
|
export class VarCreate extends VarBase {
|
||||||
override type: string;
|
override type = eventUtils.VAR_CREATE;
|
||||||
|
varType?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
varName?: string;
|
||||||
varType!: string;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
varName!: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_variable The created variable. Undefined for a blank event.
|
* @param opt_variable The created variable. Undefined for a blank event.
|
||||||
@@ -38,9 +35,6 @@ export class VarCreate extends VarBase {
|
|||||||
constructor(opt_variable?: VariableModel) {
|
constructor(opt_variable?: VariableModel) {
|
||||||
super(opt_variable);
|
super(opt_variable);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.VAR_CREATE;
|
|
||||||
|
|
||||||
if (!opt_variable) {
|
if (!opt_variable) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -53,8 +47,18 @@ export class VarCreate extends VarBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): VarCreateJson {
|
||||||
const json = super.toJson();
|
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['varType'] = this.varType;
|
||||||
json['varName'] = this.varName;
|
json['varName'] = this.varName;
|
||||||
return json;
|
return json;
|
||||||
@@ -65,7 +69,7 @@ export class VarCreate extends VarBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: VarCreateJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.varType = json['varType'];
|
this.varType = json['varType'];
|
||||||
this.varName = json['varName'];
|
this.varName = json['varName'];
|
||||||
@@ -78,6 +82,16 @@ export class VarCreate extends VarBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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) {
|
if (forward) {
|
||||||
workspace.createVariable(this.varName, this.varType, this.varId);
|
workspace.createVariable(this.varName, this.varType, this.varId);
|
||||||
} else {
|
} 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);
|
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 * as registry from '../registry.js';
|
||||||
import type {VariableModel} from '../variable_model.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.VarDelete
|
* @alias Blockly.Events.VarDelete
|
||||||
*/
|
*/
|
||||||
export class VarDelete extends VarBase {
|
export class VarDelete extends VarBase {
|
||||||
override type: string;
|
override type = eventUtils.VAR_DELETE;
|
||||||
|
varType?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
varName?: string;
|
||||||
varType!: string;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
varName!: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_variable The deleted variable. Undefined for a blank event.
|
* @param opt_variable The deleted variable. Undefined for a blank event.
|
||||||
@@ -38,9 +35,6 @@ export class VarDelete extends VarBase {
|
|||||||
constructor(opt_variable?: VariableModel) {
|
constructor(opt_variable?: VariableModel) {
|
||||||
super(opt_variable);
|
super(opt_variable);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.VAR_DELETE;
|
|
||||||
|
|
||||||
if (!opt_variable) {
|
if (!opt_variable) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -53,8 +47,18 @@ export class VarDelete extends VarBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): VarDeleteJson {
|
||||||
const json = super.toJson();
|
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['varType'] = this.varType;
|
||||||
json['varName'] = this.varName;
|
json['varName'] = this.varName;
|
||||||
return json;
|
return json;
|
||||||
@@ -65,7 +69,7 @@ export class VarDelete extends VarBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: VarDeleteJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.varType = json['varType'];
|
this.varType = json['varType'];
|
||||||
this.varName = json['varName'];
|
this.varName = json['varName'];
|
||||||
@@ -78,6 +82,16 @@ export class VarDelete extends VarBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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) {
|
if (forward) {
|
||||||
workspace.deleteVariableById(this.varId);
|
workspace.deleteVariableById(this.varId);
|
||||||
} else {
|
} 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);
|
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 * as registry from '../registry.js';
|
||||||
import type {VariableModel} from '../variable_model.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';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,12 +25,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.VarRename
|
* @alias Blockly.Events.VarRename
|
||||||
*/
|
*/
|
||||||
export class VarRename extends VarBase {
|
export class VarRename extends VarBase {
|
||||||
override type: string;
|
override type = eventUtils.VAR_RENAME;
|
||||||
|
oldName?: string;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
newName?: string;
|
||||||
oldName!: string;
|
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
newName!: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_variable The renamed variable. Undefined for a blank event.
|
* @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) {
|
constructor(opt_variable?: VariableModel, newName?: string) {
|
||||||
super(opt_variable);
|
super(opt_variable);
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.VAR_RENAME;
|
|
||||||
|
|
||||||
if (!opt_variable) {
|
if (!opt_variable) {
|
||||||
return; // Blank event to be populated by fromJson.
|
return; // Blank event to be populated by fromJson.
|
||||||
}
|
}
|
||||||
@@ -54,8 +48,18 @@ export class VarRename extends VarBase {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): VarRenameJson {
|
||||||
const json = super.toJson();
|
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['oldName'] = this.oldName;
|
||||||
json['newName'] = this.newName;
|
json['newName'] = this.newName;
|
||||||
return json;
|
return json;
|
||||||
@@ -66,7 +70,7 @@ export class VarRename extends VarBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: VarRenameJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.oldName = json['oldName'];
|
this.oldName = json['oldName'];
|
||||||
this.newName = json['newName'];
|
this.newName = json['newName'];
|
||||||
@@ -79,6 +83,21 @@ export class VarRename extends VarBase {
|
|||||||
*/
|
*/
|
||||||
override run(forward: boolean) {
|
override run(forward: boolean) {
|
||||||
const workspace = this.getEventWorkspace_();
|
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) {
|
if (forward) {
|
||||||
workspace.renameVariableById(this.varId, this.newName);
|
workspace.renameVariableById(this.varId, this.newName);
|
||||||
} else {
|
} 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);
|
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');
|
goog.declareModuleId('Blockly.Events.ViewportChange');
|
||||||
|
|
||||||
import * as registry from '../registry.js';
|
import * as registry from '../registry.js';
|
||||||
|
import {AbstractEventJson} from './events_abstract.js';
|
||||||
import {UiBase} from './events_ui_base.js';
|
import {UiBase} from './events_ui_base.js';
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export class ViewportChange extends UiBase {
|
|||||||
viewLeft?: number;
|
viewLeft?: number;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
oldScale?: number;
|
oldScale?: number;
|
||||||
override type: string;
|
override type = eventUtils.VIEWPORT_CHANGE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_top Top-edge of the visible portion of the workspace, relative
|
* @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. */
|
/** The old scale of the workspace. */
|
||||||
this.oldScale = opt_oldScale;
|
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.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): ViewportChangeJson {
|
||||||
const json = super.toJson();
|
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['viewTop'] = this.viewTop;
|
||||||
json['viewLeft'] = this.viewLeft;
|
json['viewLeft'] = this.viewLeft;
|
||||||
json['scale'] = this.scale;
|
json['scale'] = this.scale;
|
||||||
@@ -87,7 +104,7 @@ export class ViewportChange extends UiBase {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: ViewportChangeJson) {
|
||||||
super.fromJson(json);
|
super.fromJson(json);
|
||||||
this.viewTop = json['viewTop'];
|
this.viewTop = json['viewTop'];
|
||||||
this.viewLeft = json['viewLeft'];
|
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.register(
|
||||||
registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange);
|
registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange);
|
||||||
|
|||||||
@@ -536,6 +536,9 @@ export function disableOrphans(event: Abstract) {
|
|||||||
}
|
}
|
||||||
const eventWorkspace =
|
const eventWorkspace =
|
||||||
common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg;
|
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);
|
let block = eventWorkspace.getBlockById(blockEvent.blockId);
|
||||||
if (block) {
|
if (block) {
|
||||||
// Changing blocks as part of this event shouldn't be undoable.
|
// 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 * as registry from '../registry.js';
|
||||||
import type {Workspace} from '../workspace.js';
|
import type {Workspace} from '../workspace.js';
|
||||||
|
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||||
import {Abstract as AbstractEvent} from './events_abstract.js';
|
|
||||||
import * as eventUtils from './utils.js';
|
import * as eventUtils from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -28,13 +27,9 @@ import * as eventUtils from './utils.js';
|
|||||||
* @alias Blockly.Events.FinishedLoading
|
* @alias Blockly.Events.FinishedLoading
|
||||||
*/
|
*/
|
||||||
export class FinishedLoading extends AbstractEvent {
|
export class FinishedLoading extends AbstractEvent {
|
||||||
override isBlank: boolean;
|
override isBlank = true;
|
||||||
override workspaceId: string;
|
|
||||||
|
|
||||||
// Workspace events do not undo or redo.
|
|
||||||
override recordUndo = false;
|
override recordUndo = false;
|
||||||
override type: string;
|
override type = eventUtils.FINISHED_LOADING;
|
||||||
override group: AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param opt_workspace The workspace that has finished loading. Undefined
|
* @param opt_workspace The workspace that has finished loading. Undefined
|
||||||
@@ -43,13 +38,12 @@ export class FinishedLoading extends AbstractEvent {
|
|||||||
constructor(opt_workspace?: Workspace) {
|
constructor(opt_workspace?: Workspace) {
|
||||||
super();
|
super();
|
||||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
/** 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. */
|
/** The workspace identifier for this event. */
|
||||||
this.workspaceId = opt_workspace ? opt_workspace.id : '';
|
this.workspaceId = opt_workspace.id;
|
||||||
|
|
||||||
/** Type of this event. */
|
|
||||||
this.type = eventUtils.FINISHED_LOADING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,16 +51,14 @@ export class FinishedLoading extends AbstractEvent {
|
|||||||
*
|
*
|
||||||
* @returns JSON representation.
|
* @returns JSON representation.
|
||||||
*/
|
*/
|
||||||
override toJson(): AnyDuringMigration {
|
override toJson(): FinishedLoadingJson {
|
||||||
const json = {
|
const json = super.toJson() as FinishedLoadingJson;
|
||||||
'type': this.type,
|
if (!this.workspaceId) {
|
||||||
};
|
throw new Error(
|
||||||
if (this.group) {
|
'The workspace ID is undefined. Either pass a workspace to ' +
|
||||||
(json as AnyDuringMigration)['group'] = this.group;
|
'the constructor, or call fromJson');
|
||||||
}
|
|
||||||
if (this.workspaceId) {
|
|
||||||
(json as AnyDuringMigration)['workspaceId'] = this.workspaceId;
|
|
||||||
}
|
}
|
||||||
|
json['workspaceId'] = this.workspaceId;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +67,15 @@ export class FinishedLoading extends AbstractEvent {
|
|||||||
*
|
*
|
||||||
* @param json JSON representation.
|
* @param json JSON representation.
|
||||||
*/
|
*/
|
||||||
override fromJson(json: AnyDuringMigration) {
|
override fromJson(json: FinishedLoadingJson) {
|
||||||
this.isBlank = false;
|
super.fromJson(json);
|
||||||
this.workspaceId = json['workspaceId'];
|
this.workspaceId = json['workspaceId'];
|
||||||
this.group = json['group'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FinishedLoadingJson extends AbstractEventJson {
|
||||||
|
workspaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
registry.register(
|
registry.register(
|
||||||
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
|
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function registerMutator(
|
|||||||
// Sanity checks passed.
|
// Sanity checks passed.
|
||||||
register(name, function(this: Block) {
|
register(name, function(this: Block) {
|
||||||
if (hasMutatorDialog) {
|
if (hasMutatorDialog) {
|
||||||
this.setMutator(new Mutator(this as BlockSvg, opt_blockList || []));
|
this.setMutator(new Mutator(opt_blockList || [], this as BlockSvg));
|
||||||
}
|
}
|
||||||
// Mixin the object.
|
// Mixin the object.
|
||||||
this.mixin(mixinObj);
|
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,
|
export abstract class Field implements IASTNodeLocationSvg,
|
||||||
IASTNodeLocationWithBlock,
|
IASTNodeLocationWithBlock,
|
||||||
IKeyboardAccessible, IRegistrable {
|
IKeyboardAccessible, IRegistrable {
|
||||||
/** The default value for this field. */
|
|
||||||
protected DEFAULT_VALUE: any = null;
|
|
||||||
|
|
||||||
/** Non-breaking space. */
|
/** Non-breaking space. */
|
||||||
static readonly NBSP = '\u00A0';
|
static readonly NBSP = '\u00A0';
|
||||||
|
|
||||||
@@ -75,9 +72,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
protected value_: AnyDuringMigration;
|
protected value_: AnyDuringMigration;
|
||||||
|
|
||||||
/** Validation function called when user edits an editable field. */
|
/** Validation function called when user edits an editable field. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected validator_: Function|null = null;
|
||||||
// 'Function'.
|
|
||||||
protected validator_: Function = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to cache the field's tooltip value if setTooltip is called when the
|
* 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.
|
* Holds the cursors svg element when the cursor is attached to the field.
|
||||||
* This is null if there is no cursor on the field.
|
* This is null if there is no cursor on the field.
|
||||||
*/
|
*/
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
private cursorSvg_: SVGElement|null = null;
|
||||||
// 'SVGElement'.
|
|
||||||
private cursorSvg_: SVGElement = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the markers svg element when the marker is attached to the field.
|
* Holds the markers svg element when the marker is attached to the field.
|
||||||
* This is null if there is no marker on the field.
|
* This is null if there is no marker on the field.
|
||||||
*/
|
*/
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
private markerSvg_: SVGElement|null = null;
|
||||||
// 'SVGElement'.
|
|
||||||
private markerSvg_: SVGElement = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** The rendered field's SVG group element. */
|
/** The rendered field's SVG group element. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected fieldGroup_: SVGGElement|null = null;
|
||||||
// 'SVGGElement'.
|
|
||||||
protected fieldGroup_: SVGGElement = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** The rendered field's SVG border element. */
|
/** The rendered field's SVG border element. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected borderRect_: SVGRectElement|null = null;
|
||||||
// 'SVGRectElement'.
|
|
||||||
protected borderRect_: SVGRectElement = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** The rendered field's SVG text element. */
|
/** The rendered field's SVG text element. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected textElement_: SVGTextElement|null = null;
|
||||||
// 'SVGTextElement'.
|
|
||||||
protected textElement_: SVGTextElement = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** The rendered field's text content element. */
|
/** The rendered field's text content element. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type 'Text'.
|
protected textContent_: Text|null = null;
|
||||||
protected textContent_: Text = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** Mouse down event listener data. */
|
/** Mouse down event listener data. */
|
||||||
private mouseDownWrapper_: browserEvents.Data|null = null;
|
private mouseDownWrapper_: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Constants associated with the source block's renderer. */
|
/** Constants associated with the source block's renderer. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected constants_: ConstantProvider|null = null;
|
||||||
// 'ConstantProvider'.
|
|
||||||
protected constants_: ConstantProvider = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Has this field been disposed of?
|
* Has this field been disposed of?
|
||||||
@@ -140,8 +122,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
maxDisplayLength = 50;
|
maxDisplayLength = 50;
|
||||||
|
|
||||||
/** Block this field is attached to. Starts as null, then set in init. */
|
/** 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 = null;
|
||||||
protected sourceBlock_: Block = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/** Does this block need to be re-rendered? */
|
/** Does this block need to be re-rendered? */
|
||||||
protected isDirty_ = true;
|
protected isDirty_ = true;
|
||||||
@@ -155,9 +136,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
protected enabled_ = true;
|
protected enabled_ = true;
|
||||||
|
|
||||||
/** The element the click handler is bound to. */
|
/** The element the click handler is bound to. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
protected clickTarget_: Element|null = null;
|
||||||
// 'Element'.
|
|
||||||
protected clickTarget_: Element = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The prefix field.
|
* The prefix field.
|
||||||
@@ -208,7 +187,9 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* A generic value possessed by the field.
|
* A generic value possessed by the field.
|
||||||
* Should generally be non-null, only null when the field is created.
|
* 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. */
|
/** The size of the area rendered by the field. */
|
||||||
this.size_ = new Size(0, 0);
|
this.size_ = new Size(0, 0);
|
||||||
@@ -258,7 +239,8 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* @returns The renderer constant provider.
|
* @returns The renderer constant provider.
|
||||||
*/
|
*/
|
||||||
getConstants(): ConstantProvider|null {
|
getConstants(): ConstantProvider|null {
|
||||||
if (!this.constants_ && this.sourceBlock_ && !this.sourceBlock_.disposed &&
|
if (!this.constants_ && this.sourceBlock_ &&
|
||||||
|
!this.sourceBlock_.isDeadOrDying() &&
|
||||||
this.sourceBlock_.workspace.rendered) {
|
this.sourceBlock_.workspace.rendered) {
|
||||||
this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg)
|
this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg)
|
||||||
.getRenderer()
|
.getRenderer()
|
||||||
@@ -271,8 +253,9 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* Get the block this field is attached to.
|
* Get the block this field is attached to.
|
||||||
*
|
*
|
||||||
* @returns The block containing this field.
|
* @returns The block containing this field.
|
||||||
|
* @throws An error if the source block is not defined.
|
||||||
*/
|
*/
|
||||||
getSourceBlock(): Block {
|
getSourceBlock(): Block|null {
|
||||||
return this.sourceBlock_;
|
return this.sourceBlock_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,9 +344,11 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* do custom input handling.
|
* do custom input handling.
|
||||||
*/
|
*/
|
||||||
protected bindEvents_() {
|
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.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.
|
* Used to see if `this` has overridden any relevant hooks.
|
||||||
* @returns The stringified version of the XML state, or null.
|
* @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 &&
|
if (callingClass.prototype.saveState === this.saveState &&
|
||||||
callingClass.prototype.toXml !== this.toXml) {
|
callingClass.prototype.toXml !== this.toXml) {
|
||||||
const elem = utilsXml.createElement('field');
|
const elem = utilsXml.createElement('field');
|
||||||
@@ -454,7 +439,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* @param state The state to apply to the field.
|
* @param state The state to apply to the field.
|
||||||
* @returns Whether the state was applied or not.
|
* @returns Whether the state was applied or not.
|
||||||
*/
|
*/
|
||||||
loadLegacyState(callingClass: AnyDuringMigration, state: AnyDuringMigration):
|
loadLegacyState(callingClass: FieldProto, state: AnyDuringMigration):
|
||||||
boolean {
|
boolean {
|
||||||
if (callingClass.prototype.loadState === this.loadState &&
|
if (callingClass.prototype.loadState === this.loadState &&
|
||||||
callingClass.prototype.fromXml !== this.fromXml) {
|
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. */
|
/** Add or remove the UI indicating if this field is editable or not. */
|
||||||
updateEditable() {
|
updateEditable() {
|
||||||
const group = this.fieldGroup_;
|
const group = this.fieldGroup_;
|
||||||
if (!this.EDITABLE || !group) {
|
const block = this.getSourceBlock();
|
||||||
|
if (!this.EDITABLE || !group || !block) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.enabled_ && this.sourceBlock_.isEditable()) {
|
if (this.enabled_ && block.isEditable()) {
|
||||||
group.classList.add('blocklyEditableText');
|
dom.addClass(group, 'blocklyEditableText');
|
||||||
group.classList.remove('blocklyNonEditableText');
|
dom.removeClass(group, 'blocklyNonEditableText');
|
||||||
group.style.cursor = this.CURSOR;
|
group.style.cursor = this.CURSOR;
|
||||||
} else {
|
} else {
|
||||||
group.classList.add('blocklyNonEditableText');
|
dom.addClass(group, 'blocklyNonEditableText');
|
||||||
group.classList.remove('blocklyEditableText');
|
dom.removeClass(group, 'blocklyEditableText');
|
||||||
group.style.cursor = '';
|
group.style.cursor = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,7 +576,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.visible_ = visible;
|
this.visible_ = visible;
|
||||||
const root = this.getSvgRoot();
|
const root = this.fieldGroup_;
|
||||||
if (root) {
|
if (root) {
|
||||||
root.style.display = visible ? 'block' : 'none';
|
root.style.display = visible ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
@@ -630,10 +616,49 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
*
|
*
|
||||||
* @returns The group element.
|
* @returns The group element.
|
||||||
*/
|
*/
|
||||||
getSvgRoot(): SVGGElement {
|
getSvgRoot(): SVGGElement|null {
|
||||||
return this.fieldGroup_;
|
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
|
* Updates the field to match the colour/style of the block. Should only be
|
||||||
* called by BlockSvg.applyColour().
|
* called by BlockSvg.applyColour().
|
||||||
@@ -726,20 +751,19 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
const constants = this.getConstants();
|
const constants = this.getConstants();
|
||||||
const halfHeight = this.size_.height / 2;
|
const halfHeight = this.size_.height / 2;
|
||||||
|
|
||||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
|
||||||
// to parameter of type 'string'.
|
|
||||||
this.textElement_.setAttribute(
|
this.textElement_.setAttribute(
|
||||||
'x',
|
'x',
|
||||||
(this.sourceBlock_.RTL ? this.size_.width - contentWidth - xOffset :
|
`${
|
||||||
xOffset) as AnyDuringMigration);
|
this.getSourceBlock()?.RTL ?
|
||||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
this.size_.width - contentWidth - xOffset :
|
||||||
// to parameter of type 'string'.
|
xOffset}`);
|
||||||
this.textElement_.setAttribute(
|
this.textElement_.setAttribute(
|
||||||
'y',
|
'y',
|
||||||
(constants!.FIELD_TEXT_BASELINE_CENTER ?
|
`${
|
||||||
halfHeight :
|
constants!.FIELD_TEXT_BASELINE_CENTER ?
|
||||||
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
halfHeight :
|
||||||
constants!.FIELD_TEXT_BASELINE) as AnyDuringMigration);
|
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
||||||
|
constants!.FIELD_TEXT_BASELINE}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Position a field's border rect after a size change. */
|
/** Position a field's border rect after a size change. */
|
||||||
@@ -747,24 +771,12 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
if (!this.borderRect_) {
|
if (!this.borderRect_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
this.borderRect_.setAttribute('width', `${this.size_.width}`);
|
||||||
// to parameter of type 'string'.
|
this.borderRect_.setAttribute('height', `${this.size_.height}`);
|
||||||
this.borderRect_.setAttribute(
|
this.borderRect_.setAttribute(
|
||||||
'width', this.size_.width as AnyDuringMigration);
|
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
|
||||||
// to parameter of type 'string'.
|
|
||||||
this.borderRect_.setAttribute(
|
this.borderRect_.setAttribute(
|
||||||
'height', this.size_.height as AnyDuringMigration);
|
'ry', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -785,10 +797,13 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
} else if (this.visible_ && this.size_.width === 0) {
|
} else if (this.visible_ && this.size_.width === 0) {
|
||||||
// If the field is not visible the width will be 0 as well, one of the
|
// If the field is not visible the width will be 0 as well, one of the
|
||||||
// problems with the old system.
|
// 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_();
|
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_;
|
return this.size_;
|
||||||
}
|
}
|
||||||
@@ -805,12 +820,17 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
let scaledWidth;
|
let scaledWidth;
|
||||||
let scaledHeight;
|
let scaledHeight;
|
||||||
let xy;
|
let xy;
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.borderRect_) {
|
if (!this.borderRect_) {
|
||||||
// Browsers are inconsistent in what they return for a bounding box.
|
// Browsers are inconsistent in what they return for a bounding box.
|
||||||
// - Webkit / Blink: fill-box / object bounding box
|
// - Webkit / Blink: fill-box / object bounding box
|
||||||
// - Gecko: stroke-box
|
// - Gecko: stroke-box
|
||||||
const bBox = (this.sourceBlock_ as BlockSvg).getHeightWidth();
|
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_();
|
xy = this.getAbsoluteXY_();
|
||||||
scaledWidth = (bBox.width + 1) * scale;
|
scaledWidth = (bBox.width + 1) * scale;
|
||||||
scaledHeight = (bBox.height + 1) * scale;
|
scaledHeight = (bBox.height + 1) * scale;
|
||||||
@@ -896,9 +916,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
*/
|
*/
|
||||||
markDirty() {
|
markDirty() {
|
||||||
this.isDirty_ = true;
|
this.isDirty_ = true;
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
this.constants_ = null;
|
||||||
// 'ConstantProvider'.
|
|
||||||
this.constants_ = null as AnyDuringMigration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1050,7 +1068,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
* @param e Mouse down event.
|
* @param e Mouse down event.
|
||||||
*/
|
*/
|
||||||
protected onMouseDown_(e: Event) {
|
protected onMouseDown_(e: Event) {
|
||||||
if (!this.sourceBlock_ || this.sourceBlock_.disposed) {
|
if (!this.sourceBlock_ || this.sourceBlock_.isDeadOrDying()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const gesture = (this.sourceBlock_.workspace as WorkspaceSvg).getGesture(e);
|
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.
|
* @returns Element to bind click handler to.
|
||||||
*/
|
*/
|
||||||
protected getClickTarget_(): Element {
|
protected getClickTarget_(): Element|null {
|
||||||
return this.clickTarget_ || this.getSvgRoot();
|
return this.clickTarget_ || this.getSvgRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1145,7 +1163,10 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
*/
|
*/
|
||||||
getParentInput(): Input {
|
getParentInput(): Input {
|
||||||
let parentInput = null;
|
let parentInput = null;
|
||||||
const block = this.sourceBlock_;
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const inputs = block.inputList;
|
const inputs = block.inputList;
|
||||||
|
|
||||||
for (let idx = 0; idx < block.inputList.length; idx++) {
|
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
|
return parentInput!;
|
||||||
// type 'Input'.
|
|
||||||
return parentInput as AnyDuringMigration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1199,12 +1218,13 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
*/
|
*/
|
||||||
setCursorSvg(cursorSvg: SVGElement) {
|
setCursorSvg(cursorSvg: SVGElement) {
|
||||||
if (!cursorSvg) {
|
if (!cursorSvg) {
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
this.cursorSvg_ = null;
|
||||||
// 'SVGElement'.
|
|
||||||
this.cursorSvg_ = null as AnyDuringMigration;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.fieldGroup_) {
|
||||||
|
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||||
|
}
|
||||||
this.fieldGroup_.appendChild(cursorSvg);
|
this.fieldGroup_.appendChild(cursorSvg);
|
||||||
this.cursorSvg_ = cursorSvg;
|
this.cursorSvg_ = cursorSvg;
|
||||||
}
|
}
|
||||||
@@ -1217,19 +1237,24 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
*/
|
*/
|
||||||
setMarkerSvg(markerSvg: SVGElement) {
|
setMarkerSvg(markerSvg: SVGElement) {
|
||||||
if (!markerSvg) {
|
if (!markerSvg) {
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
this.markerSvg_ = null;
|
||||||
// 'SVGElement'.
|
|
||||||
this.markerSvg_ = null as AnyDuringMigration;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.fieldGroup_) {
|
||||||
|
throw new Error(`The field group is ${this.fieldGroup_}.`);
|
||||||
|
}
|
||||||
this.fieldGroup_.appendChild(markerSvg);
|
this.fieldGroup_.appendChild(markerSvg);
|
||||||
this.markerSvg_ = markerSvg;
|
this.markerSvg_ = markerSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Redraw any attached marker or cursor svgs if needed. */
|
/** Redraw any attached marker or cursor svgs if needed. */
|
||||||
protected updateMarkers_() {
|
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_) {
|
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
|
||||||
workspace.getCursor()!.draw();
|
workspace.getCursor()!.draw();
|
||||||
}
|
}
|
||||||
@@ -1246,3 +1271,23 @@ export abstract class Field implements IASTNodeLocationSvg,
|
|||||||
export interface FieldConfig {
|
export interface FieldConfig {
|
||||||
tooltip?: string;
|
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 browserEvents from './browser_events.js';
|
||||||
import * as Css from './css.js';
|
import * as Css from './css.js';
|
||||||
import * as dropDownDiv from './dropdowndiv.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 * as fieldRegistry from './field_registry.js';
|
||||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||||
import * as dom from './utils/dom.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.
|
* otherwise SVG crops off half the border at the edges.
|
||||||
*/
|
*/
|
||||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||||
private clockwise_: boolean;
|
|
||||||
private offset_: number;
|
/**
|
||||||
private wrap_: number;
|
* Whether the angle should increase as the angle picker is moved clockwise
|
||||||
private round_: number;
|
* (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. */
|
/** 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. */
|
/** 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. */
|
/** 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. */
|
/** The degree symbol for this field. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||||
@@ -122,35 +139,6 @@ export class FieldAngle extends FieldTextInput {
|
|||||||
opt_config?: FieldAngleConfig) {
|
opt_config?: FieldAngleConfig) {
|
||||||
super(Field.SKIP_SETUP);
|
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) {
|
if (opt_value === Field.SKIP_SETUP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -202,7 +190,7 @@ export class FieldAngle extends FieldTextInput {
|
|||||||
// #2380)
|
// #2380)
|
||||||
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
|
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
|
||||||
this.symbol_.appendChild(document.createTextNode('°'));
|
this.symbol_.appendChild(document.createTextNode('°'));
|
||||||
this.textElement_.appendChild(this.symbol_);
|
this.getTextElement().appendChild(this.symbol_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the graph when the field rerenders. */
|
/** Updates the graph when the field rerenders. */
|
||||||
@@ -223,9 +211,7 @@ export class FieldAngle extends FieldTextInput {
|
|||||||
super.showEditor_(opt_e, noFocus);
|
super.showEditor_(opt_e, noFocus);
|
||||||
|
|
||||||
this.dropdownCreate_();
|
this.dropdownCreate_();
|
||||||
// AnyDuringMigration because: Argument of type 'SVGElement | null' is not
|
dropDownDiv.getContentDiv().appendChild(this.editor_!);
|
||||||
// assignable to parameter of type 'Node'.
|
|
||||||
dropDownDiv.getContentDiv().appendChild(this.editor_ as AnyDuringMigration);
|
|
||||||
|
|
||||||
if (this.sourceBlock_ instanceof BlockSvg) {
|
if (this.sourceBlock_ instanceof BlockSvg) {
|
||||||
dropDownDiv.setColour(
|
dropDownDiv.setColour(
|
||||||
@@ -426,26 +412,23 @@ export class FieldAngle extends FieldTextInput {
|
|||||||
*/
|
*/
|
||||||
protected override onHtmlInputKeyDown_(e: Event) {
|
protected override onHtmlInputKeyDown_(e: Event) {
|
||||||
super.onHtmlInputKeyDown_(e);
|
super.onHtmlInputKeyDown_(e);
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboardEvent = e as KeyboardEvent;
|
||||||
let multiplier;
|
let multiplier;
|
||||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
if (keyboardEvent.keyCode === KeyCodes.LEFT) {
|
||||||
// 'Event'.
|
|
||||||
if ((e as AnyDuringMigration).keyCode === KeyCodes.LEFT) {
|
|
||||||
// decrement (increment in RTL)
|
// decrement (increment in RTL)
|
||||||
multiplier = this.sourceBlock_.RTL ? 1 : -1;
|
multiplier = block.RTL ? 1 : -1;
|
||||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
} else if (keyboardEvent.keyCode === KeyCodes.RIGHT) {
|
||||||
// 'Event'.
|
|
||||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.RIGHT) {
|
|
||||||
// increment (decrement in RTL)
|
// increment (decrement in RTL)
|
||||||
multiplier = this.sourceBlock_.RTL ? -1 : 1;
|
multiplier = block.RTL ? -1 : 1;
|
||||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
} else if (keyboardEvent.keyCode === KeyCodes.DOWN) {
|
||||||
// 'Event'.
|
|
||||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.DOWN) {
|
|
||||||
// decrement
|
// decrement
|
||||||
multiplier = -1;
|
multiplier = -1;
|
||||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
} else if (keyboardEvent.keyCode === KeyCodes.UP) {
|
||||||
// 'Event'.
|
|
||||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.UP) {
|
|
||||||
// increment
|
// increment
|
||||||
multiplier = 1;
|
multiplier = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.FieldCheckbox');
|
|||||||
// Unused import preserved for side-effects. Remove if unneeded.
|
// Unused import preserved for side-effects. Remove if unneeded.
|
||||||
import './events/events_block_change.js';
|
import './events/events_block_change.js';
|
||||||
|
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import {FieldConfig, Field} from './field.js';
|
import {FieldConfig, Field} from './field.js';
|
||||||
import * as fieldRegistry from './field_registry.js';
|
import * as fieldRegistry from './field_registry.js';
|
||||||
import type {Sentinel} from './utils/sentinel.js';
|
import type {Sentinel} from './utils/sentinel.js';
|
||||||
@@ -111,8 +112,9 @@ export class FieldCheckbox extends Field {
|
|||||||
override initView() {
|
override initView() {
|
||||||
super.initView();
|
super.initView();
|
||||||
|
|
||||||
this.textElement_.classList.add('blocklyCheckbox');
|
const textElement = this.getTextElement();
|
||||||
this.textElement_.style.display = this.value_ ? 'block' : 'none';
|
dom.addClass(textElement, 'blocklyCheckbox');
|
||||||
|
textElement.style.display = this.value_ ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
override render_() {
|
override render_() {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import './events/events_block_change.js';
|
|||||||
import {BlockSvg} from './block_svg.js';
|
import {BlockSvg} from './block_svg.js';
|
||||||
import * as browserEvents from './browser_events.js';
|
import * as browserEvents from './browser_events.js';
|
||||||
import * as Css from './css.js';
|
import * as Css from './css.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import * as dropDownDiv from './dropdowndiv.js';
|
import * as dropDownDiv from './dropdowndiv.js';
|
||||||
import {FieldConfig, Field} from './field.js';
|
import {FieldConfig, Field} from './field.js';
|
||||||
import * as fieldRegistry from './field_registry.js';
|
import * as fieldRegistry from './field_registry.js';
|
||||||
@@ -190,7 +191,7 @@ export class FieldColour extends Field {
|
|||||||
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT);
|
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT);
|
||||||
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
|
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
|
||||||
this.createBorderRect_();
|
this.createBorderRect_();
|
||||||
this.borderRect_.style['fillOpacity'] = '1';
|
this.getBorderRect().style['fillOpacity'] = '1';
|
||||||
} else if (this.sourceBlock_ instanceof BlockSvg) {
|
} else if (this.sourceBlock_ instanceof BlockSvg) {
|
||||||
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
|
this.clickTarget_ = this.sourceBlock_.getSvgRoot();
|
||||||
}
|
}
|
||||||
@@ -438,7 +439,7 @@ export class FieldColour extends Field {
|
|||||||
(this.picker_ as AnyDuringMigration)!.blur();
|
(this.picker_ as AnyDuringMigration)!.blur();
|
||||||
const highlighted = this.getHighlighted_();
|
const highlighted = this.getHighlighted_();
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
highlighted.classList.remove('blocklyColourHighlighted');
|
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,10 +474,10 @@ export class FieldColour extends Field {
|
|||||||
// Unhighlight the current item.
|
// Unhighlight the current item.
|
||||||
const highlighted = this.getHighlighted_();
|
const highlighted = this.getHighlighted_();
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
highlighted.classList.remove('blocklyColourHighlighted');
|
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||||
}
|
}
|
||||||
// Highlight new item.
|
// Highlight new item.
|
||||||
cell.classList.add('blocklyColourHighlighted');
|
dom.addClass(cell, 'blocklyColourHighlighted');
|
||||||
// Set new highlighted index.
|
// Set new highlighted index.
|
||||||
this.highlightedIndex_ = index;
|
this.highlightedIndex_ = index;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ goog.declareModuleId('Blockly.FieldDropdown');
|
|||||||
|
|
||||||
import type {BlockSvg} from './block_svg.js';
|
import type {BlockSvg} from './block_svg.js';
|
||||||
import * as dropDownDiv from './dropdowndiv.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 * as fieldRegistry from './field_registry.js';
|
||||||
import {Menu} from './menu.js';
|
import {Menu} from './menu.js';
|
||||||
import {MenuItem} from './menuitem.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 type {Sentinel} from './utils/sentinel.js';
|
||||||
import * as utilsString from './utils/string.js';
|
import * as utilsString from './utils/string.js';
|
||||||
import {Svg} from './utils/svg.js';
|
import {Svg} from './utils/svg.js';
|
||||||
import * as userAgent from './utils/useragent.js';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for an editable dropdown field.
|
* Class for an editable dropdown field.
|
||||||
@@ -44,7 +42,8 @@ export class FieldDropdown extends Field {
|
|||||||
* height.
|
* height.
|
||||||
*/
|
*/
|
||||||
static MAX_MENU_HEIGHT_VH = 0.45;
|
static MAX_MENU_HEIGHT_VH = 0.45;
|
||||||
static ARROW_CHAR: AnyDuringMigration;
|
|
||||||
|
static ARROW_CHAR = '▾';
|
||||||
|
|
||||||
/** A reference to the currently selected menu item. */
|
/** A reference to the currently selected menu item. */
|
||||||
private selectedMenuItem_: MenuItem|null = null;
|
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. */
|
/** Mouse cursor style when over the hotspot that initiates the editor. */
|
||||||
override CURSOR = 'default';
|
override CURSOR = 'default';
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
|
||||||
protected menuGenerator_!: AnyDuringMigration[][]|
|
protected menuGenerator_?: MenuGenerator;
|
||||||
((this: FieldDropdown) => AnyDuringMigration[][]);
|
|
||||||
|
|
||||||
/** A cache of the most recently generated options. */
|
/** A cache of the most recently generated options. */
|
||||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
private generatedOptions_: MenuOption[]|null = null;
|
||||||
// 'string[][]'.
|
|
||||||
private generatedOptions_: string[][] = null as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The prefix field label, of common words set after options are trimmed.
|
* 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;
|
override suffixField: string|null = null;
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||||
private selectedOption_!: Array<string|ImageProperties>;
|
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
|
* @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.
|
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
menuGenerator: AnyDuringMigration[][]|Function|Sentinel,
|
menuGenerator: MenuGenerator,
|
||||||
opt_validator?: Function, opt_config?: FieldConfig) {
|
opt_validator?: Function,
|
||||||
|
opt_config?: FieldConfig,
|
||||||
|
);
|
||||||
|
constructor(menuGenerator: Sentinel);
|
||||||
|
constructor(
|
||||||
|
menuGenerator: MenuGenerator|Sentinel,
|
||||||
|
opt_validator?: Function,
|
||||||
|
opt_config?: FieldConfig,
|
||||||
|
) {
|
||||||
super(Field.SKIP_SETUP);
|
super(Field.SKIP_SETUP);
|
||||||
|
|
||||||
// If we pass SKIP_SETUP, don't do *anything* with the menu generator.
|
// If we pass SKIP_SETUP, don't do *anything* with the menu generator.
|
||||||
if (menuGenerator === Field.SKIP_SETUP) {
|
if (!isMenuGenerator(menuGenerator)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(menuGenerator)) {
|
if (Array.isArray(menuGenerator)) {
|
||||||
validateOptions(menuGenerator);
|
validateOptions(menuGenerator);
|
||||||
// Deep copy the option structure so it doesn't change.
|
const trimmed = trimOptions(menuGenerator);
|
||||||
menuGenerator = JSON.parse(JSON.stringify(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
|
* The currently selected option. The field is initialized with the
|
||||||
* first option selected.
|
* first option selected.
|
||||||
@@ -205,7 +202,7 @@ export class FieldDropdown extends Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.borderRect_) {
|
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 {
|
protected shouldAddBorderRect_(): boolean {
|
||||||
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
||||||
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. */
|
/** Create a tspan based arrow. */
|
||||||
protected createTextArrow_() {
|
protected createTextArrow_() {
|
||||||
this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
|
this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
|
||||||
this.arrow_!.appendChild(document.createTextNode(
|
this.arrow_!.appendChild(document.createTextNode(
|
||||||
this.sourceBlock_.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
||||||
' ' + FieldDropdown.ARROW_CHAR));
|
' ' + FieldDropdown.ARROW_CHAR));
|
||||||
if (this.sourceBlock_.RTL) {
|
if (this.getSourceBlock()?.RTL) {
|
||||||
// AnyDuringMigration because: Argument of type 'SVGTSpanElement | null'
|
this.getTextElement().insertBefore(this.arrow_, this.textContent_);
|
||||||
// is not assignable to parameter of type 'Node'.
|
|
||||||
this.textElement_.insertBefore(
|
|
||||||
this.arrow_ as AnyDuringMigration, this.textContent_);
|
|
||||||
} else {
|
} else {
|
||||||
// AnyDuringMigration because: Argument of type 'SVGTSpanElement | null'
|
this.getTextElement().appendChild(this.arrow_);
|
||||||
// is not assignable to parameter of type 'Node'.
|
|
||||||
this.textElement_.appendChild(this.arrow_ as AnyDuringMigration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,17 +249,14 @@ export class FieldDropdown extends Field {
|
|||||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||||
* undefined if triggered programmatically.
|
* 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_();
|
this.dropdownCreate_();
|
||||||
// AnyDuringMigration because: Property 'clientX' does not exist on type
|
if (opt_e && typeof opt_e.clientX === 'number') {
|
||||||
// 'Event'.
|
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
this.menu_!.openingCoords = null;
|
this.menu_!.openingCoords = null;
|
||||||
}
|
}
|
||||||
@@ -276,14 +265,13 @@ export class FieldDropdown extends Field {
|
|||||||
dropDownDiv.clearContent();
|
dropDownDiv.clearContent();
|
||||||
// Element gets created in render.
|
// Element gets created in render.
|
||||||
const menuElement = this.menu_!.render(dropDownDiv.getContentDiv());
|
const menuElement = this.menu_!.render(dropDownDiv.getContentDiv());
|
||||||
menuElement.classList.add('blocklyDropdownMenu');
|
dom.addClass(menuElement, 'blocklyDropdownMenu');
|
||||||
|
|
||||||
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
|
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
|
||||||
const primaryColour = this.sourceBlock_.isShadow() ?
|
const primaryColour =
|
||||||
this.sourceBlock_.getParent()!.getColour() :
|
block.isShadow() ? block.getParent()!.getColour() : block.getColour();
|
||||||
this.sourceBlock_.getColour();
|
const borderColour = block.isShadow() ?
|
||||||
const borderColour = this.sourceBlock_.isShadow() ?
|
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||||
(this.sourceBlock_.getParent() as BlockSvg).style.colourTertiary :
|
|
||||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||||
dropDownDiv.setColour(primaryColour, borderColour);
|
dropDownDiv.setColour(primaryColour, borderColour);
|
||||||
}
|
}
|
||||||
@@ -304,6 +292,10 @@ export class FieldDropdown extends Field {
|
|||||||
|
|
||||||
/** Create the dropdown editor. */
|
/** Create the dropdown editor. */
|
||||||
private dropdownCreate_() {
|
private dropdownCreate_() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const menu = new Menu();
|
const menu = new Menu();
|
||||||
menu.setRole(aria.Role.LISTBOX);
|
menu.setRole(aria.Role.LISTBOX);
|
||||||
this.menu_ = menu;
|
this.menu_ = menu;
|
||||||
@@ -311,18 +303,20 @@ export class FieldDropdown extends Field {
|
|||||||
const options = this.getOptions(false);
|
const options = this.getOptions(false);
|
||||||
this.selectedMenuItem_ = null;
|
this.selectedMenuItem_ = null;
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
let content = options[i][0]; // Human-readable text or image.
|
const [label, value] = options[i];
|
||||||
const value = options[i][1]; // Language-neutral value.
|
const content = (() => {
|
||||||
if (typeof content === 'object') {
|
if (typeof label === 'object') {
|
||||||
// An image, not text.
|
// Convert ImageProperties to an HTMLImageElement.
|
||||||
const image = new Image(content['width'], content['height']);
|
const image = new Image(label['width'], label['height']);
|
||||||
image.src = content['src'];
|
image.src = label['src'];
|
||||||
image.alt = content['alt'] || '';
|
image.alt = label['alt'] || '';
|
||||||
content = image;
|
return image;
|
||||||
}
|
}
|
||||||
|
return label;
|
||||||
|
})();
|
||||||
const menuItem = new MenuItem(content, value);
|
const menuItem = new MenuItem(content, value);
|
||||||
menuItem.setRole(aria.Role.OPTION);
|
menuItem.setRole(aria.Role.OPTION);
|
||||||
menuItem.setRightToLeft(this.sourceBlock_.RTL);
|
menuItem.setRightToLeft(block.RTL);
|
||||||
menuItem.setCheckable(true);
|
menuItem.setCheckable(true);
|
||||||
menu.addChild(menuItem);
|
menu.addChild(menuItem);
|
||||||
menuItem.setChecked(value === this.value_);
|
menuItem.setChecked(value === this.value_);
|
||||||
@@ -365,57 +359,6 @@ export class FieldDropdown extends Field {
|
|||||||
this.setValue(menuItem.getValue());
|
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.
|
* @returns True if the option list is generated by a function.
|
||||||
* Otherwise false.
|
* Otherwise false.
|
||||||
@@ -433,18 +376,18 @@ export class FieldDropdown extends Field {
|
|||||||
* (human-readable text or image, language-neutral name).
|
* (human-readable text or image, language-neutral name).
|
||||||
* @throws {TypeError} If generated options are incorrectly structured.
|
* @throws {TypeError} If generated options are incorrectly structured.
|
||||||
*/
|
*/
|
||||||
getOptions(opt_useCache?: boolean): AnyDuringMigration[][] {
|
getOptions(opt_useCache?: boolean): MenuOption[] {
|
||||||
if (this.isOptionListDynamic()) {
|
if (!this.menuGenerator_) {
|
||||||
if (!this.generatedOptions_ || !opt_useCache) {
|
// A subclass improperly skipped setup without defining the menu
|
||||||
// AnyDuringMigration because: Property 'call' does not exist on type
|
// generator.
|
||||||
// 'any[][] | ((this: FieldDropdown) => any[][])'.
|
throw TypeError('A menu generator was never defined.');
|
||||||
this.generatedOptions_ =
|
|
||||||
(this.menuGenerator_ as AnyDuringMigration).call(this);
|
|
||||||
validateOptions(this.generatedOptions_);
|
|
||||||
}
|
|
||||||
return this.generatedOptions_;
|
|
||||||
}
|
}
|
||||||
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.
|
* @param opt_newValue The input value.
|
||||||
* @returns A valid language-neutral option, or null if invalid.
|
* @returns A valid language-neutral option, or null if invalid.
|
||||||
*/
|
*/
|
||||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
protected override doClassValidation_(opt_newValue?: MenuOption[1]): string
|
||||||
string|null {
|
|null {
|
||||||
let isValueValid = false;
|
|
||||||
const options = this.getOptions(true);
|
const options = this.getOptions(true);
|
||||||
for (let i = 0, option; option = options[i]; i++) {
|
const isValueValid = options.some((option) => option[1] === opt_newValue);
|
||||||
// Options are tuples of human-readable text and language-neutral values.
|
|
||||||
if (option[1] === opt_newValue) {
|
|
||||||
isValueValid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isValueValid) {
|
if (!isValueValid) {
|
||||||
if (this.sourceBlock_) {
|
if (this.sourceBlock_) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -482,7 +419,7 @@ export class FieldDropdown extends Field {
|
|||||||
* @param newValue The value to be saved. The default validator guarantees
|
* @param newValue The value to be saved. The default validator guarantees
|
||||||
* that this is one of the valid dropdown options.
|
* that this is one of the valid dropdown options.
|
||||||
*/
|
*/
|
||||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
protected override doValueUpdate_(newValue: MenuOption[1]) {
|
||||||
super.doValueUpdate_(newValue);
|
super.doValueUpdate_(newValue);
|
||||||
const options = this.getOptions(true);
|
const options = this.getOptions(true);
|
||||||
for (let i = 0, option; option = options[i]; i++) {
|
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. */
|
/** Draws the border with the correct width. */
|
||||||
protected override render_() {
|
protected override render_() {
|
||||||
// Hide both elements.
|
// Hide both elements.
|
||||||
this.textContent_.nodeValue = '';
|
this.getTextContent().nodeValue = '';
|
||||||
this.imageElement_!.style.display = 'none';
|
this.imageElement_!.style.display = 'none';
|
||||||
|
|
||||||
// Show correct element.
|
// Show correct element.
|
||||||
@@ -540,17 +477,15 @@ export class FieldDropdown extends Field {
|
|||||||
* @param imageJson Selected option that must be an image.
|
* @param imageJson Selected option that must be an image.
|
||||||
*/
|
*/
|
||||||
private renderSelectedImage_(imageJson: ImageProperties) {
|
private renderSelectedImage_(imageJson: ImageProperties) {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
this.imageElement_!.style.display = '';
|
this.imageElement_!.style.display = '';
|
||||||
this.imageElement_!.setAttributeNS(
|
this.imageElement_!.setAttributeNS(
|
||||||
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
||||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
this.imageElement_!.setAttribute('height', `${imageJson.height}`);
|
||||||
// to parameter of type 'string'.
|
this.imageElement_!.setAttribute('width', `${imageJson.width}`);
|
||||||
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);
|
|
||||||
|
|
||||||
const imageHeight = Number(imageJson.height);
|
const imageHeight = Number(imageJson.height);
|
||||||
const imageWidth = Number(imageJson.width);
|
const imageWidth = Number(imageJson.width);
|
||||||
@@ -578,12 +513,12 @@ export class FieldDropdown extends Field {
|
|||||||
this.size_.height = height;
|
this.size_.height = height;
|
||||||
|
|
||||||
let arrowX = 0;
|
let arrowX = 0;
|
||||||
if (this.sourceBlock_.RTL) {
|
if (block.RTL) {
|
||||||
const imageX = xPadding + arrowWidth;
|
const imageX = xPadding + arrowWidth;
|
||||||
this.imageElement_!.setAttribute('x', imageX.toString());
|
this.imageElement_!.setAttribute('x', imageX.toString());
|
||||||
} else {
|
} else {
|
||||||
arrowX = imageWidth + arrowWidth;
|
arrowX = imageWidth + arrowWidth;
|
||||||
this.textElement_.setAttribute('text-anchor', 'end');
|
this.getTextElement().setAttribute('text-anchor', 'end');
|
||||||
this.imageElement_!.setAttribute('x', xPadding.toString());
|
this.imageElement_!.setAttribute('x', xPadding.toString());
|
||||||
}
|
}
|
||||||
this.imageElement_!.setAttribute(
|
this.imageElement_!.setAttribute(
|
||||||
@@ -595,9 +530,10 @@ export class FieldDropdown extends Field {
|
|||||||
/** Renders the selected option, which must be text. */
|
/** Renders the selected option, which must be text. */
|
||||||
private renderSelectedText_() {
|
private renderSelectedText_() {
|
||||||
// Retrieves the selected option to display through getText_.
|
// Retrieves the selected option to display through getText_.
|
||||||
this.textContent_.nodeValue = this.getDisplayText_();
|
this.getTextContent().nodeValue = this.getDisplayText_();
|
||||||
this.textElement_.classList.add('blocklyDropdownText');
|
const textElement = this.getTextElement();
|
||||||
this.textElement_.setAttribute('text-anchor', 'start');
|
dom.addClass(textElement, 'blocklyDropdownText');
|
||||||
|
textElement.setAttribute('text-anchor', 'start');
|
||||||
|
|
||||||
// Height and width include the border rect.
|
// Height and width include the border rect.
|
||||||
const hasBorder = !!this.borderRect_;
|
const hasBorder = !!this.borderRect_;
|
||||||
@@ -605,7 +541,7 @@ export class FieldDropdown extends Field {
|
|||||||
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
|
||||||
this.getConstants()!.FIELD_TEXT_HEIGHT);
|
this.getConstants()!.FIELD_TEXT_HEIGHT);
|
||||||
const textWidth = dom.getFastTextWidth(
|
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_FONTWEIGHT,
|
||||||
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
|
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
|
||||||
const xPadding =
|
const xPadding =
|
||||||
@@ -633,12 +569,16 @@ export class FieldDropdown extends Field {
|
|||||||
if (!this.svgArrow_) {
|
if (!this.svgArrow_) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const hasBorder = !!this.borderRect_;
|
const hasBorder = !!this.borderRect_;
|
||||||
const xPadding =
|
const xPadding =
|
||||||
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
|
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
|
||||||
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
||||||
const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
|
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(
|
this.svgArrow_.setAttribute(
|
||||||
'transform', 'translate(' + arrowX + ',' + y + ')');
|
'transform', 'translate(' + arrowX + ',' + y + ')');
|
||||||
return svgArrowSize + textPadding;
|
return svgArrowSize + textPadding;
|
||||||
@@ -681,30 +621,6 @@ export class FieldDropdown extends Field {
|
|||||||
// override the static fromJson method.
|
// override the static fromJson method.
|
||||||
return new this(options.options, undefined, options);
|
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];
|
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.
|
* fromJson config for the dropdown field.
|
||||||
*/
|
*/
|
||||||
@@ -740,8 +668,81 @@ const IMAGE_Y_OFFSET = 5;
|
|||||||
/** The total vertical padding above and below an image. */
|
/** The total vertical padding above and below an image. */
|
||||||
const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2;
|
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.
|
* 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.
|
* @param options The proposed dropdown options.
|
||||||
* @throws {TypeError} If proposed options are incorrectly structured.
|
* @throws {TypeError} If proposed options are incorrectly structured.
|
||||||
*/
|
*/
|
||||||
function validateOptions(options: AnyDuringMigration) {
|
function validateOptions(options: MenuOption[]) {
|
||||||
if (!Array.isArray(options)) {
|
if (!Array.isArray(options)) {
|
||||||
throw TypeError('FieldDropdown options must be an array.');
|
throw TypeError('FieldDropdown options must be an array.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import * as goog from '../closure/goog/goog.js';
|
import * as goog from '../closure/goog/goog.js';
|
||||||
goog.declareModuleId('Blockly.FieldLabel');
|
goog.declareModuleId('Blockly.FieldLabel');
|
||||||
|
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import {FieldConfig, Field} from './field.js';
|
import {FieldConfig, Field} from './field.js';
|
||||||
import * as fieldRegistry from './field_registry.js';
|
import * as fieldRegistry from './field_registry.js';
|
||||||
import * as parsing from './utils/parsing.js';
|
import * as parsing from './utils/parsing.js';
|
||||||
@@ -75,7 +76,7 @@ export class FieldLabel extends Field {
|
|||||||
override initView() {
|
override initView() {
|
||||||
this.createTextElement_();
|
this.createTextElement_();
|
||||||
if (this.class_) {
|
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) {
|
setClass(cssClass: string|null) {
|
||||||
if (this.textElement_) {
|
if (this.textElement_) {
|
||||||
if (this.class_) {
|
if (this.class_) {
|
||||||
this.textElement_.classList.remove(this.class_);
|
dom.removeClass(this.textElement_, this.class_);
|
||||||
}
|
}
|
||||||
if (cssClass) {
|
if (cssClass) {
|
||||||
this.textElement_.classList.add(cssClass);
|
dom.addClass(this.textElement_, cssClass);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.class_ = cssClass;
|
this.class_ = cssClass;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
|||||||
goog.declareModuleId('Blockly.FieldMultilineInput');
|
goog.declareModuleId('Blockly.FieldMultilineInput');
|
||||||
|
|
||||||
import * as Css from './css.js';
|
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 * as fieldRegistry from './field_registry.js';
|
||||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||||
import * as aria from './utils/aria.js';
|
import * as aria from './utils/aria.js';
|
||||||
@@ -163,6 +163,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
* @returns Currently displayed text.
|
* @returns Currently displayed text.
|
||||||
*/
|
*/
|
||||||
protected override getDisplayText_(): string {
|
protected override getDisplayText_(): string {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
let textLines = this.getText();
|
let textLines = this.getText();
|
||||||
if (!textLines) {
|
if (!textLines) {
|
||||||
// Prevent the field from disappearing if empty.
|
// Prevent the field from disappearing if empty.
|
||||||
@@ -189,8 +193,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
textLines += '\n';
|
textLines += '\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.sourceBlock_.RTL) {
|
if (block.RTL) {
|
||||||
// The SVG is LTR, force value to be RTL by adding an RLM.
|
// The SVG is LTR, force value to be RTL.
|
||||||
textLines += '\u200F';
|
textLines += '\u200F';
|
||||||
}
|
}
|
||||||
return textLines;
|
return textLines;
|
||||||
@@ -212,6 +216,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
|
|
||||||
/** Updates the text of the textElement. */
|
/** Updates the text of the textElement. */
|
||||||
protected override render_() {
|
protected override render_() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
// Remove all text group children.
|
// Remove all text group children.
|
||||||
let currentChild;
|
let currentChild;
|
||||||
while (currentChild = this.textGroup_.firstChild) {
|
while (currentChild = this.textGroup_.firstChild) {
|
||||||
@@ -239,16 +247,16 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
if (this.isBeingEdited_) {
|
if (this.isBeingEdited_) {
|
||||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||||
if (this.isOverflowedY_) {
|
if (this.isOverflowedY_) {
|
||||||
htmlInput.classList.add('blocklyHtmlTextAreaInputOverflowedY');
|
dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
|
||||||
} else {
|
} else {
|
||||||
htmlInput.classList.remove('blocklyHtmlTextAreaInputOverflowedY');
|
dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSize_();
|
this.updateSize_();
|
||||||
|
|
||||||
if (this.isBeingEdited_) {
|
if (this.isBeingEdited_) {
|
||||||
if (this.sourceBlock_.RTL) {
|
if (block.RTL) {
|
||||||
// in RTL, we need to let the browser reflow before resizing
|
// in RTL, we need to let the browser reflow before resizing
|
||||||
// in order to get the correct bounding box of the borderRect
|
// in order to get the correct bounding box of the borderRect
|
||||||
// avoiding issue #2777.
|
// avoiding issue #2777.
|
||||||
@@ -258,10 +266,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
}
|
}
|
||||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||||
if (!this.isTextValid_) {
|
if (!this.isTextValid_) {
|
||||||
htmlInput.classList.add('blocklyInvalidInput');
|
dom.addClass(htmlInput, 'blocklyInvalidInput');
|
||||||
aria.setState(htmlInput, aria.State.INVALID, true);
|
aria.setState(htmlInput, aria.State.INVALID, true);
|
||||||
} else {
|
} else {
|
||||||
htmlInput.classList.remove('blocklyInvalidInput');
|
dom.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||||
aria.setState(htmlInput, aria.State.INVALID, false);
|
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. */
|
/** Updates the size of the field based on the text. */
|
||||||
protected override updateSize_() {
|
protected override updateSize_() {
|
||||||
const nodes = this.textGroup_.childNodes;
|
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 totalWidth = 0;
|
||||||
let totalHeight = 0;
|
let totalHeight = 0;
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const tspan = nodes[i] as SVGTextElement;
|
const tspan = nodes[i] as SVGTextElement;
|
||||||
const textWidth = dom.getTextWidth(tspan);
|
const textWidth =
|
||||||
|
dom.getFastTextWidth(tspan, fontSize, fontWeight, fontFamily);
|
||||||
if (textWidth > totalWidth) {
|
if (textWidth > totalWidth) {
|
||||||
totalWidth = textWidth;
|
totalWidth = textWidth;
|
||||||
}
|
}
|
||||||
@@ -290,9 +302,6 @@ export class FieldMultilineInput extends FieldTextInput {
|
|||||||
const actualEditorLines = this.value_.split('\n');
|
const actualEditorLines = this.value_.split('\n');
|
||||||
const dummyTextElement = dom.createSvgElement(
|
const dummyTextElement = dom.createSvgElement(
|
||||||
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
|
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++) {
|
for (let i = 0; i < actualEditorLines.length; i++) {
|
||||||
if (actualEditorLines[i].length > this.maxDisplayLength) {
|
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' +
|
' the file is not loaded, the field does not register itself (Issue' +
|
||||||
' #1584), or the registration is not being reached.');
|
' #1584), or the registration is not being reached.');
|
||||||
return null;
|
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');
|
throw new TypeError('returned Field was not a IRegistrableField');
|
||||||
} else {
|
} else {
|
||||||
return (fieldObject as unknown as IRegistrableField).fromJson(options);
|
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 type {BlockSvg} from './block_svg.js';
|
||||||
import * as browserEvents from './browser_events.js';
|
import * as browserEvents from './browser_events.js';
|
||||||
import * as dialog from './dialog.js';
|
import * as dialog from './dialog.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import * as dropDownDiv from './dropdowndiv.js';
|
import * as dropDownDiv from './dropdowndiv.js';
|
||||||
import * as eventUtils from './events/utils.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 * as fieldRegistry from './field_registry.js';
|
||||||
import {Msg} from './msg.js';
|
import {Msg} from './msg.js';
|
||||||
import * as aria from './utils/aria.js';
|
import * as aria from './utils/aria.js';
|
||||||
@@ -126,13 +127,17 @@ export class FieldTextInput extends Field {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override initView() {
|
override initView() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
if (this.getConstants()!.FULL_BLOCK_FIELDS) {
|
if (this.getConstants()!.FULL_BLOCK_FIELDS) {
|
||||||
// Step one: figure out if this is the only field on this block.
|
// Step one: figure out if this is the only field on this block.
|
||||||
// Rendering is quite different in that case.
|
// Rendering is quite different in that case.
|
||||||
let nFields = 0;
|
let nFields = 0;
|
||||||
let nConnections = 0;
|
let nConnections = 0;
|
||||||
// Count the number of fields, excluding text fields
|
// 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++) {
|
for (let j = 0; input.fieldRow[j]; j++) {
|
||||||
nFields++;
|
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
|
// The special case is when this is the only non-label field on the block
|
||||||
// and it has an output but no inputs.
|
// and it has an output but no inputs.
|
||||||
this.fullBlockClickTarget_ =
|
this.fullBlockClickTarget_ =
|
||||||
nFields <= 1 && this.sourceBlock_.outputConnection && !nConnections;
|
nFields <= 1 && block.outputConnection && !nConnections;
|
||||||
} else {
|
} else {
|
||||||
this.fullBlockClickTarget_ = false;
|
this.fullBlockClickTarget_ = false;
|
||||||
}
|
}
|
||||||
@@ -216,15 +221,15 @@ export class FieldTextInput extends Field {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override applyColour() {
|
override applyColour() {
|
||||||
if (this.sourceBlock_ && this.getConstants()!.FULL_BLOCK_FIELDS) {
|
if (!this.sourceBlock_ || !this.getConstants()!.FULL_BLOCK_FIELDS) return;
|
||||||
if (this.borderRect_) {
|
|
||||||
this.borderRect_.setAttribute(
|
const source = this.sourceBlock_ as BlockSvg;
|
||||||
'stroke', (this.sourceBlock_ as BlockSvg).style.colourTertiary);
|
|
||||||
} else {
|
if (this.borderRect_) {
|
||||||
(this.sourceBlock_ as BlockSvg)
|
this.borderRect_.setAttribute('stroke', source.style.colourTertiary);
|
||||||
.pathObject.svgPath.setAttribute(
|
} else {
|
||||||
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
|
source.pathObject.svgPath.setAttribute(
|
||||||
}
|
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,10 +245,10 @@ export class FieldTextInput extends Field {
|
|||||||
this.resizeEditor_();
|
this.resizeEditor_();
|
||||||
const htmlInput = this.htmlInput_ as HTMLElement;
|
const htmlInput = this.htmlInput_ as HTMLElement;
|
||||||
if (!this.isTextValid_) {
|
if (!this.isTextValid_) {
|
||||||
htmlInput.classList.add('blocklyInvalidInput');
|
dom.addClass(htmlInput, 'blocklyInvalidInput');
|
||||||
aria.setState(htmlInput, aria.State.INVALID, true);
|
aria.setState(htmlInput, aria.State.INVALID, true);
|
||||||
} else {
|
} else {
|
||||||
htmlInput.classList.remove('blocklyInvalidInput');
|
dom.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||||
aria.setState(htmlInput, aria.State.INVALID, false);
|
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.
|
* @param quietInput True if editor should be created without focus.
|
||||||
*/
|
*/
|
||||||
private showInlineEditor_(quietInput: boolean) {
|
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.htmlInput_ = this.widgetCreate_() as HTMLInputElement;
|
||||||
this.isBeingEdited_ = true;
|
this.isBeingEdited_ = true;
|
||||||
|
|
||||||
@@ -324,10 +333,16 @@ export class FieldTextInput extends Field {
|
|||||||
* @returns The newly created text input editor.
|
* @returns The newly created text input editor.
|
||||||
*/
|
*/
|
||||||
protected widgetCreate_(): HTMLElement {
|
protected widgetCreate_(): HTMLElement {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
eventUtils.setGroup(true);
|
eventUtils.setGroup(true);
|
||||||
const div = WidgetDiv.getDiv();
|
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'));
|
const htmlInput = (document.createElement('input'));
|
||||||
htmlInput.className = 'blocklyHtmlInput';
|
htmlInput.className = 'blocklyHtmlInput';
|
||||||
@@ -347,8 +362,8 @@ export class FieldTextInput extends Field {
|
|||||||
// Override border radius.
|
// Override border radius.
|
||||||
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
|
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
|
||||||
// Pull stroke colour from the existing shadow block
|
// Pull stroke colour from the existing shadow block
|
||||||
const strokeColour = this.sourceBlock_.getParent() ?
|
const strokeColour = block.getParent() ?
|
||||||
(this.sourceBlock_.getParent() as BlockSvg).style.colourTertiary :
|
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||||
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
|
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
|
||||||
div!.style.borderRadius = borderRadius;
|
div!.style.borderRadius = borderRadius;
|
||||||
@@ -396,7 +411,9 @@ export class FieldTextInput extends Field {
|
|||||||
style.boxShadow = '';
|
style.boxShadow = '';
|
||||||
this.htmlInput_ = null;
|
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. */
|
/** Resize the editor to fit the text. */
|
||||||
protected resizeEditor_() {
|
protected resizeEditor_() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const div = WidgetDiv.getDiv();
|
const div = WidgetDiv.getDiv();
|
||||||
const bBox = this.getScaledBBox();
|
const bBox = this.getScaledBBox();
|
||||||
div!.style.width = bBox.right - bBox.left + 'px';
|
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,
|
// In RTL mode block fields and LTR input fields the left edge moves,
|
||||||
// whereas the right edge is fixed. Reposition the editor.
|
// 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);
|
const xy = new Coordinate(x, bBox.top);
|
||||||
|
|
||||||
div!.style.left = xy.x + 'px';
|
div!.style.left = xy.x + 'px';
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ goog.declareModuleId('Blockly.FieldVariable');
|
|||||||
import './events/events_block_change.js';
|
import './events/events_block_change.js';
|
||||||
|
|
||||||
import type {Block} from './block.js';
|
import type {Block} from './block.js';
|
||||||
import {Field, FieldConfig} from './field.js';
|
import {Field, FieldConfig, UnattachedFieldError} from './field.js';
|
||||||
import {FieldDropdown} from './field_dropdown.js';
|
import {FieldDropdown, MenuGenerator, MenuOption} from './field_dropdown.js';
|
||||||
import * as fieldRegistry from './field_registry.js';
|
import * as fieldRegistry from './field_registry.js';
|
||||||
import * as internalConstants from './internal_constants.js';
|
import * as internalConstants from './internal_constants.js';
|
||||||
import type {Menu} from './menu.js';
|
import type {Menu} from './menu.js';
|
||||||
@@ -37,8 +37,7 @@ import * as Xml from './xml.js';
|
|||||||
* @alias Blockly.FieldVariable
|
* @alias Blockly.FieldVariable
|
||||||
*/
|
*/
|
||||||
export class FieldVariable extends FieldDropdown {
|
export class FieldVariable extends FieldDropdown {
|
||||||
protected override menuGenerator_: AnyDuringMigration[][]|
|
protected override menuGenerator_: MenuGenerator|undefined;
|
||||||
((this: FieldDropdown) => AnyDuringMigration[][]);
|
|
||||||
defaultVariableName: string;
|
defaultVariableName: string;
|
||||||
|
|
||||||
/** The type of the default variable for this field. */
|
/** 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,
|
* An array of options for a dropdown list,
|
||||||
* or a function which generates these options.
|
* or a function which generates these options.
|
||||||
*/
|
*/
|
||||||
// AnyDuringMigration because: Type '(this: FieldVariable) => any[][]' is
|
this.menuGenerator_ = FieldVariable.dropdownCreate as MenuGenerator;
|
||||||
// not assignable to type 'any[][] | ((this: FieldDropdown) => any[][])'.
|
|
||||||
this.menuGenerator_ = FieldVariable.dropdownCreate as AnyDuringMigration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initial variable name passed to this field's constructor, or an
|
* The initial variable name passed to this field's constructor, or an
|
||||||
@@ -135,20 +132,27 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override initModel() {
|
override initModel() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
if (this.variable_) {
|
if (this.variable_) {
|
||||||
return; // Initialization already happened.
|
return; // Initialization already happened.
|
||||||
}
|
}
|
||||||
const variable = Variables.getOrCreateVariablePackage(
|
const variable = Variables.getOrCreateVariablePackage(
|
||||||
this.sourceBlock_.workspace, null, this.defaultVariableName,
|
block.workspace, null, this.defaultVariableName, this.defaultType_);
|
||||||
this.defaultType_);
|
|
||||||
// Don't call setValue because we don't want to cause a rerender.
|
// Don't call setValue because we don't want to cause a rerender.
|
||||||
this.doValueUpdate_(variable.getId());
|
this.doValueUpdate_(variable.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
override shouldAddBorderRect_() {
|
override shouldAddBorderRect_() {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
return super.shouldAddBorderRect_() &&
|
return super.shouldAddBorderRect_() &&
|
||||||
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
(!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.
|
* field's state.
|
||||||
*/
|
*/
|
||||||
override fromXml(fieldElement: Element) {
|
override fromXml(fieldElement: Element) {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const id = fieldElement.getAttribute('id');
|
const id = fieldElement.getAttribute('id');
|
||||||
const variableName = fieldElement.textContent;
|
const variableName = fieldElement.textContent;
|
||||||
// 'variabletype' should be lowercase, but until July 2019 it was sometimes
|
// '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
|
// AnyDuringMigration because: Argument of type 'string | null' is not
|
||||||
// assignable to parameter of type 'string | undefined'.
|
// assignable to parameter of type 'string | undefined'.
|
||||||
const variable = Variables.getOrCreateVariablePackage(
|
const variable = Variables.getOrCreateVariablePackage(
|
||||||
this.sourceBlock_.workspace, id, variableName as AnyDuringMigration,
|
block.workspace, id, variableName as AnyDuringMigration, variableType);
|
||||||
variableType);
|
|
||||||
|
|
||||||
// This should never happen :)
|
// This should never happen :)
|
||||||
if (variableType !== null && variableType !== variable.type) {
|
if (variableType !== null && variableType !== variable.type) {
|
||||||
@@ -233,12 +240,16 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override loadState(state: AnyDuringMigration) {
|
override loadState(state: AnyDuringMigration) {
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
if (this.loadLegacyState(FieldVariable, state)) {
|
if (this.loadLegacyState(FieldVariable, state)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// This is necessary so that blocks in the flyout can have custom var names.
|
// This is necessary so that blocks in the flyout can have custom var names.
|
||||||
const variable = Variables.getOrCreateVariablePackage(
|
const variable = Variables.getOrCreateVariablePackage(
|
||||||
this.sourceBlock_.workspace, state['id'] || null, state['name'],
|
block.workspace, state['id'] || null, state['name'],
|
||||||
state['type'] || '');
|
state['type'] || '');
|
||||||
this.setValue(variable.getId());
|
this.setValue(variable.getId());
|
||||||
}
|
}
|
||||||
@@ -315,8 +326,12 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
if (opt_newValue === null) {
|
if (opt_newValue === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const block = this.getSourceBlock();
|
||||||
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
const newId = opt_newValue as string;
|
const newId = opt_newValue as string;
|
||||||
const variable = Variables.getVariable(this.sourceBlock_.workspace, newId);
|
const variable = Variables.getVariable(block.workspace, newId);
|
||||||
if (!variable) {
|
if (!variable) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'Variable id doesn\'t point to a real variable! ' +
|
'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.
|
* @param newId The value to be saved.
|
||||||
*/
|
*/
|
||||||
protected override doValueUpdate_(newId: AnyDuringMigration) {
|
protected override doValueUpdate_(newId: AnyDuringMigration) {
|
||||||
this.variable_ =
|
const block = this.getSourceBlock();
|
||||||
Variables.getVariable(this.sourceBlock_.workspace, newId as string);
|
if (!block) {
|
||||||
|
throw new UnattachedFieldError();
|
||||||
|
}
|
||||||
|
this.variable_ = Variables.getVariable(block.workspace, newId as string);
|
||||||
super.doValueUpdate_(newId);
|
super.doValueUpdate_(newId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +395,7 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
let variableTypes = this.variableTypes;
|
let variableTypes = this.variableTypes;
|
||||||
if (variableTypes === null) {
|
if (variableTypes === null) {
|
||||||
// If variableTypes is null, return all variable types.
|
// 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();
|
return this.sourceBlock_.workspace.getVariableTypes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +474,7 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
protected override onItemSelected_(menu: Menu, menuItem: MenuItem) {
|
protected override onItemSelected_(menu: Menu, menuItem: MenuItem) {
|
||||||
const id = menuItem.getValue();
|
const id = menuItem.getValue();
|
||||||
// Handle special cases.
|
// Handle special cases.
|
||||||
if (this.sourceBlock_ && !this.sourceBlock_.disposed) {
|
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
|
||||||
if (id === internalConstants.RENAME_VARIABLE_ID) {
|
if (id === internalConstants.RENAME_VARIABLE_ID) {
|
||||||
// Rename variable.
|
// Rename variable.
|
||||||
Variables.renameVariable(
|
Variables.renameVariable(
|
||||||
@@ -507,15 +525,15 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
*
|
*
|
||||||
* @returns Array of variable names/id tuples.
|
* @returns Array of variable names/id tuples.
|
||||||
*/
|
*/
|
||||||
static dropdownCreate(this: FieldVariable): AnyDuringMigration[][] {
|
static dropdownCreate(this: FieldVariable): MenuOption[] {
|
||||||
if (!this.variable_) {
|
if (!this.variable_) {
|
||||||
throw Error(
|
throw Error(
|
||||||
'Tried to call dropdownCreate on a variable field with no' +
|
'Tried to call dropdownCreate on a variable field with no' +
|
||||||
' variable selected.');
|
' variable selected.');
|
||||||
}
|
}
|
||||||
const name = this.getText();
|
const name = this.getText();
|
||||||
let variableModelList: AnyDuringMigration[] = [];
|
let variableModelList: VariableModel[] = [];
|
||||||
if (this.sourceBlock_ && !this.sourceBlock_.disposed) {
|
if (this.sourceBlock_ && !this.sourceBlock_.isDeadOrDying()) {
|
||||||
const variableTypes = this.getVariableTypes_();
|
const variableTypes = this.getVariableTypes_();
|
||||||
// Get a copy of the list, so that adding rename and new variable options
|
// Get a copy of the list, so that adding rename and new variable options
|
||||||
// doesn't modify the workspace's list.
|
// doesn't modify the workspace's list.
|
||||||
@@ -528,7 +546,7 @@ export class FieldVariable extends FieldDropdown {
|
|||||||
}
|
}
|
||||||
variableModelList.sort(VariableModel.compareByName);
|
variableModelList.sort(VariableModel.compareByName);
|
||||||
|
|
||||||
const options = [];
|
const options: [string, string][] = [];
|
||||||
for (let i = 0; i < variableModelList.length; i++) {
|
for (let i = 0; i < variableModelList.length; i++) {
|
||||||
// Set the UUID as the internal representation of the variable.
|
// Set the UUID as the internal representation of the variable.
|
||||||
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
import * as goog from '../closure/goog/goog.js';
|
import * as goog from '../closure/goog/goog.js';
|
||||||
goog.declareModuleId('Blockly.Generator');
|
goog.declareModuleId('Blockly.CodeGenerator');
|
||||||
|
|
||||||
import type {Block} from './block.js';
|
import type {Block} from './block.js';
|
||||||
import * as common from './common.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.
|
* Class for a code generator that translates the blocks into a language.
|
||||||
*
|
*
|
||||||
* @unrestricted
|
* @unrestricted
|
||||||
* @alias Blockly.Generator
|
* @alias Blockly.CodeGenerator
|
||||||
*/
|
*/
|
||||||
export class Generator {
|
export class CodeGenerator {
|
||||||
name_: string;
|
name_: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used as a placeholder in functions defined using
|
* 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
|
* legitimately appear in a function definition (or comment), and it must
|
||||||
* not confuse the regular expression parser.
|
* not confuse the regular expression parser.
|
||||||
*/
|
*/
|
||||||
@@ -205,7 +205,7 @@ export class Generator {
|
|||||||
|[string, number] {
|
|[string, number] {
|
||||||
if (this.isInitialized === false) {
|
if (this.isInitialized === false) {
|
||||||
console.warn(
|
console.warn(
|
||||||
'Generator init was not called before blockToCode was called.');
|
'CodeGenerator init was not called before blockToCode was called.');
|
||||||
}
|
}
|
||||||
if (!block) {
|
if (!block) {
|
||||||
return '';
|
return '';
|
||||||
@@ -414,7 +414,7 @@ export class Generator {
|
|||||||
* "listRandom", not "random"). There is no danger of colliding with reserved
|
* "listRandom", not "random"). There is no danger of colliding with reserved
|
||||||
* words, or user-defined variable or procedure names.
|
* 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 desiredName The desired name of the function (e.g. mathIsPrime).
|
||||||
* @param code A list of statements or one multi-line code string. Use ' '
|
* @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.
|
* A database of variable names.
|
||||||
*
|
*
|
||||||
* @name Blockly.Generator.prototype.variableDB_
|
* @name Blockly.CodeGenerator.prototype.variableDB_
|
||||||
* @deprecated 'variableDB_' was renamed to 'nameDB_' (May 2021).
|
* @deprecated 'variableDB_' was renamed to 'nameDB_' (May 2021).
|
||||||
* @suppress {checkTypes}
|
* @suppress {checkTypes}
|
||||||
*/
|
*/
|
||||||
variableDB_: ({
|
variableDB_: ({
|
||||||
/** @returns Name database. */
|
/** @returns Name database. */
|
||||||
get(this: Generator): Names |
|
get(this: CodeGenerator): Names |
|
||||||
undefined {
|
undefined {
|
||||||
deprecation.warn(
|
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
|
||||||
'variableDB_', 'May 2021', 'September 2022', 'nameDB_');
|
|
||||||
return this.nameDB_;
|
return this.nameDB_;
|
||||||
},
|
},
|
||||||
/** @param nameDb New name database. */
|
/** @param nameDb New name database. */
|
||||||
set(this: Generator, nameDb: Names|undefined) {
|
set(this: CodeGenerator, nameDb: Names|undefined) {
|
||||||
deprecation.warn('variableDB_', 'May 2021', 'September2022', 'nameDB_');
|
deprecation.warn('variableDB_', 'version 9', 'version 10', 'nameDB_');
|
||||||
this.nameDB_ = 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 {Size} from './utils/size.js';
|
||||||
import {Svg} from './utils/svg.js';
|
import {Svg} from './utils/svg.js';
|
||||||
import * as svgMath from './utils/svg_math.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
|
* @alias Blockly.Icon
|
||||||
*/
|
*/
|
||||||
export abstract class Icon {
|
export abstract class Icon {
|
||||||
protected block_: BlockSvg;
|
protected block_: BlockSvg|null;
|
||||||
/** The icon SVG group. */
|
/** The icon SVG group. */
|
||||||
iconGroup_: SVGGElement|null = null;
|
iconGroup_: SVGGElement|null = null;
|
||||||
|
|
||||||
@@ -45,7 +46,12 @@ export abstract class Icon {
|
|||||||
protected iconXY_: Coordinate|null = null;
|
protected iconXY_: Coordinate|null = null;
|
||||||
|
|
||||||
/** @param block The block associated with this icon. */
|
/** @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;
|
this.block_ = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +68,12 @@ export abstract class Icon {
|
|||||||
*/
|
*/
|
||||||
this.iconGroup_ =
|
this.iconGroup_ =
|
||||||
dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'});
|
dom.createSvgElement(Svg.G, {'class': 'blocklyIconGroup'});
|
||||||
if (this.block_.isInFlyout) {
|
if (this.getBlock().isInFlyout) {
|
||||||
this.iconGroup_.classList.add('blocklyIconGroupReadonly');
|
dom.addClass(this.iconGroup_, 'blocklyIconGroupReadonly');
|
||||||
}
|
}
|
||||||
this.drawIcon_(this.iconGroup_);
|
this.drawIcon_(this.iconGroup_);
|
||||||
|
|
||||||
this.block_.getSvgRoot().appendChild(this.iconGroup_);
|
this.getBlock().getSvgRoot().appendChild(this.iconGroup_);
|
||||||
browserEvents.conditionalBind(
|
browserEvents.conditionalBind(
|
||||||
this.iconGroup_, 'mouseup', this, this.iconClick_);
|
this.iconGroup_, 'mouseup', this, this.iconClick_);
|
||||||
this.updateEditable();
|
this.updateEditable();
|
||||||
@@ -99,19 +105,19 @@ export abstract class Icon {
|
|||||||
* @param e Mouse click event.
|
* @param e Mouse click event.
|
||||||
*/
|
*/
|
||||||
protected iconClick_(e: MouseEvent) {
|
protected iconClick_(e: MouseEvent) {
|
||||||
if (this.block_.workspace.isDragging()) {
|
if (this.getBlock().workspace.isDragging()) {
|
||||||
// Drag operation is concluding. Don't open the editor.
|
// Drag operation is concluding. Don't open the editor.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.block_.isInFlyout && !browserEvents.isRightButton(e)) {
|
if (!this.getBlock().isInFlyout && !browserEvents.isRightButton(e)) {
|
||||||
this.setVisible(!this.isVisible());
|
this.setVisible(!this.isVisible());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Change the colour of the associated bubble to match its block. */
|
/** Change the colour of the associated bubble to match its block. */
|
||||||
applyColour() {
|
applyColour() {
|
||||||
if (this.isVisible()) {
|
if (this.bubble_ && this.isVisible()) {
|
||||||
this.bubble_!.setColour(this.block_.style.colourPrimary);
|
this.bubble_.setColour(this.getBlock().style.colourPrimary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +128,8 @@ export abstract class Icon {
|
|||||||
*/
|
*/
|
||||||
setIconLocation(xy: Coordinate) {
|
setIconLocation(xy: Coordinate) {
|
||||||
this.iconXY_ = xy;
|
this.iconXY_ = xy;
|
||||||
if (this.isVisible()) {
|
if (this.bubble_ && this.isVisible()) {
|
||||||
this.bubble_!.setAnchorLocation(xy);
|
this.bubble_.setAnchorLocation(xy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +139,7 @@ export abstract class Icon {
|
|||||||
*/
|
*/
|
||||||
computeIconLocation() {
|
computeIconLocation() {
|
||||||
// Find coordinates for the centre of the icon and update the arrow.
|
// 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 iconXY = svgMath.getRelativeXY(this.iconGroup_ as SVGElement);
|
||||||
const newXY = new Coordinate(
|
const newXY = new Coordinate(
|
||||||
blockXY.x + iconXY.x + this.SIZE / 2,
|
blockXY.x + iconXY.x + this.SIZE / 2,
|
||||||
@@ -178,5 +184,16 @@ export abstract class Icon {
|
|||||||
* @param _visible True if the icon should be visible.
|
* @param _visible True if the icon should be visible.
|
||||||
*/
|
*/
|
||||||
setVisible(_visible: boolean) {}
|
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
|
// No-op on base class
|
||||||
|
|||||||
@@ -172,11 +172,11 @@ function createMainWorkspace(
|
|||||||
const injectionDiv = mainWorkspace.getInjectionDiv();
|
const injectionDiv = mainWorkspace.getInjectionDiv();
|
||||||
const rendererClassName = mainWorkspace.getRenderer().getClassName();
|
const rendererClassName = mainWorkspace.getRenderer().getClassName();
|
||||||
if (rendererClassName) {
|
if (rendererClassName) {
|
||||||
injectionDiv.classList.add(rendererClassName);
|
dom.addClass(injectionDiv, rendererClassName);
|
||||||
}
|
}
|
||||||
const themeClassName = mainWorkspace.getTheme().getClassName();
|
const themeClassName = mainWorkspace.getTheme().getClassName();
|
||||||
if (themeClassName) {
|
if (themeClassName) {
|
||||||
injectionDiv.classList.add(themeClassName);
|
dom.addClass(injectionDiv, themeClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wsOptions.hasCategories && wsOptions.languageTree) {
|
if (!wsOptions.hasCategories && wsOptions.languageTree) {
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ export interface IASTNodeLocationWithBlock extends IASTNodeLocation {
|
|||||||
*
|
*
|
||||||
* @returns The source block.
|
* @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 location = this.location_ as Field;
|
||||||
const input = location.getParentInput();
|
const input = location.getParentInput();
|
||||||
const block = location.getSourceBlock();
|
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));
|
const curIdx = block.inputList.indexOf((input));
|
||||||
let fieldIdx = input.fieldRow.indexOf(location) + 1;
|
let fieldIdx = input.fieldRow.indexOf(location) + 1;
|
||||||
for (let i = curIdx; i < block.inputList.length; i++) {
|
for (let i = curIdx; i < block.inputList.length; i++) {
|
||||||
@@ -235,6 +239,10 @@ export class ASTNode {
|
|||||||
const location = this.location_ as Field;
|
const location = this.location_ as Field;
|
||||||
const parentInput = location.getParentInput();
|
const parentInput = location.getParentInput();
|
||||||
const block = location.getSourceBlock();
|
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));
|
const curIdx = block.inputList.indexOf((parentInput));
|
||||||
let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
|
let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
|
||||||
for (let i = curIdx; i >= 0; i--) {
|
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
|
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||||
// curLocation that don't make sense.
|
// curLocation that don't make sense.
|
||||||
if ((curLocation as IASTNodeLocationWithBlock).getSourceBlock) {
|
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
|
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||||
// curLocation that don't make sense.
|
// curLocation that don't make sense.
|
||||||
const curLocationAsBlock = curLocation as Block;
|
const curLocationAsBlock = curLocation as Block;
|
||||||
if (!curLocationAsBlock || curLocationAsBlock.disposed) {
|
if (!curLocationAsBlock || curLocationAsBlock.isDeadOrDying()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const curRoot = curLocationAsBlock.getRootBlock();
|
const curRoot = curLocationAsBlock.getRootBlock();
|
||||||
@@ -531,7 +542,12 @@ export class ASTNode {
|
|||||||
}
|
}
|
||||||
case ASTNode.types.FIELD: {
|
case ASTNode.types.FIELD: {
|
||||||
const field = this.location_ as 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: {
|
case ASTNode.types.INPUT: {
|
||||||
const connection = this.location_ as Connection;
|
const connection = this.location_ as Connection;
|
||||||
|
|||||||
10
core/main.js
10
core/main.js
@@ -86,13 +86,13 @@ Object.defineProperties(Blockly, {
|
|||||||
mainWorkspace: {
|
mainWorkspace: {
|
||||||
set: function(x) {
|
set: function(x) {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.mainWorkspace', 'August 2022', 'September 2022',
|
'Blockly.mainWorkspace', 'version 9', 'version 10',
|
||||||
'Blockly.getMainWorkspace');
|
'Blockly.getMainWorkspace');
|
||||||
common.setMainWorkspace(x);
|
common.setMainWorkspace(x);
|
||||||
},
|
},
|
||||||
get: function() {
|
get: function() {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.mainWorkspace', 'August 2022', 'September 2022',
|
'Blockly.mainWorkspace', 'version 9', 'version 10',
|
||||||
'Blockly.getMainWorkspace');
|
'Blockly.getMainWorkspace');
|
||||||
return common.getMainWorkspace();
|
return common.getMainWorkspace();
|
||||||
},
|
},
|
||||||
@@ -130,14 +130,12 @@ Object.defineProperties(Blockly, {
|
|||||||
selected: {
|
selected: {
|
||||||
get: function() {
|
get: function() {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.selected', 'August 2022', 'September 2022',
|
'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected');
|
||||||
'Blockly.getSelected');
|
|
||||||
return common.getSelected();
|
return common.getSelected();
|
||||||
},
|
},
|
||||||
set: function(newSelection) {
|
set: function(newSelection) {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.selected', 'August 2022', 'September 2022',
|
'Blockly.selected', 'version 9', 'version 10', 'Blockly.getSelected');
|
||||||
'Blockly.getSelected');
|
|
||||||
common.setSelected(newSelection);
|
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 type {MenuItem} from './menuitem.js';
|
||||||
import * as aria from './utils/aria.js';
|
import * as aria from './utils/aria.js';
|
||||||
import {Coordinate} from './utils/coordinate.js';
|
import {Coordinate} from './utils/coordinate.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import {KeyCodes} from './utils/keycodes.js';
|
import {KeyCodes} from './utils/keycodes.js';
|
||||||
import type {Size} from './utils/size.js';
|
import type {Size} from './utils/size.js';
|
||||||
import * as style from './utils/style.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
|
* (Nulls are never in the array, but typing the array as nullable prevents
|
||||||
* the compiler from objecting to .indexOf(null))
|
* 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
|
* 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.
|
* This is the element that we will listen to the real focus events on.
|
||||||
* A value of null means no menu item is highlighted.
|
* A value of null means no menu item is highlighted.
|
||||||
*/
|
*/
|
||||||
private highlightedItem_: MenuItem|null = null;
|
private highlightedItem: MenuItem|null = null;
|
||||||
|
|
||||||
/** Mouse over event data. */
|
/** Mouse over event data. */
|
||||||
private mouseOverHandler_: browserEvents.Data|null = null;
|
private mouseOverHandler: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Click event data. */
|
/** Click event data. */
|
||||||
private clickHandler_: browserEvents.Data|null = null;
|
private clickHandler: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Mouse enter event data. */
|
/** Mouse enter event data. */
|
||||||
private mouseEnterHandler_: browserEvents.Data|null = null;
|
private mouseEnterHandler: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Mouse leave event data. */
|
/** Mouse leave event data. */
|
||||||
private mouseLeaveHandler_: browserEvents.Data|null = null;
|
private mouseLeaveHandler: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** Key down event data. */
|
/** Key down event data. */
|
||||||
private onKeyDownHandler_: browserEvents.Data|null = null;
|
private onKeyDownHandler: browserEvents.Data|null = null;
|
||||||
|
|
||||||
/** The menu's root DOM element. */
|
/** The menu's root DOM element. */
|
||||||
private element_: HTMLDivElement|null = null;
|
private element: HTMLDivElement|null = null;
|
||||||
|
|
||||||
/** ARIA name for this menu. */
|
/** ARIA name for this menu. */
|
||||||
private roleName_: aria.Role|null = null;
|
private roleName: aria.Role|null = null;
|
||||||
|
|
||||||
/** Constructs a new Menu instance. */
|
/** Constructs a new Menu instance. */
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@@ -78,7 +79,7 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
addChild(menuItem: MenuItem) {
|
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.
|
// goog-menu is deprecated, use blocklyMenu. May 2020.
|
||||||
element.className = 'blocklyMenu goog-menu blocklyNonSelectable';
|
element.className = 'blocklyMenu goog-menu blocklyNonSelectable';
|
||||||
element.tabIndex = 0;
|
element.tabIndex = 0;
|
||||||
if (this.roleName_) {
|
if (this.roleName) {
|
||||||
aria.setRole(element, this.roleName_);
|
aria.setRole(element, this.roleName);
|
||||||
}
|
}
|
||||||
this.element_ = element;
|
this.element = element;
|
||||||
|
|
||||||
// Add menu items.
|
// 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());
|
element.appendChild(menuItem.createDom());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add event handlers.
|
// Add event handlers.
|
||||||
this.mouseOverHandler_ = browserEvents.conditionalBind(
|
this.mouseOverHandler = browserEvents.conditionalBind(
|
||||||
element, 'mouseover', this, this.handleMouseOver_, true);
|
element, 'mouseover', this, this.handleMouseOver, true);
|
||||||
this.clickHandler_ = browserEvents.conditionalBind(
|
this.clickHandler = browserEvents.conditionalBind(
|
||||||
element, 'click', this, this.handleClick_, true);
|
element, 'click', this, this.handleClick, true);
|
||||||
this.mouseEnterHandler_ = browserEvents.conditionalBind(
|
this.mouseEnterHandler = browserEvents.conditionalBind(
|
||||||
element, 'mouseenter', this, this.handleMouseEnter_, true);
|
element, 'mouseenter', this, this.handleMouseEnter, true);
|
||||||
this.mouseLeaveHandler_ = browserEvents.conditionalBind(
|
this.mouseLeaveHandler = browserEvents.conditionalBind(
|
||||||
element, 'mouseleave', this, this.handleMouseLeave_, true);
|
element, 'mouseleave', this, this.handleMouseLeave, true);
|
||||||
this.onKeyDownHandler_ = browserEvents.conditionalBind(
|
this.onKeyDownHandler = browserEvents.conditionalBind(
|
||||||
element, 'keydown', this, this.handleKeyEvent_);
|
element, 'keydown', this, this.handleKeyEvent);
|
||||||
|
|
||||||
container.appendChild(element);
|
container.appendChild(element);
|
||||||
return element;
|
return element;
|
||||||
@@ -125,7 +126,7 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getElement(): HTMLDivElement|null {
|
getElement(): HTMLDivElement|null {
|
||||||
return this.element_;
|
return this.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -137,16 +138,16 @@ export class Menu {
|
|||||||
const el = this.getElement();
|
const el = this.getElement();
|
||||||
if (el) {
|
if (el) {
|
||||||
el.focus({preventScroll: true});
|
el.focus({preventScroll: true});
|
||||||
el.classList.add('blocklyFocused');
|
dom.addClass(el, 'blocklyFocused');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Blur the menu element. */
|
/** Blur the menu element. */
|
||||||
private blur_() {
|
private blur() {
|
||||||
const el = this.getElement();
|
const el = this.getElement();
|
||||||
if (el) {
|
if (el) {
|
||||||
el.blur();
|
el.blur();
|
||||||
el.classList.remove('blocklyFocused');
|
dom.removeClass(el, 'blocklyFocused');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,38 +158,38 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setRole(roleName: aria.Role) {
|
setRole(roleName: aria.Role) {
|
||||||
this.roleName_ = roleName;
|
this.roleName = roleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dispose of this menu. */
|
/** Dispose of this menu. */
|
||||||
dispose() {
|
dispose() {
|
||||||
// Remove event handlers.
|
// Remove event handlers.
|
||||||
if (this.mouseOverHandler_) {
|
if (this.mouseOverHandler) {
|
||||||
browserEvents.unbind(this.mouseOverHandler_);
|
browserEvents.unbind(this.mouseOverHandler);
|
||||||
this.mouseOverHandler_ = null;
|
this.mouseOverHandler = null;
|
||||||
}
|
}
|
||||||
if (this.clickHandler_) {
|
if (this.clickHandler) {
|
||||||
browserEvents.unbind(this.clickHandler_);
|
browserEvents.unbind(this.clickHandler);
|
||||||
this.clickHandler_ = null;
|
this.clickHandler = null;
|
||||||
}
|
}
|
||||||
if (this.mouseEnterHandler_) {
|
if (this.mouseEnterHandler) {
|
||||||
browserEvents.unbind(this.mouseEnterHandler_);
|
browserEvents.unbind(this.mouseEnterHandler);
|
||||||
this.mouseEnterHandler_ = null;
|
this.mouseEnterHandler = null;
|
||||||
}
|
}
|
||||||
if (this.mouseLeaveHandler_) {
|
if (this.mouseLeaveHandler) {
|
||||||
browserEvents.unbind(this.mouseLeaveHandler_);
|
browserEvents.unbind(this.mouseLeaveHandler);
|
||||||
this.mouseLeaveHandler_ = null;
|
this.mouseLeaveHandler = null;
|
||||||
}
|
}
|
||||||
if (this.onKeyDownHandler_) {
|
if (this.onKeyDownHandler) {
|
||||||
browserEvents.unbind(this.onKeyDownHandler_);
|
browserEvents.unbind(this.onKeyDownHandler);
|
||||||
this.onKeyDownHandler_ = null;
|
this.onKeyDownHandler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove menu items.
|
// 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();
|
menuItem.dispose();
|
||||||
}
|
}
|
||||||
this.element_ = null;
|
this.element = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Child component management.
|
// Child component management.
|
||||||
@@ -200,7 +201,7 @@ export class Menu {
|
|||||||
* @param elem DOM element whose owner is to be returned.
|
* @param elem DOM element whose owner is to be returned.
|
||||||
* @returns Menu item for which the DOM element belongs to.
|
* @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();
|
const menuElem = this.getElement();
|
||||||
// Node might be the menu border (resulting in no associated menu item), or
|
// 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.
|
// a menu item's div, or some element within the menu item.
|
||||||
@@ -210,7 +211,7 @@ export class Menu {
|
|||||||
while (currentElement && currentElement !== menuElem) {
|
while (currentElement && currentElement !== menuElem) {
|
||||||
if (currentElement.classList.contains('blocklyMenuItem')) {
|
if (currentElement.classList.contains('blocklyMenuItem')) {
|
||||||
// Having found a menu item's div, locate that menu item in this menu.
|
// 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) {
|
if (menuItem.getElement() === currentElement) {
|
||||||
return menuItem;
|
return menuItem;
|
||||||
}
|
}
|
||||||
@@ -230,14 +231,14 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setHighlighted(item: MenuItem|null) {
|
setHighlighted(item: MenuItem|null) {
|
||||||
const currentHighlighted = this.highlightedItem_;
|
const currentHighlighted = this.highlightedItem;
|
||||||
if (currentHighlighted) {
|
if (currentHighlighted) {
|
||||||
currentHighlighted.setHighlighted(false);
|
currentHighlighted.setHighlighted(false);
|
||||||
this.highlightedItem_ = null;
|
this.highlightedItem = null;
|
||||||
}
|
}
|
||||||
if (item) {
|
if (item) {
|
||||||
item.setHighlighted(true);
|
item.setHighlighted(true);
|
||||||
this.highlightedItem_ = item;
|
this.highlightedItem = item;
|
||||||
// Bring the highlighted item into view. This has no effect if the menu is
|
// Bring the highlighted item into view. This has no effect if the menu is
|
||||||
// not scrollable.
|
// not scrollable.
|
||||||
const el = this.getElement() as Element;
|
const el = this.getElement() as Element;
|
||||||
@@ -254,10 +255,10 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
highlightNext() {
|
highlightNext() {
|
||||||
const index = this.highlightedItem_ ?
|
const index = this.highlightedItem ?
|
||||||
this.menuItems_.indexOf(this.highlightedItem_) :
|
this.menuItems.indexOf(this.highlightedItem) :
|
||||||
-1;
|
-1;
|
||||||
this.highlightHelper_(index, 1);
|
this.highlightHelper(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,20 +268,20 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
highlightPrevious() {
|
highlightPrevious() {
|
||||||
const index = this.highlightedItem_ ?
|
const index = this.highlightedItem ?
|
||||||
this.menuItems_.indexOf(this.highlightedItem_) :
|
this.menuItems.indexOf(this.highlightedItem) :
|
||||||
-1;
|
-1;
|
||||||
this.highlightHelper_(index < 0 ? this.menuItems_.length : index, -1);
|
this.highlightHelper(index < 0 ? this.menuItems.length : index, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Highlights the first highlightable item. */
|
/** Highlights the first highlightable item. */
|
||||||
private highlightFirst_() {
|
private highlightFirst() {
|
||||||
this.highlightHelper_(-1, 1);
|
this.highlightHelper(-1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Highlights the last highlightable item. */
|
/** Highlights the last highlightable item. */
|
||||||
private highlightLast_() {
|
private highlightLast() {
|
||||||
this.highlightHelper_(this.menuItems_.length, -1);
|
this.highlightHelper(this.menuItems.length, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,10 +291,10 @@ export class Menu {
|
|||||||
* @param startIndex Start index.
|
* @param startIndex Start index.
|
||||||
* @param delta Step direction: 1 to go down, -1 to go up.
|
* @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 index = startIndex + delta;
|
||||||
let menuItem;
|
let menuItem;
|
||||||
while (menuItem = this.menuItems_[index]) {
|
while (menuItem = this.menuItems[index]) {
|
||||||
if (menuItem.isEnabled()) {
|
if (menuItem.isEnabled()) {
|
||||||
this.setHighlighted(menuItem);
|
this.setHighlighted(menuItem);
|
||||||
break;
|
break;
|
||||||
@@ -309,12 +310,12 @@ export class Menu {
|
|||||||
*
|
*
|
||||||
* @param e Mouse event to handle.
|
* @param e Mouse event to handle.
|
||||||
*/
|
*/
|
||||||
private handleMouseOver_(e: Event) {
|
private handleMouseOver(e: Event) {
|
||||||
const menuItem = this.getMenuItem_(e.target as Element);
|
const menuItem = this.getMenuItem(e.target as Element);
|
||||||
|
|
||||||
if (menuItem) {
|
if (menuItem) {
|
||||||
if (menuItem.isEnabled()) {
|
if (menuItem.isEnabled()) {
|
||||||
if (this.highlightedItem_ !== menuItem) {
|
if (this.highlightedItem !== menuItem) {
|
||||||
this.setHighlighted(menuItem);
|
this.setHighlighted(menuItem);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -328,7 +329,7 @@ export class Menu {
|
|||||||
*
|
*
|
||||||
* @param e Click event to handle.
|
* @param e Click event to handle.
|
||||||
*/
|
*/
|
||||||
private handleClick_(e: Event) {
|
private handleClick(e: Event) {
|
||||||
const oldCoords = this.openingCoords;
|
const oldCoords = this.openingCoords;
|
||||||
// Clear out the saved opening coords immediately so they're not used twice.
|
// Clear out the saved opening coords immediately so they're not used twice.
|
||||||
this.openingCoords = null;
|
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) {
|
if (menuItem) {
|
||||||
menuItem.performAction();
|
menuItem.performAction();
|
||||||
}
|
}
|
||||||
@@ -361,7 +362,7 @@ export class Menu {
|
|||||||
*
|
*
|
||||||
* @param _e Mouse event to handle.
|
* @param _e Mouse event to handle.
|
||||||
*/
|
*/
|
||||||
private handleMouseEnter_(_e: Event) {
|
private handleMouseEnter(_e: Event) {
|
||||||
this.focus();
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,9 +371,9 @@ export class Menu {
|
|||||||
*
|
*
|
||||||
* @param _e Mouse event to handle.
|
* @param _e Mouse event to handle.
|
||||||
*/
|
*/
|
||||||
private handleMouseLeave_(_e: Event) {
|
private handleMouseLeave(_e: Event) {
|
||||||
if (this.getElement()) {
|
if (this.getElement()) {
|
||||||
this.blur_();
|
this.blur();
|
||||||
this.setHighlighted(null);
|
this.setHighlighted(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,27 +387,20 @@ export class Menu {
|
|||||||
*
|
*
|
||||||
* @param e Key event to handle.
|
* @param e Key event to handle.
|
||||||
*/
|
*/
|
||||||
private handleKeyEvent_(e: Event) {
|
private handleKeyEvent(e: Event) {
|
||||||
if (!this.menuItems_.length) {
|
if (!this.menuItems.length) {
|
||||||
// Empty menu.
|
// Empty menu.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// AnyDuringMigration because: Property 'altKey' does not exist on type
|
const keyboardEvent = e as KeyboardEvent;
|
||||||
// 'Event'. AnyDuringMigration because: Property 'metaKey' does not exist
|
if (keyboardEvent.shiftKey || keyboardEvent.ctrlKey ||
|
||||||
// on type 'Event'. AnyDuringMigration because: Property 'ctrlKey' does not
|
keyboardEvent.metaKey || keyboardEvent.altKey) {
|
||||||
// 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) {
|
|
||||||
// Do not handle the key event if any modifier key is pressed.
|
// Do not handle the key event if any modifier key is pressed.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlighted = this.highlightedItem_;
|
const highlighted = this.highlightedItem;
|
||||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
switch (keyboardEvent.keyCode) {
|
||||||
// 'Event'.
|
|
||||||
switch ((e as AnyDuringMigration).keyCode) {
|
|
||||||
case KeyCodes.ENTER:
|
case KeyCodes.ENTER:
|
||||||
case KeyCodes.SPACE:
|
case KeyCodes.SPACE:
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
@@ -424,12 +418,12 @@ export class Menu {
|
|||||||
|
|
||||||
case KeyCodes.PAGE_UP:
|
case KeyCodes.PAGE_UP:
|
||||||
case KeyCodes.HOME:
|
case KeyCodes.HOME:
|
||||||
this.highlightFirst_();
|
this.highlightFirst();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCodes.PAGE_DOWN:
|
case KeyCodes.PAGE_DOWN:
|
||||||
case KeyCodes.END:
|
case KeyCodes.END:
|
||||||
this.highlightLast_();
|
this.highlightLast();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -448,10 +442,10 @@ export class Menu {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getSize(): Size {
|
getSize(): Size {
|
||||||
const menuDom = this.getElement();
|
const menuDom = this.getElement() as HTMLDivElement;
|
||||||
const menuSize = style.getSize(menuDom as Element);
|
const menuSize = style.getSize(menuDom);
|
||||||
// Recalculate height for the total content, not only box height.
|
// Recalculate height for the total content, not only box height.
|
||||||
menuSize.height = menuDom!.scrollHeight;
|
menuSize.height = menuDom.scrollHeight;
|
||||||
return menuSize;
|
return menuSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
|||||||
goog.declareModuleId('Blockly.MenuItem');
|
goog.declareModuleId('Blockly.MenuItem');
|
||||||
|
|
||||||
import * as aria from './utils/aria.js';
|
import * as aria from './utils/aria.js';
|
||||||
|
import * as dom from './utils/dom.js';
|
||||||
import * as idGenerator from './utils/idgenerator.js';
|
import * as idGenerator from './utils/idgenerator.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -23,28 +24,28 @@ import * as idGenerator from './utils/idgenerator.js';
|
|||||||
*/
|
*/
|
||||||
export class MenuItem {
|
export class MenuItem {
|
||||||
/** Is the menu item clickable, as opposed to greyed-out. */
|
/** Is the menu item clickable, as opposed to greyed-out. */
|
||||||
private enabled_ = true;
|
private enabled = true;
|
||||||
|
|
||||||
/** The DOM element for the menu item. */
|
/** 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. */
|
/** Whether the menu item is rendered right-to-left. */
|
||||||
private rightToLeft_ = false;
|
private rightToLeft = false;
|
||||||
|
|
||||||
/** ARIA name for this menu. */
|
/** ARIA name for this menu. */
|
||||||
private roleName_: aria.Role|null = null;
|
private roleName: aria.Role|null = null;
|
||||||
|
|
||||||
/** Is this menu item checkable. */
|
/** Is this menu item checkable. */
|
||||||
private checkable_ = false;
|
private checkable = false;
|
||||||
|
|
||||||
/** Is this menu item currently checked. */
|
/** Is this menu item currently checked. */
|
||||||
private checked_ = false;
|
private checked = false;
|
||||||
|
|
||||||
/** Is this menu item currently highlighted. */
|
/** Is this menu item currently highlighted. */
|
||||||
private highlight_ = false;
|
private highlight = false;
|
||||||
|
|
||||||
/** Bound function to call when this menu item is clicked. */
|
/** 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
|
* @param content Text caption to display as the content of the item, or a
|
||||||
@@ -63,22 +64,22 @@ export class MenuItem {
|
|||||||
createDom(): Element {
|
createDom(): Element {
|
||||||
const element = (document.createElement('div'));
|
const element = (document.createElement('div'));
|
||||||
element.id = idGenerator.getNextUniqueId();
|
element.id = idGenerator.getNextUniqueId();
|
||||||
this.element_ = element;
|
this.element = element;
|
||||||
|
|
||||||
// Set class and style
|
// Set class and style
|
||||||
// goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020.
|
// goog-menuitem* is deprecated, use blocklyMenuItem*. May 2020.
|
||||||
element.className = 'blocklyMenuItem goog-menuitem ' +
|
element.className = 'blocklyMenuItem goog-menuitem ' +
|
||||||
(this.enabled_ ? '' :
|
(this.enabled ? '' :
|
||||||
'blocklyMenuItemDisabled goog-menuitem-disabled ') +
|
'blocklyMenuItemDisabled goog-menuitem-disabled ') +
|
||||||
(this.checked_ ? 'blocklyMenuItemSelected goog-option-selected ' : '') +
|
(this.checked ? 'blocklyMenuItemSelected goog-option-selected ' : '') +
|
||||||
(this.highlight_ ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' :
|
(this.highlight ? 'blocklyMenuItemHighlight goog-menuitem-highlight ' :
|
||||||
'') +
|
'') +
|
||||||
(this.rightToLeft_ ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : '');
|
(this.rightToLeft ? 'blocklyMenuItemRtl goog-menuitem-rtl ' : '');
|
||||||
|
|
||||||
const content = (document.createElement('div'));
|
const content = (document.createElement('div'));
|
||||||
content.className = 'blocklyMenuItemContent goog-menuitem-content';
|
content.className = 'blocklyMenuItemContent goog-menuitem-content';
|
||||||
// Add a checkbox for checkable menu items.
|
// Add a checkbox for checkable menu items.
|
||||||
if (this.checkable_) {
|
if (this.checkable) {
|
||||||
const checkbox = (document.createElement('div'));
|
const checkbox = (document.createElement('div'));
|
||||||
checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox';
|
checkbox.className = 'blocklyMenuItemCheckbox goog-menuitem-checkbox';
|
||||||
content.appendChild(checkbox);
|
content.appendChild(checkbox);
|
||||||
@@ -92,20 +93,19 @@ export class MenuItem {
|
|||||||
element.appendChild(content);
|
element.appendChild(content);
|
||||||
|
|
||||||
// Initialize ARIA role and state.
|
// Initialize ARIA role and state.
|
||||||
if (this.roleName_) {
|
if (this.roleName) {
|
||||||
aria.setRole(element, this.roleName_);
|
aria.setRole(element, this.roleName);
|
||||||
}
|
}
|
||||||
aria.setState(
|
aria.setState(
|
||||||
element, aria.State.SELECTED,
|
element, aria.State.SELECTED, this.checkable && this.checked || false);
|
||||||
this.checkable_ && this.checked_ || false);
|
aria.setState(element, aria.State.DISABLED, !this.enabled);
|
||||||
aria.setState(element, aria.State.DISABLED, !this.enabled_);
|
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Dispose of this menu item. */
|
/** Dispose of this menu item. */
|
||||||
dispose() {
|
dispose() {
|
||||||
this.element_ = null;
|
this.element = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,7 +115,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getElement(): Element|null {
|
getElement(): Element|null {
|
||||||
return this.element_;
|
return this.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,7 +125,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getId(): string {
|
getId(): string {
|
||||||
return this.element_!.id;
|
return this.element!.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,7 +145,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setRightToLeft(rtl: boolean) {
|
setRightToLeft(rtl: boolean) {
|
||||||
this.rightToLeft_ = rtl;
|
this.rightToLeft = rtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,7 +155,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setRole(roleName: aria.Role) {
|
setRole(roleName: aria.Role) {
|
||||||
this.roleName_ = roleName;
|
this.roleName = roleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -166,7 +166,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setCheckable(checkable: boolean) {
|
setCheckable(checkable: boolean) {
|
||||||
this.checkable_ = checkable;
|
this.checkable = checkable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,7 +176,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setChecked(checked: boolean) {
|
setChecked(checked: boolean) {
|
||||||
this.checked_ = checked;
|
this.checked = checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -186,7 +186,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setHighlighted(highlight: boolean) {
|
setHighlighted(highlight: boolean) {
|
||||||
this.highlight_ = highlight;
|
this.highlight = highlight;
|
||||||
|
|
||||||
const el = this.getElement();
|
const el = this.getElement();
|
||||||
if (el && this.isEnabled()) {
|
if (el && this.isEnabled()) {
|
||||||
@@ -195,11 +195,11 @@ export class MenuItem {
|
|||||||
const name = 'blocklyMenuItemHighlight';
|
const name = 'blocklyMenuItemHighlight';
|
||||||
const nameDep = 'goog-menuitem-highlight';
|
const nameDep = 'goog-menuitem-highlight';
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
el.classList.add(name);
|
dom.addClass(el, name);
|
||||||
el.classList.add(nameDep);
|
dom.addClass(el, nameDep);
|
||||||
} else {
|
} else {
|
||||||
el.classList.remove(name);
|
dom.removeClass(el, name);
|
||||||
el.classList.remove(nameDep);
|
dom.removeClass(el, nameDep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +211,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
isEnabled(): boolean {
|
isEnabled(): boolean {
|
||||||
return this.enabled_;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,7 +221,7 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
setEnabled(enabled: boolean) {
|
setEnabled(enabled: boolean) {
|
||||||
this.enabled_ = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,8 +231,8 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
performAction() {
|
performAction() {
|
||||||
if (this.isEnabled() && this.actionHandler_) {
|
if (this.isEnabled() && this.actionHandler) {
|
||||||
this.actionHandler_(this);
|
this.actionHandler(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +245,6 @@ export class MenuItem {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
onAction(fn: (p1: MenuItem) => void, obj: object) {
|
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. */
|
/** A dictionary of localised messages. */
|
||||||
export const Msg: {[key: string]: string} = Object.create(null);
|
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 {Svg} from './utils/svg.js';
|
||||||
import * as toolbox from './utils/toolbox.js';
|
import * as toolbox from './utils/toolbox.js';
|
||||||
import * as xml from './utils/xml.js';
|
import * as xml from './utils/xml.js';
|
||||||
|
import * as deprecation from './utils/deprecation.js';
|
||||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -77,8 +78,14 @@ export class Mutator extends Icon {
|
|||||||
private updateWorkspacePid_: ReturnType<typeof setTimeout>|null = null;
|
private updateWorkspacePid_: ReturnType<typeof setTimeout>|null = null;
|
||||||
|
|
||||||
/** @param quarkNames List of names of sub-blocks for flyout. */
|
/** @param quarkNames List of names of sub-blocks for flyout. */
|
||||||
constructor(block: BlockSvg, quarkNames: string[]) {
|
constructor(quarkNames: string[], block?: BlockSvg) {
|
||||||
super(block);
|
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;
|
this.quarkNames_ = quarkNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +152,7 @@ export class Mutator extends Icon {
|
|||||||
* @param e Mouse click event.
|
* @param e Mouse click event.
|
||||||
*/
|
*/
|
||||||
protected override iconClick_(e: MouseEvent) {
|
protected override iconClick_(e: MouseEvent) {
|
||||||
if (this.block_.isEditable()) {
|
if (this.getBlock().isEditable()) {
|
||||||
super.iconClick_(e);
|
super.iconClick_(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,19 +182,20 @@ export class Mutator extends Icon {
|
|||||||
} else {
|
} else {
|
||||||
quarkXml = null;
|
quarkXml = null;
|
||||||
}
|
}
|
||||||
|
const block = this.getBlock();
|
||||||
const workspaceOptions = new Options(({
|
const workspaceOptions = new Options(({
|
||||||
// If you want to enable disabling, also remove the
|
// If you want to enable disabling, also remove the
|
||||||
// event filter from workspaceChanged_ .
|
// event filter from workspaceChanged_ .
|
||||||
'disable': false,
|
'disable': false,
|
||||||
'parentWorkspace': this.block_.workspace,
|
'parentWorkspace': block.workspace,
|
||||||
'media': this.block_.workspace.options.pathToMedia,
|
'media': block.workspace.options.pathToMedia,
|
||||||
'rtl': this.block_.RTL,
|
'rtl': block.RTL,
|
||||||
'horizontalLayout': false,
|
'horizontalLayout': false,
|
||||||
'renderer': this.block_.workspace.options.renderer,
|
'renderer': block.workspace.options.renderer,
|
||||||
'rendererOverrides': this.block_.workspace.options.rendererOverrides,
|
'rendererOverrides': block.workspace.options.rendererOverrides,
|
||||||
} as BlocklyOptions));
|
} as BlocklyOptions));
|
||||||
workspaceOptions.toolboxPosition =
|
workspaceOptions.toolboxPosition =
|
||||||
this.block_.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT;
|
block.RTL ? toolbox.Position.RIGHT : toolbox.Position.LEFT;
|
||||||
const hasFlyout = !!quarkXml;
|
const hasFlyout = !!quarkXml;
|
||||||
if (hasFlyout) {
|
if (hasFlyout) {
|
||||||
workspaceOptions.languageTree = toolbox.convertToolboxDefToJson(quarkXml);
|
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. */
|
/** Add or remove the UI indicating if this icon may be clicked or not. */
|
||||||
override updateEditable() {
|
override updateEditable() {
|
||||||
super.updateEditable();
|
super.updateEditable();
|
||||||
if (!this.block_.isInFlyout) {
|
if (!this.getBlock().isInFlyout) {
|
||||||
if (this.block_.isEditable()) {
|
if (this.getBlock().isEditable()) {
|
||||||
if (this.iconGroup_) {
|
if (this.iconGroup_) {
|
||||||
this.iconGroup_.classList.remove('blocklyIconGroupReadonly');
|
dom.removeClass(this.iconGroup_, 'blocklyIconGroupReadonly');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Close any mutator bubble. Icon is not clickable.
|
// Close any mutator bubble. Icon is not clickable.
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
if (this.iconGroup_) {
|
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);
|
height = Math.max(height, flyoutScrollMetrics.height + 20);
|
||||||
width += flyout.getWidth();
|
width += flyout.getWidth();
|
||||||
}
|
}
|
||||||
if (this.block_.RTL) {
|
if (this.getBlock().RTL) {
|
||||||
width = -workspaceSize.x;
|
width = -workspaceSize.x;
|
||||||
}
|
}
|
||||||
width += doubleBorderWidth * 3;
|
width += doubleBorderWidth * 3;
|
||||||
@@ -275,7 +283,7 @@ export class Mutator extends Icon {
|
|||||||
this.workspaceWidth_, this.workspaceHeight_);
|
this.workspaceWidth_, this.workspaceHeight_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.block_.RTL) {
|
if (this.getBlock().RTL) {
|
||||||
// Scroll the workspace to always left-align.
|
// Scroll the workspace to always left-align.
|
||||||
const translation = 'translate(' + this.workspaceWidth_ + ',0)';
|
const translation = 'translate(' + this.workspaceWidth_ + ',0)';
|
||||||
this.workspace_!.getCanvas().setAttribute('transform', translation);
|
this.workspace_!.getCanvas().setAttribute('transform', translation);
|
||||||
@@ -300,16 +308,16 @@ export class Mutator extends Icon {
|
|||||||
// No change.
|
// No change.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const block = this.getBlock();
|
||||||
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
||||||
this.block_, visible, 'mutator'));
|
block, visible, 'mutator'));
|
||||||
if (visible) {
|
if (visible) {
|
||||||
// Create the bubble.
|
// Create the bubble.
|
||||||
this.bubble_ = new Bubble(
|
this.bubble_ = new Bubble(
|
||||||
(this.block_.workspace as WorkspaceSvg), this.createEditor_(),
|
block.workspace, this.createEditor_(), block.pathObject.svgPath,
|
||||||
this.block_.pathObject.svgPath, (this.iconXY_ as Coordinate), null,
|
(this.iconXY_ as Coordinate), null, null);
|
||||||
null);
|
|
||||||
// Expose this mutator's block's ID on its top-level SVG group.
|
// 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));
|
this.bubble_.registerMoveEvent(this.onBubbleMove_.bind(this));
|
||||||
const tree = this.workspace_!.options.languageTree;
|
const tree = this.workspace_!.options.languageTree;
|
||||||
const flyout = this.workspace_!.getFlyout();
|
const flyout = this.workspace_!.getFlyout();
|
||||||
@@ -318,7 +326,7 @@ export class Mutator extends Icon {
|
|||||||
flyout!.show(tree);
|
flyout!.show(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rootBlock_ = this.block_!.decompose!(this.workspace_!)!;
|
this.rootBlock_ = block.decompose!(this.workspace_!)!;
|
||||||
const blocks = this.rootBlock_!.getDescendants(false);
|
const blocks = this.rootBlock_!.getDescendants(false);
|
||||||
for (let i = 0, child; child = blocks[i]; i++) {
|
for (let i = 0, child; child = blocks[i]; i++) {
|
||||||
child.render();
|
child.render();
|
||||||
@@ -335,20 +343,21 @@ export class Mutator extends Icon {
|
|||||||
margin = 16;
|
margin = 16;
|
||||||
x = margin;
|
x = margin;
|
||||||
}
|
}
|
||||||
if (this.block_.RTL) {
|
if (block.RTL) {
|
||||||
x = -x;
|
x = -x;
|
||||||
}
|
}
|
||||||
this.rootBlock_!.moveBy(x, margin);
|
this.rootBlock_!.moveBy(x, margin);
|
||||||
// Save the initial connections, then listen for further changes.
|
// Save the initial connections, then listen for further changes.
|
||||||
if (this.block_.saveConnections) {
|
if (block.saveConnections) {
|
||||||
const thisRootBlock = this.rootBlock_;
|
const thisRootBlock = this.rootBlock_;
|
||||||
this.block_.saveConnections(thisRootBlock);
|
block.saveConnections(thisRootBlock);
|
||||||
this.sourceListener_ = () => {
|
this.sourceListener_ = () => {
|
||||||
if (this.block_ && this.block_.saveConnections) {
|
const currentBlock = this.getBlock();
|
||||||
this.block_.saveConnections(thisRootBlock);
|
if (currentBlock.saveConnections) {
|
||||||
|
currentBlock.saveConnections(thisRootBlock);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.block_.workspace.addChangeListener(this.sourceListener_);
|
block.workspace.addChangeListener(this.sourceListener_);
|
||||||
}
|
}
|
||||||
this.resizeBubble_();
|
this.resizeBubble_();
|
||||||
// When the mutator's workspace changes, update the source block.
|
// When the mutator's workspace changes, update the source block.
|
||||||
@@ -367,7 +376,7 @@ export class Mutator extends Icon {
|
|||||||
this.workspaceWidth_ = 0;
|
this.workspaceWidth_ = 0;
|
||||||
this.workspaceHeight_ = 0;
|
this.workspaceHeight_ = 0;
|
||||||
if (this.sourceListener_) {
|
if (this.sourceListener_) {
|
||||||
this.block_.workspace.removeChangeListener(this.sourceListener_);
|
block.workspace.removeChangeListener(this.sourceListener_);
|
||||||
this.sourceListener_ = null;
|
this.sourceListener_ = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,7 +447,7 @@ export class Mutator extends Icon {
|
|||||||
if (!existingGroup) {
|
if (!existingGroup) {
|
||||||
eventUtils.setGroup(true);
|
eventUtils.setGroup(true);
|
||||||
}
|
}
|
||||||
const block = this.block_ as BlockSvg;
|
const block = this.getBlock();
|
||||||
const oldExtraState = BlockChange.getExtraBlockState_(block);
|
const oldExtraState = BlockChange.getExtraBlockState_(block);
|
||||||
|
|
||||||
// Switch off rendering while the source block is rebuilt.
|
// Switch off rendering while the source block is rebuilt.
|
||||||
@@ -482,7 +491,7 @@ export class Mutator extends Icon {
|
|||||||
|
|
||||||
/** Dispose of this mutator. */
|
/** Dispose of this mutator. */
|
||||||
override dispose() {
|
override dispose() {
|
||||||
this.block_.mutator = null;
|
this.getBlock().mutator = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ import * as common from './common.js';
|
|||||||
import type {Abstract} from './events/events_abstract.js';
|
import type {Abstract} from './events/events_abstract.js';
|
||||||
import type {BubbleOpen} from './events/events_bubble_open.js';
|
import type {BubbleOpen} from './events/events_bubble_open.js';
|
||||||
import * as eventUtils from './events/utils.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 {Msg} from './msg.js';
|
||||||
import {Names} from './names.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 utilsXml from './utils/xml.js';
|
||||||
import * as Variables from './variables.js';
|
import * as Variables from './variables.js';
|
||||||
import type {Workspace} from './workspace.js';
|
import type {Workspace} from './workspace.js';
|
||||||
@@ -180,14 +183,19 @@ export function isNameUsed(
|
|||||||
* @alias Blockly.Procedures.rename
|
* @alias Blockly.Procedures.rename
|
||||||
*/
|
*/
|
||||||
export function rename(this: Field, name: string): string {
|
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.
|
// Strip leading and trailing whitespace. Beyond this, all names are legal.
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
|
|
||||||
const legalName = findLegalName(name, (this.getSourceBlock()));
|
const legalName = findLegalName(name, block);
|
||||||
const oldName = this.getValue();
|
const oldName = this.getValue();
|
||||||
if (oldName !== name && oldName !== legalName) {
|
if (oldName !== name && oldName !== legalName) {
|
||||||
// Rename any callers.
|
// Rename any callers.
|
||||||
const blocks = this.getSourceBlock().workspace.getAllBlocks(false);
|
const blocks = block.workspace.getAllBlocks(false);
|
||||||
for (let i = 0; i < blocks.length; i++) {
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
// Assume it is a procedure so we can check.
|
// Assume it is a procedure so we can check.
|
||||||
const procedureBlock = blocks[i] as unknown as ProcedureBlock;
|
const procedureBlock = blocks[i] as unknown as ProcedureBlock;
|
||||||
@@ -445,3 +453,9 @@ export function getDefinition(name: string, workspace: Workspace): Block|null {
|
|||||||
}
|
}
|
||||||
return 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. */
|
/** Add highlighting around this connection. */
|
||||||
highlight() {
|
highlight() {
|
||||||
|
if (this.highlightPath) {
|
||||||
|
// This connection is already highlighted
|
||||||
|
return;
|
||||||
|
}
|
||||||
let steps;
|
let steps;
|
||||||
const sourceBlockSvg = (this.sourceBlock_);
|
const sourceBlockSvg = (this.sourceBlock_);
|
||||||
const renderConstants =
|
const renderConstants =
|
||||||
@@ -495,7 +499,7 @@ export class RenderedConnection extends Connection {
|
|||||||
* @returns List of connections.
|
* @returns List of connections.
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
override neighbours(maxLimit: number): Connection[] {
|
override neighbours(maxLimit: number): RenderedConnection[] {
|
||||||
return this.dbOpposite_.getNeighbours(this, maxLimit);
|
return this.dbOpposite_.getNeighbours(this, maxLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,23 +48,6 @@ import {MarkerSvg} from './marker_svg.js';
|
|||||||
import {PathObject} from './path_object.js';
|
import {PathObject} from './path_object.js';
|
||||||
import {Renderer} from './renderer.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.
|
* Registers a new renderer.
|
||||||
*
|
*
|
||||||
@@ -86,26 +69,12 @@ export function unregister(name: string) {
|
|||||||
registry.unregister(registry.Type.RENDERER, name);
|
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.
|
* Turn off the blocks debugger.
|
||||||
*
|
*
|
||||||
* @alias Blockly.blockRendering.stopDebugger
|
* @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
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function stopDebugger() {
|
export function stopDebugger() {
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ export class ConstantProvider {
|
|||||||
this.setComponentConstants_(theme);
|
this.setComponentConstants_(theme);
|
||||||
|
|
||||||
this.ADD_START_HATS =
|
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.
|
* @param blockStyle A full or partial block style object.
|
||||||
* @returns A full block style object, with all required properties populated.
|
* @returns A full block style object, with all required properties populated.
|
||||||
*/
|
*/
|
||||||
protected validatedBlockStyle_(blockStyle: {
|
protected validatedBlockStyle_(blockStyle: Partial<BlockStyle>): BlockStyle {
|
||||||
colourPrimary: string,
|
|
||||||
colourSecondary?: string,
|
|
||||||
colourTertiary?: string,
|
|
||||||
hat?: string
|
|
||||||
}): BlockStyle {
|
|
||||||
// Make a new object with all of the same properties.
|
// Make a new object with all of the same properties.
|
||||||
const valid = {} as BlockStyle;
|
const valid = {} as BlockStyle;
|
||||||
if (blockStyle) {
|
if (blockStyle) {
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ export function isDebuggerEnabled(): boolean {
|
|||||||
*/
|
*/
|
||||||
export function startDebugger() {
|
export function startDebugger() {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.blockRendering.debug.startDebugger()', 'February 2022',
|
'Blockly.blockRendering.debug.startDebugger()', 'version 8', 'version 10',
|
||||||
'September 2022',
|
|
||||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||||
useDebugger = true;
|
useDebugger = true;
|
||||||
}
|
}
|
||||||
@@ -54,8 +53,7 @@ export function startDebugger() {
|
|||||||
*/
|
*/
|
||||||
export function stopDebugger() {
|
export function stopDebugger() {
|
||||||
deprecation.warn(
|
deprecation.warn(
|
||||||
'Blockly.blockRendering.debug.stopDebugger()', 'February 2022',
|
'Blockly.blockRendering.debug.stopDebugger()', 'version 8', 'version 10',
|
||||||
'September 2022',
|
|
||||||
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
'the debug renderer in @blockly/dev-tools (See https://www.npmjs.com/package/@blockly/dev-tools.)');
|
||||||
useDebugger = false;
|
useDebugger = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export class Drawer {
|
|||||||
*/
|
*/
|
||||||
protected layoutField_(fieldInfo: Icon|Field) {
|
protected layoutField_(fieldInfo: Icon|Field) {
|
||||||
const svgGroup = Types.isField(fieldInfo) ?
|
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.
|
(fieldInfo as Icon).icon.iconGroup_!; // Never null in rendered case.
|
||||||
|
|
||||||
const yPos = fieldInfo.centerline - fieldInfo.height / 2;
|
const yPos = fieldInfo.centerline - fieldInfo.height / 2;
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ export class MarkerSvg {
|
|||||||
// Ensures the marker will be visible immediately after the move.
|
// Ensures the marker will be visible immediately after the move.
|
||||||
const animate = this.currentMarkerSvg!.childNodes[0];
|
const animate = this.currentMarkerSvg!.childNodes[0];
|
||||||
if (animate !== undefined) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
if (add) {
|
if (add) {
|
||||||
this.svgRoot.classList.add(className);
|
dom.addClass(this.svgRoot, className);
|
||||||
} else {
|
} else {
|
||||||
this.svgRoot.classList.remove(className);
|
dom.removeClass(this.svgRoot, className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ export class PathObject extends BasePathObject {
|
|||||||
override applyColour(block: BlockSvg) {
|
override applyColour(block: BlockSvg) {
|
||||||
this.svgPathLight.style.display = '';
|
this.svgPathLight.style.display = '';
|
||||||
this.svgPathDark.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.svgPathLight.setAttribute('stroke', this.style.colourTertiary);
|
||||||
this.svgPathDark.setAttribute('fill', this.colourDark);
|
this.svgPathDark.setAttribute('fill', this.colourDark);
|
||||||
|
|
||||||
@@ -118,6 +123,11 @@ export class PathObject extends BasePathObject {
|
|||||||
override updateShadow_(shadow: boolean) {
|
override updateShadow_(shadow: boolean) {
|
||||||
if (shadow) {
|
if (shadow) {
|
||||||
this.svgPathLight.style.display = 'none';
|
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.svgPathDark.setAttribute('fill', this.style.colourSecondary);
|
||||||
this.svgPath.setAttribute('stroke', 'none');
|
this.svgPath.setAttribute('stroke', 'none');
|
||||||
this.svgPath.setAttribute('fill', this.style.colourSecondary);
|
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
|
* @alias Blockly.serialization.priorities.VARIABLES
|
||||||
*/
|
*/
|
||||||
export const VARIABLES = 100;
|
export const VARIABLES = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The priority for deserializing variable data.
|
||||||
|
*/
|
||||||
|
export const PROCEDURES = 75;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The priority for deserializing blocks.
|
* 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',
|
CUT = 'cut',
|
||||||
PASTE = 'paste',
|
PASTE = 'paste',
|
||||||
UNDO = 'undo',
|
UNDO = 'undo',
|
||||||
REDO = 'redo'
|
REDO = 'redo',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ export class ShortcutRegistry {
|
|||||||
register(shortcut: KeyboardShortcut, opt_allowOverrides?: boolean) {
|
register(shortcut: KeyboardShortcut, opt_allowOverrides?: boolean) {
|
||||||
const registeredShortcut = this.shortcuts.get(shortcut.name);
|
const registeredShortcut = this.shortcuts.get(shortcut.name);
|
||||||
if (registeredShortcut && !opt_allowOverrides) {
|
if (registeredShortcut && !opt_allowOverrides) {
|
||||||
throw new Error(
|
throw new Error(`Shortcut named "${shortcut.name}" already exists.`);
|
||||||
'Shortcut with name "' + shortcut.name + '" already exists.');
|
|
||||||
}
|
}
|
||||||
this.shortcuts.set(shortcut.name, shortcut);
|
this.shortcuts.set(shortcut.name, shortcut);
|
||||||
|
|
||||||
@@ -81,8 +80,7 @@ export class ShortcutRegistry {
|
|||||||
const shortcut = this.shortcuts.get(shortcutName);
|
const shortcut = this.shortcuts.get(shortcutName);
|
||||||
|
|
||||||
if (!shortcut) {
|
if (!shortcut) {
|
||||||
console.warn(
|
console.warn(`Keyboard shortcut named "${shortcutName}" not found.`);
|
||||||
'Keyboard shortcut with name "' + shortcutName + '" not found.');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +108,8 @@ export class ShortcutRegistry {
|
|||||||
keyCode = String(keyCode);
|
keyCode = String(keyCode);
|
||||||
const shortcutNames = this.keyMap.get(keyCode);
|
const shortcutNames = this.keyMap.get(keyCode);
|
||||||
if (shortcutNames && !opt_allowCollision) {
|
if (shortcutNames && !opt_allowCollision) {
|
||||||
throw new Error(
|
throw new Error(`Shortcut named "${
|
||||||
'Shortcut with name "' + shortcutName + '" collides with shortcuts ' +
|
shortcutName}" collides with shortcuts "${shortcutNames}"`);
|
||||||
shortcutNames.toString());
|
|
||||||
} else if (shortcutNames && opt_allowCollision) {
|
} else if (shortcutNames && opt_allowCollision) {
|
||||||
shortcutNames.unshift(shortcutName);
|
shortcutNames.unshift(shortcutName);
|
||||||
} else {
|
} else {
|
||||||
@@ -138,9 +135,8 @@ export class ShortcutRegistry {
|
|||||||
|
|
||||||
if (!shortcutNames) {
|
if (!shortcutNames) {
|
||||||
if (!opt_quiet) {
|
if (!opt_quiet) {
|
||||||
console.warn(
|
console.warn(`No keyboard shortcut named "${
|
||||||
'No keyboard shortcut with name "' + shortcutName +
|
shortcutName}" registered with key code "${keyCode}"`);
|
||||||
'" registered with key code "' + keyCode + '"');
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -154,9 +150,8 @@ export class ShortcutRegistry {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!opt_quiet) {
|
if (!opt_quiet) {
|
||||||
console.warn(
|
console.warn(`No keyboard shortcut named "${
|
||||||
'No keyboard shortcut with name "' + shortcutName +
|
shortcutName}" registered with key code "${keyCode}"`);
|
||||||
'" registered with key code "' + keyCode + '"');
|
|
||||||
}
|
}
|
||||||
return false;
|
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