mirror of
https://github.com/google/blockly.git
synced 2026-01-06 16:40:07 +01:00
release: Merge branch 'develop' into rc/v12.0.0
This commit is contained in:
@@ -1046,22 +1046,19 @@ blocks['lists_split'] = {
|
||||
|
||||
/**
|
||||
* Returns the state of this block as a JSON serializable object.
|
||||
* This block does not need to serialize any specific state as it is already
|
||||
* encoded in the dropdown values, but must have an implementation to avoid
|
||||
* the backward compatible XML mutations being serialized.
|
||||
*
|
||||
* @returns The state of this block.
|
||||
*/
|
||||
saveExtraState: function (this: SplitBlock): null {
|
||||
return null;
|
||||
saveExtraState: function (this: SplitBlock): {mode: string} {
|
||||
return {'mode': this.getFieldValue('MODE')};
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given state to this block.
|
||||
* No extra state is needed or expected as it is already encoded in the
|
||||
* dropdown values.
|
||||
*/
|
||||
loadExtraState: function (this: SplitBlock) {},
|
||||
loadExtraState: function (this: SplitBlock, state: {mode: string}) {
|
||||
this.updateType_(state['mode']);
|
||||
},
|
||||
};
|
||||
|
||||
// Register provided blocks.
|
||||
|
||||
@@ -246,7 +246,7 @@ export class BlockSvg
|
||||
*
|
||||
* @returns #RRGGBB string.
|
||||
*/
|
||||
getColourSecondary(): string | undefined {
|
||||
getColourSecondary(): string {
|
||||
return this.style.colourSecondary;
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ export class BlockSvg
|
||||
*
|
||||
* @returns #RRGGBB string.
|
||||
*/
|
||||
getColourTertiary(): string | undefined {
|
||||
getColourTertiary(): string {
|
||||
return this.style.colourTertiary;
|
||||
}
|
||||
|
||||
@@ -1221,6 +1221,15 @@ export class BlockSvg
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BlockStyle object used to style this block.
|
||||
*
|
||||
* @returns This block's style object.
|
||||
*/
|
||||
getStyle(): BlockStyle {
|
||||
return this.style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this block to the front of the visible workspace.
|
||||
* <g> tags do not respect z-index so SVG renders them in the
|
||||
|
||||
@@ -83,7 +83,9 @@ function pasteFromData<T extends ICopyData>(
|
||||
workspace: WorkspaceSvg,
|
||||
coordinate?: Coordinate,
|
||||
): ICopyable<T> | null {
|
||||
workspace = workspace.getRootWorkspace() ?? workspace;
|
||||
workspace = workspace.isMutator
|
||||
? workspace
|
||||
: (workspace.getRootWorkspace() ?? workspace);
|
||||
return (globalRegistry
|
||||
.getObject(globalRegistry.Type.PASTER, copyData.paster, false)
|
||||
?.paste(copyData, workspace, coordinate) ?? null) as ICopyable<T> | null;
|
||||
|
||||
@@ -611,7 +611,9 @@ export function registerCommentDuplicate() {
|
||||
export function registerCommentCreate() {
|
||||
const createOption: RegistryItem = {
|
||||
displayText: () => Msg['ADD_COMMENT'],
|
||||
preconditionFn: () => 'enabled',
|
||||
preconditionFn: (scope: Scope) => {
|
||||
return scope.workspace?.isMutator ? 'hidden' : 'enabled';
|
||||
},
|
||||
callback: (scope: Scope, e: PointerEvent) => {
|
||||
const workspace = scope.workspace;
|
||||
if (!workspace) return;
|
||||
|
||||
@@ -29,7 +29,6 @@ import {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import * as utilsString from './utils/string.js';
|
||||
import * as style from './utils/style.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
|
||||
/**
|
||||
@@ -293,7 +292,7 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
|
||||
const primaryColour = block.getColour();
|
||||
const borderColour = (this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
const borderColour = (this.sourceBlock_ as BlockSvg).getColourTertiary();
|
||||
dropDownDiv.setColour(primaryColour, borderColour);
|
||||
}
|
||||
|
||||
@@ -306,11 +305,6 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
if (this.selectedMenuItem) {
|
||||
this.menu_!.setHighlighted(this.selectedMenuItem);
|
||||
style.scrollIntoContainerView(
|
||||
this.selectedMenuItem.getElement()!,
|
||||
dropDownDiv.getContentDiv(),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
this.applyColour();
|
||||
@@ -469,21 +463,21 @@ export class FieldDropdown extends Field<string> {
|
||||
* Updates the dropdown arrow to match the colour/style of the block.
|
||||
*/
|
||||
override applyColour() {
|
||||
const style = (this.sourceBlock_ as BlockSvg).style;
|
||||
const sourceBlock = this.sourceBlock_ as BlockSvg;
|
||||
if (this.borderRect_) {
|
||||
this.borderRect_.setAttribute('stroke', style.colourTertiary);
|
||||
this.borderRect_.setAttribute('stroke', sourceBlock.getColourTertiary());
|
||||
if (this.menu_) {
|
||||
this.borderRect_.setAttribute('fill', style.colourTertiary);
|
||||
this.borderRect_.setAttribute('fill', sourceBlock.getColourTertiary());
|
||||
} else {
|
||||
this.borderRect_.setAttribute('fill', 'transparent');
|
||||
}
|
||||
}
|
||||
// Update arrow's colour.
|
||||
if (this.sourceBlock_ && this.arrow) {
|
||||
if (this.sourceBlock_.isShadow()) {
|
||||
this.arrow.style.fill = style.colourSecondary;
|
||||
if (sourceBlock && this.arrow) {
|
||||
if (sourceBlock.isShadow()) {
|
||||
this.arrow.style.fill = sourceBlock.getColourSecondary();
|
||||
} else {
|
||||
this.arrow.style.fill = style.colourPrimary;
|
||||
this.arrow.style.fill = sourceBlock.getColour();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
|
||||
|
||||
if (!this.isFullBlockField() && this.borderRect_) {
|
||||
this.borderRect_!.style.display = 'block';
|
||||
this.borderRect_.setAttribute('stroke', block.style.colourTertiary);
|
||||
this.borderRect_.setAttribute('stroke', block.getColourTertiary());
|
||||
} else {
|
||||
this.borderRect_!.style.display = 'none';
|
||||
// In general, do *not* let fields control the color of blocks. Having the
|
||||
@@ -430,8 +430,8 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
|
||||
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
|
||||
// Pull stroke colour from the existing shadow block
|
||||
const strokeColour = block.getParent()
|
||||
? (block.getParent() as BlockSvg).style.colourTertiary
|
||||
: (this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
? (block.getParent() as BlockSvg).getColourTertiary()
|
||||
: (this.sourceBlock_ as BlockSvg).getColourTertiary();
|
||||
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
|
||||
div!.style.borderRadius = borderRadius;
|
||||
div!.style.transition = 'box-shadow 0.25s ease 0s';
|
||||
|
||||
@@ -129,7 +129,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
|
||||
|
||||
override applyColour(): void {
|
||||
super.applyColour();
|
||||
const colour = (this.sourceBlock as BlockSvg).style.colourPrimary;
|
||||
const colour = (this.sourceBlock as BlockSvg).getColour();
|
||||
this.textInputBubble?.setColour(colour);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export class MutatorIcon extends Icon implements IHasBubble {
|
||||
|
||||
override applyColour(): void {
|
||||
super.applyColour();
|
||||
this.miniWorkspaceBubble?.setColour(this.sourceBlock.style.colourPrimary);
|
||||
this.miniWorkspaceBubble?.setColour(this.sourceBlock.getColour());
|
||||
this.miniWorkspaceBubble?.updateBlockStyles();
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export class WarningIcon extends Icon implements IHasBubble {
|
||||
|
||||
override applyColour(): void {
|
||||
super.applyColour();
|
||||
this.textBubble?.setColour(this.sourceBlock.style.colourPrimary);
|
||||
this.textBubble?.setColour(this.sourceBlock.getColour());
|
||||
}
|
||||
|
||||
override updateCollapsed(): void {
|
||||
|
||||
@@ -255,10 +255,11 @@ export class Menu {
|
||||
this.highlightedItem = item;
|
||||
// Bring the highlighted item into view. This has no effect if the menu is
|
||||
// not scrollable.
|
||||
const el = this.getElement() as Element;
|
||||
style.scrollIntoContainerView(item.getElement() as Element, el);
|
||||
|
||||
aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId());
|
||||
const el = this.getElement();
|
||||
if (el) {
|
||||
aria.setState(el, aria.State.ACTIVEDESCENDANT, item.getId());
|
||||
}
|
||||
item.getElement()?.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export class PathObject extends BasePathObject {
|
||||
// Set shadow stroke colour.
|
||||
const parent = block.getParent();
|
||||
if (block.isShadow() && parent) {
|
||||
this.svgPath.setAttribute('stroke', parent.style.colourTertiary);
|
||||
this.svgPath.setAttribute('stroke', parent.getColourTertiary());
|
||||
}
|
||||
|
||||
// Apply colour to outlines.
|
||||
|
||||
@@ -18,7 +18,7 @@ import {KeyboardShortcut, ShortcutRegistry} from './shortcut_registry.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
/**
|
||||
* Object holding the names of the default shortcut items.
|
||||
@@ -131,7 +131,10 @@ export function registerCopy() {
|
||||
const selected = common.getSelected();
|
||||
if (!selected || !isCopyable(selected)) return false;
|
||||
copyData = selected.toCopyData();
|
||||
copyWorkspace = workspace;
|
||||
copyWorkspace =
|
||||
selected.workspace instanceof WorkspaceSvg
|
||||
? selected.workspace
|
||||
: workspace;
|
||||
copyCoords = isDraggable(selected)
|
||||
? selected.getRelativeToSurfaceXY()
|
||||
: null;
|
||||
|
||||
@@ -394,6 +394,8 @@ export class Trashcan
|
||||
'transform',
|
||||
'translate(' + this.left + ',' + this.top + ')',
|
||||
);
|
||||
|
||||
this.flyout?.position();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// Former goog.module ID: Blockly.utils.style
|
||||
|
||||
import {Coordinate} from './coordinate.js';
|
||||
import * as deprecation from './deprecation.js';
|
||||
import {Rect} from './rect.js';
|
||||
import {Size} from './size.js';
|
||||
|
||||
@@ -58,6 +59,7 @@ function getSizeInternal(element: Element): Size {
|
||||
* @returns Object with width/height properties.
|
||||
*/
|
||||
function getSizeWithDisplay(element: Element): Size {
|
||||
deprecation.warn(`Blockly.utils.style.getSizeWithDisplay()`, 'v11.2', 'v13');
|
||||
const offsetWidth = (element as HTMLElement).offsetWidth;
|
||||
const offsetHeight = (element as HTMLElement).offsetHeight;
|
||||
return new Size(offsetWidth, offsetHeight);
|
||||
@@ -130,6 +132,7 @@ export function getViewportPageOffset(): Coordinate {
|
||||
* @returns The computed border widths.
|
||||
*/
|
||||
export function getBorderBox(element: Element): Rect {
|
||||
deprecation.warn(`Blockly.utils.style.getBorderBox()`, 'v11.2', 'v13');
|
||||
const left = parseFloat(getComputedStyle(element, 'borderLeftWidth'));
|
||||
const right = parseFloat(getComputedStyle(element, 'borderRightWidth'));
|
||||
const top = parseFloat(getComputedStyle(element, 'borderTopWidth'));
|
||||
@@ -156,6 +159,12 @@ export function scrollIntoContainerView(
|
||||
container: Element,
|
||||
opt_center?: boolean,
|
||||
) {
|
||||
deprecation.warn(
|
||||
`Blockly.utils.style.scrollIntoContainerView()`,
|
||||
'v11.2',
|
||||
'v13',
|
||||
'the native Element.scrollIntoView()',
|
||||
);
|
||||
const offset = getContainerOffsetToScrollInto(element, container, opt_center);
|
||||
container.scrollLeft = offset.x;
|
||||
container.scrollTop = offset.y;
|
||||
@@ -180,6 +189,11 @@ export function getContainerOffsetToScrollInto(
|
||||
container: Element,
|
||||
opt_center?: boolean,
|
||||
): Coordinate {
|
||||
deprecation.warn(
|
||||
`Blockly.utils.style.getContainerOffsetToScrollInto()`,
|
||||
'v11.2',
|
||||
'v13',
|
||||
);
|
||||
// Absolute position of the element's border's top left corner.
|
||||
const elementPos = getPageOffset(element);
|
||||
// Absolute position of the container's border's top left corner.
|
||||
|
||||
605
package-lock.json
generated
605
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -159,7 +159,7 @@ module.exports = require('./${bundle}');
|
||||
* This task copies all the media/* files into the release directory.
|
||||
*/
|
||||
function packageMedia() {
|
||||
return gulp.src('media/*')
|
||||
return gulp.src('media/*', {encoding: false})
|
||||
.pipe(gulp.dest(`${RELEASE_DIR}/media`));
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function start() {
|
||||
* If some tests are failing, load test suites individually to continue
|
||||
* debugging.
|
||||
*/
|
||||
function loadSelected() {
|
||||
async function loadSelected() {
|
||||
var output = document.getElementById('importExport');
|
||||
output.style.background = 'gray';
|
||||
|
||||
@@ -53,9 +53,12 @@ function loadSelected() {
|
||||
if (boxList[i].checked) {
|
||||
var testUrl = boxList[i].value;
|
||||
if (testUrl) {
|
||||
var xmlText = fetchFile(testUrl);
|
||||
var xmlText = await fetchFile(testUrl);
|
||||
if (xmlText !== null) {
|
||||
fromXml(testUrl, xmlText, /* opt_append */ true);
|
||||
// Clean up the workspace to normalize the position of blocks and
|
||||
// thus the order of functions in the generated code.
|
||||
Blockly.getMainWorkspace().cleanUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,23 +70,24 @@ function loadSelected() {
|
||||
/**
|
||||
* Ask the user for a file name, then load that file's contents.
|
||||
*/
|
||||
function loadOther() {
|
||||
async function loadOther() {
|
||||
var url = window.prompt('Enter URL of test file.');
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
var xmlText = fetchFile(url);
|
||||
var xmlText = await fetchFile(url);
|
||||
if (xmlText !== null) {
|
||||
fromXml(url, xmlText);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchFile(xmlUrl) {
|
||||
async function fetchFile(xmlUrl) {
|
||||
try {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open('GET', xmlUrl, false);
|
||||
xmlHttp.setRequestHeader('Content-Type', 'text/xml');
|
||||
xmlHttp.send('');
|
||||
const response = await fetch(xmlUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Got a 404 when loading ${xmlUrl}`);
|
||||
}
|
||||
return response.text();
|
||||
} catch (e) {
|
||||
// Attempt to diagnose the problem.
|
||||
var msg = 'Error: Unable to load XML data.\n';
|
||||
@@ -95,7 +99,6 @@ function fetchFile(xmlUrl) {
|
||||
alert(msg + '\n' + e);
|
||||
return null;
|
||||
}
|
||||
return xmlHttp.responseText;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +191,7 @@ function toDart() {
|
||||
function changeIndex() {
|
||||
var oneBasedIndex = document.getElementById('indexing').checked;
|
||||
demoWorkspace.options.oneBasedIndex = oneBasedIndex;
|
||||
demoWorkspace.getToolbox().flyout_.workspace_.options.oneBasedIndex = oneBasedIndex;
|
||||
demoWorkspace.getToolbox().getFlyout().getWorkspace().options.oneBasedIndex = oneBasedIndex;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ async function runGeneratorsInBrowser(outputDir) {
|
||||
|
||||
await browser.execute(function() {
|
||||
checkAll();
|
||||
loadSelected();
|
||||
return loadSelected();
|
||||
});
|
||||
|
||||
await runLangGeneratorInBrowser(browser, prefix + '.js',
|
||||
|
||||
@@ -181,16 +181,58 @@ suite('Lists', function () {
|
||||
* Test cases for serialization tests.
|
||||
* @type {Array<SerializationTestCase>}
|
||||
*/
|
||||
const testCases = makeTestCasesForBlockNotNeedingExtraState_(
|
||||
const testCases = [
|
||||
{
|
||||
'type': 'lists_split',
|
||||
'id': '1',
|
||||
'fields': {
|
||||
'MODE': 'SPLIT',
|
||||
title: 'JSON for splitting',
|
||||
json: {
|
||||
type: 'lists_split',
|
||||
id: '1',
|
||||
extraState: {mode: 'SPLIT'},
|
||||
fields: {MODE: 'SPLIT'},
|
||||
inputs: {
|
||||
DELIM: {
|
||||
shadow: {
|
||||
type: 'text',
|
||||
id: '2',
|
||||
fields: {
|
||||
TEXT: ',',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertBlockStructure: (block) => {
|
||||
assert.equal(block.type, 'lists_split');
|
||||
assert.deepEqual(block.outputConnection.getCheck(), ['Array']);
|
||||
assert.isTrue(block.getField('MODE').getValue() === 'SPLIT');
|
||||
},
|
||||
},
|
||||
'<mutation mode="SPLIT"></mutation>',
|
||||
);
|
||||
{
|
||||
title: 'JSON for joining',
|
||||
json: {
|
||||
type: 'lists_split',
|
||||
id: '1',
|
||||
extraState: {mode: 'JOIN'},
|
||||
fields: {MODE: 'JOIN'},
|
||||
inputs: {
|
||||
DELIM: {
|
||||
shadow: {
|
||||
type: 'text',
|
||||
id: '2',
|
||||
fields: {
|
||||
TEXT: ',',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
assertBlockStructure: (block) => {
|
||||
assert.equal(block.type, 'lists_split');
|
||||
assert.deepEqual(block.outputConnection.getCheck(), ['String']);
|
||||
assert.isTrue(block.getField('MODE').getValue() === 'JOIN');
|
||||
},
|
||||
},
|
||||
];
|
||||
runSerializationTestSuite(testCases);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,6 +61,30 @@ suite('Clipboard', function () {
|
||||
);
|
||||
});
|
||||
|
||||
test('copied from a mutator pastes them into the mutator', async function () {
|
||||
const block = Blockly.serialization.blocks.append(
|
||||
{
|
||||
'type': 'controls_if',
|
||||
'id': 'blockId',
|
||||
'extraState': {
|
||||
'elseIfCount': 1,
|
||||
},
|
||||
},
|
||||
this.workspace,
|
||||
);
|
||||
const mutatorIcon = block.getIcon(Blockly.icons.IconType.MUTATOR);
|
||||
await mutatorIcon.setBubbleVisible(true);
|
||||
const mutatorWorkspace = mutatorIcon.getWorkspace();
|
||||
const elseIf = mutatorWorkspace.getBlocksByType('controls_if_elseif')[0];
|
||||
assert.notEqual(elseIf, undefined);
|
||||
assert.lengthOf(mutatorWorkspace.getAllBlocks(), 2);
|
||||
assert.lengthOf(this.workspace.getAllBlocks(), 1);
|
||||
const data = elseIf.toCopyData();
|
||||
Blockly.clipboard.paste(data, mutatorWorkspace);
|
||||
assert.lengthOf(mutatorWorkspace.getAllBlocks(), 3);
|
||||
assert.lengthOf(this.workspace.getAllBlocks(), 1);
|
||||
});
|
||||
|
||||
suite('pasted blocks are placed in unambiguous locations', function () {
|
||||
test('pasted blocks are bumped to not overlap', function () {
|
||||
const block = Blockly.serialization.blocks.append(
|
||||
|
||||
Reference in New Issue
Block a user