[\s\S]*?
\s*([\s\S]*?)\s*<\/p>/i)) === null || _htmlText$match3 === void 0 ? void 0 : _htmlText$match3[1]) || "";
+ const type = isScouted ? ccLicense2Rating(ccLicense) : "unwhitelisted";
+ if (infoBox) infoBox.remove();
+ infoBox = genCopyrightInfoBox(type, name, author);
+ if (type === "bad" || type === "unwhitelisted") {
+ name = undefined;
+ okayButton.style.filter = "brightness(70%)";
+ okayButton.style.pointerEvents = "none";
+ } else {
+ okayButton.style.filter = "";
+ okayButton.style.pointerEvents = "";
+ }
+ modal.appendChild(infoBox);
+ e.stopPropagation();
+ });
+ idInputDiv.append(idLabel, idInput, searchBtn);
+ modal.append(label, idInputDiv);
+ }
+ function ccLicense2Rating(licence) {
+ licence = String(licence).toLowerCase().trim();
+ const goodTexts = ["you may only use this piece for commercial purposes if your work is a web-based game or animation,"];
+ for (const text of goodTexts) {
+ if (licence.startsWith(text)) return "good";
+ }
+ const badTexts = ["you may not use this work for any purposes"];
+ for (const text of badTexts) {
+ if (licence.startsWith(text)) return "bad";
+ }
+ const warnTexts = ["you are free to copy, distribute and transmit this work under the following conditions:", "please contact me if you would like to use this in a project. we can discuss the details."];
+ for (const text of warnTexts) {
+ if (licence.startsWith(text)) return "warn";
+ }
+ return "warn"; // warn is the default
+ }
+ function genCopyrightInfoBox(type, name, author) {
+ const color = type === "good" ? "#00ff00" : type === "bad" || type === "unwhitelisted" ? "#ff0000" : "#ffc400";
+ const box = document.createElement("div");
+ box.setAttribute("style", "display: flex; width: 100%; margin: 15px 0; padding: 10px 20px 10px 30px; border-radius: 15px; border: solid 2px ".concat(color, "; background: ").concat(color, "30; text-align: center; font-size: .9rem; font-weight: bold;"));
+ const img = document.createElement("img");
+ img.setAttribute("style", "width:35px; margin-right: 5px;");
+ img.src = type === "good" ? safeIcon : type === "bad" || type === "unwhitelisted" ? unsafeIcon : warnIcon;
+ const label = document.createElement("span");
+ if (type === "good") label.innerHTML = "The Track: ".concat(name, " by ").concat(author, ", can freely be used for web-based games");else if (type === "bad") label.innerHTML = "The Track: ".concat(name, " by ").concat(author, ", is not allowed for use!");else if (type === "unwhitelisted") label.innerHTML = "The Track: ".concat(name, " by ").concat(author, ", is not allowed for use. ").concat(author, " is not scouted on Newgrounds!");else label.innerHTML = "The Track: ".concat(name, " by ").concat(author, ", can only be used for non-profit web-based games WITH credit. Further use requires permission from ").concat(author);
+ box.append(img, label);
+ return box;
+ }
+ function addButtonNG() {
+ // TODO add a tooltip maybe
+ const itemDiv = document.querySelector("div[class^=\"action-menu_menu-container\"] div[class^=\"action-menu_more-buttons-outer\"] div[class^=\"action-menu_more-buttons\"]");
+ ngButtonElement = itemDiv.children[0].cloneNode(true);
+ const innerButton = ngButtonElement.firstChild;
+ innerButton.setAttribute("data-tip", "Newgrounds Sound");
+ innerButton.setAttribute("aria-label", "Newgrounds Sound");
+ /* cleanup */
+ for (var i = 1; i < innerButton.children.length; i++) {
+ const child = innerButton.children[i];
+ if (child) child.remove();
+ }
+ innerButton.firstChild.src = ngIcon;
+ ngButtonElement.addEventListener("click", openNewgroundsPopup);
+ itemDiv.insertBefore(ngButtonElement, itemDiv.children[0]);
+ }
+ function startListenerWorker() {
+ ReduxStore.subscribe(() => queueMicrotask(() => {
+ const reduxState = ReduxStore.getState().scratchGui;
+ /* sound tab */
+ if (!reduxState.mode.isPlayerOnly && reduxState.editorTab.activeTabIndex === 2) {
+ if (!ngButtonElement) addButtonNG();
+ } else {
+ ngButtonElement = undefined;
+ }
+ }));
+ }
+ if (typeof scaffolding === "undefined") startListenerWorker();
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/toolbox-category-drag/_runtime_entry.js":
+/*!*******************************************************************!*\
+ !*** ./src/addons/addons/toolbox-category-drag/_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/toolbox-category-drag/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/toolbox-category-drag/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/toolbox-category-drag/userscript.js":
+/*!***************************************************************!*\
+ !*** ./src/addons/addons/toolbox-category-drag/userscript.js ***!
+ \***************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+// Toolbox Category Drag
+// By: SharkPool
+/* harmony default export */ __webpack_exports__["default"] = (async function (_ref) {
+ let {
+ addon
+ } = _ref;
+ // wait for scratchblocks to be defined
+ await addon.tab.traps.getBlockly();
+ const COMMENT_TRAPPER_ID = "--Category_Order_ADDON-config";
+ const soup = "!#%()*+,-./:;=?@[]^_`{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let categoryOrdering = undefined;
+ const genUID = () => {
+ const id = [];
+ for (let i = 0; i < 20; i++) {
+ id[i] = soup.charAt(Math.random() * soup.length);
+ }
+ return id.join("");
+ };
+ const createSep = () => {
+ const sep = document.createElement("sep");
+ sep.setAttribute("gap", "36");
+ return sep;
+ };
+ const extractCategoryID = classList => {
+ for (const text of classList) {
+ if (text.startsWith("scratchCategoryId-")) return text.replace("scratchCategoryId-", "");
+ }
+ return undefined;
+ };
+ const ogPopulate = ScratchBlocks.Toolbox.CategoryMenu.prototype.populate;
+ ScratchBlocks.Toolbox.CategoryMenu.prototype.populate = function () {
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+ if (!categoryOrdering) {
+ ogPopulate.call(this, ...args);
+ return;
+ }
+ const toolboxXml = args[0];
+ const children = Array.from(toolboxXml.children);
+ const categories = children.filter(e => e.tagName === "category");
+
+ /* sort categories based on categoryOrdering */
+ categories.sort((a, b) => {
+ const aIndex = categoryOrdering.indexOf(a.getAttribute("id"));
+ const bIndex = categoryOrdering.indexOf(b.getAttribute("id"));
+ return (aIndex === -1 ? 999 : aIndex) - (bIndex === -1 ? 999 : bIndex);
+ });
+ while (toolboxXml.firstChild) toolboxXml.removeChild(toolboxXml.firstChild);
+
+ /* + + + ... + + */
+ toolboxXml.appendChild(createSep());
+ categories.forEach(cat => {
+ toolboxXml.appendChild(cat);
+ toolboxXml.appendChild(createSep());
+ });
+ ogPopulate.call(this, ...args);
+ };
+ const ogSaveJSON = vm.toJSON;
+ vm.toJSON = function () {
+ if (categoryOrdering !== undefined) saveOrdering();
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+ args[_key2] = arguments[_key2];
+ }
+ return ogSaveJSON.call(this, ...args);
+ };
+ vm.runtime.on("PROJECT_LOADED", () => {
+ const storedOrder = findOrderingComment(true);
+ if (storedOrder) {
+ try {
+ categoryOrdering = JSON.parse(storedOrder);
+ setTimeout(forceRefreshToolbox, 100);
+ } catch (_unused) {}
+ }
+ });
+ function findOrderingComment(optParse) {
+ const stageTarget = vm.runtime.getTargetForStage();
+ if (!stageTarget) return undefined;
+ let configComment;
+ const comments = Object.values(stageTarget.comments);
+ for (const comment of comments) {
+ if (comment.text.endsWith(COMMENT_TRAPPER_ID)) {
+ configComment = comment.text;
+ break;
+ }
+ }
+ if (configComment) {
+ if (!optParse) return true;
+ const dataLine = configComment.split("\n").find(i => i.endsWith(COMMENT_TRAPPER_ID));
+ if (!dataLine) return undefined;
+ return dataLine.substr(0, dataLine.length - COMMENT_TRAPPER_ID.length);
+ }
+ return optParse ? undefined : false;
+ }
+ function saveOrdering() {
+ if (findOrderingComment()) return;
+ const stageTarget = vm.runtime.getTargetForStage();
+ if (!stageTarget) return;
+ const text = "Configuration for 'Category Ordering' Addon\nYou can move, resize, and minimize this comment, but don't edit it by hand. This comment can be deleted to remove the stored settings.\n".concat(JSON.stringify(categoryOrdering)).concat(COMMENT_TRAPPER_ID);
+ stageTarget.createComment(genUID(), null, text, 50, 50, 350, 170, false);
+ vm.runtime.emitProjectChanged();
+ }
+ function compileNewOrder(htmlCategoryList) {
+ const orderedIDs = [];
+ for (const cat of htmlCategoryList) {
+ const id = extractCategoryID(cat.firstChild.classList);
+ if (id) orderedIDs.push(id);
+ }
+ categoryOrdering = orderedIDs;
+ }
+ function forceRefreshToolbox() {
+ const workspace = ScratchBlocks.getMainWorkspace();
+ const toolbox = workspace.getToolbox();
+ if (!toolbox) return;
+ const categoryMenu = toolbox.categoryMenu_;
+ if (!categoryMenu) return;
+ if (categoryMenu.secondTable) return;
+ categoryMenu.dispose();
+ categoryMenu.createDom();
+ toolbox.populate_(workspace.options.languageTree);
+ toolbox.position();
+ }
+ function initDragDroper(clickEvent) {
+ const draggedCat = clickEvent.target.closest("div[class=\"scratchCategoryMenuRow\"]");
+ if (!draggedCat) return;
+ const categoryList = blocklyToolboxDiv.querySelectorAll("div[class*=\"scratchCategoryMenuRow\"]");
+ const rect = draggedCat.getBoundingClientRect();
+ const generalHeight = rect.height;
+ const offsetX = clickEvent.clientX - rect.left;
+ const offsetY = clickEvent.clientY - rect.top;
+ const dragger = draggedCat.cloneNode(true);
+ draggedCat.style.opacity = 0.5;
+ dragger.setAttribute("style", "position: absolute; z-index: 99999; left: ".concat(rect.left, "px; top: ").concat(rect.top, "px; width: ").concat(rect.width, "px; pointer-events: none;"));
+ dragger.firstChild.setAttribute("style", "box-shadow: #000 5px 5px 10px; border-radius: 8px;");
+ dragger.dataset.dragger = true;
+ document.body.appendChild(dragger);
+ let lastHovered = null;
+ const onMouseMove = moveEvent => {
+ /* drag visual */
+ const newLeft = moveEvent.clientX - offsetX;
+ const newTop = moveEvent.clientY - offsetY;
+ dragger.style.left = "".concat(newLeft, "px");
+ dragger.style.top = "".concat(newTop, "px");
+
+ // auto scroll if dragger is near the top/bottom
+ const scrollZoneSize = 40;
+ const bounds = blocklyToolboxDiv.getBoundingClientRect();
+ if (moveEvent.clientY < bounds.top + scrollZoneSize) {
+ blocklyToolboxDiv.scrollTop -= 4;
+ } else if (moveEvent.clientY > bounds.bottom - scrollZoneSize) {
+ blocklyToolboxDiv.scrollTop += 4;
+ }
+
+ // check if we are near any category
+ // if so, bump down everything below the dragger
+ let target;
+ for (const cat of categoryList) {
+ if (cat === draggedCat) continue;
+ const catRect = cat.getBoundingClientRect();
+ const midpointY = catRect.top + catRect.height / 2;
+ const midpointX = catRect.left + catRect.width / 2;
+ const xDist = Math.abs(moveEvent.clientX - midpointX);
+ const yCheck = moveEvent.clientY < midpointY;
+ if (yCheck && xDist < 100) {
+ target = cat;
+ break;
+ }
+ }
+ for (const cat of categoryList) cat.style.transform = "";
+ if (target) {
+ lastHovered = target;
+ let shifter = target;
+ while (shifter) {
+ if (shifter === draggedCat) return;
+ shifter.style.transform = "translateY(".concat(generalHeight, "px)");
+ shifter = shifter.nextSibling;
+ }
+ } else {
+ lastHovered = null;
+ }
+ };
+ const onMouseUp = () => {
+ /* cleanup */
+ document.removeEventListener("mousemove", onMouseMove);
+ document.removeEventListener("mouseup", onMouseUp);
+ for (const cat of categoryList) cat.style.transform = "";
+ draggedCat.style.opacity = "";
+ dragger.remove();
+
+ // if the category drag was valid, move the category
+ if (lastHovered) {
+ const id = extractCategoryID(draggedCat.firstChild.classList);
+ draggedCat.parentNode.insertBefore(draggedCat, lastHovered);
+ const newCatList = blocklyToolboxDiv.querySelectorAll("div[class*=\"scratchCategoryMenuRow\"]");
+ compileNewOrder(newCatList);
+ setTimeout(() => {
+ forceRefreshToolbox();
+ if (id) ScratchBlocks.mainWorkspace.toolbox_.setSelectedCategoryById(id);
+ }, 100);
+ }
+ };
+ document.addEventListener("mousemove", onMouseMove);
+ document.addEventListener("mouseup", onMouseUp);
+ }
+
+ /* Check for Long (500ms) Presses to not confuse with Selecting Categories */
+ const blocklyToolboxDiv = document.querySelector("div[class*=\"blocklyToolboxDiv\"");
+ blocklyToolboxDiv.addEventListener("mousedown", e => {
+ const longPressTimer = setTimeout(() => initDragDroper(e), 500);
+ const cancel = () => clearTimeout(longPressTimer);
+ document.addEventListener("mouseup", cancel, {
+ once: true
+ });
+ document.addEventListener("mouseleave", cancel, {
+ once: true
+ });
+ });
+});
+
+/***/ }),
+
+/***/ "./src/addons/addons/toolbox-full-blocks-on-hover/_runtime_entry.js":
+/*!**************************************************************************!*\
+ !*** ./src/addons/addons/toolbox-full-blocks-on-hover/_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 _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! css-loader!./userstyle.css */ "./node_modules/css-loader/index.js!./src/addons/addons/toolbox-full-blocks-on-hover/userstyle.css");
+/* harmony import */ var _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_0__);
+/* generated by pull.js */
+
+const resources = {
+ "userstyle.css": _css_loader_userstyle_css__WEBPACK_IMPORTED_MODULE_0___default.a
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/waveform-chunk-size/_runtime_entry.js":
+/*!*****************************************************************!*\
+ !*** ./src/addons/addons/waveform-chunk-size/_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/waveform-chunk-size/userscript.js");
+
+const resources = {
+ "userscript.js": _userscript_js__WEBPACK_IMPORTED_MODULE_0__["default"]
+};
+
+/***/ }),
+
+/***/ "./src/addons/addons/waveform-chunk-size/userscript.js":
+/*!*************************************************************!*\
+ !*** ./src/addons/addons/waveform-chunk-size/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;
+ let chunkSize = addon.settings.get("quality");
+ const updateChunkSize = () => {
+ ReduxStore.dispatch({
+ type: 'scratch-gui/addon-util/SET_SOUND_EDITOR_WAVEFORM_CHUNK_SIZE',
+ chunkSize: chunkSize
+ });
+ };
+ updateChunkSize();
+ addon.self.addEventListener("disabled", () => {
+ chunkSize = 1024;
+ updateChunkSize();
+ });
+ addon.self.addEventListener("reenabled", () => {
+ chunkSize = addon.settings.get("quality");
+ updateChunkSize();
+ });
+});
+
+/***/ }),
+
+/***/ "./src/addons/libraries/common/cs/normalize-color.js":
+/*!***********************************************************!*\
+ !*** ./src/addons/libraries/common/cs/normalize-color.js ***!
+ \***********************************************************/
+/*! exports provided: getHexRegex, normalizeHex */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getHexRegex", function() { return getHexRegex; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "normalizeHex", function() { return normalizeHex; });
+const getHexRegex = () => /^#?[0-9a-fA-F]{3,8}$/;
+const normalizeHex = input => {
+ let hex = String(input);
+ if (!getHexRegex().test(hex)) return "#000000";
+ if (!hex.startsWith("#")) hex = "#".concat(hex);
+ if (hex.length === 4) {
+ const [_, r, g, b] = hex;
+ hex = "#".concat(r).concat(r).concat(g).concat(g).concat(b).concat(b);
+ }
+ return hex.toLowerCase();
+};
+
+/***/ }),
+
+/***/ "./src/addons/libraries/common/cs/rate-limiter.js":
+/*!********************************************************!*\
+ !*** ./src/addons/libraries/common/cs/rate-limiter.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 RateLimiter; });
+class RateLimiter {
+ constructor(wait) {
+ this.timeout = null;
+ this.callback = null;
+ this.wait = wait;
+ }
+ abort() {
+ let call = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ if (call) this.callback();
+ this.timeout = this.callback = null;
+ }
+ }
+ limit(callback) {
+ this.abort(false);
+ this.callback = callback;
+ this.timeout = setTimeout(() => {
+ this.timeout = this.callback = null;
+ callback();
+ }, this.wait);
+ }
+}
+
+/***/ }),
+
+/***/ "./src/addons/libraries/thirdparty/cs/tinycolor-min.js":
+/*!*************************************************************!*\
+ !*** ./src/addons/libraries/thirdparty/cs/tinycolor-min.js ***!
+ \*************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+// TinyColor v1.4.2
+// https://github.com/bgrins/TinyColor
+// Brian Grinstead, MIT License
+// Modified to use ES6 export
+
+const tinycolor = function (Math) {
+ var trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+ tinyCounter = 0,
+ mathRound = Math.round,
+ mathMin = Math.min,
+ mathMax = Math.max,
+ mathRandom = Math.random;
+ function tinycolor(color, opts) {
+ color = color ? color : '';
+ opts = opts || {};
+
+ // If input is already a tinycolor, return itself
+ if (color instanceof tinycolor) {
+ return color;
+ }
+ // If we are called as a function, call using new instead
+ if (!(this instanceof tinycolor)) {
+ return new tinycolor(color, opts);
+ }
+ var rgb = inputToRGB(color);
+ this._originalInput = color, this._r = rgb.r, this._g = rgb.g, this._b = rgb.b, this._a = rgb.a, this._roundA = mathRound(100 * this._a) / 100, this._format = opts.format || rgb.format;
+ this._gradientType = opts.gradientType;
+
+ // Don't let the range of [0,255] come back in [0,1].
+ // Potentially lose a little bit of precision here, but will fix issues where
+ // .5 gets interpreted as half of the total, instead of half of 1
+ // If it was supposed to be 128, this was already taken care of by `inputToRgb`
+ if (this._r < 1) {
+ this._r = mathRound(this._r);
+ }
+ if (this._g < 1) {
+ this._g = mathRound(this._g);
+ }
+ if (this._b < 1) {
+ this._b = mathRound(this._b);
+ }
+ this._ok = rgb.ok;
+ this._tc_id = tinyCounter++;
+ }
+ tinycolor.prototype = {
+ isDark: function isDark() {
+ return this.getBrightness() < 128;
+ },
+ isLight: function isLight() {
+ return !this.isDark();
+ },
+ isValid: function isValid() {
+ return this._ok;
+ },
+ getOriginalInput: function getOriginalInput() {
+ return this._originalInput;
+ },
+ getFormat: function getFormat() {
+ return this._format;
+ },
+ getAlpha: function getAlpha() {
+ return this._a;
+ },
+ getBrightness: function getBrightness() {
+ //http://www.w3.org/TR/AERT#color-contrast
+ var rgb = this.toRgb();
+ return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
+ },
+ getLuminance: function getLuminance() {
+ //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+ var rgb = this.toRgb();
+ var RsRGB, GsRGB, BsRGB, R, G, B;
+ RsRGB = rgb.r / 255;
+ GsRGB = rgb.g / 255;
+ BsRGB = rgb.b / 255;
+ if (RsRGB <= 0.03928) {
+ R = RsRGB / 12.92;
+ } else {
+ R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
+ }
+ if (GsRGB <= 0.03928) {
+ G = GsRGB / 12.92;
+ } else {
+ G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
+ }
+ if (BsRGB <= 0.03928) {
+ B = BsRGB / 12.92;
+ } else {
+ B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
+ }
+ return 0.2126 * R + 0.7152 * G + 0.0722 * B;
+ },
+ setAlpha: function setAlpha(value) {
+ this._a = boundAlpha(value);
+ this._roundA = mathRound(100 * this._a) / 100;
+ return this;
+ },
+ toHsv: function toHsv() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ return {
+ h: hsv.h * 360,
+ s: hsv.s,
+ v: hsv.v,
+ a: this._a
+ };
+ },
+ toHsvString: function toHsvString() {
+ var hsv = rgbToHsv(this._r, this._g, this._b);
+ var h = mathRound(hsv.h * 360),
+ s = mathRound(hsv.s * 100),
+ v = mathRound(hsv.v * 100);
+ return this._a == 1 ? "hsv(" + h + ", " + s + "%, " + v + "%)" : "hsva(" + h + ", " + s + "%, " + v + "%, " + this._roundA + ")";
+ },
+ toHsl: function toHsl() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ return {
+ h: hsl.h * 360,
+ s: hsl.s,
+ l: hsl.l,
+ a: this._a
+ };
+ },
+ toHslString: function toHslString() {
+ var hsl = rgbToHsl(this._r, this._g, this._b);
+ var h = mathRound(hsl.h * 360),
+ s = mathRound(hsl.s * 100),
+ l = mathRound(hsl.l * 100);
+ return this._a == 1 ? "hsl(" + h + ", " + s + "%, " + l + "%)" : "hsla(" + h + ", " + s + "%, " + l + "%, " + this._roundA + ")";
+ },
+ toHex: function toHex(allow3Char) {
+ return rgbToHex(this._r, this._g, this._b, allow3Char);
+ },
+ toHexString: function toHexString(allow3Char) {
+ return '#' + this.toHex(allow3Char);
+ },
+ toHex8: function toHex8(allow4Char) {
+ return rgbaToHex(this._r, this._g, this._b, this._a, allow4Char);
+ },
+ toHex8String: function toHex8String(allow4Char) {
+ return '#' + this.toHex8(allow4Char);
+ },
+ toRgb: function toRgb() {
+ return {
+ r: mathRound(this._r),
+ g: mathRound(this._g),
+ b: mathRound(this._b),
+ a: this._a
+ };
+ },
+ toRgbString: function toRgbString() {
+ return this._a == 1 ? "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" : "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
+ },
+ toPercentageRgb: function toPercentageRgb() {
+ return {
+ r: mathRound(bound01(this._r, 255) * 100) + "%",
+ g: mathRound(bound01(this._g, 255) * 100) + "%",
+ b: mathRound(bound01(this._b, 255) * 100) + "%",
+ a: this._a
+ };
+ },
+ toPercentageRgbString: function toPercentageRgbString() {
+ return this._a == 1 ? "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" : "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
+ },
+ toName: function toName() {
+ if (this._a === 0) {
+ return "transparent";
+ }
+ if (this._a < 1) {
+ return false;
+ }
+ return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
+ },
+ toFilter: function toFilter(secondColor) {
+ var hex8String = '#' + rgbaToArgbHex(this._r, this._g, this._b, this._a);
+ var secondHex8String = hex8String;
+ var gradientType = this._gradientType ? "GradientType = 1, " : "";
+ if (secondColor) {
+ var s = tinycolor(secondColor);
+ secondHex8String = '#' + rgbaToArgbHex(s._r, s._g, s._b, s._a);
+ }
+ return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=" + hex8String + ",endColorstr=" + secondHex8String + ")";
+ },
+ toString: function toString(format) {
+ var formatSet = !!format;
+ format = format || this._format;
+ var formattedString = false;
+ var hasAlpha = this._a < 1 && this._a >= 0;
+ var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "hex4" || format === "hex8" || format === "name");
+ if (needsAlphaFormat) {
+ // Special case for "transparent", all other non-alpha formats
+ // will return rgba when there is transparency.
+ if (format === "name" && this._a === 0) {
+ return this.toName();
+ }
+ return this.toRgbString();
+ }
+ if (format === "rgb") {
+ formattedString = this.toRgbString();
+ }
+ if (format === "prgb") {
+ formattedString = this.toPercentageRgbString();
+ }
+ if (format === "hex" || format === "hex6") {
+ formattedString = this.toHexString();
+ }
+ if (format === "hex3") {
+ formattedString = this.toHexString(true);
+ }
+ if (format === "hex4") {
+ formattedString = this.toHex8String(true);
+ }
+ if (format === "hex8") {
+ formattedString = this.toHex8String();
+ }
+ if (format === "name") {
+ formattedString = this.toName();
+ }
+ if (format === "hsl") {
+ formattedString = this.toHslString();
+ }
+ if (format === "hsv") {
+ formattedString = this.toHsvString();
+ }
+ return formattedString || this.toHexString();
+ },
+ clone: function clone() {
+ return tinycolor(this.toString());
+ },
+ _applyModification: function _applyModification(fn, args) {
+ var color = fn.apply(null, [this].concat([].slice.call(args)));
+ this._r = color._r;
+ this._g = color._g;
+ this._b = color._b;
+ this.setAlpha(color._a);
+ return this;
+ },
+ lighten: function lighten() {
+ return this._applyModification(_lighten, arguments);
+ },
+ brighten: function brighten() {
+ return this._applyModification(_brighten, arguments);
+ },
+ darken: function darken() {
+ return this._applyModification(_darken, arguments);
+ },
+ desaturate: function desaturate() {
+ return this._applyModification(_desaturate, arguments);
+ },
+ saturate: function saturate() {
+ return this._applyModification(_saturate, arguments);
+ },
+ greyscale: function greyscale() {
+ return this._applyModification(_greyscale, arguments);
+ },
+ spin: function spin() {
+ return this._applyModification(_spin, arguments);
+ },
+ _applyCombination: function _applyCombination(fn, args) {
+ return fn.apply(null, [this].concat([].slice.call(args)));
+ },
+ analogous: function analogous() {
+ return this._applyCombination(_analogous, arguments);
+ },
+ complement: function complement() {
+ return this._applyCombination(_complement, arguments);
+ },
+ monochromatic: function monochromatic() {
+ return this._applyCombination(_monochromatic, arguments);
+ },
+ splitcomplement: function splitcomplement() {
+ return this._applyCombination(_splitcomplement, arguments);
+ },
+ triad: function triad() {
+ return this._applyCombination(_triad, arguments);
+ },
+ tetrad: function tetrad() {
+ return this._applyCombination(_tetrad, arguments);
+ }
+ };
+
+ // If input is an object, force 1 into "1.0" to handle ratios properly
+ // String input requires "1.0" as input, so 1 will be treated as 1
+ tinycolor.fromRatio = function (color, opts) {
+ if (typeof color == "object") {
+ var newColor = {};
+ for (var i in color) {
+ if (color.hasOwnProperty(i)) {
+ if (i === "a") {
+ newColor[i] = color[i];
+ } else {
+ newColor[i] = convertToPercentage(color[i]);
+ }
+ }
+ }
+ color = newColor;
+ }
+ return tinycolor(color, opts);
+ };
+
+ // Given a string or object, convert that input to RGB
+ // Possible string inputs:
+ //
+ // "red"
+ // "#f00" or "f00"
+ // "#ff0000" or "ff0000"
+ // "#ff000000" or "ff000000"
+ // "rgb 255 0 0" or "rgb (255, 0, 0)"
+ // "rgb 1.0 0 0" or "rgb (1, 0, 0)"
+ // "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
+ // "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
+ // "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
+ // "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
+ // "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
+ //
+ function inputToRGB(color) {
+ var rgb = {
+ r: 0,
+ g: 0,
+ b: 0
+ };
+ var a = 1;
+ var s = null;
+ var v = null;
+ var l = null;
+ var ok = false;
+ var format = false;
+ if (typeof color == "string") {
+ color = stringInputToObject(color);
+ }
+ if (typeof color == "object") {
+ if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
+ rgb = rgbToRgb(color.r, color.g, color.b);
+ ok = true;
+ format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
+ s = convertToPercentage(color.s);
+ v = convertToPercentage(color.v);
+ rgb = hsvToRgb(color.h, s, v);
+ ok = true;
+ format = "hsv";
+ } else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
+ s = convertToPercentage(color.s);
+ l = convertToPercentage(color.l);
+ rgb = hslToRgb(color.h, s, l);
+ ok = true;
+ format = "hsl";
+ }
+ if (color.hasOwnProperty("a")) {
+ a = color.a;
+ }
+ }
+ a = boundAlpha(a);
+ return {
+ ok: ok,
+ format: color.format || format,
+ r: mathMin(255, mathMax(rgb.r, 0)),
+ g: mathMin(255, mathMax(rgb.g, 0)),
+ b: mathMin(255, mathMax(rgb.b, 0)),
+ a: a
+ };
+ }
+
+ // Conversion Functions
+ // --------------------
+
+ // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
+ //
+
+ // `rgbToRgb`
+ // Handle bounds / percentage checking to conform to CSS color spec
+ //
+ // *Assumes:* r, g, b in [0, 255] or [0, 1]
+ // *Returns:* { r, g, b } in [0, 255]
+ function rgbToRgb(r, g, b) {
+ return {
+ r: bound01(r, 255) * 255,
+ g: bound01(g, 255) * 255,
+ b: bound01(b, 255) * 255
+ };
+ }
+
+ // `rgbToHsl`
+ // Converts an RGB color value to HSL.
+ // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
+ // *Returns:* { h, s, l } in [0,1]
+ function rgbToHsl(r, g, b) {
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+ var max = mathMax(r, g, b),
+ min = mathMin(r, g, b);
+ var h,
+ s,
+ l = (max + min) / 2;
+ if (max == min) {
+ h = s = 0; // achromatic
+ } else {
+ var d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+ return {
+ h: h,
+ s: s,
+ l: l
+ };
+ }
+
+ // `hslToRgb`
+ // Converts an HSL color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hslToRgb(h, s, l) {
+ var r, g, b;
+ h = bound01(h, 360);
+ s = bound01(s, 100);
+ l = bound01(l, 100);
+ function hue2rgb(p, q, t) {
+ if (t < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
+ if (t < 1 / 2) return q;
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+ return p;
+ }
+ if (s === 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hue2rgb(p, q, h + 1 / 3);
+ g = hue2rgb(p, q, h);
+ b = hue2rgb(p, q, h - 1 / 3);
+ }
+ return {
+ r: r * 255,
+ g: g * 255,
+ b: b * 255
+ };
+ }
+
+ // `rgbToHsv`
+ // Converts an RGB color value to HSV
+ // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
+ // *Returns:* { h, s, v } in [0,1]
+ function rgbToHsv(r, g, b) {
+ r = bound01(r, 255);
+ g = bound01(g, 255);
+ b = bound01(b, 255);
+ var max = mathMax(r, g, b),
+ min = mathMin(r, g, b);
+ var h,
+ s,
+ v = max;
+ var d = max - min;
+ s = max === 0 ? 0 : d / max;
+ if (max == min) {
+ h = 0; // achromatic
+ } else {
+ switch (max) {
+ case r:
+ h = (g - b) / d + (g < b ? 6 : 0);
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h /= 6;
+ }
+ return {
+ h: h,
+ s: s,
+ v: v
+ };
+ }
+
+ // `hsvToRgb`
+ // Converts an HSV color value to RGB.
+ // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
+ // *Returns:* { r, g, b } in the set [0, 255]
+ function hsvToRgb(h, s, v) {
+ h = bound01(h, 360) * 6;
+ s = bound01(s, 100);
+ v = bound01(v, 100);
+ var i = Math.floor(h),
+ f = h - i,
+ p = v * (1 - s),
+ q = v * (1 - f * s),
+ t = v * (1 - (1 - f) * s),
+ mod = i % 6,
+ r = [v, q, p, p, t, v][mod],
+ g = [t, v, v, q, p, p][mod],
+ b = [p, p, t, v, v, q][mod];
+ return {
+ r: r * 255,
+ g: g * 255,
+ b: b * 255
+ };
+ }
+
+ // `rgbToHex`
+ // Converts an RGB color to hex
+ // Assumes r, g, and b are contained in the set [0, 255]
+ // Returns a 3 or 6 character hex
+ function rgbToHex(r, g, b, allow3Char) {
+ var hex = [pad2(mathRound(r).toString(16)), pad2(mathRound(g).toString(16)), pad2(mathRound(b).toString(16))];
+
+ // Return a 3 character hex if possible
+ if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
+ }
+ return hex.join("");
+ }
+
+ // `rgbaToHex`
+ // Converts an RGBA color plus alpha transparency to hex
+ // Assumes r, g, b are contained in the set [0, 255] and
+ // a in [0, 1]. Returns a 4 or 8 character rgba hex
+ function rgbaToHex(r, g, b, a, allow4Char) {
+ var hex = [pad2(mathRound(r).toString(16)), pad2(mathRound(g).toString(16)), pad2(mathRound(b).toString(16)), pad2(convertDecimalToHex(a))];
+
+ // Return a 4 character hex if possible
+ if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) {
+ return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
+ }
+ return hex.join("");
+ }
+
+ // `rgbaToArgbHex`
+ // Converts an RGBA color to an ARGB Hex8 string
+ // Rarely used, but required for "toFilter()"
+ function rgbaToArgbHex(r, g, b, a) {
+ var hex = [pad2(convertDecimalToHex(a)), pad2(mathRound(r).toString(16)), pad2(mathRound(g).toString(16)), pad2(mathRound(b).toString(16))];
+ return hex.join("");
+ }
+
+ // `equals`
+ // Can be called with any tinycolor input
+ tinycolor.equals = function (color1, color2) {
+ if (!color1 || !color2) {
+ return false;
+ }
+ return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
+ };
+ tinycolor.random = function () {
+ return tinycolor.fromRatio({
+ r: mathRandom(),
+ g: mathRandom(),
+ b: mathRandom()
+ });
+ };
+
+ // Modification Functions
+ // ----------------------
+ // Thanks to less.js for some of the basics here
+ //
+
+ function _desaturate(color, amount) {
+ amount = amount === 0 ? 0 : amount || 10;
+ var hsl = tinycolor(color).toHsl();
+ hsl.s -= amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+ function _saturate(color, amount) {
+ amount = amount === 0 ? 0 : amount || 10;
+ var hsl = tinycolor(color).toHsl();
+ hsl.s += amount / 100;
+ hsl.s = clamp01(hsl.s);
+ return tinycolor(hsl);
+ }
+ function _greyscale(color) {
+ return tinycolor(color).desaturate(100);
+ }
+ function _lighten(color, amount) {
+ amount = amount === 0 ? 0 : amount || 10;
+ var hsl = tinycolor(color).toHsl();
+ hsl.l += amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+ function _brighten(color, amount) {
+ amount = amount === 0 ? 0 : amount || 10;
+ var rgb = tinycolor(color).toRgb();
+ rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * -(amount / 100))));
+ rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * -(amount / 100))));
+ rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * -(amount / 100))));
+ return tinycolor(rgb);
+ }
+ function _darken(color, amount) {
+ amount = amount === 0 ? 0 : amount || 10;
+ var hsl = tinycolor(color).toHsl();
+ hsl.l -= amount / 100;
+ hsl.l = clamp01(hsl.l);
+ return tinycolor(hsl);
+ }
+
+ // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
+ // Values outside of this range will be wrapped into this range.
+ function _spin(color, amount) {
+ var hsl = tinycolor(color).toHsl();
+ var hue = (hsl.h + amount) % 360;
+ hsl.h = hue < 0 ? 360 + hue : hue;
+ return tinycolor(hsl);
+ }
+
+ // Combination Functions
+ // ---------------------
+ // Thanks to jQuery xColor for some of the ideas behind these
+ //
+
+ function _complement(color) {
+ var hsl = tinycolor(color).toHsl();
+ hsl.h = (hsl.h + 180) % 360;
+ return tinycolor(hsl);
+ }
+ function _triad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [tinycolor(color), tinycolor({
+ h: (h + 120) % 360,
+ s: hsl.s,
+ l: hsl.l
+ }), tinycolor({
+ h: (h + 240) % 360,
+ s: hsl.s,
+ l: hsl.l
+ })];
+ }
+ function _tetrad(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [tinycolor(color), tinycolor({
+ h: (h + 90) % 360,
+ s: hsl.s,
+ l: hsl.l
+ }), tinycolor({
+ h: (h + 180) % 360,
+ s: hsl.s,
+ l: hsl.l
+ }), tinycolor({
+ h: (h + 270) % 360,
+ s: hsl.s,
+ l: hsl.l
+ })];
+ }
+ function _splitcomplement(color) {
+ var hsl = tinycolor(color).toHsl();
+ var h = hsl.h;
+ return [tinycolor(color), tinycolor({
+ h: (h + 72) % 360,
+ s: hsl.s,
+ l: hsl.l
+ }), tinycolor({
+ h: (h + 216) % 360,
+ s: hsl.s,
+ l: hsl.l
+ })];
+ }
+ function _analogous(color, results, slices) {
+ results = results || 6;
+ slices = slices || 30;
+ var hsl = tinycolor(color).toHsl();
+ var part = 360 / slices;
+ var ret = [tinycolor(color)];
+ for (hsl.h = (hsl.h - (part * results >> 1) + 720) % 360; --results;) {
+ hsl.h = (hsl.h + part) % 360;
+ ret.push(tinycolor(hsl));
+ }
+ return ret;
+ }
+ function _monochromatic(color, results) {
+ results = results || 6;
+ var hsv = tinycolor(color).toHsv();
+ var h = hsv.h,
+ s = hsv.s,
+ v = hsv.v;
+ var ret = [];
+ var modification = 1 / results;
+ while (results--) {
+ ret.push(tinycolor({
+ h: h,
+ s: s,
+ v: v
+ }));
+ v = (v + modification) % 1;
+ }
+ return ret;
+ }
+
+ // Utility Functions
+ // ---------------------
+
+ tinycolor.mix = function (color1, color2, amount) {
+ amount = amount === 0 ? 0 : amount || 50;
+ var rgb1 = tinycolor(color1).toRgb();
+ var rgb2 = tinycolor(color2).toRgb();
+ var p = amount / 100;
+ var rgba = {
+ r: (rgb2.r - rgb1.r) * p + rgb1.r,
+ g: (rgb2.g - rgb1.g) * p + rgb1.g,
+ b: (rgb2.b - rgb1.b) * p + rgb1.b,
+ a: (rgb2.a - rgb1.a) * p + rgb1.a
+ };
+ return tinycolor(rgba);
+ };
+
+ // Readability Functions
+ // ---------------------
+ // false
+ // tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false
+ tinycolor.isReadable = function (color1, color2, wcag2) {
+ var readability = tinycolor.readability(color1, color2);
+ var wcag2Parms, out;
+ out = false;
+ wcag2Parms = validateWCAG2Parms(wcag2);
+ switch (wcag2Parms.level + wcag2Parms.size) {
+ case "AAsmall":
+ case "AAAlarge":
+ out = readability >= 4.5;
+ break;
+ case "AAlarge":
+ out = readability >= 3;
+ break;
+ case "AAAsmall":
+ out = readability >= 7;
+ break;
+ }
+ return out;
+ };
+
+ // `mostReadable`
+ // Given a base color and a list of possible foreground or background
+ // colors for that base, returns the most readable color.
+ // Optionally returns Black or White if the most readable color is unreadable.
+ // *Example*
+ // tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255"
+ // tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString(); // "#ffffff"
+ // tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3"
+ // tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff"
+ tinycolor.mostReadable = function (baseColor, colorList, args) {
+ var bestColor = null;
+ var bestScore = 0;
+ var readability;
+ var includeFallbackColors, level, size;
+ args = args || {};
+ includeFallbackColors = args.includeFallbackColors;
+ level = args.level;
+ size = args.size;
+ for (var i = 0; i < colorList.length; i++) {
+ readability = tinycolor.readability(baseColor, colorList[i]);
+ if (readability > bestScore) {
+ bestScore = readability;
+ bestColor = tinycolor(colorList[i]);
+ }
+ }
+ if (tinycolor.isReadable(baseColor, bestColor, {
+ "level": level,
+ "size": size
+ }) || !includeFallbackColors) {
+ return bestColor;
+ } else {
+ args.includeFallbackColors = false;
+ return tinycolor.mostReadable(baseColor, ["#fff", "#000"], args);
+ }
+ };
+
+ // Big List of Colors
+ // ------------------
+ //
+ var names = tinycolor.names = {
+ aliceblue: "f0f8ff",
+ antiquewhite: "faebd7",
+ aqua: "0ff",
+ aquamarine: "7fffd4",
+ azure: "f0ffff",
+ beige: "f5f5dc",
+ bisque: "ffe4c4",
+ black: "000",
+ blanchedalmond: "ffebcd",
+ blue: "00f",
+ blueviolet: "8a2be2",
+ brown: "a52a2a",
+ burlywood: "deb887",
+ burntsienna: "ea7e5d",
+ cadetblue: "5f9ea0",
+ chartreuse: "7fff00",
+ chocolate: "d2691e",
+ coral: "ff7f50",
+ cornflowerblue: "6495ed",
+ cornsilk: "fff8dc",
+ crimson: "dc143c",
+ cyan: "0ff",
+ darkblue: "00008b",
+ darkcyan: "008b8b",
+ darkgoldenrod: "b8860b",
+ darkgray: "a9a9a9",
+ darkgreen: "006400",
+ darkgrey: "a9a9a9",
+ darkkhaki: "bdb76b",
+ darkmagenta: "8b008b",
+ darkolivegreen: "556b2f",
+ darkorange: "ff8c00",
+ darkorchid: "9932cc",
+ darkred: "8b0000",
+ darksalmon: "e9967a",
+ darkseagreen: "8fbc8f",
+ darkslateblue: "483d8b",
+ darkslategray: "2f4f4f",
+ darkslategrey: "2f4f4f",
+ darkturquoise: "00ced1",
+ darkviolet: "9400d3",
+ deeppink: "ff1493",
+ deepskyblue: "00bfff",
+ dimgray: "696969",
+ dimgrey: "696969",
+ dodgerblue: "1e90ff",
+ firebrick: "b22222",
+ floralwhite: "fffaf0",
+ forestgreen: "228b22",
+ fuchsia: "f0f",
+ gainsboro: "dcdcdc",
+ ghostwhite: "f8f8ff",
+ gold: "ffd700",
+ goldenrod: "daa520",
+ gray: "808080",
+ green: "008000",
+ greenyellow: "adff2f",
+ grey: "808080",
+ honeydew: "f0fff0",
+ hotpink: "ff69b4",
+ indianred: "cd5c5c",
+ indigo: "4b0082",
+ ivory: "fffff0",
+ khaki: "f0e68c",
+ lavender: "e6e6fa",
+ lavenderblush: "fff0f5",
+ lawngreen: "7cfc00",
+ lemonchiffon: "fffacd",
+ lightblue: "add8e6",
+ lightcoral: "f08080",
+ lightcyan: "e0ffff",
+ lightgoldenrodyellow: "fafad2",
+ lightgray: "d3d3d3",
+ lightgreen: "90ee90",
+ lightgrey: "d3d3d3",
+ lightpink: "ffb6c1",
+ lightsalmon: "ffa07a",
+ lightseagreen: "20b2aa",
+ lightskyblue: "87cefa",
+ lightslategray: "789",
+ lightslategrey: "789",
+ lightsteelblue: "b0c4de",
+ lightyellow: "ffffe0",
+ lime: "0f0",
+ limegreen: "32cd32",
+ linen: "faf0e6",
+ magenta: "f0f",
+ maroon: "800000",
+ mediumaquamarine: "66cdaa",
+ mediumblue: "0000cd",
+ mediumorchid: "ba55d3",
+ mediumpurple: "9370db",
+ mediumseagreen: "3cb371",
+ mediumslateblue: "7b68ee",
+ mediumspringgreen: "00fa9a",
+ mediumturquoise: "48d1cc",
+ mediumvioletred: "c71585",
+ midnightblue: "191970",
+ mintcream: "f5fffa",
+ mistyrose: "ffe4e1",
+ moccasin: "ffe4b5",
+ navajowhite: "ffdead",
+ navy: "000080",
+ oldlace: "fdf5e6",
+ olive: "808000",
+ olivedrab: "6b8e23",
+ orange: "ffa500",
+ orangered: "ff4500",
+ orchid: "da70d6",
+ palegoldenrod: "eee8aa",
+ palegreen: "98fb98",
+ paleturquoise: "afeeee",
+ palevioletred: "db7093",
+ papayawhip: "ffefd5",
+ peachpuff: "ffdab9",
+ peru: "cd853f",
+ pink: "ffc0cb",
+ plum: "dda0dd",
+ powderblue: "b0e0e6",
+ purple: "800080",
+ rebeccapurple: "663399",
+ red: "f00",
+ rosybrown: "bc8f8f",
+ royalblue: "4169e1",
+ saddlebrown: "8b4513",
+ salmon: "fa8072",
+ sandybrown: "f4a460",
+ seagreen: "2e8b57",
+ seashell: "fff5ee",
+ sienna: "a0522d",
+ silver: "c0c0c0",
+ skyblue: "87ceeb",
+ slateblue: "6a5acd",
+ slategray: "708090",
+ slategrey: "708090",
+ snow: "fffafa",
+ springgreen: "00ff7f",
+ steelblue: "4682b4",
+ tan: "d2b48c",
+ teal: "008080",
+ thistle: "d8bfd8",
+ tomato: "ff6347",
+ turquoise: "40e0d0",
+ violet: "ee82ee",
+ wheat: "f5deb3",
+ white: "fff",
+ whitesmoke: "f5f5f5",
+ yellow: "ff0",
+ yellowgreen: "9acd32"
+ };
+
+ // Make it easy to access colors via `hexNames[hex]`
+ var hexNames = tinycolor.hexNames = flip(names);
+
+ // Utilities
+ // ---------
+
+ // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
+ function flip(o) {
+ var flipped = {};
+ for (var i in o) {
+ if (o.hasOwnProperty(i)) {
+ flipped[o[i]] = i;
+ }
+ }
+ return flipped;
+ }
+
+ // Return a valid alpha value [0,1] with all invalid values being set to 1
+ function boundAlpha(a) {
+ a = parseFloat(a);
+ if (isNaN(a) || a < 0 || a > 1) {
+ a = 1;
+ }
+ return a;
+ }
+
+ // Take input from [0, n] and return it as [0, 1]
+ function bound01(n, max) {
+ if (isOnePointZero(n)) {
+ n = "100%";
+ }
+ var processPercent = isPercentage(n);
+ n = mathMin(max, mathMax(0, parseFloat(n)));
+
+ // Automatically convert percentage into number
+ if (processPercent) {
+ n = parseInt(n * max, 10) / 100;
+ }
+
+ // Handle floating point rounding errors
+ if (Math.abs(n - max) < 0.000001) {
+ return 1;
+ }
+
+ // Convert into [0, 1] range if it isn't already
+ return n % max / parseFloat(max);
+ }
+
+ // Force a number between 0 and 1
+ function clamp01(val) {
+ return mathMin(1, mathMax(0, val));
+ }
+
+ // Parse a base-16 hex value into a base-10 integer
+ function parseIntFromHex(val) {
+ return parseInt(val, 16);
+ }
+
+ // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
+ //
+ function isOnePointZero(n) {
+ return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
+ }
+
+ // Check to see if string passed in is a percentage
+ function isPercentage(n) {
+ return typeof n === "string" && n.indexOf('%') != -1;
+ }
+
+ // Force a hex value to have 2 characters
+ function pad2(c) {
+ return c.length == 1 ? '0' + c : '' + c;
+ }
+
+ // Replace a decimal with it's percentage value
+ function convertToPercentage(n) {
+ if (n <= 1) {
+ n = n * 100 + "%";
+ }
+ return n;
+ }
+
+ // Converts a decimal to a hex value
+ function convertDecimalToHex(d) {
+ return Math.round(parseFloat(d) * 255).toString(16);
+ }
+ // Converts a hex value to a decimal
+ function convertHexToDecimal(h) {
+ return parseIntFromHex(h) / 255;
+ }
+ var matchers = function () {
+ //
+ var CSS_INTEGER = "[-\\+]?\\d+%?";
+
+ //
+ var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
+
+ // Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
+ var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
+
+ // Actual matching.
+ // Parentheses and commas are optional, but not required.
+ // Whitespace can take the place of commas or opening paren
+ var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+ var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+ return {
+ CSS_UNIT: new RegExp(CSS_UNIT),
+ rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
+ rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
+ hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
+ hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
+ hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
+ hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
+ hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+ hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
+ hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+ hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
+ };
+ }();
+
+ // `isValidCSSUnit`
+ // Take in a single string / number and check to see if it looks like a CSS unit
+ // (see `matchers` above for definition).
+ function isValidCSSUnit(color) {
+ return !!matchers.CSS_UNIT.exec(color);
+ }
+
+ // `stringInputToObject`
+ // Permissive string parsing. Take in a number of formats, and output an object
+ // based on detected format. Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
+ function stringInputToObject(color) {
+ color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase();
+ var named = false;
+ if (names[color]) {
+ color = names[color];
+ named = true;
+ } else if (color == 'transparent') {
+ return {
+ r: 0,
+ g: 0,
+ b: 0,
+ a: 0,
+ format: "name"
+ };
+ }
+
+ // Try to match string input using regular expressions.
+ // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
+ // Just return an object and let the conversion functions handle that.
+ // This way the result will be the same whether the tinycolor is initialized with string or object.
+ var match;
+ if (match = matchers.rgb.exec(color)) {
+ return {
+ r: match[1],
+ g: match[2],
+ b: match[3]
+ };
+ }
+ if (match = matchers.rgba.exec(color)) {
+ return {
+ r: match[1],
+ g: match[2],
+ b: match[3],
+ a: match[4]
+ };
+ }
+ if (match = matchers.hsl.exec(color)) {
+ return {
+ h: match[1],
+ s: match[2],
+ l: match[3]
+ };
+ }
+ if (match = matchers.hsla.exec(color)) {
+ return {
+ h: match[1],
+ s: match[2],
+ l: match[3],
+ a: match[4]
+ };
+ }
+ if (match = matchers.hsv.exec(color)) {
+ return {
+ h: match[1],
+ s: match[2],
+ v: match[3]
+ };
+ }
+ if (match = matchers.hsva.exec(color)) {
+ return {
+ h: match[1],
+ s: match[2],
+ v: match[3],
+ a: match[4]
+ };
+ }
+ if (match = matchers.hex8.exec(color)) {
+ return {
+ r: parseIntFromHex(match[1]),
+ g: parseIntFromHex(match[2]),
+ b: parseIntFromHex(match[3]),
+ a: convertHexToDecimal(match[4]),
+ format: named ? "name" : "hex8"
+ };
+ }
+ if (match = matchers.hex6.exec(color)) {
+ return {
+ r: parseIntFromHex(match[1]),
+ g: parseIntFromHex(match[2]),
+ b: parseIntFromHex(match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+ if (match = matchers.hex4.exec(color)) {
+ return {
+ r: parseIntFromHex(match[1] + '' + match[1]),
+ g: parseIntFromHex(match[2] + '' + match[2]),
+ b: parseIntFromHex(match[3] + '' + match[3]),
+ a: convertHexToDecimal(match[4] + '' + match[4]),
+ format: named ? "name" : "hex8"
+ };
+ }
+ if (match = matchers.hex3.exec(color)) {
+ return {
+ r: parseIntFromHex(match[1] + '' + match[1]),
+ g: parseIntFromHex(match[2] + '' + match[2]),
+ b: parseIntFromHex(match[3] + '' + match[3]),
+ format: named ? "name" : "hex"
+ };
+ }
+ return false;
+ }
+ function validateWCAG2Parms(parms) {
+ // return valid WCAG2 parms for isReadable.
+ // If input parms are invalid, return {"level":"AA", "size":"small"}
+ var level, size;
+ parms = parms || {
+ "level": "AA",
+ "size": "small"
+ };
+ level = (parms.level || "AA").toUpperCase();
+ size = (parms.size || "small").toLowerCase();
+ if (level !== "AA" && level !== "AAA") {
+ level = "AA";
+ }
+ if (size !== "small" && size !== "large") {
+ size = "small";
+ }
+ return {
+ "level": level,
+ "size": size
+ };
+ }
+
+ /*// Node: Export function
+ if (typeof module !== "undefined" && module.exports) {
+ module.exports = tinycolor;
+ }
+ // AMD/requirejs: Define the module
+ else if (typeof define === 'function' && define.amd) {
+ define(function () {return tinycolor;});
+ }
+ // Browser: Expose to window
+ else {
+ window.tinycolor = tinycolor;
+ }*/
+
+ return tinycolor;
+}(Math);
+/* harmony default export */ __webpack_exports__["default"] = (tinycolor);
+
+/***/ })
+
+}]);
+//# sourceMappingURL=addon-default-entry.js.map
\ No newline at end of file
diff --git a/js/addon-default-entry.js.map b/js/addon-default-entry.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..9a31df516ffc082efb9cb1961c27991a23e3dc12
--- /dev/null
+++ b/js/addon-default-entry.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"js/addon-default-entry.js","sources":["webpack://GUI/./src/addons/addons/color-picker/style.css","webpack://GUI/./src/addons/addons/editor-comment-previews/userstyle.css","webpack://GUI/./src/addons/addons/editor-searchable-dropdowns/userscript.css","webpack://GUI/./src/addons/addons/editor-theme3/compatibility.css","webpack://GUI/./src/addons/addons/find-bar/userstyle.css","webpack://GUI/./src/addons/addons/folders/style.css","webpack://GUI/./src/addons/addons/middle-click-popup/userstyle.css","webpack://GUI/./src/addons/addons/multi-tab-code/userstyle.css","webpack://GUI/./src/addons/addons/onion-skinning/style.css","webpack://GUI/./src/addons/addons/pick-colors-from-stage/style.css","webpack://GUI/./src/addons/addons/reorder-custom-inputs/arrows.css","webpack://GUI/./src/addons/addons/toolbox-category-drag/userstyle.css","webpack://GUI/./src/addons/addons/toolbox-full-blocks-on-hover/userstyle.css","webpack://GUI/./src/addons/addons/editor-devtools/icon--close.svg","webpack://GUI/./src/addons/addons/folders/folder.svg","webpack://GUI/./src/addons/addons/multi-tab-code/add.svg","webpack://GUI/./src/addons/addons/onion-skinning/decrement.svg","webpack://GUI/./src/addons/addons/onion-skinning/increment.svg","webpack://GUI/./src/addons/addons/onion-skinning/settings.svg","webpack://GUI/./src/addons/addons/onion-skinning/toggle.svg","webpack://GUI/./src/addons/addons/bitmap-copy/_runtime_entry.js","webpack://GUI/./src/addons/addons/bitmap-copy/userscript.js","webpack://GUI/./src/addons/addons/block-cherry-picking/_runtime_entry.js","webpack://GUI/./src/addons/addons/block-cherry-picking/userscript.js","webpack://GUI/./src/addons/addons/block-duplicate/_runtime_entry.js","webpack://GUI/./src/addons/addons/block-duplicate/module.js","webpack://GUI/./src/addons/addons/block-duplicate/userscript.js","webpack://GUI/./src/addons/addons/block-switching/_runtime_entry.js","webpack://GUI/./src/addons/addons/block-switching/userscript.js","webpack://GUI/./src/addons/addons/color-picker/_runtime_entry.js","webpack://GUI/./src/addons/addons/color-picker/code-editor.js","webpack://GUI/./src/addons/addons/color-picker/userscript.js","webpack://GUI/./src/addons/addons/editor-animations/_runtime_entry.js","webpack://GUI/./src/addons/addons/editor-animations/userscript.js","webpack://GUI/./src/addons/addons/editor-block-chomping/_runtime_entry.js","webpack://GUI/./src/addons/addons/editor-block-chomping/userscript.js","webpack://GUI/./src/addons/addons/editor-comment-previews/_runtime_entry.js","webpack://GUI/./src/addons/addons/editor-comment-previews/userscript.js","webpack://GUI/./src/addons/addons/editor-devtools/DevTools.js","webpack://GUI/./src/addons/addons/editor-devtools/DomHelpers.js","webpack://GUI/./src/addons/addons/editor-devtools/UndoGroup.js","webpack://GUI/./src/addons/addons/editor-devtools/_runtime_entry.js","webpack://GUI/./src/addons/addons/editor-devtools/userscript.js","webpack://GUI/./src/addons/addons/editor-searchable-dropdowns/_runtime_entry.js","webpack://GUI/./src/addons/addons/editor-searchable-dropdowns/userscript.js","webpack://GUI/./src/addons/addons/find-bar/_runtime_entry.js","webpack://GUI/./src/addons/addons/find-bar/blockly/BlockItem.js","webpack://GUI/./src/addons/addons/find-bar/userscript.js","webpack://GUI/./src/addons/addons/folders/_runtime_entry.js","webpack://GUI/./src/addons/addons/folders/userscript.js","webpack://GUI/./src/addons/addons/middle-click-popup/BlockRenderer.js","webpack://GUI/./src/addons/addons/middle-click-popup/BlockTypeInfo.js","webpack://GUI/./src/addons/addons/middle-click-popup/WorkspaceQuerier.js","webpack://GUI/./src/addons/addons/middle-click-popup/_runtime_entry.js","webpack://GUI/./src/addons/addons/middle-click-popup/module.js","webpack://GUI/./src/addons/addons/middle-click-popup/userscript.js","webpack://GUI/./src/addons/addons/move-to-top-bottom/_runtime_entry.js","webpack://GUI/./src/addons/addons/move-to-top-bottom/userscript.js","webpack://GUI/./src/addons/addons/multi-tab-code/_runtime_entry.js","webpack://GUI/./src/addons/addons/multi-tab-code/userscript.js","webpack://GUI/./src/addons/addons/onion-skinning/_runtime_entry.js","webpack://GUI/./src/addons/addons/onion-skinning/userscript.js","webpack://GUI/./src/addons/addons/paint-default-smoothing/_runtime_entry.js","webpack://GUI/./src/addons/addons/paint-default-smoothing/userscript.js","webpack://GUI/./src/addons/addons/paint-gradient-maker/_runtime_entry.js","webpack://GUI/./src/addons/addons/paint-gradient-maker/userscript.js","webpack://GUI/./src/addons/addons/paint-rounded-rect-seperate/_runtime_entry.js","webpack://GUI/./src/addons/addons/paint-rounded-rect-seperate/userscript.js","webpack://GUI/./src/addons/addons/paint-tool-panel/_runtime_entry.js","webpack://GUI/./src/addons/addons/paint-tool-panel/userscript.js","webpack://GUI/./src/addons/addons/pick-colors-from-stage/_runtime_entry.js","webpack://GUI/./src/addons/addons/pick-colors-from-stage/userscript.js","webpack://GUI/./src/addons/addons/reorder-custom-inputs/_runtime_entry.js","webpack://GUI/./src/addons/addons/reorder-custom-inputs/modified-funcs.js","webpack://GUI/./src/addons/addons/reorder-custom-inputs/userscript.js","webpack://GUI/./src/addons/addons/sounds-newgrounds-button/_runtime_entry.js","webpack://GUI/./src/addons/addons/sounds-newgrounds-button/userscript.js","webpack://GUI/./src/addons/addons/toolbox-category-drag/_runtime_entry.js","webpack://GUI/./src/addons/addons/toolbox-category-drag/userscript.js","webpack://GUI/./src/addons/addons/toolbox-full-blocks-on-hover/_runtime_entry.js","webpack://GUI/./src/addons/addons/waveform-chunk-size/_runtime_entry.js","webpack://GUI/./src/addons/addons/waveform-chunk-size/userscript.js","webpack://GUI/./src/addons/libraries/common/cs/normalize-color.js","webpack://GUI/./src/addons/libraries/common/cs/rate-limiter.js","webpack://GUI/./src/addons/libraries/thirdparty/cs/tinycolor-min.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, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \"/* 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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\nexports.i(require(\"-!../../../../node_modules/css-loader/index.js!../editor-theme3/compatibility.css\"), \"\");\n\n// module\nexports.push([module.id, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\nexports.i(require(\"-!../../../../node_modules/css-loader/index.js!../editor-theme3/compatibility.css\"), \"\");\n\n// module\nexports.push([module.id, \".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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".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}\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".sa-onion-button {\\n position: relative;\\n}\\n.sa-onion-button:focus-within {\\n background-color: hsla(259, 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: #5100ff;\\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\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".sa-stage-color-picker-picking [class^=\\\"stage_color-picker-background_\\\"] {\\n display: none;\\n}\\n\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".blocklyTextShiftArrow {\\n position: absolute;\\n top: -50px;\\n left: 50%;\\n margin-left: -12.5px;\\n cursor: pointer;\\n}\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \"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}\", \"\"]);\n\n// exports\n","exports = module.exports = require(\"../../../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \"[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}\", \"\"]);\n\n// exports\n","export default \"data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3LjQ4IDcuNDgiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lO3N0cm9rZTojZmZmO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MnB4O308L3N0eWxlPjwvZGVmcz48dGl0bGU+aWNvbi0tYWRkPC90aXRsZT48bGluZSBjbGFzcz0iY2xzLTEiIHgxPSIzLjc0IiB5MT0iNi40OCIgeDI9IjMuNzQiIHkyPSIxIi8+PGxpbmUgY2xhc3M9ImNscy0xIiB4MT0iMSIgeTE9IjMuNzQiIHgyPSI2LjQ4IiB5Mj0iMy43NCIvPjwvc3ZnPg==\"","export default \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjxwYXRoIGQ9Ik0xMCA0SDRjLTEuMSAwLTEuOTkuOS0xLjk5IDJMMiAxOGMwIDEuMS45IDIgMiAyaDE2YzEuMSAwIDItLjkgMi0yVjhjMC0xLjEtLjktMi0yLTJoLThsLTItMnoiLz48L3N2Zz4=\"","export default \"data:image/svg+xml;base64,PHN2ZwogICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICAgd2lkdGg9IjE0IgogICAgaGVpZ2h0PSIxNCIKICAgIHZpZXdCb3g9IjAgMCAxMCAxMCIKICAgIHN0cm9rZT0iYmxhY2siCiAgICBzdHJva2UtbGluZWNhcD0icm91bmQiCj4KICAgIDxsaW5lIHgxPSI1IiB5MT0iMS41IiB4Mj0iNSIgeTI9IjguNSI+PC9saW5lPgogICAgPGxpbmUgeDE9IjEuNSIgeTE9IjUiIHgyPSI4LjUiIHkyPSI1Ij48L2xpbmU+Cjwvc3ZnPg==\"","export default \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUgMTEuMzU4aDE3LjUwMXYxLjI4NUgzLjI1eiIvPjwvc3ZnPg==\"","export default \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHdpZHRoPSIyNCI+PHBhdGggc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUgMTEuMzU4aDE3LjUwMXYxLjI4NUgzLjI1eiIvPjxwYXRoIHRyYW5zZm9ybT0icm90YXRlKDkwKSIgc3R5bGU9ImZpbGw6IzU3NWU3NTtzdHJva2Utd2lkdGg6LjczNDczNiIgZD0iTTMuMjUtMTIuNjQyaDE3LjUwMXYxLjI4NUgzLjI1eiIvPjwvc3ZnPg==\"","export default \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHBhdGggc3R5bGU9Im9wYWNpdHk6Ljc1O2ZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIgZD0iTTU2IDE2djk2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMi4zNzYgLTIuMzc2KSBzY2FsZSguMjI0NjIpIi8+PHBhdGggc3R5bGU9Im9wYWNpdHk6LjU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNNDAgMzJ2NjQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouMjU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNMjQgNDh2MzIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouNzU7ZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJNNzIgMTZ2OTYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48cGF0aCBzdHlsZT0ib3BhY2l0eTouNTtmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Ik04OCAzMnY2NCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIuMzc2IC0yLjM3Nikgc2NhbGUoLjIyNDYyKSIvPjxwYXRoIHN0eWxlPSJvcGFjaXR5Oi4yNTtmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIGQ9Ik0xMDQgNDh2MzIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjM3NiAtMi4zNzYpIHNjYWxlKC4yMjQ2MikiLz48L3N2Zz4=\"","export default \"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PGcgc3Ryb2tlLXdpZHRoPSIxLjIiIHN0cm9rZT0iIzAwMTAyNiI+PGcgc3R5bGU9Im9wYWNpdHk6LjU7c3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLWRhc2hhcnJheTpub25lIj48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6bm9uZTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgZmlsbD0iI2JmYmZiZiIgZD0iTTY4LjQ1IDMyLjQ1N2MuMy0uMi44LS4xLjkuM2wyLjYgMTAuN3M2LjQgNC43IDguMyA4YzMuMiA1LjUgMy4zIDEwIDMuMyAxMHM3LjEgMi4xIDguMyA3LjhjMS4yIDUuNy0zLjIgMTYuNS0yMiAyMC4yLTE4LjggMy43LTMzLjktMS40LTQxLTEyLjgtNy4xLTExLjQgNC4xLTI1IDMuNS0yNC4ybC0yLjEtMTcuOWMtLjEtLjQuNC0uNy44LS41bDEyLjEgNy45czQuNS0xLjcgOS4yLTEuOWMyLjgtLjIgNS4yIDAgNy41LjR6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC42NTIgLTQuNjUpIHNjYWxlKC4yMzI5KSIvPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIGQ9Ik0xMDEuNjUgNjIuOTU3Yy00LjcgMy44LTExLjkgMy45LTExLjkgMy45bTEwLjYgNy4zYy02LjMuNS0xMC4yLTEuNC0xMC4yLTEuNG0tNjguOC0xMC4xczguNiAyLjggMTIuMSA1LjltLjMgMy41Yy00LjMgMS43LTExLjcuNi0xMS43LjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC00LjY1MiAtNC42NSkgc2NhbGUoLjIzMjkpIi8+PC9nPjxnIHN0eWxlPSJvcGFjaXR5Oi43NTtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmUiPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBmaWxsPSIjYmZiZmJmIiBkPSJNNjguNDUgMzIuNDU3Yy4zLS4yLjgtLjEuOS4zbDIuNiAxMC43czYuNCA0LjcgOC4zIDhjMy4yIDUuNSAzLjMgMTAgMy4zIDEwczcuMSAyLjEgOC4zIDcuOGMxLjIgNS43LTMuMiAxNi41LTIyIDIwLjItMTguOCAzLjctMzMuOS0xLjQtNDEtMTIuOC03LjEtMTEuNCA0LjEtMjUgMy41LTI0LjJsLTIuMS0xNy45Yy0uMS0uNC40LS43LjgtLjVsMTIuMSA3LjlzNC41LTEuNyA5LjItMS45YzIuOC0uMiA1LjIgMCA3LjUuNHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjMyMyAtMi4zMjEpIHNjYWxlKC4yMzI5KSIvPjxwYXRoIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTpub3JtYWw7ZmlsbDpub25lO2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDo0O3N0cm9rZS1taXRlcmxpbWl0OjEwO3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiIGQ9Ik0xMDEuNjUgNjIuOTU3Yy00LjcgMy44LTExLjkgMy45LTExLjkgMy45bTEwLjYgNy4zYy02LjMuNS0xMC4yLTEuNC0xMC4yLTEuNG0tNjguOC0xMC4xczguNiAyLjggMTIuMSA1LjltLjMgMy41Yy00LjMgMS43LTExLjcuNi0xMS43LjYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yLjMyMyAtMi4zMjEpIHNjYWxlKC4yMzI5KSIvPjwvZz48ZyBzdHlsZT0ic3Ryb2tlLXdpZHRoOjQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLWRhc2hhcnJheTpub25lIj48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6I2ZmZjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgZmlsbD0iI2JmYmZiZiIgZD0iTTczLjQ1IDM3LjQ1N2MuMy0uMi44LS4xLjkuM2wyLjYgMTAuN3M2LjQgNC43IDguMyA4YzMuMiA1LjUgMy4zIDEwIDMuMyAxMHM3LjEgMi4xIDguMyA3LjhjMS4yIDUuNy0zLjIgMTYuNS0yMiAyMC4yLTE4LjggMy43LTMzLjktMS40LTQxLTEyLjgtNy4xLTExLjQgNC4xLTI1IDMuNS0yNC4ybC0yLjEtMTcuOWMtLjEtLjQuNC0uNy44LS41bDEyLjEgNy45czQuNS0xLjcgOS4yLTEuOWMyLjgtLjIgNS4yIDAgNy41LjR6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMS4xNTkgLTEuMTU3KSBzY2FsZSguMjMyOSkiLz48cGF0aCBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6bm9ybWFsO2ZpbGw6bm9uZTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6NDtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLWRhc2hvZmZzZXQ6MCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIiBkPSJNMTA2LjY1IDY3Ljk1N2MtNC43IDMuOC0xMS45IDMuOS0xMS45IDMuOW0xMC42IDcuM2MtNi4zLjUtMTAuMi0xLjQtMTAuMi0xLjRtLTY4LjgtMTAuMXM4LjYgMi44IDEyLjEgNS45bS4zIDMuNWMtNC4zIDEuNy0xMS43LjYtMTEuNy42IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMS4xNTkgLTEuMTU3KSBzY2FsZSguMjMyOSkiLz48L2c+PC9nPjwvc3ZnPg==\"","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};\n","export default async ({ addon, console }) => {\n if (!addon.tab.redux.state) return console.warn(\"Redux is not available!\");\n addon.tab.redux.initialize();\n addon.tab.redux.addEventListener(\"statechanged\", ({ detail }) => {\n if (addon.self.disabled) return;\n const e = detail;\n if (!e.action || e.action.type !== \"scratch-paint/clipboard/SET\") return;\n const items = e.next.scratchPaint.clipboard.items;\n if (items.length !== 1) return;\n const firstItem = items[0];\n // TODO vector support\n if (!Array.isArray(firstItem) || firstItem[0] !== \"Raster\") return console.log(\"copied element is vector\");\n const dataURL = firstItem[1].source;\n addon.tab\n .copyImage(dataURL)\n .then(() => console.log(\"Image successfully copied\"))\n .catch((e) => console.error(`Image could not be copied: ${e}`));\n });\n};\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};\n","import * as sharedModule from \"../block-duplicate/module.js\";\n\nexport default async function ({ addon, console }) {\n const update = () => {\n sharedModule.setCherryPicking(!addon.self.disabled, addon.settings.get(\"invertDrag\"));\n };\n addon.self.addEventListener(\"disabled\", update);\n addon.self.addEventListener(\"reenabled\", update);\n addon.settings.addEventListener(\"change\", update);\n update();\n sharedModule.load(addon);\n}\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};\n","let enableCherryPicking = false;\nlet invertCherryPicking = false;\nexport function setCherryPicking(newEnabled, newInverted) {\n enableCherryPicking = newEnabled;\n // If cherry picking is disabled, also disable invert. Duplicating blocks can still cause\n // this setting to be used.\n invertCherryPicking = newEnabled && newInverted;\n}\n\nlet enableDuplication = false;\nexport function setDuplication(newEnabled) {\n enableDuplication = newEnabled;\n}\n\n// mostRecentEvent_ is sometimes a fake event, so we can't rely on reading its properties.\nlet ctrlOrMetaPressed = false;\nlet altPressed = false;\ndocument.addEventListener(\n \"mousedown\",\n function (e) {\n ctrlOrMetaPressed = e.ctrlKey || e.metaKey;\n altPressed = e.altKey;\n },\n {\n capture: true,\n }\n);\n\nlet loaded = false;\n\nexport async function load(addon) {\n if (loaded) {\n return;\n }\n loaded = true;\n\n const ScratchBlocks = await addon.tab.traps.getBlockly();\n\n // https://github.com/LLK/scratch-blocks/blob/912b8cc728bea8fd91af85078c64fcdbfe21c87a/core/gesture.js#L454\n const originalStartDraggingBlock = ScratchBlocks.Gesture.prototype.startDraggingBlock_;\n ScratchBlocks.Gesture.prototype.startDraggingBlock_ = function (...args) {\n let block = this.targetBlock_;\n\n // Scratch uses fake mouse events to implement right click > duplicate\n const isRightClickDuplicate = !(this.mostRecentEvent_ instanceof MouseEvent);\n\n const isDuplicating =\n enableDuplication &&\n altPressed &&\n !isRightClickDuplicate &&\n !this.flyout_ &&\n !this.shouldDuplicateOnDrag_ &&\n (this.targetBlock_.type !== \"procedures_definition\" ||\n this.targetBlock_.type !== \"procedures_definition_return\");\n\n const isCherryPickingInverted = invertCherryPicking && !isRightClickDuplicate && block.getParent();\n const canCherryPick = enableCherryPicking || isDuplicating;\n const isCherryPicking = canCherryPick && ctrlOrMetaPressed === !isCherryPickingInverted && !block.isShadow();\n\n if (isDuplicating || isCherryPicking) {\n if (!ScratchBlocks.Events.getGroup()) {\n // Scratch will disable grouping on its own later.\n ScratchBlocks.Events.setGroup(true);\n }\n }\n\n if (isDuplicating) {\n // Based on https://github.com/LLK/scratch-blocks/blob/feda366947432b9d82a4f212f43ff6d4ab6f252f/core/scratch_blocks_utils.js#L171\n // Setting this.shouldDuplicateOnDrag_ = true does NOT work because it doesn't call changeObscuredShadowIds\n this.startWorkspace_.setResizesEnabled(false);\n ScratchBlocks.Events.disable();\n let newBlock;\n try {\n const xmlBlock = ScratchBlocks.Xml.blockToDom(block);\n newBlock = ScratchBlocks.Xml.domToBlock(xmlBlock, this.startWorkspace_);\n ScratchBlocks.scratchBlocksUtils.changeObscuredShadowIds(newBlock);\n const xy = block.getRelativeToSurfaceXY();\n newBlock.moveBy(xy.x, xy.y);\n } catch (e) {\n console.error(e);\n }\n ScratchBlocks.Events.enable();\n\n if (newBlock) {\n block = newBlock;\n this.targetBlock_ = newBlock;\n if (ScratchBlocks.Events.isEnabled()) {\n ScratchBlocks.Events.fire(new ScratchBlocks.Events.BlockCreate(newBlock));\n }\n }\n }\n\n if (isCherryPicking) {\n if (isRightClickDuplicate || isDuplicating) {\n const nextBlock = block.getNextBlock();\n if (nextBlock) {\n nextBlock.dispose();\n }\n }\n block.unplug(true);\n }\n\n return originalStartDraggingBlock.call(this, ...args);\n };\n}\n","import * as sharedModule from \"./module.js\";\n\nexport default async function ({ addon, console }) {\n const update = () => {\n sharedModule.setDuplication(!addon.self.disabled);\n };\n addon.self.addEventListener(\"disabled\", update);\n addon.self.addEventListener(\"reenabled\", update);\n update();\n sharedModule.load(addon);\n}\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};\n","/* eslint-disable */\n\nexport default async function ({ addon, console, msg }) {\n const ScratchBlocks = await addon.tab.traps.getBlockly();\n const vm = addon.tab.traps.vm;\n\n let blockSwitches = {};\n let procedureSwitches = {};\n const noopSwitch = {\n isNoop: true,\n };\n\n const randomColor = () => {\n const num = Math.floor(Math.random() * 256 * 256 * 256);\n return `#${num.toString(16).padStart(6, \"0\")}`;\n };\n\n const buildSwitches = () => {\n blockSwitches = {};\n procedureSwitches = {};\n\n if (addon.settings.get(\"motion\")) {\n blockSwitches[\"motion_turnright\"] = [\n noopSwitch,\n {\n opcode: \"motion_turnleft\",\n },\n ];\n blockSwitches[\"motion_turnleft\"] = [\n {\n opcode: \"motion_turnright\",\n },\n noopSwitch,\n ];\n blockSwitches[\"motion_gotoxy\"] = [\n noopSwitch,\n {\n opcode: \"motion_changebyxy\",\n remapInputName: { X: \"DX\", Y: \"DY\" },\n }\n ];\n blockSwitches[\"motion_changebyxy\"] = [\n {\n opcode: \"motion_gotoxy\",\n remapInputName: { DX: \"X\", DY: \"Y\" },\n },\n noopSwitch,\n ];\n blockSwitches[\"motion_setx\"] = [\n noopSwitch,\n {\n opcode: \"motion_changexby\",\n remapInputName: { X: \"DX\" },\n },\n {\n opcode: \"motion_sety\",\n remapInputName: { X: \"Y\" },\n },\n {\n opcode: \"motion_changeyby\",\n remapInputName: { X: \"DY\" },\n },\n ];\n blockSwitches[\"motion_changexby\"] = [\n {\n opcode: \"motion_setx\",\n remapInputName: { DX: \"X\" },\n },\n noopSwitch,\n {\n opcode: \"motion_sety\",\n remapInputName: { DX: \"Y\" },\n },\n {\n opcode: \"motion_changeyby\",\n remapInputName: { DX: \"DY\" },\n },\n ];\n blockSwitches[\"motion_sety\"] = [\n {\n opcode: \"motion_setx\",\n remapInputName: { Y: \"X\" },\n },\n {\n opcode: \"motion_changexby\",\n remapInputName: { Y: \"DX\" },\n },\n noopSwitch,\n {\n opcode: \"motion_changeyby\",\n remapInputName: { Y: \"DY\" },\n },\n ];\n blockSwitches[\"motion_changeyby\"] = [\n {\n opcode: \"motion_setx\",\n remapInputName: { DY: \"X\" },\n },\n {\n opcode: \"motion_changexby\",\n remapInputName: { DY: \"DX\" },\n },\n {\n opcode: \"motion_sety\",\n remapInputName: { DY: \"Y\" },\n },\n noopSwitch,\n ];\n blockSwitches[\"motion_xposition\"] = [\n noopSwitch,\n {\n opcode: \"motion_yposition\",\n },\n ];\n blockSwitches[\"motion_yposition\"] = [\n {\n opcode: \"motion_xposition\",\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"looks\")) {\n blockSwitches[\"looks_seteffectto\"] = [\n noopSwitch,\n {\n opcode: \"looks_changeeffectby\",\n remapInputName: { VALUE: \"CHANGE\" },\n },\n ];\n blockSwitches[\"looks_changeeffectby\"] = [\n {\n opcode: \"looks_seteffectto\",\n remapInputName: { CHANGE: \"VALUE\" },\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_setsizeto\"] = [\n noopSwitch,\n {\n opcode: \"looks_changesizeby\",\n remapInputName: { SIZE: \"CHANGE\" },\n },\n ];\n blockSwitches[\"looks_changesizeby\"] = [\n {\n opcode: \"looks_setsizeto\",\n remapInputName: { CHANGE: \"SIZE\" },\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_costumenumbername\"] = [\n noopSwitch,\n {\n opcode: \"looks_backdropnumbername\",\n },\n ];\n blockSwitches[\"looks_backdropnumbername\"] = [\n {\n opcode: \"looks_costumenumbername\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_show\"] = [\n noopSwitch,\n {\n opcode: \"looks_hide\",\n },\n ];\n blockSwitches[\"looks_hide\"] = [\n {\n opcode: \"looks_show\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_setShape\"] = [\n {\n opcode: \"looks_setShape\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_setColor\"] = [\n {\n opcode: \"looks_setColor\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_setFont\"] = [\n {\n opcode: \"looks_setFont\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_nextcostume\"] = [\n noopSwitch,\n {\n opcode: \"looks_nextbackdrop\",\n },\n ];\n blockSwitches[\"looks_nextbackdrop\"] = [\n {\n opcode: \"looks_nextcostume\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_say\"] = [\n noopSwitch,\n {\n opcode: \"looks_sayforsecs\",\n createInputs: {\n SECS: {\n shadowType: \"math_number\",\n value: \"2\",\n },\n },\n },\n {\n opcode: \"looks_think\",\n },\n {\n opcode: \"looks_thinkforsecs\",\n createInputs: {\n SECS: {\n shadowType: \"math_number\",\n value: \"2\",\n },\n },\n },\n ];\n blockSwitches[\"looks_think\"] = [\n {\n opcode: \"looks_say\",\n },\n {\n opcode: \"looks_sayforsecs\",\n createInputs: {\n SECS: {\n shadowType: \"math_number\",\n value: \"2\",\n },\n },\n },\n noopSwitch,\n {\n opcode: \"looks_thinkforsecs\",\n createInputs: {\n SECS: {\n shadowType: \"math_number\",\n value: \"2\",\n },\n },\n },\n ];\n blockSwitches[\"looks_sayforsecs\"] = [\n {\n opcode: \"looks_say\",\n splitInputs: [\"SECS\"],\n },\n {\n opcode: \"looks_think\",\n splitInputs: [\"SECS\"],\n },\n noopSwitch,\n {\n opcode: \"looks_thinkforsecs\",\n },\n ];\n blockSwitches[\"looks_thinkforsecs\"] = [\n {\n opcode: \"looks_say\",\n splitInputs: [\"SECS\"],\n },\n {\n opcode: \"looks_think\",\n splitInputs: [\"SECS\"],\n },\n {\n opcode: \"looks_sayforsecs\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_switchbackdropto\"] = [\n noopSwitch,\n {\n opcode: \"looks_switchbackdroptoandwait\",\n },\n ];\n blockSwitches[\"looks_switchbackdroptoandwait\"] = [\n {\n opcode: \"looks_switchbackdropto\",\n },\n noopSwitch,\n ];\n blockSwitches[\"looks_gotofrontback\"] = [\n noopSwitch,\n {\n opcode: \"looks_goforwardbackwardlayers\",\n remapInputName: {\n FRONT_BACK: \"FORWARD_BACKWARD\",\n },\n mapFieldValues: {\n FRONT_BACK: {\n front: \"forward\",\n back: \"backward\",\n },\n },\n createInputs: {\n NUM: {\n shadowType: \"math_integer\",\n value: \"1\",\n },\n },\n },\n ];\n blockSwitches[\"looks_goforwardbackwardlayers\"] = [\n {\n opcode: \"looks_gotofrontback\",\n splitInputs: [\"NUM\"],\n remapInputName: {\n FORWARD_BACKWARD: \"FRONT_BACK\",\n },\n mapFieldValues: {\n FORWARD_BACKWARD: {\n forward: \"front\",\n backward: \"back\",\n },\n },\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"sound\")) {\n blockSwitches[\"sound_play\"] = [\n noopSwitch,\n {\n opcode: \"sound_playuntildone\",\n },\n ];\n blockSwitches[\"sound_playuntildone\"] = [\n {\n opcode: \"sound_play\",\n },\n noopSwitch,\n ];\n blockSwitches[\"sound_seteffectto\"] = [\n noopSwitch,\n {\n opcode: \"sound_changeeffectby\",\n },\n ];\n blockSwitches[\"sound_changeeffectby\"] = [\n {\n opcode: \"sound_seteffectto\",\n },\n noopSwitch,\n ];\n blockSwitches[\"sound_setvolumeto\"] = [\n noopSwitch,\n {\n opcode: \"sound_changevolumeby\",\n },\n ];\n blockSwitches[\"sound_changevolumeby\"] = [\n {\n opcode: \"sound_setvolumeto\",\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"event\")) {\n blockSwitches[\"event_broadcast\"] = [\n noopSwitch,\n {\n opcode: \"event_broadcastandwait\",\n },\n ];\n blockSwitches[\"event_broadcastandwait\"] = [\n {\n opcode: \"event_broadcast\",\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"control\")) {\n blockSwitches[\"control_if\"] = [\n noopSwitch,\n {\n opcode: \"control_if_else\",\n },\n ];\n blockSwitches[\"control_if_else\"] = [\n {\n opcode: \"control_if\",\n splitInputs: [\"SUBSTACK2\"],\n },\n noopSwitch,\n ];\n blockSwitches[\"control_repeat_until\"] = [\n noopSwitch,\n {\n opcode: \"control_while\",\n },\n {\n opcode: \"control_wait_until\",\n splitInputs: [\"SUBSTACK\"],\n },\n {\n opcode: \"control_forever\",\n splitInputs: [\"CONDITION\"],\n },\n ];\n blockSwitches[\"control_forever\"] = [\n {\n opcode: \"control_repeat_until\",\n },\n {\n opcode: \"control_while\",\n },\n noopSwitch,\n ];\n blockSwitches[\"control_wait_until\"] = [\n {\n opcode: \"control_repeat_until\",\n },\n noopSwitch,\n ];\n\n blockSwitches[\"control_while\"] = [\n {\n opcode: \"control_repeat_until\",\n },\n noopSwitch,\n {\n opcode: \"control_forever\",\n splitInputs: [\"CONDITION\"],\n },\n ];\n }\n\n if (addon.settings.get(\"operator\")) {\n blockSwitches[\"operator_equals\"] = [\n {\n opcode: \"operator_gt\",\n },\n {\n opcode: \"operator_gtorequal\",\n },\n {\n opcode: \"operator_lt\",\n },\n {\n opcode: \"operator_ltorequal\",\n },\n noopSwitch,\n {\n opcode: \"operator_notequal\",\n },\n ];\n blockSwitches[\"operator_gt\"] = [\n noopSwitch,\n {\n opcode: \"operator_gtorequal\",\n },\n {\n opcode: \"operator_lt\",\n },\n {\n opcode: \"operator_ltorequal\",\n },\n {\n opcode: \"operator_equals\",\n },\n {\n opcode: \"operator_notequal\",\n },\n ];\n blockSwitches[\"operator_lt\"] = [\n {\n opcode: \"operator_gt\",\n },\n {\n opcode: \"operator_gtorequal\",\n },\n noopSwitch,\n {\n opcode: \"operator_ltorequal\",\n },\n {\n opcode: \"operator_equals\",\n },\n {\n opcode: \"operator_notequal\",\n },\n ];\n blockSwitches[\"operator_notequal\"] = [\n {\n opcode: \"operator_gt\",\n },\n {\n opcode: \"operator_gtorequal\",\n },\n {\n opcode: \"operator_lt\",\n },\n {\n opcode: \"operator_ltorequal\",\n },\n {\n opcode: \"operator_equals\",\n },\n noopSwitch,\n ];\n blockSwitches[\"operator_gtorequal\"] = [\n {\n opcode: \"operator_gt\",\n },\n noopSwitch,\n {\n opcode: \"operator_lt\",\n },\n {\n opcode: \"operator_ltorequal\",\n },\n {\n opcode: \"operator_equals\",\n },\n {\n opcode: \"operator_notequal\",\n },\n ];\n blockSwitches[\"operator_ltorequal\"] = [\n {\n opcode: \"operator_gt\",\n },\n {\n opcode: \"operator_gtorequal\",\n },\n {\n opcode: \"operator_lt\",\n },\n noopSwitch,\n {\n opcode: \"operator_equals\",\n },\n {\n opcode: \"operator_notequal\",\n },\n ];\n blockSwitches[\"operator_add\"] = [\n noopSwitch,\n {\n opcode: \"operator_subtract\",\n },\n {\n opcode: \"operator_multiply\",\n },\n {\n opcode: \"operator_divide\",\n },\n {\n opcode: \"operator_power\",\n },\n {\n opcode: \"operator_mod\",\n },\n ];\n blockSwitches[\"operator_subtract\"] = [\n {\n opcode: \"operator_add\",\n },\n noopSwitch,\n {\n opcode: \"operator_multiply\",\n },\n {\n opcode: \"operator_divide\",\n },\n {\n opcode: \"operator_power\",\n },\n {\n opcode: \"operator_mod\",\n },\n ];\n blockSwitches[\"operator_multiply\"] = [\n {\n opcode: \"operator_add\",\n },\n {\n opcode: \"operator_subtract\",\n },\n noopSwitch,\n {\n opcode: \"operator_divide\",\n },\n {\n opcode: \"operator_power\",\n },\n {\n opcode: \"operator_mod\",\n },\n ];\n blockSwitches[\"operator_divide\"] = [\n {\n opcode: \"operator_add\",\n },\n {\n opcode: \"operator_subtract\",\n },\n {\n opcode: \"operator_multiply\",\n },\n noopSwitch,\n {\n opcode: \"operator_power\",\n },\n {\n opcode: \"operator_mod\",\n },\n ];\n blockSwitches[\"operator_power\"] = [\n {\n opcode: \"operator_add\",\n },\n {\n opcode: \"operator_subtract\",\n },\n {\n opcode: \"operator_multiply\",\n },\n {\n opcode: \"operator_divide\",\n },\n noopSwitch,\n {\n opcode: \"operator_mod\",\n },\n ];\n blockSwitches[\"operator_mod\"] = [\n {\n opcode: \"operator_add\",\n },\n {\n opcode: \"operator_subtract\",\n },\n {\n opcode: \"operator_multiply\",\n },\n {\n opcode: \"operator_divide\",\n },\n {\n opcode: \"operator_power\",\n },\n noopSwitch,\n ];\n blockSwitches[\"operator_and\"] = [\n noopSwitch,\n {\n opcode: \"operator_or\",\n },\n ];\n blockSwitches[\"operator_or\"] = [\n {\n opcode: \"operator_and\",\n },\n noopSwitch,\n ];\n blockSwitches[\"operator_trueBoolean\"] = [\n noopSwitch,\n {\n opcode: \"operator_falseBoolean\",\n },\n ];\n blockSwitches[\"operator_falseBoolean\"] = [\n {\n opcode: \"operator_trueBoolean\",\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"sensing\")) {\n blockSwitches[\"sensing_mousex\"] = [\n noopSwitch,\n {\n opcode: \"sensing_mousey\",\n },\n ];\n blockSwitches[\"sensing_mousey\"] = [\n {\n opcode: \"sensing_mousex\",\n },\n noopSwitch,\n ];\n blockSwitches[\"sensing_touchingcolor\"] = [\n noopSwitch,\n {\n opcode: \"sensing_coloristouchingcolor\",\n createInputs: {\n COLOR2: {\n shadowType: \"colour_picker\",\n value: randomColor,\n },\n },\n },\n ];\n blockSwitches[\"sensing_coloristouchingcolor\"] = [\n {\n opcode: \"sensing_touchingcolor\",\n splitInputs: [\"COLOR2\"],\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"data\")) {\n blockSwitches[\"data_setvariableto\"] = [\n noopSwitch,\n {\n opcode: \"data_changevariableby\",\n remapShadowType: { VALUE: \"math_number\" },\n },\n ];\n blockSwitches[\"data_changevariableby\"] = [\n {\n opcode: \"data_setvariableto\",\n remapShadowType: { VALUE: \"text\" },\n },\n noopSwitch,\n ];\n blockSwitches[\"data_showvariable\"] = [\n noopSwitch,\n {\n opcode: \"data_hidevariable\",\n },\n ];\n blockSwitches[\"data_hidevariable\"] = [\n {\n opcode: \"data_showvariable\",\n },\n noopSwitch,\n ];\n blockSwitches[\"data_showlist\"] = [\n noopSwitch,\n {\n opcode: \"data_hidelist\",\n },\n ];\n blockSwitches[\"data_hidelist\"] = [\n {\n opcode: \"data_showlist\",\n },\n noopSwitch,\n ];\n blockSwitches[\"data_replaceitemoflist\"] = [\n noopSwitch,\n {\n opcode: \"data_insertatlist\",\n },\n ];\n blockSwitches[\"data_insertatlist\"] = [\n {\n opcode: \"data_replaceitemoflist\",\n },\n noopSwitch,\n ];\n blockSwitches[\"data_deleteoflist\"] = [\n noopSwitch,\n {\n opcode: \"data_deletealloflist\",\n splitInputs: [\"INDEX\"],\n },\n ];\n blockSwitches[\"data_deletealloflist\"] = [\n {\n opcode: \"data_deleteoflist\",\n createInputs: {\n INDEX: {\n shadowType: \"math_integer\",\n value: \"1\",\n },\n },\n },\n noopSwitch,\n ];\n }\n\n if (addon.settings.get(\"extension\")) {\n blockSwitches[\"pen_penDown\"] = [\n noopSwitch,\n {\n opcode: \"pen_penUp\",\n },\n ];\n blockSwitches[\"pen_penUp\"] = [\n {\n opcode: \"pen_penDown\",\n },\n noopSwitch,\n ];\n blockSwitches[\"pen_setPenColorParamTo\"] = [\n noopSwitch,\n {\n opcode: \"pen_changePenColorParamBy\",\n },\n ];\n blockSwitches[\"pen_changePenColorParamBy\"] = [\n {\n opcode: \"pen_setPenColorParamTo\",\n },\n noopSwitch,\n ];\n blockSwitches[\"pen_setPenHueToNumber\"] = [\n noopSwitch,\n {\n opcode: \"pen_changePenHueBy\",\n },\n ];\n blockSwitches[\"pen_changePenHueBy\"] = [\n {\n opcode: \"pen_setPenHueToNumber\",\n },\n noopSwitch,\n ];\n blockSwitches[\"pen_setPenShadeToNumber\"] = [\n noopSwitch,\n {\n opcode: \"pen_changePenShadeBy\",\n },\n ];\n blockSwitches[\"pen_changePenShadeBy\"] = [\n {\n opcode: \"pen_setPenShadeToNumber\",\n },\n noopSwitch,\n ];\n blockSwitches[\"pen_setPenSizeTo\"] = [\n noopSwitch,\n {\n opcode: \"pen_changePenSizeBy\",\n },\n ];\n blockSwitches[\"pen_changePenSizeBy\"] = [\n {\n opcode: \"pen_setPenSizeTo\",\n },\n noopSwitch,\n ];\n blockSwitches[\"music_setTempo\"] = [\n noopSwitch,\n {\n opcode: \"music_changeTempo\",\n },\n ];\n blockSwitches[\"music_changeTempo\"] = [\n {\n opcode: \"music_setTempo\",\n },\n noopSwitch,\n ];\n\n if (vm.extensionManager) {\n const switches = vm.extensionManager.getAddonBlockSwitches();\n Object.getOwnPropertyNames(switches).forEach(extID => {\n Object.getOwnPropertyNames(switches[extID]).forEach(block => {\n blockSwitches[`${extID}_${block}`] = switches[extID][block];\n })\n })\n }\n vm.runtime.on(\"EXTENSION_ADDED\", () => {\n const switches = vm.extensionManager.getAddonBlockSwitches();\n Object.getOwnPropertyNames(switches).forEach(extID => {\n Object.getOwnPropertyNames(switches[extID]).forEach(block => {\n blockSwitches[`${extID}_${block}`] = switches[extID][block];\n })\n })\n })\n }\n\n if (addon.settings.get(\"sa\")) {\n const logProc = \"\\u200B\\u200Blog\\u200B\\u200B %s\";\n const warnProc = \"\\u200B\\u200Bwarn\\u200B\\u200B %s\";\n const errorProc = \"\\u200B\\u200Berror\\u200B\\u200B %s\";\n const logMessage = msg(\"debugger_log\");\n const warnMessage = msg(\"debugger_warn\");\n const errorMessage = msg(\"debugger_error\");\n const logSwitch = {\n mutate: {\n proccode: logProc,\n },\n msg: logMessage,\n };\n const warnSwitch = {\n mutate: {\n proccode: warnProc,\n },\n msg: warnMessage,\n };\n const errorSwitch = {\n mutate: {\n proccode: errorProc,\n },\n msg: errorMessage,\n };\n procedureSwitches[logProc] = [\n {\n msg: logMessage,\n isNoop: true,\n },\n warnSwitch,\n errorSwitch,\n ];\n procedureSwitches[warnProc] = [\n logSwitch,\n {\n msg: warnMessage,\n isNoop: true,\n },\n errorSwitch,\n ];\n procedureSwitches[errorProc] = [\n logSwitch,\n warnSwitch,\n {\n msg: errorMessage,\n isNoop: true,\n },\n ];\n }\n\n // Switching for these is implemented by Scratch. We only define them here to optionally add a border.\n // Because we don't implement the switching ourselves, this is not controlled by the data category option.\n blockSwitches[\"data_variable\"] = [];\n blockSwitches[\"data_listcontents\"] = [];\n };\n buildSwitches();\n addon.settings.addEventListener(\"change\", buildSwitches);\n\n /**\n * @param {*} workspace\n * @param {Element} xmlBlock\n */\n const pasteBlockXML = (workspace, xmlBlock) => {\n // Similar to https://github.com/LLK/scratch-blocks/blob/7575c9a0f2c267676569c4b102b76d77f35d9fd6/core/workspace_svg.js#L1020\n // but without the collision checking.\n const block = ScratchBlocks.Xml.domToBlock(xmlBlock, workspace);\n const x = +xmlBlock.getAttribute(\"x\");\n const y = +xmlBlock.getAttribute(\"y\");\n // Don't need to handle RTL here\n block.moveBy(x, y);\n return block;\n };\n\n /**\n * @param {string} shadowType The type of shadow eg. \"math_number\"\n * @returns {string} The name of the shadow's inner field that contains the user-visible value\n */\n const getShadowFieldName = (shadowType) => {\n // This is non-comprehensive.\n if (shadowType === \"text\") {\n return \"TEXT\";\n }\n if (shadowType === \"colour_picker\") {\n return \"COLOUR\";\n }\n return \"NUM\";\n };\n\n /**\n * @template T\n * @param {T|()=>T} value\n * @returns {T}\n */\n const callIfFunction = (value) => {\n if (typeof value === \"function\") {\n return value();\n }\n return value;\n };\n\n const menuCallbackFactory = (block, opcodeData) => () => {\n if (opcodeData.isNoop) {\n return;\n }\n\n if (opcodeData.fieldValue) {\n block.setFieldValue(opcodeData.fieldValue, \"VALUE\");\n return;\n }\n\n try {\n ScratchBlocks.Events.setGroup(true);\n\n const workspace = block.workspace;\n\n const blocksToBringToForeground = [];\n // Split inputs before we clone the block.\n if (opcodeData.splitInputs) {\n for (const inputName of opcodeData.splitInputs) {\n const input = block.getInput(inputName);\n if (!input) {\n continue;\n }\n const connection = input.connection;\n if (!connection) {\n continue;\n }\n if (connection.isConnected()) {\n const targetBlock = connection.targetBlock();\n if (targetBlock.isShadow()) {\n // Deleting shadows is handled later.\n } else {\n connection.disconnect();\n blocksToBringToForeground.push(targetBlock);\n }\n }\n }\n }\n\n // Make a copy of the block with the proper type set.\n // 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.\n const xml = ScratchBlocks.Xml.blockToDom(block);\n // blockToDomWithXY's handling of RTL is strange, so we encode the position ourselves.\n const position = block.getRelativeToSurfaceXY();\n xml.setAttribute(\"x\", position.x);\n xml.setAttribute(\"y\", position.y);\n if (opcodeData.opcode) {\n xml.setAttribute(\"type\", opcodeData.opcode);\n }\n\n const parentBlock = block.getParent();\n let parentConnection;\n let blockConnectionType;\n if (parentBlock) {\n // If the block has a parent, find the parent -> child connection that will be reattached later.\n const parentConnections = parentBlock.getConnections_();\n parentConnection = parentConnections.find(\n (c) => c.targetConnection && c.targetConnection.sourceBlock_ === block\n );\n // There's two types of connections from child -> parent. We need to figure out which one is used.\n const blockConnections = block.getConnections_();\n const blockToParentConnection = blockConnections.find(\n (c) => c.targetConnection && c.targetConnection.sourceBlock_ === parentBlock\n );\n blockConnectionType = blockToParentConnection.type;\n }\n\n // Array.from creates a clone of the children list. This is important as we may remove\n // children as we iterate.\n for (const child of Array.from(xml.children)) {\n const oldName = child.getAttribute(\"name\");\n\n // Any inputs that were supposed to be split that were not should be removed.\n // (eg. shadow inputs)\n if (opcodeData.splitInputs && opcodeData.splitInputs.includes(oldName)) {\n xml.removeChild(child);\n continue;\n }\n\n const newName = opcodeData.remapInputName && opcodeData.remapInputName[oldName];\n if (newName) {\n child.setAttribute(\"name\", newName);\n }\n\n const newShadowType = opcodeData.remapShadowType && opcodeData.remapShadowType[oldName];\n if (newShadowType) {\n const valueNode = child.firstChild;\n const fieldNode = valueNode.firstChild;\n valueNode.setAttribute(\"type\", newShadowType);\n fieldNode.setAttribute(\"name\", getShadowFieldName(newShadowType));\n }\n\n const fieldValueMap = opcodeData.mapFieldValues && opcodeData.mapFieldValues[oldName];\n if (fieldValueMap && child.tagName === \"FIELD\") {\n const oldValue = child.innerText;\n const newValue = fieldValueMap[oldValue];\n if (typeof newValue === \"string\") {\n child.innerText = newValue;\n }\n }\n }\n\n if (opcodeData.mutate) {\n const mutation = xml.querySelector(\"mutation\");\n for (const [key, value] of Object.entries(opcodeData.mutate)) {\n mutation.setAttribute(key, value);\n }\n }\n\n if (opcodeData.createInputs) {\n for (const [inputName, inputData] of Object.entries(opcodeData.createInputs)) {\n const valueElement = document.createElement(\"value\");\n valueElement.setAttribute(\"name\", inputName);\n\n const shadowElement = document.createElement(\"shadow\");\n shadowElement.setAttribute(\"type\", inputData.shadowType);\n\n const shadowFieldElement = document.createElement(\"field\");\n shadowFieldElement.setAttribute(\"name\", getShadowFieldName(inputData.shadowType));\n shadowFieldElement.innerText = callIfFunction(inputData.value);\n\n shadowElement.appendChild(shadowFieldElement);\n valueElement.appendChild(shadowElement);\n xml.appendChild(valueElement);\n }\n }\n\n // Remove the old block and insert the new one.\n block.dispose();\n const newBlock = pasteBlockXML(workspace, xml);\n\n if (parentConnection) {\n // Search for the same type of connection on the new block as on the old block.\n const newBlockConnections = newBlock.getConnections_();\n const newBlockConnection = newBlockConnections.find((c) => c.type === blockConnectionType);\n newBlockConnection.connect(parentConnection);\n }\n\n for (const otherBlock of blocksToBringToForeground) {\n // By re-appending the element, we move it to the end, which will make it display\n // on top.\n const svgRoot = otherBlock.getSvgRoot();\n svgRoot.parentNode.appendChild(svgRoot);\n }\n } finally {\n ScratchBlocks.Events.setGroup(false);\n }\n };\n\n const uniques = (array) => [...new Set(array)];\n\n addon.tab.createBlockContextMenu(\n (items, block) => {\n if (!addon.self.disabled) {\n const type = block.type;\n let switches = blockSwitches[block.type] || [];\n\n const customArgsMode = addon.settings.get(\"customargs\") ? addon.settings.get(\"customargsmode\") : \"off\";\n if (\n customArgsMode !== \"off\" &&\n [\"argument_reporter_boolean\", \"argument_reporter_string_number\"].includes(type) &&\n // if the arg is a shadow, it's in a procedures_prototype so we don't want it to be switchable\n !block.isShadow()\n ) {\n const customBlocks = getCustomBlocks();\n if (customArgsMode === \"all\") {\n switch (type) {\n case \"argument_reporter_string_number\":\n switches = Object.values(customBlocks)\n .map((cb) => cb.stringArgs)\n .flat(1);\n break;\n case \"argument_reporter_boolean\":\n switches = Object.values(customBlocks)\n .map((cb) => cb.boolArgs)\n .flat(1);\n break;\n }\n } else if (customArgsMode === \"defOnly\") {\n const root = block.getRootBlock();\n if (root.type !== \"procedures_definition\" || root.type !== \"procedures_definition_return\") return items;\n const customBlockObj = customBlocks[root.getChildren(true)[0].getProcCode()];\n switch (type) {\n case \"argument_reporter_string_number\":\n switches = customBlockObj.stringArgs;\n break;\n case \"argument_reporter_boolean\":\n switches = customBlockObj.boolArgs;\n break;\n }\n }\n const currentValue = block.getFieldValue(\"VALUE\");\n switches = uniques(switches).map((i) => ({\n isNoop: i === currentValue,\n fieldValue: i,\n msg: i,\n }));\n }\n\n if (block.type === \"procedures_call\") {\n const proccode = block.getProcCode();\n if (procedureSwitches[proccode]) {\n switches = procedureSwitches[proccode];\n }\n }\n\n if (!addon.settings.get(\"noop\")) {\n switches = switches.filter((i) => !i.isNoop);\n }\n\n switches.forEach((opcodeData, i) => {\n const makeSpaceItemIndex = items.findIndex((obj) => obj._isDevtoolsFirstItem);\n const insertBeforeIndex =\n makeSpaceItemIndex !== -1\n ? // If \"make space\" button exists, add own items before it\n makeSpaceItemIndex\n : // If there's no such button, insert at end\n items.length;\n const text = opcodeData.msg ? opcodeData.msg : opcodeData.opcode ? msg(opcodeData.opcode) : msg(block.type);\n items.splice(insertBeforeIndex, 0, {\n enabled: true,\n text,\n callback: menuCallbackFactory(block, opcodeData),\n separator: i === 0,\n });\n });\n\n if (block.type === \"data_variable\" || block.type === \"data_listcontents\") {\n // Add top border to first variable (if it exists)\n const delBlockIndex = items.findIndex((item) => item.text === ScratchBlocks.Msg.DELETE_BLOCK);\n // firstVariableItem might be undefined, a variable to switch to,\n // or an item added by editor-devtools (or any addon before this one)\n const firstVariableItem = items[delBlockIndex + 1];\n if (firstVariableItem) firstVariableItem.separator = true;\n }\n }\n return items;\n },\n { blocks: true }\n );\n\n // https://github.com/LLK/scratch-blocks/blob/abbfe93136fef57fdfb9a077198b0bc64726f012/blocks_vertical/procedures.js#L207-L215\n // Returns a list like [\"%s\", \"%d\"]\n const parseArguments = (code) =>\n code\n .split(/(?=[^\\\\]%[nbs])/g)\n .map((i) => i.trim())\n .filter((i) => i.charAt(0) === \"%\")\n .map((i) => i.substring(0, 2));\n\n const getCustomBlocks = () => {\n const customBlocks = {};\n const target = vm.editingTarget;\n Object.values(target.blocks._blocks)\n .filter((block) => block.opcode === \"procedures_prototype\")\n .forEach((block) => {\n const procCode = block.mutation.proccode;\n const argumentNames = JSON.parse(block.mutation.argumentnames);\n // argumentdefaults is unreliable, so we have to parse the procedure code to determine argument types\n const parsedArguments = parseArguments(procCode);\n const stringArgs = [];\n const boolArgs = [];\n for (let i = 0; i < argumentNames.length; i++) {\n if (parsedArguments[i] === \"%b\") {\n boolArgs.push(argumentNames[i]);\n } else {\n stringArgs.push(argumentNames[i]);\n }\n }\n customBlocks[procCode] = {\n stringArgs,\n boolArgs,\n };\n });\n return customBlocks;\n };\n}\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};\n","import { normalizeHex, getHexRegex } from \"../../libraries/common/cs/normalize-color.js\";\nimport RateLimiter from \"../../libraries/common/cs/rate-limiter.js\";\nimport tinycolor from \"../../libraries/thirdparty/cs/tinycolor-min.js\";\n\nexport default async ({ addon, console, msg }) => {\n // 250-ms rate limit\n const rateLimiter = new RateLimiter(250);\n const getColor = (element) => {\n const { children } = element.parentElement;\n // h: 0 - 360\n const h = children[1].getAttribute(\"aria-valuenow\");\n // s: 0 - 1\n const s = children[3].getAttribute(\"aria-valuenow\");\n // v: 0 - 255, divide by 255\n const vMultipliedBy255 = children[5].getAttribute(\"aria-valuenow\");\n const v = Number(vMultipliedBy255) / 255;\n return tinycolor(`hsv(${h}, ${s}, ${v || 0})`).toHexString();\n };\n const setColor = (hex, element) => {\n hex = normalizeHex(hex);\n if (!addon.tab.redux.state || !addon.tab.redux.state.scratchGui) return;\n // The only way to reliably set color is to invoke eye dropper via click()\n // then faking that the eye dropper reported the value.\n const onEyeDropperClosed = ({ detail }) => {\n if (detail.action.type !== \"scratch-gui/color-picker/DEACTIVATE_COLOR_PICKER\") return;\n addon.tab.redux.removeEventListener(\"statechanged\", onEyeDropperClosed);\n setTimeout(() => {\n document.body.classList.remove(\"sa-hide-eye-dropper-background\");\n }, 50);\n };\n const onEyeDropperOpened = ({ detail }) => {\n if (detail.action.type !== \"scratch-gui/color-picker/ACTIVATE_COLOR_PICKER\") return;\n addon.tab.redux.removeEventListener(\"statechanged\", onEyeDropperOpened);\n addon.tab.redux.addEventListener(\"statechanged\", onEyeDropperClosed);\n setTimeout(() => {\n addon.tab.redux.dispatch({\n type: \"scratch-gui/color-picker/DEACTIVATE_COLOR_PICKER\",\n color: hex,\n });\n }, 50);\n };\n addon.tab.redux.addEventListener(\"statechanged\", onEyeDropperOpened);\n document.body.classList.add(\"sa-hide-eye-dropper-background\");\n element.click();\n };\n const addColorPicker = () => {\n const element = document.querySelector(\"button.scratchEyedropper\");\n rateLimiter.abort(false);\n addon.tab.redux.initialize();\n const defaultColor = getColor(element);\n const saColorPicker = Object.assign(document.createElement(\"div\"), {\n className: \"sa-color-picker sa-color-picker-code\",\n });\n addon.tab.displayNoneWhileDisabled(saColorPicker, { display: \"flex\" });\n const saColorPickerColor = Object.assign(document.createElement(\"input\"), {\n className: \"sa-color-picker-color sa-color-picker-code-color\",\n type: \"color\",\n value: defaultColor || \"#000000\",\n });\n const saColorPickerText = Object.assign(document.createElement(\"input\"), {\n className: addon.tab.scratchClass(\"input_input-form\", {\n others: \"sa-color-picker-text sa-color-picker-code-text\",\n }),\n type: \"text\",\n pattern: \"^#?([0-9a-fA-F]{3}){1,2}$\",\n placeholder: msg(\"hex\"),\n value: defaultColor || \"\",\n });\n saColorPickerColor.addEventListener(\"input\", () =>\n rateLimiter.limit(() => setColor((saColorPickerText.value = saColorPickerColor.value), element))\n );\n saColorPickerText.addEventListener(\"change\", () => {\n const { value } = saColorPickerText;\n if (!getHexRegex().test(value)) return;\n setColor((saColorPickerColor.value = normalizeHex(value)), element);\n });\n saColorPicker.appendChild(saColorPickerColor);\n saColorPicker.appendChild(saColorPickerText);\n element.parentElement.insertBefore(saColorPicker, element);\n };\n const ScratchBlocks = await addon.tab.traps.getBlockly();\n const originalShowEditor = ScratchBlocks.FieldColourSlider.prototype.showEditor_;\n ScratchBlocks.FieldColourSlider.prototype.showEditor_ = function (...args) {\n const r = originalShowEditor.call(this, ...args);\n addColorPicker();\n return r;\n };\n};\n","import codeEditorHandler from \"./code-editor.js\";\n\nexport default async (api) => {\n codeEditorHandler(api);\n};\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};","// Editor Animations (remake of Reactive Animation by )\n// By: SharkPool\n// By: reflow \n\n/* TODO\n- patch custom modal api when added\n- patch adding modals from addons (they dont use react)\n*/\n\nexport default async function({ addon }) {\n const mediaQuery = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const addonKey = \"addonAnimations-\";\n const animationTypes = {\n \"default\": \"cubic-bezier(0.63, 0.32, 0.08, 0.95)\",\n \"easeIn\": \"cubic-bezier(0.42, 0, 1.0, 1.0)\",\n \"easeOut\": \"cubic-bezier(0, 0, 0.58, 1.0)\",\n \"easeInOut\": \"cubic-bezier(0.42, 0, 0.58, 1.0)\",\n \"smoothStep\": \"cubic-bezier(0.25, 0.1, 0.25, 1.0)\",\n \"fastInSlowOut\": \"cubic-bezier(0.4, 0.0, 0.2, 1.0)\",\n \"sineIn\": \"cubic-bezier(0.47, 0, 0.745, 0.715)\",\n \"sineOut\": \"cubic-bezier(0.39, 0.575, 0.565, 1)\",\n \"sineInOut\": \"cubic-bezier(0.445, 0.05, 0.55, 0.95)\",\n \"quadIn\": \"cubic-bezier(0.55, 0.085, 0.68, 0.53)\",\n \"quadOut\": \"cubic-bezier(0.25, 0.46, 0.45, 0.94)\",\n \"quadInOut\": \"cubic-bezier(0.455, 0.03, 0.515, 0.955)\",\n \"cubicIn\": \"cubic-bezier(0.55, 0.055, 0.675, 0.19)\",\n \"cubicOut\": \"cubic-bezier(0.215, 0.61, 0.355, 1)\",\n \"cubicInOut\": \"cubic-bezier(0.645, 0.045, 0.355, 1)\",\n \"quartIn\": \"cubic-bezier(0.895, 0.03, 0.685, 0.22)\",\n \"quartOut\": \"cubic-bezier(0.165, 0.84, 0.44, 1)\",\n \"quartInOut\": \"cubic-bezier(0.77, 0, 0.175, 1)\",\n \"quintIn\": \"cubic-bezier(0.755, 0.05, 0.855, 0.06)\",\n \"quintOut\": \"cubic-bezier(0.23, 1, 0.32, 1)\",\n \"quintInOut\": \"cubic-bezier(0.86, 0, 0.07, 1)\",\n \"backIn\": \"cubic-bezier(0.6, -0.28, 0.74, 0.05)\",\n \"backOut\": \"cubic-bezier(0.18, 0.89, 0.32, 1.28)\",\n \"backInOut\": \"cubic-bezier(0.68, -0.55, 0.27, 1.55)\",\n \"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%)\",\n \"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%)\",\n \"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%)\",\n };\n const hasNoVariation = [\"default\", \"fastInSlowOut\", \"smoothStep\", \"elastic\", \"bounce\", \"emphasis\"];\n \n \n let needsInit = true, animateModals = true, animateLibraries = true, animateButtons = true,\n animationSpeed = 1, animationType = \"default\", animationDir = \"InOut\";\n let patchedBody = false, sbPatched = false, sbEverPatched = false, listenerAttached = false;\n\n const genStyles = () => `\n/* Top Bar Items */\n.${addonKey}top-bar-scaler {\n transition: transform ${getAnim(.1)};\n transform-origin: center center;\n transform-box: fill-box;\n}\n.${addonKey}top-bar-scaler:hover:not([addon-scale-stop=\"true\"]) {\n transform: scale(1.05);\n}\n.${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.${addonKey}static-scaler {\n transition: transform ${getAnim(.1)};\n transform-origin: center center;\n transform-box: fill-box;\n}\n.${addonKey}static-scaler:hover:not([addon-scale-stop=\"true\"]) {\n transform: scale(1.05);\n}\n.${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.${addonKey}static-scaler-big {\n transition: transform ${getAnim(.2)} !important;\n transform-origin: center center;\n transform-box: fill-box;\n}\n.${addonKey}static-scaler-big:hover {\n transform: scale(1.1);\n}\n.${addonKey}static-scaler-big:active {\n transform: scale(.9);\n}\n\n/* Custom Extension Button (Library) */\n.${addonKey}custom-ext-hover {\n transition: transform ${getAnim(.2)}, border ${getAnim(.5)};\n justify-content: center;\n border: none;\n}\n.${addonKey}custom-ext-hover:hover {\n border: solid 3px #00000050;\n}\n.${addonKey}custom-ext-hover:active {\n transform: scale(.95);\n border: none;\n}\n\n/* Library Items */\n.${addonKey}library-item-scaler div[class^=\"library-item_library-item\"] {\n transition: transform ${getAnim(.1)};\n transform-origin: center center;\n transform-box: fill-box;\n}\n.${addonKey}library-item-scaler div[class^=\"library-item_library-item\"]:hover {\n transform: scale(1.05);\n}\n.${addonKey}library-item-scaler div[class^=\"library-item_library-item\"]:active {\n transform: scale(.95);\n}\n\n/* Categories */\n.${addonKey}category-scaler div div[class=\"scratchCategoryMenuRow\"] {\n transition: transform ${getAnim(.1)};\n transform-origin: center center;\n transform-box: fill-box;\n}\n.${addonKey}category-scaler div div[class=\"scratchCategoryMenuRow\"]:hover {\n transform: scale(1.05);\n}\n.${addonKey}category-scaler div div[class=\"scratchCategoryMenuRow\"]:active {\n transform: scale(.95);\n}\n`;\n\n const styleElement = document.createElement(\"style\");\n styleElement.classList.add(\"addon-editorAnimations\");\n styleElement.textContent = genStyles();\n document.head.appendChild(styleElement);\n \n let animationEnabled = !mediaQuery.matches;\n mediaQuery.addEventListener(\"change\", (e) => {\n animationEnabled = !e.matches;\n });\n\n function requestAddonState() {\n animateModals = addon.settings.get(\"animateModals\");\n animateLibraries = addon.settings.get(\"animateLibraries\");\n animateButtons = addon.settings.get(\"animateButtons\");\n animationType = addon.settings.get(\"animationType\");\n animationDir = addon.settings.get(\"animationDir\");\n const oldSpeed = animationSpeed;\n animationSpeed = 1 / (Number(addon.settings.get(\"animateSpeed\")) / 100);\n if (oldSpeed !== animationSpeed) styleElement.textContent = genStyles();\n }\n\n function getEasing() {\n if (hasNoVariation.includes(animationType)) {\n return animationTypes[animationType];\n } else {\n return animationTypes[animationType + animationDir];\n }\n }\n\n function getAnim(time) {\n time *= animationSpeed;\n return `${time}s ${getEasing()}`;\n };\n\n function observeMenuScalers(element, observerSub, observerAtt) {\n if (!animateModals) return;\n if (element.hasAttribute(\"addon-scale-listeners-bound\")) return;\n\n const onMouseOver = () => element.setAttribute(\"addon-scale-stop\", true);\n const onMouseOut = () => element.setAttribute(\"addon-scale-stop\", false);\n\n element.addEventListener(\"mouseover\", onMouseOver);\n element.addEventListener(\"mouseout\", onMouseOut);\n element.setAttribute(\"addon-scale-listeners-bound\", \"true\");\n element.setAttribute(\"addon-scale-stop\", true);\n\n const observer = new MutationObserver(() => {\n if (\n !element.classList.contains(\"menu-bar_active\") &&\n element.querySelector(\"nav\")?.style?.opacity !== \"1\"\n ) {\n element.removeEventListener(\"mouseover\", onMouseOver);\n element.removeEventListener(\"mouseout\", onMouseOut);\n element.removeAttribute(\"addon-scale-listeners-bound\");\n element.setAttribute(\"addon-scale-stop\", false);\n observer.disconnect();\n }\n });\n observer.observe(\n element,\n { subtree: observerSub, attributes: true, attributeFilter: observerAtt }\n );\n }\n\n function handleOpenAnimation(elementName) {\n const type = elementName.endsWith(\"Library\") ? \"library\" : elementName.endsWith(\"Menu\") ? \"menu\" : \"modal\";\n\n if (!animateLibraries && type === \"library\") return;\n if (!animateModals && type !== \"library\") return;\n\n let element;\n let animTime = 200;\n if (type === \"menu\") {\n if (elementName === \"ctxMenu\") element = document.querySelector(`div[class*=\"blocklyContextMenu\"]`);\n else if (elementName === \"guiCtxMenu\") element = document.querySelector(`nav[class*=\"context-menu_context-menu\"][class*=\"react-contextmenu--visible\"]`);\n else {\n element = Array.from(document.querySelectorAll(`div[class*=\"menu-bar_menu-bar-menu_\"] ul[class*=\"menu_menu_\"]`));\n if (!element.length) return;\n element = element.find((e) => !e.hasAttribute(\"style\"));\n }\n if (!element) return;\n\n if (type === \"menu\") {\n const menuItem = element.parentNode.parentNode;\n setTimeout(() => observeMenuScalers(menuItem, false, [\"class\"]), 10);\n }\n\n const ogHeight = element.getBoundingClientRect().height;\n element.style.overflow = \"hidden\";\n element.style.transition = `transform ${getAnim(.2)}`;\n element.style.transformOrigin = \"left top\";\n if (elementName === \"guiCtxMenu\") animTime = 500;\n else element.style.transform = \"translateY(-2px) scale(.999)\";\n\n const animation = element.animate(\n [{ height: \"0px\", opacity: 0 }, { height: `${ogHeight}px`, opacity: 1 }],\n { duration: animTime * animationSpeed, easing: getEasing() }\n );\n animation.onfinish = () => {\n element.style.overflow = \"\";\n };\n return;\n }\n\n element = document.querySelector(`div[class=\"ReactModalPortal\"] div[class*=\"ReactModal__Overlay\"]`)?.firstChild;\n if (!element) return;\n if (type === \"library\") {\n animTime = 500;\n if (elementName === \"extensionLibrary\" || elementName === \"costumeLibrary\") element.style.transformOrigin = \"left bottom\";\n else element.style.transformOrigin = \"center bottom\";\n }\n\n element.animate(\n [{ transform: \"scale(0)\", opacity: 0 }, { transform: \"scale(1)\", opacity: 1 }],\n { duration: animTime * animationSpeed, easing: getEasing() }\n );\n }\n\n function attachCloseHijack(elementName) {\n const type = elementName.endsWith(\"Library\") ? \"library\" : elementName.endsWith(\"Menu\") ? \"menu\" : \"modal\";\n if (type === \"menu\" || patchedBody) return;\n\n if (!animateLibraries && type === \"library\") return;\n if (!animateModals && type !== \"library\") return;\n\n // Monkey Patch\n const ogRemoveChild = document.body.constructor.prototype.removeChild;\n document.body.constructor.prototype.removeChild = function(child) {\n const element = document.querySelector(`div[class=\"ReactModalPortal\"]`);\n if (!element) return ogRemoveChild.call(this, child);\n\n let animTime = 200;\n patchedBody = true;\n if (child === element) {\n const child = element.firstChild;\n if (child) {\n const animClone = child.cloneNode(true);\n animClone.style.position = \"fixed\";\n animClone.style.top = child.getBoundingClientRect().top + \"px\";\n animClone.style.left = child.getBoundingClientRect().left + \"px\";\n animClone.style.zIndex = \"99999\";\n animClone.style.pointerEvents = \"none\";\n if (type === \"library\") {\n animTime = 500;\n if (elementName === \"extensionLibrary\" || elementName === \"costumeLibrary\") animClone.style.transformOrigin = \"left bottom\";\n else animClone.style.transformOrigin = \"center bottom\";\n }\n document.body.appendChild(animClone);\n\n animClone.animate(\n [{ opacity: 1 }, { opacity: 0 }],\n { duration: animTime * animationSpeed, easing: getEasing() }\n );\n const animation = animClone.firstChild.animate(\n [{ transform: \"scale(1)\", opacity: 1 }, { transform: \"scale(0)\", opacity: 0 }],\n { duration: animTime * animationSpeed, easing: getEasing() }\n );\n animation.onfinish = () => {\n animClone.remove();\n ogRemoveChild.call(element.parentNode, element);\n };\n document.body.constructor.prototype.removeChild = ogRemoveChild;\n patchedBody = false;\n return child;\n }\n }\n return ogRemoveChild.call(this, child);\n };\n }\n\n function compileClasses(optLibrary) {\n if (!animateButtons) return;\n const classMapper = new Map();\n\n if (optLibrary) {\n const collapser = document.querySelector(`button[class^=\"library_library-filter-collapse\"]`);\n const filterDiv = document.querySelector(`div[class^=\"library_library-filter-bar\"]`);\n collapser.style.transform = \"rotateY(180deg)\";\n collapser.addEventListener(\"click\", (e) => {\n e.preventDefault();\n const isClosed = collapser.hasAttribute(\"closed\");\n if (isClosed) {\n collapser.style.transform = \"rotateY(180deg)\";\n collapser.removeAttribute(\"closed\");\n filterDiv.style.display = \"\";\n filterDiv.animate(\n [{ width: \"0px\", opacity: 0 }, { width: \"342px\", opacity: 1 }],\n { duration: 300, easing: getEasing() }\n );\n } else {\n collapser.style.transform = \"rotateY(0deg)\";\n const animation = filterDiv.animate(\n [{ width: \"342px\", opacity: 1 }, { width: \"0px\", opacity: 0 }],\n { duration: 300, easing: getEasing() }\n );\n animation.onfinish = () => {\n collapser.setAttribute(\"closed\", \"true\");\n filterDiv.style.display = \"none\";\n };\n }\n\n e.stopPropagation();\n });\n\n if (optLibrary === \"extensionLibrary\") {\n classMapper.set(\"custom-ext-hover\", [document.querySelector(`span[class*=\"button_outlined-button\"][class*=\"tag-button_tag-button\"]`)]);\n }\n classMapper.set(\"library-item-scaler\", [document.querySelector(`div[class*=\"library_library-scroll-grid\"]`)]);\n classMapper.set(\"static-scaler\", [\n document.querySelector(`span[class*=\"modal_back-button_\"]`),\n collapser\n ]);\n } else {\n classMapper.set(\"top-bar-scaler\", document.querySelectorAll(`div[class*=\"menu-bar_main-menu\"] div[class*=\"menu-bar_menu-bar-item\"][class*=\"hoverable\"]`));\n classMapper.set(\"category-scaler\", [document.querySelector(`div[class=\"blocklyToolboxDiv\"]`)]);\n classMapper.set(\"static-scaler\", [\n /* Blockly Button Texts */\n ...document.querySelectorAll(`g[class=\"blocklyFlyoutButton\"] text[class=\"blocklyText\"]`),\n /* Costume & Sound Assets */\n ...document.querySelectorAll(`div[class*=\"selector_list-item\"][class*=\"sprite-selector-item\"]`),\n /* Sprite Selector */\n ...document.querySelectorAll(`div[class*=\"sprite-selector_sprite\"][class*=\"sprite-selector-item\"]`),\n /* Backpack Selector */\n ...document.querySelectorAll(`div[class*=\"backpack_backpack-item\"][class*=\"sprite-selector-item\"]`),\n ]);\n classMapper.set(\"static-scaler-big\", [\n /* Sound & Costume Editor Buttons */\n ...document.querySelectorAll(`div[class*=\"sound-editor_effect-button\"]`),\n ...document.querySelectorAll(`div[class*=\"sound-editor_tool-button\"]`),\n ...document.querySelectorAll(`button[class*=\"sound-editor_round-button\"]`),\n ...document.querySelectorAll(`span[class*=\"tool-select-base_mod-tool-select\"]`),\n /* Project Controls */\n ...document.querySelectorAll(`div[class^=\"controls_controls-container\"] img`),\n /* Blockly Zoom */\n ...document.querySelectorAll(`g[class=\"blocklyZoom\"] image`),\n ]);\n }\n\n classMapper.forEach((elements, classN) => {\n for (const element of elements) {\n if (!element) continue;\n element.classList.add(addonKey + classN);\n }\n });\n needsInit = false;\n }\n\n function tryPatchScratchBlocks() {\n if (typeof ScratchBlocks !== \"object\") return;\n sbPatched = true;\n\n // some modals are from ScratchBlocks, patch them!\n queueMicrotask(() => {\n const ogSBPrompt = ScratchBlocks.prompt;\n ScratchBlocks.prompt = function(...args) {\n ogSBPrompt.call(this, ...args);\n\n handleOpenAnimation(\"modal\");\n attachCloseHijack(\"modal\");\n }\n\n if (sbEverPatched) return;\n sbEverPatched = true;\n\n const ogSBProcCreate = ScratchBlocks.Procedures.createProcedureDefCallback_\n ScratchBlocks.Procedures.createProcedureDefCallback_ = function(...args) {\n ogSBProcCreate.call(this, ...args);\n\n handleOpenAnimation(\"modal\");\n attachCloseHijack(\"modal\");\n }\n const ogSBProcEdit = ScratchBlocks.Procedures.editProcedureCallback_;\n ScratchBlocks.Procedures.editProcedureCallback_ = function(...args) {\n ogSBProcEdit.call(this, ...args);\n\n handleOpenAnimation(\"modal\");\n attachCloseHijack(\"modal\");\n }\n const ogContextMenuShow = ScratchBlocks.ContextMenu.show;\n ScratchBlocks.ContextMenu.show = function(...args) {\n ogContextMenuShow.call(this, ...args);\n handleOpenAnimation(\"ctxMenu\");\n }\n\n /* this isnt a modal, but we still want to patch it for animations */\n const ogInitButton = ScratchBlocks.FlyoutButton.prototype.show;\n ScratchBlocks.FlyoutButton.prototype.show = function(...args) {\n ogInitButton.call(this, ...args);\n queueMicrotask(() => compileClasses());\n }\n });\n }\n\n function attachListeners() {\n const spriteRow = document.querySelector(`div[class^=\"sprite-selector_items-wrapper\"]`);\n if (!spriteRow) return;\n\n document.addEventListener(\"contextmenu\", (event) => {\n let element = event.target.closest(`div[class*=\"sprite-selector_sprite-wrapper\"]`);\n if (element) element = element.firstChild;\n else element = event.target.closest(`div[class^=\"react-contextmenu-wrapper\"][class*=\"sprite-selector-item_sprite-selector\"]`);\n\n if (element) {\n setTimeout(() => {\n element.querySelector(\"nav\").style.opacity = 1;\n handleOpenAnimation(\"guiCtxMenu\");\n observeMenuScalers(element, true, [\"class\", \"style\"]);\n }, 10);\n }\n });\n\n listenerAttached = true;\n }\n\n function startListenerWorker() {\n const checkInEditor = () => !ReduxStore.getState().scratchGui.mode.isPlayerOnly;\n\n window.vm.on(\"workspaceUpdate\", () => {\n queueMicrotask(() => compileClasses());\n });\n\n let lastModalStateID, inEditor;\n ReduxStore.subscribe(() => {\n const reduxState = ReduxStore.getState().scratchGui;\n let entries = Object.entries(reduxState.modals);\n entries.push(...Object.entries(reduxState.menus));\n const genID = [\n ...entries, [\"tab\", reduxState.editorTab.activeTabIndex]\n ];\n const modalStateID = genID.map(entry => entry[1]).join(\".\");\n\n const currentlyInEditor = checkInEditor();\n if (inEditor !== currentlyInEditor) {\n inEditor = currentlyInEditor;\n if (inEditor) {\n sbPatched = false;\n listenerAttached = false;\n }\n }\n\n if (!sbPatched) tryPatchScratchBlocks();\n if (!listenerAttached) attachListeners();\n\n if (!needsInit && lastModalStateID === modalStateID) return;\n lastModalStateID = modalStateID;\n queueMicrotask(() => {\n compileClasses();\n for (const entry of entries) {\n if (entry[1] === true) {\n const name = entry[0];\n handleOpenAnimation(name);\n attachCloseHijack(name);\n compileClasses(name.endsWith(\"Library\") ? name : undefined);\n break;\n }\n }\n });\n });\n }\n\n if (typeof scaffolding === \"undefined\") startListenerWorker();\n\n addon.settings.addEventListener(\"change\", requestAddonState);\n addon.self.addEventListener(\"disabled\", () => {\n animateModals = false;\n animateLibraries = false;\n animateButtons = false;\n });\n addon.self.addEventListener(\"reenabled\", () => {\n animateModals = true;\n animateLibraries = true;\n animateButtons = true;\n });\n}","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nexport const resources = {\n \"userscript.js\": _js,\n};\n","export default async function ({ addon }) {\n const ScratchBlocks = await addon.tab.traps.getBlockly();\n\n // Rerender the dragged block when updating the insertion marker\n const ogConnectMarker = ScratchBlocks.InsertionMarkerManager.prototype.connectMarker_;\n ScratchBlocks.InsertionMarkerManager.prototype.connectMarker_ = function () {\n ogConnectMarker.call(this);\n if (!addon.self.disabled && this.firstMarker_) {\n const block = this?.workspace_?.currentGesture_?.blockDragger_?.draggingBlock_;\n block.noMoveConnection = true;\n if (block) block.render(false);\n }\n };\n const ogDisconnectMarker = ScratchBlocks.InsertionMarkerManager.prototype.disconnectMarker_;\n ScratchBlocks.InsertionMarkerManager.prototype.disconnectMarker_ = function () {\n ogDisconnectMarker.call(this);\n if (!addon.self.disabled && this.firstMarker_) {\n const block = this?.workspace_?.currentGesture_?.blockDragger_?.draggingBlock_;\n block.noMoveConnection = true;\n if (block) block.render(false);\n }\n };\n\n const ogDraw = ScratchBlocks.BlockSvg.prototype.renderDraw_;\n const ogMoveConnections = ScratchBlocks.BlockSvg.prototype.renderMoveConnections_;\n ScratchBlocks.BlockSvg.prototype.renderDraw_ = function (iconWidth, inputRows) {\n if (addon.self.disabled) return ogDraw.call(this, iconWidth, inputRows);\n\n // If the block contains a statement (C) input and has an insertion marker,\n // use that to calculate the height of the statement inputs\n let computeBlock = this;\n if (this?.workspace?.currentGesture_?.blockDragger_?.draggedConnectionManager_) {\n const dragger = this.workspace.currentGesture_.blockDragger_;\n const manager = dragger.draggedConnectionManager_;\n if (\n manager.markerConnection_ &&\n manager.firstMarker_ &&\n dragger.draggingBlock_ == this &&\n dragger.draggingBlock_.type == manager.firstMarker_.type\n ) {\n if (inputRows.some((row) => row.some((input) => input.type === ScratchBlocks.NEXT_STATEMENT))) {\n computeBlock = manager.firstMarker_;\n }\n }\n }\n\n // Change the height of substacks\n // (If we set inputRows to computeBlock.renderCompute_,\n // the references to the inputs would be wrong\n // so they just won't update properly)\n if (computeBlock !== this) {\n const _inputRows = computeBlock.renderCompute_(iconWidth);\n for (let i = 0; i < inputRows.length; i++) {\n const row = inputRows[i];\n let update = false;\n for (const input of row) {\n if (input.type === ScratchBlocks.NEXT_STATEMENT) update = true;\n }\n if (update) row.height = Math.max(row.height, _inputRows[i].height);\n }\n }\n\n ogDraw.call(this, iconWidth, inputRows);\n\n // Moving the connections of a block while it's being dragged breaks it,\n // so don't\n if (computeBlock === this && !this.noMoveConnection) ogMoveConnections.call(this);\n this.noMoveConnection = false;\n };\n ScratchBlocks.BlockSvg.prototype.renderMoveConnections_ = function () {\n if (addon.self.disabled) return ogMoveConnections.call(this);\n // Do nothing (this function is instead called by renderDraw_)\n };\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};\n","export default async function ({ addon, console }) {\n const vm = addon.tab.traps.vm;\n\n const updateStyles = () => {\n previewInner.classList.toggle(\"sa-comment-preview-delay\", addon.settings.get(\"delay\") !== \"none\");\n previewInner.classList.toggle(\"sa-comment-preview-reduce-transparency\", addon.settings.get(\"reduce-transparency\"));\n previewInner.classList.toggle(\"sa-comment-preview-fade\", !addon.settings.get(\"reduce-animation\"));\n };\n\n const afterDelay = (cb) => {\n if (!previewInner.classList.contains(\"sa-comment-preview-hidden\")) {\n // If not hidden, updating immediately is preferred\n cb();\n return;\n }\n const delay = addon.settings.get(\"delay\");\n if (delay === \"long\") return setTimeout(cb, 500);\n if (delay === \"short\") return setTimeout(cb, 300);\n cb();\n };\n\n let hoveredElement = null;\n let showTimeout = null;\n let mouseX = 0;\n let mouseY = 0;\n let doNotShowUntilMoveMouse = false;\n\n const previewOuter = document.createElement(\"div\");\n previewOuter.classList.add(\"sa-comment-preview-outer\");\n const previewInner = document.createElement(\"div\");\n previewInner.classList.add(\"sa-comment-preview-inner\");\n previewInner.classList.add(\"sa-comment-preview-hidden\");\n updateStyles();\n addon.settings.addEventListener(\"change\", updateStyles);\n previewOuter.appendChild(previewInner);\n document.body.appendChild(previewOuter);\n\n const getBlock = (id) => vm.editingTarget.blocks.getBlock(id) || vm.runtime.flyoutBlocks.getBlock(id);\n const getComment = (block) => block && block.comment && vm.editingTarget.comments[block.comment];\n const getProcedureDefinitionBlock = (procCode) => {\n const procedurePrototype = Object.values(vm.editingTarget.blocks._blocks).find(\n (i) => i.opcode === \"procedures_prototype\" && i.mutation.proccode === procCode\n );\n if (procedurePrototype) {\n // Usually `parent` will exist but sometimes it doesn't\n if (procedurePrototype.parent) {\n return getBlock(procedurePrototype.parent);\n }\n const id = procedurePrototype.id;\n return Object.values(vm.editingTarget.blocks._blocks).find(\n (i) => (i.opcode === \"procedures_definition\" || i.opcode === \"procedures_definition_return\") && i.inputs.custom_block && i.inputs.custom_block.block === id\n );\n }\n return null;\n };\n\n const setText = (text) => {\n previewInner.innerText = text;\n previewInner.classList.remove(\"sa-comment-preview-hidden\");\n updateMousePosition();\n };\n\n const updateMousePosition = () => {\n previewOuter.style.transform = `translate(${mouseX + 8}px, ${mouseY + 8}px)`;\n };\n\n const hidePreview = () => {\n if (hoveredElement) {\n hoveredElement = null;\n previewInner.classList.add(\"sa-comment-preview-hidden\");\n }\n };\n\n document.addEventListener(\"mouseover\", (e) => {\n if (addon.self.disabled) {\n return;\n }\n clearTimeout(showTimeout);\n if (doNotShowUntilMoveMouse) {\n return;\n }\n\n const el = e.target.closest(\".blocklyBubbleCanvas > g, .blocklyBlockCanvas .blocklyDraggable[data-id]\");\n if (el === hoveredElement) {\n // Nothing to do.\n return;\n }\n if (!el) {\n hidePreview();\n return;\n }\n\n let text = null;\n if (\n addon.settings.get(\"hover-view\") &&\n e.target.closest(\".blocklyBubbleCanvas > g\") &&\n // Hovering over the thin line that connects comments to blocks should never show a preview\n !e.target.closest(\"line\")\n ) {\n const collapsedText = el.querySelector(\"text.scratchCommentText\");\n if (!collapsedText) return;\n if (collapsedText.getAttribute(\"display\") !== \"none\") {\n const textarea = el.querySelector(\"textarea\");\n text = textarea.value;\n }\n } else if (e.target.closest(\".blocklyBlockCanvas .blocklyDraggable[data-id]\")) {\n const id = el.dataset.id;\n const block = getBlock(id);\n const comment = getComment(block);\n if (addon.settings.get(\"hover-view-block\") && comment) {\n text = comment.text;\n } else if (block && block.opcode === \"procedures_call\" && addon.settings.get(\"hover-view-procedure\")) {\n const procCode = block.mutation.proccode;\n const procedureDefinitionBlock = getProcedureDefinitionBlock(procCode);\n const procedureComment = getComment(procedureDefinitionBlock);\n if (procedureComment) {\n text = procedureComment.text;\n }\n }\n }\n\n if (text !== null && text.trim() !== \"\") {\n showTimeout = afterDelay(() => {\n hoveredElement = el;\n setText(text);\n });\n } else {\n hidePreview();\n }\n });\n\n document.addEventListener(\"mousemove\", (e) => {\n mouseX = e.clientX;\n mouseY = e.clientY;\n doNotShowUntilMoveMouse = false;\n if (addon.settings.get(\"follow-mouse\") && !previewInner.classList.contains(\"sa-comment-preview-hidden\")) {\n updateMousePosition();\n }\n });\n\n document.addEventListener(\n \"mousedown\",\n () => {\n hidePreview();\n doNotShowUntilMoveMouse = true;\n },\n {\n capture: true,\n }\n );\n}\n","// import ShowBroadcast from \"./show-broadcast.js\";\nimport DomHelpers from \"./DomHelpers.js\";\nimport UndoGroup from \"./UndoGroup.js\";\n\nexport default class DevTools {\n constructor(addon, msg, m) {\n this.addon = addon;\n this.msg = msg;\n this.m = m;\n /**\n * @type {VirtualMachine}\n */\n this.domHelpers = new DomHelpers(addon);\n\n this.codeTab = null;\n this.costTab = null;\n this.costTabBody = null;\n this.selVarID = null;\n this.canShare = false;\n\n this.mouseXY = { x: 0, y: 0 };\n }\n\n async init() {\n this.addContextMenus();\n while (true) {\n const root = await this.addon.tab.waitForElement(\"ul[class*=gui_tab-list_]\", {\n markAsSeen: true,\n reduxEvents: [\n \"scratch-gui/mode/SET_PLAYER\",\n \"fontsLoaded/SET_FONTS_LOADED\",\n \"scratch-gui/locales/SELECT_LOCALE\",\n ],\n reduxCondition: (state) => !state.scratchGui.mode.isPlayerOnly,\n });\n this.initInner(root);\n }\n }\n async addContextMenus() {\n const blockly = await this.addon.tab.traps.getBlockly();\n const oldCleanUpFunc = blockly.WorkspaceSvg.prototype.cleanUp;\n const self = this;\n blockly.WorkspaceSvg.prototype.cleanUp = function () {\n if (self.addon.settings.get(\"enableCleanUpPlus\")) {\n self.doCleanUp();\n } else {\n oldCleanUpFunc.call(this);\n }\n };\n\n let originalMsg = blockly.Msg.CLEAN_UP;\n if (this.addon.settings.get(\"enableCleanUpPlus\")) blockly.Msg.CLEAN_UP = this.m(\"clean-plus\");\n this.addon.settings.addEventListener(\"change\", () => {\n if (this.addon.settings.get(\"enableCleanUpPlus\")) blockly.Msg.CLEAN_UP = this.m(\"clean-plus\");\n else blockly.Msg.CLEAN_UP = originalMsg;\n });\n\n this.addon.tab.createBlockContextMenu(\n (items, block) => {\n items.push({\n enabled: blockly.clipboardXml_,\n text: this.m(\"paste\"),\n separator: true,\n _isDevtoolsFirstItem: true,\n callback: () => {\n let ids = this.getTopBlockIDs();\n\n document.dispatchEvent(\n new KeyboardEvent(\"keydown\", {\n keyCode: 86,\n ctrlKey: true,\n griff: true,\n })\n );\n\n setTimeout(() => {\n this.beginDragOfNewBlocksNotInIDs(ids);\n }, 10);\n },\n });\n return items;\n },\n { workspace: true }\n );\n this.addon.tab.createBlockContextMenu(\n (items, block) => {\n items.push(\n {\n enabled: true,\n text: this.m(\"make-space\"),\n _isDevtoolsFirstItem: true,\n callback: () => {\n this.doCleanUp(block);\n },\n separator: true,\n },\n {\n enabled: true,\n text: this.m(\"copy-all\"),\n callback: () => {\n this.eventCopyClick(block);\n },\n separator: true,\n },\n {\n enabled: true,\n text: this.m(\"copy-block\"),\n callback: () => {\n this.eventCopyClick(block, 1);\n },\n },\n {\n enabled: true,\n text: this.m(\"cut-block\"),\n callback: () => {\n this.eventCopyClick(block, 2);\n },\n }\n );\n // const BROADCAST_BLOCKS = [\"event_whenbroadcastreceived\", \"event_broadcast\", \"event_broadcastandwait\"];\n // if (BROADCAST_BLOCKS.includes(block.type)) {\n // // Show Broadcast\n // const broadcastId = this.showBroadcastSingleton.getAssociatedBroadcastId(block.id);\n // if (broadcastId) {\n // [\"Senders\", \"Receivers\"].forEach((showKey, i) => {\n // items.push({\n // enabled: true,\n // text: this.msg(`show-${showKey}`.toLowerCase()),\n // callback: () => {\n // this.showBroadcastSingleton[`show${showKey}`](broadcastId);\n // },\n // separator: i == 0,\n // });\n // });\n // }\n // }\n return items;\n },\n { blocks: true }\n );\n this.addon.tab.createBlockContextMenu(\n (items, block) => {\n if (block.getCategory() === \"data\" || block.getCategory() === \"data-lists\") {\n this.selVarID = block.getVars()[0];\n items.push({\n enabled: true,\n text: this.m(\"swap\", { var: block.getCategory() === \"data\" ? this.m(\"variables\") : this.m(\"lists\") }),\n callback: async () => {\n let wksp = this.getWorkspace();\n let v = wksp.getVariableById(this.selVarID);\n // prompt() returns Promise in desktop app\n let varName = await window.prompt(this.msg(\"replace\", { name: v.name }));\n if (varName) {\n this.doReplaceVariable(this.selVarID, varName, v.type);\n }\n },\n separator: true,\n });\n }\n return items;\n },\n { blocks: true, flyout: true }\n );\n }\n\n getWorkspace() {\n return Blockly.getMainWorkspace();\n }\n\n isCostumeEditor() {\n return this.costTab.className.indexOf(\"gui_is-selected\") >= 0;\n }\n\n /**\n * A nicely ordered version of the top blocks\n * @returns {[Blockly.Block]}\n */\n getTopBlocks() {\n let result = this.getOrderedTopBlockColumns();\n let columns = result.cols;\n /**\n * @type {[[Blockly.Block]]}\n */\n let topBlocks = [];\n for (const col of columns) {\n topBlocks = topBlocks.concat(col.blocks);\n }\n return topBlocks;\n }\n\n /**\n * A much nicer way of laying out the blocks into columns\n */\n doCleanUp(block) {\n let workspace = this.getWorkspace();\n let makeSpaceForBlock = block && block.getRootBlock();\n\n UndoGroup.startUndoGroup(workspace);\n\n let result = this.getOrderedTopBlockColumns(true);\n let columns = result.cols;\n let orphanCount = result.orphans.blocks.length;\n if (orphanCount > 0 && !block) {\n let message = this.msg(\"orphaned\", {\n count: orphanCount,\n });\n if (confirm(message)) {\n for (const block of result.orphans.blocks) {\n block.dispose();\n }\n } else {\n columns.unshift(result.orphans);\n }\n }\n\n let cursorX = 48;\n\n let maxWidths = result.maxWidths;\n\n for (const column of columns) {\n let cursorY = 64;\n let maxWidth = 0;\n\n for (const block of column.blocks) {\n let extraWidth = block === makeSpaceForBlock ? 380 : 0;\n let extraHeight = block === makeSpaceForBlock ? 480 : 72;\n let xy = block.getRelativeToSurfaceXY();\n if (cursorX - xy.x !== 0 || cursorY - xy.y !== 0) {\n block.moveBy(cursorX - xy.x, cursorY - xy.y);\n }\n let heightWidth = block.getHeightWidth();\n cursorY += heightWidth.height + extraHeight;\n\n let maxWidthWithComments = maxWidths[block.id] || 0;\n maxWidth = Math.max(maxWidth, Math.max(heightWidth.width + extraWidth, maxWidthWithComments));\n }\n\n cursorX += maxWidth + 96;\n }\n\n let topComments = workspace.getTopComments();\n for (const comment of topComments) {\n if (comment.setVisible) {\n comment.setVisible(false);\n comment.needsAutoPositioning_ = true;\n comment.setVisible(true);\n }\n }\n\n setTimeout(() => {\n // Locate unused local variables...\n let workspace = this.getWorkspace();\n let map = workspace.getVariableMap();\n let vars = map.getVariablesOfType(\"\");\n let unusedLocals = [];\n\n for (const row of vars) {\n if (row.isLocal) {\n let usages = map.getVariableUsesById(row.getId());\n if (!usages || usages.length === 0) {\n unusedLocals.push(row);\n }\n }\n }\n\n if (unusedLocals.length > 0) {\n const unusedCount = unusedLocals.length;\n let message = this.msg(\"unused-var\", {\n count: unusedCount,\n });\n for (let i = 0; i < unusedLocals.length; i++) {\n let orphan = unusedLocals[i];\n if (i > 0) {\n message += \", \";\n }\n message += orphan.name;\n }\n if (confirm(message)) {\n for (const orphan of unusedLocals) {\n workspace.deleteVariableById(orphan.getId());\n }\n }\n }\n\n // Locate unused local lists...\n let lists = map.getVariablesOfType(\"list\");\n let unusedLists = [];\n\n for (const row of lists) {\n if (row.isLocal) {\n let usages = map.getVariableUsesById(row.getId());\n if (!usages || usages.length === 0) {\n unusedLists.push(row);\n }\n }\n }\n if (unusedLists.length > 0) {\n const unusedCount = unusedLists.length;\n let message = this.msg(\"unused-list\", {\n count: unusedCount,\n });\n for (let i = 0; i < unusedLists.length; i++) {\n let orphan = unusedLists[i];\n if (i > 0) {\n message += \", \";\n }\n message += orphan.name;\n }\n if (confirm(message)) {\n for (const orphan of unusedLists) {\n workspace.deleteVariableById(orphan.getId());\n }\n }\n }\n\n UndoGroup.endUndoGroup(workspace);\n }, 100);\n }\n\n /**\n * Badly Orphaned - might want to delete these!\n * @param topBlock\n * @returns {boolean}\n */\n isBlockAnOrphan(topBlock) {\n return !!topBlock.outputConnection;\n }\n\n /**\n * Split the top blocks into ordered columns\n * @param separateOrphans true to keep all orphans separate\n * @returns {{orphans: {blocks: [Block], x: number, count: number}, cols: [Col]}}\n */\n getOrderedTopBlockColumns(separateOrphans) {\n let w = this.getWorkspace();\n let topBlocks = w.getTopBlocks();\n let maxWidths = {};\n\n if (separateOrphans) {\n let topComments = w.getTopComments();\n\n // todo: tie comments to blocks... find widths and width of block stack row...\n for (const comment of topComments) {\n // comment.autoPosition_();\n // Hiding and showing repositions the comment right next to it's block - nice!\n if (comment.setVisible) {\n comment.setVisible(false);\n comment.needsAutoPositioning_ = true;\n comment.setVisible(true);\n\n // let bb = comment.block_.svgPath_.getBBox();\n let right = comment.getBoundingRectangle().bottomRight.x;\n\n // Get top block for stack...\n let root = comment.block_.getRootBlock();\n let left = root.getBoundingRectangle().topLeft.x;\n maxWidths[root.id] = Math.max(right - left, maxWidths[root.id] || 0);\n }\n }\n }\n\n // Default scratch ordering is horrid... Lets try something more clever.\n\n /**\n * @type {Col[]}\n */\n let cols = [];\n const TOLERANCE = 256;\n let orphans = { x: -999999, count: 0, blocks: [] };\n\n for (const topBlock of topBlocks) {\n // let r = b.getBoundingRectangle();\n let position = topBlock.getRelativeToSurfaceXY();\n /**\n * @type {Col}\n */\n let bestCol = null;\n let bestError = TOLERANCE;\n\n if (separateOrphans && this.isBlockAnOrphan(topBlock)) {\n orphans.blocks.push(topBlock);\n continue;\n }\n\n // Find best columns\n for (const col of cols) {\n let err = Math.abs(position.x - col.x);\n if (err < bestError) {\n bestError = err;\n bestCol = col;\n }\n }\n\n if (bestCol) {\n // We found a column that we fitted into\n bestCol.x = (bestCol.x * bestCol.count + position.x) / ++bestCol.count; // re-average the columns as more items get added...\n bestCol.blocks.push(topBlock);\n } else {\n // Create a new column\n cols.push(new Col(position.x, 1, [topBlock]));\n }\n }\n\n // if (orphans.blocks.length > 0) {\n // cols.push(orphans);\n // }\n\n // Sort columns, then blocks inside the columns\n cols.sort((a, b) => a.x - b.x);\n for (const col of cols) {\n col.blocks.sort((a, b) => a.getRelativeToSurfaceXY().y - b.getRelativeToSurfaceXY().y);\n }\n\n return { cols: cols, orphans: orphans, maxWidths: maxWidths };\n }\n\n /**\n * Find all the uses of a named variable.\n * @param {string} id ID of the variable to find.\n * @return {!Array.} Array of block usages.\n */\n getVariableUsesById(id) {\n let uses = [];\n\n let topBlocks = this.getTopBlocks(true); // todo: Confirm this was the right getTopBlocks?\n for (const topBlock of topBlocks) {\n /** @type {!Array} */\n let kids = topBlock.getDescendants();\n for (const block of kids) {\n /** @type {!Array} */\n let blockVariables = block.getVarModels();\n if (blockVariables) {\n for (const blockVar of blockVariables) {\n if (blockVar.getId() === id) {\n uses.push(block);\n }\n }\n }\n }\n }\n\n return uses;\n }\n\n /**\n * Quick and dirty replace all instances of one variable / list with another variable / list\n * @param varId original variable name\n * @param newVarName new variable name\n * @param type type of variable (\"\" = variable, anything else is a list?\n */\n doReplaceVariable(varId, newVarName, type) {\n let wksp = this.getWorkspace();\n let v = wksp.getVariable(newVarName, type);\n if (!v) {\n alert(this.msg(\"var-not-exist\"));\n return;\n }\n let newVId = v.getId();\n\n UndoGroup.startUndoGroup(wksp);\n let blocks = this.getVariableUsesById(varId);\n for (const block of blocks) {\n try {\n if (type === \"\") {\n block.getField(\"VARIABLE\").setValue(newVId);\n } else {\n block.getField(\"LIST\").setValue(newVId);\n }\n } catch (e) {\n // ignore\n }\n }\n UndoGroup.endUndoGroup(wksp);\n }\n\n /*\n function doInjectScripts(codeString) {\n let w = getWorkspace();\n let xml = new XML(); // document.implementation.createDocument(null, \"xml\");\n let x = xml.xmlDoc.firstChild;\n\n let tree = math.parse(codeString);\n console.log(tree);\n\n const binaryOperatorTypes = {\n add: \"operator_add\",\n subtract: \"operator_subtract\",\n this.multiply: \"operator_multiply\",\n divide: \"operator_divide\",\n };\n\n const BLOCK_TYPE = {\n number: \"math_number\",\n text: \"text\",\n };\n\n function translateMathToXml(x, tree, shadowType) {\n let xShadowField = null;\n if (shadowType) {\n let xShadow = xml.newXml(x, \"shadow\", { type: shadowType });\n if (shadowType === BLOCK_TYPE.number) {\n xShadowField = xml.newXml(xShadow, \"field\", { name: \"NUM\" });\n } else if (shadowType === BLOCK_TYPE.text) {\n xShadowField = xml.newXml(xShadow, \"field\", { name: \"TEXT\" });\n }\n }\n\n if (!tree || !tree.type) {\n return;\n }\n\n if (tree.type === \"OperatorNode\") {\n let operatorType = binaryOperatorTypes[tree.fn];\n if (operatorType) {\n let xOp = newXml(x, \"block\", { type: operatorType });\n translateMathToXml(xml.newXml(xOp, \"value\", { name: \"NUM1\" }), tree.args[0], BLOCK_TYPE.number);\n translateMathToXml(xml.newXml(xOp, \"value\", { name: \"NUM2\" }), tree.args[1], BLOCK_TYPE.number);\n return;\n }\n\n return;\n }\n\n if (tree.type === \"ConstantNode\") {\n // number or text in quotes\n if (xShadowField) {\n xml.setAttr(xShadowField, { text: tree.value });\n }\n return;\n }\n\n if (tree.type === \"SymbolNode\") {\n // variable\n let xVar = xml.newXml(x, \"block\", { type: \"data_variable\" });\n xml.newXml(xVar, \"field\", { name: \"VARIABLE\", text: tree.name });\n return;\n }\n\n if (tree.type === \"FunctionNode\") {\n // Method Call\n if (tree.fn.name === \"join\") {\n let xOp = newXml(x, \"block\", { type: \"operator_join\" });\n translateMathToXml(xml.newXml(xOp, \"value\", { name: \"STRING1\" }), tree.args[0], BLOCK_TYPE.text);\n translateMathToXml(xml.newXml(xOp, \"value\", { name: \"STRING2\" }), tree.args[1], BLOCK_TYPE.text);\n return;\n }\n }\n }\n\n translateMathToXml(x, tree);\n console.log(x);\n\n let ids = Blockly.Xml.domToWorkspace(x, w);\n console.log(ids);\n }\n */\n /*\n function clickInject(e) {\n let codeString = window.prompt(\"Griffpatch: Enter an expression (i.e. a+2*3)\");\n if (codeString) {\n doInjectScripts(codeString);\n }\n e.preventDefault();\n return false;\n }\n */\n\n /**\n * Returns a Set of the top blocks in this workspace / sprite\n * @returns {Set} Set of top blocks\n */\n getTopBlockIDs() {\n let wksp = this.getWorkspace();\n let topBlocks = wksp.getTopBlocks();\n let ids = new Set();\n for (const block of topBlocks) {\n ids.add(block.id);\n }\n return ids;\n }\n\n /**\n * Initiates a drag event for all block stacks except those in the set of ids.\n * But why? - Because we know all the ids of the existing stacks before we paste / duplicate - so we can find the\n * new stack by excluding all the known ones.\n * @param ids Set of previously known ids\n */\n beginDragOfNewBlocksNotInIDs(ids) {\n if (!this.addon.settings.get(\"enablePasteBlocksAtMouse\")) {\n return;\n }\n let wksp = this.getWorkspace();\n let topBlocks = wksp.getTopBlocks();\n for (const block of topBlocks) {\n if (!ids.has(block.id)) {\n // console.log(\"I found a new block!!! - \" + block.id);\n // todo: move the block to the mouse pointer?\n let mouseXYClone = { x: this.mouseXY.x, y: this.mouseXY.y };\n block.setIntersects(true); // fixes offscreen block pasting in Turbo Warp\n this.domHelpers.triggerDragAndDrop(block.svgPath_, null, mouseXYClone);\n }\n }\n }\n\n updateMousePosition(e) {\n this.mouseXY.x = e.clientX;\n this.mouseXY.y = e.clientY;\n }\n\n eventMouseMove(e) {\n this.updateMousePosition(e);\n }\n\n eventKeyDown(e) {\n const switchCostume = (up) => {\n // todo: select previous costume\n let selected = this.costTabBody.querySelector(\"div[class*='sprite-selector-item_is-selected']\");\n let node = up ? selected.parentNode.previousSibling : selected.parentNode.nextSibling;\n if (node) {\n let wrapper = node.closest(\"div[class*=gui_flex-wrapper]\");\n node.querySelector(\"div[class^='sprite-selector-item_sprite-name']\").click();\n node.scrollIntoView({\n behavior: \"auto\",\n block: \"center\",\n inline: \"start\",\n });\n wrapper.scrollTop = 0;\n }\n };\n\n if (document.URL.indexOf(\"editor\") <= 0) {\n return;\n }\n\n let ctrlKey = e.ctrlKey || e.metaKey;\n\n if (e.keyCode === 37 && ctrlKey) {\n // Ctrl + Left Arrow Key\n if (document.activeElement.tagName === \"INPUT\") {\n return;\n }\n\n if (this.isCostumeEditor()) {\n switchCostume(true);\n e.cancelBubble = true;\n e.preventDefault();\n return true;\n }\n }\n\n if (e.keyCode === 39 && ctrlKey) {\n // Ctrl + Right Arrow Key\n if (document.activeElement.tagName === \"INPUT\") {\n return;\n }\n\n if (this.isCostumeEditor()) {\n switchCostume(false);\n e.cancelBubble = true;\n e.preventDefault();\n return true;\n }\n }\n\n if (e.keyCode === 86 && ctrlKey && !e.griff) {\n // Ctrl + V\n // Set a timeout so we can take control of the paste after the event\n let ids = this.getTopBlockIDs();\n setTimeout(() => {\n this.beginDragOfNewBlocksNotInIDs(ids);\n }, 10);\n }\n\n // if (e.keyCode === 220 && (!document.activeElement || document.activeElement.tagName === 'INPUT')) {\n //\n // }\n }\n\n eventCopyClick(block, blockOnly) {\n let wksp = this.getWorkspace();\n\n if (block) {\n block.select();\n let next = blockOnly ? block.getNextBlock() : null;\n if (next) {\n next.unplug(false); // setParent(null);\n }\n\n // separate child temporarily\n document.dispatchEvent(new KeyboardEvent(\"keydown\", { keyCode: 67, ctrlKey: true }));\n if (next || blockOnly === 2) {\n setTimeout(() => {\n if (next) {\n wksp.undo(); // undo the unplug above...\n }\n if (blockOnly === 2) {\n UndoGroup.startUndoGroup(wksp);\n block.dispose(true);\n UndoGroup.endUndoGroup(wksp);\n }\n }, 0);\n }\n }\n }\n\n eventMouseDown(e) {\n this.updateMousePosition(e);\n }\n\n eventMouseUp(e) {\n this.updateMousePosition(e);\n }\n\n initInner(root) {\n let guiTabs = root.childNodes;\n\n if (this.codeTab && guiTabs[0] !== this.codeTab) {\n // We have been CHANGED!!! - Happens when going to project page, and then back inside again!!!\n this.domHelpers.unbindAllEvents();\n }\n\n this.codeTab = guiTabs[0];\n this.costTab = guiTabs[1];\n this.costTabBody = document.querySelector(\"div[aria-labelledby=\" + this.costTab.id + \"]\");\n\n this.domHelpers.bindOnce(document, \"keydown\", (...e) => this.eventKeyDown(...e), true);\n this.domHelpers.bindOnce(document, \"mousemove\", (...e) => this.eventMouseMove(...e), true);\n this.domHelpers.bindOnce(document, \"mousedown\", (...e) => this.eventMouseDown(...e), true); // true to capture all mouse downs 'before' the dom events handle them\n this.domHelpers.bindOnce(document, \"mouseup\", (...e) => this.eventMouseUp(...e), true);\n }\n}\n\nclass Col {\n /**\n * @param x {Number} x position (for ordering)\n * @param count {Number}\n * @param blocks {[Block]}\n */\n constructor(x, count, blocks) {\n /**\n * x position (for ordering)\n * @type {Number}\n */\n this.x = x;\n /**\n * @type {Number}\n */\n this.count = count;\n /**\n * @type {[Blockly.Block]}\n */\n this.blocks = blocks;\n }\n}\n","export default class DomHelpers {\n constructor(addon) {\n this.addon = addon;\n this.vm = addon.tab.traps.vm;\n /**\n * @type {eventDetails[]}\n */\n this.events = [];\n }\n\n /**\n * Simulate a drag and drop programmatically through javascript\n * @param selectorDrag\n * @param selectorDrop\n * @param mouseXY\n * @param [shiftKey=false]\n * @returns {boolean}\n */\n triggerDragAndDrop(selectorDrag, selectorDrop, mouseXY, shiftKey) {\n // function for triggering mouse events\n shiftKey = shiftKey || false;\n let fireMouseEvent = function (type, elem, centerX, centerY) {\n let evt = document.createEvent(\"MouseEvents\");\n evt.initMouseEvent(type, true, true, window, 1, 1, 1, centerX, centerY, shiftKey, false, false, false, 0, elem);\n elem.dispatchEvent(evt);\n };\n\n // fetch target elements\n let elemDrag = selectorDrag; // document.querySelector(selectorDrag);\n let elemDrop = selectorDrop; // document.querySelector(selectorDrop);\n if (!elemDrag /* || !elemDrop*/) {\n return false;\n }\n\n // calculate positions\n let pos = elemDrag.getBoundingClientRect();\n let center1X = Math.floor((pos.left + pos.right) / 2);\n let center1Y = Math.floor((pos.top + pos.bottom) / 2);\n\n // mouse over dragged element and mousedown\n fireMouseEvent(\"mouseover\", elemDrag, center1X, center1Y);\n fireMouseEvent(\"mousedown\", elemDrag, center1X, center1Y);\n\n // start dragging process over to drop target\n fireMouseEvent(\"dragstart\", elemDrag, center1X, center1Y);\n fireMouseEvent(\"drag\", elemDrag, center1X, center1Y);\n fireMouseEvent(\"mousemove\", elemDrag, center1X, center1Y);\n\n if (!elemDrop) {\n if (mouseXY) {\n // console.log(mouseXY);\n let center2X = mouseXY.x;\n let center2Y = mouseXY.y;\n fireMouseEvent(\"drag\", elemDrag, center2X, center2Y);\n fireMouseEvent(\"mousemove\", elemDrag, center2X, center2Y);\n }\n return false;\n }\n\n pos = elemDrop.getBoundingClientRect();\n let center2X = Math.floor((pos.left + pos.right) / 2);\n let center2Y = Math.floor((pos.top + pos.bottom) / 2);\n\n fireMouseEvent(\"drag\", elemDrag, center2X, center2Y);\n fireMouseEvent(\"mousemove\", elemDrop, center2X, center2Y);\n\n // trigger dragging process on top of drop target\n fireMouseEvent(\"mouseenter\", elemDrop, center2X, center2Y);\n fireMouseEvent(\"dragenter\", elemDrop, center2X, center2Y);\n fireMouseEvent(\"mouseover\", elemDrop, center2X, center2Y);\n fireMouseEvent(\"dragover\", elemDrop, center2X, center2Y);\n\n // release dragged element on top of drop target\n fireMouseEvent(\"drop\", elemDrop, center2X, center2Y);\n fireMouseEvent(\"dragend\", elemDrag, center2X, center2Y);\n fireMouseEvent(\"mouseup\", elemDrag, center2X, center2Y);\n\n return true;\n }\n\n bindOnce(dom, event, func, capture) {\n capture = !!capture;\n dom.removeEventListener(event, func, capture);\n dom.addEventListener(event, func, capture);\n this.events.push(new eventDetails(dom, event, func, capture));\n }\n\n unbindAllEvents() {\n for (const event of this.events) {\n event.dom.removeEventListener(event.event, event.func, event.capture);\n }\n this.events = [];\n }\n}\n\n/**\n * A record of an event\n */\nclass eventDetails {\n constructor(dom, event, func, capture) {\n this.dom = dom;\n this.event = event;\n this.func = func;\n this.capture = capture;\n }\n}\n","/**\n * This class is dedicated to maintaining the Undo stack of Blockly\n * It allows us to initiate an undo group such that all subsequent operations are recorded as a single\n * undoable transaction.\n */\nexport default class UndoGroup {\n /**\n * Start an Undo group - begin recording\n * @param workspace the workspace\n */\n static startUndoGroup(workspace) {\n const undoStack = workspace.undoStack_;\n if (undoStack.length) {\n undoStack[undoStack.length - 1]._devtoolsLastUndo = true;\n }\n }\n\n /**\n * End an Undo group - stops recording\n * @param workspace the workspace\n */\n static endUndoGroup(workspace) {\n const undoStack = workspace.undoStack_;\n // Events (responsible for undoStack updates) are delayed with a setTimeout(f, 0)\n // https://github.com/LLK/scratch-blocks/blob/f159a1779e5391b502d374fb2fdd0cb5ca43d6a2/core/events.js#L182\n setTimeout(() => {\n const group = generateUID();\n for (let i = undoStack.length - 1; i >= 0 && !undoStack[i]._devtoolsLastUndo; i--) {\n undoStack[i].group = group;\n }\n }, 0);\n }\n}\n\n/**\n * https://github.com/LLK/scratch-blocks/blob/f159a1779e5391b502d374fb2fdd0cb5ca43d6a2/core/events.js#L182\n * @returns {string}\n * @private\n */\nfunction generateUID() {\n const CHARACTERS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%()*+,-./:;=?@[]^_`{|}~\";\n let result = \"\";\n for (let i = 0; i < 20; i++) {\n result += CHARACTERS[Math.floor(Math.random() * CHARACTERS.length)];\n }\n return result;\n}\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nimport _asset from \"!url-loader!./icon--close.svg\";\nexport const resources = {\n \"userscript.js\": _js,\n \"icon--close.svg\": _asset,\n};\n","import DevTools from \"./DevTools.js\";\n\nexport default async function ({ addon, console, msg, safeMsg: m }) {\n const devTools = new DevTools(addon, msg, m);\n devTools.init();\n}\n","/* generated by pull.js */\nimport _js from \"./userscript.js\";\nimport _css from \"!css-loader!./userscript.css\";\nexport const resources = {\n \"userscript.js\": _js,\n \"userscript.css\": _css,\n};\n","/* eslint-disable */\nexport default async function ({ addon, console, msg }) {\n const Blockly = await addon.tab.traps.getBlockly();\n const vm = addon.tab.traps.vm;\n\n const SCRATCH_ITEMS_TO_HIDE = [\n \"RENAME_VARIABLE_ID\",\n \"DELETE_VARIABLE_ID\",\n \"NEW_BROADCAST_MESSAGE_ID\",\n // From rename-broadcasts addon\n \"RENAME_BROADCAST_MESSAGE_ID\",\n ];\n\n const canUseAsGlobalVariableName = (name, type) => {\n return !vm.runtime.getAllVarNamesOfType(type).includes(name);\n };\n\n const canUseAsLocalVariableName = (name, type) => {\n return !vm.editingTarget.lookupVariableByNameAndType(name, type);\n };\n\n const ADDON_ITEMS = {\n createGlobalVariable: {\n enabled: (name) => canUseAsGlobalVariableName(name, \"\"),\n createVariable: (workspace, name) => workspace.createVariable(name),\n },\n createLocalVariable: {\n enabled: (name) => canUseAsLocalVariableName(name, \"\"),\n createVariable: (workspace, name) => workspace.createVariable(name, \"\", null, true),\n },\n createGlobalList: {\n enabled: (name) => canUseAsGlobalVariableName(name, \"list\"),\n createVariable: (workspace, name) => workspace.createVariable(name, \"list\"),\n },\n createLocalList: {\n enabled: (name) => canUseAsLocalVariableName(name, \"list\"),\n createVariable: (workspace, name) => workspace.createVariable(name, \"list\", null, true),\n },\n createBroadcast: {\n enabled: (name) => canUseAsGlobalVariableName(name, \"broadcast_msg\"),\n createVariable: (workspace, name) => workspace.createVariable(name, \"broadcast_msg\"),\n },\n };\n\n let blocklyDropDownContent = null;\n let blocklyDropdownMenu = null;\n let searchBar = null;\n // Contains DOM and addon state\n let items = [];\n let searchedItems = [];\n // Tracks internal Scratch state\n let currentDropdownOptions = [];\n let resultOfLastGetOptions = [];\n\n const oldDropDownDivShow = Blockly.DropDownDiv.show;\n Blockly.DropDownDiv.show = function (...args) {\n blocklyDropdownMenu = document.querySelector(\".blocklyDropdownMenu\");\n if (!blocklyDropdownMenu) {\n return oldDropDownDivShow.call(this, ...args);\n }\n\n blocklyDropdownMenu.focus = () => {}; // no-op focus() so it can't steal it from the search bar\n\n searchBar = document.createElement(\"input\");\n addon.tab.displayNoneWhileDisabled(searchBar, { display: \"flex\" });\n searchBar.type = \"text\";\n searchBar.addEventListener(\"input\", updateSearch);\n searchBar.addEventListener(\"keydown\", handleKeyDownEvent);\n searchBar.classList.add(\"u-dropdown-searchbar\");\n blocklyDropdownMenu.insertBefore(searchBar, blocklyDropdownMenu.firstChild);\n\n items = Array.from(blocklyDropdownMenu.children)\n .filter((child) => child.tagName !== \"INPUT\")\n .map((element) => ({\n element,\n text: element.textContent,\n }));\n currentDropdownOptions = resultOfLastGetOptions;\n updateSearch();\n\n // Call the original show method after adding everything so that it can perform the correct size calculations\n const ret = oldDropDownDivShow.call(this, ...args);\n\n // Lock the size of the dropdown\n blocklyDropDownContent = Blockly.DropDownDiv.getContentDiv();\n blocklyDropDownContent.style.width = getComputedStyle(blocklyDropDownContent).width;\n blocklyDropDownContent.style.height = getComputedStyle(blocklyDropDownContent).height;\n\n // This is really strange, but if you don't reinsert the search bar into the DOM then focus() doesn't work\n blocklyDropdownMenu.insertBefore(searchBar, blocklyDropdownMenu.firstChild);\n searchBar.focus();\n\n return ret;\n };\n\n const oldDropDownDivClearContent = Blockly.DropDownDiv.clearContent;\n Blockly.DropDownDiv.clearContent = function () {\n oldDropDownDivClearContent.call(this);\n items = [];\n searchedItems = [];\n Blockly.DropDownDiv.content_.style.height = \"\";\n };\n\n const oldFieldDropdownGetOptions = Blockly.FieldDropdown.prototype.getOptions;\n Blockly.FieldDropdown.prototype.getOptions = function () {\n const options = oldFieldDropdownGetOptions.call(this);\n const block = this.sourceBlock_;\n const isStage = vm.editingTarget && vm.editingTarget.isStage;\n if (block) {\n if (block.category_ === \"data\") {\n options.push(getMenuItemMessage(\"createGlobalVariable\"));\n if (!isStage) {\n options.push(getMenuItemMessage(\"createLocalVariable\"));\n }\n } else if (block.category_ === \"data-lists\") {\n options.push(getMenuItemMessage(\"createGlobalList\"));\n if (!isStage) {\n options.push(getMenuItemMessage(\"createLocalList\"));\n }\n } else if (block.type === \"event_broadcast_menu\" || block.type === \"event_whenbroadcastreceived\") {\n options.push(getMenuItemMessage(\"createBroadcast\"));\n }\n }\n // Options aren't normally stored anywhere, so we'll store them ourselves.\n resultOfLastGetOptions = options;\n return options;\n };\n\n const oldFieldTextDropdownGetOptions = Blockly.FieldTextDropdown.prototype.getOptions;\n Blockly.FieldTextDropdown.prototype.getOptions = function () {\n const options = oldFieldTextDropdownGetOptions.call(this);\n const block = this.sourceBlock_;\n const isStage = vm.editingTarget && vm.editingTarget.isStage;\n if (block) {\n if (block.category_ === \"data\") {\n options.push(getMenuItemMessage(\"createGlobalVariable\"));\n if (!isStage) {\n options.push(getMenuItemMessage(\"createLocalVariable\"));\n }\n } else if (block.category_ === \"data-lists\") {\n options.push(getMenuItemMessage(\"createGlobalList\"));\n if (!isStage) {\n options.push(getMenuItemMessage(\"createLocalList\"));\n }\n } else if (block.type === \"event_broadcast_menu\" || block.type === \"event_whenbroadcastreceived\") {\n options.push(getMenuItemMessage(\"createBroadcast\"));\n }\n }\n // Options aren't normally stored anywhere, so we'll store them ourselves.\n resultOfLastGetOptions = options;\n return options;\n };\n\n const oldFieldVariableOnItemSelected = Blockly.FieldVariable.prototype.onItemSelected;\n Blockly.FieldVariable.prototype.onItemSelected = function (menu, menuItem) {\n const sourceBlock = this.sourceBlock_;\n if (sourceBlock && sourceBlock.workspace && searchBar.value.length !== 0) {\n const workspace = sourceBlock.workspace;\n const optionId = menuItem.getValue();\n\n if (Object.prototype.hasOwnProperty.call(ADDON_ITEMS, optionId)) {\n const addonItem = ADDON_ITEMS[optionId];\n Blockly.Events.setGroup(true);\n const variable = addonItem.createVariable(workspace, searchBar.value.trim());\n if (this.sourceBlock_) this.setValue(variable.getId());\n Blockly.Events.setGroup(false);\n return;\n }\n }\n return oldFieldVariableOnItemSelected.call(this, menu, menuItem);\n };\n\n function selectItem(item, click) {\n // You can't just use click() or focus() because Blockly uses mousedown and mouseup handlers, not click handlers.\n item.dispatchEvent(new MouseEvent(\"mousedown\", { relatedTarget: item, bubbles: true }));\n if (click) item.dispatchEvent(new MouseEvent(\"mouseup\", { relatedTarget: item, bubbles: true }));\n\n // Scroll the item into view if it is offscreen.\n const itemTop = item.offsetTop;\n const itemEnd = itemTop + item.offsetHeight;\n\n const scrollTop = blocklyDropDownContent.scrollTop;\n const scrollHeight = blocklyDropDownContent.offsetHeight;\n const scrollEnd = scrollTop + scrollHeight;\n\n if (scrollTop > itemTop) {\n blocklyDropDownContent.scrollTop = itemTop;\n } else if (itemEnd > scrollEnd) {\n blocklyDropDownContent.scrollTop = itemEnd - scrollHeight;\n }\n }\n\n function performSearch() {\n const rawQuery = searchBar.value.trim();\n const query = rawQuery.trim().toLowerCase();\n\n const rank = (item, index) => {\n // Negative number will hide\n // Higher numbers will appear first\n const option = currentDropdownOptions[index];\n const optionId = option[1];\n if (SCRATCH_ITEMS_TO_HIDE.includes(optionId)) {\n return query ? -1 : 0;\n } else if (Object.prototype.hasOwnProperty.call(ADDON_ITEMS, optionId)) {\n if (!query) {\n return -1;\n }\n const addonInfo = ADDON_ITEMS[optionId];\n if (addonInfo.enabled(rawQuery)) {\n item.element.lastChild.lastChild.textContent = getMenuItemMessage(optionId)[0];\n return 0;\n }\n return -1;\n }\n const itemText = item.text.toLowerCase();\n if (query === itemText) {\n return 2;\n }\n if (itemText.startsWith(query)) {\n return 1;\n }\n if (itemText.includes(query)) {\n return 0;\n }\n return -1;\n };\n return items\n .map((item, index) => ({\n item,\n score: rank(item, index),\n }))\n .sort(({ score: scoreA }, { score: scoreB }) => Math.max(0, scoreB) - Math.max(0, scoreA));\n }\n\n function updateSearch() {\n const previousSearchedItems = searchedItems;\n searchedItems = performSearch();\n let needToUpdateDOM = previousSearchedItems.length !== searchedItems.length;\n if (!needToUpdateDOM) {\n for (let i = 0; i < searchedItems.length; i++) {\n if (searchedItems[i].item !== previousSearchedItems[i].item) {\n needToUpdateDOM = true;\n break;\n }\n }\n }\n if (needToUpdateDOM && previousSearchedItems.length > 0) {\n for (const { item } of previousSearchedItems) {\n item.element.remove();\n }\n for (const { item } of searchedItems) {\n blocklyDropdownMenu.appendChild(item.element);\n }\n }\n for (const { item, score } of searchedItems) {\n item.element.hidden = score < 0;\n }\n }\n\n function handleKeyDownEvent(event) {\n if (event.key === \"Enter\") {\n // Reimplement enter to select item to account for hidden items and default to the top item.\n event.stopPropagation();\n event.preventDefault();\n\n const selectedItem = document.querySelector(\".goog-menuitem-highlight\");\n if (selectedItem && !selectedItem.hidden) {\n selectItem(selectedItem, true);\n return;\n }\n\n const selectedBlock = Blockly.selected;\n if (searchBar.value === \"\" && selectedBlock) {\n if (\n selectedBlock.type === \"event_broadcast\" ||\n selectedBlock.type === \"event_broadcastandwait\" ||\n selectedBlock.type === \"event_whenbroadcastreceived\"\n ) {\n // The top item of these dropdowns is always \"New message\"\n // When pressing enter on an empty search bar, we close the dropdown instead of making a new broadcast.\n Blockly.DropDownDiv.hide();\n return;\n }\n }\n for (const { item } of searchedItems) {\n if (!item.element.hidden) {\n selectItem(item.element, true);\n break;\n }\n }\n // If there is no top value, do nothing and leave the dropdown open\n } else if (event.key === \"Escape\") {\n Blockly.DropDownDiv.hide();\n } else if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n // Reimplement keyboard navigation to account for hidden items.\n event.preventDefault();\n event.stopPropagation();\n\n const items = searchedItems.filter((i) => i.score >= 0).map((i) => i.item);\n if (items.length === 0) {\n return;\n }\n\n let selectedIndex = -1;\n for (let i = 0; i < items.length; i++) {\n if (items[i].element.classList.contains(\"goog-menuitem-highlight\")) {\n selectedIndex = i;\n break;\n }\n }\n\n const lastIndex = items.length - 1;\n let newIndex = 0;\n if (event.key === \"ArrowDown\") {\n if (selectedIndex === -1 || selectedIndex === lastIndex) {\n newIndex = 0;\n } else {\n newIndex = selectedIndex + 1;\n }\n } else {\n if (selectedIndex === -1 || selectedIndex === 0) {\n newIndex = lastIndex;\n } else {\n newIndex = selectedIndex - 1;\n }\n }\n\n selectItem(items[newIndex].element, false);\n }\n }\n\n function getMenuItemMessage(message) {\n // Format used internally by Scratch:\n // [human readable name, internal name]\n return [msg(message, { name: searchBar?.value.trim() || \"\" }), message];\n }\n}\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};\n","export default class BlockItem {\n constructor(cls, procCode, labelID, y) {\n this.cls = cls;\n this.procCode = procCode;\n this.labelID = labelID;\n this.y = y;\n this.lower = procCode.toLowerCase();\n /**\n * An Array of block ids\n * @type {Array.}\n */\n this.clones = null;\n this.eventName = null;\n }\n\n /**\n * True if the blockID matches a black represented by this BlockItem\n * @param id\n * @returns {boolean}\n */\n matchesID(id) {\n if (this.labelID === id) {\n return true;\n }\n if (this.clones) {\n for (const cloneID of this.clones) {\n if (cloneID === id) {\n return true;\n }\n }\n }\n return false;\n }\n}\n","import BlockItem from \"./blockly/BlockItem.js\";\nimport BlockInstance from \"./blockly/BlockInstance.js\";\nimport Utils from \"./blockly/Utils.js\";\n\nexport default async function ({ addon, msg, console }) {\n const Blockly = await addon.tab.traps.getBlockly();\n\n class FindBar {\n constructor() {\n this.utils = new Utils(addon);\n\n this.prevValue = \"\";\n\n this.findBarOuter = null;\n this.findWrapper = null;\n this.findInput = null;\n this.dropdownOut = null;\n this.dropdown = new Dropdown(this.utils);\n\n document.addEventListener(\"keydown\", (e) => this.eventKeyDown(e), true);\n }\n\n get workspace() {\n return Blockly.getMainWorkspace();\n }\n\n createDom(root) {\n this.findBarOuter = document.createElement(\"div\");\n this.findBarOuter.className = \"sa-find-bar\";\n addon.tab.displayNoneWhileDisabled(this.findBarOuter, { display: \"flex\" });\n root.appendChild(this.findBarOuter);\n\n this.findWrapper = this.findBarOuter.appendChild(document.createElement(\"span\"));\n this.findWrapper.className = \"sa-find-wrapper\";\n\n this.dropdownOut = this.findWrapper.appendChild(document.createElement(\"label\"));\n this.dropdownOut.className = \"sa-find-dropdown-out\";\n\n this.findInput = this.dropdownOut.appendChild(document.createElement(\"input\"));\n this.findInput.className = addon.tab.scratchClass(\"input_input-form\", {\n others: \"sa-find-input\",\n });\n // for