diff --git a/packages/docs/docs/codelabs/context-menu-option/add-a-context-menu-item.mdx b/packages/docs/docs/codelabs/context-menu-option/add-a-context-menu-item.mdx index 4f12f6b35e5..5e5da172d97 100644 --- a/packages/docs/docs/codelabs/context-menu-option/add-a-context-menu-item.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/add-a-context-menu-item.mdx @@ -26,7 +26,7 @@ We will discuss these in detail in later sections of the codelab. ### Make a RegistryItem -Add a function to `index.js` named `registerHelloWorldItem`. Create a new registry item in your function: +Add a function to `src/index.js` named `registerHelloWorldItem`. Create a new registry item in your function: ```js function registerHelloWorldItem() { @@ -42,18 +42,15 @@ function registerHelloWorldItem() { } ``` -Call your function from `start`: +Call your function in `src/index.js`, before the UI element setup and `Blockly.inject` call: ```js -function start() { - registerHelloWorldItem(); - - Blockly.ContextMenuItems.registerCommentOptions(); - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxSimple, - }); -} +registerHelloWorldItem(); + +// Set up UI elements and inject Blockly +const codeDiv = document.getElementById('generatedCode').firstChild; +// ... +const ws = Blockly.inject(blocklyDiv, {toolbox}); ``` ### Register it @@ -70,12 +67,20 @@ function registerHelloWorldItem() { ``` :::note -you will never need to make a new `ContextMenuRegistry`. Always use the singleton `Blockly.ContextMenuRegistry.registry`. +You will never need to make a new `ContextMenuRegistry`. Always use the singleton `Blockly.ContextMenuRegistry.registry`. ::: +### Enable comment context menus + +This codelab demonstrates context menus on workspaces, blocks, and comments. Blockly registers the workspace and block options by default, but the comment options (including the **Add Comment** option you'll use to create comments while testing) must be registered separately. Add this call to `src/index.js`, before the `Blockly.inject` call: + +```js +Blockly.ContextMenuItems.registerCommentOptions(); +``` + ### Test it -Reload your web page and open a context menu on the workspace (right-click with a mouse, or press `Ctrl+Enter` (Windows) or `Command+Enter` (Mac) if you are navigating Blockly with the keyboard). You should see a new option labeled "Hello World" at the bottom of the context menu. +Refresh the running page (or run `npm start`) and open a context menu on the workspace (right-click with a mouse, or press `Ctrl+Enter` (Windows) or `Command+Enter` (Mac) if you are navigating Blockly with the keyboard). You should see a new option labeled "Hello World" at the bottom of the context menu. A text block containing the text "Now there is a block". diff --git a/packages/docs/docs/codelabs/context-menu-option/codelab-overview.mdx b/packages/docs/docs/codelabs/context-menu-option/codelab-overview.mdx index a4ce8f7fd2a..e8ba40efec3 100644 --- a/packages/docs/docs/codelabs/context-menu-option/codelab-overview.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/codelab-overview.mdx @@ -26,5 +26,9 @@ A very simple Blockly workspace with a few new context menu options. - 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. + +You can find the code for the [completed context menu codelab](https://github.com/RaspberryPiFoundation/blockly/tree/main/packages/docs/docs/codelabs/context-menu-option/complete-code) on GitHub. This codelab is focused on Blockly's context menus. Non-relevant concepts and code are glossed over and are provided for you to simply copy and paste. diff --git a/packages/docs/docs/codelabs/context-menu-option/complete-code/README.md b/packages/docs/docs/codelabs/context-menu-option/complete-code/README.md new file mode 100644 index 00000000000..6f98ed8807d --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/complete-code/README.md @@ -0,0 +1,14 @@ +# Completed code + +The finished code for the [Customizing context menus](../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/context-menu-option/complete-code/index.html b/packages/docs/docs/codelabs/context-menu-option/complete-code/index.html deleted file mode 100644 index c0821a8ec2e..00000000000 --- a/packages/docs/docs/codelabs/context-menu-option/complete-code/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Context Menu Codelab - - - - - - - - -

Context Menu Codelab

-
- - diff --git a/packages/docs/docs/codelabs/context-menu-option/complete-code/package.json b/packages/docs/docs/codelabs/context-menu-option/complete-code/package.json new file mode 100644 index 00000000000..615098ae09d --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/complete-code/package.json @@ -0,0 +1,29 @@ +{ + "name": "context-menu-option-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/context-menu-option/complete-code/src/blocks/text.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/blocks/text.js new file mode 100644 index 00000000000..244eaf1c9d4 --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/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/context-menu-option/complete-code/src/generators/javascript.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/generators/javascript.js new file mode 100644 index 00000000000..d058603e0de --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/generators/javascript.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2023 Google LLC + * 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/context-menu-option/complete-code/src/index.css b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.css new file mode 100644 index 00000000000..f282700701f --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/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/context-menu-option/complete-code/src/index.html b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.html new file mode 100644 index 00000000000..36d8eeacd99 --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.html @@ -0,0 +1,16 @@ + + + + + Blockly Sample App + + +
+
+
+
+
+
+
+ + diff --git a/packages/docs/docs/codelabs/context-menu-option/complete-code/index.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.js similarity index 55% rename from packages/docs/docs/codelabs/context-menu-option/complete-code/index.js rename to packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.js index 8d192656bb2..ee1694888ad 100644 --- a/packages/docs/docs/codelabs/context-menu-option/complete-code/index.js +++ b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/index.js @@ -1,20 +1,79 @@ -'use strict'; +/** + * @license + * Copyright 2026 Raspberry Pi Foundation + * SPDX-License-Identifier: Apache-2.0 + */ -let workspace = null; +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'; -function start() { - registerHelloWorldItem(); - registerHelpItem(); - registerDisplayItem(); - Blockly.ContextMenuRegistry.registry.unregister('workspaceDelete'); - registerSeparators(); +// Register the blocks and generator with Blockly +Blockly.common.defineBlocks(blocks); +Object.assign(javascriptGenerator.forBlock, forBlock); - Blockly.ContextMenuItems.registerCommentOptions(); - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxSimple, - }); -} +// Register context menu items +registerHelloWorldItem(); +registerHelpItem(); +registerDisplayItem(); +Blockly.ContextMenuRegistry.registry.unregister('workspaceDelete'); +registerSeparators(); +Blockly.ContextMenuItems.registerCommentOptions(); + +// 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(); +}); function registerHelloWorldItem() { const helloWorldItem = { @@ -38,7 +97,6 @@ function registerHelloWorldItem() { id: 'hello_world', weight: 100, }; - // Register. Blockly.ContextMenuRegistry.registry.register(helloWorldItem); } diff --git a/packages/docs/docs/codelabs/context-menu-option/complete-code/src/serialization.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/serialization.js new file mode 100644 index 00000000000..6a5041f215e --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/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 = 'contextMenuWorkspace'; + +/** + * 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/context-menu-option/complete-code/src/toolbox.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/src/toolbox.js new file mode 100644 index 00000000000..1ff8beb3e41 --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/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/context-menu-option/complete-code/webpack.config.js b/packages/docs/docs/codelabs/context-menu-option/complete-code/webpack.config.js new file mode 100644 index 00000000000..1a095648fd7 --- /dev/null +++ b/packages/docs/docs/codelabs/context-menu-option/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/context-menu-option/display-text.mdx b/packages/docs/docs/codelabs/context-menu-option/display-text.mdx index 7d80adfd313..710c92a86b4 100644 --- a/packages/docs/docs/codelabs/context-menu-option/display-text.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/display-text.mdx @@ -2,6 +2,8 @@ description: How to set the display text of a context menu item. --- +import Image from '@site/src/components/Image'; + # Customizing context menus ## 8. Display text @@ -37,9 +39,16 @@ function registerDisplayItem() { } ``` -As usual, remember to call `registerDisplayItem()` from your `start` function. +As usual, remember to call `registerDisplayItem()` in `src/index.js`, before `Blockly.inject`. ### Test it -- Reload the workspace and open context menus on various blocks. +- Refresh the running page and open context menus on various blocks. - The last context menu option's text should vary based on the block type. + +A controls-if block with an open context menu which has "Controls block" as the last item. \ No newline at end of file diff --git a/packages/docs/docs/codelabs/context-menu-option/precondition-blockly-state.mdx b/packages/docs/docs/codelabs/context-menu-option/precondition-blockly-state.mdx index f08bb07bbc5..4e64059bd4c 100644 --- a/packages/docs/docs/codelabs/context-menu-option/precondition-blockly-state.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/precondition-blockly-state.mdx @@ -2,11 +2,13 @@ description: How to include a context menu item based on Blockly's state. --- +import Image from '@site/src/components/Image'; + # Customizing context menus ## 6. Precondition: Blockly state -Disabling your context menu options half of the time is not useful, but you may want to show or hide an option based on what the user is doing. For example, let's show a **Help** option in the context menu if the user doesn't have any blocks on the workspace. Add this code in `index.js`: +Disabling your context menu options half of the time is not useful, but you may want to show or hide an option based on what the user is doing. For example, let's show a **Help** option in the context menu if the user doesn't have any blocks on the workspace. Add this code in `src/index.js`: ```js function registerHelpItem() { @@ -27,9 +29,24 @@ function registerHelpItem() { } ``` -Don't forget to call `registerHelpItem` from your `start` function. +Don't forget to call `registerHelpItem()` in `src/index.js`, before `Blockly.inject`. ### Test it -- Reload your page and open a context menu on the workspace. You should see an option labeled "Help! There are no blocks". +- Refresh the running page and open a context menu on the workspace. You should see an option labeled "Help! There are no blocks". + +A context menu, where the last item is "Help! There are no blocks". + - Add a block to the workspace and open a context menu on the workspace again. The **Help** option should be gone. + +A block on the workspace, and a context menu open beside it without the "Help" message. \ No newline at end of file diff --git a/packages/docs/docs/codelabs/context-menu-option/precondition-external-state.mdx b/packages/docs/docs/codelabs/context-menu-option/precondition-external-state.mdx index ba6f998f561..1260da061c7 100644 --- a/packages/docs/docs/codelabs/context-menu-option/precondition-external-state.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/precondition-external-state.mdx @@ -2,6 +2,8 @@ description: How to include a context menu item based on an external condition. --- +import Image from '@site/src/components/Image'; + # Customizing context menus ## 5. Precondition: External state @@ -26,6 +28,10 @@ Use of the `preconditionFn` is not limited to checking the type of the Blockly c ### Test it -Reload your workspace, check your watch, and open a context menu on the workspace to confirm the timing. The option will always be in the menu, but will sometimes be greyed out. +Refresh the running page, check your watch, and open a context menu on the workspace to confirm the timing. The option will always be in the menu, but will sometimes be greyed out. -![A context menu. The last option says "Hello World" but the text is grey, indicating that it cannot be selected.](../../../static/images/codelabs/context-menu-option/hello_world_grey.png) +A context menu. The last option says "Hello World" but the text is grey, indicating that it cannot be selected. diff --git a/packages/docs/docs/codelabs/context-menu-option/precondition-node-type.mdx b/packages/docs/docs/codelabs/context-menu-option/precondition-node-type.mdx index 53245713521..16b3324dba3 100644 --- a/packages/docs/docs/codelabs/context-menu-option/precondition-node-type.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/precondition-node-type.mdx @@ -2,6 +2,8 @@ description: How to include a context menu item based on the node type. --- +import Image from '@site/src/components/Image'; + # Customizing context menus ## 4 . Precondition: Node type @@ -42,4 +44,8 @@ Notice that the code tests for where context menus are allowed, rather than wher Open a context menu on the workspace, a block, and a comment. You should see a "Hello World" option on the workspace and block context menus, but not on the comment context menu. -![An if block with a context menu with five options. The last option says "Hello World".](../../../static/images/codelabs/context-menu-option/hello_world_block.png) +An if block with a context menu with five options. The last option says "Hello World". diff --git a/packages/docs/docs/codelabs/context-menu-option/separators.mdx b/packages/docs/docs/codelabs/context-menu-option/separators.mdx index 8dd3ea959e1..c66c9d11a2f 100644 --- a/packages/docs/docs/codelabs/context-menu-option/separators.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/separators.mdx @@ -2,6 +2,8 @@ description: How to add a separator to a context menu. --- +import Image from '@site/src/components/Image'; + # Customizing context menus ## 10. Separators @@ -34,8 +36,15 @@ function registerSeparators() { } ``` -As usual, remember to call `registerSeparators()` from your `start` function. +As usual, remember to call `registerSeparators()` in `src/index.js`, before `Blockly.inject`. ### Test it Open a context menu on the workspace and a block and check that the separator line is there. + +A context menu with a separator. \ No newline at end of file diff --git a/packages/docs/docs/codelabs/context-menu-option/setup.mdx b/packages/docs/docs/codelabs/context-menu-option/setup.mdx index be96bba57f5..353cdcad7ac 100644 --- a/packages/docs/docs/codelabs/context-menu-option/setup.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/setup.mdx @@ -8,34 +8,30 @@ import Image from '@site/src/components/Image'; ## 2. Setup -### Download the sample code +### Create the application -You can get the sample code for this code 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 context-menu-option-codelab` in your terminal. This will create a Blockly application in the folder `context-menu-option-codelab`. +1. Use `cd` to move into the new directory: `cd context-menu-option-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`. +### Change the storage key -The relevant files are in `packages/docs/docs/codelabs/context-menu-option`. There are two versions of the app: +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. -- `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. +In `serialization.js`, change the value of `storageKey` to some unique string. `contextMenuWorkspace` will work: -Each folder contains: +```js +// Use a unique storage key for this codelab +const storageKey = 'contextMenuWorkspace'; +``` -- `index.js` - The codelab's logic. To start, it just injects a simple workspace. -- `index.html` - A web page containing a simple blockly workspace. +### Explore the starter code -To run the code, simple open `starter-code/index.html` in a browser. You should see a Blockly workspace with an always-open flyout. +Take a moment to explore the code you just created in the `context-menu-option-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. -A web page with the text "Context Menu Codelab" and a simple Blockly workspace. +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. diff --git a/packages/docs/docs/codelabs/context-menu-option/starter-code/index.html b/packages/docs/docs/codelabs/context-menu-option/starter-code/index.html deleted file mode 100644 index c0821a8ec2e..00000000000 --- a/packages/docs/docs/codelabs/context-menu-option/starter-code/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - Context Menu Codelab - - - - - - - - -

Context Menu Codelab

-
- - diff --git a/packages/docs/docs/codelabs/context-menu-option/starter-code/index.js b/packages/docs/docs/codelabs/context-menu-option/starter-code/index.js deleted file mode 100644 index bf58b8165ec..00000000000 --- a/packages/docs/docs/codelabs/context-menu-option/starter-code/index.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -let workspace = null; - -function start() { - Blockly.ContextMenuItems.registerCommentOptions(); - // Create main workspace. - workspace = Blockly.inject('blocklyDiv', { - toolbox: toolboxSimple, - }); -} diff --git a/packages/docs/docs/codelabs/context-menu-option/summary.mdx b/packages/docs/docs/codelabs/context-menu-option/summary.mdx index 1389ad07e9b..72f88b0bca7 100644 --- a/packages/docs/docs/codelabs/context-menu-option/summary.mdx +++ b/packages/docs/docs/codelabs/context-menu-option/summary.mdx @@ -9,6 +9,8 @@ description: Summary of the "Customizing context menus" codelab. In this codelab you have learned how to create and modify context menu options. You have learned about scope, preconditions, callbacks, and display text. +You can find the code for the [completed context menu codelab](https://github.com/RaspberryPiFoundation/blockly/tree/main/packages/docs/docs/codelabs/context-menu-option/complete-code) on GitHub. + ### Additional information - [Context menu documentation](/guides/configure/context-menus) diff --git a/packages/docs/static/images/codelabs/context-menu-option/blockly_state_noblocks.png b/packages/docs/static/images/codelabs/context-menu-option/blockly_state_noblocks.png new file mode 100644 index 00000000000..9676854c976 Binary files /dev/null and b/packages/docs/static/images/codelabs/context-menu-option/blockly_state_noblocks.png differ diff --git a/packages/docs/static/images/codelabs/context-menu-option/blockly_state_withblocks.png b/packages/docs/static/images/codelabs/context-menu-option/blockly_state_withblocks.png new file mode 100644 index 00000000000..84888664e22 Binary files /dev/null and b/packages/docs/static/images/codelabs/context-menu-option/blockly_state_withblocks.png differ diff --git a/packages/docs/static/images/codelabs/context-menu-option/display_text.png b/packages/docs/static/images/codelabs/context-menu-option/display_text.png new file mode 100644 index 00000000000..775dcea1cb4 Binary files /dev/null and b/packages/docs/static/images/codelabs/context-menu-option/display_text.png differ diff --git a/packages/docs/static/images/codelabs/context-menu-option/separator.png b/packages/docs/static/images/codelabs/context-menu-option/separator.png new file mode 100644 index 00000000000..74eec1b77d0 Binary files /dev/null and b/packages/docs/static/images/codelabs/context-menu-option/separator.png differ