From 8a5f45fc5bd0623784af45143b3a7f076f5ef34b Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 10:54:31 +0000
Subject: [PATCH 01/22] implement codemirror
---
package-lock.json | 360 +++++++++++++++++++++++
package.json | 11 +-
src/components/gui/codemirror-imports.js | 4 +
src/components/gui/gui.css | 12 +
src/components/gui/gui.jsx | 96 +++++-
5 files changed, 478 insertions(+), 5 deletions(-)
create mode 100644 src/components/gui/codemirror-imports.js
diff --git a/package-lock.json b/package-lock.json
index cd1ac3789..a5a4f2985 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,15 @@
"version": "3.2.37",
"license": "GPL-3.0",
"dependencies": {
+ "@codemirror/basic-setup": "^0.20.0",
+ "@codemirror/highlight": "^0.19.8",
+ "@codemirror/language": "^6.11.3",
+ "@codemirror/state": "^6.5.2",
+ "@codemirror/view": "^6.38.8",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.4",
"@microbit/microbit-universal-hex": "0.2.2",
"@turbowarp/jszip": "^3.11.1",
"@turbowarp/nanolog": "^0.2.0",
@@ -24,6 +31,7 @@
"base64-loader": "1.0.0",
"bowser": "1.9.4",
"classnames": "2.2.6",
+ "codemirror": "^6.0.2",
"computed-style-to-inline-style": "3.0.0",
"cookie": "0.5.0",
"copy-webpack-plugin": "6.4.1",
@@ -44,6 +52,7 @@
"lodash.defaultsdeep": "4.6.1",
"lodash.omit": "4.5.0",
"lodash.throttle": "4.0.1",
+ "monaco-editor": "^0.55.1",
"omggif": "1.0.9",
"papaparse": "5.3.0",
"postcss-import": "^12.0.0",
@@ -1940,6 +1949,268 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz",
+ "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz",
+ "integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==",
+ "deprecated": "In version 6.0, this package has been renamed to just 'codemirror'",
+ "dependencies": {
+ "@codemirror/autocomplete": "^0.20.0",
+ "@codemirror/commands": "^0.20.0",
+ "@codemirror/language": "^0.20.0",
+ "@codemirror/lint": "^0.20.0",
+ "@codemirror/search": "^0.20.0",
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/autocomplete": {
+ "version": "0.20.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz",
+ "integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==",
+ "dependencies": {
+ "@codemirror/language": "^0.20.0",
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.0",
+ "@lezer/common": "^0.16.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/commands": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
+ "integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
+ "dependencies": {
+ "@codemirror/language": "^0.20.0",
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.0",
+ "@lezer/common": "^0.16.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/language": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
+ "integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
+ "dependencies": {
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.0",
+ "@lezer/common": "^0.16.0",
+ "@lezer/highlight": "^0.16.0",
+ "@lezer/lr": "^0.16.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/lint": {
+ "version": "0.20.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz",
+ "integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==",
+ "dependencies": {
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.2",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/search": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
+ "integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
+ "dependencies": {
+ "@codemirror/state": "^0.20.0",
+ "@codemirror/view": "^0.20.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
+ "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@codemirror/view": {
+ "version": "0.20.7",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
+ "integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
+ "dependencies": {
+ "@codemirror/state": "^0.20.0",
+ "style-mod": "^4.0.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@lezer/common": {
+ "version": "0.16.1",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
+ "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA=="
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@lezer/highlight": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
+ "integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
+ "dependencies": {
+ "@lezer/common": "^0.16.0"
+ }
+ },
+ "node_modules/@codemirror/basic-setup/node_modules/@lezer/lr": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
+ "integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
+ "dependencies": {
+ "@lezer/common": "^0.16.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
+ "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "node_modules/@codemirror/highlight": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@codemirror/highlight/-/highlight-0.19.8.tgz",
+ "integrity": "sha512-v/lzuHjrYR8MN2mEJcUD6fHSTXXli9C1XGYpr+ElV6fLBIUhMTNKR3qThp611xuWfXfwDxeL7ppcbkM/MzPV3A==",
+ "deprecated": "As of 0.20.0, this package has been split between @lezer/highlight and @codemirror/language",
+ "dependencies": {
+ "@codemirror/language": "^0.19.0",
+ "@codemirror/rangeset": "^0.19.0",
+ "@codemirror/state": "^0.19.3",
+ "@codemirror/view": "^0.19.39",
+ "@lezer/common": "^0.15.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/highlight/node_modules/@codemirror/language": {
+ "version": "0.19.10",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.19.10.tgz",
+ "integrity": "sha512-yA0DZ3RYn2CqAAGW62VrU8c4YxscMQn45y/I9sjBlqB1e2OTQLg4CCkMBuMSLXk4xaqjlsgazeOQWaJQOKfV8Q==",
+ "dependencies": {
+ "@codemirror/state": "^0.19.0",
+ "@codemirror/text": "^0.19.0",
+ "@codemirror/view": "^0.19.0",
+ "@lezer/common": "^0.15.5",
+ "@lezer/lr": "^0.15.0"
+ }
+ },
+ "node_modules/@codemirror/highlight/node_modules/@codemirror/state": {
+ "version": "0.19.9",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz",
+ "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==",
+ "dependencies": {
+ "@codemirror/text": "^0.19.0"
+ }
+ },
+ "node_modules/@codemirror/highlight/node_modules/@codemirror/view": {
+ "version": "0.19.48",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.19.48.tgz",
+ "integrity": "sha512-0eg7D2Nz4S8/caetCTz61rK0tkHI17V/d15Jy0kLOT8dTLGGNJUponDnW28h2B6bERmPlVHKh8MJIr5OCp1nGw==",
+ "dependencies": {
+ "@codemirror/rangeset": "^0.19.5",
+ "@codemirror/state": "^0.19.3",
+ "@codemirror/text": "^0.19.0",
+ "style-mod": "^4.0.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
+ "node_modules/@codemirror/highlight/node_modules/@lezer/common": {
+ "version": "0.15.12",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz",
+ "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig=="
+ },
+ "node_modules/@codemirror/highlight/node_modules/@lezer/lr": {
+ "version": "0.15.8",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz",
+ "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==",
+ "dependencies": {
+ "@lezer/common": "^0.15.0"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.11.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
+ "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.9.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz",
+ "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/rangeset": {
+ "version": "0.19.9",
+ "resolved": "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.9.tgz",
+ "integrity": "sha512-V8YUuOvK+ew87Xem+71nKcqu1SXd5QROMRLMS/ljT5/3MCxtgrRie1Cvild0G/Z2f1fpWxzX78V0U4jjXBorBQ==",
+ "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state",
+ "dependencies": {
+ "@codemirror/state": "^0.19.0"
+ }
+ },
+ "node_modules/@codemirror/rangeset/node_modules/@codemirror/state": {
+ "version": "0.19.9",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz",
+ "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==",
+ "dependencies": {
+ "@codemirror/text": "^0.19.0"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.11",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
+ "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
+ "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
+ "dependencies": {
+ "@marijn/find-cluster-break": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/text": {
+ "version": "0.19.6",
+ "resolved": "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz",
+ "integrity": "sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==",
+ "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state"
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.38.8",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
+ "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==",
+ "dependencies": {
+ "@codemirror/state": "^6.5.0",
+ "crelt": "^1.0.6",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@@ -2959,6 +3230,32 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lezer/common": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
+ "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg=="
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
+ "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
+ "dependencies": {
+ "@lezer/common": "^1.3.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.4.tgz",
+ "integrity": "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@marijn/find-cluster-break": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
+ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
+ },
"node_modules/@microbit/microbit-universal-hex": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz",
@@ -3419,6 +3716,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "optional": true
+ },
"node_modules/@types/uglify-js": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz",
@@ -5948,6 +6251,20 @@
"node": ">= 0.12.0"
}
},
+ "node_modules/codemirror": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
+ "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"node_modules/collect-v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
@@ -6507,6 +6824,11 @@
"node": ">=8"
}
},
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+ },
"node_modules/cross-fetch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
@@ -14818,6 +15140,17 @@
"node": ">=0.10.0"
}
},
+ "node_modules/marked": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
+ "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/matchmediaquery": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.2.1.tgz",
@@ -15317,6 +15650,23 @@
"node": ">=4"
}
},
+ "node_modules/monaco-editor": {
+ "version": "0.55.1",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
+ "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
+ "dependencies": {
+ "dompurify": "3.2.7",
+ "marked": "14.0.0"
+ }
+ },
+ "node_modules/monaco-editor/node_modules/dompurify": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
+ "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
@@ -20356,6 +20706,11 @@
"node": ">= 4"
}
},
+ "node_modules/style-mod": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
+ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
+ },
"node_modules/supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@@ -21938,6 +22293,11 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"license": "MIT"
},
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/package.json b/package.json
index 1ef42a59b..95c000ccc 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,15 @@
}
},
"dependencies": {
+ "@codemirror/basic-setup": "^0.20.0",
+ "@codemirror/highlight": "^0.19.8",
+ "@codemirror/language": "^6.11.3",
+ "@codemirror/state": "^6.5.2",
+ "@codemirror/view": "^6.38.8",
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
+ "@lezer/highlight": "^1.2.3",
+ "@lezer/lr": "^1.4.4",
"@microbit/microbit-universal-hex": "0.2.2",
"@turbowarp/jszip": "^3.11.1",
"@turbowarp/nanolog": "^0.2.0",
@@ -47,6 +54,7 @@
"base64-loader": "1.0.0",
"bowser": "1.9.4",
"classnames": "2.2.6",
+ "codemirror": "^6.0.2",
"computed-style-to-inline-style": "3.0.0",
"cookie": "0.5.0",
"copy-webpack-plugin": "6.4.1",
@@ -67,6 +75,7 @@
"lodash.defaultsdeep": "4.6.1",
"lodash.omit": "4.5.0",
"lodash.throttle": "4.0.1",
+ "monaco-editor": "^0.55.1",
"omggif": "1.0.9",
"papaparse": "5.3.0",
"postcss-import": "^12.0.0",
@@ -167,4 +176,4 @@
"terser-webpack-plugin": "^4.2.3"
}
}
-}
\ No newline at end of file
+}
diff --git a/src/components/gui/codemirror-imports.js b/src/components/gui/codemirror-imports.js
new file mode 100644
index 000000000..7dc5525d5
--- /dev/null
+++ b/src/components/gui/codemirror-imports.js
@@ -0,0 +1,4 @@
+export { EditorView } from "@codemirror/view";
+export { basicSetup } from "codemirror";
+export { LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle } from "@codemirror/language";
+export { styleTags, tags } from "@lezer/highlight";
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 8578ab24b..45944a9f7 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -54,6 +54,7 @@
display: flex;
flex-direction: column;
+ min-height: 0;
}
.tab-list {
@@ -358,4 +359,15 @@ $fade-out-distance: 15px;
.button-row {
background: $ui-white;
border-radius: $form-radius;
+}
+
+.sidebar {
+ background: $ui-white;
+ padding-right: 1px;
+ width: 4rem;
+ margin-bottom: 3.25rem;
+}
+
+.codemirror {
+ --space: $space;
}
\ No newline at end of file
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 70764e665..e57a00af4 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -74,6 +74,93 @@ const getFullscreenBackgroundColor = () => {
const fullscreenBackgroundColor = getFullscreenBackgroundColor();
+function CMView({ theme }) {
+ const el = React.useRef(null);
+ const editorRef = React.useRef(null);
+
+ React.useEffect(() => {
+ let disposed = false;
+
+ async function loadEditor() {
+ const {
+ EditorView,
+ basicSetup,
+ HighlightStyle,
+ syntaxHighlighting,
+ tags
+ } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./codemirror-imports.js");
+
+ // Define highlight rules using CSS vars
+ const scratchHighlight = HighlightStyle.define([
+ { tag: tags.variableName, color: "var(--cm-variable)" },
+ { tag: tags.keyword, color: "var(--cm-keyword)" },
+ { tag: tags.string, color: "var(--cm-string)" },
+ { tag: tags.number, color: "var(--cm-number)" },
+ { tag: tags.function, color: "var(--cm-function)" },
+ { tag: tags.operator, color: "var(--cm-operator)" }
+ ]);
+
+ const { StreamLanguage } = await import("@codemirror/language");
+ const scratchSyntax = StreamLanguage.define({
+ token(stream) {
+ if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
+ if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
+ if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
+ stream.next();
+ return "variable";
+ }
+ });
+
+ if (!el.current || disposed) return;
+
+ const cmTheme = EditorView.theme({
+ "&": {
+ backgroundColor: "var(--ui-white)",
+ color: "var(--text-primary)",
+ height: "100%",
+ borderTopRightRadius: "var(--space)",
+ borderBottomRightRadius: "var(--space)",
+ border: "1px solid var(--ui-black-transparent)"
+ },
+ ".cm-content": { caretColor: "var(--looks-secondary)" },
+ ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
+ ".cm-focused": { outline: "none" },
+ ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
+ ".cm-gutters": {
+ backgroundColor: "var(--ui-tertiary)",
+ borderRight: "1px solid var(--ui-black-transparent)"
+ }
+ }, { dark: theme === Theme.dark });
+
+ editorRef.current = new EditorView({
+ doc: `when green flag clicked
+say "Hello World!"
+repeat 10
+ say (join "hi " "there")
+end`,
+ extensions: [
+ basicSetup,
+ scratchSyntax,
+ syntaxHighlighting(scratchHighlight),
+ cmTheme
+ ],
+ parent: el.current
+ });
+ }
+
+ loadEditor();
+
+ return () => {
+ disposed = true;
+ if (editorRef.current) {
+ editorRef.current.destroy();
+ }
+ };
+ }, [theme]);
+
+ return <>
>;
+}
+
const GUIComponent = props => {
const {
accountNavOpen,
@@ -404,7 +491,7 @@ const GUIComponent = props => {
- {isNano ? 'inspired by nanoscratch by A-MARIO-PLAYER (he got banned from scratch RIP) now lets get to coding (nanoscript is not implemented yet)' : <>
+ {isNano ? blocksTabVisible && : <>
{
onOpenCustomExtensionModal={onOpenCustomExtensionModal}
theme={theme}
vm={vm} />
-
+ >}
+
{ alert('Adding extensions in NanoScript is not available yet') } : onExtensionButtonClick}
>
- >}
+
{!isNano &&
Date: Mon, 1 Dec 2025 13:23:36 +0000
Subject: [PATCH 02/22] ugh i've tried to get it to scroll instead of
overflowing but it won't work
---
src/components/gui/gui.css | 22 ++++++++++++--
src/components/gui/gui.jsx | 29 ++++++++++++-------
...or-imports.js => ob-codemirror-imports.js} | 0
3 files changed, 38 insertions(+), 13 deletions(-)
rename src/components/gui/{codemirror-imports.js => ob-codemirror-imports.js} (100%)
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 45944a9f7..c33fd9d06 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -363,11 +363,27 @@ $fade-out-distance: 15px;
.sidebar {
background: $ui-white;
- padding-right: 1px;
- width: 4rem;
- margin-bottom: 3.25rem;
+ padding: $space;
+ width: 20rem;
+ color: $text-primary;
+ user-select: none;
+ font-size: 0.9rem;
+ overflow: auto;
+}
+
+.sidebar h2 {
+ font-size: 1rem;
}
.codemirror {
--space: $space;
+ overflow: hidden;
+}
+
+.button {
+ appearance: none;
+ background: transparent;
+ padding: 6px;
+ border: $ui-black-transparent 1px solid;
+ border-radius: $space;
}
\ No newline at end of file
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index e57a00af4..192499fb0 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -53,6 +53,7 @@ import costumesIcon from '!../../lib/tw-recolor/build!./icon--costumes.svg';
import soundsIcon from '!../../lib/tw-recolor/build!./icon--sounds.svg';
import nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg';
import songsIcon from '!../../lib/tw-recolor/build!./icon--songs.svg';
+import SpinnerComponent from '../tw-loading-spinner/spinner.jsx';
const messages = defineMessages({
addExtension: {
id: 'gui.gui.addExtension',
@@ -74,7 +75,7 @@ const getFullscreenBackgroundColor = () => {
const fullscreenBackgroundColor = getFullscreenBackgroundColor();
-function CMView({ theme }) {
+function CMView({ theme, vm }) {
const el = React.useRef(null);
const editorRef = React.useRef(null);
@@ -88,12 +89,12 @@ function CMView({ theme }) {
HighlightStyle,
syntaxHighlighting,
tags
- } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./codemirror-imports.js");
+ } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
// Define highlight rules using CSS vars
const scratchHighlight = HighlightStyle.define([
- { tag: tags.variableName, color: "var(--cm-variable)" },
- { tag: tags.keyword, color: "var(--cm-keyword)" },
+ { tag: tags.variableName, color: "var(--data-primary)" },
+ { tag: tags.keyword, color: "var(--pen-primary)" },
{ tag: tags.string, color: "var(--cm-string)" },
{ tag: tags.number, color: "var(--cm-number)" },
{ tag: tags.function, color: "var(--cm-function)" },
@@ -120,7 +121,12 @@ function CMView({ theme }) {
height: "100%",
borderTopRightRadius: "var(--space)",
borderBottomRightRadius: "var(--space)",
- border: "1px solid var(--ui-black-transparent)"
+ border: "1px solid var(--ui-black-transparent)",
+ height: "100%",
+ },
+ ".cm-scroller": {
+ maxHeight: "100%",
+ overflow: "auto"
},
".cm-content": { caretColor: "var(--looks-secondary)" },
".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
@@ -158,7 +164,10 @@ end`,
};
}, [theme]);
- return <>
>;
+ return <>
+
Variables
+ Make a Variable (NOT IMPLEMENTED)
+
>;
}
const GUIComponent = props => {
@@ -491,7 +500,7 @@ const GUIComponent = props => {
- {isNano ? blocksTabVisible && : <>
+ {isNano ? blocksTabVisible && : <>
{
onOpenCustomExtensionModal={onOpenCustomExtensionModal}
theme={theme}
vm={vm} />
- >}
-
+
{
+ src={addExtensionIcon} />{isNano && intl.formatMessage(messages.addExtension)}
+ >}
{!isNano &&
Date: Mon, 1 Dec 2025 13:44:30 +0000
Subject: [PATCH 03/22] remove unused dependencies
---
package-lock.json | 225 ----------------------------------------------
package.json | 3 -
2 files changed, 228 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a5a4f2985..9d01c6c9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,6 @@
"version": "3.2.37",
"license": "GPL-3.0",
"dependencies": {
- "@codemirror/basic-setup": "^0.20.0",
- "@codemirror/highlight": "^0.19.8",
"@codemirror/language": "^6.11.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.8",
@@ -52,7 +50,6 @@
"lodash.defaultsdeep": "4.6.1",
"lodash.omit": "4.5.0",
"lodash.throttle": "4.0.1",
- "monaco-editor": "^0.55.1",
"omggif": "1.0.9",
"papaparse": "5.3.0",
"postcss-import": "^12.0.0",
@@ -1960,112 +1957,6 @@
"@lezer/common": "^1.0.0"
}
},
- "node_modules/@codemirror/basic-setup": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz",
- "integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==",
- "deprecated": "In version 6.0, this package has been renamed to just 'codemirror'",
- "dependencies": {
- "@codemirror/autocomplete": "^0.20.0",
- "@codemirror/commands": "^0.20.0",
- "@codemirror/language": "^0.20.0",
- "@codemirror/lint": "^0.20.0",
- "@codemirror/search": "^0.20.0",
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.0"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/autocomplete": {
- "version": "0.20.3",
- "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz",
- "integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==",
- "dependencies": {
- "@codemirror/language": "^0.20.0",
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.0",
- "@lezer/common": "^0.16.0"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/commands": {
- "version": "0.20.0",
- "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz",
- "integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==",
- "dependencies": {
- "@codemirror/language": "^0.20.0",
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.0",
- "@lezer/common": "^0.16.0"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/language": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
- "integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
- "dependencies": {
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.0",
- "@lezer/common": "^0.16.0",
- "@lezer/highlight": "^0.16.0",
- "@lezer/lr": "^0.16.0",
- "style-mod": "^4.0.0"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/lint": {
- "version": "0.20.3",
- "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz",
- "integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==",
- "dependencies": {
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.2",
- "crelt": "^1.0.5"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/search": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz",
- "integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==",
- "dependencies": {
- "@codemirror/state": "^0.20.0",
- "@codemirror/view": "^0.20.0",
- "crelt": "^1.0.5"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/state": {
- "version": "0.20.1",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
- "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ=="
- },
- "node_modules/@codemirror/basic-setup/node_modules/@codemirror/view": {
- "version": "0.20.7",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
- "integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
- "dependencies": {
- "@codemirror/state": "^0.20.0",
- "style-mod": "^4.0.0",
- "w3c-keyname": "^2.2.4"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@lezer/common": {
- "version": "0.16.1",
- "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
- "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA=="
- },
- "node_modules/@codemirror/basic-setup/node_modules/@lezer/highlight": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
- "integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
- "dependencies": {
- "@lezer/common": "^0.16.0"
- }
- },
- "node_modules/@codemirror/basic-setup/node_modules/@lezer/lr": {
- "version": "0.16.3",
- "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
- "integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
- "dependencies": {
- "@lezer/common": "^0.16.0"
- }
- },
"node_modules/@codemirror/commands": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz",
@@ -2077,65 +1968,6 @@
"@lezer/common": "^1.1.0"
}
},
- "node_modules/@codemirror/highlight": {
- "version": "0.19.8",
- "resolved": "https://registry.npmjs.org/@codemirror/highlight/-/highlight-0.19.8.tgz",
- "integrity": "sha512-v/lzuHjrYR8MN2mEJcUD6fHSTXXli9C1XGYpr+ElV6fLBIUhMTNKR3qThp611xuWfXfwDxeL7ppcbkM/MzPV3A==",
- "deprecated": "As of 0.20.0, this package has been split between @lezer/highlight and @codemirror/language",
- "dependencies": {
- "@codemirror/language": "^0.19.0",
- "@codemirror/rangeset": "^0.19.0",
- "@codemirror/state": "^0.19.3",
- "@codemirror/view": "^0.19.39",
- "@lezer/common": "^0.15.0",
- "style-mod": "^4.0.0"
- }
- },
- "node_modules/@codemirror/highlight/node_modules/@codemirror/language": {
- "version": "0.19.10",
- "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.19.10.tgz",
- "integrity": "sha512-yA0DZ3RYn2CqAAGW62VrU8c4YxscMQn45y/I9sjBlqB1e2OTQLg4CCkMBuMSLXk4xaqjlsgazeOQWaJQOKfV8Q==",
- "dependencies": {
- "@codemirror/state": "^0.19.0",
- "@codemirror/text": "^0.19.0",
- "@codemirror/view": "^0.19.0",
- "@lezer/common": "^0.15.5",
- "@lezer/lr": "^0.15.0"
- }
- },
- "node_modules/@codemirror/highlight/node_modules/@codemirror/state": {
- "version": "0.19.9",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz",
- "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==",
- "dependencies": {
- "@codemirror/text": "^0.19.0"
- }
- },
- "node_modules/@codemirror/highlight/node_modules/@codemirror/view": {
- "version": "0.19.48",
- "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.19.48.tgz",
- "integrity": "sha512-0eg7D2Nz4S8/caetCTz61rK0tkHI17V/d15Jy0kLOT8dTLGGNJUponDnW28h2B6bERmPlVHKh8MJIr5OCp1nGw==",
- "dependencies": {
- "@codemirror/rangeset": "^0.19.5",
- "@codemirror/state": "^0.19.3",
- "@codemirror/text": "^0.19.0",
- "style-mod": "^4.0.0",
- "w3c-keyname": "^2.2.4"
- }
- },
- "node_modules/@codemirror/highlight/node_modules/@lezer/common": {
- "version": "0.15.12",
- "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.15.12.tgz",
- "integrity": "sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig=="
- },
- "node_modules/@codemirror/highlight/node_modules/@lezer/lr": {
- "version": "0.15.8",
- "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.15.8.tgz",
- "integrity": "sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg==",
- "dependencies": {
- "@lezer/common": "^0.15.0"
- }
- },
"node_modules/@codemirror/language": {
"version": "6.11.3",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
@@ -2159,23 +1991,6 @@
"crelt": "^1.0.5"
}
},
- "node_modules/@codemirror/rangeset": {
- "version": "0.19.9",
- "resolved": "https://registry.npmjs.org/@codemirror/rangeset/-/rangeset-0.19.9.tgz",
- "integrity": "sha512-V8YUuOvK+ew87Xem+71nKcqu1SXd5QROMRLMS/ljT5/3MCxtgrRie1Cvild0G/Z2f1fpWxzX78V0U4jjXBorBQ==",
- "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state",
- "dependencies": {
- "@codemirror/state": "^0.19.0"
- }
- },
- "node_modules/@codemirror/rangeset/node_modules/@codemirror/state": {
- "version": "0.19.9",
- "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.19.9.tgz",
- "integrity": "sha512-psOzDolKTZkx4CgUqhBQ8T8gBc0xN5z4gzed109aF6x7D7umpDRoimacI/O6d9UGuyl4eYuDCZmDFr2Rq7aGOw==",
- "dependencies": {
- "@codemirror/text": "^0.19.0"
- }
- },
"node_modules/@codemirror/search": {
"version": "6.5.11",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
@@ -2194,12 +2009,6 @@
"@marijn/find-cluster-break": "^1.0.0"
}
},
- "node_modules/@codemirror/text": {
- "version": "0.19.6",
- "resolved": "https://registry.npmjs.org/@codemirror/text/-/text-0.19.6.tgz",
- "integrity": "sha512-T9jnREMIygx+TPC1bOuepz18maGq/92q2a+n4qTqObKwvNMg+8cMTslb8yxeEDEq7S3kpgGWxgO1UWbQRij0dA==",
- "deprecated": "As of 0.20.0, this package has been merged into @codemirror/state"
- },
"node_modules/@codemirror/view": {
"version": "6.38.8",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz",
@@ -3716,12 +3525,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/trusted-types": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
- "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
- "optional": true
- },
"node_modules/@types/uglify-js": {
"version": "3.17.5",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.5.tgz",
@@ -15140,17 +14943,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/marked": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
- "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
- "bin": {
- "marked": "bin/marked.js"
- },
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/matchmediaquery": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.2.1.tgz",
@@ -15650,23 +15442,6 @@
"node": ">=4"
}
},
- "node_modules/monaco-editor": {
- "version": "0.55.1",
- "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
- "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
- "dependencies": {
- "dompurify": "3.2.7",
- "marked": "14.0.0"
- }
- },
- "node_modules/monaco-editor/node_modules/dompurify": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
- "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
- "optionalDependencies": {
- "@types/trusted-types": "^2.0.7"
- }
- },
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
diff --git a/package.json b/package.json
index 95c000ccc..08965b5f3 100644
--- a/package.json
+++ b/package.json
@@ -32,8 +32,6 @@
}
},
"dependencies": {
- "@codemirror/basic-setup": "^0.20.0",
- "@codemirror/highlight": "^0.19.8",
"@codemirror/language": "^6.11.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.8",
@@ -75,7 +73,6 @@
"lodash.defaultsdeep": "4.6.1",
"lodash.omit": "4.5.0",
"lodash.throttle": "4.0.1",
- "monaco-editor": "^0.55.1",
"omggif": "1.0.9",
"papaparse": "5.3.0",
"postcss-import": "^12.0.0",
From 2352b865db3341908f4db77170c984554f6822d8 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 14:49:37 +0000
Subject: [PATCH 04/22] i hate npm my build is taking too long im gonna push
---
package.json | 1 +
src/components/gui/gui.jsx | 116 +++++++++++++++++++-
src/components/gui/ob-codemirror-imports.js | 1 +
3 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index 08965b5f3..d96475cd8 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
}
},
"dependencies": {
+ "@codemirror/autocomplete": "^6.14.0",
"@codemirror/language": "^6.11.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.8",
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index f1d30666d..34bb10a41 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -85,10 +85,12 @@ function CMView({ theme, vm }) {
basicSetup,
HighlightStyle,
syntaxHighlighting,
- tags
+ tags,
+ autocompletion,
+ completeFromList
} = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
- // Define highlight rules using CSS vars
+ // Define hghlight rules using CSS vars
const scratchHighlight = HighlightStyle.define([
{ tag: tags.variableName, color: "var(--data-primary)" },
{ tag: tags.keyword, color: "var(--pen-primary)" },
@@ -109,6 +111,112 @@ function CMView({ theme, vm }) {
}
});
+ // NanoScript autocomplete suggestions
+ const nanoScriptCompletions = [
+ // Control flow keywords
+ { label: "when green flag clicked", type: "keyword" },
+ { label: "when key pressed", type: "keyword" },
+ { label: "when this sprite clicked", type: "keyword" },
+ { label: "when I start as a clone", type: "keyword" },
+ { label: "forever", type: "keyword" },
+ { label: "repeat", type: "keyword" },
+ { label: "if", type: "keyword" },
+ { label: "else", type: "keyword" },
+ { label: "end", type: "keyword" },
+ { label: "wait", type: "keyword" },
+ { label: "stop", type: "keyword" },
+
+ // Motion blocks
+ { label: "move", type: "function" },
+ { label: "turn right", type: "function" },
+ { label: "turn left", type: "function" },
+ { label: "go to", type: "function" },
+ { label: "glide to", type: "function" },
+ { label: "point in direction", type: "function" },
+ { label: "point towards", type: "function" },
+ { label: "change x by", type: "function" },
+ { label: "set x to", type: "function" },
+ { label: "change y by", type: "function" },
+ { label: "set y to", type: "function" },
+
+ // Looks blocks
+ { label: "say", type: "function" },
+ { label: "think", type: "function" },
+ { label: "show", type: "function" },
+ { label: "hide", type: "function" },
+ { label: "switch costume to", type: "function" },
+ { label: "next costume", type: "function" },
+ { label: "change size by", type: "function" },
+ { label: "set size to", type: "function" },
+ { label: "change color effect by", type: "function" },
+ { label: "set color effect to", type: "function" },
+ { label: "clear graphic effects", type: "function" },
+
+ // Sound blocks
+ { label: "play sound", type: "function" },
+ { label: "stop all sounds", type: "function" },
+ { label: "change volume by", type: "function" },
+ { label: "set volume to", type: "function" },
+
+ // Events
+ { label: "broadcast", type: "function" },
+ { label: "broadcast and wait", type: "function" },
+ { label: "when I receive", type: "keyword" },
+
+ // Variables and lists
+ { label: "set variable to", type: "function" },
+ { label: "change variable by", type: "function" },
+ { label: "add to list", type: "function" },
+ { label: "delete from list", type: "function" },
+ { label: "insert into list", type: "function" },
+ { label: "replace list item", type: "function" },
+
+ // Operators
+ { label: "and", type: "operator" },
+ { label: "or", type: "operator" },
+ { label: "not", type: "operator" },
+ { label: "join", type: "function" },
+ { label: "letter of", type: "function" },
+ { label: "length of", type: "function" },
+ { label: "round", type: "function" },
+ { label: "abs of", type: "function" },
+ { label: "floor of", type: "function" },
+ { label: "ceiling of", type: "function" },
+ { label: "sqrt of", type: "function" },
+ { label: "sin of", type: "function" },
+ { label: "cos of", type: "function" },
+ { label: "tan of", type: "function" },
+ { label: "asin of", type: "function" },
+ { label: "acos of", type: "function" },
+ { label: "atan of", type: "function" },
+ { label: "pick random", type: "function" },
+
+ // Sensing blocks
+ { label: "touching", type: "function" },
+ { label: "touching color", type: "function" },
+ { label: "color is touching", type: "function" },
+ { label: "ask", type: "function" },
+ { label: "key pressed", type: "function" },
+ { label: "mouse down", type: "function" },
+ { label: "distance to", type: "function" },
+ ];
+
+ // Function to get completions with prefix matching
+ function scratchCompletions(context) {
+ const word = context.matchBefore(/\w*/);
+ if (!word || (word.from === word.to && !context.explicit)) {
+ return null;
+ }
+
+ return {
+ from: word.from,
+ options: nanoScriptCompletions.filter(option =>
+ option.label.toLowerCase().startsWith(word.text.toLowerCase())
+ ),
+ validFor: /\w*/
+ };
+ }
+
if (!el.current || disposed) return;
const cmTheme = EditorView.theme({
@@ -132,6 +240,9 @@ function CMView({ theme, vm }) {
".cm-gutters": {
backgroundColor: "var(--ui-tertiary)",
borderRight: "1px solid var(--ui-black-transparent)"
+ },
+ ".cm-completionLabel": {
+ fontSize: "13px"
}
}, { dark: theme === Theme.dark });
@@ -145,6 +256,7 @@ end`,
basicSetup,
scratchSyntax,
syntaxHighlighting(scratchHighlight),
+ autocompletion({ override: [scratchCompletions] }),
cmTheme
],
parent: el.current
diff --git a/src/components/gui/ob-codemirror-imports.js b/src/components/gui/ob-codemirror-imports.js
index 7dc5525d5..674e92bbe 100644
--- a/src/components/gui/ob-codemirror-imports.js
+++ b/src/components/gui/ob-codemirror-imports.js
@@ -2,3 +2,4 @@ export { EditorView } from "@codemirror/view";
export { basicSetup } from "codemirror";
export { LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle } from "@codemirror/language";
export { styleTags, tags } from "@lezer/highlight";
+export { autocompletion, completeFromList } from "@codemirror/autocomplete";
From 98bd2d06e0dfd829cb3eee0c5c1214948d8583ad Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 15:06:10 +0000
Subject: [PATCH 05/22] hi
---
src/components/gui/gui.jsx | 2 ++
src/components/gui/nanoscriptIcon.svg | 1 +
2 files changed, 3 insertions(+)
create mode 100644 src/components/gui/nanoscriptIcon.svg
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 34bb10a41..084368a19 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -41,6 +41,8 @@ import TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx
import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
import {Theme} from '../../lib/themes';
+import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';
+import nanoscriptIcon from './nanoscriptIcon.svg';
import {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober';
diff --git a/src/components/gui/nanoscriptIcon.svg b/src/components/gui/nanoscriptIcon.svg
new file mode 100644
index 000000000..13835fda1
--- /dev/null
+++ b/src/components/gui/nanoscriptIcon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
From 8205ddf031f516be4773cef8d370207650ba55b4 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 15:09:57 +0000
Subject: [PATCH 06/22] package lock
---
package-lock.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/package-lock.json b/package-lock.json
index 9d01c6c9e..d188a5e16 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "3.2.37",
"license": "GPL-3.0",
"dependencies": {
+ "@codemirror/autocomplete": "^6.14.0",
"@codemirror/language": "^6.11.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.8",
From 97cc21a6504a4c92129532d37d2595e534ca7b2f Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 15:17:47 +0000
Subject: [PATCH 07/22] fixup
---
src/components/blocks/blocks.css | 4 +++
src/components/gui/gui.css | 27 +++++++++++++++++++
src/components/gui/gui.jsx | 6 ++---
.../toggle-buttons/toggle-buttons.jsx | 4 +--
4 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css
index 583f587f7..99a8e1a79 100644
--- a/src/components/blocks/blocks.css
+++ b/src/components/blocks/blocks.css
@@ -97,3 +97,7 @@
min-width: auto;
min-height: auto;
}
+
+.blocks :global(.blocklyZoom) {
+ transform: scale(0) !important;
+}
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 51c8e95a5..c33fd9d06 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -359,4 +359,31 @@ $fade-out-distance: 15px;
.button-row {
background: $ui-white;
border-radius: $form-radius;
+}
+
+.sidebar {
+ background: $ui-white;
+ padding: $space;
+ width: 20rem;
+ color: $text-primary;
+ user-select: none;
+ font-size: 0.9rem;
+ overflow: auto;
+}
+
+.sidebar h2 {
+ font-size: 1rem;
+}
+
+.codemirror {
+ --space: $space;
+ overflow: hidden;
+}
+
+.button {
+ appearance: none;
+ background: transparent;
+ padding: 6px;
+ border: $ui-black-transparent 1px solid;
+ border-radius: $space;
}
\ No newline at end of file
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 084368a19..06160cb4f 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -42,7 +42,7 @@ import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../.
import {resolveStageSize} from '../../lib/screen-utils';
import {Theme} from '../../lib/themes';
import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';
-import nanoscriptIcon from './nanoscriptIcon.svg';
+import nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg';
import {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober';
@@ -116,7 +116,7 @@ function CMView({ theme, vm }) {
// NanoScript autocomplete suggestions
const nanoScriptCompletions = [
// Control flow keywords
- { label: "when green flag clicked", type: "keyword" },
+ { label: "when flag clicked", type: "keyword" },
{ label: "when key pressed", type: "keyword" },
{ label: "when this sprite clicked", type: "keyword" },
{ label: "when I start as a clone", type: "keyword" },
@@ -249,7 +249,7 @@ function CMView({ theme, vm }) {
}, { dark: theme === Theme.dark });
editorRef.current = new EditorView({
- doc: `when green flag clicked
+ doc: `when flag clicked
say "Hello World!"
repeat 10
say (join "hi " "there")
diff --git a/src/components/toggle-buttons/toggle-buttons.jsx b/src/components/toggle-buttons/toggle-buttons.jsx
index ab6f35229..473d38cc0 100644
--- a/src/components/toggle-buttons/toggle-buttons.jsx
+++ b/src/components/toggle-buttons/toggle-buttons.jsx
@@ -25,12 +25,12 @@ const ToggleButtons = ({buttons, className, disabled}) => (
onClick={button.handleClick}
disabled={disabled}
>
-
+ />}
))}
From b76e7cd0d104c44dac4663c1934363b5f9d14ea2 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 16:04:52 +0000
Subject: [PATCH 08/22] var support
---
src/components/gui/gui.css | 17 ++-
src/components/gui/gui.jsx | 264 +++++++++++++++++++++++++++++++++++--
2 files changed, 271 insertions(+), 10 deletions(-)
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index c33fd9d06..1d9e42f6a 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -371,7 +371,7 @@ $fade-out-distance: 15px;
overflow: auto;
}
-.sidebar h2 {
+.sidebar h2, .sidebar h3 {
font-size: 1rem;
}
@@ -386,4 +386,19 @@ $fade-out-distance: 15px;
padding: 6px;
border: $ui-black-transparent 1px solid;
border-radius: $space;
+}
+
+.vars-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.var-item {
+ font-family: monospace;
+ color: $data-primary;
+ appearance: none;
+ border: none;
+ padding: 0;
+ text-align: left;
+ background: transparent;
}
\ No newline at end of file
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 06160cb4f..d7aa70cac 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -41,7 +41,9 @@ import TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx
import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
import {Theme} from '../../lib/themes';
+import AddonHooks from '../../addons/hooks.js';
import ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';
+import Prompt from '../../containers/prompt.jsx';
import nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg';
import {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober';
@@ -77,6 +79,11 @@ const fullscreenBackgroundColor = getFullscreenBackgroundColor();
function CMView({ theme, vm }) {
const el = React.useRef(null);
const editorRef = React.useRef(null);
+ const variableRef = React.useRef([]);
+ const listsRef = React.useRef([]);
+ const spriteOnlyVariablesRef = React.useRef([]);
+ const spriteOnlyListsRef = React.useRef([]);
+ const [, setVarsState] = React.useState([]); // used to trigger renders
React.useEffect(() => {
let disposed = false;
@@ -103,18 +110,35 @@ function CMView({ theme, vm }) {
]);
const { StreamLanguage } = await import("@codemirror/language");
+ // helper to escape regex
+ const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const scratchSyntax = StreamLanguage.define({
token(stream) {
+ // Keywords
if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
+ // Operators
if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
+ // Functions
if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
+
+ // Try to match variable or list names (take current refs)
+ const names = (variableRef.current || []).concat(listsRef.current || []);
+ if (names && names.length) {
+ // sort by length to match longest first
+ const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
+ const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
+ const m = stream.match(rx, true);
+ if (m) {
+ return "variableName";
+ }
+ }
stream.next();
return "variable";
}
});
- // NanoScript autocomplete suggestions
- const nanoScriptCompletions = [
+ // NanoScript autocomplete suggestions (static)
+ const staticCompletions = [
// Control flow keywords
{ label: "when flag clicked", type: "keyword" },
{ label: "when key pressed", type: "keyword" },
@@ -203,16 +227,20 @@ function CMView({ theme, vm }) {
{ label: "distance to", type: "function" },
];
- // Function to get completions with prefix matching
+ // Function to get completions with prefix matching (dynamic includes variables & lists)
function scratchCompletions(context) {
const word = context.matchBefore(/\w*/);
if (!word || (word.from === word.to && !context.explicit)) {
return null;
}
-
+
+ const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
+ const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
+ const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
+
return {
from: word.from,
- options: nanoScriptCompletions.filter(option =>
+ options: allOptions.filter(option =>
option.label.toLowerCase().startsWith(word.text.toLowerCase())
),
validFor: /\w*/
@@ -275,10 +303,228 @@ end`,
};
}, [theme]);
- return <>
-
Variables
- Make a Variable (NOT IMPLEMENTED)
-
>;
+ // Update variable/list refs from VM and listen for changes
+ React.useEffect(() => {
+ if (!vm) return undefined;
+
+ function updateVarsLists () {
+ try {
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ if (!editing) {
+ variableRef.current = [];
+ listsRef.current = [];
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ setVarsState([]);
+ return;
+ }
+
+ const isStage = editing.isStage;
+ let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
+ let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
+ let stageVars = [];
+ let stageLists = [];
+
+ // Get stage variables
+ const stage = vm.runtime.getTargetForStage();
+ if (stage) {
+ stageVars = stage.getAllVariableNamesInScopeByType('') || [];
+ stageLists = stage.getAllVariableNamesInScopeByType('list') || [];
+ }
+
+ // For stage: only show stage variables
+ // For sprite: show stage variables in "For all sprites", sprite-only in "For this sprite only"
+ if (isStage) {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ } else {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = spriteVars;
+ spriteOnlyListsRef.current = spriteLists;
+ }
+
+ // trigger render
+ setVarsState(variableRef.current.slice());
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ updateVarsLists();
+ vm.on('targetsUpdate', updateVarsLists);
+ vm.on('PROJECT_CHANGED', updateVarsLists);
+ return () => {
+ vm.off('targetsUpdate', updateVarsLists);
+ vm.off('PROJECT_CHANGED', updateVarsLists);
+ };
+ }, [vm]);
+
+ // handlers for make variable/list and inserting into editor
+ const [promptProps, setPromptProps] = React.useState(null);
+
+ const makeVariable = (type = '') => {
+ if (!vm) {
+ alert('VM not available');
+ return;
+ }
+
+ // Determine editing target and stage status
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ const isStage = editing && editing.isStage;
+
+ // Compute props for Prompt component similar to Blocks.handlePromptStart
+ const title = type === 'list' ? 'Make a List' : 'Make a Variable';
+ const varTypeConst = type === 'list' ? 'list' : '';
+ const showListMessage = type === 'list';
+ const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);
+
+ setPromptProps({
+ defaultValue: '',
+ isStage: !!(editing && editing.isStage),
+ showListMessage,
+ label: type === 'list' ? 'List name' : 'Variable name',
+ showCloudOption,
+ showVariableOptions: true,
+ title,
+ varType: varTypeConst
+ });
+ };
+
+ const insertIntoEditor = name => {
+ if (!editorRef.current) return;
+ try {
+ const view = editorRef.current;
+ const pos = view.state.selection.main.head;
+ view.dispatch({changes: {from: pos, insert: name}});
+ view.focus();
+ } catch (e) {
+ // ignore
+ }
+ };
+
+ const handlePromptCancel = () => setPromptProps(null);
+ const handlePromptOk = (input, variableOptions) => {
+ try {
+ const varType = (promptProps && promptProps.varType) || '';
+ let allVarNames = [];
+ if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {
+ try {
+ allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];
+ } catch (e) {
+ allVarNames = [];
+ }
+ }
+ const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());
+ if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {
+ try {
+ const stage = vm.runtime.getTargetForStage();
+ if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {
+ const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];
+ for (const s of stageVars) {
+ if (!allVarNames.includes(s)) allVarNames.push(s);
+ }
+ }
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ const ws = AddonHooks.blocklyWorkspace;
+ const isLocal = variableOptions && variableOptions.scope === 'local';
+ const isCloud = !!(variableOptions && variableOptions.isCloud);
+ if (ws && typeof ws.createVariable === 'function') {
+ try {
+ ws.createVariable(input, varType, null, !!isLocal, !!isCloud);
+ } catch (e) {
+ // ignore
+ }
+ }
+ } finally {
+ setPromptProps(null);
+ }
+ };
+
+ return <>
+ {promptProps ? (
+
+ ) : null}
+
+
Variables
+
+
For all sprites
+
+ {(variableRef.current || []).map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyVariablesRef.current.map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ >
+ )}
+
+ makeVariable('')}>Make a Variable
+
+
+
Lists
+
+
For all sprites
+
+ {(listsRef.current || []).map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyListsRef.current.map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ >
+ )}
+
+ makeVariable('list')}>Make a List
+
+
+
+ >;
}
const GUIComponent = props => {
From 3795b52dec842d9c134d374576230d8e7b04269c Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 16:26:21 +0000
Subject: [PATCH 09/22] bamboozlement
Co-authored-by: supervoidcoder
---
src/components/gui/gui.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index d7aa70cac..e872991fb 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -179,7 +179,7 @@ function CMView({ theme, vm }) {
{ label: "clear graphic effects", type: "function" },
// Sound blocks
- { label: "play sound", type: "function" },
+ { label: "start sound", type: "function" },
{ label: "stop all sounds", type: "function" },
{ label: "change volume by", type: "function" },
{ label: "set volume to", type: "function" },
From 6245a651ed1402ec0cdbee73430d777c0cbd3c5b Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 17:17:32 +0000
Subject: [PATCH 10/22] nanoscript refactor
Co-authored-by: supervoidcoder
---
src/components/gui/gui.css | 42 --
src/components/gui/gui.jsx | 455 +----------------
src/components/nanoscript/nanoscript.css | 0
src/components/nanoscript/nanoscript.jsx | 419 ++++++++++++++++
.../nanoscript-editor.css | 44 ++
.../nanoscript-editor.jsx | 466 ++++++++++++++++++
.../ob-codemirror-imports.js | 5 +
src/containers/gui.jsx | 1 +
8 files changed, 938 insertions(+), 494 deletions(-)
create mode 100644 src/components/nanoscript/nanoscript.css
create mode 100644 src/components/nanoscript/nanoscript.jsx
create mode 100644 src/components/ob-nanoscript-editor/nanoscript-editor.css
create mode 100644 src/components/ob-nanoscript-editor/nanoscript-editor.jsx
create mode 100644 src/components/ob-nanoscript-editor/ob-codemirror-imports.js
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index 1d9e42f6a..51c8e95a5 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -359,46 +359,4 @@ $fade-out-distance: 15px;
.button-row {
background: $ui-white;
border-radius: $form-radius;
-}
-
-.sidebar {
- background: $ui-white;
- padding: $space;
- width: 20rem;
- color: $text-primary;
- user-select: none;
- font-size: 0.9rem;
- overflow: auto;
-}
-
-.sidebar h2, .sidebar h3 {
- font-size: 1rem;
-}
-
-.codemirror {
- --space: $space;
- overflow: hidden;
-}
-
-.button {
- appearance: none;
- background: transparent;
- padding: 6px;
- border: $ui-black-transparent 1px solid;
- border-radius: $space;
-}
-
-.vars-list {
- display: flex;
- flex-direction: column;
-}
-
-.var-item {
- font-family: monospace;
- color: $data-primary;
- appearance: none;
- border: none;
- padding: 0;
- text-align: left;
- background: transparent;
}
\ No newline at end of file
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index e872991fb..167ccf3db 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -8,6 +8,7 @@ import MediaQuery from 'react-responsive';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import tabStyles from 'react-tabs/style/react-tabs.css';
import VM from 'scratch-vm';
+import CMView from './nanoscript.jsx';
import Blocks from '../../containers/blocks.jsx';
import CostumeTab from '../../containers/costume-tab.jsx';
@@ -37,6 +38,7 @@ import TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx
import TWFontsModal from '../../containers/tw-fonts-modal.jsx';
import TWUnknownPlatformModal from '../../containers/tw-unknown-platform-modal.jsx';
import TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx';
+import NanoscriptEditor from '../ob-nanoscript-editor/nanoscript-editor.jsx';
import {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';
import {resolveStageSize} from '../../lib/screen-utils';
@@ -76,457 +78,6 @@ const getFullscreenBackgroundColor = () => {
const fullscreenBackgroundColor = getFullscreenBackgroundColor();
-function CMView({ theme, vm }) {
- const el = React.useRef(null);
- const editorRef = React.useRef(null);
- const variableRef = React.useRef([]);
- const listsRef = React.useRef([]);
- const spriteOnlyVariablesRef = React.useRef([]);
- const spriteOnlyListsRef = React.useRef([]);
- const [, setVarsState] = React.useState([]); // used to trigger renders
-
- React.useEffect(() => {
- let disposed = false;
-
- async function loadEditor() {
- const {
- EditorView,
- basicSetup,
- HighlightStyle,
- syntaxHighlighting,
- tags,
- autocompletion,
- completeFromList
- } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
-
- // Define hghlight rules using CSS vars
- const scratchHighlight = HighlightStyle.define([
- { tag: tags.variableName, color: "var(--data-primary)" },
- { tag: tags.keyword, color: "var(--pen-primary)" },
- { tag: tags.string, color: "var(--cm-string)" },
- { tag: tags.number, color: "var(--cm-number)" },
- { tag: tags.function, color: "var(--cm-function)" },
- { tag: tags.operator, color: "var(--cm-operator)" }
- ]);
-
- const { StreamLanguage } = await import("@codemirror/language");
- // helper to escape regex
- const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- const scratchSyntax = StreamLanguage.define({
- token(stream) {
- // Keywords
- if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
- // Operators
- if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
- // Functions
- if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
-
- // Try to match variable or list names (take current refs)
- const names = (variableRef.current || []).concat(listsRef.current || []);
- if (names && names.length) {
- // sort by length to match longest first
- const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
- const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
- const m = stream.match(rx, true);
- if (m) {
- return "variableName";
- }
- }
- stream.next();
- return "variable";
- }
- });
-
- // NanoScript autocomplete suggestions (static)
- const staticCompletions = [
- // Control flow keywords
- { label: "when flag clicked", type: "keyword" },
- { label: "when key pressed", type: "keyword" },
- { label: "when this sprite clicked", type: "keyword" },
- { label: "when I start as a clone", type: "keyword" },
- { label: "forever", type: "keyword" },
- { label: "repeat", type: "keyword" },
- { label: "if", type: "keyword" },
- { label: "else", type: "keyword" },
- { label: "end", type: "keyword" },
- { label: "wait", type: "keyword" },
- { label: "stop", type: "keyword" },
-
- // Motion blocks
- { label: "move", type: "function" },
- { label: "turn right", type: "function" },
- { label: "turn left", type: "function" },
- { label: "go to", type: "function" },
- { label: "glide to", type: "function" },
- { label: "point in direction", type: "function" },
- { label: "point towards", type: "function" },
- { label: "change x by", type: "function" },
- { label: "set x to", type: "function" },
- { label: "change y by", type: "function" },
- { label: "set y to", type: "function" },
-
- // Looks blocks
- { label: "say", type: "function" },
- { label: "think", type: "function" },
- { label: "show", type: "function" },
- { label: "hide", type: "function" },
- { label: "switch costume to", type: "function" },
- { label: "next costume", type: "function" },
- { label: "change size by", type: "function" },
- { label: "set size to", type: "function" },
- { label: "change color effect by", type: "function" },
- { label: "set color effect to", type: "function" },
- { label: "clear graphic effects", type: "function" },
-
- // Sound blocks
- { label: "start sound", type: "function" },
- { label: "stop all sounds", type: "function" },
- { label: "change volume by", type: "function" },
- { label: "set volume to", type: "function" },
-
- // Events
- { label: "broadcast", type: "function" },
- { label: "broadcast and wait", type: "function" },
- { label: "when I receive", type: "keyword" },
-
- // Variables and lists
- { label: "set variable to", type: "function" },
- { label: "change variable by", type: "function" },
- { label: "add to list", type: "function" },
- { label: "delete from list", type: "function" },
- { label: "insert into list", type: "function" },
- { label: "replace list item", type: "function" },
-
- // Operators
- { label: "and", type: "operator" },
- { label: "or", type: "operator" },
- { label: "not", type: "operator" },
- { label: "join", type: "function" },
- { label: "letter of", type: "function" },
- { label: "length of", type: "function" },
- { label: "round", type: "function" },
- { label: "abs of", type: "function" },
- { label: "floor of", type: "function" },
- { label: "ceiling of", type: "function" },
- { label: "sqrt of", type: "function" },
- { label: "sin of", type: "function" },
- { label: "cos of", type: "function" },
- { label: "tan of", type: "function" },
- { label: "asin of", type: "function" },
- { label: "acos of", type: "function" },
- { label: "atan of", type: "function" },
- { label: "pick random", type: "function" },
-
- // Sensing blocks
- { label: "touching", type: "function" },
- { label: "touching color", type: "function" },
- { label: "color is touching", type: "function" },
- { label: "ask", type: "function" },
- { label: "key pressed", type: "function" },
- { label: "mouse down", type: "function" },
- { label: "distance to", type: "function" },
- ];
-
- // Function to get completions with prefix matching (dynamic includes variables & lists)
- function scratchCompletions(context) {
- const word = context.matchBefore(/\w*/);
- if (!word || (word.from === word.to && !context.explicit)) {
- return null;
- }
-
- const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
- const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
- const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
-
- return {
- from: word.from,
- options: allOptions.filter(option =>
- option.label.toLowerCase().startsWith(word.text.toLowerCase())
- ),
- validFor: /\w*/
- };
- }
-
- if (!el.current || disposed) return;
-
- const cmTheme = EditorView.theme({
- "&": {
- backgroundColor: "var(--ui-white)",
- color: "var(--text-primary)",
- height: "100%",
- borderTopRightRadius: "var(--space)",
- borderBottomRightRadius: "var(--space)",
- border: "1px solid var(--ui-black-transparent)",
- height: "100%",
- },
- ".cm-scroller": {
- maxHeight: "100%",
- overflow: "auto"
- },
- ".cm-content": { caretColor: "var(--looks-secondary)" },
- ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
- ".cm-focused": { outline: "none" },
- ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
- ".cm-gutters": {
- backgroundColor: "var(--ui-tertiary)",
- borderRight: "1px solid var(--ui-black-transparent)"
- },
- ".cm-completionLabel": {
- fontSize: "13px"
- }
- }, { dark: theme === Theme.dark });
-
- editorRef.current = new EditorView({
- doc: `when flag clicked
-say "Hello World!"
-repeat 10
- say (join "hi " "there")
-end`,
- extensions: [
- basicSetup,
- scratchSyntax,
- syntaxHighlighting(scratchHighlight),
- autocompletion({ override: [scratchCompletions] }),
- cmTheme
- ],
- parent: el.current
- });
- }
-
- loadEditor();
-
- return () => {
- disposed = true;
- if (editorRef.current) {
- editorRef.current.destroy();
- }
- };
- }, [theme]);
-
- // Update variable/list refs from VM and listen for changes
- React.useEffect(() => {
- if (!vm) return undefined;
-
- function updateVarsLists () {
- try {
- const editing = vm.editingTarget || vm.runtime.getTargetForStage();
- if (!editing) {
- variableRef.current = [];
- listsRef.current = [];
- spriteOnlyVariablesRef.current = [];
- spriteOnlyListsRef.current = [];
- setVarsState([]);
- return;
- }
-
- const isStage = editing.isStage;
- let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
- let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
- let stageVars = [];
- let stageLists = [];
-
- // Get stage variables
- const stage = vm.runtime.getTargetForStage();
- if (stage) {
- stageVars = stage.getAllVariableNamesInScopeByType('') || [];
- stageLists = stage.getAllVariableNamesInScopeByType('list') || [];
- }
-
- // For stage: only show stage variables
- // For sprite: show stage variables in "For all sprites", sprite-only in "For this sprite only"
- if (isStage) {
- variableRef.current = stageVars;
- listsRef.current = stageLists;
- spriteOnlyVariablesRef.current = [];
- spriteOnlyListsRef.current = [];
- } else {
- variableRef.current = stageVars;
- listsRef.current = stageLists;
- spriteOnlyVariablesRef.current = spriteVars;
- spriteOnlyListsRef.current = spriteLists;
- }
-
- // trigger render
- setVarsState(variableRef.current.slice());
- } catch (e) {
- // ignore
- }
- }
-
- updateVarsLists();
- vm.on('targetsUpdate', updateVarsLists);
- vm.on('PROJECT_CHANGED', updateVarsLists);
- return () => {
- vm.off('targetsUpdate', updateVarsLists);
- vm.off('PROJECT_CHANGED', updateVarsLists);
- };
- }, [vm]);
-
- // handlers for make variable/list and inserting into editor
- const [promptProps, setPromptProps] = React.useState(null);
-
- const makeVariable = (type = '') => {
- if (!vm) {
- alert('VM not available');
- return;
- }
-
- // Determine editing target and stage status
- const editing = vm.editingTarget || vm.runtime.getTargetForStage();
- const isStage = editing && editing.isStage;
-
- // Compute props for Prompt component similar to Blocks.handlePromptStart
- const title = type === 'list' ? 'Make a List' : 'Make a Variable';
- const varTypeConst = type === 'list' ? 'list' : '';
- const showListMessage = type === 'list';
- const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);
-
- setPromptProps({
- defaultValue: '',
- isStage: !!(editing && editing.isStage),
- showListMessage,
- label: type === 'list' ? 'List name' : 'Variable name',
- showCloudOption,
- showVariableOptions: true,
- title,
- varType: varTypeConst
- });
- };
-
- const insertIntoEditor = name => {
- if (!editorRef.current) return;
- try {
- const view = editorRef.current;
- const pos = view.state.selection.main.head;
- view.dispatch({changes: {from: pos, insert: name}});
- view.focus();
- } catch (e) {
- // ignore
- }
- };
-
- const handlePromptCancel = () => setPromptProps(null);
- const handlePromptOk = (input, variableOptions) => {
- try {
- const varType = (promptProps && promptProps.varType) || '';
- let allVarNames = [];
- if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {
- try {
- allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];
- } catch (e) {
- allVarNames = [];
- }
- }
- const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());
- if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {
- try {
- const stage = vm.runtime.getTargetForStage();
- if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {
- const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];
- for (const s of stageVars) {
- if (!allVarNames.includes(s)) allVarNames.push(s);
- }
- }
- } catch (e) {
- // ignore
- }
- }
-
- const ws = AddonHooks.blocklyWorkspace;
- const isLocal = variableOptions && variableOptions.scope === 'local';
- const isCloud = !!(variableOptions && variableOptions.isCloud);
- if (ws && typeof ws.createVariable === 'function') {
- try {
- ws.createVariable(input, varType, null, !!isLocal, !!isCloud);
- } catch (e) {
- // ignore
- }
- }
- } finally {
- setPromptProps(null);
- }
- };
-
- return <>
- {promptProps ? (
-
- ) : null}
-
-
Variables
-
-
For all sprites
-
- {(variableRef.current || []).map(v => (
- insertIntoEditor(v)}
- >{v}
- ))}
-
- {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (
- <>
-
For this sprite only
-
- {spriteOnlyVariablesRef.current.map(v => (
- insertIntoEditor(v)}
- >{v}
- ))}
-
- >
- )}
-
- makeVariable('')}>Make a Variable
-
-
-
Lists
-
-
For all sprites
-
- {(listsRef.current || []).map(l => (
- insertIntoEditor(l)}
- >{l}
- ))}
-
- {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (
- <>
-
For this sprite only
-
- {spriteOnlyListsRef.current.map(l => (
- insertIntoEditor(l)}
- >{l}
- ))}
-
- >
- )}
-
- makeVariable('list')}>Make a List
-
-
-
- >;
-}
-
const GUIComponent = props => {
const {
accountNavOpen,
@@ -857,7 +408,7 @@ const GUIComponent = props => {
- {isNano ? blocksTabVisible && : <>
+ {isNano ? blocksTabVisible && : <>
{
+ let disposed = false;
+
+ async function loadEditor() {
+ const {
+ EditorView,
+ basicSetup,
+ HighlightStyle,
+ syntaxHighlighting,
+ tags,
+ autocompletion,
+ completeFromList
+ } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
+
+ // Define highlight rules using CSS vars
+ const scratchHighlight = HighlightStyle.define([
+ { tag: tags.variableName, color: "var(--data-primary)" },
+ { tag: tags.keyword, color: "var(--pen-primary)" },
+ { tag: tags.string, color: "var(--cm-string)" },
+ { tag: tags.number, color: "var(--cm-number)" },
+ { tag: tags.function, color: "var(--cm-function)" },
+ { tag: tags.operator, color: "var(--cm-operator)" }
+ ]);
+
+ const { StreamLanguage } = await import("@codemirror/language");
+ const escapeRegExp = s => s.replace(/[.*+?^${}()|[$$\$$/g, "\\$&");
+ const scratchSyntax = StreamLanguage.define({
+ token(stream) {
+ if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
+ if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
+ if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
+
+ const names = (variableRef.current || []).concat(listsRef.current || []);
+ if (names && names.length) {
+ const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
+ const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
+ const m = stream.match(rx, true);
+ if (m) return "variableName";
+ }
+ stream.next();
+ return "variable";
+ }
+ });
+
+ const staticCompletions = [
+ // Control flow keywords
+ { label: "when flag clicked", type: "keyword" },
+ { label: "when key pressed", type: "keyword" },
+ { label: "when this sprite clicked", type: "keyword" },
+ { label: "when I start as a clone", type: "keyword" },
+ { label: "forever", type: "keyword" },
+ { label: "repeat", type: "keyword" },
+ { label: "if", type: "keyword" },
+ { label: "else", type: "keyword" },
+ { label: "end", type: "keyword" },
+ { label: "wait", type: "keyword" },
+ { label: "stop", type: "keyword" },
+
+ // Motion blocks
+ { label: "move", type: "function" },
+ { label: "turn right", type: "function" },
+ { label: "turn left", type: "function" },
+ { label: "go to", type: "function" },
+ { label: "glide to", type: "function" },
+ { label: "point in direction", type: "function" },
+ { label: "point towards", type: "function" },
+ { label: "change x by", type: "function" },
+ { label: "set x to", type: "function" },
+ { label: "change y by", type: "function" },
+ { label: "set y to", type: "function" },
+
+ // Looks blocks
+ { label: "say", type: "function" },
+ { label: "think", type: "function" },
+ { label: "show", type: "function" },
+ { label: "hide", type: "function" },
+ { label: "switch costume to", type: "function" },
+ { label: "next costume", type: "function" },
+ { label: "change size by", type: "function" },
+ { label: "set size to", type: "function" },
+ { label: "change color effect by", type: "function" },
+ { label: "set color effect to", type: "function" },
+ { label: "clear graphic effects", type: "function" },
+
+ // Sound blocks
+ { label: "start sound", type: "function" },
+ { label: "stop all sounds", type: "function" },
+ { label: "change volume by", type: "function" },
+ { label: "set volume to", type: "function" },
+
+ // Events
+ { label: "broadcast", type: "function" },
+ { label: "broadcast and wait", type: "function" },
+ { label: "when I receive", type: "keyword" },
+
+ // Variables and lists
+ { label: "set variable to", type: "function" },
+ { label: "change variable by", type: "function" },
+ { label: "add to list", type: "function" },
+ { label: "delete from list", type: "function" },
+ { label: "insert into list", type: "function" },
+ { label: "replace list item", type: "function" },
+
+ // Operators
+ { label: "and", type: "operator" },
+ { label: "or", type: "operator" },
+ { label: "not", type: "operator" },
+ { label: "join", type: "function" },
+ { label: "letter of", type: "function" },
+ { label: "length of", type: "function" },
+ { label: "round", type: "function" },
+ { label: "abs of", type: "function" },
+ { label: "floor of", type: "function" },
+ { label: "ceiling of", type: "function" },
+ { label: "sqrt of", type: "function" },
+ { label: "sin of", type: "function" },
+ { label: "cos of", type: "function" },
+ { label: "tan of", type: "function" },
+ { label: "asin of", type: "function" },
+ { label: "acos of", type: "function" },
+ { label: "atan of", type: "function" },
+ { label: "pick random", type: "function" },
+
+ // Sensing blocks
+ { label: "touching", type: "function" },
+ { label: "touching color", type: "function" },
+ { label: "color is touching", type: "function" },
+ { label: "ask", type: "function" },
+ { label: "key pressed", type: "function" },
+ { label: "mouse down", type: "function" },
+ { label: "distance to", type: "function" },
+ ];
+
+ function scratchCompletions(context) {
+ const word = context.matchBefore(/\w*/);
+ if (!word || (word.from === word.to && !context.explicit)) return null;
+
+ const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
+ const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
+ const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
+
+ return {
+ from: word.from,
+ options: allOptions.filter(option =>
+ option.label.toLowerCase().startsWith(word.text.toLowerCase())
+ ),
+ validFor: /\w*/
+ };
+ }
+
+ if (!el.current || disposed) return;
+
+ const cmTheme = EditorView.theme({
+ "&": {
+ backgroundColor: "var(--ui-white)",
+ color: "var(--text-primary)",
+ height: "100%",
+ borderTopRightRadius: "var(--space)",
+ borderBottomRightRadius: "var(--space)",
+ border: "1px solid var(--ui-black-transparent)",
+ height: "100%",
+ },
+ ".cm-scroller": { maxHeight: "100%", overflow: "auto" },
+ ".cm-content": { caretColor: "var(--looks-secondary)" },
+ ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
+ ".cm-focused": { outline: "none" },
+ ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
+ ".cm-gutters": {
+ backgroundColor: "var(--ui-tertiary)",
+ borderRight: "1px solid var(--ui-black-transparent)"
+ },
+ ".cm-completionLabel": { fontSize: "13px" }
+ }, { dark: theme === Theme.dark });
+
+ editorRef.current = new EditorView({
+ doc: `when flag clicked\nsay "Hello World!"\nrepeat 10\n say (join "hi " "there")\nend`,
+ extensions: [
+ basicSetup,
+ scratchSyntax,
+ syntaxHighlighting(scratchHighlight),
+ autocompletion({ override: [scratchCompletions] }),
+ cmTheme
+ ],
+ parent: el.current
+ });
+ }
+
+ loadEditor();
+
+ return () => {
+ disposed = true;
+ if (editorRef.current) editorRef.current.destroy();
+ };
+ }, [theme]);
+
+ React.useEffect(() => {
+ if (!vm) return undefined;
+
+ function updateVarsLists() {
+ try {
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ if (!editing) {
+ variableRef.current = [];
+ listsRef.current = [];
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ setVarsState([]);
+ return;
+ }
+
+ const isStage = editing.isStage;
+ let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
+ let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
+ let stageVars = [];
+ let stageLists = [];
+
+ const stage = vm.runtime.getTargetForStage();
+ if (stage) {
+ stageVars = stage.getAllVariableNamesInScopeByType('') || [];
+ stageLists = stage.getAllVariableNamesInScopeByType('list') || [];
+ }
+
+ if (isStage) {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ } else {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = spriteVars;
+ spriteOnlyListsRef.current = spriteLists;
+ }
+
+ setVarsState(variableRef.current.slice());
+ } catch (e) {}
+ }
+
+ updateVarsLists();
+ vm.on('targetsUpdate', updateVarsLists);
+ vm.on('PROJECT_CHANGED', updateVarsLists);
+ return () => {
+ vm.off('targetsUpdate', updateVarsLists);
+ vm.off('PROJECT_CHANGED', updateVarsLists);
+ };
+ }, [vm]);
+
+ const [promptProps, setPromptProps] = React.useState(null);
+
+ const makeVariable = (type = '') => {
+ if (!vm) {
+ alert('VM not available');
+ return;
+ }
+
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ const isStage = editing && editing.isStage;
+
+ const title = type === 'list' ? 'Make a List' : 'Make a Variable';
+ const varTypeConst = type === 'list' ? 'list' : '';
+ const showListMessage = type === 'list';
+ const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);
+
+ setPromptProps({
+ defaultValue: '',
+ isStage: !!(editing && editing.isStage),
+ showListMessage,
+ label: type === 'list' ? 'List name' : 'Variable name',
+ showCloudOption,
+ showVariableOptions: true,
+ title,
+ varType: varTypeConst
+ });
+ };
+
+ const insertIntoEditor = name => {
+ if (!editorRef.current) return;
+ try {
+ const view = editorRef.current;
+ const pos = view.state.selection.main.head;
+ view.dispatch({changes: {from: pos, insert: name}});
+ view.focus();
+ } catch (e) {}
+ };
+
+ const handlePromptCancel = () => setPromptProps(null);
+ const handlePromptOk = (input, variableOptions) => {
+ try {
+ const varType = (promptProps && promptProps.varType) || '';
+ let allVarNames = [];
+ if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {
+ try {
+ allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];
+ } catch (e) {
+ allVarNames = [];
+ }
+ }
+ const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());
+ if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {
+ try {
+ const stage = vm.runtime.getTargetForStage();
+ if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {
+ const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];
+ for (const s of stageVars) {
+ if (!allVarNames.includes(s)) allVarNames.push(s);
+ }
+ }
+ } catch (e) {}
+ }
+
+ const ws = AddonHooks.blocklyWorkspace;
+ const isLocal = variableOptions && variableOptions.scope === 'local';
+ const isCloud = !!(variableOptions && variableOptions.isCloud);
+ if (ws && typeof ws.createVariable === 'function') {
+ try {
+ ws.createVariable(input, varType, null, !!isLocal, !!isCloud);
+ } catch (e) {}
+ }
+ } finally {
+ setPromptProps(null);
+ }
+ };
+
+ return (
+
+ {promptProps ? (
+
+ ) : null}
+
+
Variables
+
+
For all sprites
+
+ {(variableRef.current || []).map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyVariablesRef.current.map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ >
+ )}
+
+ makeVariable('')}>Make a Variable
+
+
+
Lists
+
+
For all sprites
+
+ {(listsRef.current || []).map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyListsRef.current.map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ >
+ )}
+
+ makeVariable('list')}>Make a List
+
+
+
+
+ );
+}
+
+export default CMView;
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.css b/src/components/ob-nanoscript-editor/nanoscript-editor.css
new file mode 100644
index 000000000..3af76fa76
--- /dev/null
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.css
@@ -0,0 +1,44 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
+.sidebar {
+ background: $ui-white;
+ padding: $space;
+ width: 20rem;
+ color: $text-primary;
+ user-select: none;
+ font-size: 0.9rem;
+ overflow: auto;
+}
+
+.sidebar h2, .sidebar h3 {
+ font-size: 1rem;
+}
+
+.codemirror {
+ --space: $space;
+ overflow: auto;
+}
+
+.button {
+ appearance: none;
+ background: transparent;
+ padding: 6px;
+ border: $ui-black-transparent 1px solid;
+ border-radius: $space;
+}
+
+.vars-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.var-item {
+ font-family: monospace;
+ color: $data-primary;
+ appearance: none;
+ border: none;
+ padding: 0;
+ text-align: left;
+ background: transparent;
+}
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
new file mode 100644
index 000000000..1de2b24b0
--- /dev/null
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
@@ -0,0 +1,466 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import VM from 'scratch-vm';
+import Prompt from '../../containers/prompt.jsx';
+import {Theme} from '../../lib/themes';
+import AddonHooks from '../../addons/hooks.js';
+
+import styles from './nanoscript-editor.css';
+
+function NanoscriptEditor({ theme, vm }) {
+ const el = React.useRef(null);
+ const editorRef = React.useRef(null);
+ const variableRef = React.useRef([]);
+ const listsRef = React.useRef([]);
+ const spriteOnlyVariablesRef = React.useRef([]);
+ const spriteOnlyListsRef = React.useRef([]);
+ const [, setVarsState] = React.useState([]); // used to trigger renders
+
+ React.useEffect(() => {
+ let disposed = false;
+
+ async function loadEditor() {
+ const {
+ EditorView,
+ basicSetup,
+ HighlightStyle,
+ syntaxHighlighting,
+ tags,
+ autocompletion,
+ completeFromList
+ } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
+
+ // Define hghlight rules using CSS vars
+ const scratchHighlight = HighlightStyle.define([
+ { tag: tags.variableName, color: "var(--data-primary)" },
+ { tag: tags.keyword, color: "var(--pen-primary)" },
+ { tag: tags.string, color: "var(--cm-string)" },
+ { tag: tags.number, color: "var(--cm-number)" },
+ { tag: tags.function, color: "var(--cm-function)" },
+ { tag: tags.operator, color: "var(--cm-operator)" }
+ ]);
+
+ const { StreamLanguage } = await import("@codemirror/language");
+ // helper to escape regex
+ const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const scratchSyntax = StreamLanguage.define({
+ token(stream) {
+ // Keywords
+ if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
+ // Operators
+ if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
+ // Functions
+ if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
+
+ // Try to match variable or list names (take current refs)
+ const names = (variableRef.current || []).concat(listsRef.current || []);
+ if (names && names.length) {
+ // sort by length to match longest first
+ const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
+ const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
+ const m = stream.match(rx, true);
+ if (m) {
+ return "variableName";
+ }
+ }
+ stream.next();
+ return "variable";
+ }
+ });
+
+ // NanoScript autocomplete suggestions (static)
+ const staticCompletions = [
+ // Control flow keywords
+ { label: "when flag clicked", type: "keyword" },
+ { label: "when key pressed", type: "keyword" },
+ { label: "when this sprite clicked", type: "keyword" },
+ { label: "when I start as a clone", type: "keyword" },
+ { label: "forever", type: "keyword" },
+ { label: "repeat", type: "keyword" },
+ { label: "if", type: "keyword" },
+ { label: "else", type: "keyword" },
+ { label: "end", type: "keyword" },
+ { label: "wait", type: "keyword" },
+ { label: "stop", type: "keyword" },
+
+ // Motion blocks
+ { label: "move", type: "function" },
+ { label: "turn right", type: "function" },
+ { label: "turn left", type: "function" },
+ { label: "go to", type: "function" },
+ { label: "glide to", type: "function" },
+ { label: "point in direction", type: "function" },
+ { label: "point towards", type: "function" },
+ { label: "change x by", type: "function" },
+ { label: "set x to", type: "function" },
+ { label: "change y by", type: "function" },
+ { label: "set y to", type: "function" },
+
+ // Looks blocks
+ { label: "say", type: "function" },
+ { label: "think", type: "function" },
+ { label: "show", type: "function" },
+ { label: "hide", type: "function" },
+ { label: "switch costume to", type: "function" },
+ { label: "next costume", type: "function" },
+ { label: "change size by", type: "function" },
+ { label: "set size to", type: "function" },
+ { label: "change color effect by", type: "function" },
+ { label: "set color effect to", type: "function" },
+ { label: "clear graphic effects", type: "function" },
+
+ // Sound blocks
+ { label: "play sound", type: "function" },
+ { label: "stop all sounds", type: "function" },
+ { label: "change volume by", type: "function" },
+ { label: "set volume to", type: "function" },
+
+ // Events
+ { label: "broadcast", type: "function" },
+ { label: "broadcast and wait", type: "function" },
+ { label: "when I receive", type: "keyword" },
+
+ // Variables and lists
+ { label: "set variable to", type: "function" },
+ { label: "change variable by", type: "function" },
+ { label: "add to list", type: "function" },
+ { label: "delete from list", type: "function" },
+ { label: "insert into list", type: "function" },
+ { label: "replace list item", type: "function" },
+
+ // Operators
+ { label: "and", type: "operator" },
+ { label: "or", type: "operator" },
+ { label: "not", type: "operator" },
+ { label: "join", type: "function" },
+ { label: "letter of", type: "function" },
+ { label: "length of", type: "function" },
+ { label: "round", type: "function" },
+ { label: "abs of", type: "function" },
+ { label: "floor of", type: "function" },
+ { label: "ceiling of", type: "function" },
+ { label: "sqrt of", type: "function" },
+ { label: "sin of", type: "function" },
+ { label: "cos of", type: "function" },
+ { label: "tan of", type: "function" },
+ { label: "asin of", type: "function" },
+ { label: "acos of", type: "function" },
+ { label: "atan of", type: "function" },
+ { label: "pick random", type: "function" },
+
+ // Sensing blocks
+ { label: "touching", type: "function" },
+ { label: "touching color", type: "function" },
+ { label: "color is touching", type: "function" },
+ { label: "ask", type: "function" },
+ { label: "key pressed", type: "function" },
+ { label: "mouse down", type: "function" },
+ { label: "distance to", type: "function" },
+ ];
+
+ // Function to get completions with prefix matching (dynamic includes variables & lists)
+ function scratchCompletions(context) {
+ const word = context.matchBefore(/\w*/);
+ if (!word || (word.from === word.to && !context.explicit)) {
+ return null;
+ }
+
+ const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
+ const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
+ const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
+
+ return {
+ from: word.from,
+ options: allOptions.filter(option =>
+ option.label.toLowerCase().startsWith(word.text.toLowerCase())
+ ),
+ validFor: /\w*/
+ };
+ }
+
+ if (!el.current || disposed) return;
+
+ const cmTheme = EditorView.theme({
+ "&": {
+ backgroundColor: "var(--ui-white)",
+ color: "var(--text-primary)",
+ height: "100%",
+ borderTopRightRadius: "var(--space)",
+ borderBottomRightRadius: "var(--space)",
+ border: "1px solid var(--ui-black-transparent)",
+ height: "100%",
+ },
+ ".cm-scroller": {
+ maxHeight: "100%",
+ overflow: "auto"
+ },
+ ".cm-content": { caretColor: "var(--looks-secondary)" },
+ ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
+ ".cm-focused": { outline: "none" },
+ ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
+ ".cm-gutters": {
+ backgroundColor: "var(--ui-tertiary)",
+ borderRight: "1px solid var(--ui-black-transparent)"
+ },
+ ".cm-completionLabel": {
+ fontSize: "13px"
+ }
+ }, { dark: theme === Theme.dark });
+
+ editorRef.current = new EditorView({
+ doc: `when flag clicked
+say "Hello World!"
+repeat 10
+ say (join "hi " "there")
+end`,
+ extensions: [
+ basicSetup,
+ scratchSyntax,
+ syntaxHighlighting(scratchHighlight),
+ autocompletion({ override: [scratchCompletions] }),
+ cmTheme
+ ],
+ parent: el.current
+ });
+ }
+
+ loadEditor();
+
+ return () => {
+ disposed = true;
+ if (editorRef.current) {
+ editorRef.current.destroy();
+ }
+ };
+ }, [theme]);
+
+ // Update variable/list refs from VM and listen for changes
+ React.useEffect(() => {
+ if (!vm) return undefined;
+
+ function updateVarsLists () {
+ try {
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ if (!editing) {
+ variableRef.current = [];
+ listsRef.current = [];
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ setVarsState([]);
+ return;
+ }
+
+ const isStage = editing.isStage;
+ let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
+ let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
+ let stageVars = [];
+ let stageLists = [];
+
+ // Get stage variables
+ const stage = vm.runtime.getTargetForStage();
+ if (stage) {
+ stageVars = stage.getAllVariableNamesInScopeByType('') || [];
+ stageLists = stage.getAllVariableNamesInScopeByType('list') || [];
+ }
+
+ // For stage: only show stage variables
+ // For sprite: show stage variables in "For all sprites", sprite-only in "For this sprite only"
+ if (isStage) {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = [];
+ spriteOnlyListsRef.current = [];
+ } else {
+ variableRef.current = stageVars;
+ listsRef.current = stageLists;
+ spriteOnlyVariablesRef.current = spriteVars;
+ spriteOnlyListsRef.current = spriteLists;
+ }
+
+ // trigger render
+ setVarsState(variableRef.current.slice());
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ updateVarsLists();
+ vm.on('targetsUpdate', updateVarsLists);
+ vm.on('PROJECT_CHANGED', updateVarsLists);
+ return () => {
+ vm.off('targetsUpdate', updateVarsLists);
+ vm.off('PROJECT_CHANGED', updateVarsLists);
+ };
+ }, [vm]);
+
+ // handlers for make variable/list and inserting into editor
+ const [promptProps, setPromptProps] = React.useState(null);
+
+ const makeVariable = (type = '') => {
+ if (!vm) {
+ alert('VM not available');
+ return;
+ }
+
+ // Determine editing target and stage status
+ const editing = vm.editingTarget || vm.runtime.getTargetForStage();
+ const isStage = editing && editing.isStage;
+
+ // Compute props for Prompt component similar to Blocks.handlePromptStart
+ const title = type === 'list' ? 'Make a List' : 'Make a Variable';
+ const varTypeConst = type === 'list' ? 'list' : '';
+ const showListMessage = type === 'list';
+ const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);
+
+ setPromptProps({
+ defaultValue: '',
+ isStage: !!(editing && editing.isStage),
+ showListMessage,
+ label: type === 'list' ? 'List name' : 'Variable name',
+ showCloudOption,
+ showVariableOptions: true,
+ title,
+ varType: varTypeConst
+ });
+ };
+
+ const insertIntoEditor = name => {
+ if (!editorRef.current) return;
+ try {
+ const view = editorRef.current;
+ const pos = view.state.selection.main.head;
+ view.dispatch({changes: {from: pos, insert: name}});
+ view.focus();
+ } catch (e) {
+ // ignore
+ }
+ };
+
+ const handlePromptCancel = () => setPromptProps(null);
+ const handlePromptOk = (input, variableOptions) => {
+ try {
+ const varType = (promptProps && promptProps.varType) || '';
+ let allVarNames = [];
+ if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {
+ try {
+ allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];
+ } catch (e) {
+ allVarNames = [];
+ }
+ }
+ const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());
+ if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {
+ try {
+ const stage = vm.runtime.getTargetForStage();
+ if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {
+ const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];
+ for (const s of stageVars) {
+ if (!allVarNames.includes(s)) allVarNames.push(s);
+ }
+ }
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ const ws = AddonHooks.blocklyWorkspace;
+ const isLocal = variableOptions && variableOptions.scope === 'local';
+ const isCloud = !!(variableOptions && variableOptions.isCloud);
+ if (ws && typeof ws.createVariable === 'function') {
+ try {
+ ws.createVariable(input, varType, null, !!isLocal, !!isCloud);
+ } catch (e) {
+ // ignore
+ }
+ }
+ } finally {
+ setPromptProps(null);
+ }
+ };
+
+ return
+ {promptProps ? (
+
+ ) : null}
+
+
Variables
+
+
For all sprites
+
+ {(variableRef.current || []).map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyVariablesRef.current.map(v => (
+ insertIntoEditor(v)}
+ >{v}
+ ))}
+
+ >
+ )}
+
+ makeVariable('')}>Make a Variable
+
+
+
Lists
+
+
For all sprites
+
+ {(listsRef.current || []).map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (
+ <>
+
For this sprite only
+
+ {spriteOnlyListsRef.current.map(l => (
+ insertIntoEditor(l)}
+ >{l}
+ ))}
+
+ >
+ )}
+
+ makeVariable('list')}>Make a List
+
+
+
+
;
+}
+
+NanoscriptEditor.propTypes = {
+ theme: PropTypes.instanceOf(Theme),
+ vm: PropTypes.instanceOf(VM).isRequired
+};
+
+export default NanoscriptEditor;
diff --git a/src/components/ob-nanoscript-editor/ob-codemirror-imports.js b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js
new file mode 100644
index 000000000..674e92bbe
--- /dev/null
+++ b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js
@@ -0,0 +1,5 @@
+export { EditorView } from "@codemirror/view";
+export { basicSetup } from "codemirror";
+export { LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle } from "@codemirror/language";
+export { styleTags, tags } from "@lezer/highlight";
+export { autocompletion, completeFromList } from "@codemirror/autocomplete";
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index d20dd26dd..4a4fdabd2 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -6,6 +6,7 @@ import ReactModal from 'react-modal';
import VM from 'scratch-vm';
import {injectIntl, intlShape} from 'react-intl';
+
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
import {
getIsError,
From 7e7118303d7f96be3e3b2a2c048e18157855185e Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 17:20:18 +0000
Subject: [PATCH 11/22] fix
---
src/components/gui/gui.jsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 167ccf3db..49963cc54 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -8,7 +8,6 @@ import MediaQuery from 'react-responsive';
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
import tabStyles from 'react-tabs/style/react-tabs.css';
import VM from 'scratch-vm';
-import CMView from './nanoscript.jsx';
import Blocks from '../../containers/blocks.jsx';
import CostumeTab from '../../containers/costume-tab.jsx';
From 2288ee15780470208ad85aad2dd934844afb941a Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 17:33:08 +0000
Subject: [PATCH 12/22] custom reporters by default
---
src/containers/blocks.jsx | 2 +-
src/containers/extension-library.jsx | 6 ------
src/lib/libraries/extensions/index.jsx | 22 ----------------------
3 files changed, 1 insertion(+), 29 deletions(-)
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index 1a9370850..71b770720 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -229,6 +229,7 @@ class Blocks extends React.Component {
}
gentlyRequestPersistentStorage();
+ this.handleEnableProcedureReturns();
}
shouldComponentUpdate (nextProps, nextState) {
return (
@@ -716,7 +717,6 @@ class Blocks extends React.Component {
diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx
index 4f986767b..6bd8dc719 100644
--- a/src/containers/extension-library.jsx
+++ b/src/containers/extension-library.jsx
@@ -133,12 +133,6 @@ class ExtensionLibrary extends React.PureComponent {
return;
}
- if (extensionId === 'procedures_enable_return') {
- this.props.onEnableProcedureReturns();
- this.props.onCategorySelected('myBlocks');
- return;
- }
-
const url = item.extensionURL ? item.extensionURL : extensionId;
if (!item.disabled) {
if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) {
diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx
index bbf273884..962db9808 100644
--- a/src/lib/libraries/extensions/index.jsx
+++ b/src/lib/libraries/extensions/index.jsx
@@ -335,28 +335,6 @@ export default [
),
helpLink: 'https://scratch.mit.edu/vernier'
},
- {
- // not really an extension, but it's easiest to present it as one
- name: (
-
- ),
- extensionId: 'procedures_enable_return',
- iconURL: returnIcon,
- description: (
-
- ),
- tags: ['tw'],
- incompatibleWithScratch: true,
- featured: true
- },
{
name: (
Date: Mon, 1 Dec 2025 17:47:54 +0000
Subject: [PATCH 13/22] homepage
Co-authored-by: supervoidcoder
---
src/lib/tw-state-manager-hoc.jsx | 7 ++++---
src/playground/home.jsx | 33 ++++++++++++++++++++++++++++++++
webpack.config.js | 10 +++++++++-
3 files changed, 46 insertions(+), 4 deletions(-)
create mode 100644 src/playground/home.jsx
diff --git a/src/lib/tw-state-manager-hoc.jsx b/src/lib/tw-state-manager-hoc.jsx
index d932397aa..9b942b025 100644
--- a/src/lib/tw-state-manager-hoc.jsx
+++ b/src/lib/tw-state-manager-hoc.jsx
@@ -98,9 +98,10 @@ class HashRouter extends Router {
class FileHashRouter extends HashRouter {
constructor (callbacks) {
super(callbacks);
- this.playerPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
- this.editorPath = `${this.playerPath}editor.html`;
- this.fullscreenPath = `${this.playerPath}fullscreen.html`;
+ this.rootPath = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
+ this.playerPath = `${this.rootPath}player.html`;
+ this.editorPath = `${this.rootPath}editor.html`;
+ this.fullscreenPath = `${this.rootPath}fullscreen.html`;
}
onpathchange () {
diff --git a/src/playground/home.jsx b/src/playground/home.jsx
new file mode 100644
index 000000000..385b68ed2
--- /dev/null
+++ b/src/playground/home.jsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import render from './app-target';
+
+import {APP_NAME} from '../lib/brand';
+import {applyGuiColors} from '../lib/themes/guiHelpers';
+import {detectTheme} from '../lib/themes/themePersistance';
+import styles from './credits/credits.css';
+/* eslint-disable react/jsx-no-literals */
+
+applyGuiColors(detectTheme());
+document.documentElement.lang = 'en';
+
+const Credits = () => (
+
+
+
+ OmniBlocks is a group of IDEs for easy programming online.
+
+ S-Program - A Scratch-style environment that also includes NanoScript, a text language that maps to Scratch blocks, as well as an advanced music composer!
+ PyS (Coming soon) - Python in Blocks!
+ Python (Coming soon) - Run Python in the browser.
+
+
+
+
+);
+
+render( );
diff --git a/webpack.config.js b/webpack.config.js
index 96c30a17c..c995b13b8 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -148,6 +148,7 @@ module.exports = [
defaultsDeep({}, base, {
entry: {
'editor': './src/playground/editor.jsx',
+ 'home': './src/playground/home.jsx',
'player': './src/playground/player.jsx',
'fullscreen': './src/playground/fullscreen.jsx',
'embed': './src/playground/embed.jsx',
@@ -195,10 +196,17 @@ module.exports = [
isEditor: true,
...htmlWebpackPluginCommon
}),
+ new HtmlWebpackPlugin({
+ chunks: ['home'],
+ template: 'src/playground/simple.ejs',
+ filename: 'index.html',
+ title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,
+ ...htmlWebpackPluginCommon
+ }),
new HtmlWebpackPlugin({
chunks: ['player'],
template: 'src/playground/index.ejs',
- filename: 'index.html',
+ filename: 'player.html',
title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,
...htmlWebpackPluginCommon
}),
From cf8a14e92c543c3ef82e406ea53fbda5767c8fce Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 19:12:03 +0000
Subject: [PATCH 14/22] fix the version number system, now it uses the
package.json version TODO: cherry pick to main
---
package.json | 2 +-
webpack.config.js | 6 ++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index d96475cd8..0c85dcd99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "scratch-gui",
- "version": "3.2.37",
+ "version": "0.6.0-alpha",
"description": "Graphical User Interface for creating and running Scratch 3.0 projects",
"author": "Massachusetts Institute of Technology",
"license": "GPL-3.0",
diff --git a/webpack.config.js b/webpack.config.js
index c995b13b8..49fffb72a 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -13,9 +13,7 @@ const postcssImport = require('postcss-import');
const STATIC_PATH = process.env.STATIC_PATH || '/static';
const {APP_NAME} = require('./src/lib/brand');
-console.log('🔍 DEBUG: Webpack config loading...');
-console.log('🔍 DEBUG: APP_VERSION from env:', process.env.APP_VERSION);
-console.log('🔍 DEBUG: Will inject:', JSON.stringify(process.env.APP_VERSION || ''));
+const {version} = require('./package.json');
const root = process.env.ROOT || '';
if (root.length > 0 && !root.endsWith('/')) {
@@ -186,7 +184,7 @@ module.exports = [
'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER || ''),
'process.env.ROOT': JSON.stringify(root),
'process.env.ROUTING_STYLE': JSON.stringify(process.env.ROUTING_STYLE || 'filehash'),
- 'process.env.APP_VERSION': JSON.stringify(process.env.APP_VERSION || '')
+ 'process.env.APP_VERSION': JSON.stringify(version || '')
}),
new HtmlWebpackPlugin({
chunks: ['editor'],
From f9c98e27d7a4ac1ca6dedbd3719d6ceee7d506bb Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 19:21:19 +0000
Subject: [PATCH 15/22] fix NS dark mode logic
---
src/components/ob-nanoscript-editor/nanoscript-editor.jsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
index 1de2b24b0..95d01500f 100644
--- a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import VM from 'scratch-vm';
import Prompt from '../../containers/prompt.jsx';
-import {Theme} from '../../lib/themes';
import AddonHooks from '../../addons/hooks.js';
import styles from './nanoscript-editor.css';
@@ -205,7 +204,7 @@ function NanoscriptEditor({ theme, vm }) {
".cm-completionLabel": {
fontSize: "13px"
}
- }, { dark: theme === Theme.dark });
+ }, { dark: theme.isDark() ?? false });
editorRef.current = new EditorView({
doc: `when flag clicked
From 27c350313e759bc9e8c345fa0bccb735681b0973 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 19:22:55 +0000
Subject: [PATCH 16/22] eeeeeeeeee
---
src/components/ob-nanoscript-editor/nanoscript-editor.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
index 95d01500f..92dcb5ada 100644
--- a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import VM from 'scratch-vm';
import Prompt from '../../containers/prompt.jsx';
import AddonHooks from '../../addons/hooks.js';
-
+import {Theme} from '../../lib/themes';
import styles from './nanoscript-editor.css';
function NanoscriptEditor({ theme, vm }) {
From 2f866a227e7d3a4844bfe098f344aa127adf699b Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 19:25:12 +0000
Subject: [PATCH 17/22] add border to sidebar
---
src/components/ob-nanoscript-editor/nanoscript-editor.css | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.css b/src/components/ob-nanoscript-editor/nanoscript-editor.css
index 3af76fa76..fd3429d7a 100644
--- a/src/components/ob-nanoscript-editor/nanoscript-editor.css
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.css
@@ -9,6 +9,7 @@
user-select: none;
font-size: 0.9rem;
overflow: auto;
+ border: 1px solid $ui-black-transparent;
}
.sidebar h2, .sidebar h3 {
From 15bbb8ab967d06cd98dcca0d4f863aa613750a67 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 19:27:52 +0000
Subject: [PATCH 18/22] remove old dupes
---
src/components/nanoscript/nanoscript.css | 0
src/components/nanoscript/nanoscript.jsx | 419 -----------------------
2 files changed, 419 deletions(-)
delete mode 100644 src/components/nanoscript/nanoscript.css
delete mode 100644 src/components/nanoscript/nanoscript.jsx
diff --git a/src/components/nanoscript/nanoscript.css b/src/components/nanoscript/nanoscript.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/components/nanoscript/nanoscript.jsx b/src/components/nanoscript/nanoscript.jsx
deleted file mode 100644
index 994668f1f..000000000
--- a/src/components/nanoscript/nanoscript.jsx
+++ /dev/null
@@ -1,419 +0,0 @@
-import React from 'react';
-import Prompt from '../../containers/prompt.jsx';
-import styles from './gui.css';
-
-function CMView({ theme, vm }) {
- const el = React.useRef(null);
- const editorRef = React.useRef(null);
- const variableRef = React.useRef([]);
- const listsRef = React.useRef([]);
- const spriteOnlyVariablesRef = React.useRef([]);
- const spriteOnlyListsRef = React.useRef([]);
- const [, setVarsState] = React.useState([]);
-
- React.useEffect(() => {
- let disposed = false;
-
- async function loadEditor() {
- const {
- EditorView,
- basicSetup,
- HighlightStyle,
- syntaxHighlighting,
- tags,
- autocompletion,
- completeFromList
- } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
-
- // Define highlight rules using CSS vars
- const scratchHighlight = HighlightStyle.define([
- { tag: tags.variableName, color: "var(--data-primary)" },
- { tag: tags.keyword, color: "var(--pen-primary)" },
- { tag: tags.string, color: "var(--cm-string)" },
- { tag: tags.number, color: "var(--cm-number)" },
- { tag: tags.function, color: "var(--cm-function)" },
- { tag: tags.operator, color: "var(--cm-operator)" }
- ]);
-
- const { StreamLanguage } = await import("@codemirror/language");
- const escapeRegExp = s => s.replace(/[.*+?^${}()|[$$\$$/g, "\\$&");
- const scratchSyntax = StreamLanguage.define({
- token(stream) {
- if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
- if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
- if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
-
- const names = (variableRef.current || []).concat(listsRef.current || []);
- if (names && names.length) {
- const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
- const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
- const m = stream.match(rx, true);
- if (m) return "variableName";
- }
- stream.next();
- return "variable";
- }
- });
-
- const staticCompletions = [
- // Control flow keywords
- { label: "when flag clicked", type: "keyword" },
- { label: "when key pressed", type: "keyword" },
- { label: "when this sprite clicked", type: "keyword" },
- { label: "when I start as a clone", type: "keyword" },
- { label: "forever", type: "keyword" },
- { label: "repeat", type: "keyword" },
- { label: "if", type: "keyword" },
- { label: "else", type: "keyword" },
- { label: "end", type: "keyword" },
- { label: "wait", type: "keyword" },
- { label: "stop", type: "keyword" },
-
- // Motion blocks
- { label: "move", type: "function" },
- { label: "turn right", type: "function" },
- { label: "turn left", type: "function" },
- { label: "go to", type: "function" },
- { label: "glide to", type: "function" },
- { label: "point in direction", type: "function" },
- { label: "point towards", type: "function" },
- { label: "change x by", type: "function" },
- { label: "set x to", type: "function" },
- { label: "change y by", type: "function" },
- { label: "set y to", type: "function" },
-
- // Looks blocks
- { label: "say", type: "function" },
- { label: "think", type: "function" },
- { label: "show", type: "function" },
- { label: "hide", type: "function" },
- { label: "switch costume to", type: "function" },
- { label: "next costume", type: "function" },
- { label: "change size by", type: "function" },
- { label: "set size to", type: "function" },
- { label: "change color effect by", type: "function" },
- { label: "set color effect to", type: "function" },
- { label: "clear graphic effects", type: "function" },
-
- // Sound blocks
- { label: "start sound", type: "function" },
- { label: "stop all sounds", type: "function" },
- { label: "change volume by", type: "function" },
- { label: "set volume to", type: "function" },
-
- // Events
- { label: "broadcast", type: "function" },
- { label: "broadcast and wait", type: "function" },
- { label: "when I receive", type: "keyword" },
-
- // Variables and lists
- { label: "set variable to", type: "function" },
- { label: "change variable by", type: "function" },
- { label: "add to list", type: "function" },
- { label: "delete from list", type: "function" },
- { label: "insert into list", type: "function" },
- { label: "replace list item", type: "function" },
-
- // Operators
- { label: "and", type: "operator" },
- { label: "or", type: "operator" },
- { label: "not", type: "operator" },
- { label: "join", type: "function" },
- { label: "letter of", type: "function" },
- { label: "length of", type: "function" },
- { label: "round", type: "function" },
- { label: "abs of", type: "function" },
- { label: "floor of", type: "function" },
- { label: "ceiling of", type: "function" },
- { label: "sqrt of", type: "function" },
- { label: "sin of", type: "function" },
- { label: "cos of", type: "function" },
- { label: "tan of", type: "function" },
- { label: "asin of", type: "function" },
- { label: "acos of", type: "function" },
- { label: "atan of", type: "function" },
- { label: "pick random", type: "function" },
-
- // Sensing blocks
- { label: "touching", type: "function" },
- { label: "touching color", type: "function" },
- { label: "color is touching", type: "function" },
- { label: "ask", type: "function" },
- { label: "key pressed", type: "function" },
- { label: "mouse down", type: "function" },
- { label: "distance to", type: "function" },
- ];
-
- function scratchCompletions(context) {
- const word = context.matchBefore(/\w*/);
- if (!word || (word.from === word.to && !context.explicit)) return null;
-
- const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
- const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
- const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
-
- return {
- from: word.from,
- options: allOptions.filter(option =>
- option.label.toLowerCase().startsWith(word.text.toLowerCase())
- ),
- validFor: /\w*/
- };
- }
-
- if (!el.current || disposed) return;
-
- const cmTheme = EditorView.theme({
- "&": {
- backgroundColor: "var(--ui-white)",
- color: "var(--text-primary)",
- height: "100%",
- borderTopRightRadius: "var(--space)",
- borderBottomRightRadius: "var(--space)",
- border: "1px solid var(--ui-black-transparent)",
- height: "100%",
- },
- ".cm-scroller": { maxHeight: "100%", overflow: "auto" },
- ".cm-content": { caretColor: "var(--looks-secondary)" },
- ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
- ".cm-focused": { outline: "none" },
- ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
- ".cm-gutters": {
- backgroundColor: "var(--ui-tertiary)",
- borderRight: "1px solid var(--ui-black-transparent)"
- },
- ".cm-completionLabel": { fontSize: "13px" }
- }, { dark: theme === Theme.dark });
-
- editorRef.current = new EditorView({
- doc: `when flag clicked\nsay "Hello World!"\nrepeat 10\n say (join "hi " "there")\nend`,
- extensions: [
- basicSetup,
- scratchSyntax,
- syntaxHighlighting(scratchHighlight),
- autocompletion({ override: [scratchCompletions] }),
- cmTheme
- ],
- parent: el.current
- });
- }
-
- loadEditor();
-
- return () => {
- disposed = true;
- if (editorRef.current) editorRef.current.destroy();
- };
- }, [theme]);
-
- React.useEffect(() => {
- if (!vm) return undefined;
-
- function updateVarsLists() {
- try {
- const editing = vm.editingTarget || vm.runtime.getTargetForStage();
- if (!editing) {
- variableRef.current = [];
- listsRef.current = [];
- spriteOnlyVariablesRef.current = [];
- spriteOnlyListsRef.current = [];
- setVarsState([]);
- return;
- }
-
- const isStage = editing.isStage;
- let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
- let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
- let stageVars = [];
- let stageLists = [];
-
- const stage = vm.runtime.getTargetForStage();
- if (stage) {
- stageVars = stage.getAllVariableNamesInScopeByType('') || [];
- stageLists = stage.getAllVariableNamesInScopeByType('list') || [];
- }
-
- if (isStage) {
- variableRef.current = stageVars;
- listsRef.current = stageLists;
- spriteOnlyVariablesRef.current = [];
- spriteOnlyListsRef.current = [];
- } else {
- variableRef.current = stageVars;
- listsRef.current = stageLists;
- spriteOnlyVariablesRef.current = spriteVars;
- spriteOnlyListsRef.current = spriteLists;
- }
-
- setVarsState(variableRef.current.slice());
- } catch (e) {}
- }
-
- updateVarsLists();
- vm.on('targetsUpdate', updateVarsLists);
- vm.on('PROJECT_CHANGED', updateVarsLists);
- return () => {
- vm.off('targetsUpdate', updateVarsLists);
- vm.off('PROJECT_CHANGED', updateVarsLists);
- };
- }, [vm]);
-
- const [promptProps, setPromptProps] = React.useState(null);
-
- const makeVariable = (type = '') => {
- if (!vm) {
- alert('VM not available');
- return;
- }
-
- const editing = vm.editingTarget || vm.runtime.getTargetForStage();
- const isStage = editing && editing.isStage;
-
- const title = type === 'list' ? 'Make a List' : 'Make a Variable';
- const varTypeConst = type === 'list' ? 'list' : '';
- const showListMessage = type === 'list';
- const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);
-
- setPromptProps({
- defaultValue: '',
- isStage: !!(editing && editing.isStage),
- showListMessage,
- label: type === 'list' ? 'List name' : 'Variable name',
- showCloudOption,
- showVariableOptions: true,
- title,
- varType: varTypeConst
- });
- };
-
- const insertIntoEditor = name => {
- if (!editorRef.current) return;
- try {
- const view = editorRef.current;
- const pos = view.state.selection.main.head;
- view.dispatch({changes: {from: pos, insert: name}});
- view.focus();
- } catch (e) {}
- };
-
- const handlePromptCancel = () => setPromptProps(null);
- const handlePromptOk = (input, variableOptions) => {
- try {
- const varType = (promptProps && promptProps.varType) || '';
- let allVarNames = [];
- if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {
- try {
- allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];
- } catch (e) {
- allVarNames = [];
- }
- }
- const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());
- if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {
- try {
- const stage = vm.runtime.getTargetForStage();
- if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {
- const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];
- for (const s of stageVars) {
- if (!allVarNames.includes(s)) allVarNames.push(s);
- }
- }
- } catch (e) {}
- }
-
- const ws = AddonHooks.blocklyWorkspace;
- const isLocal = variableOptions && variableOptions.scope === 'local';
- const isCloud = !!(variableOptions && variableOptions.isCloud);
- if (ws && typeof ws.createVariable === 'function') {
- try {
- ws.createVariable(input, varType, null, !!isLocal, !!isCloud);
- } catch (e) {}
- }
- } finally {
- setPromptProps(null);
- }
- };
-
- return (
-
- {promptProps ? (
-
- ) : null}
-
-
Variables
-
-
For all sprites
-
- {(variableRef.current || []).map(v => (
- insertIntoEditor(v)}
- >{v}
- ))}
-
- {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (
- <>
-
For this sprite only
-
- {spriteOnlyVariablesRef.current.map(v => (
- insertIntoEditor(v)}
- >{v}
- ))}
-
- >
- )}
-
- makeVariable('')}>Make a Variable
-
-
-
Lists
-
-
For all sprites
-
- {(listsRef.current || []).map(l => (
- insertIntoEditor(l)}
- >{l}
- ))}
-
- {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (
- <>
-
For this sprite only
-
- {spriteOnlyListsRef.current.map(l => (
- insertIntoEditor(l)}
- >{l}
- ))}
-
- >
- )}
-
- makeVariable('list')}>Make a List
-
-
-
-
- );
-}
-
-export default CMView;
From e50ecee9ff3226171880ecadc89dbe33b83c28ce Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 21:26:42 +0000
Subject: [PATCH 19/22] improve website
---
src/playground/credits/credits.jsx | 7 +-
src/playground/home.jsx | 33 --------
src/playground/home/home.css | 35 ++++++++
src/playground/home/home.jsx | 80 +++++++++++++++++++
.../{credits/credits.css => info.css} | 13 ++-
.../ws-components/header/costume1.svg | 1 +
.../ws-components/header/header.css | 49 ++++++++++++
.../ws-components/header/header.jsx | 18 +++++
.../{credits.html => ssongeditorcredits.html} | 0
webpack.config.js | 2 +-
10 files changed, 197 insertions(+), 41 deletions(-)
delete mode 100644 src/playground/home.jsx
create mode 100644 src/playground/home/home.css
create mode 100644 src/playground/home/home.jsx
rename src/playground/{credits/credits.css => info.css} (79%)
create mode 100644 src/playground/ws-components/header/costume1.svg
create mode 100644 src/playground/ws-components/header/header.css
create mode 100644 src/playground/ws-components/header/header.jsx
rename static/{credits.html => ssongeditorcredits.html} (100%)
diff --git a/src/playground/credits/credits.jsx b/src/playground/credits/credits.jsx
index 1755428d8..c1c15f196 100644
--- a/src/playground/credits/credits.jsx
+++ b/src/playground/credits/credits.jsx
@@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import render from '../app-target';
-import styles from './credits.css';
+import styles from '../info.css';
+import Header from '../ws-components/header/header.jsx';
import {APP_NAME} from '../../lib/brand';
import {applyGuiColors} from '../../lib/themes/guiHelpers';
@@ -53,7 +54,7 @@ UserList.propTypes = {
};
const Credits = () => (
-
+ <>
{APP_NAME} Credits
@@ -115,7 +116,7 @@ const Credits = () => (
-
+ >
);
render( );
diff --git a/src/playground/home.jsx b/src/playground/home.jsx
deleted file mode 100644
index 385b68ed2..000000000
--- a/src/playground/home.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import render from './app-target';
-
-import {APP_NAME} from '../lib/brand';
-import {applyGuiColors} from '../lib/themes/guiHelpers';
-import {detectTheme} from '../lib/themes/themePersistance';
-import styles from './credits/credits.css';
-/* eslint-disable react/jsx-no-literals */
-
-applyGuiColors(detectTheme());
-document.documentElement.lang = 'en';
-
-const Credits = () => (
-
-
-
- OmniBlocks is a group of IDEs for easy programming online.
-
- S-Program - A Scratch-style environment that also includes NanoScript, a text language that maps to Scratch blocks, as well as an advanced music composer!
- PyS (Coming soon) - Python in Blocks!
- Python (Coming soon) - Run Python in the browser.
-
-
-
-
-);
-
-render( );
diff --git a/src/playground/home/home.css b/src/playground/home/home.css
new file mode 100644
index 000000000..2fb874227
--- /dev/null
+++ b/src/playground/home/home.css
@@ -0,0 +1,35 @@
+/* Styles for the home IDE cards */
+@import '../../css/colors.css';
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 16px;
+ margin-top: 12px;
+}
+.card {
+ background: $badge-background;
+ border-radius: 8px;
+ padding: 16px;
+ border: 1px solid $badge-border;
+ transition: transform .12s ease, box-shadow .12s ease;
+}
+.cardTitle {
+ margin: 0 0 8px 0;
+ font-size: 1.1rem;
+}
+.cardDesc {
+ margin: 0 0 10px 0;
+ color: $text-primary;
+}
+.cardLink {
+ display: inline-block;
+ margin-top: 6px;
+ color: $link-color;
+ text-decoration: none;
+}
+.coming {
+ font-weight: 400;
+ font-size: 0.9rem;
+ margin-left: 6px;
+}
diff --git a/src/playground/home/home.jsx b/src/playground/home/home.jsx
new file mode 100644
index 000000000..3283423f3
--- /dev/null
+++ b/src/playground/home/home.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import render from '../app-target.js';
+
+import {APP_NAME} from '../../lib/brand.js';
+import {applyGuiColors} from '../../lib/themes/guiHelpers.js';
+import {detectTheme} from '../../lib/themes/themePersistance.js';
+import styles from '../info.css';
+import localStyles from './home.css';
+import Header from '../ws-components/header/header.jsx';
+/* eslint-disable react/jsx-no-literals */
+
+applyGuiColors(detectTheme());
+document.documentElement.lang = 'en';
+
+const IDE_CARDS = [
+ {
+ title: 'Visual IDE',
+ href: 'editor.html',
+ desc: 'A Scratch mod with text-based programming support!'
+ },
+ {
+ title: 'PyVisual',
+ href: null,
+ desc: 'Write Python code in blocks!',
+ coming: true
+ },
+ {
+ title: 'OmniPython',
+ href: null,
+ desc: 'An advanced Python IDE right in your browser.',
+ coming: true
+ },
+ {
+ title: 'And more...',
+ href: null,
+ desc: 'IDEs for C and more!',
+ coming: true
+ }
+];
+
+const Credits = () => (
+ <>
+
+
+
+
+
+ Current IDEs
+
+
+ {IDE_CARDS.map((ide) => (
+
+
+ {ide.title}
+ {ide.coming ? — Coming soon : null}
+
+ {ide.desc}
+ {ide.href ? (
+ Open
+ ) : null}
+
+ ))}
+
+
+
+
+ {APP_NAME} is free software under the AGPL 3.0 license
+
+ You can view the source code on GitHub .
+
+
+
+ >
+);
+
+render( );
diff --git a/src/playground/credits/credits.css b/src/playground/info.css
similarity index 79%
rename from src/playground/credits/credits.css
rename to src/playground/info.css
index 46674143c..262698bf4 100644
--- a/src/playground/credits/credits.css
+++ b/src/playground/info.css
@@ -1,4 +1,4 @@
-@import "../../css/colors.css";
+@import "../css/colors.css";
* {
box-sizing: border-box;
@@ -24,13 +24,18 @@ a {
.header-container {
color: white;
- background-color: $looks-secondary;
- padding: 20px 0;
+ background-color: $motion-tertiary;
+ padding: 30px 0;
text-align: center;
margin-bottom: 30px;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ gap: 4px;
}
.header-text {
-
+ max-width: 900px;
+ margin: 0;
}
.users {
diff --git a/src/playground/ws-components/header/costume1.svg b/src/playground/ws-components/header/costume1.svg
new file mode 100644
index 000000000..c790cb82f
--- /dev/null
+++ b/src/playground/ws-components/header/costume1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/playground/ws-components/header/header.css b/src/playground/ws-components/header/header.css
new file mode 100644
index 000000000..b5d3ab2b0
--- /dev/null
+++ b/src/playground/ws-components/header/header.css
@@ -0,0 +1,49 @@
+@import "../../../css/colors.css";
+@import "../../../css/units.css";
+
+.header {
+ background: $menu-bar-background;
+ color: $menu-bar-foreground;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ user-select: none;
+ gap: $space;
+ height: $menu-bar-height;
+ position: fixed;
+ top: 0; left: 0;
+ width: 100%;
+ z-index: 900000;
+ box-shadow: 0 2px 4px $ui-black-transparent;
+}
+
+.header-link, .header-logo {
+ color: $menu-bar-foreground;
+ text-decoration: none;
+ font-weight: bold;
+ padding: 4px 8px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.header-logo {
+ font-size: 1.4em;
+ transition: transform 0.2s ease-in-out;
+}
+
+.header-logo:hover {
+ transform: scale(1.1);
+}
+
+.header-link {
+ transition: background 0.1s ease-in-out;
+}
+
+.header-link:hover {
+ background: $ui-black-transparent;
+}
+
+.header-spacing-hack {
+ margin-bottom: $menu-bar-height;
+}
\ No newline at end of file
diff --git a/src/playground/ws-components/header/header.jsx b/src/playground/ws-components/header/header.jsx
new file mode 100644
index 000000000..a14f2beb2
--- /dev/null
+++ b/src/playground/ws-components/header/header.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import styles from './header.css';
+import logo from './costume1.svg';
+
+const Header = () => {
+ return (
+ <>
>
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/static/credits.html b/static/ssongeditorcredits.html
similarity index 100%
rename from static/credits.html
rename to static/ssongeditorcredits.html
diff --git a/webpack.config.js b/webpack.config.js
index 49fffb72a..0092559b3 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -146,7 +146,7 @@ module.exports = [
defaultsDeep({}, base, {
entry: {
'editor': './src/playground/editor.jsx',
- 'home': './src/playground/home.jsx',
+ 'home': './src/playground/home/home.jsx',
'player': './src/playground/player.jsx',
'fullscreen': './src/playground/fullscreen.jsx',
'embed': './src/playground/embed.jsx',
From 1bdcd5732bfd08b4d86beb17099e3a927dbf5442 Mon Sep 17 00:00:00 2001
From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com>
Date: Mon, 1 Dec 2025 21:32:08 +0000
Subject: [PATCH 20/22] show "visual ide" name in the editor itself
---
src/lib/brand.js | 5 ++++-
src/lib/libraries/tw-extension-tags.js | 2 +-
src/playground/credits/credits.jsx | 4 +++-
src/playground/home/home.jsx | 4 +++-
src/playground/ws-components/header/header.jsx | 4 +++-
5 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/lib/brand.js b/src/lib/brand.js
index 0cc68f1e2..c2c11dfe6 100644
--- a/src/lib/brand.js
+++ b/src/lib/brand.js
@@ -1,6 +1,9 @@
// Legacy export format because this is used by some build-time scripts stuck in the past.
// eslint-disable-next-line import/no-commonjs
module.exports = {
- APP_NAME: 'OmniBlocks', // the name of the mod
+ APP_NAME: 'Visual IDE', // the name of the Scratch mod
+ APP_NAMES: {
+ PROJECT: 'OmniBlocks'
+ },
APP_VERSION: process.env.APP_VERSION || 'v0.5.8-alpha' // Dynamically injected at build time from git tags
};
\ No newline at end of file
diff --git a/src/lib/libraries/tw-extension-tags.js b/src/lib/libraries/tw-extension-tags.js
index 6a6f3e27c..461bb3467 100644
--- a/src/lib/libraries/tw-extension-tags.js
+++ b/src/lib/libraries/tw-extension-tags.js
@@ -4,5 +4,5 @@ import {APP_NAME} from '../brand';
export default [
{tag: 'scratch', intlLabel: 'Scratch'},
{tag: 'tw', intlLabel: "TurboWarp"},
- {tag: 'ob', intlLabel: "OmniBlocks"}
+ {tag: 'ob', intlLabel: APP_NAME}
];
diff --git a/src/playground/credits/credits.jsx b/src/playground/credits/credits.jsx
index c1c15f196..3e1142c6b 100644
--- a/src/playground/credits/credits.jsx
+++ b/src/playground/credits/credits.jsx
@@ -4,7 +4,9 @@ import render from '../app-target';
import styles from '../info.css';
import Header from '../ws-components/header/header.jsx';
-import {APP_NAME} from '../../lib/brand';
+import { APP_NAMES } from '../../lib/brand';
+
+const APP_NAME = APP_NAMES.PROJECT;
import {applyGuiColors} from '../../lib/themes/guiHelpers';
import {detectTheme} from '../../lib/themes/themePersistance';
import UserData from './users';
diff --git a/src/playground/home/home.jsx b/src/playground/home/home.jsx
index 3283423f3..84a3cee4a 100644
--- a/src/playground/home/home.jsx
+++ b/src/playground/home/home.jsx
@@ -2,7 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import render from '../app-target.js';
-import {APP_NAME} from '../../lib/brand.js';
+import { APP_NAMES } from '../../lib/brand';
+
+const APP_NAME = APP_NAMES.PROJECT;
import {applyGuiColors} from '../../lib/themes/guiHelpers.js';
import {detectTheme} from '../../lib/themes/themePersistance.js';
import styles from '../info.css';
diff --git a/src/playground/ws-components/header/header.jsx b/src/playground/ws-components/header/header.jsx
index a14f2beb2..39fa2ec1b 100644
--- a/src/playground/ws-components/header/header.jsx
+++ b/src/playground/ws-components/header/header.jsx
@@ -1,12 +1,14 @@
import React from 'react';
import styles from './header.css';
import logo from './costume1.svg';
+import { APP_NAMES } from '../../../lib/brand';
+const APP_NAME = APP_NAMES.PROJECT;
const Header = () => {
return (
<>
- OmniBlocks
+ {APP_NAME}
Credits
From 6400c314278fe129b41d13a8b024818ee923f358 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Fri, 19 Dec 2025 03:57:46 +0000
Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=8E=A8=20Auto-fix=20ESLint=20issues?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixed the following:
Triggered by @supervoidcoder
Co-authored-by: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com>
---
eslint-report.json | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 eslint-report.json
diff --git a/eslint-report.json b/eslint-report.json
new file mode 100644
index 000000000..e69de29bb
From 1121b29bd8458e35c199172cd97eb53fdae874c4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Fri, 19 Dec 2025 14:05:01 +0000
Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=8E=A8=20Auto-fix=20ESLint=20issues?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixed the following:
Triggered by @github-actions[bot]
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
---
eslint-report.json | 1 +
src/components/gui/gui.jsx | 71 +++--
src/components/gui/ob-codemirror-imports.js | 10 +-
.../nanoscript-editor.jsx | 255 +++++++++---------
.../ob-codemirror-imports.js | 10 +-
src/lib/brand.js | 2 +-
src/lib/libraries/tw-extension-tags.js | 2 +-
src/playground/credits/credits.jsx | 2 +-
src/playground/home/home.jsx | 14 +-
.../ws-components/header/header.jsx | 37 ++-
webpack.config.js | 6 +-
11 files changed, 225 insertions(+), 185 deletions(-)
diff --git a/eslint-report.json b/eslint-report.json
index e69de29bb..70b7112e1 100644
--- a/eslint-report.json
+++ b/eslint-report.json
@@ -0,0 +1 @@
+[{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/gui/gui.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'AddonHooks' is defined but never used. Allowed unused vars must match /^_/u.","line":45,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":45,"endColumn":18},{"ruleId":"no-unused-vars","severity":2,"message":"'Prompt' is defined but never used. Allowed unused vars must match /^_/u.","line":47,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":47,"endColumn":14},{"ruleId":"no-unused-vars","severity":2,"message":"'SpinnerComponent' is defined but never used. Allowed unused vars must match /^_/u.","line":58,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":58,"endColumn":24},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":431,"column":49,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":433,"endColumn":76},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":432,"column":53,"nodeType":"CallExpression","messageId":"unexpected","endLine":432,"endColumn":114}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import classNames from 'classnames';\nimport omit from 'lodash.omit';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';\nimport {connect} from 'react-redux';\nimport MediaQuery from 'react-responsive';\nimport {Tab, Tabs, TabList, TabPanel} from 'react-tabs';\nimport tabStyles from 'react-tabs/style/react-tabs.css';\nimport VM from 'scratch-vm';\n\nimport Blocks from '../../containers/blocks.jsx';\nimport CostumeTab from '../../containers/costume-tab.jsx';\nimport TargetPane from '../../containers/target-pane.jsx';\nimport SoundTab from '../../containers/sound-tab.jsx';\nimport StageWrapper from '../../containers/stage-wrapper.jsx';\nimport Loader from '../loader/loader.jsx';\nimport Box from '../box/box.jsx';\nimport MenuBar from '../menu-bar/menu-bar.jsx';\nimport CostumeLibrary from '../../containers/costume-library.jsx';\nimport BackdropLibrary from '../../containers/backdrop-library.jsx';\nimport Watermark from '../../containers/watermark.jsx';\nimport SongsTab from '../../containers/songs-tab.jsx';\nimport Backpack from '../../containers/backpack.jsx';\nimport BrowserModal from '../browser-modal/browser-modal.jsx';\nimport TipsLibrary from '../../containers/tips-library.jsx';\nimport Cards from '../../containers/cards.jsx';\nimport Alerts from '../../containers/alerts.jsx';\nimport DragLayer from '../../containers/drag-layer.jsx';\nimport ConnectionModal from '../../containers/connection-modal.jsx';\nimport TelemetryModal from '../telemetry-modal/telemetry-modal.jsx';\nimport TWUsernameModal from '../../containers/tw-username-modal.jsx';\nimport TWSettingsModal from '../../containers/tw-settings-modal.jsx';\nimport TWSecurityManager from '../../containers/tw-security-manager.jsx';\nimport TWCustomExtensionModal from '../../containers/tw-custom-extension-modal.jsx';\nimport TWRestorePointManager from '../../containers/tw-restore-point-manager.jsx';\nimport TWFontsModal from '../../containers/tw-fonts-modal.jsx';\nimport TWUnknownPlatformModal from '../../containers/tw-unknown-platform-modal.jsx';\nimport TWInvalidProjectModal from '../../containers/tw-invalid-project-modal.jsx';\nimport NanoscriptEditor from '../ob-nanoscript-editor/nanoscript-editor.jsx';\n\nimport {STAGE_SIZE_MODES, FIXED_WIDTH, UNCONSTRAINED_NON_STAGE_WIDTH} from '../../lib/layout-constants';\nimport {resolveStageSize} from '../../lib/screen-utils';\nimport {Theme} from '../../lib/themes';\nimport AddonHooks from '../../addons/hooks.js';\nimport ToggleButtons from '../toggle-buttons/toggle-buttons.jsx';\nimport Prompt from '../../containers/prompt.jsx';\nimport nanoscriptIcon from '!../../lib/tw-recolor/build!./nanoscriptIcon.svg';\n\nimport {isRendererSupported, isBrowserSupported} from '../../lib/tw-environment-support-prober';\n\nimport styles from './gui.css';\nimport addExtensionIcon from './icon--extensions.svg';\nimport codeIcon from '!../../lib/tw-recolor/build!./icon--code.svg';\nimport costumesIcon from '!../../lib/tw-recolor/build!./icon--costumes.svg';\nimport soundsIcon from '!../../lib/tw-recolor/build!./icon--sounds.svg';\nimport songsIcon from '!../../lib/tw-recolor/build!./icon--songs.svg';\nimport SpinnerComponent from '../tw-loading-spinner/spinner.jsx';\nconst messages = defineMessages({\n addExtension: {\n id: 'gui.gui.addExtension',\n description: 'Button to add an extension in the target pane',\n defaultMessage: 'Add Extension'\n }\n});\n\nconst getFullscreenBackgroundColor = () => {\n const params = new URLSearchParams(location.search);\n if (params.has('fullscreen-background')) {\n return params.get('fullscreen-background');\n }\n if (window.matchMedia('(prefers-color-scheme: dark)').matches) {\n return '#111';\n }\n return 'white';\n};\n\nconst fullscreenBackgroundColor = getFullscreenBackgroundColor();\n\nconst GUIComponent = props => {\n const {\n accountNavOpen,\n activeTabIndex,\n alertsVisible,\n authorId,\n authorThumbnailUrl,\n authorUsername,\n basePath,\n backdropLibraryVisible,\n backpackHost,\n backpackVisible,\n blocksId,\n blocksTabVisible,\n cardsVisible,\n canChangeLanguage,\n canChangeTheme,\n canCreateNew,\n canEditTitle,\n canManageFiles,\n canRemix,\n canSave,\n canCreateCopy,\n canShare,\n canUseCloud,\n children,\n connectionModalVisible,\n costumeLibraryVisible,\n costumesTabVisible,\n customStageSize,\n enableCommunity,\n intl,\n isCreating,\n isEmbedded,\n isFullScreen,\n isPlayerOnly,\n isRtl,\n isShared,\n isWindowFullScreen,\n isTelemetryEnabled,\n isTotallyNormal,\n loading,\n logo,\n renderLogin,\n onClickAbout,\n onClickAccountNav,\n onCloseAccountNav,\n onClickAddonSettings,\n onClickDesktopSettings,\n onClickNewWindow,\n onClickPackager,\n onLogOut,\n onOpenRegistration,\n onToggleLoginOpen,\n onActivateCostumesTab,\n onActivateSoundsTab,\n onActivateSongsTab,\n onActivateTab,\n onClickLogo,\n onExtensionButtonClick,\n onOpenCustomExtensionModal,\n onProjectTelemetryEvent,\n onRequestCloseBackdropLibrary,\n onRequestCloseCostumeLibrary,\n onRequestCloseTelemetryModal,\n onSeeCommunity,\n onShare,\n onShowPrivacyPolicy,\n onStartSelectingFileUpload,\n onTelemetryModalCancel,\n onTelemetryModalOptIn,\n onTelemetryModalOptOut,\n securityManager,\n showComingSoon,\n showOpenFilePicker,\n showSaveFilePicker,\n soundsTabVisible,\n songsTabVisible,\n stageSizeMode,\n targetIsStage,\n telemetryModalVisible,\n theme,\n tipsLibraryVisible,\n usernameModalVisible,\n settingsModalVisible,\n customExtensionModalVisible,\n fontsModalVisible,\n unknownPlatformModalVisible,\n invalidProjectModalVisible,\n vm,\n ...componentProps\n } = omit(props, 'dispatch');\n if (children) {\n return {children} ;\n }\n\n const tabClassNames = {\n tabs: styles.tabs,\n tab: classNames(tabStyles.reactTabsTab, styles.tab),\n tabList: classNames(tabStyles.reactTabsTabList, styles.tabList),\n tabPanel: classNames(tabStyles.reactTabsTabPanel, styles.tabPanel),\n tabPanelSelected: classNames(tabStyles.reactTabsTabPanelSelected, styles.isSelected),\n tabSelected: classNames(tabStyles.reactTabsTabSelected, styles.isSelected)\n };\n\n const [isNano, setNano] = React.useState(false);\n\n const unconstrainedWidth = (\n UNCONSTRAINED_NON_STAGE_WIDTH +\n FIXED_WIDTH +\n Math.max(0, customStageSize.width - FIXED_WIDTH)\n );\n return ({isUnconstrained => {\n const stageSize = resolveStageSize(stageSizeMode, isUnconstrained);\n\n const alwaysEnabledModals = (\n \n \n \n {usernameModalVisible && }\n {settingsModalVisible && }\n {customExtensionModalVisible && }\n {fontsModalVisible && }\n {unknownPlatformModalVisible && }\n {invalidProjectModalVisible && }\n \n );\n\n return isPlayerOnly ? (\n \n {/* TW: When the window is fullscreen, use an element to display the background color */}\n {/* The default color for transparency is inconsistent between browsers and there isn't an existing */}\n {/* element for us to style that fills the entire screen. */}\n {isWindowFullScreen ? (\n
\n ) : null}\n \n {alertsVisible ? (\n \n ) : null}\n \n {alwaysEnabledModals}\n \n ) : (\n \n {alwaysEnabledModals}\n {telemetryModalVisible ? (\n \n ) : null}\n {loading ? (\n \n ) : null}\n {isCreating ? (\n \n ) : null}\n {isBrowserSupported() ? null : (\n \n )}\n {tipsLibraryVisible ? (\n \n ) : null}\n {cardsVisible ? (\n \n ) : null}\n {alertsVisible ? (\n \n ) : null}\n {connectionModalVisible ? (\n \n ) : null}\n {costumeLibraryVisible ? (\n \n ) : null}\n {backdropLibraryVisible ? (\n \n ) : null}\n \n \n \n \n \n \n \n \n \n \n \n \n {targetIsStage ? (\n \n ) : (\n \n )}\n \n \n \n \n \n \n \n \n \n \n \n {isNano ? blocksTabVisible && : <>\n \n \n {\n alert('Adding extensions in NanoScript is not available yet');\n } : onExtensionButtonClick}\n >\n {isNano && intl.formatMessage(messages.addExtension)}\n \n \n >}\n \n {!isNano && {\n window.blocklyWorkspace.zoomCenter(1);\n },\n isSelected: false,\n children: '+'\n },\n {\n handleClick: () => {\n window.blocklyWorkspace.zoomCenter(-1);\n },\n isSelected: false,\n children: '-'\n },\n {\n handleClick: () => {\n window.blocklyWorkspace.setScale(0.675);\n },\n isSelected: false,\n children: '='\n }\n ]}\n />}\n setNano(false),\n icon: codeIcon,\n isSelected: !isNano,\n title: 'Block-based'\n },\n {\n handleClick: () => setNano(true),\n icon: nanoscriptIcon,\n isSelected: isNano,\n title: 'Text-based'\n }\n ]}\n />\n
\n \n \n \n \n \n {costumesTabVisible ? : null}\n \n \n {soundsTabVisible ? : null}\n \n \n {songsTabVisible ? : null}\n \n \n {backpackVisible ? (\n \n ) : null}\n \n\n \n \n \n \n \n \n \n \n \n \n );\n }} );\n};\n\nGUIComponent.propTypes = {\n accountNavOpen: PropTypes.bool,\n activeTabIndex: PropTypes.number,\n authorId: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), // can be false\n authorThumbnailUrl: PropTypes.string,\n authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), // can be false\n backdropLibraryVisible: PropTypes.bool,\n backpackHost: PropTypes.string,\n backpackVisible: PropTypes.bool,\n basePath: PropTypes.string,\n blocksTabVisible: PropTypes.bool,\n blocksId: PropTypes.string,\n canChangeLanguage: PropTypes.bool,\n canChangeTheme: PropTypes.bool,\n canCreateCopy: PropTypes.bool,\n canCreateNew: PropTypes.bool,\n canEditTitle: PropTypes.bool,\n canManageFiles: PropTypes.bool,\n canRemix: PropTypes.bool,\n canSave: PropTypes.bool,\n canShare: PropTypes.bool,\n canUseCloud: PropTypes.bool,\n cardsVisible: PropTypes.bool,\n children: PropTypes.node,\n costumeLibraryVisible: PropTypes.bool,\n costumesTabVisible: PropTypes.bool,\n customStageSize: PropTypes.shape({\n width: PropTypes.number,\n height: PropTypes.number\n }),\n enableCommunity: PropTypes.bool,\n intl: intlShape.isRequired,\n isCreating: PropTypes.bool,\n isEmbedded: PropTypes.bool,\n isFullScreen: PropTypes.bool,\n isPlayerOnly: PropTypes.bool,\n isRtl: PropTypes.bool,\n isShared: PropTypes.bool,\n isWindowFullScreen: PropTypes.bool,\n isTotallyNormal: PropTypes.bool,\n loading: PropTypes.bool,\n logo: PropTypes.string,\n onActivateCostumesTab: PropTypes.func,\n onActivateSoundsTab: PropTypes.func,\n onActivateTab: PropTypes.func,\n onClickAccountNav: PropTypes.func,\n onClickAddonSettings: PropTypes.func,\n onClickDesktopSettings: PropTypes.func,\n onClickNewWindow: PropTypes.func,\n onClickPackager: PropTypes.func,\n onClickLogo: PropTypes.func,\n onCloseAccountNav: PropTypes.func,\n onExtensionButtonClick: PropTypes.func,\n onOpenCustomExtensionModal: PropTypes.func,\n onLogOut: PropTypes.func,\n onOpenRegistration: PropTypes.func,\n onRequestCloseBackdropLibrary: PropTypes.func,\n onRequestCloseCostumeLibrary: PropTypes.func,\n onRequestCloseTelemetryModal: PropTypes.func,\n onSeeCommunity: PropTypes.func,\n onShare: PropTypes.func,\n onShowPrivacyPolicy: PropTypes.func,\n onStartSelectingFileUpload: PropTypes.func,\n onTabSelect: PropTypes.func,\n onTelemetryModalCancel: PropTypes.func,\n onTelemetryModalOptIn: PropTypes.func,\n onTelemetryModalOptOut: PropTypes.func,\n onToggleLoginOpen: PropTypes.func,\n renderLogin: PropTypes.func,\n securityManager: PropTypes.shape({}),\n showComingSoon: PropTypes.bool,\n showOpenFilePicker: PropTypes.func,\n showSaveFilePicker: PropTypes.func,\n soundsTabVisible: PropTypes.bool,\n stageSizeMode: PropTypes.oneOf(Object.keys(STAGE_SIZE_MODES)),\n targetIsStage: PropTypes.bool,\n telemetryModalVisible: PropTypes.bool,\n theme: PropTypes.instanceOf(Theme),\n tipsLibraryVisible: PropTypes.bool,\n usernameModalVisible: PropTypes.bool,\n settingsModalVisible: PropTypes.bool,\n customExtensionModalVisible: PropTypes.bool,\n fontsModalVisible: PropTypes.bool,\n unknownPlatformModalVisible: PropTypes.bool,\n invalidProjectModalVisible: PropTypes.bool,\n vm: PropTypes.instanceOf(VM).isRequired\n};\nGUIComponent.defaultProps = {\n backpackHost: null,\n backpackVisible: false,\n basePath: './',\n blocksId: 'original',\n canChangeLanguage: true,\n canChangeTheme: true,\n canCreateNew: false,\n canEditTitle: false,\n canManageFiles: true,\n canRemix: false,\n canSave: false,\n canCreateCopy: false,\n canShare: false,\n canUseCloud: false,\n enableCommunity: false,\n isCreating: false,\n isShared: false,\n isTotallyNormal: false,\n loading: false,\n showComingSoon: false,\n stageSizeMode: STAGE_SIZE_MODES.large\n};\n\nconst mapStateToProps = state => ({\n customStageSize: state.scratchGui.customStageSize,\n isWindowFullScreen: state.scratchGui.tw.isWindowFullScreen,\n // This is the button's mode, as opposed to the actual current state\n blocksId: state.scratchGui.timeTravel.year.toString(),\n stageSizeMode: state.scratchGui.stageSize.stageSize,\n theme: state.scratchGui.theme.theme\n});\n\nexport default injectIntl(connect(\n mapStateToProps\n)(GUIComponent));\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/gui/ob-codemirror-imports.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"export {EditorView} from '@codemirror/view';\nexport {basicSetup} from 'codemirror';\nexport {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';\nexport {styleTags, tags} from '@lezer/highlight';\nexport {autocompletion, completeFromList} from '@codemirror/autocomplete';\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/ob-nanoscript-editor/nanoscript-editor.jsx","messages":[{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":9,"column":1,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":469,"endColumn":2},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":9,"column":1,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":469,"endColumn":2},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":21,"column":9,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":225,"endColumn":10},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":21,"column":9,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":225,"endColumn":10},{"ruleId":"no-unused-vars","severity":2,"message":"'completeFromList' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":29,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":29,"endColumn":33},{"ruleId":"no-useless-escape","severity":2,"message":"Unnecessary escape character: \\-.","line":50,"column":61,"nodeType":"Literal","messageId":"unnecessaryEscape","endLine":50,"endColumn":62,"suggestions":[{"messageId":"removeEscape","fix":{"range":[2144,2145],"text":""},"desc":"Remove the `\\`. This maintains the current functionality."},{"messageId":"escapeBackslash","fix":{"range":[2144,2144],"text":"\\"},"desc":"Replace the `\\` with `\\\\` to include the actual backslash character."}]},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":162,"column":13,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":179,"endColumn":14},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":162,"column":13,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":179,"endColumn":14},{"ruleId":"max-len","severity":2,"message":"This line has a length of 123. Maximum allowed is 120.","line":168,"column":1,"nodeType":"Program","messageId":"max","endLine":168,"endColumn":124},{"ruleId":"no-dupe-keys","severity":2,"message":"Duplicate key 'height'.","line":191,"column":21,"nodeType":"ObjectExpression","messageId":"unexpected","endLine":191,"endColumn":27},{"ruleId":"no-undefined","severity":2,"message":"Unexpected use of undefined.","line":239,"column":25,"nodeType":"Identifier","messageId":"unexpectedUndefined","endLine":239,"endColumn":34},{"ruleId":"func-style","severity":2,"message":"Expected a function expression.","line":241,"column":9,"nodeType":"FunctionDeclaration","messageId":"expression","endLine":285,"endColumn":10},{"ruleId":"require-jsdoc","severity":2,"message":"Missing JSDoc comment.","line":241,"column":9,"nodeType":"FunctionDeclaration","messageId":"missingJSDocComment","endLine":285,"endColumn":10},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":301,"column":13,"nodeType":"CallExpression","messageId":"unexpected","endLine":301,"endColumn":38},{"ruleId":"no-unused-vars","severity":2,"message":"'isStage' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":307,"column":15,"nodeType":"Identifier","messageId":"unusedVar","endLine":307,"endColumn":22},{"ruleId":"max-len","severity":2,"message":"This line has a length of 169. Maximum allowed is 120.","line":313,"column":1,"nodeType":"Program","messageId":"max","endLine":313,"endColumn":170},{"ruleId":"max-len","severity":2,"message":"This line has a length of 127. Maximum allowed is 120.","line":351,"column":1,"nodeType":"Program","messageId":"max","endLine":351,"endColumn":128},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":392,"column":17,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":392,"endColumn":46},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":393,"column":17,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":393,"endColumn":38},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Variables\"","line":397,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":397,"endColumn":26},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For all sprites\"","line":399,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":399,"endColumn":32},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":405,"column":25,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":405,"endColumn":60},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For this sprite only\"","line":411,"column":49,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":411,"endColumn":69},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":417,"column":33,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":417,"endColumn":68},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":426,"column":21,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":426,"endColumn":53},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Make a Variable\"","line":427,"column":18,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":427,"endColumn":33},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Lists\"","line":430,"column":41,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":430,"endColumn":46},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For all sprites\"","line":432,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":432,"endColumn":32},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":438,"column":25,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":438,"endColumn":60},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"For this sprite only\"","line":444,"column":49,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":444,"endColumn":69},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":450,"column":33,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":450,"endColumn":68},{"ruleId":"react/jsx-no-bind","severity":2,"message":"JSX props should not use arrow functions","line":459,"column":21,"nodeType":"JSXAttribute","messageId":"arrowFunc","endLine":459,"endColumn":57},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Make a List\"","line":460,"column":18,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":460,"endColumn":29}],"suppressedMessages":[],"errorCount":33,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport VM from 'scratch-vm';\nimport Prompt from '../../containers/prompt.jsx';\nimport AddonHooks from '../../addons/hooks.js';\nimport {Theme} from '../../lib/themes';\nimport styles from './nanoscript-editor.css';\n\nfunction NanoscriptEditor ({theme, vm}) {\n const el = React.useRef(null);\n const editorRef = React.useRef(null);\n const variableRef = React.useRef([]);\n const listsRef = React.useRef([]);\n const spriteOnlyVariablesRef = React.useRef([]);\n const spriteOnlyListsRef = React.useRef([]);\n const [, setVarsState] = React.useState([]); // used to trigger renders\n\n React.useEffect(() => {\n let disposed = false;\n\n async function loadEditor () {\n const {\n EditorView,\n basicSetup,\n HighlightStyle,\n syntaxHighlighting,\n tags,\n autocompletion,\n completeFromList\n } = await import(/* webpackChunkName: \"nanoscript-editor\"*/ './ob-codemirror-imports.js');\n\n // Define hghlight rules using CSS vars\n const scratchHighlight = HighlightStyle.define([\n {tag: tags.variableName, color: 'var(--data-primary)'},\n {tag: tags.keyword, color: 'var(--pen-primary)'},\n {tag: tags.string, color: 'var(--cm-string)'},\n {tag: tags.number, color: 'var(--cm-number)'},\n {tag: tags.function, color: 'var(--cm-function)'},\n {tag: tags.operator, color: 'var(--cm-operator)'}\n ]);\n\n const {StreamLanguage} = await import('@codemirror/language');\n // helper to escape regex\n const escapeRegExp = s => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const scratchSyntax = StreamLanguage.define({\n token (stream) {\n // Keywords\n if (stream.match(/\\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\\b/)) return 'keyword';\n // Operators\n if (stream.match(/\\b(and|or|not|join|\\+|\\-|\\*|\\/|(abs|sin|cos) of .*)\\b/)) return 'operator';\n // Functions\n if (stream.match(/\\b(join|pick random|length of)\\b/)) return 'function';\n\n // Try to match variable or list names (take current refs)\n const names = (variableRef.current || []).concat(listsRef.current || []);\n if (names && names.length) {\n // sort by length to match longest first\n const sorted = names.slice().sort((a, b) => b.length - a.length)\n .map(escapeRegExp);\n const rx = new RegExp(`^(${sorted.join('|')})`, 'i');\n const m = stream.match(rx, true);\n if (m) {\n return 'variableName';\n }\n }\n stream.next();\n return 'variable';\n }\n });\n\n // NanoScript autocomplete suggestions (static)\n const staticCompletions = [\n // Control flow keywords\n {label: 'when flag clicked', type: 'keyword'},\n {label: 'when key pressed', type: 'keyword'},\n {label: 'when this sprite clicked', type: 'keyword'},\n {label: 'when I start as a clone', type: 'keyword'},\n {label: 'forever', type: 'keyword'},\n {label: 'repeat', type: 'keyword'},\n {label: 'if', type: 'keyword'},\n {label: 'else', type: 'keyword'},\n {label: 'end', type: 'keyword'},\n {label: 'wait', type: 'keyword'},\n {label: 'stop', type: 'keyword'},\n \n // Motion blocks\n {label: 'move', type: 'function'},\n {label: 'turn right', type: 'function'},\n {label: 'turn left', type: 'function'},\n {label: 'go to', type: 'function'},\n {label: 'glide to', type: 'function'},\n {label: 'point in direction', type: 'function'},\n {label: 'point towards', type: 'function'},\n {label: 'change x by', type: 'function'},\n {label: 'set x to', type: 'function'},\n {label: 'change y by', type: 'function'},\n {label: 'set y to', type: 'function'},\n \n // Looks blocks\n {label: 'say', type: 'function'},\n {label: 'think', type: 'function'},\n {label: 'show', type: 'function'},\n {label: 'hide', type: 'function'},\n {label: 'switch costume to', type: 'function'},\n {label: 'next costume', type: 'function'},\n {label: 'change size by', type: 'function'},\n {label: 'set size to', type: 'function'},\n {label: 'change color effect by', type: 'function'},\n {label: 'set color effect to', type: 'function'},\n {label: 'clear graphic effects', type: 'function'},\n \n // Sound blocks\n {label: 'play sound', type: 'function'},\n {label: 'stop all sounds', type: 'function'},\n {label: 'change volume by', type: 'function'},\n {label: 'set volume to', type: 'function'},\n \n // Events\n {label: 'broadcast', type: 'function'},\n {label: 'broadcast and wait', type: 'function'},\n {label: 'when I receive', type: 'keyword'},\n \n // Variables and lists\n {label: 'set variable to', type: 'function'},\n {label: 'change variable by', type: 'function'},\n {label: 'add to list', type: 'function'},\n {label: 'delete from list', type: 'function'},\n {label: 'insert into list', type: 'function'},\n {label: 'replace list item', type: 'function'},\n \n // Operators\n {label: 'and', type: 'operator'},\n {label: 'or', type: 'operator'},\n {label: 'not', type: 'operator'},\n {label: 'join', type: 'function'},\n {label: 'letter of', type: 'function'},\n {label: 'length of', type: 'function'},\n {label: 'round', type: 'function'},\n {label: 'abs of', type: 'function'},\n {label: 'floor of', type: 'function'},\n {label: 'ceiling of', type: 'function'},\n {label: 'sqrt of', type: 'function'},\n {label: 'sin of', type: 'function'},\n {label: 'cos of', type: 'function'},\n {label: 'tan of', type: 'function'},\n {label: 'asin of', type: 'function'},\n {label: 'acos of', type: 'function'},\n {label: 'atan of', type: 'function'},\n {label: 'pick random', type: 'function'},\n \n // Sensing blocks\n {label: 'touching', type: 'function'},\n {label: 'touching color', type: 'function'},\n {label: 'color is touching', type: 'function'},\n {label: 'ask', type: 'function'},\n {label: 'key pressed', type: 'function'},\n {label: 'mouse down', type: 'function'},\n {label: 'distance to', type: 'function'}\n ];\n\n // Function to get completions with prefix matching (dynamic includes variables & lists)\n function scratchCompletions (context) {\n const word = context.matchBefore(/\\w*/);\n if (!word || (word.from === word.to && !context.explicit)) {\n return null;\n }\n\n const dynamicVars = (variableRef.current || []).map(v => ({label: v, type: 'variable', info: 'Variable'}));\n const dynamicLists = (listsRef.current || []).map(l => ({label: l, type: 'variable', info: 'List'}));\n const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);\n\n return {\n from: word.from,\n options: allOptions.filter(option =>\n option.label.toLowerCase().startsWith(word.text.toLowerCase())\n ),\n validFor: /\\w*/\n };\n }\n\n if (!el.current || disposed) return;\n\n const cmTheme = EditorView.theme({\n '&': {\n backgroundColor: 'var(--ui-white)',\n color: 'var(--text-primary)',\n height: '100%',\n borderTopRightRadius: 'var(--space)',\n borderBottomRightRadius: 'var(--space)',\n border: '1px solid var(--ui-black-transparent)',\n height: '100%'\n },\n '.cm-scroller': {\n maxHeight: '100%',\n overflow: 'auto'\n },\n '.cm-content': {caretColor: 'var(--looks-secondary)'},\n '.cm-cursor': {borderLeft: '2px solid var(--looks-secondary)'},\n '.cm-focused': {outline: 'none'},\n '.cm-selectionBackground, ::selection': {backgroundColor: 'rgba(255, 140, 26, 0.3)'},\n '.cm-gutters': {\n backgroundColor: 'var(--ui-tertiary)',\n borderRight: '1px solid var(--ui-black-transparent)'\n },\n '.cm-completionLabel': {\n fontSize: '13px'\n }\n }, {dark: theme.isDark() ?? false});\n\n editorRef.current = new EditorView({\n doc: `when flag clicked\nsay \"Hello World!\"\nrepeat 10\n say (join \"hi \" \"there\")\nend`,\n extensions: [\n basicSetup,\n scratchSyntax,\n syntaxHighlighting(scratchHighlight),\n autocompletion({override: [scratchCompletions]}),\n cmTheme\n ],\n parent: el.current\n });\n }\n\n loadEditor();\n\n return () => {\n disposed = true;\n if (editorRef.current) {\n editorRef.current.destroy();\n }\n };\n }, [theme]);\n\n // Update variable/list refs from VM and listen for changes\n React.useEffect(() => {\n if (!vm) return undefined;\n\n function updateVarsLists () {\n try {\n const editing = vm.editingTarget || vm.runtime.getTargetForStage();\n if (!editing) {\n variableRef.current = [];\n listsRef.current = [];\n spriteOnlyVariablesRef.current = [];\n spriteOnlyListsRef.current = [];\n setVarsState([]);\n return;\n }\n\n const isStage = editing.isStage;\n const spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);\n const spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);\n let stageVars = [];\n let stageLists = [];\n \n // Get stage variables\n const stage = vm.runtime.getTargetForStage();\n if (stage) {\n stageVars = stage.getAllVariableNamesInScopeByType('') || [];\n stageLists = stage.getAllVariableNamesInScopeByType('list') || [];\n }\n \n // For stage: only show stage variables\n // For sprite: show stage variables in \"For all sprites\", sprite-only in \"For this sprite only\"\n if (isStage) {\n variableRef.current = stageVars;\n listsRef.current = stageLists;\n spriteOnlyVariablesRef.current = [];\n spriteOnlyListsRef.current = [];\n } else {\n variableRef.current = stageVars;\n listsRef.current = stageLists;\n spriteOnlyVariablesRef.current = spriteVars;\n spriteOnlyListsRef.current = spriteLists;\n }\n \n // trigger render\n setVarsState(variableRef.current.slice());\n } catch (e) {\n // ignore\n }\n }\n\n updateVarsLists();\n vm.on('targetsUpdate', updateVarsLists);\n vm.on('PROJECT_CHANGED', updateVarsLists);\n return () => {\n vm.off('targetsUpdate', updateVarsLists);\n vm.off('PROJECT_CHANGED', updateVarsLists);\n };\n }, [vm]);\n\n // handlers for make variable/list and inserting into editor\n const [promptProps, setPromptProps] = React.useState(null);\n\n const makeVariable = (type = '') => {\n if (!vm) {\n alert('VM not available');\n return;\n }\n\n // Determine editing target and stage status\n const editing = vm.editingTarget || vm.runtime.getTargetForStage();\n const isStage = editing && editing.isStage;\n\n // Compute props for Prompt component similar to Blocks.handlePromptStart\n const title = type === 'list' ? 'Make a List' : 'Make a Variable';\n const varTypeConst = type === 'list' ? 'list' : '';\n const showListMessage = type === 'list';\n const showCloudOption = (varTypeConst === '') && (vm.runtime && typeof vm.runtime.canAddCloudVariable === 'function' ? vm.runtime.canAddCloudVariable() : false);\n\n setPromptProps({\n defaultValue: '',\n isStage: !!(editing && editing.isStage),\n showListMessage,\n label: type === 'list' ? 'List name' : 'Variable name',\n showCloudOption,\n showVariableOptions: true,\n title,\n varType: varTypeConst\n });\n };\n\n const insertIntoEditor = name => {\n if (!editorRef.current) return;\n try {\n const view = editorRef.current;\n const pos = view.state.selection.main.head;\n view.dispatch({changes: {from: pos, insert: name}});\n view.focus();\n } catch (e) {\n // ignore\n }\n };\n\n const handlePromptCancel = () => setPromptProps(null);\n const handlePromptOk = (input, variableOptions) => {\n try {\n const varType = (promptProps && promptProps.varType) || '';\n let allVarNames = [];\n if (vm && vm.runtime && typeof vm.runtime.getAllVarNamesOfType === 'function') {\n try {\n allVarNames = vm.runtime.getAllVarNamesOfType(varType) || [];\n } catch (e) {\n allVarNames = [];\n }\n }\n const editing = vm.editingTarget || (vm.runtime && vm.runtime.getTargetForStage && vm.runtime.getTargetForStage());\n if (editing && !editing.isStage && vm.runtime && typeof vm.runtime.getTargetForStage === 'function') {\n try {\n const stage = vm.runtime.getTargetForStage();\n if (stage && typeof stage.getAllVariableNamesInScopeByType === 'function') {\n const stageVars = stage.getAllVariableNamesInScopeByType(varType) || [];\n for (const s of stageVars) {\n if (!allVarNames.includes(s)) allVarNames.push(s);\n }\n }\n } catch (e) {\n // ignore\n }\n }\n\n const ws = AddonHooks.blocklyWorkspace;\n const isLocal = variableOptions && variableOptions.scope === 'local';\n const isCloud = !!(variableOptions && variableOptions.isCloud);\n if (ws && typeof ws.createVariable === 'function') {\n try {\n ws.createVariable(input, varType, null, !!isLocal, !!isCloud);\n } catch (e) {\n // ignore\n }\n }\n } finally {\n setPromptProps(null);\n }\n };\n\n return (\n {promptProps ? (\n
\n ) : null}\n
\n
Variables \n \n
For all sprites \n
\n {(variableRef.current || []).map(v => (\n insertIntoEditor(v)}\n >{v} \n ))}\n
\n {spriteOnlyVariablesRef.current && spriteOnlyVariablesRef.current.length > 0 && (\n <>\n
For this sprite only \n
\n {spriteOnlyVariablesRef.current.map(v => (\n insertIntoEditor(v)}\n >{v} \n ))}\n
\n >\n )}\n
\n makeVariable('')}\n >Make a Variable \n
\n\n
Lists \n \n
For all sprites \n
\n {(listsRef.current || []).map(l => (\n insertIntoEditor(l)}\n >{l} \n ))}\n
\n {spriteOnlyListsRef.current && spriteOnlyListsRef.current.length > 0 && (\n <>\n
For this sprite only \n
\n {spriteOnlyListsRef.current.map(l => (\n insertIntoEditor(l)}\n >{l} \n ))}\n
\n >\n )}\n
\n makeVariable('list')}\n >Make a List \n
\n
\n
\n
);\n}\n\nNanoscriptEditor.propTypes = {\n theme: PropTypes.instanceOf(Theme),\n vm: PropTypes.instanceOf(VM).isRequired\n};\n\nexport default NanoscriptEditor;\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/ob-nanoscript-editor/ob-codemirror-imports.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"export {EditorView} from '@codemirror/view';\nexport {basicSetup} from 'codemirror';\nexport {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';\nexport {styleTags, tags} from '@lezer/highlight';\nexport {autocompletion, completeFromList} from '@codemirror/autocomplete';\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/components/toggle-buttons/toggle-buttons.jsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/blocks.jsx","messages":[],"suppressedMessages":[{"ruleId":"max-len","severity":2,"message":"This line has a length of 138. Maximum allowed is 120.","line":53,"column":1,"nodeType":"Program","messageId":"max","endLine":53,"endColumn":139,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 138. Maximum allowed is 120.","line":59,"column":1,"nodeType":"Program","messageId":"max","endLine":59,"endColumn":139,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 148. Maximum allowed is 120.","line":65,"column":1,"nodeType":"Program","messageId":"max","endLine":65,"endColumn":149,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"max-len","severity":2,"message":"This line has a length of 124. Maximum allowed is 120.","line":71,"column":1,"nodeType":"Program","messageId":"max","endLine":71,"endColumn":125,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'anyModalVisible' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":669,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":669,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'canUseCloud' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":670,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":670,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'customStageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":671,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":671,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'stageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":675,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":675,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isRtl' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":677,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":677,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isVisible' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":678,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":678,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onActivateColorPicker' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":679,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":679,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onOpenConnectionModal' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":680,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":680,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onOpenSoundRecorder' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":681,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":681,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'updateToolboxState' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":684,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":684,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onActivateCustomProcedures' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":685,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":685,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onRequestCloseCustomProcedures' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":687,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":687,"endColumn":43,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'toolboxXML' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":688,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":688,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'updateMetricsProp' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":689,"column":28,"nodeType":"Identifier","messageId":"unusedVar","endLine":689,"endColumn":45,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'useCatBlocks' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":690,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":690,"endColumn":25,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'workspaceMetrics' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":691,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":691,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/extension-library.jsx","messages":[{"ruleId":"react/no-unused-prop-types","severity":2,"message":"'onEnableProcedureReturns' PropType is defined but prop is never used","line":192,"column":5,"nodeType":"Identifier","messageId":"unusedPropType","endLine":192,"endColumn":29}],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":148,"column":25,"nodeType":"CallExpression","messageId":"unexpected","endLine":148,"endColumn":35,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import bindAll from 'lodash.bindall';\nimport PropTypes from 'prop-types';\nimport React from 'react';\nimport VM from 'scratch-vm';\nimport {defineMessages, injectIntl, intlShape} from 'react-intl';\nimport log from '../lib/log';\n\nimport extensionLibraryContent, {\n galleryError,\n galleryLoading,\n galleryMore\n} from '../lib/libraries/extensions/index.jsx';\nimport extensionTags from '../lib/libraries/tw-extension-tags';\n\nimport LibraryComponent from '../components/library/library.jsx';\nimport extensionIcon from '../components/action-menu/icon--sprite.svg';\n\nconst messages = defineMessages({\n extensionTitle: {\n defaultMessage: 'Choose an Extension',\n description: 'Heading for the extension library',\n id: 'gui.extensionLibrary.chooseAnExtension'\n }\n});\n\nconst toLibraryItem = extension => {\n if (typeof extension === 'object') {\n return ({\n rawURL: extension.iconURL || extensionIcon,\n ...extension\n });\n }\n return extension;\n};\n\nconst translateGalleryItem = (extension, locale) => ({\n ...extension,\n name: extension.nameTranslations[locale] || extension.name,\n description: extension.descriptionTranslations[locale] || extension.description\n});\n\nlet cachedGallery = null;\n\nconst fetchLibrary = async () => {\n const res = await fetch('https://omniblocks.github.io/extensions/generated-metadata/extensions-v0.json');\n if (!res.ok) {\n throw new Error(`HTTP status ${res.status}`);\n }\n const data = await res.json();\n return data.extensions.map(extension => ({\n name: extension.name,\n nameTranslations: extension.nameTranslations || {},\n description: extension.description,\n descriptionTranslations: extension.descriptionTranslations || {},\n extensionId: extension.id,\n extensionURL: `https://omniblocks.github.io/extensions/${extension.slug}.js`,\n iconURL: `https://omniblocks.github.io/extensions/${extension.image || 'images/unknown.svg'}`,\n tags: ['tw'],\n credits: [\n ...(extension.original || []),\n ...(extension.by || [])\n ].map(credit => {\n if (credit.link) {\n return (\n \n {credit.name}\n \n );\n }\n return credit.name;\n }),\n docsURI: extension.docs ? `https://omniblocks.github.io/extensions/${extension.slug}` : null,\n samples: extension.samples ? extension.samples.map(sample => ({\n href: `${process.env.ROOT}editor?project_url=https://omniblocks.github.io/extensions/samples/${encodeURIComponent(sample)}.sb3`,\n text: sample\n })) : null,\n incompatibleWithScratch: !extension.scratchCompatible,\n featured: true\n }));\n};\n\nclass ExtensionLibrary extends React.PureComponent {\n constructor (props) {\n super(props);\n bindAll(this, [\n 'handleItemSelect'\n ]);\n this.state = {\n gallery: cachedGallery,\n galleryError: null,\n galleryTimedOut: false\n };\n }\n componentDidMount () {\n if (!this.state.gallery) {\n const timeout = setTimeout(() => {\n this.setState({\n galleryTimedOut: true\n });\n }, 750);\n\n fetchLibrary()\n .then(gallery => {\n cachedGallery = gallery;\n this.setState({\n gallery\n });\n clearTimeout(timeout);\n })\n .catch(error => {\n log.error(error);\n this.setState({\n galleryError: error\n });\n clearTimeout(timeout);\n });\n }\n }\n handleItemSelect (item) {\n if (item.href) {\n return;\n }\n\n const extensionId = item.extensionId;\n\n if (extensionId === 'custom_extension') {\n this.props.onOpenCustomExtensionModal();\n return;\n }\n\n const url = item.extensionURL ? item.extensionURL : extensionId;\n if (!item.disabled) {\n if (this.props.vm.extensionManager.isExtensionLoaded(extensionId)) {\n this.props.onCategorySelected(extensionId);\n } else {\n this.props.vm.extensionManager.loadExtensionURL(url)\n .then(() => {\n this.props.onCategorySelected(extensionId);\n })\n .catch(err => {\n log.error(err);\n // eslint-disable-next-line no-alert\n alert(err);\n });\n }\n }\n }\n render () {\n let library = null;\n if (this.state.gallery || this.state.galleryError || this.state.galleryTimedOut) {\n library = extensionLibraryContent.map(toLibraryItem);\n library.push('---');\n if (this.state.gallery) {\n library.push(toLibraryItem(galleryMore));\n const locale = this.props.intl.locale;\n library.push(\n ...this.state.gallery\n .map(i => translateGalleryItem(i, locale))\n .map(toLibraryItem)\n );\n } else if (this.state.galleryError) {\n library.push(toLibraryItem(galleryError));\n } else {\n library.push(toLibraryItem(galleryLoading));\n }\n }\n\n return (\n \n );\n }\n}\n\nExtensionLibrary.propTypes = {\n intl: intlShape.isRequired,\n onCategorySelected: PropTypes.func,\n onEnableProcedureReturns: PropTypes.func,\n onOpenCustomExtensionModal: PropTypes.func,\n onRequestClose: PropTypes.func,\n visible: PropTypes.bool,\n vm: PropTypes.instanceOf(VM).isRequired // eslint-disable-line react/no-unused-prop-types\n};\n\nexport default injectIntl(ExtensionLibrary);\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/containers/gui.jsx","messages":[],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'assetHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":84,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":84,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'cloudHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":85,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":85,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'error' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":86,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":86,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isError' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":87,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":87,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isScratchDesktop' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":88,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":88,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isShowingProject' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":89,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":89,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onProjectLoaded' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":90,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":90,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onStorageInit' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":91,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":91,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onUpdateProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":92,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":92,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onVmInit' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":93,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":93,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectHost' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":94,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":94,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":95,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":95,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/brand.js","messages":[],"suppressedMessages":[{"ruleId":"import/no-commonjs","severity":2,"message":"Expected \"export\" or \"export default\"","line":3,"column":1,"nodeType":"MemberExpression","endLine":3,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"// Legacy export format because this is used by some build-time scripts stuck in the past.\n// eslint-disable-next-line import/no-commonjs\nmodule.exports = {\n APP_NAME: 'Visual IDE', // the name of the Scratch mod\n APP_NAMES: {\n PROJECT: 'OmniBlocks'\n },\n APP_VERSION: process.env.APP_VERSION || 'v0.5.8-alpha' // Dynamically injected at build time from git tags\n};\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/libraries/extensions/index.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'returnIcon' is defined but never used. Allowed unused vars must match /^_/u.","line":51,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":51,"endColumn":18}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport {FormattedMessage} from 'react-intl';\n\nimport musicIconURL from './music/music.png';\nimport musicInsetIconURL from './music/music-small.svg';\n\nimport penIconURL from './pen/pen.png';\nimport penInsetIconURL from './pen/pen-small.svg';\n\nimport videoSensingIconURL from './videoSensing/video-sensing.png';\nimport videoSensingInsetIconURL from './videoSensing/video-sensing-small.svg';\n\nimport text2speechIconURL from './text2speech/text2speech.png';\nimport text2speechInsetIconURL from './text2speech/text2speech-small.svg';\n\nimport translateIconURL from './translate/translate.png';\nimport translateInsetIconURL from './translate/translate-small.png';\n\nimport makeymakeyIconURL from './makeymakey/makeymakey.png';\nimport makeymakeyInsetIconURL from './makeymakey/makeymakey-small.svg';\n\nimport microbitIconURL from './microbit/microbit.png';\nimport microbitInsetIconURL from './microbit/microbit-small.svg';\nimport microbitConnectionIconURL from './microbit/microbit-illustration.svg';\nimport microbitConnectionSmallIconURL from './microbit/microbit-small.svg';\n\nimport ev3IconURL from './ev3/ev3.png';\nimport ev3InsetIconURL from './ev3/ev3-small.svg';\nimport ev3ConnectionIconURL from './ev3/ev3-hub-illustration.svg';\nimport ev3ConnectionSmallIconURL from './ev3/ev3-small.svg';\n\nimport wedo2IconURL from './wedo2/wedo.png'; // TODO: Rename file names to match variable/prop names?\nimport wedo2InsetIconURL from './wedo2/wedo-small.svg';\nimport wedo2ConnectionIconURL from './wedo2/wedo-illustration.svg';\nimport wedo2ConnectionSmallIconURL from './wedo2/wedo-small.svg';\nimport wedo2ConnectionTipIconURL from './wedo2/wedo-button-illustration.svg';\n\nimport boostIconURL from './boost/boost.png';\nimport boostInsetIconURL from './boost/boost-small.svg';\nimport boostConnectionIconURL from './boost/boost-illustration.svg';\nimport boostConnectionSmallIconURL from './boost/boost-small.svg';\nimport boostConnectionTipIconURL from './boost/boost-button-illustration.svg';\n\nimport gdxforIconURL from './gdxfor/gdxfor.png';\nimport gdxforInsetIconURL from './gdxfor/gdxfor-small.svg';\nimport gdxforConnectionIconURL from './gdxfor/gdxfor-illustration.svg';\nimport gdxforConnectionSmallIconURL from './gdxfor/gdxfor-small.svg';\n\nimport twIcon from './tw/tw.svg';\nimport customExtensionIcon from './custom/custom.svg';\nimport returnIcon from './custom/return.svg';\nimport galleryIcon from './gallery/gallery.svg';\nimport {APP_NAME} from '../../brand';\n\nexport default [\n {\n name: (\n \n ),\n extensionId: 'music',\n iconURL: musicIconURL,\n insetIconURL: musicInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'pen',\n iconURL: penIconURL,\n insetIconURL: penInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'videoSensing',\n iconURL: videoSensingIconURL,\n insetIconURL: videoSensingInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'text2speech',\n collaborator: 'Amazon Web Services',\n iconURL: text2speechIconURL,\n insetIconURL: text2speechInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n internetConnectionRequired: true\n },\n {\n name: (\n \n ),\n extensionId: 'translate',\n collaborator: 'Google',\n iconURL: translateIconURL,\n insetIconURL: translateInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n internetConnectionRequired: true\n },\n {\n name: 'Makey Makey',\n extensionId: 'makeymakey',\n collaborator: 'JoyLabz',\n iconURL: makeymakeyIconURL,\n insetIconURL: makeymakeyInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true\n },\n {\n name: 'micro:bit',\n extensionId: 'microbit',\n collaborator: 'micro:bit',\n iconURL: microbitIconURL,\n insetIconURL: microbitInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: microbitConnectionIconURL,\n connectionSmallIconURL: microbitConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/microbit'\n },\n {\n name: 'LEGO MINDSTORMS EV3',\n extensionId: 'ev3',\n collaborator: 'LEGO',\n iconURL: ev3IconURL,\n insetIconURL: ev3InsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: ev3ConnectionIconURL,\n connectionSmallIconURL: ev3ConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/ev3'\n },\n {\n name: 'LEGO BOOST',\n extensionId: 'boost',\n collaborator: 'LEGO',\n iconURL: boostIconURL,\n insetIconURL: boostInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: true,\n connectionIconURL: boostConnectionIconURL,\n connectionSmallIconURL: boostConnectionSmallIconURL,\n connectionTipIconURL: boostConnectionTipIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/boost'\n },\n {\n name: 'LEGO Education WeDo 2.0',\n extensionId: 'wedo2',\n collaborator: 'LEGO',\n iconURL: wedo2IconURL,\n insetIconURL: wedo2InsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: true,\n connectionIconURL: wedo2ConnectionIconURL,\n connectionSmallIconURL: wedo2ConnectionSmallIconURL,\n connectionTipIconURL: wedo2ConnectionTipIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/wedo'\n },\n {\n name: 'Go Direct Force & Acceleration',\n extensionId: 'gdxfor',\n collaborator: 'Vernier',\n iconURL: gdxforIconURL,\n insetIconURL: gdxforInsetIconURL,\n description: (\n \n ),\n tags: ['scratch'],\n featured: true,\n disabled: false,\n bluetoothRequired: true,\n internetConnectionRequired: true,\n launchPeripheralConnectionFlow: true,\n useAutoScan: false,\n connectionIconURL: gdxforConnectionIconURL,\n connectionSmallIconURL: gdxforConnectionSmallIconURL,\n connectingMessage: (\n \n ),\n helpLink: 'https://scratch.mit.edu/vernier'\n },\n {\n name: (\n \n ),\n extensionId: 'tw',\n iconURL: twIcon,\n description: (\n \n ),\n incompatibleWithScratch: true,\n tags: ['tw'],\n featured: true\n },\n {\n name: (\n \n ),\n extensionId: 'custom_extension',\n iconURL: customExtensionIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n // Not marked as incompatible with Scratch so that clicking on it doesn't show a prompt\n }\n];\n\nexport const galleryLoading = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n\nexport const galleryMore = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n\nexport const galleryError = {\n name: (\n \n ),\n href: 'https://extensions.turbowarp.org/',\n extensionId: 'gallery',\n iconURL: galleryIcon,\n description: (\n \n ),\n tags: ['tw'],\n featured: true\n};\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/libraries/tw-extension-tags.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import {APP_NAME} from '../brand';\n\n// Because these are all brand names, it is unnecessary for them to be translatable.\nexport default [\n {tag: 'scratch', intlLabel: 'Scratch'},\n {tag: 'tw', intlLabel: 'TurboWarp'},\n {tag: 'ob', intlLabel: APP_NAME}\n];\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/lib/tw-state-manager-hoc.jsx","messages":[],"suppressedMessages":[{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":292,"column":21,"nodeType":"CallExpression","messageId":"unexpected","endLine":292,"endColumn":78,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-alert","severity":2,"message":"Unexpected alert.","line":345,"column":21,"nodeType":"CallExpression","messageId":"unexpected","endLine":345,"endColumn":81,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-alert","severity":2,"message":"Unexpected confirm.","line":499,"column":22,"nodeType":"CallExpression","messageId":"unexpected","endLine":499,"endColumn":73,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'intl' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":515,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":515,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'customStageSize' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":516,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":516,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isFullScreen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":517,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":517,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isPlayerOnly' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":518,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":518,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'isEmbedded' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":519,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":519,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'projectChanged' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":520,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":520,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'compilerOptions' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":521,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":521,"endColumn":32,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'runtimeOptions' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":522,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":522,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'highQualityPen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":523,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":523,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'framerate' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":524,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":524,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'interpolation' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":525,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":525,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'turbo' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":526,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":526,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetIsFullScreen' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":527,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":527,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetIsPlayerOnly' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":528,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":528,"endColumn":34,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":529,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":529,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'onSetUsername' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":530,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":530,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'reduxProjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":531,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":531,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'routingStyle' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":532,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":532,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'username' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":533,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":533,"endColumn":25,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'vm' is assigned a value but never used. Allowed unused vars must match /^_/u.","line":534,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":534,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/credits/credits.jsx","messages":[],"suppressedMessages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Credits\"","line":62,"column":27,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":63,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"The\"","line":66,"column":16,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":67,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"project is made possible by the work of many volunteers.\"","line":67,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":68,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp\"","line":73,"column":21,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":73,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is based on\"","line":75,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":75,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp\"","line":75,"column":77,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":75,"endColumn":86,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \".\"","line":75,"column":90,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":76,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Scratch\"","line":80,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":80,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is based on the work of the\"","line":82,"column":27,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":56,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Scratch contributors\"","line":82,"column":98,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":118,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"but is not endorsed by Scratch in any way.\"","line":82,"column":122,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":83,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Donate to support Scratch.\"","line":85,"column":58,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":87,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Contributors\"","line":91,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":91,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Addons\"","line":95,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":95,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"TurboWarp Extension Gallery\"","line":99,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":99,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Documentation\"","line":103,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":103,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Translators\"","line":107,"column":17,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":107,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"More than 100 people have helped translate\"","line":108,"column":16,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":109,"endColumn":60,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"and its addons into many languages\n — far more than we could hope to list here.\"","line":109,"column":70,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":111,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Individual contributors are listed in no particular order.\n The order is randomized each visit.\"","line":115,"column":20,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":118,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport render from '../app-target';\nimport styles from '../info.css';\nimport Header from '../ws-components/header/header.jsx';\n\nimport {APP_NAMES} from '../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nimport {applyGuiColors} from '../../lib/themes/guiHelpers';\nimport {detectTheme} from '../../lib/themes/themePersistance';\nimport UserData from './users';\n\n/* eslint-disable react/jsx-no-literals */\n\napplyGuiColors(detectTheme());\ndocument.documentElement.lang = 'en';\n\nconst User = ({image, text, href}) => (\n \n \n \n {text}\n
\n \n);\nUser.propTypes = {\n image: PropTypes.string.isRequired,\n text: PropTypes.string.isRequired,\n href: PropTypes.string\n};\n\nconst UserList = ({users}) => (\n \n {users.map((data, index) => (\n \n ))}\n
\n);\nUserList.propTypes = {\n users: PropTypes.arrayOf(PropTypes.object)\n};\n\nconst Credits = () => (\n <>\n \n \n {APP_NAME} Credits\n \n \n \n \n The {APP_NAME} project is made possible by the work of many volunteers.\n
\n \n {APP_NAME !== 'TurboWarp' && (\n // Be kind and considerate. Don't remove this :)\n \n TurboWarp \n \n {APP_NAME} is based on TurboWarp .\n
\n \n )}\n \n \n \n \n TurboWarp Extension Gallery \n \n \n \n \n Translators \n \n More than 100 people have helped translate {APP_NAME} and its addons into many languages\n — far more than we could hope to list here.\n
\n \n \n \n \n Individual contributors are listed in no particular order.\n The order is randomized each visit.\n \n
\n \n >\n);\n\nrender( );\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/home/home.jsx","messages":[{"ruleId":"no-unused-vars","severity":2,"message":"'PropTypes' is defined but never used. Allowed unused vars must match /^_/u.","line":2,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":2,"endColumn":17},{"ruleId":"max-len","severity":2,"message":"This line has a length of 131. Maximum allowed is 120.","line":50,"column":1,"nodeType":"Program","messageId":"max","endLine":50,"endColumn":132}],"suppressedMessages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is a project to develop simple IDEs for programming in the browser.\"","line":50,"column":60,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":50,"endColumn":128,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Current IDEs\"","line":54,"column":21,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":54,"endColumn":33,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"— Coming soon\"","line":64,"column":84,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":64,"endColumn":98,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Open\"","line":71,"column":34,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":71,"endColumn":38,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"is free software under the AGPL 3.0 license\"","line":79,"column":31,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":79,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"You can view the source code on\"","line":80,"column":20,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":81,"endColumn":53,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"GitHub\"","line":81,"column":105,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":81,"endColumn":111,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \".\"","line":81,"column":115,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":82,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport PropTypes from 'prop-types';\nimport render from '../app-target.js';\n\nimport {APP_NAMES} from '../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nimport {applyGuiColors} from '../../lib/themes/guiHelpers.js';\nimport {detectTheme} from '../../lib/themes/themePersistance.js';\nimport styles from '../info.css';\nimport localStyles from './home.css';\nimport Header from '../ws-components/header/header.jsx';\n/* eslint-disable react/jsx-no-literals */\n\napplyGuiColors(detectTheme());\ndocument.documentElement.lang = 'en';\n\nconst IDE_CARDS = [\n {\n title: 'Visual IDE',\n href: 'editor.html',\n desc: 'A Scratch mod with text-based programming support!'\n },\n {\n title: 'PyVisual',\n href: null,\n desc: 'Write Python code in blocks!',\n coming: true\n },\n {\n title: 'OmniPython',\n href: null,\n desc: 'An advanced Python IDE right in your browser.',\n coming: true\n },\n {\n title: 'And more...',\n href: null,\n desc: 'IDEs for C and more!',\n coming: true\n }\n];\n\nconst Credits = () => (\n <>\n \n \n \n\n \n Current IDEs \n\n \n {IDE_CARDS.map(ide => (\n
\n \n {ide.title}\n {ide.coming ? — Coming soon : null}\n \n {ide.desc}
\n {ide.href ? (\n Open \n ) : null}\n \n ))}\n
\n \n\n \n {APP_NAME} is free software under the AGPL 3.0 license \n \n You can view the source code on GitHub .\n
\n \n \n >\n);\n\nrender( );\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/src/playground/ws-components/header/header.jsx","messages":[{"ruleId":"react/jsx-no-literals","severity":2,"message":"Missing JSX expression container around literal string: \"Credits\"","line":23,"column":10,"nodeType":"JSXText","messageId":"literalNotInJSXExpression","endLine":25,"endColumn":9}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"import React from 'react';\nimport styles from './header.css';\nimport logo from './costume1.svg';\nimport {APP_NAMES} from '../../../lib/brand';\n\nconst APP_NAME = APP_NAMES.PROJECT;\nconst Header = () => (\n <>
>\n);\n\nexport default Header;\n","usedDeprecatedRules":[{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/home/runner/work/scratch-gui/scratch-gui/webpack.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"output":"const defaultsDeep = require('lodash.defaultsdeep');\nconst path = require('path');\nconst webpack = require('webpack');\n\n// Plugins\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\n// PostCss\nconst autoprefixer = require('autoprefixer');\nconst postcssVars = require('postcss-simple-vars');\nconst postcssImport = require('postcss-import');\n\nconst STATIC_PATH = process.env.STATIC_PATH || '/static';\nconst {APP_NAME} = require('./src/lib/brand');\nconst {version} = require('./package.json');\n\nconst root = process.env.ROOT || '';\nif (root.length > 0 && !root.endsWith('/')) {\n throw new Error('If ROOT is defined, it must have a trailing slash.');\n}\n\nconst htmlWebpackPluginCommon = {\n root: root,\n meta: JSON.parse(process.env.EXTRA_META || '{}'),\n APP_NAME\n};\n\n// When this changes, the path for all JS files will change, bypassing any HTTP caches\nconst CACHE_EPOCH = 'pentapod';\n\nconst base = {\n mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',\n devtool: process.env.SOURCEMAP || (process.env.NODE_ENV === 'production' ? false : 'cheap-module-source-map'),\n devServer: {\n contentBase: path.resolve(__dirname, 'build'),\n host: '0.0.0.0',\n disableHostCheck: true,\n compress: true,\n port: process.env.PORT || 8601,\n // allows ROUTING_STYLE=wildcard to work properly\n historyApiFallback: {\n rewrites: [\n {from: /^\\/\\d+\\/?$/, to: '/index.html'},\n {from: /^\\/\\d+\\/fullscreen\\/?$/, to: '/fullscreen.html'},\n {from: /^\\/\\d+\\/editor\\/?$/, to: '/editor.html'},\n {from: /^\\/\\d+\\/embed\\/?$/, to: '/embed.html'},\n {from: /^\\/addons\\/?$/, to: '/addons.html'}\n ]\n },\n hot: true\n },\n output: {\n library: 'GUI',\n filename: (\n process.env.NODE_ENV === 'production' ? `js/${CACHE_EPOCH}/[name].[contenthash].js` : 'js/[name].js'\n ),\n chunkFilename: (\n process.env.NODE_ENV === 'production' ? `js/${CACHE_EPOCH}/[name].[contenthash].js` : 'js/[name].js'\n ),\n publicPath: root\n },\n resolve: {\n symlinks: false,\n alias: {\n 'text-encoding$': path.resolve(__dirname, 'src/lib/tw-text-encoder'),\n 'scratch-render-fonts$': path.resolve(__dirname, 'src/lib/tw-scratch-render-fonts')\n }\n },\n module: {\n rules: [{\n test: /\\.jsx?$/,\n loader: 'babel-loader',\n include: [\n path.resolve(__dirname, 'src'),\n /node_modules[\\\\/]scratch-[^\\\\/]+[\\\\/]src/,\n /node_modules[\\\\/]pify/,\n /node_modules[\\\\/]@vernier[\\\\/]godirect/\n ],\n options: {\n // Explicitly disable babelrc so we don't catch various config\n // in much lower dependencies.\n babelrc: false,\n plugins: [\n ['react-intl', {\n messagesDir: './translations/messages/'\n }]],\n presets: ['@babel/preset-env', '@babel/preset-react']\n }\n },\n {\n test: /\\.css$/,\n use: [{\n loader: 'style-loader'\n }, {\n loader: 'css-loader',\n options: {\n modules: true,\n importLoaders: 1,\n localIdentName: '[name]_[local]_[hash:base64:5]',\n camelCase: true\n }\n }, {\n loader: 'postcss-loader',\n options: {\n ident: 'postcss',\n plugins: function () {\n return [\n postcssImport,\n postcssVars,\n autoprefixer\n ];\n }\n }\n }]\n }]\n },\n plugins: [\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'node_modules/scratch-blocks/media',\n to: 'static/blocks-media/default'\n },\n {\n from: 'node_modules/scratch-blocks/media',\n to: 'static/blocks-media/high-contrast'\n },\n {\n from: 'src/lib/themes/blocks/high-contrast-media/blocks-media',\n to: 'static/blocks-media/high-contrast',\n force: true\n }\n ]\n })\n ]\n};\n\nif (!process.env.CI) {\n base.plugins.push(new webpack.ProgressPlugin());\n base.plugins.push(new webpack.HotModuleReplacementPlugin());\n}\n\nmodule.exports = [\n // to run editor examples\n defaultsDeep({}, base, {\n entry: {\n 'editor': './src/playground/editor.jsx',\n 'home': './src/playground/home/home.jsx',\n 'player': './src/playground/player.jsx',\n 'fullscreen': './src/playground/fullscreen.jsx',\n 'embed': './src/playground/embed.jsx',\n 'addon-settings': './src/playground/addon-settings.jsx',\n 'credits': './src/playground/credits/credits.jsx'\n },\n output: {\n path: path.resolve(__dirname, 'build')\n },\n module: {\n rules: base.module.rules.concat([\n {\n test: /\\.(svg|png|wav|mp3|gif|jpg|woff2|hex)$/,\n loader: 'url-loader',\n options: {\n limit: 2048,\n outputPath: 'static/assets/',\n esModule: false\n }\n }\n ])\n },\n optimization: {\n splitChunks: {\n chunks: 'all',\n minChunks: 2,\n minSize: 50000,\n maxInitialRequests: 5\n }\n },\n plugins: base.plugins.concat([\n new webpack.DefinePlugin({\n 'process.env.NODE_ENV': `\"${process.env.NODE_ENV}\"`,\n 'process.env.DEBUG': Boolean(process.env.DEBUG),\n 'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER || ''),\n 'process.env.ROOT': JSON.stringify(root),\n 'process.env.ROUTING_STYLE': JSON.stringify(process.env.ROUTING_STYLE || 'filehash'),\n 'process.env.APP_VERSION': JSON.stringify(version || '')\n }),\n new HtmlWebpackPlugin({\n chunks: ['editor'],\n template: 'src/playground/index.ejs',\n filename: 'editor.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE | Editor`,\n isEditor: true,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['home'],\n template: 'src/playground/simple.ejs',\n filename: 'index.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['player'],\n template: 'src/playground/index.ejs',\n filename: 'player.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['fullscreen'],\n template: 'src/playground/index.ejs',\n filename: 'fullscreen.html',\n title: `${APP_NAME} - The Ultimate MultiLanguage IDE`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['embed'],\n template: 'src/playground/embed.ejs',\n filename: 'embed.html',\n title: `Embedded Project - ${APP_NAME}`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['addon-settings'],\n template: 'src/playground/simple.ejs',\n filename: 'addons.html',\n title: `Addon Settings - ${APP_NAME}`,\n ...htmlWebpackPluginCommon\n }),\n new HtmlWebpackPlugin({\n chunks: ['credits'],\n template: 'src/playground/simple.ejs',\n filename: 'credits.html',\n title: `${APP_NAME} - Credits`,\n ...htmlWebpackPluginCommon\n }),\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'static',\n to: ''\n }\n ]\n }),\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'extensions/**',\n to: 'static',\n context: 'src/examples'\n }\n ]\n })\n ])\n })\n].concat(\n process.env.NODE_ENV === 'production' || process.env.BUILD_MODE === 'dist' ? (\n // export as library\n defaultsDeep({}, base, {\n target: 'web',\n entry: {\n 'scratch-gui': './src/index.js'\n },\n output: {\n libraryTarget: 'umd',\n filename: 'js/[name].js',\n chunkFilename: 'js/[name].js',\n path: path.resolve('dist'),\n publicPath: `${STATIC_PATH}/`\n },\n externals: {\n 'react': 'react',\n 'react-dom': 'react-dom'\n },\n module: {\n rules: base.module.rules.concat([\n {\n test: /\\.(svg|png|wav|mp3|gif|jpg|woff2|hex)$/,\n loader: 'url-loader',\n options: {\n limit: 2048,\n outputPath: 'static/assets/',\n publicPath: `${STATIC_PATH}/assets/`,\n esModule: false\n }\n }\n ])\n },\n plugins: base.plugins.concat([\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'extension-worker.{js,js.map}',\n context: 'node_modules/scratch-vm/dist/web',\n noErrorOnMissing: true\n }\n ]\n }),\n // Include library JSON files for scratch-desktop to use for downloading\n new CopyWebpackPlugin({\n patterns: [\n {\n from: 'src/lib/libraries/*.json',\n to: 'libraries',\n flatten: true\n }\n ]\n })\n ])\n })) : []\n);\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"no-confusing-arrow","replacedBy":[]},{"ruleId":"no-return-await","replacedBy":[]},{"ruleId":"rest-spread-spacing","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"global-require","replacedBy":[]},{"ruleId":"handle-callback-err","replacedBy":[]},{"ruleId":"no-mixed-requires","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"no-path-concat","replacedBy":[]},{"ruleId":"valid-jsdoc","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-catch-shadow","replacedBy":["no-shadow"]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"jsx-quotes","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"newline-per-chained-call","replacedBy":[]},{"ruleId":"no-mixed-operators","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"object-property-newline","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"require-jsdoc","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}]
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 49963cc54..0386639ab 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -407,48 +407,61 @@ const GUIComponent = props => {
- {isNano ? blocksTabVisible && : <>
-
-
- { alert('Adding extensions in NanoScript is not available yet') } : onExtensionButtonClick}
- >
- {isNano && intl.formatMessage(messages.addExtension)}
-
-
- >}
+ {isNano ? blocksTabVisible && : <>
+
+
+ {
+ alert('Adding extensions in NanoScript is not available yet');
+ } : onExtensionButtonClick}
+ >
+ {isNano && intl.formatMessage(messages.addExtension)}
+
+
+ >}
{!isNano &&
{window.blocklyWorkspace.zoomCenter(1)},
+ handleClick: () => {
+ window.blocklyWorkspace.zoomCenter(1);
+ },
isSelected: false,
children: '+'
},
{
- handleClick: () => {window.blocklyWorkspace.zoomCenter(-1)},
+ handleClick: () => {
+ window.blocklyWorkspace.zoomCenter(-1);
+ },
isSelected: false,
children: '-'
},
{
- handleClick: () => {window.blocklyWorkspace.setScale(0.675)},
+ handleClick: () => {
+ window.blocklyWorkspace.setScale(0.675);
+ },
isSelected: false,
children: '='
}
diff --git a/src/components/gui/ob-codemirror-imports.js b/src/components/gui/ob-codemirror-imports.js
index 674e92bbe..bcd14ed4a 100644
--- a/src/components/gui/ob-codemirror-imports.js
+++ b/src/components/gui/ob-codemirror-imports.js
@@ -1,5 +1,5 @@
-export { EditorView } from "@codemirror/view";
-export { basicSetup } from "codemirror";
-export { LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle } from "@codemirror/language";
-export { styleTags, tags } from "@lezer/highlight";
-export { autocompletion, completeFromList } from "@codemirror/autocomplete";
+export {EditorView} from '@codemirror/view';
+export {basicSetup} from 'codemirror';
+export {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';
+export {styleTags, tags} from '@lezer/highlight';
+export {autocompletion, completeFromList} from '@codemirror/autocomplete';
diff --git a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
index 92dcb5ada..85f76ab41 100644
--- a/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
+++ b/src/components/ob-nanoscript-editor/nanoscript-editor.jsx
@@ -6,7 +6,7 @@ import AddonHooks from '../../addons/hooks.js';
import {Theme} from '../../lib/themes';
import styles from './nanoscript-editor.css';
-function NanoscriptEditor({ theme, vm }) {
+function NanoscriptEditor ({theme, vm}) {
const el = React.useRef(null);
const editorRef = React.useRef(null);
const variableRef = React.useRef([]);
@@ -18,7 +18,7 @@ function NanoscriptEditor({ theme, vm }) {
React.useEffect(() => {
let disposed = false;
- async function loadEditor() {
+ async function loadEditor () {
const {
EditorView,
basicSetup,
@@ -27,145 +27,146 @@ function NanoscriptEditor({ theme, vm }) {
tags,
autocompletion,
completeFromList
- } = await import(/*webpackChunkName: "nanoscript-editor"*/ "./ob-codemirror-imports.js");
+ } = await import(/* webpackChunkName: "nanoscript-editor"*/ './ob-codemirror-imports.js');
// Define hghlight rules using CSS vars
const scratchHighlight = HighlightStyle.define([
- { tag: tags.variableName, color: "var(--data-primary)" },
- { tag: tags.keyword, color: "var(--pen-primary)" },
- { tag: tags.string, color: "var(--cm-string)" },
- { tag: tags.number, color: "var(--cm-number)" },
- { tag: tags.function, color: "var(--cm-function)" },
- { tag: tags.operator, color: "var(--cm-operator)" }
+ {tag: tags.variableName, color: 'var(--data-primary)'},
+ {tag: tags.keyword, color: 'var(--pen-primary)'},
+ {tag: tags.string, color: 'var(--cm-string)'},
+ {tag: tags.number, color: 'var(--cm-number)'},
+ {tag: tags.function, color: 'var(--cm-function)'},
+ {tag: tags.operator, color: 'var(--cm-operator)'}
]);
- const { StreamLanguage } = await import("@codemirror/language");
+ const {StreamLanguage} = await import('@codemirror/language');
// helper to escape regex
- const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+ const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const scratchSyntax = StreamLanguage.define({
- token(stream) {
+ token (stream) {
// Keywords
- if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return "keyword";
+ if (stream.match(/\b(when .*|say|repeat|if|else|forever|stop|broadcast|end)\b/)) return 'keyword';
// Operators
- if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return "operator";
+ if (stream.match(/\b(and|or|not|join|\+|\-|\*|\/|(abs|sin|cos) of .*)\b/)) return 'operator';
// Functions
- if (stream.match(/\b(join|pick random|length of)\b/)) return "function";
+ if (stream.match(/\b(join|pick random|length of)\b/)) return 'function';
// Try to match variable or list names (take current refs)
const names = (variableRef.current || []).concat(listsRef.current || []);
if (names && names.length) {
// sort by length to match longest first
- const sorted = names.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
- const rx = new RegExp('^(' + sorted.join('|') + ')', 'i');
+ const sorted = names.slice().sort((a, b) => b.length - a.length)
+ .map(escapeRegExp);
+ const rx = new RegExp(`^(${sorted.join('|')})`, 'i');
const m = stream.match(rx, true);
if (m) {
- return "variableName";
+ return 'variableName';
}
}
stream.next();
- return "variable";
+ return 'variable';
}
});
// NanoScript autocomplete suggestions (static)
const staticCompletions = [
// Control flow keywords
- { label: "when flag clicked", type: "keyword" },
- { label: "when key pressed", type: "keyword" },
- { label: "when this sprite clicked", type: "keyword" },
- { label: "when I start as a clone", type: "keyword" },
- { label: "forever", type: "keyword" },
- { label: "repeat", type: "keyword" },
- { label: "if", type: "keyword" },
- { label: "else", type: "keyword" },
- { label: "end", type: "keyword" },
- { label: "wait", type: "keyword" },
- { label: "stop", type: "keyword" },
+ {label: 'when flag clicked', type: 'keyword'},
+ {label: 'when key pressed', type: 'keyword'},
+ {label: 'when this sprite clicked', type: 'keyword'},
+ {label: 'when I start as a clone', type: 'keyword'},
+ {label: 'forever', type: 'keyword'},
+ {label: 'repeat', type: 'keyword'},
+ {label: 'if', type: 'keyword'},
+ {label: 'else', type: 'keyword'},
+ {label: 'end', type: 'keyword'},
+ {label: 'wait', type: 'keyword'},
+ {label: 'stop', type: 'keyword'},
// Motion blocks
- { label: "move", type: "function" },
- { label: "turn right", type: "function" },
- { label: "turn left", type: "function" },
- { label: "go to", type: "function" },
- { label: "glide to", type: "function" },
- { label: "point in direction", type: "function" },
- { label: "point towards", type: "function" },
- { label: "change x by", type: "function" },
- { label: "set x to", type: "function" },
- { label: "change y by", type: "function" },
- { label: "set y to", type: "function" },
+ {label: 'move', type: 'function'},
+ {label: 'turn right', type: 'function'},
+ {label: 'turn left', type: 'function'},
+ {label: 'go to', type: 'function'},
+ {label: 'glide to', type: 'function'},
+ {label: 'point in direction', type: 'function'},
+ {label: 'point towards', type: 'function'},
+ {label: 'change x by', type: 'function'},
+ {label: 'set x to', type: 'function'},
+ {label: 'change y by', type: 'function'},
+ {label: 'set y to', type: 'function'},
// Looks blocks
- { label: "say", type: "function" },
- { label: "think", type: "function" },
- { label: "show", type: "function" },
- { label: "hide", type: "function" },
- { label: "switch costume to", type: "function" },
- { label: "next costume", type: "function" },
- { label: "change size by", type: "function" },
- { label: "set size to", type: "function" },
- { label: "change color effect by", type: "function" },
- { label: "set color effect to", type: "function" },
- { label: "clear graphic effects", type: "function" },
+ {label: 'say', type: 'function'},
+ {label: 'think', type: 'function'},
+ {label: 'show', type: 'function'},
+ {label: 'hide', type: 'function'},
+ {label: 'switch costume to', type: 'function'},
+ {label: 'next costume', type: 'function'},
+ {label: 'change size by', type: 'function'},
+ {label: 'set size to', type: 'function'},
+ {label: 'change color effect by', type: 'function'},
+ {label: 'set color effect to', type: 'function'},
+ {label: 'clear graphic effects', type: 'function'},
// Sound blocks
- { label: "play sound", type: "function" },
- { label: "stop all sounds", type: "function" },
- { label: "change volume by", type: "function" },
- { label: "set volume to", type: "function" },
+ {label: 'play sound', type: 'function'},
+ {label: 'stop all sounds', type: 'function'},
+ {label: 'change volume by', type: 'function'},
+ {label: 'set volume to', type: 'function'},
// Events
- { label: "broadcast", type: "function" },
- { label: "broadcast and wait", type: "function" },
- { label: "when I receive", type: "keyword" },
+ {label: 'broadcast', type: 'function'},
+ {label: 'broadcast and wait', type: 'function'},
+ {label: 'when I receive', type: 'keyword'},
// Variables and lists
- { label: "set variable to", type: "function" },
- { label: "change variable by", type: "function" },
- { label: "add to list", type: "function" },
- { label: "delete from list", type: "function" },
- { label: "insert into list", type: "function" },
- { label: "replace list item", type: "function" },
+ {label: 'set variable to', type: 'function'},
+ {label: 'change variable by', type: 'function'},
+ {label: 'add to list', type: 'function'},
+ {label: 'delete from list', type: 'function'},
+ {label: 'insert into list', type: 'function'},
+ {label: 'replace list item', type: 'function'},
// Operators
- { label: "and", type: "operator" },
- { label: "or", type: "operator" },
- { label: "not", type: "operator" },
- { label: "join", type: "function" },
- { label: "letter of", type: "function" },
- { label: "length of", type: "function" },
- { label: "round", type: "function" },
- { label: "abs of", type: "function" },
- { label: "floor of", type: "function" },
- { label: "ceiling of", type: "function" },
- { label: "sqrt of", type: "function" },
- { label: "sin of", type: "function" },
- { label: "cos of", type: "function" },
- { label: "tan of", type: "function" },
- { label: "asin of", type: "function" },
- { label: "acos of", type: "function" },
- { label: "atan of", type: "function" },
- { label: "pick random", type: "function" },
+ {label: 'and', type: 'operator'},
+ {label: 'or', type: 'operator'},
+ {label: 'not', type: 'operator'},
+ {label: 'join', type: 'function'},
+ {label: 'letter of', type: 'function'},
+ {label: 'length of', type: 'function'},
+ {label: 'round', type: 'function'},
+ {label: 'abs of', type: 'function'},
+ {label: 'floor of', type: 'function'},
+ {label: 'ceiling of', type: 'function'},
+ {label: 'sqrt of', type: 'function'},
+ {label: 'sin of', type: 'function'},
+ {label: 'cos of', type: 'function'},
+ {label: 'tan of', type: 'function'},
+ {label: 'asin of', type: 'function'},
+ {label: 'acos of', type: 'function'},
+ {label: 'atan of', type: 'function'},
+ {label: 'pick random', type: 'function'},
// Sensing blocks
- { label: "touching", type: "function" },
- { label: "touching color", type: "function" },
- { label: "color is touching", type: "function" },
- { label: "ask", type: "function" },
- { label: "key pressed", type: "function" },
- { label: "mouse down", type: "function" },
- { label: "distance to", type: "function" },
+ {label: 'touching', type: 'function'},
+ {label: 'touching color', type: 'function'},
+ {label: 'color is touching', type: 'function'},
+ {label: 'ask', type: 'function'},
+ {label: 'key pressed', type: 'function'},
+ {label: 'mouse down', type: 'function'},
+ {label: 'distance to', type: 'function'}
];
// Function to get completions with prefix matching (dynamic includes variables & lists)
- function scratchCompletions(context) {
+ function scratchCompletions (context) {
const word = context.matchBefore(/\w*/);
if (!word || (word.from === word.to && !context.explicit)) {
return null;
}
- const dynamicVars = (variableRef.current || []).map(v => ({ label: v, type: 'variable', info: 'Variable' }));
- const dynamicLists = (listsRef.current || []).map(l => ({ label: l, type: 'variable', info: 'List' }));
+ const dynamicVars = (variableRef.current || []).map(v => ({label: v, type: 'variable', info: 'Variable'}));
+ const dynamicLists = (listsRef.current || []).map(l => ({label: l, type: 'variable', info: 'List'}));
const allOptions = staticCompletions.concat(dynamicVars, dynamicLists);
return {
@@ -180,31 +181,31 @@ function NanoscriptEditor({ theme, vm }) {
if (!el.current || disposed) return;
const cmTheme = EditorView.theme({
- "&": {
- backgroundColor: "var(--ui-white)",
- color: "var(--text-primary)",
- height: "100%",
- borderTopRightRadius: "var(--space)",
- borderBottomRightRadius: "var(--space)",
- border: "1px solid var(--ui-black-transparent)",
- height: "100%",
+ '&': {
+ backgroundColor: 'var(--ui-white)',
+ color: 'var(--text-primary)',
+ height: '100%',
+ borderTopRightRadius: 'var(--space)',
+ borderBottomRightRadius: 'var(--space)',
+ border: '1px solid var(--ui-black-transparent)',
+ height: '100%'
},
- ".cm-scroller": {
- maxHeight: "100%",
- overflow: "auto"
+ '.cm-scroller': {
+ maxHeight: '100%',
+ overflow: 'auto'
},
- ".cm-content": { caretColor: "var(--looks-secondary)" },
- ".cm-cursor": { borderLeft: "2px solid var(--looks-secondary)" },
- ".cm-focused": { outline: "none" },
- ".cm-selectionBackground, ::selection": { backgroundColor: "rgba(255, 140, 26, 0.3)" },
- ".cm-gutters": {
- backgroundColor: "var(--ui-tertiary)",
- borderRight: "1px solid var(--ui-black-transparent)"
+ '.cm-content': {caretColor: 'var(--looks-secondary)'},
+ '.cm-cursor': {borderLeft: '2px solid var(--looks-secondary)'},
+ '.cm-focused': {outline: 'none'},
+ '.cm-selectionBackground, ::selection': {backgroundColor: 'rgba(255, 140, 26, 0.3)'},
+ '.cm-gutters': {
+ backgroundColor: 'var(--ui-tertiary)',
+ borderRight: '1px solid var(--ui-black-transparent)'
},
- ".cm-completionLabel": {
- fontSize: "13px"
+ '.cm-completionLabel': {
+ fontSize: '13px'
}
- }, { dark: theme.isDark() ?? false });
+ }, {dark: theme.isDark() ?? false});
editorRef.current = new EditorView({
doc: `when flag clicked
@@ -216,7 +217,7 @@ end`,
basicSetup,
scratchSyntax,
syntaxHighlighting(scratchHighlight),
- autocompletion({ override: [scratchCompletions] }),
+ autocompletion({override: [scratchCompletions]}),
cmTheme
],
parent: el.current
@@ -250,8 +251,8 @@ end`,
}
const isStage = editing.isStage;
- let spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
- let spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
+ const spriteVars = isStage ? [] : (editing.getAllVariableNamesInScopeByType('', true) || []);
+ const spriteLists = isStage ? [] : (editing.getAllVariableNamesInScopeByType('list', true) || []);
let stageVars = [];
let stageLists = [];
@@ -377,7 +378,7 @@ end`,
}
};
- return
+ return (
{promptProps ? (
)}
- makeVariable('')}>Make a Variable
+ makeVariable('')}
+ >Make a Variable
Lists
@@ -450,11 +454,18 @@ end`,
>
)}
- makeVariable('list')}>Make a List
+ makeVariable('list')}
+ >Make a List
-
-
;
+
+ );
}
NanoscriptEditor.propTypes = {
diff --git a/src/components/ob-nanoscript-editor/ob-codemirror-imports.js b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js
index 674e92bbe..bcd14ed4a 100644
--- a/src/components/ob-nanoscript-editor/ob-codemirror-imports.js
+++ b/src/components/ob-nanoscript-editor/ob-codemirror-imports.js
@@ -1,5 +1,5 @@
-export { EditorView } from "@codemirror/view";
-export { basicSetup } from "codemirror";
-export { LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle } from "@codemirror/language";
-export { styleTags, tags } from "@lezer/highlight";
-export { autocompletion, completeFromList } from "@codemirror/autocomplete";
+export {EditorView} from '@codemirror/view';
+export {basicSetup} from 'codemirror';
+export {LRLanguage, LanguageSupport, syntaxHighlighting, HighlightStyle} from '@codemirror/language';
+export {styleTags, tags} from '@lezer/highlight';
+export {autocompletion, completeFromList} from '@codemirror/autocomplete';
diff --git a/src/lib/brand.js b/src/lib/brand.js
index c2c11dfe6..07db3bb29 100644
--- a/src/lib/brand.js
+++ b/src/lib/brand.js
@@ -6,4 +6,4 @@ module.exports = {
PROJECT: 'OmniBlocks'
},
APP_VERSION: process.env.APP_VERSION || 'v0.5.8-alpha' // Dynamically injected at build time from git tags
-};
\ No newline at end of file
+};
diff --git a/src/lib/libraries/tw-extension-tags.js b/src/lib/libraries/tw-extension-tags.js
index 461bb3467..a48662362 100644
--- a/src/lib/libraries/tw-extension-tags.js
+++ b/src/lib/libraries/tw-extension-tags.js
@@ -3,6 +3,6 @@ import {APP_NAME} from '../brand';
// Because these are all brand names, it is unnecessary for them to be translatable.
export default [
{tag: 'scratch', intlLabel: 'Scratch'},
- {tag: 'tw', intlLabel: "TurboWarp"},
+ {tag: 'tw', intlLabel: 'TurboWarp'},
{tag: 'ob', intlLabel: APP_NAME}
];
diff --git a/src/playground/credits/credits.jsx b/src/playground/credits/credits.jsx
index 3e1142c6b..bf333a8b2 100644
--- a/src/playground/credits/credits.jsx
+++ b/src/playground/credits/credits.jsx
@@ -4,7 +4,7 @@ import render from '../app-target';
import styles from '../info.css';
import Header from '../ws-components/header/header.jsx';
-import { APP_NAMES } from '../../lib/brand';
+import {APP_NAMES} from '../../lib/brand';
const APP_NAME = APP_NAMES.PROJECT;
import {applyGuiColors} from '../../lib/themes/guiHelpers';
diff --git a/src/playground/home/home.jsx b/src/playground/home/home.jsx
index 84a3cee4a..b774b2715 100644
--- a/src/playground/home/home.jsx
+++ b/src/playground/home/home.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import render from '../app-target.js';
-import { APP_NAMES } from '../../lib/brand';
+import {APP_NAMES} from '../../lib/brand';
const APP_NAME = APP_NAMES.PROJECT;
import {applyGuiColors} from '../../lib/themes/guiHelpers.js';
@@ -54,15 +54,21 @@ const Credits = () => (
Current IDEs
- {IDE_CARDS.map((ide) => (
-
+ {IDE_CARDS.map(ide => (
+
{ide.title}
{ide.coming ? — Coming soon : null}
{ide.desc}
{ide.href ? (
- Open
+ Open
) : null}
))}
diff --git a/src/playground/ws-components/header/header.jsx b/src/playground/ws-components/header/header.jsx
index 39fa2ec1b..8d6e7070b 100644
--- a/src/playground/ws-components/header/header.jsx
+++ b/src/playground/ws-components/header/header.jsx
@@ -1,20 +1,29 @@
import React from 'react';
import styles from './header.css';
import logo from './costume1.svg';
-import { APP_NAMES } from '../../../lib/brand';
+import {APP_NAMES} from '../../../lib/brand';
const APP_NAME = APP_NAMES.PROJECT;
-const Header = () => {
- return (
- <>
>
- );
-};
+const Header = () => (
+ <>
>
+);
-export default Header;
\ No newline at end of file
+export default Header;
diff --git a/webpack.config.js b/webpack.config.js
index 0092559b3..247ce5dee 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -48,7 +48,7 @@ const base = {
{from: /^\/addons\/?$/, to: '/addons.html'}
]
},
- hot: true
+ hot: true
},
output: {
library: 'GUI',
@@ -138,7 +138,7 @@ const base = {
if (!process.env.CI) {
base.plugins.push(new webpack.ProgressPlugin());
- base.plugins.push(new webpack.HotModuleReplacementPlugin());
+ base.plugins.push(new webpack.HotModuleReplacementPlugin());
}
module.exports = [
@@ -184,7 +184,7 @@ module.exports = [
'process.env.ENABLE_SERVICE_WORKER': JSON.stringify(process.env.ENABLE_SERVICE_WORKER || ''),
'process.env.ROOT': JSON.stringify(root),
'process.env.ROUTING_STYLE': JSON.stringify(process.env.ROUTING_STYLE || 'filehash'),
- 'process.env.APP_VERSION': JSON.stringify(version || '')
+ 'process.env.APP_VERSION': JSON.stringify(version || '')
}),
new HtmlWebpackPlugin({
chunks: ['editor'],