diff --git a/src/lib/extensions.js b/src/lib/extensions.js
index adb9602ce..10333e541 100644
--- a/src/lib/extensions.js
+++ b/src/lib/extensions.js
@@ -442,6 +442,14 @@ export default [
creatorAlias: "gaimerI17",
note: "Extension thumbnail made by Dillon."
},
+ {
+ name: "Enumerations",
+ description: "Create and use enumerations in your project. Enums allow you to assign meaningful labels to arbitrary numbers, and even change them later without issue.",
+ code: "DogeisCut/Enumerations.js",
+ banner: "DogeisCut/Enumerations.svg",
+ creator: "DogeisCut",
+ isGitHub: true,
+ },
/* these extensions are completely dead as of now
{
name: "Online Captcha",
diff --git a/static/extensions/DogeisCut/Enumerations.js b/static/extensions/DogeisCut/Enumerations.js
new file mode 100644
index 000000000..880479e86
--- /dev/null
+++ b/static/extensions/DogeisCut/Enumerations.js
@@ -0,0 +1,406 @@
+// Name: Enumerations
+// ID: dogeiscutenumerations
+// Description: Create and use enumerations in your project. Enums allow you to assign meaningful labels to arbitrary numbers, and even change them later without issue.
+// By: DogeisCut
+// License: MIT
+
+// Version V.1.0.0
+
+// TODO:
+// - Use dynamic blocks instead of this opcode fuckery
+// "being real" "you shouldnt create blocks using an opcode like that" "the better way is to just use dynamic blocks"
+// "pretty simple" "you define it" "then" "when you make the block (using xml) you can set its blockInfo" "so"
+// "you can change the text to literally anything" "and" "it still has the same opcode and everything"
+// - Figure out more useful blocks to add
+// - keys of [ENUM]
+// - labels of [ENUM]
+
+(function(Scratch) {
+ 'use strict';
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error('\'Enumerations\' must run unsandboxed!');
+ }
+
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+
+ let hideEnumBlocks = true;
+
+ let enums = {};
+
+ function createNewEnum(name, target, scope) {
+ const newUid = uid();
+ const clones = target.sprite.clones;
+
+ for (const clone of clones) {
+ if (!clone) {
+
+ }
+ }
+ }
+
+ // tiny patch for events to update the dropdown
+ if (Scratch.gui) Scratch.gui.getBlockly().then(SB => {
+ const { Events, mainWorkspace } = SB;
+ const workspaceEvents = (event) => {
+ if (mainWorkspace.id === event.workspaceId) {
+ if (event.type === Events.CHANGE) {
+ if (event.name == "ENUM") {
+ const block = mainWorkspace.getBlockById(event.blockId)
+ if (block.type == "dogeiscutenumerations_keyOfEnum") {
+ const chosenEnum = event.newValue
+ block.getInput(0).fieldRow[0].setValue(Object.keys(enums[chosenEnum])[0])
+ }
+ }
+ }
+ }
+ };
+ mainWorkspace.addChangeListener(workspaceEvents);
+ });
+
+ function openModal(titleName, promptTitle, addSelector, func) {
+ let enumData = {};
+ ScratchBlocks.prompt(
+ titleName,
+ "",
+ (value) => {
+ enumData = getEnumData();
+ func(value, enumData);
+ },
+ promptTitle,
+ "broadcast_msg"
+ );
+
+ if (addSelector) {
+ const input = document.querySelector(`div[class="ReactModalPortal"] input`);
+ const newLabel = input.parentNode.previousSibling.cloneNode(true);
+ newLabel.textContent = "Labels and values:";
+
+ const container = document.createElement("div");
+ container.setAttribute("class", "enum-container");
+
+ const addButton = document.createElement("button");
+ addButton.textContent = "+";
+ addButton.addEventListener("click", () => {
+ const enumEntry = document.createElement("div");
+ enumEntry.setAttribute("class", "enum-entry");
+
+ const label = document.createElement("input");
+ label.setAttribute("type", "text");
+ label.setAttribute("placeholder", "Label");
+
+ const numberInput = document.createElement("input");
+ numberInput.setAttribute("type", "number");
+ numberInput.setAttribute("placeholder", "Value");
+
+ // Autofill with a unique number
+ const existingEntries = container.querySelectorAll('.enum-entry');
+ numberInput.value = existingEntries.length + 1;
+
+ const removeButton = document.createElement("button");
+ removeButton.textContent = "-";
+ removeButton.addEventListener("click", () => {
+ container.removeChild(enumEntry);
+ });
+
+ enumEntry.appendChild(label);
+ enumEntry.appendChild(numberInput);
+ enumEntry.appendChild(removeButton);
+ container.appendChild(enumEntry);
+ });
+
+ input.parentNode.append(newLabel, container, addButton);
+ }
+ }
+
+ function getEnumData() {
+ const enumData = {};
+ const entries = document.querySelectorAll('.enum-entry');
+ entries.forEach((entry, index) => {
+ let label = entry.querySelector('input[type="text"]').value.trim();
+ let value = parseFloat(entry.querySelector('input[type="number"]').value);
+ if (!label) {
+ label = `Label${index + 1}`;
+ }
+ if (isNaN(value)) {
+ value = index + 1;
+ }
+ let uniqueLabel = label;
+ let counter = 1;
+ while (enumData.hasOwnProperty(uniqueLabel)) {
+ uniqueLabel = `${label}${counter}`;
+ counter++;
+ }
+ if (uniqueLabel !== label) {
+ entry.querySelector('input[type="text"]').value = uniqueLabel;
+ }
+ enumData[uniqueLabel] = value;
+ });
+ return enumData;
+ }
+
+ function getCurrentBlockArgs() {
+ const ScratchBlocks = window.ScratchBlocks;
+ if (!ScratchBlocks) return {};
+ const source = ScratchBlocks.selected;
+ if (!source) return {};
+
+ const args = {};
+ for (const input of source.inputList) {
+ for (const field of input.fieldRow) {
+ if (field.isCurrentlyEditable()) args[field.name] = field.getValue();
+ }
+ if (!input.connection) continue;
+ const block = input.connection.targetConnection.sourceBlock_;
+ if (!block || !block.isShadow()) continue;
+ for (const input2 of block.inputList) {
+ for (const field2 of input2.fieldRow) {
+ if (field2.isCurrentlyEditable()) args[input.name] = field2.getValue();
+ }
+ }
+ }
+ return args;
+ }
+
+ const icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgd2lkdGg9Ijc3LjIyMiIKICAgaGVpZ2h0PSI3Ny4yMjIiCiAgIHZpZXdCb3g9IjAgMCA3Ny4yMjIgNzcuMjIyIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmczIgogICBzb2RpcG9kaTpkb2NuYW1lPSJFbnVtZXJhdG9ycy1JY29uLnN2ZyIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4zICgwZTE1MGVkNmM0LCAyMDIzLTA3LTIxKSIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzMyIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9Im5hbWVkdmlldzMiCiAgICAgcGFnZWNvbG9yPSIjNTA1MDUwIgogICAgIGJvcmRlcmNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgaW5rc2NhcGU6c2hvd3BhZ2VzaGFkb3c9IjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAiCiAgICAgaW5rc2NhcGU6cGFnZWNoZWNrZXJib2FyZD0iMSIKICAgICBpbmtzY2FwZTpkZXNrY29sb3I9IiM1MDUwNTAiCiAgICAgaW5rc2NhcGU6em9vbT0iMS43NTU4MTczIgogICAgIGlua3NjYXBlOmN4PSItMTE4LjE3ODU4IgogICAgIGlua3NjYXBlOmN5PSItMjguNDc2NzY3IgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDI3IgogICAgIGlua3NjYXBlOndpbmRvdy14PSItOCIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTgiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJzdmczIiAvPgogIDxwYXRoCiAgICAgZD0iTSAyLDM4LjYxMSBDIDIsMTguMzkxIDE4LjM5MSwyIDM4LjYxMSwyIGMgMjAuMjIsMCAzNi42MTEsMTYuMzkxIDM2LjYxMSwzNi42MTEgMCwyMC4yMiAtMTYuMzkxLDM2LjYxMSAtMzYuNjExLDM2LjYxMSBDIDE4LjM5MSw3NS4yMjIgMiw1OC44MzEgMiwzOC42MTEgWiIKICAgICBmaWxsPSIjNTljMDU5IgogICAgIHN0cm9rZT0iIzQ3OWE0NyIKICAgICBzdHJva2Utd2lkdGg9IjQiCiAgICAgaWQ9InBhdGgxIgogICAgIHN0eWxlPSJmaWxsOiNiZTI3MWE7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOiM5ODFmMTU7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLW9wYWNpdHk6MSIgLz4KICA8cGF0aAogICAgIGQ9Ik0gMSwxIEggNzUuMjIyIFYgNzUuMjIyIEggMSBaIgogICAgIGZpbGw9Im5vbmUiCiAgICAgaWQ9InBhdGgyIgogICAgIHN0eWxlPSJzdHJva2UtbWl0ZXJsaW1pdDoxMCIgLz4KICA8ZwogICAgIGlkPSJnNCIKICAgICB0cmFuc2Zvcm09Im1hdHJpeCgxLjMwNTM0MjUsMCwwLDEuMzA1MzQyNSwtMTEuNzkwMDA4LC0xMS43ODk1NzkpIgogICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuNzY2MDgyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utb3BhY2l0eToxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtmb250LXNpemU6NDBweDtsaW5lLWhlaWdodDoxLjI1O2ZvbnQtZmFtaWx5OkltcGFjdDstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkltcGFjdDtmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuNTg2ODgyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0ibSA0Ny42MzQ3NjYsMTkuODQxNDY5IHYgNC42Njc5NjkgYyAxLjYyNzYwMiwwIDIuNjM2NzE5LDAuMTE3MTg3IDMuMDI3MzQzLDAuMzUxNTYyIDAuNDAzNjQ2LDAuMjM0Mzc1IDAuNjU3NTUzLDAuNTE0MzIzIDAuNzYxNzE5LDAuODM5ODQ0IDAuMTA0MTY3LDAuMzI1NTIgMC4xOTUzMTMsMS42MTQ1ODUgMC4yNzM0MzgsMy44NjcxODcgMC4wNzgxMywyLjIzOTU4MSAwLjE5NTMxMiwzLjc3NjA0MyAwLjM1MTU2Miw0LjYwOTM3NSAwLjE1NjI1LDAuODIwMzEyIDAuNDU1NzMsMS41ODIwMzIgMC44OTg0MzgsMi4yODUxNTcgMC40NDI3MDgsMC43MDMxMjQgMS4yMjM5NTksMS40MTkyNzEgMi4zNDM3NSwyLjE0ODQzNyAtMS4wNTQ2ODcsMC42MTE5NzkgLTEuODI5NDI4LDEuMzI4MTI2IC0yLjMyNDIxOSwyLjE0ODQzOCAtMC40ODE3NywwLjgyMDMxMSAtMC44MDcyOTIsMS43MDU3MyAtMC45NzY1NjMsMi42NTYyNSAtMC4xNTYyNDksMC45Mzc0OTkgLTAuMjczNDM3LDMuMDcyOTIgLTAuMzUxNTYyLDYuNDA2MjUgLTAuMDI2MDQsMS4xNTg4NTMgLTAuMjQ3Mzk2LDEuOTI3MDgzIC0wLjY2NDA2MywyLjMwNDY4NyAtMC40MDM2NDUsMC4zNzc2MDQgLTEuNTEwNDE4LDAuNTY2NDA2IC0zLjMyMDMxMiwwLjU2NjQwNiB2IDQuNjg3NSBoIDEuMDM1MTU2IGMgMS44MDk4OTQsMCAzLjIwOTYzNywtMC4xNjI3NiA0LjE5OTIxOSwtMC40ODgyODEgMC45ODk1ODIsLTAuMzEyNSAxLjc5Njg3NSwtMC44MjY4MjQgMi40MjE4NzUsLTEuNTQyOTY5IDAuNjI0OTk5LC0wLjcxNjE0NSAxLjAxNTYyNSwtMS42MDgwNzQgMS4xNzE4NzUsLTIuNjc1NzgxIDAuMTU2MjUsLTEuMDU0Njg2IDAuMjM0Mzc1LC0yLjY4ODgwNCAwLjIzNDM3NSwtNC45MDIzNDQgMCwtMi44NjQ1OCAwLjM1ODA3NCwtNC43MTM1NDIgMS4wNzQyMTksLTUuNTQ2ODc1IDAuNzE2MTQ1LC0wLjgzMzMzMiAxLjc4Mzg1NSwtMS4yNTY1MSAzLjIwMzEyNSwtMS4yNjk1MzEgdiAtNC42ODc1IGMgLTEuNDE5MjcsLTAuMDUyMDggLTIuMzg5MzI0LC0wLjM3MTA5NCAtMi45MTAxNTcsLTAuOTU3MDMxIC0wLjUwNzgxMiwtMC41OTg5NTggLTAuODY1ODg1LC0xLjI1NjUxMSAtMS4wNzQyMTgsLTEuOTcyNjU2IC0wLjE5NTMxMywtMC43MTYxNDUgLTAuMjkyOTY5LC0yLjQ1NDQzIC0wLjI5Mjk2OSwtNS4yMTQ4NDQgMCwtMi40MDg4NTIgLTAuMjUzOTA3LC00LjE1MzY0NyAtMC43NjE3MTksLTUuMjM0Mzc1IC0wLjUwNzgxMiwtMS4wODA3MjggLTEuMzI4MTI2LC0xLjg1NTQ2OSAtMi40NjA5MzcsLTIuMzI0MjE5IC0xLjEzMjgxMiwtMC40ODE3NyAtMi43NDA4ODgsLTAuNzIyNjU2IC00LjgyNDIxOSwtMC43MjI2NTYgeiIKICAgICAgIGlkPSJwYXRoNCIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDtmb250LXNpemU6NDBweDtsaW5lLWhlaWdodDoxLjI1O2ZvbnQtZmFtaWx5OkltcGFjdDstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkltcGFjdDtmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjAuNTg2ODgyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0ibSAzMS45NTExNzIsMjIuODAwNDUzIHYgMzEuNjIxMDk0IGggMTQuMjU3ODEyIHYgLTYuMzI4MTI1IGggLTYuMDM1MTU2IHYgLTYuOTUzMTI1IGggNS4xMzY3MTkgdiAtNi4wMTU2MjUgaCAtNS4xMzY3MTkgdiAtNS45OTYwOTQgaCA1LjQ4ODI4MSB2IC02LjMyODEyNSB6IgogICAgICAgaWQ9InBhdGgzIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmb250LXN0eWxlOm5vcm1hbDtmb250LXZhcmlhbnQ6bm9ybWFsO2ZvbnQtd2VpZ2h0Om5vcm1hbDtmb250LXN0cmV0Y2g6bm9ybWFsO2ZvbnQtc2l6ZTo0MHB4O2xpbmUtaGVpZ2h0OjEuMjU7Zm9udC1mYW1pbHk6SW1wYWN0Oy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246SW1wYWN0O2ZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MC41ODY4ODI7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDI4LjU1MjczNCwxOS44NDE0NjkgYyAtMS44MDk4OTQsMCAtMy4yMDk2MzYsMC4xNjI3NjEgLTQuMTk5MjE4LDAuNDg4MjgxIC0wLjk4OTU4MywwLjMxMjUgLTEuNzk2ODc2LDAuODI2ODI0IC0yLjQyMTg3NSwxLjU0Mjk2OSAtMC42MjUsMC43MTYxNDUgLTEuMDIyMTM2LDEuNjA4MDc0IC0xLjE5MTQwNywyLjY3NTc4MSAtMC4xNTYyNDksMS4wNTQ2ODcgLTAuMjM0Mzc1LDIuNjgyMjk0IC0wLjIzNDM3NSw0Ljg4MjgxMyAwLDIuODkwNjIyIC0wLjM1ODA3Myw0Ljc1MjYwNSAtMS4wNzQyMTgsNS41ODU5MzcgLTAuNzE2MTQ1LDAuODIwMzEyIC0xLjc4Mzg1NiwxLjIzNjk3OSAtMy4yMDMxMjUsMS4yNSB2IDQuNjg3NSBjIDEuNDQ1MzExLDAuMDUyMDggMi40MjgzODYsMC4zOTA2MjYgMi45NDkyMTgsMS4wMTU2MjUgMC41MjA4MzMsMC42MTE5NzkgMC44NzIzOTYsMS4yODkwNjMgMS4wNTQ2ODgsMi4wMzEyNSAwLjE4MjI5MSwwLjc0MjE4NyAwLjI3MzQzNywyLjQ0MTQwOSAwLjI3MzQzNyw1LjA5NzY1NiAwLDIuNDA4ODUyIDAuMjUzOTA3LDQuMTUzNjQ3IDAuNzYxNzE5LDUuMjM0Mzc1IDAuNTA3ODEyLDEuMDgwNzI4IDEuMzI4MTI2LDEuODU1NDcgMi40NjA5MzgsMi4zMjQyMTkgMS4xMzI4MTEsMC40ODE3NyAyLjc0MDg4NywwLjcyMjY1NiA0LjgyNDIxOCwwLjcyMjY1NiBoIDEuMDE1NjI1IHYgLTQuNjg3NSBjIC0xLjU4ODU0LDAgLTIuNjA0MTY3LC0wLjE0MzIyOSAtMy4wNDY4NzUsLTAuNDI5Njg3IC0wLjQ0MjcwOCwtMC4yNzM0MzcgLTAuNzIyNjU2LC0wLjY4MzU5NCAtMC44Mzk4NDMsLTEuMjMwNDY5IGwgLTAuMjkyOTY5LC01LjY4MzU5NCBjIC0wLjA5MTE1LC0xLjgzNTkzNSAtMC40MDM2NDYsLTMuMjQyMTg4IC0wLjkzNzUsLTQuMjE4NzUgLTAuNTIwODMzLC0wLjk3NjU2MSAtMS4zNjA2NzgsLTEuODE2NDA3IC0yLjUxOTUzMSwtMi41MTk1MzEgMS4yMjM5NTcsLTAuNzgxMjQ5IDIuMDU3MjkyLC0xLjU5NTA1MyAyLjUsLTIuNDQxNDA2IDAuNDU1NzI4LC0wLjg1OTM3NCAwLjc0MjE4NywtMS43NTc4MTQgMC44NTkzNzUsLTIuNjk1MzEzIDAuMTMwMjA4LC0wLjkzNzQ5OSAwLjIzNDM3NSwtMi45Njg3NTMgMC4zMTI1LC02LjA5Mzc1IDAuMDI2MDQsLTEuMTMyODExIDAuMjIxMzU0LC0xLjg5NDUzMSAwLjU4NTkzNywtMi4yODUxNTYgMC4zNzc2MDQsLTAuMzkwNjI0IDEuNTEwNDE5LC0wLjU4NTkzNyAzLjM5ODQzOCwtMC41ODU5MzcgdiAtNC42Njc5NjkgeiIKICAgICAgIGlkPSJ0ZXh0MSIgLz4KICA8L2c+Cjwvc3ZnPgo='
+
+ class Enumerations {
+ getInfo() {
+ return {
+ id: 'dogeiscutenumerations',
+ name: 'Enumerations',
+ color1: "#BE271A",
+ menuIconURI: icon,
+ blocks: [
+ {
+ func: "makeAnEnum",
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Make an Enum"
+ },
+ {
+ func: "removeAnEnum",
+ blockType: Scratch.BlockType.BUTTON,
+ text: "Remove an Enum"
+ },
+ '---',
+ {
+ opcode: "enum",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "",
+ isDynamic: true,
+ hideFromPalette: true,
+ arguments: {
+ DICTIONARY: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "",
+ },
+ },
+ },
+ {
+ blockType: Scratch.BlockType.XML,
+ xml: `
+
+
+
+
+ `
+ },
+ '---',
+ {
+ opcode: "keyOfEnum",
+ text: "[KEY] of enum [ENUM]",
+ blockType: Scratch.BlockType.REPORTER,
+ hideFromPalette: hideEnumBlocks,
+ disableMonitor: true,
+ arguments: {
+ KEY: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'getKeysOf'
+ },
+ ENUM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'getEnums'
+ }
+ }
+ },
+ {
+ opcode: "enumLength",
+ text: "length of [ENUM]",
+ blockType: Scratch.BlockType.REPORTER,
+ hideFromPalette: hideEnumBlocks,
+ disableMonitor: true,
+ arguments: {
+ ENUM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'getEnums'
+ }
+ }
+ },
+ {
+ opcode: "enumContains",
+ text: "[ENUM] contains [KEY]?",
+ blockType: Scratch.BlockType.BOOLEAN,
+ hideFromPalette: hideEnumBlocks,
+ disableMonitor: true,
+ arguments: {
+ ENUM: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'getEnums'
+ },
+ KEY: {
+ type: Scratch.ArgumentType.STRING
+ }
+ }
+ }
+ ],
+ /*menus: enumMenus*/
+ menus: {
+ getEnums: {
+ acceptReporters: false,
+ items: 'getEnums'
+ },
+ getKeysOf: {
+ acceptReporters: false,
+ items: 'getKeysOf'
+ }
+ }
+ }
+ }
+
+ serialize() {
+ return { dogeiscutenumerations: {enumBlocks, enums} }
+ }
+
+ deserialize(data) {
+ if (data.dogeiscutenumerations) {
+ const { enumBlocks: savedEnumBlocks, enums: savedEnums } = data.dogeiscutenumerations;
+ enumBlocks = savedEnumBlocks || [];
+ enums = savedEnums || {};
+
+ enumBlocks.forEach(block => {
+ this.addBlock(block.opcode);
+ });
+
+ hideEnumBlocks = false;
+ }
+ }
+
+ /* helper functions */
+
+ addBlock(opcode) {
+ Object.defineProperty(Enumerations.prototype, opcode, {
+ value: function (_, util, blockInfo) {
+ return this.thisEnum("", util, blockInfo);
+ },
+ writable: true,
+ configurable: true,
+ });
+ }
+
+ addDropdownBlock(opcode) {
+ Object.defineProperty(Enumerations.prototype, opcode, {
+ value: function (args, util, blockInfo) {
+ return this.thisEnum(args, util, blockInfo);
+ },
+ writable: true,
+ configurable: true,
+ });
+ }
+
+ getPrevBlock(util) {
+ const contain = util.thread.blockContainer;
+ const block = contain.getBlock(util.thread.isCompiled ? util.thread.peekStack() : util.thread.peekStackFrame().op?.id);
+ return contain.getBlock(block?.parent);
+ }
+
+ /* menus */
+
+ getEnums() {
+ return Object.keys(enums);
+ }
+
+ getKeysOf() {
+ const args = getCurrentBlockArgs();
+ const enumName = args.ENUM;
+ if (enums[enumName]) {
+ const keys = Object.keys(enums[enumName])
+ if (keys.length != 0) {
+ return keys;
+ }
+ }
+ return [""];
+ }
+
+ /* blocks */
+
+ enum({ mutator }) {
+ return enums[mutator.blockInfo.text];
+ }
+
+ thisEnum(_, util, blockInfo) {
+ const isInExtBlock = this.getPrevBlock(util)?.opcode.startsWith("dogeiscutenumerations_");
+ const enumInfo = enums[blockInfo.text];
+ return enumInfo ? isInExtBlock ? enumInfo : JSON.stringify(enumInfo) : "";
+ }
+
+ keyOfEnum(args) {
+ const enumName = args.ENUM;
+ const key = args.KEY;
+ if (enums[enumName] && enums[enumName].hasOwnProperty(key)) {
+ return enums[enumName][key];
+ }
+ return "";
+ }
+
+ enumLength(args) {
+ const enumName = args.ENUM;
+ if (enums[enumName]) {
+ return Object.keys(enums[enumName]).length;
+ }
+ return 0;
+ }
+
+ enumContains(args) {
+ const enumName = args.ENUM;
+ const key = args.KEY;
+ if (enums[enumName]) {
+ return enums[enumName].hasOwnProperty(key);
+ }
+ return false;
+ }
+
+ /* buttons */
+
+ makeAnEnum() {
+ openModal("New enum name:", "New Enum", true, ((value, enumData) => {
+ if (!value) return;
+ if (Object.keys(enumData).length === 0) return;
+ const editingTarget = runtime.getEditingTarget();
+ const stage = runtime.getTargetForStage();
+
+
+ hideEnumBlocks = false;
+ vm.extensionManager.refreshBlocks("dogeiscutenumerations");
+ this.serialize();
+ enums[value] = enumData
+ }))
+ }
+
+ removeAnEnum() {
+ openModal("Remove enum named:", "Remove Enum", false, (value) => {
+ const block = enumBlocks.find((i) => { return i.text == value });
+ if (!block) return;
+ block.hideFromPalette = true;
+ delete enums[value];
+ runtime.monitorBlocks.changeBlock({ id: `dogeiscutenumerations_enum_${value}`, element: "checkbox", value: false }, runtime);
+
+ if (Object.keys(enums).length === 0) hideEnumBlocks = true;
+ this.serialize();
+ vm.extensionManager.refreshBlocks("dogeiscutenumerations");
+ });
+ }
+ }
+
+ Scratch.extensions.register(new Enumerations());
+})(Scratch);
\ No newline at end of file
diff --git a/static/images/DogeisCut/Enumerations.svg b/static/images/DogeisCut/Enumerations.svg
new file mode 100644
index 000000000..9c2cf16b6
--- /dev/null
+++ b/static/images/DogeisCut/Enumerations.svg
@@ -0,0 +1,230 @@
+
+
+
+