From 80bcc04a92c3137a90bc38ec530c2a07e4ba1849 Mon Sep 17 00:00:00 2001
From: Laura Sach <5183697+lawsie@users.noreply.github.com>
Date: Tue, 28 Jul 2026 11:12:38 +0100
Subject: [PATCH] Format entire repo with Prettier
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The repo had never been run through Prettier, so `prettier --check .`
failed on 215 files and the CI check was left non-blocking. This applies
`prettier --write .` across the whole tree in one pass.
Formatting only โ no behavioural change:
- ASTs compared before/after for all 191 changed JS/MJS files: identical,
except input/onScreenSource.js where quoted object keys became bare
identifiers ("ArrowUp": -> ArrowUp:), which is semantically equivalent.
- HTML tag inventories unchanged;
content untouched.
- Markdown trailing double-spaces removed only on list items and
paragraph ends, where they are not hard line breaks.
- eslint clean, test:input 242 passing, test:microbit 72 passing,
vite build succeeds โ all matching the pre-format baseline.
Co-Authored-By: Claude Opus 5
---
.claude/skills/verify/SKILL.md | 12 +-
.github/ISSUE_TEMPLATE/bug_report.md | 4 +-
.github/ISSUE_TEMPLATE/feature_request.md | 4 +-
.github/workflows/run-mocha-tests.yml | 6 +-
.github/workflows/static.yml | 6 +-
AGENTS.md | 2 +-
AI_POLICY.md | 14 +-
API.md | 110 +-
CLAUDE.md | 2 +-
CONTRIBUTING.md | 2 +
README.md | 32 +-
TRADEMARK.md | 2 +-
TRANSLATION.md | 10 +-
accessibility/accessibility.js | 549 ++--
accessibility/uiA11y.js | 87 +-
api/animate.js | 8 +-
api/camera.js | 138 +-
api/control.js | 20 +-
api/csg.js | 1328 ++++-----
api/effects.js | 104 +-
api/events.js | 74 +-
api/material.js | 3 +-
api/mesh.js | 3 +-
api/microbit.js | 45 +-
api/models.js | 155 +-
api/movement.js | 4 +-
api/physics.js | 4 +-
api/sensing.js | 142 +-
api/sound.js | 624 ++--
api/transform.js | 7 +-
api/xr.js | 99 +-
blocks/animate.js | 1002 +++----
blocks/base.js | 28 +-
blocks/blockIcons.js | 338 +--
blocks/blocks.js | 833 +++---
blocks/camera.js | 180 +-
blocks/colour.js | 114 +-
blocks/colourvalidation.js | 18 +-
blocks/combine.js | 138 +-
blocks/condition.js | 93 +-
blocks/connect.js | 304 +-
blocks/control.js | 311 +-
blocks/effects.js | 126 +-
blocks/events.js | 512 ++--
blocks/fieldMicrobitImage.js | 262 +-
blocks/materials.js | 743 +++--
blocks/microbitImagePattern.js | 42 +-
blocks/models.js | 370 ++-
blocks/physics.js | 232 +-
blocks/scene.js | 150 +-
blocks/sensing.js | 650 ++--
blocks/shapes.js | 775 +++--
blocks/sound.js | 1167 ++++----
blocks/text.js | 484 ++-
blocks/text_join_shadow.js | 54 +-
blocks/transform.js | 555 ++--
blocks/xr.js | 221 +-
config.js | 718 +++--
cubeart.html | 5 +-
dev-docs/API_QUALITY_TOOLS.md | 10 +-
dev-docs/API_RECONCILIATION_PLAN.md | 24 +-
docs/endless.md | 3 +-
docs/screen-reader.md | 32 +-
embed-example.html | 46 +-
eslint.config.mjs | 44 +-
flock.js | 20 +-
generators/generators-animate.js | 395 ++-
generators/generators-condition.js | 32 +-
generators/generators-control.js | 160 +-
generators/generators-data.js | 175 +-
generators/generators-deprecated.js | 329 +-
generators/generators-events.js | 94 +-
generators/generators-functions.js | 74 +-
generators/generators-material.js | 126 +-
generators/generators-math.js | 37 +-
generators/generators-scene.js | 483 ++-
generators/generators-sensing.js | 4 +-
generators/generators-sound.js | 274 +-
generators/generators-text.js | 322 +-
generators/generators-transform.js | 480 ++-
generators/generators-utilities.js | 163 +-
generators/generators.js | 64 +-
generators/mesh-state.js | 7 +-
index.html | 865 +++---
input/bindings.js | 48 +-
input/gamepadSource.js | 16 +-
input/inputManager.js | 25 +-
input/joystickSource.js | 26 +-
input/keyboardSource.js | 26 +-
input/normaliseKey.js | 2 +-
input/onScreenSource.js | 42 +-
input/xrSource.js | 106 +-
locale/de.js | 625 ++--
locale/en.js | 201 +-
locale/es.js | 195 +-
locale/fr.js | 81 +-
locale/it.js | 96 +-
locale/pl.js | 195 +-
locale/pt.js | 192 +-
locale/sv.js | 201 +-
main/blockhandling.js | 114 +-
main/blocklyinit.js | 3 +-
main/context.js | 76 +-
main/customCommentIcon.js | 23 +-
main/debug.js | 76 +-
main/examples.js | 267 +-
main/export.js | 222 +-
main/files.js | 457 ++-
main/loading.js | 16 +-
main/main.js | 4 +-
main/menu.js | 162 +-
main/themes.js | 481 ++-
main/translation.js | 182 +-
main/view.js | 11 +-
microbit/manager.js | 44 +-
microbit/protocol.js | 94 +-
microbit/usbTransport.js | 8 +-
package.json | 2 +-
playwright.config.js | 22 +-
scripts/api-coverage-report.mjs | 146 +-
scripts/canvas-buffer-check.mjs | 40 +-
scripts/check-link-security.mjs | 46 +-
scripts/csp-smoke.mjs | 88 +-
scripts/run-api-tests.mjs | 653 ++--
scripts/run-input-tests.mjs | 18 +-
scripts/run-microbit-tests.mjs | 16 +-
scripts/test-coverage-matrix.mjs | 111 +-
scripts/utils/extract-api-methods.mjs | 65 +-
scripts/utils/parse-api-md.mjs | 28 +-
scripts/utils/parse-jsdoc.mjs | 50 +-
scripts/utils/test-analyzer.mjs | 42 +-
style.css | 6 +-
tests/accessibility/getPlayerMesh.test.js | 50 +-
tests/accessibility/index.test.js | 2 +-
tests/animate.test.js | 384 ++-
tests/babylon.test.js | 18 +-
tests/blocks.test.js | 422 +--
tests/boxes.test.js | 67 +-
tests/buttoncontrols.test.js | 205 +-
tests/camera.test.js | 60 +-
tests/characterAnimations.test.js | 96 +-
tests/colourvalidation.test.js | 68 +-
tests/concurrency.test.js | 149 +-
tests/control.test.js | 22 +-
tests/effects.test.js | 167 +-
tests/events.test.js | 242 +-
tests/generators-material.test.js | 34 +-
tests/getProperty.test.js | 193 +-
tests/glide.test.js | 44 +-
tests/glideToObject.test.js | 55 +-
tests/input/bindings.test.js | 91 +-
tests/input/cameraControls.test.js | 54 +-
tests/input/consumers.test.js | 142 +-
tests/input/gamepadSource.test.js | 172 +-
tests/input/index.test.js | 18 +-
tests/input/inputManager.test.js | 238 +-
tests/input/joystickSource.test.js | 210 +-
tests/input/keyboardSource.test.js | 308 +-
tests/input/onScreenSource.test.js | 182 +-
tests/input/xrSource.test.js | 210 +-
tests/materials.test.js | 328 +-
tests/math.test.js | 26 +-
tests/mesh-hierarchy.test.js | 210 +-
tests/microbit/field.test.js | 63 +-
tests/microbit/generators.test.js | 202 +-
tests/microbit/index.test.js | 8 +-
tests/microbit/manager.test.js | 389 ++-
tests/microbit/protocol.test.js | 328 +-
tests/movement.test.js | 124 +-
tests/notifications.test.js | 125 +-
tests/objects.test.js | 205 +-
tests/onscreencontrols.test.js | 233 +-
tests/physics.test.js | 420 +--
tests/playwright/aria-input-labels.spec.js | 89 +-
tests/playwright/blocks.spec.js | 168 +-
tests/playwright/example-loading.spec.js | 57 +-
tests/playwright/flock.spec.js | 20 +-
.../playwright/speak-language-options.spec.js | 20 +-
tests/printtext.test.js | 53 +-
tests/scene.test.js | 236 +-
tests/security/color-validation.test.js | 175 +-
tests/security/free-text-validation.test.js | 114 +-
tests/security/index.test.js | 4 +-
tests/sensing.test.js | 88 +-
tests/shapes.test.js | 133 +-
tests/sound-integration.test.js | 132 +-
tests/sound-verification.test.js | 115 +-
tests/sound.test.js | 221 +-
tests/sound2.test.js | 26 +-
tests/status.test.js | 156 +-
tests/tests.html | 670 +++--
tests/transform.rotate.test.js | 168 +-
tests/transform.scale.test.js | 209 +-
tests/transform.translate.test.js | 138 +-
tests/ui/interactIndicator.test.js | 171 +-
tests/uitextbutton.test.js | 305 +-
tests/utils/audioTestUtils.js | 32 +-
tests/xr-export.test.js | 46 +-
tests/xr.test.js | 20 +-
toolbox.js | 2653 ++++++++---------
tutorial.js | 99 +-
ui/addmenu.js | 394 ++-
ui/addmeshes.js | 342 +--
ui/axis-keyboard.js | 125 +-
ui/blocklyshadowutil.js | 33 +-
ui/blocklyutil.js | 179 +-
ui/blockmesh.js | 994 +++---
ui/canvas-utils.js | 118 +-
ui/colourpicker.css | 78 +-
ui/colourpicker.js | 972 +++---
ui/designview.js | 6 +-
ui/gizmos.js | 66 +-
ui/meshhelpers.js | 9 +-
ui/notifications.js | 83 +-
vite.config.js | 16 +-
215 files changed, 18579 insertions(+), 21656 deletions(-)
diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md
index 92b1294b7..91f1a7727 100644
--- a/.claude/skills/verify/SKILL.md
+++ b/.claude/skills/verify/SKILL.md
@@ -13,7 +13,7 @@ the script lives outside the repo).
## Launch
- Chromium needs `headless: false` plus args `--no-sandbox
- --disable-setuid-sandbox --headless=old`. This is not a contradiction:
+--disable-setuid-sandbox --headless=old`. This is not a contradiction:
`headless: false` stops Playwright adding its own headless flags (its
headless mode uses the new headless shell, which drops WebGL), and
`--headless=old` then puts Chromium itself in legacy headless mode,
@@ -53,8 +53,10 @@ the script lives outside the repo).
flock.js listens on the top document. Stub and dispatch:
```js
-Object.defineProperty(document, "visibilityState",
- { configurable: true, get: () => document.__fakeVis || "visible" });
-document.__fakeVis = "hidden"; // or "visible"
-document.dispatchEvent(new Event("visibilitychange"));
+Object.defineProperty(document, 'visibilityState', {
+ configurable: true,
+ get: () => document.__fakeVis || 'visible',
+});
+document.__fakeVis = 'hidden'; // or "visible"
+document.dispatchEvent(new Event('visibilitychange'));
```
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 55fa77646..d153ba570 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,9 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
-title: ""
+title: ''
labels: bug
-assignees: ""
+assignees: ''
---
**Describe the bug**
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 8eec841f2..a080037a4 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,9 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
-title: ""
+title: ''
labels: enhancement
-assignees: ""
+assignees: ''
---
**Is your feature request related to a problem? Please describe.**
diff --git a/.github/workflows/run-mocha-tests.yml b/.github/workflows/run-mocha-tests.yml
index f63ff2c64..7b3d7d3b8 100644
--- a/.github/workflows/run-mocha-tests.yml
+++ b/.github/workflows/run-mocha-tests.yml
@@ -5,9 +5,9 @@ name: Run mocha tests
on:
push:
- branches: ["testable"]
+ branches: ['testable']
pull_request:
- branches: ["testable"]
+ branches: ['testable']
jobs:
build:
@@ -28,7 +28,7 @@ jobs:
uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- cache: "npm"
+ cache: 'npm'
- run: npm ci
- name: Get installed Playwright version
id: playwright-version
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
index 27bcd98e7..5c5531023 100644
--- a/.github/workflows/static.yml
+++ b/.github/workflows/static.yml
@@ -3,7 +3,7 @@ name: Vite Github Pages Deploy
on:
# Runs on pushes targeting the default branch
push:
- branches: ["master", "main"]
+ branches: ['master', 'main']
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -14,11 +14,11 @@ permissions:
id-token: write
concurrency:
- group: "pages"
+ group: 'pages'
cancel-in-progress: false
env:
- VITE_BASE_URL: "/${{ github.event.repository.name }}/"
+ VITE_BASE_URL: '/${{ github.event.repository.name }}/'
jobs:
# Build job
diff --git a/AGENTS.md b/AGENTS.md
index 348289d76..d4dfef157 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,6 +14,7 @@
- Review code for unnecessary complexity
## Style
+
- Match the existing style
- Comments should be infrequent
- Comments should be genuinely noteworthy
@@ -27,4 +28,3 @@
- If user verification is needed, describe the test to be carried out and request confirmation
- Don't introduce security issues
- Don't introduce changes that would break offline PWA behaviour
-
diff --git a/AI_POLICY.md b/AI_POLICY.md
index 88cdeb7cc..d21095d55 100644
--- a/AI_POLICY.md
+++ b/AI_POLICY.md
@@ -4,15 +4,15 @@ AI is a tool to support human creativity and responsibility, not replace it.
## Our overall goals are:
-- Open source principles
-- Efficiency, particularly when funded with public money and adding accessibility features
-- Quality and maintainability of code
-- Consider workload on maintainers and contributors and avoiding barriers to contribution
-- Humans are responsible for code and other assets, AI is a tool
+- Open source principles
+- Efficiency, particularly when funded with public money and adding accessibility features
+- Quality and maintainability of code
+- Consider workload on maintainers and contributors and avoiding barriers to contribution
+- Humans are responsible for code and other assets, AI is a tool
## Contribution expectations
-The world has changed. We believe that open source projects need to be able to benefit from generative AI tools that have largely been trained on open source content. We need to do this in a way that is aligned with the goals of the project.
+The world has changed. We believe that open source projects need to be able to benefit from generative AI tools that have largely been trained on open source content. We need to do this in a way that is aligned with the goals of the project.
At this point we are not able to accept large contributions from fully automated contributors or contributors who are not known to the project.
@@ -54,4 +54,4 @@ We recognise that some potential contributors may wish to write code completely
This policy will be reviewed regularly as the generative AI landscape changes.
-This policy is influenced by [NLnet Generative AI Policy](https://nlnet.nl/foundation/policies/generativeAI/) (weโre grateful that they listened to feedback as the open source community navigates this unprecedented change in how we develop software), the measured policy from [Apache Generative Tooling Guidelines](https://www.apache.org/legal/generative-tooling.html), and the ideas around community and human maintainers from [Ghostty AI Policy](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md).
\ No newline at end of file
+This policy is influenced by [NLnet Generative AI Policy](https://nlnet.nl/foundation/policies/generativeAI/) (weโre grateful that they listened to feedback as the open source community navigates this unprecedented change in how we develop software), the measured policy from [Apache Generative Tooling Guidelines](https://www.apache.org/legal/generative-tooling.html), and the ideas around community and human maintainers from [Ghostty AI Policy](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md).
diff --git a/API.md b/API.md
index b0582359d..42e62ce59 100644
--- a/API.md
+++ b/API.md
@@ -43,8 +43,8 @@ Plays an animation on a specified mesh.
**Example:**
```javascript
-await playAnimation("character1", {
- animationName: "walk",
+await playAnimation('character1', {
+ animationName: 'walk',
loop: true,
});
```
@@ -64,8 +64,8 @@ Switches to a different animation on a mesh.
**Example:**
```javascript
-await switchAnimation("character1", {
- animationName: "run",
+await switchAnimation('character1', {
+ animationName: 'run',
});
```
@@ -86,12 +86,12 @@ Animates a property using keyframes.
**Example:**
```javascript
-await animateKeyFrames("box1", {
+await animateKeyFrames('box1', {
keyframes: [
- { duration: 0, value: "#FF0000" },
- { duration: 2, value: "#00FF00" },
+ { duration: 0, value: '#FF0000' },
+ { duration: 2, value: '#00FF00' },
],
- property: "color",
+ property: 'color',
loop: true,
});
```
@@ -113,12 +113,12 @@ Smoothly moves a mesh to a new position.
**Example:**
```javascript
-await glideTo("player", {
+await glideTo('player', {
x: 5,
y: 0,
z: 3,
duration: 2,
- easing: "SineEase",
+ easing: 'SineEase',
});
```
@@ -139,7 +139,7 @@ Rotates a mesh with animation.
**Example:**
```javascript
-await rotateAnim("box1", {
+await rotateAnim('box1', {
x: 90,
y: 180,
z: 0,
@@ -199,14 +199,14 @@ Creates a character model in the scene.
```javascript
const player = createCharacter({
- modelName: "Character2.glb",
- modelId: "player_unique_id",
+ modelName: 'Character2.glb',
+ modelId: 'player_unique_id',
scale: 1,
position: { x: 0, y: 0, z: 0 },
colors: {
- hair: "#ffcc00",
- skin: "#f0d5b1",
- eyes: "#33cc00",
+ hair: '#ffcc00',
+ skin: '#f0d5b1',
+ eyes: '#33cc00',
},
});
```
@@ -235,7 +235,7 @@ Creates a box geometry.
**Example:**
```javascript
-const box1 = createBox("myBox", "#ff0000", 2, 2, 2, [0, 1, 0]);
+const box1 = createBox('myBox', '#ff0000', 2, 2, 2, [0, 1, 0]);
```
#### `createSphere(name, options)`
@@ -268,7 +268,7 @@ Sets the transparency of a mesh.
**Example:**
```javascript
-await setAlpha("box1", 0.75);
+await setAlpha('box1', 0.75);
```
#### `changeColor(meshName, color)`
@@ -337,7 +337,7 @@ Applies physics properties to a mesh.
**Example:**
```javascript
-await setPhysics("player", "DYNAMIC");
+await setPhysics('player', 'DYNAMIC');
```
#### `applyForce(meshName, force)`
@@ -358,7 +358,7 @@ Creates a ground plane.
**Example:**
```javascript
-createGround("#ffffff", "ground");
+createGround('#ffffff', 'ground');
```
#### `setSky(color)`
@@ -372,7 +372,7 @@ Sets the skybox/environment color.
**Example:**
```javascript
-setSky("#ffffff");
+setSky('#ffffff');
```
#### `lightIntensity(intensity)`
@@ -395,8 +395,8 @@ Adds fog to the scene for atmospheric effects.
```javascript
setFog({
- fogColorHex: "#ffffff",
- fogMode: "LINEAR",
+ fogColorHex: '#ffffff',
+ fogMode: 'LINEAR',
fogDensity: 0.1,
fogStart: 50,
fogEnd: 100,
@@ -431,7 +431,7 @@ Attaches the camera to follow a specific mesh.
**Example:**
```javascript
-await attachCamera("player", 7);
+await attachCamera('player', 7);
```
### UI
@@ -449,7 +449,7 @@ Displays text in the UI.
**Example:**
```javascript
-printText("๐ Hello", 30, "#000080");
+printText('๐ Hello', 30, '#000080');
```
#### `buttonControls(type, enabled, color)`
@@ -465,7 +465,7 @@ Creates button controls for user interaction.
**Example:**
```javascript
-buttonControls("ARROWS", true, "#cc33cc");
+buttonControls('ARROWS', true, '#cc33cc');
```
#### `say(meshName, text, duration)`
@@ -527,7 +527,7 @@ Checks if a specific key is currently pressed.
**Example:**
```javascript
-if (keyPressed("w")) {
+if (keyPressed('w')) {
// Move forward
}
```
@@ -543,7 +543,7 @@ Checks if a movement or action input is active across keyboard, touch, or XR con
**Example:**
```javascript
-if (actionPressed("FORWARD")) {
+if (actionPressed('FORWARD')) {
// Move forward regardless of whether the player is using W, Z, touch, or XR input
}
```
@@ -561,7 +561,7 @@ Run a callback when the chosen action is pressed or released across keyboard, to
**Example:**
```javascript
-whenActionEvent("BUTTON1", async () => {
+whenActionEvent('BUTTON1', async () => {
// Respond to the action button, regardless of whether it came from E, touch, or XR
});
```
@@ -589,11 +589,11 @@ Runs a function in an infinite loop.
```javascript
forever(async () => {
- if (keyPressed("w")) {
- moveForward("player", 3);
- await switchAnimation("player", { animationName: "Walk" });
+ if (keyPressed('w')) {
+ moveForward('player', 3);
+ await switchAnimation('player', { animationName: 'Walk' });
} else {
- await switchAnimation("player", { animationName: "Idle" });
+ await switchAnimation('player', { animationName: 'Idle' });
}
});
```
@@ -638,7 +638,7 @@ Shows a 5ร5 image on a micro:bit's LED display. The micro:bit must be connected
```javascript
// A sun on every plugged-in micro:bit
-microbitShowImage("", "9090909990999990999090909");
+microbitShowImage('', '9090909990999990999090909');
```
## Examples
@@ -648,19 +648,19 @@ For a complete working example, see [example.html](example.html) in the reposito
### Basic Scene Setup
```javascript
-setSky("#ffffff");
-createGround("#ffffff", "ground");
-printText("๐ Hello", 30, "#000080");
-buttonControls("ARROWS", true, "#cc33cc");
+setSky('#ffffff');
+createGround('#ffffff', 'ground');
+printText('๐ Hello', 30, '#000080');
+buttonControls('ARROWS', true, '#cc33cc');
const player = createCharacter({
- modelName: "Character2.glb",
- modelId: "player_unique_id",
+ modelName: 'Character2.glb',
+ modelId: 'player_unique_id',
scale: 1,
position: { x: 0, y: 0, z: 0 },
});
-await setPhysics(player, "DYNAMIC");
+await setPhysics(player, 'DYNAMIC');
await attachCamera(player, 7);
```
@@ -668,14 +668,14 @@ await attachCamera(player, 7);
```javascript
forever(async () => {
- if (keyPressed("w")) {
- moveForward("player", 3);
- await switchAnimation("player", { animationName: "Walk" });
- } else if (keyPressed("s")) {
- moveForward("player", -3);
- await switchAnimation("player", { animationName: "Walk" });
+ if (keyPressed('w')) {
+ moveForward('player', 3);
+ await switchAnimation('player', { animationName: 'Walk' });
+ } else if (keyPressed('s')) {
+ moveForward('player', -3);
+ await switchAnimation('player', { animationName: 'Walk' });
} else {
- await switchAnimation("player", { animationName: "Idle" });
+ await switchAnimation('player', { animationName: 'Idle' });
}
});
```
@@ -684,23 +684,23 @@ forever(async () => {
```javascript
// Keyframe animation
-await animateKeyFrames("box1", {
+await animateKeyFrames('box1', {
keyframes: [
- { duration: 0, value: "#FF0000" },
- { duration: 2, value: "#00FF00" },
- { duration: 4, value: "#0000FF" },
+ { duration: 0, value: '#FF0000' },
+ { duration: 2, value: '#00FF00' },
+ { duration: 4, value: '#0000FF' },
],
- property: "color",
+ property: 'color',
loop: true,
});
// Smooth movement
-await glideTo("player", {
+await glideTo('player', {
x: 10,
y: 0,
z: 5,
duration: 3,
- easing: "SineEase",
+ easing: 'SineEase',
});
```
diff --git a/CLAUDE.md b/CLAUDE.md
index d250fcfdf..bcb6d35ea 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,3 +1,3 @@
## claude.md
-See [AGENTS.md](AGENTS.md)
\ No newline at end of file
+See [AGENTS.md](AGENTS.md)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5fa2d8995..607bfa3ea 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,7 +72,9 @@ Before starting, we suggest you [get in touch](https://flipcomputing.com/contact
7. **Create a Pull Request** on GitHub
### Deploying your fork to GitHub Pages
+
1. **Push your fork to GitHub**:
+
```bash
git push origin main
```
diff --git a/README.md b/README.md
index a05d6d421..eb825a43d 100644
--- a/README.md
+++ b/README.md
@@ -8,35 +8,36 @@ It enables educators, students, and families to build interactive 3D experiences
## Key features
-- No login required
-- Runs entirely in the browser (including Chromebooks, tablets, and low-spec devices)
-- Designed for schools, clubs, and home learning
-- Supported by curriculum resources and ready-to-use lesson materials
+- No login required
+- Runs entirely in the browser (including Chromebooks, tablets, and low-spec devices)
+- Designed for schools, clubs, and home learning
+- Supported by curriculum resources and ready-to-use lesson materials
### What can you do with Flock XR?
-- Build and explore interactive 3D environments
-- Create worlds using visual gizmos and block-based coding
-- Teach coding, game design, and digital storytelling through hands-on projects
+- Build and explore interactive 3D environments
+- Create worlds using visual gizmos and block-based coding
+- Teach coding, game design, and digital storytelling through hands-on projects
- Run engaging learning activities without installing software
- Use games engine concepts such as aniamtions, particle effects and physics
- Create [spatial computing](https://www.techagekids.com/2026/06/spatial-computing-skills-children.html) projects that link the digital and physical worlds
-Flock XR has been designed to start with younger children aged 7+, often using tablets. Then take them through upper primary, middle school and high school right through to professional 3D tools, such as Babylon JS, UEFN, Unity and Godot.
+Flock XR has been designed to start with younger children aged 7+, often using tablets. Then take them through upper primary, middle school and high school right through to professional 3D tools, such as Babylon JS, UEFN, Unity and Godot.
### Why 3D and XR?
-3D is engaging and age appropriate for young people and can lead to careers across a range of industries. Technology that originated in games is now used in many sectors. XR and spatial computing are changing how humans interact with technology, bridging the physical and digital worlds.
+
+3D is engaging and age appropriate for young people and can lead to careers across a range of industries. Technology that originated in games is now used in many sectors. XR and spatial computing are changing how humans interact with technology, bridging the physical and digital worlds.
- Young people love 3D games and social platforms such as Minecraft, Roblox, Fortnite and more. Flock XR brings that knowledge into the classroom.
- The real world is 3D. It turns out that some things are just easier when you don't have the complexity of having to map to 3D.
-- 3D is the basis for eXtended Reality and spatial computing, the metaverse and immersive tech which are growing dramatically across industries.
+- 3D is the basis for eXtended Reality and spatial computing, the metaverse and immersive tech which are growing dramatically across industries.
### Who is it for?
- Young people age 7-14+ (primary through secondary education, K-12)
-- Teachers and educators worldwide
-- Clubs, coding groups, and informal learning communities
-- Parents and home educators supporting creative learning
+- Teachers and educators worldwide
+- Clubs, coding groups, and informal learning communities
+- Parents and home educators supporting creative learning
๐ Try it now: [app.flockxr.com](https://app.flockxr.com/)
A project from: [Flip Computing](https://flipcomputing.com/).
@@ -53,9 +54,9 @@ Please see our [documentation hub](https://hub.flockxr.com) and [free resources
## Development info
-Full details of Flock XR versions including the latest Development version can be found at [flockxr.com/versions/](https://flockxr.com/versions/)
+Full details of Flock XR versions including the latest Development version can be found at [flockxr.com/versions/](https://flockxr.com/versions/)
-You will also find full dev setup for [contributing to Flock XR](https://github.com/flipcomputing/flockupdate/blob/main/CONTRIBUTING.md) in our guide.
+You will also find full dev setup for [contributing to Flock XR](https://github.com/flipcomputing/flockupdate/blob/main/CONTRIBUTING.md) in our guide.
Flock XR is licensed under the MIT License. By contributing, you agree that your contributions will be licensed under the same license.
@@ -75,4 +76,5 @@ Flock XR is based on Blockly, the Babylon JS library and the Havok physics engin
This project is tested with BrowserStack.
**๐ Documentation:**
+
- [Getting Started](docs/GETTING_STARTED.md) - Quick start for improving API quality
diff --git a/TRADEMARK.md b/TRADEMARK.md
index 166257ddc..9c0dfd6ef 100644
--- a/TRADEMARK.md
+++ b/TRADEMARK.md
@@ -1 +1 @@
-Flock XRยฎ and the Flock XR purple bird are trademarks registered to Flip Computing Ltd. The marks may not be used to endorse or promote products derived from this software without specific prior written permission.
\ No newline at end of file
+Flock XRยฎ and the Flock XR purple bird are trademarks registered to Flip Computing Ltd. The marks may not be used to endorse or promote products derived from this software without specific prior written permission.
diff --git a/TRANSLATION.md b/TRANSLATION.md
index 8bf0f4d20..14789f33b 100644
--- a/TRANSLATION.md
+++ b/TRANSLATION.md
@@ -21,8 +21,8 @@ Follow these steps to wire a new locale into the app:
2. **Import the locale in the translation module**
- In `main/translation.js`, add the Blockly pack import (if it exists) and the custom locale import:
```javascript
- import * as xx from "blockly/msg/xx";
- import xxLocale from "../locale/xx.js";
+ import * as xx from 'blockly/msg/xx';
+ import xxLocale from '../locale/xx.js';
```
- Add the locale to the `translations` map (e.g., `xx: xxLocale,`).
- Extend `applySavedLanguageTranslations` and `setLanguage` with a branch that applies the Blockly pack for the new language (mirroring the existing languages).
@@ -59,10 +59,10 @@ When you introduce new UI text in the codebase, wire it to the translation syste
- **JavaScript** (manual assignment):
```javascript
- import { translate } from "../main/translation.js";
+ import { translate } from '../main/translation.js';
- const snackbar = document.querySelector("#snackbar");
- snackbar.textContent = translate("snackbar_saved_ui");
+ const snackbar = document.querySelector('#snackbar');
+ snackbar.textContent = translate('snackbar_saved_ui');
```
Add `snackbar_saved_ui: "Saved",` to `locale/en.js` and provide translations for the same key.
diff --git a/accessibility/accessibility.js b/accessibility/accessibility.js
index 2b05a1179..6fdbb9e13 100644
--- a/accessibility/accessibility.js
+++ b/accessibility/accessibility.js
@@ -1,6 +1,6 @@
// flock/accessibility/accessibility.js
-import { translate } from "../main/translation.js";
-import { onInteractObservable } from "../ui/interactIndicator.js";
+import { translate } from '../main/translation.js';
+import { onInteractObservable } from '../ui/interactIndicator.js';
let speechMuted = false;
let currentScene = null;
@@ -10,17 +10,16 @@ let keyListenerAttached = false;
let pointerObserverRef = null;
let pointerObserverScene = null;
-
// Message sequencing to stop stale/laggy announcements
let announceSeq = 0;
-let lastAnnouncedText = "";
+let lastAnnouncedText = '';
let lastAnnouncedAt = 0;
// Separate channel for say-block announcements so they don't cancel printText
let sayAnnounceSeq = 0;
// Helps avoid repeating the same click announcement too many times in a row
-let lastInteractionKey = "";
+let lastInteractionKey = '';
let lastInteractionTime = 0;
// Two persistent assertive regions that alternate so each announce writes to whichever
@@ -41,18 +40,18 @@ let hasSpokenInitialPageIntro = false;
let worldInstructionTexts = [];
function createA11yRoot() {
- let root = document.getElementById("flock-a11y-root");
+ let root = document.getElementById('flock-a11y-root');
if (!root) {
- root = document.createElement("div");
- root.id = "flock-a11y-root";
+ root = document.createElement('div');
+ root.id = 'flock-a11y-root';
// Visually hidden but readable by screen readers
- root.style.position = "absolute";
- root.style.left = "-9999px";
- root.style.top = "0";
- root.style.width = "1px";
- root.style.height = "1px";
- root.style.overflow = "hidden";
+ root.style.position = 'absolute';
+ root.style.left = '-9999px';
+ root.style.top = '0';
+ root.style.width = '1px';
+ root.style.height = '1px';
+ root.style.overflow = 'hidden';
document.body.appendChild(root);
}
@@ -60,17 +59,17 @@ function createA11yRoot() {
}
export function createLiveRegion() {
- let region = document.getElementById("flock-live-region");
+ let region = document.getElementById('flock-live-region');
if (!region) {
const root = createA11yRoot();
- region = document.createElement("div");
- region.id = "flock-live-region";
+ region = document.createElement('div');
+ region.id = 'flock-live-region';
// Status (not log) avoids a backlog of old messages
- region.setAttribute("role", "status");
- region.setAttribute("aria-live", "polite");
- region.setAttribute("aria-atomic", "true");
+ region.setAttribute('role', 'status');
+ region.setAttribute('aria-live', 'polite');
+ region.setAttribute('aria-atomic', 'true');
root.appendChild(region);
}
@@ -78,14 +77,14 @@ export function createLiveRegion() {
}
function createSayLiveRegion() {
- let region = document.getElementById("flock-say-region");
+ let region = document.getElementById('flock-say-region');
if (!region) {
const root = createA11yRoot();
- region = document.createElement("div");
- region.id = "flock-say-region";
- region.setAttribute("role", "status");
- region.setAttribute("aria-live", "polite");
- region.setAttribute("aria-atomic", "true");
+ region = document.createElement('div');
+ region.id = 'flock-say-region';
+ region.setAttribute('role', 'status');
+ region.setAttribute('aria-live', 'polite');
+ region.setAttribute('aria-atomic', 'true');
root.appendChild(region);
}
return region;
@@ -100,7 +99,7 @@ export function announceObjectSay(text, options = {}) {
const region = createSayLiveRegion();
const mySeq = ++sayAnnounceSeq;
- region.textContent = "";
+ region.textContent = '';
setTimeout(() => {
if (mySeq !== sayAnnounceSeq) return;
region.textContent = spoken;
@@ -111,7 +110,7 @@ export function announce(message, options = {}) {
const { force = false, noDedup = false } = options;
if (speechMuted && !force) return;
- const text = String(message ?? "").trim();
+ const text = String(message ?? '').trim();
if (!text) return;
const now = Date.now();
@@ -128,10 +127,11 @@ export function announce(message, options = {}) {
if (!_interactRegions) {
const root = createA11yRoot();
_interactRegions = [0, 1].map(() => {
- const el = document.createElement("div");
- el.setAttribute("aria-live", "assertive");
- el.setAttribute("aria-atomic", "true");
- el.style.cssText = "position:absolute;left:-9999px;top:0;width:1px;height:1px;overflow:hidden";
+ const el = document.createElement('div');
+ el.setAttribute('aria-live', 'assertive');
+ el.setAttribute('aria-atomic', 'true');
+ el.style.cssText =
+ 'position:absolute;left:-9999px;top:0;width:1px;height:1px;overflow:hidden';
root.appendChild(el);
return el;
});
@@ -139,15 +139,17 @@ export function announce(message, options = {}) {
_interactRegionIdx = 1 - _interactRegionIdx;
const next = _interactRegions[_interactRegionIdx];
const prev = _interactRegions[1 - _interactRegionIdx];
- prev.textContent = "";
- setTimeout(() => { next.textContent = text; }, 0);
+ prev.textContent = '';
+ setTimeout(() => {
+ next.textContent = text;
+ }, 0);
return;
}
const region = createLiveRegion();
const mySeq = ++announceSeq;
- region.textContent = "";
+ region.textContent = '';
setTimeout(() => {
if (mySeq !== announceSeq) return;
@@ -156,41 +158,36 @@ export function announce(message, options = {}) {
}
function normaliseName(name) {
- return String(name || "object")
- .replace(/[_-]/g, " ")
- .replace(/([a-z])([A-Z])/g, "$1 $2")
- .replace(/\s+/g, " ")
+ return String(name || 'object')
+ .replace(/[_-]/g, ' ')
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
+ .replace(/\s+/g, ' ')
.trim();
}
function looksLikeInternalMeshName(name) {
- const n = String(name || "").toLowerCase();
+ const n = String(name || '').toLowerCase();
return (
!n ||
- n === "__root__" ||
- n === "textplane" ||
- n.startsWith("__flock_") ||
- n.includes("camera") ||
- n.includes("light") ||
- n.includes("highlighter") ||
- n.includes("gizmo") ||
- n.includes("bounding") ||
- n.includes("debug") ||
- n.includes("hitbox") ||
- n.includes("collider") ||
- n.includes("armature") ||
- n.includes("mixamo")
+ n === '__root__' ||
+ n === 'textplane' ||
+ n.startsWith('__flock_') ||
+ n.includes('camera') ||
+ n.includes('light') ||
+ n.includes('highlighter') ||
+ n.includes('gizmo') ||
+ n.includes('bounding') ||
+ n.includes('debug') ||
+ n.includes('hitbox') ||
+ n.includes('collider') ||
+ n.includes('armature') ||
+ n.includes('mixamo')
);
}
function looksLikeTextName(name) {
- const n = String(name || "").toLowerCase();
- return (
- n.includes("text") ||
- n.includes("label") ||
- n.includes("caption") ||
- n.includes("title")
- );
+ const n = String(name || '').toLowerCase();
+ return n.includes('text') || n.includes('label') || n.includes('caption') || n.includes('title');
}
function getEntityRoot(mesh) {
@@ -198,7 +195,7 @@ function getEntityRoot(mesh) {
let lastValid = mesh;
while (node) {
- const n = node.name || "";
+ const n = node.name || '';
if (!looksLikeInternalMeshName(n)) {
lastValid = node;
}
@@ -225,23 +222,23 @@ function getMetadataText(mesh) {
md.description;
if (text && String(text).trim()) return resolveSpokenText(text);
}
- return "";
+ return '';
}
function cleanSpokenAnnouncement(text) {
- let s = String(text || "");
+ let s = String(text || '');
// Remove variation selectors
- s = s.replace(/[\uFE0E\uFE0F]/g, "");
+ s = s.replace(/[\uFE0E\uFE0F]/g, '');
// Remove Fitzpatrick skin tone modifiers
- s = s.replace(/[\u{1F3FB}-\u{1F3FF}]/gu, "");
+ s = s.replace(/[\u{1F3FB}-\u{1F3FF}]/gu, '');
// Remove most emoji / pictographs
- s = s.replace(/[\p{Extended_Pictographic}]/gu, "");
+ s = s.replace(/[\p{Extended_Pictographic}]/gu, '');
// Collapse extra spacing left behind
- s = s.replace(/\s+/g, " ").trim();
+ s = s.replace(/\s+/g, ' ').trim();
return s;
}
@@ -249,40 +246,35 @@ function cleanSpokenAnnouncement(text) {
function resolveSpokenText(value) {
// Unwrap common object shapes
let rawValue = value;
- if (rawValue && typeof rawValue === "object") {
+ if (rawValue && typeof rawValue === 'object') {
rawValue =
- rawValue.key ??
- rawValue.id ??
- rawValue.name ??
- rawValue.label ??
- rawValue.value ??
- "";
+ rawValue.key ?? rawValue.id ?? rawValue.name ?? rawValue.label ?? rawValue.value ?? '';
}
- const raw = String(rawValue ?? "").trim();
- if (!raw) return "";
+ const raw = String(rawValue ?? '').trim();
+ if (!raw) return '';
const original = raw;
const lower = raw.toLowerCase();
const underscored = lower
- .replace(/[\s-]+/g, "_")
- .replace(/_+/g, "_")
- .replace(/^_+|_+$/g, "");
+ .replace(/[\s-]+/g, '_')
+ .replace(/_+/g, '_')
+ .replace(/^_+|_+$/g, '');
- const stripped = underscored.replace(/^(key|id|name|label)[:=]\s*/i, "");
+ const stripped = underscored.replace(/^(key|id|name|label)[:=]\s*/i, '');
const candidates = [original, lower, underscored, stripped].filter(
(v, i, arr) => v && arr.indexOf(v) === i
);
const looksKeyLike = (s) =>
- s.startsWith("model_display_") ||
- s.endsWith("_ui") ||
- s.endsWith("_aria") ||
- s.endsWith("_tooltip") ||
- s.includes("_option") ||
- s.includes("_message") ||
- s.includes("_label") ||
+ s.startsWith('model_display_') ||
+ s.endsWith('_ui') ||
+ s.endsWith('_aria') ||
+ s.endsWith('_tooltip') ||
+ s.includes('_option') ||
+ s.includes('_message') ||
+ s.includes('_label') ||
/^[a-z0-9]+(_[a-z0-9]+){1,}$/.test(s);
for (const c of candidates) {
@@ -296,8 +288,8 @@ function resolveSpokenText(value) {
// If it still looks like a key, humanise it
if (looksKeyLike(stripped)) {
const human = stripped
- .replace(/^model_display_/, "")
- .replace(/_/g, " ")
+ .replace(/^model_display_/, '')
+ .replace(/_/g, ' ')
.trim();
if (human) return human;
}
@@ -317,9 +309,9 @@ export function recordWorldInstructionText(text) {
// Skip generic startup chatter
if (
- lower.includes("flock xr loaded successfully") ||
- lower.includes("flock world successfully loaded") ||
- lower.includes("loading flock xr")
+ lower.includes('flock xr loaded successfully') ||
+ lower.includes('flock world successfully loaded') ||
+ lower.includes('loading flock xr')
) {
return;
}
@@ -344,43 +336,41 @@ export function getObjectLabel(mesh) {
const root = getEntityRoot(mesh);
const rootMd = root?.metadata || {};
- const rootExplicit =
- rootMd.a11yLabel || rootMd.label || rootMd.displayName || rootMd.name;
+ const rootExplicit = rootMd.a11yLabel || rootMd.label || rootMd.displayName || rootMd.name;
if (rootExplicit) return resolveSpokenText(rootExplicit);
- const rootName = normaliseName(root?.name || "");
+ const rootName = normaliseName(root?.name || '');
if (rootName && !/^mesh\b/i.test(rootName) && !/^node\b/i.test(rootName)) {
return rootName;
}
- return normaliseName(mesh?.name || "object");
+ return normaliseName(mesh?.name || 'object');
}
function getDistanceLabel(distance) {
- if (distance < 1.5) return "very close";
- if (distance < 4) return "nearby";
- if (distance < 8) return "a short distance away";
- if (distance < 15) return "further away";
- return "far away";
+ if (distance < 1.5) return 'very close';
+ if (distance < 4) return 'nearby';
+ if (distance < 8) return 'a short distance away';
+ if (distance < 15) return 'further away';
+ return 'far away';
}
function getVerticalLabel(dy) {
- if (dy > 1.5) return "above you";
- if (dy < -1.5) return "below you";
- return "";
+ if (dy > 1.5) return 'above you';
+ if (dy < -1.5) return 'below you';
+ return '';
}
function getHorizontalLabel(dot, cross) {
- const frontBack =
- dot > 0.45 ? "in front of you" : dot < -0.45 ? "behind you" : "beside you";
+ const frontBack = dot > 0.45 ? 'in front of you' : dot < -0.45 ? 'behind you' : 'beside you';
- let leftRight = "";
+ let leftRight = '';
if (Math.abs(cross) > 0.3) {
- leftRight = cross > 0 ? "to your left" : "to your right";
+ leftRight = cross > 0 ? 'to your left' : 'to your right';
}
- if (frontBack === "beside you" && leftRight) return leftRight;
+ if (frontBack === 'beside you' && leftRight) return leftRight;
if (leftRight) return `${frontBack}, ${leftRight}`;
return frontBack;
}
@@ -396,7 +386,7 @@ function getCameraForward(scene) {
return { x: dir.x / len, z: dir.z / len };
}
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
try {
@@ -409,40 +399,40 @@ function getCameraForward(scene) {
return { x: x / len, z: z / len };
}
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
return { x: 0, z: 1 };
}
function isEnvironmentObject(label) {
- const n = String(label || "").toLowerCase();
+ const n = String(label || '').toLowerCase();
return (
- n.includes("sky") ||
- n.includes("ground") ||
- n.includes("floor") ||
- n.includes("terrain") ||
- n.includes("land") ||
- n.includes("grass") ||
- n.includes("road") ||
- n.includes("path")
+ n.includes('sky') ||
+ n.includes('ground') ||
+ n.includes('floor') ||
+ n.includes('terrain') ||
+ n.includes('land') ||
+ n.includes('grass') ||
+ n.includes('road') ||
+ n.includes('path')
);
}
function isSkyLike(label) {
- const n = String(label || "").toLowerCase();
- return n.includes("sky") || n.includes("cloud");
+ const n = String(label || '').toLowerCase();
+ return n.includes('sky') || n.includes('cloud');
}
function isGroundLike(label) {
- const n = String(label || "").toLowerCase();
+ const n = String(label || '').toLowerCase();
return (
- n.includes("ground") ||
- n.includes("floor") ||
- n.includes("terrain") ||
- n.includes("grass") ||
- n.includes("road") ||
- n.includes("path")
+ n.includes('ground') ||
+ n.includes('floor') ||
+ n.includes('terrain') ||
+ n.includes('grass') ||
+ n.includes('road') ||
+ n.includes('path')
);
}
@@ -466,7 +456,6 @@ function getInteractionHint(mesh) {
return String(hint).trim();
}
}
-
}
function closestBoundingBoxDistance(mesh, point) {
@@ -477,7 +466,9 @@ function closestBoundingBoxDistance(mesh, point) {
const cx = Math.max(bb.minimumWorld.x, Math.min(point.x, bb.maximumWorld.x));
const cy = Math.max(bb.minimumWorld.y, Math.min(point.y, bb.maximumWorld.y));
const cz = Math.max(bb.minimumWorld.z, Math.min(point.z, bb.maximumWorld.z));
- const dx = cx - point.x, dy = cy - point.y, dz = cz - point.z;
+ const dx = cx - point.x,
+ dy = cy - point.y,
+ dz = cz - point.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
} catch {
return null;
@@ -489,9 +480,14 @@ function isInsideBoundingBox(mesh, point) {
mesh.computeWorldMatrix?.(true);
const bb = mesh.getBoundingInfo?.()?.boundingBox;
if (!bb) return false;
- return point.x > bb.minimumWorld.x && point.x < bb.maximumWorld.x &&
- point.y > bb.minimumWorld.y && point.y < bb.maximumWorld.y &&
- point.z > bb.minimumWorld.z && point.z < bb.maximumWorld.z;
+ return (
+ point.x > bb.minimumWorld.x &&
+ point.x < bb.maximumWorld.x &&
+ point.y > bb.minimumWorld.y &&
+ point.y < bb.maximumWorld.y &&
+ point.z > bb.minimumWorld.z &&
+ point.z < bb.maximumWorld.z
+ );
} catch {
return false;
}
@@ -502,8 +498,11 @@ function isUnderfootOf(mesh, playerPos) {
mesh.computeWorldMatrix?.(true);
const bb = mesh.getBoundingInfo?.()?.boundingBox;
if (!bb || bb.maximumWorld.y === bb.minimumWorld.y) return false;
- const withinXZ = playerPos.x > bb.minimumWorld.x && playerPos.x < bb.maximumWorld.x &&
- playerPos.z > bb.minimumWorld.z && playerPos.z < bb.maximumWorld.z;
+ const withinXZ =
+ playerPos.x > bb.minimumWorld.x &&
+ playerPos.x < bb.maximumWorld.x &&
+ playerPos.z > bb.minimumWorld.z &&
+ playerPos.z < bb.maximumWorld.z;
return withinXZ && Math.abs(playerPos.y - bb.maximumWorld.y) < 2.0;
} catch {
return false;
@@ -519,7 +518,7 @@ function getRepresentativePosition(root, fallbackMesh) {
try {
node.computeWorldMatrix?.(true);
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
try {
@@ -533,21 +532,16 @@ function getRepresentativePosition(root, fallbackMesh) {
return center;
}
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
try {
const p = node.getAbsolutePosition?.() ?? node.position;
- if (
- p &&
- Number.isFinite(p.x) &&
- Number.isFinite(p.y) &&
- Number.isFinite(p.z)
- ) {
+ if (p && Number.isFinite(p.x) && Number.isFinite(p.y) && Number.isFinite(p.z)) {
return p;
}
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
}
@@ -563,17 +557,17 @@ export function getPlayerMesh(scene) {
// 1. Camera-attached mesh (highest priority)
const locked = camera.lockedTarget;
- if (locked && typeof locked.name === "string" && !locked.isDisposed?.()) {
+ if (locked && typeof locked.name === 'string' && !locked.isDisposed?.()) {
return locked;
}
const parent = camera.parent;
- if (parent && typeof parent.name === "string" && !parent.isDisposed?.()) {
+ if (parent && typeof parent.name === 'string' && !parent.isDisposed?.()) {
return parent;
}
const following = camera.metadata?.following;
- if (following && typeof following.name === "string" && !following.isDisposed?.()) {
+ if (following && typeof following.name === 'string' && !following.isDisposed?.()) {
return following;
}
@@ -596,16 +590,16 @@ export function getPlayerMesh(scene) {
let score = 0;
- if (md.a11yAnchor === "player" || md.a11yRole === "player") score += 200;
- if (md.a11yRole === "character" || md.role === "character") score += 150;
+ if (md.a11yAnchor === 'player' || md.a11yRole === 'player') score += 200;
+ if (md.a11yRole === 'character' || md.role === 'character') score += 150;
if (md.character === true) score += 120;
if (md.isPlayer === true) score += 200;
if (md.player === true) score += 180;
if (md.mainCharacter === true) score += 180;
- if (label.includes("player")) score += 80;
- if (label.includes("avatar")) score += 70;
- if (label.includes("character")) score += 60;
+ if (label.includes('player')) score += 80;
+ if (label.includes('avatar')) score += 70;
+ if (label.includes('character')) score += 60;
const p = getRepresentativePosition(root, mesh);
if (p && cameraPos) {
@@ -638,19 +632,19 @@ function getReferenceAnchor(scene) {
// Use feet position (bottom of bounding box) so ground-level objects
// measure correctly โ using the centre inflates distance to flat surfaces.
const pos = { x: bb.centerWorld.x, y: bb.minimumWorld.y, z: bb.centerWorld.z };
- return { kind: "character", mesh: playerMesh, position: pos };
+ return { kind: 'character', mesh: playerMesh, position: pos };
}
} catch {
/* no bounding info; fall through to representative position */
}
const pos = getRepresentativePosition(playerMesh, playerMesh);
if (pos) {
- return { kind: "character", mesh: playerMesh, position: pos };
+ return { kind: 'character', mesh: playerMesh, position: pos };
}
}
return {
- kind: "camera",
+ kind: 'camera',
mesh: camera || null,
position: cameraPos || { x: 0, y: 0, z: 0 },
};
@@ -730,9 +724,7 @@ function getSceneObjects(scene, options = {}) {
const dxCam = p.x - cameraPos.x;
const dyCam = p.y - cameraPos.y;
const dzCam = p.z - cameraPos.z;
- const distFromCamera = Math.sqrt(
- dxCam * dxCam + dyCam * dyCam + dzCam * dzCam,
- );
+ const distFromCamera = Math.sqrt(dxCam * dxCam + dyCam * dyCam + dzCam * dzCam);
if (distFromCamera < 0.2) continue;
const lenXZ = Math.sqrt(dxCam * dxCam + dzCam * dzCam) || 1;
@@ -766,7 +758,7 @@ function getSceneObjects(scene, options = {}) {
mesh.actionManager ||
root?.metadata?.interactive ||
root?.metadata?.clickable ||
- interactionHint,
+ interactionHint
);
const textLabels = collectNearbyTextForObject(scene, p, root);
@@ -798,21 +790,18 @@ function getSceneObjects(scene, options = {}) {
return Array.from(byEntityName.values());
}
-function objectToSentence(
- obj,
- { includeActionHint = false, includeText = false } = {},
-) {
- const where = [obj.horizontal, obj.vertical].filter(Boolean).join(" and ");
- let sentence = `${obj.label} is ${where || "nearby"}, ${obj.distanceLabel}.`;
+function objectToSentence(obj, { includeActionHint = false, includeText = false } = {}) {
+ const where = [obj.horizontal, obj.vertical].filter(Boolean).join(' and ');
+ let sentence = `${obj.label} is ${where || 'nearby'}, ${obj.distanceLabel}.`;
if (includeText && obj.sayText) {
sentence += ` ${obj.label} says: ${obj.sayText}.`;
} else if (includeText && obj.textLabels?.length) {
- sentence += ` Text: ${obj.textLabels.join(". ")}.`;
+ sentence += ` Text: ${obj.textLabels.join('. ')}.`;
}
if (includeActionHint && obj.interactive) {
- const hint = obj.interactionHint || "You can interact with this.";
+ const hint = obj.interactionHint || 'You can interact with this.';
sentence += ` ${hint}`;
}
@@ -820,13 +809,13 @@ function objectToSentence(
}
function buildEnvironmentSummary(objects, anchor = null, scene = null) {
- if (!objects?.length) return "";
+ if (!objects?.length) return '';
- const normalise = (s) => String(s || "").trim();
+ const normalise = (s) => String(s || '').trim();
const lower = (s) => normalise(s).toLowerCase();
// Exclude the main character label from the environment glimpse
- const anchorLabel = anchor?.mesh ? lower(getObjectLabel(anchor.mesh)) : "";
+ const anchorLabel = anchor?.mesh ? lower(getObjectLabel(anchor.mesh)) : '';
// Group objects by spoken label
const counts = new Map();
@@ -840,12 +829,12 @@ function buildEnvironmentSummary(objects, anchor = null, scene = null) {
if (labelLower === anchorLabel) continue;
- if (obj.isGroundLike || ["ground", "floor", "terrain", "grass"].includes(labelLower)) {
+ if (obj.isGroundLike || ['ground', 'floor', 'terrain', 'grass'].includes(labelLower)) {
hasGround = true;
continue;
}
- if (obj.isSkyLike || labelLower.includes("sky")) {
+ if (obj.isSkyLike || labelLower.includes('sky')) {
hasSky = true;
continue;
}
@@ -859,11 +848,10 @@ function buildEnvironmentSummary(objects, anchor = null, scene = null) {
}
// Sort repeated scene elements by count, then label
- const grouped = Array.from(counts.entries())
- .sort((a, b) => {
- if (b[1] !== a[1]) return b[1] - a[1];
- return a[0].localeCompare(b[0]);
- });
+ const grouped = Array.from(counts.entries()).sort((a, b) => {
+ if (b[1] !== a[1]) return b[1] - a[1];
+ return a[0].localeCompare(b[0]);
+ });
const parts = [];
@@ -872,20 +860,20 @@ function buildEnvironmentSummary(objects, anchor = null, scene = null) {
if (count === 1) {
parts.push(`${label}`);
} else {
- parts.push(`${count} ${label}${count === 1 ? "" : "s"}`);
+ parts.push(`${count} ${label}${count === 1 ? '' : 's'}`);
}
}
if (hasGround) {
- parts.push("ground");
+ parts.push('ground');
}
if (hasSky) {
- parts.push("sky");
+ parts.push('sky');
}
const chosen = parts.slice(0, 6);
- if (!chosen.length) return "";
+ if (!chosen.length) return '';
if (chosen.length === 1) {
return `The scene includes ${chosen[0]}.`;
@@ -895,18 +883,22 @@ function buildEnvironmentSummary(objects, anchor = null, scene = null) {
return `The scene includes ${chosen[0]} and ${chosen[1]}.`;
}
- return `The scene includes ${chosen.slice(0, -1).join(", ")}, and ${chosen[chosen.length - 1]}.`;
+ return `The scene includes ${chosen.slice(0, -1).join(', ')}, and ${chosen[chosen.length - 1]}.`;
}
export function recordObjectSayText(targetName, text) {
- const key = String(targetName || "").trim().toLowerCase();
+ const key = String(targetName || '')
+ .trim()
+ .toLowerCase();
const spoken = cleanSpokenAnnouncement(resolveSpokenText(text));
if (!key || !spoken) return;
objectSayTextCache.set(key, spoken);
}
export function setTransientSayText(targetName, text, duration) {
- const key = String(targetName || "").trim().toLowerCase();
+ const key = String(targetName || '')
+ .trim()
+ .toLowerCase();
if (!key) return;
if (objectTransientSayTimers.has(key)) {
@@ -938,21 +930,25 @@ function getSayMeshCandidates(mesh) {
getObjectLabel(mesh),
getObjectLabel(getEntityRoot(mesh)),
]
- .map((v) => String(v || "").trim().toLowerCase())
+ .map((v) =>
+ String(v || '')
+ .trim()
+ .toLowerCase()
+ )
.filter(Boolean);
}
function getTransientSayTextForMesh(mesh) {
- if (!mesh) return "";
+ if (!mesh) return '';
for (const key of getSayMeshCandidates(mesh)) {
const hit = objectTransientSayCache.get(key);
if (hit) return hit;
}
- return "";
+ return '';
}
function getCachedSayTextForMesh(mesh) {
- if (!mesh) return "";
+ if (!mesh) return '';
const candidates = getSayMeshCandidates(mesh);
for (const key of candidates) {
@@ -965,23 +961,21 @@ function getCachedSayTextForMesh(mesh) {
if (hit) return hit;
}
- return "";
+ return '';
}
function describeCharacterIntro(scene) {
const anchor = getReferenceAnchor(scene);
- if (!anchor?.mesh || anchor.kind !== "character") {
- return "";
+ if (!anchor?.mesh || anchor.kind !== 'character') {
+ return '';
}
const label = getObjectLabel(anchor.mesh);
const promptText = getCachedPromptTextForMesh(anchor.mesh);
- const hint = cleanSpokenAnnouncement(
- resolveSpokenText(getInteractionHint(anchor.mesh))
- );
+ const hint = cleanSpokenAnnouncement(resolveSpokenText(getInteractionHint(anchor.mesh)));
- let msg = "";
+ let msg = '';
if (promptText) {
msg += ` You can interact with ${label}. ${label} says: ${promptText}.`;
@@ -993,15 +987,17 @@ function describeCharacterIntro(scene) {
}
export function describeScene(scene) {
- if (!scene) return "No scene loaded.";
- if (!scene.activeCamera) return "No active camera is available.";
+ if (!scene) return 'No scene loaded.';
+ if (!scene.activeCamera) return 'No active camera is available.';
const anchor = getReferenceAnchor(scene);
const objects = getSceneObjects(scene, { anchor });
const labelCounts = new Map();
for (const o of objects) {
- const key = String(o.label || "").trim().toLowerCase();
+ const key = String(o.label || '')
+ .trim()
+ .toLowerCase();
if (!key) continue;
labelCounts.set(key, (labelCounts.get(key) || 0) + 1);
}
@@ -1014,15 +1010,17 @@ export function describeScene(scene) {
}
if (objects.length === 0) {
- if (parts.length) return parts.join(" ");
- return "I cannot detect any objects around you yet.";
+ if (parts.length) return parts.join(' ');
+ return 'I cannot detect any objects around you yet.';
}
const environmentSummary = buildEnvironmentSummary(objects, anchor, scene);
const mainObjects = objects
.filter((o) => {
- const key = String(o.label || "").trim().toLowerCase();
+ const key = String(o.label || '')
+ .trim()
+ .toLowerCase();
const repeatedScenery = (labelCounts.get(key) || 0) >= 4;
return !o.isEnvironment && !repeatedScenery;
})
@@ -1035,23 +1033,21 @@ export function describeScene(scene) {
}
if (mainObjects.length > 0) {
- parts.push(
- top.map((o) => objectToSentence(o, { includeText: true })).join(" "),
- );
+ parts.push(top.map((o) => objectToSentence(o, { includeText: true })).join(' '));
} else {
- parts.push("I can detect the environment, but no nearby main objects.");
+ parts.push('I can detect the environment, but no nearby main objects.');
}
if (worldInstructionTexts.length) {
- parts.push(`Instructions: ${worldInstructionTexts.join(". ")}.`);
+ parts.push(`Instructions: ${worldInstructionTexts.join('. ')}.`);
}
- return parts.join(" ");
+ return parts.join(' ');
}
export function describeNearestObject(scene) {
- if (!scene) return "No scene loaded.";
- if (!scene.activeCamera) return "No active camera is available.";
+ if (!scene) return 'No scene loaded.';
+ if (!scene.activeCamera) return 'No active camera is available.';
const anchor = getReferenceAnchor(scene);
const objects = getSceneObjects(scene, { anchor })
@@ -1059,7 +1055,7 @@ export function describeNearestObject(scene) {
.sort((a, b) => a.distFromAnchor - b.distFromAnchor);
if (objects.length === 0) {
- return "I cannot detect any nearby objects.";
+ return 'I cannot detect any nearby objects.';
}
const nearest = objects[0];
@@ -1070,15 +1066,16 @@ export function describeNearestObject(scene) {
}
export function describeFacingObject(scene) {
- if (!scene) return "No scene loaded.";
+ if (!scene) return 'No scene loaded.';
const camera = scene?.activeCamera;
- if (!camera) return "No active camera is available.";
+ if (!camera) return 'No active camera is available.';
const cameraPos = camera.globalPosition || camera.position;
- if (!cameraPos) return "No active camera is available.";
+ if (!cameraPos) return 'No active camera is available.';
// Full 3D forward direction from the camera (where the player is looking).
- let fwdX = 0, fwdZ = 1;
+ let fwdX = 0,
+ fwdZ = 1;
try {
const dir = camera.getForwardRay?.(1)?.direction;
if (dir && Number.isFinite(dir.x) && Number.isFinite(dir.y) && Number.isFinite(dir.z)) {
@@ -1086,13 +1083,15 @@ export function describeFacingObject(scene) {
fwdX = dir.x / len;
fwdZ = dir.z / len;
}
- } catch { /* fall through to default forward */ }
+ } catch {
+ /* fall through to default forward */
+ }
const anchor = getReferenceAnchor(scene);
// Cast the ray from the player's position when available, not the camera's.
// The camera may be offset behind/above in third-person; the player is what moves forward.
- const rayOrigin = (anchor?.kind === "character" && anchor.position) ? anchor.position : cameraPos;
+ const rayOrigin = anchor?.kind === 'character' && anchor.position ? anchor.position : cameraPos;
// Tube test is XZ-only: the player moves on the ground plane, so vertical offset
// (e.g. a tall tree whose bounding-box centre is several metres up) is irrelevant.
@@ -1178,28 +1177,25 @@ export function describeFacingObject(scene) {
}
}
- if (!bestLabel) return "Nothing ahead.";
+ if (!bestLabel) return 'Nothing ahead.';
const reportDist = anchor?.position
? (closestBoundingBoxDistance(bestRoot, anchor.position) ?? bestRawDist)
: bestRawDist;
- const dir = Math.abs(bestSigned) < 1.0 ? "Forward"
- : bestSigned > 0 ? "Left"
- : "Right";
+ const dir = Math.abs(bestSigned) < 1.0 ? 'Forward' : bestSigned > 0 ? 'Left' : 'Right';
return `${dir}: ${bestLabel}, ${getDistanceLabel(reportDist)}.`;
}
export function getHelpText(scene) {
- const custom =
- scene?.metadata?.a11yInstructions || scene?.metadata?.instructions;
+ const custom = scene?.metadata?.a11yInstructions || scene?.metadata?.instructions;
const baseInstructions = custom
? String(custom).trim()
- : "Use keyboard controls to navigate and interact with objects. Canvas keyboard controls: Arrow keys or WASD to move camera, Mouse to look around, Space for actions, Tab to navigate to other interface elements.";
+ : 'Use keyboard controls to navigate and interact with objects. Canvas keyboard controls: Arrow keys or WASD to move camera, Mouse to look around, Space for actions, Tab to navigate to other interface elements.';
const ctrlInstructions =
- " Press Control plus I to hear a scene summary. Press Control plus J to hear the nearest object. Press Control plus K to hear the object directly ahead. Press Control plus H to repeat these instructions.";
+ ' Press Control plus I to hear a scene summary. Press Control plus J to hear the nearest object. Press Control plus K to hear the object directly ahead. Press Control plus H to repeat these instructions.';
return `${baseInstructions}${ctrlInstructions}`;
}
@@ -1207,7 +1203,7 @@ export function announceHelp(scene) {
announce(getHelpText(scene));
}
-function announceInteraction(mesh, actionWord = "interacted with", options = {}) {
+function announceInteraction(mesh, actionWord = 'interacted with', options = {}) {
if (!mesh) return;
const { noDedup = false } = options;
@@ -1215,17 +1211,11 @@ function announceInteraction(mesh, actionWord = "interacted with", options = {})
const label = getObjectLabel(root);
const hint = getInteractionHint(root);
const pos = getRepresentativePosition(root, mesh);
- const textLabels = currentScene
- ? collectNearbyTextForObject(currentScene, pos, root)
- : [];
+ const textLabels = currentScene ? collectNearbyTextForObject(currentScene, pos, root) : [];
const now = Date.now();
- const interactionKey = `${actionWord}:${label}:${hint}:${(textLabels || []).join("|")}`;
- if (
- !noDedup &&
- interactionKey === lastInteractionKey &&
- now - lastInteractionTime < 400
- ) {
+ const interactionKey = `${actionWord}:${label}:${hint}:${(textLabels || []).join('|')}`;
+ if (!noDedup && interactionKey === lastInteractionKey && now - lastInteractionTime < 400) {
return;
}
lastInteractionKey = interactionKey;
@@ -1233,7 +1223,7 @@ function announceInteraction(mesh, actionWord = "interacted with", options = {})
let msg = `You ${actionWord} ${label}.`;
if (textLabels.length) {
- msg += ` Text: ${textLabels.join(". ")}.`;
+ msg += ` Text: ${textLabels.join('. ')}.`;
}
if (hint) {
msg += ` ${hint}`;
@@ -1250,9 +1240,7 @@ function announceMeshAction(mesh, actionWord, options = {}) {
// is for Ctrl+J discovery; the say block announces its own text when it
// fires, so repeating the old text on interaction is confusing.
const transientSay = getTransientSayTextForMesh(root);
- const textLabels = currentScene
- ? collectNearbyTextForObject(currentScene, pos, root)
- : [];
+ const textLabels = currentScene ? collectNearbyTextForObject(currentScene, pos, root) : [];
if (transientSay) {
announce(`${label} says: ${transientSay}`, options);
@@ -1260,7 +1248,7 @@ function announceMeshAction(mesh, actionWord, options = {}) {
}
if (textLabels.length) {
- announce(`${label}. ${textLabels.join(". ")}`, options);
+ announce(`${label}. ${textLabels.join('. ')}`, options);
return;
}
@@ -1273,14 +1261,14 @@ function attachPointerAnnouncements(scene) {
if (pointerObserverScene && pointerObserverRef) {
try {
pointerObserverScene.onPointerObservable.remove(pointerObserverRef);
- } catch { /* observer already removed */ }
+ } catch {
+ /* observer already removed */
+ }
pointerObserverRef = null;
pointerObserverScene = null;
}
- const PointerTypes =
- window.BABYLON?.PointerEventTypes ||
- globalThis.BABYLON?.PointerEventTypes;
+ const PointerTypes = window.BABYLON?.PointerEventTypes || globalThis.BABYLON?.PointerEventTypes;
pointerObserverScene = scene;
@@ -1295,23 +1283,21 @@ function attachPointerAnnouncements(scene) {
if (!pickedMesh) return;
- const isBabylonPick = PointerTypes
- ? type === PointerTypes.POINTERPICK
- : true;
+ const isBabylonPick = PointerTypes ? type === PointerTypes.POINTERPICK : true;
// Require a real primary click / tap, not hover or passive movement
const isPrimaryMouseClick =
!evt ||
- evt.pointerType === "touch" ||
- evt.type === "click" ||
- evt.type === "pointerup" ||
- evt.type === "mouseup";
+ evt.pointerType === 'touch' ||
+ evt.type === 'click' ||
+ evt.type === 'pointerup' ||
+ evt.type === 'mouseup';
if (!isBabylonPick || !isPrimaryMouseClick) return;
if (introInProgress || Date.now() < suppressPointerUntil) return;
- announceMeshAction(pickedMesh, "selected");
+ announceMeshAction(pickedMesh, 'selected');
} catch {
// fail silently
}
@@ -1320,11 +1306,13 @@ function attachPointerAnnouncements(scene) {
onInteractObservable.add((mesh) => {
if (introInProgress || Date.now() < suppressPointerUntil) return;
- announceMeshAction(mesh, "interacted with", { noDedup: true });
+ announceMeshAction(mesh, 'interacted with', { noDedup: true });
});
export function recordObjectPromptText(targetName, text) {
- const key = String(targetName || "").trim().toLowerCase();
+ const key = String(targetName || '')
+ .trim()
+ .toLowerCase();
const spoken = cleanSpokenAnnouncement(resolveSpokenText(text));
if (!key || !spoken) return;
@@ -1336,15 +1324,19 @@ export function recordObjectPromptText(targetName, text) {
}
function getCachedPromptTextForMesh(mesh) {
- if (!mesh) return "";
+ if (!mesh) return '';
const candidates = [
mesh?.name,
getEntityRoot(mesh)?.name,
getObjectLabel(mesh),
- getObjectLabel(getEntityRoot(mesh))
+ getObjectLabel(getEntityRoot(mesh)),
]
- .map((v) => String(v || "").trim().toLowerCase())
+ .map((v) =>
+ String(v || '')
+ .trim()
+ .toLowerCase()
+ )
.filter(Boolean);
for (const key of candidates) {
@@ -1352,7 +1344,7 @@ function getCachedPromptTextForMesh(mesh) {
if (hit) return hit;
}
- return "";
+ return '';
}
export function announceSayText(text, options = {}) {
@@ -1363,8 +1355,8 @@ export function announceSayText(text, options = {}) {
const lower = spoken.toLowerCase();
if (
- lower.includes("flock xr loaded successfully") ||
- lower.includes("flock world successfully loaded")
+ lower.includes('flock xr loaded successfully') ||
+ lower.includes('flock world successfully loaded')
) {
return;
}
@@ -1422,10 +1414,10 @@ function scheduleInitialIntro(scene) {
const waitUntilReady = (tries = 0) => {
if (scene !== currentScene) return;
- const loadingScreen = document.getElementById("loadingScreen");
+ const loadingScreen = document.getElementById('loadingScreen');
const stillLoading =
- document.body.classList.contains("loading") ||
- (loadingScreen && !loadingScreen.classList.contains("fade-out"));
+ document.body.classList.contains('loading') ||
+ (loadingScreen && !loadingScreen.classList.contains('fade-out'));
if (stillLoading && tries < 40) {
setTimeout(() => waitUntilReady(tries + 1), 250);
@@ -1442,10 +1434,10 @@ function scheduleInitialIntro(scene) {
});
};
- if (document.readyState === "complete") {
+ if (document.readyState === 'complete') {
waitUntilReady();
} else {
- window.addEventListener("load", () => waitUntilReady(), { once: true });
+ window.addEventListener('load', () => waitUntilReady(), { once: true });
}
}
@@ -1456,9 +1448,9 @@ export function enableSceneDescription(scene, inputManager) {
createLiveRegion();
// Reset per-world state
- lastInteractionKey = "";
+ lastInteractionKey = '';
lastInteractionTime = 0;
- lastAnnouncedText = "";
+ lastAnnouncedText = '';
lastAnnouncedAt = 0;
announceSeq += 1;
@@ -1474,43 +1466,42 @@ export function enableSceneDescription(scene, inputManager) {
attachPointerAnnouncements(scene);
scheduleInitialIntro(scene);
-
if (keyListenerAttached) return;
keyListenerAttached = true;
document.addEventListener(
- "keydown",
+ 'keydown',
(e) => {
- const tag = (e.target && e.target.tagName) ? e.target.tagName.toLowerCase() : "";
- if (tag === "input" || tag === "textarea" || e.target?.isContentEditable) return;
+ const tag = e.target && e.target.tagName ? e.target.tagName.toLowerCase() : '';
+ if (tag === 'input' || tag === 'textarea' || e.target?.isContentEditable) return;
if (!e.ctrlKey || e.altKey || e.metaKey) return;
if (!e.key) return;
const key = e.key.toLowerCase();
- if (key === "i") {
+ if (key === 'i') {
e.preventDefault();
e.stopPropagation();
announce(describeScene(currentScene));
return;
}
- if (key === "j") {
+ if (key === 'j') {
e.preventDefault();
e.stopPropagation();
announce(describeNearestObject(currentScene));
return;
}
- if (key === "h") {
+ if (key === 'h') {
e.preventDefault();
e.stopPropagation();
announceHelp(currentScene);
return;
}
- if (key === "k") {
+ if (key === 'k') {
e.preventDefault();
e.stopPropagation();
announce(describeFacingObject(currentScene), { noDedup: true });
@@ -1521,9 +1512,9 @@ export function enableSceneDescription(scene, inputManager) {
if (inputManager) {
inputManager.onActionDownObservable.add((action) => {
- if (action === "A11Y_I") announce(describeScene(currentScene));
- else if (action === "A11Y_J") announce(describeNearestObject(currentScene));
- else if (action === "A11Y_K") announce(describeFacingObject(currentScene), { noDedup: true });
+ if (action === 'A11Y_I') announce(describeScene(currentScene));
+ else if (action === 'A11Y_J') announce(describeNearestObject(currentScene));
+ else if (action === 'A11Y_K') announce(describeFacingObject(currentScene), { noDedup: true });
});
}
}
@@ -1532,6 +1523,6 @@ export function enableSceneDescription(scene, inputManager) {
* Helper for Flock code to call directly on custom events
* (e.g., collisions, scripted triggers, button clicks).
*/
-export function announceSceneEvent(mesh, verb = "interacted with") {
+export function announceSceneEvent(mesh, verb = 'interacted with') {
announceInteraction(mesh, verb);
}
diff --git a/accessibility/uiA11y.js b/accessibility/uiA11y.js
index 601b97171..74f8f4cc8 100644
--- a/accessibility/uiA11y.js
+++ b/accessibility/uiA11y.js
@@ -32,8 +32,7 @@ function _createSrRoot() {
root.setAttribute('role', 'group');
root.setAttribute('aria-label', 'UI controls');
// Visually hidden but accessible; individual elements are moved into view on focus
- root.style.cssText =
- 'position:absolute;left:-9999px;top:0;width:1px;height:1px;overflow:hidden;';
+ root.style.cssText = 'position:absolute;left:-9999px;top:0;width:1px;height:1px;overflow:hidden;';
document.body.appendChild(root);
return root;
}
@@ -52,9 +51,13 @@ function _installCanvasTabHandler(canvas) {
function _getFocusableElements() {
if (!_srRoot) return [];
- return Array.from(
- _srRoot.querySelectorAll('[data-flock-twin]')
- ).filter((el) => el.tagName === 'BUTTON' || el.tagName === 'INPUT' || el.tagName === 'P' || el.tagName === 'SPAN');
+ return Array.from(_srRoot.querySelectorAll('[data-flock-twin]')).filter(
+ (el) =>
+ el.tagName === 'BUTTON' ||
+ el.tagName === 'INPUT' ||
+ el.tagName === 'P' ||
+ el.tagName === 'SPAN'
+ );
}
// Cycle focus within shadow controls; return to canvas at either end
@@ -89,12 +92,12 @@ function _computeFixedRect({ x, y, w, h }) {
// Negative coordinates mean right/bottom alignment (same logic as ui.js)
const cssLeft = x < 0 ? cw - Math.abs(px) - pw : px;
- const cssTop = y < 0 ? ch - Math.abs(py) - ph : py;
+ const cssTop = y < 0 ? ch - Math.abs(py) - ph : py;
return {
- left: cr.left + cssLeft,
- top: cr.top + cssTop,
- width: pw,
+ left: cr.left + cssLeft,
+ top: cr.top + cssTop,
+ width: pw,
height: ph,
};
}
@@ -121,9 +124,9 @@ function _attachFocusBehavior(el, posParams) {
el.addEventListener('focus', () => {
const rect = _computeFixedRect(posParams);
if (rect) {
- indicator.style.left = `${rect.left}px`;
- indicator.style.top = `${rect.top}px`;
- indicator.style.width = `${rect.width}px`;
+ indicator.style.left = `${rect.left}px`;
+ indicator.style.top = `${rect.top}px`;
+ indicator.style.width = `${rect.width}px`;
indicator.style.height = `${rect.height}px`;
indicator.style.display = 'block';
}
@@ -168,7 +171,11 @@ export function registerUIButton(id, text, babylonButton, posParams) {
btn.removeEventListener('click', onClick);
btn.removeEventListener('keydown', _onTwinKeydown);
indicator.remove();
- try { babylonButton.onDisposeObservable.remove(disposeObs); } catch { /* already gone */ }
+ try {
+ babylonButton.onDisposeObservable.remove(disposeObs);
+ } catch {
+ /* already gone */
+ }
},
});
}
@@ -208,15 +215,30 @@ export function registerUISlider(id, babylonSlider, posParams) {
range.removeEventListener('input', onInput);
range.removeEventListener('keydown', _onTwinKeydown);
indicator.remove();
- try { babylonSlider.onValueChangedObservable.remove(valueObs); } catch { /* already gone */ }
- try { babylonSlider.onDisposeObservable.remove(disposeObs); } catch { /* already gone */ }
+ try {
+ babylonSlider.onValueChangedObservable.remove(valueObs);
+ } catch {
+ /* already gone */
+ }
+ try {
+ babylonSlider.onDisposeObservable.remove(disposeObs);
+ } catch {
+ /* already gone */
+ }
},
});
}
// โโโ UIInput twin โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-export function registerUIInput(inputId, submitId, babylonInput, babylonSubmit, inputPosParams, submitPosParams) {
+export function registerUIInput(
+ inputId,
+ submitId,
+ babylonInput,
+ babylonSubmit,
+ inputPosParams,
+ submitPosParams
+) {
if (!_srRoot) return;
unregisterUIControl(inputId);
@@ -233,9 +255,7 @@ export function registerUIInput(inputId, submitId, babylonInput, babylonSubmit,
// Capture next focus target before the twins are removed by dispose
const focusable = _getFocusableElements();
const idx = focusable.indexOf(submitBtn);
- const nextTarget = idx >= 0 && idx < focusable.length - 1
- ? focusable[idx + 1]
- : _canvas;
+ const nextTarget = idx >= 0 && idx < focusable.length - 1 ? focusable[idx + 1] : _canvas;
babylonInput.text = htmlInput.value;
babylonSubmit.onPointerUpObservable.notifyObservers({});
@@ -251,12 +271,11 @@ export function registerUIInput(inputId, submitId, babylonInput, babylonSubmit,
const _applyStyle = (focused) => {
const rect = _computeFixedRect(inputPosParams);
if (!rect) return;
- const bg = babylonInput.background || '#ffffff';
- const fg = babylonInput.color || '#000000';
- const fs = babylonInput.fontSize || '16px';
+ const bg = babylonInput.background || '#ffffff';
+ const fg = babylonInput.color || '#000000';
+ const fs = babylonInput.fontSize || '16px';
const border = focused ? '2px solid #005fcc' : '1px solid rgba(128,128,128,0.6)';
- _phStyle.textContent =
- `#${_styleId}-el::placeholder { color: ${fg}; opacity: 0.5; }`;
+ _phStyle.textContent = `#${_styleId}-el::placeholder { color: ${fg}; opacity: 0.5; }`;
htmlInput.id = `${_styleId}-el`;
htmlInput.style.cssText =
`position:fixed;left:${rect.left}px;top:${rect.top}px;` +
@@ -288,8 +307,12 @@ export function registerUIInput(inputId, submitId, babylonInput, babylonSubmit,
const idx = focusable.indexOf(htmlInput);
e.preventDefault();
(e.shiftKey
- ? (idx > 0 ? focusable[idx - 1] : _canvas)
- : (idx < focusable.length - 1 ? focusable[idx + 1] : _canvas)
+ ? idx > 0
+ ? focusable[idx - 1]
+ : _canvas
+ : idx < focusable.length - 1
+ ? focusable[idx + 1]
+ : _canvas
)?.focus();
}
};
@@ -319,7 +342,11 @@ export function registerUIInput(inputId, submitId, babylonInput, babylonSubmit,
submitBtn.removeEventListener('keydown', _onTwinKeydown);
submitIndicator.remove();
_phStyle.remove();
- try { babylonInput.onDisposeObservable.remove(disposeObs); } catch { /* already gone */ }
+ try {
+ babylonInput.onDisposeObservable.remove(disposeObs);
+ } catch {
+ /* already gone */
+ }
},
});
}
@@ -362,9 +389,9 @@ export function registerUIText(id, text, posParams) {
p.addEventListener('focus', () => {
const rect = _computeFixedRect(posParams);
if (rect) {
- indicator.style.left = `${rect.left}px`;
- indicator.style.top = `${rect.top}px`;
- indicator.style.width = `${rect.width}px`;
+ indicator.style.left = `${rect.left}px`;
+ indicator.style.top = `${rect.top}px`;
+ indicator.style.width = `${rect.width}px`;
indicator.style.height = `${rect.height}px`;
indicator.style.display = 'block';
}
diff --git a/api/animate.js b/api/animate.js
index decad4ae5..65cd5af7c 100644
--- a/api/animate.js
+++ b/api/animate.js
@@ -393,7 +393,13 @@ export const flockAnimate = {
// interrupted final frame) can't leave the mesh slightly off: the target for a
// one-way glide, or back at the start for a reverse (there-and-back) glide.
if (!loop) mesh.position = (reverse ? startPosition : endPosition).clone();
- if (isPhysicsActive && originalMotionType !== null && !loop && !reverse && isBodyAlive(mesh.physics)) {
+ if (
+ isPhysicsActive &&
+ originalMotionType !== null &&
+ !loop &&
+ !reverse &&
+ isBodyAlive(mesh.physics)
+ ) {
mesh.physics.setMotionType(originalMotionType);
}
resolve();
diff --git a/api/camera.js b/api/camera.js
index d675fa884..ff3b8ae8a 100644
--- a/api/camera.js
+++ b/api/camera.js
@@ -14,7 +14,7 @@ export const flockCamera = {
return new Promise((resolve) => {
flock.whenModelReady(meshName, async function (mesh) {
if (!mesh) {
- console.log("Model not loaded:", meshName);
+ console.log('Model not loaded:', meshName);
resolve();
return;
}
@@ -42,17 +42,17 @@ export const flockCamera = {
const forward = new flock.BABYLON.Vector3(
Math.sin(mesh.rotation.y),
0,
- Math.cos(mesh.rotation.y),
+ Math.cos(mesh.rotation.y)
);
// STEP 1: Place camera IN FRONT of character (facing them)
camera = new flock.BABYLON.ArcRotateCamera(
- "camera",
+ 'camera',
Math.PI / 2,
Math.PI,
radius,
mesh.position.add(forward.scale(3)), // in front
- flock.scene,
+ flock.scene
);
camera.lockedTarget = mesh;
@@ -67,7 +67,7 @@ export const flockCamera = {
camera.angularSensibilityX = 2000;
camera.angularSensibilityY = 2000;
camera.panningSensibility = 0;
- camera.inputs.removeByType("ArcRotateCameraMouseWheelInput");
+ camera.inputs.removeByType('ArcRotateCameraMouseWheelInput');
camera.inputs.attached.pointers.multiTouchPanAndZoom = false;
camera.inputs.attached.pointers.multiTouchPanning = false;
@@ -90,12 +90,12 @@ export const flockCamera = {
savedCamera?.detachControl();
camera = new flock.BABYLON.ArcRotateCamera(
- "camera",
+ 'camera',
Math.PI / 2,
Math.PI,
radius,
mesh.position,
- flock.scene,
+ flock.scene
);
camera.checkCollisions = true;
@@ -106,7 +106,7 @@ export const flockCamera = {
camera.angularSensibilityX = 2000;
camera.angularSensibilityY = 2000;
camera.panningSensibility = 0;
- camera.inputs.removeByType("ArcRotateCameraMouseWheelInput");
+ camera.inputs.removeByType('ArcRotateCameraMouseWheelInput');
camera.inputs.attached.pointers.multiTouchPanAndZoom = false;
camera.inputs.attached.pointers.multiTouchPanning = false;
@@ -136,43 +136,37 @@ export const flockCamera = {
// --- find or create a reusable constraint box (anchor) ---
let constraintBox =
- (flock._constraintBox &&
- !flock._constraintBox.isDisposed() &&
- flock._constraintBox) ||
+ (flock._constraintBox && !flock._constraintBox.isDisposed() && flock._constraintBox) ||
(scene.meshes || []).find(
(m) =>
- m &&
- typeof m.name === "string" &&
- m.name.startsWith("Constraint_") &&
- !m.isDisposed(),
+ m && typeof m.name === 'string' && m.name.startsWith('Constraint_') && !m.isDisposed()
);
if (!constraintBox) {
// create a new hidden static box
constraintBox = flock.BABYLON.MeshBuilder.CreateBox(
- "Constraint",
+ 'Constraint',
{ height: 1, width: 1, depth: 1 },
- scene,
+ scene
);
constraintBox.metadata = constraintBox.metadata || {};
constraintBox.metadata.blockKey = constraintBox.name;
- constraintBox.name = constraintBox.name + "_" + constraintBox.uniqueId;
+ constraintBox.name = constraintBox.name + '_' + constraintBox.uniqueId;
constraintBox.isVisible = false;
constraintBox.material =
- constraintBox.material ||
- new flock.BABYLON.StandardMaterial("staticMaterial", scene);
+ constraintBox.material || new flock.BABYLON.StandardMaterial('staticMaterial', scene);
const body = new flock.BABYLON.PhysicsBody(
constraintBox,
flock.BABYLON.PhysicsMotionType.STATIC,
false,
- scene,
+ scene
);
const shape = new flock.BABYLON.PhysicsShapeBox(
flock.BABYLON.Vector3.Zero(),
new flock.BABYLON.Quaternion(0, 0, 0, 1),
flock.BABYLON.Vector3.One(),
- scene,
+ scene
);
body.shape = shape;
body.setMassProperties({ mass: 1 });
@@ -188,13 +182,13 @@ export const flockCamera = {
constraintBox,
flock.BABYLON.PhysicsMotionType.STATIC,
false,
- scene,
+ scene
);
const shape = new flock.BABYLON.PhysicsShapeBox(
flock.BABYLON.Vector3.Zero(),
new flock.BABYLON.Quaternion(0, 0, 0, 1),
flock.BABYLON.Vector3.One(),
- scene,
+ scene
);
body.shape = shape;
body.setMassProperties({ mass: 1 });
@@ -205,7 +199,7 @@ export const flockCamera = {
flock.BABYLON.Vector3.Zero(),
new flock.BABYLON.Quaternion(0, 0, 0, 1),
flock.BABYLON.Vector3.One(),
- scene,
+ scene
);
}
}
@@ -225,7 +219,7 @@ export const flockCamera = {
constraintBox.physics.disablePreStep = false;
constraintBox.physics.setTargetTransform(
constraintBox.position,
- constraintBox.rotationQuaternion,
+ constraintBox.rotationQuaternion
);
}
@@ -249,7 +243,7 @@ export const flockCamera = {
maxLimit: 0,
},
],
- scene,
+ scene
);
try {
@@ -257,37 +251,27 @@ export const flockCamera = {
mesh.metadata.constraint = true;
mesh.metadata.uprightConstraint = constraint;
} catch (e) {
- console.warn("[ensureVerticalConstraint] addConstraint failed:", e);
+ console.warn('[ensureVerticalConstraint] addConstraint failed:', e);
}
// --- stabiliser: add only once per mesh to avoid stacking effects after swaps ---
if (!mesh.metadata._uprightStabiliser) {
- mesh.metadata._uprightStabiliser = scene.onAfterPhysicsObservable.add(
- () => {
- if (
- !mesh ||
- mesh.isDisposed() ||
- !mesh.physics ||
- !mesh.physics._pluginData
- )
- return;
- try {
- // preserve Y motion; zero X/Z linear velocity
- const v = mesh.physics.getLinearVelocity();
- mesh.physics.setLinearVelocity(
- new flock.BABYLON.Vector3(0, v.y, 0),
- );
-
- mesh.physics.setAngularVelocity(new flock.BABYLON.Vector3(0, 0, 0));
- } catch (err) {
- console.warn("Physics body became invalid:", err);
- }
- },
- );
+ mesh.metadata._uprightStabiliser = scene.onAfterPhysicsObservable.add(() => {
+ if (!mesh || mesh.isDisposed() || !mesh.physics || !mesh.physics._pluginData) return;
+ try {
+ // preserve Y motion; zero X/Z linear velocity
+ const v = mesh.physics.getLinearVelocity();
+ mesh.physics.setLinearVelocity(new flock.BABYLON.Vector3(0, v.y, 0));
+
+ mesh.physics.setAngularVelocity(new flock.BABYLON.Vector3(0, 0, 0));
+ } catch (err) {
+ console.warn('Physics body became invalid:', err);
+ }
+ });
}
},
getCamera() {
- return "__active_camera__";
+ return '__active_camera__';
},
_normalizeKeyCode(inputKey) {
const keyMap = {
@@ -295,17 +279,17 @@ export const flockCamera = {
ArrowUp: 38,
ArrowRight: 39,
ArrowDown: 40,
- " ": 32,
- ",": 188,
- ".": 190,
- "/": 191,
+ ' ': 32,
+ ',': 188,
+ '.': 190,
+ '/': 191,
};
- if (typeof inputKey === "number") {
+ if (typeof inputKey === 'number') {
return inputKey;
}
- if (typeof inputKey !== "string") {
+ if (typeof inputKey !== 'string') {
return null;
}
@@ -330,47 +314,47 @@ export const flockCamera = {
_applyCameraBinding(camera, normalizedKey, action) {
if (camera.keysRotateLeft) {
switch (action) {
- case "moveUp":
+ case 'moveUp':
camera.keysUp = [normalizedKey];
break;
- case "moveDown":
+ case 'moveDown':
camera.keysDown = [normalizedKey];
break;
- case "moveLeft":
+ case 'moveLeft':
camera.keysLeft = [normalizedKey];
break;
- case "moveRight":
+ case 'moveRight':
camera.keysRight = [normalizedKey];
break;
- case "rotateUp":
+ case 'rotateUp':
camera.keysRotateUp = [normalizedKey];
break;
- case "rotateDown":
+ case 'rotateDown':
camera.keysRotateDown = [normalizedKey];
break;
- case "rotateLeft":
+ case 'rotateLeft':
camera.keysRotateLeft = [normalizedKey];
break;
- case "rotateRight":
+ case 'rotateRight':
camera.keysRotateRight = [normalizedKey];
break;
}
} else {
switch (action) {
- case "rotateLeft":
- case "moveLeft":
+ case 'rotateLeft':
+ case 'moveLeft':
camera.keysLeft = [normalizedKey];
break;
- case "rotateRight":
- case "moveRight":
+ case 'rotateRight':
+ case 'moveRight':
camera.keysRight = [normalizedKey];
break;
- case "moveUp":
- case "rotateUp":
+ case 'moveUp':
+ case 'rotateUp':
camera.keysUp = [normalizedKey];
break;
- case "moveDown":
- case "rotateDown":
+ case 'moveDown':
+ case 'rotateDown':
camera.keysDown = [normalizedKey];
break;
}
@@ -385,22 +369,20 @@ export const flockCamera = {
cameraControl(key, action) {
const normalizedKey = this._normalizeKeyCode(key);
if (normalizedKey == null) {
- console.warn("Unsupported camera control key:", key);
+ console.warn('Unsupported camera control key:', key);
return;
}
if (!flock._cameraControlBindings) {
flock._cameraControlBindings = [];
}
- flock._cameraControlBindings = flock._cameraControlBindings.filter(
- (b) => b.action !== action,
- );
+ flock._cameraControlBindings = flock._cameraControlBindings.filter((b) => b.action !== action);
flock._cameraControlBindings.push({ normalizedKey, action });
if (flock.scene.activeCamera) {
this._applyCameraBinding(flock.scene.activeCamera, normalizedKey, action);
} else {
- console.error("No active camera found in the scene.");
+ console.error('No active camera found in the scene.');
}
},
};
diff --git a/api/control.js b/api/control.js
index bfc18e14b..bdce69cbc 100644
--- a/api/control.js
+++ b/api/control.js
@@ -22,22 +22,22 @@ export const flockControl = {
return;
}
const timeoutId = setTimeout(() => {
- signal?.removeEventListener("abort", onAbort);
+ signal?.removeEventListener('abort', onAbort);
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timeoutId);
- signal?.removeEventListener("abort", onAbort);
+ signal?.removeEventListener('abort', onAbort);
reject(flock.makeAbortError());
};
- signal?.addEventListener("abort", onAbort);
+ signal?.addEventListener('abort', onAbort);
});
},
makeAbortError() {
- const err = new Error("Run stopped");
- err.name = "AbortError";
+ const err = new Error('Run stopped');
+ err.name = 'AbortError';
return err;
},
async safeLoop(
@@ -45,7 +45,7 @@ export const flockControl = {
loopBody,
chunkSize = 100,
timing = { lastFrameTime: performance.now() },
- state = {},
+ state = {}
) {
if (state.stopExecution) return; // Check if we should stop further iterations
if (flock.abortController?.signal?.aborted) return;
@@ -64,8 +64,8 @@ export const flockControl = {
}
},
waitUntil(conditionFunc) {
- if (typeof conditionFunc !== "function") {
- console.warn("waitUntil: conditionFunc must be a function");
+ if (typeof conditionFunc !== 'function') {
+ console.warn('waitUntil: conditionFunc must be a function');
return Promise.resolve();
}
const signal = flock.abortController?.signal;
@@ -95,12 +95,12 @@ export const flockControl = {
const observer = flock.scene.onBeforeRenderObservable.add(checkCondition);
signal?.addEventListener(
- "abort",
+ 'abort',
() => {
flock.scene?.onBeforeRenderObservable?.remove(observer);
reject(flock.makeAbortError());
},
- { once: true },
+ { once: true }
);
});
},
diff --git a/api/csg.js b/api/csg.js
index 68334f4b0..af658de26 100644
--- a/api/csg.js
+++ b/api/csg.js
@@ -16,8 +16,7 @@ function prepareMeshForCSG(mesh) {
// Check if mesh itself has valid geometry using multiple methods
const hasPositions =
- mesh.getVerticesData &&
- mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
+ mesh.getVerticesData && mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
const hasIndices = mesh.getIndices && mesh.getIndices();
// If mesh has positions but no indices, it might need conversion
@@ -27,10 +26,7 @@ function prepareMeshForCSG(mesh) {
mesh.convertToUnIndexedMesh();
mesh.forceSharedVertices();
} catch (error) {
- console.warn(
- "[prepareMeshForCSG] Failed to convert mesh geometry:",
- error,
- );
+ console.warn('[prepareMeshForCSG] Failed to convert mesh geometry:', error);
}
}
@@ -49,10 +45,8 @@ function prepareMeshForCSG(mesh) {
// Mesh has no geometry, check for children with geometry
const children = mesh.getChildMeshes ? mesh.getChildMeshes(false) : [];
const meshesWithGeometry = children.filter((child) => {
- const childHasVertices =
- child.getTotalVertices && child.getTotalVertices() > 0;
- const childHasIndices =
- child.getIndices && child.getIndices() && child.getIndices().length > 0;
+ const childHasVertices = child.getTotalVertices && child.getTotalVertices() > 0;
+ const childHasIndices = child.getIndices && child.getIndices() && child.getIndices().length > 0;
return childHasVertices && childHasIndices;
});
@@ -60,8 +54,7 @@ function prepareMeshForCSG(mesh) {
if (meshesWithGeometry.length === 0) {
const childrenWithPositions = children.filter((child) => {
const positions =
- child.getVerticesData &&
- child.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
+ child.getVerticesData && child.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
return positions && positions.length > 0;
});
@@ -72,27 +65,18 @@ function prepareMeshForCSG(mesh) {
if (!child.getIndices() || child.getIndices().length === 0) {
child.forceSharedVertices();
}
- if (
- child.getTotalVertices() > 0 &&
- child.getIndices() &&
- child.getIndices().length > 0
- ) {
+ if (child.getTotalVertices() > 0 && child.getIndices() && child.getIndices().length > 0) {
meshesWithGeometry.push(child);
}
} catch (error) {
- console.warn(
- "[prepareMeshForCSG] Failed to process child mesh geometry:",
- error,
- );
+ console.warn('[prepareMeshForCSG] Failed to process child mesh geometry:', error);
}
});
}
}
if (meshesWithGeometry.length === 0) {
- console.warn(
- `[prepareMeshForCSG] No valid geometry found for mesh: ${mesh.name}`,
- );
+ console.warn(`[prepareMeshForCSG] No valid geometry found for mesh: ${mesh.name}`);
return null;
}
@@ -121,7 +105,7 @@ function prepareMeshForCSG(mesh) {
true, // allow32BitsIndices
undefined,
false,
- true,
+ true
);
if (merged) {
@@ -162,11 +146,7 @@ function recenterMeshLocalOrigin(mesh) {
if (!worldCenter) return;
mesh.bakeTransformIntoVertices(
- flock.BABYLON.Matrix.Translation(
- -worldCenter.x,
- -worldCenter.y,
- -worldCenter.z,
- ),
+ flock.BABYLON.Matrix.Translation(-worldCenter.x, -worldCenter.y, -worldCenter.z)
);
mesh.position.copyFrom(worldCenter);
mesh.computeWorldMatrix?.(true);
@@ -174,8 +154,7 @@ function recenterMeshLocalOrigin(mesh) {
}
function applyBoxProjectionUV(mesh, uvScale = 1) {
- if (!mesh?.getVerticesData || typeof flock.setSizeBasedBoxUVs !== "function")
- return;
+ if (!mesh?.getVerticesData || typeof flock.setSizeBasedBoxUVs !== 'function') return;
// Ensure per-face UV projection behaves predictably:
// indexed meshes share vertices across hard edges, which can blend normals
@@ -186,17 +165,13 @@ function applyBoxProjectionUV(mesh, uvScale = 1) {
currentIndices &&
currentIndices.length > 0 &&
currentIndices.length !==
- (mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind)?.length ||
- 0) /
- 3 &&
- typeof mesh.convertToUnIndexedMesh === "function"
+ (mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind)?.length || 0) / 3 &&
+ typeof mesh.convertToUnIndexedMesh === 'function'
) {
mesh.convertToUnIndexedMesh();
}
- const positions = mesh.getVerticesData(
- flock.BABYLON.VertexBuffer.PositionKind,
- );
+ const positions = mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
if (!positions || positions.length === 0) return;
const indices = mesh.getIndices ? mesh.getIndices() : null;
@@ -265,15 +240,15 @@ function hasUsableUVs(mesh) {
}
function shouldApplyBoxProjection(resultMesh, options = {}) {
- if (options.uvProjection === "box") return true;
- if (options.uvProjection !== "auto") return false;
+ if (options.uvProjection === 'box') return true;
+ if (options.uvProjection !== 'auto') return false;
const hasRenderableTexture = (texture) => {
if (!texture) return false;
- const textureName = String(texture.name || "").toLowerCase();
+ const textureName = String(texture.name || '').toLowerCase();
if (!textureName) return false;
- if (textureName.endsWith("undefined")) return false;
- if (textureName.includes("none.png")) return false;
+ if (textureName.endsWith('undefined')) return false;
+ if (textureName.includes('none.png')) return false;
return true;
};
@@ -315,13 +290,10 @@ function normalizeMeshAttributesForMerge(meshes, { logWarning = true } = {}) {
if (!normalizationRequired) return;
if (logWarning) {
- console.warn(
- "[mergeMeshes] Normalizing vertex attributes before fallback merge",
- {
- meshes: getMeshKindsSummary(meshes),
- requiredKinds: Array.from(kindUnion),
- },
- );
+ console.warn('[mergeMeshes] Normalizing vertex attributes before fallback merge', {
+ meshes: getMeshKindsSummary(meshes),
+ requiredKinds: Array.from(kindUnion),
+ });
}
const vertexBuffer = flock.BABYLON.VertexBuffer;
@@ -380,9 +352,7 @@ function normalizeMeshAttributesForMerge(meshes, { logWarning = true } = {}) {
function hasNonFinitePositions(mesh) {
if (!mesh?.getVerticesData) return true;
- const positions = mesh.getVerticesData(
- flock.BABYLON.VertexBuffer.PositionKind,
- );
+ const positions = mesh.getVerticesData(flock.BABYLON.VertexBuffer.PositionKind);
if (!positions || positions.length === 0) return true;
for (let i = 0; i < positions.length; i++) {
if (!Number.isFinite(positions[i])) return true;
@@ -404,20 +374,13 @@ function sanitizeMeshVertexDataForCSG(mesh) {
const positionKind = flock.BABYLON.VertexBuffer.PositionKind;
const normalKind = flock.BABYLON.VertexBuffer.NormalKind;
const positions = mesh.getVerticesData(positionKind);
- if (
- !positions ||
- positions.length === 0 ||
- arrayHasNonFiniteValues(positions)
- ) {
+ if (!positions || positions.length === 0 || arrayHasNonFiniteValues(positions)) {
return false;
}
const kinds = mesh.getVerticesDataKinds() || [];
let indices = mesh.getIndices ? mesh.getIndices() : null;
- if (
- (!indices || indices.length === 0) &&
- typeof mesh.setIndices === "function"
- ) {
+ if ((!indices || indices.length === 0) && typeof mesh.setIndices === 'function') {
indices = Array.from({ length: positions.length / 3 }, (_, i) => i);
mesh.setIndices(indices);
}
@@ -447,8 +410,8 @@ function resolveCsgModelIdentity(requestedModelId) {
let resolvedModelId = requestedModelId;
let blockKey = requestedModelId;
- if (typeof requestedModelId === "string" && requestedModelId.includes("__")) {
- const separatorIndex = requestedModelId.lastIndexOf("__");
+ if (typeof requestedModelId === 'string' && requestedModelId.includes('__')) {
+ const separatorIndex = requestedModelId.lastIndexOf('__');
resolvedModelId = requestedModelId.slice(0, separatorIndex);
blockKey = requestedModelId.slice(separatorIndex + 2);
}
@@ -463,29 +426,25 @@ export const flockCSG = {
if (!Array.isArray(meshes) || meshes.length === 0) return false;
return meshes.some((meshOrName) => {
const raw =
- typeof meshOrName === "string"
+ typeof meshOrName === 'string'
? meshOrName
- : meshOrName?.name || meshOrName?.metadata?.modelName || "";
+ : meshOrName?.name || meshOrName?.metadata?.modelName || '';
const name = raw.toLowerCase();
const modelName =
- typeof meshOrName === "string"
- ? ""
- : meshOrName?.metadata?.modelName?.toLowerCase?.() || "";
- return (
- name.includes("3dtext") ||
- modelName.includes("3dtext") ||
- modelName.includes("text")
- );
+ typeof meshOrName === 'string'
+ ? ''
+ : meshOrName?.metadata?.modelName?.toLowerCase?.() || '';
+ return name.includes('3dtext') || modelName.includes('3dtext') || modelName.includes('text');
});
},
toolMeshesUseTextures(meshes) {
if (!Array.isArray(meshes) || meshes.length === 0) return false;
const hasRenderableTexture = (texture) => {
if (!texture) return false;
- const textureName = String(texture.name || "").toLowerCase();
+ const textureName = String(texture.name || '').toLowerCase();
if (!textureName) return false;
- if (textureName.endsWith("undefined")) return false;
- if (textureName.includes("none.png")) return false;
+ if (textureName.endsWith('undefined')) return false;
+ if (textureName.includes('none.png')) return false;
return true;
};
const materialHasTexture = (material) => {
@@ -503,9 +462,7 @@ export const flockCSG = {
return meshes.some((mesh) => {
if (materialHasTexture(mesh?.material)) return true;
if (!mesh?.getChildMeshes) return false;
- return mesh
- .getChildMeshes()
- .some((child) => materialHasTexture(child?.material));
+ return mesh.getChildMeshes().some((child) => materialHasTexture(child?.material));
});
},
mergeCompositeMesh(meshes) {
@@ -518,228 +475,200 @@ export const flockCSG = {
true, // allow32BitsIndices
null, // meshSubclass
false, // subdivideWithSubMeshes (Set to false for CSG)
- true, // multiMultiMaterials
+ true // multiMultiMaterials
);
return merged;
},
mergeMeshes(modelId, meshList) {
- const { modelId: resolvedModelId, blockKey } =
- resolveCsgModelIdentity(modelId);
+ const { modelId: resolvedModelId, blockKey } = resolveCsgModelIdentity(modelId);
modelId = resolvedModelId;
- return flock
- .prepareMeshes(modelId, meshList, blockKey)
- .then((validMeshes) => {
- if (validMeshes.length) {
- const meshesToMerge = [];
- let referenceMesh = validMeshes[0];
+ return flock.prepareMeshes(modelId, meshList, blockKey).then((validMeshes) => {
+ if (validMeshes.length) {
+ const meshesToMerge = [];
+ let referenceMesh = validMeshes[0];
- validMeshes.forEach((mesh) => {
- let targetMesh = mesh;
-
- if (mesh.metadata?.modelName) {
- const meshWithMaterial =
- flock._findFirstDescendantWithMaterial(mesh);
- if (meshWithMaterial) {
- targetMesh = meshWithMaterial;
- targetMesh.refreshBoundingInfo();
- }
+ validMeshes.forEach((mesh) => {
+ let targetMesh = mesh;
+
+ if (mesh.metadata?.modelName) {
+ const meshWithMaterial = flock._findFirstDescendantWithMaterial(mesh);
+ if (meshWithMaterial) {
+ targetMesh = meshWithMaterial;
+ targetMesh.refreshBoundingInfo();
}
+ }
- targetMesh = prepareMeshForCSG(targetMesh);
- if (!targetMesh) return;
+ targetMesh = prepareMeshForCSG(targetMesh);
+ if (!targetMesh) return;
- targetMesh.computeWorldMatrix(true);
+ targetMesh.computeWorldMatrix(true);
- if (targetMesh.getTotalVertices() > 0) {
- meshesToMerge.push(targetMesh);
- }
- });
+ if (targetMesh.getTotalVertices() > 0) {
+ meshesToMerge.push(targetMesh);
+ }
+ });
- if (meshesToMerge.length === 0) return null;
+ if (meshesToMerge.length === 0) return null;
- if (meshesToMerge.length === 1) {
- const singleMesh = meshesToMerge[0];
- let mergedMesh = singleMesh.clone(modelId);
- if (!mergedMesh) mergedMesh = singleMesh;
+ if (meshesToMerge.length === 1) {
+ const singleMesh = meshesToMerge[0];
+ let mergedMesh = singleMesh.clone(modelId);
+ if (!mergedMesh) mergedMesh = singleMesh;
- mergedMesh.name = modelId;
- mergedMesh.metadata = mergedMesh.metadata || {};
- mergedMesh.metadata.blockKey = blockKey;
- mergedMesh.metadata.sharedMaterial = false;
+ mergedMesh.name = modelId;
+ mergedMesh.metadata = mergedMesh.metadata || {};
+ mergedMesh.metadata.blockKey = blockKey;
+ mergedMesh.metadata.sharedMaterial = false;
- return modelId;
- }
+ return modelId;
+ }
- const originalMaterial = referenceMesh.material;
- let mergedMesh = null;
- let csgSucceeded = false;
+ const originalMaterial = referenceMesh.material;
+ let mergedMesh = null;
+ let csgSucceeded = false;
- normalizeMeshAttributesForMerge(meshesToMerge, { logWarning: false });
+ normalizeMeshAttributesForMerge(meshesToMerge, { logWarning: false });
- const csgUnsafe = meshesToMerge.some((mesh) => {
- const positionsFinite = !hasNonFinitePositions(mesh);
- if (!positionsFinite) return true;
- return !sanitizeMeshVertexDataForCSG(mesh);
- });
+ const csgUnsafe = meshesToMerge.some((mesh) => {
+ const positionsFinite = !hasNonFinitePositions(mesh);
+ if (!positionsFinite) return true;
+ return !sanitizeMeshVertexDataForCSG(mesh);
+ });
- if (!csgUnsafe) {
- try {
- let currentMesh = meshesToMerge[0];
-
- for (let i = 1; i < meshesToMerge.length; i++) {
- const nextMesh = meshesToMerge[i];
-
- // Perform pairwise normalization to prevent property mismatch
- const pair = [currentMesh, nextMesh];
- normalizeMeshAttributesForMerge(pair, { logWarning: false });
-
- const leftCSG = flock.BABYLON.CSG2.FromMesh(currentMesh, false);
- const rightCSG = flock.BABYLON.CSG2.FromMesh(nextMesh, false);
- const combinedCSG = leftCSG.add(rightCSG);
-
- const oldIntermediate = currentMesh;
-
- // Convert back to mesh to "bake" geometry before the next addition
- currentMesh = combinedCSG.toMesh(
- "temp_step_" + i,
- referenceMesh.getScene(),
- {
- centerMesh: false,
- rebuildNormals: true,
- },
- );
-
- // Dispose the intermediate mesh if it's not part of the original source list
- if (i > 1 || oldIntermediate !== meshesToMerge[0]) {
- oldIntermediate.dispose();
- }
- }
+ if (!csgUnsafe) {
+ try {
+ let currentMesh = meshesToMerge[0];
- mergedMesh = currentMesh;
+ for (let i = 1; i < meshesToMerge.length; i++) {
+ const nextMesh = meshesToMerge[i];
- if (mergedMesh && mergedMesh.getTotalVertices() > 0) {
- csgSucceeded = true;
- } else {
- if (mergedMesh) mergedMesh.dispose();
- mergedMesh = null;
- }
- } catch (error) {
- const emptyMeshes = flock.scene.meshes.filter(
- (m) => m.name === modelId && m.getTotalVertices() === 0,
- );
- emptyMeshes.forEach((m) => m.dispose());
-
- if (
- !String(error?.message || "").includes(
- "same number of properties",
- ) ||
- flock?.materialsDebug
- ) {
- console.warn(
- "[mergeMeshes] Pairwise CSG attempt failed:",
- error,
- );
+ // Perform pairwise normalization to prevent property mismatch
+ const pair = [currentMesh, nextMesh];
+ normalizeMeshAttributesForMerge(pair, { logWarning: false });
+
+ const leftCSG = flock.BABYLON.CSG2.FromMesh(currentMesh, false);
+ const rightCSG = flock.BABYLON.CSG2.FromMesh(nextMesh, false);
+ const combinedCSG = leftCSG.add(rightCSG);
+
+ const oldIntermediate = currentMesh;
+
+ // Convert back to mesh to "bake" geometry before the next addition
+ currentMesh = combinedCSG.toMesh('temp_step_' + i, referenceMesh.getScene(), {
+ centerMesh: false,
+ rebuildNormals: true,
+ });
+
+ // Dispose the intermediate mesh if it's not part of the original source list
+ if (i > 1 || oldIntermediate !== meshesToMerge[0]) {
+ oldIntermediate.dispose();
}
- csgSucceeded = false;
}
- }
- if (!csgSucceeded) {
- try {
- normalizeMeshAttributesForMerge(meshesToMerge, {
- logWarning: false,
- });
- mergedMesh = flock.BABYLON.Mesh.MergeMeshes(
- meshesToMerge,
- false,
- true,
- undefined,
- true,
- true,
- );
- } catch (mergeError) {
- console.warn(
- "[mergeMeshes] Mesh.MergeMeshes fallback failed:",
- mergeError,
- );
- return null;
+ mergedMesh = currentMesh;
+
+ if (mergedMesh && mergedMesh.getTotalVertices() > 0) {
+ csgSucceeded = true;
+ } else {
+ if (mergedMesh) mergedMesh.dispose();
+ mergedMesh = null;
}
+ } catch (error) {
+ const emptyMeshes = flock.scene.meshes.filter(
+ (m) => m.name === modelId && m.getTotalVertices() === 0
+ );
+ emptyMeshes.forEach((m) => m.dispose());
+
+ if (
+ !String(error?.message || '').includes('same number of properties') ||
+ flock?.materialsDebug
+ ) {
+ console.warn('[mergeMeshes] Pairwise CSG attempt failed:', error);
+ }
+ csgSucceeded = false;
}
+ }
- if (!mergedMesh) return null;
+ if (!csgSucceeded) {
+ try {
+ normalizeMeshAttributesForMerge(meshesToMerge, {
+ logWarning: false,
+ });
+ mergedMesh = flock.BABYLON.Mesh.MergeMeshes(
+ meshesToMerge,
+ false,
+ true,
+ undefined,
+ true,
+ true
+ );
+ } catch (mergeError) {
+ console.warn('[mergeMeshes] Mesh.MergeMeshes fallback failed:', mergeError);
+ return null;
+ }
+ }
- recenterMeshLocalOrigin(mergedMesh);
+ if (!mergedMesh) return null;
- mergedMesh.name = modelId;
- mergedMesh.metadata = mergedMesh.metadata || {};
- mergedMesh.metadata.blockKey = blockKey;
- mergedMesh.metadata.sharedMaterial = false;
+ recenterMeshLocalOrigin(mergedMesh);
- const isDefaultMaterial = (material) => {
- return (
- material instanceof flock.BABYLON.StandardMaterial &&
- material.name === "default material"
+ mergedMesh.name = modelId;
+ mergedMesh.metadata = mergedMesh.metadata || {};
+ mergedMesh.metadata.blockKey = blockKey;
+ mergedMesh.metadata.sharedMaterial = false;
+
+ const isDefaultMaterial = (material) => {
+ return (
+ material instanceof flock.BABYLON.StandardMaterial &&
+ material.name === 'default material'
+ );
+ };
+
+ if (mergedMesh.material) {
+ if (mergedMesh.material instanceof flock.BABYLON.MultiMaterial) {
+ mergedMesh.material.subMaterials = mergedMesh.material.subMaterials.map(
+ (subMaterial) => {
+ if (subMaterial && isDefaultMaterial(subMaterial) && originalMaterial) {
+ const replacement = originalMaterial.clone(modelId + '_material');
+ replacement.backFaceCulling = false;
+ return replacement;
+ }
+ return subMaterial;
+ }
);
- };
-
- if (mergedMesh.material) {
- if (mergedMesh.material instanceof flock.BABYLON.MultiMaterial) {
- mergedMesh.material.subMaterials =
- mergedMesh.material.subMaterials.map((subMaterial) => {
- if (
- subMaterial &&
- isDefaultMaterial(subMaterial) &&
- originalMaterial
- ) {
- const replacement = originalMaterial.clone(
- modelId + "_material",
- );
- replacement.backFaceCulling = false;
- return replacement;
- }
- return subMaterial;
- });
- } else if (
- isDefaultMaterial(mergedMesh.material) &&
- originalMaterial
- ) {
- const newMat = originalMaterial.clone(modelId + "_material");
- newMat.backFaceCulling = false;
- mergedMesh.material = newMat;
- }
- } else if (originalMaterial) {
- const newMat = originalMaterial.clone(modelId + "_material");
+ } else if (isDefaultMaterial(mergedMesh.material) && originalMaterial) {
+ const newMat = originalMaterial.clone(modelId + '_material');
newMat.backFaceCulling = false;
mergedMesh.material = newMat;
}
+ } else if (originalMaterial) {
+ const newMat = originalMaterial.clone(modelId + '_material');
+ newMat.backFaceCulling = false;
+ mergedMesh.material = newMat;
+ }
- mergedMesh.createNormals(true);
+ mergedMesh.createNormals(true);
- try {
- const physicsShape = new flock.BABYLON.PhysicsShapeMesh(
- mergedMesh,
- flock.scene,
- );
- flock.applyPhysics(mergedMesh, physicsShape);
- } catch (e) {
- console.warn("Suppressed non-critical error:", e);
- }
+ try {
+ const physicsShape = new flock.BABYLON.PhysicsShapeMesh(mergedMesh, flock.scene);
+ flock.applyPhysics(mergedMesh, physicsShape);
+ } catch (e) {
+ console.warn('Suppressed non-critical error:', e);
+ }
- validMeshes.forEach((mesh) => {
- if (mesh !== mergedMesh) mesh.dispose();
- });
+ validMeshes.forEach((mesh) => {
+ if (mesh !== mergedMesh) mesh.dispose();
+ });
- return modelId;
- } else {
- return null;
- }
- });
+ return modelId;
+ } else {
+ return null;
+ }
+ });
},
subtractMeshesMerge(modelId, baseMeshName, meshNames, options = {}) {
- const { modelId: resolvedModelId, blockKey } =
- resolveCsgModelIdentity(modelId);
+ const { modelId: resolvedModelId, blockKey } = resolveCsgModelIdentity(modelId);
modelId = resolvedModelId;
const collectMaterialMeshesDeep = (root) => {
@@ -748,12 +677,7 @@ export const flockCSG = {
while (stack.length) {
const node = stack.pop();
if (!node) continue;
- if (
- node.getTotalVertices &&
- node.getTotalVertices() > 0 &&
- node.material
- )
- out.push(node);
+ if (node.getTotalVertices && node.getTotalVertices() > 0 && node.material) out.push(node);
const kids = node.getChildren ? node.getChildren() : [];
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
}
@@ -793,150 +717,119 @@ export const flockCSG = {
// Ensure base mesh has valid geometry for CSG
actualBase = prepareMeshForCSG(actualBase);
if (!actualBase) {
- console.warn(
- "[subtractMeshesMerge] Base mesh has no valid geometry for CSG.",
- );
+ console.warn('[subtractMeshesMerge] Base mesh has no valid geometry for CSG.');
return resolve(null);
}
- flock
- .prepareMeshes(modelId, meshNames, blockKey)
- .then((validMeshes) => {
- const inferredUvProjection =
- options.uvProjection === undefined &&
- flock.toolMeshesUseTextures(validMeshes)
- ? "auto"
- : options.uvProjection;
- const scene = baseMesh.getScene();
- const baseDuplicate = cloneForCSG(actualBase, "baseDuplicate");
- let outerCSG = flock.BABYLON.CSG2.FromMesh(baseDuplicate, false);
- const subtractDuplicates = [];
-
- validMeshes.forEach((mesh, meshIndex) => {
- const parts = collectMaterialMeshesDeep(mesh);
-
- // Check if mesh itself has valid geometry (e.g., manifold text meshes)
- const meshHasGeometry =
- mesh.getTotalVertices && mesh.getTotalVertices() > 0;
-
- if (parts.length > 0) {
- const partClones = parts.map((p, i) =>
- cloneForCSG(p, `temp_${meshIndex}_${i}`),
- );
- const isDonut =
- mesh.name.toLowerCase().includes("donut") ||
- mesh.metadata?.modelName?.toLowerCase().includes("donut");
-
- if (isDonut) {
- partClones.forEach((pc) => subtractDuplicates.push(pc));
- } else {
- let unified =
- partClones.length > 1
- ? flock.BABYLON.Mesh.MergeMeshes(
- partClones,
- true,
- true,
- undefined,
- false,
- true,
- )
- : partClones[0];
- if (unified) {
- unified.forceSharedVertices();
- if (
- mesh.metadata?.modelName &&
- typeof unified.flipFaces === "function"
- )
- unified.flipFaces();
- subtractDuplicates.push(unified);
- }
+ flock.prepareMeshes(modelId, meshNames, blockKey).then((validMeshes) => {
+ const inferredUvProjection =
+ options.uvProjection === undefined && flock.toolMeshesUseTextures(validMeshes)
+ ? 'auto'
+ : options.uvProjection;
+ const scene = baseMesh.getScene();
+ const baseDuplicate = cloneForCSG(actualBase, 'baseDuplicate');
+ let outerCSG = flock.BABYLON.CSG2.FromMesh(baseDuplicate, false);
+ const subtractDuplicates = [];
+
+ validMeshes.forEach((mesh, meshIndex) => {
+ const parts = collectMaterialMeshesDeep(mesh);
+
+ // Check if mesh itself has valid geometry (e.g., manifold text meshes)
+ const meshHasGeometry = mesh.getTotalVertices && mesh.getTotalVertices() > 0;
+
+ if (parts.length > 0) {
+ const partClones = parts.map((p, i) => cloneForCSG(p, `temp_${meshIndex}_${i}`));
+ const isDonut =
+ mesh.name.toLowerCase().includes('donut') ||
+ mesh.metadata?.modelName?.toLowerCase().includes('donut');
+
+ if (isDonut) {
+ partClones.forEach((pc) => subtractDuplicates.push(pc));
+ } else {
+ let unified =
+ partClones.length > 1
+ ? flock.BABYLON.Mesh.MergeMeshes(partClones, true, true, undefined, false, true)
+ : partClones[0];
+ if (unified) {
+ unified.forceSharedVertices();
+ if (mesh.metadata?.modelName && typeof unified.flipFaces === 'function')
+ unified.flipFaces();
+ subtractDuplicates.push(unified);
}
- } else if (meshHasGeometry) {
- // Direct mesh without children (e.g., manifold text mesh)
- const clone = cloneForCSG(mesh, `direct_tool_${meshIndex}`);
- subtractDuplicates.push(clone);
}
- });
-
- subtractDuplicates.forEach((m, idx) => {
- try {
- const meshCSG = flock.BABYLON.CSG2.FromMesh(m, false);
- outerCSG = outerCSG.subtract(meshCSG);
- } catch (e) {
- console.warn(
- `[subtractMeshesMerge] Subtraction ${idx} failed:`,
- e.message,
- );
- }
- });
+ } else if (meshHasGeometry) {
+ // Direct mesh without children (e.g., manifold text mesh)
+ const clone = cloneForCSG(mesh, `direct_tool_${meshIndex}`);
+ subtractDuplicates.push(clone);
+ }
+ });
- let resultMesh;
+ subtractDuplicates.forEach((m, idx) => {
try {
- resultMesh = outerCSG.toMesh("resultMesh", scene, {
- centerMesh: false,
- });
-
- if (!resultMesh || resultMesh.getTotalVertices() === 0) {
- throw new Error("CSG produced empty mesh");
- }
+ const meshCSG = flock.BABYLON.CSG2.FromMesh(m, false);
+ outerCSG = outerCSG.subtract(meshCSG);
} catch (e) {
- flock.reportBlockError({
- key: "csg_empty_result",
- api: "subtractMeshesMerge",
- values: {
- hint: "CSG requires watertight (manifold) geometry; 3D text and merged meshes are typically non-manifold.",
- },
- error: e,
- });
+ console.warn(`[subtractMeshesMerge] Subtraction ${idx} failed:`, e.message);
+ }
+ });
- // Clean up any empty meshes
- flock.scene.meshes
- .filter(
- (m) => m.name === "resultMesh" && m.getTotalVertices() === 0,
- )
- .forEach((m) => m.dispose());
+ let resultMesh;
+ try {
+ resultMesh = outerCSG.toMesh('resultMesh', scene, {
+ centerMesh: false,
+ });
- baseDuplicate.dispose();
- subtractDuplicates.forEach((m) => m.dispose());
- return resolve(null);
+ if (!resultMesh || resultMesh.getTotalVertices() === 0) {
+ throw new Error('CSG produced empty mesh');
}
-
- resultMesh.position.set(0, 0, 0);
- resultMesh.rotation.set(0, 0, 0);
- resultMesh.scaling.set(1, 1, 1);
- resultMesh.computeWorldMatrix(true);
- flock.applyResultMeshProperties(
- resultMesh,
- actualBase,
- modelId,
- blockKey,
- {
- forceReferenceMaterial: options.forceReferenceMaterial === true,
- flattenNonReferenceSubMaterials:
- options.flattenNonReferenceSubMaterials === true,
+ } catch (e) {
+ flock.reportBlockError({
+ key: 'csg_empty_result',
+ api: 'subtractMeshesMerge',
+ values: {
+ hint: 'CSG requires watertight (manifold) geometry; 3D text and merged meshes are typically non-manifold.',
},
- );
- if (
- shouldApplyBoxProjection(resultMesh, {
- ...options,
- uvProjection: inferredUvProjection,
- })
- ) {
- applyBoxProjectionUV(resultMesh, options.uvScale);
- }
+ error: e,
+ });
+
+ // Clean up any empty meshes
+ flock.scene.meshes
+ .filter((m) => m.name === 'resultMesh' && m.getTotalVertices() === 0)
+ .forEach((m) => m.dispose());
baseDuplicate.dispose();
subtractDuplicates.forEach((m) => m.dispose());
- baseMesh.dispose();
- validMeshes.forEach((m) => m.dispose());
- resolve(modelId);
+ return resolve(null);
+ }
+
+ resultMesh.position.set(0, 0, 0);
+ resultMesh.rotation.set(0, 0, 0);
+ resultMesh.scaling.set(1, 1, 1);
+ resultMesh.computeWorldMatrix(true);
+ flock.applyResultMeshProperties(resultMesh, actualBase, modelId, blockKey, {
+ forceReferenceMaterial: options.forceReferenceMaterial === true,
+ flattenNonReferenceSubMaterials: options.flattenNonReferenceSubMaterials === true,
});
+ if (
+ shouldApplyBoxProjection(resultMesh, {
+ ...options,
+ uvProjection: inferredUvProjection,
+ })
+ ) {
+ applyBoxProjectionUV(resultMesh, options.uvScale);
+ }
+
+ baseDuplicate.dispose();
+ subtractDuplicates.forEach((m) => m.dispose());
+ baseMesh.dispose();
+ validMeshes.forEach((m) => m.dispose());
+ resolve(modelId);
+ });
});
});
},
subtractMeshesIndividual(modelId, baseMeshName, meshNames, options = {}) {
- const { modelId: resolvedModelId, blockKey } =
- resolveCsgModelIdentity(modelId);
+ const { modelId: resolvedModelId, blockKey } = resolveCsgModelIdentity(modelId);
modelId = resolvedModelId;
const collectMaterialMeshesDeep = (root) => {
@@ -945,12 +838,7 @@ export const flockCSG = {
while (stack.length) {
const node = stack.pop();
if (!node) continue;
- if (
- node.getTotalVertices &&
- node.getTotalVertices() > 0 &&
- node.material
- )
- out.push(node);
+ if (node.getTotalVertices && node.getTotalVertices() > 0 && node.material) out.push(node);
const kids = node.getChildren ? node.getChildren() : [];
for (let i = kids.length - 1; i >= 0; i--) stack.push(kids[i]);
}
@@ -962,408 +850,323 @@ export const flockCSG = {
if (!baseMesh) return resolve(null);
let actualBase = baseMesh;
if (baseMesh.metadata?.modelName) {
- const meshWithMaterial =
- flock._findFirstDescendantWithMaterial(baseMesh);
+ const meshWithMaterial = flock._findFirstDescendantWithMaterial(baseMesh);
if (meshWithMaterial) actualBase = meshWithMaterial;
}
// Ensure base mesh has valid geometry for CSG
actualBase = prepareMeshForCSG(actualBase);
if (!actualBase) {
- console.warn(
- "[subtractMeshesIndividual] Base mesh has no valid geometry for CSG.",
- );
+ console.warn('[subtractMeshesIndividual] Base mesh has no valid geometry for CSG.');
return resolve(null);
}
- flock
- .prepareMeshes(modelId, meshNames, blockKey)
- .then((validMeshes) => {
- const inferredUvProjection =
- options.uvProjection === undefined &&
- flock.toolMeshesUseTextures(validMeshes)
- ? "auto"
- : options.uvProjection;
- const scene = baseMesh.getScene();
- const baseDuplicate = actualBase.clone("baseDuplicate");
- baseDuplicate.setParent(null);
- baseDuplicate.position = actualBase.getAbsolutePosition().clone();
- baseDuplicate.rotationQuaternion = null;
- baseDuplicate.rotation = actualBase.absoluteRotationQuaternion
- ? actualBase.absoluteRotationQuaternion.toEulerAngles()
- : actualBase.rotation.clone();
- baseDuplicate.computeWorldMatrix(true);
-
- let outerCSG = flock.BABYLON.CSG2.FromMesh(baseDuplicate, false);
- const allToolParts = [];
- validMeshes.forEach((mesh) => {
- const parts = collectMaterialMeshesDeep(mesh);
- parts.forEach((p) => {
- const dup = p.clone("partDup", null, true);
- dup.computeWorldMatrix(true);
- if (typeof dup.flipFaces === "function") dup.flipFaces();
- allToolParts.push(dup);
- });
- });
-
- allToolParts.forEach((part) => {
- try {
- const partCSG = flock.BABYLON.CSG2.FromMesh(part, false);
- outerCSG = outerCSG.subtract(partCSG);
- } catch (e) {
- console.warn(e);
- }
+ flock.prepareMeshes(modelId, meshNames, blockKey).then((validMeshes) => {
+ const inferredUvProjection =
+ options.uvProjection === undefined && flock.toolMeshesUseTextures(validMeshes)
+ ? 'auto'
+ : options.uvProjection;
+ const scene = baseMesh.getScene();
+ const baseDuplicate = actualBase.clone('baseDuplicate');
+ baseDuplicate.setParent(null);
+ baseDuplicate.position = actualBase.getAbsolutePosition().clone();
+ baseDuplicate.rotationQuaternion = null;
+ baseDuplicate.rotation = actualBase.absoluteRotationQuaternion
+ ? actualBase.absoluteRotationQuaternion.toEulerAngles()
+ : actualBase.rotation.clone();
+ baseDuplicate.computeWorldMatrix(true);
+
+ let outerCSG = flock.BABYLON.CSG2.FromMesh(baseDuplicate, false);
+ const allToolParts = [];
+ validMeshes.forEach((mesh) => {
+ const parts = collectMaterialMeshesDeep(mesh);
+ parts.forEach((p) => {
+ const dup = p.clone('partDup', null, true);
+ dup.computeWorldMatrix(true);
+ if (typeof dup.flipFaces === 'function') dup.flipFaces();
+ allToolParts.push(dup);
});
+ });
- let resultMesh;
+ allToolParts.forEach((part) => {
try {
- resultMesh = outerCSG.toMesh("resultMesh", scene, {
- centerMesh: false,
- });
-
- if (!resultMesh || resultMesh.getTotalVertices() === 0) {
- throw new Error("CSG produced empty mesh");
- }
+ const partCSG = flock.BABYLON.CSG2.FromMesh(part, false);
+ outerCSG = outerCSG.subtract(partCSG);
} catch (e) {
- flock.reportBlockError({
- key: "csg_empty_result",
- api: "subtractMeshesIndividual",
- values: {
- hint: "CSG requires watertight (manifold) geometry; 3D text and merged meshes are typically non-manifold.",
- },
- error: e,
- });
+ console.warn(e);
+ }
+ });
- // Clean up any empty meshes
- flock.scene.meshes
- .filter(
- (m) => m.name === "resultMesh" && m.getTotalVertices() === 0,
- )
- .forEach((m) => m.dispose());
+ let resultMesh;
+ try {
+ resultMesh = outerCSG.toMesh('resultMesh', scene, {
+ centerMesh: false,
+ });
- baseDuplicate.dispose();
- allToolParts.forEach((t) => t.dispose());
- return resolve(null);
+ if (!resultMesh || resultMesh.getTotalVertices() === 0) {
+ throw new Error('CSG produced empty mesh');
}
-
- const localCenter = resultMesh
- .getBoundingInfo()
- .boundingBox.center.clone();
- resultMesh.setPivotMatrix(
- flock.BABYLON.Matrix.Translation(
- localCenter.x,
- localCenter.y,
- localCenter.z,
- ),
- false,
- );
- resultMesh.position.subtractInPlace(localCenter);
- resultMesh.computeWorldMatrix(true);
- flock.applyResultMeshProperties(
- resultMesh,
- actualBase,
- modelId,
- blockKey,
- {
- forceReferenceMaterial: options.forceReferenceMaterial === true,
- flattenNonReferenceSubMaterials:
- options.flattenNonReferenceSubMaterials === true,
+ } catch (e) {
+ flock.reportBlockError({
+ key: 'csg_empty_result',
+ api: 'subtractMeshesIndividual',
+ values: {
+ hint: 'CSG requires watertight (manifold) geometry; 3D text and merged meshes are typically non-manifold.',
},
- );
- if (
- shouldApplyBoxProjection(resultMesh, {
- ...options,
- uvProjection: inferredUvProjection,
- })
- ) {
- applyBoxProjectionUV(resultMesh, options.uvScale);
- }
+ error: e,
+ });
+
+ // Clean up any empty meshes
+ flock.scene.meshes
+ .filter((m) => m.name === 'resultMesh' && m.getTotalVertices() === 0)
+ .forEach((m) => m.dispose());
baseDuplicate.dispose();
allToolParts.forEach((t) => t.dispose());
- baseMesh.dispose();
- validMeshes.forEach((m) => m.dispose());
- resolve(modelId);
+ return resolve(null);
+ }
+
+ const localCenter = resultMesh.getBoundingInfo().boundingBox.center.clone();
+ resultMesh.setPivotMatrix(
+ flock.BABYLON.Matrix.Translation(localCenter.x, localCenter.y, localCenter.z),
+ false
+ );
+ resultMesh.position.subtractInPlace(localCenter);
+ resultMesh.computeWorldMatrix(true);
+ flock.applyResultMeshProperties(resultMesh, actualBase, modelId, blockKey, {
+ forceReferenceMaterial: options.forceReferenceMaterial === true,
+ flattenNonReferenceSubMaterials: options.flattenNonReferenceSubMaterials === true,
});
+ if (
+ shouldApplyBoxProjection(resultMesh, {
+ ...options,
+ uvProjection: inferredUvProjection,
+ })
+ ) {
+ applyBoxProjectionUV(resultMesh, options.uvScale);
+ }
+
+ baseDuplicate.dispose();
+ allToolParts.forEach((t) => t.dispose());
+ baseMesh.dispose();
+ validMeshes.forEach((m) => m.dispose());
+ resolve(modelId);
+ });
});
});
},
- subtractMeshes(
- modelId,
- baseMeshName,
- meshNames,
- optionsOrApproach = "merge",
- ) {
+ subtractMeshes(modelId, baseMeshName, meshNames, optionsOrApproach = 'merge') {
const options =
- optionsOrApproach && typeof optionsOrApproach === "object"
- ? optionsOrApproach
- : {};
+ optionsOrApproach && typeof optionsOrApproach === 'object' ? optionsOrApproach : {};
const approach =
- typeof optionsOrApproach === "string"
- ? optionsOrApproach
- : options.approach || "merge";
-
- if (approach === "individual") {
- return this.subtractMeshesIndividual(
- modelId,
- baseMeshName,
- meshNames,
- options,
- );
+ typeof optionsOrApproach === 'string' ? optionsOrApproach : options.approach || 'merge';
+
+ if (approach === 'individual') {
+ return this.subtractMeshesIndividual(modelId, baseMeshName, meshNames, options);
} else {
- return this.subtractMeshesMerge(
- modelId,
- baseMeshName,
- meshNames,
- options,
- );
+ return this.subtractMeshesMerge(modelId, baseMeshName, meshNames, options);
}
},
intersectMeshes(modelId, meshList) {
- const { modelId: resolvedModelId, blockKey } =
- resolveCsgModelIdentity(modelId);
+ const { modelId: resolvedModelId, blockKey } = resolveCsgModelIdentity(modelId);
modelId = resolvedModelId;
- return flock
- .prepareMeshes(modelId, meshList, blockKey)
- .then((validMeshes) => {
- if (validMeshes.length) {
- let firstMesh = validMeshes[0];
- // If metadata exists, use the mesh with material.
- if (firstMesh.metadata?.modelName) {
- const meshWithMaterial =
- flock._findFirstDescendantWithMaterial(firstMesh);
+ return flock.prepareMeshes(modelId, meshList, blockKey).then((validMeshes) => {
+ if (validMeshes.length) {
+ let firstMesh = validMeshes[0];
+ // If metadata exists, use the mesh with material.
+ if (firstMesh.metadata?.modelName) {
+ const meshWithMaterial = flock._findFirstDescendantWithMaterial(firstMesh);
+ if (meshWithMaterial) {
+ firstMesh = meshWithMaterial;
+ firstMesh.refreshBoundingInfo();
+ firstMesh.flipFaces();
+ }
+ }
+
+ // Ensure mesh has valid geometry for CSG
+ firstMesh = prepareMeshForCSG(firstMesh);
+ if (!firstMesh) {
+ console.warn('First mesh has no valid geometry for CSG intersect.');
+ return null;
+ }
+
+ // Create the base CSG
+ let baseCSG;
+ try {
+ baseCSG = flock.BABYLON.CSG2.FromMesh(firstMesh, false);
+ } catch (e) {
+ console.warn('[intersectMeshes] CSG2.FromMesh failed on first mesh:', e.message);
+ console.warn(
+ '[intersectMeshes] Note: CSG operations require watertight (manifold) geometry. 3D text and merged meshes are typically non-manifold.'
+ );
+ return null;
+ }
+
+ // Intersect each subsequent mesh
+ let csgFailed = false;
+ validMeshes.slice(1).forEach((mesh) => {
+ if (csgFailed) return;
+
+ if (mesh.metadata?.modelName) {
+ const meshWithMaterial = flock._findFirstDescendantWithMaterial(mesh);
if (meshWithMaterial) {
- firstMesh = meshWithMaterial;
- firstMesh.refreshBoundingInfo();
- firstMesh.flipFaces();
+ mesh = meshWithMaterial;
+ mesh.refreshBoundingInfo();
+ mesh.flipFaces();
}
}
// Ensure mesh has valid geometry for CSG
- firstMesh = prepareMeshForCSG(firstMesh);
- if (!firstMesh) {
- console.warn("First mesh has no valid geometry for CSG intersect.");
- return null;
+ mesh = prepareMeshForCSG(mesh);
+ if (!mesh) {
+ console.warn('Skipping mesh with no valid geometry for CSG intersect.');
+ return;
}
- // Create the base CSG
- let baseCSG;
try {
- baseCSG = flock.BABYLON.CSG2.FromMesh(firstMesh, false);
+ const meshCSG = flock.BABYLON.CSG2.FromMesh(mesh, false);
+ baseCSG = baseCSG.intersect(meshCSG);
} catch (e) {
- console.warn(
- "[intersectMeshes] CSG2.FromMesh failed on first mesh:",
- e.message,
- );
- console.warn(
- "[intersectMeshes] Note: CSG operations require watertight (manifold) geometry. 3D text and merged meshes are typically non-manifold.",
- );
- return null;
+ console.warn('[intersectMeshes] CSG intersect failed:', e.message);
+ csgFailed = true;
}
+ });
- // Intersect each subsequent mesh
- let csgFailed = false;
- validMeshes.slice(1).forEach((mesh) => {
- if (csgFailed) return;
-
- if (mesh.metadata?.modelName) {
- const meshWithMaterial =
- flock._findFirstDescendantWithMaterial(mesh);
- if (meshWithMaterial) {
- mesh = meshWithMaterial;
- mesh.refreshBoundingInfo();
- mesh.flipFaces();
- }
- }
-
- // Ensure mesh has valid geometry for CSG
- mesh = prepareMeshForCSG(mesh);
- if (!mesh) {
- console.warn(
- "Skipping mesh with no valid geometry for CSG intersect.",
- );
- return;
- }
+ if (csgFailed) {
+ console.warn(
+ '[intersectMeshes] Note: CSG operations require watertight (manifold) geometry.'
+ );
+ return null;
+ }
- try {
- const meshCSG = flock.BABYLON.CSG2.FromMesh(mesh, false);
- baseCSG = baseCSG.intersect(meshCSG);
- } catch (e) {
- console.warn(
- "[intersectMeshes] CSG intersect failed:",
- e.message,
- );
- csgFailed = true;
- }
+ // Generate the resulting intersected mesh
+ let intersectedMesh;
+ try {
+ intersectedMesh = baseCSG.toMesh('intersectedMesh', validMeshes[0].getScene(), {
+ centerMesh: false,
+ rebuildNormals: true,
});
- if (csgFailed) {
- console.warn(
- "[intersectMeshes] Note: CSG operations require watertight (manifold) geometry.",
- );
- return null;
+ if (!intersectedMesh || intersectedMesh.getTotalVertices() === 0) {
+ throw new Error('CSG produced empty mesh');
}
+ } catch (e) {
+ flock.reportBlockError({
+ key: 'csg_empty_result',
+ api: 'intersectMeshes',
+ values: {
+ hint: 'CSG requires watertight (manifold) geometry.',
+ },
+ error: e,
+ });
- // Generate the resulting intersected mesh
- let intersectedMesh;
- try {
- intersectedMesh = baseCSG.toMesh(
- "intersectedMesh",
- validMeshes[0].getScene(),
- {
- centerMesh: false,
- rebuildNormals: true,
- },
- );
-
- if (!intersectedMesh || intersectedMesh.getTotalVertices() === 0) {
- throw new Error("CSG produced empty mesh");
- }
- } catch (e) {
- flock.reportBlockError({
- key: "csg_empty_result",
- api: "intersectMeshes",
- values: {
- hint: "CSG requires watertight (manifold) geometry.",
- },
- error: e,
- });
-
- // Clean up any empty meshes
- flock.scene.meshes
- .filter(
- (m) =>
- m.name === "intersectedMesh" && m.getTotalVertices() === 0,
- )
- .forEach((m) => m.dispose());
+ // Clean up any empty meshes
+ flock.scene.meshes
+ .filter((m) => m.name === 'intersectedMesh' && m.getTotalVertices() === 0)
+ .forEach((m) => m.dispose());
- return null;
- }
+ return null;
+ }
- // Keep local origin aligned with mesh bounds while preserving world placement.
- recenterMeshLocalOrigin(intersectedMesh);
+ // Keep local origin aligned with mesh bounds while preserving world placement.
+ recenterMeshLocalOrigin(intersectedMesh);
- // Apply properties to the resulting mesh
- flock.applyResultMeshProperties(
- intersectedMesh,
- firstMesh,
- modelId,
- blockKey,
- );
+ // Apply properties to the resulting mesh
+ flock.applyResultMeshProperties(intersectedMesh, firstMesh, modelId, blockKey);
- validMeshes.forEach((mesh) => mesh.dispose());
+ validMeshes.forEach((mesh) => mesh.dispose());
- return modelId; // Return the modelId as per original functionality
- } else {
- console.warn("No valid meshes to intersect.");
- return null;
- }
- });
+ return modelId; // Return the modelId as per original functionality
+ } else {
+ console.warn('No valid meshes to intersect.');
+ return null;
+ }
+ });
},
createHull(modelId, meshList) {
- const { modelId: resolvedModelId, blockKey } =
- resolveCsgModelIdentity(modelId);
+ const { modelId: resolvedModelId, blockKey } = resolveCsgModelIdentity(modelId);
modelId = resolvedModelId;
- return flock
- .prepareMeshes(modelId, meshList, blockKey)
- .then((validMeshes) => {
- if (validMeshes.length) {
- // Calculate the combined bounding box centre
- let min = validMeshes[0]
- .getBoundingInfo()
- .boundingBox.minimumWorld.clone();
- let max = validMeshes[0]
- .getBoundingInfo()
- .boundingBox.maximumWorld.clone();
+ return flock.prepareMeshes(modelId, meshList, blockKey).then((validMeshes) => {
+ if (validMeshes.length) {
+ // Calculate the combined bounding box centre
+ let min = validMeshes[0].getBoundingInfo().boundingBox.minimumWorld.clone();
+ let max = validMeshes[0].getBoundingInfo().boundingBox.maximumWorld.clone();
- validMeshes.forEach((mesh) => {
- const boundingInfo = mesh.getBoundingInfo();
- const meshMin = boundingInfo.boundingBox.minimumWorld;
- const meshMax = boundingInfo.boundingBox.maximumWorld;
+ validMeshes.forEach((mesh) => {
+ const boundingInfo = mesh.getBoundingInfo();
+ const meshMin = boundingInfo.boundingBox.minimumWorld;
+ const meshMax = boundingInfo.boundingBox.maximumWorld;
- min = flock.BABYLON.Vector3.Minimize(min, meshMin);
- max = flock.BABYLON.Vector3.Maximize(max, meshMax);
- });
+ min = flock.BABYLON.Vector3.Minimize(min, meshMin);
+ max = flock.BABYLON.Vector3.Maximize(max, meshMax);
+ });
- const combinedCentre = min.add(max).scale(0.5);
-
- // Merge the valid meshes into a single mesh
- const updatedValidMeshes = validMeshes.map((mesh) => {
- if (mesh.metadata?.modelName) {
- const meshWithMaterial =
- flock._findFirstDescendantWithMaterial(mesh);
- if (meshWithMaterial) {
- meshWithMaterial.refreshBoundingInfo();
- meshWithMaterial.flipFaces();
- return meshWithMaterial;
- }
+ const combinedCentre = min.add(max).scale(0.5);
+
+ // Merge the valid meshes into a single mesh
+ const updatedValidMeshes = validMeshes.map((mesh) => {
+ if (mesh.metadata?.modelName) {
+ const meshWithMaterial = flock._findFirstDescendantWithMaterial(mesh);
+ if (meshWithMaterial) {
+ meshWithMaterial.refreshBoundingInfo();
+ meshWithMaterial.flipFaces();
+ return meshWithMaterial;
}
- return mesh;
- });
+ }
+ return mesh;
+ });
- const mergedMesh = flock.BABYLON.Mesh.MergeMeshes(
- updatedValidMeshes,
- true,
- );
+ const mergedMesh = flock.BABYLON.Mesh.MergeMeshes(updatedValidMeshes, true);
- if (!mergedMesh) {
- console.warn("Failed to merge meshes for hull creation.");
- return null;
- }
+ if (!mergedMesh) {
+ console.warn('Failed to merge meshes for hull creation.');
+ return null;
+ }
- // Offset the merged mesh to be locally centred
- mergedMesh.bakeTransformIntoVertices(
- flock.BABYLON.Matrix.Translation(
- -combinedCentre.x,
- -combinedCentre.y,
- -combinedCentre.z,
- ),
- );
+ // Offset the merged mesh to be locally centred
+ mergedMesh.bakeTransformIntoVertices(
+ flock.BABYLON.Matrix.Translation(-combinedCentre.x, -combinedCentre.y, -combinedCentre.z)
+ );
- // Apply the material of the first mesh to the merged mesh
- mergedMesh.material = updatedValidMeshes[0].material;
+ // Apply the material of the first mesh to the merged mesh
+ mergedMesh.material = updatedValidMeshes[0].material;
- // Create the convex hull physics aggregate
- const hullAggregate = new flock.BABYLON.PhysicsAggregate(
- mergedMesh,
- flock.BABYLON.PhysicsShapeType.CONVEX_HULL,
- { mass: 0 }, // Adjust mass based on use case
- flock.scene,
- );
+ // Create the convex hull physics aggregate
+ const hullAggregate = new flock.BABYLON.PhysicsAggregate(
+ mergedMesh,
+ flock.BABYLON.PhysicsShapeType.CONVEX_HULL,
+ { mass: 0 }, // Adjust mass based on use case
+ flock.scene
+ );
- // Create a debug mesh to visualize the convex hull
- const hullMesh = flock.hullMeshFromBody(hullAggregate.body);
+ // Create a debug mesh to visualize the convex hull
+ const hullMesh = flock.hullMeshFromBody(hullAggregate.body);
- // Offset the debug mesh to the original world position
- hullMesh.position = combinedCentre;
+ // Offset the debug mesh to the original world position
+ hullMesh.position = combinedCentre;
- hullMesh.material = updatedValidMeshes[0].material;
+ hullMesh.material = updatedValidMeshes[0].material;
- // Apply properties to the resulting mesh
- flock.applyResultMeshProperties(
- hullMesh,
- updatedValidMeshes[0],
- modelId,
- blockKey,
- );
- // Dispose of original meshes after creating the hull
- validMeshes.forEach((mesh) => mesh.dispose());
- mergedMesh.dispose();
+ // Apply properties to the resulting mesh
+ flock.applyResultMeshProperties(hullMesh, updatedValidMeshes[0], modelId, blockKey);
+ // Dispose of original meshes after creating the hull
+ validMeshes.forEach((mesh) => mesh.dispose());
+ mergedMesh.dispose();
- return modelId; // Return the debug mesh for further use
- } else {
- console.warn("No valid meshes to create a hull.");
- return null;
- }
- });
+ return modelId; // Return the debug mesh for further use
+ } else {
+ console.warn('No valid meshes to create a hull.');
+ return null;
+ }
+ });
},
hullMeshFromBody(body) {
const bodyInfoGeom = flock.hk.getBodyGeometry(body);
const { positions, indices } = bodyInfoGeom;
- const hullMesh = new flock.BABYLON.Mesh("custom", flock.scene);
+ const hullMesh = new flock.BABYLON.Mesh('custom', flock.scene);
indices.reverse();
const vertexData = new flock.BABYLON.VertexData();
@@ -1390,7 +1193,7 @@ export const flockCSG = {
}
});
});
- }),
+ })
).then((meshes) => meshes.filter((mesh) => mesh !== null));
},
applyResultMeshProperties(
@@ -1398,10 +1201,7 @@ export const flockCSG = {
referenceMesh,
modelId,
blockId,
- {
- forceReferenceMaterial = false,
- flattenNonReferenceSubMaterials = false,
- } = {},
+ { forceReferenceMaterial = false, flattenNonReferenceSubMaterials = false } = {}
) {
// Copy transformation properties
referenceMesh.material.backFaceCulling = false;
@@ -1413,35 +1213,27 @@ export const flockCSG = {
resultMesh.metadata.blockKey = blockId;
// Apply physics
- flock.applyPhysics(
- resultMesh,
- new flock.BABYLON.PhysicsShapeMesh(resultMesh, flock.scene),
- );
+ flock.applyPhysics(resultMesh, new flock.BABYLON.PhysicsShapeMesh(resultMesh, flock.scene));
// Log and replace default materials
const isDefaultMaterial = (material) => {
return (
- material instanceof flock.BABYLON.StandardMaterial &&
- material.name === "default material"
+ material instanceof flock.BABYLON.StandardMaterial && material.name === 'default material'
);
};
const replaceMaterial = () => {
- return referenceMesh.material.clone("clonedMaterial");
+ return referenceMesh.material.clone('clonedMaterial');
};
if (forceReferenceMaterial) {
- resultMesh.material = referenceMesh.material.clone("csgResultMaterial");
+ resultMesh.material = referenceMesh.material.clone('csgResultMaterial');
resultMesh.material.backFaceCulling = false;
const textureName = String(
- resultMesh.material.diffuseTexture?.name ||
- resultMesh.material.albedoTexture?.name ||
- "",
+ resultMesh.material.diffuseTexture?.name || resultMesh.material.albedoTexture?.name || ''
).toLowerCase();
const hasRenderableTexture =
- textureName &&
- !textureName.endsWith("undefined") &&
- !textureName.includes("none.png");
+ textureName && !textureName.endsWith('undefined') && !textureName.includes('none.png');
if (!hasRenderableTexture && resultMesh.convertToFlatShadedMesh) {
try {
resultMesh.convertToFlatShadedMesh();
@@ -1456,21 +1248,19 @@ export const flockCSG = {
if (resultMesh.material) {
if (resultMesh.material instanceof flock.BABYLON.MultiMaterial) {
- resultMesh.material.subMaterials = resultMesh.material.subMaterials.map(
- (subMaterial) => {
- if (subMaterial && isDefaultMaterial(subMaterial)) {
- return replaceMaterial();
- }
- return subMaterial;
- },
- );
+ resultMesh.material.subMaterials = resultMesh.material.subMaterials.map((subMaterial) => {
+ if (subMaterial && isDefaultMaterial(subMaterial)) {
+ return replaceMaterial();
+ }
+ return subMaterial;
+ });
} else if (isDefaultMaterial(resultMesh.material)) {
resultMesh.material = replaceMaterial();
resultMesh.material.backFaceCulling = false;
}
} else {
// No material assigned by CSG - copy from reference mesh
- resultMesh.material = referenceMesh.material.clone("csgResultMaterial");
+ resultMesh.material = referenceMesh.material.clone('csgResultMaterial');
resultMesh.material.backFaceCulling = false;
}
@@ -1479,26 +1269,24 @@ export const flockCSG = {
resultMesh.material instanceof flock.BABYLON.MultiMaterial
) {
const baseName = referenceMesh.material?.name;
- resultMesh.material.subMaterials = resultMesh.material.subMaterials.map(
- (subMaterial) => {
- if (!subMaterial) return subMaterial;
- if (baseName && subMaterial.name === baseName) return subMaterial;
+ resultMesh.material.subMaterials = resultMesh.material.subMaterials.map((subMaterial) => {
+ if (!subMaterial) return subMaterial;
+ if (baseName && subMaterial.name === baseName) return subMaterial;
- if (typeof subMaterial.clone === "function") {
- subMaterial = subMaterial.clone(`${subMaterial.name}_csg`);
- }
+ if (typeof subMaterial.clone === 'function') {
+ subMaterial = subMaterial.clone(`${subMaterial.name}_csg`);
+ }
- if (subMaterial.diffuseColor) {
- subMaterial.emissiveColor = subMaterial.diffuseColor.scale(0.2);
- }
- subMaterial.disableLighting = false;
- if (subMaterial.specularColor) {
- subMaterial.specularColor = flock.BABYLON.Color3.Black();
- }
- subMaterial.backFaceCulling = false;
- return subMaterial;
- },
- );
+ if (subMaterial.diffuseColor) {
+ subMaterial.emissiveColor = subMaterial.diffuseColor.scale(0.2);
+ }
+ subMaterial.disableLighting = false;
+ if (subMaterial.specularColor) {
+ subMaterial.specularColor = flock.BABYLON.Color3.Black();
+ }
+ subMaterial.backFaceCulling = false;
+ return subMaterial;
+ });
}
},
};
diff --git a/api/effects.js b/api/effects.js
index 3bbd276ab..d528d1a64 100644
--- a/api/effects.js
+++ b/api/effects.js
@@ -10,30 +10,23 @@ export const flockEffects = {
if (flock.mainLight) {
flock.mainLight.intensity = intensity;
} else {
- console.warn(
- "Main light is not defined. Please ensure flock.mainLight exists.",
- );
+ console.warn('Main light is not defined. Please ensure flock.mainLight exists.');
}
},
lightColor(diffuse, groundColor) {
if (flock.mainLight) {
flock.mainLight.diffuse = flock.BABYLON.Color3.FromHexString(diffuse);
- flock.mainLight.groundColor =
- flock.BABYLON.Color3.FromHexString(groundColor);
+ flock.mainLight.groundColor = flock.BABYLON.Color3.FromHexString(groundColor);
} else {
- console.warn(
- "Main light is not defined. Please ensure flock.mainLight exists.",
- );
+ console.warn('Main light is not defined. Please ensure flock.mainLight exists.');
}
},
getMainLight() {
- return "__main_light__";
+ return '__main_light__';
},
enableShadows({ enabled = true } = {}) {
if (!flock.shadowLight) {
- console.warn(
- "Shadow light is not defined. Please ensure flock.shadowLight exists.",
- );
+ console.warn('Shadow light is not defined. Please ensure flock.shadowLight exists.');
return;
}
if (enabled) {
@@ -67,8 +60,7 @@ export const flockEffects = {
return new Promise((resolve) => {
flock.whenModelReady(meshName, (mesh) => {
const meshes = [mesh, ...mesh.getDescendants()].filter(
- (m) =>
- m instanceof flock.BABYLON.Mesh && m.getTotalVertices() > 0,
+ (m) => m instanceof flock.BABYLON.Mesh && m.getTotalVertices() > 0
);
// Marking a caster does NOT turn shadows on โ enableShadows is the
// master switch. Track the intent and apply it live if a generator
@@ -99,7 +91,7 @@ export const flockEffects = {
gravity,
direction,
rotation,
- } = {},
+ } = {}
) {
const resultName = flock._reserveName(name);
@@ -107,8 +99,8 @@ export const flockEffects = {
flock.whenModelReady(emitterMesh, (meshInstance) => {
if (!(meshInstance instanceof flock.BABYLON.AbstractMesh)) {
flock.reportBlockError({
- key: "particle_emitter_not_mesh",
- api: "createParticleEffect",
+ key: 'particle_emitter_not_mesh',
+ api: 'createParticleEffect',
values: { effect: resultName, emitter: emitterMesh },
});
resolve(null);
@@ -127,21 +119,12 @@ export const flockEffects = {
});
}
- const particleSystem = new flock.BABYLON.ParticleSystem(
- resultName,
- 500,
- flock.scene,
- );
+ const particleSystem = new flock.BABYLON.ParticleSystem(resultName, 500, flock.scene);
const texturePath = flock.texturePath + shape;
- particleSystem.particleTexture = new flock.BABYLON.Texture(
- texturePath,
- flock.scene,
- );
+ particleSystem.particleTexture = new flock.BABYLON.Texture(texturePath, flock.scene);
particleSystem.emitter = meshInstance;
- const meshEmitter = new flock.BABYLON.MeshParticleEmitter(
- meshInstance,
- );
+ const meshEmitter = new flock.BABYLON.MeshParticleEmitter(meshInstance);
particleSystem.particleEmitterType = meshEmitter;
particleSystem.blendMode = 4;
@@ -152,19 +135,19 @@ export const flockEffects = {
startColor.r,
startColor.g,
startColor.b,
- alphas.start,
+ alphas.start
);
particleSystem.color2 = new flock.BABYLON.Color4(
endColor.r,
endColor.g,
endColor.b,
- alphas.end,
+ alphas.end
);
particleSystem.colorDead = new flock.BABYLON.Color4(
endColor.r,
endColor.g,
endColor.b,
- 0,
+ 0
);
particleSystem.minSize = sizes.start;
@@ -180,38 +163,31 @@ export const flockEffects = {
? new flock.BABYLON.Vector3(0, -9.81, 0)
: flock.BABYLON.Vector3.Zero();
- if (
- direction &&
- (direction.x !== 0 || direction.y !== 0 || direction.z !== 0)
- ) {
+ if (direction && (direction.x !== 0 || direction.y !== 0 || direction.z !== 0)) {
particleSystem.minEmitPower = 1;
particleSystem.maxEmitPower = 3;
meshEmitter.useMeshNormalsForDirection = false;
meshEmitter.direction1 = new flock.BABYLON.Vector3(
direction.x,
direction.y,
- direction.z,
+ direction.z
);
meshEmitter.direction2 = new flock.BABYLON.Vector3(
direction.x,
direction.y,
- direction.z,
+ direction.z
);
}
if (rotation) {
const toRad = Math.PI / 180;
if (rotation.angularSpeed) {
- particleSystem.minAngularSpeed =
- rotation.angularSpeed.min * toRad;
- particleSystem.maxAngularSpeed =
- rotation.angularSpeed.max * toRad;
+ particleSystem.minAngularSpeed = rotation.angularSpeed.min * toRad;
+ particleSystem.maxAngularSpeed = rotation.angularSpeed.max * toRad;
}
if (rotation.initialRotation) {
- particleSystem.minInitialRotation =
- rotation.initialRotation.min * toRad;
- particleSystem.maxInitialRotation =
- rotation.initialRotation.max * toRad;
+ particleSystem.minInitialRotation = rotation.initialRotation.min * toRad;
+ particleSystem.maxInitialRotation = rotation.initialRotation.max * toRad;
}
}
@@ -219,8 +195,8 @@ export const flockEffects = {
resolve(particleSystem);
} catch (error) {
flock.reportBlockError({
- key: "particle_effect_failed",
- api: "createParticleEffect",
+ key: 'particle_effect_failed',
+ api: 'createParticleEffect',
values: { effect: resultName },
error,
});
@@ -233,9 +209,7 @@ export const flockEffects = {
return resultName;
},
startParticleSystem(systemName) {
- const particleSystem = flock.scene.particleSystems.find(
- (system) => system.name === systemName,
- );
+ const particleSystem = flock.scene.particleSystems.find((system) => system.name === systemName);
if (particleSystem) {
particleSystem.start();
} else {
@@ -243,9 +217,7 @@ export const flockEffects = {
}
},
stopParticleSystem(systemName) {
- const particleSystem = flock.scene.particleSystems.find(
- (system) => system.name === systemName,
- );
+ const particleSystem = flock.scene.particleSystems.find((system) => system.name === systemName);
if (particleSystem) {
particleSystem.stop();
@@ -254,37 +226,27 @@ export const flockEffects = {
}
},
resetParticleSystem(systemName) {
- const particleSystem = flock.scene.particleSystems.find(
- (system) => system.name === systemName,
- );
+ const particleSystem = flock.scene.particleSystems.find((system) => system.name === systemName);
if (particleSystem) {
particleSystem.reset();
} else {
console.warn(`Particle system '${systemName}' not found.`);
}
},
- setFog({
- fogColorHex,
- fogMode,
- fogDensity = 0.1,
- fogStart = 50,
- fogEnd = 100,
- } = {}) {
- const fogColorRgb = flock.BABYLON.Color3.FromHexString(
- flock.getColorFromString(fogColorHex),
- );
+ setFog({ fogColorHex, fogMode, fogDensity = 0.1, fogStart = 50, fogEnd = 100 } = {}) {
+ const fogColorRgb = flock.BABYLON.Color3.FromHexString(flock.getColorFromString(fogColorHex));
switch (fogMode) {
- case "NONE":
+ case 'NONE':
flock.scene.fogMode = flock.BABYLON.Scene.FOGMODE_NONE;
break;
- case "EXP":
+ case 'EXP':
flock.scene.fogMode = flock.BABYLON.Scene.FOGMODE_EXP;
break;
- case "EXP2":
+ case 'EXP2':
flock.scene.fogMode = flock.BABYLON.Scene.FOGMODE_EXP2;
break;
- case "LINEAR":
+ case 'LINEAR':
flock.scene.fogMode = flock.BABYLON.Scene.FOGMODE_LINEAR;
break;
}
diff --git a/api/events.js b/api/events.js
index 36d6f5e3b..eb373bee7 100644
--- a/api/events.js
+++ b/api/events.js
@@ -1,4 +1,4 @@
-import { getMicrobitManager } from "../microbit/manager.js";
+import { getMicrobitManager } from '../microbit/manager.js';
let flock;
@@ -12,15 +12,15 @@ export const flockEvents = {
*/
onEvent(eventName, handler, once = false) {
- if (typeof handler !== "function") {
- flock.reportBlockError({ key: "invalid_callback", api: "onEvent" });
+ if (typeof handler !== 'function') {
+ flock.reportBlockError({ key: 'invalid_callback', api: 'onEvent' });
return;
}
eventName = flock.sanitizeEventName(eventName);
if (!flock.isAllowedEventName(eventName)) {
flock.reportBlockError({
- key: "event_name_reserved",
- api: "onEvent",
+ key: 'event_name_reserved',
+ api: 'onEvent',
values: { event: eventName },
});
return;
@@ -43,19 +43,19 @@ export const flockEvents = {
}
signal?.addEventListener(
- "abort",
+ 'abort',
() => {
flock.events[eventName]?.remove(observer);
},
- { once: true },
+ { once: true }
);
},
broadcastEvent(eventName, data) {
eventName = flock.sanitizeEventName(eventName);
if (!flock.isAllowedEventName(eventName)) {
flock.reportBlockError({
- key: "event_name_reserved",
- api: "broadcastEvent",
+ key: 'event_name_reserved',
+ api: 'broadcastEvent',
values: { event: eventName },
});
return;
@@ -65,8 +65,8 @@ export const flockEvents = {
}
},
whenActionEvent(action, callback, isReleased = false) {
- if (typeof callback !== "function") {
- flock.reportBlockError({ key: "invalid_callback", api: "whenActionEvent" });
+ if (typeof callback !== 'function') {
+ flock.reportBlockError({ key: 'invalid_callback', api: 'whenActionEvent' });
return;
}
const signal = flock.abortController?.signal;
@@ -79,7 +79,7 @@ export const flockEvents = {
if (isReleased) {
const upObs = flock.inputManager.onActionUpObservable;
const observer = upObs.add(handler);
- signal?.addEventListener("abort", () => upObs.remove(observer), {
+ signal?.addEventListener('abort', () => upObs.remove(observer), {
once: true,
});
} else {
@@ -90,29 +90,31 @@ export const flockEvents = {
const downObserver = downObs.add(handler);
const repeatObserver = repeatObs.add(handler);
signal?.addEventListener(
- "abort",
+ 'abort',
() => {
downObs.remove(downObserver);
repeatObs.remove(repeatObserver);
},
- { once: true },
+ { once: true }
);
}
},
whenKeyEvent(key, callback, isReleased = false) {
- if (typeof callback !== "function") {
- flock.reportBlockError({ key: "invalid_callback", api: "whenKeyEvent" });
+ if (typeof callback !== 'function') {
+ flock.reportBlockError({ key: 'invalid_callback', api: 'whenKeyEvent' });
return;
}
const signal = flock.abortController?.signal;
if (signal?.aborted) return;
- const handler = (k) => { if (k === key) callback(); };
+ const handler = (k) => {
+ if (k === key) callback();
+ };
if (isReleased) {
const upObs = flock.inputManager.onKeyUpObservable;
const observer = upObs.add(handler);
- signal?.addEventListener("abort", () => upObs.remove(observer), {
+ signal?.addEventListener('abort', () => upObs.remove(observer), {
once: true,
});
} else {
@@ -123,18 +125,18 @@ export const flockEvents = {
const downObserver = downObs.add(handler);
const repeatObserver = repeatObs.add(handler);
signal?.addEventListener(
- "abort",
+ 'abort',
() => {
downObs.remove(downObserver);
repeatObs.remove(repeatObserver);
},
- { once: true },
+ { once: true }
);
}
},
onMicrobitEvent(variableName, eventChar, callback) {
- if (typeof callback !== "function") {
- flock.reportBlockError({ key: "invalid_callback", api: "onMicrobitEvent" });
+ if (typeof callback !== 'function') {
+ flock.reportBlockError({ key: 'invalid_callback', api: 'onMicrobitEvent' });
return;
}
const signal = flock.abortController?.signal;
@@ -145,7 +147,7 @@ export const flockEvents = {
const unsubscribe = getMicrobitManager().subscribe(variableName, (char) => {
if (char === eventChar) callback();
});
- signal?.addEventListener("abort", unsubscribe, { once: true });
+ signal?.addEventListener('abort', unsubscribe, { once: true });
},
start(action) {
flock.scene.onBeforeRenderObservable.addOnce(action);
@@ -157,7 +159,7 @@ export const flockEvents = {
// Function to run the action
const runAction = async () => {
if (isDisposed) {
- console.log("Scene is disposed. Exiting action.");
+ console.log('Scene is disposed. Exiting action.');
return; // Exit if the scene is disposed
}
@@ -174,8 +176,8 @@ export const flockEvents = {
await action();
} catch (error) {
flock.reportBlockError({
- key: "forever_block_failed",
- api: "forever",
+ key: 'forever_block_failed',
+ api: 'forever',
error,
});
} finally {
@@ -199,7 +201,7 @@ export const flockEvents = {
flock.scene.onDisposeObservable.addOnce(disposeHandler);
},
isAllowedEventName(eventName) {
- if (!eventName || typeof eventName !== "string") {
+ if (!eventName || typeof eventName !== 'string') {
return false;
}
@@ -208,14 +210,7 @@ export const flockEvents = {
}
const lower = eventName.toLowerCase();
- const reservedPrefixes = [
- "_",
- "on",
- "system",
- "internal",
- "babylon",
- "flock",
- ];
+ const reservedPrefixes = ['_', 'on', 'system', 'internal', 'babylon', 'flock'];
if (reservedPrefixes.some((prefix) => lower.startsWith(prefix))) {
return false;
}
@@ -228,15 +223,12 @@ export const flockEvents = {
return true;
},
sanitizeEventName(eventName) {
- if (typeof eventName !== "string") {
- return "";
+ if (typeof eventName !== 'string') {
+ return '';
}
// Remove disallowed characters (symbols, control chars), allow emoji, spaces, letters, numbers
// This allows everything except common punctuation and control characters
- const clean = eventName.replace(
- /[!@#$%^&*()+=[\]{};:'"\\|,<>?/\n\r\t]/g,
- "",
- );
+ const clean = eventName.replace(/[!@#$%^&*()+=[\]{};:'"\\|,<>?/\n\r\t]/g, '');
return clean.substring(0, 50);
},
};
diff --git a/api/material.js b/api/material.js
index 19c83a227..0807d2b84 100644
--- a/api/material.js
+++ b/api/material.js
@@ -57,8 +57,7 @@ export const flockMaterial = {
// Babylon's Color3.fromHexString only accepts #RRGGBB, so expand the
// #RGB shorthand (e.g. #f0c -> #ff00cc) rather than passing it through.
- const expandShortHex = (hex) =>
- hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex;
+ const expandShortHex = (hex) => (hex.length === 3 ? hex.replace(/(.)/g, '$1$1') : hex);
if (/^([0-9A-F]{3}|[0-9A-F]{6})$/i.test(colourString)) {
return `#${expandShortHex(colourString).toLowerCase()}`;
diff --git a/api/mesh.js b/api/mesh.js
index 6ffa5316e..2d33d98cb 100644
--- a/api/mesh.js
+++ b/api/mesh.js
@@ -654,8 +654,7 @@ export const flockMesh = {
// double-tile them). Returns true if the mesh was a recognised primitive.
retilePrimitiveUVs(mesh, { width, height, depth }, scale = null, texturePhysicalSize = 4) {
if (!mesh) return false;
- const shape =
- mesh.metadata?.shapeType || (mesh.metadata?.shape === 'plane' ? 'Plane' : null);
+ const shape = mesh.metadata?.shapeType || (mesh.metadata?.shape === 'plane' ? 'Plane' : null);
switch (shape) {
case 'Box':
flock.setSizeBasedBoxUVs(mesh, width, height, depth, texturePhysicalSize, scale);
diff --git a/api/microbit.js b/api/microbit.js
index a7ab86145..aa7462f4a 100644
--- a/api/microbit.js
+++ b/api/microbit.js
@@ -1,4 +1,4 @@
-import { getMicrobitManager } from "../microbit/manager.js";
+import { getMicrobitManager } from '../microbit/manager.js';
// Kept for consistency with the other api modules; addMicrobit itself only
// needs the manager.
@@ -20,19 +20,18 @@ export const flockMicrobit = {
* tethers.
*/
addMicrobit(variableName, channel = 1) {
- if (typeof variableName !== "string" || variableName === "") {
- console.warn("addMicrobit: variableName must be a non-empty string");
+ if (typeof variableName !== 'string' || variableName === '') {
+ console.warn('addMicrobit: variableName must be a non-empty string');
return;
}
- const channelValue =
- typeof channel === "string" ? Number(channel) : channel;
+ const channelValue = typeof channel === 'string' ? Number(channel) : channel;
if (
- typeof channelValue !== "number" ||
+ typeof channelValue !== 'number' ||
!Number.isSafeInteger(channelValue) ||
channelValue < 0 ||
channelValue > 255
) {
- console.warn("addMicrobit: channel must be an integer between 0 and 255");
+ console.warn('addMicrobit: channel must be an integer between 0 and 255');
return;
}
getMicrobitManager().setVariableChannel(variableName, channelValue, {
@@ -47,24 +46,24 @@ export const flockMicrobit = {
* are a silent no-op โ images can only be sent over USB.
*/
microbitShowImage(deviceName, pattern) {
- if (typeof deviceName !== "string") {
- console.warn("microbitShowImage: deviceName must be a string");
+ if (typeof deviceName !== 'string') {
+ console.warn('microbitShowImage: deviceName must be a string');
return;
}
- if (typeof pattern !== "string") {
- console.warn("microbitShowImage: pattern must be a string");
+ if (typeof pattern !== 'string') {
+ console.warn('microbitShowImage: pattern must be a string');
return;
}
const manager = getMicrobitManager();
- if (deviceName !== "" && !manager.isMicrobitVariable(deviceName)) {
+ if (deviceName !== '' && !manager.isMicrobitVariable(deviceName)) {
_flock?.reportBlockError?.({
- key: "not_a_microbit",
- api: "microbitShowImage",
+ key: 'not_a_microbit',
+ api: 'microbitShowImage',
values: { object: deviceName },
});
return;
}
- manager.showImage(deviceName === "" ? null : deviceName, pattern);
+ manager.showImage(deviceName === '' ? null : deviceName, pattern);
},
/**
@@ -75,23 +74,23 @@ export const flockMicrobit = {
* are a silent no-op โ text can only be sent over USB.
*/
microbitScrollText(deviceName, text) {
- if (typeof deviceName !== "string") {
- console.warn("microbitScrollText: deviceName must be a string");
+ if (typeof deviceName !== 'string') {
+ console.warn('microbitScrollText: deviceName must be a string');
return;
}
- if (typeof text !== "string" && typeof text !== "number") {
- console.warn("microbitScrollText: text must be a string");
+ if (typeof text !== 'string' && typeof text !== 'number') {
+ console.warn('microbitScrollText: text must be a string');
return;
}
const manager = getMicrobitManager();
- if (deviceName !== "" && !manager.isMicrobitVariable(deviceName)) {
+ if (deviceName !== '' && !manager.isMicrobitVariable(deviceName)) {
_flock?.reportBlockError?.({
- key: "not_a_microbit",
- api: "microbitScrollText",
+ key: 'not_a_microbit',
+ api: 'microbitScrollText',
values: { object: deviceName },
});
return;
}
- manager.scrollText(deviceName === "" ? null : deviceName, String(text));
+ manager.scrollText(deviceName === '' ? null : deviceName, String(text));
},
};
diff --git a/api/models.js b/api/models.js
index 5f939f78a..0d88c77a4 100644
--- a/api/models.js
+++ b/api/models.js
@@ -11,12 +11,12 @@ export const flockModels = {
scale = 1,
position = { x: 0, y: 0, z: 0 },
colors = {
- hair: "#000000",
- skin: "#a15c33",
- eyes: "#0000ff",
- sleeves: "#ff0000",
- shorts: "#00ff00",
- tshirt: "#0000ff",
+ hair: '#000000',
+ skin: '#a15c33',
+ eyes: '#0000ff',
+ sleeves: '#ff0000',
+ shorts: '#00ff00',
+ tshirt: '#0000ff',
},
callback = () => {},
}) {
@@ -29,42 +29,42 @@ export const flockModels = {
container.skeletons = [];
container.animationGroups = [];
} catch (_) {
- console.warn("Suppressed non-critical error:", _);
+ console.warn('Suppressed non-critical error:', _);
}
};
// --- bail if there is no live scene (e.g. called during dispose) ---
if (!flock.scene || flock.scene.isDisposed) {
- console.warn("createCharacter: no active scene");
- return "error_no_scene";
+ console.warn('createCharacter: no active scene');
+ return 'error_no_scene';
}
// --- validate ---
- if (!modelName || typeof modelName !== "string" || modelName.length > 100) {
- console.warn("createCharacter: invalid modelName");
- return "error_" + flock.scene.getUniqueId();
+ if (!modelName || typeof modelName !== 'string' || modelName.length > 100) {
+ console.warn('createCharacter: invalid modelName');
+ return 'error_' + flock.scene.getUniqueId();
}
- if (!modelId || typeof modelId !== "string" || modelId.length > 100) {
- console.warn("createCharacter: invalid modelId");
- return "error_" + flock.scene.getUniqueId();
+ if (!modelId || typeof modelId !== 'string' || modelId.length > 100) {
+ console.warn('createCharacter: invalid modelId');
+ return 'error_' + flock.scene.getUniqueId();
}
// --- parse BEFORE sanitizing so blockKey remains RAW ---
let desiredBase = modelId; // e.g. "player__j9#WY5..."
let blockKey = null;
- if (desiredBase.includes("__")) {
- [desiredBase, blockKey] = desiredBase.split("__");
+ if (desiredBase.includes('__')) {
+ [desiredBase, blockKey] = desiredBase.split('__');
}
// If no "__" was provided, fall back to base as the key (keeps prior behavior safe)
if (!blockKey) blockKey = desiredBase;
// --- sanitize ONLY modelName + BASE (NOT the blockKey) ---
- modelName = modelName.replace(/[^a-zA-Z0-9._-]/g, "");
+ modelName = modelName.replace(/[^a-zA-Z0-9._-]/g, '');
// Capture the original base name before sanitization so we can register it as
// an alias in modelReadyPromises. This lets whenModelReady("dimnnd monkey", ...)
// resolve correctly even though the actual mesh name is "dimnndmonkey".
const originalBase = desiredBase;
- desiredBase = desiredBase.replace(/[^a-zA-Z0-9._-]/g, "");
+ desiredBase = desiredBase.replace(/[^a-zA-Z0-9._-]/g, '');
// The sanitized base, captured before _reserveName may append a collision
// suffix. The bare-base alias must only bridge a genuine sanitization
// difference (originalBase !== sanitizedBase); it must NOT be registered for
@@ -72,7 +72,7 @@ export const flockModels = {
// would otherwise alias the bare model name onto the wrong instance.
const sanitizedBase = desiredBase;
- if (flock.maxMeshesReached()) return "error_" + flock.scene.getUniqueId();
+ if (flock.maxMeshesReached()) return 'error_' + flock.scene.getUniqueId();
flock._recycleOldestByKey(modelName);
@@ -82,23 +82,17 @@ export const flockModels = {
const groupName = desiredBase; // group by base for onTrigger applyToGroup
// position/scale clamps
- const x = Number.isFinite(position?.x)
- ? Math.max(-1000, Math.min(1000, position.x))
- : 0;
+ const x = Number.isFinite(position?.x) ? Math.max(-1000, Math.min(1000, position.x)) : 0;
const groundLevelSentinel = -999999;
- const numericY =
- typeof position?.y === "string" ? Number(position.y) : position?.y;
+ const numericY = typeof position?.y === 'string' ? Number(position.y) : position?.y;
const y =
- position?.y === "__ground__level__" || numericY === groundLevelSentinel
+ position?.y === '__ground__level__' || numericY === groundLevelSentinel
? position.y
: Number.isFinite(position?.y)
? Math.max(-1000, Math.min(1000, position.y))
: 0;
- const z = Number.isFinite(position?.z)
- ? Math.max(-1000, Math.min(1000, position.z))
- : 0;
- if (!(typeof scale === "number" && scale >= 0.01 && scale <= 100))
- scale = 1;
+ const z = Number.isFinite(position?.z) ? Math.max(-1000, Math.min(1000, position.z)) : 0;
+ if (!(typeof scale === 'number' && scale >= 0.01 && scale <= 100)) scale = 1;
// --- create readiness deferred (resolve OR reject) + abort hookup ---
let resolveReady, rejectReady;
@@ -125,9 +119,9 @@ export const flockModels = {
const signal = flock.abortController?.signal;
const onAbort = () => {
try {
- rejectReady(new Error("aborted"));
+ rejectReady(new Error('aborted'));
} catch (error) {
- console.warn("Suppressed non-critical error:", error);
+ console.warn('Suppressed non-critical error:', error);
}
flock.modelReadyPromises.delete(meshName);
if (
@@ -136,10 +130,10 @@ export const flockModels = {
)
flock.modelReadyPromises.delete(originalBase);
flock._releaseName?.(meshName);
- signal?.removeEventListener("abort", onAbort);
+ signal?.removeEventListener('abort', onAbort);
};
- signal?.addEventListener("abort", onAbort, { once: true });
- const cleanupAbort = () => signal?.removeEventListener("abort", onAbort);
+ signal?.addEventListener('abort', onAbort, { once: true });
+ const cleanupAbort = () => signal?.removeEventListener('abort', onAbort);
// --- single load path ---
flock.BABYLON.SceneLoader.LoadAssetContainerAsync(
@@ -148,7 +142,7 @@ export const flockModels = {
flock.scene,
null,
null,
- { signal: flock.abortController?.signal },
+ { signal: flock.abortController?.signal }
)
.then((container) => {
// The scene was disposed / the load was aborted while this
@@ -161,10 +155,10 @@ export const flockModels = {
try {
container?.dispose?.();
} catch (_) {
- console.warn("Suppressed non-critical error:", _);
+ console.warn('Suppressed non-critical error:', _);
}
if (!signal?.aborted) {
- rejectReady(new Error("scene disposed"));
+ rejectReady(new Error('scene disposed'));
flock.modelReadyPromises.delete(meshName);
if (
originalBase !== sanitizedBase &&
@@ -181,16 +175,7 @@ export const flockModels = {
container.addAllToScene();
const mesh = container.meshes[0];
- const bb = flock.setupMesh(
- mesh,
- modelName,
- meshName,
- blockKey,
- scale,
- x,
- y,
- z,
- );
+ const bb = flock.setupMesh(mesh, modelName, meshName, blockKey, scale, x, y, z);
// materials & colors
flock.ensureStandardMaterial(mesh);
@@ -209,8 +194,8 @@ export const flockModels = {
mesh.setEnabled(false);
// Preload animations (non-blocking for readiness)
- const animationPromises = ["Walk", "Jump", "Idle"].map((name) =>
- flock.switchToAnimation(flock.scene, bb, name, false, false, false),
+ const animationPromises = ['Walk', 'Jump', 'Idle'].map((name) =>
+ flock.switchToAnimation(flock.scene, bb, name, false, false, false)
);
// After anims, run optional callback (await if it returns a promise), then show
@@ -219,9 +204,9 @@ export const flockModels = {
if (callback) {
try {
const result = callback();
- if (result && typeof result.then === "function") await result;
+ if (result && typeof result.then === 'function') await result;
} catch (err) {
- console.error("Callback error:", err);
+ console.error('Callback error:', err);
}
}
})
@@ -256,7 +241,7 @@ export const flockModels = {
releaseContainer(container);
})
.catch((error) => {
- console.log("โ Error loading character:", error);
+ console.log('โ Error loading character:', error);
rejectReady(error);
flock._releaseName(meshName);
flock.modelReadyPromises.delete(meshName);
@@ -299,7 +284,7 @@ export const flockModels = {
container.skeletons = [];
container.animationGroups = [];
} catch (_) {
- console.warn("Suppressed non-critical error:", _);
+ console.warn('Suppressed non-critical error:', _);
}
};
@@ -316,10 +301,10 @@ export const flockModels = {
m.metadata.originalNodeName = m.metadata.originalNodeName || m.name;
m.metadata.isTemplate = true;
m.metadata.templateTag = tag;
- if ("isPickable" in m) m.isPickable = false;
- if (typeof m.setEnabled === "function") m.setEnabled(false);
- if ("isVisible" in m) m.isVisible = false;
- if ("visibility" in m) m.visibility = 0;
+ if ('isPickable' in m) m.isPickable = false;
+ if (typeof m.setEnabled === 'function') m.setEnabled(false);
+ if ('isVisible' in m) m.isVisible = false;
+ if ('visibility' in m) m.visibility = 0;
});
};
@@ -329,10 +314,10 @@ export const flockModels = {
if (m.metadata?.isTemplate) {
m.metadata = { ...m.metadata, isTemplate: false };
}
- if ("isPickable" in m) m.isPickable = true;
- if (typeof m.setEnabled === "function") m.setEnabled(true);
- if ("isVisible" in m) m.isVisible = true;
- if ("visibility" in m) m.visibility = 1;
+ if ('isPickable' in m) m.isPickable = true;
+ if (typeof m.setEnabled === 'function') m.setEnabled(true);
+ if ('isVisible' in m) m.isVisible = true;
+ if ('visibility' in m) m.visibility = 1;
});
};
@@ -353,7 +338,7 @@ export const flockModels = {
position.x,
position.y,
position.z,
- color,
+ color
);
applyMaterialToHierarchy(mesh, color);
mesh.computeWorldMatrix(true);
@@ -366,22 +351,20 @@ export const flockModels = {
try {
if (!flock.scene || flock.scene.isDisposed) {
- console.warn("createObject: no active scene");
- return "error_no_scene";
+ console.warn('createObject: no active scene');
+ return 'error_no_scene';
}
- if (flock.maxMeshesReached()) return "error_" + flock.scene.getUniqueId();
+ if (flock.maxMeshesReached()) return 'error_' + flock.scene.getUniqueId();
- let [desiredBase, bKey] = modelId.includes("__")
- ? modelId.split("__")
- : [modelId, modelId];
- modelName = modelName.replace(/[^a-zA-Z0-9._-]/g, "");
- desiredBase = desiredBase.replace(/[^a-zA-Z0-9._-]/g, "");
+ let [desiredBase, bKey] = modelId.includes('__') ? modelId.split('__') : [modelId, modelId];
+ modelName = modelName.replace(/[^a-zA-Z0-9._-]/g, '');
+ desiredBase = desiredBase.replace(/[^a-zA-Z0-9._-]/g, '');
const meshName = flock._reserveName(desiredBase);
const groupName = desiredBase;
if (applyColor && !color) {
- color = flock.objectColours?.[modelName] || ["#FFFFFF", "#FFFFFF"];
+ color = flock.objectColours?.[modelName] || ['#FFFFFF', '#FFFFFF'];
}
let resolveReady;
@@ -392,9 +375,7 @@ export const flockModels = {
if (flock.modelCache[modelName]) {
flock._recycleOldestByKey(modelName);
- const mesh = flock.modelCache[modelName].clone(
- flock.modelCache[modelName].name,
- );
+ const mesh = flock.modelCache[modelName].clone(flock.modelCache[modelName].name);
flock._registerInstance(modelName, meshName);
finalizeMesh(mesh, meshName, groupName, bKey);
resolveReady(mesh);
@@ -405,9 +386,7 @@ export const flockModels = {
flock.modelsBeingLoaded[modelName].then(() => {
if (!flock.scene || flock.scene.isDisposed) return;
flock._recycleOldestByKey(modelName);
- const mesh = flock.modelCache[modelName].clone(
- flock.modelCache[modelName].name,
- );
+ const mesh = flock.modelCache[modelName].clone(flock.modelCache[modelName].name);
flock._registerInstance(modelName, meshName);
finalizeMesh(mesh, meshName, groupName, bKey);
resolveReady(mesh);
@@ -418,7 +397,7 @@ export const flockModels = {
const loadPromise = flock.BABYLON.SceneLoader.LoadAssetContainerAsync(
flock.modelPath,
modelName,
- flock.scene,
+ flock.scene
);
flock.modelsBeingLoaded[modelName] = loadPromise;
@@ -430,7 +409,7 @@ export const flockModels = {
try {
container?.dispose?.();
} catch (_) {
- console.warn("Suppressed non-critical error:", _);
+ console.warn('Suppressed non-critical error:', _);
}
delete flock.modelsBeingLoaded[modelName];
return;
@@ -475,17 +454,11 @@ export const flockModels = {
return meshName;
} catch (error) {
- console.warn("createObject failed; returning error id:", error);
- return "error_" + flock.scene.getUniqueId();
+ console.warn('createObject failed; returning error id:', error);
+ return 'error_' + flock.scene.getUniqueId();
}
},
- createModel({
- modelName,
- modelId,
- scale = 1,
- position = { x: 0, y: 0, z: 0 },
- callback = null,
- }) {
+ createModel({ modelName, modelId, scale = 1, position = { x: 0, y: 0, z: 0 }, callback = null }) {
return flock.createObject({
modelName,
modelId,
@@ -507,7 +480,7 @@ export const flockModels = {
container.skeletons = [];
container.animationGroups = [];
} catch (error) {
- console.warn("releaseContainer cleanup failed:", error);
+ console.warn('releaseContainer cleanup failed:', error);
}
},
};
diff --git a/api/movement.js b/api/movement.js
index 32eacda64..be8d3ab21 100644
--- a/api/movement.js
+++ b/api/movement.js
@@ -40,9 +40,7 @@ function sceneGravityMagnitude() {
}
function nowMs() {
- return typeof performance !== 'undefined' && performance.now
- ? performance.now()
- : Date.now();
+ return typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now();
}
export const flockMovement = {
diff --git a/api/physics.js b/api/physics.js
index cd5853a00..bf773c605 100644
--- a/api/physics.js
+++ b/api/physics.js
@@ -793,8 +793,8 @@ export const flockPhysics = {
currentIndex = (currentIndex + 1) % callbacks.length;
} catch (e) {
flock.reportBlockError({
- key: "trigger_block_failed",
- api: "onTrigger",
+ key: 'trigger_block_failed',
+ api: 'onTrigger',
error: e,
});
} finally {
diff --git a/api/sensing.js b/api/sensing.js
index 2db6b410d..09729f868 100644
--- a/api/sensing.js
+++ b/api/sensing.js
@@ -11,9 +11,9 @@ export const flockSensing = {
getProperty(modelName, propertyName) {
const mesh =
- modelName === "__active_camera__"
+ modelName === '__active_camera__'
? flock.scene.activeCamera
- : modelName === "__main_light__"
+ : modelName === '__main_light__'
? flock.mainLight
: flock.scene.getMeshByName(modelName);
@@ -24,32 +24,28 @@ export const flockSensing = {
// Use a consistent world position call (works for meshes, lights, and cameras).
const absolutePosition =
- typeof mesh.getAbsolutePosition === "function"
+ typeof mesh.getAbsolutePosition === 'function'
? mesh.getAbsolutePosition()
: (mesh.globalPosition ?? mesh.position);
const usesAbsolutePosition =
- modelName === "__active_camera__" || modelName === "__main_light__";
+ modelName === '__active_camera__' || modelName === '__main_light__';
const hasNumericXYZ = (value) =>
- value &&
- Number.isFinite(value.x) &&
- Number.isFinite(value.y) &&
- Number.isFinite(value.z);
+ value && Number.isFinite(value.x) && Number.isFinite(value.y) && Number.isFinite(value.z);
const position = (() => {
if (usesAbsolutePosition) {
return absolutePosition;
}
- const anchor =
- typeof flock._getAnchor === "function" ? flock._getAnchor(mesh) : null;
+ const anchor = typeof flock._getAnchor === 'function' ? flock._getAnchor(mesh) : null;
if (hasNumericXYZ(anchor)) {
return anchor;
}
const blockPosition =
- typeof flock.getBlockPositionFromMesh === "function"
+ typeof flock.getBlockPositionFromMesh === 'function'
? flock.getBlockPositionFromMesh(mesh)
: null;
if (hasNumericXYZ(blockPosition)) {
@@ -72,96 +68,81 @@ export const flockSensing = {
let allMeshes, materialNode, materialNodes;
switch (propertyName) {
- case "POSITION_X":
+ case 'POSITION_X':
propertyValue = position ? parseFloat(position.x.toFixed(2)) : null;
break;
- case "POSITION_Y":
+ case 'POSITION_Y':
propertyValue = position ? parseFloat(position.y.toFixed(2)) : null;
break;
- case "POSITION_Z":
+ case 'POSITION_Z':
propertyValue = position ? parseFloat(position.z.toFixed(2)) : null;
break;
- case "ROTATION_X":
- propertyValue = parseFloat(
- flock.BABYLON.Tools.ToDegrees(rotEuler.x).toFixed(2),
- );
+ case 'ROTATION_X':
+ propertyValue = parseFloat(flock.BABYLON.Tools.ToDegrees(rotEuler.x).toFixed(2));
break;
- case "ROTATION_Y":
- propertyValue = parseFloat(
- flock.BABYLON.Tools.ToDegrees(rotEuler.y).toFixed(2),
- );
+ case 'ROTATION_Y':
+ propertyValue = parseFloat(flock.BABYLON.Tools.ToDegrees(rotEuler.y).toFixed(2));
break;
- case "ROTATION_Z":
- propertyValue = parseFloat(
- flock.BABYLON.Tools.ToDegrees(rotEuler.z).toFixed(2),
- );
+ case 'ROTATION_Z':
+ propertyValue = parseFloat(flock.BABYLON.Tools.ToDegrees(rotEuler.z).toFixed(2));
break;
- case "SCALE_X":
+ case 'SCALE_X':
propertyValue = parseFloat(mesh.scaling.x.toFixed(2));
break;
- case "SCALE_Y":
+ case 'SCALE_Y':
propertyValue = parseFloat(mesh.scaling.y.toFixed(2));
break;
- case "SCALE_Z":
+ case 'SCALE_Z':
propertyValue = parseFloat(mesh.scaling.z.toFixed(2));
break;
- case "SIZE_X": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'SIZE_X': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = parseFloat(
- (
- bi.boundingBox.maximumWorld.x - bi.boundingBox.minimumWorld.x
- ).toFixed(2),
+ (bi.boundingBox.maximumWorld.x - bi.boundingBox.minimumWorld.x).toFixed(2)
);
break;
}
- case "SIZE_Y": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'SIZE_Y': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = parseFloat(
- (
- bi.boundingBox.maximumWorld.y - bi.boundingBox.minimumWorld.y
- ).toFixed(2),
+ (bi.boundingBox.maximumWorld.y - bi.boundingBox.minimumWorld.y).toFixed(2)
);
break;
}
- case "SIZE_Z": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'SIZE_Z': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = parseFloat(
- (
- bi.boundingBox.maximumWorld.z - bi.boundingBox.minimumWorld.z
- ).toFixed(2),
+ (bi.boundingBox.maximumWorld.z - bi.boundingBox.minimumWorld.z).toFixed(2)
);
break;
}
// Speed per world axis, and overall speed. 0 with no live physics body.
- case "SPEED_X":
- case "SPEED_Y":
- case "SPEED_Z":
- case "SPEED": {
+ case 'SPEED_X':
+ case 'SPEED_Y':
+ case 'SPEED_Z':
+ case 'SPEED': {
const body = mesh.physics;
- const v =
- body && body._pluginData?.hpBodyId
- ? body.getLinearVelocity()
- : null;
+ const v = body && body._pluginData?.hpBodyId ? body.getLinearVelocity() : null;
if (!v) {
propertyValue = 0;
- } else if (propertyName === "SPEED_X") {
+ } else if (propertyName === 'SPEED_X') {
propertyValue = parseFloat(v.x.toFixed(2));
- } else if (propertyName === "SPEED_Y") {
+ } else if (propertyName === 'SPEED_Y') {
propertyValue = parseFloat(v.y.toFixed(2));
- } else if (propertyName === "SPEED_Z") {
+ } else if (propertyName === 'SPEED_Z') {
propertyValue = parseFloat(v.z.toFixed(2));
} else {
propertyValue = parseFloat(v.length().toFixed(2));
@@ -171,48 +152,48 @@ export const flockSensing = {
// MIN/MAX: return consistent world AABB extents.
// (Origin-specific adjustments previously mixed local/world spaces and manual scaling.)
- case "MIN_X": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MIN_X': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = bi.boundingBox.minimumWorld.x;
break;
}
- case "MAX_X": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MAX_X': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = bi.boundingBox.maximumWorld.x;
break;
}
- case "MIN_Y": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MIN_Y': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = bi.boundingBox.minimumWorld.y;
break;
}
- case "MAX_Y": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MAX_Y': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = bi.boundingBox.maximumWorld.y;
break;
}
- case "MIN_Z": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MIN_Z': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
propertyValue = bi.boundingBox.minimumWorld.z;
break;
}
- case "MAX_Z": {
- if (typeof mesh.refreshBoundingInfo === "function") {
+ case 'MAX_Z': {
+ if (typeof mesh.refreshBoundingInfo === 'function') {
mesh.refreshBoundingInfo(true);
}
const bi = mesh.getBoundingInfo();
@@ -220,11 +201,11 @@ export const flockSensing = {
break;
}
- case "VISIBLE":
+ case 'VISIBLE':
propertyValue = mesh.isEnabled() && mesh.isVisible;
break;
- case "ALPHA":
+ case 'ALPHA':
allMeshes = [mesh].concat(mesh.getDescendants?.() ?? []);
materialNode = allMeshes.find((node) => node.material);
@@ -233,7 +214,7 @@ export const flockSensing = {
}
break;
- case "COLOUR":
+ case 'COLOUR':
allMeshes = [mesh].concat(mesh.getDescendants?.() ?? []);
materialNodes = allMeshes.filter((node) => node.material);
@@ -251,17 +232,14 @@ export const flockSensing = {
if (colors.length === 1) {
propertyValue = colors[0];
} else if (colors.length > 1) {
- propertyValue = colors.join(", ");
+ propertyValue = colors.join(', ');
}
break;
- case "DESCRIPTION": {
+ case 'DESCRIPTION': {
const root = mesh.metadata?.boundingBox ?? mesh;
propertyValue = root.metadata?.displayName ?? null;
- if (
- propertyValue === null &&
- typeof root.getChildMeshes === "function"
- ) {
+ if (propertyValue === null && typeof root.getChildMeshes === 'function') {
for (const child of root.getChildMeshes(false)) {
if (child.metadata?.displayName != null) {
propertyValue = child.metadata.displayName;
@@ -273,14 +251,14 @@ export const flockSensing = {
}
default:
- console.log("Property not recognized.");
+ console.log('Property not recognized.');
}
return propertyValue;
},
keyPressed(key) {
- if (key === "ANY") return flock.inputManager.heldKeyCount() > 0;
- if (key === "NONE") return flock.inputManager.heldKeyCount() === 0;
+ if (key === 'ANY') return flock.inputManager.heldKeyCount() > 0;
+ if (key === 'NONE') return flock.inputManager.heldKeyCount() === 0;
return flock.inputManager.isKeyDown(key);
},
setActionKey(action, key) {
@@ -297,11 +275,11 @@ export const flockSensing = {
},
convertTime(ms, unit) {
switch (unit) {
- case "milliseconds":
+ case 'milliseconds':
return ms;
- case "minutes":
+ case 'minutes':
return Math.floor(ms / 60000);
- case "seconds":
+ case 'seconds':
default:
return Math.floor(ms / 1000);
}
diff --git a/api/sound.js b/api/sound.js
index bedb66940..4fa4b619d 100644
--- a/api/sound.js
+++ b/api/sound.js
@@ -23,14 +23,18 @@ const soundBufferCache = new Map(); // soundUrl โ Promise
// ฮฑ = 0.4 gives a ~33 ms time constant at 60 fps, enough to damp
// rapid panning swings without perceptible spatial lag.
const LISTENER_SMOOTH = 0.4;
-const PANNER_SMOOTH = 0.4;
+const PANNER_SMOOTH = 0.4;
// Smoothed listener state โ shared across all sounds in the same context.
// Stored at module level so every updateListenerPositionAndOrientation call
// continues from the same running average rather than resetting each time.
let _listenerCtx = null;
-let _slx = 0, _sly = 0, _slz = 0; // smoothed listener position
-let _sfx = 0, _sfy = 0, _sfz = 0; // smoothed listener forward
+let _slx = 0,
+ _sly = 0,
+ _slz = 0; // smoothed listener position
+let _sfx = 0,
+ _sfy = 0,
+ _sfz = 0; // smoothed listener forward
const _gestureWaits = new WeakMap();
@@ -47,7 +51,7 @@ async function loadAudioBuffer(url, context) {
.catch((err) => {
soundBufferCache.delete(url);
throw err;
- }),
+ })
);
}
return soundBufferCache.get(url);
@@ -68,8 +72,16 @@ function playBufferEverywhere(context, buffer, soundName, { loop, volume, playba
const finish = () => {
if (done) return;
done = true;
- try { source.stop(); } catch { /* already stopped */ }
- try { gainNode.disconnect(); } catch { /* already disconnected */ }
+ try {
+ source.stop();
+ } catch {
+ /* already stopped */
+ }
+ try {
+ gainNode.disconnect();
+ } catch {
+ /* already disconnected */
+ }
const idx = flock.globalSounds.indexOf(soundRef);
if (idx !== -1) flock.globalSounds.splice(idx, 1);
};
@@ -86,7 +98,10 @@ function playBufferEverywhere(context, buffer, soundName, { loop, volume, playba
if (!loop) {
return new Promise((resolve) => {
- source.onended = () => { finish(); resolve(); };
+ source.onended = () => {
+ finish();
+ resolve();
+ };
});
}
return soundRef;
@@ -97,7 +112,11 @@ function playBufferOnMesh(context, mesh, buffer, soundName, { loop, volume, play
const currentSound = mesh.metadata.currentSound;
if (currentSound) {
- try { currentSound.stop(); } catch { /* already stopped */ }
+ try {
+ currentSound.stop();
+ } catch {
+ /* already stopped */
+ }
}
const panner = context.createPanner();
@@ -119,11 +138,19 @@ function playBufferOnMesh(context, mesh, buffer, soundName, { loop, volume, play
source.connect(gainNode);
// Smoothed panner position โ initialised to the mesh's starting position.
- let sx = mesh.position.x, sy = mesh.position.y, sz = mesh.position.z;
+ let sx = mesh.position.x,
+ sy = mesh.position.y,
+ sz = mesh.position.z;
const updatePosition = () => {
- if (!flock.scene || context.state === 'closed') { finish(); return; }
- if (mesh.isDisposed?.()) { finish(); return; }
+ if (!flock.scene || context.state === 'closed') {
+ finish();
+ return;
+ }
+ if (mesh.isDisposed?.()) {
+ finish();
+ return;
+ }
const { x, y, z } = mesh.position;
sx += PANNER_SMOOTH * (x - sx);
sy += PANNER_SMOOTH * (y - sy);
@@ -143,10 +170,26 @@ function playBufferOnMesh(context, mesh, buffer, soundName, { loop, volume, play
if (done) return;
done = true;
flock.scene?.onBeforeRenderObservable?.remove(observer);
- try { source.stop(); } catch { /* already stopped */ }
- try { source.disconnect(); } catch { /* already disconnected */ }
- try { gainNode.disconnect(); } catch { /* already disconnected */ }
- try { panner.disconnect(); } catch { /* already disconnected */ }
+ try {
+ source.stop();
+ } catch {
+ /* already stopped */
+ }
+ try {
+ source.disconnect();
+ } catch {
+ /* already disconnected */
+ }
+ try {
+ gainNode.disconnect();
+ } catch {
+ /* already disconnected */
+ }
+ try {
+ panner.disconnect();
+ } catch {
+ /* already disconnected */
+ }
if (mesh.metadata?.currentSound === soundRef) delete mesh.metadata.currentSound;
const idx = flock.globalSounds.indexOf(soundRef);
if (idx !== -1) flock.globalSounds.splice(idx, 1);
@@ -166,7 +209,10 @@ function playBufferOnMesh(context, mesh, buffer, soundName, { loop, volume, play
if (!loop) {
return new Promise((resolve) => {
- source.onended = () => { finish(); resolve(); };
+ source.onended = () => {
+ finish();
+ resolve();
+ };
});
}
return soundRef;
@@ -222,13 +268,19 @@ async function safeResume(context) {
};
const handler = () => {
cleanup();
- context.resume().catch(() => {}).finally(resolve);
+ context
+ .resume()
+ .catch(() => {})
+ .finally(resolve);
};
document.addEventListener('pointerdown', handler);
document.addEventListener('touchstart', handler, { passive: true });
// Abandon after 10 s โ prevents a permanent listener leak when programmatic
// audio fires but the user never gestures (e.g. background tab, navigation).
- timer = setTimeout(() => { cleanup(); resolve(); }, 10000);
+ timer = setTimeout(() => {
+ cleanup();
+ resolve();
+ }, 10000);
});
_gestureWaits.set(context, wait);
}
@@ -283,17 +335,12 @@ function getNoteBus(context) {
}
export const flockSound = {
- async playSound(
- meshName,
- { soundName, loop = false, volume = 1, playbackRate = 1 } = {},
- ) {
+ async playSound(meshName, { soundName, loop = false, volume = 1, playbackRate = 1 } = {}) {
volume = Number.isFinite(Number(volume)) ? Math.max(0, Math.min(1, Number(volume))) : 1;
playbackRate =
- Number.isFinite(Number(playbackRate)) && Number(playbackRate) > 0
- ? Number(playbackRate)
- : 1;
- if (!soundName || typeof soundName !== "string") {
- console.warn("playSound: invalid soundName");
+ Number.isFinite(Number(playbackRate)) && Number(playbackRate) > 0 ? Number(playbackRate) : 1;
+ if (!soundName || typeof soundName !== 'string') {
+ console.warn('playSound: invalid soundName');
return;
}
@@ -309,17 +356,17 @@ export const flockSound = {
buffer = await loadAudioBuffer(soundUrl, context);
} catch (err) {
flock.reportBlockError({
- key: "sound_load_failed",
- api: "playSound",
+ key: 'sound_load_failed',
+ api: 'playSound',
values: { sound: soundName, url: soundUrl },
error: err,
});
return;
}
- if (context.state === "closed") return;
+ if (context.state === 'closed') return;
- if (meshName === "__everywhere__") {
+ if (meshName === '__everywhere__') {
return playBufferEverywhere(context, buffer, soundName, { loop, volume, playbackRate });
}
@@ -330,19 +377,20 @@ export const flockSound = {
return new Promise((resolve) => {
flock.whenModelReady(meshName, async (resolvedMesh) => {
- if (flock.audioContext !== context) { resolve(); return; }
+ if (flock.audioContext !== context) {
+ resolve();
+ return;
+ }
// Spatial playback reads resolvedMesh.position every frame.
- if (!flock.requireMesh(resolvedMesh, { api: "playSound", name: meshName })) {
+ if (!flock.requireMesh(resolvedMesh, { api: 'playSound', name: meshName })) {
resolve();
return;
}
- const result = await playBufferOnMesh(
- context,
- resolvedMesh,
- buffer,
- soundName,
- { loop, volume, playbackRate },
- );
+ const result = await playBufferOnMesh(context, resolvedMesh, buffer, soundName, {
+ loop,
+ volume,
+ playbackRate,
+ });
resolve(result);
});
});
@@ -353,7 +401,11 @@ export const flockSound = {
flock._speechGeneration = (flock._speechGeneration ?? 0) + 1;
flock._speechPausedByVisibility = false;
}
- try { window.speechSynthesis?.cancel(); } catch { /* unsupported */ }
+ try {
+ window.speechSynthesis?.cancel();
+ } catch {
+ /* unsupported */
+ }
if (!flock?.globalSounds) return;
soundBufferCache.clear();
@@ -363,14 +415,18 @@ export const flockSound = {
try {
sound.stop();
} catch (e) {
- console.warn("Error stopping sound:", sound.name, e);
+ console.warn('Error stopping sound:', sound.name, e);
}
}
// Immediately disconnect the __everywhere__ gain โ context.close() is async and
// a brief window exists where the old gain can still feed the destination.
if (flock._everywhereGain) {
- try { flock._everywhereGain.disconnect(); } catch { /* already detached */ }
+ try {
+ flock._everywhereGain.disconnect();
+ } catch {
+ /* already detached */
+ }
flock._everywhereGain = null;
}
@@ -383,14 +439,14 @@ export const flockSound = {
// so _listenerCtx would otherwise hold the last closed context indefinitely.
_listenerCtx = null;
- if (!ctx || ctx.state === "closed") return;
+ if (!ctx || ctx.state === 'closed') return;
// Don't close Babylon's own context โ it gets closed when audioEngine.dispose() runs.
// Closing it here would break Babylon's audio graph on the next scene load.
const isBabylonOwned = flock.audioEngine?._audioContext === ctx;
if (!isBabylonOwned) {
ctx.close().catch((error) => {
- console.error("Error closing audio context:", error);
+ console.error('Error closing audio context:', error);
});
}
},
@@ -406,7 +462,11 @@ export const flockSound = {
const synth = window.speechSynthesis;
if (synth?.speaking && !synth.paused) {
flock._speechPausedByVisibility = true;
- try { synth.pause(); } catch { /* unsupported */ }
+ try {
+ synth.pause();
+ } catch {
+ /* unsupported */
+ }
}
// An interruption before visibilitychange leaves 'interrupted', not 'running'.
@@ -428,7 +488,11 @@ export const flockSound = {
if (flock._speechPausedByVisibility) {
flock._speechPausedByVisibility = false;
- try { window.speechSynthesis?.resume(); } catch { /* unsupported */ }
+ try {
+ window.speechSynthesis?.resume();
+ } catch {
+ /* unsupported */
+ }
}
if (!flock._audioSuspendedByVisibility) return;
@@ -442,33 +506,25 @@ export const flockSound = {
},
async playNotes(
meshName,
- {
- notes = [],
- durations = [],
- instrument = flock.createInstrument("square"),
- } = {},
+ { notes = [], durations = [], instrument = flock.createInstrument('square') } = {}
) {
// Clear any prior stopAllSounds abort; stop must not disable future playback.
flock._audioStopped = false;
- notes = notes.map((note) => (note === "_" ? null : note));
+ notes = notes.map((note) => (note === '_' ? null : note));
durations = durations.map(Number);
const getBPM = (obj) => obj?.metadata?.bpm || null;
await flock.ensureAudio();
const context = getOrCreateContext();
- if (!context || context.state === "closed") return;
+ if (!context || context.state === 'closed') return;
await safeResume(context);
- if (context.state === "closed") return;
+ if (context.state === 'closed') return;
if (flock._audioStopped) return;
const scheduleNotes = (mesh, outputNode, observer) => {
return new Promise((resolve) => {
- let bpm =
- getBPM(mesh) ||
- getBPM(mesh?.parent) ||
- getBPM(flock.scene) ||
- 60;
+ let bpm = getBPM(mesh) || getBPM(mesh?.parent) || getBPM(flock.scene) || 60;
bpm = Number(bpm);
if (!isFinite(bpm) || bpm <= 0) bpm = 60;
@@ -488,7 +544,7 @@ export const flockSound = {
duration,
bpm,
baseTime + offsetTime,
- instrument,
+ instrument
);
}
@@ -505,10 +561,14 @@ export const flockSound = {
});
};
- if (meshName === "__everywhere__") {
+ if (meshName === '__everywhere__') {
// context.close() is async; a lingering prior gain would briefly double the output.
if (flock._everywhereGain) {
- try { flock._everywhereGain.disconnect(); } catch { /* already detached */ }
+ try {
+ flock._everywhereGain.disconnect();
+ } catch {
+ /* already detached */
+ }
flock._everywhereGain = null;
}
const gain = context.createGain();
@@ -516,7 +576,11 @@ export const flockSound = {
gain.connect(getNoteBus(context));
return scheduleNotes(null, gain, null).then(() => {
if (flock._everywhereGain === gain) flock._everywhereGain = null;
- try { gain.disconnect(); } catch { /* already detached */ }
+ try {
+ gain.disconnect();
+ } catch {
+ /* already detached */
+ }
});
}
@@ -527,7 +591,7 @@ export const flockSound = {
return;
}
if (!mesh?.position) {
- console.error("Mesh does not have a position property:", mesh);
+ console.error('Mesh does not have a position property:', mesh);
resolve();
return;
}
@@ -536,18 +600,26 @@ export const flockSound = {
if (!mesh.metadata.panner || mesh.metadata.panner.context !== context) {
if (mesh.metadata.panner) {
- try { mesh.metadata.panner.disconnect(); } catch { /* already disconnected */ }
+ try {
+ mesh.metadata.panner.disconnect();
+ } catch {
+ /* already disconnected */
+ }
}
const panner = context.createPanner();
mesh.metadata.panner = panner;
- panner.panningModel = "equalpower";
- panner.distanceModel = "linear";
+ panner.panningModel = 'equalpower';
+ panner.distanceModel = 'linear';
panner.refDistance = 1;
panner.maxDistance = 20;
panner.rolloffFactor = 1;
panner.connect(getNoteBus(context));
mesh.onDisposeObservable?.addOnce(() => {
- try { panner.disconnect(); } catch { /* already disconnected */ }
+ try {
+ panner.disconnect();
+ } catch {
+ /* already disconnected */
+ }
if (mesh.metadata?.panner === panner) delete mesh.metadata.panner;
});
}
@@ -561,10 +633,7 @@ export const flockSound = {
panner.positionY.value = y;
panner.positionZ.value = z;
if (!flock.audioEngine && flock.scene.activeCamera) {
- flockSound.updateListenerPositionAndOrientation(
- context,
- flock.scene.activeCamera,
- );
+ flockSound.updateListenerPositionAndOrientation(context, flock.scene.activeCamera);
}
};
updatePositions();
@@ -575,19 +644,11 @@ export const flockSound = {
});
});
},
- playMidiNote(
- context,
- mesh,
- note,
- duration,
- bpm,
- playTime,
- instrument = null,
- ) {
- if (!context || context.state === "closed") return;
+ playMidiNote(context, mesh, note, duration, bpm, playTime, instrument = null) {
+ if (!context || context.state === 'closed') return;
if (!isFinite(duration) || !isFinite(playTime) || !isFinite(bpm)) {
- console.warn("playMidiNote: Invalid parameters", {
+ console.warn('playMidiNote: Invalid parameters', {
duration,
playTime,
bpm,
@@ -601,25 +662,23 @@ export const flockSound = {
gainNode.gain.setValueAtTime(0, context.currentTime); // silent until envelope starts
const panner = mesh.metadata.panner;
- osc.type = instrument?.type ?? "sine";
+ osc.type = instrument?.type ?? 'sine';
osc.frequency.value = flock.midiToFrequency(note);
- const effect = instrument?.effect ?? "none";
+ const effect = instrument?.effect ?? 'none';
const effectRate = instrument?.effectRate ?? 5;
const effectDepth = instrument?.effectDepth ?? 0.5;
let lfo = null;
let lfoGain = null;
- if (effect !== "none") {
+ if (effect !== 'none') {
lfo = context.createOscillator();
lfoGain = context.createGain();
- lfo.type = effect === "warble" ? "square" : "sine";
- lfo.frequency.value = effect === "robot" ? effectRate * 100 : effectRate;
+ lfo.type = effect === 'warble' ? 'square' : 'sine';
+ lfo.frequency.value = effect === 'robot' ? effectRate * 100 : effectRate;
lfoGain.gain.value =
- effect === "tremolo"
- ? effectDepth
- : osc.frequency.value * effectDepth * 0.5;
+ effect === 'tremolo' ? effectDepth : osc.frequency.value * effectDepth * 0.5;
lfo.connect(lfoGain);
- if (effect === "tremolo") {
+ if (effect === 'tremolo') {
lfoGain.connect(gainNode.gain);
} else {
lfoGain.connect(osc.frequency);
@@ -683,15 +742,22 @@ export const flockSound = {
const cleanupDelay = Math.max(100, (stopTime - context.currentTime + 0.5) * 1000);
cancelCleanup = audioTimer(context, cleanupDelay, doDisconnect);
- noteRef = { name: 'note', stop() { try { osc.stop(0); } catch { /* already stopped */ } doDisconnect(); } };
+ noteRef = {
+ name: 'note',
+ stop() {
+ try {
+ osc.stop(0);
+ } catch {
+ /* already stopped */
+ }
+ doDisconnect();
+ },
+ };
if (flock.globalSounds) flock.globalSounds.push(noteRef);
},
midiToFrequency(note) {
const parsed = Number(note);
- note = Math.min(
- 127,
- Math.max(0, Math.round(Number.isNaN(parsed) ? 60 : parsed)),
- ); // Clamp to valid MIDI range 0-127
+ note = Math.min(127, Math.max(0, Math.round(Number.isNaN(parsed) ? 60 : parsed))); // Clamp to valid MIDI range 0-127
return 440 * Math.pow(2, (note - 69) / 12); // Convert MIDI note to frequency
},
durationInSeconds(duration, bpm) {
@@ -705,18 +771,18 @@ export const flockSound = {
decay = 0.3,
sustain = 0.7,
release = 1.0,
- effect = "none",
+ effect = 'none',
effectRate = 5,
effectDepth = 0.5,
- } = {},
+ } = {}
) {
// Clamp parameters to valid ranges
const toNum = (v, def) => {
const n = Number(v);
return Number.isNaN(n) ? def : n;
};
- const validEffects = ["none", "tremolo", "vibrato", "warble", "robot"];
- effect = validEffects.includes(effect) ? effect : "none";
+ const validEffects = ['none', 'tremolo', 'vibrato', 'warble', 'robot'];
+ effect = validEffects.includes(effect) ? effect : 'none';
volume = Math.min(1, Math.max(0, toNum(volume, 1.0)));
attack = Math.min(5, Math.max(0, toNum(attack, 0.1)));
decay = Math.min(5, Math.max(0, toNum(decay, 0.3)));
@@ -742,8 +808,8 @@ export const flockSound = {
const safeBpm = Number.isFinite(Number(bpm)) && Number(bpm) > 0 ? Number(bpm) : 60;
bpm = safeBpm;
- if (meshName === "__everywhere__") {
- if (!flock.scene.metadata || typeof flock.scene.metadata !== "object") {
+ if (meshName === '__everywhere__') {
+ if (!flock.scene.metadata || typeof flock.scene.metadata !== 'object') {
flock.scene.metadata = {};
}
flock.scene.metadata.bpm = bpm;
@@ -754,15 +820,15 @@ export const flockSound = {
flock.whenModelReady(meshName, async function (mesh) {
if (!mesh) {
flock.reportBlockError({
- key: "object_not_found",
- api: "setBPM",
+ key: 'object_not_found',
+ api: 'setBPM',
values: { object: meshName },
});
resolve();
return;
}
- if (!mesh.metadata || typeof mesh.metadata !== "object") {
+ if (!mesh.metadata || typeof mesh.metadata !== 'object') {
mesh.metadata = {};
}
@@ -772,11 +838,8 @@ export const flockSound = {
});
},
async playMusic(meshName, { notes = [], instrument = null } = {}) {
- const effectiveInstrument = instrument ?? flock.createInstrument("sine");
- const flatNotes =
- notes.length > 0 && Array.isArray(notes[0])
- ? notes.flat()
- : notes;
+ const effectiveInstrument = instrument ?? flock.createInstrument('sine');
+ const flatNotes = notes.length > 0 && Array.isArray(notes[0]) ? notes.flat() : notes;
const pitches = flatNotes.map((n) => n?.pitch ?? null);
const baseDurations = flatNotes.map((n) => n?.duration ?? 0.5);
@@ -784,10 +847,10 @@ export const flockSound = {
Math.max(
0.01,
Number(
- meshName === "__everywhere__"
+ meshName === '__everywhere__'
? flock.scene?.metadata?.musicSpeed
- : mesh?.metadata?.musicSpeed,
- ) || 1,
+ : mesh?.metadata?.musicSpeed
+ ) || 1
);
const playForMesh = async (mesh) => {
@@ -800,7 +863,7 @@ export const flockSound = {
});
};
- if (meshName === "__everywhere__") {
+ if (meshName === '__everywhere__') {
return playForMesh(null);
}
@@ -814,8 +877,8 @@ export const flockSound = {
setMusicSpeed(meshName, speed) {
const validSpeed = Math.max(0.01, Number(speed) || 1);
- if (meshName === "__everywhere__") {
- if (!flock.scene.metadata || typeof flock.scene.metadata !== "object") {
+ if (meshName === '__everywhere__') {
+ if (!flock.scene.metadata || typeof flock.scene.metadata !== 'object') {
flock.scene.metadata = {};
}
flock.scene.metadata.musicSpeed = validSpeed;
@@ -824,7 +887,7 @@ export const flockSound = {
return new Promise((resolve) => {
flock.whenModelReady(meshName, function (mesh) {
- if (!mesh.metadata || typeof mesh.metadata !== "object") {
+ if (!mesh.metadata || typeof mesh.metadata !== 'object') {
mesh.metadata = {};
}
mesh.metadata.musicSpeed = validSpeed;
@@ -836,18 +899,24 @@ export const flockSound = {
if (!context || !camera) return;
const { x: cx, y: cy, z: cz } = camera.position;
const fwd = camera.getForwardRay().direction;
- const tfx = -fwd.x, tfy = fwd.y, tfz = fwd.z;
+ const tfx = -fwd.x,
+ tfy = fwd.y,
+ tfz = fwd.z;
// Reset smooth state when the audio context changes (e.g. after stopAllSounds).
// Otherwise continue from the running average so position/orientation never jump.
if (context !== _listenerCtx) {
_listenerCtx = context;
- _slx = cx; _sly = cy; _slz = cz;
- _sfx = tfx; _sfy = tfy; _sfz = tfz;
+ _slx = cx;
+ _sly = cy;
+ _slz = cz;
+ _sfx = tfx;
+ _sfy = tfy;
+ _sfz = tfz;
} else {
- _slx += LISTENER_SMOOTH * (cx - _slx);
- _sly += LISTENER_SMOOTH * (cy - _sly);
- _slz += LISTENER_SMOOTH * (cz - _slz);
+ _slx += LISTENER_SMOOTH * (cx - _slx);
+ _sly += LISTENER_SMOOTH * (cy - _sly);
+ _slz += LISTENER_SMOOTH * (cz - _slz);
_sfx += LISTENER_SMOOTH * (tfx - _sfx);
_sfy += LISTENER_SMOOTH * (tfy - _sfy);
_sfz += LISTENER_SMOOTH * (tfz - _sfz);
@@ -880,19 +949,12 @@ export const flockSound = {
async speak(
meshName,
text,
- {
- voice = "female",
- language = "en-US",
- rate = 1,
- pitch = 1,
- volume = 1,
- mode = "start",
- } = {},
+ { voice = 'female', language = 'en-US', rate = 1, pitch = 1, volume = 1, mode = 'start' } = {}
) {
// Check for Web Speech API support
- if (!("speechSynthesis" in window)) {
- console.warn("Text-to-speech not supported in this browser");
- return mode === "await" ? Promise.resolve() : undefined;
+ if (!('speechSynthesis' in window)) {
+ console.warn('Text-to-speech not supported in this browser');
+ return mode === 'await' ? Promise.resolve() : undefined;
}
// cancel() cannot reach an utterance that is not queued yet.
@@ -913,11 +975,8 @@ export const flockSound = {
// Handle spatial audio if meshName is provided and not "__everywhere__"
let spatialAudioSetup = null;
- if (meshName && meshName !== "__everywhere__") {
- spatialAudioSetup = await flockSound.setupSpatialSpeech(
- utterance,
- meshName,
- );
+ if (meshName && meshName !== '__everywhere__') {
+ spatialAudioSetup = await flockSound.setupSpatialSpeech(utterance, meshName);
}
// Set voice if available - handle voice loading timing
@@ -950,110 +1009,108 @@ export const flockSound = {
// Common voice names by platform and gender
const commonVoices = {
- "en-US": {
+ 'en-US': {
male: [
// Windows
- "david",
- "mark",
- "zira",
- "james",
+ 'david',
+ 'mark',
+ 'zira',
+ 'james',
// macOS/iOS
- "nathan",
- "alex",
- "daniel",
- "fred",
- "jorge",
- "tom",
+ 'nathan',
+ 'alex',
+ 'daniel',
+ 'fred',
+ 'jorge',
+ 'tom',
// Android/Chrome
- "male",
- "man",
+ 'male',
+ 'man',
],
female: [
// Windows
- "zira",
- "hazel",
- "helen",
+ 'zira',
+ 'hazel',
+ 'helen',
// macOS/iOS
- "samantha",
- "susan",
- "allison",
- "ava",
- "zoe",
- "karen",
- "moira",
- "tessa",
- "veera",
- "victoria",
+ 'samantha',
+ 'susan',
+ 'allison',
+ 'ava',
+ 'zoe',
+ 'karen',
+ 'moira',
+ 'tessa',
+ 'veera',
+ 'victoria',
// Android/Chrome
- "female",
- "woman",
+ 'female',
+ 'woman',
],
},
- "en-GB": {
+ 'en-GB': {
male: [
// Windows
- "george",
- "hazel",
+ 'george',
+ 'hazel',
// macOS/iOS
- "daniel",
- "oliver",
- "jamie",
+ 'daniel',
+ 'oliver',
+ 'jamie',
// Android/Chrome
- "male",
- "man",
+ 'male',
+ 'man',
],
female: [
// Windows
- "hazel",
- "susan",
+ 'hazel',
+ 'susan',
// macOS/iOS
- "kate",
- "serena",
- "stephanie",
+ 'kate',
+ 'serena',
+ 'stephanie',
// Android/Chrome
- "female",
- "woman",
+ 'female',
+ 'woman',
],
},
- "es-ES": {
+ 'es-ES': {
male: [
// macOS/iOS
- "jorge",
- "juan",
- "diego",
- "carlos",
- "eddy",
- "grandpa",
- "reed",
- "rocko",
+ 'jorge',
+ 'juan',
+ 'diego',
+ 'carlos',
+ 'eddy',
+ 'grandpa',
+ 'reed',
+ 'rocko',
],
female: [
// macOS/iOS
- "marisol",
- "paulina",
- "isabela",
- "francisca",
- "jimena",
- "soledad",
- "angelica",
- "monica",
- "flo",
- "grandma",
- "sandy",
- "shelly",
+ 'marisol',
+ 'paulina',
+ 'isabela',
+ 'francisca',
+ 'jimena',
+ 'soledad',
+ 'angelica',
+ 'monica',
+ 'flo',
+ 'grandma',
+ 'sandy',
+ 'shelly',
],
- }
+ },
};
// Filter voices by requested language
const languageVoices = voices.filter((v) => v.lang.startsWith(language));
if (languageVoices.length === 0) {
- console.warn(
- `No voices found for language ${language}, falling back to any English voice`,
- );
+ console.warn(`No voices found for language ${language}, falling back to any English voice`);
// Fallback to any English voice
- const englishVoices = voices.filter((v) => v.lang.startsWith("en"));
+ const englishVoices = voices.filter((v) => v.lang.startsWith('en'));
if (englishVoices.length > 0) {
selectedVoice = englishVoices[0];
}
@@ -1063,7 +1120,7 @@ export const flockSound = {
// 1. Try common voice names for the platform/language
for (const voiceName of voiceNames) {
selectedVoice = languageVoices.find((v) =>
- v.name.toLowerCase().includes(voiceName.toLowerCase()),
+ v.name.toLowerCase().includes(voiceName.toLowerCase())
);
if (selectedVoice) break;
}
@@ -1071,89 +1128,83 @@ export const flockSound = {
// 2. Look for explicit "Male" or "Female" in name
if (!selectedVoice) {
selectedVoice = languageVoices.find((v) =>
- v.name.toLowerCase().includes(voice.toLowerCase()),
+ v.name.toLowerCase().includes(voice.toLowerCase())
);
}
// 3. For male voices, avoid known female names
- if (!selectedVoice && voice === "male") {
+ if (!selectedVoice && voice === 'male') {
const femaleTerms = [
- "female",
- "woman",
- "lady",
- "girl",
- "samantha",
- "susan",
- "kate",
- "zira",
- "hazel",
- "helen",
- "karen",
- "moira",
- "tessa",
- "fiona",
- "allison",
- "ava",
- "veera",
- "victoria",
- "stephanie",
- "serena",
- "marisol",
- "paulina",
- "isabela",
- "francisca",
- "jimena",
- "soledad",
- "angelica",
- "monica",
- "flo",
- "grandma",
- "sandy",
- "shelly",
- "zoe",
+ 'female',
+ 'woman',
+ 'lady',
+ 'girl',
+ 'samantha',
+ 'susan',
+ 'kate',
+ 'zira',
+ 'hazel',
+ 'helen',
+ 'karen',
+ 'moira',
+ 'tessa',
+ 'fiona',
+ 'allison',
+ 'ava',
+ 'veera',
+ 'victoria',
+ 'stephanie',
+ 'serena',
+ 'marisol',
+ 'paulina',
+ 'isabela',
+ 'francisca',
+ 'jimena',
+ 'soledad',
+ 'angelica',
+ 'monica',
+ 'flo',
+ 'grandma',
+ 'sandy',
+ 'shelly',
+ 'zoe',
];
selectedVoice = languageVoices.find(
- (v) =>
- !femaleTerms.some((term) =>
- v.name.toLowerCase().includes(term.toLowerCase()),
- ),
+ (v) => !femaleTerms.some((term) => v.name.toLowerCase().includes(term.toLowerCase()))
);
}
// 4. For female voices, avoid known male names
- if (!selectedVoice && voice === "female") {
+ if (!selectedVoice && voice === 'female') {
const maleTerms = [
- "male",
- "man",
- "david",
- "alex",
- "daniel",
- "mark",
- "tom",
- "george",
- "peter",
- "john",
- "michael",
- "robert",
- "fred",
- "jorge",
- "james",
- "oliver",
- "eddy",
- "nathan",
- "jamie",
- "juan",
- "diego",
- "carlos",
- "grandpa",
- "reed",
- "rocko",
+ 'male',
+ 'man',
+ 'david',
+ 'alex',
+ 'daniel',
+ 'mark',
+ 'tom',
+ 'george',
+ 'peter',
+ 'john',
+ 'michael',
+ 'robert',
+ 'fred',
+ 'jorge',
+ 'james',
+ 'oliver',
+ 'eddy',
+ 'nathan',
+ 'jamie',
+ 'juan',
+ 'diego',
+ 'carlos',
+ 'grandpa',
+ 'reed',
+ 'rocko',
];
selectedVoice = languageVoices.find(
- (v) =>
- !maleTerms.some((term) =>
- v.name.toLowerCase().includes(term.toLowerCase()),
- ),
+ (v) => !maleTerms.some((term) => v.name.toLowerCase().includes(term.toLowerCase()))
);
}
@@ -1171,7 +1222,7 @@ export const flockSound = {
if (selectedVoice) {
utterance.voice = selectedVoice;
} else {
- console.warn("No voice found for type:", voice, "using default");
+ console.warn('No voice found for type:', voice, 'using default');
}
}
@@ -1193,8 +1244,7 @@ export const flockSound = {
// even if onend is dropped (~14 chars/sec at rate 1, floor 2s, +1s buffer).
// Use utterance.rate (already clamped to 0.1โ10) so the estimate matches
// the rate actually spoken.
- const seconds =
- Math.max(2, String(text).length / 14) / utterance.rate + 1;
+ const seconds = Math.max(2, String(text).length / 14) / utterance.rate + 1;
flock.showSubtitle(text, seconds);
subtitleToken = flock._subtitleToken;
};
@@ -1206,7 +1256,7 @@ export const flockSound = {
};
const onSpeechError = (event) => {
- console.warn("Speech synthesis error:", event.error);
+ console.warn('Speech synthesis error:', event.error);
if (SPEECH_UNAVAILABLE.has(event.error)) {
showBanner('speech', { message: translate('error_speech') });
}
@@ -1215,7 +1265,7 @@ export const flockSound = {
dismissBanner('speech');
};
- if (mode === "await") {
+ if (mode === 'await') {
return new Promise((resolve) => {
utterance.onend = () => {
onSpeechEnd();
@@ -1272,7 +1322,7 @@ export const flockSound = {
if (!mesh || mesh.isDisposed?.() || !flock.scene.activeCamera) return;
const distance = flock.BABYLON.Vector3.Distance(
flock.scene.activeCamera.position,
- mesh.position,
+ mesh.position
);
// Camera can't get closer than ~7 units; treat that as "full volume" range.
@@ -1287,7 +1337,7 @@ export const flockSound = {
} else if (adjustedDistance < maxDistance) {
volumeGain = Math.max(
0.15,
- refDistance / (refDistance + rolloffFactor * (adjustedDistance - refDistance)),
+ refDistance / (refDistance + rolloffFactor * (adjustedDistance - refDistance))
);
} else {
volumeGain = 0.1;
diff --git a/api/transform.js b/api/transform.js
index fc92eb6d9..beb25f8f8 100644
--- a/api/transform.js
+++ b/api/transform.js
@@ -72,7 +72,7 @@ function toFinite(v, fallback = 0) {
// Exactly ยฑ90ยฐ pitch makes a camera's look-at up vector degenerate; screen
// roll then depends on platform libm rounding and can render upside down.
function clampCameraPitchDegrees(x) {
- const wrapped = ((x % 360) + 540) % 360 - 180;
+ const wrapped = (((x % 360) + 540) % 360) - 180;
if (Math.abs(Math.abs(wrapped) - 90) < 0.05) {
return Math.sign(wrapped) * (Math.abs(wrapped) > 90 ? 90.05 : 89.95);
}
@@ -500,10 +500,7 @@ export const flockTransform = {
resolve();
return;
}
- if (
- !(mesh instanceof flock.BABYLON.AbstractMesh) &&
- mesh.name !== 'hemisphericLight'
- ) {
+ if (!(mesh instanceof flock.BABYLON.AbstractMesh) && mesh.name !== 'hemisphericLight') {
flock.reportBlockError({
key: 'target_not_a_mesh',
api: 'rotateTo',
diff --git a/api/xr.js b/api/xr.js
index a5f7cbdde..5667e05ec 100644
--- a/api/xr.js
+++ b/api/xr.js
@@ -1,4 +1,4 @@
-import { translate } from "../main/translation.js";
+import { translate } from '../main/translation.js';
let flock;
@@ -14,17 +14,12 @@ export const flockXR = {
setCameraBackground(cameraType) {
if (!flock.scene) {
console.error(
- "Scene not available. Ensure the scene is initialised before setting the camera background.",
+ 'Scene not available. Ensure the scene is initialised before setting the camera background.'
);
return;
}
- const videoLayer = new flock.BABYLON.Layer(
- "videoLayer",
- null,
- flock.scene,
- true,
- );
+ const videoLayer = new flock.BABYLON.Layer('videoLayer', null, flock.scene, true);
flock.BABYLON.VideoTexture.CreateFromWebCam(
flock.scene,
@@ -39,50 +34,42 @@ export const flockXR = {
minHeight: 480,
maxWidth: 1920,
maxHeight: 1080,
- deviceId: "",
- },
+ deviceId: '',
+ }
);
},
async setXRMode(mode) {
await flock.initializeXR(mode);
flock.printText({
- text: translate("xr_mode_message"),
+ text: translate('xr_mode_message'),
duration: 5,
- color: "white",
+ color: 'white',
});
},
exportMesh(meshName, format) {
//meshName = "scene";
- if (meshName === "scene" && format === "GLB") {
+ if (meshName === 'scene' && format === 'GLB') {
const scene = flock.scene;
const cls = (n) => n?.getClassName?.();
- const isEnabledDeep = (n) =>
- typeof n.isEnabled === "function" ? n.isEnabled(true) : true;
+ const isEnabledDeep = (n) => (typeof n.isEnabled === 'function' ? n.isEnabled(true) : true);
// Treat ALL mesh subclasses as geometry; we'll still skip LinesMesh explicitly
const isAbstractMesh = (n) =>
- typeof flock.BABYLON !== "undefined" &&
- n instanceof flock.BABYLON.AbstractMesh;
- const isLines = (n) => cls(n) === "LinesMesh";
+ typeof flock.BABYLON !== 'undefined' && n instanceof flock.BABYLON.AbstractMesh;
+ const isLines = (n) => cls(n) === 'LinesMesh';
// --- Ghost: top-level + enabled + AbstractMesh + no material (not lines)
const targets = scene.meshes.filter(
- (m) =>
- !m.parent &&
- isEnabledDeep(m) &&
- isAbstractMesh(m) &&
- !isLines(m) &&
- !m.material,
+ (m) => !m.parent && isEnabledDeep(m) && isAbstractMesh(m) && !isLines(m) && !m.material
);
// Shared transparent PBR material (GLTF-friendly)
- const ghostMat = new flock.BABYLON.PBRMaterial("_tmpExportGhost", scene);
+ const ghostMat = new flock.BABYLON.PBRMaterial('_tmpExportGhost', scene);
ghostMat.alpha = 0;
ghostMat.alphaMode = flock.BABYLON.Engine.ALPHA_BLEND;
- ghostMat.transparencyMode =
- flock.BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
+ ghostMat.transparencyMode = flock.BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
ghostMat.disableLighting = true;
ghostMat.metallic = 0;
ghostMat.roughness = 1;
@@ -95,7 +82,7 @@ export const flockXR = {
for (const { mesh } of patches) mesh.material = ghostMat;
// Optional: name allowlist for safety (keeps ground even if disabled, if you want)
- const alwaysKeepNames = new Set(["ground", "Ground"]);
+ const alwaysKeepNames = new Set(['ground', 'Ground']);
const shouldExportNode = (node) => {
const c = cls(node);
@@ -108,13 +95,13 @@ export const flockXR = {
if (!isEnabledDeep(node)) return false;
// Never export cameras/lights
- if (c === "Camera" || c === "Light") return false;
+ if (c === 'Camera' || c === 'Light') return false;
// Skip line helpers entirely
- if (c === "LinesMesh") return false;
+ if (c === 'LinesMesh') return false;
// Keep all transform containers
- if (c === "TransformNode") return true;
+ if (c === 'TransformNode') return true;
// Keep ALL mesh subclasses (e.g., Mesh, InstancedMesh, GroundMesh, etc.)
if (isAbstractMesh(node)) return true;
@@ -122,7 +109,7 @@ export const flockXR = {
return false;
};
- flock.EXPORT.GLTF2Export.GLBAsync(scene, "scene.glb", {
+ flock.EXPORT.GLTF2Export.GLBAsync(scene, 'scene.glb', {
exportMaterials: true,
exportTextures: true,
shouldExportNode,
@@ -139,14 +126,12 @@ export const flockXR = {
return new Promise((resolve) => {
flock.whenModelReady(meshName, async function (mesh) {
- if (!flock.requireMesh(mesh, { api: "exportMesh", name: meshName })) {
+ if (!flock.requireMesh(mesh, { api: 'exportMesh', name: meshName })) {
resolve();
return;
}
const anchorMesh = mesh;
- const rootChild = anchorMesh
- .getChildMeshes()
- .find((child) => child.name === "__root__");
+ const rootChild = anchorMesh.getChildMeshes().find((child) => child.name === '__root__');
const exportAnchors = [anchorMesh];
if (rootChild) {
@@ -156,39 +141,27 @@ export const flockXR = {
const allowedNodes = new Set();
for (const anchor of exportAnchors) {
allowedNodes.add(anchor);
- anchor
- .getChildMeshes(false)
- .forEach((childMesh) => allowedNodes.add(childMesh));
+ anchor.getChildMeshes(false).forEach((childMesh) => allowedNodes.add(childMesh));
}
const hasDirectRootChild = (node) =>
- typeof node?.getChildMeshes === "function" &&
- node.getChildMeshes(true).some((child) => child.name === "__root__");
+ typeof node?.getChildMeshes === 'function' &&
+ node.getChildMeshes(true).some((child) => child.name === '__root__');
const wrapperNodes = [...allowedNodes].filter(
- (node) => node.name !== "__root__" && hasDirectRootChild(node),
+ (node) => node.name !== '__root__' && hasDirectRootChild(node)
);
const childMeshes = mesh.getChildMeshes(false);
const meshList = [mesh, ...childMeshes];
- if (format === "STL") {
- flock.EXPORT.STLExport.CreateSTL(
- meshList,
- true,
- mesh.name,
- false,
- false,
- );
- } else if (format === "OBJ") {
+ if (format === 'STL') {
+ flock.EXPORT.STLExport.CreateSTL(meshList, true, mesh.name, false, false);
+ } else if (format === 'OBJ') {
flock.EXPORT.OBJExport.OBJ(mesh);
- } else if (format === "GLB") {
- const ghostMat = new flock.BABYLON.PBRMaterial(
- "_tmpExportWrapperGhost",
- flock.scene,
- );
+ } else if (format === 'GLB') {
+ const ghostMat = new flock.BABYLON.PBRMaterial('_tmpExportWrapperGhost', flock.scene);
ghostMat.alpha = 0;
ghostMat.alphaMode = flock.BABYLON.Engine.ALPHA_BLEND;
- ghostMat.transparencyMode =
- flock.BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
+ ghostMat.transparencyMode = flock.BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
ghostMat.disableLighting = true;
ghostMat.metallic = 0;
ghostMat.roughness = 1;
@@ -204,13 +177,9 @@ export const flockXR = {
mesh.flipFaces();
try {
- await flock.EXPORT.GLTF2Export.GLBAsync(
- flock.scene,
- mesh.name + ".glb",
- {
- shouldExportNode: (node) => allowedNodes.has(node),
- },
- ).then((glb) => {
+ await flock.EXPORT.GLTF2Export.GLBAsync(flock.scene, mesh.name + '.glb', {
+ shouldExportNode: (node) => allowedNodes.has(node),
+ }).then((glb) => {
glb.downloadFiles();
});
} finally {
diff --git a/blocks/animate.js b/blocks/animate.js
index 9b9085003..43111bc1a 100644
--- a/blocks/animate.js
+++ b/blocks/animate.js
@@ -1,240 +1,236 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
nextVariableIndexes,
handleBlockCreateEvent,
getHelpUrlFor,
registerBlockHandler,
-} from "./blocks.js";
-import { animationNames } from "../config.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+} from './blocks.js';
+import { animationNames } from '../config.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
export function defineAnimateBlocks() {
- Blockly.Blocks["glide_to"] = {
+ Blockly.Blocks['glide_to'] = {
init: function () {
this.jsonInit({
- type: "glide_to",
- message0: translate("glide_to"),
+ type: 'glide_to',
+ message0: translate('glide_to'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("glide_to"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('glide_to'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["glide_to_seconds"] = {
+ Blockly.Blocks['glide_to_seconds'] = {
init: function () {
this.jsonInit({
- type: "glide_to_seconds",
- message0: translate("glide_to_seconds"),
+ type: 'glide_to_seconds',
+ message0: translate('glide_to_seconds'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("glide_to_seconds"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('glide_to_seconds'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["glide_to_object"] = {
+ Blockly.Blocks['glide_to_object'] = {
init: function () {
this.jsonInit({
- type: "glide_to_object",
- message0: translate("glide_to_object"),
+ type: 'glide_to_object',
+ message0: translate('glide_to_object'),
args0: [
{
- type: "field_variable",
- name: "MODEL1",
+ type: 'field_variable',
+ name: 'MODEL1',
variable: window.currentMesh,
},
{
- type: "field_variable",
- name: "MODEL2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MODEL2',
+ variable: 'object2',
},
{
- type: "input_value",
- name: "X_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'X_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Y_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Z_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("glide_to_object"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('glide_to_object'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
const addZeroShadow = (inputName) => {
const input = this.getInput(inputName);
@@ -249,605 +245,601 @@ export function defineAnimateBlocks() {
input.connection.respawnShadow_();
};
- ["X_OFFSET", "Y_OFFSET", "Z_OFFSET"].forEach(addZeroShadow);
+ ['X_OFFSET', 'Y_OFFSET', 'Z_OFFSET'].forEach(addZeroShadow);
},
};
- Blockly.Blocks["glide_to_axis"] = {
+ Blockly.Blocks['glide_to_axis'] = {
init: function () {
this.jsonInit({
- type: "glide_to_axis",
- message0: translate("glide_to_axis"),
+ type: 'glide_to_axis',
+ message0: translate('glide_to_axis'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "AXIS",
+ type: 'field_dropdown',
+ name: 'AXIS',
options: [
- ["x", "x"],
- ["y", "y"],
- ["z", "z"],
- getDropdownOption("forward"),
- getDropdownOption("sideways"),
+ ['x', 'x'],
+ ['y', 'y'],
+ ['z', 'z'],
+ getDropdownOption('forward'),
+ getDropdownOption('sideways'),
],
},
{
- type: "input_value",
- name: "TARGET",
- check: "Number",
+ type: 'input_value',
+ name: 'TARGET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("glide_to_axis"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('glide_to_axis'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["rotate_anim"] = {
+ Blockly.Blocks['rotate_anim'] = {
init: function () {
this.jsonInit({
- type: "rotate_anim",
- message0: translate("rotate_anim"),
+ type: 'rotate_anim',
+ message0: translate('rotate_anim'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "ROT_X",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ROT_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ROT_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("rotate_anim"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('rotate_anim'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["rotate_anim_seconds"] = {
+ Blockly.Blocks['rotate_anim_seconds'] = {
init: function () {
this.jsonInit({
- type: "rotate_anim_seconds",
- message0: translate("rotate_anim_seconds"),
+ type: 'rotate_anim_seconds',
+ message0: translate('rotate_anim_seconds'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "ROT_X",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ROT_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ROT_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'ROT_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("rotate_anim_seconds"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('rotate_anim_seconds'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["rotate_to_object"] = {
+ Blockly.Blocks['rotate_to_object'] = {
init: function () {
this.jsonInit({
- type: "rotate_to_object",
- message0: translate("rotate_to_object"),
+ type: 'rotate_to_object',
+ message0: translate('rotate_to_object'),
args0: [
{
- type: "field_variable",
- name: "MODEL1",
+ type: 'field_variable',
+ name: 'MODEL1',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "ROTATE_MODE",
- options: [
- getDropdownOption("TOWARDS"),
- getDropdownOption("SAME_ROTATION"),
- ],
+ type: 'field_dropdown',
+ name: 'ROTATE_MODE',
+ options: [getDropdownOption('TOWARDS'), getDropdownOption('SAME_ROTATION')],
},
{
- type: "field_variable",
- name: "MODEL2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MODEL2',
+ variable: 'object2',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("Linear"),
- getDropdownOption("SineEase"),
- getDropdownOption("CubicEase"),
- getDropdownOption("QuadraticEase"),
- getDropdownOption("ExponentialEase"),
- getDropdownOption("BounceEase"),
- getDropdownOption("ElasticEase"),
- getDropdownOption("BackEase"),
+ getDropdownOption('Linear'),
+ getDropdownOption('SineEase'),
+ getDropdownOption('CubicEase'),
+ getDropdownOption('QuadraticEase'),
+ getDropdownOption('ExponentialEase'),
+ getDropdownOption('BounceEase'),
+ getDropdownOption('ElasticEase'),
+ getDropdownOption('BackEase'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("rotate_to_object"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('rotate_to_object'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["animate_property"] = {
+ Blockly.Blocks['animate_property'] = {
init: function () {
this.jsonInit({
- type: "animate_property",
- message0: translate("animate_property"),
+ type: 'animate_property',
+ message0: translate('animate_property'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "PROPERTY",
+ type: 'field_dropdown',
+ name: 'PROPERTY',
options: [
- getDropdownOption("diffuseColor"),
- getDropdownOption("emissiveColor"),
- getDropdownOption("ambientColor"),
- getDropdownOption("specularColor"),
- getDropdownOption("alpha"),
+ getDropdownOption('diffuseColor'),
+ getDropdownOption('emissiveColor'),
+ getDropdownOption('ambientColor'),
+ getDropdownOption('specularColor'),
+ getDropdownOption('alpha'),
],
},
{
- type: "input_value",
- name: "TO",
+ type: 'input_value',
+ name: 'TO',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
- text: "reverse",
+ text: 'reverse',
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
- text: "loop",
+ text: 'loop',
},
{
- type: "field_dropdown",
- name: "START_AWAIT",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'START_AWAIT',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("animate_property"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('animate_property'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["colour_keyframe"] = {
+ Blockly.Blocks['colour_keyframe'] = {
init: function () {
this.jsonInit({
- type: "colour_keyframe",
- message0: translate("colour_keyframe"),
+ type: 'colour_keyframe',
+ message0: translate('colour_keyframe'),
args0: [
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "VALUE",
- check: "Colour", // Reusing your existing colour block
+ type: 'input_value',
+ name: 'VALUE',
+ check: 'Colour', // Reusing your existing colour block
},
],
- colour: categoryColours["Animate"],
+ colour: categoryColours['Animate'],
inputsInline: true,
- output: "Keyframe",
- tooltip: getTooltip("colour_keyframe"),
+ output: 'Keyframe',
+ tooltip: getTooltip('colour_keyframe'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["number_keyframe"] = {
+ Blockly.Blocks['number_keyframe'] = {
init: function () {
this.jsonInit({
- type: "number_keyframe",
- message0: translate("number_keyframe"),
+ type: 'number_keyframe',
+ message0: translate('number_keyframe'),
args0: [
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "VALUE",
- check: "Number", // Reusing your existing colour block
+ type: 'input_value',
+ name: 'VALUE',
+ check: 'Number', // Reusing your existing colour block
},
],
- colour: categoryColours["Animate"],
+ colour: categoryColours['Animate'],
inputsInline: true,
- output: "Keyframe",
- tooltip: getTooltip("number_keyframe"),
+ output: 'Keyframe',
+ tooltip: getTooltip('number_keyframe'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["xyz_keyframe"] = {
+ Blockly.Blocks['xyz_keyframe'] = {
init: function () {
this.jsonInit({
- type: "xyz_keyframe",
- message0: translate("xyz_keyframe"),
+ type: 'xyz_keyframe',
+ message0: translate('xyz_keyframe'),
args0: [
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
- colour: categoryColours["Animate"],
+ colour: categoryColours['Animate'],
inputsInline: true,
- output: "Keyframe",
- tooltip: getTooltip("xyz_keyframe"),
+ output: 'Keyframe',
+ tooltip: getTooltip('xyz_keyframe'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["animate_keyframes"] = {
+ Blockly.Blocks['animate_keyframes'] = {
init: function () {
this.jsonInit({
- type: "animate_keyframes",
- message0: translate("animate_keyframes"),
+ type: 'animate_keyframes',
+ message0: translate('animate_keyframes'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh, // Assuming current mesh is stored here
},
{
- type: "field_dropdown",
- name: "PROPERTY",
+ type: 'field_dropdown',
+ name: 'PROPERTY',
options: [
- getDropdownOption("color"),
- getDropdownOption("alpha"),
- getDropdownOption("position"),
- getDropdownOption("rotation"),
- getDropdownOption("scaling"),
+ getDropdownOption('color'),
+ getDropdownOption('alpha'),
+ getDropdownOption('position'),
+ getDropdownOption('rotation'),
+ getDropdownOption('scaling'),
],
},
{
- type: "input_value",
- name: "KEYFRAMES",
- check: "Array", // Accepts an array of keyframes
+ type: 'input_value',
+ name: 'KEYFRAMES',
+ check: 'Array', // Accepts an array of keyframes
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("LINEAR"),
- getDropdownOption("EASEIN"),
- getDropdownOption("EASEOUT"),
- getDropdownOption("EASEINOUT"),
+ getDropdownOption('LINEAR'),
+ getDropdownOption('EASEIN'),
+ getDropdownOption('EASEOUT'),
+ getDropdownOption('EASEINOUT'),
],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false, // Checkbox for reversing the animation
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false, // Checkbox for looping
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("AWAIT"), getDropdownOption("START")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('AWAIT'), getDropdownOption('START')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("animate_keyframes"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('animate_keyframes'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["animation"] = {
+ Blockly.Blocks['animation'] = {
init: function () {
- const variableNamePrefix = "animation";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'animation';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "animation",
- message0: translate("animation"),
+ type: 'animation',
+ message0: translate('animation'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh, // Assuming current mesh is stored here
},
{
- type: "field_dropdown",
- name: "PROPERTY",
+ type: 'field_dropdown',
+ name: 'PROPERTY',
options: [
- getDropdownOption("color"),
- getDropdownOption("alpha"),
- getDropdownOption("position"),
- getDropdownOption("rotation"),
- getDropdownOption("scaling"),
- getDropdownOption("position.x"),
- getDropdownOption("position.y"),
- getDropdownOption("position.z"),
- getDropdownOption("rotation.x"),
- getDropdownOption("rotation.y"),
- getDropdownOption("rotation.z"),
- getDropdownOption("scaling.x"),
- getDropdownOption("scaling.y"),
- getDropdownOption("scaling.z"),
+ getDropdownOption('color'),
+ getDropdownOption('alpha'),
+ getDropdownOption('position'),
+ getDropdownOption('rotation'),
+ getDropdownOption('scaling'),
+ getDropdownOption('position.x'),
+ getDropdownOption('position.y'),
+ getDropdownOption('position.z'),
+ getDropdownOption('rotation.x'),
+ getDropdownOption('rotation.y'),
+ getDropdownOption('rotation.z'),
+ getDropdownOption('scaling.x'),
+ getDropdownOption('scaling.y'),
+ getDropdownOption('scaling.z'),
],
},
{
- type: "field_variable",
- name: "ANIMATION_GROUP",
+ type: 'field_variable',
+ name: 'ANIMATION_GROUP',
variable: nextVariableName, // Use the dynamic variable name
},
{
- type: "input_value",
- name: "KEYFRAMES",
- check: "Array",
+ type: 'input_value',
+ name: 'KEYFRAMES',
+ check: 'Array',
},
{
- type: "field_dropdown",
- name: "EASING",
+ type: 'field_dropdown',
+ name: 'EASING',
options: [
- getDropdownOption("LINEAR"),
- getDropdownOption("EASEIN"),
- getDropdownOption("EASEOUT"),
- getDropdownOption("EASEINOUT"),
+ getDropdownOption('LINEAR'),
+ getDropdownOption('EASEIN'),
+ getDropdownOption('EASEOUT'),
+ getDropdownOption('EASEINOUT'),
],
},
{
- type: "field_checkbox",
- name: "REVERSE",
+ type: 'field_checkbox',
+ name: 'REVERSE',
checked: false,
},
{
- type: "field_checkbox",
- name: "LOOP",
+ type: 'field_checkbox',
+ name: 'LOOP',
checked: false,
},
{
- type: "field_dropdown",
- name: "MODE",
+ type: 'field_dropdown',
+ name: 'MODE',
options: [
- getDropdownOption("AWAIT"),
- getDropdownOption("START"),
- getDropdownOption("CREATE"),
+ getDropdownOption('AWAIT'),
+ getDropdownOption('START'),
+ getDropdownOption('CREATE'),
],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("animation"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('animation'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
registerBlockHandler(this, (changeEvent) => {
handleBlockCreateEvent(
this,
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "ANIMATION_GROUP",
+ 'ANIMATION_GROUP'
);
if (window.loadingCode) return;
@@ -855,160 +847,160 @@ export function defineAnimateBlocks() {
},
};
- Blockly.Blocks["control_animation_group"] = {
+ Blockly.Blocks['control_animation_group'] = {
init: function () {
this.jsonInit({
- type: "animation_group_control",
- message0: translate("control_animation_group"),
+ type: 'animation_group_control',
+ message0: translate('control_animation_group'),
args0: [
{
- type: "field_variable",
- name: "GROUP_NAME",
- variable: "animation1",
+ type: 'field_variable',
+ name: 'GROUP_NAME',
+ variable: 'animation1',
},
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: [
- getDropdownOption("play"),
- getDropdownOption("pause"),
- getDropdownOption("stop"),
+ getDropdownOption('play'),
+ getDropdownOption('pause'),
+ getDropdownOption('stop'),
],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("control_animation_group"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('control_animation_group'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["animate_from"] = {
+ Blockly.Blocks['animate_from'] = {
init: function () {
this.jsonInit({
- type: "animate_from",
- message0: translate("animate_from"),
+ type: 'animate_from',
+ message0: translate('animate_from'),
args0: [
{
- type: "field_variable",
- name: "GROUP_NAME",
- variable: "animation1",
+ type: 'field_variable',
+ name: 'GROUP_NAME',
+ variable: 'animation1',
},
{
- type: "input_value",
- name: "TIME",
- check: "Number",
+ type: 'input_value',
+ name: 'TIME',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("animate_from"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('animate_from'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["stop_animations"] = {
+ Blockly.Blocks['stop_animations'] = {
init: function () {
this.jsonInit({
- type: "stop_animations",
- message0: translate("stop_animations"),
+ type: 'stop_animations',
+ message0: translate('stop_animations'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("stop_animations"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('stop_animations'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["animation_name"] = {
+ Blockly.Blocks['animation_name'] = {
init: function () {
this.jsonInit({
- type: "animation_name",
- message0: "%1",
+ type: 'animation_name',
+ message0: '%1',
args0: [
{
- type: "field_dropdown",
- name: "ANIMATION_NAME",
+ type: 'field_dropdown',
+ name: 'ANIMATION_NAME',
options: animationNames(),
},
],
- output: "String",
- colour: categoryColours["Animate"],
- tooltip: getTooltip("play_animation"),
+ output: 'String',
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('play_animation'),
});
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["switch_animation"] = {
+ Blockly.Blocks['switch_animation'] = {
init: function () {
this.jsonInit({
- type: "switch_model_animation",
- message0: translate("switch_animation"),
+ type: 'switch_model_animation',
+ message0: translate('switch_animation'),
args0: [
{
- type: "input_value",
- name: "ANIMATION_NAME",
- check: "String",
+ type: 'input_value',
+ name: 'ANIMATION_NAME',
+ check: 'String',
},
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("switch_animation"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('switch_animation'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
- Blockly.Blocks["play_animation"] = {
+ Blockly.Blocks['play_animation'] = {
init: function () {
this.jsonInit({
- type: "play_model_animation_once",
- message0: translate("play_animation"),
+ type: 'play_model_animation_once',
+ message0: translate('play_animation'),
args0: [
{
- type: "input_value",
- name: "ANIMATION_NAME",
- check: "String",
+ type: 'input_value',
+ name: 'ANIMATION_NAME',
+ check: 'String',
},
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Animate"],
- tooltip: getTooltip("play_animation"),
+ colour: categoryColours['Animate'],
+ tooltip: getTooltip('play_animation'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("animate_blocks");
+ this.setStyle('animate_blocks');
},
};
}
diff --git a/blocks/base.js b/blocks/base.js
index 4acc16745..377f81b86 100644
--- a/blocks/base.js
+++ b/blocks/base.js
@@ -1,26 +1,26 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { translate, getTooltip } from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { translate, getTooltip } from '../main/translation.js';
-window.currentMesh = "object";
+window.currentMesh = 'object';
window.currentBlock = null;
export function defineBaseBlocks() {
- Blockly.Blocks["xyz"] = {
+ Blockly.Blocks['xyz'] = {
init: function () {
this.jsonInit({
- type: "xyz",
- message0: translate("xyz"),
+ type: 'xyz',
+ message0: translate('xyz'),
args0: [
- { type: "input_value", name: "X", check: "Number" },
- { type: "input_value", name: "Y", check: "Number" },
- { type: "input_value", name: "Z", check: "Number" },
+ { type: 'input_value', name: 'X', check: 'Number' },
+ { type: 'input_value', name: 'Y', check: 'Number' },
+ { type: 'input_value', name: 'Z', check: 'Number' },
],
inputsInline: true,
- output: "Vector",
- colour: categoryColours["Transform"],
- tooltip: getTooltip("xyz"),
- helpUrl: "",
+ output: 'Vector',
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('xyz'),
+ helpUrl: '',
});
},
};
diff --git a/blocks/blockIcons.js b/blocks/blockIcons.js
index 0bcd3b6ef..d1487179f 100644
--- a/blocks/blockIcons.js
+++ b/blocks/blockIcons.js
@@ -1,5 +1,5 @@
-import * as Blockly from "blockly";
-import { toolbox as toolboxDefinition } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { toolbox as toolboxDefinition } from '../toolbox.js';
// A purely decorative image field (block-type and category icons). The default
// FieldImage contributes an "empty" placeholder to the block's ARIA label when
@@ -7,12 +7,12 @@ import { toolbox as toolboxDefinition } from "../toolbox.js";
// the icon is silent to screen readers.
export class DecorativeFieldImage extends Blockly.FieldImage {
computeAriaLabel() {
- return "";
+ return '';
}
}
function buildSvgDataUri(svgContent) {
- return "data:image/svg+xml," + encodeURIComponent(svgContent);
+ return 'data:image/svg+xml,' + encodeURIComponent(svgContent);
}
/**
@@ -20,10 +20,9 @@ function buildSvgDataUri(svgContent) {
* Returns a value in [0, 1] (0 = black, 1 = white).
*/
function relativeLuminance(hex) {
- const clean = hex.replace("#", "");
+ const clean = hex.replace('#', '');
if (clean.length !== 6) return 0;
- const toLinear = (c) =>
- c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
+ const toLinear = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
const r = toLinear(parseInt(clean.slice(0, 2), 16) / 255);
const g = toLinear(parseInt(clean.slice(2, 4), 16) / 255);
const b = toLinear(parseInt(clean.slice(4, 6), 16) / 255);
@@ -31,52 +30,52 @@ function relativeLuminance(hex) {
}
export function getIconColorForBackground(hexColor) {
- if (!hexColor || typeof hexColor !== "string") return "white";
+ if (!hexColor || typeof hexColor !== 'string') return 'white';
const L = relativeLuminance(hexColor);
- return L > 0.179 ? "black" : "white";
+ return L > 0.179 ? 'black' : 'white';
}
export function makeIconDataUrl(viewBox, pathD, fillColor) {
return buildSvgDataUri(
- ` `,
+ ` `
);
}
function makePathIcon(viewBox, pathD, color) {
return buildSvgDataUri(
- ` `,
+ ` `
);
}
const ICON_PATHS = {
start: {
- viewBox: "0 0 384 512",
- d: "M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z",
+ viewBox: '0 0 384 512',
+ d: 'M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z',
},
repeat: {
- viewBox: "0 0 720 512",
- d: "M470.6 118.6c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S352 19.1 352 32l0 32-160 0C86 64 0 150 0 256 0 273.7 14.3 288 32 288s32-14.3 32-32c0-70.7 57.3-128 128-128l160 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64zM41.4 393.4c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9S160 492.9 160 480l0-32 160 0c106 0 192-86 192-192 0-17.7-14.3-32-32-32s-32 14.3-32 32c0 70.7-57.3 128-128 128l-160 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64z",
+ viewBox: '0 0 720 512',
+ d: 'M470.6 118.6c12.5-12.5 12.5-32.8 0-45.3l-64-64c-9.2-9.2-22.9-11.9-34.9-6.9S352 19.1 352 32l0 32-160 0C86 64 0 150 0 256 0 273.7 14.3 288 32 288s32-14.3 32-32c0-70.7 57.3-128 128-128l160 0 0 32c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l64-64zM41.4 393.4c-12.5 12.5-12.5 32.8 0 45.3l64 64c9.2 9.2 22.9 11.9 34.9 6.9S160 492.9 160 480l0-32 160 0c106 0 192-86 192-192 0-17.7-14.3-32-32-32s-32 14.3-32 32c0 70.7-57.3 128-128 128l-160 0 0-32c0-12.9-7.8-24.6-19.8-29.6s-25.7-2.2-34.9 6.9l-64 64z',
},
click: {
- viewBox: "0 0 448 512",
- d: "M429.6 92.1c4.9-11.9 2.1-25.6-7-34.7s-22.8-11.9-34.7-7l-352 144c-14.2 5.8-22.2 20.8-19.3 35.8s16.1 25.8 31.4 25.8l176 0 0 176c0 15.3 10.8 28.4 25.8 31.4s30-5.1 35.8-19.3l144-352z",
+ viewBox: '0 0 448 512',
+ d: 'M429.6 92.1c4.9-11.9 2.1-25.6-7-34.7s-22.8-11.9-34.7-7l-352 144c-14.2 5.8-22.2 20.8-19.3 35.8s16.1 25.8 31.4 25.8l176 0 0 176c0 15.3 10.8 28.4 25.8 31.4s30-5.1 35.8-19.3l144-352z',
},
collision: {
- viewBox: "0 0 448 512",
- d: "M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288H175.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3s-16.6-20.7-30-20.7H272.5L349.4 44.6z",
+ viewBox: '0 0 448 512',
+ d: 'M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288H175.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3s-16.6-20.7-30-20.7H272.5L349.4 44.6z',
},
keyboard: {
- viewBox: "0 0 576 512",
- d: "M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm16 64h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zm0 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm16 160c-8.8 0-16-7.2-16-16v-32h224v32c0 8.8-7.2 16-16 16H96zm176-160c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H288c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H288c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM160 144c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V144zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm176 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H352c-8.8 0-16-7.2-16-16V320c0-8.8 7.2-16 16-16zm16-96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zm80 96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16z",
+ viewBox: '0 0 576 512',
+ d: 'M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm16 64h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zm0 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm16 160c-8.8 0-16-7.2-16-16v-32h224v32c0 8.8-7.2 16-16 16H96zm176-160c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H288c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H288c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM160 144c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V144zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm176 96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H352c-8.8 0-16-7.2-16-16V320c0-8.8 7.2-16 16-16zm16-96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zm80 96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V240zm16-96h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16z',
},
press: {
- viewBox: "0 0 640 512",
- d: "M256 0a256 256 0 1 0 0 512 256 256 0 1 0 0-512zM244.7 387.3l-104-104c-4.6-4.6-5.9-11.5-3.5-17.4s8.3-9.9 14.8-9.9l56 0 0-96c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32l0 96 56 0c6.5 0 12.3 3.9 14.8 9.9s1.1 12.9-3.5 17.4l-104 104c-6.2 6.2-16.4 6.2-22.6 0z",
+ viewBox: '0 0 640 512',
+ d: 'M256 0a256 256 0 1 0 0 512 256 256 0 1 0 0-512zM244.7 387.3l-104-104c-4.6-4.6-5.9-11.5-3.5-17.4s8.3-9.9 14.8-9.9l56 0 0-96c0-17.7 14.3-32 32-32l32 0c17.7 0 32 14.3 32 32l0 96 56 0c6.5 0 12.3 3.9 14.8 9.9s1.1 12.9-3.5 17.4l-104 104c-6.2 6.2-16.4 6.2-22.6 0z',
},
event: {
- viewBox: "0 0 640 640",
- d: "M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z",
+ viewBox: '0 0 640 640',
+ d: 'M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z',
},
};
@@ -87,21 +86,13 @@ export function makeRepeatIcon(color) {
return makePathIcon(ICON_PATHS.repeat.viewBox, ICON_PATHS.repeat.d, color);
}
export function makeClickIcon(color) {
- return makePathIcon(
- ICON_PATHS.click.viewBox,
- ICON_PATHS.click.d,
- color,
- );
+ return makePathIcon(ICON_PATHS.click.viewBox, ICON_PATHS.click.d, color);
}
export function makeCollisionIcon(color) {
return makePathIcon(ICON_PATHS.collision.viewBox, ICON_PATHS.collision.d, color);
}
export function makeKeyboardIcon(color) {
- return makePathIcon(
- ICON_PATHS.keyboard.viewBox,
- ICON_PATHS.keyboard.d,
- color,
- );
+ return makePathIcon(ICON_PATHS.keyboard.viewBox, ICON_PATHS.keyboard.d, color);
}
export function makePressIcon(color) {
return makePathIcon(ICON_PATHS.press.viewBox, ICON_PATHS.press.d, color);
@@ -110,7 +101,7 @@ export function makeOnEventIcon(color) {
return makePathIcon(ICON_PATHS.event.viewBox, ICON_PATHS.event.d, color);
}
-let _currentIconColor = "white";
+let _currentIconColor = 'white';
export function setCurrentIconColor(color) {
_currentIconColor = color;
@@ -120,77 +111,77 @@ export function getCurrentIconColor() {
return _currentIconColor;
}
-export const startIcon = makeStartIcon("white");
-export const repeatIcon = makeRepeatIcon("white");
-export const clickIcon = makeClickIcon("white");
-export const collisionIcon = makeCollisionIcon("white");
-export const keyboardIcon = makeKeyboardIcon("white");
-export const pressIcon = makePressIcon("white");
-export const eventIcon = makeOnEventIcon("white");
+export const startIcon = makeStartIcon('white');
+export const repeatIcon = makeRepeatIcon('white');
+export const clickIcon = makeClickIcon('white');
+export const collisionIcon = makeCollisionIcon('white');
+export const keyboardIcon = makeKeyboardIcon('white');
+export const pressIcon = makePressIcon('white');
+export const eventIcon = makeOnEventIcon('white');
-export const BLOCK_ICON_FIELD_NAME = "BLOCK_ICON";
-export const TOGGLE_BUTTON_FIELD_NAME = "TOGGLE_BUTTON";
-const LOW_VISION_ICON_FIELD_NAME = "LOW_VISION_CATEGORY_ICON";
-const LOW_VISION_BAR_FIELD_NAME = "LOW_VISION_CATEGORY_BAR";
+export const BLOCK_ICON_FIELD_NAME = 'BLOCK_ICON';
+export const TOGGLE_BUTTON_FIELD_NAME = 'TOGGLE_BUTTON';
+const LOW_VISION_ICON_FIELD_NAME = 'LOW_VISION_CATEGORY_ICON';
+const LOW_VISION_BAR_FIELD_NAME = 'LOW_VISION_CATEGORY_BAR';
const CATEGORY_ICON_PATH_BY_STYLE = {
- events_blocks: "../images/events.svg",
- scene_blocks: "../images/scene.svg",
- scene_meshes_blocks: "../images/meshes.svg",
- scene_xr_blocks: "../images/xr.svg",
- scene_lights_blocks: "../images/lights.svg",
- scene_camera_blocks: "../images/camera.svg",
- transform_blocks: "../images/motion.svg",
- transform_physics_blocks: "../images/physics.svg",
- transform_connect_blocks: "../images/connect.svg",
- transform_combine_blocks: "../images/combine.svg",
- animate_blocks: "../images/animate.svg",
- animate_keyframe_blocks: "../images/keyframe.svg",
- materials_blocks: "../images/looks.svg",
- sound_blocks: "../images/sound.svg",
- sensing_blocks: "../images/sensing.svg",
- snippets_blocks: "../images/snippets.svg",
- snippets_physics_blocks: "../images/physics.svg",
- snippets_arrows_blocks: "../images/arrows.svg",
- control_blocks: "../images/control.svg",
- logic_blocks: "../images/conditions.svg",
- variable_blocks: "../images/variables.svg",
- variables_blocks: "../images/variables.svg",
- text_blocks: "../images/text.svg",
- list_blocks: "../images/lists.svg",
- lists_blocks: "../images/lists.svg",
- math_blocks: "../images/math.svg",
- procedure_blocks: "../images/functions.svg",
+ events_blocks: '../images/events.svg',
+ scene_blocks: '../images/scene.svg',
+ scene_meshes_blocks: '../images/meshes.svg',
+ scene_xr_blocks: '../images/xr.svg',
+ scene_lights_blocks: '../images/lights.svg',
+ scene_camera_blocks: '../images/camera.svg',
+ transform_blocks: '../images/motion.svg',
+ transform_physics_blocks: '../images/physics.svg',
+ transform_connect_blocks: '../images/connect.svg',
+ transform_combine_blocks: '../images/combine.svg',
+ animate_blocks: '../images/animate.svg',
+ animate_keyframe_blocks: '../images/keyframe.svg',
+ materials_blocks: '../images/looks.svg',
+ sound_blocks: '../images/sound.svg',
+ sensing_blocks: '../images/sensing.svg',
+ snippets_blocks: '../images/snippets.svg',
+ snippets_physics_blocks: '../images/physics.svg',
+ snippets_arrows_blocks: '../images/arrows.svg',
+ control_blocks: '../images/control.svg',
+ logic_blocks: '../images/conditions.svg',
+ variable_blocks: '../images/variables.svg',
+ variables_blocks: '../images/variables.svg',
+ text_blocks: '../images/text.svg',
+ list_blocks: '../images/lists.svg',
+ lists_blocks: '../images/lists.svg',
+ math_blocks: '../images/math.svg',
+ procedure_blocks: '../images/functions.svg',
};
const CATEGORY_ACCENT_BY_STYLE = {
- events_blocks: "#d99d98",
- scene_blocks: "#bed998",
- scene_meshes_blocks: "#bed998",
- scene_xr_blocks: "#bed998",
- scene_lights_blocks: "#bed998",
- scene_camera_blocks: "#bed998",
- transform_blocks: "#d3d998",
- transform_physics_blocks: "#d3d998",
- transform_connect_blocks: "#d3d998",
- transform_combine_blocks: "#d3d998",
- animate_blocks: "#d9c898",
- animate_keyframe_blocks: "#d9c898",
- materials_blocks: "#c398d9",
- sound_blocks: "#d9b398",
- sensing_blocks: "#98d9d9",
- snippets_blocks: "#98c3d9",
- snippets_physics_blocks: "#98c3d9",
- snippets_arrows_blocks: "#98c3d9",
- control_blocks: "#98d998",
- logic_blocks: "#98b8d9",
- variable_blocks: "#d998b8",
- variables_blocks: "#d998b8",
- text_blocks: "#98d9c3",
- list_blocks: "#ad98d9",
- lists_blocks: "#ad98d9",
- math_blocks: "#98a3d9",
- procedure_blocks: "#ce98d9",
+ events_blocks: '#d99d98',
+ scene_blocks: '#bed998',
+ scene_meshes_blocks: '#bed998',
+ scene_xr_blocks: '#bed998',
+ scene_lights_blocks: '#bed998',
+ scene_camera_blocks: '#bed998',
+ transform_blocks: '#d3d998',
+ transform_physics_blocks: '#d3d998',
+ transform_connect_blocks: '#d3d998',
+ transform_combine_blocks: '#d3d998',
+ animate_blocks: '#d9c898',
+ animate_keyframe_blocks: '#d9c898',
+ materials_blocks: '#c398d9',
+ sound_blocks: '#d9b398',
+ sensing_blocks: '#98d9d9',
+ snippets_blocks: '#98c3d9',
+ snippets_physics_blocks: '#98c3d9',
+ snippets_arrows_blocks: '#98c3d9',
+ control_blocks: '#98d998',
+ logic_blocks: '#98b8d9',
+ variable_blocks: '#d998b8',
+ variables_blocks: '#d998b8',
+ text_blocks: '#98d9c3',
+ list_blocks: '#ad98d9',
+ lists_blocks: '#ad98d9',
+ math_blocks: '#98a3d9',
+ procedure_blocks: '#ce98d9',
};
const LOW_VISION_ICON_DATA_URL_BY_STYLE = new Map();
const LOW_VISION_ICON_SVG_BY_STYLE = new Map();
@@ -198,43 +189,43 @@ const LOW_VISION_ICON_LOAD_PROMISE_BY_STYLE = new Map();
const LOW_VISION_REFRESH_PENDING_BY_WORKSPACE = new WeakMap();
const LOW_VISION_STYLE_BY_ICON_FILE = {
- "events.svg": "events_blocks",
- "scene.svg": "scene_blocks",
- "meshes.svg": "scene_meshes_blocks",
- "xr.svg": "scene_xr_blocks",
- "lights.svg": "scene_lights_blocks",
- "camera.svg": "scene_camera_blocks",
- "motion.svg": "transform_blocks",
- "physics.svg": "transform_physics_blocks",
- "connect.svg": "transform_connect_blocks",
- "combine.svg": "transform_combine_blocks",
- "animate.svg": "animate_blocks",
- "keyframe.svg": "animate_keyframe_blocks",
- "looks.svg": "materials_blocks",
- "sound.svg": "sound_blocks",
- "sensing.svg": "sensing_blocks",
- "snippets.svg": "snippets_blocks",
- "arrows.svg": "snippets_arrows_blocks",
- "control.svg": "control_blocks",
- "conditions.svg": "logic_blocks",
- "variables.svg": "variable_blocks",
- "data.svg": "variable_blocks",
- "text.svg": "text_blocks",
- "lists.svg": "list_blocks",
- "math.svg": "math_blocks",
- "functions.svg": "procedure_blocks",
+ 'events.svg': 'events_blocks',
+ 'scene.svg': 'scene_blocks',
+ 'meshes.svg': 'scene_meshes_blocks',
+ 'xr.svg': 'scene_xr_blocks',
+ 'lights.svg': 'scene_lights_blocks',
+ 'camera.svg': 'scene_camera_blocks',
+ 'motion.svg': 'transform_blocks',
+ 'physics.svg': 'transform_physics_blocks',
+ 'connect.svg': 'transform_connect_blocks',
+ 'combine.svg': 'transform_combine_blocks',
+ 'animate.svg': 'animate_blocks',
+ 'keyframe.svg': 'animate_keyframe_blocks',
+ 'looks.svg': 'materials_blocks',
+ 'sound.svg': 'sound_blocks',
+ 'sensing.svg': 'sensing_blocks',
+ 'snippets.svg': 'snippets_blocks',
+ 'arrows.svg': 'snippets_arrows_blocks',
+ 'control.svg': 'control_blocks',
+ 'conditions.svg': 'logic_blocks',
+ 'variables.svg': 'variable_blocks',
+ 'data.svg': 'variable_blocks',
+ 'text.svg': 'text_blocks',
+ 'lists.svg': 'list_blocks',
+ 'math.svg': 'math_blocks',
+ 'functions.svg': 'procedure_blocks',
};
const LOW_VISION_STYLE_BY_BLOCK_TYPE = buildLowVisionStyleByBlockType();
const LOW_VISION_SUBCATEGORY_STYLE_OVERRIDES = new Set([
- "scene_blocks",
- "transform_blocks",
- "animate_blocks",
- "snippets_blocks",
+ 'scene_blocks',
+ 'transform_blocks',
+ 'animate_blocks',
+ 'snippets_blocks',
]);
export function makeInlineIcon(color) {
const svg = ` `;
- return "data:image/svg+xml," + encodeURIComponent(svg);
+ return 'data:image/svg+xml,' + encodeURIComponent(svg);
}
const BLOCK_ICON_MAKERS = {
@@ -251,7 +242,7 @@ export function updateBlockIcons(workspace, iconColor) {
if (!workspace) return;
const blocks = getWorkspaceAndFlyoutBlocks(workspace);
for (const block of blocks) {
- if (!block || typeof block.getField !== "function") continue;
+ if (!block || typeof block.getField !== 'function') continue;
const iconField = block.getField(BLOCK_ICON_FIELD_NAME);
if (iconField) {
const maker = BLOCK_ICON_MAKERS[block.type];
@@ -267,11 +258,11 @@ export function updateAllBlockIcons(workspace, iconColor) {
}
function getBlockStyleName(block) {
- if (!block) return "";
- if (typeof block.getStyleName === "function") {
- return block.getStyleName() || "";
+ if (!block) return '';
+ if (typeof block.getStyleName === 'function') {
+ return block.getStyleName() || '';
}
- return block.styleName_ || "";
+ return block.styleName_ || '';
}
function scheduleLowVisionIconRefresh(workspace, styleName) {
@@ -307,42 +298,41 @@ function getLowVisionStyleNameForBlock(block) {
if (explicitSubcategoryStyle) return explicitSubcategoryStyle;
if (styleName) return styleName;
- const blockType = block?.type || "";
- if (blockType.startsWith("lists_")) return "list_blocks";
- if (blockType.startsWith("variables_")) return "variable_blocks";
+ const blockType = block?.type || '';
+ if (blockType.startsWith('lists_')) return 'list_blocks';
+ if (blockType.startsWith('variables_')) return 'variable_blocks';
return styleName;
}
function withSvgFill(svg, fillColor) {
- if (!svg || !fillColor || !svg.includes("*{fill:${fillColor} !important;}`;
- if (svg.includes(" ")) {
+ if (svg.includes('')) {
return svg.replace(/]*)>/, `${fillStyle}`);
}
return svg;
}
function getStyleNameFromIconPath(iconPath) {
- const iconName = (iconPath || "").toLowerCase().split("/").pop();
- return LOW_VISION_STYLE_BY_ICON_FILE[iconName] || "";
+ const iconName = (iconPath || '').toLowerCase().split('/').pop();
+ return LOW_VISION_STYLE_BY_ICON_FILE[iconName] || '';
}
function buildLowVisionStyleByBlockType() {
const byType = {};
- const walkToolbox = (items, inheritedStyle = "") => {
+ const walkToolbox = (items, inheritedStyle = '') => {
if (!Array.isArray(items)) return;
for (const item of items) {
- if (!item || typeof item !== "object") continue;
+ if (!item || typeof item !== 'object') continue;
- if (item.kind === "category") {
- const categoryStyle =
- getStyleNameFromIconPath(item.icon) || inheritedStyle;
+ if (item.kind === 'category') {
+ const categoryStyle = getStyleNameFromIconPath(item.icon) || inheritedStyle;
walkToolbox(item.contents, categoryStyle);
continue;
}
- if (item.kind === "block" && item.type && inheritedStyle) {
+ if (item.kind === 'block' && item.type && inheritedStyle) {
byType[item.type] = inheritedStyle;
}
}
@@ -359,10 +349,10 @@ export function makeLowVisionCategoryIconDataUrl(styleName) {
const iconSvg = LOW_VISION_ICON_SVG_BY_STYLE.get(styleName);
if (!iconSvg) {
loadLowVisionCategoryIconSvg(styleName);
- return "";
+ return '';
}
const accent = CATEGORY_ACCENT_BY_STYLE[styleName];
- if (!iconSvg || !accent) return "";
+ if (!iconSvg || !accent) return '';
const dataUrl = buildSvgDataUri(withSvgFill(iconSvg, accent));
LOW_VISION_ICON_DATA_URL_BY_STYLE.set(styleName, dataUrl);
return dataUrl;
@@ -376,26 +366,26 @@ function loadLowVisionCategoryIconSvg(styleName) {
return LOW_VISION_ICON_LOAD_PROMISE_BY_STYLE.get(styleName);
}
const iconPath = CATEGORY_ICON_PATH_BY_STYLE[styleName];
- if (!iconPath) return Promise.resolve("");
+ if (!iconPath) return Promise.resolve('');
const iconUrl = new URL(iconPath, import.meta.url).href;
const loadPromise = fetch(iconUrl)
- .then((response) => (response.ok ? response.text() : ""))
+ .then((response) => (response.ok ? response.text() : ''))
.then((svgText) => {
- LOW_VISION_ICON_SVG_BY_STYLE.set(styleName, svgText || "");
+ LOW_VISION_ICON_SVG_BY_STYLE.set(styleName, svgText || '');
LOW_VISION_ICON_LOAD_PROMISE_BY_STYLE.delete(styleName);
- return svgText || "";
+ return svgText || '';
})
.catch(() => {
LOW_VISION_ICON_LOAD_PROMISE_BY_STYLE.delete(styleName);
- return "";
+ return '';
});
LOW_VISION_ICON_LOAD_PROMISE_BY_STYLE.set(styleName, loadPromise);
return loadPromise;
}
export function preloadLowVisionCategoryIcons() {
- if (typeof Image === "undefined" || typeof fetch !== "function") {
+ if (typeof Image === 'undefined' || typeof fetch !== 'function') {
return Promise.resolve();
}
const pendingDecodes = [];
@@ -405,12 +395,12 @@ export function preloadLowVisionCategoryIcons() {
const dataUrl = makeLowVisionCategoryIconDataUrl(styleName);
if (!dataUrl) return;
const img = new Image();
- img.decoding = "sync";
+ img.decoding = 'sync';
img.src = dataUrl;
- if (typeof img.decode === "function") {
+ if (typeof img.decode === 'function') {
return img.decode().catch(() => {});
}
- }),
+ })
);
}
return Promise.allSettled(pendingDecodes);
@@ -420,11 +410,7 @@ export function applyLowVisionCategoryIcons(workspace) {
if (!workspace) return;
const blocks = getWorkspaceAndFlyoutBlocks(workspace);
for (const block of blocks) {
- if (
- !block ||
- typeof block.getField !== "function" ||
- !Array.isArray(block.inputList)
- ) {
+ if (!block || typeof block.getField !== 'function' || !Array.isArray(block.inputList)) {
continue;
}
const styleName = getLowVisionStyleNameForBlock(block);
@@ -439,8 +425,8 @@ export function applyLowVisionCategoryIcons(workspace) {
if (!block.getField(LOW_VISION_ICON_FIELD_NAME)) {
firstInput.insertFieldAt(
0,
- new DecorativeFieldImage(iconPath, 18, 18, "", null),
- LOW_VISION_ICON_FIELD_NAME,
+ new DecorativeFieldImage(iconPath, 18, 18, '', null),
+ LOW_VISION_ICON_FIELD_NAME
);
}
}
@@ -450,11 +436,11 @@ export function clearLowVisionCategoryIcons(workspace) {
if (!workspace) return;
const blocks = getWorkspaceAndFlyoutBlocks(workspace);
for (const block of blocks) {
- if (!block || typeof block.getField !== "function") {
+ if (!block || typeof block.getField !== 'function') {
continue;
}
const firstInput = block.inputList?.[0];
- if (!firstInput || typeof firstInput.removeField !== "function") continue;
+ if (!firstInput || typeof firstInput.removeField !== 'function') continue;
if (block.getField(LOW_VISION_BAR_FIELD_NAME))
firstInput.removeField(LOW_VISION_BAR_FIELD_NAME, true);
if (block.getField(LOW_VISION_ICON_FIELD_NAME))
@@ -481,25 +467,25 @@ const MICROBIT_GLYPHS = {
plug: {
width: 384,
height: 512,
- d: "M96 0C78.3 0 64 14.3 64 32l0 96 64 0 0-96c0-17.7-14.3-32-32-32zM288 0c-17.7 0-32 14.3-32 32l0 96 64 0 0-96c0-17.7-14.3-32-32-32zM32 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l0 32c0 77.4 55 142 128 156.8l0 67.2c0 17.7 14.3 32 32 32s32-14.3 32-32l0-67.2C297 398 352 333.4 352 256l0-32c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 160z",
+ d: 'M96 0C78.3 0 64 14.3 64 32l0 96 64 0 0-96c0-17.7-14.3-32-32-32zM288 0c-17.7 0-32 14.3-32 32l0 96 64 0 0-96c0-17.7-14.3-32-32-32zM32 160c-17.7 0-32 14.3-32 32s14.3 32 32 32l0 32c0 77.4 55 142 128 156.8l0 67.2c0 17.7 14.3 32 32 32s32-14.3 32-32l0-67.2C297 398 352 333.4 352 256l0-32c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 160z',
},
waves: {
width: 640,
height: 512,
- d: "M54.2 202.9C123.2 136.7 216.8 96 320 96s196.8 40.7 265.8 106.9c12.8 12.2 33 11.8 45.2-.9s11.8-33-.9-45.2C549.7 79.5 440.4 32 320 32S90.3 79.5 9.8 156.7C-2.9 169-3.3 189.2 8.9 202s32.5 13.2 45.2 .9zM320 256c56.8 0 108.6 21.1 148.2 56c13.3 11.7 33.5 10.4 45.2-2.8s10.4-33.5-2.8-45.2C459.8 219.2 393 192 320 192s-139.8 27.2-190.5 72c-13.3 11.7-14.5 32-2.8 45.2s32 14.5 45.2 2.8c39.5-34.9 91.3-56 148.2-56zm64 160a64 64 0 1 0 -128 0 64 64 0 1 0 128 0z",
+ d: 'M54.2 202.9C123.2 136.7 216.8 96 320 96s196.8 40.7 265.8 106.9c12.8 12.2 33 11.8 45.2-.9s11.8-33-.9-45.2C549.7 79.5 440.4 32 320 32S90.3 79.5 9.8 156.7C-2.9 169-3.3 189.2 8.9 202s32.5 13.2 45.2 .9zM320 256c56.8 0 108.6 21.1 148.2 56c13.3 11.7 33.5 10.4 45.2-2.8s10.4-33.5-2.8-45.2C459.8 219.2 393 192 320 192s-139.8 27.2-190.5 72c-13.3 11.7-14.5 32-2.8 45.2s32 14.5 45.2 2.8c39.5-34.9 91.3-56 148.2-56zm64 160a64 64 0 1 0 -128 0 64 64 0 1 0 128 0z',
},
hourglass: {
width: 384,
height: 512,
- d: "M32 0C14.3 0 0 14.3 0 32S14.3 64 32 64l0 11c0 42.4 16.9 83.1 46.9 113.1L146.7 256 78.9 323.9C48.9 353.9 32 394.6 32 437l0 11c-17.7 0-32 14.3-32 32s14.3 32 32 32l32 0 256 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-11c0-42.4-16.9-83.1-46.9-113.1L237.3 256l67.9-67.9c30-30 46.9-70.7 46.9-113.1l0-11c17.7 0 32-14.3 32-32s-14.3-32-32-32L320 0 64 0 32 0zM96 75l0-11 192 0 0 11c0 25.5-10.1 49.9-28.1 67.9L192 210.7l-67.9-67.9C106.1 124.9 96 100.5 96 75z",
+ d: 'M32 0C14.3 0 0 14.3 0 32S14.3 64 32 64l0 11c0 42.4 16.9 83.1 46.9 113.1L146.7 256 78.9 323.9C48.9 353.9 32 394.6 32 437l0 11c-17.7 0-32 14.3-32 32s14.3 32 32 32l32 0 256 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-11c0-42.4-16.9-83.1-46.9-113.1L237.3 256l67.9-67.9c30-30 46.9-70.7 46.9-113.1l0-11c17.7 0 32-14.3 32-32s-14.3-32-32-32L320 0 64 0 32 0zM96 75l0-11 192 0 0 11c0 25.5-10.1 49.9-28.1 67.9L192 210.7l-67.9-67.9C106.1 124.9 96 100.5 96 75z',
},
};
const MICROBIT_STATUS_SPECS = {
- unbound: { color: "#757575", glyph: "plug" },
- busy: { color: "#e6a817", glyph: "hourglass" },
- tethered: { color: "#2e8b57", glyph: "plug" },
- radio: { color: "#2e8b57", glyph: "waves" },
+ unbound: { color: '#757575', glyph: 'plug' },
+ busy: { color: '#e6a817', glyph: 'hourglass' },
+ tethered: { color: '#2e8b57', glyph: 'plug' },
+ radio: { color: '#2e8b57', glyph: 'waves' },
};
export function makeMicrobitStatusIcon(status) {
diff --git a/blocks/blocks.js b/blocks/blocks.js
index e43d48fc7..08a8e928e 100644
--- a/blocks/blocks.js
+++ b/blocks/blocks.js
@@ -1,9 +1,9 @@
-import * as Blockly from "blockly";
+import * as Blockly from 'blockly';
//import "@blockly/block-plus-minus";
-import * as BlockDynamicConnection from "@blockly/block-dynamic-connection";
-import { toolbox, categoryColours } from "../toolbox.js";
-import { translate, getTooltip } from "../main/translation.js";
-import { flock } from "../flock.js";
+import * as BlockDynamicConnection from '@blockly/block-dynamic-connection';
+import { toolbox, categoryColours } from '../toolbox.js';
+import { translate, getTooltip } from '../main/translation.js';
+import { flock } from '../flock.js';
import {
deleteMeshFromBlock,
@@ -12,18 +12,18 @@ import {
getActiveSceneControllerBlockId,
clearSkyMesh,
setClearSkyToBlack,
-} from "../ui/blockmesh.js";
-import { FieldColour, registerFieldColour } from "@blockly/field-colour";
-import { createThemeConfig } from "../main/themes.js";
-import { makeInlineIcon, TOGGLE_BUTTON_FIELD_NAME } from "./blockIcons.js";
+} from '../ui/blockmesh.js';
+import { FieldColour, registerFieldColour } from '@blockly/field-colour';
+import { createThemeConfig } from '../main/themes.js';
+import { makeInlineIcon, TOGGLE_BUTTON_FIELD_NAME } from './blockIcons.js';
registerFieldColour();
const normaliseHexColour = (value) => {
- if (typeof value !== "string") return "";
+ if (typeof value !== 'string') return '';
let hex = value.trim().toLowerCase();
- if (!hex) return "";
- if (!hex.startsWith("#")) hex = `#${hex}`;
+ if (!hex) return '';
+ if (!hex.startsWith('#')) hex = `#${hex}`;
if (/^#[\da-f]{3}$/.test(hex)) {
hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
}
@@ -31,7 +31,7 @@ const normaliseHexColour = (value) => {
};
// When using keyboard navigation, when the colour in a block isn't one of those in the grid this makes the editor start with the container selected so you can use arrow keys to navigate to the swatches.
-const flockFocusPatchKey = Symbol.for("flock.fieldColourFocusPatch");
+const flockFocusPatchKey = Symbol.for('flock.fieldColourFocusPatch');
const fieldColourPrototype = FieldColour.prototype;
if (!fieldColourPrototype[flockFocusPatchKey]) {
const originalShowEditor = fieldColourPrototype.showEditor_;
@@ -55,7 +55,7 @@ if (!fieldColourPrototype[flockFocusPatchKey]) {
// the field manually as a stopgap until upstream ships a v13 fix.
const xy = this.getAbsoluteXY_();
const size = this.getSize();
- const dd = document.querySelector(".blocklyDropDownDiv");
+ const dd = document.querySelector('.blocklyDropDownDiv');
const parent = dd?.parentElement;
if (dd && parent && xy) {
const parentRect = parent.getBoundingClientRect();
@@ -67,15 +67,13 @@ if (!fieldColourPrototype[flockFocusPatchKey]) {
if (!currentValue) return;
const hasMatchingSwatch = this.getOptions?.(false)?.some(
- (option) => normaliseHexColour(option?.[1]) === currentValue,
+ (option) => normaliseHexColour(option?.[1]) === currentValue
);
if (hasMatchingSwatch) return;
const openedWithKeyboard = e === undefined || e instanceof KeyboardEvent;
if (!openedWithKeyboard) return;
- Blockly.DropDownDiv.getContentDiv()
- .querySelector(".blocklyFieldGrid")
- ?.focus();
+ Blockly.DropDownDiv.getContentDiv().querySelector('.blocklyFieldGrid')?.focus();
};
}
@@ -86,10 +84,9 @@ if (!fieldColourPrototype[flockFocusPatchKey]) {
// loading a saved workspace) and our own refreshReporterAriaLabels both call
// recomputeAriaContext on fields that may not be rendered yet. Guard the same
// way applyColour() already does upstream.
-const flockAriaPatchKey = Symbol.for("flock.fieldColourAriaPatch");
+const flockAriaPatchKey = Symbol.for('flock.fieldColourAriaPatch');
if (!fieldColourPrototype[flockAriaPatchKey]) {
- const originalRecomputeAriaContext =
- fieldColourPrototype.recomputeAriaContext;
+ const originalRecomputeAriaContext = fieldColourPrototype.recomputeAriaContext;
fieldColourPrototype.recomputeAriaContext = function () {
if (!this.fieldGroup_) return true;
return originalRecomputeAriaContext.call(this);
@@ -102,12 +99,12 @@ if (!fieldColourPrototype[flockAriaPatchKey]) {
// field's SVG click target. The result is stored as a flag on the picker so
// that excludeFromClose can let the click through only for colour-field hits.
document.addEventListener(
- "pointerdown",
+ 'pointerdown',
(e) => {
const picker = window.flockColorPicker;
if (!picker?.isOpen) return;
- const blocklyDiv = document.getElementById("blocklyDiv");
+ const blocklyDiv = document.getElementById('blocklyDiv');
if (!blocklyDiv?.contains(e.target)) return;
const workspace = Blockly.getMainWorkspace();
@@ -121,11 +118,11 @@ document.addEventListener(
if (!(field instanceof FieldColour)) return false;
const ct = field.clickTarget_ ?? field.fieldGroup_;
return ct && (ct === e.target || ct.contains(e.target));
- }),
- ),
+ })
+ )
);
},
- true,
+ true
);
export let nextVariableIndexes = Object.create(null);
@@ -184,10 +181,10 @@ export function registerBlockHandler(block, handler) {
blockHandlerRegistry.set(block.id, handler);
}
-export const inlineIcon = makeInlineIcon("white");
+export const inlineIcon = makeInlineIcon('white');
export function getHelpUrlFor(_blockType) {
- return "https://hub.flockxr.com";
+ return 'https://hub.flockxr.com';
}
// Text of the static labels immediately preceding an input on its row (e.g.
@@ -199,12 +196,12 @@ function inputFieldRowLabel(input) {
const labels = [];
for (let i = fields.length - 1; i >= 0; i--) {
if (!(fields[i] instanceof Blockly.FieldLabel)) break;
- labels.unshift(fields[i].getText ? fields[i].getText() : "");
+ labels.unshift(fields[i].getText ? fields[i].getText() : '');
}
- const text = labels.join(" ").replace(/\s+/g, " ").trim();
+ const text = labels.join(' ').replace(/\s+/g, ' ').trim();
// Strip surrounding punctuation/separators so "x:" reads as "x" and
// "| skin:" reads as "skin" (messages separate fields with "|").
- return text.replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "");
+ return text.replace(/^[^\p{L}\p{N}]+/u, '').replace(/[^\p{L}\p{N}]+$/u, '');
}
// Derived field-row labels that add no useful per-input context: connectors and
@@ -212,10 +209,31 @@ function inputFieldRowLabel(input) {
// after the input it describes, so "for %2 seconds %3" would otherwise label the
// %3 input "seconds". Meaningful nouns (hair, skin, x, size, color) are kept.
const SKIP_DERIVED_LABELS = new Set([
- "for", "to", "on", "with", "at", "by", "from", "of", "in", "into", "and",
- "or", "the", "a", "an",
- "seconds", "second", "s", "ms", "milliseconds", "degrees", "degree", "deg",
- "times", "ms.",
+ 'for',
+ 'to',
+ 'on',
+ 'with',
+ 'at',
+ 'by',
+ 'from',
+ 'of',
+ 'in',
+ 'into',
+ 'and',
+ 'or',
+ 'the',
+ 'a',
+ 'an',
+ 'seconds',
+ 'second',
+ 's',
+ 'ms',
+ 'milliseconds',
+ 'degrees',
+ 'degree',
+ 'deg',
+ 'times',
+ 'ms.',
]);
// Gives value/statement inputs an ARIA label so screen readers announce the
@@ -247,7 +265,7 @@ export function applyInputAriaLabels(block, overrides) {
label = derived;
}
}
- if (label && typeof input.setAriaLabelProvider === "function") {
+ if (label && typeof input.setAriaLabelProvider === 'function') {
input.setAriaLabelProvider(label);
}
}
@@ -259,7 +277,7 @@ export function applyInputAriaLabels(block, overrides) {
// into a slot. Recompute affected fields so e.g. a number moved into scale's X
// slot updates from "number" to "x, number". Called from a workspace listener.
export function refreshReporterAriaLabels(block) {
- if (!block || typeof block.getDescendants !== "function") return;
+ if (!block || typeof block.getDescendants !== 'function') return;
for (const descendant of block.getDescendants(false)) {
if (descendant.isSimpleReporter?.()) {
descendant.getFullBlockField?.()?.recomputeAriaContext?.();
@@ -270,13 +288,13 @@ export function refreshReporterAriaLabels(block) {
// Shared utility to add the toggle button to a block
export function addToggleButton(block) {
const toggleButton = new Blockly.FieldImage(
- makeInlineIcon("white"),
+ makeInlineIcon('white'),
30,
30,
- "toggle inline blocks",
+ 'toggle inline blocks',
() => {
block.toggleDoBlock();
- },
+ }
);
block
@@ -287,14 +305,14 @@ export function addToggleButton(block) {
// Shared utility for the mutationToDom function
export function mutationToDom(block) {
- const container = document.createElement("mutation");
- container.setAttribute("inline", block.isInline);
+ const container = document.createElement('mutation');
+ container.setAttribute('inline', block.isInline);
return container;
}
// Shared utility for the domToMutation function
export function domToMutation(block, xmlElement) {
- const isInline = xmlElement.getAttribute("inline") === "true";
+ const isInline = xmlElement.getAttribute('inline') === 'true';
block.updateShape_(isInline);
}
@@ -315,12 +333,12 @@ export function handleBlockSelect(event) {
if (
block &&
- block.type !== "create_ground" &&
- block.type !== "create_map" &&
- (block.type.startsWith("create_") || block.type.startsWith("load_"))
+ block.type !== 'create_ground' &&
+ block.type !== 'create_map' &&
+ (block.type.startsWith('create_') || block.type.startsWith('load_'))
) {
// If the block is a create block, update the window.currentMesh variable
- window.updateCurrentMeshName(block, "ID_VAR");
+ window.updateCurrentMeshName(block, 'ID_VAR');
}
}
}
@@ -333,8 +351,8 @@ export function handleBlockDelete(event) {
type: Blockly.Events.BLOCK_CHANGE,
blockId: b.id,
workspaceId: b.workspace?.id,
- element: "field",
- name: "__restore__",
+ element: 'field',
+ name: '__restore__',
oldValue: null,
newValue: null,
recordUndo: false,
@@ -343,29 +361,20 @@ export function handleBlockDelete(event) {
// Recursively delete meshes for qualifying blocks
function deleteMeshesRecursively(blockJson) {
// Check if block type matches the prefixes
- if (
- blockJson.type.startsWith("load_") ||
- blockJson.type.startsWith("create_")
- ) {
+ if (blockJson.type.startsWith('load_') || blockJson.type.startsWith('create_')) {
deleteMeshFromBlock(blockJson.id);
- if (blockJson.type === "create_map") {
+ if (blockJson.type === 'create_map') {
const ws = Blockly.getMainWorkspace();
const nextMapBlock = ws
?.getAllBlocks(false)
.find(
(b) =>
- b.type === "create_map" &&
- b.id !== blockJson.id &&
- b.isEnabled() &&
- b.getParent(),
+ b.type === 'create_map' && b.id !== blockJson.id && b.isEnabled() && b.getParent()
);
if (nextMapBlock)
- updateOrCreateMeshFromBlock(
- nextMapBlock,
- makeRestoreEvent(nextMapBlock),
- );
+ updateOrCreateMeshFromBlock(nextMapBlock, makeRestoreEvent(nextMapBlock));
}
- } else if (blockJson.type === "set_background_color") {
+ } else if (blockJson.type === 'set_background_color') {
deleteMeshFromBlock(blockJson.id);
if (activeControllerBlockId === blockJson.id) {
clearSkyMesh();
@@ -374,20 +383,16 @@ export function handleBlockDelete(event) {
?.getAllBlocks(false)
.find(
(b) =>
- (b.type === "set_sky_color" ||
- b.type === "set_background_color") &&
+ (b.type === 'set_sky_color' || b.type === 'set_background_color') &&
b.id !== blockJson.id &&
b.isEnabled() &&
- b.getParent(),
+ b.getParent()
);
if (nextSkyBlock)
- updateOrCreateMeshFromBlock(
- nextSkyBlock,
- makeRestoreEvent(nextSkyBlock),
- );
+ updateOrCreateMeshFromBlock(nextSkyBlock, makeRestoreEvent(nextSkyBlock));
else setClearSkyToBlack();
}
- } else if (blockJson.type === "set_sky_color") {
+ } else if (blockJson.type === 'set_sky_color') {
if (activeControllerBlockId === blockJson.id) {
clearSkyMesh();
const ws = Blockly.getMainWorkspace();
@@ -395,17 +400,13 @@ export function handleBlockDelete(event) {
?.getAllBlocks(false)
.find(
(b) =>
- (b.type === "set_sky_color" ||
- b.type === "set_background_color") &&
+ (b.type === 'set_sky_color' || b.type === 'set_background_color') &&
b.id !== blockJson.id &&
b.isEnabled() &&
- b.getParent(),
+ b.getParent()
);
if (nextSkyBlock)
- updateOrCreateMeshFromBlock(
- nextSkyBlock,
- makeRestoreEvent(nextSkyBlock),
- );
+ updateOrCreateMeshFromBlock(nextSkyBlock, makeRestoreEvent(nextSkyBlock));
else setClearSkyToBlack();
}
}
@@ -432,21 +433,13 @@ export function handleBlockDelete(event) {
}
export function handleMeshLifecycleChange(block, changeEvent) {
- if (
- !block ||
- block.disposed ||
- !block.workspace ||
- block.workspace.isFlyout
- ) {
+ if (!block || block.disposed || !block.workspace || block.workspace.isFlyout) {
return false;
}
const mesh = getMeshFromBlock(block);
- if (
- changeEvent.type === Blockly.Events.BLOCK_MOVE &&
- changeEvent.blockId === block.id
- ) {
+ if (changeEvent.type === Blockly.Events.BLOCK_MOVE && changeEvent.blockId === block.id) {
if (block.getParent() && !mesh) {
updateOrCreateMeshFromBlock(block, changeEvent);
}
@@ -456,10 +449,9 @@ export function handleMeshLifecycleChange(block, changeEvent) {
if (
changeEvent.type === Blockly.Events.BLOCK_CHANGE &&
changeEvent.blockId === block.id &&
- changeEvent.element === "disabled"
+ changeEvent.element === 'disabled'
) {
- const isDisabling =
- changeEvent.newValue === true || changeEvent.newValue === "true";
+ const isDisabling = changeEvent.newValue === true || changeEvent.newValue === 'true';
if (!isDisabling) {
setTimeout(() => {
@@ -472,8 +464,7 @@ export function handleMeshLifecycleChange(block, changeEvent) {
} else {
deleteMeshFromBlock(block.id);
if (
- (block.type === "set_background_color" ||
- block.type === "set_sky_color") &&
+ (block.type === 'set_background_color' || block.type === 'set_sky_color') &&
getActiveSceneControllerBlockId() === block.id
) {
clearSkyMesh();
@@ -483,10 +474,7 @@ export function handleMeshLifecycleChange(block, changeEvent) {
return true;
}
- if (
- changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- block.workspace.getBlockById(block.id)
- ) {
+ if (changeEvent.type === Blockly.Events.BLOCK_CREATE && block.workspace.getBlockById(block.id)) {
const createdBlockIds = Array.isArray(changeEvent.ids)
? changeEvent.ids
: [changeEvent.blockId];
@@ -513,9 +501,7 @@ export function isValueInputDescendantOf(containerBlock, changedBlock) {
if (!parent) return false;
const viaValueInput = (parent.inputList || []).some(
- (inp) =>
- inp?.type === Blockly.INPUT_VALUE &&
- inp?.connection?.targetBlock?.() === child,
+ (inp) => inp?.type === Blockly.INPUT_VALUE && inp?.connection?.targetBlock?.() === child
);
if (!viaValueInput) return false;
@@ -526,10 +512,7 @@ export function isValueInputDescendantOf(containerBlock, changedBlock) {
}
export function handleFieldOrChildChange(containerBlock, changeEvent) {
- if (
- changeEvent.type !== Blockly.Events.BLOCK_CHANGE ||
- changeEvent.element !== "field"
- ) {
+ if (changeEvent.type !== Blockly.Events.BLOCK_CHANGE || changeEvent.element !== 'field') {
return false;
}
@@ -559,19 +542,14 @@ export function handleParentLinkedUpdate(containerBlock, changeEvent) {
const ws = containerBlock.workspace;
const changedBlocks =
- changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- Array.isArray(changeEvent.ids)
+ changeEvent.type === Blockly.Events.BLOCK_CREATE && Array.isArray(changeEvent.ids)
? changeEvent.ids.map((id) => ws.getBlockById(id)).filter(Boolean)
: [ws.getBlockById(changeEvent.blockId)].filter(Boolean);
for (const changed of changedBlocks) {
const parent = findCreateBlock(changed);
- if (
- changed &&
- parent === containerBlock &&
- isValueInputDescendantOf(containerBlock, changed)
- ) {
+ if (changed && parent === containerBlock && isValueInputDescendantOf(containerBlock, changed)) {
if (!window.loadingCode) {
updateOrCreateMeshFromBlock(containerBlock, changeEvent);
}
@@ -583,23 +561,23 @@ export function handleParentLinkedUpdate(containerBlock, changeEvent) {
}
export function findCreateBlock(block) {
- if (!block || typeof block.getParent !== "function") {
+ if (!block || typeof block.getParent !== 'function') {
return null;
}
let parent = block;
while (parent) {
- if (parent.type === "scale" || parent.type === "rotate_to") {
+ if (parent.type === 'scale' || parent.type === 'rotate_to') {
// Don't update parent if we're modifying a nested scale or rotate
return null;
}
if (
- parent.type.startsWith("create_") ||
- parent.type.startsWith("load_") ||
- parent.type === "set_sky_color" ||
- parent.type === "set_background_color"
+ parent.type.startsWith('create_') ||
+ parent.type.startsWith('load_') ||
+ parent.type === 'set_sky_color' ||
+ parent.type === 'set_background_color'
) {
return parent;
}
@@ -614,12 +592,7 @@ export function findCreateBlock(block) {
export function handleBlockChange(block, changeEvent, variableNamePrefix) {
// Always run first to handle variable naming
- handleBlockCreateEvent(
- block,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(block, changeEvent, variableNamePrefix, nextVariableIndexes);
// Handle lifecycle events like enable/disable/move on the block directly.
// Also handle BLOCK_CREATE events where this block is in the created ids
@@ -643,45 +616,34 @@ export function handleBlockChange(block, changeEvent, variableNamePrefix) {
changeEvent.type === Blockly.Events.BLOCK_MOVE) &&
changeEvent.workspaceId === ws.id
) {
- if (flock.blockDebug)
- console.log("The changed block is", changeEvent.block);
- if (flock.blockDebug)
- console.log("The changed block is", changeEvent.blockId);
+ if (flock.blockDebug) console.log('The changed block is', changeEvent.block);
+ if (flock.blockDebug) console.log('The changed block is', changeEvent.blockId);
const changedBlock = ws.getBlockById(changeEvent.blockId);
const createdBlocks =
- changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- Array.isArray(changeEvent.ids)
+ changeEvent.type === Blockly.Events.BLOCK_CREATE && Array.isArray(changeEvent.ids)
? changeEvent.ids.map((id) => ws.getBlockById(id)).filter(Boolean)
: [changedBlock].filter(Boolean);
if (!createdBlocks.length) {
- if (flock.blockDebug) console.log("Changed block not found in workspace");
+ if (flock.blockDebug) console.log('Changed block not found in workspace');
return;
}
const parents = createdBlocks.map((cb) => findCreateBlock(cb));
- if (flock.blockDebug)
- console.log("The type of the changed block is", changedBlock.type);
+ if (flock.blockDebug) console.log('The type of the changed block is', changedBlock.type);
if (changedBlock.getParent()) {
if (flock.blockDebug)
- console.log(
- "The ID of the parent of the changed block is",
- changedBlock.getParent().id,
- );
+ console.log('The ID of the parent of the changed block is', changedBlock.getParent().id);
if (flock.blockDebug)
console.log(
- "The type of the parent of the changed block is",
- changedBlock.getParent().type,
+ 'The type of the parent of the changed block is',
+ changedBlock.getParent().type
);
}
- if (flock.blockDebug) console.log("This block is", block.id);
- if (flock.blockDebug) console.log("The type of this block is", block.type);
- if (
- changedBlock &&
- parents.includes(block) &&
- isValueInputDescendantOf(block, changedBlock)
- ) {
+ if (flock.blockDebug) console.log('This block is', block.id);
+ if (flock.blockDebug) console.log('The type of this block is', block.type);
+ if (changedBlock && parents.includes(block) && isValueInputDescendantOf(block, changedBlock)) {
// Only configuration inputs (value-input subtree) affect preview mesh; runtime statement blocks do not.
const blockInWorkspace = ws.getBlockById(block.id);
if (blockInWorkspace) {
@@ -694,9 +656,7 @@ export function handleBlockChange(block, changeEvent, variableNamePrefix) {
const _pendingRetarget = new WeakMap(); // block -> { from, to, type, prefix } | undefined
function getBlockly(opts) {
- return (
- (opts && opts.Blockly) || (typeof Blockly !== "undefined" ? Blockly : null)
- );
+ return (opts && opts.Blockly) || (typeof Blockly !== 'undefined' ? Blockly : null);
}
function getVariableFieldsOnBlock(block, BlocklyNS) {
@@ -709,13 +669,7 @@ function getVariableFieldsOnBlock(block, BlocklyNS) {
return out;
}
-function isVariableUsedElsewhere(
- workspace,
- varId,
- excludingBlockId,
- BlocklyNS,
- allBlocks,
-) {
+function isVariableUsedElsewhere(workspace, varId, excludingBlockId, BlocklyNS, allBlocks) {
if (!varId) return false;
const blocks = allBlocks ?? workspace.getAllBlocks(false);
for (const b of blocks) {
@@ -730,13 +684,12 @@ function isVariableUsedElsewhere(
function getFieldVariableType(block, fieldName) {
const field = block.getField(fieldName);
- if (!field) return "";
- const model =
- typeof field.getVariable === "function" ? field.getVariable() : null;
- if (model && typeof model.type === "string") return model.type || "";
+ if (!field) return '';
+ const model = typeof field.getVariable === 'function' ? field.getVariable() : null;
+ if (model && typeof model.type === 'string') return model.type || '';
const varId = field.getValue && field.getValue();
const byId = varId ? block.workspace.getVariableMap().getVariableById(varId) : null;
- return (byId && byId.type) || "";
+ return (byId && byId.type) || '';
}
function parseNumericSuffix(name, prefix) {
@@ -747,7 +700,7 @@ function parseNumericSuffix(name, prefix) {
}
function deriveVariableNameParts(name, fallbackPrefix) {
- if (typeof name !== "string" || !name.length) {
+ if (typeof name !== 'string' || !name.length) {
return { prefix: fallbackPrefix, suffix: null };
}
const numberMatch = name.match(/^(.*?)(\d+)$/);
@@ -768,10 +721,7 @@ function createFreshVariable(workspace, prefix, type, nextVariableIndexes) {
while (workspace.getVariableMap().getVariable(`${prefix}${n}`, type)) n += 1;
// Update the counter
- nextVariableIndexes[prefix] = Math.max(
- nextVariableIndexes[prefix] || 1,
- n + 1,
- );
+ nextVariableIndexes[prefix] = Math.max(nextVariableIndexes[prefix] || 1, n + 1);
const newVarName = `${prefix}${n}`;
@@ -787,13 +737,7 @@ function createFreshVariable(workspace, prefix, type, nextVariableIndexes) {
return workspace.getVariableMap().createVariable(`${prefix}${n}`, type);
}
-function retargetDescendantsVariables(
- rootBlock,
- fromVarId,
- toVarId,
- BlocklyNS,
- createdIds = null,
-) {
+function retargetDescendantsVariables(rootBlock, fromVarId, toVarId, BlocklyNS, createdIds = null) {
if (!fromVarId || !toVarId || fromVarId === toVarId) return 0;
// Get descendants but ONLY through input connections, not next/previous
@@ -853,7 +797,7 @@ function adoptIsolatedDefaultVarsTo(
workspace,
BlocklyNS,
createdIds,
- allBlocks,
+ allBlocks
) {
const descendantIds = buildDescendantIdSet(rootBlock);
let adopted = 0;
@@ -913,7 +857,7 @@ function adoptIsolatedDefaultVarsTo(
try {
workspace.getVariableMap().deleteVariable(model);
} catch (error) {
- console.warn("Failed to delete unreferenced variable by id:", error);
+ console.warn('Failed to delete unreferenced variable by id:', error);
}
}
}
@@ -934,12 +878,12 @@ export function ensureFreshVarOnDuplicate(
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- opts = {},
+ opts = {}
) {
const BlocklyNS = getBlockly(opts);
if (!BlocklyNS) return false;
- const fieldName = opts.fieldName || "ID_VAR";
+ const fieldName = opts.fieldName || 'ID_VAR';
// Finish any pending work (retarget, adopt, normalize) from earlier in the same dup group.
const pending = _pendingRetarget.get(block);
@@ -949,13 +893,7 @@ export function ensureFreshVarOnDuplicate(
BlocklyNS.Events.disable();
// Only retarget blocks that were created in the same copy operation
- retargetDescendantsVariables(
- block,
- pending.from,
- pending.to,
- BlocklyNS,
- pending.createdIds,
- );
+ retargetDescendantsVariables(block, pending.from, pending.to, BlocklyNS, pending.createdIds);
adoptIsolatedDefaultVarsTo(
block,
pending.to,
@@ -963,12 +901,10 @@ export function ensureFreshVarOnDuplicate(
pending.prefix,
block.workspace,
BlocklyNS,
- pending.createdIds,
+ pending.createdIds
);
- if (
- !subtreeHasVarId(block, pending.from, BlocklyNS, pending.createdIds)
- ) {
+ if (!subtreeHasVarId(block, pending.from, BlocklyNS, pending.createdIds)) {
_pendingRetarget.set(block, undefined);
}
} finally {
@@ -988,19 +924,20 @@ export function ensureFreshVarOnDuplicate(
const oldVarId = idField.getValue && idField.getValue();
if (!oldVarId) return false;
const oldVarModel = ws.getVariableMap().getVariableById(oldVarId);
- const { prefix: duplicatePrefix, suffix: duplicateSuffix } =
- deriveVariableNameParts(oldVarModel?.name, variableNamePrefix);
+ const { prefix: duplicatePrefix, suffix: duplicateSuffix } = deriveVariableNameParts(
+ oldVarModel?.name,
+ variableNamePrefix
+ );
// Duplicate/copy/duplicate-parent case?
const allBlocks = ws.getAllBlocks(false);
- if (!isVariableUsedElsewhere(ws, oldVarId, block.id, BlocklyNS, allBlocks))
- return false;
+ if (!isVariableUsedElsewhere(ws, oldVarId, block.id, BlocklyNS, allBlocks)) return false;
if (Number.isInteger(duplicateSuffix)) {
const nextFromSource = duplicateSuffix + 1;
nextVariableIndexes[duplicatePrefix] = Math.max(
nextVariableIndexes[duplicatePrefix] || 1,
- nextFromSource,
+ nextFromSource
);
}
@@ -1015,28 +952,16 @@ export function ensureFreshVarOnDuplicate(
BlocklyNS.Events.disable();
// Mint a new var with the *lowest* available suffix now.
- const newVarModel = createFreshVariable(
- ws,
- duplicatePrefix,
- varType,
- nextVariableIndexes,
- );
+ const newVarModel = createFreshVariable(ws, duplicatePrefix, varType, nextVariableIndexes);
const newVarId =
- newVarModel.id ||
- (typeof newVarModel.getId === "function" ? newVarModel.getId() : null);
+ newVarModel.id || (typeof newVarModel.getId === 'function' ? newVarModel.getId() : null);
if (!newVarId) return false;
// Point the creator at the fresh variable.
idField.setValue(newVarId);
// Pass 1: retarget descendants old -> new (ONLY blocks created in this event)
- retargetDescendantsVariables(
- block,
- oldVarId,
- newVarId,
- BlocklyNS,
- createdIds,
- );
+ retargetDescendantsVariables(block, oldVarId, newVarId, BlocklyNS, createdIds);
// Pass 2: adopt any isolated default-looking vars inside subtree to the new var
adoptIsolatedDefaultVarsTo(
@@ -1047,7 +972,7 @@ export function ensureFreshVarOnDuplicate(
ws,
BlocklyNS,
createdIds,
- allBlocks,
+ allBlocks
);
// If more children will connect later, remember to finish on subsequent events.
@@ -1092,11 +1017,11 @@ export class CustomConstantProvider extends Blockly.zelos.ConstantProvider {
this.NOTCH_OFFSET_LEFT = 2 * this.GRID_UNIT;
this.NOTCH_HEIGHT = 2 * this.GRID_UNIT;
this.FIELD_DROPDOWN_SVG_ARROW_DATAURI =
- "data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMi43MSIgaGVpZ2h0PSI4Ljc5IiB2aWV3Qm94PSIwIDAgMTIuNzEgOC43OSI+PHRpdGxlPmRyb3Bkb3duLWFycm93PC90aXRsZT48ZyBvcGFjaXR5PSIwLjEiPjxwYXRoIGQ9Ik0xMi43MSwyLjQ0QTIuNDEsMi40MSwwLDAsMSwxMiw0LjE2TDguMDgsOC4wOGEyLjQ1LDIuNDUsMCwwLDEtMy40NSwwTDAuNzIsNC4xNkEyLjQyLDIuNDIsMCwwLDEsMCwyLjQ0LDIuNDgsMi40OCwwLDAsMSwuNzEuNzFDMSwwLjQ3LDEuNDMsMCw2LjM2LDBTMTEuNzUsMC40NiwxMiwuNzFBMi40NCwyLjQ0LDAsMCwxLDEyLjcxLDIuNDRaIiBmaWxsPSIjMjMxZjIwIi8+PC9nPjxwYXRoIGQ9Ik02LjM2LDcuNzlhMS40MywxLjQzLDAsMCwxLTEuNDItTDEuNDIsMy40NWExLjQ0LDEuNDQsMCwwLDEsMC0yYzAuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMzdBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiMwMDAiLz48L3N2Zz4=";
+ 'data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMi43MSIgaGVpZ2h0PSI4Ljc5IiB2aWV3Qm94PSIwIDAgMTIuNzEgOC43OSI+PHRpdGxlPmRyb3Bkb3duLWFycm93PC90aXRsZT48ZyBvcGFjaXR5PSIwLjEiPjxwYXRoIGQ9Ik0xMi43MSwyLjQ0QTIuNDEsMi40MSwwLDAsMSwxMiw0LjE2TDguMDgsOC4wOGEyLjQ1LDIuNDUsMCwwLDEtMy40NSwwTDAuNzIsNC4xNkEyLjQyLDIuNDIsMCwwLDEsMCwyLjQ0LDIuNDgsMi40OCwwLDAsMSwuNzEuNzFDMSwwLjQ3LDEuNDMsMCw2LjM2LDBTMTEuNzUsMC40NiwxMiwuNzFBMi40NCwyLjQ0LDAsMCwxLDEyLjcxLDIuNDRaIiBmaWxsPSIjMjMxZjIwIi8+PC9nPjxwYXRoIGQ9Ik02LjM2LDcuNzlhMS40MywxLjQzLDAsMCwxLTEuNDItTDEuNDIsMy40NWExLjQ0LDEuNDQsMCwwLDEsMC0yYzAuNTYtLjU2LDkuMzEtMC41Niw5Ljg3LDBhMS40NCwxLjQ0LDAsMCwxLDAsMkw3LjM3LDcuMzdBMS40MywxLjQzLDAsMCwxLDYuMzYsNy43OVoiIGZpbGw9IiMwMDAiLz48L3N2Zz4=';
}
}
-const MODE = { IF: "IF", ELSEIF: "ELSEIF", ELSE: "ELSE" };
+const MODE = { IF: 'IF', ELSEIF: 'ELSEIF', ELSE: 'ELSE' };
class CustomRenderInfo extends Blockly.zelos.RenderInfo {
constructor(renderer, block) {
@@ -1109,8 +1034,8 @@ class CustomRenderInfo extends Blockly.zelos.RenderInfo {
super.addElemSpacing_();
// Add extra height to the top row for IF blocks only
- if (this.block_.type === "if_clause") {
- const mode = this.block_.getFieldValue?.("MODE");
+ if (this.block_.type === 'if_clause') {
+ const mode = this.block_.getFieldValue?.('MODE');
if (mode === MODE.IF && this.rows.length > 0) {
// Find the first row with fields or inputs (skip the top cap row)
for (let i = 0; i < this.rows.length; i++) {
@@ -1120,7 +1045,7 @@ class CustomRenderInfo extends Blockly.zelos.RenderInfo {
if (row.elements && row.elements.length > 0) {
// Check if it's not just a top/bottom cap or connection row
const hasContent = row.elements.some(
- (el) => el.field || el.input || (el.type && el.type !== 0),
+ (el) => el.field || el.input || (el.type && el.type !== 0)
);
if (hasContent) {
@@ -1144,21 +1069,19 @@ class CustomZelosDrawer extends Blockly.zelos.Drawer {
drawTop_() {
const b = this.block_;
- if (b?.type !== "if_clause") return super.drawTop_();
+ if (b?.type !== 'if_clause') return super.drawTop_();
// Never change shape for insertion markers.
if (b.isInsertionMarker?.()) return super.drawTop_();
- const mode = b.getFieldValue?.("MODE");
+ const mode = b.getFieldValue?.('MODE');
const isClause = mode === MODE.ELSE || mode === MODE.ELSEIF;
// Require an ACTUAL previous connection to a real if_clause block.
const prevConn = b.previousConnection;
- const prev =
- prevConn && prevConn.isConnected() ? prevConn.targetBlock() : null;
+ const prev = prevConn && prevConn.isConnected() ? prevConn.targetBlock() : null;
- const prevIsRealIfClause =
- prev && prev.type === "if_clause" && !prev.isInsertionMarker?.();
+ const prevIsRealIfClause = prev && prev.type === 'if_clause' && !prev.isInsertionMarker?.();
if (isClause && prevIsRealIfClause) {
this.drawFlatTop_();
@@ -1170,12 +1093,12 @@ class CustomZelosDrawer extends Blockly.zelos.Drawer {
drawBottom_() {
const b = this.block_;
- if (b?.type !== "if_clause") return super.drawBottom_();
+ if (b?.type !== 'if_clause') return super.drawBottom_();
// Never change shape for insertion markers.
if (b.isInsertionMarker?.()) return super.drawBottom_();
- const mode = b.getFieldValue?.("MODE");
+ const mode = b.getFieldValue?.('MODE');
// Only clauses that can legally have something after them in the same chain.
const canContinueChain = mode === MODE.IF || mode === MODE.ELSEIF;
@@ -1183,19 +1106,16 @@ class CustomZelosDrawer extends Blockly.zelos.Drawer {
// Require an ACTUAL next connection to a real if_clause block.
const nextConn = b.nextConnection;
- const next =
- nextConn && nextConn.isConnected() ? nextConn.targetBlock() : null;
+ const next = nextConn && nextConn.isConnected() ? nextConn.targetBlock() : null;
- const nextIsRealIfClause =
- next && next.type === "if_clause" && !next.isInsertionMarker?.();
+ const nextIsRealIfClause = next && next.type === 'if_clause' && !next.isInsertionMarker?.();
if (!nextIsRealIfClause) return super.drawBottom_();
// Only flatten when the NEXT clause is a joined clause (else/else if),
// not when itโs a new IF statement.
- const nextMode = next.getFieldValue?.("MODE");
- const nextIsJoinedClause =
- nextMode === MODE.ELSE || nextMode === MODE.ELSEIF;
+ const nextMode = next.getFieldValue?.('MODE');
+ const nextIsJoinedClause = nextMode === MODE.ELSE || nextMode === MODE.ELSEIF;
if (nextIsJoinedClause) {
this.drawFlatBottom_();
@@ -1209,22 +1129,18 @@ class CustomZelosDrawer extends Blockly.zelos.Drawer {
const b = this.block_;
const svgRoot = b.getSvgRoot?.();
if (!svgRoot) return;
- svgRoot.removeAttribute("data-axis");
+ svgRoot.removeAttribute('data-axis');
if (!b.outputConnection?.isConnected()) return;
const targetConn = b.outputConnection.targetConnection;
if (!targetConn) return;
const parentBlock = b.outputConnection.targetBlock();
if (!parentBlock) return;
const parentInput = (parentBlock.inputList || []).find(
- (input) => input.connection === targetConn,
+ (input) => input.connection === targetConn
);
if (!parentInput) return;
- if (
- parentInput.name === "X" ||
- parentInput.name === "Y" ||
- parentInput.name === "Z"
- ) {
- svgRoot.setAttribute("data-axis", parentInput.name);
+ if (parentInput.name === 'X' || parentInput.name === 'Y' || parentInput.name === 'Z') {
+ svgRoot.setAttribute('data-axis', parentInput.name);
}
}
@@ -1233,61 +1149,54 @@ class CustomZelosDrawer extends Blockly.zelos.Drawer {
this.colorizeAxisInput_();
const b = this.block_;
- if (b?.type !== "if_clause") return;
+ if (b?.type !== 'if_clause') return;
// Donโt paint seam covers on insertion markers / connection previews.
- if (typeof b.isInsertionMarker === "function" && b.isInsertionMarker())
- return;
+ if (typeof b.isInsertionMarker === 'function' && b.isInsertionMarker()) return;
const svgRoot = b.getSvgRoot?.();
if (!svgRoot) return;
// Always remove any previous cover (so disabling can hide it).
- const existing = svgRoot.querySelector?.(
- ":scope > rect.ifclause-seam-cover",
- );
+ const existing = svgRoot.querySelector?.(':scope > rect.ifclause-seam-cover');
if (existing) existing.remove();
// If the block is disabled, weโre done (no cover).
// Use isEnabled (covers setDisabledReason etc), with a fallback to `disabled`.
- const isDisabled =
- (typeof b.isEnabled === "function" ? !b.isEnabled() : false) ||
- !!b.disabled;
+ const isDisabled = (typeof b.isEnabled === 'function' ? !b.isEnabled() : false) || !!b.disabled;
if (isDisabled) return;
const prev = b.getPreviousBlock?.();
- const prevIsIfClause = prev && prev.type === "if_clause";
+ const prevIsIfClause = prev && prev.type === 'if_clause';
- const mode = b.getFieldValue?.("MODE");
+ const mode = b.getFieldValue?.('MODE');
const isJoinedClause = mode === MODE.ELSE || mode === MODE.ELSEIF;
if (!prevIsIfClause || !isJoinedClause) return;
// Get the actual rendered fill from the block path (avoids black during previews).
const pathObj = this.pathObject_;
- const mainPath =
- pathObj?.svgPath_ || pathObj?.svgPath || pathObj?.path_ || null;
+ const mainPath = pathObj?.svgPath_ || pathObj?.svgPath || pathObj?.path_ || null;
const fill =
- (mainPath?.getAttribute && mainPath.getAttribute("fill")) ||
+ (mainPath?.getAttribute && mainPath.getAttribute('fill')) ||
mainPath?.style?.fill ||
- (typeof b.getColour === "function" ? b.getColour() : null);
+ (typeof b.getColour === 'function' ? b.getColour() : null);
- if (!fill || fill === "none") return;
+ if (!fill || fill === 'none') return;
const coverPx = 16;
- const strokePx =
- this.constants_?.OUTLINE_WIDTH ?? this.constants_?.STROKE_WIDTH ?? 1;
-
- const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
- rect.setAttribute("class", "ifclause-seam-cover");
- rect.setAttribute("x", "1");
- rect.setAttribute("y", String(-strokePx * 2));
- rect.setAttribute("width", String(coverPx));
- rect.setAttribute("height", String(strokePx * 4));
- rect.setAttribute("fill", fill);
- rect.setAttribute("stroke", "none");
- rect.setAttribute("pointer-events", "none");
+ const strokePx = this.constants_?.OUTLINE_WIDTH ?? this.constants_?.STROKE_WIDTH ?? 1;
+
+ const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
+ rect.setAttribute('class', 'ifclause-seam-cover');
+ rect.setAttribute('x', '1');
+ rect.setAttribute('y', String(-strokePx * 2));
+ rect.setAttribute('width', String(coverPx));
+ rect.setAttribute('height', String(strokePx * 4));
+ rect.setAttribute('fill', fill);
+ rect.setAttribute('stroke', 'none');
+ rect.setAttribute('pointer-events', 'none');
svgRoot.appendChild(rect);
}
@@ -1312,8 +1221,8 @@ export class CustomZelosRenderer extends Blockly.zelos.Renderer {
}
function getBlocklyMediaPath() {
- let baseUrl = import.meta.env.BASE_URL || "/";
- if (!baseUrl.endsWith("/")) baseUrl += "/";
+ let baseUrl = import.meta.env.BASE_URL || '/';
+ if (!baseUrl.endsWith('/')) baseUrl += '/';
return `${baseUrl}blockly/media/`;
}
@@ -1321,10 +1230,10 @@ const mediaPath = getBlocklyMediaPath();
export const options = {
//theme: FlockTheme,
- theme: createThemeConfig("light"),
+ theme: createThemeConfig('light'),
//theme: "flockTheme",
//renderer: "zelos",
- renderer: "custom_zelos_renderer",
+ renderer: 'custom_zelos_renderer',
media: mediaPath,
modalInputs: false,
zoom: {
@@ -1376,7 +1285,7 @@ export function initializeVariableIndexes() {
plane: 1,
wall: 1,
text: 1,
- "3dtext": 1,
+ '3dtext': 1,
sound: 1,
character: 1,
object: 1,
@@ -1402,7 +1311,7 @@ export function initializeVariableIndexes() {
// Process each type of variable and use the lowest available suffix.
if (workspace) {
Object.keys(nextVariableIndexes).forEach(function (type) {
- nextVariableIndexes[type] = lowestAvailableSuffix(workspace, type, "");
+ nextVariableIndexes[type] = lowestAvailableSuffix(workspace, type, '');
});
}
@@ -1430,68 +1339,66 @@ export function defineBlocks() {
window.updateCurrentMeshName = updateCurrentMeshName;
- Blockly.Blocks["create_wall"] = {
+ Blockly.Blocks['create_wall'] = {
init: function () {
- const variableNamePrefix = "wall";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix]; // Start with "wall1";
+ const variableNamePrefix = 'wall';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix]; // Start with "wall1";
this.jsonInit({
- type: "create_wall",
- message0:
- "new wall %1 type %2 colour %3 \n start x %4 z %5 end x %6 z %7 y position %8",
+ type: 'create_wall',
+ message0: 'new wall %1 type %2 colour %3 \n start x %4 z %5 end x %6 z %7 y position %8',
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_dropdown",
- name: "WALL_TYPE",
+ type: 'field_dropdown',
+ name: 'WALL_TYPE',
options: [
- ["solid", "SOLID_WALL"],
- ["door", "WALL_WITH_DOOR"],
- ["window", "WALL_WITH_WINDOW"],
- ["floor/roof", "FLOOR"],
+ ['solid', 'SOLID_WALL'],
+ ['door', 'WALL_WITH_DOOR'],
+ ['window', 'WALL_WITH_WINDOW'],
+ ['floor/roof', 'FLOOR'],
],
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "START_X",
- check: "Number",
+ type: 'input_value',
+ name: 'START_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "START_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'START_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "END_X",
- check: "Number",
+ type: 'input_value',
+ name: 'END_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "END_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'END_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y_POSITION",
- check: "Number",
+ type: 'input_value',
+ name: 'Y_POSITION',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
+ colour: categoryColours['Scene'],
tooltip:
- "Create a wall with the selected type and color between specified start and end positions.\nKeyword: wall",
+ 'Create a wall with the selected type and color between specified start and end positions.\nKeyword: wall',
});
this.setHelpUrl(getHelpUrlFor(this.type));
registerBlockHandler(this, (changeEvent) => {
@@ -1502,23 +1409,18 @@ export function defineBlocks() {
const blockInWorkspace = this.workspace?.getBlockById(this.id); // Check if block is in the main workspace
if (blockInWorkspace) {
- window.updateCurrentMeshName(this, "ID_VAR"); // Call the function to update window.currentMesh
+ window.updateCurrentMeshName(this, 'ID_VAR'); // Call the function to update window.currentMesh
}
}
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
});
},
};
- Blockly.Extensions.register("dynamic_mesh_dropdown", function () {
+ Blockly.Extensions.register('dynamic_mesh_dropdown', function () {
const dropdown = new Blockly.FieldDropdown(function () {
- const options = [[translate("everywhere_option"), "__everywhere__"]];
+ const options = [[translate('everywhere_option'), '__everywhere__']];
const workspace = this.sourceBlock_ && this.sourceBlock_.workspace;
if (workspace) {
const variables = workspace.getVariableMap().getAllVariables();
@@ -1530,180 +1432,180 @@ export function defineBlocks() {
});
// Attach the dropdown to the block
- this.getInput("MESH_INPUT").appendField(dropdown, "MESH_NAME");
+ this.getInput('MESH_INPUT').appendField(dropdown, 'MESH_NAME');
});
- Blockly.Blocks["rotate_camera"] = {
+ Blockly.Blocks['rotate_camera'] = {
init: function () {
this.jsonInit({
- type: "rotate_camera",
- message0: translate("rotate_camera"),
+ type: 'rotate_camera',
+ message0: translate('rotate_camera'),
args0: [
{
- type: "input_value",
- name: "DEGREES",
- check: "Number",
+ type: 'input_value',
+ name: 'DEGREES',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("rotate_camera"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('rotate_camera'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
},
};
- Blockly.Blocks["up"] = {
+ Blockly.Blocks['up'] = {
init: function () {
this.jsonInit({
- type: "up",
- message0: translate("up"),
+ type: 'up',
+ message0: translate('up'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "UP_FORCE",
- check: "Number",
+ type: 'input_value',
+ name: 'UP_FORCE',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("up"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('up'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
},
};
- Blockly.Blocks["random_seeded_int"] = {
+ Blockly.Blocks['random_seeded_int'] = {
init: function () {
this.jsonInit({
- type: "random_seeded_int",
- message0: translate("random_seeded_int"),
+ type: 'random_seeded_int',
+ message0: translate('random_seeded_int'),
args0: [
{
- type: "input_value",
- name: "FROM",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'FROM',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "TO",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'TO',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "SEED",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'SEED',
+ check: 'Number',
+ align: 'RIGHT',
},
],
inputsInline: true,
- output: "Number",
+ output: 'Number',
colour: 230,
- tooltip: getTooltip("random_seeded_int"),
+ tooltip: getTooltip('random_seeded_int'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("math_blocks");
+ this.setStyle('math_blocks');
},
};
- Blockly.Blocks["lists_add_item"] = {
+ Blockly.Blocks['lists_add_item'] = {
init: function () {
this.jsonInit({
- type: "lists_add_item",
- message0: "add %1 to %2",
+ type: 'lists_add_item',
+ message0: 'add %1 to %2',
args0: [
{
- type: "input_value",
- name: "TO",
+ type: 'input_value',
+ name: 'TO',
},
{
- type: "field_variable",
- name: "LIST",
- variable: "list1",
+ type: 'field_variable',
+ name: 'LIST',
+ variable: 'list1',
},
],
previousStatement: null,
nextStatement: null,
- tooltip: "Add an item to the end of a list.",
+ tooltip: 'Add an item to the end of a list.',
});
- this.setStyle("list_blocks");
+ this.setStyle('list_blocks');
this.setHelpUrl(getHelpUrlFor(this.type));
},
};
- Blockly.Blocks["lists_delete_nth"] = {
+ Blockly.Blocks['lists_delete_nth'] = {
init: function () {
this.jsonInit({
- type: "lists_delete_nth",
- message0: "delete %1 from %2",
+ type: 'lists_delete_nth',
+ message0: 'delete %1 from %2',
args0: [
{
- type: "input_value",
- name: "INDEX",
- check: "Number",
+ type: 'input_value',
+ name: 'INDEX',
+ check: 'Number',
},
{
- type: "field_variable",
- name: "LIST",
- variable: "list1",
+ type: 'field_variable',
+ name: 'LIST',
+ variable: 'list1',
},
],
previousStatement: null,
nextStatement: null,
- tooltip: "Delete item at index n from a list (0-based).",
+ tooltip: 'Delete item at index n from a list (0-based).',
});
- this.setStyle("list_blocks");
+ this.setStyle('list_blocks');
this.setHelpUrl(getHelpUrlFor(this.type));
},
};
- Blockly.Blocks["to_number"] = {
+ Blockly.Blocks['to_number'] = {
init: function () {
this.jsonInit({
- type: "to_number",
- message0: translate("to_number"),
+ type: 'to_number',
+ message0: translate('to_number'),
args0: [
{
- type: "input_value",
- name: "STRING",
- check: "String",
+ type: 'input_value',
+ name: 'STRING',
+ check: 'String',
},
{
- type: "field_dropdown",
- name: "TYPE",
+ type: 'field_dropdown',
+ name: 'TYPE',
options: [
- ["integer", "INT"],
- ["float", "FLOAT"],
+ ['integer', 'INT'],
+ ['float', 'FLOAT'],
],
},
],
inputsInline: true,
- output: "Number",
+ output: 'Number',
colour: 230,
- tooltip: getTooltip("to_number"),
+ tooltip: getTooltip('to_number'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("math_blocks");
+ this.setStyle('math_blocks');
},
};
- Blockly.Blocks["keyword_block"] = {
+ Blockly.Blocks['keyword_block'] = {
init: function () {
this.appendDummyInput().appendField(
- new Blockly.FieldTextInput("type a keyword to add a block"),
- "KEYWORD",
+ new Blockly.FieldTextInput('type a keyword to add a block'),
+ 'KEYWORD'
);
- this.setTooltip("Type a keyword to change this block.");
+ this.setTooltip('Type a keyword to change this block.');
this.setHelpUrl(getHelpUrlFor(this.type));
this.setOnChange(function () {
@@ -1712,7 +1614,7 @@ export function defineBlocks() {
return;
}
// Get the entered keyword.
- const keyword = this.getFieldValue("KEYWORD").trim();
+ const keyword = this.getFieldValue('KEYWORD').trim();
// Lookup the exact toolbox definition based on the keyword.
const blockDefinition = findBlockDefinitionByKeyword(keyword);
if (blockDefinition?.type) {
@@ -1730,10 +1632,7 @@ export function defineBlocks() {
const pos = this.getRelativeToSurfaceXY();
newBlock.moveBy(pos.x, pos.y);
- if (
- this.previousConnection &&
- this.previousConnection.isConnected()
- ) {
+ if (this.previousConnection && this.previousConnection.isConnected()) {
const parentConnection = this.previousConnection.targetConnection;
if (parentConnection) {
parentConnection.disconnect();
@@ -1764,10 +1663,10 @@ export function defineBlocks() {
},
};
- Blockly.Blocks["keyword"] = {
+ Blockly.Blocks['keyword'] = {
init: function () {
// Call the original keyword_block init method.
- Blockly.Blocks["keyword_block"].init.call(this);
+ Blockly.Blocks['keyword_block'].init.call(this);
// Add chaining connections.
this.setPreviousStatement(true);
this.setNextStatement(true);
@@ -1782,11 +1681,11 @@ export function defineBlocks() {
}
for (const item of contents) {
// If this item is a block with the matching keyword, return its definition.
- if (item.kind === "block" && item.keyword === keyword) {
+ if (item.kind === 'block' && item.keyword === keyword) {
return item;
}
// If the item is a category with its own contents, search recursively.
- if (item.kind === "category" && Array.isArray(item.contents)) {
+ if (item.kind === 'category' && Array.isArray(item.contents)) {
const result = searchContents(item.contents);
if (result !== null) {
return result;
@@ -1807,10 +1706,7 @@ export function defineBlocks() {
block.loadExtraState(definition.extraState);
}
- if (
- typeof definition.inline === "boolean" &&
- typeof block.setInputsInline === "function"
- ) {
+ if (typeof definition.inline === 'boolean' && typeof block.setInputsInline === 'function') {
block.setInputsInline(definition.inline);
}
@@ -1820,37 +1716,37 @@ export function defineBlocks() {
const field = block.getField?.(fieldName);
if (
- typeof fieldValue === "string" ||
- typeof fieldValue === "number" ||
- typeof fieldValue === "boolean"
+ typeof fieldValue === 'string' ||
+ typeof fieldValue === 'number' ||
+ typeof fieldValue === 'boolean'
) {
block.setFieldValue(fieldValue, fieldName);
continue;
}
- if (!fieldValue || typeof fieldValue !== "object") {
+ if (!fieldValue || typeof fieldValue !== 'object') {
continue;
}
if (
- typeof block.setVariableFieldValue === "function" &&
- typeof fieldValue.name === "string"
+ typeof block.setVariableFieldValue === 'function' &&
+ typeof fieldValue.name === 'string'
) {
block.setVariableFieldValue(fieldValue.name, fieldName);
continue;
}
- if (typeof field?.loadState === "function") {
+ if (typeof field?.loadState === 'function') {
field.loadState(fieldValue);
continue;
}
- if (fieldValue.id && typeof block.setFieldValue === "function") {
+ if (fieldValue.id && typeof block.setFieldValue === 'function') {
block.setFieldValue(fieldValue.id, fieldName);
continue;
}
- if (fieldValue.name && typeof block.setFieldValue === "function") {
+ if (fieldValue.name && typeof block.setFieldValue === 'function') {
block.setFieldValue(fieldValue.name, fieldName);
}
}
@@ -1897,8 +1793,7 @@ export function defineBlocks() {
childBlock.render();
applyBlockDefinition(childBlock, definition);
- const childConnection =
- childBlock.outputConnection || childBlock.previousConnection;
+ const childConnection = childBlock.outputConnection || childBlock.previousConnection;
if (!childConnection) {
childBlock.dispose();
return;
@@ -1923,41 +1818,41 @@ export function defineBlocks() {
export function addDoMutatorWithToggleBehavior(block) {
// Custom function to toggle the "do" block mutation
block.toggleDoBlock = function () {
- const hasDo = this.getInput("DO") ? true : false;
+ const hasDo = this.getInput('DO') ? true : false;
if (hasDo) {
- this.removeInput("DO");
+ this.removeInput('DO');
} else {
- this.appendStatementInput("DO").setCheck(null).appendField("");
+ this.appendStatementInput('DO').setCheck(null).appendField('');
}
};
// Add the toggle button to the block
const toggleButton = new Blockly.FieldImage(
- "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xNSA2djloLTl2M2g5djloM3YtOWg5di0zaC05di05eiIvPjwvc3ZnPg==", // Custom icon
+ 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMzAiIHZpZXdCb3g9IjAgMCAzMCAzMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4gPHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xNSA2djloLTl2M2g5djloM3YtOWg5di0zaC05di05eiIvPjwvc3ZnPg==', // Custom icon
30,
30,
- "toggle do block", // Width, Height, Alt text
- block.toggleDoBlock.bind(block), // Bind the event handler to the block
+ 'toggle do block', // Width, Height, Alt text
+ block.toggleDoBlock.bind(block) // Bind the event handler to the block
);
// Add the button to the block
block
.appendDummyInput()
.setAlign(Blockly.inputs.Align.RIGHT)
- .appendField(toggleButton, "TOGGLE_BUTTON");
+ .appendField(toggleButton, 'TOGGLE_BUTTON');
// Save the mutation state
block.mutationToDom = function () {
- const container = document.createElement("mutation");
- container.setAttribute("has_do", this.getInput("DO") ? "true" : "false");
+ const container = document.createElement('mutation');
+ container.setAttribute('has_do', this.getInput('DO') ? 'true' : 'false');
return container;
};
// Restore the mutation state
block.domToMutation = function (xmlElement) {
- const hasDo = xmlElement.getAttribute("has_do") === "true";
+ const hasDo = xmlElement.getAttribute('has_do') === 'true';
if (hasDo) {
- this.appendStatementInput("DO").setCheck(null).appendField("");
+ this.appendStatementInput('DO').setCheck(null).appendField('');
}
};
}
@@ -1967,7 +1862,7 @@ export function handleBlockCreateEvent(
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- fieldName = "ID_VAR", // Default field name to handle
+ fieldName = 'ID_VAR' // Default field name to handle
) {
if (window.loadingCode) return; // Don't rename variables during code loading
@@ -1977,8 +1872,8 @@ export function handleBlockCreateEvent(
variableNamePrefix,
nextVariableIndexes,
{
- fieldName: "ID_VAR",
- },
+ fieldName: 'ID_VAR',
+ }
);
if (handledDuplicate) return;
if (blockInstance.id !== changeEvent.blockId) return;
@@ -1996,13 +1891,11 @@ export function handleBlockCreateEvent(
const variableField = blockInstance.getField(fieldName);
if (variableField) {
const variableId = variableField.getValue();
- const variable = blockInstance.workspace
- .getVariableMap()
- .getVariableById(variableId);
+ const variable = blockInstance.workspace.getVariableMap().getVariableById(variableId);
// Check if the variable name matches the pattern "prefixn"
const variableNamePattern = new RegExp(`^${variableNamePrefix}\\d+$`);
- const variableName = variable ? variable.name : "";
+ const variableName = variable ? variable.name : '';
if (!variableNamePattern.test(variableName)) {
// Handle custom variables
@@ -2012,12 +1905,10 @@ export function handleBlockCreateEvent(
if (numberMatch) {
newVariableName = numberMatch[1] + (parseInt(numberMatch[2]) + 1);
} else {
- newVariableName = variableName + "1";
+ newVariableName = variableName + '1';
}
- let newVariable = blockInstance.workspace
- .getVariableMap()
- .getVariable(newVariableName);
+ let newVariable = blockInstance.workspace.getVariableMap().getVariable(newVariableName);
if (!newVariable) {
newVariable = blockInstance.workspace
.getVariableMap()
@@ -2030,7 +1921,7 @@ export function handleBlockCreateEvent(
if (newSuffix !== null) {
nextVariableIndexes[variableNamePrefix] = Math.max(
nextVariableIndexes[variableNamePrefix] || 1,
- newSuffix + 1,
+ newSuffix + 1
);
}
}
@@ -2039,18 +1930,13 @@ export function handleBlockCreateEvent(
if (!nextVariableIndexes[variableNamePrefix]) {
nextVariableIndexes[variableNamePrefix] = 1;
}
- const currentSuffix = parseNumericSuffix(
- variableName,
- variableNamePrefix,
- );
+ const currentSuffix = parseNumericSuffix(variableName, variableNamePrefix);
if (currentSuffix) {
const nextIndex = nextVariableIndexes[variableNamePrefix];
// If the current suffix doesn't match the expected next index, rename it
if (currentSuffix !== nextIndex) {
const newVariableName = variableNamePrefix + nextIndex;
- let newVariable = blockInstance.workspace
- .getVariableMap()
- .getVariable(newVariableName);
+ let newVariable = blockInstance.workspace.getVariableMap().getVariable(newVariableName);
if (!newVariable) {
newVariable = blockInstance.workspace
.getVariableMap()
@@ -2060,7 +1946,7 @@ export function handleBlockCreateEvent(
}
nextVariableIndexes[variableNamePrefix] = Math.max(
nextVariableIndexes[variableNamePrefix],
- currentSuffix + 1,
+ currentSuffix + 1
);
}
}
@@ -2071,7 +1957,7 @@ export function handleBlockCreateEvent(
// Extend the built-in Blockly procedures_defreturn block to add custom toggle functionality
// Reference to the original init function of the procedures_defreturn block
-Blockly.Blocks["procedures_defreturn"].init = (function (originalInit) {
+Blockly.Blocks['procedures_defreturn'].init = (function (originalInit) {
return function () {
// Call the original initialization function to ensure the block retains its default behaviour
originalInit.call(this);
@@ -2079,10 +1965,10 @@ Blockly.Blocks["procedures_defreturn"].init = (function (originalInit) {
// Use the existing addToggleButton helper to add the button to the block
addToggleButton(this);
};
-})(Blockly.Blocks["procedures_defreturn"].init);
+})(Blockly.Blocks['procedures_defreturn'].init);
// Create an extension that adds extra UI logic without modifying the core mutator methods
-Blockly.Extensions.register("custom_procedure_ui_extension", function () {
+Blockly.Extensions.register('custom_procedure_ui_extension', function () {
this.toggleDoBlock = function () {
const isInline = !this.isInline;
@@ -2095,28 +1981,23 @@ Blockly.Extensions.register("custom_procedure_ui_extension", function () {
updateShape(this, isInline);
// Optionally re-enable if previously disabled (for orphaned block UX)
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
// Fire Blockly events so undo/redo and UI updates are tracked
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
};
});
// Apply the extension to the built-in 'procedures_defreturn' block
-Blockly.Extensions.apply(
- "custom_procedure_ui_extension",
- Blockly.Blocks["procedures_defreturn"],
-);
+Blockly.Extensions.apply('custom_procedure_ui_extension', Blockly.Blocks['procedures_defreturn']);
// Extend the built-in Blockly procedures_defnoreturn block to add custom toggle functionality
// Reference to the original init function of the procedures_defnoreturn block
-Blockly.Blocks["procedures_defnoreturn"].init = (function (originalInit) {
+Blockly.Blocks['procedures_defnoreturn'].init = (function (originalInit) {
return function () {
// Call the original initialization function to ensure the block retains its default behaviour
originalInit.call(this);
@@ -2124,18 +2005,15 @@ Blockly.Blocks["procedures_defnoreturn"].init = (function (originalInit) {
// Use the existing addToggleButton helper to add the button to the block
addToggleButton(this);
};
-})(Blockly.Blocks["procedures_defnoreturn"].init);
+})(Blockly.Blocks['procedures_defnoreturn'].init);
// Apply the extension to the built-in 'procedures_defnoreturn' block
-Blockly.Extensions.apply(
- "custom_procedure_ui_extension",
- Blockly.Blocks["procedures_defnoreturn"],
-);
+Blockly.Extensions.apply('custom_procedure_ui_extension', Blockly.Blocks['procedures_defnoreturn']);
// Define unique IDs for each option
-Blockly.FieldVariable.ADD_VARIABLE_ID = "ADD_VARIABLE_ID";
-Blockly.FieldVariable.RENAME_VARIABLE_ID = "RENAME_VARIABLE_ID";
-Blockly.FieldVariable.DELETE_VARIABLE_ID = "DELETE_VARIABLE_ID";
+Blockly.FieldVariable.ADD_VARIABLE_ID = 'ADD_VARIABLE_ID';
+Blockly.FieldVariable.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID';
+Blockly.FieldVariable.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID';
// Extend `getOptions` to include "New variable..." at the top of the dropdown
const originalGetOptions = Blockly.FieldVariable.prototype.getOptions;
@@ -2144,10 +2022,7 @@ Blockly.FieldVariable.prototype.getOptions = function () {
const options = originalGetOptions.call(this);
// Add the "New variable..." option at the beginning
- options.unshift([
- translate("new_variable_decision"),
- Blockly.FieldVariable.ADD_VARIABLE_ID,
- ]);
+ options.unshift([translate('new_variable_decision'), Blockly.FieldVariable.ADD_VARIABLE_ID]);
return options;
};
@@ -2174,7 +2049,7 @@ Blockly.FieldVariable.prototype.onItemSelected_ = function (menu, menuItem) {
this.forceRerender(); // Refresh the UI to show the new selection
}
}
- },
+ }
);
} else {
// Use the stored reference to avoid recursion
@@ -2183,7 +2058,7 @@ Blockly.FieldVariable.prototype.onItemSelected_ = function (menu, menuItem) {
};
(function () {
- const dynamicIf = Blockly.Blocks["dynamic_if"];
+ const dynamicIf = Blockly.Blocks['dynamic_if'];
if (!dynamicIf) return;
const originalFinalize = dynamicIf.finalizeConnections;
@@ -2192,12 +2067,12 @@ Blockly.FieldVariable.prototype.onItemSelected_ = function (menu, menuItem) {
dynamicIf.mutationToDom = function () {
if (this._skipFinalizeInMutationToDom) {
if (!this.elseifCount && !this.elseCount) return null;
- const container = Blockly.utils.xml.createElement("mutation");
+ const container = Blockly.utils.xml.createElement('mutation');
if (this.elseifCount) {
- container.setAttribute("elseif", `${this.elseifCount}`);
+ container.setAttribute('elseif', `${this.elseifCount}`);
}
if (this.elseCount) {
- container.setAttribute("else", "1");
+ container.setAttribute('else', '1');
}
return container;
}
@@ -2227,16 +2102,10 @@ Blockly.FieldVariable.prototype.onItemSelected_ = function (menu, menuItem) {
// Fire one synthetic mutation event to represent the entire rebuild.
let mutationEvent;
- if (typeof Blockly.Events.Mutation === "function") {
+ if (typeof Blockly.Events.Mutation === 'function') {
mutationEvent = new Blockly.Events.Mutation(this, oldState, newState);
} else {
- mutationEvent = new Blockly.Events.BlockChange(
- this,
- "mutation",
- "",
- oldState,
- newState,
- );
+ mutationEvent = new Blockly.Events.BlockChange(this, 'mutation', '', oldState, newState);
}
Blockly.Events.fire(mutationEvent);
};
diff --git a/blocks/camera.js b/blocks/camera.js
index 5d32a98d0..6ef5aa3ed 100644
--- a/blocks/camera.js
+++ b/blocks/camera.js
@@ -1,146 +1,142 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
export function defineCameraBlocks() {
- Blockly.Blocks["camera_control"] = {
+ Blockly.Blocks['camera_control'] = {
init: function () {
this.jsonInit({
- type: "camera_control",
- message0: translate("camera_control"),
+ type: 'camera_control',
+ message0: translate('camera_control'),
args0: [
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: [
- getDropdownOption("rotateLeft"),
- getDropdownOption("rotateRight"),
- getDropdownOption("rotateUp"),
- getDropdownOption("rotateDown"),
- getDropdownOption("moveUp"),
- getDropdownOption("moveDown"),
- getDropdownOption("moveLeft"),
- getDropdownOption("moveRight"),
+ getDropdownOption('rotateLeft'),
+ getDropdownOption('rotateRight'),
+ getDropdownOption('rotateUp'),
+ getDropdownOption('rotateDown'),
+ getDropdownOption('moveUp'),
+ getDropdownOption('moveDown'),
+ getDropdownOption('moveLeft'),
+ getDropdownOption('moveRight'),
],
},
{
- type: "field_grid_dropdown",
- name: "KEY",
+ type: 'field_grid_dropdown',
+ name: 'KEY',
columns: 10,
options: [
- getDropdownOption("0"),
- getDropdownOption("1"),
- getDropdownOption("2"),
- getDropdownOption("3"),
- getDropdownOption("4"),
- getDropdownOption("5"),
- getDropdownOption("6"),
- getDropdownOption("7"),
- getDropdownOption("8"),
- getDropdownOption("9"),
- getDropdownOption("a"),
- getDropdownOption("b"),
- getDropdownOption("c"),
- getDropdownOption("d"),
- getDropdownOption("e"),
- getDropdownOption("f"),
- getDropdownOption("g"),
- getDropdownOption("h"),
- getDropdownOption("i"),
- getDropdownOption("j"),
- getDropdownOption("k"),
- getDropdownOption("l"),
- getDropdownOption("m"),
- getDropdownOption("n"),
- getDropdownOption("o"),
- getDropdownOption("p"),
- getDropdownOption("q"),
- getDropdownOption("r"),
- getDropdownOption("s"),
- getDropdownOption("t"),
- getDropdownOption("u"),
- getDropdownOption("v"),
- getDropdownOption("w"),
- getDropdownOption("x"),
- getDropdownOption("y"),
- getDropdownOption("z"),
- getDropdownOption(" "),
- getDropdownOption(","),
- getDropdownOption("."),
- getDropdownOption("/"),
- getDropdownOption("ArrowLeft"),
- getDropdownOption("ArrowUp"),
- getDropdownOption("ArrowRight"),
- getDropdownOption("ArrowDown"),
+ getDropdownOption('0'),
+ getDropdownOption('1'),
+ getDropdownOption('2'),
+ getDropdownOption('3'),
+ getDropdownOption('4'),
+ getDropdownOption('5'),
+ getDropdownOption('6'),
+ getDropdownOption('7'),
+ getDropdownOption('8'),
+ getDropdownOption('9'),
+ getDropdownOption('a'),
+ getDropdownOption('b'),
+ getDropdownOption('c'),
+ getDropdownOption('d'),
+ getDropdownOption('e'),
+ getDropdownOption('f'),
+ getDropdownOption('g'),
+ getDropdownOption('h'),
+ getDropdownOption('i'),
+ getDropdownOption('j'),
+ getDropdownOption('k'),
+ getDropdownOption('l'),
+ getDropdownOption('m'),
+ getDropdownOption('n'),
+ getDropdownOption('o'),
+ getDropdownOption('p'),
+ getDropdownOption('q'),
+ getDropdownOption('r'),
+ getDropdownOption('s'),
+ getDropdownOption('t'),
+ getDropdownOption('u'),
+ getDropdownOption('v'),
+ getDropdownOption('w'),
+ getDropdownOption('x'),
+ getDropdownOption('y'),
+ getDropdownOption('z'),
+ getDropdownOption(' '),
+ getDropdownOption(','),
+ getDropdownOption('.'),
+ getDropdownOption('/'),
+ getDropdownOption('ArrowLeft'),
+ getDropdownOption('ArrowUp'),
+ getDropdownOption('ArrowRight'),
+ getDropdownOption('ArrowDown'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("camera_control"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('camera_control'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["camera_follow"] = {
+ Blockly.Blocks['camera_follow'] = {
init: function () {
this.jsonInit({
- type: "camera_follow",
- message0: translate("camera_follow"),
+ type: 'camera_follow',
+ message0: translate('camera_follow'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "RADIUS",
- check: "Number",
+ type: 'input_value',
+ name: 'RADIUS',
+ check: 'Number',
},
{
- type: "field_checkbox",
- name: "FRONT",
+ type: 'field_checkbox',
+ name: 'FRONT',
checked: false,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("camera_follow"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('camera_follow'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["get_camera"] = {
+ Blockly.Blocks['get_camera'] = {
init: function () {
this.jsonInit({
- type: "get_camera",
- message0: translate("get_camera"),
+ type: 'get_camera',
+ message0: translate('get_camera'),
args0: [
{
- type: "field_variable",
- name: "VAR",
- variable: "camera", // Default variable is 'camera'
+ type: 'field_variable',
+ name: 'VAR',
+ variable: 'camera', // Default variable is 'camera'
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("get_camera"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('get_camera'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
}
diff --git a/blocks/colour.js b/blocks/colour.js
index 717da59a0..87c99a1de 100644
--- a/blocks/colour.js
+++ b/blocks/colour.js
@@ -1,113 +1,113 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import { translate, getTooltip } from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip } from '../main/translation.js';
export function defineColourBlocks() {
// Basic colour picker block
- Blockly.Blocks["colour_picker"] = {
+ Blockly.Blocks['colour_picker'] = {
init: function () {
this.jsonInit({
- type: "colour_picker",
- message0: "%1",
+ type: 'colour_picker',
+ message0: '%1',
args0: [
{
- type: "field_colour",
- name: "COLOUR",
- colour: "#ff0000",
+ type: 'field_colour',
+ name: 'COLOUR',
+ colour: '#ff0000',
},
],
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("colour_picker"),
- helpUrl: "",
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('colour_picker'),
+ helpUrl: '',
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
// Random colour block
- Blockly.Blocks["colour_random"] = {
+ Blockly.Blocks['colour_random'] = {
init: function () {
this.jsonInit({
- type: "colour_random",
- message0: translate("colour_random"),
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("colour_random"),
- helpUrl: "",
+ type: 'colour_random',
+ message0: translate('colour_random'),
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('colour_random'),
+ helpUrl: '',
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
// RGB colour block
- Blockly.Blocks["colour_rgb"] = {
+ Blockly.Blocks['colour_rgb'] = {
init: function () {
this.jsonInit({
- type: "colour_rgb",
- message0: translate("colour_rgb"),
+ type: 'colour_rgb',
+ message0: translate('colour_rgb'),
args0: [
{
- type: "input_value",
- name: "RED",
- check: "Number",
+ type: 'input_value',
+ name: 'RED',
+ check: 'Number',
},
{
- type: "input_value",
- name: "GREEN",
- check: "Number",
+ type: 'input_value',
+ name: 'GREEN',
+ check: 'Number',
},
{
- type: "input_value",
- name: "BLUE",
- check: "Number",
+ type: 'input_value',
+ name: 'BLUE',
+ check: 'Number',
},
],
inputsInline: true,
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("colour_rgb"),
- helpUrl: "",
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('colour_rgb'),
+ helpUrl: '',
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
// Blend colours block
- Blockly.Blocks["colour_blend"] = {
+ Blockly.Blocks['colour_blend'] = {
init: function () {
this.jsonInit({
- type: "colour_blend",
- message0: translate("colour_blend"),
+ type: 'colour_blend',
+ message0: translate('colour_blend'),
args0: [
{
- type: "input_value",
- name: "COLOUR1",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOUR1',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "COLOUR2",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOUR2',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "RATIO",
- check: "Number",
+ type: 'input_value',
+ name: 'RATIO',
+ check: 'Number',
},
],
inputsInline: true,
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("colour_blend"),
- helpUrl: "",
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('colour_blend'),
+ helpUrl: '',
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
}
diff --git a/blocks/colourvalidation.js b/blocks/colourvalidation.js
index 62b3f237e..0d0179568 100644
--- a/blocks/colourvalidation.js
+++ b/blocks/colourvalidation.js
@@ -1,12 +1,12 @@
// CSS keywords that resolve against surrounding context and so have no
// meaning for a Babylon material colour; treated as invalid.
const CONTEXT_DEPENDENT_KEYWORDS = new Set([
- "currentcolor",
- "inherit",
- "initial",
- "unset",
- "revert",
- "revert-layer",
+ 'currentcolor',
+ 'inherit',
+ 'initial',
+ 'unset',
+ 'revert',
+ 'revert-layer',
]);
// True when the string is something colour_from_string can turn into a real
@@ -14,7 +14,7 @@ const CONTEXT_DEPENDENT_KEYWORDS = new Set([
// rgb(), hsl(), transparent, ...), excluding the context-dependent keywords
// and var() references (which resolve against surrounding custom properties).
export function isValidColourInput(raw) {
- const value = typeof raw === "string" ? raw.trim().replace(/^#/, "") : "";
+ const value = typeof raw === 'string' ? raw.trim().replace(/^#/, '') : '';
if (!value) return false;
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value)) return true;
// var() references only resolve against a surrounding context we don't have,
@@ -22,7 +22,7 @@ export function isValidColourInput(raw) {
if (/var\s*\(/i.test(value)) return false;
if (CONTEXT_DEPENDENT_KEYWORDS.has(value.toLowerCase())) return false;
const probe = new Option().style;
- probe.color = "";
+ probe.color = '';
probe.color = value;
- return probe.color !== "";
+ return probe.color !== '';
}
diff --git a/blocks/combine.js b/blocks/combine.js
index 1da2bd23a..be8d47c5f 100644
--- a/blocks/combine.js
+++ b/blocks/combine.js
@@ -1,43 +1,42 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
getHelpUrlFor,
nextVariableIndexes,
handleBlockCreateEvent,
registerBlockHandler,
-} from "./blocks.js";
-import { translate, getTooltip } from "../main/translation.js";
+} from './blocks.js';
+import { translate, getTooltip } from '../main/translation.js';
export function defineCombineBlocks() {
- Blockly.Blocks["merge_meshes"] = {
+ Blockly.Blocks['merge_meshes'] = {
init: function () {
- const variableNamePrefix = "merged";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'merged';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "merge_meshes",
- message0: translate("merge_meshes"),
+ type: 'merge_meshes',
+ message0: translate('merge_meshes'),
args0: [
{
- type: "field_variable",
- name: "RESULT_VAR",
+ type: 'field_variable',
+ name: 'RESULT_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "MESH_LIST",
- check: "Array",
+ type: 'input_value',
+ name: 'MESH_LIST',
+ check: 'Array',
},
],
- colour: categoryColours["Transform"],
- tooltip: getTooltip("merge_meshes"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('merge_meshes'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -45,46 +44,45 @@ export function defineCombineBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "RESULT_VAR",
- ),
+ 'RESULT_VAR'
+ )
);
},
};
- Blockly.Blocks["subtract_meshes"] = {
+ Blockly.Blocks['subtract_meshes'] = {
init: function () {
- const variableNamePrefix = "subtracted";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'subtracted';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "subtract_meshes",
- message0: translate("subtract_meshes"),
+ type: 'subtract_meshes',
+ message0: translate('subtract_meshes'),
args0: [
{
- type: "field_variable",
- name: "RESULT_VAR",
+ type: 'field_variable',
+ name: 'RESULT_VAR',
variable: nextVariableName,
},
{
- type: "field_variable",
- name: "BASE_MESH",
- variable: "object",
+ type: 'field_variable',
+ name: 'BASE_MESH',
+ variable: 'object',
},
{
- type: "input_value",
- name: "MESH_LIST",
- check: "Array",
+ type: 'input_value',
+ name: 'MESH_LIST',
+ check: 'Array',
},
],
- colour: categoryColours["Transform"],
- tooltip: getTooltip("subtract_meshes"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('subtract_meshes'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -92,41 +90,40 @@ export function defineCombineBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "RESULT_VAR",
- ),
+ 'RESULT_VAR'
+ )
);
},
};
- Blockly.Blocks["intersection_meshes"] = {
+ Blockly.Blocks['intersection_meshes'] = {
init: function () {
- const variableNamePrefix = "intersection";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'intersection';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "intersection_meshes",
- message0: translate("intersection_meshes"),
+ type: 'intersection_meshes',
+ message0: translate('intersection_meshes'),
args0: [
{
- type: "field_variable",
- name: "RESULT_VAR",
+ type: 'field_variable',
+ name: 'RESULT_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "MESH_LIST",
- check: "Array",
+ type: 'input_value',
+ name: 'MESH_LIST',
+ check: 'Array',
},
],
- colour: categoryColours["Transform"],
- tooltip: getTooltip("intersection_meshes"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('intersection_meshes'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -134,41 +131,40 @@ export function defineCombineBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "RESULT_VAR",
- ),
+ 'RESULT_VAR'
+ )
);
},
};
- Blockly.Blocks["hull_meshes"] = {
+ Blockly.Blocks['hull_meshes'] = {
init: function () {
- const variableNamePrefix = "hull";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'hull';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "hull_meshes",
- message0: translate("hull_meshes"),
+ type: 'hull_meshes',
+ message0: translate('hull_meshes'),
args0: [
{
- type: "field_variable",
- name: "RESULT_VAR",
+ type: 'field_variable',
+ name: 'RESULT_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "MESH_LIST",
- check: "Array",
+ type: 'input_value',
+ name: 'MESH_LIST',
+ check: 'Array',
},
],
- colour: categoryColours["Transform"],
- tooltip: getTooltip("hull_meshes"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('hull_meshes'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -176,8 +172,8 @@ export function defineCombineBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "RESULT_VAR",
- ),
+ 'RESULT_VAR'
+ )
);
},
};
diff --git a/blocks/condition.js b/blocks/condition.js
index e6354d6ad..0d1559338 100644
--- a/blocks/condition.js
+++ b/blocks/condition.js
@@ -1,31 +1,31 @@
-import * as Blockly from "blockly";
-import { getHelpUrlFor } from "./blocks.js";
+import * as Blockly from 'blockly';
+import { getHelpUrlFor } from './blocks.js';
export function defineConditionBlocks() {
- Blockly.Blocks["controls_if"] = Blockly.Blocks["dynamic_if"];
+ Blockly.Blocks['controls_if'] = Blockly.Blocks['dynamic_if'];
- const oldInit = Blockly.Blocks["controls_if"].init;
+ const oldInit = Blockly.Blocks['controls_if'].init;
- Blockly.Blocks["controls_if"].init = function () {
+ Blockly.Blocks['controls_if'].init = function () {
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("condition_blocks");
+ this.setStyle('condition_blocks');
// Call the original init function
oldInit.call(this);
// Override the tooltip after the original init
this.setTooltip(() => {
- let tooltip = "Execute actions if a condition is true.";
+ let tooltip = 'Execute actions if a condition is true.';
tooltip += ` Drag additional conditions to create else if branches.`;
- tooltip += " Drag a statement at the end to create an else branch.";
+ tooltip += ' Drag a statement at the end to create an else branch.';
return tooltip;
});
};
- Blockly.Blocks["controls_if_custom"] = {
+ Blockly.Blocks['controls_if_custom'] = {
init: function () {
this.elseifCount_ = 0;
this.elseCount_ = 0;
@@ -57,7 +57,7 @@ export function defineConditionBlocks() {
makePlusIcon_: function (onClick) {
return new Blockly.FieldImage(
- "data:image/svg+xml;utf8," +
+ 'data:image/svg+xml;utf8,' +
encodeURIComponent(`
@@ -66,14 +66,14 @@ export function defineConditionBlocks() {
`),
20,
20,
- "add branch",
- onClick,
+ 'add branch',
+ onClick
);
},
makeMinusIcon_: function (onClick) {
return new Blockly.FieldImage(
- "data:image/svg+xml;utf8," +
+ 'data:image/svg+xml;utf8,' +
encodeURIComponent(`
@@ -81,8 +81,8 @@ export function defineConditionBlocks() {
`),
20,
20,
- "remove branch",
- onClick,
+ 'remove branch',
+ onClick
);
},
@@ -91,40 +91,34 @@ export function defineConditionBlocks() {
while (this.inputList.length) this.removeInput(this.inputList[0].name);
// IF0 row
- this.appendValueInput("IF0")
- .setCheck("Boolean")
- .appendField("if");
- this.appendDummyInput("ICONS_IF0")
+ this.appendValueInput('IF0').setCheck('Boolean').appendField('if');
+ this.appendDummyInput('ICONS_IF0')
.setAlign(Blockly.inputs.Align.RIGHT)
.appendField(this.makePlusIcon_(() => this.addElseIfAt(0)));
- this.appendStatementInput("DO0").appendField(new Blockly.FieldLabel(""));
+ this.appendStatementInput('DO0').appendField(new Blockly.FieldLabel(''));
// ELSE IFs
for (let i = 1; i <= this.elseifCount_; i++) {
- this.appendValueInput("IF" + i)
- .setCheck("Boolean")
- .appendField("else if");
- this.appendDummyInput("ICONS_" + i)
+ this.appendValueInput('IF' + i)
+ .setCheck('Boolean')
+ .appendField('else if');
+ this.appendDummyInput('ICONS_' + i)
.setAlign(Blockly.inputs.Align.RIGHT)
.appendField(this.makePlusIcon_(() => this.addElseIfAt(i)))
.appendField(this.makeMinusIcon_(() => this.removeElseIf_(i)));
- this.appendStatementInput("DO" + i).appendField(
- new Blockly.FieldLabel(""),
- );
+ this.appendStatementInput('DO' + i).appendField(new Blockly.FieldLabel(''));
}
// ELSE
if (this.elseCount_) {
- const elseInput = this.appendDummyInput("ELSE_ROW");
- elseInput.appendField("else");
+ const elseInput = this.appendDummyInput('ELSE_ROW');
+ elseInput.appendField('else');
elseInput.appendField(this.makeMinusIcon_(() => this.removeElse_()));
- this.appendStatementInput("ELSE")
- .setCheck(null)
- .appendField(new Blockly.FieldLabel(""));
+ this.appendStatementInput('ELSE').setCheck(null).appendField(new Blockly.FieldLabel(''));
}
- const bottom = this.appendDummyInput("BOTTOM_ADD");
+ const bottom = this.appendDummyInput('BOTTOM_ADD');
bottom
.setAlign(Blockly.inputs.Align.RIGHT)
.appendField(this.makePlusIcon_(() => this.addElseOrElseIf()));
@@ -144,29 +138,29 @@ export function defineConditionBlocks() {
addElseIfAt: function (index) {
this.elseifCount_++;
for (let i = this.elseifCount_ - 1; i >= index + 1; i--) {
- this.renameInput_("IF" + i, "IF" + (i + 1));
- this.renameInput_("DO" + i, "DO" + (i + 1));
- this.renameInput_("ICONS_" + i, "ICONS_" + (i + 1));
+ this.renameInput_('IF' + i, 'IF' + (i + 1));
+ this.renameInput_('DO' + i, 'DO' + (i + 1));
+ this.renameInput_('ICONS_' + i, 'ICONS_' + (i + 1));
}
this.updateShape_();
},
removeElseIf_: function (index) {
- this.removeInput("IF" + index);
- this.removeInput("DO" + index);
- this.removeInput("ICONS_" + index);
+ this.removeInput('IF' + index);
+ this.removeInput('DO' + index);
+ this.removeInput('ICONS_' + index);
for (let i = index + 1; i <= this.elseifCount_; i++) {
- this.renameInput_("IF" + i, "IF" + (i - 1));
- this.renameInput_("DO" + i, "DO" + (i - 1));
- this.renameInput_("ICONS_" + i, "ICONS_" + (i - 1));
+ this.renameInput_('IF' + i, 'IF' + (i - 1));
+ this.renameInput_('DO' + i, 'DO' + (i - 1));
+ this.renameInput_('ICONS_' + i, 'ICONS_' + (i - 1));
}
this.elseifCount_--;
this.updateShape_();
},
removeElse_: function () {
- this.removeInput("ELSE");
- this.removeInput("ELSE_ROW");
+ this.removeInput('ELSE');
+ this.removeInput('ELSE_ROW');
this.elseCount_ = 0;
this.updateShape_();
},
@@ -177,16 +171,15 @@ export function defineConditionBlocks() {
},
mutationToDom: function () {
- const container = document.createElement("mutation");
- if (this.elseifCount_)
- container.setAttribute("elseif", this.elseifCount_);
- if (this.elseCount_) container.setAttribute("else", 1);
+ const container = document.createElement('mutation');
+ if (this.elseifCount_) container.setAttribute('elseif', this.elseifCount_);
+ if (this.elseCount_) container.setAttribute('else', 1);
return container;
},
domToMutation: function (xmlElement) {
- this.elseifCount_ = parseInt(xmlElement.getAttribute("elseif")) || 0;
- this.elseCount_ = parseInt(xmlElement.getAttribute("else")) || 0;
+ this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif')) || 0;
+ this.elseCount_ = parseInt(xmlElement.getAttribute('else')) || 0;
this.updateShape_();
},
diff --git a/blocks/connect.js b/blocks/connect.js
index 83d7a77c1..b5dee4773 100644
--- a/blocks/connect.js
+++ b/blocks/connect.js
@@ -1,329 +1,325 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
-import { getAttachNames } from "../config.js";
+import { getAttachNames } from '../config.js';
export function defineConnectBlocks() {
- Blockly.Blocks["parent"] = {
+ Blockly.Blocks['parent'] = {
init: function () {
this.jsonInit({
- type: "parent",
- message0: translate("parent"),
+ type: 'parent',
+ message0: translate('parent'),
args0: [
{
- type: "field_variable",
- name: "PARENT_MESH",
- variable: "parent",
+ type: 'field_variable',
+ name: 'PARENT_MESH',
+ variable: 'parent',
},
{
- type: "field_variable",
- name: "CHILD_MESH",
+ type: 'field_variable',
+ name: 'CHILD_MESH',
variable: window.currentMesh,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("parent"),
+ tooltip: getTooltip('parent'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["parent_child"] = {
+ Blockly.Blocks['parent_child'] = {
init: function () {
this.jsonInit({
- type: "parent_child",
- message0: translate("parent_child"),
+ type: 'parent_child',
+ message0: translate('parent_child'),
args0: [
{
- type: "field_variable",
- name: "PARENT_MESH",
- variable: "parent",
+ type: 'field_variable',
+ name: 'PARENT_MESH',
+ variable: 'parent',
},
{
- type: "field_variable",
- name: "CHILD_MESH",
+ type: 'field_variable',
+ name: 'CHILD_MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'X_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Y_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Z_OFFSET',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("parent_child"),
+ tooltip: getTooltip('parent_child'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["remove_parent"] = {
+ Blockly.Blocks['remove_parent'] = {
init: function () {
this.jsonInit({
- type: "remove_parent",
- message0: translate("remove_parent"),
+ type: 'remove_parent',
+ message0: translate('remove_parent'),
args0: [
{
- type: "field_variable",
- name: "CHILD_MESH",
+ type: 'field_variable',
+ name: 'CHILD_MESH',
variable: window.currentMesh,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("remove_parent"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('remove_parent'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["stop_follow"] = {
+ Blockly.Blocks['stop_follow'] = {
init: function () {
this.jsonInit({
- type: "stop_follow",
- message0: translate("stop_follow"),
+ type: 'stop_follow',
+ message0: translate('stop_follow'),
args0: [
{
- type: "field_variable",
- name: "FOLLOWER_MESH",
+ type: 'field_variable',
+ name: 'FOLLOWER_MESH',
variable: window.currentMesh,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("stop_follow"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('stop_follow'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["hold"] = {
+ Blockly.Blocks['hold'] = {
init: function () {
this.jsonInit({
- type: "hold",
- message0: translate("hold"),
+ type: 'hold',
+ message0: translate('hold'),
args0: [
{
- type: "field_variable",
- name: "TARGET_MESH",
- variable: "target",
+ type: 'field_variable',
+ name: 'TARGET_MESH',
+ variable: 'target',
},
{
- type: "field_variable",
- name: "MESH_TO_ATTACH",
- variable: "object",
+ type: 'field_variable',
+ name: 'MESH_TO_ATTACH',
+ variable: 'object',
},
{
- type: "input_value",
- name: "X_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("hold"),
+ tooltip: getTooltip('hold'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["attach"] = {
+ Blockly.Blocks['attach'] = {
init: function () {
this.jsonInit({
- type: "attach",
- message0: translate("attach"),
+ type: 'attach',
+ message0: translate('attach'),
args0: [
{
- type: "field_variable",
- name: "MESH_TO_ATTACH",
- variable: "object",
+ type: 'field_variable',
+ name: 'MESH_TO_ATTACH',
+ variable: 'object',
},
{
- type: "field_variable",
- name: "TARGET_MESH",
- variable: "target",
+ type: 'field_variable',
+ name: 'TARGET_MESH',
+ variable: 'target',
},
{
- type: "field_dropdown",
- name: "BONE_NAME",
+ type: 'field_dropdown',
+ name: 'BONE_NAME',
options: getAttachNames(),
},
{
- type: "input_value",
- name: "X_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z_OFFSET",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z_OFFSET',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("attach"),
+ tooltip: getTooltip('attach'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["drop"] = {
+ Blockly.Blocks['drop'] = {
init: function () {
this.jsonInit({
- type: "drop",
- message0: translate("drop"),
+ type: 'drop',
+ message0: translate('drop'),
args0: [
{
- type: "field_variable",
- name: "MESH_TO_DETACH",
- variable: "object",
+ type: 'field_variable',
+ name: 'MESH_TO_DETACH',
+ variable: 'object',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("drop"),
+ tooltip: getTooltip('drop'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["follow"] = {
+ Blockly.Blocks['follow'] = {
init: function () {
this.jsonInit({
- type: "follow",
- message0: translate("follow"),
+ type: 'follow',
+ message0: translate('follow'),
args0: [
{
- type: "field_variable",
- name: "FOLLOWER_MESH",
- variable: "follower",
+ type: 'field_variable',
+ name: 'FOLLOWER_MESH',
+ variable: 'follower',
},
{
- type: "field_variable",
- name: "TARGET_MESH",
- variable: "target",
+ type: 'field_variable',
+ name: 'TARGET_MESH',
+ variable: 'target',
},
{
- type: "field_dropdown",
- name: "FOLLOW_POSITION",
+ type: 'field_dropdown',
+ name: 'FOLLOW_POSITION',
options: [
- getDropdownOption("TOP"),
- getDropdownOption("CENTER"),
- getDropdownOption("BOTTOM"),
+ getDropdownOption('TOP'),
+ getDropdownOption('CENTER'),
+ getDropdownOption('BOTTOM'),
],
},
{
- type: "input_value",
- name: "X_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'X_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Y_OFFSET',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z_OFFSET",
- check: "Number",
+ type: 'input_value',
+ name: 'Z_OFFSET',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("follow"),
+ tooltip: getTooltip('follow'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["export_mesh"] = {
+ Blockly.Blocks['export_mesh'] = {
init: function () {
this.jsonInit({
- type: "export_mesh",
- message0: translate("export_mesh"),
+ type: 'export_mesh',
+ message0: translate('export_mesh'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "FORMAT",
+ type: 'field_dropdown',
+ name: 'FORMAT',
options: [
- ["STL", "STL"],
- ["OBJ", "OBJ"],
- ["GLB", "GLB"],
+ ['STL', 'STL'],
+ ['OBJ', 'OBJ'],
+ ['GLB', 'GLB'],
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("export_mesh"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('export_mesh'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
}
diff --git a/blocks/control.js b/blocks/control.js
index a48f78b4e..89c00f422 100644
--- a/blocks/control.js
+++ b/blocks/control.js
@@ -1,155 +1,152 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor, applyInputAriaLabels } from "./blocks.js";
-import { translate, getTooltip } from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor, applyInputAriaLabels } from './blocks.js';
+import { translate, getTooltip } from '../main/translation.js';
export function defineControlBlocks() {
- Blockly.Blocks["wait"] = {
+ Blockly.Blocks['wait'] = {
init: function () {
this.jsonInit({
- type: "wait",
- message0: translate("wait"),
+ type: 'wait',
+ message0: translate('wait'),
args0: [
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Control"],
- tooltip: getTooltip("wait"),
+ colour: categoryColours['Control'],
+ tooltip: getTooltip('wait'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
},
};
- Blockly.Blocks["wait_seconds"] = {
+ Blockly.Blocks['wait_seconds'] = {
init: function () {
this.jsonInit({
- type: "wait",
- message0: translate("wait_seconds"),
+ type: 'wait',
+ message0: translate('wait_seconds'),
args0: [
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Control"],
- tooltip: getTooltip("wait_seconds"),
+ colour: categoryColours['Control'],
+ tooltip: getTooltip('wait_seconds'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
},
};
- Blockly.Blocks["wait_until"] = {
+ Blockly.Blocks['wait_until'] = {
init: function () {
this.jsonInit({
- type: "wait_until",
- message0: translate("wait_until"),
+ type: 'wait_until',
+ message0: translate('wait_until'),
args0: [
{
- type: "input_value",
- name: "CONDITION",
- check: "Boolean",
+ type: 'input_value',
+ name: 'CONDITION',
+ check: 'Boolean',
},
],
previousStatement: null,
nextStatement: null,
//colour: categoryColours["Control"],
- tooltip: getTooltip("wait_until"),
+ tooltip: getTooltip('wait_until'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
},
};
- Blockly.Blocks["local_variable"] = {
+ Blockly.Blocks['local_variable'] = {
init: function () {
this.jsonInit({
- type: "local_variable",
- message0: translate("local_variable"),
+ type: 'local_variable',
+ message0: translate('local_variable'),
args0: [
{
- type: "field_variable",
- name: "VAR",
- variable: "item", // default variable name
+ type: 'field_variable',
+ name: 'VAR',
+ variable: 'item', // default variable name
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Control"],
- tooltip: getTooltip("local_variable"),
+ colour: categoryColours['Control'],
+ tooltip: getTooltip('local_variable'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
},
};
- Blockly.Blocks["for_loop"] = {
+ Blockly.Blocks['for_loop'] = {
init: function () {
this.jsonInit({
- type: "for_loop",
- message0: translate("for_loop"),
+ type: 'for_loop',
+ message0: translate('for_loop'),
args0: [
{
- type: "field_lexical_variable",
- name: "VAR",
- text: "count", // Default variable name is "count"
- options: [["count", "count"]],
+ type: 'field_lexical_variable',
+ name: 'VAR',
+ text: 'count', // Default variable name is "count"
+ options: [['count', 'count']],
},
{
- type: "input_value",
- name: "FROM",
- check: "Number",
+ type: 'input_value',
+ name: 'FROM',
+ check: 'Number',
},
{
- type: "input_value",
- name: "TO",
- check: "Number",
+ type: 'input_value',
+ name: 'TO',
+ check: 'Number',
},
{
- type: "input_value",
- name: "BY",
- check: "Number",
+ type: 'input_value',
+ name: 'BY',
+ check: 'Number',
},
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
previousStatement: null,
nextStatement: null,
//colour: categoryColours["Control"],
inputsInline: true,
- tooltip: getTooltip("for_loop"),
+ tooltip: getTooltip('for_loop'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
},
// Returns an array of local variable names.
getLexicalVariables: function () {
- return [this.getFieldValue("VAR")];
+ return [this.getFieldValue('VAR')];
},
// Update the variable name on this block.
setLexicalVariable: function (newName) {
- this.setFieldValue(String(newName), "VAR");
+ this.setFieldValue(String(newName), 'VAR');
},
onchange: function (event) {
- if (
- event.type === Blockly.Events.BLOCK_CREATE &&
- event.ids.includes(this.id)
- ) {
- const field = this.getField("VAR");
+ if (event.type === Blockly.Events.BLOCK_CREATE && event.ids.includes(this.id)) {
+ const field = this.getField('VAR');
if (field && field.variableId_) {
const oldId = field.variableId_;
const newId = Blockly.utils.idGenerator.genUid();
@@ -157,17 +154,14 @@ export function defineControlBlocks() {
// Recursively update nested getter blocks.
const updateNestedBlocks = (block) => {
- if (
- block.type === "get_lexical_variable" &&
- block.variableSourceId === oldId
- ) {
+ if (block.type === 'get_lexical_variable' && block.variableSourceId === oldId) {
block.variableSourceId = newId;
}
block.getChildren(false).forEach(updateNestedBlocks);
};
// Update getter blocks inside the DO input.
- const doConnection = this.getInput("DO")?.connection;
+ const doConnection = this.getInput('DO')?.connection;
if (doConnection && doConnection.targetBlock()) {
updateNestedBlocks(doConnection.targetBlock());
}
@@ -177,31 +171,31 @@ export function defineControlBlocks() {
// Save the current variable name and its unique id in a mutation.
mutationToDom: function () {
- const container = document.createElement("mutation");
- const field = this.getField("VAR");
+ const container = document.createElement('mutation');
+ const field = this.getField('VAR');
if (field && field.saveExtraState) {
const extraState = field.saveExtraState();
- container.setAttribute("var", extraState.value);
- container.setAttribute("variableid", extraState.variableId);
+ container.setAttribute('var', extraState.value);
+ container.setAttribute('variableid', extraState.variableId);
} else {
- container.setAttribute("var", this.getFieldValue("VAR"));
+ container.setAttribute('var', this.getFieldValue('VAR'));
}
return container;
},
// Restore the variable name and unique id from the mutation.
domToMutation: function (xmlElement) {
- const varName = xmlElement.getAttribute("var");
- const variableId = xmlElement.getAttribute("variableid");
- const field = this.getField("VAR");
+ const varName = xmlElement.getAttribute('var');
+ const variableId = xmlElement.getAttribute('variableid');
+ const field = this.getField('VAR');
if (field && field.loadExtraState) {
field.loadExtraState({
value: varName,
variableId: variableId,
});
} else {
- this.setFieldValue(varName, "VAR");
+ this.setFieldValue(varName, 'VAR');
}
},
};
@@ -209,8 +203,8 @@ export function defineControlBlocks() {
class FieldLexicalVariable extends Blockly.FieldDropdown {
constructor(opt_value, opt_validator) {
// Force a default value of "count" if none is provided.
- if (opt_value === null || opt_value === undefined || opt_value === "") {
- opt_value = "count";
+ if (opt_value === null || opt_value === undefined || opt_value === '') {
+ opt_value = 'count';
}
super(opt_value, opt_validator);
// Always use our custom options generator.
@@ -222,7 +216,7 @@ export function defineControlBlocks() {
// Ensure that the value is set (if somehow still null).
if (!this.getValue()) {
- super.setValue("count");
+ super.setValue('count');
}
}
@@ -230,16 +224,16 @@ export function defineControlBlocks() {
computeOptions() {
// If the field's value is null-ish, force it to "count".
let current = super.getValue();
- if (current === null || current === undefined || current === "") {
- current = "count";
+ if (current === null || current === undefined || current === '') {
+ current = 'count';
super.setValue(current);
} else {
current = String(current);
}
return [
[current, current],
- ["Rename variableโฆ", "__RENAME__"],
- ["Get variable", "__GET__"],
+ ['Rename variableโฆ', '__RENAME__'],
+ ['Get variable', '__GET__'],
];
}
@@ -263,27 +257,24 @@ export function defineControlBlocks() {
}
getAriaTypeName() {
- return "variable";
+ return 'variable';
}
setValue(value) {
- if (value === "__RENAME__") {
+ if (value === '__RENAME__') {
setTimeout(() => {
const currentName = String(super.getValue());
- const newName = window.prompt("Rename variable", currentName);
+ const newName = window.prompt('Rename variable', currentName);
if (newName && newName !== currentName) {
- if (
- this.sourceBlock_ &&
- typeof this.sourceBlock_.setLexicalVariable === "function"
- ) {
+ if (this.sourceBlock_ && typeof this.sourceBlock_.setLexicalVariable === 'function') {
this.sourceBlock_.setLexicalVariable(String(newName));
}
// Recompute and "lock in" our options with the new variable.
this.cachedOptions_ = [
[String(newName), String(newName)],
- ["Rename variableโฆ", "__RENAME__"],
- ["Get variable", "__GET__"],
+ ['Rename variableโฆ', '__RENAME__'],
+ ['Get variable', '__GET__'],
];
// Force our generator to return the updated options.
this.menuGenerator_ = () => this.cachedOptions_;
@@ -293,13 +284,13 @@ export function defineControlBlocks() {
const allBlocks = workspace.getAllBlocks(false);
allBlocks.forEach((block) => {
if (
- block.type === "get_lexical_variable" &&
+ block.type === 'get_lexical_variable' &&
block.variableSourceId === this.variableId_
) {
- if (typeof block.updateVariable === "function") {
+ if (typeof block.updateVariable === 'function') {
block.updateVariable(newName);
} else {
- block.setFieldValue(String(newName), "VAR");
+ block.setFieldValue(String(newName), 'VAR');
}
}
});
@@ -312,14 +303,14 @@ export function defineControlBlocks() {
}
}, 0);
return null;
- } else if (value === "__GET__") {
+ } else if (value === '__GET__') {
setTimeout(() => {
const variableName = String(super.getValue());
const workspace = this.sourceBlock_.workspace;
- const newBlock = workspace.newBlock("get_lexical_variable");
+ const newBlock = workspace.newBlock('get_lexical_variable');
newBlock.initSvg();
newBlock.render();
- newBlock.setFieldValue(String(variableName), "VAR");
+ newBlock.setFieldValue(String(variableName), 'VAR');
newBlock.variableSourceId = this.variableId_;
const xy = this.sourceBlock_.getRelativeToSurfaceXY();
@@ -347,63 +338,60 @@ export function defineControlBlocks() {
super.setValue(state.value);
this.cachedOptions_ = [
[state.value, state.value],
- ["Rename variableโฆ", "__RENAME__"],
- ["Get variable", "__GET__"],
+ ['Rename variableโฆ', '__RENAME__'],
+ ['Get variable', '__GET__'],
];
this.menuGenerator_ = () => this.cachedOptions_;
}
}
- Blockly.fieldRegistry.register(
- "field_lexical_variable",
- FieldLexicalVariable,
- );
+ Blockly.fieldRegistry.register('field_lexical_variable', FieldLexicalVariable);
- Blockly.Blocks["get_lexical_variable"] = {
+ Blockly.Blocks['get_lexical_variable'] = {
init: function () {
this.jsonInit({
- message0: translate("get_lexical_variable"),
+ message0: translate('get_lexical_variable'),
args0: [
{
- type: "field_label",
- name: "VAR",
- text: "count",
+ type: 'field_label',
+ name: 'VAR',
+ text: 'count',
},
],
output: null,
- colour: categoryColours["Variables"],
- tooltip: getTooltip("get_lexical_variable"),
+ colour: categoryColours['Variables'],
+ tooltip: getTooltip('get_lexical_variable'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
// Initialize with a null variable source ID.
this.variableSourceId = null;
},
updateVariable: function (newName) {
- this.setFieldValue(String(newName), "VAR");
+ this.setFieldValue(String(newName), 'VAR');
},
// Save the current variable name and source ID to the XML mutation.
mutationToDom: function () {
- const container = document.createElement("mutation");
- container.setAttribute("var", this.getFieldValue("VAR"));
+ const container = document.createElement('mutation');
+ container.setAttribute('var', this.getFieldValue('VAR'));
if (this.variableSourceId) {
- container.setAttribute("sourceid", this.variableSourceId);
+ container.setAttribute('sourceid', this.variableSourceId);
}
return container;
},
// Restore the variable name and source ID from the XML mutation.
domToMutation: function (xmlElement) {
- const variableName = xmlElement.getAttribute("var");
- this.setFieldValue(variableName, "VAR");
- const sourceId = xmlElement.getAttribute("sourceid");
+ const variableName = xmlElement.getAttribute('var');
+ this.setFieldValue(variableName, 'VAR');
+ const sourceId = xmlElement.getAttribute('sourceid');
if (sourceId) {
this.variableSourceId = sourceId;
}
},
};
- Blockly.Blocks["get_lexical_variable"].onchange = function (event) {
+ Blockly.Blocks['get_lexical_variable'].onchange = function (event) {
// Only process if this is a move, create, or similar event that might affect scoping
if (
event.type === Blockly.Events.BLOCK_MOVE ||
@@ -412,19 +400,19 @@ export function defineControlBlocks() {
) {
if (!this.workspace) return; // Skip if no workspace
- const variableName = this.getFieldValue("VAR");
+ const variableName = this.getFieldValue('VAR');
let currentBlock = this;
let found = false;
// Traverse up the block hierarchy to find the closest for_loop with matching variable
while ((currentBlock = currentBlock.getParent())) {
- if (currentBlock.type === "for_loop") {
- const loopVarName = currentBlock.getFieldValue("VAR");
- const field = currentBlock.getField("VAR");
+ if (currentBlock.type === 'for_loop') {
+ const loopVarName = currentBlock.getFieldValue('VAR');
+ const field = currentBlock.getField('VAR');
if (loopVarName === variableName && field) {
// Found a matching for_loop parent
- const variableId = field.variableId_ || "";
+ const variableId = field.variableId_ || '';
// Update this getter's source ID
this.variableSourceId = variableId;
@@ -444,12 +432,12 @@ export function defineControlBlocks() {
}
};
- const MODE = { IF: "IF", ELSEIF: "ELSEIF", ELSE: "ELSE" };
- const INVALID_IF_STACK_REASON = "INVALID_IF_STACK";
+ const MODE = { IF: 'IF', ELSEIF: 'ELSEIF', ELSE: 'ELSE' };
+ const INVALID_IF_STACK_REASON = 'INVALID_IF_STACK';
- Blockly.Blocks["if_clause"] = {
+ Blockly.Blocks['if_clause'] = {
init: function () {
- this.setStyle("logic_blocks");
+ this.setStyle('logic_blocks');
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setInputsInline(true);
@@ -459,29 +447,29 @@ export function defineControlBlocks() {
// True while Blockly is constructing/rehydrating the block graph.
this._isRestoring = true;
- this.appendDummyInput("HEAD").appendField(
+ this.appendDummyInput('HEAD').appendField(
new Blockly.FieldDropdown(
[
- ["%{BKY_CONTROLS_IF_MSG_IF}", MODE.IF],
- ["%{BKY_CONTROLS_IF_MSG_ELSEIF}", MODE.ELSEIF],
- ["%{BKY_CONTROLS_IF_MSG_ELSE}", MODE.ELSE],
+ ['%{BKY_CONTROLS_IF_MSG_IF}', MODE.IF],
+ ['%{BKY_CONTROLS_IF_MSG_ELSEIF}', MODE.ELSEIF],
+ ['%{BKY_CONTROLS_IF_MSG_ELSE}', MODE.ELSE],
],
(newValue) => {
this.updateShape_(newValue);
return newValue;
- },
+ }
),
- "MODE",
+ 'MODE'
);
- this.appendValueInput("COND").setCheck("Boolean");
- this.appendStatementInput("DO");
+ this.appendValueInput('COND').setCheck('Boolean');
+ this.appendStatementInput('DO');
- this.updateShape_(this.getFieldValue("MODE") ?? MODE.IF);
+ this.updateShape_(this.getFieldValue('MODE') ?? MODE.IF);
// COND's context is the MODE dropdown (not a label field), so it can't be
// auto-derived โ give it an explicit ARIA label.
- applyInputAriaLabels(this, { COND: "condition" });
+ applyInputAriaLabels(this, { COND: 'condition' });
// Clear restore flag after Blockly has had a chance to reconnect blocks.
setTimeout(() => {
@@ -492,7 +480,7 @@ export function defineControlBlocks() {
saveExtraState: function () {
return {
- mode: this.getFieldValue("MODE"),
+ mode: this.getFieldValue('MODE'),
stashedCondState: this._stashedCondState ?? null,
};
},
@@ -504,7 +492,7 @@ export function defineControlBlocks() {
this._stashedCondState = state?.stashedCondState ?? null;
const mode = state?.mode ?? MODE.IF;
- this.setFieldValue(mode, "MODE");
+ this.setFieldValue(mode, 'MODE');
// Only show/hide here; no dispose/restore while restoring.
this.updateShape_(mode);
@@ -518,7 +506,7 @@ export function defineControlBlocks() {
updateShape_: function (mode) {
const wantsCond = mode !== MODE.ELSE;
- const condInput = this.getInput("COND");
+ const condInput = this.getInput('COND');
if (!condInput) return;
// During restore: never stash/dispose/append; only toggle visibility.
@@ -560,7 +548,7 @@ export function defineControlBlocks() {
const restored = Blockly.serialization.blocks.append(
this._stashedCondState,
this.workspace,
- { recordUndo: false },
+ { recordUndo: false }
);
if (restored?.outputConnection && condInput.connection) {
@@ -596,10 +584,7 @@ export function defineControlBlocks() {
}
// Ignore block-level disabled toggles to avoid event loops and undo noise.
- if (
- event.type === Blockly.Events.BLOCK_CHANGE &&
- event.element === "disabled"
- ) {
+ if (event.type === Blockly.Events.BLOCK_CHANGE && event.element === 'disabled') {
return;
}
@@ -610,7 +595,7 @@ export function defineControlBlocks() {
const maybeTarget = connection?.targetBlock?.();
if (
maybeTarget &&
- typeof maybeTarget.isInsertionMarker === "function" &&
+ typeof maybeTarget.isInsertionMarker === 'function' &&
maybeTarget.isInsertionMarker()
) {
return null;
@@ -621,7 +606,7 @@ export function defineControlBlocks() {
getIfClauseChainHead_: function () {
let head = this;
let prev = this.getRealTargetBlock_(head.previousConnection);
- while (prev?.type === "if_clause") {
+ while (prev?.type === 'if_clause') {
head = prev;
prev = this.getRealTargetBlock_(head.previousConnection);
}
@@ -632,7 +617,7 @@ export function defineControlBlocks() {
const chain = [];
let current = head;
- while (current?.type === "if_clause") {
+ while (current?.type === 'if_clause') {
chain.push(current);
current = this.getRealTargetBlock_(current.nextConnection);
}
@@ -655,7 +640,7 @@ export function defineControlBlocks() {
try {
for (const block of chain) {
- const mode = block.getFieldValue("MODE");
+ const mode = block.getFieldValue('MODE');
let isValid = false;
if (mode === MODE.IF) {
@@ -683,13 +668,13 @@ export function defineControlBlocks() {
}
const builtInControlBlocks = [
- "controls_if",
- "controls_ifelse",
- "controls_repeat_ext",
- "controls_whileUntil",
- "controls_for",
- "controls_flow_statements",
- "controls_forEach",
+ 'controls_if',
+ 'controls_ifelse',
+ 'controls_repeat_ext',
+ 'controls_whileUntil',
+ 'controls_for',
+ 'controls_flow_statements',
+ 'controls_forEach',
];
builtInControlBlocks.forEach((typeName) => {
@@ -699,6 +684,6 @@ builtInControlBlocks.forEach((typeName) => {
blockDef.init = function (...args) {
originalInit.apply(this, args);
- this.setStyle("control_blocks");
+ this.setStyle('control_blocks');
};
});
diff --git a/blocks/effects.js b/blocks/effects.js
index 487e903a9..41456ff0f 100644
--- a/blocks/effects.js
+++ b/blocks/effects.js
@@ -1,137 +1,133 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
export function defineEffectsBlocks() {
- Blockly.Blocks["main_light"] = {
+ Blockly.Blocks['main_light'] = {
init: function () {
this.jsonInit({
- type: "main_light",
- message0: translate("main_light"),
+ type: 'main_light',
+ message0: translate('main_light'),
args0: [
{
- type: "input_value",
- name: "INTENSITY",
- check: "Number",
+ type: 'input_value',
+ name: 'INTENSITY',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DIFFUSE",
- check: "Colour",
+ type: 'input_value',
+ name: 'DIFFUSE',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "GROUND_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'GROUND_COLOR',
+ check: 'Colour',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("main_light"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('main_light'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["set_fog"] = {
+ Blockly.Blocks['set_fog'] = {
init: function () {
this.jsonInit({
- type: "set_fog",
- message0: translate("set_fog"),
+ type: 'set_fog',
+ message0: translate('set_fog'),
args0: [
{
- type: "input_value",
- name: "FOG_COLOR",
- colour: "#ffffff",
- check: "Colour",
+ type: 'input_value',
+ name: 'FOG_COLOR',
+ colour: '#ffffff',
+ check: 'Colour',
},
{
- type: "field_dropdown",
- name: "FOG_MODE",
+ type: 'field_dropdown',
+ name: 'FOG_MODE',
options: [
- getDropdownOption("LINEAR"),
- getDropdownOption("NONE"),
- getDropdownOption("EXP"),
- getDropdownOption("EXP2"),
+ getDropdownOption('LINEAR'),
+ getDropdownOption('NONE'),
+ getDropdownOption('EXP'),
+ getDropdownOption('EXP2'),
],
},
{
- type: "input_value",
- name: "DENSITY",
- check: "Number",
+ type: 'input_value',
+ name: 'DENSITY',
+ check: 'Number',
},
{
- type: "input_value",
- name: "START",
- check: "Number",
+ type: 'input_value',
+ name: 'START',
+ check: 'Number',
},
{
- type: "input_value",
- name: "END",
- check: "Number",
+ type: 'input_value',
+ name: 'END',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("set_fog"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('set_fog'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["enable_shadows"] = {
+ Blockly.Blocks['enable_shadows'] = {
init: function () {
this.jsonInit({
- type: "enable_shadows",
- message0: translate("enable_shadows"),
+ type: 'enable_shadows',
+ message0: translate('enable_shadows'),
args0: [
{
- type: "field_checkbox",
- name: "ENABLED",
+ type: 'field_checkbox',
+ name: 'ENABLED',
checked: true,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("enable_shadows"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('enable_shadows'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["get_light"] = {
+ Blockly.Blocks['get_light'] = {
init: function () {
this.jsonInit({
- type: "get_light",
- message0: translate("get_light"),
+ type: 'get_light',
+ message0: translate('get_light'),
args0: [
{
- type: "field_variable",
- name: "VAR",
- variable: "light", // Default variable is 'camera'
+ type: 'field_variable',
+ name: 'VAR',
+ variable: 'light', // Default variable is 'camera'
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("get_light"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('get_light'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
}
diff --git a/blocks/events.js b/blocks/events.js
index 7984a5fe9..e1a1f0060 100644
--- a/blocks/events.js
+++ b/blocks/events.js
@@ -1,18 +1,13 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
getHelpUrlFor,
addToggleButton,
mutationToDom,
domToMutation,
updateShape,
-} from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
- getOption,
-} from "../main/translation.js";
+} from './blocks.js';
+import { translate, getTooltip, getDropdownOption, getOption } from '../main/translation.js';
import {
makeStartIcon,
makeRepeatIcon,
@@ -26,70 +21,58 @@ import {
BLOCK_ICON_FIELD_NAME,
TOGGLE_BUTTON_FIELD_NAME,
DecorativeFieldImage,
-} from "./blockIcons.js";
+} from './blockIcons.js';
export function defineEventsBlocks() {
- Blockly.Blocks["start"] = {
+ Blockly.Blocks['start'] = {
init: function () {
this.jsonInit({
- type: "start",
- message0: translate("start"),
- message1: "%1",
+ type: 'start',
+ message0: translate('start'),
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
+ colour: categoryColours['Events'],
inputsInline: true,
- tooltip: getTooltip("start"),
+ tooltip: getTooltip('start'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeStartIcon(getCurrentIconColor()),
- 18,
- 18,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeStartIcon(getCurrentIconColor()), 18, 18, '', null),
+ BLOCK_ICON_FIELD_NAME
);
},
};
// Define the forever block
- Blockly.Blocks["forever"] = {
+ Blockly.Blocks['forever'] = {
init: function () {
this.jsonInit({
- type: "forever",
- message0: translate("forever"),
+ type: 'forever',
+ message0: translate('forever'),
args0: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
check: null,
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("forever"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('forever'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
this.isInline = false;
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeRepeatIcon(getCurrentIconColor()),
- 32,
- 32,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeRepeatIcon(getCurrentIconColor()), 32, 32, '', null),
+ BLOCK_ICON_FIELD_NAME
);
addToggleButton(this);
},
@@ -112,37 +95,35 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
// Optional: Re-enable the block if it was disabled
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
};
- Blockly.Blocks["when_clicked"] = {
+ Blockly.Blocks['when_clicked'] = {
init: function () {
this.jsonInit({
- type: "when_clicked",
- message0: translate("when_clicked"),
+ type: 'when_clicked',
+ message0: translate('when_clicked'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "TRIGGER",
+ type: 'field_dropdown',
+ name: 'TRIGGER',
options: [
- getDropdownOption("OnPickTrigger"),
- getDropdownOption("OnLeftPickTrigger"),
- getDropdownOption("OnDoublePickTrigger"),
- getDropdownOption("OnPickDownTrigger"),
- getDropdownOption("OnPickUpTrigger"),
+ getDropdownOption('OnPickTrigger'),
+ getDropdownOption('OnLeftPickTrigger'),
+ getDropdownOption('OnDoublePickTrigger'),
+ getDropdownOption('OnPickDownTrigger'),
+ getDropdownOption('OnPickUpTrigger'),
],
},
/*{
@@ -155,44 +136,38 @@ export function defineEventsBlocks() {
],
},*/
],
- message1: "%1",
- implicitAlign1: "LEFT",
+ message1: '%1',
+ implicitAlign1: 'LEFT',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("when_clicked"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('when_clicked'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
// Default to top-level mode
this.isInline = false;
this.setPreviousStatement(false);
this.setNextStatement(false);
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeClickIcon(getCurrentIconColor()),
- 22,
- 22,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeClickIcon(getCurrentIconColor()), 22, 22, '', null),
+ BLOCK_ICON_FIELD_NAME
);
// Add inline toggle button
const toggleButton = new Blockly.FieldImage(
- makeInlineIcon("white"),
+ makeInlineIcon('white'),
30,
30,
- "toggle inline blocks",
+ 'toggle inline blocks',
() => {
this.toggleDoBlock();
- },
+ }
);
// Add toggle button to a separate input
@@ -202,13 +177,13 @@ export function defineEventsBlocks() {
},
mutationToDom: function () {
- const container = document.createElement("mutation");
- container.setAttribute("inline", this.isInline);
+ const container = document.createElement('mutation');
+ container.setAttribute('inline', this.isInline);
return container;
},
domToMutation: function (xmlElement) {
- const isInline = xmlElement.getAttribute("inline") === "true" || false;
+ const isInline = xmlElement.getAttribute('inline') === 'true' || false;
this.updateShape_(isInline);
},
@@ -233,79 +208,71 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
- if (this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
};
- Blockly.Blocks["on_collision"] = {
+ Blockly.Blocks['on_collision'] = {
init: function () {
this.jsonInit({
- type: "when_touches",
- message0: translate("on_collision"),
+ type: 'when_touches',
+ message0: translate('on_collision'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "TRIGGER",
+ type: 'field_dropdown',
+ name: 'TRIGGER',
options: [
- getDropdownOption("OnIntersectionEnterTrigger"),
- getDropdownOption("OnIntersectionExitTrigger"),
+ getDropdownOption('OnIntersectionEnterTrigger'),
+ getDropdownOption('OnIntersectionExitTrigger'),
],
},
{
- type: "field_variable",
- name: "OTHER_MODEL_VAR",
- variable: "object2",
+ type: 'field_variable',
+ name: 'OTHER_MODEL_VAR',
+ variable: 'object2',
},
],
- message1: "%1",
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("on_collision"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('on_collision'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
// Set default state to top-level block
this.isInline = false;
this.callbackVar2Id = null;
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeCollisionIcon(getCurrentIconColor()),
- 24,
- 24,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeCollisionIcon(getCurrentIconColor()), 24, 24, '', null),
+ BLOCK_ICON_FIELD_NAME
);
// Add the toggle button
const toggleButton = new Blockly.FieldImage(
- makeInlineIcon("white"),
+ makeInlineIcon('white'),
30,
30,
- "toggle inline blocks",
+ 'toggle inline blocks',
() => {
this.toggleDoBlock();
- },
+ }
);
// Append the toggle button to the block
@@ -316,8 +283,8 @@ export function defineEventsBlocks() {
onchange: function () {
if (!this.workspace || this.isInFlyout) return;
- const modelVarId = this.getFieldValue("MODEL_VAR");
- const otherModelVarId = this.getFieldValue("OTHER_MODEL_VAR");
+ const modelVarId = this.getFieldValue('MODEL_VAR');
+ const otherModelVarId = this.getFieldValue('OTHER_MODEL_VAR');
if (!modelVarId || !otherModelVarId) return;
@@ -338,17 +305,17 @@ export function defineEventsBlocks() {
}
},
mutationToDom: function () {
- const container = document.createElement("mutation");
- container.setAttribute("inline", this.isInline);
+ const container = document.createElement('mutation');
+ container.setAttribute('inline', this.isInline);
if (this.callbackVar2Id) {
- container.setAttribute("callback_var_2", this.callbackVar2Id);
+ container.setAttribute('callback_var_2', this.callbackVar2Id);
}
return container;
},
domToMutation: function (xmlElement) {
- const isInline = xmlElement.getAttribute("inline") === "true";
+ const isInline = xmlElement.getAttribute('inline') === 'true';
this.updateShape_(isInline);
- this.callbackVar2Id = xmlElement.getAttribute("callback_var_2") || null;
+ this.callbackVar2Id = xmlElement.getAttribute('callback_var_2') || null;
},
updateShape_: function (isInline) {
this.isInline = isInline;
@@ -370,108 +337,97 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
// Optional: Re-enable the block if it was disabled
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
};
// For backward compatibility
- Blockly.Blocks["when_touches"] = Blockly.Blocks["on_collision"];
+ Blockly.Blocks['when_touches'] = Blockly.Blocks['on_collision'];
- Blockly.Blocks["when_key_event"] = {
+ Blockly.Blocks['when_key_event'] = {
init: function () {
this.jsonInit({
- type: "when_key_event",
- message0: translate("when_key_event"),
+ type: 'when_key_event',
+ message0: translate('when_key_event'),
args0: [
{
- type: "field_grid_dropdown",
- name: "KEY",
+ type: 'field_grid_dropdown',
+ name: 'KEY',
columns: 10,
options: [
- getDropdownOption("0"),
- getDropdownOption("1"),
- getDropdownOption("2"),
- getDropdownOption("3"),
- getDropdownOption("4"),
- getDropdownOption("5"),
- getDropdownOption("6"),
- getDropdownOption("7"),
- getDropdownOption("8"),
- getDropdownOption("9"),
- getDropdownOption("a"),
- getDropdownOption("b"),
- getDropdownOption("c"),
- getDropdownOption("d"),
- getDropdownOption("e"),
- getDropdownOption("f"),
- getDropdownOption("g"),
- getDropdownOption("h"),
- getDropdownOption("i"),
- getDropdownOption("j"),
- getDropdownOption("k"),
- getDropdownOption("l"),
- getDropdownOption("m"),
- getDropdownOption("n"),
- getDropdownOption("o"),
- getDropdownOption("p"),
- getDropdownOption("q"),
- getDropdownOption("r"),
- getDropdownOption("s"),
- getDropdownOption("t"),
- getDropdownOption("u"),
- getDropdownOption("v"),
- getDropdownOption("w"),
- getDropdownOption("x"),
- getDropdownOption("y"),
- getDropdownOption("z"),
- getDropdownOption(" "),
- getDropdownOption(","),
- getDropdownOption("."),
- getDropdownOption("/"),
- getDropdownOption("ArrowLeft"),
- getDropdownOption("ArrowUp"),
- getDropdownOption("ArrowRight"),
- getDropdownOption("ArrowDown"),
+ getDropdownOption('0'),
+ getDropdownOption('1'),
+ getDropdownOption('2'),
+ getDropdownOption('3'),
+ getDropdownOption('4'),
+ getDropdownOption('5'),
+ getDropdownOption('6'),
+ getDropdownOption('7'),
+ getDropdownOption('8'),
+ getDropdownOption('9'),
+ getDropdownOption('a'),
+ getDropdownOption('b'),
+ getDropdownOption('c'),
+ getDropdownOption('d'),
+ getDropdownOption('e'),
+ getDropdownOption('f'),
+ getDropdownOption('g'),
+ getDropdownOption('h'),
+ getDropdownOption('i'),
+ getDropdownOption('j'),
+ getDropdownOption('k'),
+ getDropdownOption('l'),
+ getDropdownOption('m'),
+ getDropdownOption('n'),
+ getDropdownOption('o'),
+ getDropdownOption('p'),
+ getDropdownOption('q'),
+ getDropdownOption('r'),
+ getDropdownOption('s'),
+ getDropdownOption('t'),
+ getDropdownOption('u'),
+ getDropdownOption('v'),
+ getDropdownOption('w'),
+ getDropdownOption('x'),
+ getDropdownOption('y'),
+ getDropdownOption('z'),
+ getDropdownOption(' '),
+ getDropdownOption(','),
+ getDropdownOption('.'),
+ getDropdownOption('/'),
+ getDropdownOption('ArrowLeft'),
+ getDropdownOption('ArrowUp'),
+ getDropdownOption('ArrowRight'),
+ getDropdownOption('ArrowDown'),
],
},
{
- type: "field_dropdown",
- name: "EVENT",
- options: [
- getDropdownOption("pressed"),
- getDropdownOption("released"),
- ],
+ type: 'field_dropdown',
+ name: 'EVENT',
+ options: [getDropdownOption('pressed'), getDropdownOption('released')],
},
],
- message1: "%1",
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("when_key_event"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('when_key_event'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeKeyboardIcon(getCurrentIconColor()),
- 36,
- 36,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeKeyboardIcon(getCurrentIconColor()), 36, 36, '', null),
+ BLOCK_ICON_FIELD_NAME
);
addToggleButton(this);
},
@@ -494,68 +450,57 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
// Optional: Re-enable the block if it was disabled
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
};
- Blockly.Blocks["when_action_event"] = {
+ Blockly.Blocks['when_action_event'] = {
init: function () {
this.jsonInit({
- type: "when_action_event",
- message0: translate("when_action_event"),
+ type: 'when_action_event',
+ message0: translate('when_action_event'),
args0: [
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: [
- [getOption("ACTION_FORWARD"), "FORWARD"],
- [getOption("ACTION_BACKWARD"), "BACKWARD"],
- [getOption("ACTION_LEFT"), "LEFT"],
- [getOption("ACTION_RIGHT"), "RIGHT"],
- [getOption("ACTION_BUTTON1"), "BUTTON1"],
- [getOption("ACTION_BUTTON2"), "BUTTON2"],
- [getOption("ACTION_BUTTON3"), "BUTTON3"],
- [getOption("ACTION_BUTTON4"), "BUTTON4"],
+ [getOption('ACTION_FORWARD'), 'FORWARD'],
+ [getOption('ACTION_BACKWARD'), 'BACKWARD'],
+ [getOption('ACTION_LEFT'), 'LEFT'],
+ [getOption('ACTION_RIGHT'), 'RIGHT'],
+ [getOption('ACTION_BUTTON1'), 'BUTTON1'],
+ [getOption('ACTION_BUTTON2'), 'BUTTON2'],
+ [getOption('ACTION_BUTTON3'), 'BUTTON3'],
+ [getOption('ACTION_BUTTON4'), 'BUTTON4'],
],
},
{
- type: "field_dropdown",
- name: "EVENT",
- options: [
- getDropdownOption("pressed"),
- getDropdownOption("released"),
- ],
+ type: 'field_dropdown',
+ name: 'EVENT',
+ options: [getDropdownOption('pressed'), getDropdownOption('released')],
},
],
- message1: "%1",
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("when_action_event"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('when_action_event'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makePressIcon(getCurrentIconColor()),
- 32,
- 32,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makePressIcon(getCurrentIconColor()), 32, 32, '', null),
+ BLOCK_ICON_FIELD_NAME
);
addToggleButton(this);
},
@@ -578,76 +523,67 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
// Optional: Re-enable the block if it was disabled
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
};
function getEventNameValidationError(name) {
- if (!name || typeof name !== "string") {
- return "Event name must be a valid string.";
+ if (!name || typeof name !== 'string') {
+ return 'Event name must be a valid string.';
}
if (name.length > 30) {
- return "Event name must be 30 characters or fewer.";
+ return 'Event name must be 30 characters or fewer.';
}
const lower = name.toLowerCase();
- const reservedPrefixes = [
- "_",
- "on",
- "system",
- "internal",
- "babylon",
- "flock",
- ];
+ const reservedPrefixes = ['_', 'on', 'system', 'internal', 'babylon', 'flock'];
if (reservedPrefixes.some((prefix) => lower.startsWith(prefix))) {
return "Event name must not start with reserved words like 'on', 'system', or 'flock'.";
}
const disallowedChars = /[!@#$%^&*()+=[\]{};:'"\\|,<>?/\n\r\t]/;
if (disallowedChars.test(name)) {
- return "Event name must not include punctuation or special characters.";
+ return 'Event name must not include punctuation or special characters.';
}
return null; // valid
}
- Blockly.Blocks["broadcast_event"] = {
+ Blockly.Blocks['broadcast_event'] = {
init: function () {
this.jsonInit({
- type: "broadcast_event",
- message0: translate("broadcast_event"),
+ type: 'broadcast_event',
+ message0: translate('broadcast_event'),
args0: [
{
- type: "input_value",
- name: "EVENT_NAME",
- check: "String",
+ type: 'input_value',
+ name: 'EVENT_NAME',
+ check: 'String',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Events"],
- tooltip: getTooltip("broadcast_event"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('broadcast_event'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
},
/** Called whenever anything changes */
onchange: function () {
if (!this.workspace || this.isInFlyout) return;
- const inputBlock = this.getInputTargetBlock("EVENT_NAME");
+ const inputBlock = this.getInputTargetBlock('EVENT_NAME');
- if (inputBlock && inputBlock.type === "text") {
- const value = inputBlock.getFieldValue("TEXT");
+ if (inputBlock && inputBlock.type === 'text') {
+ const value = inputBlock.getFieldValue('TEXT');
const error = getEventNameValidationError(value);
if (error) {
@@ -661,40 +597,34 @@ export function defineEventsBlocks() {
},
};
- Blockly.Blocks["on_event"] = {
+ Blockly.Blocks['on_event'] = {
init: function () {
this.jsonInit({
- type: "on_event",
- message0: translate("on_event"),
+ type: 'on_event',
+ message0: translate('on_event'),
args0: [
{
- type: "input_value",
- name: "EVENT_NAME",
- check: "String",
+ type: 'input_value',
+ name: 'EVENT_NAME',
+ check: 'String',
},
],
- message1: "%1",
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Events"],
- tooltip: getTooltip("on_event"),
+ colour: categoryColours['Events'],
+ tooltip: getTooltip('on_event'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("events_blocks");
+ this.setStyle('events_blocks');
this.inputList[0].insertFieldAt(
0,
- new DecorativeFieldImage(
- makeOnEventIcon(getCurrentIconColor()),
- 28,
- 28,
- "",
- null,
- ),
- BLOCK_ICON_FIELD_NAME,
+ new DecorativeFieldImage(makeOnEventIcon(getCurrentIconColor()), 28, 28, '', null),
+ BLOCK_ICON_FIELD_NAME
);
addToggleButton(this);
},
@@ -717,22 +647,20 @@ export function defineEventsBlocks() {
this.updateShape_(isInline);
// Optional: Re-enable the block if it was disabled
- if (this.hasDisabledReason && this.hasDisabledReason("ORPHANED_BLOCK")) {
- this.setDisabledReason(false, "ORPHANED_BLOCK");
+ if (this.hasDisabledReason && this.hasDisabledReason('ORPHANED_BLOCK')) {
+ this.setDisabledReason(false, 'ORPHANED_BLOCK');
}
- Blockly.Events.fire(
- new Blockly.Events.BlockChange(this, "mutation", null, "", ""),
- );
+ Blockly.Events.fire(new Blockly.Events.BlockChange(this, 'mutation', null, '', ''));
Blockly.Events.fire(new Blockly.Events.BlockMove(this));
},
onchange: function () {
if (!this.workspace || this.isInFlyout) return;
- const inputBlock = this.getInputTargetBlock("EVENT_NAME");
+ const inputBlock = this.getInputTargetBlock('EVENT_NAME');
- if (inputBlock && inputBlock.type === "text") {
- const value = inputBlock.getFieldValue("TEXT");
+ if (inputBlock && inputBlock.type === 'text') {
+ const value = inputBlock.getFieldValue('TEXT');
const error = getEventNameValidationError(value);
if (error) {
diff --git a/blocks/fieldMicrobitImage.js b/blocks/fieldMicrobitImage.js
index 79a4758fb..f55a1d419 100644
--- a/blocks/fieldMicrobitImage.js
+++ b/blocks/fieldMicrobitImage.js
@@ -3,8 +3,8 @@
// preset buttons and a paintable grid. The value is the wire pattern itself
// (25 brightness digits, row-major; the editor writes only 0 and 9), so
// XML/JSON save, undo/redo, and copy/paste all use the plain string.
-import * as Blockly from "blockly";
-import { translate } from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { translate } from '../main/translation.js';
import {
MICROBIT_IMAGE_GRID_SIZE,
MICROBIT_IMAGE_DEFAULT,
@@ -18,7 +18,7 @@ import {
setImageCell,
toggleImageCell,
makeMicrobitImageDataUri,
-} from "./microbitImagePattern.js";
+} from './microbitImagePattern.js';
const FIELD_IMAGE_SIZE = 50; // px on the block face, same as material previews
const PRESET_PREVIEW_SIZE = 28;
@@ -30,7 +30,7 @@ const CELL_OFF_COLOUR = MICROBIT_IMAGE_UNLIT_COLOUR;
export class FieldMicrobitImage extends Blockly.Field {
SERIALIZABLE = true;
EDITABLE = true;
- CURSOR = "pointer";
+ CURSOR = 'pointer';
constructor(value = MICROBIT_IMAGE_DEFAULT) {
super(value);
@@ -56,7 +56,7 @@ export class FieldMicrobitImage extends Blockly.Field {
height: `${FIELD_IMAGE_SIZE}px`,
width: `${FIELD_IMAGE_SIZE}px`,
},
- this.fieldGroup_,
+ this.fieldGroup_
);
this._updateFaceImage();
}
@@ -75,28 +75,25 @@ export class FieldMicrobitImage extends Blockly.Field {
_updateFaceImage() {
this.imageElement_?.setAttributeNS(
Blockly.utils.dom.XLINK_NS,
- "xlink:href",
- makeMicrobitImageDataUri(this.getValue()),
+ 'xlink:href',
+ makeMicrobitImageDataUri(this.getValue())
);
}
// The 25-digit value is meaningless read aloud; describe the field instead.
getText_() {
- return translate("microbit_image_field_label");
+ return translate('microbit_image_field_label');
}
computeAriaLabel() {
- return translate("microbit_image_field_label");
+ return translate('microbit_image_field_label');
}
// ------------------------------------------------------------------ editor
showEditor_() {
Blockly.DropDownDiv.getContentDiv().appendChild(this._createEditor());
- Blockly.DropDownDiv.showPositionedByField(
- this,
- this._disposeEditor.bind(this),
- );
+ Blockly.DropDownDiv.showPositionedByField(this, this._disposeEditor.bind(this));
this._cellButtons?.[0]?.focus();
}
@@ -106,18 +103,16 @@ export class FieldMicrobitImage extends Blockly.Field {
}
_createEditor() {
- const container = document.createElement("div");
- container.style.padding = "8px";
-
- const presetRow = document.createElement("div");
- presetRow.style.display = "flex";
- presetRow.style.gap = "6px";
- presetRow.style.marginBottom = "8px";
- presetRow.style.alignItems = "stretch";
- for (const name of ["clear", "full"]) {
- presetRow.appendChild(
- this._createPresetButton(name, MICROBIT_IMAGE_PRESETS[name]),
- );
+ const container = document.createElement('div');
+ container.style.padding = '8px';
+
+ const presetRow = document.createElement('div');
+ presetRow.style.display = 'flex';
+ presetRow.style.gap = '6px';
+ presetRow.style.marginBottom = '8px';
+ presetRow.style.alignItems = 'stretch';
+ for (const name of ['clear', 'full']) {
+ presetRow.appendChild(this._createPresetButton(name, MICROBIT_IMAGE_PRESETS[name]));
}
presetRow.appendChild(this._createPictureMenu());
container.appendChild(presetRow);
@@ -129,75 +124,72 @@ export class FieldMicrobitImage extends Blockly.Field {
// the material dropdowns: each menu entry is the picture itself, rendered
// by the same SVG used on the block face.
_createPictureMenu() {
- const wrapper = document.createElement("div");
- wrapper.style.position = "relative";
-
- const trigger = document.createElement("button");
- trigger.type = "button";
- trigger.setAttribute("aria-label", translate("microbit_image_picture_menu"));
- trigger.setAttribute("aria-haspopup", "true");
- trigger.setAttribute("aria-expanded", "false");
- trigger.style.cursor = "pointer";
- trigger.style.height = "100%";
- trigger.style.padding = "2px 4px";
- trigger.style.border = "1px solid #666";
- trigger.style.borderRadius = "4px";
- trigger.style.background = "transparent";
- trigger.style.display = "flex";
- trigger.style.alignItems = "center";
- trigger.style.gap = "2px";
- const triggerPreview = document.createElement("img");
+ const wrapper = document.createElement('div');
+ wrapper.style.position = 'relative';
+
+ const trigger = document.createElement('button');
+ trigger.type = 'button';
+ trigger.setAttribute('aria-label', translate('microbit_image_picture_menu'));
+ trigger.setAttribute('aria-haspopup', 'true');
+ trigger.setAttribute('aria-expanded', 'false');
+ trigger.style.cursor = 'pointer';
+ trigger.style.height = '100%';
+ trigger.style.padding = '2px 4px';
+ trigger.style.border = '1px solid #666';
+ trigger.style.borderRadius = '4px';
+ trigger.style.background = 'transparent';
+ trigger.style.display = 'flex';
+ trigger.style.alignItems = 'center';
+ trigger.style.gap = '2px';
+ const triggerPreview = document.createElement('img');
triggerPreview.src = makeMicrobitImageDataUri(
- MICROBIT_IMAGE_PRESETS[MICROBIT_IMAGE_PICTURE_NAMES[0]],
+ MICROBIT_IMAGE_PRESETS[MICROBIT_IMAGE_PICTURE_NAMES[0]]
);
- triggerPreview.alt = "";
+ triggerPreview.alt = '';
triggerPreview.width = PRESET_PREVIEW_SIZE;
triggerPreview.height = PRESET_PREVIEW_SIZE;
- triggerPreview.style.display = "block";
+ triggerPreview.style.display = 'block';
trigger.appendChild(triggerPreview);
- trigger.appendChild(document.createTextNode("โพ"));
-
- const panel = document.createElement("div");
- panel.style.position = "absolute";
- panel.style.top = "calc(100% + 2px)";
- panel.style.left = "0";
- panel.style.zIndex = "1";
- panel.style.padding = "4px";
- panel.style.background = "#fff";
- panel.style.border = "1px solid #666";
- panel.style.borderRadius = "4px";
- panel.style.display = "none";
+ trigger.appendChild(document.createTextNode('โพ'));
+
+ const panel = document.createElement('div');
+ panel.style.position = 'absolute';
+ panel.style.top = 'calc(100% + 2px)';
+ panel.style.left = '0';
+ panel.style.zIndex = '1';
+ panel.style.padding = '4px';
+ panel.style.background = '#fff';
+ panel.style.border = '1px solid #666';
+ panel.style.borderRadius = '4px';
+ panel.style.display = 'none';
panel.style.gridTemplateColumns = `repeat(${Math.min(
4,
- MICROBIT_IMAGE_PICTURE_NAMES.length,
+ MICROBIT_IMAGE_PICTURE_NAMES.length
)}, auto)`;
- panel.style.gap = "4px";
+ panel.style.gap = '4px';
const setOpen = (open) => {
- panel.style.display = open ? "grid" : "none";
- trigger.setAttribute("aria-expanded", String(open));
+ panel.style.display = open ? 'grid' : 'none';
+ trigger.setAttribute('aria-expanded', String(open));
};
for (const name of MICROBIT_IMAGE_PICTURE_NAMES) {
- const option = document.createElement("button");
- option.type = "button";
- option.setAttribute(
- "aria-label",
- translate(`microbit_image_preset_${name}`),
- );
- option.style.cursor = "pointer";
- option.style.padding = "2px";
- option.style.border = "none";
- option.style.borderRadius = "4px";
- option.style.background = "transparent";
- const preview = document.createElement("img");
+ const option = document.createElement('button');
+ option.type = 'button';
+ option.setAttribute('aria-label', translate(`microbit_image_preset_${name}`));
+ option.style.cursor = 'pointer';
+ option.style.padding = '2px';
+ option.style.border = 'none';
+ option.style.borderRadius = '4px';
+ option.style.background = 'transparent';
+ const preview = document.createElement('img');
preview.src = makeMicrobitImageDataUri(MICROBIT_IMAGE_PRESETS[name]);
- preview.alt = "";
+ preview.alt = '';
preview.width = PICTURE_PREVIEW_SIZE;
preview.height = PICTURE_PREVIEW_SIZE;
- preview.style.display = "block";
+ preview.style.display = 'block';
option.appendChild(preview);
- option.addEventListener("click", () => {
+ option.addEventListener('click', () => {
this.setValue(MICROBIT_IMAGE_PRESETS[name]);
this._refreshCells();
setOpen(false);
@@ -206,14 +198,14 @@ export class FieldMicrobitImage extends Blockly.Field {
panel.appendChild(option);
}
- trigger.addEventListener("click", () => {
- const open = panel.style.display === "none";
+ trigger.addEventListener('click', () => {
+ const open = panel.style.display === 'none';
setOpen(open);
- if (open) panel.querySelector("button")?.focus();
+ if (open) panel.querySelector('button')?.focus();
});
// Escape closes just the picture menu, not the whole field editor.
- wrapper.addEventListener("keydown", (event) => {
- if (event.key !== "Escape" || panel.style.display === "none") return;
+ wrapper.addEventListener('keydown', (event) => {
+ if (event.key !== 'Escape' || panel.style.display === 'none') return;
event.stopPropagation();
setOpen(false);
trigger.focus();
@@ -225,25 +217,22 @@ export class FieldMicrobitImage extends Blockly.Field {
}
_createPresetButton(name, pattern) {
- const button = document.createElement("button");
- button.type = "button";
- button.setAttribute(
- "aria-label",
- translate(`microbit_image_preset_${name}`),
- );
- button.style.cursor = "pointer";
- button.style.padding = "2px";
- button.style.border = "1px solid #666";
- button.style.borderRadius = "4px";
- button.style.background = "transparent";
- const preview = document.createElement("img");
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.setAttribute('aria-label', translate(`microbit_image_preset_${name}`));
+ button.style.cursor = 'pointer';
+ button.style.padding = '2px';
+ button.style.border = '1px solid #666';
+ button.style.borderRadius = '4px';
+ button.style.background = 'transparent';
+ const preview = document.createElement('img');
preview.src = makeMicrobitImageDataUri(pattern);
- preview.alt = "";
+ preview.alt = '';
preview.width = PRESET_PREVIEW_SIZE;
preview.height = PRESET_PREVIEW_SIZE;
- preview.style.display = "block";
+ preview.style.display = 'block';
button.appendChild(preview);
- button.addEventListener("click", () => {
+ button.addEventListener('click', () => {
this.setValue(pattern);
this._refreshCells();
});
@@ -251,17 +240,17 @@ export class FieldMicrobitImage extends Blockly.Field {
}
_createGrid() {
- const grid = document.createElement("div");
- grid.setAttribute("role", "group");
- grid.setAttribute("aria-label", translate("microbit_image_grid_label"));
- grid.style.display = "grid";
+ const grid = document.createElement('div');
+ grid.setAttribute('role', 'group');
+ grid.setAttribute('aria-label', translate('microbit_image_grid_label'));
+ grid.style.display = 'grid';
grid.style.gridTemplateColumns = `repeat(${MICROBIT_IMAGE_GRID_SIZE}, ${CELL_SIZE}px)`;
- grid.style.gap = "4px";
- grid.style.padding = "6px";
+ grid.style.gap = '4px';
+ grid.style.padding = '6px';
grid.style.background = MICROBIT_IMAGE_PLATE_COLOUR;
- grid.style.borderRadius = "6px";
- grid.style.width = "fit-content";
- grid.style.touchAction = "none";
+ grid.style.borderRadius = '6px';
+ grid.style.width = 'fit-content';
+ grid.style.touchAction = 'none';
this._cellButtons = [];
for (let row = 0; row < MICROBIT_IMAGE_GRID_SIZE; row++) {
@@ -273,54 +262,48 @@ export class FieldMicrobitImage extends Blockly.Field {
// Pointer-drag painting: the first cell toggles and every cell swept
// afterwards takes the digit the first cell became.
- grid.addEventListener("pointerdown", (event) => {
+ grid.addEventListener('pointerdown', (event) => {
const cell = this._cellAt(event);
if (!cell) return;
event.preventDefault(); // no text-selection/drag artefacts while painting
- const { pattern, digit } = toggleImageCell(
- this.getValue(),
- cell.row,
- cell.col,
- );
+ const { pattern, digit } = toggleImageCell(this.getValue(), cell.row, cell.col);
this._paintDigit = digit;
this.setValue(pattern);
this._refreshCells();
});
- grid.addEventListener("pointermove", (event) => {
+ grid.addEventListener('pointermove', (event) => {
if (this._paintDigit === null) return;
const cell = this._cellAt(event);
if (!cell) return;
- this.setValue(
- setImageCell(this.getValue(), cell.row, cell.col, this._paintDigit),
- );
+ this.setValue(setImageCell(this.getValue(), cell.row, cell.col, this._paintDigit));
this._refreshCells();
});
const endPaint = () => {
this._paintDigit = null;
};
- grid.addEventListener("pointerup", endPaint);
- grid.addEventListener("pointercancel", endPaint);
+ grid.addEventListener('pointerup', endPaint);
+ grid.addEventListener('pointercancel', endPaint);
- grid.addEventListener("keydown", (event) => this._onGridKeyDown(event));
+ grid.addEventListener('keydown', (event) => this._onGridKeyDown(event));
return grid;
}
_createCell(row, col) {
- const button = document.createElement("button");
- button.type = "button";
+ const button = document.createElement('button');
+ button.type = 'button';
button.dataset.row = String(row);
button.dataset.col = String(col);
// Roving tabindex: one tab stop for the whole grid, arrows move within.
button.tabIndex = row === 0 && col === 0 ? 0 : -1;
button.style.width = `${CELL_SIZE}px`;
button.style.height = `${CELL_SIZE}px`;
- button.style.border = "none";
- button.style.borderRadius = "4px";
- button.style.cursor = "pointer";
- button.style.padding = "0";
+ button.style.border = 'none';
+ button.style.borderRadius = '4px';
+ button.style.cursor = 'pointer';
+ button.style.padding = '0';
// Keyboard activation only: pointer input is handled by the grid's
// painting listeners, and a mouse click would otherwise toggle twice.
- button.addEventListener("click", (event) => {
+ button.addEventListener('click', (event) => {
if (event.detail !== 0) return;
this.setValue(toggleImageCell(this.getValue(), row, col).pattern);
this._refreshCells();
@@ -334,7 +317,7 @@ export class FieldMicrobitImage extends Blockly.Field {
// captures the pointer on the pressed cell, so during a drag the events
// keep targeting that first cell rather than the one under the finger.
const element = document.elementFromPoint(event.clientX, event.clientY);
- const button = element?.closest?.("button[data-row]");
+ const button = element?.closest?.('button[data-row]');
if (!button || !this._cellButtons?.includes(button)) return null;
return { row: Number(button.dataset.row), col: Number(button.dataset.col) };
}
@@ -348,34 +331,31 @@ export class FieldMicrobitImage extends Blockly.Field {
const on = isImageCellOn(pattern, row, col);
button.style.background = on ? CELL_ON_COLOUR : CELL_OFF_COLOUR;
button.setAttribute(
- "aria-label",
- translate("microbit_image_cell_label")
- .replace("%1", String(row + 1))
- .replace("%2", String(col + 1))
- .replace(
- "%3",
- translate(on ? "microbit_image_cell_on" : "microbit_image_cell_off"),
- ),
+ 'aria-label',
+ translate('microbit_image_cell_label')
+ .replace('%1', String(row + 1))
+ .replace('%2', String(col + 1))
+ .replace('%3', translate(on ? 'microbit_image_cell_on' : 'microbit_image_cell_off'))
);
}
}
_onGridKeyDown(event) {
- const target = event.target?.closest?.("button[data-row]");
+ const target = event.target?.closest?.('button[data-row]');
if (!target || !this._cellButtons) return;
let row = Number(target.dataset.row);
let col = Number(target.dataset.col);
switch (event.key) {
- case "ArrowUp":
+ case 'ArrowUp':
row = Math.max(0, row - 1);
break;
- case "ArrowDown":
+ case 'ArrowDown':
row = Math.min(MICROBIT_IMAGE_GRID_SIZE - 1, row + 1);
break;
- case "ArrowLeft":
+ case 'ArrowLeft':
col = Math.max(0, col - 1);
break;
- case "ArrowRight":
+ case 'ArrowRight':
col = Math.min(MICROBIT_IMAGE_GRID_SIZE - 1, col + 1);
break;
default:
@@ -390,4 +370,4 @@ export class FieldMicrobitImage extends Blockly.Field {
}
}
-Blockly.fieldRegistry.register("field_microbit_image", FieldMicrobitImage);
+Blockly.fieldRegistry.register('field_microbit_image', FieldMicrobitImage);
diff --git a/blocks/materials.js b/blocks/materials.js
index 1b5ea1786..6e7372198 100644
--- a/blocks/materials.js
+++ b/blocks/materials.js
@@ -1,10 +1,10 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import { materialNames } from "../config.js";
-import { flock } from "../flock.js";
-import { translate, getTooltip } from "../main/translation.js";
-import { isValidColourInput } from "./colourvalidation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { materialNames } from '../config.js';
+import { flock } from '../flock.js';
+import { translate, getTooltip } from '../main/translation.js';
+import { isValidColourInput } from './colourvalidation.js';
import {
makeTouchesInputSubtree,
wasBlockDeleted,
@@ -13,48 +13,48 @@ import {
promoteMaterialContainerFromShadow,
respawnMaterialShadow,
cacheMaterialState,
-} from "./scene.js";
+} from './scene.js';
export function defineMaterialsBlocks() {
- Blockly.Blocks["change_color"] = {
+ Blockly.Blocks['change_color'] = {
init: function () {
this.jsonInit({
- type: "change_color",
- message0: translate("change_color"),
+ type: 'change_color',
+ message0: translate('change_color'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Array"], // Accepts either Colour or Array
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Array'], // Accepts either Colour or Array
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("change_color"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('change_color'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["change_material"] = {
+ Blockly.Blocks['change_material'] = {
init: function () {
this.jsonInit({
- message0: translate("change_material"),
+ message0: translate('change_material'),
args0: [
{
- type: "field_grid_dropdown",
- name: "MATERIALS",
+ type: 'field_grid_dropdown',
+ name: 'MATERIALS',
columns: 4,
options: materialNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `./textures/${baseName}.png`,
@@ -67,570 +67,566 @@ export function defineMaterialsBlocks() {
}),
},
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Array"], // Accepts either Colour or Array
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Array'], // Accepts either Colour or Array
},
],
inputsInline: true,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("change_material"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('change_material'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["text_material"] = {
+ Blockly.Blocks['text_material'] = {
init: function () {
this.jsonInit({
- type: "text_material",
- message0: translate("text_material"),
+ type: 'text_material',
+ message0: translate('text_material'),
args0: [
{
- type: "field_variable",
- name: "MATERIAL_VAR",
- variable: "material",
+ type: 'field_variable',
+ name: 'MATERIAL_VAR',
+ variable: 'material',
},
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "WIDTH",
- check: "Number",
+ type: 'input_value',
+ name: 'WIDTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'HEIGHT',
+ check: 'Number',
},
{
- type: "input_value",
- name: "TEXT_SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'TEXT_SIZE',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("text_material"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('text_material'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["place_decal"] = {
+ Blockly.Blocks['place_decal'] = {
init: function () {
this.jsonInit({
- message0: translate("place_decal"),
+ message0: translate('place_decal'),
args0: [
{
- type: "field_variable",
- name: "MATERIAL",
- variable: "material",
+ type: 'field_variable',
+ name: 'MATERIAL',
+ variable: 'material',
},
{
- type: "input_value",
- name: "ANGLE",
- check: "Number",
+ type: 'input_value',
+ name: 'ANGLE',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("place_decal"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('place_decal'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["decal"] = {
+ Blockly.Blocks['decal'] = {
init: function () {
this.jsonInit({
- type: "decal",
- message0: translate("decal"),
+ type: 'decal',
+ message0: translate('decal'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "POSITION_X",
- check: "Number",
+ type: 'input_value',
+ name: 'POSITION_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "POSITION_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'POSITION_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "POSITION_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'POSITION_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "NORMAL_X",
- check: "Number",
+ type: 'input_value',
+ name: 'NORMAL_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "NORMAL_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'NORMAL_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "NORMAL_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'NORMAL_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "SIZE_X",
- check: "Number",
+ type: 'input_value',
+ name: 'SIZE_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "SIZE_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'SIZE_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "SIZE_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'SIZE_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MATERIAL",
+ type: 'input_value',
+ name: 'MATERIAL',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("decal"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('decal'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["highlight"] = {
+ Blockly.Blocks['highlight'] = {
init: function () {
this.jsonInit({
- type: "highlight",
- message0: translate("highlight"),
+ type: 'highlight',
+ message0: translate('highlight'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "COLOR",
- colour: "#FFD700",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ colour: '#FFD700',
+ check: 'Colour',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("highlight"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('highlight'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["glow"] = {
+ Blockly.Blocks['glow'] = {
init: function () {
this.jsonInit({
- type: "glow",
- message0: translate("glow"),
+ type: 'glow',
+ message0: translate('glow'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("glow"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('glow'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["set_shadow"] = {
+ Blockly.Blocks['set_shadow'] = {
init: function () {
this.jsonInit({
- type: "set_shadow",
- message0: translate("set_shadow"),
+ type: 'set_shadow',
+ message0: translate('set_shadow'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "field_checkbox",
- name: "CAST",
+ type: 'field_checkbox',
+ name: 'CAST',
checked: true,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("set_shadow"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('set_shadow'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["tint"] = {
+ Blockly.Blocks['tint'] = {
init: function () {
this.jsonInit({
- type: "tint",
- message0: translate("tint"),
+ type: 'tint',
+ message0: translate('tint'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "COLOR",
- colour: "#AA336A",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ colour: '#AA336A',
+ check: 'Colour',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("tint"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('tint'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["set_alpha"] = {
+ Blockly.Blocks['set_alpha'] = {
init: function () {
this.jsonInit({
- type: "set_mesh_material_alpha",
- message0: translate("set_alpha"),
+ type: 'set_mesh_material_alpha',
+ message0: translate('set_alpha'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "ALPHA",
- check: "Number",
+ type: 'input_value',
+ name: 'ALPHA',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("set_alpha"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('set_alpha'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["clear_effects"] = {
+ Blockly.Blocks['clear_effects'] = {
init: function () {
this.jsonInit({
- type: "clear_effects",
- message0: translate("clear_effects"),
+ type: 'clear_effects',
+ message0: translate('clear_effects'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("clear_effects"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('clear_effects'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["colour"] = {
+ Blockly.Blocks['colour'] = {
init: function () {
this.jsonInit({
- type: "colour",
- message0: translate("colour"),
+ type: 'colour',
+ message0: translate('colour'),
args0: [
{
- type: "field_colour",
- name: "COLOR",
- colour: "#9932CC",
+ type: 'field_colour',
+ name: 'COLOR',
+ colour: '#9932CC',
},
],
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("colour"),
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('colour'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
// put this near your blocks file top
const SKIN_HEXES = [
- "#2E1A1B",
- "#37221D",
- "#442B22",
- "#6E473C",
- "#926451",
- "#966456",
- "#D8A190",
- "#E1BCB0",
- "#EACDC7",
- "#290A0E",
- "#34130A",
- "#602C14",
- "#884B2C",
- "#C27753",
- "#E89878",
- "#EEA98A",
- "#FECCC0",
- "#FCDACE",
- "#2F2018",
- "#41291C",
- "#5A3C26",
- "#845B40",
- "#B98459",
- "#D39E74",
- "#E4BB9D",
- "#EFCFB1",
- "#ECD8C6",
- "#231304",
- "#3E1C0A",
- "#572E15",
- "#AC6534",
- "#D8945D",
- "#F4B077",
- "#FCC292",
- "#FFD2AA",
- "#FEE0C7",
- "#2D2113",
- "#4F351F",
- "#694831",
- "#8F633E",
- "#AD7D43",
- "#C59B69",
- "#DAB485",
- "#E6CEAA",
- "#E9D4B8",
- "#251601",
- "#3F1F03",
- "#5D340F",
- "#8D521E",
- "#D2894A",
- "#F2AF6C",
- "#F1C387",
- "#F4D0A4",
- "#F5DDBE",
- "#302418",
- "#3C2D1B",
- "#563E28",
- "#7C6041",
- "#957345",
- "#9F7D4B",
- "#CCB081",
- "#DFCCA5",
- "#E7DBC5",
- "#271D07",
- "#442E0F",
- "#64421B",
- "#976E2F",
- "#C69045",
- "#D9A756",
- "#EBC57E",
- "#F4DBA6",
- "#FBE6C1",
+ '#2E1A1B',
+ '#37221D',
+ '#442B22',
+ '#6E473C',
+ '#926451',
+ '#966456',
+ '#D8A190',
+ '#E1BCB0',
+ '#EACDC7',
+ '#290A0E',
+ '#34130A',
+ '#602C14',
+ '#884B2C',
+ '#C27753',
+ '#E89878',
+ '#EEA98A',
+ '#FECCC0',
+ '#FCDACE',
+ '#2F2018',
+ '#41291C',
+ '#5A3C26',
+ '#845B40',
+ '#B98459',
+ '#D39E74',
+ '#E4BB9D',
+ '#EFCFB1',
+ '#ECD8C6',
+ '#231304',
+ '#3E1C0A',
+ '#572E15',
+ '#AC6534',
+ '#D8945D',
+ '#F4B077',
+ '#FCC292',
+ '#FFD2AA',
+ '#FEE0C7',
+ '#2D2113',
+ '#4F351F',
+ '#694831',
+ '#8F633E',
+ '#AD7D43',
+ '#C59B69',
+ '#DAB485',
+ '#E6CEAA',
+ '#E9D4B8',
+ '#251601',
+ '#3F1F03',
+ '#5D340F',
+ '#8D521E',
+ '#D2894A',
+ '#F2AF6C',
+ '#F1C387',
+ '#F4D0A4',
+ '#F5DDBE',
+ '#302418',
+ '#3C2D1B',
+ '#563E28',
+ '#7C6041',
+ '#957345',
+ '#9F7D4B',
+ '#CCB081',
+ '#DFCCA5',
+ '#E7DBC5',
+ '#271D07',
+ '#442E0F',
+ '#64421B',
+ '#976E2F',
+ '#C69045',
+ '#D9A756',
+ '#EBC57E',
+ '#F4DBA6',
+ '#FBE6C1',
];
// Use the hex strings themselves as the hover titles
const SKIN_TITLES = SKIN_HEXES.map((h) => h);
- Blockly.Blocks["skin_colour"] = {
+ Blockly.Blocks['skin_colour'] = {
init: function () {
this.jsonInit({
- type: "skin_colour",
- message0: translate("skin_colour"),
+ type: 'skin_colour',
+ message0: translate('skin_colour'),
args0: [
{
- type: "field_colour",
- name: "COLOR",
- colour: "#FFE0BD",
+ type: 'field_colour',
+ name: 'COLOR',
+ colour: '#FFE0BD',
colourOptions: SKIN_HEXES,
colourTitles: SKIN_TITLES,
columns: 9,
},
],
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("skin_colour"),
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('skin_colour'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["greyscale_colour"] = {
+ Blockly.Blocks['greyscale_colour'] = {
init: function () {
this.jsonInit({
- type: "greyscale_colour",
- message0: translate("greyscale_colour"),
+ type: 'greyscale_colour',
+ message0: translate('greyscale_colour'),
args0: [
{
- type: "field_colour",
- name: "COLOR",
- colour: "#808080", // A neutral starting color (grey)
+ type: 'field_colour',
+ name: 'COLOR',
+ colour: '#808080', // A neutral starting color (grey)
colourOptions: [
- "#000000", // Black
- "#1a1a1a",
- "#333333",
- "#4d4d4d",
- "#666666",
- "#808080", // Middle grey
- "#999999",
- "#b3b3b3",
- "#cccccc",
- "#e6e6e6",
- "#ffffff", // White
+ '#000000', // Black
+ '#1a1a1a',
+ '#333333',
+ '#4d4d4d',
+ '#666666',
+ '#808080', // Middle grey
+ '#999999',
+ '#b3b3b3',
+ '#cccccc',
+ '#e6e6e6',
+ '#ffffff', // White
],
colourTitles: [
- "Black",
- "Dark Grey 1",
- "Dark Grey 2",
- "Dark Grey 3",
- "Grey 1",
- "Middle Grey",
- "Grey 2",
- "Light Grey 1",
- "Light Grey 2",
- "Light Grey 3",
- "White",
+ 'Black',
+ 'Dark Grey 1',
+ 'Dark Grey 2',
+ 'Dark Grey 3',
+ 'Grey 1',
+ 'Middle Grey',
+ 'Grey 2',
+ 'Light Grey 1',
+ 'Light Grey 2',
+ 'Light Grey 3',
+ 'White',
],
columns: 4,
},
],
- output: "Colour",
- colour: categoryColours["Materials"], // You can set this to any colour category you prefer
- tooltip: getTooltip("greyscale_colour"),
+ output: 'Colour',
+ colour: categoryColours['Materials'], // You can set this to any colour category you prefer
+ tooltip: getTooltip('greyscale_colour'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["colour_from_string"] = {
+ Blockly.Blocks['colour_from_string'] = {
init: function () {
this.jsonInit({
- message0: "%1 %2",
+ message0: '%1 %2',
args0: [
{
- type: "field_label_serializable",
- name: "HASH_PREFIX",
- text: "#",
+ type: 'field_label_serializable',
+ name: 'HASH_PREFIX',
+ text: '#',
},
{
- type: "field_input",
- name: "COLOR",
- text: "800080",
+ type: 'field_input',
+ name: 'COLOR',
+ text: '800080',
},
],
- output: "Colour",
+ output: 'Colour',
});
- const colorField = this.getField("COLOR");
- const hashPrefixField = this.getField("HASH_PREFIX");
+ const colorField = this.getField('COLOR');
+ const hashPrefixField = this.getField('HASH_PREFIX');
const updateHashPrefixContrast = (hexColor) => {
if (!hashPrefixField?.getSvgRoot) return;
const svgRoot = hashPrefixField.getSvgRoot();
if (!svgRoot?.style) return;
- const rgb = flock.hexToRgb(hexColor || "#000000");
+ const rgb = flock.hexToRgb(hexColor || '#000000');
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
- const textColor = luminance > 0.6 ? "#000000" : "#ffffff";
- const textEl = svgRoot.querySelector("text");
+ const textColor = luminance > 0.6 ? '#000000' : '#ffffff';
+ const textEl = svgRoot.querySelector('text');
if (textEl) {
- textEl.setAttribute("fill", textColor);
- textEl.style.setProperty("fill", textColor, "important");
+ textEl.setAttribute('fill', textColor);
+ textEl.style.setProperty('fill', textColor, 'important');
} else {
- svgRoot.style.setProperty("fill", textColor, "important");
+ svgRoot.style.setProperty('fill', textColor, 'important');
}
};
const updateInvalidWarning = (block, value) => {
if (!block || block.isInFlyout) return;
block.setWarningText(
- isValidColourInput(value)
- ? null
- : translate("colour_from_string_invalid"),
+ isValidColourInput(value) ? null : translate('colour_from_string_invalid')
);
};
colorField.setValidator(function (newVal) {
- const normalizedInput =
- typeof newVal === "string" ? newVal.trim().replace(/^#/, "") : "";
+ const normalizedInput = typeof newVal === 'string' ? newVal.trim().replace(/^#/, '') : '';
try {
- const validatedVal =
- flock.getColorFromString(normalizedInput) || "#000000";
+ const validatedVal = flock.getColorFromString(normalizedInput) || '#000000';
this.sourceBlock_.setColour(validatedVal);
updateHashPrefixContrast(validatedVal);
updateInvalidWarning(this.sourceBlock_, newVal);
return normalizedInput;
} catch (error) {
- console.warn("Failed to validate colour field value:", error);
- this.sourceBlock_.setColour("#000000");
- updateHashPrefixContrast("#000000");
+ console.warn('Failed to validate colour field value:', error);
+ this.sourceBlock_.setColour('#000000');
+ updateHashPrefixContrast('#000000');
return normalizedInput;
}
});
@@ -638,45 +634,45 @@ export function defineMaterialsBlocks() {
// Set initial block color based on the field's initial value
try {
const initialVal = colorField.getValue();
- const validatedVal = flock.getColorFromString(initialVal) || "#000000";
+ const validatedVal = flock.getColorFromString(initialVal) || '#000000';
this.setColour(validatedVal);
updateHashPrefixContrast(validatedVal);
updateInvalidWarning(this, initialVal);
} catch (error) {
- console.warn("Failed to initialize colour block value:", error);
- this.setColour("#000000");
- updateHashPrefixContrast("#000000");
+ console.warn('Failed to initialize colour block value:', error);
+ this.setColour('#000000');
+ updateHashPrefixContrast('#000000');
}
},
};
- Blockly.Blocks["random_colour"] = {
+ Blockly.Blocks['random_colour'] = {
init: function () {
this.jsonInit({
- type: "random_colour_block",
- message0: translate("random_colour"),
- output: "Colour",
- colour: categoryColours["Materials"],
- tooltip: getTooltip("random_colour"),
+ type: 'random_colour_block',
+ message0: translate('random_colour'),
+ output: 'Colour',
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('random_colour'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["material"] = {
+ Blockly.Blocks['material'] = {
init: function () {
this.jsonInit({
- type: "material",
- message0: translate("material"),
+ type: 'material',
+ message0: translate('material'),
args0: [
{
- type: "field_grid_dropdown",
- name: "TEXTURE_SET",
+ type: 'field_grid_dropdown',
+ name: 'TEXTURE_SET',
columns: 4,
options: materialNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `./textures/${baseName}.png`,
@@ -689,63 +685,63 @@ export function defineMaterialsBlocks() {
}),
},
{
- type: "input_value",
- name: "BASE_COLOR",
- colour: "#ffffff", // Default to white
+ type: 'input_value',
+ name: 'BASE_COLOR',
+ colour: '#ffffff', // Default to white
},
{
- type: "input_value",
- name: "ALPHA",
+ type: 'input_value',
+ name: 'ALPHA',
value: 1,
min: 0,
max: 1,
precision: 0.01,
},
],
- output: "Material",
+ output: 'Material',
inputsInline: true,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("material"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('material'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
- Blockly.Blocks["gradient_material"] = {
+ Blockly.Blocks['gradient_material'] = {
init: function () {
this.jsonInit({
- type: "material",
- message0: translate("gradient_material"),
+ type: 'material',
+ message0: translate('gradient_material'),
args0: [
{
- type: "input_value",
- name: "COLOR",
- colour: "#6495ED",
- check: ["Colour", "Array"],
+ type: 'input_value',
+ name: 'COLOR',
+ colour: '#6495ED',
+ check: ['Colour', 'Array'],
},
{
- type: "input_value",
- name: "ALPHA",
+ type: 'input_value',
+ name: 'ALPHA',
value: 1,
min: 0,
max: 1,
precision: 0.01,
},
],
- output: "Material",
+ output: 'Material',
inputsInline: true,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("gradient_material"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('gradient_material'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
},
};
function attachSetMaterialOnChange(block) {
const ws = block.workspace;
- const touches = makeTouchesInputSubtree(block, ws, "MATERIAL");
+ const touches = makeTouchesInputSubtree(block, ws, 'MATERIAL');
block.setOnChange((changeEvent) => {
const eventTypes = [
@@ -760,11 +756,10 @@ export function defineMaterialsBlocks() {
const relevant =
wasBlockDeleted(changeEvent, block.id) ||
changeEventHitsTouches(changeEvent, touches) ||
- (changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- changeEvent.blockId === block.id) ||
+ (changeEvent.type === Blockly.Events.BLOCK_CREATE && changeEvent.blockId === block.id) ||
(changeEvent.type === Blockly.Events.BLOCK_MOVE &&
changeEvent.oldParentId === block.id &&
- changeEvent.oldInputName === "MATERIAL");
+ changeEvent.oldInputName === 'MATERIAL');
if (!relevant) return;
@@ -772,7 +767,7 @@ export function defineMaterialsBlocks() {
promoteMaterialContainerFromShadow(block);
- if (!block.getInputTargetBlock("MATERIAL")) {
+ if (!block.getInputTargetBlock('MATERIAL')) {
respawnMaterialShadow(block);
return;
}
@@ -781,32 +776,32 @@ export function defineMaterialsBlocks() {
});
}
- Blockly.Blocks["set_material"] = {
+ Blockly.Blocks['set_material'] = {
init: function () {
this.jsonInit({
- type: "set_material",
- message0: translate("set_material"),
+ type: 'set_material',
+ message0: translate('set_material'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "MATERIAL",
- check: ["Material", "Array"],
+ type: 'input_value',
+ name: 'MATERIAL',
+ check: ['Material', 'Array'],
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Materials"],
- tooltip: getTooltip("set_material"),
+ colour: categoryColours['Materials'],
+ tooltip: getTooltip('set_material'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("materials_blocks");
+ this.setStyle('materials_blocks');
attachSetMaterialOnChange(this);
},
};
diff --git a/blocks/microbitImagePattern.js b/blocks/microbitImagePattern.js
index 010699deb..9651c4319 100644
--- a/blocks/microbitImagePattern.js
+++ b/blocks/microbitImagePattern.js
@@ -4,13 +4,13 @@
// row-major from the top-left; the editor only ever writes 0 (off) and 9
// (on). On the wire the pattern travels as five short row lines โ see
// serialiseImageRows in microbit/protocol.js.
-import { normaliseImagePattern } from "../microbit/protocol.js";
+import { normaliseImagePattern } from '../microbit/protocol.js';
export const MICROBIT_IMAGE_GRID_SIZE = 5;
-export const MICROBIT_IMAGE_OFF = "0";
-export const MICROBIT_IMAGE_ON = "9";
+export const MICROBIT_IMAGE_OFF = '0';
+export const MICROBIT_IMAGE_ON = '9';
export const MICROBIT_IMAGE_DEFAULT = MICROBIT_IMAGE_OFF.repeat(
- MICROBIT_IMAGE_GRID_SIZE * MICROBIT_IMAGE_GRID_SIZE,
+ MICROBIT_IMAGE_GRID_SIZE * MICROBIT_IMAGE_GRID_SIZE
);
// Named preset patterns. clear and full appear as one-click buttons in the
@@ -19,27 +19,26 @@ export const MICROBIT_IMAGE_DEFAULT = MICROBIT_IMAGE_OFF.repeat(
export const MICROBIT_IMAGE_PRESETS = Object.freeze({
clear: MICROBIT_IMAGE_DEFAULT,
full: MICROBIT_IMAGE_ON.repeat(MICROBIT_IMAGE_DEFAULT.length),
- sun: "9090909990999990999090909",
+ sun: '9090909990999990999090909',
});
// Dropdown pictures, in menu order. To add one: add its pattern to
// MICROBIT_IMAGE_PRESETS, its name here, and a microbit_image_preset_
// string to the locales.
-export const MICROBIT_IMAGE_PICTURE_NAMES = Object.freeze(["sun"]);
+export const MICROBIT_IMAGE_PICTURE_NAMES = Object.freeze(['sun']);
export { normaliseImagePattern };
// High-contrast LED colours, shared by the block-face SVG and the editor
// grid: bright red on near-black reads at block-icon sizes.
-export const MICROBIT_IMAGE_LIT_COLOUR = "#ff8a80";
-export const MICROBIT_IMAGE_UNLIT_COLOUR = "#303030";
-export const MICROBIT_IMAGE_PLATE_COLOUR = "#0d0d0d";
+export const MICROBIT_IMAGE_LIT_COLOUR = '#ff8a80';
+export const MICROBIT_IMAGE_UNLIT_COLOUR = '#303030';
+export const MICROBIT_IMAGE_PLATE_COLOUR = '#0d0d0d';
/** True when the LED at (row, col) is lit (any non-zero brightness). */
export function isImageCellOn(pattern, row, col) {
return (
- normaliseImagePattern(pattern)[row * MICROBIT_IMAGE_GRID_SIZE + col] !==
- MICROBIT_IMAGE_OFF
+ normaliseImagePattern(pattern)[row * MICROBIT_IMAGE_GRID_SIZE + col] !== MICROBIT_IMAGE_OFF
);
}
@@ -47,9 +46,7 @@ export function isImageCellOn(pattern, row, col) {
export function setImageCell(pattern, row, col, digit) {
const normalised = normaliseImagePattern(pattern);
const index = row * MICROBIT_IMAGE_GRID_SIZE + col;
- return (
- normalised.slice(0, index) + digit + normalised.slice(index + 1)
- );
+ return normalised.slice(0, index) + digit + normalised.slice(index + 1);
}
/**
@@ -58,9 +55,7 @@ export function setImageCell(pattern, row, col, digit) {
* every other cell it sweeps.
*/
export function toggleImageCell(pattern, row, col) {
- const digit = isImageCellOn(pattern, row, col)
- ? MICROBIT_IMAGE_OFF
- : MICROBIT_IMAGE_ON;
+ const digit = isImageCellOn(pattern, row, col) ? MICROBIT_IMAGE_OFF : MICROBIT_IMAGE_ON;
return { pattern: setImageCell(pattern, row, col, digit), digit };
}
@@ -74,16 +69,11 @@ export function makeMicrobitImageDataUri(pattern) {
const cell = 16;
const gap = 4;
const padding = 6;
- const size =
- MICROBIT_IMAGE_GRID_SIZE * cell +
- (MICROBIT_IMAGE_GRID_SIZE - 1) * gap +
- 2 * padding;
- let squares = "";
+ const size = MICROBIT_IMAGE_GRID_SIZE * cell + (MICROBIT_IMAGE_GRID_SIZE - 1) * gap + 2 * padding;
+ let squares = '';
for (let row = 0; row < MICROBIT_IMAGE_GRID_SIZE; row++) {
for (let col = 0; col < MICROBIT_IMAGE_GRID_SIZE; col++) {
- const lit =
- normalised[row * MICROBIT_IMAGE_GRID_SIZE + col] !==
- MICROBIT_IMAGE_OFF;
+ const lit = normalised[row * MICROBIT_IMAGE_GRID_SIZE + col] !== MICROBIT_IMAGE_OFF;
const x = padding + col * (cell + gap);
const y = padding + row * (cell + gap);
squares +=
@@ -96,5 +86,5 @@ export function makeMicrobitImageDataUri(pattern) {
` ` +
squares +
` `;
- return "data:image/svg+xml," + encodeURIComponent(svg);
+ return 'data:image/svg+xml,' + encodeURIComponent(svg);
}
diff --git a/blocks/models.js b/blocks/models.js
index ea0fb1c57..ce7f8385e 100644
--- a/blocks/models.js
+++ b/blocks/models.js
@@ -1,5 +1,5 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
nextVariableIndexes,
handleBlockChange,
@@ -10,7 +10,7 @@ import {
handleParentLinkedUpdate,
getHelpUrlFor,
registerBlockHandler,
-} from "./blocks.js";
+} from './blocks.js';
import {
characterNames,
objectNames,
@@ -18,30 +18,29 @@ import {
objectColours,
modelNames,
getModelDisplayName,
-} from "../config.js";
-import { flock } from "../flock.js";
-import { translate, getTooltip } from "../main/translation.js";
+} from '../config.js';
+import { flock } from '../flock.js';
+import { translate, getTooltip } from '../main/translation.js';
export function defineModelBlocks() {
- Blockly.Blocks["load_character"] = {
+ Blockly.Blocks['load_character'] = {
init: function () {
- const variableNamePrefix = "character";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'character';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- message0: translate("load_character"),
+ message0: translate('load_character'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_grid_dropdown",
- name: "MODELS",
+ type: 'field_grid_dropdown',
+ name: 'MODELS',
columns: 6,
options: characterNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `${flock.imagePath}${baseName}.png`,
@@ -54,73 +53,68 @@ export function defineModelBlocks() {
}),
},
{
- type: "input_value",
- name: "SCALE",
- check: "Number",
+ type: 'input_value',
+ name: 'SCALE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "HAIR_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'HAIR_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "SKIN_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'SKIN_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "EYES_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'EYES_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "TSHIRT_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'TSHIRT_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "SHORTS_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'SHORTS_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "SLEEVES_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'SLEEVES_COLOR',
+ check: 'Colour',
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("load_character"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('load_character'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
registerBlockHandler(this, (changeEvent) => {
// Always handle variable naming first (even if mesh is skipped)
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
// Mesh lifecycle events on this block directly (e.g. enable/disable, move),
// or when this block is part of a snippet creation (its id is in changeEvent.ids).
@@ -135,7 +129,7 @@ export function defineModelBlocks() {
// Linked children like MODELS or color inputs
if (handleParentLinkedUpdate(this, changeEvent)) {
// ๐น Additional side-effect unique to this block type
- window.updateCurrentMeshName(this, "ID_VAR");
+ window.updateCurrentMeshName(this, 'ID_VAR');
return;
}
@@ -148,32 +142,31 @@ export function defineModelBlocks() {
},
};
- Blockly.Blocks["load_object"] = {
+ Blockly.Blocks['load_object'] = {
init: function () {
- const defaultObject = "Star.glb";
+ const defaultObject = 'Star.glb';
const defaultColours = objectColours[defaultObject];
const defaultColour = Array.isArray(defaultColours)
? defaultColours[0]
- : defaultColours || "#FFD700";
- const variableNamePrefix = "item";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ : defaultColours || '#FFD700';
+ const variableNamePrefix = 'item';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
// Add the main inputs of the block
this.jsonInit({
- message0: translate("load_object"),
+ message0: translate('load_object'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_grid_dropdown",
- name: "MODELS",
+ type: 'field_grid_dropdown',
+ name: 'MODELS',
columns: 6,
options: objectNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `${flock.imagePath}${baseName}.png`,
@@ -186,51 +179,51 @@ export function defineModelBlocks() {
}),
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Array", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Array', 'Material'],
},
{
- type: "input_value",
- name: "SCALE",
- check: "Number",
+ type: 'input_value',
+ name: 'SCALE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("load_object"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('load_object'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Function to update the COLOR field based on the selected model
const updateColorField = () => {
- const selectedObject = this.getFieldValue("MODELS");
+ const selectedObject = this.getFieldValue('MODELS');
const configColors = objectColours[selectedObject];
const colour = Array.isArray(configColors)
? configColors[0]
: configColors || defaultColour;
- const colorInput = this.getInput("COLOR");
+ const colorInput = this.getInput('COLOR');
const colorField = colorInput.connection.targetBlock();
- if (colorField?.getField?.("COLOR")) {
- colorField.setFieldValue(colour, "COLOR");
+ if (colorField?.getField?.('COLOR')) {
+ colorField.setFieldValue(colour, 'COLOR');
}
};
@@ -239,8 +232,8 @@ export function defineModelBlocks() {
registerBlockHandler(this, (changeEvent) => {
if (
changeEvent.type === Blockly.Events.BLOCK_CHANGE &&
- changeEvent.element === "field" &&
- changeEvent.name === "MODELS" &&
+ changeEvent.element === 'field' &&
+ changeEvent.name === 'MODELS' &&
changeEvent.blockId === this.id
) {
updateColorField();
@@ -248,10 +241,7 @@ export function defineModelBlocks() {
handleBlockChange(this, changeEvent, variableNamePrefix);
- if (
- this.id !== changeEvent.blockId &&
- changeEvent.type !== Blockly.Events.BLOCK_CHANGE
- )
+ if (this.id !== changeEvent.blockId && changeEvent.type !== Blockly.Events.BLOCK_CHANGE)
return;
if (handleMeshLifecycleChange(this, changeEvent)) return;
// if (handleFieldOrChildChange(this, changeEvent)) return;
@@ -261,26 +251,25 @@ export function defineModelBlocks() {
},
};
- Blockly.Blocks["load_multi_object"] = {
+ Blockly.Blocks['load_multi_object'] = {
init: function () {
- const variableNamePrefix = "object";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'object';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- message0: translate("load_multi_object"),
+ message0: translate('load_multi_object'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_grid_dropdown",
- name: "MODELS",
+ type: 'field_grid_dropdown',
+ name: 'MODELS',
columns: 6,
options: multiObjectNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `${flock.imagePath}${baseName}.png`,
@@ -293,63 +282,59 @@ export function defineModelBlocks() {
}),
},
{
- type: "input_value",
- name: "SCALE",
- check: "Number",
+ type: 'input_value',
+ name: 'SCALE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "COLORS",
- check: "Array",
+ type: 'input_value',
+ name: 'COLORS',
+ check: 'Array',
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("load_multi_object"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('load_multi_object'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Change from a local constant to a method on the block prototype
- Blockly.Blocks["load_multi_object"].updateColorsField = function () {
- const selectedObject = this.getFieldValue("MODELS");
- const colours = objectColours[selectedObject] || [
- "#000000",
- "#FFFFFF",
- "#CCCCCC",
- ];
+ Blockly.Blocks['load_multi_object'].updateColorsField = function () {
+ const selectedObject = this.getFieldValue('MODELS');
+ const colours = objectColours[selectedObject] || ['#000000', '#FFFFFF', '#CCCCCC'];
const requiredItemCount = colours.length;
- const colorsInput = this.getInput("COLORS");
+ const colorsInput = this.getInput('COLORS');
let listBlock = colorsInput.connection?.targetBlock();
// Create a mutation element with the correct number of items.
- const mutation = document.createElement("mutation");
- mutation.setAttribute("items", requiredItemCount);
+ const mutation = document.createElement('mutation');
+ mutation.setAttribute('items', requiredItemCount);
- if (listBlock && listBlock.type === "lists_create_with") {
+ if (listBlock && listBlock.type === 'lists_create_with') {
// Apply the mutation to update the block's inputs.
listBlock.domToMutation(mutation);
// Remove any extra inputs beyond the required count.
listBlock.inputList
- .filter((input) => input.name && input.name.startsWith("ADD"))
+ .filter((input) => input.name && input.name.startsWith('ADD'))
.forEach((input) => {
const index = parseInt(input.name.substring(3));
if (index >= requiredItemCount) {
@@ -359,46 +344,45 @@ export function defineModelBlocks() {
// For each required input, update or create its shadow colour block.
for (let i = 0; i < requiredItemCount; i++) {
- let input = listBlock.getInput("ADD" + i);
+ let input = listBlock.getInput('ADD' + i);
if (!input) {
- input = listBlock.appendValueInput("ADD" + i).setCheck("Colour");
+ input = listBlock.appendValueInput('ADD' + i).setCheck('Colour');
}
let shadowBlock = input.connection?.targetBlock();
if (!shadowBlock || !shadowBlock.isShadow()) {
- shadowBlock = listBlock.workspace.newBlock("colour");
- shadowBlock.setFieldValue(colours[i] || "#000000", "COLOR");
+ shadowBlock = listBlock.workspace.newBlock('colour');
+ shadowBlock.setFieldValue(colours[i] || '#000000', 'COLOR');
shadowBlock.setShadow(true);
shadowBlock.initSvg();
input.connection.connect(shadowBlock.outputConnection);
} else {
- shadowBlock.setFieldValue(colours[i] || "#000000", "COLOR");
+ shadowBlock.setFieldValue(colours[i] || '#000000', 'COLOR');
}
}
listBlock.initSvg();
listBlock.render();
} else if (!listBlock) {
// Create a new list block.
- listBlock = this.workspace.newBlock("lists_create_with");
+ listBlock = this.workspace.newBlock('lists_create_with');
listBlock.setShadow(true);
listBlock.domToMutation(mutation);
for (let i = 0; i < requiredItemCount; i++) {
- let input = listBlock.getInput("ADD" + i);
+ let input = listBlock.getInput('ADD' + i);
if (!input) {
- input = listBlock.appendValueInput("ADD" + i).setCheck("Colour");
+ input = listBlock.appendValueInput('ADD' + i).setCheck('Colour');
}
- const shadowBlock = listBlock.workspace.newBlock("colour");
- shadowBlock.setFieldValue(colours[i] || "#000000", "COLOR");
+ const shadowBlock = listBlock.workspace.newBlock('colour');
+ shadowBlock.setFieldValue(colours[i] || '#000000', 'COLOR');
shadowBlock.setShadow(true);
shadowBlock.initSvg();
input.connection.connect(shadowBlock.outputConnection);
}
listBlock.setInputsInline(true);
listBlock.setTooltip(
- Blockly.Msg["LISTS_CREATE_WITH_TOOLTIP"] ||
- "Create a list of colours.",
+ Blockly.Msg['LISTS_CREATE_WITH_TOOLTIP'] || 'Create a list of colours.'
);
listBlock.setHelpUrl(
- "https://developers.google.com/blockly/guides/create-custom-blocks/define-blocks",
+ 'https://developers.google.com/blockly/guides/create-custom-blocks/define-blocks'
);
listBlock.initSvg();
@@ -407,21 +391,18 @@ export function defineModelBlocks() {
}
};
- Blockly.Blocks["load_multi_object"].updateColorAtIndex = function (
- colour,
- colourIndex,
- ) {
- const colorsInput = this.getInput("COLORS");
+ Blockly.Blocks['load_multi_object'].updateColorAtIndex = function (colour, colourIndex) {
+ const colorsInput = this.getInput('COLORS');
if (!colorsInput || !colorsInput.connection) {
return;
}
const listBlock = colorsInput.connection.targetBlock();
- if (!listBlock || listBlock.type !== "lists_create_with") {
- console.log("List block not found or of incorrect type.");
+ if (!listBlock || listBlock.type !== 'lists_create_with') {
+ console.log('List block not found or of incorrect type.');
return;
}
- const inputName = "ADD" + colourIndex;
+ const inputName = 'ADD' + colourIndex;
let input = listBlock.getInput(inputName);
if (!input) {
//input = listBlock.appendValueInput(inputName).setCheck("Colour");
@@ -430,13 +411,13 @@ export function defineModelBlocks() {
let shadowBlock = input.connection?.targetBlock();
if (!shadowBlock || !shadowBlock.isShadow()) {
- shadowBlock = listBlock.workspace.newBlock("colour");
+ shadowBlock = listBlock.workspace.newBlock('colour');
shadowBlock.setShadow(true);
shadowBlock.initSvg();
input.connection.connect(shadowBlock.outputConnection);
}
- shadowBlock.setFieldValue(colour, "COLOR");
+ shadowBlock.setFieldValue(colour, 'COLOR');
shadowBlock.render();
listBlock.render();
};
@@ -445,25 +426,18 @@ export function defineModelBlocks() {
// PRIORITY: Handle MODELS field change first (before other handlers can return early)
if (
changeEvent.type === Blockly.Events.BLOCK_CHANGE &&
- changeEvent.element === "field" &&
- changeEvent.name === "MODELS" &&
+ changeEvent.element === 'field' &&
+ changeEvent.name === 'MODELS' &&
changeEvent.blockId === this.id
) {
- const blockInWorkspace = Blockly.getMainWorkspace().getBlockById(
- this.id,
- );
+ const blockInWorkspace = Blockly.getMainWorkspace().getBlockById(this.id);
if (blockInWorkspace) {
this.updateColorsField();
}
// Don't return here - let other handlers process this event too
}
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
// Always handle mesh lifecycle if the event targets this block,
// or when this block is part of a snippet creation (its id is in changeEvent.ids).
@@ -488,26 +462,25 @@ export function defineModelBlocks() {
},
};
- Blockly.Blocks["load_model"] = {
+ Blockly.Blocks['load_model'] = {
init: function () {
- const variableNamePrefix = "model";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix]; // Start with "model1"
+ const variableNamePrefix = 'model';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix]; // Start with "model1"
this.jsonInit({
- message0: translate("load_model"),
+ message0: translate('load_model'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_grid_dropdown",
- name: "MODELS",
+ type: 'field_grid_dropdown',
+ name: 'MODELS',
columns: 6,
options: modelNames.map((name) => {
- const baseName = name.replace(/\.[^/.]+$/, "");
+ const baseName = name.replace(/\.[^/.]+$/, '');
return [
{
src: `${flock.imagePath}${baseName}.png`,
@@ -520,43 +493,38 @@ export function defineModelBlocks() {
}),
},
{
- type: "input_value",
- name: "SCALE",
- check: "Number",
+ type: 'input_value',
+ name: 'SCALE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("load_model"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('load_model'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
registerBlockHandler(this, (changeEvent) => {
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
const isThisBlockCreated =
changeEvent.type === Blockly.Events.BLOCK_CREATE &&
diff --git a/blocks/physics.js b/blocks/physics.js
index 6f1ac8c32..4309d612c 100644
--- a/blocks/physics.js
+++ b/blocks/physics.js
@@ -1,265 +1,261 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
export function definePhysicsBlocks() {
- Blockly.Blocks["add_physics"] = {
+ Blockly.Blocks['add_physics'] = {
init: function () {
this.jsonInit({
- type: "add_physics",
- message0: translate("add_physics"),
+ type: 'add_physics',
+ message0: translate('add_physics'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "PHYSICS_TYPE",
+ type: 'field_dropdown',
+ name: 'PHYSICS_TYPE',
options: [
- getDropdownOption("DYNAMIC"),
- getDropdownOption("ANIMATED"),
- getDropdownOption("STATIC"),
- getDropdownOption("NONE"),
+ getDropdownOption('DYNAMIC'),
+ getDropdownOption('ANIMATED'),
+ getDropdownOption('STATIC'),
+ getDropdownOption('NONE'),
],
- default: "DYNAMIC",
+ default: 'DYNAMIC',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("add_physics"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('add_physics'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["add_physics_shape"] = {
+ Blockly.Blocks['add_physics_shape'] = {
init: function () {
this.jsonInit({
- type: "add_physics_shape",
- message0: translate("add_physics_shape"),
+ type: 'add_physics_shape',
+ message0: translate('add_physics_shape'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "SHAPE_TYPE",
- options: [getDropdownOption("MESH"), getDropdownOption("CAPSULE")],
- default: "MESH",
+ type: 'field_dropdown',
+ name: 'SHAPE_TYPE',
+ options: [getDropdownOption('MESH'), getDropdownOption('CAPSULE')],
+ default: 'MESH',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("add_physics_shape"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('add_physics_shape'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["apply_force"] = {
+ Blockly.Blocks['apply_force'] = {
init: function () {
this.jsonInit({
- type: "apply_force",
- message0: translate("apply_force"),
+ type: 'apply_force',
+ message0: translate('apply_force'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("apply_force"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('apply_force'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["jump"] = {
+ Blockly.Blocks['jump'] = {
init: function () {
this.jsonInit({
- type: "jump",
- message0: translate("jump"),
+ type: 'jump',
+ message0: translate('jump'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "JUMP_HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'JUMP_HEIGHT',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("jump"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('jump'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["set_speed"] = {
+ Blockly.Blocks['set_speed'] = {
init: function () {
this.jsonInit({
- type: "set_speed",
- message0: translate("set_speed"),
+ type: 'set_speed',
+ message0: translate('set_speed'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "DIRECTION",
+ type: 'field_dropdown',
+ name: 'DIRECTION',
options: [
- getDropdownOption("forward"),
- getDropdownOption("sideways"),
- getDropdownOption("up"),
- getDropdownOption("x_coordinate"),
- getDropdownOption("y_coordinate"),
- getDropdownOption("z_coordinate"),
- getDropdownOption("all"),
+ getDropdownOption('forward'),
+ getDropdownOption('sideways'),
+ getDropdownOption('up'),
+ getDropdownOption('x_coordinate'),
+ getDropdownOption('y_coordinate'),
+ getDropdownOption('z_coordinate'),
+ getDropdownOption('all'),
],
},
{
- type: "input_value",
- name: "SPEED",
- check: "Number",
+ type: 'input_value',
+ name: 'SPEED',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("set_speed"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('set_speed'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["set_bounciness"] = {
+ Blockly.Blocks['set_bounciness'] = {
init: function () {
this.jsonInit({
- type: "set_bounciness",
- message0: translate("set_bounciness"),
+ type: 'set_bounciness',
+ message0: translate('set_bounciness'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "BOUNCINESS",
- check: "Number",
+ type: 'input_value',
+ name: 'BOUNCINESS',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("set_bounciness"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('set_bounciness'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["show_physics"] = {
+ Blockly.Blocks['show_physics'] = {
init: function () {
this.jsonInit({
- type: "show_physics",
- message0: translate("show_physics"),
+ type: 'show_physics',
+ message0: translate('show_physics'),
args0: [
{
- type: "field_checkbox",
- name: "SHOW",
+ type: 'field_checkbox',
+ name: 'SHOW',
checked: true,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("show_physics"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('show_physics'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
- Blockly.Blocks["move_forward"] = {
+ Blockly.Blocks['move_forward'] = {
init: function () {
this.jsonInit({
- type: "move",
- message0: translate("move_forward"),
+ type: 'move',
+ message0: translate('move_forward'),
args0: [
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "DIRECTION",
+ type: 'field_dropdown',
+ name: 'DIRECTION',
options: [
- getDropdownOption("forward"),
- getDropdownOption("sideways"),
- getDropdownOption("strafe"),
+ getDropdownOption('forward'),
+ getDropdownOption('sideways'),
+ getDropdownOption('strafe'),
],
},
{
- type: "input_value",
- name: "SPEED",
- check: "Number",
+ type: 'input_value',
+ name: 'SPEED',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("move_forward"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('move_forward'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
},
};
}
diff --git a/blocks/scene.js b/blocks/scene.js
index fc51da326..1fb86fd36 100644
--- a/blocks/scene.js
+++ b/blocks/scene.js
@@ -1,5 +1,5 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
nextVariableIndexes,
handleBlockCreateEvent,
@@ -8,10 +8,10 @@ import {
addDoMutatorWithToggleBehavior,
getHelpUrlFor,
registerBlockHandler,
-} from "./blocks.js";
-import { mapNames } from "../config.js";
-import { updateOrCreateMeshFromBlock } from "../ui/blockmesh.js";
-import { translate, getTooltip, getOption } from "../main/translation.js";
+} from './blocks.js';
+import { mapNames } from '../config.js';
+import { updateOrCreateMeshFromBlock } from '../ui/blockmesh.js';
+import { translate, getTooltip, getOption } from '../main/translation.js';
function initSceneJsonBlock(block, { type, args0, inputsInline = true }) {
block.jsonInit({
@@ -21,26 +21,26 @@ function initSceneJsonBlock(block, { type, args0, inputsInline = true }) {
previousStatement: null,
nextStatement: null,
inputsInline,
- colour: categoryColours["Scene"],
+ colour: categoryColours['Scene'],
tooltip: getTooltip(type),
});
block.setHelpUrl(getHelpUrlFor(block.type));
- block.setStyle("scene_blocks");
+ block.setStyle('scene_blocks');
}
function buildMapNameDropdownArgs() {
return [
{
- type: "field_dropdown",
- name: "MAP_NAME",
- options: [[getOption("FLAT"), "NONE"]].concat(mapNames()),
+ type: 'field_dropdown',
+ name: 'MAP_NAME',
+ options: [[getOption('FLAT'), 'NONE']].concat(mapNames()),
},
];
}
function buildInputValueArg({ name, check, colour }) {
- const arg = { type: "input_value", name };
+ const arg = { type: 'input_value', name };
if (check) arg.check = check;
if (colour) arg.colour = colour;
return arg;
@@ -68,9 +68,7 @@ export function makeTouchesInputSubtree(block, ws, inputName) {
}
function isDragStop(changeEvent) {
- return (
- changeEvent.type === Blockly.Events.UI && changeEvent.element === "dragStop"
- );
+ return changeEvent.type === Blockly.Events.UI && changeEvent.element === 'dragStop';
}
export function changeEventHitsTouches(changeEvent, touches) {
@@ -128,7 +126,7 @@ function attachMeshSyncOnChange(block, config) {
}
function initSceneColourLikeBlock(block, cfg) {
- const inputName = cfg.inputName || "COLOR";
+ const inputName = cfg.inputName || 'COLOR';
const args0 = [];
if (cfg.hasDropdown) args0.push(...buildMapNameDropdownArgs());
@@ -136,8 +134,8 @@ function initSceneColourLikeBlock(block, cfg) {
buildInputValueArg({
name: inputName,
colour: cfg.inputColor,
- check: cfg.check || ["Colour", "Array", "Material"],
- }),
+ check: cfg.check || ['Colour', 'Array', 'Material'],
+ })
);
initSceneJsonBlock(block, { type: cfg.type, args0 });
@@ -151,7 +149,7 @@ function initSceneColourLikeBlock(block, cfg) {
const input = block.getInput(inputName);
if (input?.connection) {
const shadowDom = Blockly.utils.xml.textToDom(
- `${cfg.inputColor} `,
+ `${cfg.inputColor} `
);
input.connection.setShadowDom(shadowDom);
input.connection.respawnShadow_();
@@ -260,8 +258,8 @@ export function respawnContainer(block, opts) {
if (refillContainerFromCache(block, opts)) return;
const fresh = opts.makeDefault(ws);
- if (typeof fresh.initSvg === "function") fresh.initSvg();
- if (typeof fresh.render === "function") fresh.render();
+ if (typeof fresh.initSvg === 'function') fresh.initSvg();
+ if (typeof fresh.render === 'function') fresh.render();
if (fresh?.outputConnection) {
fresh.outputConnection.connect(conn);
@@ -286,8 +284,7 @@ export function attachShadowContainerOnChange(block, opts) {
const relevant =
wasBlockDeleted(changeEvent, block.id) ||
changeEventHitsTouches(changeEvent, touches) ||
- (changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- changeEvent.blockId === block.id) ||
+ (changeEvent.type === Blockly.Events.BLOCK_CREATE && changeEvent.blockId === block.id) ||
(changeEvent.type === Blockly.Events.BLOCK_MOVE &&
changeEvent.oldParentId === block.id &&
changeEvent.oldInputName === opts.inputName);
@@ -309,10 +306,10 @@ export function attachShadowContainerOnChange(block, opts) {
// Material-specific wrappers (preserve the original API used by materials.js).
const MATERIAL_OPTS = {
- inputName: "MATERIAL",
- containerType: "material",
- cacheKey: "_cachedMaterialState",
- makeDefault: (ws) => ws.newBlock("material"),
+ inputName: 'MATERIAL',
+ containerType: 'material',
+ cacheKey: '_cachedMaterialState',
+ makeDefault: (ws) => ws.newBlock('material'),
};
export function cacheMaterialState(mapBlock) {
@@ -333,7 +330,7 @@ export function respawnMaterialShadow(mapBlock) {
function attachCreateMapOnChange(block) {
const ws = block.workspace;
- const touches = makeTouchesInputSubtree(block, ws, "MATERIAL");
+ const touches = makeTouchesInputSubtree(block, ws, 'MATERIAL');
block.setOnChange((changeEvent) => {
const eventTypes = [
@@ -348,11 +345,10 @@ function attachCreateMapOnChange(block) {
const relevant =
wasBlockDeleted(changeEvent, block.id) ||
changeEventHitsTouches(changeEvent, touches) ||
- (changeEvent.type === Blockly.Events.BLOCK_CREATE &&
- changeEvent.blockId === block.id) ||
+ (changeEvent.type === Blockly.Events.BLOCK_CREATE && changeEvent.blockId === block.id) ||
(changeEvent.type === Blockly.Events.BLOCK_MOVE &&
changeEvent.oldParentId === block.id &&
- changeEvent.oldInputName === "MATERIAL");
+ changeEvent.oldInputName === 'MATERIAL');
if (!relevant) return;
@@ -360,7 +356,7 @@ function attachCreateMapOnChange(block) {
promoteMaterialContainerFromShadow(block);
- if (!block.getInputTargetBlock("MATERIAL")) {
+ if (!block.getInputTargetBlock('MATERIAL')) {
respawnMaterialShadow(block);
return;
}
@@ -375,66 +371,60 @@ function attachCreateMapOnChange(block) {
}
export function defineSceneBlocks() {
- Blockly.Blocks["set_sky_color"] = {
+ Blockly.Blocks['set_sky_color'] = {
init: function () {
initSceneColourLikeBlock(this, {
- type: "set_sky_color",
- inputColor: "#6495ED",
+ type: 'set_sky_color',
+ inputColor: '#6495ED',
listenToMove: true,
});
},
};
- Blockly.Blocks["create_ground"] = {
+ Blockly.Blocks['create_ground'] = {
init: function () {
initSceneColourLikeBlock(this, {
- type: "create_ground",
- inputColor: "#71BC78",
+ type: 'create_ground',
+ inputColor: '#71BC78',
listenToMove: true,
});
},
};
- Blockly.Blocks["set_background_color"] = {
+ Blockly.Blocks['set_background_color'] = {
init: function () {
initSceneColourLikeBlock(this, {
- type: "set_background_color",
- inputColor: "#6495ED",
- check: ["Colour"],
+ type: 'set_background_color',
+ inputColor: '#6495ED',
+ check: ['Colour'],
listenToMove: true,
});
},
};
- Blockly.Blocks["create_map"] = {
+ Blockly.Blocks['create_map'] = {
init: function () {
const args0 = [
...buildMapNameDropdownArgs(),
buildInputValueArg({
- name: "MATERIAL",
- check: [
- "Material",
- "Array",
- "Colour",
- "material_like",
- "colour_array",
- ],
+ name: 'MATERIAL',
+ check: ['Material', 'Array', 'Colour', 'material_like', 'colour_array'],
}),
];
- initSceneJsonBlock(this, { type: "create_map", args0 });
+ initSceneJsonBlock(this, { type: 'create_map', args0 });
attachCreateMapOnChange(this);
},
};
- Blockly.Blocks["show"] = {
+ Blockly.Blocks['show'] = {
init: function () {
initSceneJsonBlock(this, {
- type: "show",
+ type: 'show',
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
@@ -443,14 +433,14 @@ export function defineSceneBlocks() {
},
};
- Blockly.Blocks["hide"] = {
+ Blockly.Blocks['hide'] = {
init: function () {
initSceneJsonBlock(this, {
- type: "hide",
+ type: 'hide',
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
@@ -459,14 +449,14 @@ export function defineSceneBlocks() {
},
};
- Blockly.Blocks["dispose"] = {
+ Blockly.Blocks['dispose'] = {
init: function () {
initSceneJsonBlock(this, {
- type: "dispose",
+ type: 'dispose',
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
@@ -474,45 +464,39 @@ export function defineSceneBlocks() {
},
};
- Blockly.Blocks["clone_mesh"] = {
+ Blockly.Blocks['clone_mesh'] = {
init: function () {
- const variableNamePrefix = "clone";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'clone';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- message0: translate("clone_mesh"),
+ message0: translate('clone_mesh'),
args0: [
{
- type: "field_variable",
- name: "CLONE_VAR",
+ type: 'field_variable',
+ name: 'CLONE_VAR',
variable: nextVariableName,
},
{
- type: "field_variable",
- name: "SOURCE_MESH",
+ type: 'field_variable',
+ name: 'SOURCE_MESH',
variable: window.currentMesh,
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("clone_mesh"),
- helpUrl: "",
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('clone_mesh'),
+ helpUrl: '',
previousStatement: null,
nextStatement: null,
});
registerBlockHandler(this, (changeEvent) => {
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
addDoMutatorWithToggleBehavior(this);
},
};
diff --git a/blocks/sensing.js b/blocks/sensing.js
index 7cf5a2cf2..10d2b40d2 100644
--- a/blocks/sensing.js
+++ b/blocks/sensing.js
@@ -1,5 +1,5 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
getHelpUrlFor,
addToggleButton,
@@ -9,21 +9,16 @@ import {
handleBlockCreateEvent,
updateShape,
registerBlockHandler,
-} from "./blocks.js";
-import {
- translate,
- getTooltip,
- getOption,
- getDropdownOption,
-} from "../main/translation.js";
-import { ACTIONS } from "../input/bindings.js";
+} from './blocks.js';
+import { translate, getTooltip, getOption, getDropdownOption } from '../main/translation.js';
+import { ACTIONS } from '../input/bindings.js';
import { makeMicrobitStatusIcon } from './blockIcons.js';
import { getMicrobitManager, VariableStatus } from '../microbit/manager.js';
import { showBanner } from '../ui/notifications.js';
// Gamepad-only accessibility actions have no keyboard equivalent, so they're
// excluded from the keyboard-facing action dropdowns below.
-const KEYBOARD_ACTIONS = ACTIONS.filter((a) => !a.startsWith("A11Y_"));
+const KEYBOARD_ACTIONS = ACTIONS.filter((a) => !a.startsWith('A11Y_'));
const MICROBIT_STATUS_FIELD = 'STATUS';
export const MICROBIT_ANY_DEVICE = '__any__';
@@ -52,9 +47,7 @@ function microbitDeviceDropdownOptions(workspace, selectedVariableId) {
// when its add_microbit block is gone or hasn't loaded yet โ the menu is
// derived from add_microbit blocks, but the selection's validity is not.
if (selectedVariableId && !seenVariableIds.has(selectedVariableId)) {
- const variable = workspace
- .getVariableMap()
- .getVariableById(selectedVariableId);
+ const variable = workspace.getVariableMap().getVariableById(selectedVariableId);
if (variable) options.push([variable.name, variable.getId()]);
}
return options;
@@ -68,10 +61,7 @@ export class MicrobitDeviceDropdown extends Blockly.FieldDropdown {
constructor() {
super(function () {
const workspace = this.sourceBlock_?.workspace;
- return microbitDeviceDropdownOptions(
- workspace,
- this._candidateValue ?? this.getValue(),
- );
+ return microbitDeviceDropdownOptions(workspace, this._candidateValue ?? this.getValue());
});
this._candidateValue = null;
}
@@ -86,9 +76,7 @@ export class MicrobitDeviceDropdown extends Blockly.FieldDropdown {
const variable =
variableMap?.getVariableById(newValue) ??
// Legacy saves stored the variable name rather than its id.
- variableMap
- ?.getAllVariables()
- .find((candidate) => candidate.name === newValue) ??
+ variableMap?.getAllVariables().find((candidate) => candidate.name === newValue) ??
null;
if (variable) {
// Refresh the cached options with the accepted value so the label
@@ -173,10 +161,7 @@ export function isMicrobitRefreshEvent(changeEvent) {
return true;
}
if (changeEvent.type !== Blockly.Events.BLOCK_CHANGE) return false;
- return (
- changeEvent.element !== 'field' ||
- MICROBIT_REFRESH_FIELDS.has(changeEvent.name)
- );
+ return changeEvent.element !== 'field' || MICROBIT_REFRESH_FIELDS.has(changeEvent.name);
}
let refreshingMicrobitBlocks = false;
@@ -250,8 +235,7 @@ export function refreshMicrobitBlocks(workspace) {
const variable = workspace.getVariableMap().getVariableById(device);
tethered =
!!variable &&
- manager.getStatusForVariable(variable.name).state ===
- VariableStatus.TETHERED;
+ manager.getStatusForVariable(variable.name).state === VariableStatus.TETHERED;
}
block.setWarningText(tethered ? null : translate(warningKey));
}
@@ -285,449 +269,446 @@ function wireMicrobitManager() {
}
export function defineSensingBlocks() {
- Blockly.Blocks["key_pressed"] = {
+ Blockly.Blocks['key_pressed'] = {
init: function () {
this.jsonInit({
- type: "key_pressed",
- message0: translate("key_pressed"),
+ type: 'key_pressed',
+ message0: translate('key_pressed'),
args0: [
{
- type: "field_dropdown",
- name: "KEY",
+ type: 'field_dropdown',
+ name: 'KEY',
options: [
- getDropdownOption("ANY"),
- getDropdownOption("NONE"),
- getDropdownOption("w"),
- getDropdownOption("a"),
- getDropdownOption("s"),
- getDropdownOption("d"),
- [getOption("space_infinity"), " "],
- [getOption("q_icon"), "q"],
- [getOption("e_icon"), "e"],
- [getOption("f_icon"), "f"],
+ getDropdownOption('ANY'),
+ getDropdownOption('NONE'),
+ getDropdownOption('w'),
+ getDropdownOption('a'),
+ getDropdownOption('s'),
+ getDropdownOption('d'),
+ [getOption('space_infinity'), ' '],
+ [getOption('q_icon'), 'q'],
+ [getOption('e_icon'), 'e'],
+ [getOption('f_icon'), 'f'],
],
},
],
- output: "Boolean",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("key_pressed"),
+ output: 'Boolean',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('key_pressed'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["action_pressed"] = {
+ Blockly.Blocks['action_pressed'] = {
init: function () {
this.jsonInit({
- type: "action_pressed",
- message0: translate("action_pressed"),
+ type: 'action_pressed',
+ message0: translate('action_pressed'),
args0: [
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: KEYBOARD_ACTIONS.map((a) => [getOption(`ACTION_${a}`), a]),
},
],
- output: "Boolean",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("action_pressed"),
+ output: 'Boolean',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('action_pressed'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["set_action_key"] = {
+ Blockly.Blocks['set_action_key'] = {
init: function () {
this.jsonInit({
- type: "set_action_key",
- message0: translate("set_action_key"),
+ type: 'set_action_key',
+ message0: translate('set_action_key'),
args0: [
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: KEYBOARD_ACTIONS.map((a) => [getOption(`ACTION_${a}`), a]),
},
{
- type: "field_grid_dropdown",
- name: "KEY",
+ type: 'field_grid_dropdown',
+ name: 'KEY',
columns: 10,
options: [
- getDropdownOption("0"),
- getDropdownOption("1"),
- getDropdownOption("2"),
- getDropdownOption("3"),
- getDropdownOption("4"),
- getDropdownOption("5"),
- getDropdownOption("6"),
- getDropdownOption("7"),
- getDropdownOption("8"),
- getDropdownOption("9"),
- getDropdownOption("a"),
- getDropdownOption("b"),
- getDropdownOption("c"),
- getDropdownOption("d"),
- getDropdownOption("e"),
- getDropdownOption("f"),
- getDropdownOption("g"),
- getDropdownOption("h"),
- getDropdownOption("i"),
- getDropdownOption("j"),
- getDropdownOption("k"),
- getDropdownOption("l"),
- getDropdownOption("m"),
- getDropdownOption("n"),
- getDropdownOption("o"),
- getDropdownOption("p"),
- getDropdownOption("q"),
- getDropdownOption("r"),
- getDropdownOption("s"),
- getDropdownOption("t"),
- getDropdownOption("u"),
- getDropdownOption("v"),
- getDropdownOption("w"),
- getDropdownOption("x"),
- getDropdownOption("y"),
- getDropdownOption("z"),
- getDropdownOption(" "),
- getDropdownOption(","),
- getDropdownOption("."),
- getDropdownOption("/"),
- getDropdownOption("ArrowLeft"),
- getDropdownOption("ArrowUp"),
- getDropdownOption("ArrowRight"),
- getDropdownOption("ArrowDown"),
+ getDropdownOption('0'),
+ getDropdownOption('1'),
+ getDropdownOption('2'),
+ getDropdownOption('3'),
+ getDropdownOption('4'),
+ getDropdownOption('5'),
+ getDropdownOption('6'),
+ getDropdownOption('7'),
+ getDropdownOption('8'),
+ getDropdownOption('9'),
+ getDropdownOption('a'),
+ getDropdownOption('b'),
+ getDropdownOption('c'),
+ getDropdownOption('d'),
+ getDropdownOption('e'),
+ getDropdownOption('f'),
+ getDropdownOption('g'),
+ getDropdownOption('h'),
+ getDropdownOption('i'),
+ getDropdownOption('j'),
+ getDropdownOption('k'),
+ getDropdownOption('l'),
+ getDropdownOption('m'),
+ getDropdownOption('n'),
+ getDropdownOption('o'),
+ getDropdownOption('p'),
+ getDropdownOption('q'),
+ getDropdownOption('r'),
+ getDropdownOption('s'),
+ getDropdownOption('t'),
+ getDropdownOption('u'),
+ getDropdownOption('v'),
+ getDropdownOption('w'),
+ getDropdownOption('x'),
+ getDropdownOption('y'),
+ getDropdownOption('z'),
+ getDropdownOption(' '),
+ getDropdownOption(','),
+ getDropdownOption('.'),
+ getDropdownOption('/'),
+ getDropdownOption('ArrowLeft'),
+ getDropdownOption('ArrowUp'),
+ getDropdownOption('ArrowRight'),
+ getDropdownOption('ArrowDown'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("set_action_key"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('set_action_key'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["meshes_touching"] = {
+ Blockly.Blocks['meshes_touching'] = {
init: function () {
this.jsonInit({
- type: "meshes_are_touching",
- message0: translate("meshes_touching"),
+ type: 'meshes_are_touching',
+ message0: translate('meshes_touching'),
args0: [
{
- type: "field_variable",
- name: "MESH1",
+ type: 'field_variable',
+ name: 'MESH1',
variable: window.currentMesh,
},
{
- type: "field_variable",
- name: "MESH2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MESH2',
+ variable: 'object2',
},
],
- output: "Boolean",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("meshes_touching"),
+ output: 'Boolean',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('meshes_touching'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["time"] = {
+ Blockly.Blocks['time'] = {
init: function () {
this.jsonInit({
- type: "time",
- message0: translate("time"),
+ type: 'time',
+ message0: translate('time'),
args0: [
{
- type: "field_dropdown",
- name: "UNIT",
+ type: 'field_dropdown',
+ name: 'UNIT',
options: [
- [getOption("seconds"), "seconds"],
- [getOption("milliseconds"), "milliseconds"],
- [getOption("minutes"), "minutes"],
+ [getOption('seconds'), 'seconds'],
+ [getOption('milliseconds'), 'milliseconds'],
+ [getOption('minutes'), 'minutes'],
],
},
],
- output: "Number",
- colour: categoryColours["Sensing"],
+ output: 'Number',
+ colour: categoryColours['Sensing'],
inputsInline: true,
- tooltip: getTooltip("time"),
+ tooltip: getTooltip('time'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["ground_level"] = {
+ Blockly.Blocks['ground_level'] = {
init: function () {
this.jsonInit({
- type: "ground_level",
- message0: translate("ground_level"),
- output: "Number",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("ground_level"),
+ type: 'ground_level',
+ message0: translate('ground_level'),
+ output: 'Number',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('ground_level'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["distance_to"] = {
+ Blockly.Blocks['distance_to'] = {
init: function () {
this.jsonInit({
- type: "distance_to",
- message0: translate("distance_to"),
+ type: 'distance_to',
+ message0: translate('distance_to'),
args0: [
{
- type: "field_variable",
- name: "MODEL1",
+ type: 'field_variable',
+ name: 'MODEL1',
variable: window.currentMesh,
},
{
- type: "field_variable",
- name: "MODEL2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MODEL2',
+ variable: 'object2',
},
],
- output: "Number",
- colour: categoryColours["Sensing"],
+ output: 'Number',
+ colour: categoryColours['Sensing'],
inputsInline: true,
- tooltip: getTooltip("distance_to"),
+ tooltip: getTooltip('distance_to'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["touching_surface"] = {
+ Blockly.Blocks['touching_surface'] = {
init: function () {
this.jsonInit({
- type: "touching_surface",
- message0: translate("touching_surface"),
+ type: 'touching_surface',
+ message0: translate('touching_surface'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
- output: "Boolean",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("touching_surface"),
+ output: 'Boolean',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('touching_surface'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["mesh_exists"] = {
+ Blockly.Blocks['mesh_exists'] = {
init: function () {
this.jsonInit({
- type: "mesh_exists",
- message0: translate("mesh_exists"),
+ type: 'mesh_exists',
+ message0: translate('mesh_exists'),
args0: [
{
- type: "field_variable",
- name: "MODEL_VAR",
+ type: 'field_variable',
+ name: 'MODEL_VAR',
variable: window.currentMesh,
},
],
- output: "Boolean",
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("mesh_exists"),
+ output: 'Boolean',
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('mesh_exists'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["get_property"] = {
+ Blockly.Blocks['get_property'] = {
init: function () {
this.jsonInit({
- type: "get_property",
- message0: translate("get_property"),
+ type: 'get_property',
+ message0: translate('get_property'),
args0: [
{
- type: "field_dropdown",
- name: "PROPERTY",
+ type: 'field_dropdown',
+ name: 'PROPERTY',
options: [
- getDropdownOption("POSITION_X"),
- getDropdownOption("POSITION_Y"),
- getDropdownOption("POSITION_Z"),
- getDropdownOption("ROTATION_X"),
- getDropdownOption("ROTATION_Y"),
- getDropdownOption("ROTATION_Z"),
- getDropdownOption("MIN_X"),
- getDropdownOption("MAX_X"),
- getDropdownOption("MIN_Y"),
- getDropdownOption("MAX_Y"),
- getDropdownOption("MIN_Z"),
- getDropdownOption("MAX_Z"),
- getDropdownOption("SCALE_X"),
- getDropdownOption("SCALE_Y"),
- getDropdownOption("SCALE_Z"),
- getDropdownOption("SIZE_X"),
- getDropdownOption("SIZE_Y"),
- getDropdownOption("SIZE_Z"),
- getDropdownOption("SPEED_X"),
- getDropdownOption("SPEED_Y"),
- getDropdownOption("SPEED_Z"),
- getDropdownOption("SPEED"),
- getDropdownOption("VISIBLE"),
- getDropdownOption("ALPHA"),
- getDropdownOption("COLOUR"),
- getDropdownOption("DESCRIPTION"),
+ getDropdownOption('POSITION_X'),
+ getDropdownOption('POSITION_Y'),
+ getDropdownOption('POSITION_Z'),
+ getDropdownOption('ROTATION_X'),
+ getDropdownOption('ROTATION_Y'),
+ getDropdownOption('ROTATION_Z'),
+ getDropdownOption('MIN_X'),
+ getDropdownOption('MAX_X'),
+ getDropdownOption('MIN_Y'),
+ getDropdownOption('MAX_Y'),
+ getDropdownOption('MIN_Z'),
+ getDropdownOption('MAX_Z'),
+ getDropdownOption('SCALE_X'),
+ getDropdownOption('SCALE_Y'),
+ getDropdownOption('SCALE_Z'),
+ getDropdownOption('SIZE_X'),
+ getDropdownOption('SIZE_Y'),
+ getDropdownOption('SIZE_Z'),
+ getDropdownOption('SPEED_X'),
+ getDropdownOption('SPEED_Y'),
+ getDropdownOption('SPEED_Z'),
+ getDropdownOption('SPEED'),
+ getDropdownOption('VISIBLE'),
+ getDropdownOption('ALPHA'),
+ getDropdownOption('COLOUR'),
+ getDropdownOption('DESCRIPTION'),
],
},
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
],
output: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("get_property"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('get_property'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["canvas_controls"] = {
+ Blockly.Blocks['canvas_controls'] = {
init: function () {
this.jsonInit({
- type: "canvas_controls",
- message0: translate("canvas_controls"),
+ type: 'canvas_controls',
+ message0: translate('canvas_controls'),
args0: [
{
- type: "field_checkbox",
- name: "CONTROLS",
+ type: 'field_checkbox',
+ name: 'CONTROLS',
checked: true,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("canvas_controls"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('canvas_controls'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["interact_indicator"] = {
+ Blockly.Blocks['interact_indicator'] = {
init: function () {
this.jsonInit({
- type: "interact_indicator",
- message0: translate("interact_indicator"),
+ type: 'interact_indicator',
+ message0: translate('interact_indicator'),
args0: [
{
- type: "field_checkbox",
- name: "ENABLED",
+ type: 'field_checkbox',
+ name: 'ENABLED',
checked: true,
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("interact_indicator"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('interact_indicator'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["button_controls"] = {
+ Blockly.Blocks['button_controls'] = {
init: function () {
this.jsonInit({
- type: "button_controls",
- message0: translate("button_controls"),
+ type: 'button_controls',
+ message0: translate('button_controls'),
args0: [
{
- type: "field_dropdown",
- name: "CONTROL",
+ type: 'field_dropdown',
+ name: 'CONTROL',
options: [
- getDropdownOption("BOTH"),
- getDropdownOption("ARROWS"),
- getDropdownOption("ACTIONS"),
+ getDropdownOption('BOTH'),
+ getDropdownOption('ARROWS'),
+ getDropdownOption('ACTIONS'),
],
},
{
- type: "field_dropdown",
- name: "ENABLED",
+ type: 'field_dropdown',
+ name: 'ENABLED',
options: [
- getDropdownOption("AUTO"),
- getDropdownOption("ENABLED"),
- getDropdownOption("DISABLED"),
+ getDropdownOption('AUTO'),
+ getDropdownOption('ENABLED'),
+ getDropdownOption('DISABLED'),
],
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("button_controls"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('button_controls'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
- Blockly.Blocks["on_screen_controls"] = {
+ Blockly.Blocks['on_screen_controls'] = {
init: function () {
this.jsonInit({
- type: "on_screen_controls",
- message0: translate("on_screen_controls"),
+ type: 'on_screen_controls',
+ message0: translate('on_screen_controls'),
args0: [
{
- type: "field_dropdown",
- name: "MOVEMENT",
+ type: 'field_dropdown',
+ name: 'MOVEMENT',
options: [
- getDropdownOption("ARROWS"),
- getDropdownOption("JOYSTICK"),
- getDropdownOption("NONE"),
+ getDropdownOption('ARROWS'),
+ getDropdownOption('JOYSTICK'),
+ getDropdownOption('NONE'),
],
},
{
- type: "field_dropdown",
- name: "ACTIONS",
- options: [
- getDropdownOption("YES"),
- getDropdownOption("NO"),
- ],
+ type: 'field_dropdown',
+ name: 'ACTIONS',
+ options: [getDropdownOption('YES'), getDropdownOption('NO')],
},
{
- type: "field_dropdown",
- name: "ENABLED",
+ type: 'field_dropdown',
+ name: 'ENABLED',
options: [
- getDropdownOption("AUTO"),
- getDropdownOption("ENABLED"),
- getDropdownOption("DISABLED"),
+ getDropdownOption('AUTO'),
+ getDropdownOption('ENABLED'),
+ getDropdownOption('DISABLED'),
],
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("on_screen_controls"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('on_screen_controls'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
},
};
@@ -790,55 +771,55 @@ export function defineSensingBlocks() {
},
};
- Blockly.Blocks["microbit_input"] = {
+ Blockly.Blocks['microbit_input'] = {
init: function () {
this.jsonInit({
- type: "microbit_input",
- message0: translate("microbit_input"),
+ type: 'microbit_input',
+ message0: translate('microbit_input'),
args0: [
{
- type: "field_dropdown",
- name: "EVENT",
+ type: 'field_dropdown',
+ name: 'EVENT',
options: [
- [getOption("pin_0"), "0"],
- [getOption("pin_1"), "1"],
- [getOption("pin_2"), "2"],
- [getOption("pin_l"), "l"],
- [getOption("pin_j"), "j"],
- [getOption("pin_h"), "h"],
- [getOption("pin_k"), "k"],
- [getOption("pin_space"), " "],
- [getOption("pin_q"), "q"],
- [getOption("pin_r"), "r"],
- [getOption("pin_t"), "t"],
- [getOption("pin_o"), "o"],
- [getOption("pin_p"), "p"],
- [getOption("pin_a"), "a"],
- [getOption("pin_d"), "d"],
- [getOption("pin_y"), "y"],
- [getOption("pin_g"), "g"],
- [getOption("pin_i"), "i"],
+ [getOption('pin_0'), '0'],
+ [getOption('pin_1'), '1'],
+ [getOption('pin_2'), '2'],
+ [getOption('pin_l'), 'l'],
+ [getOption('pin_j'), 'j'],
+ [getOption('pin_h'), 'h'],
+ [getOption('pin_k'), 'k'],
+ [getOption('pin_space'), ' '],
+ [getOption('pin_q'), 'q'],
+ [getOption('pin_r'), 'r'],
+ [getOption('pin_t'), 't'],
+ [getOption('pin_o'), 'o'],
+ [getOption('pin_p'), 'p'],
+ [getOption('pin_a'), 'a'],
+ [getOption('pin_d'), 'd'],
+ [getOption('pin_y'), 'y'],
+ [getOption('pin_g'), 'g'],
+ [getOption('pin_i'), 'i'],
],
},
],
- message1: "%1",
+ message1: '%1',
args1: [
{
- type: "input_statement",
- name: "DO",
+ type: 'input_statement',
+ name: 'DO',
},
],
- colour: categoryColours["Sensing"],
- tooltip: getTooltip("microbit_input"),
+ colour: categoryColours['Sensing'],
+ tooltip: getTooltip('microbit_input'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sensing_blocks");
+ this.setStyle('sensing_blocks');
// Device dropdown (dynamic_mesh_dropdown style): "any" plus the
// micro:bit variables defined via add_microbit blocks. Appended outside
// message0 so legacy XML โ which has no DEVICE field โ loads unchanged
// and defaults to "any".
- this.inputList[0].appendField(new MicrobitDeviceDropdown(), "DEVICE");
+ this.inputList[0].appendField(new MicrobitDeviceDropdown(), 'DEVICE');
syncMicrobitDeviceField(this);
this.setOnChange((changeEvent) => {
if (
@@ -864,82 +845,81 @@ export function defineSensingBlocks() {
this.updateShape_(!this.isInline);
},
};
- Blockly.Blocks["ui_slider"] = {
+ Blockly.Blocks['ui_slider'] = {
init: function () {
- const variableNamePrefix = "slider";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'slider';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "ui_slider",
- message0: translate("ui_slider"),
+ type: 'ui_slider',
+ message0: translate('ui_slider'),
args0: [
{
- type: "field_variable",
- name: "SLIDER_VAR",
+ type: 'field_variable',
+ name: 'SLIDER_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "MIN",
- check: "Number",
+ type: 'input_value',
+ name: 'MIN',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MAX",
- check: "Number",
+ type: 'input_value',
+ name: 'MAX',
+ check: 'Number',
},
{
- type: "input_value",
- name: "VALUE",
- check: "Number",
+ type: 'input_value',
+ name: 'VALUE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND',
+ check: 'Colour',
},
{
- type: "field_dropdown",
- name: "SIZE",
+ type: 'field_dropdown',
+ name: 'SIZE',
options: [
- getDropdownOption("SMALL"),
- getDropdownOption("MEDIUM"),
- getDropdownOption("LARGE"),
+ getDropdownOption('SMALL'),
+ getDropdownOption('MEDIUM'),
+ getDropdownOption('LARGE'),
],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Text"],
- tooltip: getTooltip("ui_slider"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('ui_slider'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
this,
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "SLIDER_VAR",
- ),
+ 'SLIDER_VAR'
+ )
);
},
};
diff --git a/blocks/shapes.js b/blocks/shapes.js
index 7eedb111b..831ac1966 100644
--- a/blocks/shapes.js
+++ b/blocks/shapes.js
@@ -1,5 +1,5 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
nextVariableIndexes,
handleBlockChange,
@@ -7,933 +7,918 @@ import {
handleBlockCreateEvent,
getHelpUrlFor,
registerBlockHandler,
-} from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+} from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
export function defineShapeBlocks() {
// Define the particle effect block.
- Blockly.Blocks["create_particle_effect"] = {
+ Blockly.Blocks['create_particle_effect'] = {
init: function () {
- const variableNamePrefix = "particleEffect";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'particleEffect';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- message0: translate("create_particle_effect"),
+ message0: translate('create_particle_effect'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_variable",
- name: "EMITTER_MESH",
+ type: 'field_variable',
+ name: 'EMITTER_MESH',
variable: window.currentMesh,
},
{
- type: "field_grid_dropdown",
- name: "SHAPE",
+ type: 'field_grid_dropdown',
+ name: 'SHAPE',
options: [
[
{
- src: "./textures/circle_texture.png",
+ src: './textures/circle_texture.png',
width: 32,
height: 32,
- alt: "Circle",
+ alt: 'Circle',
},
- "circle_texture.png",
+ 'circle_texture.png',
],
[
{
- src: "./textures/balloon_texture.png",
+ src: './textures/balloon_texture.png',
width: 32,
height: 32,
- alt: "Balloon",
+ alt: 'Balloon',
},
- "balloon_texture.png",
+ 'balloon_texture.png',
],
[
{
- src: "./textures/bee_texture.png",
+ src: './textures/bee_texture.png',
width: 32,
height: 32,
- alt: "Bee",
+ alt: 'Bee',
},
- "bee_texture.png",
+ 'bee_texture.png',
],
[
{
- src: "./textures/bird_texture.png",
+ src: './textures/bird_texture.png',
width: 32,
height: 32,
- alt: "Bird",
+ alt: 'Bird',
},
- "bird_texture.png",
+ 'bird_texture.png',
],
[
{
- src: "./textures/blast_texture.png",
+ src: './textures/blast_texture.png',
width: 32,
height: 32,
- alt: "Blast",
+ alt: 'Blast',
},
- "blast_texture.png",
+ 'blast_texture.png',
],
[
{
- src: "./textures/bubble_texture.png",
+ src: './textures/bubble_texture.png',
width: 32,
height: 32,
- alt: "Bubble",
+ alt: 'Bubble',
},
- "bubble_texture.png",
+ 'bubble_texture.png',
],
[
{
- src: "./textures/burst_texture.png",
+ src: './textures/burst_texture.png',
width: 32,
height: 32,
- alt: "Burst",
+ alt: 'Burst',
},
- "burst_texture.png",
+ 'burst_texture.png',
],
[
{
- src: "./textures/chevron_texture.png",
+ src: './textures/chevron_texture.png',
width: 32,
height: 32,
- alt: "Cheveron",
+ alt: 'Cheveron',
},
- "chevron_texture.png",
+ 'chevron_texture.png',
],
[
{
- src: "./textures/comet_texture.png",
+ src: './textures/comet_texture.png',
width: 32,
height: 32,
- alt: "Comet",
+ alt: 'Comet',
},
- "comet_texture.png",
+ 'comet_texture.png',
],
[
{
- src: "./textures/confetti_texture.png",
+ src: './textures/confetti_texture.png',
width: 32,
height: 32,
- alt: "Confetti",
+ alt: 'Confetti',
},
- "confetti_texture.png",
+ 'confetti_texture.png',
],
[
{
- src: "./textures/exclaim_texture.png",
+ src: './textures/exclaim_texture.png',
width: 32,
height: 32,
- alt: "Exclaim",
+ alt: 'Exclaim',
},
- "exclaim_texture.png",
+ 'exclaim_texture.png',
],
[
{
- src: "./textures/flock_texture.png",
+ src: './textures/flock_texture.png',
width: 32,
height: 32,
- alt: "Flock",
+ alt: 'Flock',
},
- "flock_texture.png",
+ 'flock_texture.png',
],
[
{
- src: "./textures/fish_texture.png",
+ src: './textures/fish_texture.png',
width: 32,
height: 32,
- alt: "Fish",
+ alt: 'Fish',
},
- "fish_texture.png",
+ 'fish_texture.png',
],
[
{
- src: "./textures/fragments_texture.png",
+ src: './textures/fragments_texture.png',
width: 32,
height: 32,
- alt: "Fragments",
+ alt: 'Fragments',
},
- "fragments_texture.png",
+ 'fragments_texture.png',
],
[
{
- src: "./textures/gem_texture.png",
+ src: './textures/gem_texture.png',
width: 32,
height: 32,
- alt: "Gem",
+ alt: 'Gem',
},
- "gem_texture.png",
+ 'gem_texture.png',
],
[
{
- src: "./textures/ghost_texture.png",
+ src: './textures/ghost_texture.png',
width: 32,
height: 32,
- alt: "Ghost",
+ alt: 'Ghost',
},
- "ghost_texture.png",
+ 'ghost_texture.png',
],
[
{
- src: "./textures/heart_texture.png",
+ src: './textures/heart_texture.png',
width: 32,
height: 32,
- alt: "Heart",
+ alt: 'Heart',
},
- "heart_texture.png",
+ 'heart_texture.png',
],
[
{
- src: "./textures/leaf_texture.png",
+ src: './textures/leaf_texture.png',
width: 32,
height: 32,
- alt: "Leaf",
+ alt: 'Leaf',
},
- "leaf_texture.png",
+ 'leaf_texture.png',
],
[
{
- src: "./textures/leaf2_texture.png",
+ src: './textures/leaf2_texture.png',
width: 32,
height: 32,
- alt: "Leaf",
+ alt: 'Leaf',
},
- "leaf2_texture.png",
+ 'leaf2_texture.png',
],
[
{
- src: "./textures/mic_texture.png",
+ src: './textures/mic_texture.png',
width: 32,
height: 32,
- alt: "Mic",
+ alt: 'Mic',
},
- "mic_texture.png",
+ 'mic_texture.png',
],
[
{
- src: "./textures/money_texture.png",
+ src: './textures/money_texture.png',
width: 32,
height: 32,
- alt: "Money",
+ alt: 'Money',
},
- "money_texture.png",
+ 'money_texture.png',
],
[
{
- src: "./textures/music_texture.png",
+ src: './textures/music_texture.png',
width: 32,
height: 32,
- alt: "Music",
+ alt: 'Music',
},
- "music_texture.png",
+ 'music_texture.png',
],
[
{
- src: "./textures/paw_texture.png",
+ src: './textures/paw_texture.png',
width: 32,
height: 32,
- alt: "Paw",
+ alt: 'Paw',
},
- "paw_texture.png",
+ 'paw_texture.png',
],
[
{
- src: "./textures/rays_texture.png",
+ src: './textures/rays_texture.png',
width: 32,
height: 32,
- alt: "Rays",
+ alt: 'Rays',
},
- "rays_texture.png",
+ 'rays_texture.png',
],
[
{
- src: "./textures/ripple_texture.png",
+ src: './textures/ripple_texture.png',
width: 32,
height: 32,
- alt: "Ripple",
+ alt: 'Ripple',
},
- "ripple_texture.png",
+ 'ripple_texture.png',
],
[
{
- src: "./textures/rocket_texture.png",
+ src: './textures/rocket_texture.png',
width: 32,
height: 32,
- alt: "Rocket",
+ alt: 'Rocket',
},
- "rocket_texture.png",
+ 'rocket_texture.png',
],
[
{
- src: "./textures/sleep_texture.png",
+ src: './textures/sleep_texture.png',
width: 32,
height: 32,
- alt: "Sleep",
+ alt: 'Sleep',
},
- "sleep_texture.png",
+ 'sleep_texture.png',
],
[
{
- src: "./textures/speaking_texture.png",
+ src: './textures/speaking_texture.png',
width: 32,
height: 32,
- alt: "Speaking",
+ alt: 'Speaking',
},
- "speaking_texture.png",
+ 'speaking_texture.png',
],
[
{
- src: "./textures/splash_texture.png",
+ src: './textures/splash_texture.png',
width: 32,
height: 32,
- alt: "Splash",
+ alt: 'Splash',
},
- "splash_texture.png",
+ 'splash_texture.png',
],
[
{
- src: "./textures/splat_texture.png",
+ src: './textures/splat_texture.png',
width: 32,
height: 32,
- alt: "Splat",
+ alt: 'Splat',
},
- "splat_texture.png",
+ 'splat_texture.png',
],
[
{
- src: "./textures/star_texture.png",
+ src: './textures/star_texture.png',
width: 32,
height: 32,
- alt: "Star",
+ alt: 'Star',
},
- "star_texture.png",
+ 'star_texture.png',
],
[
{
- src: "./textures/sweet_texture.png",
+ src: './textures/sweet_texture.png',
width: 32,
height: 32,
- alt: "Sweet",
+ alt: 'Sweet',
},
- "sweet_texture.png",
+ 'sweet_texture.png',
],
[
{
- src: "./textures/butterfly_texture.png",
+ src: './textures/butterfly_texture.png',
width: 32,
height: 32,
- alt: "Butterfly",
+ alt: 'Butterfly',
},
- "butterfly_texture.png",
+ 'butterfly_texture.png',
],
[
{
- src: "./textures/flower_texture.png",
+ src: './textures/flower_texture.png',
width: 32,
height: 32,
- alt: "Flower",
+ alt: 'Flower',
},
- "flower_texture.png",
+ 'flower_texture.png',
],
[
{
- src: "./textures/flame_texture.png",
+ src: './textures/flame_texture.png',
width: 32,
height: 32,
- alt: "Flame",
+ alt: 'Flame',
},
- "flame_texture.png",
+ 'flame_texture.png',
],
[
{
- src: "./textures/smoke_texture.png",
+ src: './textures/smoke_texture.png',
width: 32,
height: 32,
- alt: "Smoke",
+ alt: 'Smoke',
},
- "smoke_texture.png",
+ 'smoke_texture.png',
],
[
{
- src: "./textures/snowflake_texture.png",
+ src: './textures/snowflake_texture.png',
width: 32,
height: 32,
- alt: "Snowflake",
+ alt: 'Snowflake',
},
- "snowflake_texture.png",
+ 'snowflake_texture.png',
],
[
{
- src: "./textures/swirl_texture.png",
+ src: './textures/swirl_texture.png',
width: 32,
height: 32,
- alt: "Swirl",
+ alt: 'Swirl',
},
- "swirl_texture.png",
+ 'swirl_texture.png',
],
[
{
- src: "./textures/wave_texture.png",
+ src: './textures/wave_texture.png',
width: 32,
height: 32,
- alt: "Wave",
+ alt: 'Wave',
},
- "wave_texture.png",
+ 'wave_texture.png',
],
[
{
- src: "./textures/wind_texture.png",
+ src: './textures/wind_texture.png',
width: 32,
height: 32,
- alt: "Wind",
+ alt: 'Wind',
},
- "wind_texture.png",
+ 'wind_texture.png',
],
[
{
- src: "./textures/strip_texture.png",
+ src: './textures/strip_texture.png',
width: 32,
height: 32,
- alt: "Strip",
+ alt: 'Strip',
},
- "strip_texture.png",
+ 'strip_texture.png',
],
[
{
- src: "./textures/crescent_texture.png",
+ src: './textures/crescent_texture.png',
width: 32,
height: 32,
- alt: "Crescent",
+ alt: 'Crescent',
},
- "crescent_texture.png",
+ 'crescent_texture.png',
],
[
{
- src: "./textures/lightning_texture.png",
+ src: './textures/lightning_texture.png',
width: 32,
height: 32,
- alt: "Lightning bolt",
+ alt: 'Lightning bolt',
},
- "lightning_texture.png",
+ 'lightning_texture.png',
],
[
{
- src: "./textures/droplet_texture.png",
+ src: './textures/droplet_texture.png',
width: 32,
height: 32,
- alt: "Droplet",
+ alt: 'Droplet',
},
- "droplet_texture.png",
+ 'droplet_texture.png',
],
[
{
- src: "./textures/shard_texture.png",
+ src: './textures/shard_texture.png',
width: 32,
height: 32,
- alt: "Shard",
+ alt: 'Shard',
},
- "shard_texture.png",
+ 'shard_texture.png',
],
[
{
- src: "./textures/square_texture.png",
+ src: './textures/square_texture.png',
width: 32,
height: 32,
- alt: "Square",
+ alt: 'Square',
},
- "square_texture.png",
+ 'square_texture.png',
],
[
{
- src: "./textures/arrow_texture.png",
+ src: './textures/arrow_texture.png',
width: 32,
height: 32,
- alt: "Arrow",
+ alt: 'Arrow',
},
- "arrow_texture.png",
+ 'arrow_texture.png',
],
],
},
{
- type: "input_value",
- name: "START_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'START_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "END_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'END_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "START_ALPHA",
- check: "Number",
+ type: 'input_value',
+ name: 'START_ALPHA',
+ check: 'Number',
},
{
- type: "input_value",
- name: "END_ALPHA",
- check: "Number",
+ type: 'input_value',
+ name: 'END_ALPHA',
+ check: 'Number',
},
{
- type: "input_value",
- name: "RATE",
- check: "Number",
+ type: 'input_value',
+ name: 'RATE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MIN_SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'MIN_SIZE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MAX_SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'MAX_SIZE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MIN_LIFETIME",
- check: "Number",
+ type: 'input_value',
+ name: 'MIN_LIFETIME',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MAX_LIFETIME",
- check: "Number",
+ type: 'input_value',
+ name: 'MAX_LIFETIME',
+ check: 'Number',
},
{
- type: "field_checkbox",
- name: "GRAVITY",
+ type: 'field_checkbox',
+ name: 'GRAVITY',
checked: false,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MIN_ANGULAR_SPEED",
- check: "Number",
+ type: 'input_value',
+ name: 'MIN_ANGULAR_SPEED',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MAX_ANGULAR_SPEED",
- check: "Number",
+ type: 'input_value',
+ name: 'MAX_ANGULAR_SPEED',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MIN_INITIAL_ROTATION",
- check: "Number",
+ type: 'input_value',
+ name: 'MIN_INITIAL_ROTATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "MAX_INITIAL_ROTATION",
- check: "Number",
+ type: 'input_value',
+ name: 'MAX_INITIAL_ROTATION',
+ check: 'Number',
},
],
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_particle_effect"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_particle_effect'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
registerBlockHandler(this, (changeEvent) =>
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- ),
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes)
);
},
};
- Blockly.Blocks["create_box"] = {
+ Blockly.Blocks['create_box'] = {
init: function () {
- const variableNamePrefix = "box";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'box';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_box",
- message0: translate("create_box"),
+ type: 'create_box',
+ message0: translate('create_box'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Material'],
},
{
- type: "input_value",
- name: "WIDTH",
- check: "Number",
+ type: 'input_value',
+ name: 'WIDTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'HEIGHT',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DEPTH",
- check: "Number",
+ type: 'input_value',
+ name: 'DEPTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_box"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_box'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Set up the change handler.
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
// Add the mutator with toggle behaviour.
addDoMutatorWithToggleBehavior(this);
},
};
- Blockly.Blocks["create_sphere"] = {
+ Blockly.Blocks['create_sphere'] = {
init: function () {
- const variableNamePrefix = "sphere";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'sphere';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_sphere",
- message0: translate("create_sphere"),
+ type: 'create_sphere',
+ message0: translate('create_sphere'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Material'],
},
{
- type: "input_value",
- name: "DIAMETER_X",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER_X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DIAMETER_Y",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER_Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DIAMETER_Z",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER_Z',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_sphere"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_sphere'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Set up the change handler.
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
// Add the mutator with toggle behaviour.
addDoMutatorWithToggleBehavior(this);
},
};
- Blockly.Blocks["create_cylinder"] = {
+ Blockly.Blocks['create_cylinder'] = {
init: function () {
- const variableNamePrefix = "cylinder";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'cylinder';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_cylinder",
- message0: translate("create_cylinder"),
+ type: 'create_cylinder',
+ message0: translate('create_cylinder'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Material'],
},
{
- type: "input_value",
- name: "HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'HEIGHT',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DIAMETER_TOP",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER_TOP',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DIAMETER_BOTTOM",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER_BOTTOM',
+ check: 'Number',
},
{
- type: "input_value",
- name: "TESSELLATIONS",
- check: "Number",
+ type: 'input_value',
+ name: 'TESSELLATIONS',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_cylinder"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_cylinder'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Set up the change handler.
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
// Add the mutator with toggle behaviour.
addDoMutatorWithToggleBehavior(this);
},
};
- Blockly.Blocks["create_capsule"] = {
+ Blockly.Blocks['create_capsule'] = {
init: function () {
- const variableNamePrefix = "capsule";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'capsule';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_capsule",
- message0: translate("create_capsule"),
+ type: 'create_capsule',
+ message0: translate('create_capsule'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Material'],
},
{
- type: "input_value",
- name: "DIAMETER",
- check: "Number",
+ type: 'input_value',
+ name: 'DIAMETER',
+ check: 'Number',
},
{
- type: "input_value",
- name: "HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'HEIGHT',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_capsule"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_capsule'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Set up the change handler.
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
// Add the mutator with toggle behaviour.
addDoMutatorWithToggleBehavior(this);
},
};
- Blockly.Blocks["create_plane"] = {
+ Blockly.Blocks['create_plane'] = {
init: function () {
- const variableNamePrefix = "plane";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'plane';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_plane",
- message0: translate("create_plane"),
+ type: 'create_plane',
+ message0: translate('create_plane'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "COLOR",
- check: ["Colour", "Material"],
+ type: 'input_value',
+ name: 'COLOR',
+ check: ['Colour', 'Material'],
},
{
- type: "input_value",
- name: "WIDTH",
- check: "Number",
+ type: 'input_value',
+ name: 'WIDTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "HEIGHT",
- check: "Number",
+ type: 'input_value',
+ name: 'HEIGHT',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
inputsInline: true,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("create_plane"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('create_plane'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
// Set up the change handler.
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
// Add the mutator with toggle behaviour.
addDoMutatorWithToggleBehavior(this);
},
};
- Blockly.Blocks["control_particle_system"] = {
+ Blockly.Blocks['control_particle_system'] = {
init: function () {
this.jsonInit({
- type: "particle_system_control",
- message0: translate("control_particle_system"),
+ type: 'particle_system_control',
+ message0: translate('control_particle_system'),
args0: [
{
- type: "field_variable",
- name: "SYSTEM_NAME",
+ type: 'field_variable',
+ name: 'SYSTEM_NAME',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "ACTION",
+ type: 'field_dropdown',
+ name: 'ACTION',
options: [
- getDropdownOption("start"),
- getDropdownOption("stop"),
- getDropdownOption("reset"),
+ getDropdownOption('start'),
+ getDropdownOption('stop'),
+ getDropdownOption('reset'),
],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("control_particle_system"),
- helpUrl: "",
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('control_particle_system'),
+ helpUrl: '',
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
}
diff --git a/blocks/sound.js b/blocks/sound.js
index f67d3bdbf..31265fc80 100644
--- a/blocks/sound.js
+++ b/blocks/sound.js
@@ -1,23 +1,14 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
nextVariableIndexes,
handleBlockCreateEvent,
getHelpUrlFor,
registerBlockHandler,
-} from "./blocks.js";
-import {
- audioNames,
- themeNames,
- getSoundDisplayName,
- getThemeDisplayName,
-} from "../config.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
-import { announceToScreenReader } from "../main/input.js";
+} from './blocks.js';
+import { audioNames, themeNames, getSoundDisplayName, getThemeDisplayName } from '../config.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
+import { announceToScreenReader } from '../main/input.js';
// ---------------------------------------------------------------------------
// Custom multi-line text field for ABC notation input
@@ -25,60 +16,59 @@ import { announceToScreenReader } from "../main/input.js";
class FieldAbcInput extends Blockly.FieldTextInput {
showEditor_() {
- const current = this.getValue() || "";
+ const current = this.getValue() || '';
// Build modal entirely with DOM APIs โ no innerHTML, no user content in
// attribute values โ so there is no XSS surface.
- const overlay = document.createElement("div");
+ const overlay = document.createElement('div');
overlay.style.cssText =
- "position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9999;" +
- "display:flex;align-items:center;justify-content:center";
+ 'position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9999;' +
+ 'display:flex;align-items:center;justify-content:center';
- const dialog = document.createElement("div");
- dialog.setAttribute("role", "dialog");
- dialog.setAttribute("aria-modal", "true");
- dialog.setAttribute("aria-labelledby", "abc-dlg-title");
+ const dialog = document.createElement('div');
+ dialog.setAttribute('role', 'dialog');
+ dialog.setAttribute('aria-modal', 'true');
+ dialog.setAttribute('aria-labelledby', 'abc-dlg-title');
dialog.style.cssText =
- "background:#fff;border-radius:8px;padding:20px;display:flex;" +
- "flex-direction:column;gap:12px;width:520px;max-width:90vw;" +
- "box-shadow:0 8px 32px rgba(0,0,0,.3)";
+ 'background:#fff;border-radius:8px;padding:20px;display:flex;' +
+ 'flex-direction:column;gap:12px;width:520px;max-width:90vw;' +
+ 'box-shadow:0 8px 32px rgba(0,0,0,.3)';
- const titleEl = document.createElement("h2");
- titleEl.id = "abc-dlg-title";
- titleEl.textContent = "ABC notation";
- titleEl.style.cssText = "margin:0;font:bold 15px/1 sans-serif;color:#333";
+ const titleEl = document.createElement('h2');
+ titleEl.id = 'abc-dlg-title';
+ titleEl.textContent = 'ABC notation';
+ titleEl.style.cssText = 'margin:0;font:bold 15px/1 sans-serif;color:#333';
- const hint = document.createElement("p");
- hint.textContent =
- "Paste or type ABC notation. Ctrl+Enter to import, Escape to cancel.";
- hint.style.cssText = "margin:0;font:12px/1.4 sans-serif;color:#666";
+ const hint = document.createElement('p');
+ hint.textContent = 'Paste or type ABC notation. Ctrl+Enter to import, Escape to cancel.';
+ hint.style.cssText = 'margin:0;font:12px/1.4 sans-serif;color:#666';
- const textarea = document.createElement("textarea");
+ const textarea = document.createElement('textarea');
textarea.rows = 14;
textarea.spellcheck = false;
- textarea.setAttribute("aria-label", "ABC notation");
+ textarea.setAttribute('aria-label', 'ABC notation');
textarea.style.cssText =
- "width:100%;box-sizing:border-box;font:13px/1.5 monospace;" +
- "resize:vertical;border:1px solid #ccc;border-radius:4px;padding:8px";
+ 'width:100%;box-sizing:border-box;font:13px/1.5 monospace;' +
+ 'resize:vertical;border:1px solid #ccc;border-radius:4px;padding:8px';
// Set via .value (textarea property), never via innerHTML
textarea.value = current;
- const buttons = document.createElement("div");
- buttons.style.cssText = "display:flex;justify-content:flex-end;gap:8px";
+ const buttons = document.createElement('div');
+ buttons.style.cssText = 'display:flex;justify-content:flex-end;gap:8px';
- const cancelBtn = document.createElement("button");
- cancelBtn.type = "button";
- cancelBtn.textContent = "Cancel";
+ const cancelBtn = document.createElement('button');
+ cancelBtn.type = 'button';
+ cancelBtn.textContent = 'Cancel';
cancelBtn.style.cssText =
- "padding:7px 18px;border:1px solid #ccc;border-radius:4px;" +
- "cursor:pointer;background:#f5f5f5;font-size:13px";
+ 'padding:7px 18px;border:1px solid #ccc;border-radius:4px;' +
+ 'cursor:pointer;background:#f5f5f5;font-size:13px';
- const importBtn = document.createElement("button");
- importBtn.type = "button";
- importBtn.textContent = "Import tune";
+ const importBtn = document.createElement('button');
+ importBtn.type = 'button';
+ importBtn.textContent = 'Import tune';
importBtn.style.cssText =
- "padding:7px 18px;border:none;border-radius:4px;cursor:pointer;" +
- "background:#4a90d9;color:#fff;font-size:13px";
+ 'padding:7px 18px;border:none;border-radius:4px;cursor:pointer;' +
+ 'background:#4a90d9;color:#fff;font-size:13px';
buttons.append(cancelBtn, importBtn);
dialog.append(titleEl, hint, textarea, buttons);
@@ -86,7 +76,7 @@ class FieldAbcInput extends Blockly.FieldTextInput {
const close = () => {
overlay.remove();
- document.removeEventListener("keydown", onKeyDown);
+ document.removeEventListener('keydown', onKeyDown);
};
const confirm = () => {
@@ -95,23 +85,23 @@ class FieldAbcInput extends Blockly.FieldTextInput {
close();
};
- cancelBtn.addEventListener("click", close);
- importBtn.addEventListener("click", confirm);
+ cancelBtn.addEventListener('click', close);
+ importBtn.addEventListener('click', confirm);
// Clicking the dimmed backdrop dismisses without importing
- overlay.addEventListener("click", (e) => {
+ overlay.addEventListener('click', (e) => {
if (e.target === overlay) close();
});
const onKeyDown = (e) => {
- if (e.key === "Escape") {
+ if (e.key === 'Escape') {
e.preventDefault();
close();
- } else if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
+ } else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
confirm();
}
};
- document.addEventListener("keydown", onKeyDown);
+ document.addEventListener('keydown', onKeyDown);
document.body.append(overlay);
// Defer focus so the overlay is in the DOM before the browser moves focus
@@ -120,15 +110,15 @@ class FieldAbcInput extends Blockly.FieldTextInput {
getText_() {
const val = this.getValue();
- if (!val) return "click to enter ABCโฆ";
+ if (!val) return 'click to enter ABCโฆ';
const titleLine = String(val)
- .split("\n")
- .find((l) => l.trim().startsWith("T:"));
- return titleLine ? titleLine.trim().slice(2).trim() : "ABC";
+ .split('\n')
+ .find((l) => l.trim().startsWith('T:'));
+ return titleLine ? titleLine.trim().slice(2).trim() : 'ABC';
}
getAriaTypeName() {
- return "ABC notation";
+ return 'ABC notation';
}
}
@@ -143,25 +133,37 @@ const ABC_MAX_INPUT_LENGTH = 20000;
const ABC_BASE_MIDI = { C: 60, D: 62, E: 64, F: 65, G: 67, A: 69, B: 71 };
const ABC_KEY_ACCIDENTALS = {
- C: {}, Am: {},
- G: { F: 1 }, Em: { F: 1 },
- D: { F: 1, C: 1 }, Bm: { F: 1, C: 1 },
- A: { F: 1, C: 1, G: 1 }, "F#m": { F: 1, C: 1, G: 1 },
- E: { F: 1, C: 1, G: 1, D: 1 }, "C#m": { F: 1, C: 1, G: 1, D: 1 },
- B: { F: 1, C: 1, G: 1, D: 1, A: 1 }, "G#m": { F: 1, C: 1, G: 1, D: 1, A: 1 },
- "F#": { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1 }, "D#m": { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1 },
- "C#": { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1, B: 1 },
- F: { B: -1 }, Dm: { B: -1 },
- Bb: { B: -1, E: -1 }, Gm: { B: -1, E: -1 },
- Eb: { B: -1, E: -1, A: -1 }, Cm: { B: -1, E: -1, A: -1 },
- Ab: { B: -1, E: -1, A: -1, D: -1 }, Fm: { B: -1, E: -1, A: -1, D: -1 },
- Db: { B: -1, E: -1, A: -1, D: -1, G: -1 }, Bbm: { B: -1, E: -1, A: -1, D: -1, G: -1 },
+ C: {},
+ Am: {},
+ G: { F: 1 },
+ Em: { F: 1 },
+ D: { F: 1, C: 1 },
+ Bm: { F: 1, C: 1 },
+ A: { F: 1, C: 1, G: 1 },
+ 'F#m': { F: 1, C: 1, G: 1 },
+ E: { F: 1, C: 1, G: 1, D: 1 },
+ 'C#m': { F: 1, C: 1, G: 1, D: 1 },
+ B: { F: 1, C: 1, G: 1, D: 1, A: 1 },
+ 'G#m': { F: 1, C: 1, G: 1, D: 1, A: 1 },
+ 'F#': { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1 },
+ 'D#m': { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1 },
+ 'C#': { F: 1, C: 1, G: 1, D: 1, A: 1, E: 1, B: 1 },
+ F: { B: -1 },
+ Dm: { B: -1 },
+ Bb: { B: -1, E: -1 },
+ Gm: { B: -1, E: -1 },
+ Eb: { B: -1, E: -1, A: -1 },
+ Cm: { B: -1, E: -1, A: -1 },
+ Ab: { B: -1, E: -1, A: -1, D: -1 },
+ Fm: { B: -1, E: -1, A: -1, D: -1 },
+ Db: { B: -1, E: -1, A: -1, D: -1, G: -1 },
+ Bbm: { B: -1, E: -1, A: -1, D: -1, G: -1 },
Gb: { B: -1, E: -1, A: -1, D: -1, G: -1, C: -1 },
Cb: { B: -1, E: -1, A: -1, D: -1, G: -1, C: -1, F: -1 },
};
function abcParseFraction(str) {
- const parts = str.split("/");
+ const parts = str.split('/');
if (parts.length === 2) return parseInt(parts[0]) / parseInt(parts[1]);
return parseFloat(str) || 1;
}
@@ -171,24 +173,32 @@ function abcParseFraction(str) {
// bar. A trailing - marks a syllable that joins the next one into one word.
function abcParseLyrics(raw) {
const events = [];
- let syllable = "";
+ let syllable = '';
const flush = (joinNext) => {
if (!syllable) return;
- events.push({ t: "syl", text: syllable, joinNext });
- syllable = "";
+ events.push({ t: 'syl', text: syllable, joinNext });
+ syllable = '';
};
for (let i = 0; i < raw.length; i++) {
const c = raw[i];
- if (c === "\\" && raw[i + 1] === "-") { syllable += "-"; i++; }
- else if (/\s/.test(c)) flush(false);
- else if (c === "-") flush(true);
- else if (c === "~") syllable += " ";
- else if (c === "*") { flush(false); events.push({ t: "skip" }); }
- else if (c === "_") { flush(false); events.push({ t: "hold" }); }
- else if (c === "|") { flush(false); events.push({ t: "bar" }); }
- else syllable += c;
+ if (c === '\\' && raw[i + 1] === '-') {
+ syllable += '-';
+ i++;
+ } else if (/\s/.test(c)) flush(false);
+ else if (c === '-') flush(true);
+ else if (c === '~') syllable += ' ';
+ else if (c === '*') {
+ flush(false);
+ events.push({ t: 'skip' });
+ } else if (c === '_') {
+ flush(false);
+ events.push({ t: 'hold' });
+ } else if (c === '|') {
+ flush(false);
+ events.push({ t: 'bar' });
+ } else syllable += c;
}
flush(false);
@@ -201,15 +211,15 @@ function abcAssignLyrics(lineTokens, events) {
let idx = 0;
for (const ev of events) {
- if (ev.t === "bar") {
- while (idx < lineTokens.length && lineTokens[idx].t !== "bar") idx++;
+ if (ev.t === 'bar') {
+ while (idx < lineTokens.length && lineTokens[idx].t !== 'bar') idx++;
idx++;
continue;
}
- while (idx < lineTokens.length && lineTokens[idx].t !== "note") idx++;
+ while (idx < lineTokens.length && lineTokens[idx].t !== 'note') idx++;
const note = lineTokens[idx];
if (!note) return;
- if (ev.t === "syl") {
+ if (ev.t === 'syl') {
note.syl = ev.text;
note.joinNext = ev.joinNext;
}
@@ -218,9 +228,9 @@ function abcAssignLyrics(lineTokens, events) {
}
function parseAbc(abcText) {
- const lines = abcText.split("\n");
- let title = "";
- let composer = "";
+ const lines = abcText.split('\n');
+ let title = '';
+ let composer = '';
let lFraction = 0.125;
let bpm = 120;
let keyAcc = {};
@@ -230,31 +240,29 @@ function parseAbc(abcText) {
for (const line of lines) {
const t = line.trim();
- if (!t || t.startsWith("%")) continue;
+ if (!t || t.startsWith('%')) continue;
const hm = t.match(/^[a-z]*([A-Za-z]):(.*)/);
if (hm) {
const [, key, value] = hm;
const v = value.trim();
- if (key === "T") title = v;
- else if (key === "C") composer = v;
- else if (key === "L") lFraction = abcParseFraction(v);
- else if (key === "w") {
+ if (key === 'T') title = v;
+ else if (key === 'C') composer = v;
+ else if (key === 'L') lFraction = abcParseFraction(v);
+ else if (key === 'w') {
// Aligned lyrics belong to the music line just above them; each
// further w: line under the same music line is another verse.
const target = musicLines[musicLines.length - 1];
const events = abcParseLyrics(v);
if (target && events.length > 0) target.verses.push(events);
- } else if (key === "W") {
+ } else if (key === 'W') {
const events = abcParseLyrics(v);
if (events.length > 0) unalignedLyrics.push(events);
- } else if (key === "Q") {
+ } else if (key === 'Q') {
const qm = v.match(/(\d+)$/);
if (qm) bpm = parseInt(qm[1]);
- } else if (key === "K") {
- const keyName = v.split(" ")[0];
- keyAcc = Object.hasOwn(ABC_KEY_ACCIDENTALS, keyName)
- ? ABC_KEY_ACCIDENTALS[keyName]
- : {};
+ } else if (key === 'K') {
+ const keyName = v.split(' ')[0];
+ keyAcc = Object.hasOwn(ABC_KEY_ACCIDENTALS, keyName) ? ABC_KEY_ACCIDENTALS[keyName] : {};
}
} else {
// [V:2] at the head of a line switches voice, and holds until the next
@@ -267,9 +275,7 @@ function parseAbc(abcText) {
const melodyVoice = musicLines.find((l) => l.voice !== null)?.voice ?? null;
const melody =
- melodyVoice === null
- ? musicLines
- : musicLines.filter((l) => l.voice === melodyVoice);
+ melodyVoice === null ? musicLines : musicLines.filter((l) => l.voice === melodyVoice);
// W: lines have no alignment of their own, so fall back to one per music
// line in order โ only when the tune used no aligned lyrics at all.
@@ -284,7 +290,7 @@ function parseAbc(abcText) {
const verseCount = Math.max(1, ...melody.map((l) => l.verses.length));
// One pass per verse: same notes, different words on them.
const verses = Array.from({ length: verseCount }, (_, verseIdx) =>
- abcParseSections(melody, noteBase, keyAcc, verseIdx),
+ abcParseSections(melody, noteBase, keyAcc, verseIdx)
);
return { title: fullTitle, bpm, verses };
}
@@ -297,54 +303,81 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
const verses = musicLines[lineIdx].verses;
// A verse whose line is all skips (w:*****) is the chorus: the words are
// written out once, under the first verse, and sung again each time.
- const sung = (events) => events?.some((e) => e.t === "syl");
+ const sung = (events) => events?.some((e) => e.t === 'syl');
const events = sung(verses[verseIdx]) ? verses[verseIdx] : verses[0];
- const text = musicLines[lineIdx].text.replace(/%.*/, "");
+ const text = musicLines[lineIdx].text.replace(/%.*/, '');
const lineStart = tokens.length;
let i = 0;
while (i < text.length) {
const c = text[i];
- if (/\s/.test(c)) { i++; continue; }
+ if (/\s/.test(c)) {
+ i++;
+ continue;
+ }
// Chord symbols ("G") and decorations (!f!) hold letters that would
// otherwise be read as notes, so skip them whole.
- if (c === '"' || c === "!" || c === "+") {
+ if (c === '"' || c === '!' || c === '+') {
const end = text.indexOf(c, i + 1);
i = end === -1 ? text.length : end + 1;
continue;
}
- if (c === "|") {
+ if (c === '|') {
const n = text[i + 1];
- if (n === ":") { tokens.push({ t: "rs" }); i += 2; }
- else if (n === "]") { tokens.push({ t: "bar" }); i += 2; }
- else if (n === "1") { tokens.push({ t: "v1" }); i += 2; }
- else if (n === "2") { tokens.push({ t: "v2" }); i += 2; }
- else if (n === "|") { tokens.push({ t: "bar" }); i += 2; }
- else { tokens.push({ t: "bar" }); i++; }
- } else if (c === ":") {
- if (text[i + 1] === "|") {
- tokens.push({ t: "re" });
+ if (n === ':') {
+ tokens.push({ t: 'rs' });
+ i += 2;
+ } else if (n === ']') {
+ tokens.push({ t: 'bar' });
+ i += 2;
+ } else if (n === '1') {
+ tokens.push({ t: 'v1' });
+ i += 2;
+ } else if (n === '2') {
+ tokens.push({ t: 'v2' });
+ i += 2;
+ } else if (n === '|') {
+ tokens.push({ t: 'bar' });
+ i += 2;
+ } else {
+ tokens.push({ t: 'bar' });
+ i++;
+ }
+ } else if (c === ':') {
+ if (text[i + 1] === '|') {
+ tokens.push({ t: 're' });
i += 2;
// Look ahead for volta markers (e.g. :|[1 or :|1)
while (i < text.length && /\s/.test(text[i])) i++;
- if (text[i] === "[" && (text[i + 1] === "1" || text[i + 1] === "2")) {
- tokens.push({ t: text[i + 1] === "1" ? "v1" : "v2" }); i += 2;
- } else if (text[i] === "1") {
- tokens.push({ t: "v1" }); i++;
- } else if (text[i] === "2") {
- tokens.push({ t: "v2" }); i++;
+ if (text[i] === '[' && (text[i + 1] === '1' || text[i + 1] === '2')) {
+ tokens.push({ t: text[i + 1] === '1' ? 'v1' : 'v2' });
+ i += 2;
+ } else if (text[i] === '1') {
+ tokens.push({ t: 'v1' });
+ i++;
+ } else if (text[i] === '2') {
+ tokens.push({ t: 'v2' });
+ i++;
}
- } else { i++; }
- } else if (c === "[") {
- if (text[i + 1] === "1") { tokens.push({ t: "v1" }); i += 2; }
- else if (text[i + 1] === "2") { tokens.push({ t: "v2" }); i += 2; }
- else if (/^\[[A-Za-z]:/.test(text.slice(i))) {
+ } else {
+ i++;
+ }
+ } else if (c === '[') {
+ if (text[i + 1] === '1') {
+ tokens.push({ t: 'v1' });
+ i += 2;
+ } else if (text[i + 1] === '2') {
+ tokens.push({ t: 'v2' });
+ i += 2;
+ } else if (/^\[[A-Za-z]:/.test(text.slice(i))) {
// Inline field such as [V:1] โ not music
- const end = text.indexOf("]", i);
+ const end = text.indexOf(']', i);
i = end === -1 ? text.length : end + 1;
- } else { i++; }
+ } else {
+ i++;
+ }
} else {
const sub = text.slice(i);
const m = sub.match(/^([_^=]*)([A-Ga-gz])([',]*)(\d*)(\/\d*)?/);
@@ -352,32 +385,34 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
const [full, accStr, letter, octStr, numStr, denomStr] = m;
const num = numStr ? parseInt(numStr) : 1;
let denom = 1;
- if (denomStr) denom = denomStr === "/" ? 2 : parseInt(denomStr.slice(1)) || 2;
+ if (denomStr) denom = denomStr === '/' ? 2 : parseInt(denomStr.slice(1)) || 2;
const beats = noteBase * (num / denom);
const lo = letter.toLowerCase();
- if (lo === "z" || lo === "x") {
- tokens.push({ t: "rest", beats });
+ if (lo === 'z' || lo === 'x') {
+ tokens.push({ t: 'rest', beats });
} else {
const base = letter.toUpperCase();
let midi = ABC_BASE_MIDI[base] + (letter !== letter.toUpperCase() ? 12 : 0);
for (const oc of octStr) {
if (oc === "'") midi += 12;
- else if (oc === ",") midi -= 12;
+ else if (oc === ',') midi -= 12;
}
let acc = 0;
let hasAcc = false;
if (accStr) {
hasAcc = true;
for (const a of accStr) {
- if (a === "^") acc++;
- else if (a === "_") acc--;
+ if (a === '^') acc++;
+ else if (a === '_') acc--;
}
}
- tokens.push({ t: "note", base, midi, beats, hasAcc, acc, lyricId: lineIdx });
+ tokens.push({ t: 'note', base, midi, beats, hasAcc, acc, lyricId: lineIdx });
}
i += full.length;
- } else { i++; }
+ } else {
+ i++;
+ }
}
}
@@ -389,7 +424,7 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
let currentBars = [];
let currentBar = [];
let barAcc = {};
- let state = "plain";
+ let state = 'plain';
let repeatBars = null;
let volta1Bars = null;
@@ -423,15 +458,15 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
for (let tokIdx = 0; tokIdx < tokens.length; tokIdx++) {
const tok = tokens[tokIdx];
switch (tok.t) {
- case "bar":
+ case 'bar':
flushBar();
break;
- case "rs":
+ case 'rs':
flushBar();
- if (state === "volta" && repeatBars !== null) {
+ if (state === 'volta' && repeatBars !== null) {
// Finalize the preceding volta before starting a new repeat section
sections.push({
- type: "volta",
+ type: 'volta',
commonBars: repeatBars,
firstBars: volta1Bars || [],
secondBars: [...currentBars],
@@ -439,29 +474,29 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
repeatBars = null;
volta1Bars = null;
} else if (currentBars.length > 0) {
- sections.push({ type: "plain", bars: [...currentBars] });
+ sections.push({ type: 'plain', bars: [...currentBars] });
}
currentBars = [];
- state = "repeat";
+ state = 'repeat';
break;
- case "re":
+ case 're':
flushBar();
- if (state === "repeat" || state === "plain") {
+ if (state === 'repeat' || state === 'plain') {
const nextTok = tokens[tokIdx + 1];
- if (nextTok?.t === "v1" || nextTok?.t === "v2") {
+ if (nextTok?.t === 'v1' || nextTok?.t === 'v2') {
// :| immediately followed by [1 or [2 โ the body so far is the common part
repeatBars = [...currentBars];
volta1Bars = [];
currentBars = [];
- state = "volta";
+ state = 'volta';
} else if (currentBars.length > 0) {
- sections.push({ type: "repeat", bars: [...currentBars] });
+ sections.push({ type: 'repeat', bars: [...currentBars] });
currentBars = [];
- state = "plain";
+ state = 'plain';
}
- } else if (state === "volta") {
+ } else if (state === 'volta') {
const nextTok = tokens[tokIdx + 1];
- if (nextTok?.t === "v1" || nextTok?.t === "v2") {
+ if (nextTok?.t === 'v1' || nextTok?.t === 'v2') {
// Transitioning between endings โ accumulate into firstBars
volta1Bars = [...(volta1Bars || []), ...currentBars];
currentBars = [];
@@ -469,99 +504,100 @@ function abcParseSections(musicLines, noteBase, keyAcc, verseIdx = 0) {
// Closing :| of last ending โ leave currentBars for secondBars
}
break;
- case "v1":
+ case 'v1':
flushBar();
- if (state !== "volta") {
+ if (state !== 'volta') {
// v1 appearing without a preceding :| โ current bars are the common part
repeatBars = [...currentBars];
volta1Bars = [];
currentBars = [];
- state = "volta";
+ state = 'volta';
}
// already in volta (set by :| lookahead above) โ leave repeatBars alone
break;
- case "v2":
+ case 'v2':
flushBar();
- if (state !== "volta") {
+ if (state !== 'volta') {
volta1Bars = [...currentBars];
repeatBars = repeatBars || [];
- state = "volta";
+ state = 'volta';
}
currentBars = [];
break;
- case "note":
+ case 'note':
addNote(tok);
break;
- case "rest":
+ case 'rest':
currentBar.push({ midi: null, beats: tok.beats });
break;
}
}
flushBar();
- if (state === "volta" && repeatBars !== null) {
+ if (state === 'volta' && repeatBars !== null) {
sections.push({
- type: "volta",
+ type: 'volta',
commonBars: repeatBars,
firstBars: volta1Bars || [],
secondBars: [...currentBars],
});
} else if (currentBars.length > 0) {
- sections.push({ type: "plain", bars: currentBars });
+ sections.push({ type: 'plain', bars: currentBars });
}
return sections;
}
-
function buildPlayBlock(ws, bars, meshName) {
const validBars = (bars || []).filter((b) => b && b.length > 0);
if (validBars.length === 0) return null;
- const outerList = ws.newBlock("lists_create_with");
+ const outerList = ws.newBlock('lists_create_with');
outerList.loadExtraState({ itemCount: validBars.length });
outerList.initSvg();
outerList.render();
validBars.forEach((bar, barIdx) => {
- const barBlock = ws.newBlock("lists_create_with");
+ const barBlock = ws.newBlock('lists_create_with');
barBlock.loadExtraState({ itemCount: bar.length });
barBlock.setInputsInline(true);
barBlock.initSvg();
barBlock.render();
bar.forEach((note, noteIdx) => {
- const noteBlock = ws.newBlock("note");
+ const noteBlock = ws.newBlock('note');
noteBlock.initSvg();
noteBlock.render();
if (note.midi === null) {
- const restBlock = ws.newBlock("rest");
+ const restBlock = ws.newBlock('rest');
restBlock.initSvg();
restBlock.render();
- noteBlock.getInput("PITCH").connection.connect(restBlock.outputConnection);
+ noteBlock.getInput('PITCH').connection.connect(restBlock.outputConnection);
} else {
- noteBlock.getInput("PITCH").connection.connect(makeNumBlock(ws, note.midi).outputConnection);
+ noteBlock
+ .getInput('PITCH')
+ .connection.connect(makeNumBlock(ws, note.midi).outputConnection);
}
const dur = Math.round(note.beats * 1000) / 1000;
- noteBlock.getInput("DURATION").connection.connect(makeNumBlock(ws, dur).outputConnection);
+ noteBlock.getInput('DURATION').connection.connect(makeNumBlock(ws, dur).outputConnection);
barBlock.getInput(`ADD${noteIdx}`).connection.connect(noteBlock.outputConnection);
});
outerList.getInput(`ADD${barIdx}`).connection.connect(barBlock.outputConnection);
});
- const playBlock = ws.newBlock("play_tune_notes");
+ const playBlock = ws.newBlock('play_tune_notes');
playBlock.initSvg();
playBlock.render();
- const instBlock = ws.newBlock("instrument");
- instBlock.setFieldValue("default", "INSTRUMENT_TYPE");
+ const instBlock = ws.newBlock('instrument');
+ instBlock.setFieldValue('default', 'INSTRUMENT_TYPE');
instBlock.initSvg();
instBlock.render();
- if (meshName) playBlock.setFieldValue(meshName, "MESH_NAME");
- playBlock.getInput("INSTRUMENT").connection.connect(instBlock.outputConnection);
- playBlock.getInput("NOTES").connection.connect(outerList.outputConnection);
+ if (meshName) playBlock.setFieldValue(meshName, 'MESH_NAME');
+ playBlock.getInput('INSTRUMENT').connection.connect(instBlock.outputConnection);
+ playBlock.getInput('NOTES').connection.connect(outerList.outputConnection);
return playBlock;
}
@@ -572,8 +608,8 @@ function buildPlayBlock(ws, bars, meshName) {
// value inputs connect to blocks with outputConnection, not previousConnection.
function propagateMesh(block, meshName) {
while (block) {
- if (block.getField("MESH_NAME")) {
- block.setFieldValue(meshName, "MESH_NAME");
+ if (block.getField('MESH_NAME')) {
+ block.setFieldValue(meshName, 'MESH_NAME');
}
for (const input of block.inputList) {
const child = input.connection?.targetBlock();
@@ -588,8 +624,8 @@ function propagateInstrument(block, instrumentState) {
delete state.id;
delete state.shadow;
while (block) {
- if (block.type === "play_tune_notes") {
- const instrConn = block.getInput("INSTRUMENT")?.connection;
+ if (block.type === 'play_tune_notes') {
+ const instrConn = block.getInput('INSTRUMENT')?.connection;
if (instrConn) {
const existing = instrConn.targetBlock();
if (existing && !existing.isShadow()) existing.dispose(false);
@@ -606,19 +642,19 @@ function propagateInstrument(block, instrumentState) {
}
function makeTextBlock(ws, value) {
- const b = ws.newBlock("text");
- b.setFieldValue(value, "TEXT");
+ const b = ws.newBlock('text');
+ b.setFieldValue(value, 'TEXT');
b.initSvg();
b.render();
return b;
}
function buildSubtitleBlock(ws, words, seconds) {
- const b = ws.newBlock("subtitle");
+ const b = ws.newBlock('subtitle');
b.initSvg();
b.render();
- b.getInput("TEXT").connection.connect(makeTextBlock(ws, words).outputConnection);
- b.getInput("DURATION").connection.connect(makeNumBlock(ws, seconds).outputConnection);
+ b.getInput('TEXT').connection.connect(makeTextBlock(ws, words).outputConnection);
+ b.getInput('DURATION').connection.connect(makeNumBlock(ws, seconds).outputConnection);
return b;
}
@@ -630,12 +666,12 @@ const TAIL_CAPTION_WORDS = 3;
// Join the syllables sung across a run of bars back into words: a syllable
// marked joinNext runs straight into the one after it (mid-word hyphen).
function barsCaption(bars) {
- let text = "";
+ let text = '';
let join = false;
for (const bar of bars) {
for (const note of bar) {
if (!note.syl) continue;
- if (text && !join) text += " ";
+ if (text && !join) text += ' ';
text += note.syl;
join = note.joinNext;
}
@@ -645,7 +681,7 @@ function barsCaption(bars) {
function captionWordCount(bars) {
const caption = barsCaption(bars);
- return caption ? caption.split(" ").length : 0;
+ return caption ? caption.split(' ').length : 0;
}
// Build the statement chain for a run of bars, split into subtitle + play
@@ -695,7 +731,7 @@ function buildPlayChain(ws, bars, meshName, speed) {
if (caption && speed > 0) {
const beats = group.bars.reduce(
(total, bar) => total + bar.reduce((sum, note) => sum + note.beats, 0),
- 0,
+ 0
);
const seconds = Math.round((beats / speed) * 1000) / 1000;
head = buildSubtitleBlock(ws, caption, seconds);
@@ -711,8 +747,8 @@ function buildPlayChain(ws, bars, meshName, speed) {
}
function makeNumBlock(ws, value) {
- const b = ws.newBlock("math_number");
- b.setFieldValue(String(value), "NUM");
+ const b = ws.newBlock('math_number');
+ b.setFieldValue(String(value), 'NUM');
b.initSvg();
b.render();
return b;
@@ -722,84 +758,84 @@ function makeNumBlock(ws, value) {
// carrying that verse's words, so the tune body is laid out once per verse.
function abcBuildDoBlocks(ws, doInput, verses, bpm, meshName) {
const hasBars = verses[0]?.some((s) =>
- s.type === "plain" ? s.bars?.length > 0 :
- s.type === "repeat" ? s.bars?.length > 0 :
- (s.commonBars?.length > 0 || s.firstBars?.length > 0 || s.secondBars?.length > 0)
+ s.type === 'plain'
+ ? s.bars?.length > 0
+ : s.type === 'repeat'
+ ? s.bars?.length > 0
+ : s.commonBars?.length > 0 || s.firstBars?.length > 0 || s.secondBars?.length > 0
);
if (!hasBars) {
- console.warn("[play_tune] no bars found in ABC");
+ console.warn('[play_tune] no bars found in ABC');
return;
}
const speed = Math.round((bpm / 60) * 1000) / 1000;
- const speedBlock = ws.newBlock("set_music_speed");
+ const speedBlock = ws.newBlock('set_music_speed');
speedBlock.initSvg();
speedBlock.render();
- speedBlock.getInput("SPEED").connection.connect(makeNumBlock(ws, speed).outputConnection);
+ speedBlock.getInput('SPEED').connection.connect(makeNumBlock(ws, speed).outputConnection);
doInput.connection.connect(speedBlock.previousConnection);
let prevNext = speedBlock.nextConnection;
for (const section of verses.flat()) {
- if (section.type === "plain") {
+ if (section.type === 'plain') {
const chain = buildPlayChain(ws, section.bars, meshName, speed);
if (!chain) continue;
prevNext.connect(chain.first.previousConnection);
prevNext = chain.last.nextConnection;
-
- } else if (section.type === "repeat") {
- const repeatBlock = ws.newBlock("controls_repeat_ext");
+ } else if (section.type === 'repeat') {
+ const repeatBlock = ws.newBlock('controls_repeat_ext');
repeatBlock.initSvg();
repeatBlock.render();
- repeatBlock.getInput("TIMES").connection.connect(makeNumBlock(ws, 2).outputConnection);
+ repeatBlock.getInput('TIMES').connection.connect(makeNumBlock(ws, 2).outputConnection);
const chain = buildPlayChain(ws, section.bars, meshName, speed);
if (chain) {
- repeatBlock.getInput("DO").connection.connect(chain.first.previousConnection);
+ repeatBlock.getInput('DO').connection.connect(chain.first.previousConnection);
}
prevNext.connect(repeatBlock.previousConnection);
prevNext = repeatBlock.nextConnection;
-
- } else if (section.type === "volta") {
+ } else if (section.type === 'volta') {
const varModel =
- ws.getVariableMap().getVariable("_repeat", "") || ws.createVariable("_repeat");
+ ws.getVariableMap().getVariable('_repeat', '') || ws.createVariable('_repeat');
- const forBlock = ws.newBlock("controls_for");
+ const forBlock = ws.newBlock('controls_for');
forBlock.initSvg();
forBlock.render();
- forBlock.getField("VAR").setValue(varModel.getId());
- forBlock.getInput("FROM").connection.connect(makeNumBlock(ws, 1).outputConnection);
- forBlock.getInput("TO").connection.connect(makeNumBlock(ws, 2).outputConnection);
- forBlock.getInput("BY").connection.connect(makeNumBlock(ws, 1).outputConnection);
+ forBlock.getField('VAR').setValue(varModel.getId());
+ forBlock.getInput('FROM').connection.connect(makeNumBlock(ws, 1).outputConnection);
+ forBlock.getInput('TO').connection.connect(makeNumBlock(ws, 2).outputConnection);
+ forBlock.getInput('BY').connection.connect(makeNumBlock(ws, 1).outputConnection);
- const ifBlock = ws.newBlock("controls_if");
+ const ifBlock = ws.newBlock('controls_if');
ifBlock.loadExtraState({ hasElse: true });
ifBlock.initSvg();
ifBlock.render();
- const compareBlock = ws.newBlock("logic_compare");
- compareBlock.setFieldValue("EQ", "OP");
+ const compareBlock = ws.newBlock('logic_compare');
+ compareBlock.setFieldValue('EQ', 'OP');
compareBlock.initSvg();
compareBlock.render();
- const varGetBlock = ws.newBlock("variables_get");
- varGetBlock.getField("VAR").setValue(varModel.getId());
+ const varGetBlock = ws.newBlock('variables_get');
+ varGetBlock.getField('VAR').setValue(varModel.getId());
varGetBlock.initSvg();
varGetBlock.render();
- compareBlock.getInput("A").connection.connect(varGetBlock.outputConnection);
- compareBlock.getInput("B").connection.connect(makeNumBlock(ws, 1).outputConnection);
- ifBlock.getInput("IF0").connection.connect(compareBlock.outputConnection);
+ compareBlock.getInput('A').connection.connect(varGetBlock.outputConnection);
+ compareBlock.getInput('B').connection.connect(makeNumBlock(ws, 1).outputConnection);
+ ifBlock.getInput('IF0').connection.connect(compareBlock.outputConnection);
const firstChain = buildPlayChain(ws, section.firstBars, meshName, speed);
if (firstChain) {
- ifBlock.getInput("DO0").connection.connect(firstChain.first.previousConnection);
+ ifBlock.getInput('DO0').connection.connect(firstChain.first.previousConnection);
}
const secondChain = buildPlayChain(ws, section.secondBars, meshName, speed);
if (secondChain) {
- ifBlock.getInput("ELSE").connection.connect(secondChain.first.previousConnection);
+ ifBlock.getInput('ELSE').connection.connect(secondChain.first.previousConnection);
}
const commonChain = buildPlayChain(ws, section.commonBars, meshName, speed);
@@ -807,7 +843,7 @@ function abcBuildDoBlocks(ws, doInput, verses, bpm, meshName) {
if (commonChain) {
commonChain.last.nextConnection.connect(ifBlock.previousConnection);
}
- forBlock.getInput("DO").connection.connect(doFirst.previousConnection);
+ forBlock.getInput('DO').connection.connect(doFirst.previousConnection);
prevNext.connect(forBlock.previousConnection);
prevNext = forBlock.nextConnection;
@@ -816,305 +852,289 @@ function abcBuildDoBlocks(ws, doInput, verses, bpm, meshName) {
}
export function defineSoundBlocks() {
- Blockly.Blocks["play_theme"] = {
+ Blockly.Blocks['play_theme'] = {
init: function () {
- const variableNamePrefix = "sound";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'sound';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "play_theme",
- message0: translate("play_theme"),
+ type: 'play_theme',
+ message0: translate('play_theme'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_dropdown",
- name: "THEME_NAME",
+ type: 'field_dropdown',
+ name: 'THEME_NAME',
options: function () {
return themeNames.map((name) => [getThemeDisplayName(name), name]);
},
},
{
- type: "input_dummy",
- name: "MESH_INPUT",
+ type: 'input_dummy',
+ name: 'MESH_INPUT',
},
{
- type: "input_value",
- name: "SPEED",
+ type: 'input_value',
+ name: 'SPEED',
value: 1,
min: 0.1,
max: 3,
precision: 0.1,
},
{
- type: "input_value",
- name: "VOLUME",
+ type: 'input_value',
+ name: 'VOLUME',
value: 1,
min: 0,
max: 1,
precision: 0.1,
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("ONCE"), getDropdownOption("LOOP")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('ONCE'), getDropdownOption('LOOP')],
},
{
- type: "field_dropdown",
- name: "ASYNC",
- options: [getDropdownOption("START"), getDropdownOption("AWAIT")],
+ type: 'field_dropdown',
+ name: 'ASYNC',
+ options: [getDropdownOption('START'), getDropdownOption('AWAIT')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("play_theme"),
- extensions: ["dynamic_mesh_dropdown"],
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('play_theme'),
+ extensions: ['dynamic_mesh_dropdown'],
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
registerBlockHandler(this, (changeEvent) => {
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
});
},
};
- Blockly.Blocks["play_sound"] = {
+ Blockly.Blocks['play_sound'] = {
init: function () {
- const variableNamePrefix = "sound";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'sound';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "play_sound",
- message0: translate("play_sound"),
+ type: 'play_sound',
+ message0: translate('play_sound'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "field_dropdown",
- name: "SOUND_NAME",
+ type: 'field_dropdown',
+ name: 'SOUND_NAME',
options: function () {
return audioNames.map((name) => [getSoundDisplayName(name), name]);
},
},
{
- type: "input_dummy",
- name: "MESH_INPUT", // Dummy input for the dropdown
+ type: 'input_dummy',
+ name: 'MESH_INPUT', // Dummy input for the dropdown
},
{
- type: "input_value",
- name: "SPEED",
+ type: 'input_value',
+ name: 'SPEED',
value: 1,
min: 0.1,
max: 3,
precision: 0.1,
},
{
- type: "input_value",
- name: "VOLUME",
+ type: 'input_value',
+ name: 'VOLUME',
value: 1,
min: 0,
max: 1,
precision: 0.1,
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("ONCE"), getDropdownOption("LOOP")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('ONCE'), getDropdownOption('LOOP')],
},
{
- type: "field_dropdown",
- name: "ASYNC",
- options: [getDropdownOption("START"), getDropdownOption("AWAIT")],
+ type: 'field_dropdown',
+ name: 'ASYNC',
+ options: [getDropdownOption('START'), getDropdownOption('AWAIT')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("play_sound"),
- extensions: ["dynamic_mesh_dropdown"], // Attach the extension
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('play_sound'),
+ extensions: ['dynamic_mesh_dropdown'], // Attach the extension
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
registerBlockHandler(this, (changeEvent) => {
- handleBlockCreateEvent(
- this,
- changeEvent,
- variableNamePrefix,
- nextVariableIndexes,
- );
+ handleBlockCreateEvent(this, changeEvent, variableNamePrefix, nextVariableIndexes);
});
},
};
- Blockly.Blocks["stop_all_sounds"] = {
+ Blockly.Blocks['stop_all_sounds'] = {
init: function () {
this.jsonInit({
- message0: translate("stop_all_sounds"),
+ message0: translate('stop_all_sounds'),
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("stop_all_sounds"),
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('stop_all_sounds'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["midi_note"] = {
+ Blockly.Blocks['midi_note'] = {
init: function () {
this.jsonInit({
- type: "midi_note",
- message0: translate("midi_note"),
+ type: 'midi_note',
+ message0: translate('midi_note'),
args0: [
{
- type: "input_value",
- name: "NOTE",
- check: "Number",
+ type: 'input_value',
+ name: 'NOTE',
+ check: 'Number',
},
],
inputsInline: true,
- output: "Number",
- colour: categoryColours["Sound"],
- tooltip: getTooltip("midi_note"),
+ output: 'Number',
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('midi_note'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["note"] = {
+ Blockly.Blocks['note'] = {
init: function () {
this.jsonInit({
- type: "note",
- message0: translate("note"),
+ type: 'note',
+ message0: translate('note'),
args0: [
{
- type: "input_value",
- name: "PITCH",
- check: "Number",
+ type: 'input_value',
+ name: 'PITCH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
],
inputsInline: true,
- output: "NoteEvent",
- colour: categoryColours["Sound"],
- tooltip: getTooltip("note"),
+ output: 'NoteEvent',
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('note'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
- this.getInput("PITCH").setAriaLabelProvider("pitch");
- this.getInput("DURATION").setAriaLabelProvider("duration");
+ this.setStyle('sound_blocks');
+ this.getInput('PITCH').setAriaLabelProvider('pitch');
+ this.getInput('DURATION').setAriaLabelProvider('duration');
},
};
- Blockly.Blocks["rest"] = {
+ Blockly.Blocks['rest'] = {
init: function () {
this.jsonInit({
- type: "rest",
- message0: translate("rest"),
+ type: 'rest',
+ message0: translate('rest'),
inputsInline: true,
- output: "Number",
- colour: categoryColours["Sound"],
- tooltip: getTooltip("rest"),
+ output: 'Number',
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('rest'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["play_tune_notes"] = {
+ Blockly.Blocks['play_tune_notes'] = {
init: function () {
this.jsonInit({
- type: "play_tune_notes",
- message0: translate("play_tune_notes"),
+ type: 'play_tune_notes',
+ message0: translate('play_tune_notes'),
args0: [
{
- type: "input_dummy",
- name: "MESH_INPUT",
+ type: 'input_dummy',
+ name: 'MESH_INPUT',
},
{
- type: "input_value",
- name: "INSTRUMENT",
- check: "Instrument",
+ type: 'input_value',
+ name: 'INSTRUMENT',
+ check: 'Instrument',
},
{
- type: "input_value",
- name: "NOTES",
- check: "Array",
+ type: 'input_value',
+ name: 'NOTES',
+ check: 'Array',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("play_tune_notes"),
- extensions: ["dynamic_mesh_dropdown"],
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('play_tune_notes'),
+ extensions: ['dynamic_mesh_dropdown'],
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
this.setOnChange(function (changeEvent) {
if (this.workspace?.isFlyout) return;
if (changeEvent.type !== Blockly.Events.BLOCK_MOVE) return;
// When a bar (inner list) is connected to the outer tune list, make it inline.
- const outerList = this.getInputTargetBlock("NOTES");
+ const outerList = this.getInputTargetBlock('NOTES');
if (outerList && changeEvent.newParentId === outerList.id) {
const movedBlock = this.workspace.getBlockById(changeEvent.blockId);
- if (movedBlock?.type === "lists_create_with") {
+ if (movedBlock?.type === 'lists_create_with') {
movedBlock.setInputsInline(true);
}
}
// Restore the default bar structure when NOTES is disconnected.
- if (
- changeEvent.oldParentId !== this.id ||
- changeEvent.oldInputName !== "NOTES"
- )
- return;
- if (this.getInputTargetBlock("NOTES")) return;
+ if (changeEvent.oldParentId !== this.id || changeEvent.oldInputName !== 'NOTES') return;
+ if (this.getInputTargetBlock('NOTES')) return;
const ws = this.workspace;
const listBlock = Blockly.serialization.blocks.append(
{
- type: "lists_create_with",
+ type: 'lists_create_with',
extraState: { itemCount: 1 },
inputs: {
ADD0: {
block: {
- type: "lists_create_with",
+ type: 'lists_create_with',
extraState: { itemCount: 1 },
inline: true,
inputs: {
ADD0: {
block: {
- type: "note",
+ type: 'note',
inputs: {
PITCH: {
- shadow: { type: "math_number", fields: { NUM: 60 } },
+ shadow: { type: 'math_number', fields: { NUM: 60 } },
},
DURATION: {
- shadow: { type: "math_number", fields: { NUM: 0.5 } },
+ shadow: { type: 'math_number', fields: { NUM: 0.5 } },
},
},
},
@@ -1124,217 +1144,215 @@ export function defineSoundBlocks() {
},
},
},
- ws,
+ ws
);
- const notesConn = this.getInput("NOTES")?.connection;
- if (notesConn && listBlock?.outputConnection)
- notesConn.connect(listBlock.outputConnection);
+ const notesConn = this.getInput('NOTES')?.connection;
+ if (notesConn && listBlock?.outputConnection) notesConn.connect(listBlock.outputConnection);
});
},
};
- Blockly.Blocks["set_music_speed"] = {
+ Blockly.Blocks['set_music_speed'] = {
init: function () {
this.jsonInit({
- type: "set_music_speed",
- message0: translate("set_music_speed"),
+ type: 'set_music_speed',
+ message0: translate('set_music_speed'),
args0: [
{
- type: "input_dummy",
- name: "MESH_INPUT",
+ type: 'input_dummy',
+ name: 'MESH_INPUT',
},
{
- type: "input_value",
- name: "SPEED",
- check: "Number",
+ type: 'input_value',
+ name: 'SPEED',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("set_music_speed"),
- extensions: ["dynamic_mesh_dropdown"],
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('set_music_speed'),
+ extensions: ['dynamic_mesh_dropdown'],
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["play_notes"] = {
+ Blockly.Blocks['play_notes'] = {
init: function () {
this.jsonInit({
- type: "play_notes",
- message0: translate("play_notes"),
+ type: 'play_notes',
+ message0: translate('play_notes'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "NOTES",
- check: "Array",
+ type: 'input_value',
+ name: 'NOTES',
+ check: 'Array',
},
{
- type: "input_value",
- name: "DURATIONS",
- check: "Array",
+ type: 'input_value',
+ name: 'DURATIONS',
+ check: 'Array',
},
{
- type: "input_value",
- name: "INSTRUMENT",
- check: "Instrument",
+ type: 'input_value',
+ name: 'INSTRUMENT',
+ check: 'Instrument',
},
{
- type: "field_dropdown",
- name: "ASYNC",
- options: [getDropdownOption("START"), getDropdownOption("AWAIT")],
+ type: 'field_dropdown',
+ name: 'ASYNC',
+ options: [getDropdownOption('START'), getDropdownOption('AWAIT')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("play_notes"),
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('play_notes'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["set_scene_bpm"] = {
+ Blockly.Blocks['set_scene_bpm'] = {
init: function () {
this.jsonInit({
- type: "set_scene_bpm",
- message0: translate("set_scene_bpm"),
+ type: 'set_scene_bpm',
+ message0: translate('set_scene_bpm'),
args0: [
{
- type: "input_value",
- name: "BPM",
- check: "Number",
+ type: 'input_value',
+ name: 'BPM',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"], // Assuming "Sound" category
- tooltip: getTooltip("set_scene_bpm"),
+ colour: categoryColours['Sound'], // Assuming "Sound" category
+ tooltip: getTooltip('set_scene_bpm'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["set_mesh_bpm"] = {
+ Blockly.Blocks['set_mesh_bpm'] = {
init: function () {
this.jsonInit({
- type: "set_mesh_bpm",
- message0: translate("set_mesh_bpm"),
+ type: 'set_mesh_bpm',
+ message0: translate('set_mesh_bpm'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "BPM",
- check: "Number",
+ type: 'input_value',
+ name: 'BPM',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"], // Assuming "Sound" category
- tooltip: getTooltip("set_mesh_bpm"),
+ colour: categoryColours['Sound'], // Assuming "Sound" category
+ tooltip: getTooltip('set_mesh_bpm'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["create_instrument"] = {
+ Blockly.Blocks['create_instrument'] = {
init: function () {
- const variableNamePrefix = "instrument";
- let nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'instrument';
+ let nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_instrument",
- message0: translate("create_instrument"),
+ type: 'create_instrument',
+ message0: translate('create_instrument'),
args0: [
{
- type: "field_variable",
- name: "INSTRUMENT",
+ type: 'field_variable',
+ name: 'INSTRUMENT',
variable: nextVariableName,
},
{
- type: "field_dropdown",
- name: "TYPE",
+ type: 'field_dropdown',
+ name: 'TYPE',
options: [
- getDropdownOption("sine"),
- getDropdownOption("square"),
- getDropdownOption("sawtooth"),
- getDropdownOption("triangle"),
+ getDropdownOption('sine'),
+ getDropdownOption('square'),
+ getDropdownOption('sawtooth'),
+ getDropdownOption('triangle'),
],
},
{
- type: "input_value",
- name: "VOLUME",
- check: "Number",
+ type: 'input_value',
+ name: 'VOLUME',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "EFFECT",
+ type: 'field_dropdown',
+ name: 'EFFECT',
options: [
- getDropdownOption("none"),
- getDropdownOption("tremolo"),
- getDropdownOption("vibrato"),
- getDropdownOption("warble"),
- getDropdownOption("robot"),
+ getDropdownOption('none'),
+ getDropdownOption('tremolo'),
+ getDropdownOption('vibrato'),
+ getDropdownOption('warble'),
+ getDropdownOption('robot'),
],
},
{
- type: "input_value",
- name: "EFFECT_RATE",
- check: "Number",
+ type: 'input_value',
+ name: 'EFFECT_RATE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "EFFECT_DEPTH",
- check: "Number",
+ type: 'input_value',
+ name: 'EFFECT_DEPTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ATTACK",
- check: "Number",
+ type: 'input_value',
+ name: 'ATTACK',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DECAY",
- check: "Number",
+ type: 'input_value',
+ name: 'DECAY',
+ check: 'Number',
},
{
- type: "input_value",
- name: "SUSTAIN",
- check: "Number",
+ type: 'input_value',
+ name: 'SUSTAIN',
+ check: 'Number',
},
{
- type: "input_value",
- name: "RELEASE",
- check: "Number",
+ type: 'input_value',
+ name: 'RELEASE',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("create_instrument"),
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('create_instrument'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
registerBlockHandler(this, (changeEvent) => {
handleBlockCreateEvent(
@@ -1342,61 +1360,61 @@ export function defineSoundBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "INSTRUMENT",
+ 'INSTRUMENT'
);
});
},
};
- Blockly.Blocks["instrument"] = {
+ Blockly.Blocks['instrument'] = {
init: function () {
this.jsonInit({
- type: "instrument",
- message0: translate("instrument"),
+ type: 'instrument',
+ message0: translate('instrument'),
args0: [
{
- type: "field_dropdown",
- name: "INSTRUMENT_TYPE",
+ type: 'field_dropdown',
+ name: 'INSTRUMENT_TYPE',
options: [
- getDropdownOption("default"),
- getDropdownOption("piano"),
- getDropdownOption("guitar"),
- getDropdownOption("violin"),
+ getDropdownOption('default'),
+ getDropdownOption('piano'),
+ getDropdownOption('guitar'),
+ getDropdownOption('violin'),
],
},
],
- output: "Instrument",
- colour: categoryColours["Sound"],
- tooltip: getTooltip("instrument"),
+ output: 'Instrument',
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('instrument'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["play_tune"] = {
+ Blockly.Blocks['play_tune'] = {
init: function () {
this.hasImported_ = false;
- this.appendDummyInput("ABC_INPUT")
- .appendField(translate("play_tune"))
- .appendField(new FieldAbcInput(""), "ABC_TEXT");
+ this.appendDummyInput('ABC_INPUT')
+ .appendField(translate('play_tune'))
+ .appendField(new FieldAbcInput(''), 'ABC_TEXT');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
- this.setColour(categoryColours["Sound"]);
- this.setStyle("sound_blocks");
- this.setTooltip(getTooltip("play_tune"));
+ this.setColour(categoryColours['Sound']);
+ this.setStyle('sound_blocks');
+ this.setTooltip(getTooltip('play_tune'));
this.setHelpUrl(getHelpUrlFor(this.type));
setTimeout(() => {
if (this.isDisposed()) return;
- const f = this.getField("ABC_TEXT");
+ const f = this.getField('ABC_TEXT');
if (f && f.getSvgRoot && f.getSvgRoot()) {
Blockly.utils.aria.setState(
f.getSvgRoot(),
Blockly.utils.aria.State.LABEL,
- "ABC notation, click away to import",
+ 'ABC notation, click away to import'
);
}
}, 0);
@@ -1406,21 +1424,21 @@ export function defineSoundBlocks() {
if (ev.type === Blockly.Events.BLOCK_CHANGE && ev.blockId === this.id) {
if (!this.hasImported_) {
- const value = this.getFieldValue("ABC_TEXT");
+ const value = this.getFieldValue('ABC_TEXT');
if (!value) return;
clearTimeout(this._abcTimer);
this._abcTimer = setTimeout(() => {
if (!this.isDisposed() && !this.hasImported_) this.importAbc(value);
}, 150);
- } else if (ev.name === "MESH_NAME") {
- const meshName = this.getFieldValue("MESH_NAME");
- const doStart = this.getInputTargetBlock("DO");
+ } else if (ev.name === 'MESH_NAME') {
+ const meshName = this.getFieldValue('MESH_NAME');
+ const doStart = this.getInputTargetBlock('DO');
if (doStart && meshName) propagateMesh(doStart, meshName);
}
} else if (this.hasImported_ && ev.type === Blockly.Events.BLOCK_CHANGE) {
- const instrTarget = this.getInput("INSTRUMENT")?.connection?.targetBlock();
+ const instrTarget = this.getInput('INSTRUMENT')?.connection?.targetBlock();
if (instrTarget && ev.blockId === instrTarget.id) {
- const doStart = this.getInputTargetBlock("DO");
+ const doStart = this.getInputTargetBlock('DO');
if (doStart) {
const instrState = Blockly.serialization.blocks.save(instrTarget);
Blockly.Events.setGroup(true);
@@ -1435,10 +1453,10 @@ export function defineSoundBlocks() {
this.hasImported_ &&
ev.type === Blockly.Events.BLOCK_MOVE &&
ev.newParentId === this.id &&
- ev.newInputName === "INSTRUMENT"
+ ev.newInputName === 'INSTRUMENT'
) {
- const instrBlock = this.getInput("INSTRUMENT")?.connection?.targetBlock();
- const doStart = this.getInputTargetBlock("DO");
+ const instrBlock = this.getInput('INSTRUMENT')?.connection?.targetBlock();
+ const doStart = this.getInputTargetBlock('DO');
if (instrBlock && doStart) {
const instrState = Blockly.serialization.blocks.save(instrBlock);
Blockly.Events.setGroup(true);
@@ -1456,62 +1474,72 @@ export function defineSoundBlocks() {
if (!this.hasImported_) return null;
return {
imported: true,
- title: this.getFieldValue("TITLE") || "",
- mesh: this.getFieldValue("MESH_NAME") || "__everywhere__",
+ title: this.getFieldValue('TITLE') || '',
+ mesh: this.getFieldValue('MESH_NAME') || '__everywhere__',
};
},
loadExtraState: function (state) {
if (!state?.imported) return;
this._buildImportedShape(state.title);
- if (state.mesh) this.setFieldValue(state.mesh, "MESH_NAME");
+ if (state.mesh) this.setFieldValue(state.mesh, 'MESH_NAME');
},
_buildImportedShape: function (title) {
- if (this.getInput("ABC_INPUT")) this.removeInput("ABC_INPUT");
+ if (this.getInput('ABC_INPUT')) this.removeInput('ABC_INPUT');
- if (!this.getInput("TITLE_INPUT")) {
- this.appendDummyInput("TITLE_INPUT")
- .appendField(translate("play_tune"))
- .appendField(new Blockly.FieldTextInput(title || ""), "TITLE")
- .appendField(" on ")
+ if (!this.getInput('TITLE_INPUT')) {
+ this.appendDummyInput('TITLE_INPUT')
+ .appendField(translate('play_tune'))
+ .appendField(new Blockly.FieldTextInput(title || ''), 'TITLE')
+ .appendField(' on ')
.appendField(
new Blockly.FieldDropdown(function () {
- const options = [[translate("everywhere_option"), "__everywhere__"]];
+ const options = [[translate('everywhere_option'), '__everywhere__']];
const ws = this.sourceBlock_?.workspace;
- if (ws) ws.getVariableMap().getAllVariables()
- .forEach((v) => options.push([v.name, v.name]));
+ if (ws)
+ ws.getVariableMap()
+ .getAllVariables()
+ .forEach((v) => options.push([v.name, v.name]));
return options;
}),
- "MESH_NAME",
+ 'MESH_NAME'
);
- const instrInput = this.appendValueInput("INSTRUMENT")
- .setCheck("Instrument")
- .appendField("instrument ");
+ const instrInput = this.appendValueInput('INSTRUMENT')
+ .setCheck('Instrument')
+ .appendField('instrument ');
instrInput.connection.setShadowState({
- type: "instrument",
- fields: { INSTRUMENT_TYPE: "default" },
+ type: 'instrument',
+ fields: { INSTRUMENT_TYPE: 'default' },
});
this.setInputsInline(true);
- this.appendStatementInput("DO");
+ this.appendStatementInput('DO');
setTimeout(() => {
if (this.isDisposed()) return;
- const tf = this.getField("TITLE");
+ const tf = this.getField('TITLE');
if (tf && tf.getSvgRoot && tf.getSvgRoot()) {
- Blockly.utils.aria.setState(tf.getSvgRoot(), Blockly.utils.aria.State.LABEL, "tune title");
+ Blockly.utils.aria.setState(
+ tf.getSvgRoot(),
+ Blockly.utils.aria.State.LABEL,
+ 'tune title'
+ );
}
- const mf = this.getField("MESH_NAME");
+ const mf = this.getField('MESH_NAME');
if (mf && mf.getSvgRoot && mf.getSvgRoot()) {
- Blockly.utils.aria.setState(mf.getSvgRoot(), Blockly.utils.aria.State.LABEL, "tune mesh");
+ Blockly.utils.aria.setState(
+ mf.getSvgRoot(),
+ Blockly.utils.aria.State.LABEL,
+ 'tune mesh'
+ );
}
- const doInput = this.getInput("DO");
- if (doInput) doInput.setAriaLabelProvider("tune bars");
+ const doInput = this.getInput('DO');
+ if (doInput) doInput.setAriaLabelProvider('tune bars');
}, 0);
} else {
- this.setFieldValue(title || "", "TITLE");
+ this.setFieldValue(title || '', 'TITLE');
}
this.hasImported_ = true;
@@ -1519,12 +1547,21 @@ export function defineSoundBlocks() {
importAbc: function (abcText) {
if (abcText.length > ABC_MAX_INPUT_LENGTH) {
- console.warn("[play_tune] ABC input too long, not importing:", abcText.length);
- announceToScreenReader("tune too long to import");
+ console.warn('[play_tune] ABC input too long, not importing:', abcText.length);
+ announceToScreenReader('tune too long to import');
return;
}
const parsed = parseAbc(abcText);
- console.log("[play_tune] importing:", parsed.title, "verses:", parsed.verses.length, "sections:", parsed.verses[0].length, "bpm:", parsed.bpm);
+ console.log(
+ '[play_tune] importing:',
+ parsed.title,
+ 'verses:',
+ parsed.verses.length,
+ 'sections:',
+ parsed.verses[0].length,
+ 'bpm:',
+ parsed.bpm
+ );
Blockly.Events.setGroup(true);
try {
@@ -1533,8 +1570,8 @@ export function defineSoundBlocks() {
const ws = this.workspace;
if (!ws) return;
- const meshName = this.getFieldValue("MESH_NAME") || "__everywhere__";
- const doInput = this.getInput("DO");
+ const meshName = this.getFieldValue('MESH_NAME') || '__everywhere__';
+ const doInput = this.getInput('DO');
if (doInput) {
const existing = doInput.connection.targetBlock();
if (existing) existing.dispose(false);
@@ -1542,7 +1579,7 @@ export function defineSoundBlocks() {
try {
abcBuildDoBlocks(ws, doInput, parsed.verses, parsed.bpm, meshName);
} catch (e) {
- console.error("[play_tune] DO block creation failed:", e);
+ console.error('[play_tune] DO block creation failed:', e);
}
}
} finally {
@@ -1553,91 +1590,91 @@ export function defineSoundBlocks() {
},
};
- Blockly.Blocks["speak"] = {
+ Blockly.Blocks['speak'] = {
init: function () {
this.jsonInit({
- type: "speak",
- message0: translate("speak"),
+ type: 'speak',
+ message0: translate('speak'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "input_dummy",
- name: "MESH_INPUT", // Dummy input for the dropdown
+ type: 'input_dummy',
+ name: 'MESH_INPUT', // Dummy input for the dropdown
},
{
- type: "field_dropdown",
- name: "VOICE",
- options: [getDropdownOption("female"), getDropdownOption("male")],
+ type: 'field_dropdown',
+ name: 'VOICE',
+ options: [getDropdownOption('female'), getDropdownOption('male')],
},
{
- type: "field_dropdown",
- name: "LANGUAGE",
+ type: 'field_dropdown',
+ name: 'LANGUAGE',
// Bare "es" so voice matching (v.lang.startsWith) accepts es-ES,
// es-MX and the rest, not just Spain.
options: [
- getDropdownOption("en-GB"),
- getDropdownOption("en-US"),
- getDropdownOption("es"),
+ getDropdownOption('en-GB'),
+ getDropdownOption('en-US'),
+ getDropdownOption('es'),
],
},
{
- type: "input_value",
- name: "RATE",
- check: "Number",
+ type: 'input_value',
+ name: 'RATE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "PITCH",
- check: "Number",
+ type: 'input_value',
+ name: 'PITCH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "VOLUME",
- check: "Number",
+ type: 'input_value',
+ name: 'VOLUME',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "ASYNC",
- options: [getDropdownOption("START"), getDropdownOption("AWAIT")],
+ type: 'field_dropdown',
+ name: 'ASYNC',
+ options: [getDropdownOption('START'), getDropdownOption('AWAIT')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("speak"),
- extensions: ["dynamic_mesh_dropdown"], // Attach the extension
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('speak'),
+ extensions: ['dynamic_mesh_dropdown'], // Attach the extension
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
- Blockly.Blocks["enable_subtitles"] = {
+ Blockly.Blocks['enable_subtitles'] = {
init: function () {
this.jsonInit({
- type: "enable_subtitles",
- message0: translate("enable_subtitles"),
+ type: 'enable_subtitles',
+ message0: translate('enable_subtitles'),
args0: [
{
- type: "field_checkbox",
- name: "ENABLED",
+ type: 'field_checkbox',
+ name: 'ENABLED',
checked: true,
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Sound"],
- tooltip: getTooltip("enable_subtitles"),
+ colour: categoryColours['Sound'],
+ tooltip: getTooltip('enable_subtitles'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("sound_blocks");
+ this.setStyle('sound_blocks');
},
};
}
diff --git a/blocks/text.js b/blocks/text.js
index 7e8d08d19..1c1a6d74e 100644
--- a/blocks/text.js
+++ b/blocks/text.js
@@ -1,263 +1,258 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
import {
getHelpUrlFor,
nextVariableIndexes,
handleBlockCreateEvent,
handleBlockChange,
registerBlockHandler,
-} from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
-import { attachShadowContainerOnChange } from "./scene.js";
+} from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
+import { attachShadowContainerOnChange } from './scene.js';
// print_text keeps a text_join in its TEXT input as a "shadow container": it is
// promoted to a real block on the canvas (so blocks can be dropped into its
// items) and respawned when dragged out. See attachShadowContainerOnChange.
const PRINT_TEXT_LIST_OPTS = {
- inputName: "TEXT",
- containerType: "text_join",
- cacheKey: "_cachedTextListState",
+ inputName: 'TEXT',
+ containerType: 'text_join',
+ cacheKey: '_cachedTextListState',
makeDefault: (ws) => {
- const list = ws.newBlock("text_join");
+ const list = ws.newBlock('text_join');
if (list.loadExtraState) list.loadExtraState({ itemCount: 1 });
list.setInputsInline(true);
- const item = ws.newBlock("text");
+ const item = ws.newBlock('text');
item.setShadow(true);
- item.setFieldValue("Hello ๐", "TEXT");
- if (typeof item.initSvg === "function") item.initSvg();
- const add0 = list.getInput("ADD0");
+ item.setFieldValue('Hello ๐', 'TEXT');
+ if (typeof item.initSvg === 'function') item.initSvg();
+ const add0 = list.getInput('ADD0');
if (add0?.connection) add0.connection.connect(item.outputConnection);
return list;
},
};
export function defineTextBlocks() {
- Blockly.Blocks["comment"] = {
+ Blockly.Blocks['comment'] = {
init: function () {
this.jsonInit({
- type: "comment",
- message0: translate("comment"),
+ type: 'comment',
+ message0: translate('comment'),
args0: [
{
- type: "input_value",
- name: "COMMENT",
- check: "String",
+ type: 'input_value',
+ name: 'COMMENT',
+ check: 'String',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: "#d3d3d3",
- tooltip: getTooltip("comment"),
+ colour: '#d3d3d3',
+ tooltip: getTooltip('comment'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
},
};
- Blockly.Blocks["print_text"] = {
+ Blockly.Blocks['print_text'] = {
init: function () {
this.jsonInit({
- type: "print_text",
- message0: translate("print_text"),
+ type: 'print_text',
+ message0: translate('print_text'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: ["String", "Number", "Array"],
+ type: 'input_value',
+ name: 'TEXT',
+ check: ['String', 'Number', 'Array'],
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "COLOR",
- colour: "#000080",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ colour: '#000080',
+ check: 'Colour',
},
],
// The message words ("print"/"for"/"seconds") don't read well as
// per-input labels, so label each input explicitly.
- ariaLabels: { TEXT: "text", DURATION: "seconds", COLOR: "color" },
+ ariaLabels: { TEXT: 'text', DURATION: 'seconds', COLOR: 'color' },
inputsInline: true,
previousStatement: null,
nextStatement: null,
colour: 160,
- tooltip: getTooltip("print_text"),
+ tooltip: getTooltip('print_text'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
attachShadowContainerOnChange(this, PRINT_TEXT_LIST_OPTS);
},
};
- Blockly.Blocks["subtitle"] = {
+ Blockly.Blocks['subtitle'] = {
init: function () {
this.jsonInit({
- type: "subtitle",
- message0: translate("subtitle"),
+ type: 'subtitle',
+ message0: translate('subtitle'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: ["String", "Number", "Array"],
+ type: 'input_value',
+ name: 'TEXT',
+ check: ['String', 'Number', 'Array'],
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
],
// Label each input explicitly so the message words read well.
- ariaLabels: { TEXT: "text", DURATION: "seconds" },
+ ariaLabels: { TEXT: 'text', DURATION: 'seconds' },
inputsInline: true,
previousStatement: null,
nextStatement: null,
colour: 160,
- tooltip: getTooltip("subtitle"),
+ tooltip: getTooltip('subtitle'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
},
};
- Blockly.Blocks["say"] = {
+ Blockly.Blocks['say'] = {
init: function () {
this.jsonInit({
- type: "say",
- message0: translate("say"),
+ type: 'say',
+ message0: translate('say'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "TEXT_COLOR",
- colour: "#000000",
- check: "Colour",
+ type: 'input_value',
+ name: 'TEXT_COLOR',
+ colour: '#000000',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND_COLOR",
- colour: "#ffffff",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND_COLOR',
+ colour: '#ffffff',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "ALPHA",
- check: "Number",
+ type: 'input_value',
+ name: 'ALPHA',
+ check: 'Number',
},
{
- type: "input_value",
- name: "SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'SIZE',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "MODE",
- options: [getDropdownOption("ADD"), getDropdownOption("REPLACE")],
+ type: 'field_dropdown',
+ name: 'MODE',
+ options: [getDropdownOption('ADD'), getDropdownOption('REPLACE')],
},
{
- type: "field_dropdown",
- name: "ASYNC",
- options: [getDropdownOption("START"), getDropdownOption("AWAIT")],
+ type: 'field_dropdown',
+ name: 'ASYNC',
+ options: [getDropdownOption('START'), getDropdownOption('AWAIT')],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
colour: 160,
- tooltip: getTooltip("say"),
+ tooltip: getTooltip('say'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
},
};
- Blockly.Blocks["ui_text"] = {
+ Blockly.Blocks['ui_text'] = {
init: function () {
- const variableNamePrefix = "uitext";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'uitext';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "ui_text",
- message0: translate("ui_text"),
+ type: 'ui_text',
+ message0: translate('ui_text'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "field_variable",
- name: "TEXTBLOCK_VAR",
+ type: 'field_variable',
+ name: 'TEXTBLOCK_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "FONT_SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'FONT_SIZE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND_COLOR",
- colour: "#ffffff",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND_COLOR',
+ colour: '#ffffff',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "ALPHA",
- check: "Number",
+ type: 'input_value',
+ name: 'ALPHA',
+ check: 'Number',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Text"],
- tooltip: getTooltip("ui_text"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('ui_text'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -265,80 +260,79 @@ export function defineTextBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "TEXTBLOCK_VAR",
- ),
+ 'TEXTBLOCK_VAR'
+ )
);
},
};
- Blockly.Blocks["ui_button"] = {
+ Blockly.Blocks['ui_button'] = {
init: function () {
- const variableNamePrefix = "button";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'button';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "ui_button",
- message0: translate("ui_button"),
+ type: 'ui_button',
+ message0: translate('ui_button'),
args0: [
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "field_variable",
- name: "BUTTON_VAR",
+ type: 'field_variable',
+ name: 'BUTTON_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "SIZE",
+ type: 'field_dropdown',
+ name: 'SIZE',
options: [
- getDropdownOption("SMALL"),
- getDropdownOption("MEDIUM"),
- getDropdownOption("LARGE"),
+ getDropdownOption('SMALL'),
+ getDropdownOption('MEDIUM'),
+ getDropdownOption('LARGE'),
],
},
{
- type: "field_dropdown",
- name: "TEXT_SIZE",
+ type: 'field_dropdown',
+ name: 'TEXT_SIZE',
options: [
- getDropdownOption("14px"),
- getDropdownOption("18px"),
- getDropdownOption("24px"),
+ getDropdownOption('14px'),
+ getDropdownOption('18px'),
+ getDropdownOption('24px'),
],
},
{
- type: "input_value",
- name: "TEXT_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'TEXT_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND_COLOR',
+ check: 'Colour',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Text"],
- tooltip: getTooltip("ui_button"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('ui_button'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -346,76 +340,75 @@ export function defineTextBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "BUTTON_VAR",
- ),
+ 'BUTTON_VAR'
+ )
);
},
};
- Blockly.Blocks["ui_input"] = {
+ Blockly.Blocks['ui_input'] = {
init: function () {
- const variableNamePrefix = "input";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = 'input';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "ui_input",
- message0: translate("ui_input"),
+ type: 'ui_input',
+ message0: translate('ui_input'),
args0: [
{
- type: "field_variable",
- name: "INPUT_VAR",
+ type: 'field_variable',
+ name: 'INPUT_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "SIZE",
+ type: 'field_dropdown',
+ name: 'SIZE',
options: [
- getDropdownOption("SMALL"),
- getDropdownOption("MEDIUM"),
- getDropdownOption("LARGE"),
+ getDropdownOption('SMALL'),
+ getDropdownOption('MEDIUM'),
+ getDropdownOption('LARGE'),
],
},
{
- type: "input_value",
- name: "TEXT_SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'TEXT_SIZE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "TEXT_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'TEXT_COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "BACKGROUND_COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'BACKGROUND_COLOR',
+ check: 'Colour',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Text"],
- tooltip: getTooltip("ui_input"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('ui_input'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
registerBlockHandler(this, (changeEvent) =>
handleBlockCreateEvent(
@@ -423,108 +416,107 @@ export function defineTextBlocks() {
changeEvent,
variableNamePrefix,
nextVariableIndexes,
- "INPUT_VAR",
- ),
+ 'INPUT_VAR'
+ )
);
},
};
- Blockly.Blocks["describe"] = {
+ Blockly.Blocks['describe'] = {
init: function () {
this.jsonInit({
- type: "describe",
- message0: translate("describe"),
+ type: 'describe',
+ message0: translate('describe'),
args0: [
{
- type: "field_variable",
- name: "MESH_VAR",
+ type: 'field_variable',
+ name: 'MESH_VAR',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Text"],
- tooltip: getTooltip("describe"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('describe'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
},
};
- Blockly.Blocks["create_3d_text"] = {
+ Blockly.Blocks['create_3d_text'] = {
init: function () {
- const variableNamePrefix = "3dtext";
- const nextVariableName =
- variableNamePrefix + nextVariableIndexes[variableNamePrefix];
+ const variableNamePrefix = '3dtext';
+ const nextVariableName = variableNamePrefix + nextVariableIndexes[variableNamePrefix];
this.jsonInit({
- type: "create_3d_text",
- message0: translate("create_3d_text"),
+ type: 'create_3d_text',
+ message0: translate('create_3d_text'),
args0: [
{
- type: "field_variable",
- name: "ID_VAR",
+ type: 'field_variable',
+ name: 'ID_VAR',
variable: nextVariableName,
},
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
{
- type: "field_dropdown",
- name: "FONT",
- options: [getDropdownOption("__fonts_FreeSans_Bold_json")],
+ type: 'field_dropdown',
+ name: 'FONT',
+ options: [getDropdownOption('__fonts_FreeSans_Bold_json')],
},
{
- type: "input_value",
- name: "SIZE",
- check: "Number",
+ type: 'input_value',
+ name: 'SIZE',
+ check: 'Number',
},
{
- type: "input_value",
- name: "COLOR",
- check: "Colour",
+ type: 'input_value',
+ name: 'COLOR',
+ check: 'Colour',
},
{
- type: "input_value",
- name: "DEPTH",
- check: "Number",
+ type: 'input_value',
+ name: 'DEPTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
],
inputsInline: true,
- colour: categoryColours["Text"],
- tooltip: getTooltip("create_3d_text"),
+ colour: categoryColours['Text'],
+ tooltip: getTooltip('create_3d_text'),
previousStatement: null,
nextStatement: null,
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("text_blocks");
+ this.setStyle('text_blocks');
registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent, variableNamePrefix),
+ handleBlockChange(this, changeEvent, variableNamePrefix)
);
},
};
diff --git a/blocks/text_join_shadow.js b/blocks/text_join_shadow.js
index cbab0350f..d1791bdf5 100644
--- a/blocks/text_join_shadow.js
+++ b/blocks/text_join_shadow.js
@@ -8,21 +8,21 @@
* registration, and before any workspace/blocks are created.
*/
-import * as Blockly from "blockly";
-import { createPlusField } from "@blockly/block-plus-minus/src/field_plus.js";
-import { createMinusField } from "@blockly/block-plus-minus/src/field_minus.js";
+import * as Blockly from 'blockly';
+import { createPlusField } from '@blockly/block-plus-minus/src/field_plus.js';
+import { createMinusField } from '@blockly/block-plus-minus/src/field_minus.js';
const textJoinMutator = {
itemCount_: 0,
mutationToDom: function () {
- const container = Blockly.utils.xml.createElement("mutation");
- container.setAttribute("items", this.itemCount_);
+ const container = Blockly.utils.xml.createElement('mutation');
+ container.setAttribute('items', this.itemCount_);
return container;
},
domToMutation: function (xmlElement) {
- const targetCount = parseInt(xmlElement.getAttribute("items"), 10);
+ const targetCount = parseInt(xmlElement.getAttribute('items'), 10);
this.updateShape_(targetCount);
},
@@ -33,7 +33,7 @@ const textJoinMutator = {
},
loadExtraState: function (state) {
- this.updateShape_(state["itemCount"]);
+ this.updateShape_(state['itemCount']);
},
updateShape_: function (targetCount) {
@@ -53,9 +53,9 @@ const textJoinMutator = {
*/
plus: function () {
this.addPart_();
- const input = this.getInput("ADD" + (this.itemCount_ - 1));
+ const input = this.getInput('ADD' + (this.itemCount_ - 1));
if (input && input.connection && !input.connection.targetBlock()) {
- const shadow = this.workspace.newBlock("text"); // TEXT defaults to ""
+ const shadow = this.workspace.newBlock('text'); // TEXT defaults to ""
shadow.setShadow(true);
shadow.initSvg();
shadow.render();
@@ -74,14 +74,14 @@ const textJoinMutator = {
addPart_: function () {
if (this.itemCount_ == 0) {
- if (this.getInput("EMPTY")) {
- this.removeInput("EMPTY");
+ if (this.getInput('EMPTY')) {
+ this.removeInput('EMPTY');
}
- this.topInput_ = this.appendValueInput("ADD" + this.itemCount_)
- .appendField(createPlusField(), "PLUS")
- .appendField(Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"]);
+ this.topInput_ = this.appendValueInput('ADD' + this.itemCount_)
+ .appendField(createPlusField(), 'PLUS')
+ .appendField(Blockly.Msg['TEXT_JOIN_TITLE_CREATEWITH']);
} else {
- this.appendValueInput("ADD" + this.itemCount_);
+ this.appendValueInput('ADD' + this.itemCount_);
}
// Because item inputs are 0-index we decrement first, increment last.
this.itemCount_++;
@@ -89,35 +89,31 @@ const textJoinMutator = {
removePart_: function () {
this.itemCount_--;
- this.removeInput("ADD" + this.itemCount_);
+ this.removeInput('ADD' + this.itemCount_);
if (this.itemCount_ == 0) {
- this.topInput_ = this.appendDummyInput("EMPTY")
- .appendField(createPlusField(), "PLUS")
+ this.topInput_ = this.appendDummyInput('EMPTY')
+ .appendField(createPlusField(), 'PLUS')
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
}
},
updateMinus_: function () {
- const minusField = this.getField("MINUS");
+ const minusField = this.getField('MINUS');
if (!minusField && this.itemCount_ > 0) {
- this.topInput_.insertFieldAt(1, createMinusField(), "MINUS");
+ this.topInput_.insertFieldAt(1, createMinusField(), 'MINUS');
} else if (minusField && this.itemCount_ < 1) {
- this.topInput_.removeField("MINUS");
+ this.topInput_.removeField('MINUS');
}
},
};
const textJoinHelper = function () {
- Blockly.Extensions.apply("quote_image_mixin", this, false);
+ Blockly.Extensions.apply('quote_image_mixin', this, false);
this.updateShape_(2);
};
-if (Blockly.Extensions.isRegistered("text_join_mutator")) {
- Blockly.Extensions.unregister("text_join_mutator");
+if (Blockly.Extensions.isRegistered('text_join_mutator')) {
+ Blockly.Extensions.unregister('text_join_mutator');
}
-Blockly.Extensions.registerMutator(
- "text_join_mutator",
- textJoinMutator,
- textJoinHelper,
-);
+Blockly.Extensions.registerMutator('text_join_mutator', textJoinMutator, textJoinHelper);
diff --git a/blocks/transform.js b/blocks/transform.js
index 3c8e196f7..116f95775 100644
--- a/blocks/transform.js
+++ b/blocks/transform.js
@@ -1,600 +1,563 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import {
- getHelpUrlFor,
- handleFieldOrChildChange,
- registerBlockHandler,
-} from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
-import { flock } from "../flock.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor, handleFieldOrChildChange, registerBlockHandler } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
+import { flock } from '../flock.js';
export function defineTransformBlocks() {
function handleBlockChange(block, changeEvent) {
- const changeEventBlock = Blockly.getMainWorkspace().getBlockById(
- changeEvent.blockId,
- );
+ const changeEventBlock = Blockly.getMainWorkspace().getBlockById(changeEvent.blockId);
if (!changeEventBlock) return;
- if (flock.blockDebug)
- console.log("The ID of this change event is", changeEventBlock.id);
+ if (flock.blockDebug) console.log('The ID of this change event is', changeEventBlock.id);
const changeEventParentBlock = changeEventBlock.getParent();
if (!changeEventParentBlock) return;
const changeEventBlockType = changeEventParentBlock.type;
- if (flock.blockDebug)
- console.log("The type of this change event is", changeEventBlockType);
- if (changeEventBlockType != "rotate_to" && changeEventBlockType != "resize")
- return;
+ if (flock.blockDebug) console.log('The type of this change event is', changeEventBlockType);
+ if (changeEventBlockType != 'rotate_to' && changeEventBlockType != 'resize') return;
const handleChange = handleFieldOrChildChange(block, changeEvent);
if (flock.blockDebug) console.log(handleChange);
}
- Blockly.Blocks["move_by_xyz"] = {
+ Blockly.Blocks['move_by_xyz'] = {
init: function () {
this.jsonInit({
- type: "move_by_xyz",
- message0: translate("move_by_xyz"),
+ type: 'move_by_xyz',
+ message0: translate('move_by_xyz'),
args0: [
{
- type: "field_variable",
- name: "BLOCK_NAME",
+ type: 'field_variable',
+ name: 'BLOCK_NAME',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("move_by_xyz"),
+ tooltip: getTooltip('move_by_xyz'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["move_by_xyz_single"] = {
+ Blockly.Blocks['move_by_xyz_single'] = {
init: function () {
this.jsonInit({
- type: "move_by_xyz_single",
- message0: translate("move_by_xyz_single"),
+ type: 'move_by_xyz_single',
+ message0: translate('move_by_xyz_single'),
args0: [
{
- type: "field_variable",
- name: "BLOCK_NAME",
+ type: 'field_variable',
+ name: 'BLOCK_NAME',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "COORDINATE",
+ type: 'field_dropdown',
+ name: 'COORDINATE',
options: [
- getDropdownOption("x_coordinate"),
- getDropdownOption("y_coordinate"),
- getDropdownOption("z_coordinate"),
+ getDropdownOption('x_coordinate'),
+ getDropdownOption('y_coordinate'),
+ getDropdownOption('z_coordinate'),
],
},
{
- type: "input_value",
- name: "VALUE",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'VALUE',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("move_by_xyz_single"),
+ tooltip: getTooltip('move_by_xyz_single'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["move_to_xyz"] = {
+ Blockly.Blocks['move_to_xyz'] = {
init: function () {
this.jsonInit({
- type: "move_to_xyz",
- message0: translate("move_to_xyz"),
+ type: 'move_to_xyz',
+ message0: translate('move_to_xyz'),
args0: [
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "field_checkbox",
- name: "USE_Y",
+ type: 'field_checkbox',
+ name: 'USE_Y',
checked: true,
- text: "Use Y axis",
+ text: 'Use Y axis',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("move_to_xyz"),
+ tooltip: getTooltip('move_to_xyz'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["move_to_xyz_single"] = {
+ Blockly.Blocks['move_to_xyz_single'] = {
init: function () {
this.jsonInit({
- type: "move_to_xyz_single",
- message0: translate("move_to_xyz_single"),
+ type: 'move_to_xyz_single',
+ message0: translate('move_to_xyz_single'),
args0: [
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
{
- type: "field_dropdown",
- name: "COORDINATE",
+ type: 'field_dropdown',
+ name: 'COORDINATE',
options: [
- getDropdownOption("x_coordinate"),
- getDropdownOption("y_coordinate"),
- getDropdownOption("z_coordinate"),
+ getDropdownOption('x_coordinate'),
+ getDropdownOption('y_coordinate'),
+ getDropdownOption('z_coordinate'),
],
},
{
- type: "input_value",
- name: "VALUE",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'VALUE',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("move_to_xyz_single"),
+ tooltip: getTooltip('move_to_xyz_single'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["move_to"] = {
+ Blockly.Blocks['move_to'] = {
init: function () {
this.jsonInit({
- type: "move_to",
- message0: translate("move_to"),
+ type: 'move_to',
+ message0: translate('move_to'),
args0: [
{
- type: "field_variable",
- name: "MODEL1",
+ type: 'field_variable',
+ name: 'MODEL1',
variable: window.currentMesh,
},
{
- type: "field_variable",
- name: "MODEL2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MODEL2',
+ variable: 'object2',
},
{
- type: "field_checkbox",
- name: "USE_Y",
+ type: 'field_checkbox',
+ name: 'USE_Y',
checked: false,
- text: "Use Y axis",
+ text: 'Use Y axis',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("move_to"),
+ tooltip: getTooltip('move_to'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["scale"] = {
+ Blockly.Blocks['scale'] = {
init: function () {
this.jsonInit({
- type: "scale",
- message0: translate("scale"),
+ type: 'scale',
+ message0: translate('scale'),
args0: [
{
- type: "field_variable",
- name: "BLOCK_NAME",
+ type: 'field_variable',
+ name: 'BLOCK_NAME',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "X_ORIGIN",
+ type: 'field_dropdown',
+ name: 'X_ORIGIN',
options: [
- getDropdownOption("CENTRE"),
- getDropdownOption("LEFT"),
- getDropdownOption("RIGHT"),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('LEFT'),
+ getDropdownOption('RIGHT'),
],
},
{
- type: "field_dropdown",
- name: "Y_ORIGIN",
+ type: 'field_dropdown',
+ name: 'Y_ORIGIN',
options: [
- getDropdownOption("BASE"),
- getDropdownOption("CENTRE"),
- getDropdownOption("TOP"),
+ getDropdownOption('BASE'),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('TOP'),
],
},
{
- type: "field_dropdown",
- name: "Z_ORIGIN",
+ type: 'field_dropdown',
+ name: 'Z_ORIGIN',
options: [
- getDropdownOption("CENTRE"),
- getDropdownOption("FRONT"),
- getDropdownOption("BACK"),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('FRONT'),
+ getDropdownOption('BACK'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("scale"),
+ tooltip: getTooltip('scale'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["resize"] = {
+ Blockly.Blocks['resize'] = {
init: function () {
this.jsonInit({
- type: "resize",
- message0: translate("resize"),
+ type: 'resize',
+ message0: translate('resize'),
args0: [
{
- type: "field_variable",
- name: "BLOCK_NAME",
+ type: 'field_variable',
+ name: 'BLOCK_NAME',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
},
{
- type: "field_dropdown",
- name: "X_ORIGIN",
+ type: 'field_dropdown',
+ name: 'X_ORIGIN',
options: [
- getDropdownOption("CENTRE"),
- getDropdownOption("LEFT"),
- getDropdownOption("RIGHT"),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('LEFT'),
+ getDropdownOption('RIGHT'),
],
},
{
- type: "field_dropdown",
- name: "Y_ORIGIN",
+ type: 'field_dropdown',
+ name: 'Y_ORIGIN',
options: [
- getDropdownOption("BASE"),
- getDropdownOption("CENTRE"),
- getDropdownOption("TOP"),
+ getDropdownOption('BASE'),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('TOP'),
],
},
{
- type: "field_dropdown",
- name: "Z_ORIGIN",
+ type: 'field_dropdown',
+ name: 'Z_ORIGIN',
options: [
- getDropdownOption("CENTRE"),
- getDropdownOption("FRONT"),
- getDropdownOption("BACK"),
+ getDropdownOption('CENTRE'),
+ getDropdownOption('FRONT'),
+ getDropdownOption('BACK'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("resize"),
+ tooltip: getTooltip('resize'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["rotate_model_xyz"] = {
+ Blockly.Blocks['rotate_model_xyz'] = {
init: function () {
this.jsonInit({
- type: "rotate_model_xyz",
- message0: translate("rotate_model_xyz"),
+ type: 'rotate_model_xyz',
+ message0: translate('rotate_model_xyz'),
args0: [
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("rotate_model_xyz"),
+ tooltip: getTooltip('rotate_model_xyz'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["rotate_to"] = {
+ Blockly.Blocks['rotate_to'] = {
init: function () {
this.jsonInit({
- type: "rotate_to",
- message0: translate("rotate_to"),
+ type: 'rotate_to',
+ message0: translate('rotate_to'),
args0: [
{
- type: "field_variable",
- name: "MODEL",
+ type: 'field_variable',
+ name: 'MODEL',
variable: window.currentMesh,
},
{
- type: "input_value",
- name: "X",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'X',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Y",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Y',
+ check: 'Number',
+ align: 'RIGHT',
},
{
- type: "input_value",
- name: "Z",
- check: "Number",
- align: "RIGHT",
+ type: 'input_value',
+ name: 'Z',
+ check: 'Number',
+ align: 'RIGHT',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("rotate_to"),
+ tooltip: getTooltip('rotate_to'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["look_at"] = {
+ Blockly.Blocks['look_at'] = {
init: function () {
this.jsonInit({
- type: "look_at",
- message0: translate("look_at"),
+ type: 'look_at',
+ message0: translate('look_at'),
args0: [
{
- type: "field_variable",
- name: "MODEL1",
+ type: 'field_variable',
+ name: 'MODEL1',
variable: window.currentMesh,
},
{
- type: "field_variable",
- name: "MODEL2",
- variable: "object2",
+ type: 'field_variable',
+ name: 'MODEL2',
+ variable: 'object2',
},
{
- type: "field_checkbox",
- name: "USE_Y",
+ type: 'field_checkbox',
+ name: 'USE_Y',
checked: false,
- text: "Use Y axis",
+ text: 'Use Y axis',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
+ colour: categoryColours['Transform'],
inputsInline: true,
- tooltip: getTooltip("look_at"),
+ tooltip: getTooltip('look_at'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["set_pivot"] = {
+ Blockly.Blocks['set_pivot'] = {
init: function () {
this.jsonInit({
- type: "set_pivot",
- message0: translate("set_pivot"),
+ type: 'set_pivot',
+ message0: translate('set_pivot'),
args0: [
{
- type: "field_variable",
- name: "MESH",
+ type: 'field_variable',
+ name: 'MESH',
variable: window.currentMesh, // Assuming the mesh is stored here
},
{
- type: "input_value",
- name: "X_PIVOT",
- check: ["Number", "String"],
+ type: 'input_value',
+ name: 'X_PIVOT',
+ check: ['Number', 'String'],
},
{
- type: "input_value",
- name: "Y_PIVOT",
- check: ["Number", "String"],
+ type: 'input_value',
+ name: 'Y_PIVOT',
+ check: ['Number', 'String'],
},
{
- type: "input_value",
- name: "Z_PIVOT",
- check: ["Number", "String"],
+ type: 'input_value',
+ name: 'Z_PIVOT',
+ check: ['Number', 'String'],
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Transform"],
- tooltip: getTooltip("set_pivot"),
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('set_pivot'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
- Blockly.Blocks["min_centre_max"] = {
+ Blockly.Blocks['min_centre_max'] = {
init: function () {
this.jsonInit({
- type: "min_centre_max",
- message0: translate("min_centre_max"),
+ type: 'min_centre_max',
+ message0: translate('min_centre_max'),
args0: [
{
- type: "field_dropdown",
- name: "PIVOT_OPTION",
+ type: 'field_dropdown',
+ name: 'PIVOT_OPTION',
options: [
- getDropdownOption("MIN"),
- getDropdownOption("CENTER"),
- getDropdownOption("MAX"),
+ getDropdownOption('MIN'),
+ getDropdownOption('CENTER'),
+ getDropdownOption('MAX'),
],
},
],
- output: "String", // Now returns a symbolic string
- colour: categoryColours["Transform"],
- tooltip: getTooltip("min_centre_max"),
+ output: 'String', // Now returns a symbolic string
+ colour: categoryColours['Transform'],
+ tooltip: getTooltip('min_centre_max'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("transform_blocks");
+ this.setStyle('transform_blocks');
// Set up the change handler.
- registerBlockHandler(this, (changeEvent) =>
- handleBlockChange(this, changeEvent),
- );
+ registerBlockHandler(this, (changeEvent) => handleBlockChange(this, changeEvent));
},
};
}
diff --git a/blocks/xr.js b/blocks/xr.js
index 6ec72050c..3b5895d1c 100644
--- a/blocks/xr.js
+++ b/blocks/xr.js
@@ -1,17 +1,13 @@
-import * as Blockly from "blockly";
-import { categoryColours } from "../toolbox.js";
-import { getHelpUrlFor } from "./blocks.js";
-import {
- translate,
- getTooltip,
- getDropdownOption,
-} from "../main/translation.js";
+import * as Blockly from 'blockly';
+import { categoryColours } from '../toolbox.js';
+import { getHelpUrlFor } from './blocks.js';
+import { translate, getTooltip, getDropdownOption } from '../main/translation.js';
import {
syncMicrobitDeviceField,
refreshMicrobitBlocks,
isMicrobitRefreshEvent,
-} from "./sensing.js";
-import "./fieldMicrobitImage.js"; // registers field_microbit_image
+} from './sensing.js';
+import './fieldMicrobitImage.js'; // registers field_microbit_image
// Shared by the micro:bit display blocks (show image / scroll text): keep
// the device menu in sync with variable renames/deletes and the
@@ -34,227 +30,224 @@ function wireMicrobitDeviceBlock(block) {
}
export function defineXRBlocks() {
- Blockly.Blocks["microbit_show_image"] = {
+ Blockly.Blocks['microbit_show_image'] = {
init: function () {
this.jsonInit({
- type: "microbit_show_image",
- message0: translate("microbit_show_image"),
+ type: 'microbit_show_image',
+ message0: translate('microbit_show_image'),
args0: [
{
// Same device menu as microbit_input: "any" plus the variables
// defined by add_microbit blocks.
- type: "field_microbit_device",
- name: "DEVICE",
+ type: 'field_microbit_device',
+ name: 'DEVICE',
},
{
- type: "field_microbit_image",
- name: "IMAGE",
+ type: 'field_microbit_image',
+ name: 'IMAGE',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("microbit_show_image"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('microbit_show_image'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
wireMicrobitDeviceBlock(this);
},
};
- Blockly.Blocks["microbit_scroll_text"] = {
+ Blockly.Blocks['microbit_scroll_text'] = {
init: function () {
this.jsonInit({
- type: "microbit_scroll_text",
- message0: translate("microbit_scroll_text"),
+ type: 'microbit_scroll_text',
+ message0: translate('microbit_scroll_text'),
args0: [
{
- type: "field_microbit_device",
- name: "DEVICE",
+ type: 'field_microbit_device',
+ name: 'DEVICE',
},
{
- type: "input_value",
- name: "TEXT",
- check: "String",
+ type: 'input_value',
+ name: 'TEXT',
+ check: 'String',
},
],
inputsInline: true,
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("microbit_scroll_text"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('microbit_scroll_text'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
wireMicrobitDeviceBlock(this);
},
};
- Blockly.Blocks["device_camera_background"] = {
+ Blockly.Blocks['device_camera_background'] = {
init: function () {
this.jsonInit({
- type: "device_camera_background",
- message0: translate("device_camera_background"),
+ type: 'device_camera_background',
+ message0: translate('device_camera_background'),
args0: [
{
- type: "field_dropdown",
- name: "CAMERA",
- options: [
- getDropdownOption("user"),
- getDropdownOption("environment"),
- ],
+ type: 'field_dropdown',
+ name: 'CAMERA',
+ options: [getDropdownOption('user'), getDropdownOption('environment')],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("device_camera_background"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('device_camera_background'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["set_xr_mode"] = {
+ Blockly.Blocks['set_xr_mode'] = {
init: function () {
this.jsonInit({
- type: "set_xr_mode",
- message0: translate("set_xr_mode"),
+ type: 'set_xr_mode',
+ message0: translate('set_xr_mode'),
args0: [
{
- type: "field_dropdown",
- name: "MODE",
+ type: 'field_dropdown',
+ name: 'MODE',
options: [
- getDropdownOption("VR"),
- getDropdownOption("AR"),
- getDropdownOption("MAGIC_WINDOW"),
+ getDropdownOption('VR'),
+ getDropdownOption('AR'),
+ getDropdownOption('MAGIC_WINDOW'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("set_xr_mode"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('set_xr_mode'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["play_rumble_pattern"] = {
+ Blockly.Blocks['play_rumble_pattern'] = {
init: function () {
this.jsonInit({
- type: "play_rumble_pattern",
- message0: translate("play_rumble_pattern"),
+ type: 'play_rumble_pattern',
+ message0: translate('play_rumble_pattern'),
args0: [
{
- type: "field_dropdown",
- name: "PATTERN",
+ type: 'field_dropdown',
+ name: 'PATTERN',
options: [
- getDropdownOption("objectGrab"),
- getDropdownOption("objectDrop"),
- getDropdownOption("smallCollision"),
- getDropdownOption("heavyCollision"),
- getDropdownOption("snapToGrid"),
- getDropdownOption("errorInvalid"),
- getDropdownOption("successConfirmation"),
- getDropdownOption("slidingGravel"),
- getDropdownOption("slidingMetal"),
- getDropdownOption("machineRunning"),
- getDropdownOption("explosion"),
- getDropdownOption("teleport"),
+ getDropdownOption('objectGrab'),
+ getDropdownOption('objectDrop'),
+ getDropdownOption('smallCollision'),
+ getDropdownOption('heavyCollision'),
+ getDropdownOption('snapToGrid'),
+ getDropdownOption('errorInvalid'),
+ getDropdownOption('successConfirmation'),
+ getDropdownOption('slidingGravel'),
+ getDropdownOption('slidingMetal'),
+ getDropdownOption('machineRunning'),
+ getDropdownOption('explosion'),
+ getDropdownOption('teleport'),
],
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("play_rumble_pattern"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('play_rumble_pattern'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["controller_rumble"] = {
+ Blockly.Blocks['controller_rumble'] = {
init: function () {
this.jsonInit({
- type: "controller_rumble",
- message0: translate("controller_rumble"),
+ type: 'controller_rumble',
+ message0: translate('controller_rumble'),
args0: [
{
- type: "field_dropdown",
- name: "MOTOR",
+ type: 'field_dropdown',
+ name: 'MOTOR',
options: [
- getDropdownOption("all"),
- getDropdownOption("left"),
- getDropdownOption("right"),
+ getDropdownOption('all'),
+ getDropdownOption('left'),
+ getDropdownOption('right'),
],
},
{
- type: "input_value",
- name: "STRENGTH",
- check: "Number",
+ type: 'input_value',
+ name: 'STRENGTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'DURATION',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("controller_rumble"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('controller_rumble'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
- Blockly.Blocks["controller_rumble_pattern"] = {
+ Blockly.Blocks['controller_rumble_pattern'] = {
init: function () {
this.jsonInit({
- type: "controller_rumble_pattern",
- message0: translate("controller_rumble_pattern"),
+ type: 'controller_rumble_pattern',
+ message0: translate('controller_rumble_pattern'),
args0: [
{
- type: "field_dropdown",
- name: "MOTOR",
+ type: 'field_dropdown',
+ name: 'MOTOR',
options: [
- getDropdownOption("all"),
- getDropdownOption("left"),
- getDropdownOption("right"),
+ getDropdownOption('all'),
+ getDropdownOption('left'),
+ getDropdownOption('right'),
],
},
{
- type: "input_value",
- name: "STRENGTH",
- check: "Number",
+ type: 'input_value',
+ name: 'STRENGTH',
+ check: 'Number',
},
{
- type: "input_value",
- name: "ON_DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'ON_DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "OFF_DURATION",
- check: "Number",
+ type: 'input_value',
+ name: 'OFF_DURATION',
+ check: 'Number',
},
{
- type: "input_value",
- name: "REPEATS",
- check: "Number",
+ type: 'input_value',
+ name: 'REPEATS',
+ check: 'Number',
},
],
previousStatement: null,
nextStatement: null,
- colour: categoryColours["Scene"],
- tooltip: getTooltip("controller_rumble_pattern"),
+ colour: categoryColours['Scene'],
+ tooltip: getTooltip('controller_rumble_pattern'),
});
this.setHelpUrl(getHelpUrlFor(this.type));
- this.setStyle("scene_blocks");
+ this.setStyle('scene_blocks');
},
};
}
diff --git a/config.js b/config.js
index df8db19c8..e5cd90cf5 100644
--- a/config.js
+++ b/config.js
@@ -1,70 +1,69 @@
-import { getDropdownOption, translate } from "./main/translation.js";
+import { getDropdownOption, translate } from './main/translation.js';
-export const SHORTCUTS_HELP_URL =
- "https://hub.flockxr.com/knowledge-base/keyboard-controls/";
+export const SHORTCUTS_HELP_URL = 'https://hub.flockxr.com/knowledge-base/keyboard-controls/';
export const TOP_BLOCK_TYPES = Object.freeze([
- "start",
- "forever",
- "when_clicked",
- "when_touches",
- "on_collision",
- "when_key_event",
- "when_action_event",
- "on_event",
- "procedures_defnoreturn",
- "procedures_defreturn",
- "microbit_input",
+ 'start',
+ 'forever',
+ 'when_clicked',
+ 'when_touches',
+ 'on_collision',
+ 'when_key_event',
+ 'when_action_event',
+ 'on_event',
+ 'procedures_defnoreturn',
+ 'procedures_defreturn',
+ 'microbit_input',
]);
export const themeNames = [
- "theme-bright.mp3",
- "theme-calm.mp3",
- "theme-electronic.mp3",
- "theme-game.mp3",
- "theme-medieval.mp3",
- "theme-metal.mp3",
+ 'theme-bright.mp3',
+ 'theme-calm.mp3',
+ 'theme-electronic.mp3',
+ 'theme-game.mp3',
+ 'theme-medieval.mp3',
+ 'theme-metal.mp3',
];
export const audioNames = [
- "highDown.mp3",
- "highUp.mp3",
- "laser1.mp3",
- "laser2.mp3",
- "laser3.mp3",
- "lowDown.mp3",
- "lowRandom.mp3",
- "lowThreeTone.mp3",
- "phaseJump1.mp3",
- "powerUp1.mp3",
- "powerUp2.mp3",
- "powerUp3.mp3",
- "powerUp4.mp3",
- "powerUp5.mp3",
- "spaceTrash.mp3",
- "threeTone1.mp3",
- "threeTone2.mp3",
- "chop.mp3",
- "creak.mp3",
- "door_close.mp3",
- "door_open.mp3",
- "footstep.mp3",
- "metal_latch.mp3",
+ 'highDown.mp3',
+ 'highUp.mp3',
+ 'laser1.mp3',
+ 'laser2.mp3',
+ 'laser3.mp3',
+ 'lowDown.mp3',
+ 'lowRandom.mp3',
+ 'lowThreeTone.mp3',
+ 'phaseJump1.mp3',
+ 'powerUp1.mp3',
+ 'powerUp2.mp3',
+ 'powerUp3.mp3',
+ 'powerUp4.mp3',
+ 'powerUp5.mp3',
+ 'spaceTrash.mp3',
+ 'threeTone1.mp3',
+ 'threeTone2.mp3',
+ 'chop.mp3',
+ 'creak.mp3',
+ 'door_close.mp3',
+ 'door_open.mp3',
+ 'footstep.mp3',
+ 'metal_latch.mp3',
];
export function audioFileToLabel(filename) {
return filename
- .replace(".mp3", "")
- .replace(/([A-Z])/g, " $1")
- .replace(/(\d+)/g, " $1")
+ .replace('.mp3', '')
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/(\d+)/g, ' $1')
.trim()
- .replace(/\s+/g, " ")
+ .replace(/\s+/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
}
export function getThemeDisplayName(filename) {
- const baseName = filename.replace("theme-", "").replace(".mp3", "");
- const key = "theme_" + baseName + "_option";
+ const baseName = filename.replace('theme-', '').replace('.mp3', '');
+ const key = 'theme_' + baseName + '_option';
const translated = translate(key);
return translated && translated !== key
? translated
@@ -72,135 +71,133 @@ export function getThemeDisplayName(filename) {
}
export function getSoundDisplayName(filename) {
- const baseName = filename.replace(".mp3", "");
- const key = "sound_" + baseName + "_option";
+ const baseName = filename.replace('.mp3', '');
+ const key = 'sound_' + baseName + '_option';
const translated = translate(key);
- return translated && translated !== key
- ? translated
- : audioFileToLabel(filename);
+ return translated && translated !== key ? translated : audioFileToLabel(filename);
}
export const characterNames = [
- "Liz1.glb",
- "Liz2.glb",
- "Block1.glb",
- "Block2.glb",
- "Block3.glb",
- "Block4.glb",
- "Block5.glb",
- "Block6.glb",
- "Liz3.glb",
- "Liz4.glb",
- "Liz5.glb",
- "Liz6.glb",
+ 'Liz1.glb',
+ 'Liz2.glb',
+ 'Block1.glb',
+ 'Block2.glb',
+ 'Block3.glb',
+ 'Block4.glb',
+ 'Block5.glb',
+ 'Block6.glb',
+ 'Liz3.glb',
+ 'Liz4.glb',
+ 'Liz5.glb',
+ 'Liz6.glb',
];
export const multiObjectNames = [
- "tree.glb",
- "tree2.glb",
- "tree3.glb",
- "tree4.glb",
- "hut.glb",
- "hut2.glb",
- "hut3.glb",
- "hut4.glb",
- "rocks.glb",
- "rocks2.glb",
- "rocks3.glb",
- "rocks4.glb",
- "pond.glb",
- "boat.glb",
- "airplane.glb",
- "airplane2.glb",
- "skateboard.glb",
- "humped.glb",
- "jetty.glb",
- "boardwalk_straight.glb",
- "flower.glb",
- "flower2.glb",
+ 'tree.glb',
+ 'tree2.glb',
+ 'tree3.glb',
+ 'tree4.glb',
+ 'hut.glb',
+ 'hut2.glb',
+ 'hut3.glb',
+ 'hut4.glb',
+ 'rocks.glb',
+ 'rocks2.glb',
+ 'rocks3.glb',
+ 'rocks4.glb',
+ 'pond.glb',
+ 'boat.glb',
+ 'airplane.glb',
+ 'airplane2.glb',
+ 'skateboard.glb',
+ 'humped.glb',
+ 'jetty.glb',
+ 'boardwalk_straight.glb',
+ 'flower.glb',
+ 'flower2.glb',
];
export const objectNames = [
- "Star.glb",
- "Heart.glb",
- "Coin.glb",
- "egg.glb",
- "Gem1.glb",
- "Gem2.glb",
- "Gem3.glb",
- "Key.glb",
- "Wand.glb",
- "Hat.glb",
- "donut.glb",
- "pumpkin.glb",
- "apple.glb",
- "starboppers.glb",
- "headphones.glb",
+ 'Star.glb',
+ 'Heart.glb',
+ 'Coin.glb',
+ 'egg.glb',
+ 'Gem1.glb',
+ 'Gem2.glb',
+ 'Gem3.glb',
+ 'Key.glb',
+ 'Wand.glb',
+ 'Hat.glb',
+ 'donut.glb',
+ 'pumpkin.glb',
+ 'apple.glb',
+ 'starboppers.glb',
+ 'headphones.glb',
];
function modelNameToDisplayName(modelName) {
- return String(modelName || "")
- .replace(/\.[^/.]+$/, "")
- .replace(/[_-]+/g, " ")
- .replace(/\s+/g, " ")
+ return String(modelName || '')
+ .replace(/\.[^/.]+$/, '')
+ .replace(/[_-]+/g, ' ')
+ .replace(/\s+/g, ' ')
.trim()
.replace(/\b\w/g, (char) => char.toUpperCase());
}
export const objectDisplayNameTranslationKeys = {
- "Liz1.glb": "model_display_liz1",
- "Liz2.glb": "model_display_liz2",
- "Liz3.glb": "model_display_liz3",
- "Liz4.glb": "model_display_liz4",
- "Liz5.glb": "model_display_liz5",
- "Liz6.glb": "model_display_liz6",
- "Block1.glb": "model_display_block1",
- "Block2.glb": "model_display_block2",
- "Block3.glb": "model_display_block3",
- "Block4.glb": "model_display_block4",
- "Block5.glb": "model_display_block5",
- "Block6.glb": "model_display_block6",
-
- "tree.glb": "model_display_tree",
- "tree2.glb": "model_display_tree2",
- "tree3.glb": "model_display_tree3",
- "tree4.glb": "model_display_tree4",
- "hut.glb": "model_display_hut",
- "hut2.glb": "model_display_hut2",
- "hut3.glb": "model_display_hut3",
- "hut4.glb": "model_display_hut4",
- "rocks.glb": "model_display_rocks",
- "rocks2.glb": "model_display_rocks2",
- "rocks3.glb": "model_display_rocks3",
- "rocks4.glb": "model_display_rocks4",
- "pond.glb": "model_display_pond",
- "boat.glb": "model_display_boat",
- "airplane.glb": "model_display_airplane",
- "airplane2.glb": "model_display_airplane2",
- "skateboard.glb": "model_display_skateboard",
- "humped.glb": "model_display_humped",
- "jetty.glb": "model_display_jetty",
- "flower.glb": "model_display_flower",
- "flower2.glb": "model_display_flower2",
- "Star.glb": "model_display_star",
- "Heart.glb": "model_display_heart",
- "Coin.glb": "model_display_coin",
- "egg.glb": "model_display_egg",
- "Gem1.glb": "model_display_gem1",
- "Gem2.glb": "model_display_gem2",
- "Gem3.glb": "model_display_gem3",
- "Key.glb": "model_display_key",
- "Wand.glb": "model_display_wand",
- "Hat.glb": "model_display_hat",
- "donut.glb": "model_display_donut",
- "pumpkin.glb": "model_display_pumpkin",
- "apple.glb": "model_display_apple",
- "starboppers.glb": "model_display_starboppers",
- "headphones.glb": "model_display_headphones",
- "Flock.glb": "model_display_flock",
- "Flock_Santa.glb": "model_display_flock_santa",
- "Character.glb": "model_display_character",
- "rhino.glb": "model_display_rhino",
+ 'Liz1.glb': 'model_display_liz1',
+ 'Liz2.glb': 'model_display_liz2',
+ 'Liz3.glb': 'model_display_liz3',
+ 'Liz4.glb': 'model_display_liz4',
+ 'Liz5.glb': 'model_display_liz5',
+ 'Liz6.glb': 'model_display_liz6',
+ 'Block1.glb': 'model_display_block1',
+ 'Block2.glb': 'model_display_block2',
+ 'Block3.glb': 'model_display_block3',
+ 'Block4.glb': 'model_display_block4',
+ 'Block5.glb': 'model_display_block5',
+ 'Block6.glb': 'model_display_block6',
+
+ 'tree.glb': 'model_display_tree',
+ 'tree2.glb': 'model_display_tree2',
+ 'tree3.glb': 'model_display_tree3',
+ 'tree4.glb': 'model_display_tree4',
+ 'hut.glb': 'model_display_hut',
+ 'hut2.glb': 'model_display_hut2',
+ 'hut3.glb': 'model_display_hut3',
+ 'hut4.glb': 'model_display_hut4',
+ 'rocks.glb': 'model_display_rocks',
+ 'rocks2.glb': 'model_display_rocks2',
+ 'rocks3.glb': 'model_display_rocks3',
+ 'rocks4.glb': 'model_display_rocks4',
+ 'pond.glb': 'model_display_pond',
+ 'boat.glb': 'model_display_boat',
+ 'airplane.glb': 'model_display_airplane',
+ 'airplane2.glb': 'model_display_airplane2',
+ 'skateboard.glb': 'model_display_skateboard',
+ 'humped.glb': 'model_display_humped',
+ 'jetty.glb': 'model_display_jetty',
+ 'flower.glb': 'model_display_flower',
+ 'flower2.glb': 'model_display_flower2',
+ 'Star.glb': 'model_display_star',
+ 'Heart.glb': 'model_display_heart',
+ 'Coin.glb': 'model_display_coin',
+ 'egg.glb': 'model_display_egg',
+ 'Gem1.glb': 'model_display_gem1',
+ 'Gem2.glb': 'model_display_gem2',
+ 'Gem3.glb': 'model_display_gem3',
+ 'Key.glb': 'model_display_key',
+ 'Wand.glb': 'model_display_wand',
+ 'Hat.glb': 'model_display_hat',
+ 'donut.glb': 'model_display_donut',
+ 'pumpkin.glb': 'model_display_pumpkin',
+ 'apple.glb': 'model_display_apple',
+ 'starboppers.glb': 'model_display_starboppers',
+ 'headphones.glb': 'model_display_headphones',
+ 'Flock.glb': 'model_display_flock',
+ 'Flock_Santa.glb': 'model_display_flock_santa',
+ 'Character.glb': 'model_display_character',
+ 'rhino.glb': 'model_display_rhino',
};
export function getModelDisplayName(modelName) {
@@ -215,262 +212,237 @@ export function getModelDisplayName(modelName) {
}
export const objectColours = {
- "Star.glb": ["#FFD700", "#FFD700", "#FFD700"],
- "Heart.glb": ["#FF69B4", "#FF69B4", "#FF69B4"],
- "Coin.glb": ["#A47E1B", "#C9A227", "#76520E"],
- "egg.glb": ["#fffcec", "#fffcec", "#fffcec"],
- "Gem1.glb": ["#00BFFF", "#00BFFF", "#00BFFF"],
- "Gem2.glb": ["#8A2BE2", "#8A2BE2", "#8A2BE2"],
- "Gem3.glb": ["#FF4500", "#FF4500", "#FF4500"],
- "Key.glb": ["#A47E1B", "#C9A227", "#76520E"],
- "Wand.glb": ["#FF4500", "#8A2BE2", "#92614A"],
- "Hat.glb": ["#9D3F72", "#B5FDFD", "#3D0073"],
- "donut.glb": ["#f9cb9c", "#fba0c3"],
- "pumpkin.glb": ["#E78632", "#75430F"],
- "apple.glb": ["#3FAF45", "#A9323F", "#624A20"],
- "starboppers.glb": ["#FFD700", "#FFD700", "#FFD700", "#f9f9f9"],
- "headphones.glb": ["#53E0E7", "#3291E7", "#7D7D7D"],
-
- "tree.glb": ["#66CDAA", "#CD853F"],
- "tree2.glb": ["#7F9F7F", "#A1623B"],
- "tree3.glb": ["#403C3C", "#312616"],
- "tree4.glb": ["#0D5B28", "#6D6C51"],
-
- "rocks.glb": ["#898D86", "#99a83d"],
- "rocks2.glb": ["#898D86", "#99a83d"],
- "rocks3.glb": ["#898D86", "#99a83d"],
- "rocks4.glb": ["#898D86", "#99a83d", "#6BC6EF", "#f9f9f9"],
- "pond.glb": ["#00E704", "#5A91E7", "#9A9A9A"],
-
- "hut.glb": ["#B66946", "#5F2524", "#C25A5C", "#E1B46E", "#3BACBA", "#878787"],
- "hut2.glb": [
- "#814C22",
- "#231E1D",
- "#FFF6A6",
- "#E7AF3A",
- "#E73627",
- "#878787",
- ],
- "hut3.glb": [
- "#F6DAB6",
- "#6CC3C1",
- "#9DC45C",
- "#EEB975",
- "#F3B4BE",
- "#878787",
- ],
- "hut4.glb": [
- "#F2E8CF",
- "#BC4749",
- "#EEB975",
- "#AF1B3F",
- "#6A994E",
- "#878787",
+ 'Star.glb': ['#FFD700', '#FFD700', '#FFD700'],
+ 'Heart.glb': ['#FF69B4', '#FF69B4', '#FF69B4'],
+ 'Coin.glb': ['#A47E1B', '#C9A227', '#76520E'],
+ 'egg.glb': ['#fffcec', '#fffcec', '#fffcec'],
+ 'Gem1.glb': ['#00BFFF', '#00BFFF', '#00BFFF'],
+ 'Gem2.glb': ['#8A2BE2', '#8A2BE2', '#8A2BE2'],
+ 'Gem3.glb': ['#FF4500', '#FF4500', '#FF4500'],
+ 'Key.glb': ['#A47E1B', '#C9A227', '#76520E'],
+ 'Wand.glb': ['#FF4500', '#8A2BE2', '#92614A'],
+ 'Hat.glb': ['#9D3F72', '#B5FDFD', '#3D0073'],
+ 'donut.glb': ['#f9cb9c', '#fba0c3'],
+ 'pumpkin.glb': ['#E78632', '#75430F'],
+ 'apple.glb': ['#3FAF45', '#A9323F', '#624A20'],
+ 'starboppers.glb': ['#FFD700', '#FFD700', '#FFD700', '#f9f9f9'],
+ 'headphones.glb': ['#53E0E7', '#3291E7', '#7D7D7D'],
+
+ 'tree.glb': ['#66CDAA', '#CD853F'],
+ 'tree2.glb': ['#7F9F7F', '#A1623B'],
+ 'tree3.glb': ['#403C3C', '#312616'],
+ 'tree4.glb': ['#0D5B28', '#6D6C51'],
+
+ 'rocks.glb': ['#898D86', '#99a83d'],
+ 'rocks2.glb': ['#898D86', '#99a83d'],
+ 'rocks3.glb': ['#898D86', '#99a83d'],
+ 'rocks4.glb': ['#898D86', '#99a83d', '#6BC6EF', '#f9f9f9'],
+ 'pond.glb': ['#00E704', '#5A91E7', '#9A9A9A'],
+
+ 'hut.glb': ['#B66946', '#5F2524', '#C25A5C', '#E1B46E', '#3BACBA', '#878787'],
+ 'hut2.glb': ['#814C22', '#231E1D', '#FFF6A6', '#E7AF3A', '#E73627', '#878787'],
+ 'hut3.glb': ['#F6DAB6', '#6CC3C1', '#9DC45C', '#EEB975', '#F3B4BE', '#878787'],
+ 'hut4.glb': ['#F2E8CF', '#BC4749', '#EEB975', '#AF1B3F', '#6A994E', '#878787'],
+
+ 'boat.glb': [
+ '#4F8A46',
+ '#E7D48E',
+ '#E76635',
+ '#E76C69',
+ '#5E64E7',
+ '#4A4A4A',
+ '#AAAAAA',
+ '#E711CD',
],
- "boat.glb": [
- "#4F8A46",
- "#E7D48E",
- "#E76635",
- "#E76C69",
- "#5E64E7",
- "#4A4A4A",
- "#AAAAAA",
- "#E711CD",
- ],
-
- "airplane.glb": ["#E75D43", "#6A6A6A", "#E7C777", "#979797", "#A033E7"],
+ 'airplane.glb': ['#E75D43', '#6A6A6A', '#E7C777', '#979797', '#A033E7'],
- "airplane2.glb": ["#E75D43", "#6A6A6A", "#E7C777", "#979797", "#A033E7"],
+ 'airplane2.glb': ['#E75D43', '#6A6A6A', '#E7C777', '#979797', '#A033E7'],
- "skateboard.glb": ["#E769D3", "#484848", "#251BE7"],
+ 'skateboard.glb': ['#E769D3', '#484848', '#251BE7'],
- "rhino.glb": ["#6D6B6C", "#F6F6F6", "#373737", "#230F0F"],
- "lion.glb": ["#000000", "#DECC9C", "#8A4900", "#C69452"],
+ 'rhino.glb': ['#6D6B6C', '#F6F6F6', '#373737', '#230F0F'],
+ 'lion.glb': ['#000000', '#DECC9C', '#8A4900', '#C69452'],
- "humped.glb": ["#FFA869", "#E76F31", "#7E5024"],
- "boardwalk_straight.glb": ["#7E5024", "#7E5024", "#7E5024"],
- "jetty.glb": ["#FFA869", "#7E5024", "#E76F31"],
- "flower.glb": ["#E73F9F", "#4AB700", "#E7D535"],
- "flower2.glb": ["#7C38E7", "#4AB700", "#E7D535"],
+ 'humped.glb': ['#FFA869', '#E76F31', '#7E5024'],
+ 'boardwalk_straight.glb': ['#7E5024', '#7E5024', '#7E5024'],
+ 'jetty.glb': ['#FFA869', '#7E5024', '#E76F31'],
+ 'flower.glb': ['#E73F9F', '#4AB700', '#E7D535'],
+ 'flower2.glb': ['#7C38E7', '#4AB700', '#E7D535'],
};
-export const modelNames = [
- "Flock.glb",
- "lion.glb",
- "rhino.glb",
-];
+export const modelNames = ['Flock.glb', 'lion.glb', 'rhino.glb'];
export const blockNames = [
- "Character1.glb",
- "Character2.glb",
- "Character3.glb",
- "Character4.glb",
- "Flock.glb",
- "Flock_Santa.glb",
- "Character.glb",
- "lion.glb",
- "rhino.glb",
+ 'Character1.glb',
+ 'Character2.glb',
+ 'Character3.glb',
+ 'Character4.glb',
+ 'Flock.glb',
+ 'Flock_Santa.glb',
+ 'Character.glb',
+ 'lion.glb',
+ 'rhino.glb',
];
export const modelAnimationNames = [
- "Flock.glb",
- "rhino.glb",
- "lion.glb",
- "airplane.glb",
- "airplane2.glb",
+ 'Flock.glb',
+ 'rhino.glb',
+ 'lion.glb',
+ 'airplane.glb',
+ 'airplane2.glb',
];
export function mapNames() {
return [
- getDropdownOption("circular_depression.png"),
- getDropdownOption("checkerboard.png"),
- getDropdownOption("sloped_plane.png"),
- getDropdownOption("cove_plateau.png"),
- getDropdownOption("random_hills.png"),
- getDropdownOption("diagonal_ridge.png"),
- getDropdownOption("mixed_heights.png"),
- getDropdownOption("uneven_terrain.png"),
- getDropdownOption("mountains.png"),
- getDropdownOption("Islands.png"),
- getDropdownOption("Lookout.png"),
- getDropdownOption("Valley.png"),
+ getDropdownOption('circular_depression.png'),
+ getDropdownOption('checkerboard.png'),
+ getDropdownOption('sloped_plane.png'),
+ getDropdownOption('cove_plateau.png'),
+ getDropdownOption('random_hills.png'),
+ getDropdownOption('diagonal_ridge.png'),
+ getDropdownOption('mixed_heights.png'),
+ getDropdownOption('uneven_terrain.png'),
+ getDropdownOption('mountains.png'),
+ getDropdownOption('Islands.png'),
+ getDropdownOption('Lookout.png'),
+ getDropdownOption('Valley.png'),
];
}
export function animationNames() {
return [
- getDropdownOption("Idle"),
- getDropdownOption("Walk"),
- getDropdownOption("Run"),
- getDropdownOption("Wave"),
- getDropdownOption("Yes"),
- getDropdownOption("No"),
- getDropdownOption("Duck"),
- getDropdownOption("Fall"),
- getDropdownOption("Fly"),
- getDropdownOption("Jump"),
- getDropdownOption("JumpUp"),
- getDropdownOption("JumpIdle"),
- getDropdownOption("JumpLand"),
- getDropdownOption("Flip"),
- getDropdownOption("Dance1"),
- getDropdownOption("Dance2"),
- getDropdownOption("Dance3"),
- getDropdownOption("Dance4"),
- getDropdownOption("Punch"),
- getDropdownOption("HitReact"),
- getDropdownOption("Idle_Hold"),
- getDropdownOption("Walk_Hold"),
- getDropdownOption("Run_Hold"),
- getDropdownOption("Sit_Down"),
- getDropdownOption("Sitting"),
- getDropdownOption("Stand_Up"),
- getDropdownOption("Wobble"),
- getDropdownOption("Clap"),
- getDropdownOption("Climb_rope"),
+ getDropdownOption('Idle'),
+ getDropdownOption('Walk'),
+ getDropdownOption('Run'),
+ getDropdownOption('Wave'),
+ getDropdownOption('Yes'),
+ getDropdownOption('No'),
+ getDropdownOption('Duck'),
+ getDropdownOption('Fall'),
+ getDropdownOption('Fly'),
+ getDropdownOption('Jump'),
+ getDropdownOption('JumpUp'),
+ getDropdownOption('JumpIdle'),
+ getDropdownOption('JumpLand'),
+ getDropdownOption('Flip'),
+ getDropdownOption('Dance1'),
+ getDropdownOption('Dance2'),
+ getDropdownOption('Dance3'),
+ getDropdownOption('Dance4'),
+ getDropdownOption('Punch'),
+ getDropdownOption('HitReact'),
+ getDropdownOption('Idle_Hold'),
+ getDropdownOption('Walk_Hold'),
+ getDropdownOption('Run_Hold'),
+ getDropdownOption('Sit_Down'),
+ getDropdownOption('Sitting'),
+ getDropdownOption('Stand_Up'),
+ getDropdownOption('Wobble'),
+ getDropdownOption('Clap'),
+ getDropdownOption('Climb_rope'),
];
}
export const materialNames = [
- "none.png",
- "arrows.png",
- "bricks.png",
- "carbonfibre.png",
- "carpet.png",
- "circles.png",
- "eyeball.png",
- "fabric.png",
- "fishes.png",
- "fish_above.png",
- "flowers.png",
- "flower_tile.png",
- "fruit.png",
- "grass.png",
- "gravel.png",
- "Grid.png",
- "hedge.png",
- "jigsaw.png",
- "leaves.png",
- "marble.png",
- "mosaic.png",
- "mushroom.png",
- "planks.png",
- "road.png",
- "rough.png",
- "shapes.png",
- "squares.png",
- "stars.png",
- "stripes.png",
- "swirl.png",
- "tiles.png",
- "triangles.png",
- "wiggles.png",
- "windmill.png",
- "wood.png",
- "gridxy.png",
+ 'none.png',
+ 'arrows.png',
+ 'bricks.png',
+ 'carbonfibre.png',
+ 'carpet.png',
+ 'circles.png',
+ 'eyeball.png',
+ 'fabric.png',
+ 'fishes.png',
+ 'fish_above.png',
+ 'flowers.png',
+ 'flower_tile.png',
+ 'fruit.png',
+ 'grass.png',
+ 'gravel.png',
+ 'Grid.png',
+ 'hedge.png',
+ 'jigsaw.png',
+ 'leaves.png',
+ 'marble.png',
+ 'mosaic.png',
+ 'mushroom.png',
+ 'planks.png',
+ 'road.png',
+ 'rough.png',
+ 'shapes.png',
+ 'squares.png',
+ 'stars.png',
+ 'stripes.png',
+ 'swirl.png',
+ 'tiles.png',
+ 'triangles.png',
+ 'wiggles.png',
+ 'windmill.png',
+ 'wood.png',
+ 'gridxy.png',
];
export const attachNames = [
- "LeftHand",
- "RightHand",
- "Head",
- "Hips",
- "Spine",
- "Spine1",
- "Spine2",
- "Neck",
- "LeftShoulder",
- "LeftArm",
- "LeftForeArm",
- "RightShoulder",
- "RightArm",
- "RightForeArm",
- "LeftUpLeg",
- "LeftLeg",
- "LeftFoot",
- "RightUpLeg",
- "RightLeg",
- "RightFoot",
+ 'LeftHand',
+ 'RightHand',
+ 'Head',
+ 'Hips',
+ 'Spine',
+ 'Spine1',
+ 'Spine2',
+ 'Neck',
+ 'LeftShoulder',
+ 'LeftArm',
+ 'LeftForeArm',
+ 'RightShoulder',
+ 'RightArm',
+ 'RightForeArm',
+ 'LeftUpLeg',
+ 'LeftLeg',
+ 'LeftFoot',
+ 'RightUpLeg',
+ 'RightLeg',
+ 'RightFoot',
];
export const attachBlockMapping = {
- LeftHand: "Hold",
- Hold: "Hold",
- Head: "Head",
+ LeftHand: 'Hold',
+ Hold: 'Hold',
+ Head: 'Head',
};
// Mixamo bone names are from the character's own perspective, but GLTF
// import mirrors the model, so left/right are swapped from the viewer's
// perspective. LeftHand (viewer's left) maps to mixamorig:RightHand, etc.
-export const AUTOSAVE_KEY = "flock_autosave.flock";
+export const AUTOSAVE_KEY = 'flock_autosave.flock';
// Feature flag for autosaving to the last explicitly-saved file. localStorage
// autosave is unaffected by this flag.
export const AUTOSAVE_TO_FILE_ENABLED = false;
export const attachMixamoMapping = {
- LeftHand: "mixamorig:RightHand",
- Hold: "mixamorig:RightHand",
- RightHand: "mixamorig:LeftHand",
- Head: "mixamorig:HeadTop_End",
- Hips: "mixamorig:Hips",
- Spine: "mixamorig:Spine",
- Spine1: "mixamorig:Spine1",
- Spine2: "mixamorig:Spine2",
- Neck: "mixamorig:Neck",
- LeftShoulder: "mixamorig:RightShoulder",
- LeftArm: "mixamorig:RightArm",
- LeftForeArm: "mixamorig:RightForeArm",
- RightShoulder: "mixamorig:LeftShoulder",
- RightArm: "mixamorig:LeftArm",
- RightForeArm: "mixamorig:LeftForeArm",
- LeftUpLeg: "mixamorig:RightUpLeg",
- LeftLeg: "mixamorig:RightLeg",
- LeftFoot: "mixamorig:RightFoot",
- RightUpLeg: "mixamorig:LeftUpLeg",
- RightLeg: "mixamorig:LeftLeg",
- RightFoot: "mixamorig:LeftFoot",
+ LeftHand: 'mixamorig:RightHand',
+ Hold: 'mixamorig:RightHand',
+ RightHand: 'mixamorig:LeftHand',
+ Head: 'mixamorig:HeadTop_End',
+ Hips: 'mixamorig:Hips',
+ Spine: 'mixamorig:Spine',
+ Spine1: 'mixamorig:Spine1',
+ Spine2: 'mixamorig:Spine2',
+ Neck: 'mixamorig:Neck',
+ LeftShoulder: 'mixamorig:RightShoulder',
+ LeftArm: 'mixamorig:RightArm',
+ LeftForeArm: 'mixamorig:RightForeArm',
+ RightShoulder: 'mixamorig:LeftShoulder',
+ RightArm: 'mixamorig:LeftArm',
+ RightForeArm: 'mixamorig:LeftForeArm',
+ LeftUpLeg: 'mixamorig:RightUpLeg',
+ LeftLeg: 'mixamorig:RightLeg',
+ LeftFoot: 'mixamorig:RightFoot',
+ RightUpLeg: 'mixamorig:LeftUpLeg',
+ RightLeg: 'mixamorig:LeftLeg',
+ RightFoot: 'mixamorig:LeftFoot',
};
export function getAttachNames() {
diff --git a/cubeart.html b/cubeart.html
index 199e8994c..d196c2036 100644
--- a/cubeart.html
+++ b/cubeart.html
@@ -8,8 +8,7 @@
@@ -86,7 +85,7 @@