diff --git a/.gitattributes b/.gitattributes
index a6344aac8c09253b3b630fb776ae94478aa0275b..75c7c36268690dc4877e3a3c0cc4a1006505da55 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -33,3 +33,14 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
+favicon.ico filter=lfs diff=lfs merge=lfs -text
+js/vendors~editor~embed~fullscreen~player~playground.js filter=lfs diff=lfs merge=lfs -text
+js/vendors~editor~embed~fullscreen~player~playground.js.map filter=lfs diff=lfs merge=lfs -text
+static/assets/48d66af328a669a1c84c5dc87a4e9a3b.ttf filter=lfs diff=lfs merge=lfs -text
+static/assets/4c768843c9fa6593074231cd762b83f8.ttf filter=lfs diff=lfs merge=lfs -text
+static/assets/57f5c040b2ec7c6f269042b4e1c32a03.png filter=lfs diff=lfs merge=lfs -text
+static/assets/5f46c8dc3b8cc2296eb73f013dfce3db.ttf filter=lfs diff=lfs merge=lfs -text
+static/assets/8996930d8e83f63cc3341172205df460.otf filter=lfs diff=lfs merge=lfs -text
+static/assets/8d83d454d2c42614a203322393c23a5b.ttf filter=lfs diff=lfs merge=lfs -text
+static/assets/9ec3449723269a806ec6eaba0f8f6cef.ttf filter=lfs diff=lfs merge=lfs -text
+static/assets/eb64e706eb7a3b324ba50d3279a980b1.ttf filter=lfs diff=lfs merge=lfs -text
diff --git a/41f50eb9f984c12c2544.worker.js b/41f50eb9f984c12c2544.worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d66d96d7ee0f2f2ec6007a83576c2bf12e45c30
--- /dev/null
+++ b/41f50eb9f984c12c2544.worker.js
@@ -0,0 +1,228 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId]) {
+/******/ return installedModules[moduleId].exports;
+/******/ }
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ i: moduleId,
+/******/ l: false,
+/******/ exports: {}
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.l = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // define getter function for harmony exports
+/******/ __webpack_require__.d = function(exports, name, getter) {
+/******/ if(!__webpack_require__.o(exports, name)) {
+/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ }
+/******/ };
+/******/
+/******/ // define __esModule on exports
+/******/ __webpack_require__.r = function(exports) {
+/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ }
+/******/ Object.defineProperty(exports, '__esModule', { value: true });
+/******/ };
+/******/
+/******/ // create a fake namespace object
+/******/ // mode & 1: value is a module id, require it
+/******/ // mode & 2: merge all properties of value into the ns
+/******/ // mode & 4: return value when already ns object
+/******/ // mode & 8|1: behave like require
+/******/ __webpack_require__.t = function(value, mode) {
+/******/ if(mode & 1) value = __webpack_require__(value);
+/******/ if(mode & 8) return value;
+/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ var ns = Object.create(null);
+/******/ __webpack_require__.r(ns);
+/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ return ns;
+/******/ };
+/******/
+/******/ // getDefaultExport function for compatibility with non-harmony modules
+/******/ __webpack_require__.n = function(module) {
+/******/ var getter = module && module.__esModule ?
+/******/ function getDefault() { return module['default']; } :
+/******/ function getModuleExports() { return module; };
+/******/ __webpack_require__.d(getter, 'a', getter);
+/******/ return getter;
+/******/ };
+/******/
+/******/ // Object.prototype.hasOwnProperty.call
+/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(__webpack_require__.s = "./node_modules/babel-loader/lib/index.js?!./node_modules/scratch-storage/src/FetchWorkerTool.worker.js");
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ "./node_modules/babel-loader/lib/index.js?!./node_modules/scratch-storage/src/FetchWorkerTool.worker.js":
+/*!************************************************************************************************************!*\
+ !*** ./node_modules/babel-loader/lib??ref--4!./node_modules/scratch-storage/src/FetchWorkerTool.worker.js ***!
+ \************************************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* eslint-env worker */
+
+const saferFetchAsArrayBuffer = __webpack_require__(/*! ./safer-fetch */ "./node_modules/scratch-storage/src/safer-fetch.js");
+const complete = [];
+let timeoutId = null;
+const checkCompleted = () => {
+ if (timeoutId) return;
+ timeoutId = setTimeout(() => {
+ timeoutId = null;
+ if (complete.length) {
+ // Send our chunk of completed requests and instruct postMessage to
+ // transfer the buffers instead of copying them.
+ postMessage(complete.slice(),
+ // Instruct postMessage that these buffers in the sent message
+ // should use their Transferable trait. After the postMessage
+ // call the "buffers" will still be in complete if you looked,
+ // but they will all be length 0 as the data they reference has
+ // been sent to the window. This lets us send a lot of data
+ // without the normal postMessage behaviour of making a copy of
+ // all of the data for the window.
+ complete.map(response => response.buffer).filter(Boolean));
+ complete.length = 0;
+ }
+ });
+};
+
+/**
+ * Receive a job from the parent and fetch the requested data.
+ * @param {object} options.job A job id, url, and options descriptor to perform.
+ */
+const onMessage = _ref => {
+ let {
+ data: job
+ } = _ref;
+ saferFetchAsArrayBuffer(job.url, job.options).then(buffer => complete.push({
+ id: job.id,
+ buffer
+ })).catch(error => complete.push({
+ id: job.id,
+ error: error && error.message || "Failed request: ".concat(job.url)
+ })).then(checkCompleted);
+};
+if (self.fetch) {
+ postMessage({
+ support: {
+ fetch: true
+ }
+ });
+ self.addEventListener('message', onMessage);
+} else {
+ postMessage({
+ support: {
+ fetch: false
+ }
+ });
+ self.addEventListener('message', _ref2 => {
+ let {
+ data: job
+ } = _ref2;
+ postMessage([{
+ id: job.id,
+ error: 'fetch is unavailable'
+ }]);
+ });
+}
+
+/***/ }),
+
+/***/ "./node_modules/scratch-storage/src/safer-fetch.js":
+/*!*********************************************************!*\
+ !*** ./node_modules/scratch-storage/src/safer-fetch.js ***!
+ \*********************************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+/* eslint-env browser */
+/* eslint-disable no-use-before-define */
+
+// This throttles and retries fetch() to mitigate the effect of random network errors and
+// random browser errors (especially in Chrome)
+
+let currentFetches = 0;
+const queue = [];
+const startNextFetch = _ref => {
+ let [resolve, url, options] = _ref;
+ let firstError;
+ let failedAttempts = 0;
+ const attemptToFetch = () => fetch(url, options).then(result => {
+ // In a macOS WKWebView, requests from file: URLs to other file: URLs always have status: 0 and ok: false
+ // even though the requests were successful. If the requested file doesn't exist, fetch() rejects instead.
+ // We aren't aware of any other cases where fetch() can resolve with status 0, so this should be safe.
+ if (result.ok || result.status === 0) return result.arrayBuffer();
+ if (result.status === 404) return null;
+ return Promise.reject(result.status);
+ }).then(buffer => {
+ currentFetches--;
+ checkStartNextFetch();
+ return buffer;
+ }).catch(error => {
+ if (error === 403) {
+ // Retrying this request will not help, so return an error now.
+ throw error;
+ }
+ console.warn("Attempt to fetch ".concat(url, " failed"), error);
+ if (!firstError) {
+ firstError = error;
+ }
+ if (failedAttempts < 2) {
+ failedAttempts++;
+ return new Promise(cb => setTimeout(cb, (failedAttempts + Math.random() - 1) * 5000)).then(attemptToFetch);
+ }
+ currentFetches--;
+ checkStartNextFetch();
+ throw firstError;
+ });
+ return resolve(attemptToFetch());
+};
+const checkStartNextFetch = () => {
+ if (currentFetches < 100 && queue.length > 0) {
+ currentFetches++;
+ startNextFetch(queue.shift());
+ }
+};
+const saferFetchAsArrayBuffer = (url, options) => new Promise(resolve => {
+ queue.push([resolve, url, options]);
+ checkStartNextFetch();
+});
+module.exports = saferFetchAsArrayBuffer;
+
+/***/ })
+
+/******/ });
+//# sourceMappingURL=41f50eb9f984c12c2544.worker.js.map
\ No newline at end of file
diff --git a/41f50eb9f984c12c2544.worker.js.map b/41f50eb9f984c12c2544.worker.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..fd90466f2d3b4ee46e3596819c944d91c46036a6
--- /dev/null
+++ b/41f50eb9f984c12c2544.worker.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"41f50eb9f984c12c2544.worker.js","sources":["webpack://GUI/webpack/bootstrap","webpack://GUI/./node_modules/scratch-storage/src/FetchWorkerTool.worker.js","webpack://GUI/./node_modules/scratch-storage/src/safer-fetch.js"],"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./node_modules/babel-loader/lib/index.js?!./node_modules/scratch-storage/src/FetchWorkerTool.worker.js\");\n","/* eslint-env worker */\n\nconst saferFetchAsArrayBuffer = require('./safer-fetch');\n\nconst complete = [];\n\nlet timeoutId = null;\n\nconst checkCompleted = () => {\n if (timeoutId) return;\n timeoutId = setTimeout(() => {\n timeoutId = null;\n if (complete.length) {\n // Send our chunk of completed requests and instruct postMessage to\n // transfer the buffers instead of copying them.\n postMessage(\n complete.slice(),\n // Instruct postMessage that these buffers in the sent message\n // should use their Transferable trait. After the postMessage\n // call the \"buffers\" will still be in complete if you looked,\n // but they will all be length 0 as the data they reference has\n // been sent to the window. This lets us send a lot of data\n // without the normal postMessage behaviour of making a copy of\n // all of the data for the window.\n complete.map(response => response.buffer).filter(Boolean)\n );\n complete.length = 0;\n }\n });\n};\n\n/**\n * Receive a job from the parent and fetch the requested data.\n * @param {object} options.job A job id, url, and options descriptor to perform.\n */\nconst onMessage = ({data: job}) => {\n saferFetchAsArrayBuffer(job.url, job.options)\n .then(buffer => complete.push({id: job.id, buffer}))\n .catch(error => complete.push({id: job.id, error: (error && error.message) || `Failed request: ${job.url}`}))\n .then(checkCompleted);\n};\n\nif (self.fetch) {\n postMessage({support: {fetch: true}});\n self.addEventListener('message', onMessage);\n} else {\n postMessage({support: {fetch: false}});\n self.addEventListener('message', ({data: job}) => {\n postMessage([{id: job.id, error: 'fetch is unavailable'}]);\n });\n}\n","/* eslint-env browser */\n/* eslint-disable no-use-before-define */\n\n// This throttles and retries fetch() to mitigate the effect of random network errors and\n// random browser errors (especially in Chrome)\n\nlet currentFetches = 0;\nconst queue = [];\n\nconst startNextFetch = ([resolve, url, options]) => {\n let firstError;\n let failedAttempts = 0;\n\n const attemptToFetch = () => fetch(url, options)\n .then(result => {\n // In a macOS WKWebView, requests from file: URLs to other file: URLs always have status: 0 and ok: false\n // even though the requests were successful. If the requested file doesn't exist, fetch() rejects instead.\n // We aren't aware of any other cases where fetch() can resolve with status 0, so this should be safe.\n if (result.ok || result.status === 0) return result.arrayBuffer();\n if (result.status === 404) return null;\n return Promise.reject(result.status);\n })\n .then(buffer => {\n currentFetches--;\n checkStartNextFetch();\n return buffer;\n })\n .catch(error => {\n if (error === 403) {\n // Retrying this request will not help, so return an error now.\n throw error;\n }\n\n console.warn(`Attempt to fetch ${url} failed`, error);\n if (!firstError) {\n firstError = error;\n }\n\n if (failedAttempts < 2) {\n failedAttempts++;\n return new Promise(cb => setTimeout(cb, (failedAttempts + Math.random() - 1) * 5000))\n .then(attemptToFetch);\n }\n\n currentFetches--;\n checkStartNextFetch();\n throw firstError;\n });\n\n return resolve(attemptToFetch());\n};\n\nconst checkStartNextFetch = () => {\n if (currentFetches < 100 && queue.length > 0) {\n currentFetches++;\n startNextFetch(queue.shift());\n }\n};\n\nconst saferFetchAsArrayBuffer = (url, options) => new Promise(resolve => {\n queue.push([resolve, url, options]);\n checkStartNextFetch();\n});\n\nmodule.exports = saferFetchAsArrayBuffer;\n"],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;AClFA;AACA;AACA;AAEA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAEA;AAEA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AACA;AACA;;;;;;;;;;;AClDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;;;;A","sourceRoot":""}
\ No newline at end of file
diff --git a/addons.html b/addons.html
new file mode 100644
index 0000000000000000000000000000000000000000..cb06398a389d378677d5604aee23bc21280bc7c4
--- /dev/null
+++ b/addons.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Addon Settings - PenguinMod
+
+
+
+
+
+
+
diff --git a/bloomfilter.svg b/bloomfilter.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fa538bb9b3965f55a4ab85997a298d6a885692e9
--- /dev/null
+++ b/bloomfilter.svg
@@ -0,0 +1,6 @@
+
diff --git a/contact.html b/contact.html
new file mode 100644
index 0000000000000000000000000000000000000000..fffe02d2c4afdc2d70e301bf0e4c5644064cfd48
--- /dev/null
+++ b/contact.html
@@ -0,0 +1,49 @@
+
+
+
+ PenguinMod - Contact
+
+
+
+
+
+
+
+
+
+
+ Please only contact us personally for serious matters such as security, copyright, etc.
+
+ Spam, advertisments, or other mail may be blocked.
+
+
+
+
+ Email us at penguinmodhelp@gmail.com
+ Make an issue on our GitHub
+ Post or reply mentioning our X account
+ Join our Discord Server
+
+
+
diff --git a/credits.html b/credits.html
new file mode 100644
index 0000000000000000000000000000000000000000..94277a1b0ae38c11696cb0df58fb3ecaf38d3ea2
--- /dev/null
+++ b/credits.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ PenguinMod & TurboWarp Credits
+
+
+
+
+
+
+
diff --git a/editor.html b/editor.html
new file mode 100644
index 0000000000000000000000000000000000000000..235b11ef02c99b31e7924fd563f1cac4bb50bdd4
--- /dev/null
+++ b/editor.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ PenguinMod - Editor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embed.html b/embed.html
new file mode 100644
index 0000000000000000000000000000000000000000..bbf7167d830ff5e518f41e821717b7415db875b9
--- /dev/null
+++ b/embed.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+ Embedded Project - PenguinMod
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embedtest.html b/embedtest.html
new file mode 100644
index 0000000000000000000000000000000000000000..3ddd79a21bf6dac0372f3dfc2be5a63b5b848a92
--- /dev/null
+++ b/embedtest.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..7902be7a25a944acb76ef00b73f721e76bca444b
--- /dev/null
+++ b/favicon.ico
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7b6414945f53b55f68a2b3f62381e2ab92f2385a39a5329bfd7a8baa2e2ecba2
+size 238142
diff --git a/favicon.png b/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..5edd20a8cfbfbac54e21554850b882cfc89597eb
Binary files /dev/null and b/favicon.png differ
diff --git a/featured_project.png b/featured_project.png
new file mode 100644
index 0000000000000000000000000000000000000000..d92d93a88142c372154829d93616e52322a6be66
Binary files /dev/null and b/featured_project.png differ
diff --git a/fullscreen.html b/fullscreen.html
new file mode 100644
index 0000000000000000000000000000000000000000..7132eef44d04f5d1868c6353272aac056125daae
--- /dev/null
+++ b/fullscreen.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ PenguinMod - A mod of TurboWarp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iframeposttest.html b/iframeposttest.html
new file mode 100644
index 0000000000000000000000000000000000000000..1d3f4fab5ef1bdfe39ecfe235ee9c384f8611c4b
--- /dev/null
+++ b/iframeposttest.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ aegewg
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/iframereceivetest.html b/iframereceivetest.html
new file mode 100644
index 0000000000000000000000000000000000000000..a23aa256cfd81eab2cff2a461f94e5c7608c164a
--- /dev/null
+++ b/iframereceivetest.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/192.png b/images/192.png
new file mode 100644
index 0000000000000000000000000000000000000000..5edd20a8cfbfbac54e21554850b882cfc89597eb
Binary files /dev/null and b/images/192.png differ
diff --git a/images/512.png b/images/512.png
new file mode 100644
index 0000000000000000000000000000000000000000..5edd20a8cfbfbac54e21554850b882cfc89597eb
Binary files /dev/null and b/images/512.png differ
diff --git a/images/apple-touch-icon.png b/images/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..5edd20a8cfbfbac54e21554850b882cfc89597eb
Binary files /dev/null and b/images/apple-touch-icon.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..37024f23807acd0bd8053723c275865cfc424707
--- /dev/null
+++ b/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ PenguinMod - A mod of TurboWarp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/0.js b/js/0.js
new file mode 100644
index 0000000000000000000000000000000000000000..035597b19595bcfaf68078d98871af9dbc8b01dd
--- /dev/null
+++ b/js/0.js
@@ -0,0 +1,74 @@
+(window["webpackJsonpGUI"] = window["webpackJsonpGUI"] || []).push([[0],{
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/debug-console/style.css":
+/*!*****************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/debug-console/style.css ***!
+ \*****************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".mediaRecorderPopup {\n box-sizing: border-box;\n width: 700px;\n max-height: min(800px, 80vh);\n max-width: 85%;\n margin-top: 12vh;\n overflow-y: auto;\n margin-left: auto;\n margin-right: auto;\n}\n\n.mediaRecorderPopupContent {\n padding: 1.5rem 2.25rem;\n}\n\n.mediaRecorderPopup p {\n font-size: 1rem;\n margin: 0.5rem auto;\n}\n\n.mediaRecorderPopup p :last-child {\n margin-left: 1rem;\n}\n\n.mediaRecorderPopup[dir=\"rtl\"] p :last-child {\n margin-left: 0;\n margin-right: 1rem;\n}\n\np.mediaRecorderPopupOption {\n display: flex;\n align-items: center;\n}\n\n.mediaRecorderPopupOption input[type=\"checkbox\"] {\n height: 1.5rem;\n}\n\n#recordOptionSecondsInput,\n#recordOptionDelayInput {\n width: 6rem;\n}\n\n.mediaRecorderPopupButtons {\n margin-top: 1.5rem;\n}\n\n.mediaRecorderPopupButtons button {\n margin-left: 0.5rem;\n}\n\n/* TW: Fixes cancel button in dark mode */\n.mediaRecorderPopupButtons button:nth-of-type(1) {\n color: black;\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./src/addons/addons/debug-console/_runtime_entry.js":
+/*!***********************************************************!*\
+ !*** ./src/addons/addons/debug-console/_runtime_entry.js ***!
+ \***********************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/debug-console/userscript.js");
+/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./style.css */ "./node_modules/css-loader/index.js!./src/addons/addons/debug-console/style.css");
+/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "style.css": _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/debug-console/userscript.js":
+/*!*******************************************************!*\
+ !*** ./src/addons/addons/debug-console/userscript.js ***!
+ \*******************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = (async _ref => {
+ let {
+ addon,
+ console,
+ msg
+ } = _ref;
+ // ページ読み込み直後にerudaを読み込む
+ if (!window.eruda) {
+ const script = document.createElement('script');
+ script.src = 'https://soiz1-eruda3.hf.space/eruda.js';
+ script.onload = () => {
+ eruda.init();
+ };
+ document.body.appendChild(script);
+ }
+});
+
+/***/ })
+
+}]);
+//# sourceMappingURL=0.js.map
\ No newline at end of file
diff --git a/js/0.js.map b/js/0.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..81fe3262851aca34409a1d6d6d4975ff777604b2
--- /dev/null
+++ b/js/0.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"js/0.js","sources":["webpack://GUI/./src/addons/addons/debug-console/style.css","webpack://GUI/./src/addons/addons/debug-console/_runtime_entry.js","webpack://GUI/./src/addons/addons/debug-console/userscript.js"],"sourcesContent":["exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".mediaRecorderPopup {\\n box-sizing: border-box;\\n width: 700px;\\n max-height: min(800px, 80vh);\\n max-width: 85%;\\n margin-top: 12vh;\\n overflow-y: auto;\\n margin-left: auto;\\n margin-right: auto;\\n}\\n\\n.mediaRecorderPopupContent {\\n padding: 1.5rem 2.25rem;\\n}\\n\\n.mediaRecorderPopup p {\\n font-size: 1rem;\\n margin: 0.5rem auto;\\n}\\n\\n.mediaRecorderPopup p :last-child {\\n margin-left: 1rem;\\n}\\n\\n.mediaRecorderPopup[dir=\\\"rtl\\\"] p :last-child {\\n margin-left: 0;\\n margin-right: 1rem;\\n}\\n\\np.mediaRecorderPopupOption {\\n display: flex;\\n align-items: center;\\n}\\n\\n.mediaRecorderPopupOption input[type=\\\"checkbox\\\"] {\\n height: 1.5rem;\\n}\\n\\n#recordOptionSecondsInput,\\n#recordOptionDelayInput {\\n width: 6rem;\\n}\\n\\n.mediaRecorderPopupButtons {\\n margin-top: 1.5rem;\\n}\\n\\n.mediaRecorderPopupButtons button {\\n margin-left: 0.5rem;\\n}\\n\\n/* TW: Fixes cancel button in dark mode */\\n.mediaRecorderPopupButtons button:nth-of-type(1) {\\n color: black;\\n}\", \"\"]);\n\n// exports\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nimport _css from \"!css-loader!./style.css\";\nexport const resources = {\n \"userscript.js\": _js,\n \"style.css\": _css,\n};","export default async ({ addon, console, msg }) => {\n // ページ読み込み直後にerudaを読み込む\n if (!window.eruda) {\n const script = document.createElement('script');\n script.src = 'https://soiz1-eruda3.hf.space/eruda.js';\n script.onload = () => {\n eruda.init();\n };\n document.body.appendChild(script);\n }\n};\n"],"mappings":";;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;ACPA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;ACNA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;A","sourceRoot":""}
\ No newline at end of file
diff --git a/js/1.js b/js/1.js
new file mode 100644
index 0000000000000000000000000000000000000000..8886c3d2115abe6b651529c6958f10e3435b70ba
--- /dev/null
+++ b/js/1.js
@@ -0,0 +1,124 @@
+(window["webpackJsonpGUI"] = window["webpackJsonpGUI"] || []).push([[1],{
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/fps/userstyle.css":
+/*!***********************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/fps/userstyle.css ***!
+ \***********************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".fps-counter {\n font-size: 0.625rem;\n font-weight: bold;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n white-space: nowrap;\n padding: 0.25rem;\n user-select: none;\n color: #0fbd8c;\n display: none;\n align-items: center;\n}\n\n.fps-counter.show {\n display: flex;\n}\n\n.sa-small-stage .fps-counter {\n display: none;\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./src/addons/addons/fps/_runtime_entry.js":
+/*!*************************************************!*\
+ !*** ./src/addons/addons/fps/_runtime_entry.js ***!
+ \*************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/fps/userscript.js");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./userstyle.css */ "./node_modules/css-loader/index.js!./src/addons/addons/fps/userstyle.css");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "userstyle.css": _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/fps/userscript.js":
+/*!*********************************************!*\
+ !*** ./src/addons/addons/fps/userscript.js ***!
+ \*********************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console,
+ msg
+ } = _ref;
+ const {
+ vm
+ } = addon.tab.traps;
+ const {
+ runtime
+ } = vm;
+ if (!vm.editingTarget) {
+ await new Promise(resolve => runtime.once("PROJECT_LOADED", resolve));
+ }
+ let fpsCounterElement = document.createElement("span");
+ fpsCounterElement.className = "fps-counter";
+ addon.tab.displayNoneWhileDisabled(fpsCounterElement);
+ let lastRender;
+ let lastFps;
+ let wasRunning = false;
+ const {
+ renderer
+ } = runtime;
+ const _draw = renderer.draw;
+ renderer.draw = function () {
+ _draw.call(this);
+ const now = runtime.currentMSecs;
+ // If it's been more than 500ms since the last draw, we want to reset the variables.
+ if (typeof lastRender !== "number" || now - lastRender > 500) {
+ lastRender = now;
+ lastFps = null;
+ return;
+ }
+ // If the current time has been rendered, return, Don't show infinity.
+ if (now === lastRender) return;
+ // Every time this function is ran, store the current time and remove times from half a second ago
+ let smoothing = 0.9;
+ let calculatedFps = 1000 / (now - lastRender);
+ if (typeof lastFps !== "number") lastFps = calculatedFps;
+ // Calculate a smoothed FPS so that numbers aren't changing too fast.
+ const fps = Math.round(lastFps * smoothing + calculatedFps * (1 - smoothing));
+ lastRender = now;
+
+ // Show/Hide the element based on if there are any threads running
+ if (runtime.threads.length === 0) {
+ if (wasRunning) fpsCounterElement.classList.remove("show");
+ wasRunning = false;
+ return;
+ }
+ if (!wasRunning) fpsCounterElement.classList.add("show");
+ if (fps !== lastFps || !wasRunning) fpsCounterElement.innerText = "FPS: ".concat(lastFps = fps);
+ wasRunning = true;
+ };
+ while (true) {
+ await addon.tab.waitForElement('[class*="controls_controls-container"]', {
+ markAsSeen: true,
+ reduxEvents: ["scratch-gui/mode/SET_PLAYER", "fontsLoaded/SET_FONTS_LOADED", "scratch-gui/locales/SELECT_LOCALE"]
+ });
+ addon.tab.appendToSharedSpace({
+ space: "afterStopButton",
+ element: fpsCounterElement,
+ order: 3
+ });
+ }
+});
+
+/***/ })
+
+}]);
+//# sourceMappingURL=1.js.map
\ No newline at end of file
diff --git a/js/1.js.map b/js/1.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..c063cebf8e9a94238e96d763578ced61f196be8d
--- /dev/null
+++ b/js/1.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"js/1.js","sources":["webpack://GUI/./src/addons/addons/fps/userstyle.css","webpack://GUI/./src/addons/addons/fps/_runtime_entry.js","webpack://GUI/./src/addons/addons/fps/userscript.js"],"sourcesContent":["exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".fps-counter {\\n font-size: 0.625rem;\\n font-weight: bold;\\n font-family: \\\"Helvetica Neue\\\", Helvetica, Arial, sans-serif;\\n white-space: nowrap;\\n padding: 0.25rem;\\n user-select: none;\\n color: #0fbd8c;\\n display: none;\\n align-items: center;\\n}\\n\\n.fps-counter.show {\\n display: flex;\\n}\\n\\n.sa-small-stage .fps-counter {\\n display: none;\\n}\", \"\"]);\n\n// exports\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nimport _css from \"!css-loader!./userstyle.css\";\nexport const resources = {\n \"userscript.js\": _js,\n \"userstyle.css\": _css,\n};","\nexport default async function ({ addon, console, msg }) {\n const { vm } = addon.tab.traps;\n const { runtime } = vm;\n if (!vm.editingTarget) {\n await new Promise((resolve) => runtime.once(\"PROJECT_LOADED\", resolve));\n }\n\n let fpsCounterElement = document.createElement(\"span\");\n fpsCounterElement.className = \"fps-counter\";\n addon.tab.displayNoneWhileDisabled(fpsCounterElement);\n\n let lastRender;\n let lastFps;\n let wasRunning = false;\n\n const { renderer } = runtime;\n const _draw = renderer.draw;\n renderer.draw = function () {\n _draw.call(this);\n\n const now = runtime.currentMSecs;\n // If it's been more than 500ms since the last draw, we want to reset the variables.\n if (typeof lastRender !== \"number\" || now - lastRender > 500) {\n lastRender = now;\n lastFps = null;\n return;\n }\n // If the current time has been rendered, return, Don't show infinity.\n if (now === lastRender) return;\n // Every time this function is ran, store the current time and remove times from half a second ago\n let smoothing = 0.9;\n let calculatedFps = 1000 / (now - lastRender);\n if (typeof lastFps !== \"number\") lastFps = calculatedFps;\n // Calculate a smoothed FPS so that numbers aren't changing too fast.\n const fps = Math.round(lastFps * smoothing + calculatedFps * (1 - smoothing));\n lastRender = now;\n\n // Show/Hide the element based on if there are any threads running\n if (runtime.threads.length === 0) {\n if (wasRunning) fpsCounterElement.classList.remove(\"show\");\n wasRunning = false;\n return;\n }\n if (!wasRunning) fpsCounterElement.classList.add(\"show\");\n if (fps !== lastFps || !wasRunning) fpsCounterElement.innerText = `FPS: ${lastFps = fps}`;\n wasRunning = true;\n };\n\n while (true) {\n await addon.tab.waitForElement('[class*=\"controls_controls-container\"]', {\n markAsSeen: true,\n reduxEvents: [\"scratch-gui/mode/SET_PLAYER\", \"fontsLoaded/SET_FONTS_LOADED\", \"scratch-gui/locales/SELECT_LOCALE\"],\n });\n addon.tab.appendToSharedSpace({ space: \"afterStopButton\", element: fpsCounterElement, order: 3 });\n }\n}"],"mappings":";;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;ACPA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;ACLA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAAA;AAAA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;;;;A","sourceRoot":""}
\ No newline at end of file
diff --git a/js/2.js b/js/2.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f95b34351cea325700df0e96288c582f470ca0f
--- /dev/null
+++ b/js/2.js
@@ -0,0 +1,53 @@
+(window["webpackJsonpGUI"] = window["webpackJsonpGUI"] || []).push([[2],{
+
+/***/ "./src/addons/addons/ScratchHighlightFullwidthNumber/_runtime_entry.js":
+/*!*****************************************************************************!*\
+ !*** ./src/addons/addons/ScratchHighlightFullwidthNumber/_runtime_entry.js ***!
+ \*****************************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/ScratchHighlightFullwidthNumber/userscript.js");
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_userscript_js__WEBPACK_IMPORTED_MODULE_0__);
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/ScratchHighlightFullwidthNumber/userscript.js":
+/*!*************************************************************************!*\
+ !*** ./src/addons/addons/ScratchHighlightFullwidthNumber/userscript.js ***!
+ \*************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports) {
+
+const target = document.querySelectorAll('.blocklyBlockCanvas');
+const regexp = /[0-9]/;
+const observer = new MutationObserver(records => {
+ document.querySelectorAll('*[data-argument-type~="text"] text, *[data-argument-type~="number"] text').forEach(e => {
+ if (regexp.test(e.textContent)) {
+ e.style.fill = 'red';
+ } else {
+ e.style.fill = '';
+ }
+ });
+});
+target.forEach(e => {
+ observer.observe(e, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true
+ });
+});
+
+/***/ })
+
+}]);
+//# sourceMappingURL=2.js.map
\ No newline at end of file
diff --git a/js/2.js.map b/js/2.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..165b3f1331fccef00d8a1331ca9985eca50b7715
--- /dev/null
+++ b/js/2.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"js/2.js","sources":["webpack://GUI/./src/addons/addons/ScratchHighlightFullwidthNumber/_runtime_entry.js","webpack://GUI/./src/addons/addons/ScratchHighlightFullwidthNumber/userscript.js"],"sourcesContent":["/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js\n};","const target = document.querySelectorAll('.blocklyBlockCanvas');\nconst regexp = /[0-9]/;\n\nconst observer = new MutationObserver(records => {\n document.querySelectorAll('*[data-argument-type~=\"text\"] text, *[data-argument-type~=\"number\"] text').forEach(e => {\n if (regexp.test(e.textContent)){\n e.style.fill = 'red';\n } else {\n e.style.fill = '';\n }\n });\n});\n\ntarget.forEach(e => {\n observer.observe(e, {\n attributes: true,\n characterData: true,\n childList: true,\n subtree: true\n });\n});"],"mappings":";;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;;;;;;;;;;;ACJA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;A","sourceRoot":""}
\ No newline at end of file
diff --git a/js/addon-default-entry.js b/js/addon-default-entry.js
new file mode 100644
index 0000000000000000000000000000000000000000..1506b100e47f4e64782bb23fc1d78407143cbbd2
--- /dev/null
+++ b/js/addon-default-entry.js
@@ -0,0 +1,13439 @@
+(window["webpackJsonpGUI"] = window["webpackJsonpGUI"] || []).push([["addon-default-entry"],{
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/color-picker/style.css":
+/*!****************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/color-picker/style.css ***!
+ \****************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".sa-color-picker {\n display: flex;\n}\n\n.sa-color-picker-code {\n margin: 8px 0;\n}\n\n.sa-color-picker-paint {\n margin-top: 16px;\n margin-bottom: 4px;\n}\n\n.sa-color-picker > .sa-color-picker-color {\n border: none;\n border-top-left-radius: 1rem;\n border-bottom-left-radius: 1rem;\n padding: 0;\n padding-left: 0.6rem;\n padding-right: 0.4rem;\n margin-left: 0.5rem;\n outline: none;\n box-sizing: border-box;\n width: 3rem;\n height: 2rem;\n}\n[theme=\"dark\"] .sa-color-picker > .sa-color-picker-color {\n background: var(--ui-secondary);\n}\n\n.sa-color-picker > .sa-color-picker-text {\n box-sizing: border-box;\n width: calc(150px - 3rem);\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n[dir=\"rtl\"] .sa-color-picker > .sa-color-picker-color {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n border-top-right-radius: 1rem;\n border-bottom-right-radius: 1rem;\n margin-left: 0;\n margin-right: 0.5rem;\n}\n\n[dir=\"rtl\"] .sa-color-picker > .sa-color-picker-text {\n border-top-left-radius: 1rem;\n border-bottom-left-radius: 1rem;\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\nbody.sa-hide-eye-dropper-background div[class*=\"stage_color-picker-background\"] {\n /* Do not show eye dropper background if the color picker is \"fake\" */\n display: none;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/editor-comment-previews/userstyle.css":
+/*!*******************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/editor-comment-previews/userstyle.css ***!
+ \*******************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".sa-comment-preview-outer {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 100000000;\n pointer-events: none;\n}\n\n.sa-comment-preview-inner {\n width: calc(200px - 16px);\n max-height: calc(132px - 8px);\n padding: 8px;\n overflow: hidden;\n\n font-size: 12px;\n white-space: pre-wrap;\n pointer-events: none;\n\n color: rgb(87, 94, 117);\n background-color: rgb(255 255 255 / 90%);\n border-style: none;\n border-radius: 8px;\n filter: drop-shadow(0px 5px 5px rgb(0 0 0 / 10%));\n\n transform: perspective(200px);\n}\n\n@supports (backdrop-filter: blur(16px)) {\n .sa-comment-preview-inner {\n background-color: rgb(255 255 255 / 75%);\n backdrop-filter: blur(16px);\n }\n}\n\n.sa-comment-preview-fade {\n transition: opacity 0.1s, filter 0.1s, transform 0.1s linear;\n}\n\n.sa-comment-preview-hidden {\n opacity: 0;\n filter: none;\n transform: perspective(200px) translateZ(-20px);\n}\n\n.sa-comment-preview-reduce-transparency {\n background-color: rgb(255 255 255);\n backdrop-filter: none;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/editor-searchable-dropdowns/userscript.css":
+/*!************************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/editor-searchable-dropdowns/userscript.css ***!
+ \************************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".u-dropdown-searchbar {\n width: 100%;\n box-sizing: border-box;\n /* based on styles for the title input */\n color: white;\n background-color: hsla(0, 100%, 100%, 0.25);\n border: 1px solid hsla(0, 0%, 0%, 0.15);\n padding: 0.5rem;\n outline: none;\n transition: 0.25s ease-out;\n font-size: 13px;\n font-weight: bold;\n border-radius: 4px;\n}\n.u-dropdown-searchbar:hover {\n background-color: hsla(0, 100%, 100%, 0.5);\n}\n.u-dropdown-searchbar:focus {\n background-color: white;\n color: black;\n}\n.blocklyDropDownDiv .goog-menu {\n overflow-x: hidden;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/editor-theme3/compatibility.css":
+/*!*************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/editor-theme3/compatibility.css ***!
+ \*************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, "/* Imported by other addons */\n\n.sa-block-color {\n --sa-block-colored-background: var(--sa-block-background-primary);\n --sa-block-colored-background-secondary: var(--sa-block-field-background);\n --sa-block-bright-background: var(--sa-block-background-primary);\n --sa-block-text: white;\n --sa-block-gray-text: white;\n --sa-block-colored-text: var(--sa-block-background-primary);\n --sa-block-text-on-bright-background: white;\n}\n\n.sa-block-color-motion {\n --sa-block-background-primary: var(--editorTheme3-motion-primary, #4c97ff);\n --sa-block-background-secondary: var(--editorTheme3-motion-secondary, #4280d7);\n --sa-block-background-tertiary: var(--editorTheme3-motion-tertiary, #3373cc);\n --sa-block-field-background: var(--editorTheme3-motion-field, #3373cc);\n}\n\n.sa-block-color-looks {\n --sa-block-background-primary: var(--editorTheme3-looks-primary, #9966ff);\n --sa-block-background-secondary: var(--editorTheme3-looks-secondary, #855cd6);\n --sa-block-background-tertiary: var(--editorTheme3-looks-tertiary, #774dcb);\n --sa-block-field-background: var(--editorTheme3-looks-field, #774dcb);\n}\n\n.sa-block-color-sounds {\n --sa-block-background-primary: var(--editorTheme3-sounds-primary, #cf63cf);\n --sa-block-background-secondary: var(--editorTheme3-sounds-secondary, #c94fc9);\n --sa-block-background-tertiary: var(--editorTheme3-sounds-tertiary, #bd42bd);\n --sa-block-field-background: var(--editorTheme3-sounds-field, #bd42bd);\n}\n\n.sa-block-color-events {\n --sa-block-background-primary: var(--editorTheme3-event-primary, #ffbf00);\n --sa-block-background-secondary: var(--editorTheme3-event-secondary, #e6ac00);\n --sa-block-background-tertiary: var(--editorTheme3-event-tertiary, #cc9900);\n --sa-block-field-background: var(--editorTheme3-event-field, #cc9900);\n}\n\n.sa-block-color-control {\n --sa-block-background-primary: var(--editorTheme3-control-primary, #ffab19);\n --sa-block-background-secondary: var(--editorTheme3-control-secondary, #ec9c13);\n --sa-block-background-tertiary: var(--editorTheme3-control-tertiary, #cf8b17);\n --sa-block-field-background: var(--editorTheme3-control-field, #cf8b17);\n}\n\n.sa-block-color-sensing {\n --sa-block-background-primary: var(--editorTheme3-sensing-primary, #5cb1d6);\n --sa-block-background-secondary: var(--editorTheme3-sensing-secondary, #47a8d1);\n --sa-block-background-tertiary: var(--editorTheme3-sensing-tertiary, #2e8eb8);\n --sa-block-field-background: var(--editorTheme3-sensing-field, #2e8eb8);\n}\n\n.sa-block-color-operators {\n --sa-block-background-primary: var(--editorTheme3-operators-primary, #59c059);\n --sa-block-background-secondary: var(--editorTheme3-operators-secondary, #46b946);\n --sa-block-background-tertiary: var(--editorTheme3-operators-tertiary, #389438);\n --sa-block-field-background: var(--editorTheme3-operators-field, #389438);\n}\n\n.sa-block-color-data {\n --sa-block-background-primary: var(--editorTheme3-data-primary, #ff8c1a);\n --sa-block-background-secondary: var(--editorTheme3-data-secondary, #ff8000);\n --sa-block-background-tertiary: var(--editorTheme3-data-tertiary, #db6e00);\n --sa-block-field-background: var(--editorTheme3-data-field, #db6e00);\n}\n\n.sa-block-color-data-lists,\n.sa-block-color-list {\n --sa-block-background-primary: var(--editorTheme3-data_lists-primary, #ff661a);\n --sa-block-background-secondary: var(--editorTheme3-data_lists-secondary, #ff5500);\n --sa-block-background-tertiary: var(--editorTheme3-data_lists-tertiary, #e64d00);\n --sa-block-field-background: var(--editorTheme3-data_lists-field, #e64d00);\n}\n\n.sa-block-color-more,\n.sa-block-color-null {\n --sa-block-background-primary: var(--editorTheme3-more-primary, #ff6680);\n --sa-block-background-secondary: var(--editorTheme3-more-secondary, #ff4d6a);\n --sa-block-background-tertiary: var(--editorTheme3-more-tertiary, #ff3355);\n --sa-block-field-background: var(--editorTheme3-more-field, #ff3355);\n}\n\n.sa-block-color-pen {\n --sa-block-background-primary: var(--editorTheme3-pen-primary, #0fbd8c);\n --sa-block-background-secondary: var(--editorTheme3-pen-secondary, #0da57a);\n --sa-block-background-tertiary: var(--editorTheme3-pen-tertiary, #0b8e69);\n --sa-block-field-background: var(--editorTheme3-pen-field, #0b8e69);\n}\n\n.sa-block-color-addon-custom-block {\n --sa-block-background-primary: var(--editorTheme3-sa-primary, #29beb8);\n --sa-block-background-secondary: var(--editorTheme3-sa-secondary, #3aa8a4);\n --sa-block-background-tertiary: var(--editorTheme3-sa-tertiary, #3aa8a4);\n --sa-block-field-background: var(--editorTheme3-sa-field, #3aa8a4);\n}\n\n.sa-block-color-TurboWarp {\n --sa-block-background-primary: var(--editorTheme3-tw-primary, #ff4c4c);\n --sa-block-background-secondary: var(--editorTheme3-tw-secondary, #e64444);\n --sa-block-background-tertiary: var(--editorTheme3-tw-tertiary, #e64444);\n --sa-block-field-background: var(--editorTheme3-tw-field, #e64444);\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/find-bar/userstyle.css":
+/*!****************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/find-bar/userstyle.css ***!
+ \****************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+exports.i(__webpack_require__(/*! -!../../../../node_modules/css-loader!../editor-theme3/compatibility.css */ "./node_modules/css-loader/index.js!./src/addons/addons/editor-theme3/compatibility.css"), "");
+
+// module
+exports.push([module.i, ".sa-find-bar {\n display: flex;\n align-items: center;\n white-space: nowrap;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n width: 100%;\n height: 100%;\n margin-left: 1em;\n}\n.sa-find-bar[hidden] {\n /* !important to override displayNoneWhileDisabled */\n display: none !important;\n}\n\n.sa-find-wrapper {\n overflow: visible;\n position: relative;\n height: 2rem;\n width: 100%;\n max-width: 16em;\n}\n\n.sa-find-dropdown-out {\n display: block;\n top: -6px;\n z-index: 100;\n width: 100%;\n max-width: 16em;\n position: relative;\n padding: 4px;\n border: none;\n border-radius: 4px;\n margin-top: 6px;\n}\n\n.sa-find-dropdown-out.visible {\n position: absolute;\n width: 16em;\n box-shadow: 0px 0px 8px 1px var(--ui-black-transparent, rgba(0, 0, 0, 0.3));\n background-color: var(--ui-primary, white);\n}\n\n/* We need to modify Scratch styles so that the place where the find bar is injected */\n/* has actually correct size information, which is used to make the find bar not cover up controls */\n[class*=\"gui_tab-list_\"] {\n width: 100%;\n}\n[class*=\"gui_tab_\"] {\n flex-grow: 0;\n}\n\n.sa-find-input {\n width: 100%;\n box-sizing: border-box !important;\n /* !important required for extension, because CSS injection method (and hence order) differs from addon */\n height: 1.5rem;\n\n /* Change Scratch default styles */\n border-radius: 0.25rem;\n font-size: 0.75rem;\n padding-left: 0.4em;\n}\n\n.sa-find-input:focus {\n /* Change Scratch default styles */\n box-shadow: none;\n}\n\n.sa-find-dropdown {\n display: none;\n position: relative;\n padding: 0.2em 0;\n font-size: 0.75rem;\n line-height: 1;\n overflow-y: auto;\n min-height: 128px;\n max-height: 65vh;\n user-select: none;\n max-width: 100%;\n margin-top: 6px;\n border: none;\n}\n\n.sa-find-dropdown-out.visible > .sa-find-dropdown {\n display: block;\n}\n\n.sa-find-dropdown > li {\n display: block;\n padding: 0.5em 0.3em;\n white-space: nowrap;\n margin: 0;\n font-weight: bold;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.sa-find-dropdown > li > b {\n background-color: #aaffaa;\n color: black;\n}\n\n/* Drop down items */\n.sa-find-dropdown > li:hover,\n.sa-find-dropdown > li.sel {\n color: var(--sa-block-text-on-bright-background);\n cursor: pointer;\n}\n\n.sa-find-dropdown > li::before {\n content: \"\\25CF \"; /* ● */\n}\n\n.sa-find-flag {\n color: #4cbf56;\n}\n/* .sa-find-dropdown added for specificity */\n.sa-find-dropdown > .sa-find-flag:hover,\n.sa-find-dropdown > .sa-find-flag.sel {\n background-color: #4cbf56;\n color: white;\n}\n\n.sa-find-dropdown .sa-block-color {\n color: var(--sa-block-colored-text);\n}\n.sa-find-dropdown .sa-block-color:hover,\n.sa-find-dropdown .sa-block-color.sel {\n background-color: var(--sa-block-bright-background);\n}\n\n.sa-find-carousel {\n font-weight: normal;\n position: absolute;\n right: 0;\n white-space: nowrap;\n background-color: inherit;\n z-index: 1;\n padding: 0;\n}\n\n.sa-find-carousel-control {\n padding: 0 6px;\n}\n\n.sa-find-carousel-control:hover {\n color: #ffff80;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/folders/style.css":
+/*!***********************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/folders/style.css ***!
+ \***********************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".sa-folders-contextmenu-item {\n max-width: 250px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n[sa-folders-context-type=\"folder\"] .react-contextmenu > :not(.sa-ctx-menu) {\n display: none;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/middle-click-popup/userstyle.css":
+/*!**************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/middle-click-popup/userstyle.css ***!
+ \**************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+exports.i(__webpack_require__(/*! -!../../../../node_modules/css-loader!../editor-theme3/compatibility.css */ "./node_modules/css-loader/index.js!./src/addons/addons/editor-theme3/compatibility.css"), "");
+
+// module
+exports.push([module.i, ".sa-mcp-root {\n display: flex;\n white-space: nowrap;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n\n position: absolute;\n min-width: 100px;\n background-color: white;\n border-radius: 4px;\n box-shadow:\n rgba(0, 0, 0, 0.3) 0 0 3px,\n rgba(0, 0, 0, 0.2) 0 3px 10px;\n\n z-index: 999;\n}\n\n.sa-mcp-container {\n display: flex;\n flex-flow: column;\n top: -6px;\n z-index: 100;\n position: absolute;\n box-shadow: 0px 0px 8px 1px rgba(0, 0, 0, 0.3);\n background-color: white;\n border: none;\n border-radius: 4px;\n overflow: auto;\n resize: both;\n}\n[theme=\"dark\"] .sa-mcp-container {\n background-color: #222;\n}\n\n.sa-mcp-input-wrapper {\n position: relative;\n margin: 4px;\n /* !important required for extension, because CSS injection method (and hence order) differs from addon */\n box-sizing: border-box !important;\n height: 1.5rem;\n min-height: 1.5rem;\n\n /* Change Scratch default styles */\n border-radius: 0.25rem;\n font-size: 0.75rem;\n padding-left: 0.2rem;\n padding-right: 0.2rem;\n}\n\n.sa-mcp-input-wrapper:focus {\n /* Change Scratch default styles */\n box-shadow: none;\n}\n\n.sa-mcp-input-wrapper[data-error=\"true\"] {\n border-color: red;\n}\n\n.sa-mcp-input-wrapper > input {\n position: absolute;\n border: 0;\n background-color: transparent;\n outline: none;\n width: 100%;\n height: 100%;\n line-height: 100%;\n box-sizing: border-box;\n}\n\n.sa-mcp-input-suggestion {\n color: hsla(225, 15%, 40%, 0.65);\n}\n\n.sa-mcp-preview-container {\n flex: auto;\n overflow-y: scroll;\n scrollbar-width: none;\n}\n\n.sa-mcp-preview-container::-webkit-scrollbar {\n width: 0;\n height: 0;\n}\n\n.sa-mcp-preview-blocks {\n width: 100%;\n min-height: 100%;\n /* https://stackoverflow.com/a/22166728/8448397 */\n float: left;\n}\n\n.sa-mcp-preview-scrollbar {\n position: absolute;\n width: 11px;\n right: 0;\n bottom: 0;\n}\n\n.sa-mcp-preview-block-bg {\n width: 100%;\n fill: transparent;\n cursor: grab;\n}\n\n.sa-mcp-preview-block {\n filter: brightness(95%);\n cursor: grab;\n}\n\n.sa-mcp-preview-block-selection {\n filter: brightness(103%);\n}\n\n.sa-mcp-preview-block-bg-selection {\n fill: #7774;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/multi-tab-code/userstyle.css":
+/*!**********************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/multi-tab-code/userstyle.css ***!
+ \**********************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".tab-scroller {\n width: max-content;\n display: flex;\n position: absolute;\n}\n.tab-scrollbar {\n position: absolute;\n background-color: black;\n height: 1px;\n width: 20px;\n bottom: 0;\n}\n.tab-scrollbar:hover { height: 3px; }\n.tab-wrapper {\n position: absolute;\n right: 0;\n height: 17px;\n overflow-x: hidden;\n overflow-y: visible;\n scrollbar-width: thin;\n}\n.tab-wrapper.copying {\n height: 29px;\n}\n.tab-adder-button {\n width: 12px;\n height: 12px;\n margin: 1px;\n margin-right: 11px;\n border-radius: 3px;\n border: none;\n}\n[theme=dark] .tab-adder-button {\n filter: invert(1);\n}\n.tab-adder-button:hover {\n background-color: rgba(0 0 0 / 15%);\n}\n.tab-bounds {\n height: 14px;\n display: inline-flex;\n flex-direction: column;\n align-items: flex-start;\n width: max-content;\n}\n.tab-bounds.copying {\n height: 28px;\n}\n.tab {\n border-radius: 3px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n padding: 1px 4px;\n min-width: 20px;\n font-size: 10px;\n text-align: center;\n cursor: pointer;\n overflow-y: hidden;\n user-select: none;\n background-color: var(--ui-primary, hsla(215, 100%, 95%, 1));\n border: 1px solid var(--ui-black-transparent, hsla(0, 0%, 0%, 0.15));\n color: var(--text-primary, hsla(225, 15%, 40%, 1));\n border-top: none;\n transition: height 100ms ease-in, color 100ms ease-in;\n}\n.tab.unselected {\n height: 7px;\n color: transparent;\n}\n.tab.hover {\n height: 10px;\n color: #333;\n}\n.tab.selected {\n height: 14px;\n color: var(--text-primary, hsla(225, 15%, 40%, 1));\n}\n.tab.copying {\n height: 28px;\n color: var(--text-primary, hsla(225, 15%, 40%, 1));\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/onion-skinning/style.css":
+/*!******************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/onion-skinning/style.css ***!
+ \******************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".sa-onion-button {\n position: relative;\n}\n.sa-onion-button:focus-within {\n background-color: hsla(194, 100%, 50%, 0.2);\n}\n[theme=\"dark\"] .sa-onion-image {\n filter: brightness(0) invert(0.8);\n}\n.sa-onion-button[data-enabled=\"true\"] .sa-onion-image {\n filter: brightness(0) invert(1);\n}\n.sa-onion-button[data-enabled=\"true\"] {\n background-color: #00c3ff;\n}\n\n.sa-onion-group {\n position: relative;\n flex-direction: row;\n}\n\n.sa-onion-settings-wrapper {\n position: absolute;\n justify-items: center;\n left: 50%;\n width: 1.95rem;\n height: 1.95rem;\n display: grid;\n}\n\n.sa-onion-settings {\n position: absolute;\n bottom: 100%;\n /* based on the styles for the color dropdown */\n padding: 4px;\n border-radius: 4px;\n border: 1px solid var(--paint-ui-pane-border, #ddd);\n box-shadow: 0px 0px 8px 1px rgba(0, 0, 0, 0.3);\n transition-property: bottom, opacity;\n transition-duration: 500ms;\n transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);\n opacity: 0;\n pointer-events: none;\n background: var(--ui-primary, white);\n min-height: 100%;\n min-width: 100%;\n display: flex;\n flex-direction: column;\n gap: 0.25em;\n}\n.sa-onion-settings[data-visible=\"true\"] {\n bottom: calc(100% + 22px);\n pointer-events: auto;\n opacity: 1;\n}\n\n.sa-onion-settings-line {\n display: flex;\n justify-content: flex-end;\n align-items: baseline;\n gap: 0.25em;\n}\n\n.sa-onion-settings-input {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n text-align: center;\n border: 0;\n background: transparent;\n -moz-appearance: textfield;\n border: 0;\n outline: 0;\n}\n\n.sa-onion-settings-input::-webkit-outer-spin-button,\n.sa-onion-settings-input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.sa-onion-settings-tip {\n position: absolute;\n bottom: 0;\n transform: translateY(100%);\n right: calc(50% - 7px);\n}\n.sa-onion-settings-polygon {\n fill: var(--ui-primary, white);\n stroke: var(--paint-ui-pane-border, #ddd);\n}\n\n.sa-onion-settings-label {\n white-space: nowrap;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/pick-colors-from-stage/style.css":
+/*!**************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/pick-colors-from-stage/style.css ***!
+ \**************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".sa-stage-color-picker-picking [class^=\"stage_color-picker-background_\"] {\n display: none;\n}\n", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/reorder-custom-inputs/arrows.css":
+/*!**************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/reorder-custom-inputs/arrows.css ***!
+ \**************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, ".blocklyTextShiftArrow {\n position: absolute;\n top: -50px;\n left: 50%;\n margin-left: -12.5px;\n cursor: pointer;\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/toolbox-category-drag/userstyle.css":
+/*!*****************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/toolbox-category-drag/userstyle.css ***!
+ \*****************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, "div[class=\"scratchCategoryMenuRow\"][data-dragger=\"true\"] > div {\n background: white;\n font-size: 0.65rem;\n}\n\n[theme=\"dark\"] div[class=\"scratchCategoryMenuRow\"][data-dragger=\"true\"] > div {\n background: var(--ui-secondary);\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/css-loader/index.js!./src/addons/addons/toolbox-full-blocks-on-hover/userstyle.css":
+/*!************************************************************************************************!*\
+ !*** ./node_modules/css-loader!./src/addons/addons/toolbox-full-blocks-on-hover/userstyle.css ***!
+ \************************************************************************************************/
+/*! no static exports found */
+/***/ (function(module, exports, __webpack_require__) {
+
+exports = module.exports = __webpack_require__(/*! ../../../../node_modules/css-loader/lib/css-base.js */ "./node_modules/css-loader/lib/css-base.js")(false);
+// imports
+
+
+// module
+exports.push([module.i, "[class*=\"blocks_blocks_\"] .blocklyFlyout:has(.blocklyDraggable:hover) {\n overflow: visible;\n z-index: 31;\n}\n\n[class*=\"blocks_blocks_\"] .blocklyFlyout:has(.blocklyDraggable:hover) .blocklyWorkspace {\n clip-path: unset !important;\n}", ""]);
+
+// exports
+
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/editor-devtools/icon--close.svg":
+/*!*************************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/editor-devtools/icon--close.svg ***!
+ \*************************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3LjQ4IDcuNDgiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MnB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tYWRkPC90aXRsZT48bGluZSBjbGFzcz0iY2xzLTEiIHgxPSIzLjc0IiB5MT0iNi40OCIgeDI9IjMuNzQiIHkyPSIxIi8+PGxpbmUgY2xhc3M9ImNscy0xIiB4MT0iMSIgeTE9IjMuNzQiIHgyPSI2LjQ4IiB5Mj0iMy43NCIvPjwvc3ZnPg==");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/folders/folder.svg":
+/*!************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/folders/folder.svg ***!
+ \************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xMCA0SDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAxOGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjhjMC0xLjEtLjktMi0yLTJoLThsLTItMnoiLz48L3N2Zz4=");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/multi-tab-code/add.svg":
+/*!****************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/multi-tab-code/add.svg ***!
+ \****************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZwogICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICAgd2lkdGg9IjE0IgogICAgaGVpZ2h0PSIxNCIKICAgIHZpZXdCb3g9IjAgMCAxMCAxMCIKICAgIHN0cm9rZT0iYmxhY2siCiAgICBzdHJva2UtbGluZWNhcD0icm91bmQiCj4KICAgIDxsaW5lIHgxPSI1IiB5MT0iMS41IiB4Mj0iNSIgeTI9IjguNSI+PC9saW5lPgogICAgPGxpbmUgeDE9IjEuNSIgeTE9IjUiIHgyPSI4LjUiIHkyPSI1Ij48L2xpbmU+Cjwvc3ZnPg==");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/decrement.svg":
+/*!**********************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/decrement.svg ***!
+ \**********************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUgMTEuMzU4aDE3LjUwMXYxLjI4NUgzLjI1eiIvPjwvc3ZnPg==");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/increment.svg":
+/*!**********************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/increment.svg ***!
+ \**********************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUgMTEuMzU4aDE3LjUwMXYxLjI4NUgzLjI1eiIvPjxwYXRoIHRyYW5zZm9ybT0icm90YXRlKDkwKSIgc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUtMTIuNjQyaDE3LjUwMXYxLjI4NUgzLjI1eiIvPjwvc3ZnPg==");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/settings.svg":
+/*!*********************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/settings.svg ***!
+ \*********************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggc3R5bGU9Im9wYWNpdHk6Ljc1O2ZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIgZD0iTTU2IDE2djk2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMi4zNzYgLTIuMzc2KSBzY2FsZSguMjI0NjIpIi8+PHBhdGggc3R5bGU9Im9wYWNpdHk6LjU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNNDAgMzJ2NjQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouMjU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNMjQgNDh2MzIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouNzU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNNzIgMTZ2OTYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouNTtmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Ik04OCAzMnY2NCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIuMzc2IC0yLjM3Nikgc2NhbGUoLjIyNDYyKSIvPjxwYXRoIHN0eWxlPSJvcGFjaXR5Oi4yNTtmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Ik0xMDQgNDh2MzIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48L3N2Zz4=");
+
+/***/ }),
+
+/***/ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/toggle.svg":
+/*!*******************************************************************************************!*\
+ !*** ./node_modules/url-loader/dist/cjs.js!./src/addons/addons/onion-skinning/toggle.svg ***!
+ \*******************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = ("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PGcgc3Ryb2tlLXdpZHRoPSIxLjIiIHN0cm9rZT0iIzAwMTAyNiI+PGcgc3R5bGU9Im9wYWNpdHk6LjU7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLWRhc2hhcnJheTpub25lIj48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6bm9uZTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgZmlsbD0iI2JmYmZiZiIgZD0iTTY4LjQ1IDMyLjQ1N2MuMy0uMi44LS4xLjkuM2wyLjYgMTAuN3M2LjQgNC43IDguMyA4YzMuMiA1LjUgMy4zIDEwIDMuMyAxMHM3LjEgMi4xIDguMyA3LjhjMS4yIDUuNy0zLjIgMTYuNS0yMiAyMC4yLTE4LjggMy43LTMzLjktMS40LTQxLTEyLjgtNy4xLTExLjQgNC4xLTI1IDMuNS0yNC4ybC0yLjEtMTcuOWMtLjEtLjQuNC0uNy44LS41bDEyLjEgNy45czQuNS0xLjcgOS4yLTEuOWMyLjgtLjIgNS4yIDAgNy41LjR6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42NTIgLTQuNjUpIHNjYWxlKC4yMzI5KSIvPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIGQ9Ik0xMDEuNjUgNjIuOTU3Yy00LjcgMy44LTExLjkgMy45LTExLjkgMy45bTEwLjYgNy4zYy02LjMuNS0xMC4yLTEuNC0xMC4yLTEuNG0tNjguOC0xMC4xczguNiAyLjggMTIuMSA1LjltLjMgMy41Yy00LjMgMS43LTExLjcuNi0xMS43LjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00LjY1MiAtNC42NSkgc2NhbGUoLjIzMjkpIi8+PC9nPjxnIHN0eWxlPSJvcGFjaXR5Oi43NTtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmUiPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBmaWxsPSIjYmZiZmJmIiBkPSJNNjguNDUgMzIuNDU3Yy4zLS4yLjgtLjEuOS4zbDIuNiAxMC43czYuNCA0LjcgOC4zIDhjMy4yIDUuNSAzLjMgMTAgMy4zIDEwczcuMSAyLjEgOC4zIDcuOGMxLjIgNS43LTMuMiAxNi41LTIyIDIwLjItMTguOCAzLjctMzMuOS0xLjQtNDEtMTIuOC03LjEtMTEuNCA0LjEtMjUgMy41LTI0LjJsLTIuMS0xNy45Yy0uMS0uNC40LS43LjgtLjVsMTIuMSA3LjlzNC41LTEuNyA5LjItMS45YzIuOC0uMiA1LjIgMCA3LjUuNHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjMyMyAtMi4zMjEpIHNjYWxlKC4yMzI5KSIvPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIGQ9Ik0xMDEuNjUgNjIuOTU3Yy00LjcgMy44LTExLjkgMy45LTExLjkgMy45bTEwLjYgNy4zYy02LjMuNS0xMC4yLTEuNC0xMC4yLTEuNG0tNjguOC0xMC4xczguNiAyLjggMTIuMSA1LjltLjMgMy41Yy00LjMgMS43LTExLjcuNi0xMS43LjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjMyMyAtMi4zMjEpIHNjYWxlKC4yMzI5KSIvPjwvZz48ZyBzdHlsZT0ic3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLWRhc2hhcnJheTpub25lIj48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6I2ZmZjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgZmlsbD0iI2JmYmZiZiIgZD0iTTczLjQ1IDM3LjQ1N2MuMy0uMi44LS4xLjkuM2wyLjYgMTAuN3M2LjQgNC43IDguMyA4YzMuMiA1LjUgMy4zIDEwIDMuMyAxMHM3LjEgMi4xIDguMyA3LjhjMS4yIDUuNy0zLjIgMTYuNS0yMiAyMC4yLTE4LjggMy43LTMzLjktMS40LTQxLTEyLjgtNy4xLTExLjQgNC4xLTI1IDMuNS0yNC4ybC0yLjEtMTcuOWMtLjEtLjQuNC0uNy44LS41bDEyLjEgNy45czQuNS0xLjcgOS4yLTEuOWMyLjgtLjIgNS4yIDAgNy41LjR6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMS4xNTkgLTEuMTU3KSBzY2FsZSguMjMyOSkiLz48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6bm9uZTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIiBkPSJNMTA2LjY1IDY3Ljk1N2MtNC43IDMuOC0xMS45IDMuOS0xMS45IDMuOW0xMC42IDcuM2MtNi4zLjUtMTAuMi0xLjQtMTAuMi0xLjRtLTY4LjgtMTAuMXM4LjYgMi44IDEyLjEgNS45bS4zIDMuNWMtNC4zIDEuNy0xMS43LjYtMTEuNy42IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMS4xNTkgLTEuMTU3KSBzY2FsZSguMjMyOSkiLz48L2c+PC9nPjwvc3ZnPg==");
+
+/***/ }),
+
+/***/ "./src/addons/addons/bitmap-copy/_runtime_entry.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/bitmap-copy/_runtime_entry.js ***!
+ \*********************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/bitmap-copy/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/bitmap-copy/userscript.js":
+/*!*****************************************************!*\
+ !*** ./src/addons/addons/bitmap-copy/userscript.js ***!
+ \*****************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = (async _ref => {
+ let {
+ addon,
+ console
+ } = _ref;
+ if (!addon.tab.redux.state) return console.warn("Redux is not available!");
+ addon.tab.redux.initialize();
+ addon.tab.redux.addEventListener("statechanged", _ref2 => {
+ let {
+ detail
+ } = _ref2;
+ if (addon.self.disabled) return;
+ const e = detail;
+ if (!e.action || e.action.type !== "scratch-paint/clipboard/SET") return;
+ const items = e.next.scratchPaint.clipboard.items;
+ if (items.length !== 1) return;
+ const firstItem = items[0];
+ // TODO vector support
+ if (!Array.isArray(firstItem) || firstItem[0] !== "Raster") return console.log("copied element is vector");
+ const dataURL = firstItem[1].source;
+ addon.tab.copyImage(dataURL).then(() => console.log("Image successfully copied")).catch(e => console.error("Image could not be copied: ".concat(e)));
+ });
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-cherry-picking/_runtime_entry.js":
+/*!******************************************************************!*\
+ !*** ./src/addons/addons/block-cherry-picking/_runtime_entry.js ***!
+ \******************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/block-cherry-picking/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-cherry-picking/userscript.js":
+/*!**************************************************************!*\
+ !*** ./src/addons/addons/block-cherry-picking/userscript.js ***!
+ \**************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _block_duplicate_module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../block-duplicate/module.js */ "./src/addons/addons/block-duplicate/module.js");
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console
+ } = _ref;
+ const update = () => {
+ _block_duplicate_module_js__WEBPACK_IMPORTED_MODULE_0__["setCherryPicking"](!addon.self.disabled, addon.settings.get("invertDrag"));
+ };
+ addon.self.addEventListener("disabled", update);
+ addon.self.addEventListener("reenabled", update);
+ addon.settings.addEventListener("change", update);
+ update();
+ _block_duplicate_module_js__WEBPACK_IMPORTED_MODULE_0__["load"](addon);
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-duplicate/_runtime_entry.js":
+/*!*************************************************************!*\
+ !*** ./src/addons/addons/block-duplicate/_runtime_entry.js ***!
+ \*************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/block-duplicate/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-duplicate/module.js":
+/*!*****************************************************!*\
+ !*** ./src/addons/addons/block-duplicate/module.js ***!
+ \*****************************************************/
+/*! exports provided: setCherryPicking, setDuplication, load */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "setCherryPicking", function() { return setCherryPicking; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "setDuplication", function() { return setDuplication; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "load", function() { return load; });
+let enableCherryPicking = false;
+let invertCherryPicking = false;
+function setCherryPicking(newEnabled, newInverted) {
+ enableCherryPicking = newEnabled;
+ // If cherry picking is disabled, also disable invert. Duplicating blocks can still cause
+ // this setting to be used.
+ invertCherryPicking = newEnabled && newInverted;
+}
+let enableDuplication = false;
+function setDuplication(newEnabled) {
+ enableDuplication = newEnabled;
+}
+
+// mostRecentEvent_ is sometimes a fake event, so we can't rely on reading its properties.
+let ctrlOrMetaPressed = false;
+let altPressed = false;
+document.addEventListener("mousedown", function (e) {
+ ctrlOrMetaPressed = e.ctrlKey || e.metaKey;
+ altPressed = e.altKey;
+}, {
+ capture: true
+});
+let loaded = false;
+async function load(addon) {
+ if (loaded) {
+ return;
+ }
+ loaded = true;
+ const ScratchBlocks = await addon.tab.traps.getBlockly();
+
+ // https://github.com/LLK/scratch-blocks/blob/912b8cc728bea8fd91af85078c64fcdbfe21c87a/core/gesture.js#L454
+ const originalStartDraggingBlock = ScratchBlocks.Gesture.prototype.startDraggingBlock_;
+ ScratchBlocks.Gesture.prototype.startDraggingBlock_ = function () {
+ let block = this.targetBlock_;
+
+ // Scratch uses fake mouse events to implement right click > duplicate
+ const isRightClickDuplicate = !(this.mostRecentEvent_ instanceof MouseEvent);
+ const isDuplicating = enableDuplication && altPressed && !isRightClickDuplicate && !this.flyout_ && !this.shouldDuplicateOnDrag_ && (this.targetBlock_.type !== "procedures_definition" || this.targetBlock_.type !== "procedures_definition_return");
+ const isCherryPickingInverted = invertCherryPicking && !isRightClickDuplicate && block.getParent();
+ const canCherryPick = enableCherryPicking || isDuplicating;
+ const isCherryPicking = canCherryPick && ctrlOrMetaPressed === !isCherryPickingInverted && !block.isShadow();
+ if (isDuplicating || isCherryPicking) {
+ if (!ScratchBlocks.Events.getGroup()) {
+ // Scratch will disable grouping on its own later.
+ ScratchBlocks.Events.setGroup(true);
+ }
+ }
+ if (isDuplicating) {
+ // Based on https://github.com/LLK/scratch-blocks/blob/feda366947432b9d82a4f212f43ff6d4ab6f252f/core/scratch_blocks_utils.js#L171
+ // Setting this.shouldDuplicateOnDrag_ = true does NOT work because it doesn't call changeObscuredShadowIds
+ this.startWorkspace_.setResizesEnabled(false);
+ ScratchBlocks.Events.disable();
+ let newBlock;
+ try {
+ const xmlBlock = ScratchBlocks.Xml.blockToDom(block);
+ newBlock = ScratchBlocks.Xml.domToBlock(xmlBlock, this.startWorkspace_);
+ ScratchBlocks.scratchBlocksUtils.changeObscuredShadowIds(newBlock);
+ const xy = block.getRelativeToSurfaceXY();
+ newBlock.moveBy(xy.x, xy.y);
+ } catch (e) {
+ console.error(e);
+ }
+ ScratchBlocks.Events.enable();
+ if (newBlock) {
+ block = newBlock;
+ this.targetBlock_ = newBlock;
+ if (ScratchBlocks.Events.isEnabled()) {
+ ScratchBlocks.Events.fire(new ScratchBlocks.Events.BlockCreate(newBlock));
+ }
+ }
+ }
+ if (isCherryPicking) {
+ if (isRightClickDuplicate || isDuplicating) {
+ const nextBlock = block.getNextBlock();
+ if (nextBlock) {
+ nextBlock.dispose();
+ }
+ }
+ block.unplug(true);
+ }
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ return originalStartDraggingBlock.call(this, ...args);
+ };
+}
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-duplicate/userscript.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/block-duplicate/userscript.js ***!
+ \*********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _module_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./module.js */ "./src/addons/addons/block-duplicate/module.js");
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console
+ } = _ref;
+ const update = () => {
+ _module_js__WEBPACK_IMPORTED_MODULE_0__["setDuplication"](!addon.self.disabled);
+ };
+ addon.self.addEventListener("disabled", update);
+ addon.self.addEventListener("reenabled", update);
+ update();
+ _module_js__WEBPACK_IMPORTED_MODULE_0__["load"](addon);
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-switching/_runtime_entry.js":
+/*!*************************************************************!*\
+ !*** ./src/addons/addons/block-switching/_runtime_entry.js ***!
+ \*************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/block-switching/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/block-switching/userscript.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/block-switching/userscript.js ***!
+ \*********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* eslint-disable */
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console,
+ msg
+ } = _ref;
+ const ScratchBlocks = await addon.tab.traps.getBlockly();
+ const vm = addon.tab.traps.vm;
+ let blockSwitches = {};
+ let procedureSwitches = {};
+ const noopSwitch = {
+ isNoop: true
+ };
+ const randomColor = () => {
+ const num = Math.floor(Math.random() * 256 * 256 * 256);
+ return "#".concat(num.toString(16).padStart(6, "0"));
+ };
+ const buildSwitches = () => {
+ blockSwitches = {};
+ procedureSwitches = {};
+ if (addon.settings.get("motion")) {
+ blockSwitches["motion_turnright"] = [noopSwitch, {
+ opcode: "motion_turnleft"
+ }];
+ blockSwitches["motion_turnleft"] = [{
+ opcode: "motion_turnright"
+ }, noopSwitch];
+ blockSwitches["motion_gotoxy"] = [noopSwitch, {
+ opcode: "motion_changebyxy",
+ remapInputName: {
+ X: "DX",
+ Y: "DY"
+ }
+ }];
+ blockSwitches["motion_changebyxy"] = [{
+ opcode: "motion_gotoxy",
+ remapInputName: {
+ DX: "X",
+ DY: "Y"
+ }
+ }, noopSwitch];
+ blockSwitches["motion_setx"] = [noopSwitch, {
+ opcode: "motion_changexby",
+ remapInputName: {
+ X: "DX"
+ }
+ }, {
+ opcode: "motion_sety",
+ remapInputName: {
+ X: "Y"
+ }
+ }, {
+ opcode: "motion_changeyby",
+ remapInputName: {
+ X: "DY"
+ }
+ }];
+ blockSwitches["motion_changexby"] = [{
+ opcode: "motion_setx",
+ remapInputName: {
+ DX: "X"
+ }
+ }, noopSwitch, {
+ opcode: "motion_sety",
+ remapInputName: {
+ DX: "Y"
+ }
+ }, {
+ opcode: "motion_changeyby",
+ remapInputName: {
+ DX: "DY"
+ }
+ }];
+ blockSwitches["motion_sety"] = [{
+ opcode: "motion_setx",
+ remapInputName: {
+ Y: "X"
+ }
+ }, {
+ opcode: "motion_changexby",
+ remapInputName: {
+ Y: "DX"
+ }
+ }, noopSwitch, {
+ opcode: "motion_changeyby",
+ remapInputName: {
+ Y: "DY"
+ }
+ }];
+ blockSwitches["motion_changeyby"] = [{
+ opcode: "motion_setx",
+ remapInputName: {
+ DY: "X"
+ }
+ }, {
+ opcode: "motion_changexby",
+ remapInputName: {
+ DY: "DX"
+ }
+ }, {
+ opcode: "motion_sety",
+ remapInputName: {
+ DY: "Y"
+ }
+ }, noopSwitch];
+ blockSwitches["motion_xposition"] = [noopSwitch, {
+ opcode: "motion_yposition"
+ }];
+ blockSwitches["motion_yposition"] = [{
+ opcode: "motion_xposition"
+ }, noopSwitch];
+ }
+ if (addon.settings.get("looks")) {
+ blockSwitches["looks_seteffectto"] = [noopSwitch, {
+ opcode: "looks_changeeffectby",
+ remapInputName: {
+ VALUE: "CHANGE"
+ }
+ }];
+ blockSwitches["looks_changeeffectby"] = [{
+ opcode: "looks_seteffectto",
+ remapInputName: {
+ CHANGE: "VALUE"
+ }
+ }, noopSwitch];
+ blockSwitches["looks_setsizeto"] = [noopSwitch, {
+ opcode: "looks_changesizeby",
+ remapInputName: {
+ SIZE: "CHANGE"
+ }
+ }];
+ blockSwitches["looks_changesizeby"] = [{
+ opcode: "looks_setsizeto",
+ remapInputName: {
+ CHANGE: "SIZE"
+ }
+ }, noopSwitch];
+ blockSwitches["looks_costumenumbername"] = [noopSwitch, {
+ opcode: "looks_backdropnumbername"
+ }];
+ blockSwitches["looks_backdropnumbername"] = [{
+ opcode: "looks_costumenumbername"
+ }, noopSwitch];
+ blockSwitches["looks_show"] = [noopSwitch, {
+ opcode: "looks_hide"
+ }];
+ blockSwitches["looks_hide"] = [{
+ opcode: "looks_show"
+ }, noopSwitch];
+ blockSwitches["looks_setShape"] = [{
+ opcode: "looks_setShape"
+ }, noopSwitch];
+ blockSwitches["looks_setColor"] = [{
+ opcode: "looks_setColor"
+ }, noopSwitch];
+ blockSwitches["looks_setFont"] = [{
+ opcode: "looks_setFont"
+ }, noopSwitch];
+ blockSwitches["looks_nextcostume"] = [noopSwitch, {
+ opcode: "looks_nextbackdrop"
+ }];
+ blockSwitches["looks_nextbackdrop"] = [{
+ opcode: "looks_nextcostume"
+ }, noopSwitch];
+ blockSwitches["looks_say"] = [noopSwitch, {
+ opcode: "looks_sayforsecs",
+ createInputs: {
+ SECS: {
+ shadowType: "math_number",
+ value: "2"
+ }
+ }
+ }, {
+ opcode: "looks_think"
+ }, {
+ opcode: "looks_thinkforsecs",
+ createInputs: {
+ SECS: {
+ shadowType: "math_number",
+ value: "2"
+ }
+ }
+ }];
+ blockSwitches["looks_think"] = [{
+ opcode: "looks_say"
+ }, {
+ opcode: "looks_sayforsecs",
+ createInputs: {
+ SECS: {
+ shadowType: "math_number",
+ value: "2"
+ }
+ }
+ }, noopSwitch, {
+ opcode: "looks_thinkforsecs",
+ createInputs: {
+ SECS: {
+ shadowType: "math_number",
+ value: "2"
+ }
+ }
+ }];
+ blockSwitches["looks_sayforsecs"] = [{
+ opcode: "looks_say",
+ splitInputs: ["SECS"]
+ }, {
+ opcode: "looks_think",
+ splitInputs: ["SECS"]
+ }, noopSwitch, {
+ opcode: "looks_thinkforsecs"
+ }];
+ blockSwitches["looks_thinkforsecs"] = [{
+ opcode: "looks_say",
+ splitInputs: ["SECS"]
+ }, {
+ opcode: "looks_think",
+ splitInputs: ["SECS"]
+ }, {
+ opcode: "looks_sayforsecs"
+ }, noopSwitch];
+ blockSwitches["looks_switchbackdropto"] = [noopSwitch, {
+ opcode: "looks_switchbackdroptoandwait"
+ }];
+ blockSwitches["looks_switchbackdroptoandwait"] = [{
+ opcode: "looks_switchbackdropto"
+ }, noopSwitch];
+ blockSwitches["looks_gotofrontback"] = [noopSwitch, {
+ opcode: "looks_goforwardbackwardlayers",
+ remapInputName: {
+ FRONT_BACK: "FORWARD_BACKWARD"
+ },
+ mapFieldValues: {
+ FRONT_BACK: {
+ front: "forward",
+ back: "backward"
+ }
+ },
+ createInputs: {
+ NUM: {
+ shadowType: "math_integer",
+ value: "1"
+ }
+ }
+ }];
+ blockSwitches["looks_goforwardbackwardlayers"] = [{
+ opcode: "looks_gotofrontback",
+ splitInputs: ["NUM"],
+ remapInputName: {
+ FORWARD_BACKWARD: "FRONT_BACK"
+ },
+ mapFieldValues: {
+ FORWARD_BACKWARD: {
+ forward: "front",
+ backward: "back"
+ }
+ }
+ }, noopSwitch];
+ }
+ if (addon.settings.get("sound")) {
+ blockSwitches["sound_play"] = [noopSwitch, {
+ opcode: "sound_playuntildone"
+ }];
+ blockSwitches["sound_playuntildone"] = [{
+ opcode: "sound_play"
+ }, noopSwitch];
+ blockSwitches["sound_seteffectto"] = [noopSwitch, {
+ opcode: "sound_changeeffectby"
+ }];
+ blockSwitches["sound_changeeffectby"] = [{
+ opcode: "sound_seteffectto"
+ }, noopSwitch];
+ blockSwitches["sound_setvolumeto"] = [noopSwitch, {
+ opcode: "sound_changevolumeby"
+ }];
+ blockSwitches["sound_changevolumeby"] = [{
+ opcode: "sound_setvolumeto"
+ }, noopSwitch];
+ }
+ if (addon.settings.get("event")) {
+ blockSwitches["event_broadcast"] = [noopSwitch, {
+ opcode: "event_broadcastandwait"
+ }];
+ blockSwitches["event_broadcastandwait"] = [{
+ opcode: "event_broadcast"
+ }, noopSwitch];
+ }
+ if (addon.settings.get("control")) {
+ blockSwitches["control_if"] = [noopSwitch, {
+ opcode: "control_if_else"
+ }];
+ blockSwitches["control_if_else"] = [{
+ opcode: "control_if",
+ splitInputs: ["SUBSTACK2"]
+ }, noopSwitch];
+ blockSwitches["control_repeat_until"] = [noopSwitch, {
+ opcode: "control_while"
+ }, {
+ opcode: "control_wait_until",
+ splitInputs: ["SUBSTACK"]
+ }, {
+ opcode: "control_forever",
+ splitInputs: ["CONDITION"]
+ }];
+ blockSwitches["control_forever"] = [{
+ opcode: "control_repeat_until"
+ }, {
+ opcode: "control_while"
+ }, noopSwitch];
+ blockSwitches["control_wait_until"] = [{
+ opcode: "control_repeat_until"
+ }, noopSwitch];
+ blockSwitches["control_while"] = [{
+ opcode: "control_repeat_until"
+ }, noopSwitch, {
+ opcode: "control_forever",
+ splitInputs: ["CONDITION"]
+ }];
+ }
+ if (addon.settings.get("operator")) {
+ blockSwitches["operator_equals"] = [{
+ opcode: "operator_gt"
+ }, {
+ opcode: "operator_gtorequal"
+ }, {
+ opcode: "operator_lt"
+ }, {
+ opcode: "operator_ltorequal"
+ }, noopSwitch, {
+ opcode: "operator_notequal"
+ }];
+ blockSwitches["operator_gt"] = [noopSwitch, {
+ opcode: "operator_gtorequal"
+ }, {
+ opcode: "operator_lt"
+ }, {
+ opcode: "operator_ltorequal"
+ }, {
+ opcode: "operator_equals"
+ }, {
+ opcode: "operator_notequal"
+ }];
+ blockSwitches["operator_lt"] = [{
+ opcode: "operator_gt"
+ }, {
+ opcode: "operator_gtorequal"
+ }, noopSwitch, {
+ opcode: "operator_ltorequal"
+ }, {
+ opcode: "operator_equals"
+ }, {
+ opcode: "operator_notequal"
+ }];
+ blockSwitches["operator_notequal"] = [{
+ opcode: "operator_gt"
+ }, {
+ opcode: "operator_gtorequal"
+ }, {
+ opcode: "operator_lt"
+ }, {
+ opcode: "operator_ltorequal"
+ }, {
+ opcode: "operator_equals"
+ }, noopSwitch];
+ blockSwitches["operator_gtorequal"] = [{
+ opcode: "operator_gt"
+ }, noopSwitch, {
+ opcode: "operator_lt"
+ }, {
+ opcode: "operator_ltorequal"
+ }, {
+ opcode: "operator_equals"
+ }, {
+ opcode: "operator_notequal"
+ }];
+ blockSwitches["operator_ltorequal"] = [{
+ opcode: "operator_gt"
+ }, {
+ opcode: "operator_gtorequal"
+ }, {
+ opcode: "operator_lt"
+ }, noopSwitch, {
+ opcode: "operator_equals"
+ }, {
+ opcode: "operator_notequal"
+ }];
+ blockSwitches["operator_add"] = [noopSwitch, {
+ opcode: "operator_subtract"
+ }, {
+ opcode: "operator_multiply"
+ }, {
+ opcode: "operator_divide"
+ }, {
+ opcode: "operator_power"
+ }, {
+ opcode: "operator_mod"
+ }];
+ blockSwitches["operator_subtract"] = [{
+ opcode: "operator_add"
+ }, noopSwitch, {
+ opcode: "operator_multiply"
+ }, {
+ opcode: "operator_divide"
+ }, {
+ opcode: "operator_power"
+ }, {
+ opcode: "operator_mod"
+ }];
+ blockSwitches["operator_multiply"] = [{
+ opcode: "operator_add"
+ }, {
+ opcode: "operator_subtract"
+ }, noopSwitch, {
+ opcode: "operator_divide"
+ }, {
+ opcode: "operator_power"
+ }, {
+ opcode: "operator_mod"
+ }];
+ blockSwitches["operator_divide"] = [{
+ opcode: "operator_add"
+ }, {
+ opcode: "operator_subtract"
+ }, {
+ opcode: "operator_multiply"
+ }, noopSwitch, {
+ opcode: "operator_power"
+ }, {
+ opcode: "operator_mod"
+ }];
+ blockSwitches["operator_power"] = [{
+ opcode: "operator_add"
+ }, {
+ opcode: "operator_subtract"
+ }, {
+ opcode: "operator_multiply"
+ }, {
+ opcode: "operator_divide"
+ }, noopSwitch, {
+ opcode: "operator_mod"
+ }];
+ blockSwitches["operator_mod"] = [{
+ opcode: "operator_add"
+ }, {
+ opcode: "operator_subtract"
+ }, {
+ opcode: "operator_multiply"
+ }, {
+ opcode: "operator_divide"
+ }, {
+ opcode: "operator_power"
+ }, noopSwitch];
+ blockSwitches["operator_and"] = [noopSwitch, {
+ opcode: "operator_or"
+ }];
+ blockSwitches["operator_or"] = [{
+ opcode: "operator_and"
+ }, noopSwitch];
+ blockSwitches["operator_trueBoolean"] = [noopSwitch, {
+ opcode: "operator_falseBoolean"
+ }];
+ blockSwitches["operator_falseBoolean"] = [{
+ opcode: "operator_trueBoolean"
+ }, noopSwitch];
+ }
+ if (addon.settings.get("sensing")) {
+ blockSwitches["sensing_mousex"] = [noopSwitch, {
+ opcode: "sensing_mousey"
+ }];
+ blockSwitches["sensing_mousey"] = [{
+ opcode: "sensing_mousex"
+ }, noopSwitch];
+ blockSwitches["sensing_touchingcolor"] = [noopSwitch, {
+ opcode: "sensing_coloristouchingcolor",
+ createInputs: {
+ COLOR2: {
+ shadowType: "colour_picker",
+ value: randomColor
+ }
+ }
+ }];
+ blockSwitches["sensing_coloristouchingcolor"] = [{
+ opcode: "sensing_touchingcolor",
+ splitInputs: ["COLOR2"]
+ }, noopSwitch];
+ }
+ if (addon.settings.get("data")) {
+ blockSwitches["data_setvariableto"] = [noopSwitch, {
+ opcode: "data_changevariableby",
+ remapShadowType: {
+ VALUE: "math_number"
+ }
+ }];
+ blockSwitches["data_changevariableby"] = [{
+ opcode: "data_setvariableto",
+ remapShadowType: {
+ VALUE: "text"
+ }
+ }, noopSwitch];
+ blockSwitches["data_showvariable"] = [noopSwitch, {
+ opcode: "data_hidevariable"
+ }];
+ blockSwitches["data_hidevariable"] = [{
+ opcode: "data_showvariable"
+ }, noopSwitch];
+ blockSwitches["data_showlist"] = [noopSwitch, {
+ opcode: "data_hidelist"
+ }];
+ blockSwitches["data_hidelist"] = [{
+ opcode: "data_showlist"
+ }, noopSwitch];
+ blockSwitches["data_replaceitemoflist"] = [noopSwitch, {
+ opcode: "data_insertatlist"
+ }];
+ blockSwitches["data_insertatlist"] = [{
+ opcode: "data_replaceitemoflist"
+ }, noopSwitch];
+ blockSwitches["data_deleteoflist"] = [noopSwitch, {
+ opcode: "data_deletealloflist",
+ splitInputs: ["INDEX"]
+ }];
+ blockSwitches["data_deletealloflist"] = [{
+ opcode: "data_deleteoflist",
+ createInputs: {
+ INDEX: {
+ shadowType: "math_integer",
+ value: "1"
+ }
+ }
+ }, noopSwitch];
+ }
+ if (addon.settings.get("extension")) {
+ blockSwitches["pen_penDown"] = [noopSwitch, {
+ opcode: "pen_penUp"
+ }];
+ blockSwitches["pen_penUp"] = [{
+ opcode: "pen_penDown"
+ }, noopSwitch];
+ blockSwitches["pen_setPenColorParamTo"] = [noopSwitch, {
+ opcode: "pen_changePenColorParamBy"
+ }];
+ blockSwitches["pen_changePenColorParamBy"] = [{
+ opcode: "pen_setPenColorParamTo"
+ }, noopSwitch];
+ blockSwitches["pen_setPenHueToNumber"] = [noopSwitch, {
+ opcode: "pen_changePenHueBy"
+ }];
+ blockSwitches["pen_changePenHueBy"] = [{
+ opcode: "pen_setPenHueToNumber"
+ }, noopSwitch];
+ blockSwitches["pen_setPenShadeToNumber"] = [noopSwitch, {
+ opcode: "pen_changePenShadeBy"
+ }];
+ blockSwitches["pen_changePenShadeBy"] = [{
+ opcode: "pen_setPenShadeToNumber"
+ }, noopSwitch];
+ blockSwitches["pen_setPenSizeTo"] = [noopSwitch, {
+ opcode: "pen_changePenSizeBy"
+ }];
+ blockSwitches["pen_changePenSizeBy"] = [{
+ opcode: "pen_setPenSizeTo"
+ }, noopSwitch];
+ blockSwitches["music_setTempo"] = [noopSwitch, {
+ opcode: "music_changeTempo"
+ }];
+ blockSwitches["music_changeTempo"] = [{
+ opcode: "music_setTempo"
+ }, noopSwitch];
+ if (vm.extensionManager) {
+ const switches = vm.extensionManager.getAddonBlockSwitches();
+ Object.getOwnPropertyNames(switches).forEach(extID => {
+ Object.getOwnPropertyNames(switches[extID]).forEach(block => {
+ blockSwitches["".concat(extID, "_").concat(block)] = switches[extID][block];
+ });
+ });
+ }
+ vm.runtime.on("EXTENSION_ADDED", () => {
+ const switches = vm.extensionManager.getAddonBlockSwitches();
+ Object.getOwnPropertyNames(switches).forEach(extID => {
+ Object.getOwnPropertyNames(switches[extID]).forEach(block => {
+ blockSwitches["".concat(extID, "_").concat(block)] = switches[extID][block];
+ });
+ });
+ });
+ }
+ if (addon.settings.get("sa")) {
+ const logProc = "\u200B\u200Blog\u200B\u200B %s";
+ const warnProc = "\u200B\u200Bwarn\u200B\u200B %s";
+ const errorProc = "\u200B\u200Berror\u200B\u200B %s";
+ const logMessage = msg("debugger_log");
+ const warnMessage = msg("debugger_warn");
+ const errorMessage = msg("debugger_error");
+ const logSwitch = {
+ mutate: {
+ proccode: logProc
+ },
+ msg: logMessage
+ };
+ const warnSwitch = {
+ mutate: {
+ proccode: warnProc
+ },
+ msg: warnMessage
+ };
+ const errorSwitch = {
+ mutate: {
+ proccode: errorProc
+ },
+ msg: errorMessage
+ };
+ procedureSwitches[logProc] = [{
+ msg: logMessage,
+ isNoop: true
+ }, warnSwitch, errorSwitch];
+ procedureSwitches[warnProc] = [logSwitch, {
+ msg: warnMessage,
+ isNoop: true
+ }, errorSwitch];
+ procedureSwitches[errorProc] = [logSwitch, warnSwitch, {
+ msg: errorMessage,
+ isNoop: true
+ }];
+ }
+
+ // Switching for these is implemented by Scratch. We only define them here to optionally add a border.
+ // Because we don't implement the switching ourselves, this is not controlled by the data category option.
+ blockSwitches["data_variable"] = [];
+ blockSwitches["data_listcontents"] = [];
+ };
+ buildSwitches();
+ addon.settings.addEventListener("change", buildSwitches);
+
+ /**
+ * @param {*} workspace
+ * @param {Element} xmlBlock
+ */
+ const pasteBlockXML = (workspace, xmlBlock) => {
+ // Similar to https://github.com/LLK/scratch-blocks/blob/7575c9a0f2c267676569c4b102b76d77f35d9fd6/core/workspace_svg.js#L1020
+ // but without the collision checking.
+ const block = ScratchBlocks.Xml.domToBlock(xmlBlock, workspace);
+ const x = +xmlBlock.getAttribute("x");
+ const y = +xmlBlock.getAttribute("y");
+ // Don't need to handle RTL here
+ block.moveBy(x, y);
+ return block;
+ };
+
+ /**
+ * @param {string} shadowType The type of shadow eg. "math_number"
+ * @returns {string} The name of the shadow's inner field that contains the user-visible value
+ */
+ const getShadowFieldName = shadowType => {
+ // This is non-comprehensive.
+ if (shadowType === "text") {
+ return "TEXT";
+ }
+ if (shadowType === "colour_picker") {
+ return "COLOUR";
+ }
+ return "NUM";
+ };
+
+ /**
+ * @template T
+ * @param {T|()=>T} value
+ * @returns {T}
+ */
+ const callIfFunction = value => {
+ if (typeof value === "function") {
+ return value();
+ }
+ return value;
+ };
+ const menuCallbackFactory = (block, opcodeData) => () => {
+ if (opcodeData.isNoop) {
+ return;
+ }
+ if (opcodeData.fieldValue) {
+ block.setFieldValue(opcodeData.fieldValue, "VALUE");
+ return;
+ }
+ try {
+ ScratchBlocks.Events.setGroup(true);
+ const workspace = block.workspace;
+ const blocksToBringToForeground = [];
+ // Split inputs before we clone the block.
+ if (opcodeData.splitInputs) {
+ for (const inputName of opcodeData.splitInputs) {
+ const input = block.getInput(inputName);
+ if (!input) {
+ continue;
+ }
+ const connection = input.connection;
+ if (!connection) {
+ continue;
+ }
+ if (connection.isConnected()) {
+ const targetBlock = connection.targetBlock();
+ if (targetBlock.isShadow()) {
+ // Deleting shadows is handled later.
+ } else {
+ connection.disconnect();
+ blocksToBringToForeground.push(targetBlock);
+ }
+ }
+ }
+ }
+
+ // Make a copy of the block with the proper type set.
+ // It doesn't seem to be possible to change a Block's type after it's created, so we'll just make a new block instead.
+ const xml = ScratchBlocks.Xml.blockToDom(block);
+ // blockToDomWithXY's handling of RTL is strange, so we encode the position ourselves.
+ const position = block.getRelativeToSurfaceXY();
+ xml.setAttribute("x", position.x);
+ xml.setAttribute("y", position.y);
+ if (opcodeData.opcode) {
+ xml.setAttribute("type", opcodeData.opcode);
+ }
+ const parentBlock = block.getParent();
+ let parentConnection;
+ let blockConnectionType;
+ if (parentBlock) {
+ // If the block has a parent, find the parent -> child connection that will be reattached later.
+ const parentConnections = parentBlock.getConnections_();
+ parentConnection = parentConnections.find(c => c.targetConnection && c.targetConnection.sourceBlock_ === block);
+ // There's two types of connections from child -> parent. We need to figure out which one is used.
+ const blockConnections = block.getConnections_();
+ const blockToParentConnection = blockConnections.find(c => c.targetConnection && c.targetConnection.sourceBlock_ === parentBlock);
+ blockConnectionType = blockToParentConnection.type;
+ }
+
+ // Array.from creates a clone of the children list. This is important as we may remove
+ // children as we iterate.
+ for (const child of Array.from(xml.children)) {
+ const oldName = child.getAttribute("name");
+
+ // Any inputs that were supposed to be split that were not should be removed.
+ // (eg. shadow inputs)
+ if (opcodeData.splitInputs && opcodeData.splitInputs.includes(oldName)) {
+ xml.removeChild(child);
+ continue;
+ }
+ const newName = opcodeData.remapInputName && opcodeData.remapInputName[oldName];
+ if (newName) {
+ child.setAttribute("name", newName);
+ }
+ const newShadowType = opcodeData.remapShadowType && opcodeData.remapShadowType[oldName];
+ if (newShadowType) {
+ const valueNode = child.firstChild;
+ const fieldNode = valueNode.firstChild;
+ valueNode.setAttribute("type", newShadowType);
+ fieldNode.setAttribute("name", getShadowFieldName(newShadowType));
+ }
+ const fieldValueMap = opcodeData.mapFieldValues && opcodeData.mapFieldValues[oldName];
+ if (fieldValueMap && child.tagName === "FIELD") {
+ const oldValue = child.innerText;
+ const newValue = fieldValueMap[oldValue];
+ if (typeof newValue === "string") {
+ child.innerText = newValue;
+ }
+ }
+ }
+ if (opcodeData.mutate) {
+ const mutation = xml.querySelector("mutation");
+ for (const [key, value] of Object.entries(opcodeData.mutate)) {
+ mutation.setAttribute(key, value);
+ }
+ }
+ if (opcodeData.createInputs) {
+ for (const [inputName, inputData] of Object.entries(opcodeData.createInputs)) {
+ const valueElement = document.createElement("value");
+ valueElement.setAttribute("name", inputName);
+ const shadowElement = document.createElement("shadow");
+ shadowElement.setAttribute("type", inputData.shadowType);
+ const shadowFieldElement = document.createElement("field");
+ shadowFieldElement.setAttribute("name", getShadowFieldName(inputData.shadowType));
+ shadowFieldElement.innerText = callIfFunction(inputData.value);
+ shadowElement.appendChild(shadowFieldElement);
+ valueElement.appendChild(shadowElement);
+ xml.appendChild(valueElement);
+ }
+ }
+
+ // Remove the old block and insert the new one.
+ block.dispose();
+ const newBlock = pasteBlockXML(workspace, xml);
+ if (parentConnection) {
+ // Search for the same type of connection on the new block as on the old block.
+ const newBlockConnections = newBlock.getConnections_();
+ const newBlockConnection = newBlockConnections.find(c => c.type === blockConnectionType);
+ newBlockConnection.connect(parentConnection);
+ }
+ for (const otherBlock of blocksToBringToForeground) {
+ // By re-appending the element, we move it to the end, which will make it display
+ // on top.
+ const svgRoot = otherBlock.getSvgRoot();
+ svgRoot.parentNode.appendChild(svgRoot);
+ }
+ } finally {
+ ScratchBlocks.Events.setGroup(false);
+ }
+ };
+ const uniques = array => [...new Set(array)];
+ addon.tab.createBlockContextMenu((items, block) => {
+ if (!addon.self.disabled) {
+ const type = block.type;
+ let switches = blockSwitches[block.type] || [];
+ const customArgsMode = addon.settings.get("customargs") ? addon.settings.get("customargsmode") : "off";
+ if (customArgsMode !== "off" && ["argument_reporter_boolean", "argument_reporter_string_number"].includes(type) &&
+ // if the arg is a shadow, it's in a procedures_prototype so we don't want it to be switchable
+ !block.isShadow()) {
+ const customBlocks = getCustomBlocks();
+ if (customArgsMode === "all") {
+ switch (type) {
+ case "argument_reporter_string_number":
+ switches = Object.values(customBlocks).map(cb => cb.stringArgs).flat(1);
+ break;
+ case "argument_reporter_boolean":
+ switches = Object.values(customBlocks).map(cb => cb.boolArgs).flat(1);
+ break;
+ }
+ } else if (customArgsMode === "defOnly") {
+ const root = block.getRootBlock();
+ if (root.type !== "procedures_definition" || root.type !== "procedures_definition_return") return items;
+ const customBlockObj = customBlocks[root.getChildren(true)[0].getProcCode()];
+ switch (type) {
+ case "argument_reporter_string_number":
+ switches = customBlockObj.stringArgs;
+ break;
+ case "argument_reporter_boolean":
+ switches = customBlockObj.boolArgs;
+ break;
+ }
+ }
+ const currentValue = block.getFieldValue("VALUE");
+ switches = uniques(switches).map(i => ({
+ isNoop: i === currentValue,
+ fieldValue: i,
+ msg: i
+ }));
+ }
+ if (block.type === "procedures_call") {
+ const proccode = block.getProcCode();
+ if (procedureSwitches[proccode]) {
+ switches = procedureSwitches[proccode];
+ }
+ }
+ if (!addon.settings.get("noop")) {
+ switches = switches.filter(i => !i.isNoop);
+ }
+ switches.forEach((opcodeData, i) => {
+ const makeSpaceItemIndex = items.findIndex(obj => obj._isDevtoolsFirstItem);
+ const insertBeforeIndex = makeSpaceItemIndex !== -1 ?
+ // If "make space" button exists, add own items before it
+ makeSpaceItemIndex :
+ // If there's no such button, insert at end
+ items.length;
+ const text = opcodeData.msg ? opcodeData.msg : opcodeData.opcode ? msg(opcodeData.opcode) : msg(block.type);
+ items.splice(insertBeforeIndex, 0, {
+ enabled: true,
+ text,
+ callback: menuCallbackFactory(block, opcodeData),
+ separator: i === 0
+ });
+ });
+ if (block.type === "data_variable" || block.type === "data_listcontents") {
+ // Add top border to first variable (if it exists)
+ const delBlockIndex = items.findIndex(item => item.text === ScratchBlocks.Msg.DELETE_BLOCK);
+ // firstVariableItem might be undefined, a variable to switch to,
+ // or an item added by editor-devtools (or any addon before this one)
+ const firstVariableItem = items[delBlockIndex + 1];
+ if (firstVariableItem) firstVariableItem.separator = true;
+ }
+ }
+ return items;
+ }, {
+ blocks: true
+ });
+
+ // https://github.com/LLK/scratch-blocks/blob/abbfe93136fef57fdfb9a077198b0bc64726f012/blocks_vertical/procedures.js#L207-L215
+ // Returns a list like ["%s", "%d"]
+ const parseArguments = code => code.split(/(?=[^\\]%[nbs])/g).map(i => i.trim()).filter(i => i.charAt(0) === "%").map(i => i.substring(0, 2));
+ const getCustomBlocks = () => {
+ const customBlocks = {};
+ const target = vm.editingTarget;
+ Object.values(target.blocks._blocks).filter(block => block.opcode === "procedures_prototype").forEach(block => {
+ const procCode = block.mutation.proccode;
+ const argumentNames = JSON.parse(block.mutation.argumentnames);
+ // argumentdefaults is unreliable, so we have to parse the procedure code to determine argument types
+ const parsedArguments = parseArguments(procCode);
+ const stringArgs = [];
+ const boolArgs = [];
+ for (let i = 0; i < argumentNames.length; i++) {
+ if (parsedArguments[i] === "%b") {
+ boolArgs.push(argumentNames[i]);
+ } else {
+ stringArgs.push(argumentNames[i]);
+ }
+ }
+ customBlocks[procCode] = {
+ stringArgs,
+ boolArgs
+ };
+ });
+ return customBlocks;
+ };
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/color-picker/_runtime_entry.js":
+/*!**********************************************************!*\
+ !*** ./src/addons/addons/color-picker/_runtime_entry.js ***!
+ \**********************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/color-picker/userscript.js");
+/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./style.css */ "./node_modules/css-loader/index.js!./src/addons/addons/color-picker/style.css");
+/* harmony import */ var _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_style_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "style.css": _css_loader_style_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/color-picker/code-editor.js":
+/*!*******************************************************!*\
+ !*** ./src/addons/addons/color-picker/code-editor.js ***!
+ \*******************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _libraries_common_cs_normalize_color_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../libraries/common/cs/normalize-color.js */ "./src/addons/libraries/common/cs/normalize-color.js");
+/* harmony import */ var _libraries_common_cs_rate_limiter_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../libraries/common/cs/rate-limiter.js */ "./src/addons/libraries/common/cs/rate-limiter.js");
+/* harmony import */ var _libraries_thirdparty_cs_tinycolor_min_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../libraries/thirdparty/cs/tinycolor-min.js */ "./src/addons/libraries/thirdparty/cs/tinycolor-min.js");
+
+
+
+/* harmony default export */ __webpack_exports__["default"] = (async _ref => {
+ let {
+ addon,
+ console,
+ msg
+ } = _ref;
+ // 250-ms rate limit
+ const rateLimiter = new _libraries_common_cs_rate_limiter_js__WEBPACK_IMPORTED_MODULE_1__["default"](250);
+ const getColor = element => {
+ const {
+ children
+ } = element.parentElement;
+ // h: 0 - 360
+ const h = children[1].getAttribute("aria-valuenow");
+ // s: 0 - 1
+ const s = children[3].getAttribute("aria-valuenow");
+ // v: 0 - 255, divide by 255
+ const vMultipliedBy255 = children[5].getAttribute("aria-valuenow");
+ const v = Number(vMultipliedBy255) / 255;
+ return Object(_libraries_thirdparty_cs_tinycolor_min_js__WEBPACK_IMPORTED_MODULE_2__["default"])("hsv(".concat(h, ", ").concat(s, ", ").concat(v || 0, ")")).toHexString();
+ };
+ const setColor = (hex, element) => {
+ hex = Object(_libraries_common_cs_normalize_color_js__WEBPACK_IMPORTED_MODULE_0__["normalizeHex"])(hex);
+ if (!addon.tab.redux.state || !addon.tab.redux.state.scratchGui) return;
+ // The only way to reliably set color is to invoke eye dropper via click()
+ // then faking that the eye dropper reported the value.
+ const onEyeDropperClosed = _ref2 => {
+ let {
+ detail
+ } = _ref2;
+ if (detail.action.type !== "scratch-gui/color-picker/DEACTIVATE_COLOR_PICKER") return;
+ addon.tab.redux.removeEventListener("statechanged", onEyeDropperClosed);
+ setTimeout(() => {
+ document.body.classList.remove("sa-hide-eye-dropper-background");
+ }, 50);
+ };
+ const onEyeDropperOpened = _ref3 => {
+ let {
+ detail
+ } = _ref3;
+ if (detail.action.type !== "scratch-gui/color-picker/ACTIVATE_COLOR_PICKER") return;
+ addon.tab.redux.removeEventListener("statechanged", onEyeDropperOpened);
+ addon.tab.redux.addEventListener("statechanged", onEyeDropperClosed);
+ setTimeout(() => {
+ addon.tab.redux.dispatch({
+ type: "scratch-gui/color-picker/DEACTIVATE_COLOR_PICKER",
+ color: hex
+ });
+ }, 50);
+ };
+ addon.tab.redux.addEventListener("statechanged", onEyeDropperOpened);
+ document.body.classList.add("sa-hide-eye-dropper-background");
+ element.click();
+ };
+ const addColorPicker = () => {
+ const element = document.querySelector("button.scratchEyedropper");
+ rateLimiter.abort(false);
+ addon.tab.redux.initialize();
+ const defaultColor = getColor(element);
+ const saColorPicker = Object.assign(document.createElement("div"), {
+ className: "sa-color-picker sa-color-picker-code"
+ });
+ addon.tab.displayNoneWhileDisabled(saColorPicker, {
+ display: "flex"
+ });
+ const saColorPickerColor = Object.assign(document.createElement("input"), {
+ className: "sa-color-picker-color sa-color-picker-code-color",
+ type: "color",
+ value: defaultColor || "#000000"
+ });
+ const saColorPickerText = Object.assign(document.createElement("input"), {
+ className: addon.tab.scratchClass("input_input-form", {
+ others: "sa-color-picker-text sa-color-picker-code-text"
+ }),
+ type: "text",
+ pattern: "^#?([0-9a-fA-F]{3}){1,2}$",
+ placeholder: msg("hex"),
+ value: defaultColor || ""
+ });
+ saColorPickerColor.addEventListener("input", () => rateLimiter.limit(() => setColor(saColorPickerText.value = saColorPickerColor.value, element)));
+ saColorPickerText.addEventListener("change", () => {
+ const {
+ value
+ } = saColorPickerText;
+ if (!Object(_libraries_common_cs_normalize_color_js__WEBPACK_IMPORTED_MODULE_0__["getHexRegex"])().test(value)) return;
+ setColor(saColorPickerColor.value = Object(_libraries_common_cs_normalize_color_js__WEBPACK_IMPORTED_MODULE_0__["normalizeHex"])(value), element);
+ });
+ saColorPicker.appendChild(saColorPickerColor);
+ saColorPicker.appendChild(saColorPickerText);
+ element.parentElement.insertBefore(saColorPicker, element);
+ };
+ const ScratchBlocks = await addon.tab.traps.getBlockly();
+ const originalShowEditor = ScratchBlocks.FieldColourSlider.prototype.showEditor_;
+ ScratchBlocks.FieldColourSlider.prototype.showEditor_ = function () {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ const r = originalShowEditor.call(this, ...args);
+ addColorPicker();
+ return r;
+ };
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/color-picker/userscript.js":
+/*!******************************************************!*\
+ !*** ./src/addons/addons/color-picker/userscript.js ***!
+ \******************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _code_editor_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./code-editor.js */ "./src/addons/addons/color-picker/code-editor.js");
+
+/* harmony default export */ __webpack_exports__["default"] = (async api => {
+ Object(_code_editor_js__WEBPACK_IMPORTED_MODULE_0__["default"])(api);
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-animations/_runtime_entry.js":
+/*!***************************************************************!*\
+ !*** ./src/addons/addons/editor-animations/_runtime_entry.js ***!
+ \***************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/editor-animations/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-animations/userscript.js":
+/*!***********************************************************!*\
+ !*** ./src/addons/addons/editor-animations/userscript.js ***!
+ \***********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+// Editor Animations (remake of Reactive Animation by )
+// By: SharkPool
+// By: reflow
+
+/* TODO
+- patch custom modal api when added
+- patch adding modals from addons (they dont use react)
+*/
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon
+ } = _ref;
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const addonKey = "addonAnimations-";
+ const animationTypes = {
+ "default": "cubic-bezier(0.63, 0.32, 0.08, 0.95)",
+ "easeIn": "cubic-bezier(0.42, 0, 1.0, 1.0)",
+ "easeOut": "cubic-bezier(0, 0, 0.58, 1.0)",
+ "easeInOut": "cubic-bezier(0.42, 0, 0.58, 1.0)",
+ "smoothStep": "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
+ "fastInSlowOut": "cubic-bezier(0.4, 0.0, 0.2, 1.0)",
+ "sineIn": "cubic-bezier(0.47, 0, 0.745, 0.715)",
+ "sineOut": "cubic-bezier(0.39, 0.575, 0.565, 1)",
+ "sineInOut": "cubic-bezier(0.445, 0.05, 0.55, 0.95)",
+ "quadIn": "cubic-bezier(0.55, 0.085, 0.68, 0.53)",
+ "quadOut": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
+ "quadInOut": "cubic-bezier(0.455, 0.03, 0.515, 0.955)",
+ "cubicIn": "cubic-bezier(0.55, 0.055, 0.675, 0.19)",
+ "cubicOut": "cubic-bezier(0.215, 0.61, 0.355, 1)",
+ "cubicInOut": "cubic-bezier(0.645, 0.045, 0.355, 1)",
+ "quartIn": "cubic-bezier(0.895, 0.03, 0.685, 0.22)",
+ "quartOut": "cubic-bezier(0.165, 0.84, 0.44, 1)",
+ "quartInOut": "cubic-bezier(0.77, 0, 0.175, 1)",
+ "quintIn": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
+ "quintOut": "cubic-bezier(0.23, 1, 0.32, 1)",
+ "quintInOut": "cubic-bezier(0.86, 0, 0.07, 1)",
+ "backIn": "cubic-bezier(0.6, -0.28, 0.74, 0.05)",
+ "backOut": "cubic-bezier(0.18, 0.89, 0.32, 1.28)",
+ "backInOut": "cubic-bezier(0.68, -0.55, 0.27, 1.55)",
+ "elastic": "linear(0 0%, 0.22 2.1%, 0.86 6.5%, 1.11 8.6%, 1.3 10.7%, 1.35 11.8%, 1.37 12.9%, 1.37 13.7%, 1.36 14.5%, 1.32 16.2%, 1.03 21.8%, 0.94 24%, 0.89 25.9%, 0.88 26.85%, 0.87 27.8%, 0.87 29.25%, 0.88 30.7%, 0.91 32.4%, 0.98 36.4%, 1.01 38.3%, 1.04 40.5%, 1.05 42.7%, 1.05 44.1%, 1.04 45.7%, 1 53.3%, 0.99 55.4%, 0.98 57.5%, 0.99 60.7%, 1 68.1%, 1.01 72.2%, 1 86.7%, 1 100%)",
+ "bounce": "linear(0 0%, 0 2.27%, 0.02 4.53%, 0.04 6.8%, 0.06 9.07%, 0.1 11.33%, 0.14 13.6%, 0.25 18.15%, 0.39 22.7%, 0.56 27.25%, 0.77 31.8%, 1 36.35%, 0.89 40.9%, 0.85 43.18%, 0.81 45.45%, 0.79 47.72%, 0.77 50%, 0.75 52.27%, 0.75 54.55%, 0.75 56.82%, 0.77 59.1%, 0.79 61.38%, 0.81 63.65%, 0.85 65.93%, 0.89 68.2%, 1 72.7%, 0.97 74.98%, 0.95 77.25%, 0.94 79.53%, 0.94 81.8%, 0.94 84.08%, 0.95 86.35%, 0.97 88.63%, 1 90.9%, 0.99 93.18%, 0.98 95.45%, 0.99 97.73%, 1 100%)",
+ "emphasis": "linear(0 0%, 0 1.8%, 0.01 3.6%, 0.03 6.35%, 0.07 9.1%, 0.13 11.4%, 0.19 13.4%, 0.27 15%, 0.34 16.1%, 0.54 18.35%, 0.66 20.6%, 0.72 22.4%, 0.77 24.6%, 0.81 27.3%, 0.85 30.4%, 0.88 35.1%, 0.92 40.6%, 0.94 47.2%, 0.96 55%, 0.98 64%, 0.99 74.4%, 1 86.4%, 1 100%)"
+ };
+ const hasNoVariation = ["default", "fastInSlowOut", "smoothStep", "elastic", "bounce", "emphasis"];
+ let needsInit = true,
+ animateModals = true,
+ animateLibraries = true,
+ animateButtons = true,
+ animationSpeed = 1,
+ animationType = "default",
+ animationDir = "InOut";
+ let patchedBody = false,
+ sbPatched = false,
+ sbEverPatched = false,
+ listenerAttached = false;
+ const genStyles = () => "\n/* Top Bar Items */\n.".concat(addonKey, "top-bar-scaler {\n transition: transform ").concat(getAnim(.1), ";\n transform-origin: center center;\n transform-box: fill-box;\n}\n.").concat(addonKey, "top-bar-scaler:hover:not([addon-scale-stop=\"true\"]) {\n transform: scale(1.05);\n}\n.").concat(addonKey, "top-bar-scaler:active:not([addon-scale-stop=\"true\"]) {\n transform: scale(.95);\n}\n\n/*\n Assets, Blockly Button Texts\n Costume/Extension/Sprite/Sound Library UI\n*/\n.").concat(addonKey, "static-scaler {\n transition: transform ").concat(getAnim(.1), ";\n transform-origin: center center;\n transform-box: fill-box;\n}\n.").concat(addonKey, "static-scaler:hover:not([addon-scale-stop=\"true\"]) {\n transform: scale(1.05);\n}\n.").concat(addonKey, "static-scaler:active:not([addon-scale-stop=\"true\"]) {\n transform: scale(.95);\n}\n\n/*\n Sound & Costume Editor Buttons,\n Project Controls, Blockly Zoom\n*/\n.").concat(addonKey, "static-scaler-big {\n transition: transform ").concat(getAnim(.2), " !important;\n transform-origin: center center;\n transform-box: fill-box;\n}\n.").concat(addonKey, "static-scaler-big:hover {\n transform: scale(1.1);\n}\n.").concat(addonKey, "static-scaler-big:active {\n transform: scale(.9);\n}\n\n/* Custom Extension Button (Library) */\n.").concat(addonKey, "custom-ext-hover {\n transition: transform ").concat(getAnim(.2), ", border ").concat(getAnim(.5), ";\n justify-content: center;\n border: none;\n}\n.").concat(addonKey, "custom-ext-hover:hover {\n border: solid 3px #00000050;\n}\n.").concat(addonKey, "custom-ext-hover:active {\n transform: scale(.95);\n border: none;\n}\n\n/* Library Items */\n.").concat(addonKey, "library-item-scaler div[class^=\"library-item_library-item\"] {\n transition: transform ").concat(getAnim(.1), ";\n transform-origin: center center;\n transform-box: fill-box;\n}\n.").concat(addonKey, "library-item-scaler div[class^=\"library-item_library-item\"]:hover {\n transform: scale(1.05);\n}\n.").concat(addonKey, "library-item-scaler div[class^=\"library-item_library-item\"]:active {\n transform: scale(.95);\n}\n\n/* Categories */\n.").concat(addonKey, "category-scaler div div[class=\"scratchCategoryMenuRow\"] {\n transition: transform ").concat(getAnim(.1), ";\n transform-origin: center center;\n transform-box: fill-box;\n}\n.").concat(addonKey, "category-scaler div div[class=\"scratchCategoryMenuRow\"]:hover {\n transform: scale(1.05);\n}\n.").concat(addonKey, "category-scaler div div[class=\"scratchCategoryMenuRow\"]:active {\n transform: scale(.95);\n}\n");
+ const styleElement = document.createElement("style");
+ styleElement.classList.add("addon-editorAnimations");
+ styleElement.textContent = genStyles();
+ document.head.appendChild(styleElement);
+ let animationEnabled = !mediaQuery.matches;
+ mediaQuery.addEventListener("change", e => {
+ animationEnabled = !e.matches;
+ });
+ function requestAddonState() {
+ animateModals = addon.settings.get("animateModals");
+ animateLibraries = addon.settings.get("animateLibraries");
+ animateButtons = addon.settings.get("animateButtons");
+ animationType = addon.settings.get("animationType");
+ animationDir = addon.settings.get("animationDir");
+ const oldSpeed = animationSpeed;
+ animationSpeed = 1 / (Number(addon.settings.get("animateSpeed")) / 100);
+ if (oldSpeed !== animationSpeed) styleElement.textContent = genStyles();
+ }
+ function getEasing() {
+ if (hasNoVariation.includes(animationType)) {
+ return animationTypes[animationType];
+ } else {
+ return animationTypes[animationType + animationDir];
+ }
+ }
+ function getAnim(time) {
+ time *= animationSpeed;
+ return "".concat(time, "s ").concat(getEasing());
+ }
+ ;
+ function observeMenuScalers(element, observerSub, observerAtt) {
+ if (!animateModals) return;
+ if (element.hasAttribute("addon-scale-listeners-bound")) return;
+ const onMouseOver = () => element.setAttribute("addon-scale-stop", true);
+ const onMouseOut = () => element.setAttribute("addon-scale-stop", false);
+ element.addEventListener("mouseover", onMouseOver);
+ element.addEventListener("mouseout", onMouseOut);
+ element.setAttribute("addon-scale-listeners-bound", "true");
+ element.setAttribute("addon-scale-stop", true);
+ const observer = new MutationObserver(() => {
+ var _element$querySelecto, _element$querySelecto2;
+ if (!element.classList.contains("menu-bar_active") && ((_element$querySelecto = element.querySelector("nav")) === null || _element$querySelecto === void 0 ? void 0 : (_element$querySelecto2 = _element$querySelecto.style) === null || _element$querySelecto2 === void 0 ? void 0 : _element$querySelecto2.opacity) !== "1") {
+ element.removeEventListener("mouseover", onMouseOver);
+ element.removeEventListener("mouseout", onMouseOut);
+ element.removeAttribute("addon-scale-listeners-bound");
+ element.setAttribute("addon-scale-stop", false);
+ observer.disconnect();
+ }
+ });
+ observer.observe(element, {
+ subtree: observerSub,
+ attributes: true,
+ attributeFilter: observerAtt
+ });
+ }
+ function handleOpenAnimation(elementName) {
+ var _document$querySelect;
+ const type = elementName.endsWith("Library") ? "library" : elementName.endsWith("Menu") ? "menu" : "modal";
+ if (!animateLibraries && type === "library") return;
+ if (!animateModals && type !== "library") return;
+ let element;
+ let animTime = 200;
+ if (type === "menu") {
+ if (elementName === "ctxMenu") element = document.querySelector("div[class*=\"blocklyContextMenu\"]");else if (elementName === "guiCtxMenu") element = document.querySelector("nav[class*=\"context-menu_context-menu\"][class*=\"react-contextmenu--visible\"]");else {
+ element = Array.from(document.querySelectorAll("div[class*=\"menu-bar_menu-bar-menu_\"] ul[class*=\"menu_menu_\"]"));
+ if (!element.length) return;
+ element = element.find(e => !e.hasAttribute("style"));
+ }
+ if (!element) return;
+ if (type === "menu") {
+ const menuItem = element.parentNode.parentNode;
+ setTimeout(() => observeMenuScalers(menuItem, false, ["class"]), 10);
+ }
+ const ogHeight = element.getBoundingClientRect().height;
+ element.style.overflow = "hidden";
+ element.style.transition = "transform ".concat(getAnim(.2));
+ element.style.transformOrigin = "left top";
+ if (elementName === "guiCtxMenu") animTime = 500;else element.style.transform = "translateY(-2px) scale(.999)";
+ const animation = element.animate([{
+ height: "0px",
+ opacity: 0
+ }, {
+ height: "".concat(ogHeight, "px"),
+ opacity: 1
+ }], {
+ duration: animTime * animationSpeed,
+ easing: getEasing()
+ });
+ animation.onfinish = () => {
+ element.style.overflow = "hidden";
+ };
+ return;
+ }
+ element = (_document$querySelect = document.querySelector("div[class=\"ReactModalPortal\"] div[class*=\"ReactModal__Overlay\"]")) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.firstChild;
+ if (!element) return;
+ if (type === "library") {
+ animTime = 500;
+ if (elementName === "extensionLibrary" || elementName === "costumeLibrary") element.style.transformOrigin = "left bottom";else element.style.transformOrigin = "center bottom";
+ }
+ element.animate([{
+ transform: "scale(0)",
+ opacity: 0
+ }, {
+ transform: "scale(1)",
+ opacity: 1
+ }], {
+ duration: animTime * animationSpeed,
+ easing: getEasing()
+ });
+ }
+ function attachCloseHijack(elementName) {
+ const type = elementName.endsWith("Library") ? "library" : elementName.endsWith("Menu") ? "menu" : "modal";
+ if (type === "menu" || patchedBody) return;
+ if (!animateLibraries && type === "library") return;
+ if (!animateModals && type !== "library") return;
+
+ // Monkey Patch
+ const ogRemoveChild = document.body.constructor.prototype.removeChild;
+ document.body.constructor.prototype.removeChild = function (child) {
+ const element = document.querySelector("div[class=\"ReactModalPortal\"]");
+ if (!element) return ogRemoveChild.call(this, child);
+ let animTime = 200;
+ patchedBody = true;
+ if (child === element) {
+ const child = element.firstChild;
+ if (child) {
+ const animClone = child.cloneNode(true);
+ animClone.style.position = "fixed";
+ animClone.style.top = child.getBoundingClientRect().top + "px";
+ animClone.style.left = child.getBoundingClientRect().left + "px";
+ animClone.style.zIndex = "99999";
+ animClone.style.pointerEvents = "none";
+ if (type === "library") {
+ animTime = 500;
+ if (elementName === "extensionLibrary" || elementName === "costumeLibrary") animClone.style.transformOrigin = "left bottom";else animClone.style.transformOrigin = "center bottom";
+ }
+ document.body.appendChild(animClone);
+ animClone.animate([{
+ opacity: 1
+ }, {
+ opacity: 0
+ }], {
+ duration: animTime * animationSpeed,
+ easing: getEasing()
+ });
+ const animation = animClone.firstChild.animate([{
+ transform: "scale(1)",
+ opacity: 1
+ }, {
+ transform: "scale(0)",
+ opacity: 0
+ }], {
+ duration: animTime * animationSpeed,
+ easing: getEasing()
+ });
+ animation.onfinish = () => {
+ animClone.remove();
+ ogRemoveChild.call(element.parentNode, element);
+ };
+ document.body.constructor.prototype.removeChild = ogRemoveChild;
+ patchedBody = false;
+ return child;
+ }
+ }
+ return ogRemoveChild.call(this, child);
+ };
+ }
+ function compileClasses(optLibrary) {
+ if (!animateButtons) return;
+ const classMapper = new Map();
+ if (optLibrary) {
+ const collapser = document.querySelector("button[class^=\"library_library-filter-collapse\"]");
+ const filterDiv = document.querySelector("div[class^=\"library_library-filter-bar\"]");
+ collapser.style.transform = "rotateY(180deg)";
+ collapser.addEventListener("click", e => {
+ e.preventDefault();
+ const isClosed = collapser.hasAttribute("closed");
+ if (isClosed) {
+ collapser.style.transform = "rotateY(180deg)";
+ collapser.removeAttribute("closed");
+ filterDiv.style.display = "";
+ filterDiv.animate([{
+ width: "0px",
+ opacity: 0
+ }, {
+ width: "342px",
+ opacity: 1
+ }], {
+ duration: 300,
+ easing: getEasing()
+ });
+ } else {
+ collapser.style.transform = "rotateY(0deg)";
+ const animation = filterDiv.animate([{
+ width: "342px",
+ opacity: 1
+ }, {
+ width: "0px",
+ opacity: 0
+ }], {
+ duration: 300,
+ easing: getEasing()
+ });
+ animation.onfinish = () => {
+ collapser.setAttribute("closed", "true");
+ filterDiv.style.display = "none";
+ };
+ }
+ e.stopPropagation();
+ });
+ if (optLibrary === "extensionLibrary") {
+ classMapper.set("custom-ext-hover", [document.querySelector("span[class*=\"button_outlined-button\"][class*=\"tag-button_tag-button\"]")]);
+ }
+ classMapper.set("library-item-scaler", [document.querySelector("div[class*=\"library_library-scroll-grid\"]")]);
+ classMapper.set("static-scaler", [document.querySelector("span[class*=\"modal_back-button_\"]"), collapser]);
+ } else {
+ classMapper.set("top-bar-scaler", document.querySelectorAll("div[class*=\"menu-bar_main-menu\"] div[class*=\"menu-bar_menu-bar-item\"][class*=\"hoverable\"]"));
+ classMapper.set("category-scaler", [document.querySelector("div[class=\"blocklyToolboxDiv\"]")]);
+ classMapper.set("static-scaler", [/* Blockly Button Texts */
+ ...document.querySelectorAll("g[class=\"blocklyFlyoutButton\"] text[class=\"blocklyText\"]"), /* Costume & Sound Assets */
+ ...document.querySelectorAll("div[class*=\"selector_list-item\"][class*=\"sprite-selector-item\"]"), /* Sprite Selector */
+ ...document.querySelectorAll("div[class*=\"sprite-selector_sprite\"][class*=\"sprite-selector-item\"]"), /* Backpack Selector */
+ ...document.querySelectorAll("div[class*=\"backpack_backpack-item\"][class*=\"sprite-selector-item\"]")]);
+ classMapper.set("static-scaler-big", [/* Sound & Costume Editor Buttons */
+ ...document.querySelectorAll("div[class*=\"sound-editor_effect-button\"]"), ...document.querySelectorAll("div[class*=\"sound-editor_tool-button\"]"), ...document.querySelectorAll("button[class*=\"sound-editor_round-button\"]"), ...document.querySelectorAll("span[class*=\"tool-select-base_mod-tool-select\"]"), /* Project Controls */
+ ...document.querySelectorAll("div[class^=\"controls_controls-container\"] img"), /* Blockly Zoom */
+ ...document.querySelectorAll("g[class=\"blocklyZoom\"] image")]);
+ }
+ classMapper.forEach((elements, classN) => {
+ for (const element of elements) {
+ if (!element) continue;
+ element.classList.add(addonKey + classN);
+ }
+ });
+ needsInit = false;
+ }
+ function tryPatchScratchBlocks() {
+ if (typeof ScratchBlocks !== "object") return;
+ sbPatched = true;
+
+ // some modals are from ScratchBlocks, patch them!
+ queueMicrotask(() => {
+ const ogSBPrompt = ScratchBlocks.prompt;
+ ScratchBlocks.prompt = function () {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ ogSBPrompt.call(this, ...args);
+ handleOpenAnimation("modal");
+ attachCloseHijack("modal");
+ };
+ if (sbEverPatched) return;
+ sbEverPatched = true;
+ const ogSBProcCreate = ScratchBlocks.Procedures.createProcedureDefCallback_;
+ ScratchBlocks.Procedures.createProcedureDefCallback_ = function () {
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+ ogSBProcCreate.call(this, ...args);
+ handleOpenAnimation("modal");
+ attachCloseHijack("modal");
+ };
+ const ogSBProcEdit = ScratchBlocks.Procedures.editProcedureCallback_;
+ ScratchBlocks.Procedures.editProcedureCallback_ = function () {
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
+ args[_key3] = arguments[_key3];
+ }
+ ogSBProcEdit.call(this, ...args);
+ handleOpenAnimation("modal");
+ attachCloseHijack("modal");
+ };
+ const ogContextMenuShow = ScratchBlocks.ContextMenu.show;
+ ScratchBlocks.ContextMenu.show = function () {
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
+ args[_key4] = arguments[_key4];
+ }
+ ogContextMenuShow.call(this, ...args);
+ handleOpenAnimation("ctxMenu");
+ };
+
+ /* this isnt a modal, but we still want to patch it for animations */
+ const ogInitButton = ScratchBlocks.FlyoutButton.prototype.show;
+ ScratchBlocks.FlyoutButton.prototype.show = function () {
+ for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
+ args[_key5] = arguments[_key5];
+ }
+ ogInitButton.call(this, ...args);
+ queueMicrotask(() => compileClasses());
+ };
+ });
+ }
+ function attachListeners() {
+ const spriteRow = document.querySelector("div[class^=\"sprite-selector_items-wrapper\"]");
+ if (!spriteRow) return;
+ document.addEventListener("contextmenu", event => {
+ let element = event.target.closest("div[class*=\"sprite-selector_sprite-wrapper\"]");
+ if (element) element = element.firstChild;else element = event.target.closest("div[class^=\"react-contextmenu-wrapper\"][class*=\"sprite-selector-item_sprite-selector\"]");
+ if (element) {
+ setTimeout(() => {
+ element.querySelector("nav").style.opacity = 1;
+ handleOpenAnimation("guiCtxMenu");
+ observeMenuScalers(element, true, ["class", "style"]);
+ }, 10);
+ }
+ });
+ listenerAttached = true;
+ }
+ function startListenerWorker() {
+ const checkInEditor = () => !ReduxStore.getState().scratchGui.mode.isPlayerOnly;
+ window.vm.on("workspaceUpdate", () => {
+ queueMicrotask(() => compileClasses());
+ });
+ let lastModalStateID, inEditor;
+ ReduxStore.subscribe(() => {
+ const reduxState = ReduxStore.getState().scratchGui;
+ let entries = Object.entries(reduxState.modals);
+ entries.push(...Object.entries(reduxState.menus));
+ const genID = [...entries, ["tab", reduxState.editorTab.activeTabIndex]];
+ const modalStateID = genID.map(entry => entry[1]).join(".");
+ const currentlyInEditor = checkInEditor();
+ if (inEditor !== currentlyInEditor) {
+ inEditor = currentlyInEditor;
+ if (inEditor) {
+ sbPatched = false;
+ listenerAttached = false;
+ }
+ }
+ if (!sbPatched) tryPatchScratchBlocks();
+ if (!listenerAttached) attachListeners();
+ if (!needsInit && lastModalStateID === modalStateID) return;
+ lastModalStateID = modalStateID;
+ queueMicrotask(() => {
+ compileClasses();
+ for (const entry of entries) {
+ if (entry[1] === true) {
+ const name = entry[0];
+ handleOpenAnimation(name);
+ attachCloseHijack(name);
+ compileClasses(name.endsWith("Library") ? name : undefined);
+ break;
+ }
+ }
+ });
+ });
+ }
+ if (typeof scaffolding === "undefined") startListenerWorker();
+ addon.settings.addEventListener("change", requestAddonState);
+ addon.self.addEventListener("disabled", () => {
+ animateModals = false;
+ animateLibraries = false;
+ animateButtons = false;
+ });
+ addon.self.addEventListener("reenabled", () => {
+ animateModals = true;
+ animateLibraries = true;
+ animateButtons = true;
+ });
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-block-chomping/_runtime_entry.js":
+/*!*******************************************************************!*\
+ !*** ./src/addons/addons/editor-block-chomping/_runtime_entry.js ***!
+ \*******************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/editor-block-chomping/userscript.js");
+/* generated by pull.js */
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-block-chomping/userscript.js":
+/*!***************************************************************!*\
+ !*** ./src/addons/addons/editor-block-chomping/userscript.js ***!
+ \***************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon
+ } = _ref;
+ const ScratchBlocks = await addon.tab.traps.getBlockly();
+
+ // Rerender the dragged block when updating the insertion marker
+ const ogConnectMarker = ScratchBlocks.InsertionMarkerManager.prototype.connectMarker_;
+ ScratchBlocks.InsertionMarkerManager.prototype.connectMarker_ = function () {
+ ogConnectMarker.call(this);
+ if (!addon.self.disabled && this.firstMarker_) {
+ var _this$workspace_, _this$workspace_$curr, _this$workspace_$curr2;
+ const block = this === null || this === void 0 ? void 0 : (_this$workspace_ = this.workspace_) === null || _this$workspace_ === void 0 ? void 0 : (_this$workspace_$curr = _this$workspace_.currentGesture_) === null || _this$workspace_$curr === void 0 ? void 0 : (_this$workspace_$curr2 = _this$workspace_$curr.blockDragger_) === null || _this$workspace_$curr2 === void 0 ? void 0 : _this$workspace_$curr2.draggingBlock_;
+ block.noMoveConnection = true;
+ if (block) block.render(false);
+ }
+ };
+ const ogDisconnectMarker = ScratchBlocks.InsertionMarkerManager.prototype.disconnectMarker_;
+ ScratchBlocks.InsertionMarkerManager.prototype.disconnectMarker_ = function () {
+ ogDisconnectMarker.call(this);
+ if (!addon.self.disabled && this.firstMarker_) {
+ var _this$workspace_2, _this$workspace_2$cur, _this$workspace_2$cur2;
+ const block = this === null || this === void 0 ? void 0 : (_this$workspace_2 = this.workspace_) === null || _this$workspace_2 === void 0 ? void 0 : (_this$workspace_2$cur = _this$workspace_2.currentGesture_) === null || _this$workspace_2$cur === void 0 ? void 0 : (_this$workspace_2$cur2 = _this$workspace_2$cur.blockDragger_) === null || _this$workspace_2$cur2 === void 0 ? void 0 : _this$workspace_2$cur2.draggingBlock_;
+ block.noMoveConnection = true;
+ if (block) block.render(false);
+ }
+ };
+ const ogDraw = ScratchBlocks.BlockSvg.prototype.renderDraw_;
+ const ogMoveConnections = ScratchBlocks.BlockSvg.prototype.renderMoveConnections_;
+ ScratchBlocks.BlockSvg.prototype.renderDraw_ = function (iconWidth, inputRows) {
+ var _this$workspace, _this$workspace$curre, _this$workspace$curre2;
+ if (addon.self.disabled) return ogDraw.call(this, iconWidth, inputRows);
+
+ // If the block contains a statement (C) input and has an insertion marker,
+ // use that to calculate the height of the statement inputs
+ let computeBlock = this;
+ if (this !== null && this !== void 0 && (_this$workspace = this.workspace) !== null && _this$workspace !== void 0 && (_this$workspace$curre = _this$workspace.currentGesture_) !== null && _this$workspace$curre !== void 0 && (_this$workspace$curre2 = _this$workspace$curre.blockDragger_) !== null && _this$workspace$curre2 !== void 0 && _this$workspace$curre2.draggedConnectionManager_) {
+ const dragger = this.workspace.currentGesture_.blockDragger_;
+ const manager = dragger.draggedConnectionManager_;
+ if (manager.markerConnection_ && manager.firstMarker_ && dragger.draggingBlock_ == this && dragger.draggingBlock_.type == manager.firstMarker_.type) {
+ if (inputRows.some(row => row.some(input => input.type === ScratchBlocks.NEXT_STATEMENT))) {
+ computeBlock = manager.firstMarker_;
+ }
+ }
+ }
+
+ // Change the height of substacks
+ // (If we set inputRows to computeBlock.renderCompute_,
+ // the references to the inputs would be wrong
+ // so they just won't update properly)
+ if (computeBlock !== this) {
+ const _inputRows = computeBlock.renderCompute_(iconWidth);
+ for (let i = 0; i < inputRows.length; i++) {
+ const row = inputRows[i];
+ let update = false;
+ for (const input of row) {
+ if (input.type === ScratchBlocks.NEXT_STATEMENT) update = true;
+ }
+ if (update) row.height = Math.max(row.height, _inputRows[i].height);
+ }
+ }
+ ogDraw.call(this, iconWidth, inputRows);
+
+ // Moving the connections of a block while it's being dragged breaks it,
+ // so don't
+ if (computeBlock === this && !this.noMoveConnection) ogMoveConnections.call(this);
+ this.noMoveConnection = false;
+ };
+ ScratchBlocks.BlockSvg.prototype.renderMoveConnections_ = function () {
+ if (addon.self.disabled) return ogMoveConnections.call(this);
+ // Do nothing (this function is instead called by renderDraw_)
+ };
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-comment-previews/_runtime_entry.js":
+/*!*********************************************************************!*\
+ !*** ./src/addons/addons/editor-comment-previews/_runtime_entry.js ***!
+ \*********************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/editor-comment-previews/userscript.js");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./userstyle.css */ "./node_modules/css-loader/index.js!./src/addons/addons/editor-comment-previews/userstyle.css");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "userstyle.css": _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-comment-previews/userscript.js":
+/*!*****************************************************************!*\
+ !*** ./src/addons/addons/editor-comment-previews/userscript.js ***!
+ \*****************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console
+ } = _ref;
+ const vm = addon.tab.traps.vm;
+ const updateStyles = () => {
+ previewInner.classList.toggle("sa-comment-preview-delay", addon.settings.get("delay") !== "none");
+ previewInner.classList.toggle("sa-comment-preview-reduce-transparency", addon.settings.get("reduce-transparency"));
+ previewInner.classList.toggle("sa-comment-preview-fade", !addon.settings.get("reduce-animation"));
+ };
+ const afterDelay = cb => {
+ if (!previewInner.classList.contains("sa-comment-preview-hidden")) {
+ // If not hidden, updating immediately is preferred
+ cb();
+ return;
+ }
+ const delay = addon.settings.get("delay");
+ if (delay === "long") return setTimeout(cb, 500);
+ if (delay === "short") return setTimeout(cb, 300);
+ cb();
+ };
+ let hoveredElement = null;
+ let showTimeout = null;
+ let mouseX = 0;
+ let mouseY = 0;
+ let doNotShowUntilMoveMouse = false;
+ const previewOuter = document.createElement("div");
+ previewOuter.classList.add("sa-comment-preview-outer");
+ const previewInner = document.createElement("div");
+ previewInner.classList.add("sa-comment-preview-inner");
+ previewInner.classList.add("sa-comment-preview-hidden");
+ updateStyles();
+ addon.settings.addEventListener("change", updateStyles);
+ previewOuter.appendChild(previewInner);
+ document.body.appendChild(previewOuter);
+ const getBlock = id => vm.editingTarget.blocks.getBlock(id) || vm.runtime.flyoutBlocks.getBlock(id);
+ const getComment = block => block && block.comment && vm.editingTarget.comments[block.comment];
+ const getProcedureDefinitionBlock = procCode => {
+ const procedurePrototype = Object.values(vm.editingTarget.blocks._blocks).find(i => i.opcode === "procedures_prototype" && i.mutation.proccode === procCode);
+ if (procedurePrototype) {
+ // Usually `parent` will exist but sometimes it doesn't
+ if (procedurePrototype.parent) {
+ return getBlock(procedurePrototype.parent);
+ }
+ const id = procedurePrototype.id;
+ return Object.values(vm.editingTarget.blocks._blocks).find(i => (i.opcode === "procedures_definition" || i.opcode === "procedures_definition_return") && i.inputs.custom_block && i.inputs.custom_block.block === id);
+ }
+ return null;
+ };
+ const setText = text => {
+ previewInner.innerText = text;
+ previewInner.classList.remove("sa-comment-preview-hidden");
+ updateMousePosition();
+ };
+ const updateMousePosition = () => {
+ previewOuter.style.transform = "translate(".concat(mouseX + 8, "px, ").concat(mouseY + 8, "px)");
+ };
+ const hidePreview = () => {
+ if (hoveredElement) {
+ hoveredElement = null;
+ previewInner.classList.add("sa-comment-preview-hidden");
+ }
+ };
+ document.addEventListener("mouseover", e => {
+ if (addon.self.disabled) {
+ return;
+ }
+ clearTimeout(showTimeout);
+ if (doNotShowUntilMoveMouse) {
+ return;
+ }
+ const el = e.target.closest(".blocklyBubbleCanvas > g, .blocklyBlockCanvas .blocklyDraggable[data-id]");
+ if (el === hoveredElement) {
+ // Nothing to do.
+ return;
+ }
+ if (!el) {
+ hidePreview();
+ return;
+ }
+ let text = null;
+ if (addon.settings.get("hover-view") && e.target.closest(".blocklyBubbleCanvas > g") &&
+ // Hovering over the thin line that connects comments to blocks should never show a preview
+ !e.target.closest("line")) {
+ const collapsedText = el.querySelector("text.scratchCommentText");
+ if (!collapsedText) return;
+ if (collapsedText.getAttribute("display") !== "none") {
+ const textarea = el.querySelector("textarea");
+ text = textarea.value;
+ }
+ } else if (e.target.closest(".blocklyBlockCanvas .blocklyDraggable[data-id]")) {
+ const id = el.dataset.id;
+ const block = getBlock(id);
+ const comment = getComment(block);
+ if (addon.settings.get("hover-view-block") && comment) {
+ text = comment.text;
+ } else if (block && block.opcode === "procedures_call" && addon.settings.get("hover-view-procedure")) {
+ const procCode = block.mutation.proccode;
+ const procedureDefinitionBlock = getProcedureDefinitionBlock(procCode);
+ const procedureComment = getComment(procedureDefinitionBlock);
+ if (procedureComment) {
+ text = procedureComment.text;
+ }
+ }
+ }
+ if (text !== null && text.trim() !== "") {
+ showTimeout = afterDelay(() => {
+ hoveredElement = el;
+ setText(text);
+ });
+ } else {
+ hidePreview();
+ }
+ });
+ document.addEventListener("mousemove", e => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ doNotShowUntilMoveMouse = false;
+ if (addon.settings.get("follow-mouse") && !previewInner.classList.contains("sa-comment-preview-hidden")) {
+ updateMousePosition();
+ }
+ });
+ document.addEventListener("mousedown", () => {
+ hidePreview();
+ doNotShowUntilMoveMouse = true;
+ }, {
+ capture: true
+ });
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-devtools/DevTools.js":
+/*!*******************************************************!*\
+ !*** ./src/addons/addons/editor-devtools/DevTools.js ***!
+ \*******************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return DevTools; });
+/* harmony import */ var _DomHelpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DomHelpers.js */ "./src/addons/addons/editor-devtools/DomHelpers.js");
+/* harmony import */ var _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./UndoGroup.js */ "./src/addons/addons/editor-devtools/UndoGroup.js");
+// import ShowBroadcast from "./show-broadcast.js";
+
+
+class DevTools {
+ constructor(addon, msg, m) {
+ this.addon = addon;
+ this.msg = msg;
+ this.m = m;
+ /**
+ * @type {VirtualMachine}
+ */
+ this.domHelpers = new _DomHelpers_js__WEBPACK_IMPORTED_MODULE_0__["default"](addon);
+ this.codeTab = null;
+ this.costTab = null;
+ this.costTabBody = null;
+ this.selVarID = null;
+ this.canShare = false;
+ this.mouseXY = {
+ x: 0,
+ y: 0
+ };
+ }
+ async init() {
+ this.addContextMenus();
+ while (true) {
+ const root = await this.addon.tab.waitForElement("ul[class*=gui_tab-list_]", {
+ markAsSeen: true,
+ reduxEvents: ["scratch-gui/mode/SET_PLAYER", "fontsLoaded/SET_FONTS_LOADED", "scratch-gui/locales/SELECT_LOCALE"],
+ reduxCondition: state => !state.scratchGui.mode.isPlayerOnly
+ });
+ this.initInner(root);
+ }
+ }
+ async addContextMenus() {
+ const blockly = await this.addon.tab.traps.getBlockly();
+ const oldCleanUpFunc = blockly.WorkspaceSvg.prototype.cleanUp;
+ const self = this;
+ blockly.WorkspaceSvg.prototype.cleanUp = function () {
+ if (self.addon.settings.get("enableCleanUpPlus")) {
+ self.doCleanUp();
+ } else {
+ oldCleanUpFunc.call(this);
+ }
+ };
+ let originalMsg = blockly.Msg.CLEAN_UP;
+ if (this.addon.settings.get("enableCleanUpPlus")) blockly.Msg.CLEAN_UP = this.m("clean-plus");
+ this.addon.settings.addEventListener("change", () => {
+ if (this.addon.settings.get("enableCleanUpPlus")) blockly.Msg.CLEAN_UP = this.m("clean-plus");else blockly.Msg.CLEAN_UP = originalMsg;
+ });
+ this.addon.tab.createBlockContextMenu((items, block) => {
+ items.push({
+ enabled: blockly.clipboardXml_,
+ text: this.m("paste"),
+ separator: true,
+ _isDevtoolsFirstItem: true,
+ callback: () => {
+ let ids = this.getTopBlockIDs();
+ document.dispatchEvent(new KeyboardEvent("keydown", {
+ keyCode: 86,
+ ctrlKey: true,
+ griff: true
+ }));
+ setTimeout(() => {
+ this.beginDragOfNewBlocksNotInIDs(ids);
+ }, 10);
+ }
+ });
+ return items;
+ }, {
+ workspace: true
+ });
+ this.addon.tab.createBlockContextMenu((items, block) => {
+ items.push({
+ enabled: true,
+ text: this.m("make-space"),
+ _isDevtoolsFirstItem: true,
+ callback: () => {
+ this.doCleanUp(block);
+ },
+ separator: true
+ }, {
+ enabled: true,
+ text: this.m("copy-all"),
+ callback: () => {
+ this.eventCopyClick(block);
+ },
+ separator: true
+ }, {
+ enabled: true,
+ text: this.m("copy-block"),
+ callback: () => {
+ this.eventCopyClick(block, 1);
+ }
+ }, {
+ enabled: true,
+ text: this.m("cut-block"),
+ callback: () => {
+ this.eventCopyClick(block, 2);
+ }
+ });
+ // const BROADCAST_BLOCKS = ["event_whenbroadcastreceived", "event_broadcast", "event_broadcastandwait"];
+ // if (BROADCAST_BLOCKS.includes(block.type)) {
+ // // Show Broadcast
+ // const broadcastId = this.showBroadcastSingleton.getAssociatedBroadcastId(block.id);
+ // if (broadcastId) {
+ // ["Senders", "Receivers"].forEach((showKey, i) => {
+ // items.push({
+ // enabled: true,
+ // text: this.msg(`show-${showKey}`.toLowerCase()),
+ // callback: () => {
+ // this.showBroadcastSingleton[`show${showKey}`](broadcastId);
+ // },
+ // separator: i == 0,
+ // });
+ // });
+ // }
+ // }
+ return items;
+ }, {
+ blocks: true
+ });
+ this.addon.tab.createBlockContextMenu((items, block) => {
+ if (block.getCategory() === "data" || block.getCategory() === "data-lists") {
+ this.selVarID = block.getVars()[0];
+ items.push({
+ enabled: true,
+ text: this.m("swap", {
+ var: block.getCategory() === "data" ? this.m("variables") : this.m("lists")
+ }),
+ callback: async () => {
+ let wksp = this.getWorkspace();
+ let v = wksp.getVariableById(this.selVarID);
+ // prompt() returns Promise in desktop app
+ let varName = await window.prompt(this.msg("replace", {
+ name: v.name
+ }));
+ if (varName) {
+ this.doReplaceVariable(this.selVarID, varName, v.type);
+ }
+ },
+ separator: true
+ });
+ }
+ return items;
+ }, {
+ blocks: true,
+ flyout: true
+ });
+ }
+ getWorkspace() {
+ return Blockly.getMainWorkspace();
+ }
+ isCostumeEditor() {
+ return this.costTab.className.indexOf("gui_is-selected") >= 0;
+ }
+
+ /**
+ * A nicely ordered version of the top blocks
+ * @returns {[Blockly.Block]}
+ */
+ getTopBlocks() {
+ let result = this.getOrderedTopBlockColumns();
+ let columns = result.cols;
+ /**
+ * @type {[[Blockly.Block]]}
+ */
+ let topBlocks = [];
+ for (const col of columns) {
+ topBlocks = topBlocks.concat(col.blocks);
+ }
+ return topBlocks;
+ }
+
+ /**
+ * A much nicer way of laying out the blocks into columns
+ */
+ doCleanUp(block) {
+ let workspace = this.getWorkspace();
+ let makeSpaceForBlock = block && block.getRootBlock();
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].startUndoGroup(workspace);
+ let result = this.getOrderedTopBlockColumns(true);
+ let columns = result.cols;
+ let orphanCount = result.orphans.blocks.length;
+ if (orphanCount > 0 && !block) {
+ let message = this.msg("orphaned", {
+ count: orphanCount
+ });
+ if (confirm(message)) {
+ for (const block of result.orphans.blocks) {
+ block.dispose();
+ }
+ } else {
+ columns.unshift(result.orphans);
+ }
+ }
+ let cursorX = 48;
+ let maxWidths = result.maxWidths;
+ for (const column of columns) {
+ let cursorY = 64;
+ let maxWidth = 0;
+ for (const block of column.blocks) {
+ let extraWidth = block === makeSpaceForBlock ? 380 : 0;
+ let extraHeight = block === makeSpaceForBlock ? 480 : 72;
+ let xy = block.getRelativeToSurfaceXY();
+ if (cursorX - xy.x !== 0 || cursorY - xy.y !== 0) {
+ block.moveBy(cursorX - xy.x, cursorY - xy.y);
+ }
+ let heightWidth = block.getHeightWidth();
+ cursorY += heightWidth.height + extraHeight;
+ let maxWidthWithComments = maxWidths[block.id] || 0;
+ maxWidth = Math.max(maxWidth, Math.max(heightWidth.width + extraWidth, maxWidthWithComments));
+ }
+ cursorX += maxWidth + 96;
+ }
+ let topComments = workspace.getTopComments();
+ for (const comment of topComments) {
+ if (comment.setVisible) {
+ comment.setVisible(false);
+ comment.needsAutoPositioning_ = true;
+ comment.setVisible(true);
+ }
+ }
+ setTimeout(() => {
+ // Locate unused local variables...
+ let workspace = this.getWorkspace();
+ let map = workspace.getVariableMap();
+ let vars = map.getVariablesOfType("");
+ let unusedLocals = [];
+ for (const row of vars) {
+ if (row.isLocal) {
+ let usages = map.getVariableUsesById(row.getId());
+ if (!usages || usages.length === 0) {
+ unusedLocals.push(row);
+ }
+ }
+ }
+ if (unusedLocals.length > 0) {
+ const unusedCount = unusedLocals.length;
+ let message = this.msg("unused-var", {
+ count: unusedCount
+ });
+ for (let i = 0; i < unusedLocals.length; i++) {
+ let orphan = unusedLocals[i];
+ if (i > 0) {
+ message += ", ";
+ }
+ message += orphan.name;
+ }
+ if (confirm(message)) {
+ for (const orphan of unusedLocals) {
+ workspace.deleteVariableById(orphan.getId());
+ }
+ }
+ }
+
+ // Locate unused local lists...
+ let lists = map.getVariablesOfType("list");
+ let unusedLists = [];
+ for (const row of lists) {
+ if (row.isLocal) {
+ let usages = map.getVariableUsesById(row.getId());
+ if (!usages || usages.length === 0) {
+ unusedLists.push(row);
+ }
+ }
+ }
+ if (unusedLists.length > 0) {
+ const unusedCount = unusedLists.length;
+ let message = this.msg("unused-list", {
+ count: unusedCount
+ });
+ for (let i = 0; i < unusedLists.length; i++) {
+ let orphan = unusedLists[i];
+ if (i > 0) {
+ message += ", ";
+ }
+ message += orphan.name;
+ }
+ if (confirm(message)) {
+ for (const orphan of unusedLists) {
+ workspace.deleteVariableById(orphan.getId());
+ }
+ }
+ }
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].endUndoGroup(workspace);
+ }, 100);
+ }
+
+ /**
+ * Badly Orphaned - might want to delete these!
+ * @param topBlock
+ * @returns {boolean}
+ */
+ isBlockAnOrphan(topBlock) {
+ return !!topBlock.outputConnection;
+ }
+
+ /**
+ * Split the top blocks into ordered columns
+ * @param separateOrphans true to keep all orphans separate
+ * @returns {{orphans: {blocks: [Block], x: number, count: number}, cols: [Col]}}
+ */
+ getOrderedTopBlockColumns(separateOrphans) {
+ let w = this.getWorkspace();
+ let topBlocks = w.getTopBlocks();
+ let maxWidths = {};
+ if (separateOrphans) {
+ let topComments = w.getTopComments();
+
+ // todo: tie comments to blocks... find widths and width of block stack row...
+ for (const comment of topComments) {
+ // comment.autoPosition_();
+ // Hiding and showing repositions the comment right next to it's block - nice!
+ if (comment.setVisible) {
+ comment.setVisible(false);
+ comment.needsAutoPositioning_ = true;
+ comment.setVisible(true);
+
+ // let bb = comment.block_.svgPath_.getBBox();
+ let right = comment.getBoundingRectangle().bottomRight.x;
+
+ // Get top block for stack...
+ let root = comment.block_.getRootBlock();
+ let left = root.getBoundingRectangle().topLeft.x;
+ maxWidths[root.id] = Math.max(right - left, maxWidths[root.id] || 0);
+ }
+ }
+ }
+
+ // Default scratch ordering is horrid... Lets try something more clever.
+
+ /**
+ * @type {Col[]}
+ */
+ let cols = [];
+ const TOLERANCE = 256;
+ let orphans = {
+ x: -999999,
+ count: 0,
+ blocks: []
+ };
+ for (const topBlock of topBlocks) {
+ // let r = b.getBoundingRectangle();
+ let position = topBlock.getRelativeToSurfaceXY();
+ /**
+ * @type {Col}
+ */
+ let bestCol = null;
+ let bestError = TOLERANCE;
+ if (separateOrphans && this.isBlockAnOrphan(topBlock)) {
+ orphans.blocks.push(topBlock);
+ continue;
+ }
+
+ // Find best columns
+ for (const col of cols) {
+ let err = Math.abs(position.x - col.x);
+ if (err < bestError) {
+ bestError = err;
+ bestCol = col;
+ }
+ }
+ if (bestCol) {
+ // We found a column that we fitted into
+ bestCol.x = (bestCol.x * bestCol.count + position.x) / ++bestCol.count; // re-average the columns as more items get added...
+ bestCol.blocks.push(topBlock);
+ } else {
+ // Create a new column
+ cols.push(new Col(position.x, 1, [topBlock]));
+ }
+ }
+
+ // if (orphans.blocks.length > 0) {
+ // cols.push(orphans);
+ // }
+
+ // Sort columns, then blocks inside the columns
+ cols.sort((a, b) => a.x - b.x);
+ for (const col of cols) {
+ col.blocks.sort((a, b) => a.getRelativeToSurfaceXY().y - b.getRelativeToSurfaceXY().y);
+ }
+ return {
+ cols: cols,
+ orphans: orphans,
+ maxWidths: maxWidths
+ };
+ }
+
+ /**
+ * Find all the uses of a named variable.
+ * @param {string} id ID of the variable to find.
+ * @return {!Array.} Array of block usages.
+ */
+ getVariableUsesById(id) {
+ let uses = [];
+ let topBlocks = this.getTopBlocks(true); // todo: Confirm this was the right getTopBlocks?
+ for (const topBlock of topBlocks) {
+ /** @type {!Array} */
+ let kids = topBlock.getDescendants();
+ for (const block of kids) {
+ /** @type {!Array} */
+ let blockVariables = block.getVarModels();
+ if (blockVariables) {
+ for (const blockVar of blockVariables) {
+ if (blockVar.getId() === id) {
+ uses.push(block);
+ }
+ }
+ }
+ }
+ }
+ return uses;
+ }
+
+ /**
+ * Quick and dirty replace all instances of one variable / list with another variable / list
+ * @param varId original variable name
+ * @param newVarName new variable name
+ * @param type type of variable ("" = variable, anything else is a list?
+ */
+ doReplaceVariable(varId, newVarName, type) {
+ let wksp = this.getWorkspace();
+ let v = wksp.getVariable(newVarName, type);
+ if (!v) {
+ alert(this.msg("var-not-exist"));
+ return;
+ }
+ let newVId = v.getId();
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].startUndoGroup(wksp);
+ let blocks = this.getVariableUsesById(varId);
+ for (const block of blocks) {
+ try {
+ if (type === "") {
+ block.getField("VARIABLE").setValue(newVId);
+ } else {
+ block.getField("LIST").setValue(newVId);
+ }
+ } catch (e) {
+ // ignore
+ }
+ }
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].endUndoGroup(wksp);
+ }
+
+ /*
+ function doInjectScripts(codeString) {
+ let w = getWorkspace();
+ let xml = new XML(); // document.implementation.createDocument(null, "xml");
+ let x = xml.xmlDoc.firstChild;
+ let tree = math.parse(codeString);
+ console.log(tree);
+ const binaryOperatorTypes = {
+ add: "operator_add",
+ subtract: "operator_subtract",
+ this.multiply: "operator_multiply",
+ divide: "operator_divide",
+ };
+ const BLOCK_TYPE = {
+ number: "math_number",
+ text: "text",
+ };
+ function translateMathToXml(x, tree, shadowType) {
+ let xShadowField = null;
+ if (shadowType) {
+ let xShadow = xml.newXml(x, "shadow", { type: shadowType });
+ if (shadowType === BLOCK_TYPE.number) {
+ xShadowField = xml.newXml(xShadow, "field", { name: "NUM" });
+ } else if (shadowType === BLOCK_TYPE.text) {
+ xShadowField = xml.newXml(xShadow, "field", { name: "TEXT" });
+ }
+ }
+ if (!tree || !tree.type) {
+ return;
+ }
+ if (tree.type === "OperatorNode") {
+ let operatorType = binaryOperatorTypes[tree.fn];
+ if (operatorType) {
+ let xOp = newXml(x, "block", { type: operatorType });
+ translateMathToXml(xml.newXml(xOp, "value", { name: "NUM1" }), tree.args[0], BLOCK_TYPE.number);
+ translateMathToXml(xml.newXml(xOp, "value", { name: "NUM2" }), tree.args[1], BLOCK_TYPE.number);
+ return;
+ }
+ return;
+ }
+ if (tree.type === "ConstantNode") {
+ // number or text in quotes
+ if (xShadowField) {
+ xml.setAttr(xShadowField, { text: tree.value });
+ }
+ return;
+ }
+ if (tree.type === "SymbolNode") {
+ // variable
+ let xVar = xml.newXml(x, "block", { type: "data_variable" });
+ xml.newXml(xVar, "field", { name: "VARIABLE", text: tree.name });
+ return;
+ }
+ if (tree.type === "FunctionNode") {
+ // Method Call
+ if (tree.fn.name === "join") {
+ let xOp = newXml(x, "block", { type: "operator_join" });
+ translateMathToXml(xml.newXml(xOp, "value", { name: "STRING1" }), tree.args[0], BLOCK_TYPE.text);
+ translateMathToXml(xml.newXml(xOp, "value", { name: "STRING2" }), tree.args[1], BLOCK_TYPE.text);
+ return;
+ }
+ }
+ }
+ translateMathToXml(x, tree);
+ console.log(x);
+ let ids = Blockly.Xml.domToWorkspace(x, w);
+ console.log(ids);
+ }
+ */
+ /*
+ function clickInject(e) {
+ let codeString = window.prompt("Griffpatch: Enter an expression (i.e. a+2*3)");
+ if (codeString) {
+ doInjectScripts(codeString);
+ }
+ e.preventDefault();
+ return false;
+ }
+ */
+
+ /**
+ * Returns a Set of the top blocks in this workspace / sprite
+ * @returns {Set} Set of top blocks
+ */
+ getTopBlockIDs() {
+ let wksp = this.getWorkspace();
+ let topBlocks = wksp.getTopBlocks();
+ let ids = new Set();
+ for (const block of topBlocks) {
+ ids.add(block.id);
+ }
+ return ids;
+ }
+
+ /**
+ * Initiates a drag event for all block stacks except those in the set of ids.
+ * But why? - Because we know all the ids of the existing stacks before we paste / duplicate - so we can find the
+ * new stack by excluding all the known ones.
+ * @param ids Set of previously known ids
+ */
+ beginDragOfNewBlocksNotInIDs(ids) {
+ if (!this.addon.settings.get("enablePasteBlocksAtMouse")) {
+ return;
+ }
+ let wksp = this.getWorkspace();
+ let topBlocks = wksp.getTopBlocks();
+ for (const block of topBlocks) {
+ if (!ids.has(block.id)) {
+ // console.log("I found a new block!!! - " + block.id);
+ // todo: move the block to the mouse pointer?
+ let mouseXYClone = {
+ x: this.mouseXY.x,
+ y: this.mouseXY.y
+ };
+ block.setIntersects(true); // fixes offscreen block pasting in Turbo Warp
+ this.domHelpers.triggerDragAndDrop(block.svgPath_, null, mouseXYClone);
+ }
+ }
+ }
+ updateMousePosition(e) {
+ this.mouseXY.x = e.clientX;
+ this.mouseXY.y = e.clientY;
+ }
+ eventMouseMove(e) {
+ this.updateMousePosition(e);
+ }
+ eventKeyDown(e) {
+ const switchCostume = up => {
+ // todo: select previous costume
+ let selected = this.costTabBody.querySelector("div[class*='sprite-selector-item_is-selected']");
+ let node = up ? selected.parentNode.previousSibling : selected.parentNode.nextSibling;
+ if (node) {
+ let wrapper = node.closest("div[class*=gui_flex-wrapper]");
+ node.querySelector("div[class^='sprite-selector-item_sprite-name']").click();
+ node.scrollIntoView({
+ behavior: "auto",
+ block: "center",
+ inline: "start"
+ });
+ wrapper.scrollTop = 0;
+ }
+ };
+ if (document.URL.indexOf("editor") <= 0) {
+ return;
+ }
+ let ctrlKey = e.ctrlKey || e.metaKey;
+ if (e.keyCode === 37 && ctrlKey) {
+ // Ctrl + Left Arrow Key
+ if (document.activeElement.tagName === "INPUT") {
+ return;
+ }
+ if (this.isCostumeEditor()) {
+ switchCostume(true);
+ e.cancelBubble = true;
+ e.preventDefault();
+ return true;
+ }
+ }
+ if (e.keyCode === 39 && ctrlKey) {
+ // Ctrl + Right Arrow Key
+ if (document.activeElement.tagName === "INPUT") {
+ return;
+ }
+ if (this.isCostumeEditor()) {
+ switchCostume(false);
+ e.cancelBubble = true;
+ e.preventDefault();
+ return true;
+ }
+ }
+ if (e.keyCode === 86 && ctrlKey && !e.griff) {
+ // Ctrl + V
+ // Set a timeout so we can take control of the paste after the event
+ let ids = this.getTopBlockIDs();
+ setTimeout(() => {
+ this.beginDragOfNewBlocksNotInIDs(ids);
+ }, 10);
+ }
+
+ // if (e.keyCode === 220 && (!document.activeElement || document.activeElement.tagName === 'INPUT')) {
+ //
+ // }
+ }
+ eventCopyClick(block, blockOnly) {
+ let wksp = this.getWorkspace();
+ if (block) {
+ block.select();
+ let next = blockOnly ? block.getNextBlock() : null;
+ if (next) {
+ next.unplug(false); // setParent(null);
+ }
+
+ // separate child temporarily
+ document.dispatchEvent(new KeyboardEvent("keydown", {
+ keyCode: 67,
+ ctrlKey: true
+ }));
+ if (next || blockOnly === 2) {
+ setTimeout(() => {
+ if (next) {
+ wksp.undo(); // undo the unplug above...
+ }
+ if (blockOnly === 2) {
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].startUndoGroup(wksp);
+ block.dispose(true);
+ _UndoGroup_js__WEBPACK_IMPORTED_MODULE_1__["default"].endUndoGroup(wksp);
+ }
+ }, 0);
+ }
+ }
+ }
+ eventMouseDown(e) {
+ this.updateMousePosition(e);
+ }
+ eventMouseUp(e) {
+ this.updateMousePosition(e);
+ }
+ initInner(root) {
+ var _this = this;
+ let guiTabs = root.childNodes;
+ if (this.codeTab && guiTabs[0] !== this.codeTab) {
+ // We have been CHANGED!!! - Happens when going to project page, and then back inside again!!!
+ this.domHelpers.unbindAllEvents();
+ }
+ this.codeTab = guiTabs[0];
+ this.costTab = guiTabs[1];
+ this.costTabBody = document.querySelector("div[aria-labelledby=" + this.costTab.id + "]");
+ this.domHelpers.bindOnce(document, "keydown", function () {
+ return _this.eventKeyDown(...arguments);
+ }, true);
+ this.domHelpers.bindOnce(document, "mousemove", function () {
+ return _this.eventMouseMove(...arguments);
+ }, true);
+ this.domHelpers.bindOnce(document, "mousedown", function () {
+ return _this.eventMouseDown(...arguments);
+ }, true); // true to capture all mouse downs 'before' the dom events handle them
+ this.domHelpers.bindOnce(document, "mouseup", function () {
+ return _this.eventMouseUp(...arguments);
+ }, true);
+ }
+}
+class Col {
+ /**
+ * @param x {Number} x position (for ordering)
+ * @param count {Number}
+ * @param blocks {[Block]}
+ */
+ constructor(x, count, blocks) {
+ /**
+ * x position (for ordering)
+ * @type {Number}
+ */
+ this.x = x;
+ /**
+ * @type {Number}
+ */
+ this.count = count;
+ /**
+ * @type {[Blockly.Block]}
+ */
+ this.blocks = blocks;
+ }
+}
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-devtools/DomHelpers.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/editor-devtools/DomHelpers.js ***!
+ \*********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return DomHelpers; });
+class DomHelpers {
+ constructor(addon) {
+ this.addon = addon;
+ this.vm = addon.tab.traps.vm;
+ /**
+ * @type {eventDetails[]}
+ */
+ this.events = [];
+ }
+
+ /**
+ * Simulate a drag and drop programmatically through javascript
+ * @param selectorDrag
+ * @param selectorDrop
+ * @param mouseXY
+ * @param [shiftKey=false]
+ * @returns {boolean}
+ */
+ triggerDragAndDrop(selectorDrag, selectorDrop, mouseXY, shiftKey) {
+ // function for triggering mouse events
+ shiftKey = shiftKey || false;
+ let fireMouseEvent = function fireMouseEvent(type, elem, centerX, centerY) {
+ let evt = document.createEvent("MouseEvents");
+ evt.initMouseEvent(type, true, true, window, 1, 1, 1, centerX, centerY, shiftKey, false, false, false, 0, elem);
+ elem.dispatchEvent(evt);
+ };
+
+ // fetch target elements
+ let elemDrag = selectorDrag; // document.querySelector(selectorDrag);
+ let elemDrop = selectorDrop; // document.querySelector(selectorDrop);
+ if (!elemDrag /* || !elemDrop*/) {
+ return false;
+ }
+
+ // calculate positions
+ let pos = elemDrag.getBoundingClientRect();
+ let center1X = Math.floor((pos.left + pos.right) / 2);
+ let center1Y = Math.floor((pos.top + pos.bottom) / 2);
+
+ // mouse over dragged element and mousedown
+ fireMouseEvent("mouseover", elemDrag, center1X, center1Y);
+ fireMouseEvent("mousedown", elemDrag, center1X, center1Y);
+
+ // start dragging process over to drop target
+ fireMouseEvent("dragstart", elemDrag, center1X, center1Y);
+ fireMouseEvent("drag", elemDrag, center1X, center1Y);
+ fireMouseEvent("mousemove", elemDrag, center1X, center1Y);
+ if (!elemDrop) {
+ if (mouseXY) {
+ // console.log(mouseXY);
+ let center2X = mouseXY.x;
+ let center2Y = mouseXY.y;
+ fireMouseEvent("drag", elemDrag, center2X, center2Y);
+ fireMouseEvent("mousemove", elemDrag, center2X, center2Y);
+ }
+ return false;
+ }
+ pos = elemDrop.getBoundingClientRect();
+ let center2X = Math.floor((pos.left + pos.right) / 2);
+ let center2Y = Math.floor((pos.top + pos.bottom) / 2);
+ fireMouseEvent("drag", elemDrag, center2X, center2Y);
+ fireMouseEvent("mousemove", elemDrop, center2X, center2Y);
+
+ // trigger dragging process on top of drop target
+ fireMouseEvent("mouseenter", elemDrop, center2X, center2Y);
+ fireMouseEvent("dragenter", elemDrop, center2X, center2Y);
+ fireMouseEvent("mouseover", elemDrop, center2X, center2Y);
+ fireMouseEvent("dragover", elemDrop, center2X, center2Y);
+
+ // release dragged element on top of drop target
+ fireMouseEvent("drop", elemDrop, center2X, center2Y);
+ fireMouseEvent("dragend", elemDrag, center2X, center2Y);
+ fireMouseEvent("mouseup", elemDrag, center2X, center2Y);
+ return true;
+ }
+ bindOnce(dom, event, func, capture) {
+ capture = !!capture;
+ dom.removeEventListener(event, func, capture);
+ dom.addEventListener(event, func, capture);
+ this.events.push(new eventDetails(dom, event, func, capture));
+ }
+ unbindAllEvents() {
+ for (const event of this.events) {
+ event.dom.removeEventListener(event.event, event.func, event.capture);
+ }
+ this.events = [];
+ }
+}
+
+/**
+ * A record of an event
+ */
+class eventDetails {
+ constructor(dom, event, func, capture) {
+ this.dom = dom;
+ this.event = event;
+ this.func = func;
+ this.capture = capture;
+ }
+}
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-devtools/UndoGroup.js":
+/*!********************************************************!*\
+ !*** ./src/addons/addons/editor-devtools/UndoGroup.js ***!
+ \********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return UndoGroup; });
+/**
+ * This class is dedicated to maintaining the Undo stack of Blockly
+ * It allows us to initiate an undo group such that all subsequent operations are recorded as a single
+ * undoable transaction.
+ */
+class UndoGroup {
+ /**
+ * Start an Undo group - begin recording
+ * @param workspace the workspace
+ */
+ static startUndoGroup(workspace) {
+ const undoStack = workspace.undoStack_;
+ if (undoStack.length) {
+ undoStack[undoStack.length - 1]._devtoolsLastUndo = true;
+ }
+ }
+
+ /**
+ * End an Undo group - stops recording
+ * @param workspace the workspace
+ */
+ static endUndoGroup(workspace) {
+ const undoStack = workspace.undoStack_;
+ // Events (responsible for undoStack updates) are delayed with a setTimeout(f, 0)
+ // https://github.com/LLK/scratch-blocks/blob/f159a1779e5391b502d374fb2fdd0cb5ca43d6a2/core/events.js#L182
+ setTimeout(() => {
+ const group = generateUID();
+ for (let i = undoStack.length - 1; i >= 0 && !undoStack[i]._devtoolsLastUndo; i--) {
+ undoStack[i].group = group;
+ }
+ }, 0);
+ }
+}
+
+/**
+ * https://github.com/LLK/scratch-blocks/blob/f159a1779e5391b502d374fb2fdd0cb5ca43d6a2/core/events.js#L182
+ * @returns {string}
+ * @private
+ */
+function generateUID() {
+ const CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%()*+,-./:;=?@[]^_`{|}~";
+ let result = "";
+ for (let i = 0; i < 20; i++) {
+ result += CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)];
+ }
+ return result;
+}
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-devtools/_runtime_entry.js":
+/*!*************************************************************!*\
+ !*** ./src/addons/addons/editor-devtools/_runtime_entry.js ***!
+ \*************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/editor-devtools/userscript.js");
+/* harmony import */ var _url_loader_icon_close_svg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! url-loader!./icon--close.svg */ "./node_modules/url-loader/dist/cjs.js!./src/addons/addons/editor-devtools/icon--close.svg");
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "icon--close.svg": _url_loader_icon_close_svg__WEBPACK_IMPORTED_MODULE_1__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-devtools/userscript.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/editor-devtools/userscript.js ***!
+ \*********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _DevTools_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DevTools.js */ "./src/addons/addons/editor-devtools/DevTools.js");
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console,
+ msg,
+ safeMsg: m
+ } = _ref;
+ const devTools = new _DevTools_js__WEBPACK_IMPORTED_MODULE_0__["default"](addon, msg, m);
+ devTools.init();
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-searchable-dropdowns/_runtime_entry.js":
+/*!*************************************************************************!*\
+ !*** ./src/addons/addons/editor-searchable-dropdowns/_runtime_entry.js ***!
+ \*************************************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/editor-searchable-dropdowns/userscript.js");
+/* harmony import */ var _css_loader_userscript_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./userscript.css */ "./node_modules/css-loader/index.js!./src/addons/addons/editor-searchable-dropdowns/userscript.css");
+/* harmony import */ var _css_loader_userscript_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_userscript_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "userscript.css": _css_loader_userscript_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/editor-searchable-dropdowns/userscript.js":
+/*!*********************************************************************!*\
+ !*** ./src/addons/addons/editor-searchable-dropdowns/userscript.js ***!
+ \*********************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* eslint-disable */
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ console,
+ msg
+ } = _ref;
+ const Blockly = await addon.tab.traps.getBlockly();
+ const vm = addon.tab.traps.vm;
+ const SCRATCH_ITEMS_TO_HIDE = ["RENAME_VARIABLE_ID", "DELETE_VARIABLE_ID", "NEW_BROADCAST_MESSAGE_ID",
+ // From rename-broadcasts addon
+ "RENAME_BROADCAST_MESSAGE_ID"];
+ const canUseAsGlobalVariableName = (name, type) => {
+ return !vm.runtime.getAllVarNamesOfType(type).includes(name);
+ };
+ const canUseAsLocalVariableName = (name, type) => {
+ return !vm.editingTarget.lookupVariableByNameAndType(name, type);
+ };
+ const ADDON_ITEMS = {
+ createGlobalVariable: {
+ enabled: name => canUseAsGlobalVariableName(name, ""),
+ createVariable: (workspace, name) => workspace.createVariable(name)
+ },
+ createLocalVariable: {
+ enabled: name => canUseAsLocalVariableName(name, ""),
+ createVariable: (workspace, name) => workspace.createVariable(name, "", null, true)
+ },
+ createGlobalList: {
+ enabled: name => canUseAsGlobalVariableName(name, "list"),
+ createVariable: (workspace, name) => workspace.createVariable(name, "list")
+ },
+ createLocalList: {
+ enabled: name => canUseAsLocalVariableName(name, "list"),
+ createVariable: (workspace, name) => workspace.createVariable(name, "list", null, true)
+ },
+ createBroadcast: {
+ enabled: name => canUseAsGlobalVariableName(name, "broadcast_msg"),
+ createVariable: (workspace, name) => workspace.createVariable(name, "broadcast_msg")
+ }
+ };
+ let blocklyDropDownContent = null;
+ let blocklyDropdownMenu = null;
+ let searchBar = null;
+ // Contains DOM and addon state
+ let items = [];
+ let searchedItems = [];
+ // Tracks internal Scratch state
+ let currentDropdownOptions = [];
+ let resultOfLastGetOptions = [];
+ const oldDropDownDivShow = Blockly.DropDownDiv.show;
+ Blockly.DropDownDiv.show = function () {
+ blocklyDropdownMenu = document.querySelector(".blocklyDropdownMenu");
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ if (!blocklyDropdownMenu) {
+ return oldDropDownDivShow.call(this, ...args);
+ }
+ blocklyDropdownMenu.focus = () => {}; // no-op focus() so it can't steal it from the search bar
+
+ searchBar = document.createElement("input");
+ addon.tab.displayNoneWhileDisabled(searchBar, {
+ display: "flex"
+ });
+ searchBar.type = "text";
+ searchBar.addEventListener("input", updateSearch);
+ searchBar.addEventListener("keydown", handleKeyDownEvent);
+ searchBar.classList.add("u-dropdown-searchbar");
+ blocklyDropdownMenu.insertBefore(searchBar, blocklyDropdownMenu.firstChild);
+ items = Array.from(blocklyDropdownMenu.children).filter(child => child.tagName !== "INPUT").map(element => ({
+ element,
+ text: element.textContent
+ }));
+ currentDropdownOptions = resultOfLastGetOptions;
+ updateSearch();
+
+ // Call the original show method after adding everything so that it can perform the correct size calculations
+ const ret = oldDropDownDivShow.call(this, ...args);
+
+ // Lock the size of the dropdown
+ blocklyDropDownContent = Blockly.DropDownDiv.getContentDiv();
+ blocklyDropDownContent.style.width = getComputedStyle(blocklyDropDownContent).width;
+ blocklyDropDownContent.style.height = getComputedStyle(blocklyDropDownContent).height;
+
+ // This is really strange, but if you don't reinsert the search bar into the DOM then focus() doesn't work
+ blocklyDropdownMenu.insertBefore(searchBar, blocklyDropdownMenu.firstChild);
+ searchBar.focus();
+ return ret;
+ };
+ const oldDropDownDivClearContent = Blockly.DropDownDiv.clearContent;
+ Blockly.DropDownDiv.clearContent = function () {
+ oldDropDownDivClearContent.call(this);
+ items = [];
+ searchedItems = [];
+ Blockly.DropDownDiv.content_.style.height = "";
+ };
+ const oldFieldDropdownGetOptions = Blockly.FieldDropdown.prototype.getOptions;
+ Blockly.FieldDropdown.prototype.getOptions = function () {
+ const options = oldFieldDropdownGetOptions.call(this);
+ const block = this.sourceBlock_;
+ const isStage = vm.editingTarget && vm.editingTarget.isStage;
+ if (block) {
+ if (block.category_ === "data") {
+ options.push(getMenuItemMessage("createGlobalVariable"));
+ if (!isStage) {
+ options.push(getMenuItemMessage("createLocalVariable"));
+ }
+ } else if (block.category_ === "data-lists") {
+ options.push(getMenuItemMessage("createGlobalList"));
+ if (!isStage) {
+ options.push(getMenuItemMessage("createLocalList"));
+ }
+ } else if (block.type === "event_broadcast_menu" || block.type === "event_whenbroadcastreceived") {
+ options.push(getMenuItemMessage("createBroadcast"));
+ }
+ }
+ // Options aren't normally stored anywhere, so we'll store them ourselves.
+ resultOfLastGetOptions = options;
+ return options;
+ };
+ const oldFieldTextDropdownGetOptions = Blockly.FieldTextDropdown.prototype.getOptions;
+ Blockly.FieldTextDropdown.prototype.getOptions = function () {
+ const options = oldFieldTextDropdownGetOptions.call(this);
+ const block = this.sourceBlock_;
+ const isStage = vm.editingTarget && vm.editingTarget.isStage;
+ if (block) {
+ if (block.category_ === "data") {
+ options.push(getMenuItemMessage("createGlobalVariable"));
+ if (!isStage) {
+ options.push(getMenuItemMessage("createLocalVariable"));
+ }
+ } else if (block.category_ === "data-lists") {
+ options.push(getMenuItemMessage("createGlobalList"));
+ if (!isStage) {
+ options.push(getMenuItemMessage("createLocalList"));
+ }
+ } else if (block.type === "event_broadcast_menu" || block.type === "event_whenbroadcastreceived") {
+ options.push(getMenuItemMessage("createBroadcast"));
+ }
+ }
+ // Options aren't normally stored anywhere, so we'll store them ourselves.
+ resultOfLastGetOptions = options;
+ return options;
+ };
+ const oldFieldVariableOnItemSelected = Blockly.FieldVariable.prototype.onItemSelected;
+ Blockly.FieldVariable.prototype.onItemSelected = function (menu, menuItem) {
+ const sourceBlock = this.sourceBlock_;
+ if (sourceBlock && sourceBlock.workspace && searchBar.value.length !== 0) {
+ const workspace = sourceBlock.workspace;
+ const optionId = menuItem.getValue();
+ if (Object.prototype.hasOwnProperty.call(ADDON_ITEMS, optionId)) {
+ const addonItem = ADDON_ITEMS[optionId];
+ Blockly.Events.setGroup(true);
+ const variable = addonItem.createVariable(workspace, searchBar.value.trim());
+ if (this.sourceBlock_) this.setValue(variable.getId());
+ Blockly.Events.setGroup(false);
+ return;
+ }
+ }
+ return oldFieldVariableOnItemSelected.call(this, menu, menuItem);
+ };
+ function selectItem(item, click) {
+ // You can't just use click() or focus() because Blockly uses mousedown and mouseup handlers, not click handlers.
+ item.dispatchEvent(new MouseEvent("mousedown", {
+ relatedTarget: item,
+ bubbles: true
+ }));
+ if (click) item.dispatchEvent(new MouseEvent("mouseup", {
+ relatedTarget: item,
+ bubbles: true
+ }));
+
+ // Scroll the item into view if it is offscreen.
+ const itemTop = item.offsetTop;
+ const itemEnd = itemTop + item.offsetHeight;
+ const scrollTop = blocklyDropDownContent.scrollTop;
+ const scrollHeight = blocklyDropDownContent.offsetHeight;
+ const scrollEnd = scrollTop + scrollHeight;
+ if (scrollTop > itemTop) {
+ blocklyDropDownContent.scrollTop = itemTop;
+ } else if (itemEnd > scrollEnd) {
+ blocklyDropDownContent.scrollTop = itemEnd - scrollHeight;
+ }
+ }
+ function performSearch() {
+ const rawQuery = searchBar.value.trim();
+ const query = rawQuery.trim().toLowerCase();
+ const rank = (item, index) => {
+ // Negative number will hide
+ // Higher numbers will appear first
+ const option = currentDropdownOptions[index];
+ const optionId = option[1];
+ if (SCRATCH_ITEMS_TO_HIDE.includes(optionId)) {
+ return query ? -1 : 0;
+ } else if (Object.prototype.hasOwnProperty.call(ADDON_ITEMS, optionId)) {
+ if (!query) {
+ return -1;
+ }
+ const addonInfo = ADDON_ITEMS[optionId];
+ if (addonInfo.enabled(rawQuery)) {
+ item.element.lastChild.lastChild.textContent = getMenuItemMessage(optionId)[0];
+ return 0;
+ }
+ return -1;
+ }
+ const itemText = item.text.toLowerCase();
+ if (query === itemText) {
+ return 2;
+ }
+ if (itemText.startsWith(query)) {
+ return 1;
+ }
+ if (itemText.includes(query)) {
+ return 0;
+ }
+ return -1;
+ };
+ return items.map((item, index) => ({
+ item,
+ score: rank(item, index)
+ })).sort((_ref2, _ref3) => {
+ let {
+ score: scoreA
+ } = _ref2;
+ let {
+ score: scoreB
+ } = _ref3;
+ return Math.max(0, scoreB) - Math.max(0, scoreA);
+ });
+ }
+ function updateSearch() {
+ const previousSearchedItems = searchedItems;
+ searchedItems = performSearch();
+ let needToUpdateDOM = previousSearchedItems.length !== searchedItems.length;
+ if (!needToUpdateDOM) {
+ for (let i = 0; i < searchedItems.length; i++) {
+ if (searchedItems[i].item !== previousSearchedItems[i].item) {
+ needToUpdateDOM = true;
+ break;
+ }
+ }
+ }
+ if (needToUpdateDOM && previousSearchedItems.length > 0) {
+ for (const {
+ item
+ } of previousSearchedItems) {
+ item.element.remove();
+ }
+ for (const {
+ item
+ } of searchedItems) {
+ blocklyDropdownMenu.appendChild(item.element);
+ }
+ }
+ for (const {
+ item,
+ score
+ } of searchedItems) {
+ item.element.hidden = score < 0;
+ }
+ }
+ function handleKeyDownEvent(event) {
+ if (event.key === "Enter") {
+ // Reimplement enter to select item to account for hidden items and default to the top item.
+ event.stopPropagation();
+ event.preventDefault();
+ const selectedItem = document.querySelector(".goog-menuitem-highlight");
+ if (selectedItem && !selectedItem.hidden) {
+ selectItem(selectedItem, true);
+ return;
+ }
+ const selectedBlock = Blockly.selected;
+ if (searchBar.value === "" && selectedBlock) {
+ if (selectedBlock.type === "event_broadcast" || selectedBlock.type === "event_broadcastandwait" || selectedBlock.type === "event_whenbroadcastreceived") {
+ // The top item of these dropdowns is always "New message"
+ // When pressing enter on an empty search bar, we close the dropdown instead of making a new broadcast.
+ Blockly.DropDownDiv.hide();
+ return;
+ }
+ }
+ for (const {
+ item
+ } of searchedItems) {
+ if (!item.element.hidden) {
+ selectItem(item.element, true);
+ break;
+ }
+ }
+ // If there is no top value, do nothing and leave the dropdown open
+ } else if (event.key === "Escape") {
+ Blockly.DropDownDiv.hide();
+ } else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
+ // Reimplement keyboard navigation to account for hidden items.
+ event.preventDefault();
+ event.stopPropagation();
+ const items = searchedItems.filter(i => i.score >= 0).map(i => i.item);
+ if (items.length === 0) {
+ return;
+ }
+ let selectedIndex = -1;
+ for (let i = 0; i < items.length; i++) {
+ if (items[i].element.classList.contains("goog-menuitem-highlight")) {
+ selectedIndex = i;
+ break;
+ }
+ }
+ const lastIndex = items.length - 1;
+ let newIndex = 0;
+ if (event.key === "ArrowDown") {
+ if (selectedIndex === -1 || selectedIndex === lastIndex) {
+ newIndex = 0;
+ } else {
+ newIndex = selectedIndex + 1;
+ }
+ } else {
+ if (selectedIndex === -1 || selectedIndex === 0) {
+ newIndex = lastIndex;
+ } else {
+ newIndex = selectedIndex - 1;
+ }
+ }
+ selectItem(items[newIndex].element, false);
+ }
+ }
+ function getMenuItemMessage(message) {
+ var _searchBar;
+ // Format used internally by Scratch:
+ // [human readable name, internal name]
+ return [msg(message, {
+ name: ((_searchBar = searchBar) === null || _searchBar === void 0 ? void 0 : _searchBar.value.trim()) || ""
+ }), message];
+ }
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/find-bar/_runtime_entry.js":
+/*!******************************************************!*\
+ !*** ./src/addons/addons/find-bar/_runtime_entry.js ***!
+ \******************************************************/
+/*! exports provided: resources */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "resources", function() { return resources; });
+/* harmony import */ var _userscript_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./userscript.js */ "./src/addons/addons/find-bar/userscript.js");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! css-loader!./userstyle.css */ "./node_modules/css-loader/index.js!./src/addons/addons/find-bar/userstyle.css");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1__);
+/* generated by pull.js */
+
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"],
+ "userstyle.css": _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_1___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/find-bar/blockly/BlockItem.js":
+/*!*********************************************************!*\
+ !*** ./src/addons/addons/find-bar/blockly/BlockItem.js ***!
+ \*********************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return BlockItem; });
+class BlockItem {
+ constructor(cls, procCode, labelID, y) {
+ this.cls = cls;
+ this.procCode = procCode;
+ this.labelID = labelID;
+ this.y = y;
+ this.lower = procCode.toLowerCase();
+ /**
+ * An Array of block ids
+ * @type {Array.}
+ */
+ this.clones = null;
+ this.eventName = null;
+ }
+
+ /**
+ * True if the blockID matches a black represented by this BlockItem
+ * @param id
+ * @returns {boolean}
+ */
+ matchesID(id) {
+ if (this.labelID === id) {
+ return true;
+ }
+ if (this.clones) {
+ for (const cloneID of this.clones) {
+ if (cloneID === id) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
+
+/***/ }),
+
+/***/ "./src/addons/addons/find-bar/userscript.js":
+/*!**************************************************!*\
+ !*** ./src/addons/addons/find-bar/userscript.js ***!
+ \**************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _blockly_BlockItem_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./blockly/BlockItem.js */ "./src/addons/addons/find-bar/blockly/BlockItem.js");
+/* harmony import */ var _blockly_BlockInstance_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./blockly/BlockInstance.js */ "./src/addons/addons/find-bar/blockly/BlockInstance.js");
+/* harmony import */ var _blockly_Utils_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./blockly/Utils.js */ "./src/addons/addons/find-bar/blockly/Utils.js");
+
+
+
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon,
+ msg,
+ console
+ } = _ref;
+ const Blockly = await addon.tab.traps.getBlockly();
+ class FindBar {
+ constructor() {
+ this.utils = new _blockly_Utils_js__WEBPACK_IMPORTED_MODULE_2__["default"](addon);
+ this.prevValue = "";
+ this.findBarOuter = null;
+ this.findWrapper = null;
+ this.findInput = null;
+ this.dropdownOut = null;
+ this.dropdown = new Dropdown(this.utils);
+ document.addEventListener("keydown", e => this.eventKeyDown(e), true);
+ }
+ get workspace() {
+ return Blockly.getMainWorkspace();
+ }
+ createDom(root) {
+ this.findBarOuter = document.createElement("div");
+ this.findBarOuter.className = "sa-find-bar";
+ addon.tab.displayNoneWhileDisabled(this.findBarOuter, {
+ display: "flex"
+ });
+ root.appendChild(this.findBarOuter);
+ this.findWrapper = this.findBarOuter.appendChild(document.createElement("span"));
+ this.findWrapper.className = "sa-find-wrapper";
+ this.dropdownOut = this.findWrapper.appendChild(document.createElement("label"));
+ this.dropdownOut.className = "sa-find-dropdown-out";
+ this.findInput = this.dropdownOut.appendChild(document.createElement("input"));
+ this.findInput.className = addon.tab.scratchClass("input_input-form", {
+ others: "sa-find-input"
+ });
+ // for