From 0ebebe28a459c057ec2e71cde607508612fb2c60 Mon Sep 17 00:00:00 2001 From: Fabian Rassi Date: Fri, 13 Oct 2023 13:12:49 +0200 Subject: [PATCH] BLOCKS-320 add indentation to the starting blocks --- src/library/js/blocks/bricks.js | 4 +- src/library/js/blocks/categories/device.js | 22 +++ src/library/js/blocks/categories/event.js | 162 ++++++++++++++++++++- src/library/js/blocks/categories/motion.js | 22 +++ src/library/js/blocks/categories/script.js | 44 ++++++ src/library/js/blocks/categories/user.js | 22 +++ src/library/js/integration/catroid.js | 4 +- src/library/js/integration/utils.js | 22 ++- test/jsunit/block/block.test.js | 40 +++++ test/jsunit/catroid/advanced-mode.test.js | 115 ++++++++++++++- 10 files changed, 444 insertions(+), 13 deletions(-) diff --git a/src/library/js/blocks/bricks.js b/src/library/js/blocks/bricks.js index 743e5d9a..fba8cf80 100644 --- a/src/library/js/blocks/bricks.js +++ b/src/library/js/blocks/bricks.js @@ -32,7 +32,6 @@ export const scriptBricks = [ 'WhenConditionScript', 'WhenBounceOffScript', 'WhenBackgroundChangesScript', - 'WhenRaspiPinChangedBrick', 'UserDefinedScript', 'EmptyScript', 'RaspiInterruptScript', @@ -111,6 +110,9 @@ const loadBricks = (cats = categories, blockly = Blockly, advancedMode = false) if (advancedMode) { this.setStyle(catName); } + if (scriptBricks.includes(brickName)) { + this.setNextStatement(false); + } } }; } diff --git a/src/library/js/blocks/categories/device.js b/src/library/js/blocks/categories/device.js index 4ad8ca63..f63dd8df 100644 --- a/src/library/js/blocks/categories/device.js +++ b/src/library/js/blocks/categories/device.js @@ -3,6 +3,8 @@ export default { WhenNfcScript: { message0: '%{BKY_EVENT_WHENNFC}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -20,6 +22,26 @@ export default { flip_rtl: true, name: 'DROPDOWN_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] }, SetNfcTagBrick: { diff --git a/src/library/js/blocks/categories/event.js b/src/library/js/blocks/categories/event.js index c7a74349..616ac685 100644 --- a/src/library/js/blocks/categories/event.js +++ b/src/library/js/blocks/categories/event.js @@ -2,19 +2,109 @@ export default { StartScript: { - message0: '%{BKY_EVENT_WHENSCENESTARTS}' + message0: '%{BKY_EVENT_WHENSCENESTARTS}', + message1: '%1', + message2: '%1%2', + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } + ] }, WhenScript: { - message0: '%{BKY_EVENT_WHENTAPPED}' + message0: '%{BKY_EVENT_WHENTAPPED}', + message1: '%1', + message2: '%1%2', + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } + ] }, WhenTouchDownScript: { - message0: '%{BKY_EVENT_WHENSTAGEISTAPPED}' + message0: '%{BKY_EVENT_WHENSTAGEISTAPPED}', + message1: '%1', + message2: '%1%2', + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } + ] }, WhenClonedScript: { - message0: '%{BKY_CONTROL_WHENYOUSTARTASACLONE}' + message0: '%{BKY_CONTROL_WHENYOUSTARTASACLONE}', + message1: '%1', + message2: '%1%2', + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } + ] }, BroadcastScript: { message0: '%{BKY_EVENT_WHENYOURECEIVE}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -32,10 +122,32 @@ export default { flip_rtl: true, name: 'DROPDOWN_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] }, WhenConditionScript: { message0: '%{BKY_EVENT_WHENBECOMESTRUE}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblockstext', @@ -51,10 +163,32 @@ export default { flip_rtl: true, name: 'IF_CONDITION_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] }, WhenBackgroundChangesScript: { message0: '%{BKY_EVENT_WHENBACKGROUNDCHANGES}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -72,6 +206,26 @@ export default { flip_rtl: true, name: 'look_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] } }; diff --git a/src/library/js/blocks/categories/motion.js b/src/library/js/blocks/categories/motion.js index a0f7f73d..9debf463 100644 --- a/src/library/js/blocks/categories/motion.js +++ b/src/library/js/blocks/categories/motion.js @@ -956,6 +956,8 @@ export default { }, WhenBounceOffScript: { message0: '%{BKY_EVENT_WHENYOUBOUNCEOFF}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -973,6 +975,26 @@ export default { flip_rtl: true, name: 'DROPDOWN_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] } }; diff --git a/src/library/js/blocks/categories/script.js b/src/library/js/blocks/categories/script.js index d3cbb380..2d00c285 100644 --- a/src/library/js/blocks/categories/script.js +++ b/src/library/js/blocks/categories/script.js @@ -3,6 +3,8 @@ export default { WhenGamepadButtonScript: { message0: '%{BKY_CAST_WHEN_GAMEPAD_BUTTON}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -20,10 +22,32 @@ export default { flip_rtl: true, name: 'ACTION_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] }, RaspiInterruptScript: { message0: '%{BKY_EVENT_RASPI_INTERRUPT_SCRIPT}', + message1: '%1', + message2: '%1%2', args0: [ { type: 'field_catblocksspinner', @@ -57,6 +81,26 @@ export default { flip_rtl: true, name: 'eventValue_INFO' } + ], + args1: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args2: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] } }; diff --git a/src/library/js/blocks/categories/user.js b/src/library/js/blocks/categories/user.js index c3045a67..97689ae8 100644 --- a/src/library/js/blocks/categories/user.js +++ b/src/library/js/blocks/categories/user.js @@ -5,6 +5,8 @@ export default { message0: '%{BKY_USER_DEFINED_SCRIPT_DEFINE}', message1: '%1', message2: '%{BKY_USER_DEFINED_SCRIPT_SCREEN_REFRESH_AS}', + message3: '%1', + message4: '%1%2', args1: [ { type: 'input_statement', @@ -37,6 +39,26 @@ export default { flip_rtl: true, name: 'UDB_SCREEN_REFRESH_INFO' } + ], + args3: [ + { + type: 'input_statement', + name: 'SUBSTACK' + } + ], + args4: [ + { + type: 'field_label', + name: 'ADVANCED_MODE_PLACEHOLDER' + }, + { + type: 'field_image', + src: `${document.location.pathname}media/empty_icon.svg`, + height: 24, + width: 24, + flip_rtl: true, + name: 'ADVANCED_MODE_PLACEHOLDER' + } ] } }; diff --git a/src/library/js/integration/catroid.js b/src/library/js/integration/catroid.js index b71e459e..658febb1 100644 --- a/src/library/js/integration/catroid.js +++ b/src/library/js/integration/catroid.js @@ -287,6 +287,7 @@ export class Catroid { const newEmptyBrickPositionY = position.y - newEmptyBrickSize.height + connectionOffset; newEmptyBrick.moveBy(newEmptyBrickPositionX, newEmptyBrickPositionY); + newEmptyBrick.setNextStatement(true); newEmptyBrick.nextConnection.connect(droppedBrick.previousConnection); droppedBrick.setParent(newEmptyBrick); @@ -312,7 +313,7 @@ export class Catroid { for (let i = 0; i < subStacks.length; ++i) { if (subStacks[i].connection.targetConnection) { if (subStacks[i].connection.targetConnection.sourceBlock_.id == firstBrickInStack.id) { - subStackIdx = i; + subStackIdx = i - 1; break; } } @@ -653,6 +654,7 @@ export class Catroid { this.workspace.getRenderer().constants_.FIELD_BORDER_RECT_HEIGHT = 14; // Determines height of block with input field this.workspace.getRenderer().constants_.FIELD_TEXT_HEIGHT = 14; // Determines height of a block without input field this.workspace.getRenderer().constants_.BOTTOM_ROW_AFTER_STATEMENT_MIN_HEIGHT = 14; // Height of bottom part of e.g. 'if' block + this.workspace.getRenderer().constants_.FIELD_DROPDOWN_BORDER_RECT_HEIGHT = 14; // Determines height of a block with a dropdown field this.workspace.getRenderer().constants_.FIELD_BORDER_RECT_X_PADDING = 0; this.workspace.getRenderer().constants_.BETWEEN_STATEMENT_PADDING_Y = 0; this.readonlyWorkspace.getRenderer().constants_.BETWEEN_STATEMENT_PADDING_Y = 0; diff --git a/src/library/js/integration/utils.js b/src/library/js/integration/utils.js index 8ff60992..803ef96e 100644 --- a/src/library/js/integration/utils.js +++ b/src/library/js/integration/utils.js @@ -320,7 +320,7 @@ export const renderAndConnectBlocksInList = (parentBrick, brickList, brickListTy } if (brickList[i].brickList !== undefined && brickList[i].brickList.length > 0) { - if (brickList[i].userDefinedBrickID !== undefined) { + if (brickList[i].userDefinedBrickID !== null && brickList[i].userDefinedBrickID !== undefined) { // if there are bricks in the brickList and the userBrickId is set, it is a UserDefinedScript renderAndConnectBlocksInList( childBrick, @@ -440,7 +440,17 @@ export const renderBrick = (parentBrick, jsonBrick, brickListType, workspace, re } if (brickListType === brickListTypes.brickList || brickListType === brickListTypes.userBrickList) { - parentBrick.nextConnection.connect(childBrick.previousConnection); + if (brickListType === brickListTypes.userBrickList) { + parentBrick.inputList[3].connection.connect(childBrick.previousConnection); + } else { + if (parentBrick.domBrickID.includes('EmptyScript')) { + parentBrick.setNextStatement(true); + parentBrick.nextConnection.connect(childBrick.previousConnection); + } else { + parentBrick.inputList[1].connection.connect(childBrick.previousConnection); + parentBrick.setNextStatement(false); + } + } } else if (brickListType === brickListTypes.elseBrickList) { parentBrick.inputList[3].connection.connect(childBrick.previousConnection); } else if ( @@ -852,11 +862,11 @@ export function advancedModeAddSemicolonsAndClassifyTopBricks(childBrick) { ) { return; } + if (scriptBricks.includes(childBrick.type)) { + childBrick.hat = 'top'; + return; + } if (childBrick.inputList.length === 1) { - if (scriptBricks.includes(childBrick.type)) { - childBrick.hat = 'top'; - return; - } const fieldRow = childBrick.inputList[0].fieldRow; const newVal = fieldRow[fieldRow.length - 1].getValue() + ';'; fieldRow[fieldRow.length - 1].setValue(newVal); diff --git a/test/jsunit/block/block.test.js b/test/jsunit/block/block.test.js index c9f724e3..22d27088 100644 --- a/test/jsunit/block/block.test.js +++ b/test/jsunit/block/block.test.js @@ -21,6 +21,25 @@ const BLOCKS = (function () { return result; })(); +/** + * All script blocks + */ +const SCRIPTBLOCKS = [ + 'WhenClonedScript', + 'StartScript', + 'WhenScript', + 'WhenTouchDownScript', + 'BroadcastScript', + 'WhenConditionScript', + 'WhenBounceOffScript', + 'WhenBackgroundChangesScript', + 'UserDefinedScript', + 'EmptyScript', + 'RaspiInterruptScript', + 'WhenNfcScript', + 'WhenGamepadButtonScript' +]; + /** * Load block messages mapping */ @@ -257,6 +276,27 @@ describe('WebView Block tests', () => { }, allBlocks); expect(result).toBeTruthy(); }); + + test('All ScriptBlocks have smaller heights without a nextStatement', async () => { + const expectedMaxHeights = Array.from({ length: SCRIPTBLOCKS.length }, () => 150); + // UserDefinedScript + expectedMaxHeights[8] = 230; + + const scriptBlockHeights = await page.evaluate(pAllScriptBlocks => { + const blockHeights = []; + for (const scriptBlock of pAllScriptBlocks) { + const block = Test.Playground.workspace.newBlock(scriptBlock); + block.initSvg(); + block.render(false); + blockHeights.push(block.height); + } + return blockHeights; + }, SCRIPTBLOCKS); + + for (let i = 0; i < SCRIPTBLOCKS.length; i++) { + expect(scriptBlockHeights[i]).toBeLessThan(expectedMaxHeights[i]); + } + }); }); describe('Workspace actions', () => { diff --git a/test/jsunit/catroid/advanced-mode.test.js b/test/jsunit/catroid/advanced-mode.test.js index 8b0f340a..7ee8b625 100644 --- a/test/jsunit/catroid/advanced-mode.test.js +++ b/test/jsunit/catroid/advanced-mode.test.js @@ -139,7 +139,7 @@ describe('Catroid Integration Advanced Mode tests', () => { return startBlock.tooltip.height; }); - expect(blocksHeight).toBeLessThan(40); + expect(blocksHeight).toBeLessThan(100); }); test('Semicolon test', async () => { @@ -159,6 +159,28 @@ describe('Catroid Integration Advanced Mode tests', () => { } }); + test('Script blocks have curly brackets', async () => { + const startBlockSyntax = await page.evaluate(() => { + const blocks = document.querySelectorAll('.blocklyPath'); + const startBlock = Array.from(blocks).find(block => block.tooltip.type === 'StartScript'); + const broadcastBlock = Array.from(blocks).find(block => block.tooltip.type === 'BroadcastScript'); + + return [ + startBlock.tooltip.inputList[0].fieldRow[0].getValue(), + startBlock.tooltip.inputList[2].fieldRow[0].getValue(), + broadcastBlock.tooltip.inputList[0].fieldRow[0].getValue(), + broadcastBlock.tooltip.inputList[0].fieldRow[2].getValue(), + broadcastBlock.tooltip.inputList[2].fieldRow[0].getValue() + ]; + }); + + expect(startBlockSyntax[0]).toBe('When scene starts {'); + expect(startBlockSyntax[1]).toBe('}'); + expect(startBlockSyntax[2]).toBe('When you receive ('); + expect(startBlockSyntax[3]).toBe(') {'); + expect(startBlockSyntax[4]).toBe('}'); + }); + test('Formulas formatting test', async () => { const fieldValues = await page.evaluate(() => { const blocks = document.querySelectorAll('.blocklyPath'); @@ -181,3 +203,94 @@ describe('Catroid Integration Advanced Mode tests', () => { expect(commentedOut).toBe('// Show variable ('); }); }); + +describe('Catroid Integration Advanced Mode tests for height', () => { + beforeAll(async () => { + await page.goto('http://localhost:8080', { + waitUntil: 'networkidle0' + }); + const programXML = fs.readFileSync(path.resolve(__dirname, '../../programs/binding_of_krishna_1_12.xml'), 'utf8'); + const language = 'en'; + const rtl = false; + const advancedMode = true; + await page.evaluate( + async (pLanguage, pRTL, pAdvancedMode) => { + try { + await Test.CatroidCatBlocks.init({ + container: 'catblocks-container', + renderSize: 0.75, + language: pLanguage, + rtl: pRTL, + i18n: '/i18n', + shareRoot: '', + media: 'media/', + noImageFound: 'No_Image_Available.jpg', + renderLooks: false, + renderSounds: false, + readOnly: false, + advancedMode: pAdvancedMode + }); + } catch (e) { + console.error(e); + } + }, + language, + rtl, + advancedMode + ); + await page.evaluate(async pProgramXML => { + await Test.CatroidCatBlocks.render(pProgramXML, 'Scene 1', 'Krishna', '38a46c1f-a2b7-4229-9f1d-c12435df8758'); + }, programXML); + + await page.evaluate(() => { + // function to JSON.stringify circular objects + window.shallowJSON = (obj, indent = 2) => { + let cache = []; + const retVal = JSON.stringify( + obj, + (key, value) => + typeof value === 'object' && value !== null + ? cache.includes(value) + ? undefined // Duplicate reference found, discard key + : cache.push(value) && value // Store value in our collection + : value, + indent + ); + cache = null; + return retVal; + }; + }); + }); + + test('Same vertical space between each block', async () => { + const waitBrickHeight = await page.evaluate(() => { + const blocks = document.querySelectorAll('.blocklyPath'); + const waitBrick = Array.from(blocks).find(block => block.tooltip.type === 'WaitBrick'); + return waitBrick.tooltip.height; + }); + + const setVarBrickHeight = await page.evaluate(() => { + const blocks = document.querySelectorAll('.blocklyPath'); + const setVarBlock = Array.from(blocks).find(block => block.tooltip.type === 'SetVariableBrick'); + return setVarBlock.tooltip.height; + }); + + expect(setVarBrickHeight).toBe(waitBrickHeight); + }); + + test('Blocks within scripts have a shorter width than the script', async () => { + const startBrickWidth = await page.evaluate(() => { + const blocks = document.querySelectorAll('.blocklyPath'); + const waitBrick = Array.from(blocks).find(block => block.tooltip.type === 'StartScript'); + return waitBrick.tooltip.width; + }); + + const setVarBrickWidth = await page.evaluate(() => { + const blocks = document.querySelectorAll('.blocklyPath'); + const setVarBlock = Array.from(blocks).find(block => block.tooltip.type === 'SetVariableBrick'); + return setVarBlock.tooltip.width; + }); + + expect(setVarBrickWidth).toBeLessThan(startBrickWidth); + }); +});