From 11bd5aa72b5d6c35acd2e30f5926f214d6059316 Mon Sep 17 00:00:00 2001 From: arc360 Date: Mon, 19 Jan 2026 23:44:32 -0600 Subject: [PATCH] Init --- .gitignore | 24 + README.md | 4 + editor.html | 564 ++++++ ext.js | 101 + index.html | 92 + login.html | 46 + package-lock.json | 2667 +++++++++++++++++++++++++++ package.json | 23 + public/favicon.ico | Bin 0 -> 114198 bytes public/icons/NeoIDE.svg | 9 + public/icons/R-Rarry.svg | 1 + public/icons/blur.png | Bin 0 -> 203755 bytes public/icons/ddededodediamante.png | Bin 0 -> 19702 bytes public/icons/flag.svg | 1 + public/icons/fullscreen.svg | 23 + public/icons/left.svg | 1 + public/icons/pen.svg | 1 + public/icons/play.svg | 1 + public/icons/right.svg | 1 + public/icons/sets.svg | 1 + public/icons/smallscreen.svg | 23 + public/icons/statement.svg | 1 + public/icons/stop.svg | 1 + public/icons/stopAudio.svg | 1 + public/icons/terminal.svg | 1 + public/icons/trash.svg | 1 + public/icons/tween.svg | 1 + signup.html | 46 + src/blocks/control.js | 187 ++ src/blocks/event.js | 172 ++ src/blocks/functions.js | 811 ++++++++ src/blocks/json.js | 512 ++++++ src/blocks/list.js | 454 +++++ src/blocks/looks.js | 218 +++ src/blocks/motion.js | 223 +++ src/blocks/pen.js | 115 ++ src/blocks/set.js | 353 ++++ src/blocks/sound.js | 137 ++ src/blocks/system.js | 137 ++ src/blocks/tween.js | 291 +++ src/blocks/variable.js | 85 + src/cache.js | 3 + src/config.js | 5 + src/editor.css | 415 +++++ src/functions/extensionManager.js | 248 +++ src/functions/patches.js | 334 ++++ src/functions/render.js | 217 +++ src/functions/runCode.js | 441 +++++ src/functions/theme.js | 282 +++ src/functions/threads.js | 84 + src/functions/utils.js | 260 +++ src/home.css | 70 + src/index.css | 489 +++++ src/login.css | 24 + src/scripts/editor.js | 2743 ++++++++++++++++++++++++++++ src/scripts/index.js | 4 + src/scripts/login.js | 59 + src/scripts/signup.js | 59 + src/scripts/testExt.js | 128 ++ src/scripts/userprofile.js | 129 ++ src/userprofile.css | 27 + user.html | 61 + vercel.json | 4 + vite.config.js | 17 + 64 files changed, 13433 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 editor.html create mode 100644 ext.js create mode 100644 index.html create mode 100644 login.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.ico create mode 100644 public/icons/NeoIDE.svg create mode 100644 public/icons/R-Rarry.svg create mode 100644 public/icons/blur.png create mode 100644 public/icons/ddededodediamante.png create mode 100644 public/icons/flag.svg create mode 100644 public/icons/fullscreen.svg create mode 100644 public/icons/left.svg create mode 100644 public/icons/pen.svg create mode 100644 public/icons/play.svg create mode 100644 public/icons/right.svg create mode 100644 public/icons/sets.svg create mode 100644 public/icons/smallscreen.svg create mode 100644 public/icons/statement.svg create mode 100644 public/icons/stop.svg create mode 100644 public/icons/stopAudio.svg create mode 100644 public/icons/terminal.svg create mode 100644 public/icons/trash.svg create mode 100644 public/icons/tween.svg create mode 100644 signup.html create mode 100644 src/blocks/control.js create mode 100644 src/blocks/event.js create mode 100644 src/blocks/functions.js create mode 100644 src/blocks/json.js create mode 100644 src/blocks/list.js create mode 100644 src/blocks/looks.js create mode 100644 src/blocks/motion.js create mode 100644 src/blocks/pen.js create mode 100644 src/blocks/set.js create mode 100644 src/blocks/sound.js create mode 100644 src/blocks/system.js create mode 100644 src/blocks/tween.js create mode 100644 src/blocks/variable.js create mode 100644 src/cache.js create mode 100644 src/config.js create mode 100644 src/editor.css create mode 100644 src/functions/extensionManager.js create mode 100644 src/functions/patches.js create mode 100644 src/functions/render.js create mode 100644 src/functions/runCode.js create mode 100644 src/functions/theme.js create mode 100644 src/functions/threads.js create mode 100644 src/functions/utils.js create mode 100644 src/home.css create mode 100644 src/index.css create mode 100644 src/login.css create mode 100644 src/scripts/editor.js create mode 100644 src/scripts/index.js create mode 100644 src/scripts/login.js create mode 100644 src/scripts/signup.js create mode 100644 src/scripts/testExt.js create mode 100644 src/scripts/userprofile.js create mode 100644 src/userprofile.css create mode 100644 user.html create mode 100644 vercel.json create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md new file mode 100644 index 0000000..69bd336 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# NeoIDE +NeoIDE is a web-based platform that allows to create games or projects using visual block-coding, inspired by Scratch. + +[Website](https://NeoIDE.vercel.app/) | [Documentation](https://NeoIDE-docs.vercel.app/) diff --git a/editor.html b/editor.html new file mode 100644 index 0000000..452d5b8 --- /dev/null +++ b/editor.html @@ -0,0 +1,564 @@ + + + + + + + Editor - NeoIDE + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+ +
+
+ + + + +
+
+ +
+
+
+ + + +
+ +
+
+
+ +
+

Costumes

+
+ +
+ +
+

Sounds

+
+ +
+
+ +
+
+
+ + + +
+ +
+
+
+
+ +
+

Loading...

+
+ +
+

Sprites

+
+
+ + +
+
+
+

Backdrops

+
+
+ + +
+ +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/ext.js b/ext.js new file mode 100644 index 0000000..aacaabc --- /dev/null +++ b/ext.js @@ -0,0 +1,101 @@ +class TextSpriteExtension { + id = "textSprite"; + + registerCategory() { + return { + name: "Text Sprite", + color: "#8E44AD", + }; + } + + registerBlocks() { + return [ + { + type: "statement", + id: "createTextSprite", + text: "create text sprite [text] font [font] size [size] color [color]", + tooltip: "Create a new sprite rendered from text", + fields: { + text: { kind: "value", type: "String", default: "Hello!" }, + font: { + kind: "menu", + items: [ + "Arial", + "Verdana", + "Courier New", + "Times New Roman", + "Comic Sans MS", + ], + }, + size: { kind: "value", type: "Number", default: "48" }, + color: { kind: "value", type: "String", default: "#000000" }, + }, + }, + ]; + } + + registerCode() { + return { + createTextSprite: (inputs) => { + if (!window.app || !window.PIXI) return; + + const text = String(inputs.text ?? ""); + const font = inputs.font || "Arial"; + const size = Number(inputs.size || 48); + const color = String(inputs.color || "#000000"); + + /* ---------- Canvas ---------- */ + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + ctx.font = `${size}px ${font}`; + const metrics = ctx.measureText(text); + + canvas.width = Math.ceil(metrics.width) + 20; + canvas.height = size + 20; + + ctx.font = `${size}px ${font}`; + ctx.fillStyle = color; + ctx.textBaseline = "top"; + ctx.fillText(text, 10, 10); + + /* ---------- PIXI Texture ---------- */ + const baseTexture = new PIXI.BaseTexture(canvas); + const texture = new PIXI.Texture(baseTexture); + baseTexture.update(); + + /* ---------- Create Sprite (same as addSprite) ---------- */ + const pixiSprite = new PIXI.Sprite(texture); + pixiSprite.anchor.set(0.5); + pixiSprite.x = 0; + pixiSprite.y = 0; + pixiSprite.scale._parentScaleEvent = pixiSprite; + + app.stage.addChild(pixiSprite); + + const id = "text-sprite-" + Date.now(); + + const spriteData = { + id, + pixiSprite, + code: "", + costumes: [{ name: "text", texture }], + sounds: [], + }; + + sprites.push(spriteData); + + // Register globally + if (!window.projectCostumes.includes("text")) { + window.projectCostumes.push("text"); + } + + // Select it + setActiveSprite(spriteData); + renderSpritesList(true); + }, + }; + } +} + +registerExtension(TextSpriteExtension); diff --git a/index.html b/index.html new file mode 100644 index 0000000..2b102dc --- /dev/null +++ b/index.html @@ -0,0 +1,92 @@ + + + + + + + Home - NeoIDE + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ +
+
+ +
+

What is NeoIDE?

+

+ NeoIDE is a web-based platform that allows to create games or projects + using visual block-coding, inspired by Scratch. +

+ +

Features

+
+
+

Visual Block Editor

+

Write code by snapping blocks together, no typing required.

+
+
+

Custom Extensions

+

+ Extend your projects with custom blocks, new categories, and + powerful features. +

+
+
+

Themes

+

Switch between light and dark modes to match your style.

+
+
+
+ +
+

Ready to build your first project?

+ +
+ + + + + + + \ No newline at end of file diff --git a/login.html b/login.html new file mode 100644 index 0000000..95216bf --- /dev/null +++ b/login.html @@ -0,0 +1,46 @@ + + + + + + + Login - NeoIDE + + + + + + + + + + + + + + + + + + +
+ NeoIDE logo + Create an account + +

Username

+ + +

Password

+ + + +
+ + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..76fa927 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2667 @@ +{ + "name": "rarry-vite", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rarry-vite", + "version": "0.0.0", + "dependencies": { + "@blockly/plugin-strict-connection-checker": "^6.0.1", + "@fortawesome/fontawesome-free": "^7.0.1", + "blockly": "^12.2.0", + "jszip": "^3.10.1", + "pako": "^2.1.0", + "pixi.js-legacy": "^7.4.3", + "socket.io-client": "^4.8.1" + }, + "devDependencies": { + "vite": "^7.0.4" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@blockly/plugin-strict-connection-checker": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@blockly/plugin-strict-connection-checker/-/plugin-strict-connection-checker-6.0.1.tgz", + "integrity": "sha512-r59BoafH7WRDSjef/8xAjbEhWQlQl65ZoKdV/E2ykUBXPHUznTtSID0sgCbD4Ut3X9qcBPPsWakrm55Y+7FSXg==", + "license": "Apache 2.0", + "engines": { + "node": ">=8.17.0" + }, + "peerDependencies": { + "blockly": "^12.0.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", + "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", + "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", + "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", + "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", + "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", + "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", + "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", + "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", + "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", + "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", + "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", + "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", + "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", + "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", + "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", + "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", + "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", + "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", + "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", + "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", + "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", + "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", + "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", + "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", + "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", + "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.0.1.tgz", + "integrity": "sha512-RLmb9U6H2rJDnGxEqXxzy7ANPrQz7WK2/eTjdZqyU9uRU5W+FkAec9uU5gTYzFBH7aoXIw2WTJSCJR4KPlReQw==", + "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)", + "engines": { + "node": ">=6" + } + }, + "node_modules/@pixi/accessibility": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.4.3.tgz", + "integrity": "sha512-tCr0yeWpMe0yucFvEPidy5a7gVJGpTjqGrDpSEBYT/kbScfUwcoX49RrckCCCiXDlyO4WRh9lVVuHXTvqRLIMg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3" + } + }, + "node_modules/@pixi/app": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.4.3.tgz", + "integrity": "sha512-opyWMuO0Ir8pf1DYUR++wAA6ZfNU+nIX2z95R2OD172HbcdhB4/HD7leLIIAny/LciEdMqlWEBhXK7N93YWbdg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/assets": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.4.3.tgz", + "integrity": "sha512-StvjiJBSp/j9hHkGu8AFHNvwYUazXq64WhyhytztyDMRkg/l/cL7EcttY5T0qZNWlIpccdr60LUKrWDOuMpkiw==", + "license": "MIT", + "dependencies": { + "@types/css-font-loading-module": "^0.0.12" + }, + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/canvas-display": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-display/-/canvas-display-7.4.3.tgz", + "integrity": "sha512-Y33a6DL2s6RFIPKfC1tCw8eimVnaQVrzhOUgSZ03jE5B2pC7FwcOIZ64sDi2VpVToXMl2f5EgkgtEXYkWjTYHg==", + "license": "MIT", + "peerDependencies": { + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/canvas-extract": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-extract/-/canvas-extract-7.4.3.tgz", + "integrity": "sha512-sSpIf1C7nlEO22sW/bcVPZzUJyyh+4QcekhwzyYVbr1LVz0zk1WPOXu82MgiRxaHkyCfWdxes7drKQ2XHYk3eA==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-renderer": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/extract": "7.4.3" + } + }, + "node_modules/@pixi/canvas-graphics": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-graphics/-/canvas-graphics-7.4.3.tgz", + "integrity": "sha512-E3kizZDI6m8WZg1AeHryanEhVGqMVsu75NZIwWu6mWkjHqGqvj/vG17Ua0jl9ThSFfPc4Y/xI9eAewni12IG8A==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-display": "7.4.3", + "@pixi/canvas-renderer": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/graphics": "7.4.3" + } + }, + "node_modules/@pixi/canvas-mesh": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-mesh/-/canvas-mesh-7.4.3.tgz", + "integrity": "sha512-0yBalm1bRIfozdbuOqRcCWYN4XxxsDLcBA0NjpPFQn8LKImUpYxPayGbraxfIu8kF+z5rFubk5JiAGZc5UHorw==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-display": "7.4.3", + "@pixi/canvas-renderer": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/mesh-extras": "7.4.3" + } + }, + "node_modules/@pixi/canvas-particle-container": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-particle-container/-/canvas-particle-container-7.4.3.tgz", + "integrity": "sha512-0gBIt2q8NtX9ZrHiY371AVM2Vmip1azQEy1ruOXq3b2PZgIaegZT7eAQ0997ljynheXWf3DBY/9Z6m3ejoV3jw==", + "license": "MIT", + "peerDependencies": { + "@pixi/particle-container": "7.4.3" + } + }, + "node_modules/@pixi/canvas-prepare": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-prepare/-/canvas-prepare-7.4.3.tgz", + "integrity": "sha512-ATCvDO9sMpteZl4LvZkFlOjCAAwa2i756oXQBDWiAEF/WkkyZ4y2O3CX4w5oYg+9G+S8b1iEDfKLcWr4HHk9yA==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-renderer": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/prepare": "7.4.3" + } + }, + "node_modules/@pixi/canvas-renderer": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-renderer/-/canvas-renderer-7.4.3.tgz", + "integrity": "sha512-mZnx/tUaNlREh6Yn/z54g0NRvLEdlWIgA5qioeZbJA2HeG+zKEio7hUEOsNIu2o6xzliUoFGzFr7jaS+m7onfg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/canvas-sprite": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-sprite/-/canvas-sprite-7.4.3.tgz", + "integrity": "sha512-Qr17o6AgDihXJGg+lZr8HV5mhvWW46v/+15AY7TFQhJytPv32Y6tJMfojVWquOpzREczMqryun1K5vJpAuinQA==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-display": "7.4.3", + "@pixi/canvas-renderer": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/canvas-sprite-tiling": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-sprite-tiling/-/canvas-sprite-tiling-7.4.3.tgz", + "integrity": "sha512-o8PVghNynaFr2bVcxFj1TB6BrnK27yHDLyHaBhw67cijl0Fjf1uSy5ywVTDCP9ihcK3vadY65tKhQNytuu7uFA==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-renderer": "7.4.3", + "@pixi/canvas-sprite": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/sprite-tiling": "7.4.3" + } + }, + "node_modules/@pixi/canvas-text": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/canvas-text/-/canvas-text-7.4.3.tgz", + "integrity": "sha512-tbh4+k3Q0rEAhK42KvDhrEA6qSU1rFT4Y/Nj3kvK2QOqO6m+LwkbFPFnoSVRvN6jGJ+wA3RZG1LKS0kxAxoxTA==", + "license": "MIT", + "peerDependencies": { + "@pixi/canvas-sprite": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/color": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.4.3.tgz", + "integrity": "sha512-a6R+bXKeXMDcRmjYQoBIK+v2EYqxSX49wcjAY579EYM/WrFKS98nSees6lqVUcLKrcQh2DT9srJHX7XMny3voQ==", + "license": "MIT", + "dependencies": { + "@pixi/colord": "^2.9.6" + } + }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==", + "license": "MIT" + }, + "node_modules/@pixi/compressed-textures": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.4.3.tgz", + "integrity": "sha512-uJ3CC+lNX4HIxs6IxEESO50/0A1KxSVm6CO9UlkXzTsNj9ynmdy5BkJ1dzii7LCdqGcHIXHO01yvKuUbJBBQtw==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/constants": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.4.3.tgz", + "integrity": "sha512-QGmwJUNQy/vVEHzL6VGQvnwawLZ1wceZMI8HwJAT4/I2uAzbBeFDdmCS8WsTpSWLZjF/DszDc1D8BFp4pVJ5UQ==", + "license": "MIT" + }, + "node_modules/@pixi/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.4.3.tgz", + "integrity": "sha512-5YDs11faWgVVTL8VZtLU05/Fl47vaP5Tnsbf+y/WRR0VSW3KhRRGTBU1J3Gdc2xEWbJhUK07KGP7eSZpvtPVgA==", + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/math": "7.4.3", + "@pixi/runner": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/ticker": "7.4.3", + "@pixi/utils": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/@pixi/display": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.4.3.tgz", + "integrity": "sha512-b5m2dAaoNAVdxz1oDaxl3XZ059NEOcNtGkxTOZ4EYCw/jcp9sZXkgSROHRzsGn4k+NugH7+9MP4Id2Z0kkdUhw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/events": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.4.3.tgz", + "integrity": "sha512-o3j/5Dxq6WDVS6eHfURB/cf/MP+NcsF/eC5PnbSHjXxJmDE7PoTVwLvxexm5uuvNRpFh/6/Fn0V8Vl4gV8sc8w==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/extensions": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.4.3.tgz", + "integrity": "sha512-FhoiYkHQEDYHUE7wXhqfsTRz6KxLXjuMbSiAwnLb9uG1vAgp6q6qd6HEsf4X30YaZbLFY8a4KY6hFZWjF+4Fdw==", + "license": "MIT" + }, + "node_modules/@pixi/extract": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.4.3.tgz", + "integrity": "sha512-HNvGNrEVaeVsbcnIO1MsHpjZbTwo9nIlaOEBzDGcL6JWwzuB1RnzUke7WUCndCUt91sGUdvPnvgCvy9/NNFg3w==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-alpha": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.4.3.tgz", + "integrity": "sha512-YFdUB1I53USQb+9TEhS849dV2KZhbnNGIoBbOSThUJfXQc4pDguIFWMagVToAQYgmZ4C4AtYfVjaSEELrMcCdA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-blur": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.4.3.tgz", + "integrity": "sha512-ZFzS9L/whdRbs5A/EUgF3yQaBcxNarmbuwaMgrfnpQ84mRczkGByqDLGToadiufyals07ufTrXBGRle9lbtEDA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-color-matrix": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.4.3.tgz", + "integrity": "sha512-TNu0h20SrzjUWIb5v19dAp1vPpqtG0w2XF9kIHN91bMNaf3R1jzhpWG6TtaVO9eo1IolWcEJLw38jIohyC+KNw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-displacement": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.4.3.tgz", + "integrity": "sha512-ax+cFA2mEnKgqf9F8qInpv09GNWzjwnASLETpwPXzWBtlAlNCeHV2tCv3+SlMdEKUkwG9sA7AmjjjC2JBUyt+Q==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-fxaa": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.4.3.tgz", + "integrity": "sha512-y9jhho5cCflhEsPtNqqsd+XJHsb+/ysht4rG/VHQ8Z6pScHYpbgiEpowryGq8uSMQQwx6zKNS2DPiXdiOHPZsg==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/filter-noise": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.4.3.tgz", + "integrity": "sha512-rwgSO3BKe1jW/P5CaOcfLKjfpl674aBEo/igi/3QLxA3ORhILNqWRsKkOwP8xF/ejI5NE4rMEkdv0LScbdGFhA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/graphics": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.4.3.tgz", + "integrity": "sha512-wWLivD8/URb8A7X4TqCZGG39C91IE+aOuWY/z9NCz5Z6WvA/VWnsc5fLTlO+ggjGHgKF0cSucCXZfUe1wm0AOQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/math": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.4.3.tgz", + "integrity": "sha512-/uJOVhR2DOZ+zgdI6Bs/CwcXT4bNRKsS+TqX3ekRIxPCwaLra+Qdm7aDxT5cTToDzdxbKL5+rwiLu3Y1egILDw==", + "license": "MIT" + }, + "node_modules/@pixi/mesh": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.4.3.tgz", + "integrity": "sha512-CikqFPtKvU3Zj986/MSoC8X39CWv5CEpiEW/tYp47p4tgQNDSkNWYnDiNYgb+4VX6pNsBrgX4DALLdTR17SlSA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mesh-extras": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.4.3.tgz", + "integrity": "sha512-EqpxpVZoTObyupxMSzuUsCGmWPQioW84n9EO9Ajawkk/HYA+qKFZ5viKiEThIUBYgv4Apn/7c0U3Feg7Ez4uQQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/mesh": "7.4.3" + } + }, + "node_modules/@pixi/mixin-cache-as-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.4.3.tgz", + "integrity": "sha512-NgvDdgSgd2tfcTSc+SWF12JJjVVz5ZrkSlhX0idSp/LSako82AiFJlD2xqH9GUsEcA6sqBBlnu7nrGkPTHQdhA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-child-by-name": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.4.3.tgz", + "integrity": "sha512-HLhDxHwafQT+CxbqQx9w9ivJIyAOg9JJ/6m4fNymVuDWeuMGcxDxBD7DukdUYIieT+RD/RlxdPEmq8YoromlFA==", + "license": "MIT", + "peerDependencies": { + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/mixin-get-global-position": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.4.3.tgz", + "integrity": "sha512-k09kvkS379EypCIWgXMY7uiXtWk1BsaJyTYlV16Co0AsmNPdFd+wUozMx1xV6rxcGiWXsxr/1k9fbETuYkcXCQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/particle-container": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.4.3.tgz", + "integrity": "sha512-0DfJF5C0XTfuI2FsLYvMKCOtqWjXWGOWfA6m4l0W/Ke/qw5zKIOEhgjPLw4qNRtOhmEfkVKJUGp66Ap/ya2YzA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/prepare": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.4.3.tgz", + "integrity": "sha512-OjJHGKXPzwP5OLKxBnTBnKMOktHynLvO0TQPqTYgNtmGQzY109mypCqM4M+s/V+uYmBo/T+sXvBahj98q/f1tA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/runner": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.4.3.tgz", + "integrity": "sha512-TJyfp7y23u5vvRAyYhVSa7ytq0PdKSvPLXu4G3meoFh1oxTLHH6g/RIzLuxUAThPG2z7ftthuW3qWq6dRV+dhw==", + "license": "MIT" + }, + "node_modules/@pixi/settings": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.4.3.tgz", + "integrity": "sha512-SmGK8smc0PxRB9nr0UJioEtE9hl4gvj9OedCvZx3bxBwA3omA5BmP3CyhQfN8XJ29+o2OUL01r3zAPVol4l4lA==", + "license": "MIT", + "dependencies": { + "@pixi/constants": "7.4.3", + "@types/css-font-loading-module": "^0.0.12", + "ismobilejs": "^1.1.0" + } + }, + "node_modules/@pixi/sprite": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.4.3.tgz", + "integrity": "sha512-iNBrpOFF9nXDT6m2jcyYy6l/sRzklLDDck1eFHprHZwvNquY2nzRfh+RGBCecxhBcijiLJ3fsZN33fP0LDXkvw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3" + } + }, + "node_modules/@pixi/sprite-animated": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.4.3.tgz", + "integrity": "sha512-mw5YIec8KfO1Jv9qrDNvGoD7Dlmcgww5YlMtd+ARi7Zzo+6ziNw899LXtoaKX1+3BXdZbYNyJAx3C5r30NtwXA==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/sprite-tiling": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.4.3.tgz", + "integrity": "sha512-kUa9cEcMsGXSIZoXA7LhW4oo0eWa30w0KYd7mZ0bqalBMfOcvsGZMN701Lc5lpE8URw+8yu5bnyGLbrxhWBTuw==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/spritesheet": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.4.3.tgz", + "integrity": "sha512-Ce4xZzUxUSKfiROUjjVCBYNLuCcDEWKJ822bSV9rkgVHItu3q04VnEww0DXO+9K0hKv4Ukjjk8aP6Pz0LgPm7A==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3" + } + }, + "node_modules/@pixi/text": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.4.3.tgz", + "integrity": "sha512-IAF0iu04rPg3oiL0HZsEZI44fpJxq3UZ4xTmx8l1RyhhSXiElLvvSlSH57vt/BKMQZtCs+AqEit7yn8heK2+nQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/sprite": "7.4.3" + } + }, + "node_modules/@pixi/text-bitmap": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.4.3.tgz", + "integrity": "sha512-TnBocJm7f5nMAYwYcsojc62uCrOYauAGH26o3pNrlqmHDRDQ7FzPOGvkYZGYFREbUycloLSRlYpSy0FB9ZdV4Q==", + "license": "MIT", + "peerDependencies": { + "@pixi/assets": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/text-html": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.4.3.tgz", + "integrity": "sha512-nm9K9gjSZAU8ETwQZBE3kMGNdO1IzyghxoRTcJCWKhekiGDpUQhopfNhqieNZ7reVJpvhpFQWjbyaHDehndUaQ==", + "license": "MIT", + "peerDependencies": { + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/text": "7.4.3" + } + }, + "node_modules/@pixi/ticker": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.4.3.tgz", + "integrity": "sha512-tHsAD0iOUb6QSGGw+c8cyRBvxsq/NlfzIFBZLEHhWZ+Bx4a0MmXup6I/yJDGmyPCYE+ctCcAfY13wKAzdiVFgQ==", + "license": "MIT", + "dependencies": { + "@pixi/extensions": "7.4.3", + "@pixi/settings": "7.4.3", + "@pixi/utils": "7.4.3" + } + }, + "node_modules/@pixi/utils": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.4.3.tgz", + "integrity": "sha512-NO3Y9HAn2UKS1YdxffqsPp+kDpVm8XWvkZcS/E+rBzY9VTLnNOI7cawSRm+dacdET3a8Jad3aDKEDZ0HmAqAFA==", + "license": "MIT", + "dependencies": { + "@pixi/color": "7.4.3", + "@pixi/constants": "7.4.3", + "@pixi/settings": "7.4.3", + "@types/earcut": "^2.1.0", + "earcut": "^2.2.4", + "eventemitter3": "^4.0.0", + "url": "^0.11.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.0.tgz", + "integrity": "sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.0.tgz", + "integrity": "sha512-PSZ0SvMOjEAxwZeTx32eI/j5xSYtDCRxGu5k9zvzoY77xUNssZM+WV6HYBLROpY5CkXsbQjvz40fBb7WPwDqtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.0.tgz", + "integrity": "sha512-BA4yPIPssPB2aRAWzmqzQ3y2/KotkLyZukVB7j3psK/U3nVJdceo6qr9pLM2xN6iRP/wKfxEbOb1yrlZH6sYZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.0.tgz", + "integrity": "sha512-Pr2o0lvTwsiG4HCr43Zy9xXrHspyMvsvEw4FwKYqhli4FuLE5FjcZzuQ4cfPe0iUFCvSQG6lACI0xj74FDZKRA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.0.tgz", + "integrity": "sha512-lYE8LkE5h4a/+6VnnLiL14zWMPnx6wNbDG23GcYFpRW1V9hYWHAw9lBZ6ZUIrOaoK7NliF1sdwYGiVmziUF4vA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.0.tgz", + "integrity": "sha512-PVQWZK9sbzpvqC9Q0GlehNNSVHR+4m7+wET+7FgSnKG3ci5nAMgGmr9mGBXzAuE5SvguCKJ6mHL6vq1JaJ/gvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.0.tgz", + "integrity": "sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.0.tgz", + "integrity": "sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.0.tgz", + "integrity": "sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.0.tgz", + "integrity": "sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.0.tgz", + "integrity": "sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.0.tgz", + "integrity": "sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.0.tgz", + "integrity": "sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.0.tgz", + "integrity": "sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.0.tgz", + "integrity": "sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.0.tgz", + "integrity": "sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.0.tgz", + "integrity": "sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.0.tgz", + "integrity": "sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.0.tgz", + "integrity": "sha512-+CXwwG66g0/FpWOnP/v1HnrGVSOygK/osUbu3wPRy8ECXjoYKjRAyfxYpDQOfghC5qPJYLPH0oN4MCOjwgdMug==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.0.tgz", + "integrity": "sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", + "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==", + "license": "MIT" + }, + "node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/blockly": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/blockly/-/blockly-12.2.0.tgz", + "integrity": "sha512-s4QL9ogEMzc4Pxfe8Oi3Kmu6SQ0ts2thzmRYjdnMSEIVZFpBZ4OUuNKvpFICqujO0yfAo99zON8KzxAFw8hA1w==", + "license": "Apache-2.0", + "dependencies": { + "jsdom": "26.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", + "license": "ISC" + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", + "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.6", + "@esbuild/android-arm": "0.25.6", + "@esbuild/android-arm64": "0.25.6", + "@esbuild/android-x64": "0.25.6", + "@esbuild/darwin-arm64": "0.25.6", + "@esbuild/darwin-x64": "0.25.6", + "@esbuild/freebsd-arm64": "0.25.6", + "@esbuild/freebsd-x64": "0.25.6", + "@esbuild/linux-arm": "0.25.6", + "@esbuild/linux-arm64": "0.25.6", + "@esbuild/linux-ia32": "0.25.6", + "@esbuild/linux-loong64": "0.25.6", + "@esbuild/linux-mips64el": "0.25.6", + "@esbuild/linux-ppc64": "0.25.6", + "@esbuild/linux-riscv64": "0.25.6", + "@esbuild/linux-s390x": "0.25.6", + "@esbuild/linux-x64": "0.25.6", + "@esbuild/netbsd-arm64": "0.25.6", + "@esbuild/netbsd-x64": "0.25.6", + "@esbuild/openbsd-arm64": "0.25.6", + "@esbuild/openbsd-x64": "0.25.6", + "@esbuild/openharmony-arm64": "0.25.6", + "@esbuild/sunos-x64": "0.25.6", + "@esbuild/win32-arm64": "0.25.6", + "@esbuild/win32-ia32": "0.25.6", + "@esbuild/win32-x64": "0.25.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==", + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "license": "MIT" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pixi.js": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.4.3.tgz", + "integrity": "sha512-uIWdH0EI2dVgNoqN9aFaHCmR0V65OEhMkXs2sek3c/QP2ItV6UoM+ouX9esSv3ibo20F+J5D1XwnQhUZI6wqeQ==", + "license": "MIT", + "dependencies": { + "@pixi/accessibility": "7.4.3", + "@pixi/app": "7.4.3", + "@pixi/assets": "7.4.3", + "@pixi/compressed-textures": "7.4.3", + "@pixi/core": "7.4.3", + "@pixi/display": "7.4.3", + "@pixi/events": "7.4.3", + "@pixi/extensions": "7.4.3", + "@pixi/extract": "7.4.3", + "@pixi/filter-alpha": "7.4.3", + "@pixi/filter-blur": "7.4.3", + "@pixi/filter-color-matrix": "7.4.3", + "@pixi/filter-displacement": "7.4.3", + "@pixi/filter-fxaa": "7.4.3", + "@pixi/filter-noise": "7.4.3", + "@pixi/graphics": "7.4.3", + "@pixi/mesh": "7.4.3", + "@pixi/mesh-extras": "7.4.3", + "@pixi/mixin-cache-as-bitmap": "7.4.3", + "@pixi/mixin-get-child-by-name": "7.4.3", + "@pixi/mixin-get-global-position": "7.4.3", + "@pixi/particle-container": "7.4.3", + "@pixi/prepare": "7.4.3", + "@pixi/sprite": "7.4.3", + "@pixi/sprite-animated": "7.4.3", + "@pixi/sprite-tiling": "7.4.3", + "@pixi/spritesheet": "7.4.3", + "@pixi/text": "7.4.3", + "@pixi/text-bitmap": "7.4.3", + "@pixi/text-html": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/pixi.js-legacy": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/pixi.js-legacy/-/pixi.js-legacy-7.4.3.tgz", + "integrity": "sha512-vFw/CSk05C4eSOtELeXY8cizjoEx444MW4o7xdwcu6Rxw3UJioJXccp6Vkchvb46xVd+v51pVL6KDpcMAsGa+g==", + "license": "MIT", + "dependencies": { + "@pixi/canvas-display": "7.4.3", + "@pixi/canvas-extract": "7.4.3", + "@pixi/canvas-graphics": "7.4.3", + "@pixi/canvas-mesh": "7.4.3", + "@pixi/canvas-particle-container": "7.4.3", + "@pixi/canvas-prepare": "7.4.3", + "@pixi/canvas-renderer": "7.4.3", + "@pixi/canvas-sprite": "7.4.3", + "@pixi/canvas-sprite-tiling": "7.4.3", + "@pixi/canvas-text": "7.4.3", + "pixi.js": "7.4.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/pixijs" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/rollup": { + "version": "4.45.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.0.tgz", + "integrity": "sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.0", + "@rollup/rollup-android-arm64": "4.45.0", + "@rollup/rollup-darwin-arm64": "4.45.0", + "@rollup/rollup-darwin-x64": "4.45.0", + "@rollup/rollup-freebsd-arm64": "4.45.0", + "@rollup/rollup-freebsd-x64": "4.45.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.0", + "@rollup/rollup-linux-arm-musleabihf": "4.45.0", + "@rollup/rollup-linux-arm64-gnu": "4.45.0", + "@rollup/rollup-linux-arm64-musl": "4.45.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.0", + "@rollup/rollup-linux-riscv64-gnu": "4.45.0", + "@rollup/rollup-linux-riscv64-musl": "4.45.0", + "@rollup/rollup-linux-s390x-gnu": "4.45.0", + "@rollup/rollup-linux-x64-gnu": "4.45.0", + "@rollup/rollup-linux-x64-musl": "4.45.0", + "@rollup/rollup-win32-arm64-msvc": "4.45.0", + "@rollup/rollup-win32-ia32-msvc": "4.45.0", + "@rollup/rollup-win32-x64-msvc": "4.45.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.4.tgz", + "integrity": "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4b74d14 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "rarry-vite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^7.0.4" + }, + "dependencies": { + "@blockly/plugin-strict-connection-checker": "^6.0.1", + "@fortawesome/fontawesome-free": "^7.0.1", + "blockly": "^12.2.0", + "jszip": "^3.10.1", + "pako": "^2.1.0", + "pixi.js-legacy": "^7.4.3", + "socket.io-client": "^4.8.1" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..26e328314dd3501b4f7c309534fd69534cdbb838 GIT binary patch literal 114198 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 literal 0 HcmV?d00001 diff --git a/public/icons/NeoIDE.svg b/public/icons/NeoIDE.svg new file mode 100644 index 0000000..f394377 --- /dev/null +++ b/public/icons/NeoIDE.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/icons/R-Rarry.svg b/public/icons/R-Rarry.svg new file mode 100644 index 0000000..c464e1f --- /dev/null +++ b/public/icons/R-Rarry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/blur.png b/public/icons/blur.png new file mode 100644 index 0000000000000000000000000000000000000000..0554ebff54578c575734094055e9795e6b4f5a47 GIT binary patch literal 203755 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�(zVxRJXFbI1P05 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 z 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 JPDHLkV1k82OIQE^ literal 0 HcmV?d00001 diff --git a/public/icons/ddededodediamante.png b/public/icons/ddededodediamante.png new file mode 100644 index 0000000000000000000000000000000000000000..bf64d694edc281f35a3b2d10fd3526714a995c18 GIT binary patch literal 19702 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 C$Ikfx literal 0 HcmV?d00001 diff --git a/public/icons/flag.svg b/public/icons/flag.svg new file mode 100644 index 0000000..835cee2 --- /dev/null +++ b/public/icons/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/fullscreen.svg b/public/icons/fullscreen.svg new file mode 100644 index 0000000..215ddb3 --- /dev/null +++ b/public/icons/fullscreen.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/left.svg b/public/icons/left.svg new file mode 100644 index 0000000..64d5558 --- /dev/null +++ b/public/icons/left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/pen.svg b/public/icons/pen.svg new file mode 100644 index 0000000..1fd2e62 --- /dev/null +++ b/public/icons/pen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/play.svg b/public/icons/play.svg new file mode 100644 index 0000000..12b655d --- /dev/null +++ b/public/icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/right.svg b/public/icons/right.svg new file mode 100644 index 0000000..7592686 --- /dev/null +++ b/public/icons/right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/sets.svg b/public/icons/sets.svg new file mode 100644 index 0000000..8e1b7d1 --- /dev/null +++ b/public/icons/sets.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/smallscreen.svg b/public/icons/smallscreen.svg new file mode 100644 index 0000000..3cba467 --- /dev/null +++ b/public/icons/smallscreen.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/statement.svg b/public/icons/statement.svg new file mode 100644 index 0000000..34c83ee --- /dev/null +++ b/public/icons/statement.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/stop.svg b/public/icons/stop.svg new file mode 100644 index 0000000..08a6f0d --- /dev/null +++ b/public/icons/stop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/stopAudio.svg b/public/icons/stopAudio.svg new file mode 100644 index 0000000..7e44afb --- /dev/null +++ b/public/icons/stopAudio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/terminal.svg b/public/icons/terminal.svg new file mode 100644 index 0000000..ccced24 --- /dev/null +++ b/public/icons/terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/trash.svg b/public/icons/trash.svg new file mode 100644 index 0000000..8685ded --- /dev/null +++ b/public/icons/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/tween.svg b/public/icons/tween.svg new file mode 100644 index 0000000..7fa8799 --- /dev/null +++ b/public/icons/tween.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/signup.html b/signup.html new file mode 100644 index 0000000..838641d --- /dev/null +++ b/signup.html @@ -0,0 +1,46 @@ + + + + + + + Login - NeoIDE + + + + + + + + + + + + + + + + + + +

+ NeoIDE logo + Login to existing account + +

Username

+ + +

Password

+ + + +
+ + + + + \ No newline at end of file diff --git a/src/blocks/control.js b/src/blocks/control.js new file mode 100644 index 0000000..7db7fa9 --- /dev/null +++ b/src/blocks/control.js @@ -0,0 +1,187 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["wait_one_frame"] = { + init: function () { + this.appendDummyInput().appendField("wait one frame"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("control_blocks"); + }, +}; + +Blockly.Blocks["wait_block"] = { + init: function () { + this.appendValueInput("AMOUNT").setCheck("Number").appendField("wait"); + this.appendDummyInput().appendField( + new Blockly.FieldDropdown([ + ["seconds", "1000"], + ["milliseconds", "1"], + ]), + "MENU" + ); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("control_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["wait_one_frame"] = function (block) { + return `await waitOneFrame();\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["wait_block"] = function ( + block, + generator +) { + const duration = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0; + const menu = block.getFieldValue("MENU") || 0; + return `await wait(${duration} * ${+menu});\n`; +}; + +Blockly.Blocks["controls_thread_create"] = { + init: function () { + this.appendDummyInput().appendField("create thread"); + this.appendStatementInput("code").setCheck("default"); + this.setTooltip("Create and run the code specified in a new thread"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("control_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_thread_create"] = function ( + block, + generator +) { + const code = generator.statementToCode(block, "code"); + return `Thread.getCurrentContext().spawn(async () => {\n${code}});`; +}; + +Blockly.Blocks["controls_thread_current"] = { + init: function () { + this.appendDummyInput().appendField("current thread"); + this.setOutput(true, "ThreadID"); + this.setStyle("control_blocks"); + this.setTooltip("Return the ID of the currently running thread"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_thread_current"] = () => [ + `Thread.getCurrentContext().id`, + BlocklyJS.Order.MEMBER, +]; + +Blockly.Blocks["controls_thread_set_var"] = { + init: function () { + this.appendValueInput("NAME") + .setCheck("String") + .appendField("set variable"); + this.appendValueInput("VALUE").appendField("to"); + this.appendValueInput("THREAD") + .setCheck("ThreadID") + .appendField("in thread"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setInputsInline(true); + this.setStyle("control_blocks"); + this.setTooltip("Set a variable inside the given thread"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_thread_set_var"] = function ( + block, + generator +) { + const threadId = + generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null"; + const name = + generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""'; + const value = + generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE) || "undefined"; + return `Thread.set(${threadId}, ${name}, ${value});\n`; +}; + +Blockly.Blocks["controls_thread_get_var"] = { + init: function () { + this.appendValueInput("NAME") + .setCheck("String") + .appendField("get variable"); + this.appendValueInput("THREAD") + .setCheck("ThreadID") + .appendField("from thread"); + this.setInputsInline(true); + this.setOutput(true, null); + this.setStyle("control_blocks"); + this.setTooltip("Get a variable from the given thread"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_thread_get_var"] = function ( + block, + generator +) { + const threadId = + generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null"; + const name = + generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""'; + const code = `Thread.get(${threadId}, ${name})`; + return [code, BlocklyJS.Order.FUNCTION_CALL]; +}; + +Blockly.Blocks["controls_thread_has_var"] = { + init: function () { + this.appendValueInput("NAME").setCheck("String").appendField("variable"); + this.appendValueInput("THREAD") + .setCheck("ThreadID") + .appendField("exists in thread"); + this.setInputsInline(true); + this.setOutput(true, null); + this.setStyle("control_blocks"); + this.setTooltip("Checks if a variable exists in the given thread"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_thread_has_var"] = function ( + block, + generator +) { + const threadId = + generator.valueToCode(block, "THREAD", BlocklyJS.Order.NONE) || "null"; + const name = + generator.valueToCode(block, "NAME", BlocklyJS.Order.NONE) || '""'; + const code = `Thread.has(${threadId}, ${name})`; + return [code, BlocklyJS.Order.FUNCTION_CALL]; +}; + +Blockly.Blocks["controls_run_instantly"] = { + init: function () { + this.appendDummyInput().appendField("run instantly"); + this.appendStatementInput("do"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("control_blocks"); + this.setTooltip("Run inside code without frame delay"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_run_instantly"] = function ( + block +) { + const branch = BlocklyJS.javascriptGenerator.statementToCode(block, "do"); + return `let _prevFast = fastExecution; +fastExecution = true; +${branch}fastExecution = _prevFast;\n`; +}; + +Blockly.Blocks["controls_stopscript"] = { + init: function () { + this.appendDummyInput().appendField("stop this script"); + this.setPreviousStatement(true, "default"); + this.setStyle("control_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["controls_stopscript"] = () => + 'throw new Error("shouldStop");\n'; diff --git a/src/blocks/event.js b/src/blocks/event.js new file mode 100644 index 0000000..55638a5 --- /dev/null +++ b/src/blocks/event.js @@ -0,0 +1,172 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["when_flag_clicked"] = { + init: function () { + this.appendDummyInput() + .appendField("when") + .appendField( + new Blockly.FieldImage("icons/flag.svg", 25, 25, { + alt: "Green flag", + flipRtl: "FALSE", + }) + ) + .appendField("clicked"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["when_flag_clicked"] = function ( + block, + generator +) { + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("flag", null, async () => {\n${branch}});\n`; +}; + +const normalKeys = [ + ..."abcdefghijklmnopqrstuvwxyz", + ..."abcdefghijklmnopqrstuvwxyz0123456789".toUpperCase(), +]; + +Blockly.Blocks["when_key_clicked"] = { + init: function () { + this.appendDummyInput() + .appendField("when") + .appendField( + new Blockly.FieldDropdown([ + ["any", "any"], + ["space", " "], + ["enter", "Enter"], + ["escape", "Escape"], + ["up arrow", "ArrowUp"], + ["down arrow", "ArrowDown"], + ["left arrow", "ArrowLeft"], + ["right arrow", "ArrowRight"], + ...normalKeys.map((i) => [i, i]), + ]), + "KEY" + ) + .appendField("key pressed"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["when_key_clicked"] = function ( + block, + generator +) { + const key = block.getFieldValue("KEY"); + const safeKey = generator.quote_(key); + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("key", ${safeKey}, async () => {\n${branch}});\n`; +}; + +Blockly.Blocks["when_stage_clicked"] = { + init: function () { + this.appendDummyInput() + .appendField("when stage clicked"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["when_stage_clicked"] = function ( + block, + generator +) { + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("stageClick", null, async () => {\n${branch}});\n`; +}; + +Blockly.Blocks["project_timer"] = { + init: function () { + this.appendDummyInput().appendField("project timer"); + this.setOutput(true, "Number"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["project_timer"] = function (block) { + return ["projectTime()", BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["when_timer_reaches"] = { + init: function () { + this.appendDummyInput() + .appendField("when timer reaches") + .appendField(new Blockly.FieldNumber(2, 0), "VALUE") + .appendField("seconds"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["when_timer_reaches"] = function ( + block, + generator +) { + const value = block.getFieldValue("VALUE"); + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("timer", ${value}, async () => {\n${branch}});\n`; +}; + +Blockly.Blocks["every_seconds"] = { + init: function () { + this.appendDummyInput() + .appendField("every") + .appendField(new Blockly.FieldNumber(2, 0.1), "SECONDS") + .appendField("seconds"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["every_seconds"] = function ( + block, + generator +) { + const seconds = block.getFieldValue("SECONDS"); + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("interval", ${seconds}, async () => {\n${branch}});\n`; +}; + +Blockly.Blocks["when_custom_event_triggered"] = { + init: function () { + this.appendDummyInput() + .appendField("when") + .appendField(new Blockly.FieldTextInput("event_name"), "EVENT") + .appendField("triggered"); + this.appendStatementInput("DO").setCheck("default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["when_custom_event_triggered"] = function ( + block, + generator +) { + const event = generator.quote_(block.getFieldValue("EVENT")); + const branch = generator.statementToCode(block, "DO"); + return `registerEvent("custom", ${event}, async () => {\n${branch}});\n`; +}; + +Blockly.Blocks["trigger_custom_event"] = { + init: function () { + this.appendDummyInput() + .appendField("trigger") + .appendField(new Blockly.FieldTextInput("event_name"), "EVENT"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("events_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["trigger_custom_event"] = function ( + block +) { + const event = BlocklyJS.javascriptGenerator.quote_(block.getFieldValue("EVENT")); + return `triggerCustomEvent(${event});\n`; +}; diff --git a/src/blocks/functions.js b/src/blocks/functions.js new file mode 100644 index 0000000..f3d5b66 --- /dev/null +++ b/src/blocks/functions.js @@ -0,0 +1,811 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +const ARG_BLOCK_TYPE = "FunctionsArgumentBlock"; + +class CustomChecker extends Blockly.ConnectionChecker { + canConnect(a, b, isDragging, opt_distance) { + if (!isDragging) { + return super.canConnect(a, b, isDragging, opt_distance); + } + + const existing = b.targetConnection && b.targetConnection.getSourceBlock(); + + if ( + existing && + existing.type === "functions_argument_block" && + existing.isShadow() + ) { + return false; + } + + return super.canConnect(a, b, isDragging, opt_distance); + } +} + +Blockly.registry.register( + Blockly.registry.Type.CONNECTION_CHECKER, + "CustomChecker", + CustomChecker, + true +); + +class DuplicateOnDrag { + constructor(block) { + this.block = block; + } + + isMovable() { + return true; + } + + startDrag(e) { + const ws = this.block.workspace; + + let typeToCreate = this.block.type; + if (this.block.argType_ === "statement") { + typeToCreate = "functions_statement_argument_block"; + } + + let data = this.block.toCopyData(); + if (data?.blockState) { + data.blockState.type = typeToCreate; + } else { + data.blockState = { type: typeToCreate }; + } + + if (this.block.mutationToDom) { + const mutation = this.block.mutationToDom(); + if (mutation) { + data.blockState.extraState = mutation.outerHTML; + } + } + + this.copy = Blockly.clipboard.paste(data, ws); + this.baseStrat = new Blockly.dragging.BlockDragStrategy(this.copy); + this.copy.setDragStrategy(this.baseStrat); + this.baseStrat.startDrag(e); + } + + drag(e) { + this.block.workspace + .getGesture(e) + .getCurrentDragger() + .setDraggable(this.copy); + this.baseStrat.drag(e); + } + + endDrag(e) { + this.baseStrat?.endDrag(e); + } + + revertDrag(e) { + this.copy?.dispose(); + } +} + +function typeToBlocklyCheck(type) { + return ( + { + string: "String", + number: "Number", + boolean: "Boolean", + array: "Array", + object: "Object", + }[type] || null + ); +} + +function findDuplicateArgNames(types, names) { + const used = {}; + const duplicates = []; + + for (let i = 0; i < types.length; i++) { + const key = types[i] + ":" + names[i]; + if (!names[i]) continue; + + if (used[key]) duplicates.push(i); + else used[key] = true; + } + return duplicates; +} + +function isValidIdentifier(name) { + return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name); +} + +Blockly.Blocks["functions_argument_block"] = { + init() { + if (!this.argType_) this.argType_ = "string"; + if (!this.argName_) this.argName_ = "arg"; + + this.setStyle("procedure_blocks"); + this.appendDummyInput().appendField( + new Blockly.FieldLabel(this.argName_), + "ARG_NAME" + ); + + this.setOutput(true, null); + this.setMovable(true); + this.setDeletable(true); + + setTimeout(() => { + if (this.setDragStrategy && this.isShadow()) { + this.setDragStrategy(new DuplicateOnDrag(this)); + } + }); + }, + + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("type", this.argType_ || "string"); + container.setAttribute("name", this.argName_ || "arg"); + return container; + }, + + domToMutation: function (xmlElement) { + const type = xmlElement.getAttribute("type") || "string"; + const name = xmlElement.getAttribute("name") || "arg"; + this.updateType_(type); + this.updateName_(name); + }, + + updateType_: function (type) { + this.argType_ = type; + if (type === "statement") { + this.setOutputShape(3); + this.setOutput(true, ARG_BLOCK_TYPE); + } else { + const outputType = typeToBlocklyCheck(type) || "String"; + this.setOutput(true, [outputType, ARG_BLOCK_TYPE]); + } + }, + + updateName_: function (name) { + this.argName_ = name; + if (this.getField("ARG_NAME")) { + this.setFieldValue(name, "ARG_NAME"); + } else { + this.appendDummyInput().appendField( + new Blockly.FieldLabel(name), + "ARG_NAME" + ); + } + }, +}; + +Blockly.Blocks["functions_statement_argument_block"] = { + init() { + if (!this.argName_) this.argName_ = "arg"; + + this.setStyle("procedure_blocks"); + this.appendDummyInput().appendField( + new Blockly.FieldLabel(this.argName_), + "ARG_NAME" + ); + + this.setNextStatement(true, "default"); + this.setPreviousStatement(true, "default"); + }, + + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("name", this.argName_ || "arg"); + return container; + }, + + domToMutation: function (xmlElement) { + const name = xmlElement.getAttribute("name") || "arg"; + this.updateName_(name); + }, + + updateName_: function (name) { + this.argName_ = name; + if (this.getField("ARG_NAME")) { + this.setFieldValue(name, "ARG_NAME"); + } else { + this.appendDummyInput().appendField( + new Blockly.FieldLabel(name), + "ARG_NAME" + ); + } + }, +}; + +Blockly.Blocks["functions_definition"] = { + init: function () { + this.setStyle("procedure_blocks"); + this.setTooltip("Function definition with a variable number of inputs."); + this.setInputsInline(true); + + this.functionId_ = Blockly.utils.idGenerator.genUid(); + this.itemCount_ = 0; + this.argTypes_ = []; + this.argNames_ = []; + this.blockShape_ = "statement"; + this.returnTypes_ = []; + + this.updateShape_(); + this.setMutator( + new Blockly.icons.MutatorIcon(["functions_args_generic"], this) + ); + }, + + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("functionid", this.functionId_); + container.setAttribute("items", String(this.itemCount_)); + container.setAttribute("shape", this.blockShape_ || "statement"); + + for (let i = 0; i < this.itemCount_; i++) { + const item = Blockly.utils.xml.createElement("item"); + item.setAttribute("type", this.argTypes_[i]); + item.setAttribute("name", this.argNames_[i]); + container.appendChild(item); + } + + return container; + }, + + domToMutation: function (xmlElement) { + const items = xmlElement.getAttribute("items"); + this.itemCount_ = items ? parseInt(items, 10) : 0; + this.argTypes_ = []; + this.argNames_ = []; + + const children = [...xmlElement.children].filter( + n => n.tagName.toLowerCase() === "item" + ); + for (let i = 0; i < children.length; i++) { + this.argTypes_[i] = children[i].getAttribute("type"); + this.argNames_[i] = children[i].getAttribute("name"); + } + + while (this.argTypes_.length < this.itemCount_) + this.argTypes_.push("label"); + while (this.argNames_.length < this.itemCount_) this.argNames_.push("text"); + + this.functionId_ = + xmlElement.getAttribute("functionid") || + Blockly.utils.idGenerator.genUid(); + this.blockShape_ = xmlElement.getAttribute("shape") || "statement"; + this.updateShape_(); + }, + + saveExtraState: function () { + return { + functionId: this.functionId_, + itemCount: this.itemCount_, + argTypes: this.argTypes_, + argNames: this.argNames_, + shape: this.blockShape_, + returnTypes: this.returnTypes_, + }; + }, + + loadExtraState: function (state) { + this.functionId_ = state.functionId || Blockly.utils.idGenerator.genUid(); + this.itemCount_ = state.itemCount || 0; + this.argTypes_ = state.argTypes || []; + this.argNames_ = state.argNames || []; + this.blockShape_ = state.shape || "statement"; + this.returnTypes_ = state.returnTypes || []; + this.updateShape_(); + }, + + createDefaultArgBlock_: function (type, name = "arg") { + Blockly.Events.disable(); + + let block; + try { + const ws = this.workspace; + block = ws.newBlock("functions_argument_block"); + block.setShadow(true); + block.setEditable(false); + block.updateType_(type); + block.updateName_(name); + + if (ws?.rendered) { + block.initSvg(); + block.render(); + } + } catch (_) {} + + Blockly.Events.enable(); + return block; + }, + + updateShape_: function () { + let savedBody = null; + + const bodyInput = this.getInput("BODY"); + if (bodyInput && bodyInput.connection?.targetConnection) { + savedBody = bodyInput.connection.targetConnection; + } + + if (bodyInput) this.removeInput("BODY"); + if (this.getInput("EMPTY")) this.removeInput("EMPTY"); + if (this.getInput("SHAPE")) this.removeInput("SHAPE"); + + [...this.inputList].forEach(input => { + const connection = input.connection?.targetConnection; + if (connection) connection.getSourceBlock()?.dispose(false); + this.removeInput(input.name); + }); + + let firstArgAdded = this.argTypes_[0] === "label"; + + for (let i = 0; i < this.itemCount_; i++) { + const type = this.argTypes_[i]; + const name = this.argNames_[i]; + + if (type === "label") { + this.appendDummyInput().appendField(new Blockly.FieldLabel(name)); + } else { + const input = this.appendValueInput(name).setCheck( + typeToBlocklyCheck(type) + ); + + if (!firstArgAdded) { + input.appendField("my block with"); + firstArgAdded = true; + } + + const reporter = this.createDefaultArgBlock_(type, name); + reporter.setFieldValue(name, "ARG_NAME"); + + try { + reporter.outputConnection.connect(input.connection); + } catch (e) {} + } + } + + if (this.itemCount_ === 0) { + this.appendDummyInput("EMPTY").appendField("my block"); + } + + const newBody = this.appendStatementInput("BODY").setCheck("default"); + if (savedBody) { + try { + newBody.connection.connect(savedBody); + } catch (e) {} + } + }, + + decompose: function (workspace) { + const containerBlock = workspace.newBlock("functions_args_container"); + if (workspace.rendered) containerBlock.initSvg(); + let connection = containerBlock.getInput("STACK").connection; + + for (let i = 0; i < this.itemCount_; i++) { + const type = this.argTypes_[i] || "label"; + const name = this.argNames_[i] || "text"; + const itemBlock = workspace.newBlock("functions_args_generic"); + itemBlock.setFieldValue(type, "ARG_TYPE"); + itemBlock.setFieldValue(name, "ARG_NAME"); + if (workspace.rendered) itemBlock.initSvg(); + itemBlock.valueConnection_ = null; + + connection.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + } + + containerBlock.setFieldValue(this.blockShape_, "SHAPEMENU"); + + return containerBlock; + }, + + compose: function (containerBlock) { + const newTypes = []; + const newNames = []; + + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + while (itemBlock) { + if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) { + const type = itemBlock.getFieldValue("ARG_TYPE"); + const name = itemBlock.getFieldValue("ARG_NAME"); + newTypes.push(type); + newNames.push(name); + } + itemBlock = itemBlock.getNextBlock(); + } + + const dups = findDuplicateArgNames(newTypes, newNames); + + const invalid = []; + for (let i = 0; i < newTypes.length; i++) { + const type = newTypes[i]; + const name = newNames[i]; + if (type !== "label") { + if (!isValidIdentifier(name)) { + invalid.push(i); + } + } + } + + itemBlock = containerBlock.getInputTargetBlock("STACK"); + let index = 0; + while (itemBlock) { + if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) { + if (dups.includes(index)) { + itemBlock.setWarningText( + "This argument name is already used for this type." + ); + } else if (invalid.includes(index)) { + itemBlock.setWarningText("This argument name is not a valid."); + } else { + itemBlock.setWarningText(null); + } + + index++; + } + itemBlock = itemBlock.getNextBlock(); + } + + const newBlockShape = + containerBlock.getFieldValue("SHAPEMENU") || "statement"; + + if (dups.length > 0 || invalid.length > 0) return; + + this.itemCount_ = newTypes.length; + this.argTypes_ = newTypes; + this.argNames_ = newNames; + this.blockShape_ = newBlockShape; + + this.updateShape_(); + }, + + saveConnections: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + let i = 0; + while (itemBlock) { + if (!(itemBlock.isInsertionMarker && itemBlock.isInsertionMarker())) { + const key = this.argTypes_[i] + "_" + this.argNames_[i]; + const input = this.getInput(key); + itemBlock.valueConnection_ = + input && input.connection && input.connection.targetConnection; + i++; + } + itemBlock = itemBlock.getNextBlock(); + } + }, + + updateReturnState_: function () { + const body = this.getInputTargetBlock("BODY"); + const types = new Set(); + + function walk(block) { + if (!block) return; + + if (block?.childBlocks_?.length > 0) block?.childBlocks_.forEach(walk); + + if (block.type === "functions_return") { + const val = block.getInputTargetBlock("VALUE"); + const checks = val?.outputConnection?.check; + if (checks !== undefined) { + (Array.isArray(checks) ? checks : [checks]).forEach(t => + types.add(t) + ); + } + } + + walk(block.getNextBlock()); + } + walk(body); + + if (types.size === 0) this.returnTypes_ = []; + else this.returnTypes_ = [...types]; + }, +}; + +Blockly.Blocks["functions_args_container"] = { + init: function () { + this.setStyle("procedure_blocks"); + this.appendDummyInput().appendField("arguments"); + this.appendStatementInput("STACK"); + this.appendDummyInput() + .appendField("shape") + .appendField( + new Blockly.FieldDropdown([ + [ + { + src: "icons/statement.svg", + width: 98 * 0.6, + height: 57 * 0.6, + alt: "A block with top and bottom connections", + }, + "statement", + ], + [ + { + src: "icons/terminal.svg", + width: 98 * 0.6, + height: 48 * 0.6, + alt: "A block with only a top connection", + }, + "terminal", + ], + ]), + "SHAPEMENU" + ); + this.contextMenu = false; + }, +}; + +Blockly.Blocks["functions_args_generic"] = { + init() { + this.setStyle("procedure_blocks"); + + this.appendDummyInput() + .appendField("argument") + .appendField( + new Blockly.FieldDropdown([ + ["label", "label"], + ["string", "string"], + ["number", "number"], + ["boolean", "boolean"], + ["array", "array"], + ["object", "object"], + ["statement", "statement"], + ]), + "ARG_TYPE" + ) + .appendField(new Blockly.FieldTextInput("arg"), "ARG_NAME"); + + this.setPreviousStatement(true); + this.setNextStatement(true); + this.contextMenu = false; + this.valueConnection_ = null; + }, +}; + +Blockly.Blocks["functions_call"] = { + init: function () { + this.setStyle("procedure_blocks"); + this.setInputsInline(true); + + this.functionId_ = null; + this.blockShape_ = null; + this.argTypes_ = []; + this.argNames_ = []; + this.previousArgTypes_ = []; + this.previousArgNames_ = []; + this.returnTypes_ = []; + + this.updateShape_(); + }, + + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("functionid", this.functionId_); + container.setAttribute("items", this.argTypes_.length); + container.setAttribute("shape", this.blockShape_ || "statement"); + container.setAttribute( + "returntypes", + JSON.stringify(this.returnTypes_ || []) + ); + + for (let i = 0; i < this.argTypes_.length; i++) { + const item = Blockly.utils.xml.createElement("item"); + item.setAttribute("type", this.argTypes_[i]); + item.setAttribute("name", this.argNames_[i]); + container.appendChild(item); + } + + return container; + }, + + domToMutation: function (xmlElement) { + this.functionId_ = xmlElement.getAttribute("functionid"); + this.blockShape_ = xmlElement.getAttribute("shape") || "statement"; + this.previousArgTypes_ = [...this.argTypes_]; + this.previousArgNames_ = [...this.argNames_]; + this.argTypes_ = []; + this.argNames_ = []; + + this.returnTypes_; + try { + this.returnTypes_ = JSON.parse( + xmlElement.getAttribute("returntypes") || "[]" + ); + } catch { + this.returnTypes_ = []; + } + + const items = parseInt(xmlElement.getAttribute("items") || "0", 10); + for (let i = 0; i < items; i++) { + const item = xmlElement.children[i]; + this.argTypes_[i] = item.getAttribute("type"); + this.argNames_[i] = item.getAttribute("name"); + } + + this.updateShape_(); + }, + + matchDefinition: function (defBlock) { + this.functionId_ = defBlock.functionId_; + this.previousArgTypes_ = [...this.argTypes_]; + this.previousArgNames_ = [...this.argNames_]; + this.argTypes_ = [...defBlock.argTypes_]; + this.argNames_ = [...defBlock.argNames_]; + this.blockShape_ = defBlock.blockShape_; + this.returnTypes_ = [...defBlock.returnTypes_]; + + this.updateShape_(); + if (defBlock.workspace.rendered) this.render(); + }, + + updateShape_: function () { + const oldConnections = {}; + + [...this.inputList].forEach(input => { + if (input.connection && input.connection.targetBlock()) { + oldConnections[input.name] = input.connection.targetConnection; + } + this.removeInput(input.name); + }); + + const shape = this.blockShape_ || "statement"; + const nextConn = this.nextConnection; + const prevConn = this.previousConnection; + const outputConn = this.outputConnection; + const returnTypes = this.returnTypes_ || []; + + if (returnTypes?.length > 0) { + if (prevConn && prevConn.isConnected()) { + const blockAbove = prevConn.targetBlock(); + blockAbove.unplug(true); + } + if (nextConn && nextConn.isConnected()) { + const blockBelow = nextConn.targetBlock(); + blockBelow.unplug(true); + } + + this.setPreviousStatement(false); + this.setNextStatement(false); + this.setOutput(true, returnTypes); + } else { + if (outputConn && outputConn.isConnected()) { + outputConn.disconnect(); + } + + if (shape === "statement") { + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setOutput(false); + } else if (shape === "terminal") { + if (nextConn && nextConn.isConnected()) { + nextConn.targetBlock().unplug(true); + } + + this.setNextStatement(false); + this.setPreviousStatement(true, "default"); + this.setOutput(false); + } + } + + if (!this.argTypes_ || this.argTypes_.length === 0) { + this.appendDummyInput("EMPTY").appendField("my block"); + return; + } + + let firstLabel = this.argTypes_[0] === "label"; + + for (let i = 0; i < this.argTypes_.length; i++) { + const type = this.argTypes_[i]; + const name = this.argNames_[i]; + + if (!type || !name) continue; + + if (type === "label") { + this.appendDummyInput().appendField(name); + continue; + } + + if (!firstLabel) { + this.appendDummyInput().appendField("my block with"); + firstLabel = true; + } + + let input; + const key = type + "_" + name; + if (type === "statement") { + input = this.appendStatementInput(key).setCheck("default"); + } else { + input = this.appendValueInput(key).setCheck(typeToBlocklyCheck(type)); + } + + if (oldConnections[key]) { + try { + input.connection.connect( + oldConnections[key].targetBlock()?.outputConnection || + oldConnections[key] + ); + } catch (e) {} + } + } + }, +}; + +Blockly.Blocks["functions_return"] = { + init() { + this.setStyle("procedure_blocks"); + this.appendValueInput("VALUE").appendField("return"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(false); + this.setInputsInline(true); + }, + + update_() { + const def = this.getSurroundParent(); + if (!def || def.type !== "functions_definition") return; + + def.updateReturnState_(); + def.workspace.updateAllFunctionCalls(); + }, + + onchange(e) { + if (e.isUiEvent || e.isBlank) return; + + this.update_(); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["functions_argument_block"] = block => [ + block.argType_ + "_" + block.argName_, + BlocklyJS.Order.NONE, +]; + +BlocklyJS.javascriptGenerator.forBlock["functions_statement_argument_block"] = + block => "statement_" + block.argName_ + "();\n"; + +BlocklyJS.javascriptGenerator.forBlock["functions_definition"] = function ( + block, + generator +) { + const params = block.argTypes_ + .map((type, i) => { + if (type === "label") return null; + return type + "_" + block.argNames_[i]; + }) + .filter(Boolean); + + const body = BlocklyJS.javascriptGenerator.statementToCode(block, "BODY"); + return `MyFunctions[${generator.quote_( + block.functionId_ + )}] = async (${params.join(", ")}) => {\n${body}};\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["functions_call"] = function ( + block, + generator +) { + const args = []; + + for (let i = 0; i < block.argTypes_.length; i++) { + const type = block.argTypes_[i]; + const name = block.argNames_[i]; + const key = `${type}_${name}`; + + if (type === "label") continue; + + if (type === "statement") + args.push(`async () => {${generator.statementToCode(block, key)}}`); + else + args.push( + generator.valueToCode(block, key, BlocklyJS.Order.NONE) || "null" + ); + } + + return `await MyFunctions[${generator.quote_(block.functionId_)}](${args.join( + ", " + )});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["functions_return"] = function ( + block, + generator +) { + const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE); + return `return ${value || "null"};\n`; +}; diff --git a/src/blocks/json.js b/src/blocks/json.js new file mode 100644 index 0000000..2fc014c --- /dev/null +++ b/src/blocks/json.js @@ -0,0 +1,512 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; +const xmlUtils = Blockly.utils.xml; + +Blockly.Blocks["json_get"] = { + init: function () { + this.appendValueInput("KEY").setCheck("String").appendField("value of"); + this.appendValueInput("OBJECT").setCheck("Object").appendField("in object"); + this.setOutput(true); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("Returns the value of a key from a JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_get"] = function (block) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.MEMBER + ) || "{}"; + const key = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.NONE + ) || '""'; + return [`${obj}[${key}]`, BlocklyJS.Order.MEMBER]; +}; + +Blockly.Blocks["json_set"] = { + init: function () { + this.appendValueInput("OBJECT").setCheck("Object").appendField("in object"); + this.appendValueInput("KEY").setCheck("String").appendField("set"); + this.appendValueInput("VALUE").appendField("to"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("Sets a value to a key in a JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_set"] = function (block) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.MEMBER + ) || "{}"; + const key = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.NONE + ) || '""'; + const value = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "VALUE", + BlocklyJS.Order.ASSIGNMENT + ) || "null"; + return `${obj}[${key}] = ${value};\n`; +}; + +Blockly.Blocks["json_delete"] = { + init: function () { + this.appendValueInput("OBJECT").setCheck("Object").appendField("in object"); + this.appendValueInput("KEY").setCheck("String").appendField("remove"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("Deletes a key from a JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_delete"] = function (block) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.MEMBER + ) || "{}"; + const key = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.NONE + ) || '""'; + return `delete ${obj}[${key}];\n`; +}; + +Blockly.Blocks["json_create_item"] = { + init: function () { + this.appendDummyInput().appendField("key and value"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("json_category"); + this.setTooltip("Add a key with a value to the object."); + this.contextMenu = false; + }, +}; + +/* --- start deprecated --- */ +Blockly.Blocks["json_key_value"] = { + init: function () { + this.appendValueInput("KEY").setCheck("String").appendField("key"); + this.appendValueInput("VALUE").setCheck(null).appendField("value"); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("A single key with a value."); + this.setOutput(true, "ObjectItem"); + this.setInputsInline(true); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_key_value"] = function (block) { + const keyCode = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.NONE + ) || '""'; + const valCode = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "VALUE", + BlocklyJS.Order.ATOMIC + ) || "null"; + const code = `${keyCode}: ${valCode}`; + return [code, BlocklyJS.Order.ATOMIC]; +}; + +Blockly.Blocks["json_create"] = { + init: function () { + this.setOutput(true, "Object"); + this.setStyle("json_category"); + this.itemCount_ = 0; + this.updateShape_(); + this.setMutator(new Blockly.icons.MutatorIcon(["json_create_item"], this)); + this.setTooltip("Create a JSON object with any number of keys."); + this.setInputsInline(false); + }, + mutationToDom: function () { + const container = xmlUtils.createElement("mutation"); + container.setAttribute("items", String(this.itemCount_)); + return container; + }, + domToMutation: function (xmlElement) { + const items = xmlElement.getAttribute("items"); + if (!items) throw new TypeError("element did not have items"); + this.itemCount_ = parseInt(items, 10); + this.updateShape_(); + }, + saveExtraState: function () { + return { + itemCount: this.itemCount_, + }; + }, + loadExtraState: function (state) { + this.itemCount_ = state["itemCount"]; + this.updateShape_(); + }, + saveConnections: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + let i = 0; + while (itemBlock) { + const input = this.getInput("ITEM" + i); + itemBlock.valueConnection_ = input && input.connection.targetConnection; + itemBlock = itemBlock.nextConnection?.targetBlock(); + i++; + } + }, + compose: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + const connections = []; + while (itemBlock) { + if (itemBlock.isInsertionMarker()) { + itemBlock = itemBlock.getNextBlock(); + continue; + } + connections.push(itemBlock.valueConnection_); + itemBlock = itemBlock.getNextBlock(); + } + for (let i = 0; i < this.itemCount_; i++) { + const connection = this.getInput("ADD" + i)?.connection?.targetConnection; + if (connection && !connections.includes(connection)) { + connection.disconnect(); + } + } + this.itemCount_ = connections.length; + this.updateShape_(); + for (let i = 0; i < this.itemCount_; i++) { + connections[i]?.reconnect(this, "ADD" + i); + } + }, + decompose: function (workspace) { + const containerBlock = workspace.newBlock("json_create_container"); + containerBlock.initSvg(); + let connection = containerBlock.getInput("STACK").connection; + for (let i = 0; i < this.itemCount_; i++) { + const itemBlock = workspace.newBlock("json_create_item"); + itemBlock.initSvg(); + if (!itemBlock.previousConnection) { + throw new Error("itemBlock has no previousConnection"); + } + connection.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + } + return containerBlock; + }, + saveConnections: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + let i = 0; + while (itemBlock) { + if (itemBlock.isInsertionMarker()) { + itemBlock = itemBlock.getNextBlock(); + continue; + } + const input = this.getInput("ADD" + i); + itemBlock.valueConnection_ = input?.connection?.targetConnection; + itemBlock = itemBlock?.getNextBlock(); + i++; + } + }, + updateShape_: function () { + if (this.itemCount_ && this.getInput("EMPTY")) { + this.removeInput("EMPTY"); + } else if (!this.itemCount_ && !this.getInput("EMPTY")) { + this.appendDummyInput("EMPTY").appendField("create empty object"); + } + + for (let i = 0; i < this.itemCount_; i++) { + if (!this.getInput("ADD" + i)) { + const input = this.appendValueInput("ADD" + i).setAlign( + Blockly.inputs.Align.RIGHT + ); + input.setCheck("ObjectItem"); + if (i === 0) input.appendField("create object with"); + } + } + + for (let i = this.itemCount_; this.getInput("ADD" + i); i++) { + this.removeInput("ADD" + i); + } + }, +}; + +Blockly.Blocks["json_create_container"] = { + init: function () { + this.appendDummyInput().appendField("object"); + this.appendStatementInput("STACK"); + this.setStyle("json_category"); + this.setTooltip( + "Add, remove, or reorder sections to configure this object block." + ); + this.contextMenu = false; + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_create"] = function (block) { + const entries = []; + for (let i = 0; i < block.itemCount_; i++) { + const pairCode = BlocklyJS.javascriptGenerator.valueToCode( + block, + "ADD" + i, + BlocklyJS.Order.NONE + ); + if (pairCode) { + entries.push(pairCode || ""); + } + } + const code = `{ ${entries.join(", ")} }`; + return [code, BlocklyJS.Order.ATOMIC]; +}; +/* --- end deprecated --- */ + +Blockly.Blocks["json_create_statement"] = { + init: function () { + this.appendDummyInput().appendField("create object"); + this.appendStatementInput("STACK").setCheck("json_key_value"); + this.setOutput(true, "Object"); + this.setStyle("json_category"); + this.setTooltip("Create a JSON object using stacked key/value pairs."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_create_statement"] = function ( + block +) { + const statements = BlocklyJS.javascriptGenerator.statementToCode( + block, + "STACK" + ); + return [`{\n${statements}}`, BlocklyJS.Order.ATOMIC]; +}; + +Blockly.Blocks["json_key_value_statement"] = { + init: function () { + this.appendValueInput("KEY").setCheck("String").appendField("key"); + this.appendValueInput("VALUE").appendField("value"); + this.setPreviousStatement(true, "json_key_value"); + this.setNextStatement(true, "json_key_value"); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("A single key/value pair for a JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_key_value_statement"] = function ( + block, + generator +) { + const key = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.ATOMIC + ) || ""; + const value = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "VALUE", + BlocklyJS.Order.ATOMIC + ) || "null"; + + if (key) return `${key}: ${value},\n`; + else return ""; +}; + +Blockly.Blocks["json_has_key"] = { + init: function () { + this.appendValueInput("OBJECT") + .setCheck("Object") + .appendField("does object"); + this.appendValueInput("KEY").setCheck("String").appendField("have"); + this.setOutput(true, "Boolean"); + this.setInputsInline(true); + this.setStyle("json_category"); + this.setTooltip("Returns true if the key exists in the object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_has_key"] = function (block) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.MEMBER + ) || "{}"; + const key = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "KEY", + BlocklyJS.Order.NONE + ) || '""'; + return [`(${key} in ${obj})`, BlocklyJS.Order.RELATIONAL]; +}; + +Blockly.Blocks["json_property_list"] = { + init: function () { + this.appendValueInput("OBJECT") + .setCheck("Object") + .appendField( + new Blockly.FieldDropdown([ + ["keys", "KEYS"], + ["values", "VALUES"], + ["entries", "ENTRIES"], + ]), + "MODE" + ) + .appendField("of"); + this.setOutput(true, "Array"); + this.setStyle("json_category"); + this.setTooltip("Gets keys, values, or entries of the JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_property_list"] = function ( + block +) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.FUNCTION_CALL + ) || "{}"; + const mode = block.getFieldValue("MODE"); + + let code; + switch (mode) { + case "KEYS": + code = `Object.keys(${obj})`; + break; + case "VALUES": + code = `Object.values(${obj})`; + break; + case "ENTRIES": + code = `Object.entries(${obj})`; + break; + default: + code = "[]"; + } + + return [code, BlocklyJS.Order.FUNCTION_CALL]; +}; + +Blockly.Blocks["json_parse"] = { + init: function () { + const dropdown = new Blockly.FieldDropdown([ + ["object from text", "PARSE"], + ["text from object", "STRINGIFY"], + ]); + dropdown.setValidator((newMode) => { + this.updateType_(newMode); + }); + + this.setStyle("json_category"); + this.appendValueInput("INPUT") + .setCheck("String") + .appendField("make") + .appendField(dropdown, "MODE"); + this.setInputsInline(true); + this.setOutput(true, "Object"); + this.setTooltip(() => { + const mode = this.getFieldValue("MODE"); + if (mode === "PARSE") { + return "Convert a stringified object into an object."; + } else if (mode === "STRINGIFY") { + return "Convert an object into text representing an object."; + } + throw Error("Unknown mode: " + mode); + }); + }, + updateType_: function (newMode) { + const mode = this.getFieldValue("MODE"); + if (mode !== newMode) { + const inputConnection = this.getInput("INPUT")?.connection; + inputConnection?.setShadowDom(null); + const inputBlock = inputConnection?.targetBlock(); + + if (inputBlock) { + inputConnection.disconnect(); + if (inputBlock.isShadow()) { + inputBlock.dispose(false); + } else { + this.bumpNeighbours(); + } + } + } + if (newMode === "PARSE") { + this.outputConnection.setCheck("Object"); + this.getInput("INPUT").setCheck("String"); + } else { + this.outputConnection.setCheck("String"); + this.getInput("INPUT").setCheck("Object"); + } + }, + mutationToDom: function () { + const container = xmlUtils.createElement("mutation"); + container.setAttribute("mode", this.getFieldValue("MODE")); + return container; + }, + domToMutation: function (xmlElement) { + this.updateType_(xmlElement.getAttribute("mode")); + }, + saveExtraState: function () { + return { mode: this.getFieldValue("MODE") }; + }, + loadExtraState: function (state) { + this.updateType_(state["mode"]); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_parse"] = function (block) { + const input = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "INPUT", + BlocklyJS.Order.ATOMIC + ) || "null"; + const mode = block.getFieldValue("MODE"); + + if (mode === "PARSE") { + return [`JSON.parse(${input})`, BlocklyJS.Order.NONE]; + } else if (mode === "STRINGIFY") { + return [`JSON.stringify(${input})`, BlocklyJS.Order.NONE]; + } +}; + +Blockly.Blocks["json_clone"] = { + init: function () { + this.appendValueInput("OBJECT") + .setCheck("Object") + .appendField("clone object"); + this.setOutput(true, "Object"); + this.setStyle("json_category"); + this.setTooltip("Creates a duplicate of a JSON object."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["json_clone"] = function (block) { + const obj = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "OBJECT", + BlocklyJS.Order.ATOMIC + ) || "{}"; + return [`JSON.parse(JSON.stringify(${obj}))`, BlocklyJS.Order.FUNCTION_CALL]; +}; diff --git a/src/blocks/list.js b/src/blocks/list.js new file mode 100644 index 0000000..dd1b98a --- /dev/null +++ b/src/blocks/list.js @@ -0,0 +1,454 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["lists_filter"] = { + init: function () { + this.appendValueInput("list").setCheck("Array").appendField("filter list"); + this.appendValueInput("method").setCheck("Boolean").appendField("by"); + this.setInputsInline(true); + this.setOutput(true, "Array"); + this.setStyle("list_blocks"); + this.setTooltip( + "Remove all items in a list which doesn't match the boolean" + ); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_filter"] = function ( + block, + generator +) { + var val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC); + var val_method = generator.valueToCode( + block, + "method", + BlocklyJS.Order.ATOMIC + ); + var code = `${val_list}.filter(findOrFilterItem => ${val_method})`; + return [code, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["lists_find"] = { + init: function () { + this.appendValueInput("list").setCheck("Array").appendField("in list"); + this.appendValueInput("method") + .setCheck("Boolean") + .appendField("find first that matches"); + this.setOutput(true, null); + this.setInputsInline(true); + this.setStyle("list_blocks"); + this.setTooltip( + "Returns the first item in a list that matches the boolean" + ); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_find"] = function ( + block, + generator +) { + var val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC); + var val_method = generator.valueToCode( + block, + "method", + BlocklyJS.Order.ATOMIC + ); + var code = `${val_list}.find(findOrFilterItem => ${val_method})`; + return [code, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["lists_filter_item"] = { + init: function () { + this.appendDummyInput("name").appendField("item in loop"); + this.setInputsInline(true); + this.setOutput(true, null); + this.setStyle("list_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_filter_item"] = () => [ + "findOrFilterItem", + BlocklyJS.Order.NONE, +]; + +Blockly.Blocks["lists_merge"] = { + init: function () { + this.appendValueInput("list").setCheck("Array").appendField("merge list"); + this.appendValueInput("list2").setCheck("Array").appendField("with"); + this.setInputsInline(true); + this.setOutput(true, "Array"); + this.setStyle("list_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_merge"] = function ( + block, + generator +) { + const val_list = generator.valueToCode(block, "list", BlocklyJS.Order.ATOMIC); + const val_list2 = generator.valueToCode( + block, + "list2", + BlocklyJS.Order.ATOMIC + ); + const code = `${val_list}.concat(${val_list2})`; + return [code, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["lists_foreach"] = { + init: function () { + this.appendValueInput("LIST") + .setCheck("Array") + .appendField("for each item in list"); + this.appendStatementInput("DO").appendField("do"); + this.setInputsInline(false); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setStyle("list_blocks"); + this.setTooltip( + "Loops through every item in a list and runs the code inside for each one" + ); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_foreach"] = function ( + block, + generator +) { + const list = + generator.valueToCode(block, "LIST", BlocklyJS.Order.NONE) || "[]"; + const branch = generator.statementToCode(block, "DO"); + const code = `${list}.forEach(findOrFilterItem => {\n${branch}});\n`; + return code; +}; + +Blockly.Blocks["lists_getIndex_modified"] = { + init: function () { + const MODE_OPTIONS = [ + [Blockly.Msg.LISTS_GET_INDEX_GET, "GET"], + [Blockly.Msg.LISTS_GET_INDEX_REMOVE, "REMOVE"], + ]; + this.WHERE_OPTIONS = [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, "FROM_START"], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, "FROM_END"], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, "FIRST"], + [Blockly.Msg.LISTS_GET_INDEX_LAST, "LAST"], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, "RANDOM"], + ]; + + this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL); + this.setStyle("list_blocks"); + + this.appendValueInput("VALUE") + .setCheck("Array") + .appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST); + + const modeField = new Blockly.FieldDropdown(MODE_OPTIONS, newMode => { + this.updateMode_(newMode); + return newMode; + }); + this.appendDummyInput() + .appendField(modeField, "MODE") + .appendField("", "SPACE"); + + const whereField = new Blockly.FieldDropdown( + this.WHERE_OPTIONS, + newWhere => { + const cur = this.getFieldValue("WHERE"); + const newNeedsAt = newWhere === "FROM_START" || newWhere === "FROM_END"; + const curNeedsAt = cur === "FROM_START" || cur === "FROM_END"; + if (newNeedsAt !== curNeedsAt) this.updateAt_(newNeedsAt); + } + ); + this.appendDummyInput().appendField(whereField, "WHERE"); + + this.appendDummyInput("AT"); + + this.setInputsInline(true); + + this.updateAt_(true); + this.updateMode_(this.getFieldValue("MODE") || "GET"); + + this.setTooltip(() => { + const mode = this.getFieldValue("MODE"); + const where = this.getFieldValue("WHERE"); + if (mode === "GET") { + switch (where) { + case "FROM_START": + case "FROM_END": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM; + case "FIRST": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST; + case "LAST": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST; + case "RANDOM": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM; + } + } else { + switch (where) { + case "FROM_START": + case "FROM_END": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM; + case "FIRST": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST; + case "LAST": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST; + case "RANDOM": + return Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM; + } + } + return ""; + }); + }, + + mutationToDom: function () { + const container = document.createElement("mutation"); + const input = this.getInput("AT"); + const isValueInput = !!input && !!input.connection; + container.setAttribute("at", String(isValueInput)); + container.setAttribute("mode", this.getFieldValue("MODE") || "GET"); + return container; + }, + + domToMutation: function (xmlElement) { + const at = xmlElement.getAttribute("at") !== "false"; + const mode = xmlElement.getAttribute("mode") || "GET"; + this.updateAt_(at); + this.updateMode_(mode); + }, + + updateAt_: function (useValueInput) { + if (this.getInput("AT")) this.removeInput("AT", true); + if (this.getInput("ORDINAL")) this.removeInput("ORDINAL", true); + + if (useValueInput) { + this.appendValueInput("AT").setCheck("Number"); + if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { + this.appendDummyInput("ORDINAL").appendField( + Blockly.Msg.ORDINAL_NUMBER_SUFFIX + ); + } + } else { + this.appendDummyInput("AT"); + } + }, + + updateMode_: function (mode) { + if (mode === "GET") { + if (!this.outputConnection) this.setOutput(true); + this.outputConnection.setCheck(null); + } else { + if (!this.outputConnection) this.setOutput(true); + this.outputConnection.setCheck("Array"); + } + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_getIndex_modified"] = function ( + block, + generator +) { + const workspaceOneBased = + block.workspace.options && block.workspace.options.oneBasedIndex; + const mode = block.getFieldValue("MODE"); + const where = block.getFieldValue("WHERE"); + + const listCode = + generator.valueToCode(block, "VALUE", BlocklyJS.Order.NONE) || "[]"; + const atCodeRaw = + generator.valueToCode(block, "AT", BlocklyJS.Order.NONE) || "0"; + + let i; + + switch (where) { + case "FIRST": + i = "0"; + break; + case "LAST": + i = "-1"; + break; + case "RANDOM": + i = `Math.floor(Math.random() * _.length)`; + break; + case "FROM_START": + i = workspaceOneBased ? `${atCodeRaw} - 1` : atCodeRaw; + break; + case "FROM_END": + i = workspaceOneBased ? `-${atCodeRaw}` : `-(${atCodeRaw} + 1)`; + break; + default: + i = "0"; + } + + return [ + mode === "REMOVE" + ? `${listCode}.toSpliced(${i}, 1)` + : `${listCode}.at(${i})`, + BlocklyJS.Order.FUNCTION_CALL, + ]; +}; + +Blockly.Blocks["lists_setIndex_modified"] = { + init: function () { + const MODE_OPTIONS = [ + [Blockly.Msg.LISTS_SET_INDEX_SET, "SET"], + [Blockly.Msg.LISTS_SET_INDEX_INSERT, "INSERT"], + ]; + this.WHERE_OPTIONS = [ + [Blockly.Msg.LISTS_GET_INDEX_FROM_START, "FROM_START"], + [Blockly.Msg.LISTS_GET_INDEX_FROM_END, "FROM_END"], + [Blockly.Msg.LISTS_GET_INDEX_FIRST, "FIRST"], + [Blockly.Msg.LISTS_GET_INDEX_LAST, "LAST"], + [Blockly.Msg.LISTS_GET_INDEX_RANDOM, "RANDOM"], + ]; + + this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL); + this.setStyle("list_blocks"); + + this.appendValueInput("LIST") + .setCheck("Array") + .appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST); + + const modeField = new Blockly.FieldDropdown(MODE_OPTIONS); + this.appendDummyInput() + .appendField(modeField, "MODE") + .appendField("", "SPACE"); + + const whereField = new Blockly.FieldDropdown( + this.WHERE_OPTIONS, + newWhere => { + this.updateAt_(newWhere === "FROM_START" || newWhere === "FROM_END"); + return newWhere; + } + ); + this.appendDummyInput().appendField(whereField, "WHERE"); + + this.appendDummyInput("AT"); + + this.appendValueInput("TO").appendField( + Blockly.Msg.LISTS_SET_INDEX_INPUT_TO + ); + + this.setInputsInline(true); + + this.setOutput(true, "Array"); + + this.updateAt_(true); + + this.setTooltip(() => { + const mode = this.getFieldValue("MODE"); + const where = this.getFieldValue("WHERE"); + if (mode === "SET") { + switch (where) { + case "FROM_START": + case "FROM_END": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM; + case "FIRST": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST; + case "LAST": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST; + case "RANDOM": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM; + } + } else { + switch (where) { + case "FROM_START": + case "FROM_END": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM; + case "FIRST": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST; + case "LAST": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST; + case "RANDOM": + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM; + } + } + return Blockly.Msg.LISTS_SET_INDEX_TOOLTIP; + }); + }, + + mutationToDom: function () { + const container = document.createElement("mutation"); + const input = this.getInput("AT"); + const isValueInput = !!input && !!input.connection; + container.setAttribute("at", String(isValueInput)); + return container; + }, + + domToMutation: function (xmlElement) { + const at = xmlElement.getAttribute("at") !== "false"; + this.updateAt_(at); + }, + + updateAt_: function (useValueInput) { + if (this.getInput("AT")) this.removeInput("AT", true); + if (this.getInput("ORDINAL")) this.removeInput("ORDINAL", true); + + if (useValueInput) { + this.appendValueInput("AT").setCheck("Number"); + if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) { + this.appendDummyInput("ORDINAL").appendField( + Blockly.Msg.ORDINAL_NUMBER_SUFFIX + ); + } + } else { + this.appendDummyInput("AT"); + } + try { + this.moveInputBefore("AT", "TO"); + if (this.getInput("ORDINAL")) this.moveInputBefore("ORDINAL", "TO"); + } catch (e) {} + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["lists_setIndex_modified"] = function ( + block, + generator +) { + const oneBased = block.workspace.options?.oneBasedIndex; + const mode = block.getFieldValue("MODE"); + const where = block.getFieldValue("WHERE"); + + const listCode = + generator.valueToCode(block, "LIST", BlocklyJS.Order.NONE) || "[]"; + const valueCode = + generator.valueToCode(block, "TO", BlocklyJS.Order.NONE) || "undefined"; + const atCode = + generator.valueToCode(block, "AT", BlocklyJS.Order.NONE) || "0"; + + let indexExpr; + switch (where) { + case "FIRST": + indexExpr = "0"; + break; + case "LAST": + indexExpr = "-1"; + break; + case "RANDOM": + indexExpr = "Math.floor(Math.random() * _.length)"; + break; + case "FROM_START": + indexExpr = oneBased ? `(${atCode} - 1)` : atCode; + break; + case "FROM_END": + indexExpr = oneBased ? `-${atCode}` : `-(${atCode} + 1)`; + break; + default: + indexExpr = "0"; + } + + const code = `((a) => { + const _ = [...a]; + let i = ${indexExpr}; + if (i < 0) i += _.length; + if (i < 0) i = 0; + if (i > _.length) i = _.length; + return ${ + mode === "INSERT" + ? `_.toSpliced(i, 0, ${valueCode})` + : `i < _.length ? _.toSpliced(i, 1, ${valueCode}) : _` + }; +})(${listCode})`; + + return [code, BlocklyJS.Order.FUNCTION_CALL]; +}; diff --git a/src/blocks/looks.js b/src/blocks/looks.js new file mode 100644 index 0000000..58183be --- /dev/null +++ b/src/blocks/looks.js @@ -0,0 +1,218 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +function getAvailableCostumes() { + if (window.projectCostumes && window.projectCostumes.length > 0) { + return window.projectCostumes.map(costume => [costume, costume]); + } + return [["default", "default"]]; +} + +Blockly.Blocks["say_message"] = { + init: function () { + this.appendValueInput("MESSAGE").appendField("say"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setInputsInline(true); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["say_message_duration"] = { + init: function () { + this.appendValueInput("MESSAGE").appendField("say"); + this.appendValueInput("DURATION").setCheck("Number").appendField("for"); + this.appendDummyInput().appendField("seconds"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setInputsInline(true); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["switch_costume"] = { + init: function () { + this.appendDummyInput() + .appendField("switch costume to") + .appendField(new Blockly.FieldDropdown( + function() { + return getAvailableCostumes(); + } + ), "COSTUME_NAME"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["set_size"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("set size to"); + this.appendDummyInput().appendField("%"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["change_size"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("change size by"); + this.appendDummyInput().appendField("%"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["get_costume_size"] = { + init: function () { + this.appendDummyInput() + .appendField("costume") + .appendField( + new Blockly.FieldDropdown([ + ["width", "width"], + ["height", "height"], + ]), + "MENU" + ); + this.setOutput(true, "Number"); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["get_sprite_scale"] = { + init: function () { + this.appendDummyInput().appendField("size"); + this.setOutput(true, "Number"); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["looks_hide_sprite"] = { + init: function () { + this.appendDummyInput().appendField("hide sprite"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["looks_show_sprite"] = { + init: function () { + this.appendDummyInput().appendField("show sprite"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("looks_blocks"); + }, +}; + +Blockly.Blocks["looks_isVisible"] = { + init: function () { + this.appendDummyInput().appendField("is visible"); + this.setOutput(true, "Boolean"); + this.setStyle("looks_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["say_message"] = function ( + block, + generator +) { + const message = + generator.valueToCode(block, "MESSAGE", BlocklyJS.Order.NONE) || ""; + + return `sayMessage(${message});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["say_message_duration"] = function ( + block, + generator +) { + const message = + generator.valueToCode(block, "MESSAGE", BlocklyJS.Order.NONE) || ""; + const duration = + generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) || 2; + + return `sayMessage(${message}, ${duration});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["switch_costume"] = function ( + block, + generator +) { + var costume = generator.valueToCode(block, "COSTUME", BlocklyJS.Order.ATOMIC); + return `switchCostume(${costume});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["switch_costume"] = function (block, generator) { + var costume = "'" + block.getFieldValue("COSTUME_NAME") + "'"; + return `switchCostume(${costume});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["set_size"] = function ( + block, + generator +) { + const amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 100; + return `setSize(${amount}, false);\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["change_size"] = function ( + block, + generator +) { + const amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 100; + return `setSize(${amount}, true);\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["get_costume_size"] = function (block) { + const menu = block.getFieldValue("MENU"); + return [`getCostumeSize("${menu}")`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["get_sprite_scale"] = function () { + return [`getSpriteScale()`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["looks_hide_sprite"] = function () { + return "toggleVisibility(false);\n"; +}; + +BlocklyJS.javascriptGenerator.forBlock["looks_show_sprite"] = function () { + return "toggleVisibility(true);\n"; +}; + +BlocklyJS.javascriptGenerator.forBlock["looks_isVisible"] = () => [ + "sprite.visible", + BlocklyJS.Order.NONE, +]; + +Blockly.Blocks["switch_backdrop"] = { + init: function () { + this.appendDummyInput() + .appendField("switch backdrop to") + .appendField(new Blockly.FieldDropdown( + function() { + if (window.projectBackdrops && window.projectBackdrops.length > 0) { + return window.projectBackdrops.map((backdrop) => [backdrop.name, backdrop.name]); + } + return [["no backdrops", ""]]; + } + ), "BACKDROP_NAME"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle("looks_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["switch_backdrop"] = function (block) { + const name = block.getFieldValue("BACKDROP_NAME"); + return `setBackdropByName('${name}');\n`; +}; diff --git a/src/blocks/motion.js b/src/blocks/motion.js new file mode 100644 index 0000000..7ae9948 --- /dev/null +++ b/src/blocks/motion.js @@ -0,0 +1,223 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["move_steps"] = { + init: function () { + this.appendValueInput("STEPS").setCheck("Number").appendField("step"); + this.appendDummyInput().appendField("times"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["change_position"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("change") + .appendField( + new Blockly.FieldDropdown([ + ["x", "x"], + ["y", "y"], + ]), + "MENU" + ) + .appendField("by"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["set_position"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("set") + .appendField( + new Blockly.FieldDropdown([ + ["x", "x"], + ["y", "y"], + ]), + "MENU" + ) + .appendField("to"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["goto_position"] = { + init: function () { + this.appendValueInput("x").setCheck("Number").appendField("go to x"); + this.appendValueInput("y").setCheck("Number").appendField("y"); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["get_position"] = { + init: function () { + this.appendDummyInput().appendField( + new Blockly.FieldDropdown([ + ["x", "x"], + ["y", "y"], + ]), + "MENU" + ); + this.setOutput(true, "Number"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["angle_turn"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("turn") + .appendField( + new Blockly.FieldDropdown([ + [ + { + src: "icons/right.svg", + height: 30, + width: 30, + alt: "A circular arrow rotating to the right", + }, + "right", + ], + [ + { + src: "icons/left.svg", + height: 30, + width: 30, + alt: "A circular arrow rotating to the left", + }, + "left", + ], + ]), + "DIRECTION" + ); + this.appendDummyInput().appendField("degrees"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["angle_set"] = { + init: function () { + this.appendValueInput("AMOUNT") + .setCheck("Number") + .appendField("set angle to"); + this.appendDummyInput().appendField("degrees"); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["point_towards"] = { + init: function () { + this.appendValueInput("x") + .setCheck("Number") + .appendField("point towards x"); + this.appendValueInput("y").setCheck("Number").appendField("y"); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("motion_blocks"); + }, +}; + +Blockly.Blocks["get_angle"] = { + init: function () { + this.appendDummyInput().appendField("angle"); + this.setOutput(true, "Number"); + this.setStyle("motion_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["move_steps"] = function ( + block, + generator +) { + const steps = + generator.valueToCode(block, "STEPS", BlocklyJS.Order.ATOMIC) || 0; + return `moveSteps(${steps});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["change_position"] = function ( + block, + generator +) { + const amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0; + const menu = block.getFieldValue("MENU"); + if (menu === "y") return `sprite["${menu}"] -= ${amount};\n`; + else return `sprite["${menu}"] += ${amount};\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["set_position"] = function ( + block, + generator +) { + const amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0; + const menu = block.getFieldValue("MENU"); + if (menu === "y") return `sprite["${menu}"] = -${amount};\n`; + else return `sprite["${menu}"] = ${amount};\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["goto_position"] = function ( + block, + generator +) { + const x = generator.valueToCode(block, "x", BlocklyJS.Order.ATOMIC) || 0; + const y = generator.valueToCode(block, "y", BlocklyJS.Order.ATOMIC) || 0; + return `sprite.x = ${x};\nsprite.y = -${y};\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["point_towards"] = function ( + block, + generator +) { + const x = generator.valueToCode(block, "x", BlocklyJS.Order.ATOMIC) || 0; + const y = generator.valueToCode(block, "y", BlocklyJS.Order.ATOMIC) || 0; + return `pointsTowards(${x}, ${y});\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["get_position"] = function (block) { + const menu = block.getFieldValue("MENU"); + return [`sprite["${menu}"]`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["angle_turn"] = function ( + block, + generator +) { + const direction = block.getFieldValue("DIRECTION"); + let amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0; + if (direction === "left") amount = `-(${amount})`; + return `setAngle(${amount}, true);\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["angle_set"] = function ( + block, + generator +) { + const amount = + generator.valueToCode(block, "AMOUNT", BlocklyJS.Order.ATOMIC) || 0; + return `setAngle(${amount}, false);\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["get_angle"] = () => [ + "sprite.angle", + BlocklyJS.Order.NONE, +]; diff --git a/src/blocks/pen.js b/src/blocks/pen.js new file mode 100644 index 0000000..c185cb1 --- /dev/null +++ b/src/blocks/pen.js @@ -0,0 +1,115 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["pen_down"] = { + init: function () { + this.appendDummyInput().appendField("pen down"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Put the pen down to draw"); + }, +}; +BlocklyJS.javascriptGenerator.forBlock["pen_down"] = function () { + return "setPenStatus(true);\n"; +}; + +Blockly.Blocks["pen_up"] = { + init: function () { + this.appendDummyInput().appendField("pen up"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Lift the pen up"); + }, +}; +BlocklyJS.javascriptGenerator.forBlock["pen_up"] = function () { + return "setPenStatus(false);\n"; +}; + +Blockly.Blocks["set_pen_color"] = { + init: function () { + this.appendDummyInput().appendField("set pen color"); + this.appendValueInput("R").setCheck("Number").appendField("R"); + this.appendValueInput("G").setCheck("Number").appendField("G"); + this.appendValueInput("B").setCheck("Number").appendField("B"); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Set the pen color to a RGB value"); + }, +}; +BlocklyJS.javascriptGenerator.forBlock["set_pen_color"] = function ( + block, + generator +) { + const r = generator.valueToCode(block, "R", BlocklyJS.Order.ATOMIC) || 0; + const g = generator.valueToCode(block, "G", BlocklyJS.Order.ATOMIC) || 0; + const b = generator.valueToCode(block, "B", BlocklyJS.Order.ATOMIC) || 0; + return `setPenColor(${r}, ${g}, ${b});\n`; +}; + +Blockly.Blocks["set_pen_color_combined"] = { + init: function () { + this.appendDummyInput("MODE") + .appendField("set pen color to") + .appendField( + new Blockly.FieldDropdown([ + ["RGB", "RGB"], + ["HEX", "HEX"], + ]), + "MODE" + ); + this.appendValueInput("VALUE").setCheck(["String", "Number"]); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Set the pen color to a RGB or HEX value."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["set_pen_color_combined"] = function ( + block, + generator +) { + const mode = block.getFieldValue("MODE"); + const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC); + if (mode === "HEX") return `setPenColorHex(${value});\n`; + else return `setPenColor(${value});\n`; +}; + +Blockly.Blocks["set_pen_size"] = { + init: function () { + this.appendValueInput("SIZE") + .setCheck("Number") + .appendField("set pen size to"); + this.appendDummyInput().appendField("px"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Set the pen thickness to a specific value in pixels"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["set_pen_size"] = function ( + block, + generator +) { + const size = + generator.valueToCode(block, "SIZE", BlocklyJS.Order.ATOMIC) || 1; + return `setPenSize("${size}");\n`; +}; + +Blockly.Blocks["clear_pen"] = { + init: function () { + this.appendDummyInput().appendField("clear pen"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#0fbd8c"); + this.setTooltip("Clear all pen drawings"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["clear_pen"] = () => "clearPen();\n"; diff --git a/src/blocks/set.js b/src/blocks/set.js new file mode 100644 index 0000000..dbcde43 --- /dev/null +++ b/src/blocks/set.js @@ -0,0 +1,353 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +Blockly.Blocks["sets_create_with"] = { + init: function () { + this.setStyle("set_blocks"); + this.setHelpUrl(""); + this.itemCount_ = 0; + this.updateShape_(); + this.setOutput(true, "Set"); + this.setMutator( + new Blockly.icons.MutatorIcon(["sets_create_with_item"], this) + ); + this.setTooltip("Create a set with any number of elements."); + }, + + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("items", String(this.itemCount_)); + return container; + }, + + domToMutation: function (xmlElement) { + const items = xmlElement.getAttribute("items"); + this.itemCount_ = items ? parseInt(items, 10) : 0; + this.updateShape_(); + }, + + saveExtraState: function () { + return { itemCount: this.itemCount_ }; + }, + + loadExtraState: function (state) { + if (state && typeof state.itemCount === "number") { + this.itemCount_ = state.itemCount; + } else { + this.itemCount_ = 0; + } + this.updateShape_(); + }, + + decompose: function (workspace) { + const containerBlock = workspace.newBlock("sets_create_with_container"); + containerBlock.initSvg(); + let connection = containerBlock.getInput("STACK").connection; + for (let i = 0; i < this.itemCount_; i++) { + const itemBlock = workspace.newBlock("sets_create_with_item"); + itemBlock.initSvg(); + connection.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + } + return containerBlock; + }, + + compose: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + const connections = []; + while (itemBlock) { + if (itemBlock.isInsertionMarker && itemBlock.isInsertionMarker()) { + itemBlock = itemBlock.getNextBlock(); + continue; + } + connections.push(itemBlock.valueConnection_ || null); + itemBlock = itemBlock.getNextBlock(); + } + + for (let i = 0; i < this.itemCount_; i++) { + const input = this.getInput("ADD" + i); + const targetConnection = + input && input.connection && input.connection.targetConnection; + if (targetConnection && !connections.includes(targetConnection)) { + targetConnection.disconnect(); + } + } + + this.itemCount_ = connections.length; + this.updateShape_(); + + for (let i = 0; i < this.itemCount_; i++) { + if (connections[i]) { + connections[i].reconnect(this, "ADD" + i); + } + } + }, + + saveConnections: function (containerBlock) { + let itemBlock = containerBlock.getInputTargetBlock("STACK"); + let i = 0; + while (itemBlock) { + if (itemBlock.isInsertionMarker && itemBlock.isInsertionMarker()) { + itemBlock = itemBlock.getNextBlock(); + continue; + } + const input = this.getInput("ADD" + i); + itemBlock.valueConnection_ = + input && input.connection && input.connection.targetConnection; + itemBlock = itemBlock.getNextBlock(); + i++; + } + }, + + updateShape_: function () { + if (this.itemCount_ === 0) { + if (!this.getInput("EMPTY")) { + this.appendDummyInput("EMPTY").appendField("create empty set"); + } + } else { + if (this.getInput("EMPTY")) { + this.removeInput("EMPTY"); + } + } + + for (let i = 0; i < this.itemCount_; i++) { + if (!this.getInput("ADD" + i)) { + const input = this.appendValueInput("ADD" + i).setAlign( + Blockly.inputs.Align.RIGHT + ); + if (i === 0) { + input.appendField("create set with"); + } + } + } + + let i = this.itemCount_; + while (this.getInput("ADD" + i)) { + this.removeInput("ADD" + i); + i++; + } + }, +}; + +Blockly.Blocks["sets_create_with_container"] = { + init: function () { + this.setStyle("set_blocks"); + this.appendDummyInput().appendField("set"); + this.appendStatementInput("STACK"); + this.contextMenu = false; + }, +}; + +Blockly.Blocks["sets_create_with_item"] = { + init: function () { + this.setStyle("set_blocks"); + this.appendDummyInput().appendField("element"); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.contextMenu = false; + /** @type {Blockly.Connection?} */ + this.valueConnection_ = null; + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_create_with"] = function ( + block, + generator +) { + const elements = []; + for (let i = 0; i < block.itemCount_; i++) { + const code = + generator.valueToCode(block, "ADD" + i, BlocklyJS.Order.NONE) || "null"; + elements.push(code); + } + let code; + if (elements.length === 0) { + code = "new Set()"; + } else { + code = "new Set([" + elements.join(", ") + "])"; + } + return [code, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["sets_convert"] = { + init: function () { + const dropdown = new Blockly.FieldDropdown([ + ["set from list", "SET"], + ["list from set", "LIST"], + ]); + dropdown.setValidator((newMode) => { + this.updateType_(newMode); + }); + + this.setStyle("set_blocks"); + this.appendValueInput("INPUT") + .setCheck("String") + .appendField("make") + .appendField(dropdown, "MODE"); + this.setInputsInline(true); + this.setOutput(true, "Set"); + this.setTooltip(() => { + const mode = this.getFieldValue("MODE"); + if (mode === "SET") { + return "Convert a list into a set (removes duplicates)."; + } else if (mode === "LIST") { + return "Convert a set into a list."; + } + throw Error("Unknown mode: " + mode); + }); + }, + updateType_: function (newMode) { + const mode = this.getFieldValue("MODE"); + if (mode !== newMode) { + const inputConnection = this.getInput("INPUT")?.connection; + inputConnection?.setShadowDom(null); + const inputBlock = inputConnection?.targetBlock(); + + if (inputBlock) { + inputConnection.disconnect(); + if (inputBlock.isShadow()) { + inputBlock.dispose(false); + } else { + this.bumpNeighbours(); + } + } + } + if (newMode === "SET") { + this.outputConnection.setCheck("Set"); + this.getInput("INPUT").setCheck("Array"); + } else { + this.outputConnection.setCheck("Array"); + this.getInput("INPUT").setCheck("Set"); + } + }, + mutationToDom: function () { + const container = Blockly.utils.xml.createElement("mutation"); + container.setAttribute("mode", this.getFieldValue("MODE")); + return container; + }, + domToMutation: function (xmlElement) { + this.updateType_(xmlElement.getAttribute("mode")); + }, + saveExtraState: function () { + return { mode: this.getFieldValue("MODE") }; + }, + loadExtraState: function (state) { + this.updateType_(state["mode"]); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_convert"] = function (block) { + const input = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "INPUT", + BlocklyJS.Order.ATOMIC + ) || "null"; + const mode = block.getFieldValue("MODE"); + + if (mode === "SET") { + return [`new Set(${input})`, BlocklyJS.Order.NONE]; + } else if (mode === "LIST") { + return [`[...${input}]`, BlocklyJS.Order.NONE]; + } +}; + +Blockly.Blocks["sets_add"] = { + init: function () { + this.appendValueInput("SET").setCheck("Set").appendField("in set"); + this.appendValueInput("VALUE").setCheck(null).appendField("add"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setStyle("set_blocks"); + this.setTooltip("Adds a value to the set."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_add"] = function ( + block, + generator +) { + const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC); + const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC); + return `${set}.add(${value});\n`; +}; + +Blockly.Blocks["sets_delete"] = { + init: function () { + this.appendValueInput("SET").setCheck("Set").appendField("in set"); + this.appendValueInput("VALUE").setCheck(null).appendField("delete"); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setInputsInline(true); + this.setStyle("set_blocks"); + this.setTooltip("Deletes a value from the set."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_delete"] = function ( + block, + generator +) { + const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC); + const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC); + return `${set}.delete(${value});\n`; +}; + +Blockly.Blocks["sets_has"] = { + init: function () { + this.appendValueInput("SET").setCheck("Set").appendField("does set"); + this.appendValueInput("VALUE").setCheck(null).appendField("have"); + this.setOutput(true, "Boolean"); + this.setInputsInline(true); + this.setStyle("set_blocks"); + this.setTooltip("Returns true if the set contains the value."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_has"] = function ( + block, + generator +) { + const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC); + const value = generator.valueToCode(block, "VALUE", BlocklyJS.Order.ATOMIC); + return [`${set}.has(${value})`, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["sets_size"] = { + init: function () { + this.appendValueInput("SET").setCheck("Set").appendField("size of set"); + this.setOutput(true, "Number"); + this.setInputsInline(true); + this.setStyle("set_blocks"); + this.setTooltip("Returns how many items are in the set."); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_size"] = function ( + block, + generator +) { + const set = generator.valueToCode(block, "SET", BlocklyJS.Order.ATOMIC); + return [`${set}.size`, BlocklyJS.Order.NONE]; +}; + +Blockly.Blocks["sets_merge"] = { + init: function () { + this.appendValueInput("SET1").setCheck("Set").appendField("merge set"); + this.appendValueInput("SET2").setCheck("Set").appendField("with"); + this.setOutput(true, "Set"); + this.setInputsInline(true); + this.setStyle("set_blocks"); + this.setTooltip("Creates a new set combining all values from two sets"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["sets_merge"] = function ( + block, + generator +) { + const set1 = generator.valueToCode(block, "SET1", BlocklyJS.Order.ATOMIC); + const set2 = generator.valueToCode(block, "SET2", BlocklyJS.Order.ATOMIC); + return [`new Set([...${set1}, ...${set2}])`, BlocklyJS.Order.NONE]; +}; diff --git a/src/blocks/sound.js b/src/blocks/sound.js new file mode 100644 index 0000000..bd449d4 --- /dev/null +++ b/src/blocks/sound.js @@ -0,0 +1,137 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +// Function to get available sounds - you'll need to implement this in your main editor +// This should return an array of sound names from your project +function getAvailableSounds() { + // This needs to be connected to your sound list + // For now, returning a default option + if (window.projectSounds && window.projectSounds.length > 0) { + return window.projectSounds.map(sound => [sound, sound]); + } + return [["no sounds", ""]]; +} + +Blockly.Blocks["play_sound"] = { + init: function () { + this.appendDummyInput() + .appendField("play sound") + .appendField(new Blockly.FieldDropdown( + function() { + return getAvailableSounds(); + } + ), "SOUND_NAME"); + this.appendDummyInput().appendField( + new Blockly.FieldDropdown([ + ["until finished", "true"], + ["without waiting", "false"], + ]), + "wait" + ); + this.setColour("#ff66ba"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["play_sound"] = function (block, generator) { + var name = "'" + block.getFieldValue("SOUND_NAME") + "'"; + var wait = block.getFieldValue("wait"); + return `await playSound(${name}, ${wait});\n`; +}; + +Blockly.Blocks["stop_sound"] = { + init: function () { + this.appendDummyInput() + .appendField("stop sound") + .appendField(new Blockly.FieldDropdown( + function() { + return getAvailableSounds(); + } + ), "SOUND_NAME"); + this.setColour("#ff66ba"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["stop_sound"] = function (block, generator) { + var name = "'" + block.getFieldValue("SOUND_NAME") + "'"; + return `stopSound(${name});\n`; +}; + +Blockly.Blocks["stop_all_sounds"] = { + init: function () { + this.appendDummyInput() + .appendField("stop") + .appendField( + new Blockly.FieldDropdown([ + ["all", "false"], + ["my", "true"], + ]), + "who" + ) + .appendField("sounds"); + this.setColour("#ff66ba"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["stop_all_sounds"] = function (block) { + var who = block.getFieldValue("who"); + var code = `stopAllSounds(${who});\n`; + return code; +}; + +Blockly.Blocks["set_sound_property"] = { + init: function () { + this.appendValueInput("value") + .setCheck("Number") + .appendField("set") + .appendField( + new Blockly.FieldDropdown([ + ["volume", "volume"], + ["speed", "speed"], + ]), + "property" + ) + .appendField("to"); + this.appendDummyInput().appendField("%"); + this.setColour("#ff66ba"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["set_sound_property"] = function ( + block, + generator +) { + var value = generator.valueToCode( + block, + "value", + BlocklyJS.Order.ATOMIC + ); + var property = block.getFieldValue("property"); + return `setSoundProperty("${property}", ${value});\n`; +}; + +Blockly.Blocks["get_sound_property"] = { + init: function () { + this.appendDummyInput().appendField( + new Blockly.FieldDropdown([ + ["volume", "volume"], + ["speed", "speed"], + ]), + "property" + ); + this.setColour("#ff66ba"); + this.setOutput(true, "Number"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["get_sound_property"] = function (block) { + var property = block.getFieldValue("property"); + return [`soundProperties["${property}"]`, BlocklyJS.Order.NONE]; +}; diff --git a/src/blocks/system.js b/src/blocks/system.js new file mode 100644 index 0000000..675b142 --- /dev/null +++ b/src/blocks/system.js @@ -0,0 +1,137 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +const normalKeys = [ + ..."abcdefghijklmnopqrstuvwxyz", + ..."abcdefghijklmnopqrstuvwxyz0123456789".toUpperCase(), +]; + +Blockly.Blocks["key_pressed"] = { + init: function () { + this.appendDummyInput() + .appendField("is") + .appendField( + new Blockly.FieldDropdown([ + ["any", "any"], + ["space", " "], + ["enter", "Enter"], + ["escape", "Escape"], + ["up arrow", "ArrowUp"], + ["down arrow", "ArrowDown"], + ["left arrow", "ArrowLeft"], + ["right arrow", "ArrowRight"], + ...normalKeys.map((i) => [i, i]), + ]), + "KEY" + ) + .appendField("key down"); + this.setOutput(true, "Boolean"); + this.setColour("#5CB1D6"); + }, +}; + +Blockly.Blocks["get_mouse_position"] = { + init: function () { + this.appendDummyInput() + .appendField("mouse") + .appendField( + new Blockly.FieldDropdown([ + ["x", "x"], + ["y", "y"], + ]), + "MENU" + ); + this.setOutput(true, "Number"); + this.setColour("#5CB1D6"); + }, +}; + +Blockly.Blocks["mouse_button_pressed"] = { + init: function () { + this.appendDummyInput() + .appendField("is") + .appendField( + new Blockly.FieldDropdown([ + ["left", "0"], + ["middle", "1"], + ["right", "2"], + ["back", "3"], + ["forward", "4"], + ["any", "any"], + ]), + "BUTTON" + ) + .appendField("mouse button down"); + this.setOutput(true, "Boolean"); + this.setColour("#5CB1D6"); + }, +}; + +Blockly.Blocks["all_keys_pressed"] = { + init: function () { + this.appendDummyInput().appendField("keys currently down"); + this.setOutput(true, "Array"); + this.setColour("#5CB1D6"); + }, +}; + +Blockly.Blocks["mouse_over"] = { + init: function () { + this.appendDummyInput().appendField("is cursor over me"); + this.setOutput(true, "Boolean"); + this.setColour("#5CB1D6"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["key_pressed"] = function (block, generator) { + const key = block.getFieldValue("KEY"); + const safeKey = generator.quote_(key); + return [`isKeyPressed(${safeKey})`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["get_mouse_position"] = function (block) { + const menu = block.getFieldValue("MENU"); + return [`getMousePosition("${menu}")`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["mouse_button_pressed"] = function ( + block, + generator +) { + const button = block.getFieldValue("BUTTON"); + const safeButton = generator.quote_(button); + return [`isMouseButtonPressed(${safeButton})`, BlocklyJS.Order.NONE]; +}; + +BlocklyJS.javascriptGenerator.forBlock["all_keys_pressed"] = () => [ + "Object.keys(keysPressed).filter(k => keysPressed[k])", + BlocklyJS.Order.NONE, +]; + +BlocklyJS.javascriptGenerator.forBlock["mouse_over"] = () => [ + "isMouseTouchingSprite()", + BlocklyJS.Order.NONE, +]; + +Blockly.Blocks["window_size"] = { + init: function () { + this.appendDummyInput() + .appendField("window") + .appendField( + new Blockly.FieldDropdown([ + ["width", "width"], + ["height", "height"], + ]), + "MENU" + ); + this.setOutput(true, "Number"); + this.setColour("#5CB1D6"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["window_size"] = function (block) { + return [ + `window.inner${block.getFieldValue("MENU") === "width" ? "Width" : "Height"}`, + BlocklyJS.Order.NONE, + ]; +}; \ No newline at end of file diff --git a/src/blocks/tween.js b/src/blocks/tween.js new file mode 100644 index 0000000..5ac98ba --- /dev/null +++ b/src/blocks/tween.js @@ -0,0 +1,291 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; + +const TweenEasing = { + InLinear: (t) => t, + OutLinear: (t) => t, + InOutLinear: (t) => t, + InSine: (t) => 1 - Math.cos((t * Math.PI) / 2), + OutSine: (t) => Math.sin((t * Math.PI) / 2), + InOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2, + InQuad: (t) => t * t, + OutQuad: (t) => 1 - (1 - t) * (1 - t), + InOutQuad: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2), + InCubic: (t) => t * t * t, + OutCubic: (t) => 1 - Math.pow(1 - t, 3), + InOutCubic: (t) => + t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2, + InQuart: (t) => t * t * t * t, + OutQuart: (t) => 1 - Math.pow(1 - t, 4), + InOutQuart: (t) => + t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2, + InQuint: (t) => t * t * t * t * t, + OutQuint: (t) => 1 - Math.pow(1 - t, 5), + InOutQuint: (t) => + t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2, + InExpo: (t) => (t === 0 ? 0 : Math.pow(2, 10 * t - 10)), + OutExpo: (t) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)), + InOutExpo: (t) => { + if (t === 0) return 0; + if (t === 1) return 1; + return t < 0.5 + ? Math.pow(2, 20 * t - 10) / 2 + : (2 - Math.pow(2, -20 * t + 10)) / 2; + }, + InCirc: (t) => 1 - Math.sqrt(1 - Math.pow(t, 2)), + OutCirc: (t) => Math.sqrt(1 - Math.pow(t - 1, 2)), + InOutCirc: (t) => + t < 0.5 + ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2, + InBack: (t) => { + const c1 = 1.70158, + c3 = c1 + 1; + return c3 * t * t * t - c1 * t * t; + }, + OutBack: (t) => { + const c1 = 1.70158, + c3 = c1 + 1; + return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2); + }, + InOutBack: (t) => { + const c1 = 1.70158, + c2 = c1 * 1.525; + return t < 0.5 + ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 + : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (2 * t - 2) + c2) + 2) / 2; + }, + InElastic: (t) => { + const c4 = (2 * Math.PI) / 3; + if (t === 0) return 0; + if (t === 1) return 1; + return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4); + }, + OutElastic: (t) => { + const c4 = (2 * Math.PI) / 3; + if (t === 0) return 0; + if (t === 1) return 1; + return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1; + }, + InOutElastic: (t) => { + const c5 = (2 * Math.PI) / 4.5; + if (t === 0) return 0; + if (t === 1) return 1; + return t < 0.5 + ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + : (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1; + }, + InBounce: (t) => 1 - TweenEasing.OutBounce(1 - t), + OutBounce: (t) => { + const n1 = 7.5625, + d1 = 2.75; + if (t < 1 / d1) { + return n1 * t * t; + } else if (t < 2 / d1) { + return n1 * (t -= 1.5 / d1) * t + 0.75; + } else if (t < 2.5 / d1) { + return n1 * (t -= 2.25 / d1) * t + 0.9375; + } else { + return n1 * (t -= 2.625 / d1) * t + 0.984375; + } + }, + InOutBounce: (t) => + t < 0.5 + ? (1 - TweenEasing.OutBounce(1 - 2 * t)) / 2 + : (1 + TweenEasing.OutBounce(2 * t - 1)) / 2, +}; +Object.defineProperty(window, "TweenEasing", { + value: Object.freeze(TweenEasing), + configurable: false, + writable: false, + enumerable: true +}); + +Blockly.Blocks["tween_block"] = { + init: function () { + this.appendValueInput("FROM").setCheck("Number").appendField("tween from"); + this.appendValueInput("TO").setCheck("Number").appendField("to"); + this.appendDummyInput().appendField("in"); + this.appendValueInput("DURATION").setCheck("Number"); + this.appendDummyInput().appendField("seconds using"); + this.appendDummyInput() + .appendField( + new Blockly.FieldDropdown([ + ["linear", "Linear"], + ["sine", "Sine"], + ["quadratic", "Quad"], + ["cubic", "Cubic"], + ["quartic", "Quart"], + ["quintic", "Quint"], + ["expo", "Expo"], + ["circ", "Circ"], + ["back", "Back"], + ["elastic", "Elastic"], + ["bounce", "Bounce"], + ]), + "EASING_TYPE" + ) + .appendField( + new Blockly.FieldDropdown([ + ["in", "In"], + ["out", "Out"], + ["in-out", "InOut"], + ]), + "EASING_MODE" + ); + + this.appendStatementInput("DO").setCheck("default"); + this.appendDummyInput() + .setAlign(1) + .appendField( + new Blockly.FieldDropdown([ + ["wait", "WAIT"], + ["don't wait", "DONT_WAIT"], + ]), + "WAIT_MODE" + ) + .appendField("until finished"); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#32a2c0"); + this.setTooltip( + "Tween a value from one number to another over time using easing" + ); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["tween_block"] = function (block, generator) { + const easingType = block.getFieldValue("EASING_TYPE"); + const easingMode = block.getFieldValue("EASING_MODE"); + const from = + generator.valueToCode(block, "FROM", BlocklyJS.Order.ATOMIC) || + "0"; + const to = + generator.valueToCode(block, "TO", BlocklyJS.Order.ATOMIC) || "0"; + const duration = + generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) || + "1"; + const waitMode = block.getFieldValue("WAIT_MODE"); + + let branch = BlocklyJS.javascriptGenerator.statementToCode(block, "DO"); + branch = BlocklyJS.javascriptGenerator.addLoopTrap(branch, block); + + const code = `await startTween({ + from: ${from}, + to: ${to}, + duration: ${duration}, + easing: "${easingMode + easingType}", + wait: ${waitMode === "WAIT"}, + onUpdate: async (tweenValue) => { + ${branch} } +});\n`; + + return code; +}; + +Blockly.Blocks["tween_block_value"] = { + init: function () { + this.appendDummyInput("name").appendField("current tween value"); + this.setInputsInline(true); + this.setColour("#32a2c0"); + this.setOutput(true, "Number"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["tween_block_value"] = () => [ + "tweenValue", + BlocklyJS.Order.NONE, +]; + +Blockly.Blocks["tween_sprite_property"] = { + init: function () { + this.appendValueInput("TO") + .setCheck("Number") + .appendField("tween") + .appendField( + new Blockly.FieldDropdown([ + ["x position", "x"], + ["y position", "y"], + ["angle", "angle"], + ["size", "size"], + ]), + "PROPERTY" + ) + .appendField("to"); + this.appendValueInput("DURATION").setCheck("Number").appendField("in"); + this.appendDummyInput() + .appendField("seconds using") + .appendField( + new Blockly.FieldDropdown([ + ["linear", "Linear"], + ["sine", "Sine"], + ["quadratic", "Quad"], + ["cubic", "Cubic"], + ["quartic", "Quart"], + ["quintic", "Quint"], + ["expo", "Expo"], + ["circ", "Circ"], + ["back", "Back"], + ["elastic", "Elastic"], + ["bounce", "Bounce"], + ]), + "EASING_TYPE" + ) + .appendField( + new Blockly.FieldDropdown([ + ["in", "In"], + ["out", "Out"], + ["in-out", "InOut"], + ]), + "EASING_MODE" + ); + this.setInputsInline(true); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setColour("#32a2c0"); + this.setTooltip( + "Tween a sprite property to a target value over time using easing" + ); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["tween_sprite_property"] = function ( + block, + generator +) { + const prop = block.getFieldValue("PROPERTY"); + const to = + generator.valueToCode(block, "TO", BlocklyJS.Order.ATOMIC) || "0"; + const duration = + generator.valueToCode(block, "DURATION", BlocklyJS.Order.ATOMIC) || + "1"; + const easingType = block.getFieldValue("EASING_TYPE"); + const easingMode = block.getFieldValue("EASING_MODE"); + + let fromGetter, setter; + if (prop === "size") { + fromGetter = "getSpriteScale()"; + setter = `setSize(tweenValue, false)`; + } else if (prop === "angle") { + fromGetter = `sprite.angle`; + setter = `setAngle(tweenValue, false)`; + } else { + fromGetter = `sprite["${prop}"]`; + setter = `sprite["${prop}"] = tweenValue`; + } + + setter = generator.addLoopTrap(setter, block); + + const code = `await startTween({ + from: ${fromGetter}, + to: ${to}, + duration: ${duration}, + easing: "${easingMode + easingType}", + onUpdate: async (tweenValue) => { + ${setter}; + } +});\n`; + + return code; +}; diff --git a/src/blocks/variable.js b/src/blocks/variable.js new file mode 100644 index 0000000..4d027a8 --- /dev/null +++ b/src/blocks/variable.js @@ -0,0 +1,85 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; +import { projectVariables } from "../scripts/editor"; + +function getVariables() { + if (Object.keys(projectVariables).length === 0) + return [["unknown", "unknown"]]; + else return Object.keys(projectVariables).map((name) => [name, name]); +} + +Blockly.Blocks["get_global_var"] = { + init: function () { + this.appendDummyInput().appendField( + new Blockly.FieldDropdown(() => getVariables()), + "VAR" + ); + this.setOutput(true); + this.setTooltip("Get a global variable"); + this.setStyle("variable_blocks"); + this.customContextMenu = function (options) { + const varName = this.getFieldValue("VAR"); + options.push({ + text: `Delete "${varName}" variable`, + enabled: true, + callback: () => { + delete projectVariables[varName]; + this.workspace.refreshToolboxSelection(); + }, + }); + }; + }, +}; + +Blockly.Blocks["set_global_var"] = { + init: function () { + this.appendValueInput("VALUE") + .setCheck(null) + .appendField("set") + .appendField(new Blockly.FieldDropdown(() => getVariables()), "VAR") + .appendField("to"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("variable_blocks"); + }, +}; + +Blockly.Blocks["change_global_var"] = { + init: function () { + this.appendValueInput("VALUE") + .setCheck("Number") + .appendField("change") + .appendField(new Blockly.FieldDropdown(() => getVariables()), "VAR") + .appendField("by"); + this.setPreviousStatement(true, "default"); + this.setNextStatement(true, "default"); + this.setStyle("variable_blocks"); + }, +}; + +BlocklyJS.javascriptGenerator.forBlock["get_global_var"] = function (block) { + const name = block.getFieldValue("VAR"); + return [`projectVariables["${name}"]`, BlocklyJS.Order.ATOMIC]; +}; + +BlocklyJS.javascriptGenerator.forBlock["set_global_var"] = function (block) { + const name = block.getFieldValue("VAR"); + const value = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "VALUE", + BlocklyJS.Order.ASSIGNMENT + ) || "0"; + return `projectVariables["${name}"] = ${value};\n`; +}; + +BlocklyJS.javascriptGenerator.forBlock["change_global_var"] = function (block) { + const name = block.getFieldValue("VAR"); + const value = + BlocklyJS.javascriptGenerator.valueToCode( + block, + "VALUE", + BlocklyJS.Order.ATOMIC + ) || "0"; + return `projectVariables["${name}"] += ${value};\n`; +}; diff --git a/src/cache.js b/src/cache.js new file mode 100644 index 0000000..c430134 --- /dev/null +++ b/src/cache.js @@ -0,0 +1,3 @@ +export const cache = { + user: null, +}; diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..bb5eeb7 --- /dev/null +++ b/src/config.js @@ -0,0 +1,5 @@ +const localhost = window.location.hostname === "localhost"; + +export default { + apiUrl: localhost ? "http://localhost:3000" : "https://rarry-api-production.up.railway.app", +}; diff --git a/src/editor.css b/src/editor.css new file mode 100644 index 0000000..33f4b25 --- /dev/null +++ b/src/editor.css @@ -0,0 +1,415 @@ +#blocklyDiv { + height: 100%; + width: 100%; +} + +.main { + display: flex; + flex: 1; + overflow: hidden; +} + +html.stageLeft .main { + flex-direction: row-reverse; +} + +.left-panel { + width: 66.66%; + display: flex; + flex-direction: column; + border-right: 2px solid var(--color3); +} + +.tab-header { + display: flex; + border-bottom: 2px solid var(--color3); +} + +button.tab-button { + flex: 1; + padding: 0.5rem 0; + text-align: center; + font-weight: 600; + color: var(--primary); + background-color: var(--color2); + border-radius: 0; +} + +button.tab-button.inactive { + background-color: var(--color1); + color: var(--dark); +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: flex; + flex: 1; + flex-direction: column; +} + +.tab-section { + padding: 1rem; + background-color: var(--color1); + overflow-y: auto; + flex: 1; +} + +.sound-container, +.costume-container { + padding: 8px; + gap: 0.5rem; + background-color: var(--color2); + border-radius: 0.375rem; + display: flex; + justify-content: flex-start; + align-items: center; + font-size: 1rem; + color: var(--dark); +} + +#stage-wrapper { + position: relative; + width: 100%; + padding-top: 56.25%; +} + +#stage-div.fullscreen { + display: flex; + flex-direction: column; + justify-content: center; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; + z-index: 9999; + background: var(--color1); +} + +#stage canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.right-panel { + width: 33.33%; + display: flex; + flex-direction: column; +} + +#stage-controls { + background-color: var(--color1); + padding: 0.5rem; + border-bottom: 2px solid var(--color3); + display: flex; + justify-content: center; + gap: 1rem; +} + +#stage-controls button { + padding: 0.5rem; + width: 2.5rem; + height: 2.5rem; + border-radius: 25%; + background-color: var(--color2); +} + +#stage-controls button:hover { + background-color: var(--color3); +} + +#stage-controls button.active { + outline: 2px solid #2acc65a6; + outline-offset: -2px; +} + +#stage-controls button img { + width: 100%; + height: 100%; + user-select: none; + pointer-events: none; +} + +html.dark #fullscreen-button img { + filter: invert(1); +} + +#sprite-info { + padding: 0.5rem; + display: flex; + flex-direction: row; + gap: 5px; +} + +#sprite-info p { + color: var(--dark); +} + +#sprite-info p:not(:last-child) { + padding-right: 5px; + border-right: 2px solid var(--color2); +} + +#sprites-section { + display: flex; + flex-direction: column; + gap: 0.5rem; + background-color: var(--color1); + padding: 0.5rem; + border-top: 2px solid var(--color3); + overflow-y: auto; +} + +#sprites-section div#sprite-buttons { + display: flex; + flex-direction: row; + gap: 0.5rem; +} + +#sprites-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + max-height: 9rem; + overflow-y: scroll; +} + +#sprites-list div { + width: 4rem; + height: 4rem; + background-color: var(--color2); + border-radius: 0.375rem; + display: flex; + justify-content: center; + align-items: center; + font-size: 0.875rem; + cursor: pointer; +} + +#sprites-list div.active { + background-color: var(--primary); + color: var(--color1); +} + +#sprites-section-buttons { + display: flex; + flex-direction: row; + gap: 0.5rem; +} + +#costumes-list, +#sounds-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +#extensionButton { + padding: 3px; + margin: 0 5px; + margin-top: auto; + background: var(--primary); + order: 9009; + cursor: pointer; +} + +.extensions-popup { + display: flex; + flex-direction: column; + gap: 5px; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; + background-color: var(--color1); +} + +.extensions-popup .extensions-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + padding: 0.7rem; +} + +.extensions-list div { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem; + border-radius: 0.5rem; + background-color: var(--color2); +} + +.extensions-list div img { + border-radius: 0.5rem; +} + +.extensions-list div h2 { + text-align: center; +} + +.extensions-popup header { + padding: 0.7rem; +} + +.blocklyMainBackground { + stroke-width: 0; + stroke: none; +} + +.blocklyToolboxCategoryGroup { + height: 100%; +} + +html.dark .blocklyToolboxCategoryIcon { + filter: invert(1); +} + +.blocklyToolboxCategory { + height: initial; + padding: 3px 0; + transition: background-color 0.2s ease; + border-left: none !important; +} + +.blocklyToolboxCategory.blocklyToolboxSelected { + background-color: rgba(0, 0, 0, 0.3) !important; +} + +html.dark .blocklyToolboxCategory.blocklyToolboxSelected { + background-color: rgba(255, 255, 255, 0.3) !important; +} + +.draggingBlocklyToolboxCategory .blocklyToolboxCategory { + background: none !important; +} + +.blocklyToolboxCategoryLabel { + text-align: center; + transition: color 0.2s ease; +} + +.blocklyTreeSeparator { + border-bottom-color: var(--dark); +} + +.blocklyTreeRowContentContainer { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: center; + padding: 3px; +} + +.blocklyWidgetDiv .blocklyMenu { + background-color: var(--color1); + border-color: var(--color2); +} + +.blocklyMenuItem { + color: var(--dark); +} + +.blocklyMenuItemDisabled { + color: var(--color4); +} + +.categoryBubble { + margin: 0; + padding: 0; + border-radius: 50%; + border: 1px solid; + width: 1.25rem; + height: 1.25rem; +} + +#room-users { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +#room-users div { + display: inline-flex; + align-items: center; + gap: 0.3rem; +} + +#room-users div img { + width: 2rem; + height: 2rem; + border-radius: 20%; +} + +.smallLabel { + margin-left: auto; + font-size: 0.8em; + color: var(--dark-light); +} +#backdrops-section { + padding: 12px; + border-top: 1px solid var(--border-color); +} + +#backdrops-section h3 { + margin: 0 0 8px 0; + font-size: 14px; + font-weight: 600; +} + +#backdrops-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 8px; + min-height: 60px; +} + +#backdrops-list > div { + width: 60px; + height: 45px; + border: 2px solid transparent; + border-radius: 4px; + cursor: pointer; + overflow: hidden; + transition: border-color 0.2s; +} + +#backdrops-list > div:hover { + border-color: var(--primary-color); +} + +#backdrops-list > div.active { + border-color: var(--primary-color); + box-shadow: 0 0 0 1px var(--primary-color); +} + +#backdrops-list > div img { + width: 100%; + height: 100%; + object-fit: cover; +} + +#backdrops-section-buttons { + display: flex; + gap: 8px; +} + +#backdrops-section-buttons button { + flex: 1; +} diff --git a/src/functions/extensionManager.js b/src/functions/extensionManager.js new file mode 100644 index 0000000..93f16a7 --- /dev/null +++ b/src/functions/extensionManager.js @@ -0,0 +1,248 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; +import { activeExtensions } from "../scripts/editor"; +import { Thread } from "./threads"; + +Thread.resetAll(); + +function textToBlock(block, text, fields) { + const regex = /\[([^\]]+)\]/g; + let lastIndex = 0; + let match; + + while ((match = regex.exec(text))) { + const before = text.slice(lastIndex, match.index); + if (before) { + block.appendDummyInput().appendField(before.trim()); + } + + const inputName = match[1].trim(); + const spec = fields?.[inputName]; + + if (spec?.kind === "statement") { + block + .appendStatementInput(inputName) + .setCheck(spec?.accepts || "default"); + } else if (spec?.kind === "value") { + block.appendValueInput(inputName).setCheck(spec?.type); + } else if (spec?.kind === "menu") { + const menuItems = spec.items.map((item) => + typeof item === "string" ? [item, item] : [item.text, item.value] + ); + const field = new Blockly.FieldDropdown(menuItems); + block.appendDummyInput().appendField(field, inputName); + } else { + block.appendDummyInput().appendField("[" + inputName + "]"); + } + + lastIndex = regex.lastIndex; + } + + const after = text.slice(lastIndex); + if (after) { + block.appendDummyInput().appendField(after.trim()); + } +} + +export function setupExtensions() { + if (!window.extensions) { + const backing = {}; + + const proxy = new Proxy(backing, { + defineProperty(target, prop, descriptor) { + if (prop in target) + throw new Error(`Extension "${prop}" already defined`); + return Reflect.defineProperty(target, prop, { + ...descriptor, + writable: false, + configurable: false, + }); + }, + set(target, prop, value) { + if (prop in target) + throw new Error(`Extension "${prop}" is already defined and locked`); + return Reflect.defineProperty(target, prop, { + value, + writable: false, + configurable: false, + enumerable: true, + }); + }, + deleteProperty() { + throw new Error("Extensions cannot be removed"); + }, + get(target, prop, receiver) { + return Reflect.get(target, prop, receiver); + }, + ownKeys(target) { + return Reflect.ownKeys(target); + }, + }); + + Object.defineProperty(window, "extensions", { + value: proxy, + writable: false, + configurable: false, + enumerable: true, + }); + } +} + +export async function registerExtension(extClass) { + const ext = new extClass(); + const id = ext.id || ext.constructor.name; + + if (activeExtensions.some((i) => (i?.id || i) === id)) { + console.warn(`Extension ${id} already registered`); + return; + } + + const coreDom = document.getElementById("toolbox"); + + const category = ext.registerCategory?.(); + let categoryEl = null; + if (category) { + categoryEl = document.createElement("category"); + categoryEl.setAttribute("name", category.name || "Extension"); + if (!category.color) category.color = "#888"; + categoryEl.setAttribute("colour", category.color); + if (category.iconURI) categoryEl.setAttribute("iconURI", category.iconURI); + } + + const blocks = ext.registerBlocks?.() || []; + const blockDefs = {}; + blocks.forEach((blockDef) => { + if (!blockDef.id) { + console.warn("Skipped registration of block with no ID"); + return; + } + + const blockType = `${id}_${blockDef.id}`; + blockDefs[blockType] = blockDef; + Blockly.Blocks[blockType] = { + init: function () { + textToBlock(this, blockDef.text, blockDef.fields); + + if (blockDef.type === "statement") { + this.setPreviousStatement(true, blockDef.statementType || "default"); + this.setNextStatement(true, blockDef.statementType || "default"); + } else if (blockDef.type === "cap") { + this.setPreviousStatement(true, blockDef.statementType || "default"); + } else if (blockDef.type === "output") { + this.setOutput(true, blockDef.outputType); + if (blockDef.outputShape) this.setOutputShape(blockDef.outputShape); + } + if (blockDef.tooltip) this.setTooltip(blockDef.tooltip); + this.setInputsInline(true); + this.setColour(blockDef?.color || category.color); + }, + }; + + if (categoryEl) { + const blockEl = document.createElement("block"); + blockEl.setAttribute("type", blockType); + + for (const [name, spec] of Object.entries(blockDef.fields || {})) { + if (spec?.kind === "menu") continue; + + if (spec.default !== undefined && spec?.kind !== "statement") { + const valueEl = document.createElement("value"); + valueEl.setAttribute("name", name.trim()); + + let shadowEl = null; + + if (spec.type === "Number") { + shadowEl = document.createElement("shadow"); + shadowEl.setAttribute("type", "math_number"); + + const fieldEl = document.createElement("field"); + fieldEl.setAttribute("name", "NUM"); + fieldEl.textContent = spec.default; + shadowEl.appendChild(fieldEl); + } else if (spec.type === "String") { + shadowEl = document.createElement("shadow"); + shadowEl.setAttribute("type", "text"); + + const fieldEl = document.createElement("field"); + fieldEl.setAttribute("name", "TEXT"); + fieldEl.textContent = spec.default; + shadowEl.appendChild(fieldEl); + } else if (spec.type === "Boolean") { + shadowEl = document.createElement("shadow"); + shadowEl.setAttribute("type", "logic_boolean"); + + const fieldEl = document.createElement("field"); + fieldEl.setAttribute("name", "BOOL"); + fieldEl.textContent = spec.default ? "TRUE" : "FALSE"; + shadowEl.appendChild(fieldEl); + } + + if (shadowEl) { + valueEl.appendChild(shadowEl); + } + + blockEl.appendChild(valueEl); + } + } + + categoryEl.appendChild(blockEl); + } + }); + + if (categoryEl) { + coreDom.appendChild(categoryEl); + Blockly.getMainWorkspace().updateToolbox(coreDom); + } + + const codeGen = ext.registerCode?.() || {}; + Object.entries(codeGen).forEach(([blockType, fn]) => { + const fullType = `${id}_${blockType}`; + + window.extensions[fullType] = fn; + const def = blockDefs[fullType] || {}; + BlocklyJS.javascriptGenerator.forBlock[fullType] = function (block) { + const inputs = {}; + + for (const input of block.inputList) { + const name = input.name; + let codeExpr; + + if (input.type === 1 || input.type === 2) { + codeExpr = + BlocklyJS.javascriptGenerator.valueToCode( + block, + name, + BlocklyJS.Order.ATOMIC + ) || undefined; + if (codeExpr !== undefined) inputs[name] = codeExpr; + } else if (input.type === 3) { + codeExpr = + BlocklyJS.javascriptGenerator.statementToCode(block, name) || + undefined; + if (codeExpr !== undefined) + inputs[name] = `async () => { ${codeExpr} }`; + } + } + + for (const [name, spec] of Object.entries(def.fields || {})) { + if (spec.kind === "menu") { + const fieldVal = block.getFieldValue(name); + if (fieldVal !== undefined) inputs[name] = JSON.stringify(fieldVal); + } + } + + const argsParts = Object.entries(inputs).map( + ([k, v]) => `${JSON.stringify(k)}:${v}` + ); + const args = `{${argsParts.join(",")}}`; + const callCode = `extensions["${fullType}"](${args},Thread)`; + + const finalCode = def.promise ? `await ${callCode}` : callCode; + + if (block.outputConnection) return [finalCode, BlocklyJS.Order.NONE]; + else return finalCode + ";\n"; + }; + }); + + activeExtensions.push({ id, code: extClass.toString() }); +} diff --git a/src/functions/patches.js b/src/functions/patches.js new file mode 100644 index 0000000..c4ea6e6 --- /dev/null +++ b/src/functions/patches.js @@ -0,0 +1,334 @@ +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; +import * as PIXI from "pixi.js-legacy"; + +BlocklyJS.javascriptGenerator.INFINITE_LOOP_TRAP = + 'if (stopped()) throw new Error("shouldStop");\nif (!fastExecution) await new Promise(r => setTimeout(r, 16));\n'; + +Blockly.VerticalFlyout.prototype.getFlyoutScale = () => 0.8; + +[ + "controls_if", + "controls_if_if", + "controls_if_elseif", + "controls_if_else", +].forEach((type) => { + Blockly.Blocks[type].init = (function (original) { + return function () { + original.call(this); + this.setColour("#FFAB19"); + }; + })(Blockly.Blocks[type].init); +}); + +Blockly.Blocks["controls_forEach"].init = (function (original) { + return function () { + original.call(this); + this.setColour("#e35340"); + }; +})(Blockly.Blocks["controls_forEach"].init); + +Blockly.Blocks["text"] = { + init: function () { + this.appendDummyInput().appendField(new Blockly.FieldTextInput(""), "TEXT"); + this.setOutput(true, "String"); + this.setStyle("text_blocks"); + this.setTooltip(Blockly.Msg["TEXT_TEXT_TOOLTIP"]); + this.setHelpUrl(Blockly.Msg["TEXT_TEXT_HELPURL"]); + + Blockly.Extensions.apply("parent_tooltip_when_inline", this, false); + setTimeout(() => { + if (!this.isShadow()) { + Blockly.Extensions.apply("text_quotes", this, false); + } + }); + }, +}; + +Object.keys(Blockly.Blocks).forEach((type) => { + const block = Blockly.Blocks[type]; + if (!block || typeof block.init !== "function") return; + + const originalInit = block.init; + block.init = function () { + originalInit.call(this); + if (this.previousConnection && this.previousConnection.check_ === null) + this.setPreviousStatement(true, "default"); + if (this.nextConnection && this.nextConnection.check_ === null) + this.setNextStatement(true, "default"); + }; +}); + +BlocklyJS.javascriptGenerator.forBlock["procedures_defnoreturn"] = function ( + block, + generator +) { + const procedureName = generator.getProcedureName(block.getFieldValue("NAME")); + + let injectedCode = ""; + if (generator.STATEMENT_PREFIX) { + injectedCode += generator.injectId(generator.STATEMENT_PREFIX, block); + } + if (generator.STATEMENT_SUFFIX) { + injectedCode += generator.injectId(generator.STATEMENT_SUFFIX, block); + } + if (injectedCode) { + injectedCode = generator.prefixLines(injectedCode, generator.INDENT); + } + + let loopTrap = ""; + if (generator.INFINITE_LOOP_TRAP) { + loopTrap = generator.prefixLines( + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT + ); + } + + let bodyCode = ""; + if (block.getInput("STACK")) { + bodyCode = generator.statementToCode(block, "STACK"); + } + + let returnCode = ""; + if (block.getInput("RETURN")) { + returnCode = + generator.valueToCode(block, "RETURN", BlocklyJS.Order.NONE) || ""; + } + + let returnWrapper = ""; + if (bodyCode && returnCode) { + returnWrapper = injectedCode; + } + + if (returnCode) { + returnCode = generator.INDENT + "return " + returnCode + ";\n"; + } + + const args = []; + const vars = block.getVars(); + for (let i = 0; i < vars.length; i++) { + args[i] = generator.getVariableName(vars[i]); + } + + let code = + "async function " + + procedureName + + "(" + + args.join(", ") + + ") {\n" + + injectedCode + + loopTrap + + bodyCode + + returnWrapper + + returnCode + + "}"; + + code = generator.scrub_(block, code); + generator.definitions_["%" + procedureName] = code; + + return null; +}; + +BlocklyJS.javascriptGenerator.forBlock["procedures_defreturn"] = function ( + block, + generator +) { + const procedureName = generator.getProcedureName(block.getFieldValue("NAME")); + + let statementWrapper = ""; + if (generator.STATEMENT_PREFIX) { + statementWrapper += generator.injectId(generator.STATEMENT_PREFIX, block); + } + if (generator.STATEMENT_SUFFIX) { + statementWrapper += generator.injectId(generator.STATEMENT_SUFFIX, block); + } + if (statementWrapper) { + statementWrapper = generator.prefixLines( + statementWrapper, + generator.INDENT + ); + } + + let loopTrapCode = ""; + if (generator.INFINITE_LOOP_TRAP) { + loopTrapCode = generator.prefixLines( + generator.injectId(generator.INFINITE_LOOP_TRAP, block), + generator.INDENT + ); + } + + let bodyCode = ""; + if (block.getInput("STACK")) { + bodyCode = generator.statementToCode(block, "STACK"); + } + + let returnCode = ""; + if (block.getInput("RETURN")) { + returnCode = + generator.valueToCode(block, "RETURN", BlocklyJS.Order.NONE) || ""; + } + + let returnWrapper = ""; + if (bodyCode && returnCode) { + returnWrapper = statementWrapper; + } + + if (returnCode) { + returnCode = generator.INDENT + "return " + returnCode + ";\n"; + } + + const args = []; + const vars = block.getVars(); + for (let i = 0; i < vars.length; i++) { + args[i] = generator.getVariableName(vars[i]); + } + + let code = + "async function " + + procedureName + + "(" + + args.join(", ") + + ") {\n" + + statementWrapper + + loopTrapCode + + bodyCode + + returnWrapper + + returnCode + + "}"; + + code = generator.scrub_(block, code); + generator.definitions_["%" + procedureName] = code; + + return null; +}; + +BlocklyJS.javascriptGenerator.forBlock["procedures_callreturn"] = function ( + block, + generator +) { + const procedureName = generator.getProcedureName(block.getFieldValue("NAME")); + + const args = []; + const vars = block.getVars(); + for (let i = 0; i < vars.length; i++) { + args[i] = + generator.valueToCode(block, "ARG" + i, BlocklyJS.Order.NONE) || "null"; + } + + return [ + "await " + procedureName + "(" + args.join(", ") + ")", + BlocklyJS.Order.FUNCTION_CALL, + ]; +}; + +BlocklyJS.javascriptGenerator.forBlock["procedures_callnoreturn"] = function ( + block, + generator +) { + const code = generator.forBlock.procedures_callreturn(block, generator)[0]; + return code + ";\n"; +}; + +export const SpriteChangeEvents = new PIXI.utils.EventEmitter(); + +const originalX = Object.getOwnPropertyDescriptor( + PIXI.DisplayObject.prototype, + "x" +); +const originalY = Object.getOwnPropertyDescriptor( + PIXI.DisplayObject.prototype, + "y" +); +const originalAngle = Object.getOwnPropertyDescriptor( + PIXI.DisplayObject.prototype, + "angle" +); +const originalTexture = Object.getOwnPropertyDescriptor( + PIXI.Sprite.prototype, + "texture" +); + +Object.defineProperty(PIXI.Sprite.prototype, "x", { + get() { + return originalX.get.call(this); + }, + set(value) { + if (this.x !== value) { + originalX.set.call(this, value); + SpriteChangeEvents.emit("positionChanged", this); + } + }, +}); + +Object.defineProperty(PIXI.Sprite.prototype, "y", { + get() { + return originalY.get.call(this); + }, + set(value) { + if (this.y !== value) { + originalY.set.call(this, value); + SpriteChangeEvents.emit("positionChanged", this); + } + }, +}); + +PIXI.Sprite.prototype.setPosition = function (x = null, y = null, add = false) { + const newX = x !== null ? (add ? this.x + x : x) : this.x; + const newY = y !== null ? (add ? this.y + y : y) : this.y; + if (this.x === newX && this.y === newY) return; + originalX.set.call(this, newX); + originalY.set.call(this, newY); + SpriteChangeEvents.emit("positionChanged", this); +}; + +Object.defineProperty(PIXI.Sprite.prototype, "angle", { + get() { + return originalAngle.get.call(this); + }, + set(value) { + if (this.angle !== value) { + originalAngle.set.call(this, value); + SpriteChangeEvents.emit("positionChanged", this); + } + }, +}); + +Object.defineProperty(PIXI.Sprite.prototype, "texture", { + get() { + return originalTexture.get.call(this); + }, + set(value) { + if (this.constructor === PIXI.Sprite && this.texture !== value) { + originalTexture.set.call(this, value); + SpriteChangeEvents.emit("textureChanged", this); + } else { + originalTexture.set.call(this, value); + } + }, +}); + +const originalObsPointSet = PIXI.ObservablePoint.prototype.set; + +PIXI.ObservablePoint.prototype.set = function (x, y) { + const result = originalObsPointSet.call(this, x, y); + if (this._parentScaleEvent) { + SpriteChangeEvents.emit("scaleChanged", this._parentScaleEvent); + } + return result; +}; + +class ToolboxBubbleCategory extends Blockly.ToolboxCategory { + createIconDom_() { + const element = document.createElement("div"); + element.classList.add("categoryBubble"); + element.style.backgroundColor = this.colour_; + return element; + } +} + +Blockly.registry.register( + Blockly.registry.Type.TOOLBOX_ITEM, + Blockly.ToolboxCategory.registrationName, + ToolboxBubbleCategory, + true +); diff --git a/src/functions/render.js b/src/functions/render.js new file mode 100644 index 0000000..b57b8d9 --- /dev/null +++ b/src/functions/render.js @@ -0,0 +1,217 @@ +import * as Blockly from "blockly"; + +class CustomConstantProvider extends Blockly.zelos.ConstantProvider { + init() { + super.init(); + this.BOWL = this.makeBowl(); + this.PILLOW = this.makePillow(); + this.SPIKEY = this.makeSpikey(); + } + + makeBowl() { + const maxW = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH; + const maxH = maxW * 2; + const roundedCopy = this.ROUNDED; + + function makeMainPath(blockHeight, up, right) { + const extra = blockHeight > maxH ? blockHeight - maxH : 0; + const h_ = Math.min(blockHeight, maxH); + const h = h_ + extra; + const radius = h / 2; + const radiusH = Math.min(h_ / 2, maxH); + const dirR = right ? 1 : -1; + const dirU = up ? -1 : 1; + + return ` + h ${radiusH * dirR} + q ${(h_ / 4) * -dirR} ${radius * dirU} 0 ${h * dirU} + h ${radiusH * -dirR} + `; + } + + return { + type: this.SHAPES.ROUND, + isDynamic: true, + width(h) { + const half = h / 2; + return half > maxW ? maxW : half; + }, + height(h) { + return h; + }, + connectionOffsetY(h) { + return h / 2; + }, + connectionOffsetX(w) { + return -w; + }, + pathDown(h) { + return makeMainPath(h, false, false); + }, + pathUp(h) { + return makeMainPath(h, true, false); + }, + pathRightDown(h) { + return roundedCopy.pathRightDown(h); + }, + pathRightUp(h) { + return roundedCopy.pathRightUp(h); + }, + }; + } + + makePillow() { + const maxWidth = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH; + const maxHeight = maxWidth * 2; + + function makeMainPath(blockHeight, up, right) { + const remainingHeight = + blockHeight > maxHeight ? blockHeight - maxHeight : 0; + const height = blockHeight > maxHeight ? maxHeight : blockHeight; + const radius = height / 8; + + const dirRight = right ? 1 : -1; + const dirUp = up ? -1 : 1; + + const radiusW = radius * dirRight; + const radiusH = radius * dirUp; + + return ` + h ${radiusW} + q ${radiusW} 0 ${radiusW} ${radiusH} + q 0 ${radiusH} ${radiusW} ${radiusH} + q ${radiusW} 0 ${radiusW} ${radiusH} + v ${(remainingHeight + height - radius * 6) * dirUp} + q 0 ${radiusH} ${-radiusW} ${radiusH} + q ${-radiusW} 0 ${-radiusW} ${radiusH} + q 0 ${radiusH} ${-radiusW} ${radiusH} + h ${-radiusW} + `; + } + + return { + type: this.SHAPES.HEXAGONAL, + isDynamic: true, + width(height) { + const halfHeight = height / 2; + return halfHeight > maxWidth ? maxWidth : halfHeight; + }, + height(height) { + return height; + }, + connectionOffsetY(connectionHeight) { + return connectionHeight / 2; + }, + connectionOffsetX(connectionWidth) { + return -connectionWidth; + }, + pathDown(height) { + return makeMainPath(height, false, false); + }, + pathUp(height) { + return makeMainPath(height, true, false); + }, + pathRightDown(height) { + return makeMainPath(height, false, true); + }, + pathRightUp(height) { + return makeMainPath(height, false, true); + }, + }; + } + + makeSpikey() { + const maxW = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH; + const maxH = maxW * 2; + + function makeMainPath(blockHeight, up, right) { + const extra = blockHeight > maxH ? blockHeight - maxH : 0; + const h_ = Math.min(blockHeight, maxH); + const h = h_ + extra; + const radius = h / 4; + const radiusH = Math.min(h_ / 4, maxH); + const dirR = right ? 1 : -1; + const dirU = up ? -1 : 1; + + return ` + h ${2 * radiusH * dirR} + l ${radiusH * -dirR} ${radius * dirU} + l ${radiusH * dirR} ${radius * dirU} + l ${radiusH * -dirR} ${radius * dirU} + l ${radiusH * dirR} ${radius * dirU} + h ${2 * radiusH * -dirR} + `; + } + + return { + type: this.SHAPES.HEXAGONAL, + isDynamic: true, + width(h) { + const half = h / 2; + return half > maxW ? maxW : half; + }, + height(h) { + return h; + }, + connectionOffsetY(h) { + return h / 2; + }, + connectionOffsetX(w) { + return -w; + }, + pathDown(h) { + return makeMainPath(h, false, false); + }, + pathUp(h) { + return makeMainPath(h, true, false); + }, + pathRightDown(h) { + return makeMainPath(h, false, true); + }, + pathRightUp(h) { + return makeMainPath(h, true, true); + }, + }; + } + + /** + * @param {Blockly.RenderedConnection} connection + */ + shapeFor(connection) { + let checks = connection.getCheck() ?? []; + if (!checks && connection.targetConnection) + checks = connection.targetConnection.getCheck() ?? []; + let outputShape = connection.sourceBlock_.getOutputShape(); + + if (connection.type === 1 || connection.type === 2) { + if ( + (checks.includes("Array") || outputShape === 4) && + !["text_length", "text_isEmpty"].includes(connection.sourceBlock_.type) + ) { + return this.BOWL; + } else if (checks.includes("Object") || outputShape === 5) { + return this.PILLOW; + } else if (checks.includes("Set") || outputShape === 6) { + return this.SPIKEY; + } /*else if ( + checks.includes("String") && + connection?.sourceBlock_?.isShadow() && + connection?.targetConnection?.shadowState?.type === "text" + ) { + return this.SQUARED; + }*/ + } + + return super.shapeFor(connection); + } +} + +export default class CustomRenderer extends Blockly.zelos.Renderer { + constructor() { + super(); + } + + makeConstants_() { + return new CustomConstantProvider(); + } +} diff --git a/src/functions/runCode.js b/src/functions/runCode.js new file mode 100644 index 0000000..d1af593 --- /dev/null +++ b/src/functions/runCode.js @@ -0,0 +1,441 @@ +import * as PIXI from "pixi.js-legacy"; +import { calculateBubblePosition, projectVariables } from "../scripts/editor"; +import { Thread } from "./threads"; +import { promiseWithAbort } from "./utils"; + +const BUBBLE_PADDING = 10; +const BUBBLE_TAIL_HEIGHT = 15; +const BUBBLE_TAIL_WIDTH = 15; +const BUBBLE_COLOR = 0xffffff; +const BUBBLE_TEXTSTYLE = new PIXI.TextStyle({ fill: 0x000000, fontSize: 24 }); +const LINE_COLOR = 0xbdc1c7; + +export function runCodeWithFunctions({ + code, + projectStartedTime, + spriteData, + app, + eventRegistry, + mouseButtonsPressed, + keysPressed, + playingSounds, + runningScripts, + signal, + penGraphics, + activeEventThreads, +}) { + Thread.resetAll(); + let fastExecution = false; + + const sprite = spriteData.pixiSprite; + const renderer = app.renderer; + const stage = app.stage; + const costumeMap = new Map( + (spriteData.costumes || []).map((c) => [c.name, c]) + ); + const soundMap = new Map((spriteData.sounds || []).map((s) => [s.name, s])); + const extensions = window.extensions; + const MyFunctions = {}; + + function stopped() { + return signal.aborted === true; + } + + function registerEvent(type, key, callback) { + if (stopped()) return; + + const entry = { + type, + cb: async () => { + if (stopped()) return; + + const threadId = Thread.create(); + Thread.enter(threadId); + activeEventThreads.count++; + + try { + const result = await promiseWithAbort( + () => callback(Thread.getCurrentContext()), + signal + ); + if (result === "shouldStop" || stopped()) return; + } catch (err) { + if (err.message !== "shouldStop") console.error(err); + } finally { + Thread.exit(); + activeEventThreads.count--; + } + }, + }; + + switch (type) { + case "flag": + eventRegistry.flag.push(entry); + break; + case "key": + if (!eventRegistry.key.has(key)) eventRegistry.key.set(key, []); + eventRegistry.key.get(key).push(entry); + break; + case "stageClick": + eventRegistry.stageClick.push(entry); + break; + case "timer": + eventRegistry.timer.push({ ...entry, value: key }); + break; + case "interval": + eventRegistry.interval.push({ ...entry, seconds: key }); + break; + case "custom": + if (!eventRegistry.custom.has(key)) eventRegistry.custom.set(key, []); + eventRegistry.custom.get(key).push(entry); + break; + } + } + + function triggerCustomEvent(eventName) { + const entries = eventRegistry.custom.get(eventName); + if (!entries) return; + for (const entry of entries) { + entry.cb(); + } + } + + function moveSteps(steps = 0) { + const { rotation: a } = sprite; + sprite.x += Math.cos(a) * steps; + sprite.y += Math.sin(a) * steps; + } + + function getMousePosition(menu) { + const mouse = renderer.events.pointer.global; + if (menu === "x") + return Math.round((mouse.x - renderer.width / 2) / stage.scale.x); + else if (menu === "y") + return -Math.round((mouse.y - renderer.height / 2) / stage.scale.y); + } + + function sayMessage(message, seconds) { + if (stopped()) return; + + message = String(message ?? ""); + if (!message) return; + + if (!spriteData.currentBubble) { + const bubble = new PIXI.Graphics(); + const text = new PIXI.Text("", BUBBLE_TEXTSTYLE); + text.x = BUBBLE_PADDING; + text.y = BUBBLE_PADDING; + + const container = new PIXI.Container(); + container.addChild(bubble); + container.addChild(text); + container.bubble = bubble; + container.text = text; + + spriteData.currentBubble = container; + stage.addChild(container); + } + + const container = spriteData.currentBubble; + const { bubble, text } = container; + + if (spriteData.sayTimeout !== null) { + clearTimeout(spriteData.sayTimeout); + spriteData.sayTimeout = null; + } + + if (text.text !== message) { + text.text = message; + + const bubbleWidth = text.width + BUBBLE_PADDING * 2; + const bubbleHeight = text.height + BUBBLE_PADDING * 2; + + bubble.clear(); + bubble.beginFill(BUBBLE_COLOR); + bubble.lineStyle(2, LINE_COLOR); + bubble.drawRoundedRect(0, 0, bubbleWidth, bubbleHeight, 10); + + bubble.moveTo(bubbleWidth / 2 - BUBBLE_TAIL_WIDTH / 2, bubbleHeight); + bubble.lineTo(bubbleWidth / 2, bubbleHeight + BUBBLE_TAIL_HEIGHT); + bubble.lineTo(bubbleWidth / 2 + BUBBLE_TAIL_WIDTH / 2, bubbleHeight); + bubble.closePath(); + bubble.endFill(); + } + + const pos = calculateBubblePosition( + sprite, + bubble.width, + bubble.height, + BUBBLE_TAIL_HEIGHT + ); + container.x = pos.x; + container.y = pos.y; + + container.visible = true; + + if (typeof seconds === "number" && seconds > 0) { + spriteData.sayTimeout = setTimeout(() => { + container.visible = false; + spriteData.sayTimeout = null; + }, Math.min(seconds * 1000, 2147483647)); + } + } + + function waitOneFrame() { + return new Promise((res, rej) => { + if (stopped()) return rej("stopped"); + + const id = requestAnimationFrame(() => { + if (stopped()) return rej("stopped"); + runningScripts.splice( + runningScripts.findIndex((t) => t.id === id), + 1 + ); + res(); + }); + runningScripts.push({ type: "raf", id }); + }); + } + + function wait(ms) { + return new Promise((res, rej) => { + if (stopped()) return rej("stopped"); + + const id = setTimeout(() => { + if (stopped()) return rej("stopped"); + runningScripts.splice( + runningScripts.findIndex((t) => t.id === id), + 1 + ); + res(); + }, ms); + runningScripts.push({ type: "timeout", id }); + }); + } + + function switchCostume(name) { + const found = costumeMap.get(name); + if (found) { + sprite.texture = found.texture; + } + } + + function setSize(amount = 0, additive) { + let amountN = amount / 100; + if (additive) + sprite.scale.set(sprite.scale.x + amountN, sprite.scale.y + amountN); + else sprite.scale.set(amountN, amountN); + } + + function setAngle(amount, additive) { + let angle = additive ? sprite.angle + amount : amount + angle = ((angle % 360) + 360) % 360; + sprite.angle = angle; + } + + function pointsTowards(x, y) { + const { width, height } = renderer + const targetX = width / 2 + x * stage.scale.x; + const targetY = height / 2 - y * stage.scale.y; + const spriteX = width / 2 + sprite.x * stage.scale.x; + const spriteY = height / 2 - sprite.y * stage.scale.y; + + let angle = Math.atan2(targetX - spriteX, targetY - spriteY) * (180 / Math.PI); + angle = ((angle % 360) + 360) % 360; + sprite.angle = angle; + } + + function projectTime() { + return (Date.now() - projectStartedTime) / 1000; + } + + function isKeyPressed(key) { + if (key === "any") { + return Object.values(keysPressed).some((pressed) => pressed); + } + + return !!keysPressed[key]; + } + + function isMouseButtonPressed(button) { + if (button === "any") { + return Object.values(mouseButtonsPressed).some((pressed) => pressed); + } + + return !!mouseButtonsPressed[button]; + } + + function getCostumeSize(type) { + const frame = sprite?.texture?.frame; + if (!frame) return 0; + + if (type === "width") return frame.width; + else if (type === "height") return frame.height; + else return 0; + } + + function getSpriteScale() { + const scaleX = sprite.scale.x; + const scaleY = sprite.scale.y; + return ((scaleX + scaleY) / 2) * 100; + } + + function startTween({ from, to, duration, easing, onUpdate, wait = true }) { + if (stopped()) return; + + const tweenPromise = new Promise((resolve) => { + const start = performance.now(); + const change = to - from; + const easeFn = window.TweenEasing[easing] || window.TweenEasing.linear; + + function tick(now) { + if (stopped()) return resolve("shouldStop"); + + const t = Math.min((now - start) / (duration * 1000), 1); + const value = from + change * easeFn(t); + + if (onUpdate) { + const result = onUpdate(value); + if (result === "shouldStop") return resolve("shouldStop"); + } + + if (t < 1) { + requestAnimationFrame(tick); + } else { + resolve(); + } + } + + const id = requestAnimationFrame(tick); + runningScripts.push({ type: "raf", id }); + }); + + return wait ? tweenPromise : undefined; + } + + let soundProperties = { + volume: 100, + speed: 100, + }; + + function setSoundProperty(property, value) { + if (!soundProperties[property]) return; + if (property === "speed") value = Math.min(1600, Math.max(7, value)); + if (property === "volume") value = Math.min(100, Math.max(0, value)); + soundProperties[property] = value; + } + + async function playSound(name, wait = false) { + const sound = soundMap.get(name); + if (!sound) return; + + if (!playingSounds.has(spriteData.id)) + playingSounds.set(spriteData.id, new Map()); + + const spriteSounds = playingSounds.get(spriteData.id); + + const oldAudio = spriteSounds.get(name); + if (oldAudio) { + oldAudio.pause(); + oldAudio.currentTime = 0; + } + + const audio = new Audio(sound.dataURL); + spriteSounds.set(name, audio); + + audio.volume = soundProperties.volume / 100; + audio.playbackRate = soundProperties.speed / 100; + audio.play(); + + const cleanup = () => { + if (spriteSounds.get(name) === audio) { + spriteSounds.delete(name); + } + }; + + audio.addEventListener("ended", cleanup); + audio.addEventListener("pause", cleanup); + + if (wait) { + return new Promise((res) => { + audio.addEventListener("ended", () => res()); + }); + } + } + + function stopSound(name) { + const spriteSounds = playingSounds.get(spriteData.id); + if (!spriteSounds || !spriteSounds.has(name)) return; + + const audio = spriteSounds.get(name); + audio.pause(); + audio.currentTime = 0; + spriteSounds.delete(name); + } + + function stopAllSounds(thisSprite = false) { + if (thisSprite) { + const spriteSounds = playingSounds.get(spriteData.id); + if (!spriteSounds) return; + + for (const audio of spriteSounds.values()) { + audio.pause(); + audio.currentTime = 0; + } + + playingSounds.delete(spriteData.id); + } else { + for (const spriteSounds of playingSounds.values()) { + for (const audio of spriteSounds.values()) { + audio.pause(); + audio.currentTime = 0; + } + } + playingSounds.clear(); + } + } + + function isMouseTouchingSprite() { + const mouse = renderer.events.pointer.global; + const bounds = sprite.getBounds(); + return bounds.contains(mouse.x, mouse.y); + } + + function setPenStatus(active) { + spriteData.penDown = !!active; + } + + function setPenColor(r, g, b) { + if (typeof r === "string") { + const [r_, g_, b_] = r.split(","); + r = +r_; + g = +g_; + b = +b_; + } + spriteData.penColor = (r << 16) | (g << 8) | b; + } + + function setPenColorHex(value) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(value); + spriteData.penColor = result + ? (parseInt(result[1], 16) << 16) | + (parseInt(result[2], 16) << 8) | + parseInt(result[3], 16) + : 0x000000; + } + + function setPenSize(size = 0) { + spriteData.penSize = Math.max(1, size); + } + + function clearPen() { + penGraphics.clear(); + } + + function toggleVisibility(bool = true) { + sprite.visible = bool; + if (spriteData.currentBubble) spriteData.currentBubble.visible = bool; + } + + eval(code); +} diff --git a/src/functions/theme.js b/src/functions/theme.js new file mode 100644 index 0000000..22f96d2 --- /dev/null +++ b/src/functions/theme.js @@ -0,0 +1,282 @@ +import "@fortawesome/fontawesome-free/css/all.min.css"; +import * as Blockly from "blockly"; +import config from "../config"; +import { cache } from "../cache"; +import { showPopup } from "./utils"; + +const root = document.documentElement; +const theme = localStorage.getItem("theme") === "dark" ?? false; +const icons = localStorage.getItem("removeIcons") === "true" ?? false; +const rarryToolbar = + localStorage.getItem("removeRarryToolbar") === "true" ?? false; +const toolboxPosition = + localStorage.getItem("toolboxPosition") || "space-between"; +const headerColor = localStorage.getItem("headerColor") || ""; +const stageLeft = localStorage.getItem("stageLeft") === "true" ?? false; + +const blockStyles = { + logic_blocks: { + colourPrimary: "#59BA57", + }, + math_blocks: { + colourPrimary: "#59BA57", + }, + text_blocks: { + colourPrimary: "#59BA57", + }, + loop_blocks: { + colourPrimary: "#FFAB19", + }, + variable_blocks: { + colourPrimary: "#FF8C1A", + }, + list_blocks: { + colourPrimary: "#E35340", + }, + procedure_blocks: { + colourPrimary: "#FF6680", + }, + motion_blocks: { + colourPrimary: "#4C97FF", + }, + looks_blocks: { + colourPrimary: "#9966FF", + }, + events_blocks: { + colourPrimary: "#e9c600", + }, + control_blocks: { + colourPrimary: "#FFAB19", + }, + json_category: { + colourPrimary: "#FF8349", + }, + set_blocks: { + colourPrimary: "#2CC2A9", + }, +}; + +const lightTheme = Blockly.Theme.defineTheme("customLightTheme", { + base: Blockly.Themes.Classic, + blockStyles: blockStyles, +}); + +const darkTheme = Blockly.Theme.defineTheme("customDarkTheme", { + base: Blockly.Themes.Classic, + blockStyles: blockStyles, + componentStyles: { + workspaceBackgroundColour: "#1a1e25", + toolboxBackgroundColour: "#303236", + toolboxForegroundColour: "#fff", + flyoutBackgroundColour: "#212327", + flyoutForegroundColour: "#ccc", + flyoutOpacity: 1, + scrollbarColour: "#797979", + insertionMarkerColour: "#fff", + insertionMarkerOpacity: 0.3, + scrollbarOpacity: 0.4, + cursorColour: "#d0d0d0", + }, +}); + +export function toggleTheme(dark, workspace) { + localStorage.setItem("theme", dark ? "dark" : "light"); + + if (dark) root.classList.add("dark"); + else root.classList.remove("dark"); + + if (workspace) workspace.setTheme(dark ? darkTheme : lightTheme); +} + +export function toggleIcons(removeIcons) { + localStorage.setItem("removeIcons", String(removeIcons)); + + if (removeIcons) root.classList.add("removeIcons"); + else root.classList.remove("removeIcons"); +} + +export function toggleRarryToolbar(removeIcon) { + localStorage.setItem("removeRarryToolbar", String(removeIcon)); + + if (removeIcon) root.classList.add("removeRarryToolbar"); + else root.classList.remove("removeRarryToolbar"); +} + +export function setToolboxPosition(pos) { + localStorage.setItem("toolboxPosition", pos); + + const header = document.querySelector("header"); + if (!header) return; + + root.classList.remove("toolbox-left", "toolbox-center", "toolbox-right"); + + if (pos === "default") return; + + root.classList.add(`toolbox-${pos}`); +} + +export function setHeaderColor(color) { + localStorage.setItem("headerColor", color); + + if (!color) root.style.removeProperty("--header-color"); + else root.style.setProperty("--header-color", color); +} + +export function toggleStageLeft(left) { + localStorage.setItem("stageLeft", String(left)); + + if (left) root.classList.add("stageLeft"); + else root.classList.remove("stageLeft"); +} + +export function setupThemeButton(workspace) { + toggleTheme(theme, workspace); + toggleIcons(icons); + toggleRarryToolbar(rarryToolbar); + toggleStageLeft(stageLeft); + setToolboxPosition(toolboxPosition); + setHeaderColor(headerColor); + + const themeButton = document.getElementById("theme-button"); + if (themeButton) + themeButton.addEventListener("click", () => + showPopup({ + title: "Appearance", + rows: [ + [ + "Theme:", + { + type: "button", + label: ' Light', + onClick: () => toggleTheme(false, workspace), + }, + { + type: "button", + label: ' Dark', + onClick: () => toggleTheme(true, workspace), + }, + ], + [ + "Show icon on buttons:", + { + type: "checkbox", + checked: + !document.documentElement.classList.contains("removeIcons"), + onChange: checked => { + toggleIcons(!checked); + }, + }, + ], + [ + "Show Rarry logo on toolbar:", + { + type: "checkbox", + checked: + !document.documentElement.classList.contains( + "removeRarryToolbar" + ), + onChange: checked => { + toggleRarryToolbar(!checked); + }, + }, + ], + [ + "Toolbar color:", + { + type: "color", + value: localStorage.getItem("headerColor") || "", + onChange: value => setHeaderColor(value), + }, + { + type: "button", + label: "Reset", + onClick: () => setHeaderColor(""), + }, + ], + [ + "Toolbar position:", + { + type: "menu", + value: localStorage.getItem("toolboxPosition") || "default", + options: [ + { label: "Space Between (default)", value: "default" }, + { label: "Left", value: "left" }, + { label: "Center", value: "center" }, + { label: "Right", value: "right" }, + ], + onChange: value => setToolboxPosition(value), + }, + ], + ...(workspace + ? [ + [ + "Renderer (applies after refresh):", + { + type: "menu", + value: localStorage.getItem("renderer"), + options: [ + { label: "Zelos (default)", value: "custom_zelos" }, + { label: "Thrasos", value: "thrasos" }, + { label: "Geras", value: "geras" }, + ], + onChange: value => localStorage.setItem("renderer", value), + }, + ], + [ + "Stage on left:", + { + type: "checkbox", + checked: + document.documentElement.classList.contains("stageLeft"), + onChange: checked => { + toggleStageLeft(checked); + }, + }, + ], + ] + : []), + ], + }) + ); +} + +export function setupUserTag() { + function setUserTag(user) { + if (user === null) { + if (cache.user === null) return; + user = cache.user; + } + + login.parentElement.innerHTML = ` + + `; + } + + const login = document.getElementById("login-button"); + if (login && localStorage.getItem("tooken") !== null) { + if (cache.user) { + setUserTag(cache.user); + } else { + fetch(`${config.apiUrl}/users/me`, { + headers: { + Authorization: localStorage.getItem("tooken"), + }, + }) + .then(response => { + if (!response.ok) + throw new Error( + "Failed to fetch user data: " + response.statusText + ); + return response.json(); + }) + .then(data => { + cache.user = data; + setUserTag(data); + }) + .catch(console.error); + } + } +} diff --git a/src/functions/threads.js b/src/functions/threads.js new file mode 100644 index 0000000..6551e5c --- /dev/null +++ b/src/functions/threads.js @@ -0,0 +1,84 @@ +export function createThreadSystem() { + const threads = new Map(); + let current = null; + let nextId = 1; + + function ensure(threadId) { + if (!threads.has(threadId)) threads.set(threadId, {}); + } + + return { + resetAll() { + threads.clear(); + current = null; + nextId = 1; + }, + + create(initialVars = {}) { + const id = `t${nextId++}`; + threads.set(id, { ...initialVars }); + return id; + }, + + enter(threadId) { + ensure(threadId); + current = threadId; + return this.getCurrentContext(); + }, + + exit() { + current = null; + }, + + set(threadId, key, value) { + ensure(threadId); + threads.get(threadId)[key] = value; + }, + get(threadId, key) { + return threads.get(threadId) ? threads.get(threadId)[key] : undefined; + }, + has(threadId, key) { + return threads.get(threadId) + ? threads.get(threadId)[key] !== undefined + : false; + }, + + getCurrentContext() { + const threadId = current; + return { + id: threadId, + vars: threadId ? threads.get(threadId) : null, + set: (k, v) => { + if (!threadId) return; + threads.get(threadId)[k] = v; + }, + get: (k) => { + if (!threadId) return undefined; + return threads.get(threadId)[k]; + }, + has: (k) => { + if (!threadId) return false; + return threads.get(threadId)[k] !== undefined; + }, + spawn: (fn, initialVars = {}) => { + const newId = `t${nextId++}`; + threads.set(newId, { ...initialVars }); + + const prev = current; + current = newId; + try { + return fn({ + id: newId, + get: (k) => threads.get(newId)[k], + set: (k, v) => (threads.get(newId)[k] = v), + }); + } finally { + current = prev; + } + }, + }; + }, + }; +}; + +export const Thread = createThreadSystem(); \ No newline at end of file diff --git a/src/functions/utils.js b/src/functions/utils.js new file mode 100644 index 0000000..ca745e3 --- /dev/null +++ b/src/functions/utils.js @@ -0,0 +1,260 @@ +let currentPopup; + +export function showNotification({ + message = "", + duration = 5000, + closable = true, +}) { + const notification = document.createElement("div"); + notification.className = "notification"; + notification.innerHTML = ` + ${message} + ${ + closable + ? '' + : "" + } + `; + + let container = document.querySelector(".notification-container"); + if (!container) { + container = document.createElement("div"); + container.className = "notification-container"; + document.body.appendChild(container); + } + + container.appendChild(notification); + + function hide() { + notification.classList.add("hide"); + setTimeout(() => notification.remove(), 300); + } + + if (closable) { + notification + .querySelector(".notification-close") + ?.addEventListener("click", hide); + } + + setTimeout(hide, duration); + + return notification; +} + +export function showPopup({ innerHTML = "", title = "", rows = [] }) { + const popup = document.createElement("div"); + popup.className = "popup"; + + if (currentPopup) currentPopup.remove(); + currentPopup = popup; + + const rowsHTML = rows + .map((row, rowIndex) => { + const rowHTML = row + .map((item, colIndex) => { + if (typeof item === "string") { + if (item === "") return; + return `${item}`; + } + + switch (item.type) { + case "custom": + return item.html || ""; + case "button": + return ``; + case "input": + return ``; + case "checkbox": + return ``; + case "textarea": + return ``; + case "label": + return `${item.text}`; + case "menu": + return ``; + case "color": + return ``; + default: + return ""; + } + }) + .join(""); + return ``; + }) + .join(""); + + popup.innerHTML = ` + `; + + document.body.appendChild(popup); + + popup.querySelector(".popup-close").addEventListener("click", () => { + currentPopup = null; + popup.remove(); + }); + + rows.forEach((row, rowIndex) => { + row.forEach((item, colIndex) => { + const el = popup.querySelector( + `[data-row="${rowIndex}"][data-col="${colIndex}"]` + ); + if (!el) return; + + if (item.type === "button" && item.onClick) { + el.addEventListener("click", () => item.onClick(popup)); + } + if (item.type === "input" && item.onInput) { + el.addEventListener("input", e => item.onInput(e.target.value, popup)); + } + if (item.type === "checkbox" && item.onChange) { + el.addEventListener("change", e => + item.onChange(e.target.checked, popup) + ); + } + if (item.type === "textarea" && item.onInput) { + el.addEventListener("input", e => item.onInput(e.target.value, popup)); + } + if (item.type === "menu" && item.onChange) { + el.addEventListener("change", e => + item.onChange(e.target.value, popup) + ); + } + if (item.type === "color" && item.onChange) { + el.addEventListener("input", e => item.onChange(e.target.value, popup)); + } + }); + }); + + return popup; +} + +export function promiseWithAbort(promiseOrFn, signal) { + try { + const p = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn; + if (!(p instanceof Promise)) return Promise.resolve(p); + + if (signal.aborted) return Promise.reject(new Error("shouldStop")); + + return Promise.race([ + p, + new Promise((_, rej) => { + signal.addEventListener("abort", () => rej(new Error("shouldStop")), { + once: true, + }); + }), + ]); + } catch (err) { + return Promise.reject(err); + } +} + +async function encodeOggWithMediaRecorder(dataURL) { + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + + const base64 = dataURL.split(",")[1]; + const raw = Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + const buffer = await audioCtx.decodeAudioData(raw.buffer); + + const src = audioCtx.createBufferSource(); + src.buffer = buffer; + + const dest = audioCtx.createMediaStreamDestination(); + src.connect(dest); + + const recorder = new MediaRecorder(dest.stream, { + mimeType: "audio/ogg", + }); + + const chunks = []; + recorder.ondataavailable = e => chunks.push(e.data); + + return new Promise(resolve => { + recorder.onstop = () => { + const blob = new Blob(chunks, { type: "audio/ogg" }); + const fr = new FileReader(); + fr.onloadend = () => resolve(fr.result); + fr.readAsDataURL(blob); + }; + + recorder.start(); + src.start(); + src.onended = () => recorder.stop(); + }); +} + +export async function compressAudio(dataURL) { + if (window.MediaRecorder && MediaRecorder.isTypeSupported("audio/ogg")) { + try { + return await encodeOggWithMediaRecorder(dataURL); + } catch (e) { + console.warn("OGG recording failed, falling back:", e); + } + } + + return dataURL; +} + +export async function compressImage(dataURL) { + if (!dataURL || typeof dataURL !== "string") return null; + if (dataURL.startsWith("data:image/webp")) return dataURL; + + return new Promise(resolve => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + resolve(canvas.toDataURL("image/webp", 0.9)); + }; + img.src = dataURL; + }); +} diff --git a/src/home.css b/src/home.css new file mode 100644 index 0000000..7c7fb8f --- /dev/null +++ b/src/home.css @@ -0,0 +1,70 @@ +.about { + display: flex; + flex-direction: column; + gap: 1.5rem; + padding: 3rem; + background-image: url("/icons/blur.png"); + background-size: cover; + background-position: center; + background-repeat: no-repeat; + text-align: center; + color: #f3f4f6; + font-size: larger; + text-shadow: 0 2px 3px black; +} + +.about * { + color: #f3f4f6; +} + +.feature-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 2rem; + justify-items: center; +} + +.feature-card { + background-color: var(--color1); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 0.5rem; + padding: 2rem; + border-radius: 1rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + max-width: 15rem; + text-shadow: none; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.feature-card:hover { + transform: translateY(-5px); +} + +.feature-card h3 { + font-size: 1.25rem; + color: var(--dark); +} + +.feature-card p { + color: var(--dark-light); + font-size: 0.95rem; +} + +.cta-section { + padding: 2rem; + text-align: center; + background-color: var(--primary); + color: white; +} + +.cta-section h2 { + font-size: 2rem; + margin-bottom: 1.5rem; +} + +button.large { + font-size: 1.5rem; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..d11c826 --- /dev/null +++ b/src/index.css @@ -0,0 +1,489 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); + +html { + --font: "Inter", sans-serif; + --dark: #2b323b; + --dark-light: #424c5a; + --primary: #833bf6; + --primary-dark: #8930dc; + --danger: #f63b3b; + --danger-dark: #dd3434; + --color1: #f3f4f6; + --color2: #e4e5e7; + --color3: #cbcdcf; + --color4: #b9bbbd; + --header-color: var(--primary); +} + +html.dark { + --dark: #e2e8f0; + --dark-light: #c8cdd4; + --primary: #833bf6; + --primary-dark: #8930dc; + --danger: #f63b3b; + --danger-dark: #dd3434; + --color1: #262d36; + --color2: #2f3741; + --color3: #3d4552; + --color4: #464f5e; + --header-color: var(--color4); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +a { + color: var(--primary-dark); + text-decoration: none; +} + +h1, +h2, +h3, +input { + color: var(--dark); +} + +body, +div#app { + font-family: var(--font); + background-color: var(--color1); + overscroll-behavior: none; + height: 100vh; + width: 100vw; + display: flex; + flex-direction: column; +} + +div#app, +div#stage { + overflow: hidden; +} + +header { + background-color: var(--header-color); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.3rem 0.5rem; + z-index: 10; + gap: 1rem; +} + +html.toolbox-left header { + justify-content: flex-start; +} + +html.toolbox-center header { + justify-content: center; +} + +html.toolbox-right header { + justify-content: flex-end; +} + +header h2, +header button:not(.white) { + color: #f3f4f6; +} + +header h1 { + font-size: 1.25rem; + font-weight: 700; + color: var(--color1); +} + +header div { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + overflow: hidden; + gap: 0.5rem; +} + +.hidden, +html.removeRarryToolbar header img.logo { + display: none !important; +} + +button, +input[type="file"] { + font-family: var(--font); + font-size: medium; + font-weight: 700; + padding: 0.5rem 0.9rem; + border-radius: 0.5rem; + border: none; + color: var(--color1); + cursor: pointer; + will-change: background-color, scale; + transition: background-color 0.1s, scale 0.1s; +} + +input[type="text"], +input[type="password"], +select, +textarea:not(.blocklyTextarea) { + font-family: var(--font); + font-size: medium; + padding: 0.5rem; + border-radius: 0.5rem; + background-color: var(--color2); + border: 1px solid var(--color4); + color: var(--dark); +} + +button { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + justify-content: center; + align-items: center; + gap: 0.3rem; + background-color: var(--dark); +} + +button:not(.tab-button):active { + scale: 0.95; +} + +input[type="file"] { + background-color: var(--primary); +} + +input[type="file"]::file-selector-button { + font-family: var(--font); + background-color: white; + color: #2b323b; + border: none; + border-radius: 0.2rem; +} + +img.button { + width: 2rem; + cursor: pointer; + will-change: scale; + transition: scale 0.1s; +} + +img.button:active { + scale: 0.95; +} + +button:hover { + background-color: var(--dark-light); +} + +button.primary { + background-color: var(--primary); +} + +button.primary:hover { + background-color: var(--primary-dark); +} + +button.danger { + background-color: var(--danger); +} + +button.danger:hover { + background-color: var(--danger-dark); +} + +button.orange { + background-color: #f18f3f; +} + +button.orange:hover { + background-color: #e28940; +} + +button.green { + background-color: #2acc66; +} + +button.green:hover { + background-color: #28b85c; +} + +button.large { + font-size: 1.5rem; +} + +html.dark header button { + color: var(--color1); +} + +html:not(.removeIcons) button:has(i:not(.stay)) { + padding-left: 0.7rem; +} + +html.removeIcons button i:not(.stay) { + display: none; +} + +@keyframes scaleIn { + from { + opacity: 0; + scale: 0; + } + + to { + opacity: 1; + scale: 1; + } +} + +@keyframes scaleOut { + from { + opacity: 1; + scale: 1; + } + to { + opacity: 0; + scale: 0; + } +} + +.popup { + position: fixed; + inset: 0; + display: flex; + justify-content: center; + align-items: center; + background: rgba(0, 0, 0, 0.5); + z-index: 9999; +} + +.popup-content { + position: relative; + border-radius: 0.5rem; + width: max(300px, 50%); + height: max(300px, 50%); + background: var(--color2); + color: var(--dark); + animation: scaleIn 0.3s ease; + overflow: auto; +} + +.popup-content header { + padding: 0.5rem; + padding-left: 0.7rem; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + width: 100%; + height: fit-content; +} + +.popup-body textarea.extension-code-input { + resize: vertical; + width: 100%; +} + +.popup-body, +.popup-row { + display: flex; + gap: 0.5rem; +} + +.popup-body { + padding: 0.5rem; + flex-direction: column; +} + +.popup-row { + flex-direction: row; + align-items: center; +} + +.popup-close { + margin-left: auto; + font-size: 1.2rem; + padding: 0.5rem; +} + +.notification-container { + position: fixed; + top: 1rem; + right: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + z-index: 10000; +} + +.notification { + background: var(--color2); + color: var(--dark); + border-radius: 0.5rem; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25); + padding: 0.4rem; + padding-left: 0.8rem; + animation: scaleIn 0.3s ease forwards; +} + +.notification.hide { + animation: scaleOut 0.3s ease forwards; +} + +.notification-content { + display: flex; + align-items: center; + justify-content: space-between; +} + +.notification-close { + background: none; + color: var(--dark); + padding: 0.5rem; + font-size: 1rem; +} + +.notification-close:hover { + background: var(--danger); +} + +input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-color: var(--color3); + cursor: pointer; + width: fit-content; + height: fit-content; + margin: 0; + padding: 0.7rem; + border-radius: 0.4rem; +} + +input[type="checkbox"]:checked { + background-color: var(--primary); + position: relative; +} + +input[type="checkbox"]:checked::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 4px; + height: 9px; + border: solid #f3f4f6; + border-width: 0 2px 2px 0; + transform: translate(-50%, -50%) rotate(45deg); +} + +input[type="color"] { + background: none; + appearance: none; + -webkit-appearance: none; + border: none; + padding: 0; + width: 32px; + height: 32px; + cursor: pointer; +} + +.userTag { + padding: 0.5rem; + border-radius: 0.5rem; + background-color: var(--primary-dark); + color: #e2e8f0; +} + +.userTag a { + color: #e2e8f0; +} + +html.dark .userTag { + background-color: var(--color3); +} + +.userTag img { + width: 2rem; + height: 2rem; + border-radius: 20%; +} + +footer { + padding: 1.5rem; + text-align: center; + background-color: var(--color2); + color: var(--dark-light); + font-size: 0.9rem; + margin-top: auto; +} + +.dropdown-content button:hover { + background-color: var(--dark-light); +} + +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--color2); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--color3); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color4); +} + +:disabled { + opacity: 0.5; + pointer-events: none; +} + +.project-name-input { + padding: 8px 16px; + border: 2px solid var(--border-color); + border-radius: 8px; + background: var(--background-primary); + color: var(--text-primary); + font-size: 15px; + font-weight: 600; + min-width: 220px; + text-align: center; + transition: all 0.2s ease; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.project-name-input:hover { + border-color: var(--primary-color); + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2); +} + +.project-name-input:focus { + outline: none; + border-color: var(--primary-color); + background: var(--background-secondary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +.project-name-input::placeholder { + color: var(--text-secondary); + opacity: 0.5; + font-weight: 500; +} + +header > div { + display: flex; + align-items: center; + gap: 12px; +} + +.project-name-input::before { + content: "📝"; + margin-right: 8px; +} \ No newline at end of file diff --git a/src/login.css b/src/login.css new file mode 100644 index 0000000..d7f2264 --- /dev/null +++ b/src/login.css @@ -0,0 +1,24 @@ +body { + background-image: url("/icons/blur.png"); + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + +body, +.info { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +.info { + border-radius: 1rem; + gap: 1.5rem; + padding: 2rem; + font-size: large; + background-color: var(--color1); + color: var(--dark); +} \ No newline at end of file diff --git a/src/scripts/editor.js b/src/scripts/editor.js new file mode 100644 index 0000000..6901146 --- /dev/null +++ b/src/scripts/editor.js @@ -0,0 +1,2743 @@ +import "@fortawesome/fontawesome-free/css/all.min.css"; + +import * as Blockly from "blockly"; +import * as BlocklyJS from "blockly/javascript"; +import * as PIXI from "pixi.js-legacy"; +import pako from "pako"; +import JSZip from "jszip"; +import { io } from "socket.io-client"; + +import CustomRenderer from "../functions/render.js"; +import { setupThemeButton } from "../functions/theme.js"; +import { + compressAudio, + compressImage, + promiseWithAbort, + showNotification, + showPopup, +} from "../functions/utils.js"; + +import { SpriteChangeEvents } from "../functions/patches.js"; +import { + registerExtension, + setupExtensions, +} from "../functions/extensionManager.js"; +import { Thread } from "../functions/threads.js"; +import { runCodeWithFunctions } from "../functions/runCode.js"; + +import config from "../config"; + +BlocklyJS.javascriptGenerator.addReservedWords( + "whenFlagClicked,moveSteps,getAngle,getMousePosition,sayMessage,waitOneFrame,wait,switchCostume,setSize,setAngle,projectTime,isKeyPressed,isMouseButtonPressed,getCostumeSize,getSpriteScale,_startTween,startTween,soundProperties,setSoundProperty,playSound,stopSound,stopAllSounds,isMouseTouchingSprite,setPenStatus,setPenColor,setPenColorHex,setPenSize,clearPen,Thread,fastExecution,BUBBLE_TEXTSTYLE,sprite,renderer,stage,costumeMap,soundMap,stopped,code,penGraphics,runningScripts,findOrFilterItem,registerEvent,triggerCustomEvent,hideSprite,showSprite,MyFunctions" +); + +import.meta.glob("../blocks/**/*.js", { eager: true }); + +Thread.resetAll(); + +let currentSocket = null; +let currentRoom = null; +let amHost = false; +let invitesEnabled = true; +let connectedUsers = []; + +const wrapper = document.getElementById("stage-wrapper"); +const stageContainer = document.getElementById("stage"); +const costumesList = document.getElementById("costumes-list"); +const loadInput = document.getElementById("load-input"); +const loadButton = document.getElementById("load-button"); +const deleteSpriteButton = document.getElementById("delete-sprite-button"); +const runButton = document.getElementById("run-button"); +const tabButtons = document.querySelectorAll(".tab-button"); +const tabContents = document.querySelectorAll(".tab-content"); +const fullscreenButton = document.getElementById("fullscreen-button"); + +export const BASE_WIDTH = 480; +export const BASE_HEIGHT = 360; +const MAX_HTTP_BUFFER = 20 * 1024 * 1024; + +const app = new PIXI.Application({ + width: BASE_WIDTH, + height: BASE_HEIGHT, + backgroundColor: 0xffffff, + powerPreference: "high-performance", +}); +app.stageWidth = BASE_WIDTH; +app.stageHeight = BASE_HEIGHT; + +export function resizeCanvas() { + if (!wrapper) return; + + const w = wrapper.clientWidth; + const h = wrapper.clientHeight; + + app.renderer.resize(w, h); + + const scale = Math.min(w / BASE_WIDTH, h / BASE_HEIGHT); + + app.stage.scale.set(scale); + + app.stage.x = w / 2; + app.stage.y = h / 2; +} +resizeCanvas(); + +stageContainer.appendChild(app.view); + +let penGraphics; +function createPenGraphics() { + if (penGraphics && !penGraphics._destroyed) return; + penGraphics = new PIXI.Graphics(); + penGraphics.clear(); + app.stage.addChildAt(penGraphics, 0); + window.penGraphics = penGraphics; +} +createPenGraphics(); + +export let projectVariables = {}; +export let sprites = []; +export let activeSprite = null; +window.projectSounds = []; +window.projectCostumes = ["default"]; +window.projectBackdrops = []; +let currentBackdrop = null; +let projectName = "Untitled Project"; + +Blockly.blockRendering.register("custom_zelos", CustomRenderer); + +let renderer = localStorage.getItem("renderer"); +if (!renderer) { + localStorage.setItem("renderer", "custom_zelos"); + renderer = "custom_zelos"; +} + +const blocklyDiv = document.getElementById("blocklyDiv"); +const toolbox = document.getElementById("toolbox"); +window.setBackdrop = setBackdrop; +export const workspace = Blockly.inject(blocklyDiv, { + toolbox: toolbox, + scrollbars: true, + trashcan: true, + renderer, + + grid: { + spacing: 20, + length: 1, + colour: "#ccc", + snap: false + }, + + zoom: { + controls: true, + wheel: true, + startScale: 0.9, + maxScale: 3, + minScale: 0.3, + scaleSpeed: 1.2, + }, + + plugins: { + connectionChecker: "CustomChecker", + }, +}); + +const observer = new ResizeObserver(() => { + Blockly.svgResize(workspace); +}); + +observer.observe(blocklyDiv); + +setupThemeButton(workspace); + +workspace.registerToolboxCategoryCallback("GLOBAL_VARIABLES", function (_) { + const xmlList = []; + + const button = Blockly.utils.xml.createElement("button"); + button.setAttribute("text", "Create variable"); + button.setAttribute("callbackKey", "ADD_GLOBAL_VARIABLE"); + xmlList.push(button); + + if (Object.keys(projectVariables).length === 0) return xmlList; + + const valueShadow = Blockly.utils.xml.createElement("value"); + valueShadow.setAttribute("name", "VALUE"); + const shadow = Blockly.utils.xml.createElement("shadow"); + shadow.setAttribute("type", "math_number"); + const field = Blockly.utils.xml.createElement("field"); + field.setAttribute("name", "NUM"); + field.textContent = "0"; + shadow.appendChild(field); + valueShadow.appendChild(shadow); + + const set = Blockly.utils.xml.createElement("block"); + set.setAttribute("type", "set_global_var"); + set.appendChild(valueShadow.cloneNode(true)); + xmlList.push(set); + + const change = Blockly.utils.xml.createElement("block"); + change.setAttribute("type", "change_global_var"); + change.appendChild(valueShadow); + xmlList.push(change); + + for (const name in projectVariables) { + const get = Blockly.utils.xml.createElement("block"); + get.setAttribute("type", "get_global_var"); + const varField = Blockly.utils.xml.createElement("field"); + varField.setAttribute("name", "VAR"); + varField.textContent = name; + get.appendChild(varField); + xmlList.push(get); + } + + return xmlList; +}); + +function addGlobalVariable(name, emit = false) { + if (!name) name = prompt("New variable name:"); + if (name) { + let newName = name, + count = 0; + while (newName in projectVariables) { + count++; + newName = name + count; + } + + projectVariables[newName] = 0; + + if (emit && currentSocket && currentRoom) + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addVariable", + data: newName, + }); + } +} + +workspace.registerButtonCallback("ADD_GLOBAL_VARIABLE", () => + addGlobalVariable(null, true) +); + +function dynamicFunctionsCategory(workspace) { + const xmlList = []; + + const block = document.createElement("block"); + block.setAttribute("type", "functions_definition"); + xmlList.push(block); + + const blockReturnValue = document.createElement("value"); + blockReturnValue.setAttribute("name", "VALUE"); + blockReturnValue.innerHTML = + 'name'; + + const blockReturn = document.createElement("block"); + blockReturn.setAttribute("type", "functions_return"); + blockReturn.appendChild(blockReturnValue); + xmlList.push(blockReturn); + + const sep = document.createElement("sep"); + sep.setAttribute("gap", "50"); + xmlList.push(sep); + + const defs = workspace + .getTopBlocks(false) + .filter(b => b.type === "functions_definition"); + + defs.forEach(defBlock => { + const block = document.createElement("block"); + block.setAttribute("type", "functions_call"); + + const mutation = document.createElement("mutation"); + mutation.setAttribute("functionId", defBlock.functionId_); + mutation.setAttribute("shape", defBlock.blockShape_); + mutation.setAttribute("items", defBlock.argTypes_.length); + mutation.setAttribute( + "returntypes", + JSON.stringify(defBlock.returnTypes_ || []) + ); + + for (let i = 0; i < defBlock.argTypes_.length; i++) { + const item = document.createElement("item"); + item.setAttribute("type", defBlock.argTypes_[i]); + item.setAttribute("name", defBlock.argNames_[i]); + mutation.appendChild(item); + } + + block.appendChild(mutation); + xmlList.push(block); + }); + + return xmlList; +} + +workspace.registerToolboxCategoryCallback( + "FUNCTIONS_CATEGORY", + dynamicFunctionsCategory +); + +function addSprite(id, emit = false) { + const texture = PIXI.Texture.from("./icons/ddededodediamante.png", { + crossorigin: true, + }); + const sprite = new PIXI.Sprite(texture); + sprite.anchor.set(0.5); + sprite.x = 0; + sprite.y = 0; + sprite.scale._parentScaleEvent = sprite; + app.stage.addChild(sprite); + + if (!id) id = "sprite-" + Date.now(); + + const spriteData = { + id, + pixiSprite: sprite, + code: "", + costumes: [{ name: "default", texture: texture }], + sounds: [], + }; + sprites.push(spriteData); + + if (emit && currentSocket && currentRoom) + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addSprite", + data: id, + }); + + return spriteData; +} + +function setActiveSprite(spriteData) { + activeSprite = spriteData; + renderSpritesList(true); + + const workspaceContainer = workspace.getParentSvg().parentNode; + + if (!spriteData) { + deleteSpriteButton.disabled = true; + workspaceContainer.style.display = "none"; + return; + } else { + deleteSpriteButton.disabled = false; + workspaceContainer.style.display = ""; + } + + Blockly.Events.disable(); + + const xmlText = + activeSprite.code || + ''; + const xmlDom = Blockly.utils.xml.textToDom(xmlText); + Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace); + + Blockly.Events.enable(); +} + +function deleteSprite(id, emit = false) { + const sprite = sprites.find(s => s.id === id); + if (!sprite) return; + + if (sprite.currentBubble) { + app.stage.removeChild(sprite.currentBubble); + sprite.currentBubble = null; + } + + app.stage.removeChild(sprite.pixiSprite); + + const index = sprites.indexOf(sprite); + + if (emit && currentSocket && currentRoom) + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "removeSprite", + data: id, + }); + + // ADD THIS CODE to remove costumes and sounds from global arrays: + sprite.costumes.forEach(costume => { + // Check if this costume exists in any other sprite + const existsElsewhere = sprites.some(s => + s.id !== sprite.id && s.costumes.some(c => c.name === costume.name) + ); + if (!existsElsewhere) { + window.projectCostumes = window.projectCostumes.filter(c => c !== costume.name); + } + }); + + sprite.sounds.forEach(sound => { + // Check if this sound exists in any other sprite + const existsElsewhere = sprites.some(s => + s.id !== sprite.id && s.sounds.some(snd => snd.name === sound.name) + ); + if (!existsElsewhere) { + window.projectSounds = window.projectSounds.filter(s => s !== sound.name); + } + }); + + sprites = sprites.filter(s => s.id !== sprite.id); + + workspace.clear(); + + if (sprites.length > 0) { + setActiveSprite(sprites[Math.min(index, sprites.length - 1)]); + } else { + setActiveSprite(null); + } + + // ADD THIS LINE to refresh toolbox: + workspace.updateToolbox(document.getElementById('toolbox')); +} + +function renderSpritesList(renderOthers = false) { + const listEl = document.getElementById("sprites-list"); + listEl.innerHTML = ""; + if (sprites.length === 0) listEl.style.display = "none"; + else listEl.style.display = ""; + + sprites.forEach(spriteData => { + const spriteIconContainer = document.createElement("div"); + if (activeSprite && activeSprite.id === spriteData.id) + spriteIconContainer.className = "active"; + + const img = new Image(50, 50); + img.style.objectFit = "contain"; + const costumeTexture = spriteData.pixiSprite.texture; + const baseTex = costumeTexture.baseTexture; + + if (baseTex.valid) { + img.src = baseTex.resource?.url || ""; + } else { + baseTex.on("loaded", () => { + img.src = baseTex.resource?.url || ""; + }); + } + + spriteIconContainer.appendChild(img); + spriteIconContainer.onclick = () => setActiveSprite(spriteData); + listEl.appendChild(spriteIconContainer); + }); + + if (renderOthers === true) { + renderSpriteInfo(); + renderCostumesList(); + renderSoundsList(); + } +} + +function renderSpriteInfo() { + const infoEl = document.getElementById("sprite-info"); + + if (!activeSprite) { + infoEl.innerHTML = "

Select a sprite to see its info.

"; + } else { + const sprite = activeSprite.pixiSprite; + + infoEl.innerHTML = ` +

${Math.round(sprite.x)}, ${Math.round(-sprite.y)}

+

${Math.round(sprite.angle)}º

+

size: ${Math.round(((sprite.scale.x + sprite.scale.y) / 2) * 100)}

+

+ `; + } +} + +function createRenameableLabel(initialName, onRename) { + const container = document.createElement("div"); + container.style.display = "flex"; + container.style.alignItems = "center"; + container.style.gap = "8px"; + + const nameLabel = document.createElement("p"); + nameLabel.textContent = initialName; + nameLabel.style.margin = "0"; + nameLabel.style.cursor = "pointer"; + + function startRename() { + let willRename = true; + + const input = document.createElement("input"); + input.type = "text"; + input.value = nameLabel.textContent; + input.style.flexGrow = "1"; + + container.replaceChild(input, nameLabel); + input.focus(); + input.select(); + + function commit() { + if (willRename) { + const newName = input.value.trim(); + if (newName && newName !== nameLabel.textContent) { + onRename(newName); + nameLabel.textContent = newName; + } + } + container.replaceChild(nameLabel, input); + } + + input.addEventListener("blur", commit); + input.addEventListener("keydown", e => { + if (e.key === "Enter") input.blur(); + else if (e.key === "Escape") { + willRename = false; + input.blur(); + } + }); + } + + nameLabel.addEventListener("click", startRename); + container.appendChild(nameLabel); + + return container; +} + +function createDeleteButton(onDelete) { + const img = document.createElement("img"); + img.src = "icons/trash.svg"; + img.className = "button"; + img.draggable = false; + img.onclick = onDelete; + return img; +} + +function renderCostumesList() { + costumesList.innerHTML = ""; + + if (!activeSprite || !activeSprite.costumes) return; + + activeSprite.costumes.forEach((costume, index) => { + const costumeContainer = document.createElement("div"); + costumeContainer.className = "costume-container"; + + const img = new Image(60, 60); + img.style.objectFit = "contain"; + img.src = costume.texture.baseTexture.resource.url; + + const renameableLabel = createRenameableLabel(costume.name, newName => { + const oldName = costume.name; + costume.name = newName; + + // ADD THIS CODE: + const oldIndex = window.projectCostumes.indexOf(oldName); + if (oldIndex !== -1 && !window.projectCostumes.includes(newName)) { + window.projectCostumes[oldIndex] = newName; + workspace.updateToolbox(document.getElementById('toolbox')); + } + + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "renameCostume", + data: { + spriteId: activeSprite.id, + oldName, + newName, + }, + }); + } + }); + + const _texture = costume.texture.baseTexture || costume.texture; + const sizeLabel = document.createElement("span"); + sizeLabel.className = "smallLabel"; + sizeLabel.textContent = "Loading..."; + if (_texture.valid) { + sizeLabel.textContent = `${_texture.width}x${_texture.height}`; + } else { + _texture.once("update", () => { + sizeLabel.textContent = `${_texture.width}x${_texture.height}`; + }); + } + + const deleteBtn = createDeleteButton(() => { + const deleted = activeSprite.costumes[index]; + activeSprite.costumes.splice(index, 1); + + // ADD THIS CODE to remove from global array if not used elsewhere: + if (deleted) { + const existsElsewhere = sprites.some(s => + s.id !== activeSprite.id && s.costumes.some(c => c.name === deleted.name) + ); + if (!existsElsewhere) { + window.projectCostumes = window.projectCostumes.filter(c => c !== deleted.name); + } + } + + if (activeSprite.costumes.length > 0) { + activeSprite.pixiSprite.texture = activeSprite.costumes[0].texture; + } else { + activeSprite.pixiSprite.texture = PIXI.Texture.EMPTY; + } + renderCostumesList(); + + // ADD THIS LINE to refresh toolbox: + workspace.updateToolbox(document.getElementById('toolbox')); + + if (currentSocket && currentRoom && deleted) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "deleteCostume", + data: { + spriteId: activeSprite.id, + name: deleted.name, + }, + }); + } + }); + + costumeContainer.appendChild(img); + costumeContainer.appendChild(renameableLabel); + costumeContainer.appendChild(deleteBtn); + costumeContainer.appendChild(sizeLabel); + + costumesList.appendChild(costumeContainer); + }); +} + +function renderSoundsList() { + const soundsList = document.getElementById("sounds-list"); + soundsList.innerHTML = ""; + + if (!activeSprite || !activeSprite.sounds) return; + + activeSprite.sounds.forEach((sound, index) => { + const container = document.createElement("div"); + container.className = "sound-container"; + + let sizeBytes = 0; + if (sound.dataURL) { + const base64Length = + sound.dataURL.length - (sound.dataURL.indexOf(",") + 1); + sizeBytes = Math.floor((base64Length * 3) / 4); + } + + const renameableLabel = createRenameableLabel(sound.name, newName => { + const oldName = sound.name; + sound.name = newName; + + // ADD THIS CODE: + const oldIndex = window.projectSounds.indexOf(oldName); + if (oldIndex !== -1 && !window.projectSounds.includes(newName)) { + window.projectSounds[oldIndex] = newName; + workspace.updateToolbox(document.getElementById('toolbox')); + } + + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "renameSound", + data: { + spriteId: activeSprite.id, + oldName, + newName, + }, + }); + } + }); + + let sizeLabel; + if (typeof sizeBytes === "number" && sizeBytes > 0) { + sizeLabel = document.createElement("span"); + sizeLabel.className = "smallLabel"; + + const sizeKB = sizeBytes / 1024; + if (sizeKB < 1024) { + sizeLabel.textContent = `${sizeKB.toFixed(2)} KB`; + } else { + sizeLabel.textContent = `${(sizeKB / 1024).toFixed(2)} MB`; + } + } + + const playButton = document.createElement("img"); + playButton.src = "icons/play.svg"; + playButton.className = "button"; + playButton.draggable = false; + playButton.onclick = () => { + if (playButton.audio) { + playButton.audio.pause(); + playButton.audio.currentTime = 0; + playButton.src = "icons/play.svg"; + playButton.audio = null; + } else { + const audio = new Audio(sound.dataURL); + playButton.audio = audio; + playButton.src = "icons/stopAudio.svg"; + + audio.addEventListener("ended", () => { + if (playButton.audio === audio) { + playButton.src = "icons/play.svg"; + playButton.audio = null; + } + }); + + audio.play(); + } + }; + + const deleteBtn = createDeleteButton(() => { + const deleted = activeSprite.sounds[index]; + activeSprite.sounds.splice(index, 1); + + if (deleted) { + const existsElsewhere = sprites.some(s => + s.id !== activeSprite.id && s.sounds.some(snd => snd.name === deleted.name) + ); + if (!existsElsewhere) { + window.projectSounds = window.projectSounds.filter(s => s !== deleted.name); + } + } + + if (playButton.audio) { + playButton.audio.pause(); + playButton.audio.currentTime = 0; + playButton.audio = null; + } + renderSoundsList(); + + workspace.updateToolbox(document.getElementById('toolbox')); + + if (currentSocket && currentRoom && deleted) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "deleteSound", + data: { + spriteId: activeSprite.id, + name: deleted.name, + }, + }); + } + }); + + container.appendChild(renameableLabel); + container.appendChild(playButton); + container.appendChild(deleteBtn); + if (sizeLabel) container.appendChild(sizeLabel); + soundsList.appendChild(container); + }); +} + +function renderBackdropsList() { + const listEl = document.getElementById("backdrops-list"); + const deleteBtn = document.getElementById("delete-backdrop-button"); + + if (!listEl) return; + + listEl.innerHTML = ""; + + if (window.projectBackdrops.length === 0) { + listEl.style.display = "none"; + if (deleteBtn) deleteBtn.disabled = true; + } else { + listEl.style.display = ""; + if (deleteBtn) deleteBtn.disabled = false; + } + + window.projectBackdrops.forEach((backdrop, index) => { + const backdropContainer = document.createElement("div"); + if (currentBackdrop === index) { + backdropContainer.className = "active"; + } + + const img = new Image(); + img.style.objectFit = "cover"; + + const baseTex = backdrop.texture.baseTexture; + if (baseTex.valid) { + img.src = baseTex.resource?.url || ""; + } else { + baseTex.on("loaded", () => { + img.src = baseTex.resource?.url || ""; + }); + } + + backdropContainer.appendChild(img); + backdropContainer.onclick = () => setBackdrop(index); + backdropContainer.title = backdrop.name; + listEl.appendChild(backdropContainer); + }); +} + +function setBackdrop(index) { + if (!window.projectBackdrops || window.projectBackdrops.length === 0) { + app.renderer.backgroundColor = 0xffffff; + currentBackdrop = null; + return; + } + + if (index < 0 || index >= window.projectBackdrops.length) { + // Clear backdrop + app.renderer.backgroundColor = 0xffffff; + currentBackdrop = null; + + // Remove any existing backdrop sprite + const oldBackdrop = app.stage.children.find(child => child.isBackdrop); + if (oldBackdrop) { + app.stage.removeChild(oldBackdrop); + } + + renderBackdropsList(); + return; + } + + currentBackdrop = index; + const backdrop = window.projectBackdrops[index]; + + if (backdrop && backdrop.texture) { + // Remove old backdrop sprite if exists + const oldBackdrop = app.stage.children.find(child => child.isBackdrop); + if (oldBackdrop) { + app.stage.removeChild(oldBackdrop); + } + + // Create new backdrop sprite + const backdropSprite = new PIXI.Sprite(backdrop.texture); + backdropSprite.isBackdrop = true; + backdropSprite.anchor.set(0.5); + backdropSprite.x = 0; + backdropSprite.y = 0; + + // Scale to cover the stage + const scaleX = BASE_WIDTH / backdrop.texture.width; + const scaleY = BASE_HEIGHT / backdrop.texture.height; + const scale = Math.max(scaleX, scaleY); + backdropSprite.scale.set(scale); + + // Add at index 0 or right after penGraphics + const penIndex = app.stage.getChildIndex(penGraphics); + app.stage.addChildAt(backdropSprite, penIndex); + + backdrop.sprite = backdropSprite; + } + + renderBackdropsList(); + + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "setBackdrop", + data: backdrop.name, // CHANGED: Send name instead of index + }); + } +} + +function setBackdropByName(name) { + if (!name || !window.projectBackdrops || window.projectBackdrops.length === 0) { + setBackdrop(-1); + return; + } + + const index = window.projectBackdrops.findIndex(b => b.name === name); + if (index === -1) { + console.warn(`Backdrop "${name}" not found`); + return; + } + + setBackdrop(index); +} + +// Make it globally available +window.setBackdropByName = setBackdropByName; + +function addBackdrop(name, textureData, emit = false) { + const texture = PIXI.Texture.from(textureData); + + let uniqueName = name; + let counter = 1; + while (window.projectBackdrops.some(b => b.name === uniqueName)) { + counter++; + uniqueName = `${name}_${counter}`; + } + + const backdropSprite = new PIXI.Sprite(texture); + backdropSprite.isBackdrop = true; + backdropSprite.anchor.set(0.5); + backdropSprite.x = 0; + backdropSprite.y = 0; + + const scaleX = BASE_WIDTH / texture.width; + const scaleY = BASE_HEIGHT / texture.height; + const scale = Math.max(scaleX, scaleY); + backdropSprite.scale.set(scale); + + window.projectBackdrops.push({ + name: uniqueName, + texture, + sprite: backdropSprite, + data: textureData, + }); + + renderBackdropsList(); + workspace.updateToolbox(document.getElementById('toolbox')); + + if (emit && currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addBackdrop", + data: { + name: uniqueName, + texture: textureData, + }, + }); + } + + return window.projectBackdrops.length - 1; +} + +function deleteBackdrop(index, emit = false) { + if (index < 0 || index >= window.projectBackdrops.length) return; + + const backdrop = window.projectBackdrops[index]; + + // Remove sprite from stage if it's current + if (currentBackdrop === index && backdrop.sprite) { + app.stage.removeChild(backdrop.sprite); + currentBackdrop = null; + app.renderer.backgroundColor = 0xffffff; + } + + window.projectBackdrops.splice(index, 1); + + // Adjust currentBackdrop index if needed + if (currentBackdrop !== null && currentBackdrop > index) { + currentBackdrop--; + } else if (currentBackdrop === index) { + currentBackdrop = null; + } + + renderBackdropsList(); + workspace.updateToolbox(document.getElementById('toolbox')); + + if (emit && currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "deleteBackdrop", + data: index, + }); + } +} + +export function calculateBubblePosition( + sprite, + bubbleWidth, + bubbleHeight, + tailHeight = 15 +) { + let bubbleX = sprite.x - bubbleWidth / 2; + let bubbleY = sprite.y - sprite.height / 2 - bubbleHeight - tailHeight; + + bubbleX = Math.max( + Math.min(bubbleX, app.stageWidth / 2), + -app.stageWidth / 2 - bubbleWidth + ); + bubbleY = Math.max( + Math.min(bubbleY, app.stageHeight / 2 - bubbleHeight), + -app.stageHeight / 2 + ); + + return { x: bubbleX, y: bubbleY }; +} + +const keysPressed = {}; +const mouseButtonsPressed = {}; +const playingSounds = new Map(); + +let currentRunController = null; + +let eventRegistry = { + flag: [], + key: new Map(), + stageClick: [], + timer: [], + interval: [], + custom: new Map(), +}; + +let _activeEventThreadsCount = 0; +const activeEventThreads = {}; + +Object.defineProperty(activeEventThreads, "count", { + get() { + return _activeEventThreadsCount; + }, + set(value) { + _activeEventThreadsCount = Math.max(0, value); + updateRunButtonState(); + }, +}); + +function updateRunButtonState() { + if (runningScripts.length > 0 || activeEventThreads.count > 0) { + runButton.classList.add("active"); + } else { + runButton.classList.remove("active"); + } +} + +const runningScripts = []; + +function stopAllScripts() { + if (currentRunController) { + try { + currentRunController.abort(); + } catch (e) {} + currentRunController = null; + } + + for (const i of runningScripts) { + if (i.type === "timeout") clearTimeout(i.id); + else if (i.type === "interval") clearInterval(i.id); + else if (i.type === "raf") cancelAnimationFrame(i.id); + } + runningScripts.length = 0; + + for (const spriteSounds of playingSounds.values()) { + for (const audio of spriteSounds.values()) { + try { + audio.pause(); + audio.currentTime = 0; + } catch (e) {} + } + } + playingSounds.clear(); + + for (const k in keysPressed) delete keysPressed[k]; + for (const k in mouseButtonsPressed) delete mouseButtonsPressed[k]; + + for (const type in eventRegistry) { + if (Array.isArray(eventRegistry[type])) { + eventRegistry[type].length = 0; + } else if (eventRegistry[type] instanceof Map) { + eventRegistry[type].clear(); + } + } + + Thread.resetAll(); + activeEventThreads.count = 0; + + for (const spriteData of sprites) { + const bubble = spriteData.currentBubble; + if (bubble) { + if (bubble.destroy) bubble.destroy({ children: true }); + spriteData.currentBubble = null; + } + + if (spriteData.sayTimeout) { + clearTimeout(spriteData.sayTimeout); + spriteData.sayTimeout = null; + } + } +} + +async function runCode() { + stopAllScripts(); + + await new Promise(r => requestAnimationFrame(r)); + + runButton.classList.add("active"); + + const controller = new AbortController(); + const signal = controller.signal; + currentRunController = controller; + + let projectStartedTime = Date.now(); + + try { + for (const spriteData of sprites) { + const tempWorkspace = new Blockly.Workspace({ + readOnly: true, + plugins: { + connectionChecker: "CustomChecker", + }, + }); + + const xmlText = spriteData.code || ""; + const xmlDom = Blockly.utils.xml.textToDom(xmlText); + Blockly.Xml.domToWorkspace(xmlDom, tempWorkspace); + + const code = BlocklyJS.javascriptGenerator.workspaceToCode(tempWorkspace); + tempWorkspace.dispose(); + + try { + runCodeWithFunctions({ + code, + projectStartedTime, + spriteData, + app, + eventRegistry, + mouseButtonsPressed, + keysPressed, + playingSounds, + runningScripts, + signal, + penGraphics, + activeEventThreads, + }); + } catch (e) { + console.error(`Error processing code for sprite ${spriteData.id}:`, e); + } + } + + const results = await Promise.allSettled( + eventRegistry.flag.map(entry => promiseWithAbort(entry.cb, signal)) + ); + + results.forEach(res => { + if (res.status === "rejected" && res.reason?.message !== "shouldStop") { + console.error("Error running flag event:", res.reason); + } + }); + + for (const entry of eventRegistry.timer) { + const id = setTimeout(() => entry.cb(), entry.value * 1000); + runningScripts.push({ type: "timeout", id }); + } + + for (const entry of eventRegistry.interval) { + const id = setInterval(() => entry.cb(), entry.seconds * 1000); + runningScripts.push({ type: "interval", id }); + } + } catch (err) { + console.error("Error running project:", err); + stopAllScripts(); + } finally { + updateRunButtonState(); + } +} + +app.view.addEventListener("click", () => { + for (const entry of eventRegistry.stageClick) { + entry.cb(); + } +}); + +document.getElementById("add-sprite-button").addEventListener("click", () => { + let spriteData = addSprite(null, true); + setActiveSprite(spriteData); +}); + +deleteSpriteButton.addEventListener("click", () => + deleteSprite(activeSprite.id, true) +); + +runButton.addEventListener("click", runCode); +document + .getElementById("stop-button") + .addEventListener("click", stopAllScripts); + +tabButtons.forEach(button => { + button.addEventListener("click", () => { + const tab = button.dataset.tab; + if (tab !== "sounds") { + document.querySelectorAll("#sounds-list .button").forEach(i => { + if (i.audio) { + i.audio.pause(); + i.audio.currentTime = 0; + i.audio = null; + i.src = "icons/play.svg"; + } + }); + } + + tabButtons.forEach(i => { + i.classList.add("inactive"); + }); + + button.classList.remove("inactive"); + + tabContents.forEach(content => { + content.classList.toggle("active", content.id === `${tab}-tab`); + }); + + if (tab === "code") { + setTimeout(() => Blockly.svgResize(workspace), 0); + } else if (tab === "costumes") { + renderCostumesList(); + } else if (tab === "sounds") { + renderSoundsList(); + } + }); +}); + +export async function getProject() { + const spritesData = await Promise.all( + sprites.map(async sprite => { + const costumesData = await Promise.all( + sprite.costumes.map(async c => { + let dataURL; + const url = c?.texture?.baseTexture?.resource?.url; + if (typeof url === "string" && url.startsWith("data:")) { + dataURL = url; + } else { + dataURL = await app.renderer.extract.base64( + new PIXI.Sprite(c.texture) + ); + } + return { + name: c.name, + data: dataURL, + }; + }) + ); + + return { + id: sprite.id, + code: sprite.code, + costumes: costumesData, + sounds: sprite.sounds.map(s => ({ name: s.name, data: s.dataURL })), + data: { + x: sprite.pixiSprite.x, + y: sprite.pixiSprite.y, + scale: { + x: sprite.pixiSprite.scale.x ?? 1, + y: sprite.pixiSprite.scale.y ?? 1, + }, + angle: sprite.pixiSprite.angle, + currentCostume: sprite.costumes.findIndex( + c => c.texture === sprite.pixiSprite.texture + ), + }, + }; + }) + ); + + const backdropsData = window.projectBackdrops.map(backdrop => ({ + name: backdrop.name, + data: backdrop.data, + })); + + return { + sprites: spritesData, + extensions: activeExtensions, + variables: projectVariables ?? {}, + backdrops: backdropsData, + currentBackdrop: currentBackdrop, + projectName: projectName, + }; +} + +async function saveProject() { + const zip = new JSZip(); + const json = { + sprites: [], + extensions: activeExtensions, + variables: projectVariables ?? {}, + backdrops: [], // ADD THIS + currentBackdrop: currentBackdrop, // ADD THIS + projectName: projectName, + }; + const toUint8Array = base64 => + Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + + await Promise.all( + sprites.map(async sprite => { + const spriteId = sprite.id; + + const costumeEntries = ( + await Promise.all( + sprite.costumes.map(async c => { + let dataURL; + const url = c?.texture?.baseTexture?.resource?.url; + if (typeof url === "string" && url.startsWith("data:")) { + dataURL = url; + } else { + dataURL = await app.renderer.extract.base64( + new PIXI.Sprite(c.texture) + ); + } + + const processed = await compressImage(dataURL); + if (!processed) return null; + + const base64 = processed.split(",")[1]; + const binary = toUint8Array(base64); + const fileName = `${spriteId}.c.${c.name}.webp`; + zip.file(fileName, binary, { binary: true }); + return { name: c.name, path: fileName }; + }) + ) + ).filter(Boolean); + + const soundEntries = ( + await Promise.all( + sprite.sounds.map(async s => { + const processed = await compressAudio(s.dataURL); + if (!processed) return null; + + const base64 = processed.split(",")[1]; + const binary = toUint8Array(base64); + const fileName = `${spriteId}.s.${s.name}.ogg`; + zip.file(fileName, binary, { binary: true }); + return { name: s.name, path: fileName }; + }) + ) + ).filter(Boolean); + + json.sprites.push({ + id: spriteId, + code: sprite.code, + costumes: costumeEntries, + sounds: soundEntries, + data: { + x: sprite.pixiSprite.x, + y: sprite.pixiSprite.y, + scale: { + x: sprite.pixiSprite.scale.x ?? 1, + y: sprite.pixiSprite.scale.y ?? 1, + }, + angle: sprite.pixiSprite.angle, + currentCostume: sprite.costumes.findIndex( + c => c.texture === sprite.pixiSprite.texture + ), + }, + }); + }) + ); + + // ADD THIS SECTION to save backdrops: + if (window.projectBackdrops && window.projectBackdrops.length > 0) { + const backdropEntries = await Promise.all( + window.projectBackdrops.map(async (backdrop, index) => { + let dataURL = backdrop.data; + + // If we don't have the data URL saved, extract it + if (!dataURL) { + dataURL = await app.renderer.extract.base64( + new PIXI.Sprite(backdrop.texture) + ); + } + + const processed = await compressImage(dataURL); + if (!processed) return null; + + const base64 = processed.split(",")[1]; + const binary = toUint8Array(base64); + const fileName = `backdrop.${index}.${backdrop.name}.webp`; + zip.file(fileName, binary, { binary: true }); + return { name: backdrop.name, path: fileName }; + }) + ); + + json.backdrops = backdropEntries.filter(Boolean); + } + + zip.file("project.json", JSON.stringify(json)); + const blob = await zip.generateAsync({ + type: "blob", + compression: "DEFLATE", + compressionOptions: { level: 9 }, + }); + + const sanitizedName = projectName.replace(/[^a-z0-9]/gi, '_').toLowerCase() || "untitled_project"; + const a = document.createElement("a"); + a.href = URL.createObjectURL(blob); + a.download = `${sanitizedName}.neo`; + a.click(); + URL.revokeObjectURL(a.href); +} + +async function loadProject(ev) { + const [file] = ev.target.files ?? []; + if (!file) return; + + // If it's an old format file, use the old loader + if (file.name.endsWith(".NeoIDE") || file.name.endsWith(".NeoIDEz")) { + return oldLoadProject(ev); + } + + // Otherwise use the new ZIP-based format + try { + const zip = await JSZip.loadAsync(await file.arrayBuffer()); + const json = JSON.parse(await zip.file("project.json").async("string")); + const sprites = []; + + for (const entry of json.sprites) { + const sprite = { ...entry, costumes: [], sounds: [] }; + + await Promise.all([ + ...(entry.costumes || []).map(async c => { + const base64 = await zip.file(c.path).async("base64"); + sprite.costumes.push({ + name: c.name, + data: `data:image/webp;base64,${base64}`, + }); + }), + ...(entry.sounds || []).map(async s => { + const base64 = await zip.file(s.path).async("base64"); + sprite.sounds.push({ + name: s.name, + data: `data:audio/ogg;base64,${base64}`, + }); + }), + ]); + + sprites.push(sprite); + } + + // Load backdrops + const backdrops = []; + if (Array.isArray(json.backdrops)) { + await Promise.all( + json.backdrops.map(async backdrop => { + const base64 = await zip.file(backdrop.path).async("base64"); + backdrops.push({ + name: backdrop.name, + data: `data:image/webp;base64,${base64}`, + }); + }) + ); + } + + handleProjectData({ + sprites, + extensions: json.extensions, + variables: json.variables, + backdrops, + currentBackdrop: json.currentBackdrop, + projectName: json.projectName, // ADD THIS LINE + }); + } catch (err) { + console.error("Failed to load project file:", err); + window.alert("Failed to load project. The file may be corrupted."); + } +} + +async function oldLoadProject(input) { + if (typeof input === "object" && !input.target) { + return await handleProjectData(input); + } + if (typeof input === "string") { + try { + const data = JSON.parse(input); + return await handleProjectData(data); + } catch (err) { + console.error("Invalid JSON string passed to loadProject:", err); + return window.alert("Invalid JSON string provided."); + } + } + + const file = input?.target?.files?.[0]; + if (!file) return; + + stopAllScripts(); + + const reader = new FileReader(); + reader.onload = async () => { + input.target.value = ""; + + const buffer = reader.result; + + let data; + try { + const text = new TextDecoder().decode(buffer); + data = JSON.parse(text); + } catch { + try { + // ADD THIS CHECK to verify it's actually compressed data + const uint8Array = new Uint8Array(buffer); + + // Check if it looks like gzip/deflate header + if (uint8Array[0] === 0x1f && uint8Array[1] === 0x8b) { + // It's gzip + const inflated = pako.inflate(uint8Array); + const json = new TextDecoder().decode(inflated); + data = JSON.parse(json); + } else if (uint8Array[0] === 0x78) { + // It's zlib/deflate + const inflated = pako.inflate(uint8Array); + const json = new TextDecoder().decode(inflated); + data = JSON.parse(json); + } else { + // Try inflating anyway as last resort + try { + const inflated = pako.inflate(uint8Array); + const json = new TextDecoder().decode(inflated); + data = JSON.parse(json); + } catch (inflateErr) { + console.error("Failed to parse file", inflateErr); + return window.alert("Invalid or corrupted project file. Please make sure you're loading a valid .neo or .NeoIDE file."); + } + } + } catch (err) { + console.error("Failed to parse file", err); + return window.alert("Invalid or corrupted project file. Please make sure you're loading a valid .neo or .NeoIDE file."); + } + } + + await handleProjectData(data); + }; + reader.readAsArrayBuffer(file); +} + +async function handleProjectData(data) { + if (!data || typeof data !== "object") { + console.error("Invalid project data:", data); + window.alert("Invalid project data."); + return; + } + + if (!data.sprites && !data.extensions) { + data = { sprites: data, extensions: [] }; + } + + try { + console.log("Loading project data:", data); + console.log("Project name from file:", data.projectName); + + if (data.projectName) { + console.log("Setting project name to:", data.projectName); + updateProjectNameInput(data.projectName); + } else { + console.log("No project name found, using default"); + updateProjectNameInput("Untitled Project"); + } + + // Verify it was set + console.log("Current projectName variable:", projectName); + console.log("Input element value:", document.getElementById("project-name-input")?.value); + + if (data?.extensions) { + const extensionsToLoad = data.extensions.filter( + i => !activeExtensions.some(z => (z?.id || z) === (i?.id || i)) + ); + + for (const ext of extensionsToLoad) { + try { + if (typeof ext === "string") { + addExtension(ext); + } else if (ext?.id) { + const ExtensionClass = await eval("(" + ext.code + ")"); + if (ExtensionClass) await registerExtension(ExtensionClass); + } + } catch (err) { + console.error("Failed to load extension", ext?.id || ext, err); + } + } + } + + for (const child of app.stage.removeChildren()) { + if (child.destroy) child.destroy({ children: true }); + } + sprites = []; + + if (!Array.isArray(data.sprites)) { + window.alert("No valid sprites found in file."); + return; + } + + if (data.variables) projectVariables = data.variables; + + // Reset arrays + window.projectCostumes = ["default"]; + window.projectSounds = []; + window.projectBackdrops = []; + currentBackdrop = null; + + createPenGraphics(); + + // MOVE BACKDROP LOADING HERE - BEFORE sprites are created + if (Array.isArray(data.backdrops)) { + window.projectBackdrops = []; + + // Use Promise.all to wait for all textures to load + await Promise.all( + data.backdrops.map(async (backdrop) => { + if (!backdrop?.data || !backdrop.name) return; + + return new Promise((resolve, reject) => { + try { + const texture = PIXI.Texture.from(backdrop.data); + + // Wait for texture to be ready + const onLoad = () => { + const backdropSprite = new PIXI.Sprite(texture); + backdropSprite.isBackdrop = true; + backdropSprite.anchor.set(0.5); + backdropSprite.x = 0; + backdropSprite.y = 0; + + const scaleX = BASE_WIDTH / texture.width; + const scaleY = BASE_HEIGHT / texture.height; + const scale = Math.max(scaleX, scaleY); + backdropSprite.scale.set(scale); + + window.projectBackdrops.push({ + name: backdrop.name, + texture, + sprite: backdropSprite, + data: backdrop.data, + }); + + resolve(); + }; + + if (texture.baseTexture.valid) { + onLoad(); + } else { + texture.baseTexture.once('loaded', onLoad); + texture.baseTexture.once('error', () => { + console.warn(`Failed to load backdrop: ${backdrop.name}`); + resolve(); // Resolve anyway to not block loading + }); + } + } catch (err) { + console.warn(`Failed to load backdrop: ${backdrop.name}`, err); + resolve(); // Resolve anyway to not block loading + } + }); + }) + ); + + console.log('Backdrops loaded:', window.projectBackdrops.map(b => b.name)); + } + + data?.sprites?.forEach((entry, i) => { + if (!entry || typeof entry !== "object") return; + + const spriteData = { + id: entry.id || `sprite-${i}`, + code: entry.code || "", + costumes: [], + sounds: [], + data: { + x: entry?.data?.x ?? 0, + y: entry?.data?.y ?? 0, + scale: { + x: entry?.data?.scale?.x ?? 1, + y: entry?.data?.scale?.y ?? 1, + }, + angle: entry?.data?.angle ?? 0, + rotation: entry?.data?.rotation ?? 0, + currentCostume: entry?.data?.currentCostume, + }, + }; + + if (Array.isArray(entry.costumes)) { + entry.costumes.forEach(c => { + if (!c?.data || !c.name) return; + try { + const texture = PIXI.Texture.from(c.data); + spriteData.costumes.push({ name: c.name, texture }); + + if (!window.projectCostumes.includes(c.name)) { + window.projectCostumes.push(c.name); + } + } catch (err) { + console.warn(`Failed to load costume: ${c.name}`, err); + const texture = PIXI.Texture.WHITE; + spriteData.costumes.push({ name: c.name, texture }); + } + }); + } + + if (Array.isArray(entry.sounds)) { + entry.sounds.forEach(s => { + if (!s?.name || !s?.data) return; + spriteData.sounds.push({ name: s.name, dataURL: s.data }); + + if (!window.projectSounds.includes(s.name)) { + window.projectSounds.push(s.name); + } + }); + } + + const sprite = + spriteData.costumes.length > 0 + ? new PIXI.Sprite(spriteData.costumes[0].texture) + : new PIXI.Sprite(); + + sprite.anchor.set(0.5); + sprite.x = spriteData.data.x; + sprite.y = spriteData.data.y; + sprite.scale.x = spriteData.data.scale.x; + sprite.scale.y = spriteData.data.scale.y; + + if (entry?.data?.angle !== null) sprite.angle = spriteData.data.angle; + else sprite.rotation = spriteData.data.rotation; + + const cc = spriteData.data.currentCostume; + if (typeof cc === "number" && spriteData.costumes[cc]) { + sprite.texture = spriteData.costumes[cc].texture; + } + + spriteData.pixiSprite = sprite; + spriteData.pixiSprite.scale._parentScaleEvent = sprite; + + app.stage.addChild(sprite); + sprites.push(spriteData); + }); + + // Set the active sprite (this loads the workspace) + setActiveSprite(sprites[0] || null); + + // NOW set the backdrop after sprites are loaded + if (typeof data.currentBackdrop === "number" && data.currentBackdrop >= 0) { + setBackdrop(data.currentBackdrop); + } + + renderBackdropsList(); + + // IMPORTANT: Update toolbox AFTER everything is loaded + workspace.updateToolbox(document.getElementById('toolbox')); + + // Force refresh all blocks with dropdowns to show correct values + setTimeout(() => { + workspace.getAllBlocks(false).forEach(block => { + if (block.type === 'switch_backdrop') { + // Trigger the dropdown to refresh + const field = block.getField('BACKDROP_NAME'); + if (field) { + field.forceRerender(); + } + } + }); + }, 100); + + } catch (err) { + console.error("Failed to load project:", err); + window.alert("Something went wrong while loading the project."); + } +} + +document.getElementById("save-button").addEventListener("click", saveProject); + +loadButton.addEventListener("click", () => { + loadInput.click(); +}); +loadInput.addEventListener("change", loadProject); + +document.getElementById("costume-upload").addEventListener("change", e => { + const file = e.target.files[0]; + if (!file || !activeSprite) return; + + const reader = new FileReader(); + reader.onload = () => { + const texture = PIXI.Texture.from(reader.result); + + let baseName = file.name.split(".")[0]; + let uniqueName = baseName; + let counter = 1; + + const nameExists = name => activeSprite.costumes.some(c => c.name === name); + + while (nameExists(uniqueName)) { + counter++; + uniqueName = `${baseName}_${counter}`; + } + + activeSprite.costumes.push({ name: uniqueName, texture }); + + // ADD THESE TWO LINES: + if (!window.projectCostumes.includes(uniqueName)) { + window.projectCostumes.push(uniqueName); + } + workspace.updateToolbox(document.getElementById('toolbox')); + + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addCostume", + data: { + spriteId: activeSprite.id, + name: uniqueName, + texture: reader.result, + }, + }); + } + + if (document.getElementById("costumes-tab").classList.contains("active")) { + tabButtons.forEach(button => { + if (button.dataset.tab === "costumes") button.click(); + }); + } + }; + reader.readAsDataURL(file); + e.target.value = ""; +}); + +document.getElementById("sound-upload").addEventListener("change", async e => { + const file = e.target.files[0]; + if (!file || !activeSprite) return; + + const reader = new FileReader(); + reader.onload = async () => { + let dataURL = reader.result; + dataURL = await compressAudio(dataURL); + + if (currentSocket && currentRoom) { + const base64 = dataURL.substring(dataURL.indexOf(",") + 1); + const estimatedBytes = base64.length * 0.75; + if (estimatedBytes >= MAX_HTTP_BUFFER) { + showNotification({ + message: + "❌ This audio file may be too large to upload. Try compressing it to avoid this.", + }); + e.target.value = ""; + return; + } + } + + let baseName = file.name.split(".")[0]; + let uniqueName = baseName; + let counter = 1; + + const nameExists = name => activeSprite.sounds.some(s => s.name === name); + + while (nameExists(uniqueName)) { + counter++; + uniqueName = `${baseName}_${counter}`; + } + + activeSprite.sounds.push({ + name: uniqueName, + dataURL, + }); + + // ADD THESE TWO LINES: + if (!window.projectSounds.includes(uniqueName)) { + window.projectSounds.push(uniqueName); + } + workspace.updateToolbox(document.getElementById('toolbox')); + + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addSound", + data: { + spriteId: activeSprite.id, + name: uniqueName, + dataURL, + }, + }); + } + + if (document.getElementById("sounds-tab").classList.contains("active")) { + renderSoundsList(); + } + }; + + reader.readAsDataURL(file); + e.target.value = ""; +}); + +document.getElementById("add-backdrop-button").addEventListener("click", () => { + document.getElementById("backdrop-upload").click(); +}); + +document.getElementById("delete-backdrop-button").addEventListener("click", () => { + if (currentBackdrop !== null) { + deleteBackdrop(currentBackdrop, true); + } +}); + +document.getElementById("backdrop-upload").addEventListener("change", e => { + const file = e.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = () => { + const baseName = file.name.split(".")[0]; + const index = addBackdrop(baseName, reader.result, true); + setBackdrop(index); + }; + reader.readAsDataURL(file); + e.target.value = ""; +}); + +// Replace the project name event listener with this: +const projectNameInput = document.getElementById("project-name-input"); +if (projectNameInput) { + projectNameInput.addEventListener("input", (e) => { + projectName = e.target.value.trim() || "Untitled Project"; + + // Sync with live share (optional) + if (currentSocket && currentRoom) { + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "setProjectName", + data: projectName, + }); + } + }); + + // Set initial value + projectNameInput.value = projectName; +} + +// ADD THIS HELPER FUNCTION to update the input +function updateProjectNameInput(name) { + projectName = name; + const nameInput = document.getElementById("project-name-input"); + if (nameInput) { + nameInput.value = name; + } +} +window.updateProjectNameInput = updateProjectNameInput; + +window.addEventListener("resize", () => { + resizeCanvas(); +}); + +function isXmlEmpty(input = "") { + input = input.trim(); + return ( + input === '' || + input === "" + ); +} + +window.addEventListener("beforeunload", e => { + if (sprites.some(sprite => !isXmlEmpty(sprite.code))) { + e.preventDefault(); + e.returnValue = ""; + if (currentSocket) currentSocket?.disconnect?.(); + } +}); + +const allowedKeys = new Set([ + "ArrowUp", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + " ", + "Enter", + "Escape", + ..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +]); +window.addEventListener("keydown", e => { + const key = e.key; + if (!allowedKeys.has(key)) return; + + keysPressed[key] = true; + + const specificHandlers = eventRegistry.key.get(key); + if (specificHandlers) { + for (const entry of specificHandlers) { + entry.cb(); + } + } + + const anyHandlers = eventRegistry.key.get("any"); + if (anyHandlers) { + for (const entry of anyHandlers) { + entry.cb(key); + } + } +}); + +window.addEventListener("keyup", e => { + const key = e.key; + if (allowedKeys.has(key)) { + keysPressed[key] = false; + } +}); + +window.addEventListener("blur", () => { + for (const key in keysPressed) { + keysPressed[key] = false; + } +}); + +window.addEventListener("mousedown", e => { + mouseButtonsPressed[e.button] = true; +}); +window.addEventListener("mouseup", e => { + mouseButtonsPressed[e.button] = false; +}); + +SpriteChangeEvents.on("scaleChanged", sprite => { + if (activeSprite?.pixiSprite === sprite) renderSpriteInfo(); +}); + +SpriteChangeEvents.on("positionChanged", sprite => { + if (activeSprite?.pixiSprite === sprite) renderSpriteInfo(); + + const spriteData = sprites.find(s => s?.pixiSprite === sprite); + if (!spriteData) return; + + if (spriteData.currentBubble) { + const { width, height } = spriteData.currentBubble; + const pos = calculateBubblePosition(sprite, width, height); + Object.assign(spriteData.currentBubble, pos); + } + + const { x, y } = sprite; + const [x0, y0] = spriteData.lastPos || [x, y]; + + if (spriteData.penDown) { + penGraphics.lineStyle(spriteData.penSize || 1, spriteData.penColor); + penGraphics.moveTo(x0, y0); + penGraphics.lineTo(x, y); + } + + spriteData.lastPos = [x, y]; +}); + +SpriteChangeEvents.on("textureChanged", event => { + renderSpritesList(false); +}); + +/* setup extensions stuff */ + +export const activeExtensions = []; + +const extensions = [ + { + id: "tween", + name: "Tween", + xml: ` + + + + 100 + + + + + 3 + + + + + + + 0 + + + + + 100 + + + + + 3 + + + + + `, + }, + { + id: "pen", + name: "Pen", + xml: ` + + + + + + 255,100,100 + + + + + 1 + + + `, + }, + { + id: "sets", + name: "Sets", + xml: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + }, +]; + +const extensionsPopup = document.querySelector(".extensions-popup"); +const extensionsList = document.querySelector(".extensions-list"); + +function addExtensionButton() { + const toolboxDiv = document.querySelector( + "div.blocklyToolbox div.blocklyToolboxCategoryGroup" + ); + if (!toolboxDiv || !extensionsPopup) return; + + const button = document.createElement("button"); + button.innerHTML = ''; + button.id = "extensionButton"; + + ["pointerdown", "mousedown", "mouseup", "click"].forEach(evt => + button.addEventListener(evt, e => { + e.stopPropagation(); + e.preventDefault(); + }) + ); + + button.addEventListener("click", () => { + extensionsPopup.classList.remove("hidden"); + }); + + toolboxDiv.appendChild(button); +} + +function addExtension(id, emit = false) { + if (activeExtensions.includes(id)) return; + + const extension = extensions.find(e => e?.id === id); + if (!extension || !extension.xml) return; + + const parser = new DOMParser(); + const extDoc = parser.parseFromString(extension.xml, "text/xml"); + const coreDom = document.getElementById("toolbox"); + + const category = extDoc.querySelector("category"); + coreDom.appendChild(category.cloneNode(true)); + + workspace.updateToolbox(coreDom); + + activeExtensions.push(id); + document.querySelector(`button[data-extension-id="${id}"]`).disabled = true; + + setTimeout(() => { + extensionsPopup.classList.add("hidden"); + }); + + if (emit && currentSocket && currentRoom); + currentSocket.emit("projectUpdate", { + roomId: currentRoom, + type: "addExtension", + data: id, + }); +} + +setupExtensions(); +addExtensionButton(); + +extensions.forEach(e => { + if (!e || !e.id) return; + + const extension = document.createElement("div"); + const addButton = document.createElement("button"); + addButton.onclick = () => addExtension(e.id, true); + addButton.dataset.extensionId = e.id; + addButton.innerText = "Add"; + extension.innerHTML = `

${e?.name ?? "Extension Name"}

+ `; + extension.appendChild(addButton); + extensionsList.appendChild(extension); +}); + +const stageDiv = document.getElementById("stage-div"); + +fullscreenButton.addEventListener("click", () => { + const isFull = stageDiv.classList.toggle("fullscreen"); + fullscreenButton.innerHTML = ``; + resizeCanvas(); +}); + +document + .getElementById("extensions-custom-button") + .addEventListener("click", () => { + const isSharing = currentSocket && currentRoom; + showPopup({ + title: "Custom Extensions", + rows: [ + [ + "⚠ Warning: Only use custom extensions from people you trust! Do not run custom extensions you don't know about.", + ], + [ + "Insert extension code:", + { + type: "textarea", + placeholder: "class Extension { ... }", + className: "extension-code-input", + }, + ], + [ + { + type: "button", + label: ' Add', + className: "primary", + disabled: isSharing, + onClick: popup => { + const input = popup.querySelector('[data-row="1"][data-col="1"]'); + const userCode = input ? input.value : ""; + + const iframe = document.createElement("iframe"); + iframe.style.display = "none"; + iframe.sandbox = "allow-scripts"; + iframe.srcdoc = ` + + `; + document.body.appendChild(iframe); + + const handleMessage = event => { + if (!event.data) return; + + switch (event.data.type) { + case "registerExtension": + try { + const extensionCode = "(" + event.data.code + ")"; + const ExtensionClass = eval(extensionCode); + registerExtension(ExtensionClass); + + console.log("extension registered:", ExtensionClass); + } catch (error) { + console.error("Error in extension:", error); + window.alert("Error in extension: " + error); + } + + iframe.remove(); + window.removeEventListener("message", handleMessage); + break; + case "error": + console.error("Error in extension:", event.data.error); + window.alert("Error in extension: " + event.data.error); + break; + case "iframeReady": + iframe.contentWindow.postMessage( + { type: "runCode", code: userCode }, + "*" + ); + break; + } + }; + + window.addEventListener("message", handleMessage); + + popup.remove(); + document + .getElementById("extensions-popup") + ?.classList.add("hidden"); + }, + }, + isSharing + ? "You can't add custom extensions while live sharing the project." + : "", + ], + ], + }); + }); + +function getToken() { + return localStorage.getItem("tooken"); +} + +function serializeWorkspace(workspace) { + const xmlDom = Blockly.Xml.workspaceToDom(workspace, true); + return Blockly.Xml.domToText(xmlDom); +} + +function createSession() { + if (currentSocket && currentSocket.connected) return currentSocket; + + currentSocket = io(`${config.apiUrl}/live`); + + currentSocket.on("connect", () => { + console.log("connected to liveshare"); + }); + + currentSocket.on("disconnect", () => { + console.log("disconnected from liveshare"); + + currentSocket = null; + currentRoom = null; + amHost = false; + connectedUsers = []; + + updateUsersList(); + }); + + currentSocket.on("userList", users => { + connectedUsers = users; + updateUsersList(); + }); + + currentSocket.on("userJoined", async ({ username, socketId }) => { + console.log(`${username} joined to room`); + if (amHost) { + currentSocket.emit("sendProjectData", { + to: socketId, + data: await getProject(), + }); + } + updateUsersList(); + }); + + currentSocket.on("projectData", async data => { + console.log("received project data from host"); + await handleProjectData(data); + }); + + currentSocket.on("projectUpdate", ({ type, data }) => { + switch (type) { + case "addVariable": { + projectVariables[data] = 0; + break; + } + case "addSprite": { + addSprite(data, false); + renderSpritesList(true); + break; + } + case "removeSprite": { + deleteSprite(data, false); + renderSpritesList(true); + break; + } + case "addExtension": { + addExtension(data, false); + break; + } + case "addCostume": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + const texture = PIXI.Texture.from(data.texture); + target.costumes.push({ name: data.name, texture }); + if (activeSprite?.id === target.id) renderCostumesList(); + break; + } + case "addSound": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + target.sounds.push({ name: data.name, dataURL: data.dataURL }); + if (activeSprite?.id === target.id) renderSoundsList(); + break; + } + case "renameCostume": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + const costume = target.costumes.find(c => c.name === data.oldName); + if (costume) costume.name = data.newName; + if (activeSprite?.id === target.id) renderCostumesList(); + break; + } + case "deleteCostume": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + target.costumes = target.costumes.filter(c => c.name !== data.name); + if (activeSprite?.id === target.id) renderCostumesList(); + break; + } + case "renameSound": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + const sound = target.sounds.find(s => s.name === data.oldName); + if (sound) sound.name = data.newName; + if (activeSprite?.id === target.id) renderSoundsList(); + break; + } + case "deleteSound": { + const target = sprites.find(s => s.id === data.spriteId); + if (!target) return; + target.sounds = target.sounds.filter(s => s.name !== data.name); + if (activeSprite?.id === target.id) renderSoundsList(); + break; + } + // ADD THESE NEW CASES: + case "addBackdrop": { + addBackdrop(data.name, data.texture, false); + break; + } + case "deleteBackdrop": { + deleteBackdrop(data, false); + break; + } + case "setBackdrop": { + if (typeof data === "number") { + setBackdrop(data); + } else if (typeof data === "string") { + setBackdropByName(data); + } + break; + } + case "setProjectName": { + projectName = data; + const nameInput = document.getElementById("project-name-input"); + if (nameInput) nameInput.value = projectName; + break; + } + } + }); + + currentSocket.on("blocklyUpdate", ({ spriteId, event, from }) => { + if (from === currentSocket?.id) return; + + if (!event || typeof event !== "object") { + console.warn("received bad blockly update (skipping):", event); + return; + } + + const sprite = sprites.find(s => s.id === spriteId); + if (!sprite) return; + + let _workspace, + temp = false; + + if (activeSprite.id === spriteId) { + _workspace = workspace; + } else { + temp = true; + _workspace = new Blockly.Workspace({ + readOnly: true, + plugins: { + connectionChecker: "CustomChecker", + }, + }); + + const xml = Blockly.utils.xml.textToDom(sprite.code || ""); + Blockly.Xml.domToWorkspace(xml, _workspace); + } + + Blockly.Events.disable(); + try { + Blockly.Events.fromJson(event, _workspace).run(true); + } catch (err) { + console.error("blockly update error:", err, event); + } finally { + if ( + event.type === Blockly.Events.BLOCK_CHANGE && + event.element === "mutation" + ) { + updateAllFunctionCalls(workspace); + } + + if (temp) { + const newXml = Blockly.Xml.domToText( + Blockly.Xml.workspaceToDom(_workspace) + ); + sprite.code = newXml; + + _workspace.dispose(); + } + + Blockly.Events.enable(); + } + }); + + currentSocket.on("invitesStatus", ({ enabled }) => { + invitesEnabled = enabled; + + const toggleInvites = document.querySelector( + '[data-row="1"][data-col="0"]' + ); + if (toggleInvites) + toggleInvites.textContent = enabled + ? "Disable Invites" + : "Enable Invites"; + + const copyLink = document.querySelector('[data-row="1"][data-col="1"]'); + if (copyLink) copyLink.disabled = !enabled; + }); + + currentSocket.on("kicked", () => { + currentSocket.disconnect(); + showNotification({ message: "You were kicked from the room" }); + }); + + return currentSocket; +} + +function updateUsersList() { + const container = document.getElementById("room-users"); + if (!liveShare) return; + + if (connectedUsers.length === 0) { + liveShare.innerHTML = ` + + Live Share + `; + if (container) container.innerHTML = "No users connected"; + return; + } + + if (!container) return; + + container.innerHTML = connectedUsers + .map(u => { + const canKick = amHost && !u.isHost; + return ` +
+ + ${u.isHost ? "👑 " : ""}${u.username} + ${ + canKick + ? `` + : "" + } +
`; + }) + .join(""); + + liveShare.innerHTML = ` + + Live Share (${connectedUsers.length}) + `; + + if (amHost) { + container.querySelectorAll(".kick-btn").forEach(btn => + btn.addEventListener("click", e => { + const targetUserId = e.target.dataset.id; + if (confirm("Kick this user?")) + currentSocket.emit("kickUser", { roomId: currentRoom, targetUserId }); + }) + ); + } +} + +const liveShare = document.getElementById("liveshare-button"); +liveShare.addEventListener("click", async () => { + let roomExisted = currentSocket !== null && currentRoom !== null; + + function showRoomPopup() { + const shareUrl = + window.location.origin + + window.location.pathname + + `?room=${currentRoom}`; + + const invitesLabel = invitesEnabled ? "Disable Invites" : "Enable Invites"; + const buttons = [ + amHost + ? { + type: "button", + label: invitesLabel, + onClick: () => { + const newStatus = !invitesEnabled; + invitesEnabled = newStatus; + currentSocket.emit("toggleInvites", { + roomId: currentRoom, + enabled: newStatus, + }); + }, + } + : invitesLabel, + { + type: "button", + className: "primary", + label: "Copy Link", + disabled: !invitesEnabled, + onClick: async () => { + try { + await navigator.clipboard.writeText(shareUrl); + showNotification({ message: "Copied room link!" }); + } catch (e) { + console.error("Copy failed", e); + window.alert(shareUrl); + } + }, + }, + { + type: "button", + className: "danger", + label: amHost ? "Close room" : "Leave room", + onClick: popup => { + showNotification({ + message: amHost ? "Room closed" : "Left room", + }); + + popup.remove(); + + currentSocket.disconnect(); + currentSocket = null; + currentRoom = null; + amHost = false; + }, + }, + ]; + + const rows = [ + [ + "Users:", + { + type: "custom", + html: `
`, + }, + ], + buttons, + ]; + + showPopup({ + title: roomExisted ? "Current Room" : "Room Created", + rows, + }); + + updateUsersList(); + } + + createSession(); + + if (!roomExisted) { + const token = getToken(); + if (!token) { + showNotification({ + message: "You must be logged in to create a shared room", + }); + } else { + currentSocket.emit("createRoom", { token }, res => { + if (res?.error) { + console.error(res.error); + showNotification({ message: `Error: ${res.error}` }); + return; + } + amHost = true; + currentRoom = res.roomId; + showRoomPopup(); + }); + } + } else showRoomPopup(); +}); + +const urlParams = new URLSearchParams(window.location.search); +const roomId = urlParams.get("room"); +if (roomId) { + const token = getToken(); + if (!token) { + showNotification({ + message: "You must be logged in to join a shared room", + }); + } else { + createSession(); + + currentSocket.emit("joinRoom", { token, roomId }, res => { + if (res?.error) { + showNotification({ message: `Error: ${res.error}` }); + return; + } + + currentRoom = roomId; + amHost = false; + + console.log(`joined room ${roomId} successfully`); + }); + } +} else { + let spriteData = addSprite(); + setActiveSprite(spriteData); +} + +const ignoredEvents = new Set([ + Blockly.Events.VIEWPORT_CHANGE, + Blockly.Events.SELECTED, + Blockly.Events.CLICK, + Blockly.Events.TOOLBOX_ITEM_SELECT, + Blockly.Events.TRASHCAN_OPEN, + Blockly.Events.FINISHED_LOADING, + Blockly.Events.BLOCK_FIELD_INTERMEDIATE_CHANGE, + Blockly.Events.BLOCK_DRAG, + Blockly.Events.THEME_CHANGE, + Blockly.Events.BUBBLE_OPEN, + "backpack_change", +]); + +function sanitizeEvent(event) { + const raw = event.toJson(); + delete raw.workspaceId; + delete raw.recordUndo; + return JSON.parse(JSON.stringify(raw)); +} + +workspace.addChangeListener(event => { + if (!activeSprite || ignoredEvents.has(event.type)) return; + + activeSprite.code = Blockly.Xml.domToText( + Blockly.Xml.workspaceToDom(workspace) + ); + + if (currentSocket && currentRoom) { + const json = sanitizeEvent(event); + + currentSocket.emit("blocklyUpdate", { + roomId: currentRoom, + spriteId: activeSprite.id, + event: json, + }); + } +}); + +workspace.addChangeListener(Blockly.Events.disableOrphans); + +class TheDragger extends Blockly.dragging.Dragger { + setDraggable(draggable) { + this.draggable = draggable; + } +} + +Blockly.registry.register( + Blockly.registry.Type.BLOCK_DRAGGER, + Blockly.registry.DEFAULT, + TheDragger, + true +); + +function updateAllFunctionCalls(workspace) { + const allBlocks = workspace.getAllBlocks(false); + const defs = allBlocks.filter(b => b.type === "functions_definition"); + const defMap = {}; + defs.forEach(def => (defMap[def.functionId_] = def)); + + const calls = allBlocks.filter(b => b.type === "functions_call"); + + Blockly.Events.disable(); + try { + calls.forEach(callBlock => { + const def = defs.find(d => d.functionId_ === callBlock.functionId_); + if (!def) return; + + def.updateReturnState_(); + callBlock.matchDefinition(def); + }); + } finally { + Blockly.Events.enable(); + } +} + +workspace.addChangeListener(event => { + if (event.isUiEvent || event.isBlank) return; + + const block = workspace.getBlockById(event?.newParentId ?? event?.oldParentId ?? event?.blockId); + + if (!block || block?.getRootBlock()?.type !== "functions_definition") return; + + updateAllFunctionCalls(workspace); +}); + +workspace.updateAllFunctionCalls = () => { + updateAllFunctionCalls(workspace); +}; diff --git a/src/scripts/index.js b/src/scripts/index.js new file mode 100644 index 0000000..8bc08b9 --- /dev/null +++ b/src/scripts/index.js @@ -0,0 +1,4 @@ +import { setupThemeButton, setupUserTag } from "../functions/theme"; + +setupThemeButton(); +setupUserTag(); \ No newline at end of file diff --git a/src/scripts/login.js b/src/scripts/login.js new file mode 100644 index 0000000..37e57be --- /dev/null +++ b/src/scripts/login.js @@ -0,0 +1,59 @@ +import "@fortawesome/fontawesome-free/css/all.min.css"; +import { toggleIcons, toggleTheme } from "../functions/theme"; +import config from "../config"; + +toggleTheme(); +toggleIcons(); + +const usernameInput = document.getElementById("username"); +const passwordInput = document.getElementById("password"); +const loginButton = document.getElementById("login"); + +async function onLoginClick(e) { + e.preventDefault(); + + const username = usernameInput.value.trim(); + const password = passwordInput.value; + + if (!username || !password) { + alert("Please enter both username and password."); + return; + } + + loginButton.disabled = true; + loginButton.dataset.origHtml = loginButton.innerHTML; + loginButton.innerHTML = `...`; + + try { + const response = await fetch(`${config.apiUrl}/users/login`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ username, password }), + }); + + if (!response.ok) { + let errText = `Request failed: ${response.status} ${response.statusText}`; + try { + const errJson = await response.json(); + + errText = errJson.message || JSON.stringify(errJson); + } catch (err) {} + throw new Error(errText); + } + + const data = await response.json(); + if (data.token) localStorage.setItem("tooken", data.token); + + window.location.href = "/"; + } catch (err) { + console.error(err); + alert("Login error: " + err.message); + + loginButton.disabled = false; + loginButton.innerHTML = loginButton.dataset.origHtml; + } +} + +loginButton.addEventListener("click", onLoginClick); diff --git a/src/scripts/signup.js b/src/scripts/signup.js new file mode 100644 index 0000000..8351b3a --- /dev/null +++ b/src/scripts/signup.js @@ -0,0 +1,59 @@ +import "@fortawesome/fontawesome-free/css/all.min.css"; +import { toggleIcons, toggleTheme } from "../functions/theme"; +import config from "../config"; + +toggleTheme(); +toggleIcons(); + +const usernameInput = document.getElementById("username"); +const passwordInput = document.getElementById("password"); +const loginButton = document.getElementById("login"); + +async function onSignupClick(e) { + e.preventDefault(); + + const username = usernameInput.value.trim(); + const password = passwordInput.value; + + if (!username || !password) { + alert("Please enter both username and password."); + return; + } + + loginButton.disabled = true; + loginButton.dataset.origHtml = loginButton.innerHTML; + loginButton.innerHTML = `...`; + + try { + const response = await fetch(`${config.apiUrl}/users/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ username, password }), + }); + + if (!response.ok) { + let errText = `Request failed: ${response.status} ${response.statusText}`; + try { + const errJson = await response.json(); + + errText = errJson.message || JSON.stringify(errJson); + } catch (err) {} + throw new Error(errText); + } + + const data = await response.json(); + if (data.token) localStorage.setItem("tooken", data.token); + + window.location.href = "/"; + } catch (err) { + console.error(err); + alert("Login error: " + err.message); + + loginButton.disabled = false; + loginButton.innerHTML = loginButton.dataset.origHtml; + } +} + +loginButton.addEventListener("click", onSignupClick); diff --git a/src/scripts/testExt.js b/src/scripts/testExt.js new file mode 100644 index 0000000..060047d --- /dev/null +++ b/src/scripts/testExt.js @@ -0,0 +1,128 @@ +class Extension { + id = "ddeTestExtension"; + registerCategory() { + return { + name: "Test Extension", + color: "#858585", + }; + } + registerBlocks() { + return [ + { + type: "statement", + id: "evil", + text: "evil block", + color: "#FF0000", + }, + { + type: "cap", + id: "statement", + fields: { poop: { kind: "statement" } }, + text: "i want statement [poop]", + }, + { + type: "statement", + id: "statementA", + text: "type statement A", + statementType: "statementA", + color: "#85c25c", + }, + { + type: "statement", + id: "onlyStatementA", + fields: { code: { kind: "statement", accepts: "statementA" } }, + text: "only statement A [code]", + color: "#69974a", + }, + { + type: "statement", + id: "if", + fields: { + bool: { kind: "value", type: "Boolean", default: true }, + code: { kind: "statement" }, + }, + text: "if [bool] then [code]", + }, + { + type: "statement", + id: "ifElse", + fields: { + bool: { kind: "value", type: "Boolean", default: true }, + code: { kind: "statement" }, + codeElse: { kind: "statement" }, + }, + text: "if [bool] then [code] else [codeElse]", + }, + { + type: "statement", + id: "menu", + fields: { + hi: { + kind: "menu", + items: ["normal", { text: "ABC display", value: "abc" }], + default: "abc", + }, + }, + text: "menu [hi]", + }, + { + type: "output", + id: "random1", + text: "random (output shape 1)", + outputShape: 1, + }, + { + type: "output", + id: "random2", + text: "random (output shape 2)", + outputShape: 2, + }, + { + type: "output", + id: "random3", + text: "random (output shape 3)", + outputShape: 3, + }, + { + type: "output", + id: "random4", + text: "random (output shape 4)", + outputShape: 4, + }, + { + type: "output", + id: "random5", + text: "random (output shape 5)", + outputShape: 5, + }, + ]; + } + registerCode() { + return { + statement: (inputs) => { + console.log(inputs.poop?.()); + }, + if: (inputs) => { + console.log(inputs); + if (inputs.bool) inputs.code?.(); + }, + ifElse: (inputs) => { + console.log(inputs); + if (inputs.bool) inputs.code?.(); + else inputs.codeElse?.(); + }, + evil: () => { + console.warn("evil is near"); + }, + random1: () => Math.random(), + random2: () => Math.random(), + random3: () => Math.random(), + random4: () => Math.random(), + random5: () => Math.random(), + actuallyBoolean: () => true, + menu: (inputs) => window.alert(inputs.hi), + }; + } +} + +registerExtension(Extension); diff --git a/src/scripts/userprofile.js b/src/scripts/userprofile.js new file mode 100644 index 0000000..31ec2e9 --- /dev/null +++ b/src/scripts/userprofile.js @@ -0,0 +1,129 @@ +import config from "../config"; +import { cache } from "../cache"; +import { showPopup } from "../functions/utils"; +import { setupThemeButton, setupUserTag } from "../functions/theme"; + +const allowedFileFormats = ["jpeg", "png", "webp", "avif", "gif", "tiff"]; +const profileDiv = document.getElementById("userProfile"); + +const urlParams = new URLSearchParams(window.location.search); +const identifier = urlParams.get("id") || urlParams.get("username"); + +setupThemeButton(); +setupUserTag(); + +if (profileDiv && identifier) { + fetch(`${config.apiUrl}/users/${identifier}`, { + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((response) => { + profileDiv.innerHTML = ` + + ${response.username} + `; + + if (cache.user && cache.user.id === response.id) { + const avatarEl = document.getElementById("profileAvatar"); + avatarEl.style.cursor = "pointer"; + avatarEl.addEventListener("click", async () => { + showPopup({ + title: "Avatar", + rows: [ + [ + { + type: "button", + label: "Upload avatar", + className: "primary", + onClick: async (popup) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = allowedFileFormats + .map((f) => `image/${f}`) + .join(","); + input.style.display = "none"; + input.click(); + + input.onchange = async () => { + const file = input.files[0]; + if (!file) return; + + const format = file.type.split("/")[1]; + if (!allowedFileFormats.includes(format)) { + alert(`Unsupported format: ${format}`); + return; + } + + const formData = new FormData(); + formData.append("avatar", file); + + try { + const res = await fetch( + `${config.apiUrl}/users/me/avatar`, + { + method: "POST", + headers: { + Authorization: localStorage.getItem("tooken"), + }, + body: formData, + } + ); + + const data = await res.json(); + if (!res.ok) + throw new Error(data.error || "Upload failed"); + + window.location.reload(); + } catch (err) { + console.error(err); + alert("Error uploading: " + err.message); + } finally { + popup.remove(); + input.remove(); + } + }; + }, + }, + { + type: "button", + label: "Remove avatar", + className: "danger", + onClick: async (popup) => { + if (!confirm("Remove your avatar?")) return; + try { + const res = await fetch( + `${config.apiUrl}/users/me/avatar`, + { + method: "DELETE", + headers: { + Authorization: localStorage.getItem("tooken"), + }, + } + ); + const data = await res.json(); + if (!res.ok) + throw new Error( + data.error || "Failed to remove avatar" + ); + + avatarEl.src = "default-avatar.png"; + popup.remove(); + } catch (err) { + console.error(err); + alert("Error deleting: " + err.message); + } + }, + }, + ], + ], + }); + }); + } + }) + .catch((err) => { + console.error(err); + alert("Login error: " + err.message); + }); +} diff --git a/src/userprofile.css b/src/userprofile.css new file mode 100644 index 0000000..2e85537 --- /dev/null +++ b/src/userprofile.css @@ -0,0 +1,27 @@ +.info { + padding: 1rem; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +#userProfile { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + font-size: x-large; + gap: 0.5rem; + padding: 0.5rem; + border-radius: 0.5rem; + background-color: var(--color2); + color: var(--dark); +} + +#userProfile img { + width: 3rem; + height: 3rem; + border-radius: 20%; +} diff --git a/user.html b/user.html new file mode 100644 index 0000000..1083e31 --- /dev/null +++ b/user.html @@ -0,0 +1,61 @@ + + + + + + + User - NeoIDE + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+ +
+
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..93b624b --- /dev/null +++ b/vercel.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "cleanUrls": true +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..79d636c --- /dev/null +++ b/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +export default defineConfig({ + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + editor: resolve(__dirname, 'editor.html'), + login: resolve(__dirname, 'login.html'), + signup: resolve(__dirname, 'signup.html'), + user: resolve(__dirname, 'user.html'), + }, + treeshake: false, + }, + }, +});