Skip to content

Commit

Permalink
feat: publish new developer tools (#2398)
Browse files Browse the repository at this point in the history
* feat: add basic infrastructure of developer-tools

* chore: add eof newline where missing

* fix: formatting, license, css attributes

* feat: add block factory blocks and toolbox (#2155)

* feat: add block factory blocks and toolbox

* fix: pr comments

* fix: change type to connection check

* fix: formatting and comments

* chore: lint and format developer-tools (#2185)

* chore: autofix lint

* chore: manually fix most of the lint

* chore: format

* feat: add basic saving and loading and start block state (#2187)

* feat: add json definition generator (#2188)

* feat: add javascript definition generators for blocks (#2196)

* feat: add javascript definition generators for blocks

* chore: format

* fix: switch statement style

Co-authored-by: Christopher Allen <cpcallen+github@gmail.com>

* fix: update to latest blockly to use JavascriptGenerator class

* fix: fix img dropdown option

---------

Co-authored-by: Christopher Allen <cpcallen+github@gmail.com>

* feat: add controllers and models to switch between definitions (#2219)

* feat: add code header generation for imports and script tags (#2286)

* feat: add save, load, and delete functionality to dev-tools (#2285)

* feat: add save, load, and delete functionality to dev-tools

* chore: format

* chore: update load name

* feat: add generator stubs to block factory (#2295)

* feat: add generator stub generator and output

* feat: add generator headers

* chore: add more tsdoc

* chore: minor refactoring of template strings

* feat: save block factory settings (#2297)

* feat: save block factory settings

* chore: const to named functions

* feat: add ability to convert old block factory json to new (#2304)

* feat: add ability to convert old block factory json to new

* chore: format and use constant

* fix: use better typings, minor refactoring

* feat: add shadow blocks for connection checks and real colour block (#2307)

* feat: add file upload for block factory (#2320)

* feat: add file upload for block factory

* chore: fix questionable html formatting

* chore: rename and comments

* feat: add angle and colour fields to block factory (#2325)

* feat: add angle and colour fields to block factory

* fix: call register fields in script header

* feat: support uploading file from old block factory (#2336)

* feat: support uploading file from old block factory

* feat: support multiple file input, minor pr fixes

* feat: update to blockly v11 & improve style (#2388)

* fix: styling

* fix: changes for v11

* fix: set max height in narrow mode

* fix: min height of code divs

* chore: format

* chore: remove log

* feat: use a js legal name for the block in code output (#2392)

* feat: use a js legal name for the block in code output

* fix: legal js name probably

* feat: add help button and favicon (#2396)

* feat: include developer-tools when publishing to gh-pages (#2395)

---------

Co-authored-by: Christopher Allen <cpcallen+github@gmail.com>
  • Loading branch information
maribethb and cpcallen authored Jun 28, 2024
1 parent 679bf08 commit 87df8e3
Show file tree
Hide file tree
Showing 42 changed files with 23,835 additions and 2 deletions.
18,803 changes: 18,803 additions & 0 deletions examples/developer-tools/package-lock.json

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions examples/developer-tools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "developer-tools",
"version": "1.0.0",
"description": "Blockly Developer Tools",
"private": true,
"main": "index.js",
"scripts": {
"start": "webpack serve --open --mode=development",
"build": "webpack --mode=production --node-env=production",
"build:dev": "webpack --mode=development",
"build:prod": "webpack --mode=production --node-env=production",
"watch": "webpack --watch",
"serve": "webpack serve",
"predeploy": "npm run build:prod"
},
"repository": {
"type": "git",
"url": "git+https://github.com/google/blockly-samples.git"
},
"keywords": [
"blockly"
],
"author": "Blockly Team",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/google/blockly-samples/issues"
},
"homepage": "https://github.com/google/blockly-samples#readme",
"devDependencies": {
"@webpack-cli/generators": "^3.0.7",
"css-loader": "^6.8.1",
"html-webpack-plugin": "^5.5.3",
"mini-css-extract-plugin": "^2.7.6",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@blockly/field-angle": "^5.0.2",
"@blockly/field-colour": "^5.0.2",
"@material/web": "^1.4.0",
"blockly": "^11.1.1"
}
}
275 changes: 275 additions & 0 deletions examples/developer-tools/src/backwards_compatibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* This file contains methods that can convert blocks saved in the old
* block factory tool that was hosted on app engine, into blocks that
* can be used with this new tool.
*
* Many of the blocks from the old tool are the same as the blocks
* in this tool. But in some cases, block, field, or input names have
* been changed for clarity. This file will edit block json so that
* the saved data from the old tool can be loaded into this tool.
*/

import * as Blockly from 'blockly/core';

/** Shadow state for a connection check block. */
const CONNECTION_CHECK_SHADOW = {
type: 'connection_check',
fields: {
CHECKDROPDOWN: 'null',
},
};

/**
* The factory_base block is largely the same. However, the inputs that spawn
* if the block has top/bottom/left connectors have been renamed from
* `OUTPUTTYPE` to `OUTPUTCHECK`. The input blocks connected to this one
* also need to be converted.
*
* @param oldBlock The JSON for the factory_base as saved from old tool.
* @returns JSON that should be loaded instead.
*/
export function convertBaseBlock(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
if (oldBlock.type !== 'factory_base') {
throw Error('Malformed block data');
}

const newBlock = {...oldBlock};
// extraState from the old tool isn't relevant.
delete newBlock.extraState;

if (oldBlock.inputs?.INPUTS?.block) {
newBlock.inputs.INPUTS.block = convertInput(oldBlock.inputs.INPUTS.block);
}

/**
* Converts the top-level connection checks on the 'factory_base' block to the new level.
*
* @param connectionName Name of the input to create the connection check block for.
*/
function convertFactoryBaseConnectionChecks(
connectionName: 'OUTPUT' | 'TOP' | 'BOTTOM',
) {
// The names of the input on the old and new blocks
// e.g. 'OUTPUTTYPE' vs 'OUTPUTCHECK'
const oldInputName = connectionName + 'TYPE';
const newInputName = connectionName + 'CHECK';

// If this input didn't exist in the old block, it also doesn't
// exist on the new block. Nothing to do here.
if (!oldBlock.inputs || !oldBlock.inputs[oldInputName]) return;

const newCheckInput: Blockly.serialization.blocks.ConnectionState = {};
// Shadow block always exists.
newCheckInput.shadow = CONNECTION_CHECK_SHADOW;
if (oldBlock.inputs[oldInputName].block) {
// Real block only exists if it existed in the old block too.
newCheckInput.block = convertCheck(oldBlock.inputs[oldInputName].block);
}
newBlock.inputs[newInputName] = newCheckInput;
// New block doesn't need the input with the old name.
delete newBlock.inputs[oldInputName];
}

convertFactoryBaseConnectionChecks('OUTPUT');
convertFactoryBaseConnectionChecks('BOTTOM');
convertFactoryBaseConnectionChecks('TOP');

return newBlock;
}

/**
* The input blocks are different. In the old tool, each type of input had its own
* block definition. In this tool, there is one "input" block that has a dropdown
* to select an input type. Also, the old blocks have a connection "type" while
* the new blocks have a connection "check".
*
* @param oldBlock JSON for the "input_foo" block as saved from old tool.
* @returns JSON that should be used for the replacement "input" block.
*/
function convertInput(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
const newBlock: Blockly.serialization.blocks.State = {
type: 'input',
fields: {
INPUTTYPE: oldBlock.type,
},
inputs: {},
};

if (oldBlock.fields?.ALIGN) {
// Note new name in new tool.
newBlock.fields.ALIGNMENT = oldBlock.fields?.ALIGN;
}

if (oldBlock.fields?.INPUTNAME) {
newBlock.fields.INPUTNAME = oldBlock.fields.INPUTNAME;
}

if (oldBlock.inputs?.TYPE) {
newBlock.inputs.CHECK = {
shadow: CONNECTION_CHECK_SHADOW,
};
if (oldBlock.inputs.TYPE.block) {
newBlock.inputs.CHECK.block = convertCheck(oldBlock.inputs.TYPE.block);
}
}

if (oldBlock.inputs?.FIELDS?.block) {
newBlock.inputs.FIELDS = {
block: convertField(oldBlock.inputs.FIELDS.block),
};
}

if (oldBlock.next?.block) {
newBlock.next = {
block: convertInput(oldBlock.next.block),
};
}
return newBlock;
}

/**
* The field blocks are all mostly the same, with a few exceptions:
* "field_static" in the old tool is called "field_label" here.
* "field_dropdown"'s extraState uses xml in the old tool and json in the new tool.
*
* TODO(#2290): Check for backwards-compatibility issues with plugin fields.
*
* @param oldBlock JSON for the "field_foo" block as saved from old tool.
* @returns JSON that should be used for the replacement field block.
*/
function convertField(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
const newBlock = {...oldBlock};
if (oldBlock.type === 'field_static') {
newBlock.type = 'field_label';
}

if (oldBlock.type === 'field_dropdown' && oldBlock.extraState) {
const extraState = Blockly.utils.xml.textToDom(oldBlock.extraState);
const options = JSON.parse(extraState.getAttribute('options'));
newBlock.extraState = {
options: options,
};
}
if (oldBlock.next?.block) {
newBlock.next.block = convertField(oldBlock.next.block);
}
return newBlock;
}

/**
* The type/check blocks are different. In the old tool, each "type"
* (e.g. Number or Boolean) had its own block definition. In this
* tool, there is one "connection_check" block that has a dropdown
* to select a check. We prefer the term "check" to "type" in all cases,
* to match documentation and reduce the confusion between multiple meanings of "type".
*
* @param oldBlock JSON for the "type_foo" block as saved from old tool.
* @returns JSON that should be used for the replacement "connection_check" block.
*/
function convertCheck(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
const oldName = oldBlock.type; // The block type i.e. name of block definition
if (!oldName.startsWith('type_')) {
throw Error(
`Found connection check block with unexpected block type ${oldName}`,
);
}
let connectionCheck = oldName.substring(5);
switch (connectionCheck) {
case 'null':
// check value does not change if 'null'
break;
case 'boolean':
connectionCheck = 'Boolean';
break;
case 'number':
connectionCheck = 'Number';
break;
case 'string':
connectionCheck = 'String';
break;
case 'list':
connectionCheck = 'Array';
break;
case 'other':
return convertCustomCheck(oldBlock);
case 'group':
return convertGroupCheck(oldBlock);
default:
throw Error(
`Found connection check block with unexpected type: ${connectionCheck}`,
);
}
return {
type: 'connection_check',
fields: {
CHECKDROPDOWN: connectionCheck,
},
};
}

/**
* Converts an old "type_other" block into a "check" block with custom value.
*
* @param oldBlock JSON for the "type_other" block as saved from old tool.
* @returns JSON that should be used for the replacement "connection_check" block.
*/
function convertCustomCheck(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
const customCheck = oldBlock.fields.TYPE;
return {
type: 'connection_check',
extraState: {
customCheck: customCheck,
},
fields: {
CHECKDROPDOWN: 'CUSTOM',
CUSTOMCHECK: customCheck,
},
};
}

/**
* Converts an old "type_group" block into a "connection_check_group" block.
* The old block has inputs named `TYPE0`, `TYPE1`, etc. The new block renames
* these inputs to `CHECK0`, `CHECK1`, etc.
*
* @param oldBlock JSON for the "type_group" block as saved from old tool.
* @returns JSON that should be used for the replacement "connection_check" block.
*/
function convertGroupCheck(
oldBlock: Blockly.serialization.blocks.State,
): Blockly.serialization.blocks.State {
const inputs: Record<string, object> = {};
const checkCount = parseInt(
Blockly.utils.xml.textToDom(oldBlock.extraState).getAttribute('types'),
);
for (let index = 0; index < checkCount; index++) {
if (oldBlock.inputs['TYPE' + index]?.block) {
inputs['CHECK' + index] = {
block: convertCheck(oldBlock.inputs['TYPE' + index].block),
};
}
}
return {
type: 'connection_check_group',
inputs: inputs,
extraState: {
checkCount: checkCount,
},
};
}
25 changes: 25 additions & 0 deletions examples/developer-tools/src/blocks/colour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {FieldAngle} from '@blockly/field-angle';

/**
* A hue-picker to set the colour of a block.
* TODO(#2159): Use the new angle field.
*/
export const colourHue = {
init: function () {
this.appendDummyInput()
.appendField('hue:')
.appendField(new FieldAngle('0', this.updateBlockColour), 'HUE');
this.setOutput(true, 'Colour');
this.setTooltip('Paint the block with this colour.');
this.setHelpUrl('https://www.youtube.com/watch?v=s2_xaEvcVI0#t=55');
},
updateBlockColour: function (hue: number) {
this.getSourceBlock().setColour(hue);
},
};
Loading

0 comments on commit 87df8e3

Please sign in to comment.