From 1bbcab91ae6a85d0af41533cd751bb4a116725d8 Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Wed, 5 Mar 2025 16:03:23 +0100
Subject: [PATCH 01/10] create prettyblocks.js, prettyblocks.md
---
docs/Hammouda101010/prettyblocks.md | 14 +
extensions/Hammouda101010/prettyblocks.js | 755 ++++++++++++++++++++++
extensions/extensions.json | 1 +
3 files changed, 770 insertions(+)
create mode 100644 docs/Hammouda101010/prettyblocks.md
create mode 100644 extensions/Hammouda101010/prettyblocks.js
diff --git a/docs/Hammouda101010/prettyblocks.md b/docs/Hammouda101010/prettyblocks.md
new file mode 100644
index 0000000000..d12c21a4aa
--- /dev/null
+++ b/docs/Hammouda101010/prettyblocks.md
@@ -0,0 +1,14 @@
+# Pretty Blocks
+An extension to add strict formatting rules to your project.
+## Rules
+- Camel Case Only:
+ - Affects all variables and sprites.
+ - Forces the "CamelCase" naming convention.
+- Griffpatch Style:
+ - Affects variables only.
+ - Makes sure that global variables are in "uppercase" and local variables in "lowercase". much like how [griffpatch](https://www.youtube.com/@griffpatch) writes his variables.
+- No Capitalized Custom Blocks:
+ - Affects custom blocks only.
+ - Makes sure that custom block names aren't capitalized.
+## Creating Custom Rules
+if you want to create your own custom rules
\ No newline at end of file
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
new file mode 100644
index 0000000000..bdf30eeec4
--- /dev/null
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -0,0 +1,755 @@
+// Name: Pretty Blocks
+// ID: HamPrettyBlocks
+// Description: Add formating to your projects for more readability. Based of Prettier.
+// By: hammouda101010
+// License: MPL-2.0
+
+(function (Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("Pretty Blocks extension must run unsandboxed");
+ }
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+ const Cast = Scratch.Cast;
+ /**Checks if the extension is in "penguinmod.com".*/
+ // @ts-ignore
+ const _isPM = Scratch.extensions.isPenguinMod;
+ /**Checks if the extension is in "unsandboxed.org".*/
+ const isUnSandBoxed =
+ JSON.parse(vm.toJSON()).meta.platform.url === "https://unsandboxed.org/";
+ /**Checks if the extension is inside the editor.*/
+ const isEditor = typeof scaffolding === "undefined";
+
+ function formatError(errors, logToConsole = false) {
+ let msg = [];
+ for (const error of errors) {
+ switch (error.level) {
+ case "warn":
+ if (logToConsole) console.warn(error.msg);
+ msg.push(
+ Scratch.translate(
+ `(⚠️ warning) "${error.type}" [${error.subject}]: ${error.msg}`
+ )
+ );
+ break;
+ case "error":
+ if (logToConsole) console.error(error.msg);
+ msg.push(
+ Scratch.translate(
+ `(❌ error) "${error.type}" [${error.subject}]: ${error.msg}`
+ )
+ );
+ break;
+ }
+ }
+
+ return msg.join("\n").trim();
+ }
+
+ /**!
+ * altered modal code from
+ * @link https://gist.github.com/yuri-kiss/345ab1e729bd5d0a87506156635d0c83
+ * @license MIT
+ *
+ */
+ const errorModal = (titleName = "Alert", error = []) => {
+ //@ts-ignore
+ // run prompt to get a GUI to modify
+ ScratchBlocks.prompt();
+
+ // get the portal/modal and its header
+ const portal = document.querySelector("div.ReactModalPortal");
+ const header = portal.querySelector(
+ 'div[class*="modal_header-item-title_"]'
+ );
+
+ // add our own custom title
+ header.textContent = Cast.toString(titleName);
+ // get the portal/modal body
+ const portalBody = portal.querySelector('div[class^="prompt_body_"]');
+ const portalHolder = portalBody.parentElement.parentElement;
+
+ // set a custom modal height
+ portalHolder.style.width = "650px";
+
+ const errorString = formatError(error, true);
+
+ const errorHTML = `
+ `;
+ const contentHTML = `
${Scratch.translate("Errors found in project:")}
${errorHTML}
${Scratch.translate('Make sure to fix these manually or with the "Format project" button.')}
`;
+ const promptButtonPos = `${portalBody.querySelector("div[class^=prompt_button-row_]").outerHTML}
`;
+
+ portalBody.innerHTML = `${contentHTML}${promptButtonPos}`;
+ const textarea = portalBody.querySelector(
+ 'textarea[class^="data-url_code_1o8oS"]'
+ );
+
+ //@ts-expect-error
+ textarea.value = textarea.value.trim();
+
+ // creating our OK button
+ const okButton = portalBody.querySelector(
+ `button[class^="prompt_ok-button_"]`
+ );
+ okButton.previousElementSibling.remove();
+ okButton.parentElement.style.display = "block";
+ okButton.parentElement.style.verticalAlign = "bottom";
+
+ okButton.addEventListener("click", () => {
+ //@ts-expect-error - included in modal
+ portal.querySelector("div[class^=close-button_close-button_]").click();
+ });
+ };
+
+ /**!
+ * altered modal code from @yuri-kiss
+ * @link https://gist.github.com/yuri-kiss/345ab1e729bd5d0a87506156635d0c83
+ * @license MIT
+ *
+ * some code was also borrowed from SharkPool's Rigidbodies extension
+ * @link https://github.com/SharkPool-SP/SharkPools-Extensions/blob/main/extension-code/Rigidbodies.js
+ * @license MIT
+ */
+ const newRuleModal = (
+ titleName = "Alert",
+ vals = [],
+ deleteRule,
+ func = (name, regex, func, scope) => {}
+ ) => {
+ let name;
+ let regex;
+ let funcType = vals[0];
+ let scope = ["all"];
+ // in a Button Context, ScratchBlocks always exists
+ // @ts-ignore
+ ScratchBlocks.prompt(
+ !deleteRule ? titleName : "test",
+ "",
+ !deleteRule ? () => func(name, regex, funcType, scope) : () => func(name),
+ "Format Rules Manager",
+ "broadcast_msg"
+ );
+
+ if (deleteRule) {
+ const input = document.querySelector(
+ `div[class="ReactModalPortal"] input`
+ );
+
+ const delLabel = input.parentNode.previousSibling.cloneNode(true);
+ delLabel.textContent = titleName;
+ const selector = document.createElement("select");
+ selector.setAttribute("class", input.getAttribute("class"));
+ selector.addEventListener("input", (e) => {
+ // @ts-ignore
+ name = e.target.value;
+ });
+ vals.forEach((option) => {
+ let opt = document.createElement("option");
+ opt.value = option;
+ opt.text = option;
+ selector.appendChild(opt);
+ });
+
+ input.parentNode.append(delLabel, selector);
+ input.parentNode.previousSibling.remove();
+ input.remove();
+ } else {
+ const portal = document.querySelector("div.ReactModalPortal");
+ const portalBody = portal.querySelector('div[class^="prompt_body_"]');
+ const portalHolder = portalBody.parentElement.parentElement;
+
+ // set a custom modal height
+ portalHolder.style.height = "65%";
+
+ // set the modal HTML
+ portalBody.parentElement.style.height = "100%";
+ //@ts-ignore
+ portalBody.style.height = "calc(100% - 3.125rem)";
+ //@ts-ignore
+ portalBody.style.wordBreak = "break-all";
+ //@ts-ignore
+ portalBody.style.position = "relative";
+ //@ts-ignore
+ portalBody.style.overflowY = "auto";
+
+ const input = document.querySelector(
+ `div[class="ReactModalPortal"] input`
+ );
+ input.addEventListener("input", (e) => {
+ // @ts-ignore
+ name = e.target.value;
+ });
+
+ const regexLabel = input.parentNode.previousSibling.cloneNode(true);
+ regexLabel.textContent = "Regular Expression:";
+
+ const regexInput = document.createElement("input");
+ regexInput.setAttribute("class", input.getAttribute("class"));
+ regexInput.addEventListener("input", (e) => {
+ // @ts-ignore
+ regex = e.target.value;
+ });
+
+ // Format Function (The funtction to use when formatting the project.)
+ const funcTypeLabel = input.parentNode.previousSibling.cloneNode(true);
+ funcTypeLabel.textContent = "Format Function:";
+ const selector = document.createElement("select");
+ selector.setAttribute("class", input.getAttribute("class"));
+ selector.addEventListener("input", (e) => {
+ // @ts-ignore
+ funcType = e.target.value;
+ });
+ vals.forEach((option) => {
+ let opt = document.createElement("option");
+ opt.value = option.value;
+ opt.text = option.text;
+ selector.appendChild(opt);
+ });
+
+ // Rule Scopes (What types of objects the rule is allowed to access.)
+ const scopeLabel = input.parentNode.previousSibling.cloneNode(true);
+ scopeLabel.textContent = "Scopes:";
+
+ const scopeInput = document.createElement("input");
+ scopeInput.setAttribute("class", input.getAttribute("class"));
+ scopeInput.addEventListener("input", (e) => {
+ // @ts-ignore
+ scope = Cast.toString(e.target.value).split(" ");
+ });
+
+ input.parentNode.append(regexLabel, regexInput);
+ input.parentNode.append(funcTypeLabel, selector);
+ input.parentNode.append(scopeLabel, scopeInput);
+ }
+
+ runtime.stopAll();
+ };
+
+ /**Opens a Turbowarp-based Modal. Will Only Work on The Editor. */
+ function openModal(type, titleName, msg, func = undefined) {
+ // Check if we are in the editor
+ if (typeof scaffolding === "undefined") {
+ if (type === "error") {
+ errorModal(titleName, msg);
+ } else if (type === "prompt") {
+ //@ts-ignore
+ ScratchBlocks.prompt(
+ titleName,
+ "",
+ (value) => func(value),
+ Scratch.translate("Pretty Blocks"),
+ "broadcast_msg"
+ );
+ }
+ runtime.stopAll();
+ }
+ }
+
+ let ignoreList;
+
+ // Function Types for Custom Rules.
+ const funcTypes = [
+ { text: Scratch.translate("to uppercase"), value: "uppercase" },
+ { text: Scratch.translate("to lowercase"), value: "lowercase" },
+ { text: Scratch.translate("regex validation"), value: "regex_validation" },
+ { text: Scratch.translate("to lowercase"), value: "lowercase" },
+ { text: Scratch.translate("to camelCase"), value: "camelcase" },
+ { text: Scratch.translate("to snake_case"), value: "snakecase" },
+ { text: Scratch.translate("to PascalCase"), value: "pascal_case" },
+ { text: Scratch.translate("space trimming"), value: "trim" },
+ ];
+
+ let customRules = {};
+ let rules = {
+ camelCaseOnly: {
+ enabled: false,
+ level: "error",
+ msg: `"{val}" should be in camelCase.`,
+ regex: "/^[a-z]+(?:[A-Z][a-z]*)*$/",
+ },
+ griffpatchStyle: {
+ enabled: true,
+ level: "error",
+ msg: `"{val}" should be entirely in {isGlobal}. just as griffpatch intened it.`,
+ regex: "if /^[^a-z]+/ else /^[^A-Z]+/",
+ },
+ customNoCapitalized: {
+ enabled: false,
+ level: "error",
+ msg: `"{val}" should not be capitalized in a custom block.`,
+ regex: "/^[A-Z]/",
+ },
+ ...customRules,
+ };
+
+ // Turbowarp's extension storage
+ runtime.on("PROJECT_LOADED", () => {
+ vm.extensionManager.refreshBlocks();
+ try {
+ // @ts-ignore
+ const storage = JSON.parse(runtime.extensionStorage["HamPrettyBlocks"]);
+
+ if (storage) {
+ ignoreList = storage.ignoreList ? JSON.parse(storage.ignoreList) : [];
+
+ rules = storage.rules ? JSON.parse(storage.rules) : rules;
+ customRules = storage.customRules
+ ? JSON.parse(storage.customRules)
+ : customRules;
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ });
+
+ class HamPrettyBlocks {
+ constructor() {
+ this.formatErrors = [];
+ }
+
+ getInfo() {
+ return {
+ id: "HamPrettyBlocks",
+ name: Scratch.translate("Pretty Blocks"),
+ docsURI: "http://localhost:8000/Hammouda101010/prettyblocks", // https://extensions.turbowarp.org/Hammouda101010/prettyblocks
+ blocks: [
+ {
+ func: "checkFormatting",
+ blockType: Scratch.BlockType.BUTTON,
+ text: Scratch.translate("Check Project Formatting"),
+ },
+ "---",
+ {
+ func: "newFormatRule",
+ blockType: Scratch.BlockType.BUTTON,
+ text: Scratch.translate("Add Format Rule"),
+ },
+ {
+ func: "delFormatRule",
+ blockType: Scratch.BlockType.BUTTON,
+ text: Scratch.translate("Delete Format Rule"),
+ },
+ {
+ opcode: "ignoreVariable",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("ignore variable named [VAR_MENU]"),
+ arguments: {
+ VAR_MENU: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PRETTYBLOCKS_VARIABLES",
+ },
+ },
+ },
+ {
+ opcode: "ignoreCustomBlock",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("ignore custom block named [BLOCK_MENU]"),
+ arguments: {
+ BLOCK_MENU: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PRETTYBLOCKS_CUSTOM_BLOCKS",
+ },
+ },
+ },
+ {
+ opcode: "formatErrorsReporter",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("format errors"),
+ },
+ {
+ opcode: "fancyFormatErrors",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("fancify format errors [FORMAT_ERROR]"),
+ arguments: {
+ FORMAT_ERROR: {},
+ },
+ },
+ ],
+ menus: {
+ PRETTYBLOCKS_CUSTOM_BLOCKS: {
+ acceptReporters: true,
+ items: "_getCustomBlocksMenu",
+ },
+ PRETTYBLOCKS_VARIABLES: {
+ acceptReporters: true,
+ items: "_getVariablesMenu",
+ },
+ },
+ };
+ }
+ // Class Utilities
+ getLogic(str, opts) {
+ let codeLine = Cast.toString(str);
+ const logicCodeLineArray = codeLine.split(" ");
+ let boolResult = false;
+ let result = null;
+
+ // Check for each spaces
+ for (const line of logicCodeLineArray) {
+ // if it's a boolean
+ if (/<.*>/.test(line)) {
+ // is it a primitive value?
+ if (line === "") {
+ boolResult = true;
+ } else if (line === "") {
+ boolResult = false;
+ // otherwise, it's an argument
+ } else {
+ const optsArray = Object.values(opts).map((value) =>
+ Cast.toBoolean(value)
+ );
+ const boolArgs = codeLine.match(/<([^<>]+)>/g);
+ for (const boolVal of boolArgs) {
+ if (
+ Cast.toBoolean(
+ Object.keys(opts).indexOf(boolVal.replace(/[<>]/g, "")) ===
+ boolArgs.indexOf(boolVal)
+ )
+ ) {
+ boolResult = optsArray[boolArgs.indexOf(boolVal)];
+ }
+ }
+ }
+ } else {
+ if (line === "if") {
+ continue;
+ } else if (line === "else") {
+ if (!boolResult) {
+ result = logicCodeLineArray
+ .slice(logicCodeLineArray.indexOf(line) + 1)
+ .join(" ");
+ console.log(`second operator: ${result}`);
+ break;
+ }
+ } else if (boolResult === undefined) {
+ continue;
+ } else if (boolResult) {
+ result = line;
+ console.log(`first operator: ${result}`);
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+ getCustomBlocks() {
+ const targets = runtime.targets;
+ const customBlocks = [];
+
+ for (const target of targets) {
+ const blocks = target.blocks._blocks;
+ for (const blockId in blocks) {
+ const block = blocks[blockId];
+ if (block.opcode === "procedures_prototype") {
+ customBlocks.push(this.formatCustomBlock(block));
+ }
+ }
+ }
+
+ return customBlocks.length > 0 ? customBlocks : [];
+ }
+ getVariables() {
+ const stage = runtime.getTargetForStage();
+ const targets = runtime.targets;
+
+ // Sort the variables
+ const globalVars = Object.values(stage.variables)
+ .filter((v) => v.type !== "list")
+ .map((v) => v.name);
+
+ const allVars = targets
+ .filter((t) => t.isOriginal)
+ .map((t) => t.variables);
+ const localVars = allVars
+ .map((v) => Object.values(v))
+ .map((v) =>
+ v
+ .filter((v) => v.type !== "list" && !globalVars.includes(v.name))
+ .map((v) => v.name)
+ )
+ .flat(1);
+
+ const variables = {
+ local: localVars,
+ global: globalVars,
+ };
+
+ return variables;
+ }
+
+ checkFormatRule(rule, val, type, opts = {}) {
+ if (rules[rule].enabled) {
+ let str = Cast.toString(rules[rule].regex);
+ if (str.startsWith("if")) {
+ str = this.getLogic(str, opts);
+ }
+
+ const regex = new RegExp(str.split("/")[1], str.split("/")[2]);
+ console.log(regex);
+ console.log(regex.test(val));
+ console.log(regex.test(val));
+
+ switch (rule) {
+ case "griffpatchStyle":
+ if (!regex.test(val)) {
+ this.formatErrors.push({
+ type: type,
+ level: rules[rule].level,
+ subject: val,
+ msg: Scratch.translate(
+ Cast.toString(rules[rule].msg).replace(
+ /\{([^}]+)\}/g,
+ (e) => {
+ console.log(e);
+ if (e === "{isGlobal}") {
+ return opts.isGlobal ? "UPPERCASE" : "lowercase";
+ } else {
+ return val;
+ }
+ }
+ )
+ ),
+ });
+ }
+ break;
+ default:
+ if (!rules[rule].check(val)) {
+ this.formatErrors.push({
+ type: type,
+ level: rules[rule].level,
+ subject: val,
+ msg: Scratch.translate(rules[rule].msg),
+ });
+ }
+ break;
+ }
+ }
+ }
+
+ checkCustomFormatRules(val, type) {
+ for (const rule in customRules) {
+ if (
+ (customRules[rule].enabled &&
+ customRules[rule].scopes.includes(type)) ||
+ customRules[rule].scopes.includes("all")
+ ) {
+ if (!customRules[rule].check(val)) {
+ this.formatErrors.push({
+ type: type,
+ level: customRules[rule].level,
+ subject: val,
+ msg: Scratch.translate(customRules[rule].msg(val)),
+ });
+ }
+ }
+ }
+ }
+
+ _checkSpriteFormatting() {
+ const targets = runtime.targets;
+ for (const target of targets) {
+ if (target.isSprite()) {
+ // Format check
+ this.checkFormatRule("camelCaseOnly", target.sprite.name, "sprite");
+ this.checkCustomFormatRules(target.sprite.name, "sprite");
+ }
+ }
+ }
+ formatCustomBlock(block) {
+ const mutation = block.mutation;
+ const args = JSON.parse(mutation.argumentnames);
+
+ console.log(args);
+
+ let i = 0;
+ const name = mutation.proccode.replace(/%[snb]/g, (match) => {
+ let value = args[i++];
+ if (match === "%s") return isUnSandBoxed ? `[${value}]` : `(${value})`;
+ if (match === "%n" && isUnSandBoxed) return `(${value})`;
+ if (match === "%b") return `<${value}>`;
+ return match;
+ });
+ return name;
+ }
+
+ _checkCustomBlockFormatting() {
+ const blocks = !(this.getCustomBlocks().length > 0)
+ ? []
+ : this.getCustomBlocks();
+
+ for (const block of blocks) {
+ this.checkFormatRule("customNoCapitalized", block, "custom_block");
+ this.checkFormatRule("camelCaseOnly", block, "custom_block");
+ this.checkCustomFormatRules(block, "custom_block");
+ }
+ }
+
+ _checkVariableFormatting() {
+ const variables = this.getVariables();
+
+ // Local variable check
+ console.log("checking local variables");
+ for (const variable of variables.local) {
+ this.checkFormatRule("griffpatchStyle", variable, "variable", {
+ isGlobal: false,
+ });
+ this.checkFormatRule("camelCaseOnly", variable, "variable");
+ this.checkCustomFormatRules(variable, "variable");
+ }
+
+ // Global variable check
+ console.log("checking global variables");
+ for (const variable of variables.global) {
+ this.checkFormatRule("griffpatchStyle", variable, "variable", {
+ isGlobal: true,
+ });
+ this.checkFormatRule("camelCaseOnly", variable, "custom_block");
+ this.checkCustomFormatRules(variable, "variable");
+ }
+ }
+
+ checkFormatting() {
+ if (!isEditor) return;
+ this.formatErrors = [];
+
+ this._checkSpriteFormatting();
+ this._checkVariableFormatting();
+ this._checkCustomBlockFormatting();
+
+ if (this.formatErrors.length !== 0) {
+ openModal("error", "Format Error", this.formatErrors);
+ } else {
+ alert("No format errors found!");
+ }
+
+ console.log(formatError(this.formatErrors));
+ }
+ newFormatRule() {
+ if (!isEditor) return; // return if we aren't in the editor
+ newRuleModal(
+ Scratch.translate("New Rule:"),
+ funcTypes,
+ false,
+ (ruleName, regex, func, scopes) => {
+ if (!ruleName || !regex)
+ return alert(Scratch.translate("Missing inputs"));
+ try {
+ new RegExp(regex.split("/")[1], regex.split("/")[2]);
+ } catch {
+ alert(Scratch.translate("Invalid Regular Expression"));
+ return;
+ }
+
+ customRules[ruleName] = {
+ funcType: func.value,
+ enabled: true,
+ level: "warn",
+ scopes: scopes,
+ msg: `"{str}" isn't following the custom rule named {str}`,
+ regex: regex,
+ };
+
+ console.log(customRules);
+ }
+ );
+ }
+
+ delFormatRule() {
+ if (!isEditor) return; // return if we aren't in the editor
+ const customRulesList = Object.keys(customRules);
+ if (customRulesList.length < 1) return alert("There are no Custom Rules");
+
+ newRuleModal(
+ Scratch.translate("Delete Rule:"),
+ customRulesList,
+ true,
+ (name) => {
+ console.log(name);
+ delete customRules[name];
+
+ console.log(customRules);
+ }
+ );
+ }
+
+ formatErrorsReporter() {
+ return JSON.stringify(this.formatErrors);
+ }
+ fancyFormatErrors(args) {
+ try {
+ return formatError(JSON.parse(args.FORMAT_ERROR));
+ } catch {
+ return "";
+ }
+ }
+
+ // Dynamic Menus
+ _getVariablesMenu() {
+ const stage = runtime.getTargetForStage();
+ const targets = runtime.targets;
+
+ const globalVars = Object.values(stage.variables)
+ .filter((v) => v.type !== "list")
+ .map((v) => v.name);
+
+ const allVars = targets
+ .filter((t) => t.isOriginal)
+ .map((t) => t.variables);
+ const localVars = allVars
+ .map((v) => Object.values(v))
+ .map((v) =>
+ v
+ .filter((v) => v.type !== "list" && !globalVars.includes(v.name))
+ .map((v) => v.name)
+ )
+ .flat(1);
+
+ return localVars.concat(globalVars);
+ }
+
+ _getCustomBlocksMenu() {
+ const targets = runtime.targets;
+ const customBlocks = [];
+
+ for (const target of targets) {
+ const blocks = target.blocks._blocks;
+ for (const blockId in blocks) {
+ const block = blocks[blockId];
+ if (block.opcode === "procedures_prototype") {
+ customBlocks.push(this.formatCustomBlock(block));
+ }
+ }
+ }
+
+ return customBlocks.length > 0
+ ? customBlocks
+ : ["no custom blocks found"];
+ }
+ }
+
+ if (isEditor) {
+ vm.on("EXTENSION_ADDED", () => {
+ runtime.extensionStorage["HamPrettyBlocks"] = JSON.stringify({
+ rules: JSON.stringify(rules),
+ customRules: JSON.stringify(customRules),
+ ignore: JSON.stringify(ignoreList),
+ });
+ });
+ vm.on("BLOCKSINFO_UPDATE", () => {
+ runtime.extensionStorage["HamPrettyBlocks"] = JSON.stringify({
+ rules: JSON.stringify(rules),
+ customRules: JSON.stringify(customRules),
+ ignore: JSON.stringify(ignoreList),
+ });
+ });
+ }
+
+ // @ts-ignore
+ Scratch.extensions.register(new HamPrettyBlocks());
+})(Scratch);
diff --git a/extensions/extensions.json b/extensions/extensions.json
index d8ed71e33d..f22f23253d 100644
--- a/extensions/extensions.json
+++ b/extensions/extensions.json
@@ -89,6 +89,7 @@
"vercte/dictionaries",
"godslayerakp/http",
"godslayerakp/ws",
+ "Hammouda101010/prettyblocks",
"Lily/CommentBlocks",
"veggiecan/LongmanDictionary",
"CubesterYT/TurboHook",
From 50a96c7948b1b23a823e6a16c6da9d6d610d1ad3 Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Wed, 5 Mar 2025 16:29:36 +0100
Subject: [PATCH 02/10] refactor errorModal
---
extensions/Hammouda101010/prettyblocks.js | 61 ++++++++++++++++-------
1 file changed, 43 insertions(+), 18 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index bdf30eeec4..6d0919a2a5 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -58,16 +58,16 @@
const errorModal = (titleName = "Alert", error = []) => {
//@ts-ignore
// run prompt to get a GUI to modify
- ScratchBlocks.prompt();
+ ScratchBlocks.prompt(
+ "test",
+ "",
+ () => {},
+ Cast.toString(titleName),
+ "broadcast_msg"
+ );
// get the portal/modal and its header
const portal = document.querySelector("div.ReactModalPortal");
- const header = portal.querySelector(
- 'div[class*="modal_header-item-title_"]'
- );
-
- // add our own custom title
- header.textContent = Cast.toString(titleName);
// get the portal/modal body
const portalBody = portal.querySelector('div[class^="prompt_body_"]');
const portalHolder = portalBody.parentElement.parentElement;
@@ -77,20 +77,45 @@
const errorString = formatError(error, true);
- const errorHTML = `
- `;
- const contentHTML = `${Scratch.translate("Errors found in project:")}
${errorHTML}
${Scratch.translate('Make sure to fix these manually or with the "Format project" button.')}
`;
- const promptButtonPos = `${portalBody.querySelector("div[class^=prompt_button-row_]").outerHTML}
`;
+ // Create the custom modal elements
+ const labelA = document.createElement("p");
+ labelA.textContent = Scratch.translate(
+ "The extension has found errors in your project:"
+ );
- portalBody.innerHTML = `${contentHTML}${promptButtonPos}`;
- const textarea = portalBody.querySelector(
- 'textarea[class^="data-url_code_1o8oS"]'
+ // The error text area
+ const errorTextArea = document.createElement("textarea");
+
+ errorTextArea.setAttribute("class", "data-url_code_1o8oS");
+ errorTextArea.setAttribute("readonly", "true");
+ errorTextArea.setAttribute("spellcheck", "false");
+ errorTextArea.setAttribute("autocomplete", "false");
+
+ errorTextArea.style.display = "inline-block";
+ errorTextArea.style.width = "100%";
+ errorTextArea.style.height = "12rem";
+ errorTextArea.value = errorString;
+
+ const labelB = document.createElement("p");
+ labelB.textContent = Scratch.translate(
+ 'Make sure to fix them manualy or with the "Format Project" button.'
);
- //@ts-expect-error
- textarea.value = textarea.value.trim();
+ // Wrap them inside a div
+ const div = document.createElement("div");
+ div.setAttribute(
+ "style",
+ "display:inline-block;width:-webkit-fill-available;height:calc(100% - 2.75rem);"
+ );
+ div.setAttribute("class", "error_list_1o85");
+ div.append(labelA, errorTextArea, labelB);
+
+ const input = document.querySelector(`div[class="ReactModalPortal"] input`);
+ input.parentNode.append(div);
+ input.parentNode.previousSibling.remove();
+ input.remove();
+
+ errorTextArea.value = errorTextArea.value.trim();
// creating our OK button
const okButton = portalBody.querySelector(
From fa1f0bbfbdd54399c367b2f01e91c4e383e1cdc0 Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Thu, 6 Mar 2025 16:46:06 +0100
Subject: [PATCH 03/10] more patches GOD DANG IT!
---
docs/Hammouda101010/prettyblocks.md | 28 ++++-
extensions/Hammouda101010/prettyblocks.js | 123 +++++++++++++++++-----
images/Hammouda101010/prettyblocks.svg | 90 ++++++++++++++++
3 files changed, 215 insertions(+), 26 deletions(-)
create mode 100644 images/Hammouda101010/prettyblocks.svg
diff --git a/docs/Hammouda101010/prettyblocks.md b/docs/Hammouda101010/prettyblocks.md
index d12c21a4aa..8b79b074fd 100644
--- a/docs/Hammouda101010/prettyblocks.md
+++ b/docs/Hammouda101010/prettyblocks.md
@@ -1,5 +1,12 @@
# Pretty Blocks
An extension to add strict formatting rules to your project.
+## Table of Contents
+- [Pretty Blocks](#pretty-blocks)
+ - [Rules](#rules)
+ - [Creating Custom Rules](#creating-custom-rules)
+ - [RegBool](#regbool)
+ - [Format Functions](#format-functions)
+
## Rules
- Camel Case Only:
- Affects all variables and sprites.
@@ -11,4 +18,23 @@ An extension to add strict formatting rules to your project.
- Affects custom blocks only.
- Makes sure that custom block names aren't capitalized.
## Creating Custom Rules
-if you want to create your own custom rules
\ No newline at end of file
+If you want to create your own custom rules, first press the "Add Format Rule" button. then fill out these fields:
+- The rule name
+- The regular expression (or RegBool code)
+- The format function
+- And optionaly, the scopes
+### RegBool
+This is a "programming language" that can make simple boolean operations, in order to add logic to your custom rules.
+
+Here is the available syntax for RegBool:
+- ``: represents a boolean value, can be `true`, `false` or a boolean function.
+- `if & else`: the tenary operators
+### Format Functions
+There are a limited amount of functions available for custom rules. more rules might come at the future, but here are the available ones:
+- To uppercase: formats the subject's text to "UPPERCASE"
+- To lowercase: formats the subject's text to "lowercase"
+- regex validation: formats the subject's text to be compatible with the custom rule's regex.
+- To camelCase: formats the subject's text to [camelCase](https://en.wikipedia.org/wiki/Camel_case)
+- To snake_case: formats the subject's text to [snake_case](https://en.wikipedia.org/wiki/Snake_case)
+- To PascalCase: much like "To camelCase", but capitalize the first letter too.
+- Space trimming: trims the subject's text
\ No newline at end of file
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index 6d0919a2a5..f647831054 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -8,7 +8,7 @@
"use strict";
if (!Scratch.extensions.unsandboxed) {
- throw new Error("Pretty Blocks extension must run unsandboxed");
+ throw new Error("Pretty Blocks must run unsandboxed");
}
const vm = Scratch.vm;
@@ -156,7 +156,7 @@
!deleteRule ? titleName : "test",
"",
!deleteRule ? () => func(name, regex, funcType, scope) : () => func(name),
- "Format Rules Manager",
+ Scratch.translate("Format Rules Manager"),
"broadcast_msg"
);
@@ -211,7 +211,7 @@
});
const regexLabel = input.parentNode.previousSibling.cloneNode(true);
- regexLabel.textContent = "Regular Expression:";
+ regexLabel.textContent = Scratch.translate("Regular Expression:");
const regexInput = document.createElement("input");
regexInput.setAttribute("class", input.getAttribute("class"));
@@ -222,7 +222,7 @@
// Format Function (The funtction to use when formatting the project.)
const funcTypeLabel = input.parentNode.previousSibling.cloneNode(true);
- funcTypeLabel.textContent = "Format Function:";
+ funcTypeLabel.textContent = Scratch.translate("Format Function:");
const selector = document.createElement("select");
selector.setAttribute("class", input.getAttribute("class"));
selector.addEventListener("input", (e) => {
@@ -275,14 +275,74 @@
}
}
- let ignoreList;
+ const squareInputBlocks = ["HamPrettyBlocks_fancyFormatErrors"];
+
+ // Custom Square Block Shapes
+ const ogConverter = runtime._convertBlockForScratchBlocks.bind(runtime);
+ runtime._convertBlockForScratchBlocks = function (blockInfo, categoryInfo) {
+ const res = ogConverter(blockInfo, categoryInfo);
+ if (blockInfo.outputShape) res.json.outputShape = blockInfo.outputShape;
+ return res;
+ };
+
+ if (Scratch.gui)
+ Scratch.gui.getBlockly().then((SB) => {
+ // Custom Square Input Shape
+ const makeShape = (width, height) => {
+ width -= 10;
+ // prettier-ignore
+ height -= 8
+ return `
+ m 0 4
+ A 4 4 0 0 1 4 0 H ${width}
+ a 4 4 0 0 1 4 4
+ v ${height}
+ a 4 4 0 0 1 -4 4
+ H 4
+ a 4 4 0 0 1 -4 -4
+ z`
+ .replaceAll("\n", "")
+ .trim();
+ };
+
+ const ogRender = SB.BlockSvg.prototype.render;
+ SB.BlockSvg.prototype.render = function (...args) {
+ const data = ogRender.call(this, ...args);
+ if (this.svgPath_ && squareInputBlocks.includes(this.type)) {
+ this.inputList.forEach((input) => {
+ if (input.name.startsWith("ARRAY")) {
+ const block = input.connection.targetBlock();
+ if (
+ block &&
+ block.type === "text" &&
+ block.svgPath_ &&
+ block.type.startsWith("HamPrettyBlocks_menu_")
+ ) {
+ block.svgPath_.setAttribute(
+ "transform",
+ `scale(1, ${block.height / 33})`
+ );
+ block.svgPath_.setAttribute(
+ "d",
+ makeShape(block.width, block.height)
+ );
+ } else if (input.outlinePath) {
+ input.outlinePath.setAttribute("d", makeShape(46, 32));
+ }
+ }
+ });
+ }
+ return data;
+ };
+ });
+
+ let ignoreList = [];
// Function Types for Custom Rules.
const funcTypes = [
{ text: Scratch.translate("to uppercase"), value: "uppercase" },
{ text: Scratch.translate("to lowercase"), value: "lowercase" },
{ text: Scratch.translate("regex validation"), value: "regex_validation" },
- { text: Scratch.translate("to lowercase"), value: "lowercase" },
{ text: Scratch.translate("to camelCase"), value: "camelcase" },
{ text: Scratch.translate("to snake_case"), value: "snakecase" },
{ text: Scratch.translate("to PascalCase"), value: "pascal_case" },
@@ -342,6 +402,8 @@
id: "HamPrettyBlocks",
name: Scratch.translate("Pretty Blocks"),
docsURI: "http://localhost:8000/Hammouda101010/prettyblocks", // https://extensions.turbowarp.org/Hammouda101010/prettyblocks
+ color1: "#0071b0",
+ color2: "#006095",
blocks: [
{
func: "checkFormatting",
@@ -381,17 +443,22 @@
},
},
},
+ "---",
{
opcode: "formatErrorsReporter",
blockType: Scratch.BlockType.REPORTER,
- text: Scratch.translate("format errors"),
+ text: Scratch.translate("format errors"), // format errors
+ disableMonitor: true,
+ outputShape: 3,
},
{
opcode: "fancyFormatErrors",
blockType: Scratch.BlockType.REPORTER,
- text: Scratch.translate("fancify format errors [FORMAT_ERROR]"),
+ text: Scratch.translate(
+ "fancify format errors [ARRAY_FORMAT_ERROR]"
+ ),
arguments: {
- FORMAT_ERROR: {},
+ ARRAY_FORMAT_ERROR: {},
},
},
],
@@ -416,14 +483,14 @@
// Check for each spaces
for (const line of logicCodeLineArray) {
- // if it's a boolean
- if (/<.*>/.test(line)) {
- // is it a primitive value?
+ // Check if it's a boolean
+ if (/\<([^>]+)\>/g.test(line)) {
+ // Is it a primitive boolean value?
if (line === "") {
boolResult = true;
} else if (line === "") {
boolResult = false;
- // otherwise, it's an argument
+ // Otherwise, it's an argument
} else {
const optsArray = Object.values(opts).map((value) =>
Cast.toBoolean(value)
@@ -441,6 +508,7 @@
}
}
} else {
+ // Tenary operator logic
if (line === "if") {
continue;
} else if (line === "else") {
@@ -494,9 +562,8 @@
const localVars = allVars
.map((v) => Object.values(v))
.map((v) =>
- v
- .filter((v) => v.type !== "list" && !globalVars.includes(v.name))
- .map((v) => v.name)
+ // prettier-ignore
+ v.filter((v) => v.type !== "list" && !globalVars.includes(v.name)).map((v) => v.name)
)
.flat(1);
@@ -516,9 +583,6 @@
}
const regex = new RegExp(str.split("/")[1], str.split("/")[2]);
- console.log(regex);
- console.log(regex.test(val));
- console.log(regex.test(val));
switch (rule) {
case "griffpatchStyle":
@@ -549,7 +613,9 @@
type: type,
level: rules[rule].level,
subject: val,
- msg: Scratch.translate(rules[rule].msg),
+ msg: Scratch.translate(
+ Cast.toString(rules[rule].msg).replace(/\{([^}]+)\}/g, val)
+ ),
});
}
break;
@@ -564,12 +630,17 @@
customRules[rule].scopes.includes(type)) ||
customRules[rule].scopes.includes("all")
) {
- if (!customRules[rule].check(val)) {
+ let str = Cast.toString(rules[rule].regex);
+
+ const regex = new RegExp(str.split("/")[1], str.split("/")[2]);
+ if (!regex.test(val)) {
this.formatErrors.push({
type: type,
level: customRules[rule].level,
subject: val,
- msg: Scratch.translate(customRules[rule].msg(val)),
+ msg: Scratch.translate(
+ Cast.toString(rules[rule].msg).replace(/\{([^}]+)\}/g, val)
+ ),
});
}
}
@@ -578,6 +649,7 @@
_checkSpriteFormatting() {
const targets = runtime.targets;
+ console.log("checking sprites");
for (const target of targets) {
if (target.isSprite()) {
// Format check
@@ -608,6 +680,7 @@
? []
: this.getCustomBlocks();
+ console.log("checking custom blocks");
for (const block of blocks) {
this.checkFormatRule("customNoCapitalized", block, "custom_block");
this.checkFormatRule("camelCaseOnly", block, "custom_block");
@@ -618,7 +691,7 @@
_checkVariableFormatting() {
const variables = this.getVariables();
- // Local variable check
+ // Local variable format check
console.log("checking local variables");
for (const variable of variables.local) {
this.checkFormatRule("griffpatchStyle", variable, "variable", {
@@ -628,7 +701,7 @@
this.checkCustomFormatRules(variable, "variable");
}
- // Global variable check
+ // Global variable format check
console.log("checking global variables");
for (const variable of variables.global) {
this.checkFormatRule("griffpatchStyle", variable, "variable", {
@@ -708,7 +781,7 @@
}
fancyFormatErrors(args) {
try {
- return formatError(JSON.parse(args.FORMAT_ERROR));
+ return formatError(JSON.parse(args.ARRAY_FORMAT_ERROR));
} catch {
return "";
}
diff --git a/images/Hammouda101010/prettyblocks.svg b/images/Hammouda101010/prettyblocks.svg
new file mode 100644
index 0000000000..3bad7a2a2f
--- /dev/null
+++ b/images/Hammouda101010/prettyblocks.svg
@@ -0,0 +1,90 @@
+
+
+
+
From 536e5d583a8c3cc4b132d9008094ef0c0c2b3f0c Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Thu, 6 Mar 2025 17:59:40 +0100
Subject: [PATCH 04/10] new block and ignore list
---
extensions/Hammouda101010/prettyblocks.js | 119 +++++++++++++---------
1 file changed, 73 insertions(+), 46 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index f647831054..fa0d9298f0 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -336,7 +336,7 @@
};
});
- let ignoreList = [];
+ let ignoreList = new Set();
// Function Types for Custom Rules.
const funcTypes = [
@@ -374,21 +374,13 @@
// Turbowarp's extension storage
runtime.on("PROJECT_LOADED", () => {
- vm.extensionManager.refreshBlocks();
- try {
- // @ts-ignore
- const storage = JSON.parse(runtime.extensionStorage["HamPrettyBlocks"]);
-
- if (storage) {
- ignoreList = storage.ignoreList ? JSON.parse(storage.ignoreList) : [];
-
- rules = storage.rules ? JSON.parse(storage.rules) : rules;
- customRules = storage.customRules
- ? JSON.parse(storage.customRules)
- : customRules;
- }
- } catch (e) {
- console.error(e);
+ // @ts-ignore
+ const storage = runtime.extensionStorage["HamPrettyBlocks"];
+
+ if (storage) {
+ // ignoreList = new Set(JSON.parse(storage.ignoreList))
+ rules = JSON.parse(storage.rules);
+ customRules = JSON.parse(storage.customRules);
}
});
@@ -425,6 +417,7 @@
opcode: "ignoreVariable",
blockType: Scratch.BlockType.COMMAND,
text: Scratch.translate("ignore variable named [VAR_MENU]"),
+ color1: "#848484",
arguments: {
VAR_MENU: {
type: Scratch.ArgumentType.STRING,
@@ -436,6 +429,7 @@
opcode: "ignoreCustomBlock",
blockType: Scratch.BlockType.COMMAND,
text: Scratch.translate("ignore custom block named [BLOCK_MENU]"),
+ color1: "#848484",
arguments: {
BLOCK_MENU: {
type: Scratch.ArgumentType.STRING,
@@ -444,6 +438,12 @@
},
},
"---",
+ {
+ opcode: "checkFormatttingBlock",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("check project formatting"),
+ },
+ "---",
{
opcode: "formatErrorsReporter",
blockType: Scratch.BlockType.REPORTER,
@@ -464,17 +464,25 @@
],
menus: {
PRETTYBLOCKS_CUSTOM_BLOCKS: {
- acceptReporters: true,
+ acceptReporters: false,
items: "_getCustomBlocksMenu",
},
PRETTYBLOCKS_VARIABLES: {
- acceptReporters: true,
+ acceptReporters: false,
items: "_getVariablesMenu",
},
},
};
}
// Class Utilities
+ refreshBlocks() {
+ vm.extensionManager.refreshBlocks();
+ runtime.extensionStorage["HamPrettyBlocks"] = {
+ rules: JSON.stringify(rules),
+ customRules: JSON.stringify(customRules),
+ ignore: ignoreList ? JSON.stringify([...ignoreList]) : "[]",
+ };
+ }
getLogic(str, opts) {
let codeLine = Cast.toString(str);
const logicCodeLineArray = codeLine.split(" ");
@@ -484,7 +492,7 @@
// Check for each spaces
for (const line of logicCodeLineArray) {
// Check if it's a boolean
- if (/\<([^>]+)\>/g.test(line)) {
+ if (/<([^>]+)>/g.test(line)) {
// Is it a primitive boolean value?
if (line === "") {
boolResult = true;
@@ -540,7 +548,10 @@
for (const blockId in blocks) {
const block = blocks[blockId];
if (block.opcode === "procedures_prototype") {
- customBlocks.push(this.formatCustomBlock(block));
+ customBlocks.push({
+ text: this.formatCustomBlock(block),
+ value: block.id,
+ });
}
}
}
@@ -662,8 +673,6 @@
const mutation = block.mutation;
const args = JSON.parse(mutation.argumentnames);
- console.log(args);
-
let i = 0;
const name = mutation.proccode.replace(/%[snb]/g, (match) => {
let value = args[i++];
@@ -681,10 +690,14 @@
: this.getCustomBlocks();
console.log("checking custom blocks");
- for (const block of blocks) {
- this.checkFormatRule("customNoCapitalized", block, "custom_block");
- this.checkFormatRule("camelCaseOnly", block, "custom_block");
- this.checkCustomFormatRules(block, "custom_block");
+ for (const block in blocks) {
+ if (!ignoreList.has(blocks[block].value)) {
+ // prettier-ignore
+ this.checkFormatRule("customNoCapitalized", blocks[block].text, "custom_block");
+ // prettier-ignore
+ this.checkFormatRule("camelCaseOnly", blocks[block].text, "custom_block");
+ this.checkCustomFormatRules(blocks[block].text, "custom_block");
+ }
}
}
@@ -753,6 +766,7 @@
regex: regex,
};
+ this.refreshBlocks();
console.log(customRules);
}
);
@@ -770,12 +784,37 @@
(name) => {
console.log(name);
delete customRules[name];
+ this.refreshBlocks();
console.log(customRules);
}
);
}
+ ignoreVariable(args) {
+ if (Cast.toBoolean(args.VAR_MENU)) {
+ ignoreList.add(args.VAR_MENU);
+ this.refreshBlocks();
+ }
+ console.log(ignoreList);
+ }
+ ignoreCustomBlock(args) {
+ if (Cast.toBoolean(args.BLOCK_MENU)) {
+ ignoreList.add(args.BLOCK_MENU);
+ this.refreshBlocks();
+ }
+ console.log(ignoreList);
+ }
+
+ checkFormatttingBlock() {
+ if (!isEditor) return;
+ this.formatErrors = [];
+
+ this._checkSpriteFormatting();
+ this._checkVariableFormatting();
+ this._checkCustomBlockFormatting();
+ }
+
formatErrorsReporter() {
return JSON.stringify(this.formatErrors);
}
@@ -808,7 +847,9 @@
)
.flat(1);
- return localVars.concat(globalVars);
+ return localVars.concat(globalVars)
+ ? localVars.concat(globalVars)
+ : [{ text: Scratch.translate("no variables found"), value: [] }];
}
_getCustomBlocksMenu() {
@@ -820,34 +861,20 @@
for (const blockId in blocks) {
const block = blocks[blockId];
if (block.opcode === "procedures_prototype") {
- customBlocks.push(this.formatCustomBlock(block));
+ customBlocks.push({
+ text: this.formatCustomBlock(block),
+ value: block.id,
+ });
}
}
}
return customBlocks.length > 0
? customBlocks
- : ["no custom blocks found"];
+ : [{ text: Scratch.translate("no custom blocks found"), value: false }];
}
}
- if (isEditor) {
- vm.on("EXTENSION_ADDED", () => {
- runtime.extensionStorage["HamPrettyBlocks"] = JSON.stringify({
- rules: JSON.stringify(rules),
- customRules: JSON.stringify(customRules),
- ignore: JSON.stringify(ignoreList),
- });
- });
- vm.on("BLOCKSINFO_UPDATE", () => {
- runtime.extensionStorage["HamPrettyBlocks"] = JSON.stringify({
- rules: JSON.stringify(rules),
- customRules: JSON.stringify(customRules),
- ignore: JSON.stringify(ignoreList),
- });
- });
- }
-
// @ts-ignore
Scratch.extensions.register(new HamPrettyBlocks());
})(Scratch);
From f7920f0345cf7a205db4a1383f1252eb046c8c54 Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Fri, 7 Mar 2025 13:30:35 +0100
Subject: [PATCH 05/10] updated isUnSandBoxed
---
extensions/Hammouda101010/prettyblocks.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index fa0d9298f0..e1055f81c5 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -16,10 +16,9 @@
const Cast = Scratch.Cast;
/**Checks if the extension is in "penguinmod.com".*/
// @ts-ignore
- const _isPM = Scratch.extensions.isPenguinMod;
+ const _isPM = runtime.platform.url === "https://penguinmod.com/";
/**Checks if the extension is in "unsandboxed.org".*/
- const isUnSandBoxed =
- JSON.parse(vm.toJSON()).meta.platform.url === "https://unsandboxed.org/";
+ const isUnSandBoxed = runtime.platform.url === "https://unsandboxed.org/";
/**Checks if the extension is inside the editor.*/
const isEditor = typeof scaffolding === "undefined";
@@ -498,7 +497,7 @@
boolResult = true;
} else if (line === "") {
boolResult = false;
- // Otherwise, it's an argument
+ // Otherwise, it's an argument/option
} else {
const optsArray = Object.values(opts).map((value) =>
Cast.toBoolean(value)
From 912ca8682a85d85cadf535067d73444002af5fba Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Sat, 8 Mar 2025 14:06:30 +0100
Subject: [PATCH 06/10] removed support for other mods
---
extensions/Hammouda101010/prettyblocks.js | 27 ++++++++++++-----------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index e1055f81c5..5e301e24da 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -16,9 +16,6 @@
const Cast = Scratch.Cast;
/**Checks if the extension is in "penguinmod.com".*/
// @ts-ignore
- const _isPM = runtime.platform.url === "https://penguinmod.com/";
- /**Checks if the extension is in "unsandboxed.org".*/
- const isUnSandBoxed = runtime.platform.url === "https://unsandboxed.org/";
/**Checks if the extension is inside the editor.*/
const isEditor = typeof scaffolding === "undefined";
@@ -561,10 +558,9 @@
const stage = runtime.getTargetForStage();
const targets = runtime.targets;
- // Sort the variables
const globalVars = Object.values(stage.variables)
.filter((v) => v.type !== "list")
- .map((v) => v.name);
+ .map((v) => ({ text: v.name, value: v.id }));
const allVars = targets
.filter((t) => t.isOriginal)
@@ -573,7 +569,10 @@
.map((v) => Object.values(v))
.map((v) =>
// prettier-ignore
- v.filter((v) => v.type !== "list" && !globalVars.includes(v.name)).map((v) => v.name)
+ v.filter(
+ (v) =>
+ v.type !== "list" && !globalVars.map((obj) => obj.text).includes(v.name)
+ ).map((v) => ({ text: v.name, value: v.id }))
)
.flat(1);
@@ -675,8 +674,8 @@
let i = 0;
const name = mutation.proccode.replace(/%[snb]/g, (match) => {
let value = args[i++];
- if (match === "%s") return isUnSandBoxed ? `[${value}]` : `(${value})`;
- if (match === "%n" && isUnSandBoxed) return `(${value})`;
+ if (match === "%s") return `[${value}]`;
+ if (match === "%n") return `(${value})`;
if (match === "%b") return `<${value}>`;
return match;
});
@@ -832,7 +831,7 @@
const globalVars = Object.values(stage.variables)
.filter((v) => v.type !== "list")
- .map((v) => v.name);
+ .map((v) => ({ text: v.name, value: v.id }));
const allVars = targets
.filter((t) => t.isOriginal)
@@ -840,15 +839,17 @@
const localVars = allVars
.map((v) => Object.values(v))
.map((v) =>
- v
- .filter((v) => v.type !== "list" && !globalVars.includes(v.name))
- .map((v) => v.name)
+ // prettier-ignore
+ v.filter(
+ (v) =>
+ v.type !== "list" && !globalVars.map((obj) => obj.text).includes(v.name)
+ ).map((v) => ({ text: v.name, value: v.id }))
)
.flat(1);
return localVars.concat(globalVars)
? localVars.concat(globalVars)
- : [{ text: Scratch.translate("no variables found"), value: [] }];
+ : [{ text: Scratch.translate("no variables found"), value: false }];
}
_getCustomBlocksMenu() {
From 35dadb2fabfa4a48fbd8b640c8003e5c986d1d4a Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Sat, 8 Mar 2025 14:45:08 +0100
Subject: [PATCH 07/10] added sprite origin to local variables
---
extensions/Hammouda101010/prettyblocks.js | 54 ++++++++++++++---------
1 file changed, 32 insertions(+), 22 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index 5e301e24da..8757f88355 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -585,6 +585,7 @@
}
checkFormatRule(rule, val, type, opts = {}) {
+ if (ignoreList.has(val.value)) return;
if (rules[rule].enabled) {
let str = Cast.toString(rules[rule].regex);
if (str.startsWith("if")) {
@@ -595,11 +596,11 @@
switch (rule) {
case "griffpatchStyle":
- if (!regex.test(val)) {
+ if (!regex.test(val.text)) {
this.formatErrors.push({
type: type,
level: rules[rule].level,
- subject: val,
+ subject: val.text,
msg: Scratch.translate(
Cast.toString(rules[rule].msg).replace(
/\{([^}]+)\}/g,
@@ -608,7 +609,7 @@
if (e === "{isGlobal}") {
return opts.isGlobal ? "UPPERCASE" : "lowercase";
} else {
- return val;
+ return val.text;
}
}
)
@@ -621,7 +622,7 @@
this.formatErrors.push({
type: type,
level: rules[rule].level,
- subject: val,
+ subject: val.text,
msg: Scratch.translate(
Cast.toString(rules[rule].msg).replace(/\{([^}]+)\}/g, val)
),
@@ -633,6 +634,7 @@
}
checkCustomFormatRules(val, type) {
+ if (ignoreList.has(val.value)) return;
for (const rule in customRules) {
if (
(customRules[rule].enabled &&
@@ -642,11 +644,11 @@
let str = Cast.toString(rules[rule].regex);
const regex = new RegExp(str.split("/")[1], str.split("/")[2]);
- if (!regex.test(val)) {
+ if (!regex.test(val.text)) {
this.formatErrors.push({
type: type,
level: customRules[rule].level,
- subject: val,
+ subject: val.text,
msg: Scratch.translate(
Cast.toString(rules[rule].msg).replace(/\{([^}]+)\}/g, val)
),
@@ -657,14 +659,14 @@
}
_checkSpriteFormatting() {
- const targets = runtime.targets;
+ const targets = runtime.targets
+ .filter((t) => t.isSprite())
+ .map((t) => ({ text: t.sprite.name, value: t.id }));
console.log("checking sprites");
for (const target of targets) {
- if (target.isSprite()) {
- // Format check
- this.checkFormatRule("camelCaseOnly", target.sprite.name, "sprite");
- this.checkCustomFormatRules(target.sprite.name, "sprite");
- }
+ // Format check
+ this.checkFormatRule("camelCaseOnly", target, "sprite");
+ this.checkCustomFormatRules(target, "sprite");
}
}
formatCustomBlock(block) {
@@ -732,12 +734,11 @@
this._checkCustomBlockFormatting();
if (this.formatErrors.length !== 0) {
+ console.log(formatError(this.formatErrors));
openModal("error", "Format Error", this.formatErrors);
} else {
alert("No format errors found!");
}
-
- console.log(formatError(this.formatErrors));
}
newFormatRule() {
if (!isEditor) return; // return if we aren't in the editor
@@ -835,15 +836,24 @@
const allVars = targets
.filter((t) => t.isOriginal)
- .map((t) => t.variables);
+ .map((t) => ({ spriteName: t.sprite.name, variables: t.variables }));
+
const localVars = allVars
- .map((v) => Object.values(v))
+ .map((t) => ({
+ spriteName: t.spriteName,
+ vars: Object.values(t.variables),
+ }))
.map((v) =>
- // prettier-ignore
- v.filter(
- (v) =>
- v.type !== "list" && !globalVars.map((obj) => obj.text).includes(v.name)
- ).map((v) => ({ text: v.name, value: v.id }))
+ v.vars
+ .filter(
+ (variable) =>
+ variable.type !== "list" &&
+ !globalVars.map((obj) => obj.text).includes(variable.name)
+ )
+ .map((variable) => ({
+ text: `${v.spriteName}: ${variable.name}`,
+ value: variable.id,
+ }))
)
.flat(1);
@@ -862,7 +872,7 @@
const block = blocks[blockId];
if (block.opcode === "procedures_prototype") {
customBlocks.push({
- text: this.formatCustomBlock(block),
+ text: `${target.sprite.name}: ${this.formatCustomBlock(block)}`,
value: block.id,
});
}
From 2fd4f45de13e1ead58b67ea8023134a2bbd7f219 Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Sun, 9 Mar 2025 16:31:11 +0100
Subject: [PATCH 08/10] added format project button
---
docs/Hammouda101010/prettyblocks.md | 36 ++++++-
extensions/Hammouda101010/prettyblocks.js | 121 +++++++++++++++++++++-
2 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/docs/Hammouda101010/prettyblocks.md b/docs/Hammouda101010/prettyblocks.md
index 8b79b074fd..e41770fbb6 100644
--- a/docs/Hammouda101010/prettyblocks.md
+++ b/docs/Hammouda101010/prettyblocks.md
@@ -6,6 +6,12 @@ An extension to add strict formatting rules to your project.
- [Creating Custom Rules](#creating-custom-rules)
- [RegBool](#regbool)
- [Format Functions](#format-functions)
+ - [Blocks](#blocks)
+ - [Ignore List](#ignore-list)
+ - [Ignore Variable Named \[\]](#ignore-variable-named-)
+ - [Ignore Custom Block Named \[\]](#ignore-custom-block-named-)
+ - [Reset Ignore List](#reset-ignore-list)
+ - [Check Project Formatting](#check-project-formatting)
## Rules
- Camel Case Only:
@@ -37,4 +43,32 @@ There are a limited amount of functions available for custom rules. more rules m
- To camelCase: formats the subject's text to [camelCase](https://en.wikipedia.org/wiki/Camel_case)
- To snake_case: formats the subject's text to [snake_case](https://en.wikipedia.org/wiki/Snake_case)
- To PascalCase: much like "To camelCase", but capitalize the first letter too.
-- Space trimming: trims the subject's text
\ No newline at end of file
+- Space trimming: trims the subject's text
+## Blocks
+Here are a list of blocks to interact with the formatter.
+### Ignore List
+```scratch
+ignore list :: #848484 reporter
+```
+Shows the list of all sprites, variables, etc. that are ignored by the formatter as a set.
+Keep in mind that it stores values by ID, not by name.
+### Ignore Variable Named []
+```scratch
+ignore variable named [my variable v] :: #848484
+```
+Adds a variable to the ignore list. It can also acces all local variables.
+### Ignore Custom Block Named []
+```scratch
+ignore custom block named [block name \(number\) \[text\] \ v] :: #848484
+```
+Adds a custom block to the ignore list.
+### Reset Ignore List
+```scratch
+reset ignore list :: #848484
+```
+Resets the ignore list.
+### Check Project Formatting
+```scratch
+check project formatting :: #0071b0
+```
+Does the same thing as the "Check Project Formatting" button.
\ No newline at end of file
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index 8757f88355..84f561d6b2 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -14,9 +14,7 @@
const vm = Scratch.vm;
const runtime = vm.runtime;
const Cast = Scratch.Cast;
- /**Checks if the extension is in "penguinmod.com".*/
- // @ts-ignore
- /**Checks if the extension is inside the editor.*/
+ const workspace = ScratchBlocks.getMainWorkspace();
const isEditor = typeof scaffolding === "undefined";
function formatError(errors, logToConsole = false) {
@@ -398,6 +396,11 @@
blockType: Scratch.BlockType.BUTTON,
text: Scratch.translate("Check Project Formatting"),
},
+ {
+ func: "formatProject",
+ blockType: Scratch.BlockType.BUTTON,
+ text: Scratch.translate("Format Project"),
+ },
"---",
{
func: "newFormatRule",
@@ -409,6 +412,12 @@
blockType: Scratch.BlockType.BUTTON,
text: Scratch.translate("Delete Format Rule"),
},
+ {
+ opcode: "ignoreList",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("ignore list"),
+ color1: "#848484",
+ },
{
opcode: "ignoreVariable",
blockType: Scratch.BlockType.COMMAND,
@@ -434,6 +443,13 @@
},
},
"---",
+ {
+ opcode: "resetIgnoreList",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("reset ignore list"),
+ color1: "#848484",
+ },
+ "---",
{
opcode: "checkFormatttingBlock",
blockType: Scratch.BlockType.COMMAND,
@@ -472,6 +488,7 @@
}
// Class Utilities
refreshBlocks() {
+ vm.refreshWorkspace();
vm.extensionManager.refreshBlocks();
runtime.extensionStorage["HamPrettyBlocks"] = {
rules: JSON.stringify(rules),
@@ -633,6 +650,82 @@
}
}
+ /**
+ * Formats a variable using a rule.
+ * @param {string} rule The rule used to format the variable name.
+ * @param { {name: string, id: string}} targetVariable The target variable to format the name of.
+ * @param {object} opts Optional options.
+ */
+ _formatVariable(rule, targetVariable, opts) {
+ const targets = runtime.targets;
+ const stage = runtime.getTargetForStage();
+ if (opts.isGlobal) {
+ for (const variable of Object.values(stage.variables)) {
+ if (variable.id === targetVariable.id)
+ workspace.renameVariableById(
+ variable.id,
+ this.formatRule(rule, variable.name, opts)
+ );
+ }
+ } else {
+ for (const target of targets) {
+ if (target.isSprite()) {
+ const variable = target.lookupOrCreateVariable(
+ targetVariable.id,
+ targetVariable.name
+ );
+ if (variable.id in stage.variables) return;
+ // @ts-ignore
+ if (variable.type !== "list")
+ workspace.renameVariableById(
+ variable.id,
+ this.formatRule(
+ rule,
+ targetVariable.name,
+ targetVariable.id,
+ opts
+ )
+ );
+ }
+ }
+ }
+ }
+
+ _formatVariables() {
+ const variables = this.getVariables();
+ for (const variable of variables.local) {
+ const variableData = { name: variable.text, id: variable.value };
+ this._formatVariable("griffpatchStyle", variableData, {
+ isGlobal: false,
+ });
+ }
+ for (const variable of variables.global) {
+ const variableData = { name: variable.text, id: variable.value };
+ this._formatVariable("griffpatchStyle", variableData, {
+ isGlobal: true,
+ });
+ }
+ }
+
+ formatRule(rule, val, valID, opts = {}) {
+ if (ignoreList.has(valID)) return;
+ if (rules[rule].enabled) {
+ switch (rule) {
+ case "griffpatchStyle":
+ const { isGlobal } = opts;
+ return isGlobal ? val.toUpperCase() : val.toLowerCase();
+ case "camelCaseOnly":
+ // prettier-ignore
+ return val.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
+ case "customNoCapitalized":
+ // prettier-ignore
+ return val.charAt(0).toLowerCase() + val.slice(1);
+ }
+ } else {
+ return val;
+ }
+ }
+
checkCustomFormatRules(val, type) {
if (ignoreList.has(val.value)) return;
for (const rule in customRules) {
@@ -740,6 +833,17 @@
alert("No format errors found!");
}
}
+
+ formatProject() {
+ if (!isEditor) return;
+ // prettier-ignore
+ if (confirm("!~~~WARNING~~~! \n\n This will format the entire project according to the enabled rules. \n\n This process is irreversible and might break the entire project. \n Do you want to proceed?")){
+ console.log("formatting project...")
+ this._formatVariables()
+ console.info("formatting completed")
+ }
+ }
+
newFormatRule() {
if (!isEditor) return; // return if we aren't in the editor
newRuleModal(
@@ -774,7 +878,8 @@
delFormatRule() {
if (!isEditor) return; // return if we aren't in the editor
const customRulesList = Object.keys(customRules);
- if (customRulesList.length < 1) return alert("There are no Custom Rules");
+ if (customRulesList.length < 1)
+ return alert("There Are No Custom Rules Left.");
newRuleModal(
Scratch.translate("Delete Rule:"),
@@ -789,6 +894,9 @@
}
);
}
+ ignoreList() {
+ return JSON.stringify([...ignoreList]);
+ }
ignoreVariable(args) {
if (Cast.toBoolean(args.VAR_MENU)) {
@@ -805,6 +913,11 @@
console.log(ignoreList);
}
+ resetIgnoreList() {
+ ignoreList = new Set();
+ this.refreshBlocks;
+ }
+
checkFormatttingBlock() {
if (!isEditor) return;
this.formatErrors = [];
From 53ef9bc7bff6551633e36acf0d10b57f5e61abfe Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Sun, 9 Mar 2025 17:40:20 +0100
Subject: [PATCH 09/10] fixed lint
---
extensions/Hammouda101010/prettyblocks.js | 36 +++++++++++++----------
1 file changed, 21 insertions(+), 15 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index 84f561d6b2..2f68e55d58 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -355,7 +355,7 @@
enabled: true,
level: "error",
msg: `"{val}" should be entirely in {isGlobal}. just as griffpatch intened it.`,
- regex: "if /^[^a-z]+/ else /^[^A-Z]+/",
+ regex: "/special/",
},
customNoCapitalized: {
enabled: false,
@@ -487,10 +487,11 @@
};
}
// Class Utilities
- refreshBlocks() {
- vm.refreshWorkspace();
- vm.extensionManager.refreshBlocks();
+ refreshProject() {
+ setTimeout(() => vm.refreshWorkspace(), 100); // Refresh workspace after a delay
+ vm.extensionManager.refreshBlocks(); // Refresh block list
runtime.extensionStorage["HamPrettyBlocks"] = {
+ // refresh extension storage
rules: JSON.stringify(rules),
customRules: JSON.stringify(customRules),
ignore: ignoreList ? JSON.stringify([...ignoreList]) : "[]",
@@ -610,10 +611,13 @@
}
const regex = new RegExp(str.split("/")[1], str.split("/")[2]);
+ regex.lastIndex = 0;
switch (rule) {
- case "griffpatchStyle":
- if (!regex.test(val.text)) {
+ case "griffpatchStyle": {
+ // prettier-ignore
+ const isValidName = Cast.toBoolean(opts.isGlobal) ? val.text === val.text.toUpperCase() : val.text === val.text.toLowerCase()
+ if (!isValidName) {
this.formatErrors.push({
type: type,
level: rules[rule].level,
@@ -634,8 +638,9 @@
});
}
break;
+ }
default:
- if (!rules[rule].check(val)) {
+ if (!regex.test(val)) {
this.formatErrors.push({
type: type,
level: rules[rule].level,
@@ -664,7 +669,7 @@
if (variable.id === targetVariable.id)
workspace.renameVariableById(
variable.id,
- this.formatRule(rule, variable.name, opts)
+ this.formatRule(rule, variable.name, variable.id, opts)
);
}
} else {
@@ -708,12 +713,13 @@
}
formatRule(rule, val, valID, opts = {}) {
- if (ignoreList.has(valID)) return;
+ if (ignoreList.has(valID)) return val;
if (rules[rule].enabled) {
switch (rule) {
- case "griffpatchStyle":
+ case "griffpatchStyle": {
const { isGlobal } = opts;
return isGlobal ? val.toUpperCase() : val.toLowerCase();
+ }
case "camelCaseOnly":
// prettier-ignore
return val.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
@@ -869,7 +875,7 @@
regex: regex,
};
- this.refreshBlocks();
+ this.refreshProject();
console.log(customRules);
}
);
@@ -888,7 +894,7 @@
(name) => {
console.log(name);
delete customRules[name];
- this.refreshBlocks();
+ this.refreshProject();
console.log(customRules);
}
@@ -901,21 +907,21 @@
ignoreVariable(args) {
if (Cast.toBoolean(args.VAR_MENU)) {
ignoreList.add(args.VAR_MENU);
- this.refreshBlocks();
+ this.refreshProject();
}
console.log(ignoreList);
}
ignoreCustomBlock(args) {
if (Cast.toBoolean(args.BLOCK_MENU)) {
ignoreList.add(args.BLOCK_MENU);
- this.refreshBlocks();
+ this.refreshProject();
}
console.log(ignoreList);
}
resetIgnoreList() {
ignoreList = new Set();
- this.refreshBlocks;
+ this.refreshProject;
}
checkFormatttingBlock() {
From 66c6edab0994f849f587f3a94c2e00ad28272c8e Mon Sep 17 00:00:00 2001
From: hammouda101010 <114447430+hammouda101010@users.noreply.github.com>
Date: Sun, 9 Mar 2025 17:51:28 +0100
Subject: [PATCH 10/10] removed patches :pensive:
---
extensions/Hammouda101010/prettyblocks.js | 61 -----------------------
1 file changed, 61 deletions(-)
diff --git a/extensions/Hammouda101010/prettyblocks.js b/extensions/Hammouda101010/prettyblocks.js
index 2f68e55d58..216c2cc4e8 100644
--- a/extensions/Hammouda101010/prettyblocks.js
+++ b/extensions/Hammouda101010/prettyblocks.js
@@ -269,67 +269,6 @@
}
}
- const squareInputBlocks = ["HamPrettyBlocks_fancyFormatErrors"];
-
- // Custom Square Block Shapes
- const ogConverter = runtime._convertBlockForScratchBlocks.bind(runtime);
- runtime._convertBlockForScratchBlocks = function (blockInfo, categoryInfo) {
- const res = ogConverter(blockInfo, categoryInfo);
- if (blockInfo.outputShape) res.json.outputShape = blockInfo.outputShape;
- return res;
- };
-
- if (Scratch.gui)
- Scratch.gui.getBlockly().then((SB) => {
- // Custom Square Input Shape
- const makeShape = (width, height) => {
- width -= 10;
- // prettier-ignore
- height -= 8
- return `
- m 0 4
- A 4 4 0 0 1 4 0 H ${width}
- a 4 4 0 0 1 4 4
- v ${height}
- a 4 4 0 0 1 -4 4
- H 4
- a 4 4 0 0 1 -4 -4
- z`
- .replaceAll("\n", "")
- .trim();
- };
-
- const ogRender = SB.BlockSvg.prototype.render;
- SB.BlockSvg.prototype.render = function (...args) {
- const data = ogRender.call(this, ...args);
- if (this.svgPath_ && squareInputBlocks.includes(this.type)) {
- this.inputList.forEach((input) => {
- if (input.name.startsWith("ARRAY")) {
- const block = input.connection.targetBlock();
- if (
- block &&
- block.type === "text" &&
- block.svgPath_ &&
- block.type.startsWith("HamPrettyBlocks_menu_")
- ) {
- block.svgPath_.setAttribute(
- "transform",
- `scale(1, ${block.height / 33})`
- );
- block.svgPath_.setAttribute(
- "d",
- makeShape(block.width, block.height)
- );
- } else if (input.outlinePath) {
- input.outlinePath.setAttribute("d", makeShape(46, 32));
- }
- }
- });
- }
- return data;
- };
- });
-
let ignoreList = new Set();
// Function Types for Custom Rules.