diff --git a/packages/docs/docs/codelabs/custom-toolbox/add-a-custom-category.mdx b/packages/docs/docs/codelabs/custom-toolbox/add-a-custom-category.mdx new file mode 100644 index 00000000000..0712c5c460c --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/add-a-custom-category.mdx @@ -0,0 +1,76 @@ +--- +description: How to create a custom category. +--- + +import Image from '@site/src/components/Image'; + +# Customizing a Blockly toolbox + +## 3. Make a custom category + +### Define and register a custom category + +To start, create a file named `custom_category.js` in the `src` directory. + +Then, add an `import` statement to `index.js` for this new file: + +```js +import './custom_category.js'; +``` + +In order to create a custom category, you will create a new category that extends +the default `Blockly.ToolboxCategory` class. Add the following code to your `custom_category.js` file: + +```js +import * as Blockly from 'blockly'; + +class CustomCategory extends Blockly.ToolboxCategory { + /** + * Constructor for a custom category. + * @override + */ + constructor(categoryDef, toolbox, opt_parent) { + super(categoryDef, toolbox, opt_parent); + } +} +``` + +After defining your category you need to tell Blockly that it exists. +Register your category by adding the below code to the end of `custom_category.js`: + +```js +Blockly.registry.register( + Blockly.registry.Type.TOOLBOX_ITEM, + Blockly.ToolboxCategory.registrationName, + CustomCategory, + true, +); +``` + +By registering our `CustomCategory` with `Blockly.ToolboxCategory.registrationName` +we are *overriding* the default category in Blockly. Because we are overriding a +toolbox item instead of adding a new one, we must pass in `true` as the last +argument. If this flag is `false`, `Blockly.registry.register` will throw +an error because we are overriding an existing class. + +### The result + +To test, save your files, then find the browser page that has the sample app. +You may also need to refresh the page. Your toolbox should look the same as it did before. + +The default toolbox. A list of categories with a strip of a different colour to the left of each category. + +However, if you run the below commands in your console you will see that +your toolbox is now using the `CustomCategory` class. + +```js +var toolbox = Blockly.common.getMainWorkspace().getToolbox(); +toolbox.getToolboxItems()[0]; +``` + +Although nothing has visually changed yet, this codelab will use the `CustomCategory` class +to change the appearance of the categories. \ No newline at end of file diff --git a/packages/docs/docs/codelabs/custom-toolbox/add-an-icon-to-your-category.mdx b/packages/docs/docs/codelabs/custom-toolbox/add-an-icon-to-your-category.mdx index 2a4b0424c5f..13ae1b5aafb 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/add-an-icon-to-your-category.mdx +++ b/packages/docs/docs/codelabs/custom-toolbox/add-an-icon-to-your-category.mdx @@ -6,30 +6,44 @@ import Image from '@site/src/components/Image'; # Customizing a Blockly toolbox -## 5. Add an icon to your category +## 6. Add an icon to your category We are going to add an icon to our "Logic" category by adding an icon library to our `index.html` file, and setting the appropriate CSS class on our category definition. -To start, we are going to grab an icon library and add it to `index.html`: +To start, we are going to grab an icon library and paste it to `index.html`, inside +the `` element: -``` +```html ``` We are going to add a cog icon from this library to the "Logic" category. To do this, we will add the appropriate CSS classes to our category definition. +For a JSON toolbox definition like the one in `toolbox.js`, you'll use a `cssconfig` object. -In `index.html` scroll down to your toolbox definition and change the below line: +In `toolbox.js` scroll in to your toolbox definition and change the category with +the name "Logic": -```xml - +```js +{ + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + contents: [ /* ...existing blocks ... */ ], +}, ``` to be: -```xml - +```js +{ + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + cssconfig: { icon: 'customIcon fa fa-cog' }, + contents: [ /* ...existing blocks... */ ], +}, ``` All the classes used to create a category can be set similar to how we set the @@ -37,7 +51,7 @@ icon class above. See the [Blockly toolbox documentation](/guides/configure/tool ### Add some CSS -If you open `index.html` you will notice that the gear icon is positioned +If you open the running app you will notice that the gear icon is positioned incorrectly and is a bit difficult to see. We will use the `customIcon` class to change the color of the icon and use the `blocklyTreeRowContentContainer` class to position the icon above the text. @@ -62,7 +76,7 @@ In your `toolbox_style.css` file add: ### Update setSelected -If you open `index.html` and click on the "Logic" category you will notice +If you open the running app and click on the "Logic" category you will notice that the white icon now blends into the white background. In order to fix this, we are going to update our `setSelected` method to set the @@ -86,6 +100,8 @@ Your `setSelected` method should look similar to below: ```js /** @override */ setSelected(isSelected){ + super.setSelected(isSelected); + // We do not store the label span on the category, so use getElementsByClassName. var labelDom = this.rowDiv_.getElementsByClassName('blocklyToolboxCategoryLabel')[0]; if (isSelected) { @@ -101,15 +117,12 @@ Your `setSelected` method should look similar to below: labelDom.style.color = 'white'; this.iconDom_.style.color = 'white'; } - // This is used for accessibility purposes. - Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_), - Blockly.utils.aria.State.SELECTED, isSelected); } ``` ### The result -If you open your `index.html` file, you should see a white gear above your "Logic" +If you open the running app, you should see a white gear above your "Logic" label, and it should change to blue when the category has been selected. +```js +import './toolbox_label.js'; ``` -Create a class in `toolbox_label.js` that extends `Blockly.ToolboxItem` -and register it. +Create a class in `toolbox_label.js` that extends `Blockly.ToolboxItem` and register it. ```js +import * as Blockly from 'blockly'; + class ToolboxLabel extends Blockly.ToolboxItem { constructor(toolboxItemDef, parentToolbox) { super(toolboxItemDef, parentToolbox); @@ -43,21 +44,28 @@ Blockly.registry.register( By registering this toolbox item with the name "toolboxlabel" we can now use this name in our toolbox definition to add our custom item to the toolbox. -Navigate to `index.html`, and scroll down to the toolbox definition. Add a -`` element as the first item in your toolbox definition: +Navigate to `toolbox.js`, and add a `toolboxlabel` as the first item in the list +of toolbox contents: -```xml - +```js +{ + kind: 'toolboxlabel', +} ``` Your toolbox definition should now look something like: -```xml - +```js +export const toolbox = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'toolboxlabel', + }, + { kind: 'category', name: 'Logic', /* etc */ }, + // ... + ] +} ``` ### Initialize the toolbox item @@ -91,11 +99,11 @@ Next, we are going to return this element: } ``` -If you open the `index.html` file you should see a label above your first category. +If you refresh the app page you should see a label above your first category. The toolbox with a label at the top. @@ -106,11 +114,18 @@ label with the text "Label". To make it possible to create different labels with different text and colour we are going to add `name` and `colour` attributes to our toolbox definition. -Open `index.html` and navigate to the toolbox definition. Change your -`toolboxlabel` element to look like the below line: +Open `toolbox.js`. Add a `name` and `colour` to your toolbox label -```xml - +```js +contents: [ + { + kind: 'toolboxlabel', + name: 'Custom Toolbox', + colour: 'darkslategrey', + }, + { kind: 'category', name: 'Logic', /* etc */ }, + // ... +] ``` These values will get passed in to our `ToolboxLabel` class through the `toolboxItemDef`. @@ -132,7 +147,7 @@ this.label.textContent = 'Label'; All attributes on our toolbox definition get added to the `toolboxItemDef_`. `this.toolboxItemDef_` is set in the `Blockly.ToolboxItem` constructor. -Open your `index.html` in a browser to see the updated label. +Refresh your app in a browser to see the updated label. +```js +contents: [ + { + kind: 'toolboxlabel', + name: 'Custom Toolbox', + colour: 'darkslategrey', + cssconfig: { label: 'customLabel' }, + }, + { kind: 'category', name: 'Logic', /* etc */ }, + // ... +] ``` -Any item that begins with `css-` will be added to a `cssconfig` object stored on -the `toolboxItemDef`. +This gets added to a `cssconfig` object stored on the `toolboxItemDef`. To use this value navigate to `toolbox_label.js` and add the following lines to your `init` method. ```js -// Any attributes that begin with css- will get added to a cssconfig object. const cssConfig = this.toolboxItemDef_['cssconfig']; // Add the class. if (cssConfig) { @@ -178,11 +199,11 @@ the below CSS to make the label bold. ### The result -If you open `index.html` you should now see a bold dark gray label at the +If you refresh your app you should now see a bold dark gray label at the top of your toolbox. A toolbox with colored background and the blockly label above the category text. diff --git a/packages/docs/docs/codelabs/custom-toolbox/change-the-category-HTML.mdx b/packages/docs/docs/codelabs/custom-toolbox/change-the-category-HTML.mdx index 85633a143c9..b44948fce28 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/change-the-category-HTML.mdx +++ b/packages/docs/docs/codelabs/custom-toolbox/change-the-category-HTML.mdx @@ -6,7 +6,7 @@ import Image from '@site/src/components/Image'; # Customizing a Blockly toolbox -## 6. Change the category HTML +## 7. Change the category HTML If you only need to change the CSS, like we did in the previous section, then using the cssConfig is a great choice. However, if you need to change the html, maybe to add text, an image, or anything else, you can override @@ -16,29 +16,27 @@ to our category by overriding the `createIconDom_` method. ### Change the element for our icon By default, the `createIconDom_` method adds a `` element for the category -icon. We can override this to return an `` element. +icon. Note that you can override this to create other types of elements, if needed. -Add the following methods to `custom_category.js`: +Add the following method to `custom_category.js`: ```js /** @override */ createIconDom_() { - const img = document.createElement('img'); - img.src = './logo_only.svg'; - img.alt = 'Lamp'; - img.width='15'; - img.height='15'; - return img; + const icon = document.createElement('span'); + icon.textContent = '🧰'; + return icon; } ``` ### The result -If you open `index.html` you should now see the blockly logo on top of all your -categories +If you refresh the running app, you should now see the toolbox emoji on top of all your categories. +Keep in mind, this method of changing the HTML changed it for *all* categories. +This means that you have effectively removed the custom gear icon that was previously on your "Logic" category. A toolbox with the blockly logo on top of the category label. diff --git a/packages/docs/docs/codelabs/custom-toolbox/change-the-look-of-a-category.mdx b/packages/docs/docs/codelabs/custom-toolbox/change-the-look-of-a-category.mdx index 15efa0debc7..68693156c64 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/change-the-look-of-a-category.mdx +++ b/packages/docs/docs/codelabs/custom-toolbox/change-the-look-of-a-category.mdx @@ -6,7 +6,7 @@ import Image from '@site/src/components/Image'; # Customizing a Blockly toolbox -## 3. Change the look of a category +## 4. Change the look of a category ### Change the background of the category @@ -22,30 +22,42 @@ addColourBorder_(colour){ } ``` -The `colour` passed in is calculated from either the `categorystyle` or the `colour` -attribute set on the category definition. +The `colour` passed in is calculated from the `categorystyle` attribute set on the category definition. -For example, the "Logic" category definition looks like: +For example, you can see in your `toolbox.js` file that the "Logic" category definition looks like: -```xml - -... - +```js +{ + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + ... +} ``` -The logic_category style looks like: +The logic_category style (defined in Blockly's theme) is: -```json +```js "logic_category": { "colour": "210" } ``` +You can also set the colour directly via the `color` attribute on the category definition. + +```json +{ + kind: 'category', + name: 'Logic', + colour: '210', +} +``` + For more information on Blockly styles please visit the [themes documentation](/guides/configure/appearance/themes#category-style). ### Add some CSS -Open `index.html` to see your updated toolbox. Your toolbox should look +Open your application page to see your updated toolbox. Your toolbox should look similar to the below toolbox. +```js +import './toolbox_style.css'; ``` Copy and paste the following CSS into your `toolbox_style.css` file. @@ -84,7 +96,7 @@ Copy and paste the following CSS into your `toolbox_style.css` file. ### The result -Open `index.html` to see your toolbox. +Open the running application to see your toolbox. - - - - Toolbox Customization Codelab - - - - - - - - - - - -

Toolbox Customization Codelab

-
- - - - - diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/index.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/index.js deleted file mode 100644 index adddf0322c4..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/complete-code/index.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -let workspace = null; - -function start() { - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: document.getElementById('toolbox-categories'), - }); -} diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/logo_only.svg b/packages/docs/docs/codelabs/custom-toolbox/complete-code/logo_only.svg deleted file mode 100644 index 4616ee99f98..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/complete-code/logo_only.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - image/svg+xml - - logo-only - - - - - - - - logo-only - - - - - diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/package.json b/packages/docs/docs/codelabs/custom-toolbox/complete-code/package.json new file mode 100644 index 00000000000..d7795717c77 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/package.json @@ -0,0 +1,29 @@ +{ + "name": "custom-toolbox-codelab", + "version": "1.0.0", + "description": "A sample app using Blockly", + "main": "index.js", + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --mode production", + "start": "webpack serve --open --mode development" + }, + "keywords": [ + "blockly" + ], + "author": "", + "license": "Apache-2.0", + "devDependencies": { + "css-loader": "^6.7.1", + "html-webpack-plugin": "^5.5.0", + "source-map-loader": "^4.0.1", + "style-loader": "^3.3.1", + "webpack": "^5.93.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" + }, + "dependencies": { + "blockly": "^13.0.0" + } +} diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/blocks/text.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/blocks/text.js new file mode 100644 index 00000000000..244eaf1c9d4 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/blocks/text.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +// Create a custom block called 'add_text' that adds +// text to the output div on the sample app. +// This is just an example and you should replace this with your +// own custom blocks. +const addText = { + type: 'add_text', + message0: 'Add text %1', + args0: [ + { + type: 'input_value', + name: 'TEXT', + check: 'String', + }, + ], + previousStatement: null, + nextStatement: null, + colour: 160, + tooltip: '', + helpUrl: '', +}; + +// Create the block definitions for the JSON-only blocks. +// This does not register their definitions with Blockly. +// This file has no side effects! +export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([ + addText, +]); diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/custom_category_es6.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/custom_category.js similarity index 81% rename from packages/docs/docs/codelabs/custom-toolbox/complete-code/custom_category_es6.js rename to packages/docs/docs/codelabs/custom-toolbox/complete-code/src/custom_category.js index c9c15ec34fd..3a4a0a00d4f 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/complete-code/custom_category_es6.js +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/custom_category.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2020 Google LLC + * Copyright 2026 Raspberry Pi Foundation * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,8 @@ * @author aschmiedt@google.com (Abby Schmiedt) */ +import * as Blockly from 'blockly'; + class CustomCategory extends Blockly.ToolboxCategory { /** * Constructor for a custom category. @@ -34,6 +36,7 @@ class CustomCategory extends Blockly.ToolboxCategory { * @override */ setSelected(isSelected) { + super.setSelected(isSelected); // We do not store the label span on the category, so use getElementsByClassName. const labelDom = this.rowDiv_.getElementsByClassName( 'blocklyToolboxCategoryLabel', @@ -51,12 +54,6 @@ class CustomCategory extends Blockly.ToolboxCategory { labelDom.style.color = 'white'; this.iconDom_.style.color = 'white'; } - // This is used for accessibility purposes. - Blockly.utils.aria.setState( - /** @type {!Element} */ (this.htmlDiv_), - Blockly.utils.aria.State.SELECTED, - isSelected, - ); } /** @@ -65,12 +62,9 @@ class CustomCategory extends Blockly.ToolboxCategory { * @override */ createIconDom_() { - const iconImg = document.createElement('img'); - iconImg.src = './logo_only.svg'; - iconImg.alt = 'Blockly Logo'; - iconImg.width = '25'; - iconImg.height = '25'; - return iconImg; + const icon = document.createElement('span'); + icon.textContent = '🧰'; + return icon; } } diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/generators/javascript.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/generators/javascript.js new file mode 100644 index 00000000000..47bca6b2af4 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/generators/javascript.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Order } from 'blockly/javascript'; + +// Export all the code generators for our custom blocks, +// but don't register them with Blockly yet. +// This file has no side effects! +export const forBlock = Object.create(null); + +forBlock['add_text'] = function (block, generator) { + const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''"; + const addText = generator.provideFunction_( + 'addText', + `function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) { + + // Add text to the output area. + const outputDiv = document.getElementById('output'); + const textEl = document.createElement('p'); + textEl.innerText = text; + outputDiv.appendChild(textEl); +}`, + ); + // Generate the function call for this block. + const code = `${addText}(${text});\n`; + return code; +}; diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.css b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.css new file mode 100644 index 00000000000..f282700701f --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.css @@ -0,0 +1,40 @@ +body { + margin: 0; + max-width: 100vw; +} + +pre, +code { + overflow: auto; +} + +#pageContainer { + display: flex; + width: 100%; + max-width: 100vw; + height: 100vh; +} + +#blocklyDiv { + flex-basis: 100%; + height: 100%; + min-width: 600px; +} + +#outputPane { + display: flex; + flex-direction: column; + width: 400px; + flex: 0 0 400px; + overflow: auto; + margin: 1rem; +} + +#generatedCode { + height: 50%; + background-color: rgb(247, 240, 228); +} + +#output { + height: 50%; +} diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.html b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.html new file mode 100644 index 00000000000..401be7f17a7 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.html @@ -0,0 +1,19 @@ + + + + + Blockly Sample App + + + +
+
+
+
+
+
+
+ + diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.js new file mode 100644 index 00000000000..e5c0a925bf5 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/index.js @@ -0,0 +1,71 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly'; +import { blocks } from './blocks/text'; +import { forBlock } from './generators/javascript'; +import { javascriptGenerator } from 'blockly/javascript'; +import { save, load } from './serialization'; +import { toolbox } from './toolbox'; +import './index.css'; +import './toolbox_style.css'; +import './custom_category.js'; +import './toolbox_label.js'; + +// Register the blocks and generator with Blockly +Blockly.common.defineBlocks(blocks); +Object.assign(javascriptGenerator.forBlock, forBlock); + +// Set up UI elements and inject Blockly +const codeDiv = document.getElementById('generatedCode').firstChild; +const outputDiv = document.getElementById('output'); +const blocklyDiv = document.getElementById('blocklyDiv'); +const ws = Blockly.inject(blocklyDiv, { toolbox }); + +// This function resets the code and output divs, shows the +// generated code from the workspace, and evals the code. +// In a real application, you probably shouldn't use `eval`. +const runCode = () => { + const code = javascriptGenerator.workspaceToCode(ws); + codeDiv.innerText = code; + + outputDiv.innerHTML = ''; + + // Wrap `eval` in a `try/catch` so that any runtime errors are + // logged to the console, instead of failing quietly. + try { + eval(code); + } catch (error) { + console.log(error); + } +}; + +// Load the initial state from storage and run the code. +load(ws); +runCode(); + +// Every time the workspace changes state, save the changes to storage. +ws.addChangeListener((e) => { + // UI events are things like scrolling, zooming, etc. + // No need to save after one of these. + if (e.isUiEvent) return; + save(ws); +}); + +// Whenever the workspace changes meaningfully, run the code again. +ws.addChangeListener((e) => { + // Don't run the code when the workspace finishes loading; we're + // already running it once when the application starts. + // Don't run the code during drags; we might have invalid state. + if ( + e.isUiEvent || + e.type == Blockly.Events.FINISHED_LOADING || + ws.isDragging() + ) { + return; + } + runCode(); +}); diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/serialization.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/serialization.js new file mode 100644 index 00000000000..175efa14f39 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/serialization.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +const storageKey = 'customToolboxWorkspace'; + +/** + * Saves the state of the workspace to browser's local storage. + * @param {Blockly.Workspace} workspace Blockly workspace to save. + */ +export const save = function (workspace) { + const data = Blockly.serialization.workspaces.save(workspace); + window.localStorage?.setItem(storageKey, JSON.stringify(data)); +}; + +/** + * Loads saved state from local storage into the given workspace. + * @param {Blockly.Workspace} workspace Blockly workspace to load into. + */ +export const load = function (workspace) { + const data = window.localStorage?.getItem(storageKey); + if (!data) return; + + // Don't emit events during loading. + Blockly.Events.disable(); + Blockly.serialization.workspaces.load(JSON.parse(data), workspace, false); + Blockly.Events.enable(); +}; diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox.js new file mode 100644 index 00000000000..49efff4d421 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox.js @@ -0,0 +1,636 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +/* +This toolbox contains nearly every single built-in block that Blockly offers, +in addition to the custom block 'add_text' this sample app adds. +You probably don't need every single block, and should consider either rewriting +your toolbox from scratch, or carefully choosing whether you need each block +listed here. +*/ + +export const toolbox = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'toolboxlabel', + name: 'Custom Toolbox', + colour: 'darkslategrey', + cssconfig: { label: 'customLabel' }, + }, + { + kind: 'category', + name: 'Logic', + categorystyle: 'logic_category', + cssconfig: { icon: 'customIcon fa fa-cog' }, + contents: [ + { + kind: 'block', + type: 'controls_if', + }, + { + kind: 'block', + type: 'logic_compare', + }, + { + kind: 'block', + type: 'logic_operation', + }, + { + kind: 'block', + type: 'logic_negate', + }, + { + kind: 'block', + type: 'logic_boolean', + }, + { + kind: 'block', + type: 'logic_null', + }, + { + kind: 'block', + type: 'logic_ternary', + }, + ], + }, + { + kind: 'category', + name: 'Loops', + categorystyle: 'loop_category', + contents: [ + { + kind: 'block', + type: 'controls_repeat_ext', + inputs: { + TIMES: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_whileUntil', + }, + { + kind: 'block', + type: 'controls_for', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + BY: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'controls_forEach', + }, + { + kind: 'block', + type: 'controls_flow_statements', + }, + ], + }, + { + kind: 'category', + name: 'Math', + categorystyle: 'math_category', + contents: [ + { + kind: 'block', + type: 'math_number', + fields: { + NUM: 123, + }, + }, + { + kind: 'block', + type: 'math_arithmetic', + inputs: { + A: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + B: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_single', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 9, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_trig', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 45, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constant', + }, + { + kind: 'block', + type: 'math_number_property', + inputs: { + NUMBER_TO_CHECK: { + shadow: { + type: 'math_number', + fields: { + NUM: 0, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_round', + fields: { + OP: 'ROUND', + }, + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 3.1, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_on_list', + fields: { + OP: 'SUM', + }, + }, + { + kind: 'block', + type: 'math_modulo', + inputs: { + DIVIDEND: { + shadow: { + type: 'math_number', + fields: { + NUM: 64, + }, + }, + }, + DIVISOR: { + shadow: { + type: 'math_number', + fields: { + NUM: 10, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_constrain', + inputs: { + VALUE: { + shadow: { + type: 'math_number', + fields: { + NUM: 50, + }, + }, + }, + LOW: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + HIGH: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_int', + inputs: { + FROM: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + TO: { + shadow: { + type: 'math_number', + fields: { + NUM: 100, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'math_random_float', + }, + { + kind: 'block', + type: 'math_atan2', + inputs: { + X: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + Y: { + shadow: { + type: 'math_number', + fields: { + NUM: 1, + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Text', + categorystyle: 'text_category', + contents: [ + { + kind: 'block', + type: 'text', + }, + { + kind: 'block', + type: 'text_join', + }, + { + kind: 'block', + type: 'text_append', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_length', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_isEmpty', + inputs: { + VALUE: { + shadow: { + type: 'text', + fields: { + TEXT: '', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + FIND: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_charAt', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_getSubstring', + inputs: { + STRING: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_changeCase', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_trim', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'text_count', + inputs: { + SUB: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_replace', + inputs: { + FROM: { + shadow: { + type: 'text', + }, + }, + TO: { + shadow: { + type: 'text', + }, + }, + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'text_reverse', + inputs: { + TEXT: { + shadow: { + type: 'text', + }, + }, + }, + }, + { + kind: 'block', + type: 'add_text', + inputs: { + TEXT: { + shadow: { + type: 'text', + fields: { + TEXT: 'abc', + }, + }, + }, + }, + }, + ], + }, + { + kind: 'category', + name: 'Lists', + categorystyle: 'list_category', + contents: [ + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_create_with', + }, + { + kind: 'block', + type: 'lists_repeat', + inputs: { + NUM: { + shadow: { + type: 'math_number', + fields: { + NUM: 5, + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_length', + }, + { + kind: 'block', + type: 'lists_isEmpty', + }, + { + kind: 'block', + type: 'lists_indexOf', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getIndex', + inputs: { + VALUE: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_setIndex', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_getSublist', + inputs: { + LIST: { + block: { + type: 'variables_get', + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_split', + inputs: { + DELIM: { + shadow: { + type: 'text', + fields: { + TEXT: ',', + }, + }, + }, + }, + }, + { + kind: 'block', + type: 'lists_sort', + }, + { + kind: 'block', + type: 'lists_reverse', + }, + ], + }, + { + kind: 'sep', + }, + { + kind: 'category', + name: 'Variables', + categorystyle: 'variable_category', + custom: 'VARIABLE', + }, + { + kind: 'category', + name: 'Functions', + categorystyle: 'procedure_category', + custom: 'PROCEDURE', + }, + ], +}; diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_label.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_label.js new file mode 100644 index 00000000000..ae1d0388b50 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_label.js @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly'; + +class ToolboxLabel extends Blockly.ToolboxItem { + constructor(toolboxItemDef, parentToolbox) { + super(toolboxItemDef, parentToolbox); + } + + /** @override */ + init() { + // Create the label. + this.label = document.createElement('label'); + // Set the name. + this.label.textContent = this.toolboxItemDef_['name']; + // Set the color. + this.label.style.color = this.toolboxItemDef_['colour']; + + const cssConfig = this.toolboxItemDef_['cssconfig']; + // Add the class. + if (cssConfig) { + this.label.classList.add(cssConfig['label']); + } + } + + /** @override */ + getDiv() { + return this.label; + } +} + +Blockly.registry.register( + Blockly.registry.Type.TOOLBOX_ITEM, + 'toolboxlabel', + ToolboxLabel, +); diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_style.css b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_style.css similarity index 94% rename from packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_style.css rename to packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_style.css index 067e90aba74..c5916879067 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_style.css +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/src/toolbox_style.css @@ -16,6 +16,7 @@ .customIcon { color: #fff; } + /* Stacks the icon on top of the label. */ .blocklyTreeRowContentContainer { display: flex; @@ -25,3 +26,7 @@ .blocklyToolboxCategory { height: initial; } + +.customLabel { + font-weight: bold; +} diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_label_es6.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_label_es6.js deleted file mode 100644 index 401ffa42aac..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/complete-code/toolbox_label_es6.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @license - * Copyright 2020 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @fileoverview The toolbox label built during the custom toolbox codelab, in es6. - * @author aschmiedt@google.com (Abby Schmiedt) - */ - -class ToolboxLabel extends Blockly.ToolboxItem { - /** - * Constructor for a label in the toolbox. - * @param {!Blockly.utils.toolbox.ToolboxItemInfo} toolboxItemDef The toolbox - * item definition. This comes directly from the toolbox definition. - * @param {!Blockly.IToolbox} parentToolbox The toolbox that holds this - * toolbox item. - * @override - */ - constructor(toolboxItemDef, parentToolbox) { - super(toolboxItemDef, parentToolbox); - /** - * The button element. - * @type {?HTMLLabelElement} - */ - this.label = null; - } - - /** - * Init method for the label. - * @override - */ - init() { - // Create the label. - this.label = document.createElement('label'); - // Set the name. - this.label.textContent = this.toolboxItemDef_['name']; - // Set the color. - this.label.style.color = this.toolboxItemDef_['colour']; - // Any attributes that begin with css- will get added to a cssconfig. - const cssConfig = this.toolboxItemDef_['cssconfig']; - // Add the class. - if (cssConfig) { - this.label.classList.add(cssConfig['label']); - } - } - - /** - * Gets the div for the toolbox item. - * @returns {HTMLLabelElement} The label element. - * @override - */ - getDiv() { - return this.label; - } -} - -Blockly.registry.register( - Blockly.registry.Type.TOOLBOX_ITEM, - 'toolboxlabel', - ToolboxLabel, -); diff --git a/packages/docs/docs/codelabs/custom-toolbox/complete-code/webpack.config.js b/packages/docs/docs/codelabs/custom-toolbox/complete-code/webpack.config.js new file mode 100644 index 00000000000..1a095648fd7 --- /dev/null +++ b/packages/docs/docs/codelabs/custom-toolbox/complete-code/webpack.config.js @@ -0,0 +1,59 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +// Base config that applies to either development or production mode. +const config = { + entry: './src/index.js', + output: { + // Compile the source files into a bundle. + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), + clean: true, + }, + // Enable webpack-dev-server to get hot refresh of the app. + devServer: { + static: './build', + }, + module: { + rules: [ + { + // Load CSS files. They can be imported into JS files. + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + ], + }, + plugins: [ + // Generate the HTML index page based on our template. + // This will output the same index page with the bundle we + // created above added in a script tag. + new HtmlWebpackPlugin({ + template: 'src/index.html', + }), + ], +}; + +module.exports = (env, argv) => { + if (argv.mode === 'development') { + // Set the output path to the `build` directory + // so we don't clobber production builds. + config.output.path = path.resolve(__dirname, 'build'); + + // Generate source maps for our code for easier debugging. + // Not suitable for production builds. If you want source maps in + // production, choose a different one from https://webpack.js.org/configuration/devtool + config.devtool = 'eval-cheap-module-source-map'; + + // Include the source maps for Blockly for easier debugging Blockly code. + config.module.rules.push({ + test: /(blockly[/\\].*\.js)$/, + use: [require.resolve('source-map-loader')], + enforce: 'pre', + }); + + // Ignore spurious warnings from source-map-loader + // It can't find source maps for some Closure modules and that is expected + config.ignoreWarnings = [/Failed to parse source map.*blockly/]; + } + return config; +}; diff --git a/packages/docs/docs/codelabs/custom-toolbox/setup.mdx b/packages/docs/docs/codelabs/custom-toolbox/setup.mdx index a0e2fe013c6..59fa432e34b 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/setup.mdx +++ b/packages/docs/docs/codelabs/custom-toolbox/setup.mdx @@ -8,92 +8,34 @@ import Image from '@site/src/components/Image'; ## 2. Setup -### Download the sample code +### Create the application -You can get the sample code for this codelab by either downloading the zip here: +Use the [`npx @blockly/create-package app`](https://www.npmjs.com/package/@blockly/create-package) command to create a standalone application that contains a sample setup of Blockly, including custom blocks and a display of the generated code and output. -[Download zip](https://github.com/RaspberryPiFoundation/blockly/archive/main.zip) +1. Run `npx @blockly/create-package app custom-toolbox-codelab` in your terminal. This will create a Blockly application in the folder `custom-toolbox-codelab`. +1. Use `cd` to move into the new directory: `cd custom-toolbox-codelab`. +1. Run `npm start` to start the server and run the sample application. +1. The sample app will automatically run in the browser window that opens. -or by cloning this git repo: +The local address in the address bar of your browser starting with `http://localhost:` shows where your app is. +If you accidentally close this window or tab, you can re-open this local address to view the app as long as `npm start` is still running. -```bash -git clone https://github.com/RaspberryPiFoundation/blockly.git -``` - -If you downloaded the source as a zip, unpacking it should give you a root folder named `blockly-main`. - -The relevant files are in `packages/docs/docs/codelabs/custom-toolbox`. There are two versions of the app: - -- `starter-code/`: The starter code that you'll build upon in this codelab. -- `complete-code/`: The code after completing the codelab, in case you get lost or want to compare to your version. - -Each folder contains: +The initial application has a toolbox definition included in already included in the `toolbox.js` file. +This codelab focuses on customizing the *appearance* of a Blockly toolbox rather than the content and categories, but feel free to experiment with customizing the content of the toolbox in `toolbox.js`. -- `index.js` - The codelab's logic. To start, it just injects a simple workspace. -- `index.html` - A web page containing a simple blockly workspace. +### Change the storage key -To run the code, simply open `starter-code/index.html` in a browser. You should see a Blockly workspace with a toolbox. - -![A web page with the text "Toolbox Customization Codelab" and a Blockly workspace.](../../../static/images/codelabs/custom-toolbox/starter_workspace.png) - -### Define and register a custom category - -To start, create a file named `custom_category.js` in the `starter-code` -directory. Include your new file by adding a script tag to `index.html`. - -```html - -``` +Before setting up the rest of the application, **change the storage key** used for this codelab application. This will ensure that the workspace is saved in its own storage, separate from the regular sample app, so that it doesn't interfere with other demos. -In order to create a custom category we will create a new category that extends -the default `Blockly.ToolboxCategory` class. Add the following code to your -`custom_category.js` file. +In `serialization.js`, change the value of `storageKey` to some unique string. `customToolboxWorkspace` will work: ```js -class CustomCategory extends Blockly.ToolboxCategory { - /** - * Constructor for a custom category. - * @override - */ - constructor(categoryDef, toolbox, opt_parent) { - super(categoryDef, toolbox, opt_parent); - } -} +// Use a unique storage key for this codelab +const storageKey = 'customToolboxWorkspace'; ``` -After defining your category you need to tell Blockly that it exists. -Register your category by adding the below code to the end of `custom_category.js`. +### Explore the starter code -```js -Blockly.registry.register( - Blockly.registry.Type.TOOLBOX_ITEM, - Blockly.ToolboxCategory.registrationName, - CustomCategory, - true, -); -``` - -By registering our `CustomCategory` with `Blockly.ToolboxCategory.registrationName` -we are overriding the default category in Blockly. Because we are overriding a -toolbox item instead of adding a new one, we must pass in `true` as the last -argument. If this flag is `false`, `Blockly.registry.register` will throw -an error because we are overriding an existing class. - -### The result - -To test, open `index.html` in a browser. Your toolbox should look the same as it -did before. +Take a moment to explore the code you just created in the `custom-toolbox-codelab` folder. If you'd like a more in-depth explanation of all of the code in the sample app, you can go through the [getting started codelab](/codelabs/getting-started/codelab-overview) which goes through creating this sample app from scratch. -The default toolbox. A list of categories with a strip of colour to the left. - -However, if you run the below commands in your console you will see that -your toolbox is now using the `CustomCategory` class. - -```js -var toolbox = Blockly.common.getMainWorkspace().getToolbox(); -toolbox.getToolboxItems()[0]; -``` +For a briefer outline of the code, visit the [sample app overview](/guides/get-started/sample-app-overview/) page for an overview of the included files and tooling. \ No newline at end of file diff --git a/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.html b/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.html deleted file mode 100644 index 6e1d8f8dc87..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.html +++ /dev/null @@ -1,349 +0,0 @@ - - - - - Toolbox Customization Codelab - - - - - - - -

Toolbox Customization Codelab

-
- - - - - diff --git a/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.js b/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.js deleted file mode 100644 index adddf0322c4..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/starter-code/index.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -let workspace = null; - -function start() { - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: document.getElementById('toolbox-categories'), - }); -} diff --git a/packages/docs/docs/codelabs/custom-toolbox/starter-code/logo_only.svg b/packages/docs/docs/codelabs/custom-toolbox/starter-code/logo_only.svg deleted file mode 100644 index 4616ee99f98..00000000000 --- a/packages/docs/docs/codelabs/custom-toolbox/starter-code/logo_only.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - image/svg+xml - - logo-only - - - - - - - - logo-only - - - - - diff --git a/packages/docs/docs/codelabs/custom-toolbox/summary.mdx b/packages/docs/docs/codelabs/custom-toolbox/summary.mdx index e58b8b26013..8ae40b4e228 100644 --- a/packages/docs/docs/codelabs/custom-toolbox/summary.mdx +++ b/packages/docs/docs/codelabs/custom-toolbox/summary.mdx @@ -5,7 +5,7 @@ description: Summary of the "Customizing a Blockly toolbox" codelab. # Customizing a Blockly toolbox -## 8. Summary +## 9. Summary The toolbox can be customized in a variety of ways to make it work for your application. In this codelab you learned: diff --git a/packages/docs/sidebars.js b/packages/docs/sidebars.js index b3f73202050..e3028c3fb56 100644 --- a/packages/docs/sidebars.js +++ b/packages/docs/sidebars.js @@ -102,32 +102,37 @@ const sidebars = { }, { type: 'doc', - label: '3. Change the look of a category', + label: '3. Add a custom category class', + id: 'codelabs/custom-toolbox/add-a-custom-category', + }, + { + type: 'doc', + label: '4. Change the look of a category', id: 'codelabs/custom-toolbox/change-the-look-of-a-category', }, { type: 'doc', - label: '4. Change the look of a selected category', + label: '5. Change the look of a selected category', id: 'codelabs/custom-toolbox/change-the-look-of-a-selected-category', }, { type: 'doc', - label: '5. Add an icon to your category', + label: '6. Add an icon to your category', id: 'codelabs/custom-toolbox/add-an-icon-to-your-category', }, { type: 'doc', - label: '6. Change the category HTML', + label: '7. Change the category HTML', id: 'codelabs/custom-toolbox/change-the-category-HTML', }, { type: 'doc', - label: '7. Adding a custom toolbox item', + label: '8. Adding a custom toolbox item', id: 'codelabs/custom-toolbox/adding-a-custom-toolbox-item', }, { type: 'doc', - label: '8. Summary', + label: '9. Summary', id: 'codelabs/custom-toolbox/summary', }, ], diff --git a/packages/docs/static/images/codelabs/custom-toolbox/custom_label.png b/packages/docs/static/images/codelabs/custom-toolbox/custom_label.png index bc080a43b41..1742dd60035 100644 Binary files a/packages/docs/static/images/codelabs/custom-toolbox/custom_label.png and b/packages/docs/static/images/codelabs/custom-toolbox/custom_label.png differ diff --git a/packages/docs/static/images/codelabs/custom-toolbox/final_toolbox.png b/packages/docs/static/images/codelabs/custom-toolbox/final_toolbox.png index a44e35b69da..6599bb636c8 100644 Binary files a/packages/docs/static/images/codelabs/custom-toolbox/final_toolbox.png and b/packages/docs/static/images/codelabs/custom-toolbox/final_toolbox.png differ diff --git a/packages/docs/static/images/codelabs/custom-toolbox/image_toolbox.png b/packages/docs/static/images/codelabs/custom-toolbox/image_toolbox.png index f855303ed8e..6f1c7f7e8a7 100644 Binary files a/packages/docs/static/images/codelabs/custom-toolbox/image_toolbox.png and b/packages/docs/static/images/codelabs/custom-toolbox/image_toolbox.png differ diff --git a/packages/docs/static/images/codelabs/custom-toolbox/toolbox_label.png b/packages/docs/static/images/codelabs/custom-toolbox/toolbox_label.png index e22c18a1a89..6e36960c686 100644 Binary files a/packages/docs/static/images/codelabs/custom-toolbox/toolbox_label.png and b/packages/docs/static/images/codelabs/custom-toolbox/toolbox_label.png differ