diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/codelab-overview.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/codelab-overview.mdx index 5c1b3fb3677..649324f1115 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/codelab-overview.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/codelab-overview.mdx @@ -3,6 +3,8 @@ pagination_prev: null description: Overview of the "Customizing your themes" codelab. --- +import Image from '@site/src/components/Image'; + # Customizing your themes ## 1. Codelab overview @@ -17,12 +19,27 @@ In this codelab you will learn how to: ### What you'll build -A very simple Blockly workspace with customized themes. +You'll start from the Blockly sample app and give it a custom "Halloween" theme, +customizing the colours of the workspace components, the toolbox categories, and +the blocks themselves. + +A Blockly workspace with a custom orange and purple Halloween theme. + +The code samples are written in ES6 syntax. You can find the code for the +[completed theme codelab](https://github.com/RaspberryPiFoundation/blockly/tree/main/packages/docs/docs/codelabs/theme-extension-identifier/complete-code) +on GitHub. ### What you'll need -- A browser +- A browser. +- A text editor. - Basic knowledge of HTML, CSS, and JavaScript. +- node.js and npm installed ([instructions](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)). +- Comfort using the command line/terminal. This codelab is focused on Blockly's theme extension. Non-relevant concepts -are glossed over and provided for you to simple copy and paste. +are glossed over and provided for you to simply copy and paste. diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/README.md b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/README.md new file mode 100644 index 00000000000..b23b9415d34 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/README.md @@ -0,0 +1,14 @@ +# Completed code + +The finished code for the [Customizing your themes](../codelab-overview.mdx) codelab. + +## Run it + +To run the complete code, navigate to the `complete-code` folder in your terminal, then run: + +```bash +npm install # downloads the libraries the app needs (one time) +npm start # opens the app at http://localhost:... +``` + +New to npm? See [installing node & npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). \ No newline at end of file diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.html b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.html deleted file mode 100644 index f43e1187827..00000000000 --- a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Theme Extension Codelab - - - - - - - - -

Theme Extension Codelab

-
- - diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.js deleted file mode 100644 index 45cdc22e1b9..00000000000 --- a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/index.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -let workspace = null; - -Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { - base: Blockly.Themes.Classic, - categoryStyles: { - list_category: { - colour: '#4a148c', - }, - logic_category: { - colour: '#8b4513', - }, - loop_category: { - colour: '#85E21F', - }, - text_category: { - colour: '#FE9B13', - }, - }, - blockStyles: { - list_blocks: { - colourPrimary: '#4a148c', - colourSecondary: '#AD7BE9', - colourTertiary: '#CDB6E9', - }, - logic_blocks: { - colourPrimary: '#8b4513', - colourSecondary: '#ff0000', - colourTertiary: '#C5EAFF', - }, - loop_blocks: { - colourPrimary: '#85E21F', - colourSecondary: '#ff0000', - colourTertiary: '#C5EAFF', - }, - text_blocks: { - colourPrimary: '#FE9B13', - colourSecondary: '#ff0000', - colourTertiary: '#C5EAFF', - }, - }, - componentStyles: { - workspaceBackgroundColour: '#ff7518', - toolboxBackgroundColour: '#F9C10E', - toolboxForegroundColour: '#fff', - flyoutBackgroundColour: '#252526', - flyoutForegroundColour: '#ccc', - flyoutOpacity: 1, - scrollbarColour: '#ff0000', - insertionMarkerColour: '#fff', - insertionMarkerOpacity: 0.3, - scrollbarOpacity: 0.4, - cursorColour: '#d0d0d0', - blackBackground: '#333', - }, -}); -function start() { - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxCategories, - theme: Blockly.Themes.Halloween, - }); -} diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/package.json b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/package.json new file mode 100644 index 00000000000..7e5f2508820 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/package.json @@ -0,0 +1,29 @@ +{ + "name": "custom-theme-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/theme-extension-identifier/complete-code/src/blocks/text.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/blocks/text.js new file mode 100644 index 00000000000..244eaf1c9d4 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/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/theme-extension-identifier/complete-code/src/generators/javascript.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/generators/javascript.js new file mode 100644 index 00000000000..47bca6b2af4 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/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/theme-extension-identifier/complete-code/src/index.css b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.css new file mode 100644 index 00000000000..f282700701f --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/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/theme-extension-identifier/complete-code/src/index.html b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.html new file mode 100644 index 00000000000..36d8eeacd99 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.html @@ -0,0 +1,16 @@ + + + + + Blockly Sample App + + +
+
+
+
+
+
+
+ + diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.js new file mode 100644 index 00000000000..debb5314be5 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/index.js @@ -0,0 +1,150 @@ +/** + * @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'; + +// Register the blocks and generator with Blockly +Blockly.common.defineBlocks(blocks); +Object.assign(javascriptGenerator.forBlock, forBlock); + +// Define a custom Halloween theme that extends the built-in Classic theme. +Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { + base: Blockly.Themes.Classic, + categoryStyles: { + logic_category: { + colour: '#8b4513', + }, + loop_category: { + colour: '#85E21F', + }, + math_category: { + colour: '#542788', + }, + text_category: { + colour: '#FE9B13', + }, + list_category: { + colour: '#4a148c', + }, + variable_category: { + colour: '#cc0000', + }, + procedure_category: { + colour: '#1b5e20', + }, + }, + blockStyles: { + logic_blocks: { + colourPrimary: '#8b4513', + colourSecondary: '#ff0000', + colourTertiary: '#C5EAFF', + }, + loop_blocks: { + colourPrimary: '#85E21F', + colourSecondary: '#ff0000', + colourTertiary: '#C5EAFF', + }, + math_blocks: { + colourPrimary: '#542788', + colourSecondary: '#9a7fc7', + colourTertiary: '#cdb6e9', + }, + text_blocks: { + colourPrimary: '#FE9B13', + colourSecondary: '#ff0000', + colourTertiary: '#C5EAFF', + }, + list_blocks: { + colourPrimary: '#4a148c', + colourSecondary: '#AD7BE9', + colourTertiary: '#CDB6E9', + }, + variable_blocks: { + colourPrimary: '#cc0000', + colourSecondary: '#ff6666', + colourTertiary: '#ffcccc', + }, + procedure_blocks: { + colourPrimary: '#1b5e20', + colourSecondary: '#4caf50', + colourTertiary: '#c8e6c9', + }, + }, + componentStyles: { + workspaceBackgroundColour: '#ff7518', + toolboxBackgroundColour: '#F9C10E', + toolboxForegroundColour: '#fff', + flyoutBackgroundColour: '#252526', + flyoutForegroundColour: '#ccc', + flyoutOpacity: 1, + scrollbarColour: '#ff0000', + insertionMarkerColour: '#fff', + insertionMarkerOpacity: 0.3, + scrollbarOpacity: 0.4, + cursorColour: '#d0d0d0', + blackBackground: '#333', + }, +}); + +// 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, + theme: Blockly.Themes.Halloween, +}); + +// 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/theme-extension-identifier/complete-code/src/serialization.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/serialization.js new file mode 100644 index 00000000000..0db3130722c --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/serialization.js @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +// Use a unique storage key for this codelab +const storageKey = 'customThemeWorkspace'; + +/** + * 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/theme-extension-identifier/complete-code/src/toolbox.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/toolbox.js new file mode 100644 index 00000000000..1ff8beb3e41 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/src/toolbox.js @@ -0,0 +1,629 @@ +/** + * @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: 'category', + name: 'Logic', + categorystyle: 'logic_category', + 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/theme-extension-identifier/complete-code/webpack.config.js b/packages/docs/docs/codelabs/theme-extension-identifier/complete-code/webpack.config.js new file mode 100644 index 00000000000..1a095648fd7 --- /dev/null +++ b/packages/docs/docs/codelabs/theme-extension-identifier/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/theme-extension-identifier/customize-block-styles.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/customize-block-styles.mdx index e85f667495b..b0b8f33cbb1 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/customize-block-styles.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/customize-block-styles.mdx @@ -2,39 +2,46 @@ description: How to use a theme to set the colours of blocks. --- +import Image from '@site/src/components/Image'; + # Customizing your themes ## 6. Customize Block Styles A block style currently only holds three different colour properties. They are 'colourPrimary', 'colourSecondary' and 'colourTertiary'. This value can either be defined as a hex value or as a hue. -For more information on block styles visit our themes [documentation](/guides/configure/appearance/themes#block-style) +For more information on block styles visit our themes [documentation.](/guides/configure/appearance/themes#block-style) -Update the Theme definition to have the block styles as below. +Add a `blockStyles` section to the theme definition in `src/index.js` so that it now looks like the below. +There is one block style for each category in the sample app's toolbox: ```js Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { base: Blockly.Themes.Classic, categoryStyles: { - list_category: { - colour: '#4a148c', - }, logic_category: { colour: '#8b4513', }, loop_category: { colour: '#85E21F', }, + math_category: { + colour: '#542788', + }, text_category: { colour: '#FE9B13', }, + list_category: { + colour: '#4a148c', + }, + variable_category: { + colour: '#cc0000', + }, + procedure_category: { + colour: '#1b5e20', + }, }, blockStyles: { - list_blocks: { - colourPrimary: '#4a148c', - colourSecondary: '#AD7BE9', - colourTertiary: '#CDB6E9', - }, logic_blocks: { colourPrimary: '#8b4513', colourSecondary: '#ff0000', @@ -45,11 +52,31 @@ Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { colourSecondary: '#ff0000', colourTertiary: '#C5EAFF', }, + math_blocks: { + colourPrimary: '#542788', + colourSecondary: '#9a7fc7', + colourTertiary: '#cdb6e9', + }, text_blocks: { colourPrimary: '#FE9B13', colourSecondary: '#ff0000', colourTertiary: '#C5EAFF', }, + list_blocks: { + colourPrimary: '#4a148c', + colourSecondary: '#AD7BE9', + colourTertiary: '#CDB6E9', + }, + variable_blocks: { + colourPrimary: '#cc0000', + colourSecondary: '#ff6666', + colourTertiary: '#ffcccc', + }, + procedure_blocks: { + colourPrimary: '#1b5e20', + colourSecondary: '#4caf50', + colourTertiary: '#c8e6c9', + }, }, componentStyles: { workspaceBackgroundColour: '#ff7518', @@ -70,6 +97,10 @@ Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { ### Test it -Click on different blocks in the component and you should see the colours that you applied show up. +Refresh the page, then click on different categories and drag out blocks. You should see the colours that you applied show up. -![Customized Block colors on the components](../../../static/images/codelabs/theme-extension/block_styles.png) +Blockly workspace and flyout with customized block colours. diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/customize-category-styles.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/customize-category-styles.mdx index 2927b430f62..262fa09e274 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/customize-category-styles.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/customize-category-styles.mdx @@ -2,6 +2,8 @@ description: How to use a theme to set the colours of categories. --- +import Image from '@site/src/components/Image'; + # Customizing your themes ## 5. Customize Category Styles @@ -11,24 +13,34 @@ This value can either be defined as a hex value or as a hue. Usually these colou the colourPrimary on the majority of blocks in the category making it easy for users to tell what blocks belong in what category. -Update the Theme definition to have the category styles as below. +Add a `categoryStyles` section to the theme definition in `src/index.js`. The sample app's toolbox includes +Logic, Loops, Math, Text, Lists, Variables, and Functions categories, so we'll provide a style for each one: ```js Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { base: Blockly.Themes.Classic, categoryStyles: { - list_category: { - colour: '#4a148c', - }, logic_category: { colour: '#8b4513', }, loop_category: { colour: '#85E21F', }, + math_category: { + colour: '#542788', + }, text_category: { colour: '#FE9B13', }, + list_category: { + colour: '#4a148c', + }, + variable_category: { + colour: '#cc0000', + }, + procedure_category: { + colour: '#1b5e20', + }, }, componentStyles: { workspaceBackgroundColour: '#ff7518', @@ -49,7 +61,11 @@ Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { ### Test it -The colour displayed next to the toolbox category should display your new colours. +Refresh the page. The colour displayed next to each toolbox category should display your new colours. Clicking on a category will highlight the row with your new colour. -![Customized Block colors on the components](../../../static/images/codelabs/theme-extension/customized_categories.png) +Customized category colours on the toolbox. diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/customize-components.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/customize-components.mdx index f07bdfd88a5..2cf5184fffc 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/customize-components.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/customize-components.mdx @@ -2,11 +2,13 @@ description: How to use a theme to set the colours of components. --- +import Image from '@site/src/components/Image'; + # Customizing your themes ## 4. Customize Components -Within the Halloween theme definition, you can customize the colours of multiple components: +Within the Halloween theme definition in `src/index.js`, you can customize the colours of multiple components. Update the theme definition to add the `componentStyles` shown below: ```js Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { @@ -30,6 +32,17 @@ Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { ### Test it -Reload your web page. You should see a themed workspace! +If the server is already running, refresh the page to see the new changes. Otherwise, run `npm start` to start the server. You should see a themed workspace! + +You may notice that the code output window looks the same. This is because the code output window is not part of the Blockly workspace. +The code output window will remain unchanged throughout this codelab, but you are encouraged to experiment with changing this style. + +Halloween themed Blockly workspace, with unchanged code output window. -![Fun with colours.](../../../static/images/codelabs/theme-extension/theme_components_workspace.png) +:::tip +Try adjusting the CSS for the output pane (found in `index.css`) to match your Blockly theme! +::: diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/setup.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/setup.mdx index 28ecc26a1ef..d1f0f4ff03b 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/setup.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/setup.mdx @@ -2,34 +2,48 @@ description: Setting up the "Customizing your themes" codelab. --- +import Image from '@site/src/components/Image'; + # Customizing your themes ## 2. Setup -### Download the sample code +This codelab will add a custom theme to the Blockly sample app. -You can get the sample code for this code by either downloading the zip here: +### Create the application -[Download zip](https://github.com/RaspberryPiFoundation/blockly/archive/main.zip) +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. -or by cloning this git repo: +1. Run `npx @blockly/create-package app custom-theme-codelab` in your terminal. This will create a Blockly application in the folder `custom-theme-codelab`. +1. Use `cd` to move into the new directory: `cd custom-theme-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. -```bash -git clone https://github.com/RaspberryPiFoundation/blockly.git -``` +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. + +### Change the storage key -If you downloaded the source as a zip, unpacking it should give you a root folder named `blockly-main`. +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 `serialization.js`, change the value of `storageKey` to some unique string. `customThemeWorkspace` will work: + +```js +// Use a unique storage key for this codelab +const storageKey = 'customThemeWorkspace'; +``` -The relevant files are in `packages/docs/docs/codelabs/theme-extension-identifier`. There are two versions of the app: +### Explore the starter code -- `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. +Take a moment to explore the code you just created in the `custom-theme-codelab` folder. If you'd like a more in-depth explanation of all of the code in the sample app, check out the [getting started codelab](/codelabs/getting-started/codelab-overview) which goes through creating this sample app from scratch. -Each folder contains: +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. -- `index.js` - The codelab's logic. To start, it just injects a simple workspace. -- `index.html` - A web page containing a simple blockly workspace. +### Inspect the workspace -To run the code, simple open `starter-code/index.html` in a browser. You should see a Blockly workspace with a flyout. +Open the window with your running app. You should see a Blockly workspace with a code output window and a toolbox using the default theme. -![A web page with the text "Theme Extension Codelab" and a Blockly workspace.](../../../static/images/codelabs/theme-extension/starter_workspace.png) +The Blockly sample app with a default-themed workspace and toolbox. diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/starter-code/index.html b/packages/docs/docs/codelabs/theme-extension-identifier/starter-code/index.html deleted file mode 100644 index f43e1187827..00000000000 --- a/packages/docs/docs/codelabs/theme-extension-identifier/starter-code/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Theme Extension Codelab - - - - - - - - -

Theme Extension Codelab

-
- - diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/starter-code/index.js b/packages/docs/docs/codelabs/theme-extension-identifier/starter-code/index.js deleted file mode 100644 index b8313f48d43..00000000000 --- a/packages/docs/docs/codelabs/theme-extension-identifier/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: toolboxCategories, - }); -} diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/summary.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/summary.mdx index ab21e00c123..34a51b0dcfd 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/summary.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/summary.mdx @@ -9,6 +9,10 @@ description: Summary of the "Customizing your themes" codelab. In this codelab you have learned how to extend and customize themes for the blocks component. +You can find the code for the +[completed theme codelab](https://github.com/RaspberryPiFoundation/blockly/tree/main/packages/docs/docs/codelabs/theme-extension-identifier/complete-code) +on GitHub. + ### Additional information - [Themes documentation](/guides/configure/appearance/themes) diff --git a/packages/docs/docs/codelabs/theme-extension-identifier/workspace-theme.mdx b/packages/docs/docs/codelabs/theme-extension-identifier/workspace-theme.mdx index f89fc07943c..c0889c4ed45 100644 --- a/packages/docs/docs/codelabs/theme-extension-identifier/workspace-theme.mdx +++ b/packages/docs/docs/codelabs/theme-extension-identifier/workspace-theme.mdx @@ -6,15 +6,15 @@ description: How to create a theme. ## 3. Workspace Theme -In this section you will create a very basic halloween theme, then inject it to display in the workspace. +In this section you will create a very basic Halloween theme, then inject it to display in the workspace. ### Themes Themes are a way to customize the look and feel of Blockly. Currently, we support customizing block colours, category colours and certain components through the Themes class. -A theme can be created using the constructor or by using defineTheme. Using `defineTheme` makes it easy to extend a pre existing theme and set all values with a single object. +A theme can be created using the constructor or by using `defineTheme`. Using `defineTheme` makes it easy to extend a pre-existing theme and set all values with a single object. -Add the below code in `index.js` right before function start() +`Blockly` is already imported at the top of `src/index.js`, so you have access to `Blockly.Theme` and the built-in `Blockly.Themes`. Add the code below in `src/index.js`, after the blocks and generator are registered and before the call to `Blockly.inject`: ```js Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { @@ -22,18 +22,15 @@ Blockly.Themes.Halloween = Blockly.Theme.defineTheme('halloween', { }); ``` -And then add a line to inject this theme in function start() in `index.js` +Then update the existing `Blockly.inject` call to use this theme: ```js -function start() { - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxCategories, - theme: Blockly.Themes.Halloween, - }); -} +const ws = Blockly.inject(blocklyDiv, { + toolbox, + theme: Blockly.Themes.Halloween, +}); ``` :::note -At this point, we have just created a new theme. But it does not have any customizations. We will do that next. +At this point, we have just created a new theme. But it does not have any customizations, so the workspace will look the same as before. We will add customizations next. ::: diff --git a/packages/docs/static/images/codelabs/theme-extension/block_styles.png b/packages/docs/static/images/codelabs/theme-extension/block_styles.png index fb3c71e2206..ac6127ca445 100644 Binary files a/packages/docs/static/images/codelabs/theme-extension/block_styles.png and b/packages/docs/static/images/codelabs/theme-extension/block_styles.png differ diff --git a/packages/docs/static/images/codelabs/theme-extension/customized_categories.png b/packages/docs/static/images/codelabs/theme-extension/customized_categories.png index 29cb8df48b3..97c1a959c8d 100644 Binary files a/packages/docs/static/images/codelabs/theme-extension/customized_categories.png and b/packages/docs/static/images/codelabs/theme-extension/customized_categories.png differ diff --git a/packages/docs/static/images/codelabs/theme-extension/starter_workspace.png b/packages/docs/static/images/codelabs/theme-extension/starter_workspace.png index 57a52d0cdad..95620c211c1 100644 Binary files a/packages/docs/static/images/codelabs/theme-extension/starter_workspace.png and b/packages/docs/static/images/codelabs/theme-extension/starter_workspace.png differ diff --git a/packages/docs/static/images/codelabs/theme-extension/theme_components_workspace.png b/packages/docs/static/images/codelabs/theme-extension/theme_components_workspace.png index cd6664bee0a..b9eb79eddf8 100644 Binary files a/packages/docs/static/images/codelabs/theme-extension/theme_components_workspace.png and b/packages/docs/static/images/codelabs/theme-extension/theme_components_workspace.png differ