diff --git a/.eslintrc.js b/.eslintrc.js index 1ca8091ca7..db32ceab95 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,29 +10,13 @@ module.exports = { ecmaVersion: 'latest' }, globals: { + Blockly: 'readonly', Scratch: 'readonly', + ScratchBlocks: 'readonly', ScratchExtensions: 'readonly', scaffolding: 'readonly' }, rules: { - // Here is where we enforce rules to have somewhat consistent code style without being overbearing - 'semi': [ - 'warn', - 'always' - ], - 'brace-style': 'warn', - 'key-spacing': 'warn', - 'keyword-spacing': 'warn', - 'new-parens': 'warn', - 'no-trailing-spaces': [ - 'warn', - { - ignoreComments: true - } - ], - 'space-infix-ops': 'warn', - 'no-tabs': 'warn', - 'no-unused-vars': 'off', // Allow while (true) { } 'no-constant-condition': [ diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index bd20acc560..2db07a1930 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -4,7 +4,7 @@ on: pull_request: jobs: - build: + validate: runs-on: ubuntu-latest steps: - name: Checkout @@ -12,10 +12,39 @@ jobs: - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: 16 + node-version: '14.x' + cache: 'npm' - name: Install dependencies run: npm ci - name: Validate run: npm run validate - - name: Lint + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: '14.x' + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Validate run: npm run lint + + format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: '14.x' + cache: 'npm' + - name: Install dependencies + run: npm ci + - name: Validate + run: npm run check-format diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..0a39a3b587 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +build +images +licenses +website +.eslintrc.js +*.json +*.yml +*.md +extensions/docs-examples diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..37cdf9afd4 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "trailingComma": "es5", + "endOfLine": "auto" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96b706319c..e522ab1276 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing extensions -Before you submit extensions, please read the NEW custom extension tutorial in full: +Before you submit extensions, please read the custom extension tutorial **in full**: - https://docs.turbowarp.org/development/extensions/introduction @@ -10,76 +10,74 @@ Please pay special attention to: - Maintaining backward compatibility: https://docs.turbowarp.org/development/extensions/compatibility - A better development server: https://docs.turbowarp.org/development/extensions/better-development-server -Pull requests that don't follow the guidelines outlined in these documents tend to take much longer to be reviewed and merged. +Read this document **in full** too. Pull requests that don't follow the guidelines will take *much* longer to be reviewed. -## Local development server +## Acceptance criteria -We recommend using our local development server: +Strictly, nothing is banned, but the following are *highly* discouraged: -```bash -# Clone the repository -git clone https://github.com/TurboWarp/extensions.git -cd extensions - -# Install dependencies -npm ci + - Broad "Utilities" extensions (break them up into multiple extensions, see https://github.com/TurboWarp/extensions/issues/674) + - Extensions that are very similar to existing ones (consider modifying the existing one instead) + - One-use personal extensions (load the extension as a local file instead) + - Joke extensions (they aren't funny when they cause us to get bug reports) -# Start development server -npm run dev -``` +Some extensions were added before these rules existed. That doesn't mean you will be exempted too. -This starts an HTTP server on [http://localhost:8000/](http://localhost:8000/) in development mode which adds a couple of extra tools to the homepage. +## Important context -After installing npm dependencies, TypeScript aware editors such as Visual Studio Code will give you smart autocomplete suggestions for most Scratch and extension APIs based on [@turbowarp/types](https://github.com/TurboWarp/types) and [@turbowarp/types-tw](https://github.com/TurboWarp/types-tw). Note that these types are not perfect; some methods are missing or incorrect. Please report any issues you find. +Every merged extension is more code that we will be expected to maintain indefinitely, even if you disappear. Remember: broken extensions mean that real projects by real people no longer work. If the renderer is rewritten one day, we will have to ensure that extensions like Clipping & Blending, RGB Channels, and Augmented Reality still work. That's not a small commitment. -If you encounter a TypeScript error, as long as you understand the error, feel free to add `// @ts-ignore`, `// @ts-expect-error`, or just ignore the error entirely. We currently do not require extensions to pass type checking. +We're all volunteers who all have lives outside of Scratch extensions. Many have full time jobs or are full time students. We'll get to you as soon as we can, so please be patient. -## Alternative development server - -If for some reason you can't set up our local development server, you can start any other HTTP server in the `extensions` folder. You won't get some of the nice things our server has, but it may be good enough. If you have Python 3 installed this is very simple: +## Writing extensions -```bash -cd extensions -python3 -m http.server -``` +Extension source code goes in the [`extensions`](extensions) folder. For example, an extension placed at `extensions/hello-world.js` would be accessible at [http://localhost:8000/hello-world.js](http://localhost:8000/hello-world.js) using our development server. -Note that browsers tend to aggressively cache JavaScript files that don't opt out of caching as our development server does, so you may have to do hard reloads to ensure that changes to your scripts are applied. +New extensions should be added in a user folder. You can name your folder anything you want; common choices are your GitHub username or your Scratch username. If your username is `TestMuffin123`, then `TestMuffin123`, `TestMuffin`, or even just `Muffin` would all be accepted -- we are very lenient about this. Do note that user folders are just for organization; other people are still allowed to edit your extension. Renaming your folder later is only allowed in very rare circumstances, so please get it right the first time. -## Types of extensions we accept +Extensions must be self-contained. All libraries and hardcoded resources should be embedded into the extension's JavaScript file. If you include minified code, please link where to find the unminified code and include a copy of the original license. -We strive to be tolerant of accepting almost any extension, including one-use novelty extensions or extensions that are similar to ones that already exist. +## Website stuff -Extensions end up in one of these categories depending on various qualities: +To add an extension to the homepage, you need to add metadata comments at the very start of the extension's JavaScript, and add the extension's path (without .js) to `extensions/extensions.json`. The order of that list determines the order of the library. Don't worry about putting it in the right spot, we'll move it if we disagree. - - Extensions that are in the repository, but not listed on the website - - Extensions that are listed on the website - - Extensions that are listed in the editor's builtin extension library +The header comments look like this: -## Writing extensions +```js +// Name: Example Extension +// ID: extensionid +// Description: Does a very cool thing. This must have punctuation at the end! +// By: GarboMuffin +// Original: TestMuffin +``` -Extension source code goes in the `extensions` folder. For example, an extension placed at `extensions/hello-world.js` would be accessible at [http://localhost:8000/hello-world.js](http://localhost:8000/hello-world.js). +Remember, this has to be the *very first* thing in the JS file. `Name`, `Description`, and `ID` are required. Make sure that `ID` exactly matches what you return in `getInfo()`. You can have zero or more `By` and `Original`. Put credit links in `` if you have one. It must point to a Scratch user profile. The parser is pretty loose, but try not to deviate too far from this format. -New extensions should be added in a user folder. You can name your folder your GitHub username, your Scratch username, or something else. For example, if your GitHub username is "TestMuffin", you could make a `TestMuffin` folder inside of the `extensions` folder and put your extensions inside there. You could then access a file placed at `extensions/TestMuffin/hello-world.js` at [http://localhost:8000/TestMuffin/hello-world.js](http://localhost:8000/TestMuffin/hello-world.js). +New extensions do not *need* images, but they are highly encouraged. Save the image in the `images` folder with the same folder name and file name (but different file extension) as the extension's source code. For example, if your extension is located in `extensions/TestMuffin/fetch.js`, save the image as `images/TestMuffin/fetch.svg` or `images/TestMuffin/fetch.png`. The homepage generator will detect it automatically. Images are displayed in a 2:1 aspect ratio. SVG (preferred), PNG, or JPG are accepted. PNG or JPG should be 600x300 in resolution. Please add proper attribution to `images/README.md` for *any* resources that were not made by you. -Static resources go in the `website` folder. This is where some example resources used by extensions such as fetch are placed. It works similarly to the `extensions` folder. +Most extensions shouldn't need external documentation -- it should be obvious what to do just by looking at the blocks. That said, some do need more explanation. Documentation is written in markdown and placed in the `docs` folder with a similar layout to images. For example, documentation for `extensions/TestMuffin/fetch.js` would be saved as `docs/TestMuffin/fetch.md`. Our version of markdown is slightly extended to allow rendering [scratchblocks](https://scratchblocks.github.io/). Just look at the existing documentation for syntax examples. It's not a perfect experience: block colors have to be manually copied, and icons aren't supported, but it's better than what we had before. Once you put your markdown there, you can set a `docsURI` like `https://extensions.turbowarp.org/TestMuffin/fetch`. -Extensions must not use `eval()`, `new Function()`, untrusted ` + + <% } %> + + diff --git a/development/homepage-template.ejs b/development/homepage-template.ejs new file mode 100644 index 0000000000..18fd2a84fa --- /dev/null +++ b/development/homepage-template.ejs @@ -0,0 +1,334 @@ + + + + + + TurboWarp Extension Gallery + + + + + + +
+

+ +
TurboWarp Extension Gallery
+

+ +

Unlike custom extensions on other websites, these aren't limited by the extension sandbox, so they are a lot more powerful. All extensions are reviewed for safety.

+

+ To use multiple of these extensions in TurboWarp, hover over the extension and press the button to copy its URL. Then go to the editor, open the extension chooser, then choose the "Custom Extension" option at the bottom, and enter the URL. +

+ +
+
These extensions are not compatible with Scratch.
+ Projects that use these extensions can't be uploaded to the Scratch website. + They can, however, be used in the packager. +
+ +
+ For compatibility, security, and offline support, each TurboWarp Desktop update contains an offline copy of these extensions from its release date, so some extensions may be outdated or missing. + Use the latest update for best results. +
+
+ + <% if (mode === "development") { %> +
+
+

Development Server Tools

+

+ Most recently modified extensions: + <% for (const extensionSlug of mostRecentExtensions) { %> + <%= extensionSlug %>.js + <% } %> +

+
+
+ <% } %> + + + +
+
+ <% + const peopleToHTML = (people) => { + let result = ''; + for (let i = 0; i < people.length; i++) { + result += people[i].toHTML(); + if (i === people.length - 1) { + // do nothing + } else if (i === people.length - 2) { + if (people.length > 2) { + result += ','; + } + result += ' and '; + } else { + result += ', '; + } + } + return result; + }; + %> + <% for (const [extensionSlug, metadata] of Object.entries(extensionMetadata)) { %> +
+
+ <% const image = extensionImages[extensionSlug] || 'images/unknown.svg'; %> + + +
+ + Open Extension +
+
+ +

<%= metadata.name %>

+

+ <%= metadata.description %> + <%- metadata.by.length ? `Created by ${peopleToHTML(metadata.by)}.` : '' %> + <%- metadata.original.length ? `Originally created by ${peopleToHTML(metadata.original)}.` : '' %> +

+
+ <% } %> +
+
+ + + + diff --git a/development/parse-extension-metadata.js b/development/parse-extension-metadata.js new file mode 100644 index 0000000000..632f01216c --- /dev/null +++ b/development/parse-extension-metadata.js @@ -0,0 +1,106 @@ +class Person { + constructor(name, link) { + /** @type {string} */ + this.name = name; + /** @type {string|null} */ + this.link = link; + } + + toHTML() { + // Don't need to bother escaping here. There's no vulnerability. + if (this.link) { + return `${this.name}`; + } + return this.name; + } +} + +class Extension { + constructor() { + this.id = ""; + this.name = ""; + this.description = ""; + /** @type {Person[]} */ + this.by = []; + /** @type {Person[]} */ + this.original = []; + } +} + +/** + * @param {string} string + * @param {string} split + * @returns {string[]} + */ +const splitFirst = (string, split) => { + const idx = string.indexOf(split); + if (idx === -1) { + return [string]; + } + return [string.substring(0, idx), string.substring(idx + split.length)]; +}; + +/** + * @param {string} person + * @returns {Person} + */ +const parsePerson = (person) => { + const parts = splitFirst(person, "<"); + if (parts.length === 1) { + return new Person(person, null); + } + + const name = parts[0].trim(); + const link = parts[1].replace(">", ""); + return new Person(name, link); +}; + +/** + * @param {string} extensionCode + * @return {Extension} + */ +const parseMetadata = (extensionCode) => { + const metadata = new Extension(); + + for (const line of extensionCode.split("\n")) { + if (!line.startsWith("//")) { + // End of header. + break; + } + + const withoutComment = line.substring(2).trim(); + const parts = splitFirst(withoutComment, ":"); + if (parts.length === 1) { + // Invalid. + continue; + } + + const key = parts[0].toLowerCase().trim(); + const value = parts[1].trim(); + + switch (key) { + case "id": + metadata.id = value; + break; + case "name": + metadata.name = value; + break; + case "description": + metadata.description = value; + break; + case "by": + metadata.by.push(parsePerson(value)); + break; + case "original": + metadata.original.push(parsePerson(value)); + break; + default: + // TODO + break; + } + } + + return metadata; +}; + +module.exports = parseMetadata; diff --git a/development/render-docs.js b/development/render-docs.js new file mode 100644 index 0000000000..3bdcfab88e --- /dev/null +++ b/development/render-docs.js @@ -0,0 +1,75 @@ +const path = require("path"); +const MarkdownIt = require("markdown-it"); +const renderTemplate = require("./render-template"); + +const md = new MarkdownIt({ + html: true, + linkify: true, + breaks: true, +}); + +md.renderer.rules.fence = function (tokens, idx, options, env, self) { + const token = tokens[idx]; + + if (token.info === "scratch") { + env.usesScratchBlocks = true; + return `
${md.utils.escapeHtml( + token.content + )}
`; + } + + // By default markdown-it will use a strange combination of and
; we'd rather it
+  // just use 
+  return `
${md.utils.escapeHtml(token.content)}
`; +}; + +/** + * @param {string} markdownSource Markdown code + * @param {string} slug Path slug like 'TestMuffin/fetch' + * @returns {string} HTML source code + */ +const renderDocs = (markdownSource, slug) => { + const env = {}; + const tokens = md.parse(markdownSource, env); + + // Extract the header + let headerHTML = "## file did not contain header ##"; + let headerText = headerHTML; + const headerStart = tokens.findIndex( + (token) => token.type === "heading_open" && token.tag === "h1" + ); + const headerEnd = tokens.findIndex( + (token) => token.type === "heading_close" && token.tag === "h1" + ); + if (headerStart !== -1 && headerEnd !== -1) { + const headerTokens = tokens.splice( + headerStart, + headerEnd - headerStart + 1 + ); + + // Discard the header tokens themselves, but render the HTML title with any formatting + headerTokens.shift(); + headerTokens.pop(); + headerHTML = md.renderer.render(headerTokens, md.options, env); + + // We also need a no-formatting version for the title + const justTextTokens = headerTokens.filter( + (token) => token.type === "inline" + ); + headerText = md.renderer.render(justTextTokens, md.options, env); + } + + const bodyHTML = md.renderer.render(tokens, md.options, env); + + return renderTemplate(path.join(__dirname, "docs-template.ejs"), { + slug, + headerHTML, + headerText, + bodyHTML, + usesScratchBlocks: !!env.usesScratchBlocks, + }); +}; + +module.exports = renderDocs; diff --git a/development/render-template.js b/development/render-template.js index fe8afd689d..41f036b4d1 100644 --- a/development/render-template.js +++ b/development/render-template.js @@ -1,10 +1,10 @@ -const fs = require('fs'); -const ejs = require('ejs'); +const fs = require("fs"); +const ejs = require("ejs"); // TODO: Investigate the value of removing dependency on `ejs` and possibly writing our own DSL. const renderTemplate = (path, data) => { - const inputEJS = fs.readFileSync(path, 'utf-8'); + const inputEJS = fs.readFileSync(path, "utf-8"); const outputHTML = ejs.render(inputEJS, data); return outputHTML; }; diff --git a/development/server.js b/development/server.js index 4f80d73dd3..875d6f6b8f 100644 --- a/development/server.js +++ b/development/server.js @@ -1,46 +1,49 @@ -const express = require('express'); -const Builder = require('./builder'); +const express = require("express"); +const Builder = require("./builder"); let mostRecentBuild = null; -const builder = new Builder('development'); +const builder = new Builder("development"); builder.startWatcher((newBuild) => { mostRecentBuild = newBuild; }); const app = express(); -app.set('strict routing', true); -app.set('x-powered-by', false); +app.set("strict routing", true); +app.set("x-powered-by", false); app.use((req, res, next) => { // If we don't tell the browser not to cache files, it does by default, and people will get confused when // script changes aren't being applied if they don't do a reload without cache. - res.setHeader('Cache-Control', 'no-store'); - res.setHeader('Pragma', 'no-cache'); + res.setHeader("Cache-Control", "no-store"); + res.setHeader("Pragma", "no-cache"); // Prevent browser from trying to guess file types. - res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader("X-Content-Type-Options", "nosniff"); // We don't want this site to be embedded in frames. - res.setHeader('X-Frame-Options', 'DENY'); + res.setHeader("X-Frame-Options", "DENY"); // No need to leak Referer headers. - res.setHeader('Referrer-Policy', 'no-referrer'); + res.setHeader("Referrer-Policy", "no-referrer"); // We want all resources used by the website to be local. // This CSP does *not* apply to the extensions, just the website. - res.setHeader('Content-Security-Policy', "default-src 'self' 'unsafe-inline' data: blob:"); + res.setHeader( + "Content-Security-Policy", + "default-src 'self' 'unsafe-inline' data: blob:" + ); // Allows loading cross-origin example images and matches GitHub pages. - res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader("Access-Control-Allow-Origin", "*"); next(); }); -app.get('/*', (req, res, next) => { +app.get("/*", (req, res, next) => { if (!mostRecentBuild) { - res.contentType('text/plain'); + res.contentType("text/plain"); res.status(500); - res.send('Build Failed; See Console'); + res.send("Build Failed; See Console"); return; } @@ -49,18 +52,14 @@ app.get('/*', (req, res, next) => { return next(); } - if (fileInBuild.getDiskPath) { - res.sendFile(fileInBuild.getDiskPath()); - } else { - res.contentType(fileInBuild.getType()); - res.send(fileInBuild.read()); - } + res.contentType(fileInBuild.getType()); + res.send(fileInBuild.read()); }); app.use((req, res) => { - res.contentType('text/plain'); + res.contentType("text/plain"); res.status(404); - res.send('404 Not Found'); + res.send("404 Not Found"); }); // The port the server runs on matters. The editor only treats port 8000 as unsandboxed. diff --git a/development/validate.js b/development/validate.js index e9386baebe..64bf9496ab 100644 --- a/development/validate.js +++ b/development/validate.js @@ -1,16 +1,22 @@ -const Builder = require('./builder'); -const Colors = require('./colors'); +const Builder = require("./builder"); +const Colors = require("./colors"); -const builder = new Builder('production'); +const builder = new Builder("production"); const errors = builder.validate(); if (errors.length === 0) { - console.log(`${Colors.GREEN}${Colors.BOLD}Validation checks passed.${Colors.RESET}`); + console.log( + `${Colors.GREEN}${Colors.BOLD}Validation checks passed.${Colors.RESET}` + ); process.exit(0); } else { - console.error(`${Colors.RED}${Colors.BOLD}${errors.length} ${errors.length === 1 ? 'file' : 'files'} failed validation.${Colors.RESET}`); - console.error(''); - for (const {fileName, error} of errors) { + console.error( + `${Colors.RED}${Colors.BOLD}${errors.length} ${ + errors.length === 1 ? "file" : "files" + } failed validation.${Colors.RESET}` + ); + console.error(""); + for (const { fileName, error } of errors) { console.error(`${Colors.BOLD}${fileName}${Colors.RESET}: ${error}`); } console.error(``); diff --git a/docs/CST1229/zip.md b/docs/CST1229/zip.md new file mode 100644 index 0000000000..ef32c21961 --- /dev/null +++ b/docs/CST1229/zip.md @@ -0,0 +1,227 @@ +# Zip + +The Zip extension allows you to read, create and edit .zip format files, including Scratch project and sprite files (.sb3, .sprite3). + +## Paths + +Most blocks in this extension work with a path format: + + - The path is relative to the current directory by default + - Directories (folders) and the filename are separated by slashes, like `folder1/folder2/file.txt` + - `..` goes to the parent directory, like `../file.txt` + - `.` goes to the current directory, like `./file.txt` + - A `/` at the very start goes to the root directory, like `/file.txt` + - A `/` at the end denotes a directory, like `folder/` + - Multiple slashes in a row or trying to go above the root directory will result in an error (usually the block doing nothing or returning the empty value) + +## Archive management blocks + +Blocks for creating and saving the current archive. Only one archive can be open at a time. + +--- + +```scratch +create empty archive :: #a49a3a +``` +Creates and opens an empty archive with nothing in it. + +--- + +```scratch +open zip from (URL v) [https://extensions.turbowarp.org] :: #a49a3a +``` + Opens a .zip (or .sb3 or .sprite3...) file. + +The type can be one of the following: + + - **URL**: A URL, which can be either a web URL or data: URL. Recommended. + - **base64**: A base64 string without the data URL header. + - **hex**: A sequence of hexadecimal bytes (like `101A1B1C`), without a separator. + - **binary**: A sequence of binary bytes (like `000000010010101001101011`), without a separator. + - **string**: Plain text. **Not recommended!** Text encoding behavior will likely break it, as it's a binary file. + +If the file is not of zip format (e.g RAR or 7z) or is password-protected, it won't be opened. Make sure to check if it loaded successfully with the archive `is open?` block. + +--- + +```scratch +(output zip type (data: URI v) compression level (6 v) :: #a49a3a) +``` +Save the zip data into a string, which can be saved with e.g the Files extension. + +The type can be one of the following: + + - **data**: URL: A base64-encoded data URL. Recommended. + - **base64**: A base64 string without the data URL header. + - **hex**: A sequence of hexadecimal bytes (like `101A1B1C`), without a separator. + - **binary**: A sequence of binary bytes (like `000000010010101001101011`), without a separator. + - **string**: Plain text. **Not recommended!** Text encoding behavior will likely break it, as it's a binary file. + +The compression level decides how much the zip is compressed. +Lower compression levels will result in a bigger file, but are faster to create. Usually, high compression levels provide diminishing returns (lesser gains the higher you go) with much slower speeds. + +A compression level of 0 (no compression) is the fastest, but will often result in a very big file. + +--- + +```scratch +close archive :: #a49a3a +``` +Closes the archive. Use after you're done working with it. + +--- + +```scratch + +``` +Returns true if an archive is open. + +## File blocks + +Blocks for working with files (and blocks that are general to both files and folders/directories.) + +--- + +```scratch + +``` +Returns if a file or directory exists or not. The slash at the end matters! If a directory named `folder` exists, `[folder] exists?` will return false, but `[folder/] exists?` will return true. + +--- + +```scratch +write file [new file.txt] content [Hello, world?] type (text v) :: #a49a3a +``` +Writes content to a file, creating the file if it doesn't exist and replacing its existing data if it does. + +The type can be one of the following: + + - **text**: Plain text. + - **URL**: A URL, which can be either a web URL or data: URL. Best for binary data (like other zip files). + - **base64**: A base64 string without the data URL header. + - **hex**: A sequence of hexadecimal bytes (like `101A1B1C`), without a separator. + - **binary**: A sequence of binary bytes (like `000000010010101001101011`), without a separator. + +--- + +```scratch +rename [hello.txt] to [hello renamed.txt] :: #a49a3a +``` +Renames a file or directory to another name. If the target file already exists, it will be overwritten. The current directory will also be updated. This block can also be used to move files to a different directory. + +--- + +```scratch +delete [hello.txt] :: #a49a3a +``` + +Deletes a file or directory (including everything in it). + +If the current directory is in that directory, it will be set to the closest existing parent directory. + +--- + +```scratch +(file [hello.txt] as (text v) :: #a49a3a) +``` +Get the contents of a file. + +The type can be one of the following: + + - **text**: Plain text. + - **data**: URL: A base64-encoded data URL. Best for binary data (like other zip files). + - **base64**: A base64 string without the data URL header. + - **hex**: A sequence of hexadecimal bytes (like `101A1B1C`), without a separator. + - **binary**: A sequence of binary bytes (like `000000010010101001101011`), without a separator. + +## File Info Blocks + +Blocks for getting and setting additional information on a file. + +--- + +```scratch +set (modified days since 2000 v) of [folder/dango.png] to [0] :: #a49a3a +``` +Set additional info on a file or directory. + +Available options: + + - **modified days since 2000**: The modification date of the file, as days since 2000. + - **unix modified timestamp**: The modification date of the file, as a Unix timestamp (milliseconds since 1970). Useful when combined with e.g the Time extension. + - **comment**: A comment on the file. Can be any text. Some programs may show this as metadata. + +--- + +```scratch +((name v) of [folder/dango.png] :: #a49a3a) +``` + Get additional info on a file or directory. + +Available options: + + - **name**: Just the name of this file (without the directories it's in). For example, the name of `/folder1/folder2/dango.png` would be `dango.png`. + - **path**: The full absolute path of this file (its name and any directories it's in). + - **folder**: Just the folders this file is in (without its filename). For example, the folder of `/folder1/folder2/dango.png` would be `/folder1/folder2/`. + - **modification date**: A human-readable version of the file's modification date. The output of this depends on the browser's language and possibly other factors. + - **long modification date**: A longer human-readable version of the file's modification date. The output of this depends on the browser's language and possibly other factors. + - **modified days since 2000**: The modification date of the file, as days since 2000. + - **unix modified timestamp**: The modification date of the file, as a Unix timestamp (milliseconds since 1970). Useful when combined with e.g the Time extension. + - **comment**: A comment on the file. Can be any text. Some programs may show this as metadata. + +## Directory blocks + +Blocks that deal with directories and the current directory. + +--- + +```scratch +create directory [new folder] :: #a49a3a +``` +Creates a directory with a name. This can create multiple directories at once (by having multiple directores in the path, like `/new folder1/new folder2/new folder3/`). + +--- + +```scratch +go to directory [folder] :: #a49a3a +``` +Moves the current directory (the default origin of most file operations) to the specified directory. If it doesn't exist, this block will do nothing. Use `..` to go to the previous (parent) directory, and `/` to go to the root. + +--- + +```scratch +(contents of directory [.] :: #a49a3a) +``` +Returns a list of files in a directory, as JSON (which you can parse with the JSON extension). + +--- + +```scratch +current directory path :: #a49a3a +``` +Returns the absolute path to the current directory. + +## Other blocks + +Miscellaneous stuff. + +--- + +```scratch +set archive comment to [any text] :: #a49a3a +``` +Sets the archive's comment to some text. Just like file comments, this is saved and may be displayed as metadata by some programs. + +--- + +```scratch +archive comment :: #a49a3a +``` +Returns the archive's comment. + +--- + +```scratch +(path [../folder3/] from [/folder/folder2] :: #a49a3a) +``` +Returns an absolute path from an origin path and a target path. Does not depend on the archive, so it works without one open. This is mostly a utility used internally, but it might be useful for users too. diff --git a/docs/CubesterYT/TurboHook.md b/docs/CubesterYT/TurboHook.md new file mode 100644 index 0000000000..4f93b66743 --- /dev/null +++ b/docs/CubesterYT/TurboHook.md @@ -0,0 +1,44 @@ +# TurboHook + +This extension allows you to post to webhooks from some common third-party websites or programs. + +## WebHook block + +Posts a message to a webhook. + +```scratch +webhook data: [] webhook url: [] :: #3c48c2 +``` +The empty area is where you insert the data reporter and/or connector reporter. The string area is where you put your webhook's URL. + +## Data Reporter + +The Menu area of the Data Reporter currently has three options, ("content","name","icon"). + +### Content + +```scratch +((content v) [] :: #3c48c2) +``` +When choosing "content", you can type in the text you want to send as the body of the webhook. + +### Name + +```scratch +((name v) [] :: #3c48c2) +``` +When choosing "name", you can type in the name of the name you want the webhook to display as on the third-party website. + +### Icon + +```scratch +((icon v) [] :: #3c48c2) +``` +When choosing "icon", you can type in a URL to the image you want to set as the profile picture of the webhook. + +## Connector Reporter + +```scratch +([], [] :: #3c48c2) +``` +This reporter connects two different data reporters together and/or another connector reporter so you can send multiple pieces of data to the webhook. diff --git a/docs/DNin/wake-lock.md b/docs/DNin/wake-lock.md new file mode 100644 index 0000000000..000f7f8ea2 --- /dev/null +++ b/docs/DNin/wake-lock.md @@ -0,0 +1,26 @@ +# Wake Lock + +The Wake Lock feature allows you to keep your computer's screen on while a project is running. This can be helpful when playing media or performing an important but time-consuming task. + +## Activating and Releasing Wake Lock + +```scratch +set wake lock to [on v] :: #0FBD8C +``` +This block will activate wake lock. + +If you ever need to check that wake lock has properly been activated, use the `is wake lock active?` boolean reporter. + +To release wake lock, simply change "on" to "off". + +You can also insert boolean reporters into the menu input. + +Wake lock will also be released automatically when the project stops or is restarted to ensure it isn't accidentally left on forever. + +## Browser support + +Not all browsers support wake lock (notably, Firefox does not). In these browsers requesting wake lock will not do anything. + +## Note + +The wake lock block takes a moment to finish running as it activates wake lock, so if you put it in a script with other blocks, it will yield briefly, so try keeping it separate from your other scripts. The `is wake lock active?` boolean reporter, however, does not have a delay. diff --git a/docs/Lily/Skins.md b/docs/Lily/Skins.md new file mode 100644 index 0000000000..2bd8771766 --- /dev/null +++ b/docs/Lily/Skins.md @@ -0,0 +1,109 @@ +# Skins + +This extension allows you to load and display images onto sprites, as Skins. + +In this extension, a "Skin" is an image that can replace what a sprite looks like. + +Unlike costumes, Skins are not loaded when the project is opened. Instead, Skins are loaded with blocks as the project is running. + +## Loading Skins + +Skins can be created in 3 different ways. Each way requires you to give the skin a name, which will be used by other blocks to reference the skin later. + +Loading a skin with the same name as another skin will overwrite the data of that skin. Any sprite that was using this skin will now show the new skin. + +--- + +```scratch +create SVG skin [] as [my skin] :: #6b56ff +``` +The first way is by creating a new skin with SVG markup data. The advantage to this is that it loads much quicker than the other loading blocks. The obvious disadvantage is that, unlike the other 2 blocks, it can only work with SVGs. + +--- + +```scratch +load skin from (costume 1 v) as [my skin] :: #6b56ff +``` +The second way is by loading a skin from a costume. + +It's important to note that this block will require the Advanced Option "Remove raw asset data after loading to save RAM" to be disabled in the packager in order for this block to work correctly in a packaged environment. **You do not need to do this within the editor.** + +If you intend to package your project, we don't encourage using this block for that reason. **None of the other blocks in this extension require this option to be disabled.** + +--- + +```scratch +load skin from URL [https://...] as [my skin] :: #6b56ff +``` +The final way is loading a skin through a URL. This block allows you to load any bitmap image as well as SVGs. + +```scratch +load skin from URL (snapshot stage :: #9966ff) as [my skin] :: #6b56ff +``` +While this block can work with a website URL, it's primarily designed to work with data URIs. Try using this with the "snapshot stage" block from the "Looks Plus" extension. + +For the final 2 blocks, the block will pause the script for a moment in order to load the skin. Treat them like "wait" blocks in your scripts, don't expect them to finish instantaneously. + +## Using Skins + +```scratch +set skin of (myself v) to [my skin] :: #6b56ff +``` +Skins can be applied to a sprite with this block, so long as you loaded the skin beforehand. Skins can be applied to multiple sprites/clones. + +Using the "myself" option will apply the skin to the sprite the block is running in: if the block is running in a clone, it will apply the skin to the clone. **Do not confuse "myself" with the sprite's name.** + +Skins will automatically be removed from every sprite when the project has stopped. + +--- + +```scratch +restore skin of (myself v) :: #6b56ff +``` +You can remove the skin of a sprite with the "restore skin" block. This will remove the skin from that specific sprite. + +--- + +```scratch +restore targets with skin [my skin] :: #6b56ff +``` +You can remove a skin from every sprite that has it applied with the "restore targets with skin" block. "Target" refers to "sprite" in this context. + +## Deleting Skins + +Skins that have been loaded will still exist after the project has stopped. In order to truly delete a skin, you have 2 methods. + +--- + +```scratch +delete skin [my skin] :: #6b56ff +``` +Delete a specified skin, and reset any sprite that had it applied. + +--- + +```scratch +delete all skins :: #6b56ff +``` +Delete every skin that has been loaded and reset all sprites that had any skin applied. + +## Other Blocks + +```scratch + +``` +Check whether a skin is actually loaded. This becomes true **after** the block has finished loading the skin. + +--- + +```scratch +((width v) of [my skin] :: #6b56ff) +``` +Get the width/height of a skin. The values are rounded. + +--- + +```scratch +(current skin of (myself v) :: #6b56ff) +``` +The name of the skin that is applied to the specified sprite. \ No newline at end of file diff --git a/docs/ar.md b/docs/ar.md new file mode 100644 index 0000000000..2946f5c96d --- /dev/null +++ b/docs/ar.md @@ -0,0 +1,344 @@ +# Augmented Reality + +## Requirements + + - [ARCore](https://play.google.com/store/apps/details?id=com.google.ar.core) + - browser with [WebXR API and immersive-ar](https://immersive-web.github.io/webxr-samples/report/) session type support + +At the moment of writing, only Chromium-based browsers support immersive-ar session type. + +## Other information + + - It is generally recommended to use this extension with fps set to 0, which in turbowarp means running project at the screen refresh rate. + - While exiting AR mode, there is a small chance TurboWarp will lose WebGL context. This problem has also been observed on other websites with AR and likely can't be fixed. If that happens, you can save the project, refresh the tab, load the project and continue. + - It isn't possible to be in AR mode and have video sensing enabled at the same time. Entering AR disables video sensing and makes it not toggleable until AR session ends. + +## Blocks + +```scratch +enter ar mode :: #d10000 +``` +If AR is not supported and the error message haven't been shown yet, shows a popup with an error message. + +If AR is supported and project is currently not in AR, attempts to enter AR mode. While doing so, it will pause the script it is in, in the same way ask block does. It will first try to enter AR mode directly. That may fail because entering AR can only be triggred by user interaction. If it fails, it will make it so after user clicks/taps the project will attempt to enter AR. If that fails as well or either of those 2 attempts succeed it will resume the script execution. After it resumes, project may or may not be in AR mode. Projects should handle both cases. + +The origin of coordinate system is placed at or close to position of the device at the time this block was called. + +--- + +```scratch +exit ar mode :: #d10000 +``` + +If the project is in AR mode, exits it. + +--- + + +```scratch + +``` +Tells if the project is currently in AR mode. + +--- + +```scratch + +``` +Tells if AR is supported on this device. + +--- + +```scratch + +``` +Tells if AR engine knows what the current camera position and orientation is. + +After entering AR mode, is is not immediately availible as the map of the environment needs to be built first. After enough information about environment has been gathered and processed, it becomes availible. It can temporarily become unavailible due to lack of detailed features in the view of camera that are used for motion tracking, fast motion causing camera image to become too blurry or camera getting covered. + +--- + +```scratch + +``` +Tells if AR engine knows where the point of ray intersection is. + +Can become unavailible for the same reasons as [is [pose] availible?] + +--- + +```scratch +(stage width :: #d10000) +``` + +Tells stage width in scratch units. Default is `480`. + +This value may change when entering AR. + +--- + +```scratch +(stage height :: #d10000) +``` + +Tells stage height in scratch units. Default is `360`. + +This value will not change when entering AR. + +--- + +```scratch +(item [1] of [view v] matrix :: #d10000) +``` + +view matrix - is a matrix that can be used to transform points from the world space (relatively to the world origin) to the view space (with origin is at the camera). It includes rotation and translation of the camera. + +Also: + +``` +view[13] = position[x] +view[14] = position[y] +view[15] = position[z] +``` + +--- + +```scratch +(item [1] of [inverse view v] matrix :: #d10000) +``` + +inverse view matrix = view matrix-1 + +Describes the opposite transformation to the view matrix. + +--- + +```scratch +(item [1] of [projection v] matrix :: #d10000) +``` + +For perspective projection in scratch you are likely used to doing something like this: + +``` +screenX = x * dist / z +screenY = y * dist / z +``` + +With this extension it's more complicated: projection is done by first doing a 4x4 projection matrix multiplication by 4D vector `x,y,z,1`, with result being `X,Y,Z,W`, then performing division of `X,Y,Z` by `W` to get screen coordinates in range from -1 to 1, and then multiplying them by half of stage width and height to get scratch coordinates. + +The matrix is a perspective projection matrix with the assumption camera faces negative z and that screen coordinates range from -1 to 1. + +It's calculated as: + +``` + /\ + /||\ + || OUT + IN || [X,Y,Z,W] +[x,y,z,1] || + \ [ 2 * dist / width, 0, 0, 0 ] [ 1, 2, 3, 4 ] +_______\ [ 0, 2 * dist / height, 0, 0 ] [ 5, 6, 7, 8 ] +^^^^^^^/ [ 0, 0, (near + far) / (near - far), -1 ] [ 9,10,11,12 ] + / [ 0, 0, near * far / (near - far) * 2, 0 ] [ 13,14,15,16 ] + +near = depth of near plane +far = depth of far plane +``` + +Point is only visible if `-W` < `X,Y,Z` < `W`. + +``` +screenX = X / W +screenY = Y / W +screenZ = Z / W (for depth buffer) +``` + +If the first codition was true, then `-1` < `screenX,screenY,screenZ` < `1` is also always true. Note that the opposite is not always true (think of what happens when W is negative). + +To get a better understanding of this topic, you may read: + + - https://webglfundamentals.org/webgl/lessons/webgl-3d-perspective.html + - https://stackoverflow.com/questions/41085117/why-does-gl-divide-gl-position-by-w-for-you-rather-than-letting-you-do-it-your + +Projection matrix contains a lot of 0s, and can be simplified. As the result of simplifications it is possible to make it look closer to how it is usually done on scratch: + +``` +screenX = x * (2 * dist / width) / -z * (width / 2) +screenY = y * (2 * dist / height) / -z * (height / 2) + +screenX = x * (2 * dist / width) * -1 * (width / 2) / z +screenY = y * (2 * dist / height) * -1 * (height / 2) / z + +negative_dist = (2 * dist / width) * (width / -2) = (2 * dist / height) * (height / -2) + +(2 * dist / width) = projection[1] +(2 * dist / height) = projection[6] + +negative_dist = projection[1] * (width / -2) = projection[6] * (height / -2) +screenX = x * negative_dist / z +screenY = y * negative_dist / z +``` + +--- + +```scratch +(item [1] of [combined v] matrix :: #d10000) +``` +combined matrix = projection matrix * inverse view matrix + +--- + +```scratch +(position [x v] :: #d10000) +``` +```scratch +(position [y v] :: #d10000) +``` +```scratch +(position [z v] :: #d10000) +``` +Camera position relatively to the world origin. + +--- + +```scratch +(orientation [r v] :: #d10000) +``` +```scratch +(orientation [i v] :: #d10000) +``` +```scratch +(orientation [j v] :: #d10000) +``` +```scratch +(orientation [k v] :: #d10000) +``` +Camera orientation represented as quaternion. + +--- + +```scratch +(hit position [x v] :: #d10000) +``` +```scratch +(hit position [y v] :: #d10000) +``` +```scratch +(hit position [z v] :: #d10000) +``` +A ray that originates from camera in the direction the camera is facing (center of the screen) intersects the first detected real world surface. This block returns the coordinates of that intersection point. + +--- + +```scratch +move everything by x: [0] y: [0] z: [0] :: #d10000 +``` +Moves the coordinate system by specified amount. + +It can also be understood as switching to a new coordinate system with origin at a given location in the current coordinate system. + +**Usage example:** + +After starting the project, it may be a good idea to give user a way to pick location for AR content and then perform this before starting the main AR game/animation/etc. + +```scratch +move everything by x: (hit position [x v] :: #d10000) y: (hit position [y v] :: #d10000) z: (hit position [z v] :: #d10000) :: #d10000 +``` + +--- + +```scratch +turn everything by r: [0] i: [0] j: [0] k: [0] :: #d10000 +``` + +Internally it also does quaternion normalization, so you don't have to worry about doing it yourself. + +**Usage example:** + +This script can be used to rotate `XZ` around `Y`-axis: + +```scratch +turn everything by r: ([cos v] of ((angle) / [2])) i: [0] j: ([sin v] of ((angle) / [2])) k: [0] :: #d10000 +``` + +--- + +```scratch +set resolution [1] :: #d10000 +``` + +accepts values from `0.1` to `1`. changes the resolution at which the project is rendered + + - `1` - is native screen resolution + - `0.5` - half the screen resolution + - `0.1` - one tenth of screen resolution + +Reducing resolution can improve performance and reduce memory usage. + +## Examples + +### Example 1 + +```scratch +when flag clicked +enter AR mode :: #d10000 +repeat until > + clear + point at [0.1] [0.5] [-0.3] +end + +define point at (x) (y) (z) +set [X v] to ((((x) * (item [1] of [combined v] matrix :: #d10000)) + ((y) * (item [5] of [combined v] matrix :: #d10000))) + (((z) * (item [9] of [combined v] matrix :: #d10000)) + (item [13] of [combined v] matrix :: #d10000))) +set [Y v] to ((((x) * (item [2] of [combined v] matrix :: #d10000)) + ((y) * (item [6] of [combined v] matrix :: #d10000))) + (((z) * (item [10] of [combined v] matrix :: #d10000)) + (item [14] of [combined v] matrix :: #d10000))) +set [Z v] to ((((x) * (item [3] of [combined v] matrix :: #d10000)) + ((y) * (item [7] of [combined v] matrix :: #d10000))) + (((z) * (item [11] of [combined v] matrix :: #d10000)) + (item [15] of [combined v] matrix :: #d10000))) +set [W v] to ((((x) * (item [4] of [combined v] matrix :: #d10000)) + ((y) * (item [8] of [combined v] matrix :: #d10000))) + (((z) * (item [12] of [combined v] matrix :: #d10000)) + (item [16] of [combined v] matrix :: #d10000))) +if <([abs v] of (X)) < (W)> then + if <([abs v] of (Y)) < (W)> then + if <([abs v] of (Z)) < (W)> then + go to x: (((X) / (W)) * ((stage width :: #d10000) / [2])) y: (((Y) / (W)) * ((stage height :: #d10000) / [2])) + pen down + pen up + end + end +end +``` + +### Example 2 + +```scratch +when flag clicked +enter AR mode :: #d10000 +repeat until > + clear + View matrix and camera coords + point at [0.1] [0.5] [-0.3] +end + +define View matrix and camera coords +set [MXX v] to (item [1] of [view v] matrix :: #d10000) +set [MXY v] to (item [5] of [view v] matrix :: #d10000) +set [MXZ v] to ([0] - (item [9] of [view v] matrix :: #d10000)) +set [MYX v] to (item [2] of [view v] matrix :: #d10000) +set [MYY v] to (item [6] of [view v] matrix :: #d10000) +set [MYZ v] to ([0] - (item [10] of [view v] matrix :: #d10000)) +set [MZX v] to (item [3] of [view v] matrix :: #d10000) +set [MZY v] to (item [7] of [view v] matrix :: #d10000) +set [MZZ v] to ([0] - (item [11] of [view v] matrix :: #d10000)) +set [camX v] to (item [13] of [view v] matrix :: #d10000) +set [camY v] to (item [14] of [view v] matrix :: #d10000) +set [camZ v] to (item [15] of [view v] matrix :: #d10000) +set [dist v] to ((item [6] of [projection v] matrix :: #d10000) * ((stage height :: #d10000) / [2])) + +define point at (x) (y) (z) +set [x2 v] to ((x) - (camX)) +set [y2 v] to ((y) - (camY)) +set [z2 v] to ((z) - (camZ)) +set [rotX v] to ((((x2) * (MXX)) + ((y2) * (MYX))) + ((z2) * (MZX))) +set [rotY v] to ((((x2) * (MXY)) + ((y2) * (MYY))) + ((z2) * (MZY))) +set [rotZ v] to ((((x2) * (MXZ)) + ((y2) * (MYZ))) + ((z2) * (MZZ))) +if <(rotZ) > [0]> then + go to x: (((rotX) * (dist)) / (rotZ)) y: (((rotY) * (dist)) / (rotZ)) + pen down + pen up +end +``` diff --git a/docs/bitwise.md b/docs/bitwise.md new file mode 100644 index 0000000000..979da2dcf1 --- /dev/null +++ b/docs/bitwise.md @@ -0,0 +1,149 @@ +# Bitwise + +This extension allows you to perform bit shifts and logic operations on integers as if they were encoded in binary. These operations can be used to create programmer calculators and more. + +The extension uses signed 32-bit integers, which can range from -2,147,483,648 to 2,147,483,647. + +## Blocks + +```scratch + +``` +Returns true if the input is a binary number. Binary numbers can only contain 0s and 1s, so this essentially tells you if the input only contains 0s and 1s. + +--- + +```scratch +([32] to binary :: #17cde6) +``` + +Converts the input from decimal to binary and returns the result. + +Note: All other blocks except for `is () binary?` and `() to number` will treat this result as a decimal number, so make sure to convert it back to decimal before you perform operations on it. + +--- + +```scratch +([0000000000100000] to number :: #17cde6) +``` + +Converts the input from binary to decimal and returns the result. + +--- + +```scratch +([x] >> [y] :: #17cde6) +``` +Arithmatically shifts each bit of the binary representation of _x_ to the right _y_ times and returns the result. + +Example: `32` → `100000` → `010000` → `16` + +The sign will be preserved, so negative numbers will stay negative. + +This is essentially the same thing as dividing by 2^*y* and rounding down to the nearest integer. + +--- + +```scratch +([x] \<\< [y] :: #17cde6) +``` +Arithmatically shifts each bit of the binary representation of _x_ to the left _y_ times and returns the result. + +Example: `32` → `0100000` → `1000000` → `64` + +The sign will be preserved, so negative numbers will stay negative. + +This is essentially the same thing as multiplying by 2^*y*. + +--- + +```scratch +([x] >>> [y] :: #17cde6) +``` +Logically shifts each bit of the binary representation of _x_ to the right _y_ times and returns the result. + +This also moves the sign bit, so it can result in negative numbers becoming positive. + +--- + +```scratch +([x] ↻ [y] :: #17cde6) +``` +Shifts each bit of the binary representation of _x_ to the right _y_ times. Numbers shifted past one end will reappear on the other (circle around). + +Example: `3` → `00000000000000000000000000000011` → `10000000000000000000000000000001` → `-2147483647` + +--- + +```scratch +([x] ↺ [y] :: #17cde6) +``` +Shifts each bit of the binary representation of _x_ to the left _y_ times. Numbers shifted past one end will reappear on the other (circle around). + +Example: `-2147483647` → `10000000000000000000000000000001` → `00000000000000000000000000000011` → `3` + +--- + +```scratch +([x] and [y] :: #17cde6) +``` +Logically ANDs the binary representation of the inputs together and returns the result. + +Example: + +``` + 14 1110 +AND 7 AND 0111 +—————————————————— + 6 0110 +``` + +--- + +```scratch +([x] or [y] :: #17cde6) +``` + +Logically ORs the binary representation of the inputs together and returns the result. + +Example: + +``` + 14 1110 +OR 7 OR 0111 +—————————————————— + 15 1111 +``` + +--- + +```scratch +([x] xor [y] :: #17cde6) +``` + +Logically XORs the binary representation of the inputs together and returns the result. + +Example: + +``` + 14 1110 +XOR 3 XOR 0011 +—————————————————— + 13 1101 +``` + +--- + +```scratch +(not [x] :: #17cde6) +``` + +Flips all bits of the binary representation of _x_ and returns the result. + +Example: + +``` +NOT 42 NOT ...00101010 +————————————————————————— + -43 ...11010101 +``` diff --git a/docs/box2d.md b/docs/box2d.md new file mode 100644 index 0000000000..c4ac9f5abf --- /dev/null +++ b/docs/box2d.md @@ -0,0 +1,175 @@ +# Box2D + +This extension allows you to easily implement proper physics using a physics library called Box2D. + +## World + +Adjust the physics of all sprites. + +```scratch +setup stage [boxed stage v] :: #0FBD8C +``` +Choose a type of containment to keep sprites within the stage. + + - Boxed stage: Keeps sprites from going off the bottom and sides. + - Open (with floor): Keeps sprites from going off the bottom. + - Open (no floor): Removes all boundaries; sprites can go wherever they want. + +--- + +```scratch +set gravity to x: [0] y: [-10] :: #0FBD8C +``` +Change the direction and strength of gravity. + +--- + +```scratch +step simulation :: #0FBD8C +``` + +Move forward in time by one step. Run this in a loop to keep the physics going. + +## Sprites + +Manipulate individual sprites. + +```scratch +enable for [this costume v] mode [normal v] :: #0FBD8C +``` + +Make physics apply to this sprite. It can also collide with other sprites that have physics enabled. + + - Enable for this costume: Enable physics only for the current sprite or clone. + - Enable for this circle: Enable physics for the current sprite or clone as if it were shaped like a circle. + - Enable for all sprites: Enable physics for all sprites. + +Precision mode will make the sprite work extra hard to make sure it doesn't overlap with anything. Note that this can decrease performance and even cause the project to get stuck, so use with care. + +--- + +```scratch +go to x: [0] y: [0] [in world v] :: #0FBD8C +``` + +Make the sprite go to the specified location. + + - In world: Relative to the center of the world. + - On stage: Relative to the center of the screen (if you've scrolled it). + - Relative: Relative to itself. + +--- + +```scratch +set velocity to sx: [0] sy: [0] :: #0FBD8C +``` + +Set the velocity (speed) of the sprite to the specified value. + +You can get the velocity of the current sprite with the (x velocity) and (y velocity) reporters. + +--- + +```scratch +push with force [25] in direction [0] :: #0FBD8C +``` + +Directly send the sprite flying in a certain direction, adding on to its current velocity. + +--- + +```scratch +set angular velocity to [30] :: #0FBD8C +``` +Set the angular velocity (rotational speed) of the sprite to the specified value. + +You can get the angular velocity of the current sprite with the (angular velocity) reporter. + +--- + +```scratch +spin with force [500] :: #0FBD8C +``` +Directly send the sprite spinning, adding on to its current angular (rotational) velocity. + +--- + +```scratch +set fixed [fixed in place v] :: #0FBD8C +``` +Choose whether the sprite is fixed in place or can move around. + +You can tell if the sprite is currently fixed in place with the (fixed?) reporter. + +--- + +```scratch +set density [normal v] :: #0FBD8C +``` +Set the sprite's density, which affects how heavy it is. + +You can get the sprite's current density with the (density) reporter. + +--- + +```scratch +set friction [normal v] :: #0FBD8C +``` +Set the sprite's roughness. Smoother settings make the sprite slipperier. + +You can get the sprite's current friction with the (friction) reporter. + +--- + +```scratch +set bounce [normal v] :: #0FBD8C +``` +Set the sprite's bounciness. + +You can get the sprite's current bounciness with the (bounce) reporter. + +--- + +```scratch +(touching [any v] :: #0FBD8C) +``` +Returns what other sprites the sprite is touching. Also includes the edges of the stage. + +If there are multiple sprites touching, it will return a comma-separated list of them. + +## Screen + +Move the camera around the world. + +```scratch +set scroll x: [0] y: [0] :: #0FBD8C +``` + +Scroll to the desired location. + +You can get the current screen position with the (x scroll) and (y scroll) reporters. + +This will not affect the world boundaries set by the [setup stage] block. + +## Example + +```scratch +when flag clicked +setup stage [boxed stage v] :: #0FBD8C +set gravity to x: [0] y: [-10] :: #0FBD8C +create clone of (myself v) +repeat [20] + step simulation :: #0FBD8C +end +create clone of (myself v) +forever + step simulation :: #0FBD8C +end + +when I start as a clone +show +enable for [this costume v] mode [normal v] :: #0FBD8C +go to x: [-150] y: [240] [in world v] :: #0FBD8C +set bounce [quite bouncy v] :: #0FBD8C +set angular velocity to [-4.1] :: #0FBD8C +``` diff --git a/docs/local-storage.md b/docs/local-storage.md new file mode 100644 index 0000000000..7969e6a33d --- /dev/null +++ b/docs/local-storage.md @@ -0,0 +1,125 @@ +# Local Storage + +This extension allows you to automatically save plain text in storage. Forget save codes! With this extension, we can make a game that doesn't require any user interaction to save progress. + +## Namespaces + +The namespace is basically like the file you want to read and write to. Each project should set this to something unique, such as developer/project title so each project gets its own storage space. If two different projects use the same namespace, they will overwrite eachother's data and cause very bad things! + +You can set which namespace to use with this block: + +```scratch +set storage namespace ID to [project title] :: #0FBD8C +``` + +Some example namespaces: + + - `griffpatch/Paper Minecraft` + - `Untitled-37 by TestMuffin` + +The format isn't important -- it just needs to be unique. Don't include a version number like "v1.4" in the namespace unless you want to discard data from old versions of your project. + +## Reading and writing + +After setting the namespace, you're ready to read from and write to storage. + +You can store data in keys, which are kind of like names of variables in any Scratch project, except they persist between sessions. + +You can store data in storage keys with this block: + +```scratch +set key [score] to [1000] :: #0FBD8C +``` + +And read it with: + +```scratch +(get key [score] :: #0FBD8C) +``` + +If you want to delete a key and its value, use this block: + +```scratch +delete key [score] :: #0FBD8C +``` + +Or wipe everything stored in the namespace: + +```scratch +delete all keys :: #0FBD8C +``` + +## Loading data into memory + +Relying on the disk to read information that gets saved such as a player's progress or stats can be pretty slow. That's why it's very useful to store this data in variables while it's in use. Variables are like your project's random-access memory. One way of doing this is by getting all keys from storage and putting their values in variables as part of your project's initialization process. + +If you're unfamiliar with computer memory, think of reading local storage as opening up your dictionary to look for a definition for someone and reading memory as remembering the definition you just read and saying it to someone. Of course, you wouldn't want to be constantly running to get the dictionary every time the same definition was requested. + +While browsers actually hold this data in memory automatically for quicker access, it's still more efficient and a better practice to take some work off the browser by not constantly getting the same storage key over and over again for no reason, and is much more important to know and think about if you ever use other programming languages where you don't want the disk to have to spend time reading the same data over and over again when it could be kept in memory (that's what memory is for). + +For example, in a game, you can speed up code that needs to know how many coins the player has collected by getting the storage key for coins and then loading it into a variable, just once on startup. + +```scratch +forever + if <(get key [coins] :: #0FBD8C) > [99]> then // Don't do this + broadcast (1-UP v) + end +end + +set [coins v] to (get key [coins] :: #0FBD8C) // Do this instead +forever + if <(coins) > [99]> then + broadcast (1-UP v) + end +end +``` + +So in general, we don't really need to load data from storage again once we have it in memory. If we already know what's in storage and what we're writing to storage because that data is already present in the project's variables, we really only need to read from storage once to initialize and then we're good. + +...Right? + +## Handling interference from other windows + +Sometimes a user may open the same project in multiple tabs or windows, each of which could be trying to read and write data to the same space. If this causes a desync, it can result in unexpected behavior. + +Here's an example scenario. Suppose someone opens the same game twice by accident. They play in Window A for a while and save the game. Then they close that window and do something else. Later, they come back to the other window they had opened before and start playing in Window B, but all the progress is "gone" because that window had already been running the game and had already loaded the save data before Window A had saved the progress that was made, and it's too late because they had saved the game in Window B before they realized the problem. This is unfortunate for the player, but more importantly for you, the developer, what is the project supposed to do now? Mix the data? + +So you can see how it's a good idea to consider that people may have multiple windows of the same project open intentionally or accidentally. + +You don't have to do things like auto-refresh content if you didn't intend for your project to support multi-window usage, but it's nice to at least make sure no glitches happen if someone accidentally had multiple windows open. Here are a few ways you can deal with this problem: + +## Initialize by loading from storage + +For games and other projects that are intended for use only in one window, we recommend you load all storage keys you need into variables only when the project starts so that nothing changes if a second instance of the project writes to storage while you're still using the first one. Then, when you need to save data, rewrite everything in the same group of data (like everything in the same save file in a game) to storage at the same time so nothing from other instances of the project gets mixed in. + +The worst that could happen with this implementation is that "data A" might be overwritten by "data B", if the user made a mistake. + +## Reload data from storage as needed + +Sometimes you might want to respond to changes in local storage. There is a block to help with this: + +```scratch +when another window changes storage :: hat #0FBD8C +``` + +The code under this block will run whenever a different instance of the project writes to storage or if a different project that's using the same namespace writes to storage. This allows your project to properly respond to and handle these events as they happen. + +This may or may not be important. You probably wouldn't want to use this technique in a game - games don't need to respond to other instances of themselves writing to storage. Plus, if game save data from one window is mixed in with data from another, it can cause glitches like sequence breaks. + +This kind of thing is more useful if you made something like a file system simulator that you want to have auto-refresh if the user makes edits in other windows, you may want to use this so that the content being displayed stays up to date even if another window modifies it. + +You could do this by constantly getting the storage key, but it's better to only grab keys from storage when necessary. The block above is how you do that. + +## Merge the data + +This one is more advanced and the way you would code it depends on the project, but you could make it so that when you're about to save data and another window wrote data that was not loaded into the first window, the two are merged - the data being written is merged with the data that was already present, so that if you collected 100 coins in one session and 100 XP in another, both of those changes in the save data would stay. + +Make sure to do this correctly because if data is merged incorrectly, it can cause glitches like sequence breaks in a game. + +I said that this is advanced, because sometimes these algorithms can get confused when merging changes to the same piece of data. (Like, what are we supposed to do if we're trying to merge two changes, one of which changes "A" to "B" and the other changes the same "A" to "C"?) This is known as a merge conflict. If you don't have any way to prioritize one change over another, you'll just be stuck with two branches of data. + +## Local storage limits + +This extension uses the browser's local storage API, which limits each website to around 5 MiB or 5,242,800 bytes of local storage data, so if we want local storage to be able to hold data for many projects, each one should stay well below this limit. We recommend only storing small files such as game save data or settings in local storage. + +In rare instances, such as when a system is running out of disk space, the browser may delete our data to make room for something else. We, unfortunately, cannot influence when this happens. diff --git a/extensions/-SIPC-/consoles.js b/extensions/-SIPC-/consoles.js index ccfa67059f..3454a518e1 100644 --- a/extensions/-SIPC-/consoles.js +++ b/extensions/-SIPC-/consoles.js @@ -1,185 +1,190 @@ -(function (Scratch) { - 'use strict'; - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS41NTIwNyIgaGVpZ2h0PSI4MC42MDMwOCIgdmlld0JveD0iMCwwLDgxLjU1MjA3LDgwLjYwMzA4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5LjIyMzk3LC0xNDAuNjk4NDYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC43NzYwMywxODFjMCwyMi4yNTc5MiAtMTguMjU2MDUsNDAuMzAxNTQgLTQwLjc3NjAzLDQwLjMwMTU0Yy0yMi41MTk5OCwwIC00MC43NzYwMywtMTguMDQzNjEgLTQwLjc3NjAzLC00MC4zMDE1NGMwLC0yMi4yNTc5MiAxOC4yNTYwNSwtNDAuMzAxNTQgNDAuNzc2MDMsLTQwLjMwMTU0YzIyLjUxOTk4LDAgNDAuNzc2MDMsMTguMDQzNjEgNDAuNzc2MDMsNDAuMzAxNTR6IiBmaWxsPSIjODA4MDgwIiBzdHJva2Utd2lkdGg9IjAiLz48cGF0aCBkPSJNMjY2LjE2NTgzLDE2Mi4xOTM1NnYyOS4yMDMwMWMwLDIuMjU4MzMgLTEuODMwNTksNC4wODg0MSAtNC4wODg0MSw0LjA4ODQxaC00NC4xNTQ4NWMtMi4yNTgzMywwIC00LjA4ODQxLC0xLjgzMDA3IC00LjA4ODQxLC00LjA4ODQxdi0yOS4yMDMwMWMwLC0yLjI1ODMzIDEuODMwMDcsLTQuMDg4NDEgNC4wODg0MSwtNC4wODg0MWg0NC4xNTQ4NWMyLjI1NzgzLDAgNC4wODg0MSwxLjgzMDA3IDQuMDg4NDEsNC4wODg0MXpNMjYyLjM1NTk1LDE2NC42NDUwOGMwLC0xLjM0MjAyIC0xLjA4ODAzLC0yLjQzMDA1IC0yLjQzMDA1LC0yLjQzMDA1aC0zOS44NTEyOGMtMS4zNDIwMiwwIC0yLjQzMDA1LDEuMDg4MDMgLTIuNDMwMDUsMi40MzAwNXYyNC4yOTk0N2MwLDEuMzQyMDIgMS4wODgwMywyLjQzMDA1IDIuNDMwMDUsMi40MzAwNWg3Ljc3NDYzdi0xMC4yMDU2OWMwLC0xLjM0MjU0IDEuMDg4MDMsLTIuNDMwMDUgMi40MzAwNSwtMi40MzAwNWMxLjM0MjU0LDAgMi40MzAwNSwxLjA4ODAzIDIuNDMwMDUsMi40MzAwNXYxMC4yMDU2OWg0Ljg2MDF2LTE4Ljk1Mzg4YzAsLTEuMzQyMDIgMS4wODgwMywtMi40MzAwNSAyLjQzMDA1LC0yLjQzMDA1YzEuMzQyNTQsMCAyLjQzMDA1LDEuMDg4MDMgMi40MzAwNSwyLjQzMDA1djE4Ljk1Mzg4aDQuODYwMXYtMTQuMDkzNzhjMCwtMS4zNDIwMiAxLjA4ODAzLC0yLjQzMDA1IDIuNDMwMDUsLTIuNDMwMDVjMS4zNDI1NCwwIDIuNDMwMDUsMS4wODgwMyAyLjQzMDA1LDIuNDMwMDV2MTQuMDkzNzhoNy43NzYxNmMxLjM0MjAyLDAgMi40MzAwNSwtMS4wODgwMyAyLjQzMDA1LC0yLjQzMDA1ek0yNTMuMDgyOTIsMjAxLjU1ODgzYzAsMS4yOTA0MSAtMS4wNDYxMiwyLjMzNjAyIC0yLjMzNjAyLDIuMzM2MDJoLTIxLjQ5MzhjLTEuMjg5ODksMCAtMi4zMzYwMiwtMS4wNDU2MSAtMi4zMzYwMiwtMi4zMzYwMmMwLC0xLjI4OTg5IDEuMDQ1NjEsLTIuMzM2MDIgMi4zMzYwMiwtMi4zMzYwMmgyMS40OTM4YzEuMjg5ODksMCAyLjMzNjAyLDEuMDQ2MTMgMi4zMzYwMiwyLjMzNjAyek0yNTAuNzQ2OSwxOTkuMjIyODEiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjc3NjAzMzMzMzMzMzQ6MzkuMzAxNTM5OTk5OTk5OTYtLT4='; - const icon2 = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjczNzk0NjIzOTU3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0NDAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTk0NC4zOCA3MC4xOWgtODY0Yy00NC4xOSAwLTgwIDM1LjgxLTgwIDgwdjU3MS40M2MwIDQ0LjE5IDM1LjgxIDgwIDgwIDgwaDg2NGM0NC4xOCAwIDgwLTM1LjgxIDgwLTgwVjE1MC4xOWMwLTQ0LjE5LTM1LjgyLTgwLTgwLTgweiBtNS40NSA2MDMuNDVjMCAyNi4yNi0yMS4yOSA0Ny41NS00Ny41NSA0Ny41NUg3NTAuMTJWNDQ1LjQxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYyNzUuNzhoLTk1LjFWMzUwLjMxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYzNzAuODhoLTk1LjF2LTE5OS43YzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOC00Ny41NSA0Ny41NXYxOTkuN0gxMjIuNDljLTI2LjI2IDAtNDcuNTUtMjEuMjktNDcuNTUtNDcuNTVWMTk4LjE2YzAtMjYuMjYgMjEuMjktNDcuNTUgNDcuNTUtNDcuNTVoNzc5Ljc5YzI2LjI2IDAgNDcuNTUgMjEuMjkgNDcuNTUgNDcuNTV2NDc1LjQ4ek03MjIuNjcgODc0Ljc2SDMwMi4wOWMtMjUuMjUgMC00NS43MSAyMC40Ny00NS43MSA0NS43MSAwIDI1LjI1IDIwLjQ3IDQ1LjcxIDQ1LjcxIDQ1LjcxaDQyMC41OGMyNS4yNCAwIDQ1LjcxLTIwLjQ2IDQ1LjcxLTQ1LjcxIDAtMjUuMjQtMjAuNDctNDUuNzEtNDUuNzEtNDUuNzF6IG0wIDAiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjQ0NDEiPjwvcGF0aD48L3N2Zz4='; - class Consoles { - constructor () {} - getInfo() { - return { - id: 'sipcconsole', - name: 'Consoles', - color1: '#808080', - color2: '#8c8c8c', - color3: '#999999', - menuIconURI: icon, - blockIconURI: icon2, - blocks: [ - { - opcode: 'Emptying', - blockType: Scratch.BlockType.COMMAND, - text: 'Clear Console', - arguments: {} - }, - { - opcode: 'Information', - blockType: Scratch.BlockType.COMMAND, - text: 'Information [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Information' - } - } - }, - { - opcode: 'Journal', - blockType: Scratch.BlockType.COMMAND, - text: 'Journal [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Journal' - } - } - }, - { - opcode: 'Warning', - blockType: Scratch.BlockType.COMMAND, - text: 'Warning [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Warning' - } - } - }, - { - opcode: 'Error', - blockType: Scratch.BlockType.COMMAND, - text: 'Error [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Error' - } - } - }, - { - opcode: 'debug', - blockType: Scratch.BlockType.COMMAND, - text: 'Debug [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Debug' - } - } - }, - +// Name: Consoles +// ID: sipcconsole +// Description: Blocks that interact the JavaScript console built in to your browser's developer tools. +// By: -SIPC- - '---', - { - opcode: 'group', - blockType: Scratch.BlockType.COMMAND, - text: 'Create a group named [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'group' - } - } - }, - { - opcode: 'groupCollapsed', - blockType: Scratch.BlockType.COMMAND, - text: 'Create a collapsed group named [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'group' - } - } - }, - { - opcode: 'groupEnd', - blockType: Scratch.BlockType.COMMAND, - text: 'Exit the current group', - arguments: {} - }, - '---', - { - opcode: 'Timeron', - blockType: Scratch.BlockType.COMMAND, - text: 'Start a timer named [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Time' - } - } - }, - { - opcode: 'Timerlog', - blockType: Scratch.BlockType.COMMAND, - text: 'Print the time run by the timer named [string]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Time' - } - } - }, - { - opcode: 'Timeroff', - blockType: Scratch.BlockType.COMMAND, - text: 'End the timer named [string] and print the time elapsed from start to end', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Time' - } - } - }, - ] - }; - } - Emptying () { - console.clear(); - } - Information ({string}) { - console.info(string); - } - Journal ({string}) { - console.log(string); - } - Warning ({string}) { - console.warn(string); - } - Error ({string}) { - console.error(string); - } - debug ({string}) { - console.debug(string); - } - group({string}) { - console.group(string); - } - groupCollapsed({string}) { - console.groupCollapsed(string); - } - groupEnd() { - console.groupEnd(); - } - Timeron ({string}) { - console.time(string); - } - Timerlog ({string}) { - console.timeLog(string); - } - Timeroff ({string}) { - console.timeEnd(string); - } +(function (Scratch) { + "use strict"; + const icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS41NTIwNyIgaGVpZ2h0PSI4MC42MDMwOCIgdmlld0JveD0iMCwwLDgxLjU1MjA3LDgwLjYwMzA4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5LjIyMzk3LC0xNDAuNjk4NDYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC43NzYwMywxODFjMCwyMi4yNTc5MiAtMTguMjU2MDUsNDAuMzAxNTQgLTQwLjc3NjAzLDQwLjMwMTU0Yy0yMi41MTk5OCwwIC00MC43NzYwMywtMTguMDQzNjEgLTQwLjc3NjAzLC00MC4zMDE1NGMwLC0yMi4yNTc5MiAxOC4yNTYwNSwtNDAuMzAxNTQgNDAuNzc2MDMsLTQwLjMwMTU0YzIyLjUxOTk4LDAgNDAuNzc2MDMsMTguMDQzNjEgNDAuNzc2MDMsNDAuMzAxNTR6IiBmaWxsPSIjODA4MDgwIiBzdHJva2Utd2lkdGg9IjAiLz48cGF0aCBkPSJNMjY2LjE2NTgzLDE2Mi4xOTM1NnYyOS4yMDMwMWMwLDIuMjU4MzMgLTEuODMwNTksNC4wODg0MSAtNC4wODg0MSw0LjA4ODQxaC00NC4xNTQ4NWMtMi4yNTgzMywwIC00LjA4ODQxLC0xLjgzMDA3IC00LjA4ODQxLC00LjA4ODQxdi0yOS4yMDMwMWMwLC0yLjI1ODMzIDEuODMwMDcsLTQuMDg4NDEgNC4wODg0MSwtNC4wODg0MWg0NC4xNTQ4NWMyLjI1NzgzLDAgNC4wODg0MSwxLjgzMDA3IDQuMDg4NDEsNC4wODg0MXpNMjYyLjM1NTk1LDE2NC42NDUwOGMwLC0xLjM0MjAyIC0xLjA4ODAzLC0yLjQzMDA1IC0yLjQzMDA1LC0yLjQzMDA1aC0zOS44NTEyOGMtMS4zNDIwMiwwIC0yLjQzMDA1LDEuMDg4MDMgLTIuNDMwMDUsMi40MzAwNXYyNC4yOTk0N2MwLDEuMzQyMDIgMS4wODgwMywyLjQzMDA1IDIuNDMwMDUsMi40MzAwNWg3Ljc3NDYzdi0xMC4yMDU2OWMwLC0xLjM0MjU0IDEuMDg4MDMsLTIuNDMwMDUgMi40MzAwNSwtMi40MzAwNWMxLjM0MjU0LDAgMi40MzAwNSwxLjA4ODAzIDIuNDMwMDUsMi40MzAwNXYxMC4yMDU2OWg0Ljg2MDF2LTE4Ljk1Mzg4YzAsLTEuMzQyMDIgMS4wODgwMywtMi40MzAwNSAyLjQzMDA1LC0yLjQzMDA1YzEuMzQyNTQsMCAyLjQzMDA1LDEuMDg4MDMgMi40MzAwNSwyLjQzMDA1djE4Ljk1Mzg4aDQuODYwMXYtMTQuMDkzNzhjMCwtMS4zNDIwMiAxLjA4ODAzLC0yLjQzMDA1IDIuNDMwMDUsLTIuNDMwMDVjMS4zNDI1NCwwIDIuNDMwMDUsMS4wODgwMyAyLjQzMDA1LDIuNDMwMDV2MTQuMDkzNzhoNy43NzYxNmMxLjM0MjAyLDAgMi40MzAwNSwtMS4wODgwMyAyLjQzMDA1LC0yLjQzMDA1ek0yNTMuMDgyOTIsMjAxLjU1ODgzYzAsMS4yOTA0MSAtMS4wNDYxMiwyLjMzNjAyIC0yLjMzNjAyLDIuMzM2MDJoLTIxLjQ5MzhjLTEuMjg5ODksMCAtMi4zMzYwMiwtMS4wNDU2MSAtMi4zMzYwMiwtMi4zMzYwMmMwLC0xLjI4OTg5IDEuMDQ1NjEsLTIuMzM2MDIgMi4zMzYwMiwtMi4zMzYwMmgyMS40OTM4YzEuMjg5ODksMCAyLjMzNjAyLDEuMDQ2MTMgMi4zMzYwMiwyLjMzNjAyek0yNTAuNzQ2OSwxOTkuMjIyODEiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjc3NjAzMzMzMzMzMzQ6MzkuMzAxNTM5OTk5OTk5OTYtLT4="; + const icon2 = + "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjczNzk0NjIzOTU3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0NDAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTk0NC4zOCA3MC4xOWgtODY0Yy00NC4xOSAwLTgwIDM1LjgxLTgwIDgwdjU3MS40M2MwIDQ0LjE5IDM1LjgxIDgwIDgwIDgwaDg2NGM0NC4xOCAwIDgwLTM1LjgxIDgwLTgwVjE1MC4xOWMwLTQ0LjE5LTM1LjgyLTgwLTgwLTgweiBtNS40NSA2MDMuNDVjMCAyNi4yNi0yMS4yOSA0Ny41NS00Ny41NSA0Ny41NUg3NTAuMTJWNDQ1LjQxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYyNzUuNzhoLTk1LjFWMzUwLjMxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYzNzAuODhoLTk1LjF2LTE5OS43YzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOC00Ny41NSA0Ny41NXYxOTkuN0gxMjIuNDljLTI2LjI2IDAtNDcuNTUtMjEuMjktNDcuNTUtNDcuNTVWMTk4LjE2YzAtMjYuMjYgMjEuMjktNDcuNTUgNDcuNTUtNDcuNTVoNzc5Ljc5YzI2LjI2IDAgNDcuNTUgMjEuMjkgNDcuNTUgNDcuNTV2NDc1LjQ4ek03MjIuNjcgODc0Ljc2SDMwMi4wOWMtMjUuMjUgMC00NS43MSAyMC40Ny00NS43MSA0NS43MSAwIDI1LjI1IDIwLjQ3IDQ1LjcxIDQ1LjcxIDQ1LjcxaDQyMC41OGMyNS4yNCAwIDQ1LjcxLTIwLjQ2IDQ1LjcxLTQ1LjcxIDAtMjUuMjQtMjAuNDctNDUuNzEtNDUuNzEtNDUuNzF6IG0wIDAiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjQ0NDEiPjwvcGF0aD48L3N2Zz4="; + class Consoles { + constructor() {} + getInfo() { + return { + id: "sipcconsole", + name: "Consoles", + color1: "#808080", + color2: "#8c8c8c", + color3: "#999999", + menuIconURI: icon, + blockIconURI: icon2, + blocks: [ + { + opcode: "Emptying", + blockType: Scratch.BlockType.COMMAND, + text: "Clear Console", + arguments: {}, + }, + { + opcode: "Information", + blockType: Scratch.BlockType.COMMAND, + text: "Information [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Information", + }, + }, + }, + { + opcode: "Journal", + blockType: Scratch.BlockType.COMMAND, + text: "Journal [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Journal", + }, + }, + }, + { + opcode: "Warning", + blockType: Scratch.BlockType.COMMAND, + text: "Warning [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Warning", + }, + }, + }, + { + opcode: "Error", + blockType: Scratch.BlockType.COMMAND, + text: "Error [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Error", + }, + }, + }, + { + opcode: "debug", + blockType: Scratch.BlockType.COMMAND, + text: "Debug [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Debug", + }, + }, + }, + "---", + { + opcode: "group", + blockType: Scratch.BlockType.COMMAND, + text: "Create a group named [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "group", + }, + }, + }, + { + opcode: "groupCollapsed", + blockType: Scratch.BlockType.COMMAND, + text: "Create a collapsed group named [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "group", + }, + }, + }, + { + opcode: "groupEnd", + blockType: Scratch.BlockType.COMMAND, + text: "Exit the current group", + arguments: {}, + }, + "---", + { + opcode: "Timeron", + blockType: Scratch.BlockType.COMMAND, + text: "Start a timer named [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Time", + }, + }, + }, + { + opcode: "Timerlog", + blockType: Scratch.BlockType.COMMAND, + text: "Print the time run by the timer named [string]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Time", + }, + }, + }, + { + opcode: "Timeroff", + blockType: Scratch.BlockType.COMMAND, + text: "End the timer named [string] and print the time elapsed from start to end", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Time", + }, + }, + }, + ], + }; + } + Emptying() { + console.clear(); + } + Information({ string }) { + console.info(string); + } + Journal({ string }) { + console.log(string); + } + Warning({ string }) { + console.warn(string); + } + Error({ string }) { + console.error(string); + } + debug({ string }) { + console.debug(string); + } + group({ string }) { + console.group(string); + } + groupCollapsed({ string }) { + console.groupCollapsed(string); + } + groupEnd() { + console.groupEnd(); + } + Timeron({ string }) { + console.time(string); + } + Timerlog({ string }) { + console.timeLog(string); + } + Timeroff({ string }) { + console.timeEnd(string); } - Scratch.extensions.register(new Consoles()); + } + Scratch.extensions.register(new Consoles()); })(Scratch); diff --git a/extensions/-SIPC-/recording.js b/extensions/-SIPC-/recording.js index 40b2fd8770..2265654c94 100644 --- a/extensions/-SIPC-/recording.js +++ b/extensions/-SIPC-/recording.js @@ -1,111 +1,114 @@ -(function (Scratch) { - 'use strict'; - if (!Scratch.extensions.unsandboxed) { - throw new Error('Recording must be run unsandboxed'); - } - /** @type {MediaRecorder|null} */ - let mediaRecorder = null; - let recordedChunks = []; - const icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjg1MTAzNzAzNDU1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMzMjIiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkxIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiPjxwYXRoIGQ9Ik04MzMgNDQ1LjVjMTcuNjczIDAgMzIgMTQuMzI3IDMyIDMyIDAgMTgzLjcyMi0xNDAuNTUzIDMzNC42MTYtMzE5Ljk5NyAzNTEuMDIxTDU0NSA4OTVoMTk0YzE3LjY3MyAwIDMyIDE0LjMyNyAzMiAzMiAwIDE3LjY3My0xNC4zMjcgMzItMzIgMzJIMjg3Yy0xNy42NzMgMC0zMi0xNC4zMjctMzItMzIgMC0xNy42NzMgMTQuMzI3LTMyIDMyLTMyaDE5NGwtMC4wMDMtNjYuMzg5QzMwMS4wNzUgODEyLjY3NyAxNjAgNjYxLjU2MyAxNjAgNDc3LjVjMC0xNy42NzMgMTQuMzI3LTMyIDMyLTMyIDE3LjQ5NiAwIDMxLjcxMyAxNC4wNDIgMzEuOTk2IDMxLjQ3bDAuMDA0IDAuNTNDMjI0IDYzNi44MzQgMzUzLjE2NiA3NjYgNTEyLjUgNzY2YzE1Ny43NCAwIDI4NS45MTQtMTI2LjU5NSAyODguNDYxLTI4My43M2wwLjAzOS00Ljc3YzAtMTcuNjczIDE0LjMyNy0zMiAzMi0zMnpNNTEzIDY1YzEyMy4wMjEgMCAyMjIuOTgzIDk4LjczMSAyMjQuOTcgMjIxLjI4TDczOCAyOTB2MTg2YzAgMTI0LjI2NC0xMDAuNzM2IDIyNS0yMjUgMjI1LTEyMy4wMjEgMC0yMjIuOTgzLTk4LjczMS0yMjQuOTctMjIxLjI4TDI4OCA0NzZWMjkwYzAtMTI0LjI2NCAxMDAuNzM2LTIyNSAyMjUtMjI1eiBtMCA2NGMtODguMDI5IDAtMTU5LjU1NyA3MC42NDgtMTYwLjk3OCAxNTguMzM4TDM1MiAyOTB2MTg2YzAgODguOTE4IDcyLjA4MiAxNjEgMTYxIDE2MSA4OC4wMjkgMCAxNTkuNTU3LTcwLjY0OCAxNjAuOTc4LTE1OC4zMzhMNjc0IDQ3NlYyOTBjMC04OC45MTgtNzIuMDgyLTE2MS0xNjEtMTYxeiBtMTI0LjU0MyAyNTguNTkxYzE3LjM4OCA4OS40NTMtNDEuMDMyIDE3Ni4wNjQtMTMwLjQ4NSAxOTMuNDUyLTE3LjM0OCAzLjM3Mi0zNC4xNDYtNy45NTgtMzcuNTE4LTI1LjMwNi0zLjMzOC0xNy4xNzUgNy43MzMtMzMuODEgMjQuNzg4LTM3LjQxM2wwLjUxOC0wLjEwNWM1NC4yMDktMTAuNTM3IDg5LjgtNjIuNjA0IDgwLjE3OC0xMTYuNzc0bC0wLjMwNS0xLjY0MmMtMy4zNzItMTcuMzQ4IDcuOTU4LTM0LjE0NiAyNS4zMDYtMzcuNTE4IDE3LjM0OS0zLjM3MiAzNC4xNDYgNy45NTggMzcuNTE4IDI1LjMwNnoiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjMzMjMiPjwvcGF0aD48L3N2Zz4='; - class Recording { - getInfo() { - return { - id: 'sipcrecording', - name: 'Recording', - color1: '#696969', - blocks: [ - { - opcode: 'startRecording', - blockType: Scratch.BlockType.COMMAND, - text: 'Start recording', - blockIconURI: icon, - arguments: {} - }, - { - opcode: 'stopRecording', - blockType: Scratch.BlockType.COMMAND, - text: 'Stop recording', - blockIconURI: icon, - arguments: {} - }, - { - opcode: 'stopRecordingAndDownload', - blockType: Scratch.BlockType.COMMAND, - text: 'Stop recording and download with [name] as filename', - blockIconURI: icon, - arguments: { - name: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'recording.wav' - } - } - }, - { - opcode: 'isRecording', - blockType: Scratch.BlockType.BOOLEAN, - text: 'Recording?', - blockIconURI: icon, - arguments: {} - }, - ] - }; - } - async startRecording() { - recordedChunks = []; - if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { - console.error('The recording function is not supported by the browser'); - return; - } - try { - if (!await Scratch.canRecordAudio()) { - throw new Error('VM denied permission'); - } - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - mediaRecorder = new MediaRecorder(stream); - mediaRecorder.addEventListener('dataavailable', function (e) { - recordedChunks.push(e.data); - }); - mediaRecorder.start(); - console.log('Start recording'); - } catch (e) { - console.error('Could not start recording', e); - } - } - stopRecording() { - if (!mediaRecorder) { - console.error('Recording not started'); - return; - } - console.log('Stop recording'); - mediaRecorder.stop(); - mediaRecorder = null; - recordedChunks = []; - } - stopRecordingAndDownload({name}) { - if (!mediaRecorder) { - console.error('Recording not started'); - return; - } - console.log('Stop recording'); - mediaRecorder.addEventListener('stop', function () { - const blob = new Blob(recordedChunks, { type: 'audio/wav' }); - const url = URL.createObjectURL(blob); - const downloadLink = document.createElement('a'); - downloadLink.href = url; - downloadLink.download = name; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - URL.revokeObjectURL(url); - recordedChunks = []; - }); - mediaRecorder.stop(); - mediaRecorder = null; - } - isRecording() { - return !!mediaRecorder; - } - } - Scratch.extensions.register(new Recording()); -})(Scratch); -//BY -SIPC- 502415953 +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) { + throw new Error("Recording must be run unsandboxed"); + } + /** @type {MediaRecorder|null} */ + let mediaRecorder = null; + let recordedChunks = []; + const icon = + "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjg1MTAzNzAzNDU1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMzMjIiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkxIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjIwMCIgaGVpZ2h0PSIyMDAiPjxwYXRoIGQ9Ik04MzMgNDQ1LjVjMTcuNjczIDAgMzIgMTQuMzI3IDMyIDMyIDAgMTgzLjcyMi0xNDAuNTUzIDMzNC42MTYtMzE5Ljk5NyAzNTEuMDIxTDU0NSA4OTVoMTk0YzE3LjY3MyAwIDMyIDE0LjMyNyAzMiAzMiAwIDE3LjY3My0xNC4zMjcgMzItMzIgMzJIMjg3Yy0xNy42NzMgMC0zMi0xNC4zMjctMzItMzIgMC0xNy42NzMgMTQuMzI3LTMyIDMyLTMyaDE5NGwtMC4wMDMtNjYuMzg5QzMwMS4wNzUgODEyLjY3NyAxNjAgNjYxLjU2MyAxNjAgNDc3LjVjMC0xNy42NzMgMTQuMzI3LTMyIDMyLTMyIDE3LjQ5NiAwIDMxLjcxMyAxNC4wNDIgMzEuOTk2IDMxLjQ3bDAuMDA0IDAuNTNDMjI0IDYzNi44MzQgMzUzLjE2NiA3NjYgNTEyLjUgNzY2YzE1Ny43NCAwIDI4NS45MTQtMTI2LjU5NSAyODguNDYxLTI4My43M2wwLjAzOS00Ljc3YzAtMTcuNjczIDE0LjMyNy0zMiAzMi0zMnpNNTEzIDY1YzEyMy4wMjEgMCAyMjIuOTgzIDk4LjczMSAyMjQuOTcgMjIxLjI4TDczOCAyOTB2MTg2YzAgMTI0LjI2NC0xMDAuNzM2IDIyNS0yMjUgMjI1LTEyMy4wMjEgMC0yMjIuOTgzLTk4LjczMS0yMjQuOTctMjIxLjI4TDI4OCA0NzZWMjkwYzAtMTI0LjI2NCAxMDAuNzM2LTIyNSAyMjUtMjI1eiBtMCA2NGMtODguMDI5IDAtMTU5LjU1NyA3MC42NDgtMTYwLjk3OCAxNTguMzM4TDM1MiAyOTB2MTg2YzAgODguOTE4IDcyLjA4MiAxNjEgMTYxIDE2MSA4OC4wMjkgMCAxNTkuNTU3LTcwLjY0OCAxNjAuOTc4LTE1OC4zMzhMNjc0IDQ3NlYyOTBjMC04OC45MTgtNzIuMDgyLTE2MS0xNjEtMTYxeiBtMTI0LjU0MyAyNTguNTkxYzE3LjM4OCA4OS40NTMtNDEuMDMyIDE3Ni4wNjQtMTMwLjQ4NSAxOTMuNDUyLTE3LjM0OCAzLjM3Mi0zNC4xNDYtNy45NTgtMzcuNTE4LTI1LjMwNi0zLjMzOC0xNy4xNzUgNy43MzMtMzMuODEgMjQuNzg4LTM3LjQxM2wwLjUxOC0wLjEwNWM1NC4yMDktMTAuNTM3IDg5LjgtNjIuNjA0IDgwLjE3OC0xMTYuNzc0bC0wLjMwNS0xLjY0MmMtMy4zNzItMTcuMzQ4IDcuOTU4LTM0LjE0NiAyNS4zMDYtMzcuNTE4IDE3LjM0OS0zLjM3MiAzNC4xNDYgNy45NTggMzcuNTE4IDI1LjMwNnoiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjMzMjMiPjwvcGF0aD48L3N2Zz4="; + class Recording { + getInfo() { + return { + id: "sipcrecording", + name: "Recording", + color1: "#696969", + blocks: [ + { + opcode: "startRecording", + blockType: Scratch.BlockType.COMMAND, + text: "Start recording", + blockIconURI: icon, + arguments: {}, + }, + { + opcode: "stopRecording", + blockType: Scratch.BlockType.COMMAND, + text: "Stop recording", + blockIconURI: icon, + arguments: {}, + }, + { + opcode: "stopRecordingAndDownload", + blockType: Scratch.BlockType.COMMAND, + text: "Stop recording and download with [name] as filename", + blockIconURI: icon, + arguments: { + name: { + type: Scratch.ArgumentType.STRING, + defaultValue: "recording.wav", + }, + }, + }, + { + opcode: "isRecording", + blockType: Scratch.BlockType.BOOLEAN, + text: "Recording?", + blockIconURI: icon, + arguments: {}, + }, + ], + }; + } + async startRecording() { + recordedChunks = []; + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + console.error("The recording function is not supported by the browser"); + return; + } + try { + if (!(await Scratch.canRecordAudio())) { + throw new Error("VM denied permission"); + } + const stream = await navigator.mediaDevices.getUserMedia({ + audio: true, + }); + mediaRecorder = new MediaRecorder(stream); + mediaRecorder.addEventListener("dataavailable", function (e) { + recordedChunks.push(e.data); + }); + mediaRecorder.start(); + console.log("Start recording"); + } catch (e) { + console.error("Could not start recording", e); + } + } + stopRecording() { + if (!mediaRecorder) { + console.error("Recording not started"); + return; + } + console.log("Stop recording"); + mediaRecorder.stop(); + mediaRecorder = null; + recordedChunks = []; + } + stopRecordingAndDownload({ name }) { + if (!mediaRecorder) { + console.error("Recording not started"); + return; + } + console.log("Stop recording"); + mediaRecorder.addEventListener("stop", function () { + const blob = new Blob(recordedChunks, { type: "audio/wav" }); + const url = URL.createObjectURL(blob); + const downloadLink = document.createElement("a"); + downloadLink.href = url; + downloadLink.download = name; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + URL.revokeObjectURL(url); + recordedChunks = []; + }); + mediaRecorder.stop(); + mediaRecorder = null; + } + isRecording() { + return !!mediaRecorder; + } + } + Scratch.extensions.register(new Recording()); +})(Scratch); +//BY -SIPC- 502415953 diff --git a/extensions/-SIPC-/time.js b/extensions/-SIPC-/time.js index 778f94c0c5..cd689f4e40 100644 --- a/extensions/-SIPC-/time.js +++ b/extensions/-SIPC-/time.js @@ -1,118 +1,345 @@ +/* Name: Time +* ID: sipctime +* Old Description: Blocks for interacting with unix timestamps and other date strings. +* Updated Description: Blocks for Time, Date, and Time Zone calculations and interactions. +* Main Extension By: -SIPC- +* Calculation Blocks By: SharkPool +*/ + (function (Scratch) { - 'use strict'; - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS44ODU0IiBoZWlnaHQ9IjgwLjYwMzA4IiB2aWV3Qm94PSIwLDAsODEuODg1NCw4MC42MDMwOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE5OS4wNTczLC0xMzkuNjk4NDYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC45NDI3LDE4MGMwLDIyLjI1NzkyIC0xOC4zMzA2Nyw0MC4zMDE1NCAtNDAuOTQyNyw0MC4zMDE1NGMtMjIuNjEyMDMsMCAtNDAuOTQyNywtMTguMDQzNjEgLTQwLjk0MjcsLTQwLjMwMTU0YzAsLTIyLjI1NzkyIDE4LjMzMDY3LC00MC4zMDE1NCA0MC45NDI3LC00MC4zMDE1NGMyMi42MTIwMywwIDQwLjk0MjcsMTguMDQzNjEgNDAuOTQyNyw0MC4zMDE1NHoiIGZpbGw9IiNmZjgwMDAiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0yNjYuNTM0MzcsMTgwYzAsMTQuNjQxMjkgLTExLjg5MzA4LDI2LjUzNDM3IC0yNi41MzQzNywyNi41MzQzN2MtMTQuNjQxMjksMCAtMjYuNTM0MzcsLTExLjg5MzA4IC0yNi41MzQzNywtMjYuNTM0MzdjMCwtMTQuNjQxMjkgMTEuODkzMDgsLTI2LjUzNDM3IDI2LjUzNDM3LC0yNi41MzQzN2MxNC42NDEyOSwwIDI2LjUzNDM3LDExLjg5MzA4IDI2LjUzNDM3LDI2LjUzNDM3ek0yNTMuMjE5OCwxODUuOTcwMjNsLTExLjMyNDQ5LC02LjUzODgzdi0xNC41OTM5aC0zLjc5MDYydjE3LjA1NzgxaDAuNTIxMjFsMTIuNjk4NTksNy4zNDQzM3oiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjk0MjY5NjA1MzgwMTE0OjQwLjMwMTUzNTI2NTQ4NjcwNi0tPg=='; - const icon2 = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzUiIGhlaWdodD0iMTc1IiB2aWV3Qm94PSIwLDAsMTc1LDE3NSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1Mi41LC05Mi41KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTMyNy41LDE4MGMwLDQ4LjI4MTI1IC0zOS4yMTg3NSw4Ny41IC04Ny41LDg3LjVjLTQ4LjI4MTI1LDAgLTg3LjUsLTM5LjIxODc1IC04Ny41LC04Ny41YzAsLTQ4LjI4MTI1IDM5LjIxODc1LC04Ny41IDg3LjUsLTg3LjVjNDguMjgxMjUsMCA4Ny41LDM5LjIxODc1IDg3LjUsODcuNXpNMjgzLjU5Mzc1LDE5OS42ODc1bC0zNy4zNDM3NSwtMjEuNTYyNXYtNDguMTI1aC0xMi41djU2LjI1aDEuNzE4NzVsNDEuODc1LDI0LjIxODc1eiIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg3LjU6ODcuNS0tPg=='; - class Time { - getInfo() { - return { - id: 'sipctime', - name: 'Time', - color1: '#ff8000', - color2: '#804000', - color3: '#804000', - menuIconURI: icon, - blockIconURI: icon2, - blocks: [ - { - opcode: 'Timestamp', - blockType: Scratch.BlockType.REPORTER, - text: 'The current timestamp', - arguments: {} - }, - { - opcode: 'timezone', - blockType: Scratch.BlockType.REPORTER, - text: 'The current time zone', - arguments: {} - }, - { - opcode: 'Timedata', - blockType: Scratch.BlockType.REPORTER, - text: 'Extract [Timedata] from [timestamp]', - arguments: { - timestamp: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1145141980000' - }, - Timedata: { - type: Scratch.ArgumentType.STRING, - menu: "Time", - defaultValue: 'year' - } - } - }, - { - opcode: 'TimestampToTime', - blockType: Scratch.BlockType.REPORTER, - text: 'Converts [timestamp] to a datetime', - arguments: { - timestamp: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1145141980000' - } - } - }, - { - opcode: 'TimeToTimestamp', - blockType: Scratch.BlockType.REPORTER, - text: 'Converts [time] to a timestamp', - arguments: { - time: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '2006-04-16 06:59:40' - } - } - } - ], - menus: { - Time: { - acceptReporters: true, - items: ['year', 'month', 'day', 'hour', 'minute', 'second'] - }, - } - }; - } - Timestamp() { - return Date.now(); - } - timezone() { - return 'UTC+' + new Date().getTimezoneOffset() / -60; - } - Timedata(args) { - args.timestamp = args.timestamp ? args.timestamp : null; - let date1 = new Date(args.timestamp); - switch (args.Timedata) { - case 'year': - return date1.getFullYear(); - case 'month': - return date1.getMonth() + 1 < 10 ? '0' + (date1.getMonth() + 1) : date1.getMonth() + 1; - case 'day': - return date1.getDate() < 10 ? '0' + date1.getDate() : date1.getDate(); - case 'hour': - return date1.getHours() < 10 ? '0' + date1.getHours() : date1.getHours(); - case 'minute': - return date1.getMinutes() < 10 ? '0' + date1.getMinutes() : date1.getMinutes(); - case 'second': - return date1.getSeconds() < 10 ? '0' + date1.getSeconds() : date1.getSeconds(); + "use strict"; + + const menuIconURI = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS44ODU0IiBoZWlnaHQ9IjgwLjYwMzA4IiB2aWV3Qm94PSIwLDAsODEuODg1NCw4MC42MDMwOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE5OS4wNTczLC0xMzkuNjk4NDYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC45NDI3LDE4MGMwLDIyLjI1NzkyIC0xOC4zMzA2Nyw0MC4zMDE1NCAtNDAuOTQyNyw0MC4zMDE1NGMtMjIuNjEyMDMsMCAtNDAuOTQyNywtMTguMDQzNjEgLTQwLjk0MjcsLTQwLjMwMTU0YzAsLTIyLjI1NzkyIDE4LjMzMDY3LC00MC4zMDE1NCA0MC45NDI3LC00MC4zMDE1NGMyMi42MTIwMywwIDQwLjk0MjcsMTguMDQzNjEgNDAuOTQyNyw0MC4zMDE1NHoiIGZpbGw9IiNmZjgwMDAiIHN0cm9rZS13aWR0aD0iMCIvPjxwYXRoIGQ9Ik0yNjYuNTM0MzcsMTgwYzAsMTQuNjQxMjkgLTExLjg5MzA4LDI2LjUzNDM3IC0yNi41MzQzNywyNi41MzQzN2MtMTQuNjQxMjksMCAtMjYuNTM0MzcsLTExLjg5MzA4IC0yNi41MzQzNywtMjYuNTM0MzdjMCwtMTQuNjQxMjkgMTEuODkzMDgsLTI2LjUzNDM3IDI2LjUzNDM3LC0yNi41MzQzN2MxNC42NDEyOSwwIDI2LjUzNDM3LDExLjg5MzA4IDI2LjUzNDM3LDI2LjUzNDM3ek0yNTMuMjE5OCwxODUuOTcwMjNsLTExLjMyNDQ5LC02LjUzODgzdi0xNC41OTM5aC0zLjc5MDYydjE3LjA1NzgxaDAuNTIxMjFsMTIuNjk4NTksNy4zNDQzM3oiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjk0MjY5NjA1MzgwMTE0OjQwLjMwMTUzNTI2NTQ4NjcwNi0tPg=="; + + const blockIconURI = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzUiIGhlaWdodD0iMTc1IiB2aWV3Qm94PSIwLDAsMTc1LDE3NSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1Mi41LC05Mi41KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTMyNy41LDE4MGMwLDQ4LjI4MTI1IC0zOS4yMTg3NSw4Ny41IC04Ny41LDg3LjVjLTQ4LjI4MTI1LDAgLTg3LjUsLTM5LjIxODc1IC04Ny41LC04Ny41YzAsLTQ4LjI4MTI1IDM5LjIxODc1LC04Ny41IDg3LjUsLTg3LjVjNDguMjgxMjUsMCA4Ny41LDM5LjIxODc1IDg3LjUsODcuNXpNMjgzLjU5Mzc1LDE5OS42ODc1bC0zNy4zNDM3NSwtMjEuNTYyNXYtNDguMTI1aC0xMi41djU2LjI1aDEuNzE4NzVsNDEuODc1LDI0LjIxODc1eiIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg3LjU6ODcuNS0tPg=="; + + class Time { + getInfo() { + return { + id: "sipctime", + name: "Time", + color1: "#ff8000", + color2: "#804000", + color3: "#804000", + menuIconURI, + blockIconURI, + blocks: [ + { + blockType: Scratch.BlockType.LABEL, + text: 'Time Conversions', + }, + { + opcode: "Timestamp", + blockType: Scratch.BlockType.REPORTER, + text: "current timestamp", + arguments: {}, + }, + { + opcode: "timezone", + blockType: Scratch.BlockType.REPORTER, + text: "current time zone", + arguments: {}, + }, + { + opcode: "Timedata", + blockType: Scratch.BlockType.REPORTER, + text: "get [Timedata] from [timestamp]", + arguments: { + timestamp: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1145141980000", + }, + Timedata: { + type: Scratch.ArgumentType.STRING, + menu: "Time", + defaultValue: "year", + }, + }, + }, + { + opcode: "TimestampToTime", + blockType: Scratch.BlockType.REPORTER, + text: "convert [timestamp] to datetime", + arguments: { + timestamp: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1145141980000", + }, + }, + }, + { + opcode: "TimeToTimestamp", + blockType: Scratch.BlockType.REPORTER, + text: "convert [time] to timestamp", + arguments: { + time: { + type: Scratch.ArgumentType.STRING, + defaultValue: "2006-04-16 06:59:40", + }, + }, + }, + { + blockType: Scratch.BlockType.LABEL, + text: 'Time Calculations', + }, + { + opcode: 'calculatetimedurationfromdate', + blockType: Scratch.BlockType.REPORTER, + text: 'time duration from [DATE] to current date in [TIME_MENU]', + arguments: { + DATE: { + type: Scratch.ArgumentType.STRING, + defaultValue: '2006-04-16' + }, + TIME_MENU: { + type: Scratch.ArgumentType.STRING, + menu: 'Time', + defaultValue: 'day' + }, } - return 0; - } - TimestampToTime({ timestamp }) { - timestamp = timestamp ? timestamp : null; - let date2 = new Date(timestamp); - let Y = date2.getFullYear() + '-'; - let M = (date2.getMonth() + 1 < 10 ? '0' + (date2.getMonth() + 1) : date2.getMonth() + 1) + '-'; - let D = (date2.getDate() < 10 ? '0' + date2.getDate() : date2.getDate()) + ' '; - let h = (date2.getHours() < 10 ? '0' + date2.getHours() : date2.getHours()) + ':'; - let m = (date2.getMinutes() < 10 ? '0' + date2.getMinutes() : date2.getMinutes()) + ':'; - let s = date2.getSeconds() < 10 ? '0' + date2.getSeconds() : date2.getSeconds(); - return Y + M + D + h + m + s; - } - TimeToTimestamp({ time }) { - let data3 = time; - let timestamp = Date.parse(data3); - return timestamp; - } + }, + { + opcode: 'calculatetimedurationfromtime', + blockType: Scratch.BlockType.REPORTER, + text: 'time duration from [START_HOUR]:[START_MINUTE] to current time in [TIME_MENU]', + arguments: { + START_HOUR: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + START_MINUTE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + TIME_MENU: { + type: Scratch.ArgumentType.STRING, + menu: 'Time', + defaultValue: 'hour' + }, + } + }, + { + opcode: 'calculatetimedifference', + blockType: Scratch.BlockType.REPORTER, + text: 'difference between [START_TIME] and [END_TIME] in [TIME_MENU]', + arguments: { + START_TIME: { + type: Scratch.ArgumentType.STRING, + defaultValue: '00:00' + }, + END_TIME: { + type: Scratch.ArgumentType.STRING, + defaultValue: '00:00' + }, + TIME_MENU: { + type: Scratch.ArgumentType.STRING, + menu: 'Time', + defaultValue: 'hour' + }, + } + }, + { + opcode: 'converttotime', + blockType: Scratch.BlockType.REPORTER, + text: 'convert [VALUE] to time (day:hour:minute)', + arguments: { + VALUE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + } + }, + { + opcode: 'daysinmonth', + blockType: Scratch.BlockType.REPORTER, + text: 'number of days in [MONTH] [YEAR]', + arguments: { + MONTH: { + type: Scratch.ArgumentType.STRING, + menu: 'Months', + defaultValue: 'January' + }, + YEAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: '2000' + }, + } + } + ], + menus: { + Time: { + acceptReporters: true, + items: ["year", "month", "day", "hour", "minute", "second"], + }, + Months: { + acceptReporters: true, + items: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ] + }, + }, + }; + } + Timestamp() { + return Date.now(); + } + timezone() { + return "UTC+" + new Date().getTimezoneOffset() / -60; + } + Timedata(args) { + args.timestamp = args.timestamp ? args.timestamp : null; + let date1 = new Date(Scratch.Cast.toNumber(args.timestamp)); + switch (args.Timedata) { + case "year": + return date1.getFullYear(); + case "month": + return date1.getMonth() + 1 < 10 + ? "0" + (date1.getMonth() + 1) + : date1.getMonth() + 1; + case "day": + return date1.getDate() < 10 ? "0" + date1.getDate() : date1.getDate(); + case "hour": + return date1.getHours() < 10 + ? "0" + date1.getHours() + : date1.getHours(); + case "minute": + return date1.getMinutes() < 10 + ? "0" + date1.getMinutes() + : date1.getMinutes(); + case "second": + return date1.getSeconds() < 10 + ? "0" + date1.getSeconds() + : date1.getSeconds(); + } + return 0; + } + TimestampToTime({ timestamp }) { + timestamp = timestamp ? timestamp : null; + let date2 = new Date(timestamp); + let Y = date2.getFullYear() + "-"; + let M = + (date2.getMonth() + 1 < 10 + ? "0" + (date2.getMonth() + 1) + : date2.getMonth() + 1) + "-"; + let D = + (date2.getDate() < 10 ? "0" + date2.getDate() : date2.getDate()) + " "; + let h = + (date2.getHours() < 10 ? "0" + date2.getHours() : date2.getHours()) + + ":"; + let m = + (date2.getMinutes() < 10 + ? "0" + date2.getMinutes() + : date2.getMinutes()) + ":"; + let s = + date2.getSeconds() < 10 ? "0" + date2.getSeconds() : date2.getSeconds(); + return Y + M + D + h + m + s; + } + TimeToTimestamp({ time }) { + let data3 = time; + let timestamp = Date.parse(data3); + return timestamp; + } + + calculateTimeDifference(startDate, endDate, timeMenu) { + const timeDiff = Math.abs(endDate.getTime() - startDate.getTime()); + + switch (timeMenu) { + case 'year': + return Math.floor(timeDiff / (1000 * 60 * 60 * 24 * 365)); + case 'month': + return Math.floor(timeDiff / (1000 * 60 * 60 * 24 * 30)); + case 'day': + return Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + case 'hour': + return Math.floor(timeDiff / (1000 * 60 * 60)); + case 'minute': + return Math.floor(timeDiff / (1000 * 60)); + case 'second': + return Math.floor(timeDiff / 1000); + } + } + + calculatetimedurationfromdate(args) { + const dateString = args.DATE ? args.DATE : null; + const timeMenu = args.TIME_MENU; + const startDate = new Date(dateString); + const endDate = new Date(); + return this.calculateTimeDifference(startDate, endDate, timeMenu); + } + + calculatetimedurationfromtime(args) { + const startHour = args.START_HOUR ? args.START_HOUR : null; + const startMinute = args.START_MINUTE ? args.START_MINUTE : null; + const timeMenu = args.TIME_MENU; + const startDate = new Date(); + startDate.setHours(startHour, startMinute, 0, 0); + const endDate = new Date(); + return this.calculateTimeDifference(startDate, endDate, timeMenu); + } + + calculatetimedifference(args) { + const startTime = args.START_TIME ? args.START_TIME : null; + const endTime = args.END_TIME ? args.END_TIME : null; + const timeMenu = args.TIME_MENU; + const startDate = new Date(); + const endDate = new Date(); + const startHour = parseInt(startTime.split(':')[0]); + const startMinute = parseInt(startTime.split(':')[1]); + const endHour = parseInt(endTime.split(':')[0]); + const endMinute = parseInt(endTime.split(':')[1]); + startDate.setHours(startHour, startMinute, 0, 0); + endDate.setHours(endHour, endMinute, 0, 0); + return this.calculateTimeDifference(startDate, endDate, timeMenu); + } + + converttotime(args) { + const value = args.VALUE ? args.VALUE : 0; + const seconds = Math.floor(value); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + const timeString = `${hours % 24}:${minutes % 60}:${seconds % 60}`; + return timeString; + } + + daysinmonth(args) { + const month = args.MONTH; + const year = args.YEAR ? args.YEAR : null; + const date = new Date(year, this._getMonthIndex(month) + 1, 0); + return date.getDate(); + } + + _getMonthIndex(month) { + const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + return months.indexOf(month); } - Scratch.extensions.register(new Time()); + } + Scratch.extensions.register(new Time()); })(Scratch); diff --git a/extensions/0832/rxFS.js b/extensions/0832/rxFS.js index 85d204539d..b903ed8828 100644 --- a/extensions/0832/rxFS.js +++ b/extensions/0832/rxFS.js @@ -2,243 +2,255 @@ * Made by 0832 * This file was originally under the rxLI Version 2.1 license: * https://0832k12.github.io/rxLi/2.1/ - * + * * However they have since claimed it to be "directly compatible with MIT license", * which is the license we use this file under. */ (function (Scratch) { - 'use strict'; - - var rxFSfi = new Array(); - var rxFSsy = new Array(); - var Search, i, str, str2; - - const file = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzMC4zMTIxMiIgaGVpZ2h0PSIyNC4yNDk2NyIgdmlld0JveD0iMCwwLDMwLjMxMjEyLDI0LjI0OTY3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzA0Ljg0Mzk0LC0xNjcuODc1MTYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGw9IiNmZmI5MDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMzE5Ljk5OTk5LDE3MC45MDYzN2gxMi4xMjQ4M2MxLjY3NDEyLDAgMy4wMzEyNCwxLjM1NzEyIDMuMDMxMjQsMy4wMzEydjE1LjE1NjA3YzAsMS42NzQwOCAtMS4zNTcxMiwzLjAzMTIgLTMuMDMxMjQsMy4wMzEyaC0yNC4yNDk2NmMtMS42NzQxMiwwIC0zLjAzMTIyLC0xLjM1NzEyIC0zLjAzMTIyLC0zLjAzMTJ2LTE4LjE4NzI3YzAsLTEuNjgyMzIgMS4zNDg5LC0zLjAzMTIgMy4wMzEyMiwtMy4wMzEyaDkuMDkzNjN6Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTUuMTU2MDYwMDAwMDAwMDI1OjEyLjEyNDgzNTAwMDAwMDAxOS0tPg=='; - const wenj = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMS4zMzc2IiBoZWlnaHQ9IjI3LjEzNTI4IiB2aWV3Qm94PSIwLDAsMjEuMzM3NiwyNy4xMzUyOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwOS4zMzEyLC0xNjYuNDMyMzYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMzMwLjU0OTY3LDE5MC41MzY0YzAsMS42NzQxMiAtMS4zNTcxMiwzLjAzMTI0IC0zLjAzMTIsMy4wMzEyNGgtMTUuMTU2MDdjLTEuNjc0MDgsMCAtMy4wMzEyLC0xLjM1NzEyIC0zLjAzMTIsLTMuMDMxMjR2LTIxLjA3MjgyYzAsLTEuNjc0MTIgMS4zNTcxMiwtMy4wMzEyMiAzLjAzMTIsLTMuMDMxMjJoMTQuMDQ5MzNsNC4xMzc5NCw0LjEwMTc3eiIgZmlsbD0iI2FmYWZhZiIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0zMzAuNjY4OCwxNzAuNzAxNDdsLTIuMTE5OTIsMC4wNTEzN2MtMS4xMzM3NCwwIC0yLjA1MjgyLC0wLjkxOTA4IC0yLjA1MjgyLC0yLjA1Mjg1di0yLjEwMjgyeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMzEyLjgzMjY0LDE3My41MTk1OGgxMi4wMDE0MSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNMzEyLjc5MjMyLDE3Ni44NzI5MWgxMi4xNzc5IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMTIuODA1NzYsMTgwLjE3MTZoNi44ODMxNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjoxMC42Njg4MDAwMDAwMDAwMzM6MTMuNTY3NjM3NTAwMDAwMDE4LS0+'; - - - class rxFS { - getInfo() { - return { - id: '0832rxfs', - name: 'rxFS', - color1: '#2bdab7', - blocks: [ - { - blockIconURI: wenj, - opcode: 'start', - blockType: Scratch.BlockType.COMMAND, - text: 'New [STR] ', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - } - } - }, - { - blockIconURI: wenj, - opcode: 'file', - blockType: Scratch.BlockType.COMMAND, - text: 'Set [STR] to [STR2] ', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - }, - STR2: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'rxFS is good!' - } - } - }, - { - blockIconURI: wenj, - opcode: 'sync', - blockType: Scratch.BlockType.COMMAND, - text: 'Change the location of [STR] to [STR2] ', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - }, - STR2: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - } - } - }, - { - blockIconURI: wenj, - opcode: 'del', - blockType: Scratch.BlockType.COMMAND, - text: 'Delete [STR] ', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - } - } - }, - { - blockIconURI: wenj, - opcode: 'webin', - blockType: Scratch.BlockType.REPORTER, - text: 'Load [STR] from the network', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'https://0832k12.github.io/rxFS/hello.txt' - } - } - }, - { - blockIconURI: wenj, - opcode: 'open', - blockType: Scratch.BlockType.REPORTER, - text: 'Open [STR]', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - } - } - }, - { - blockIconURI: file, - opcode: 'clean', - blockType: Scratch.BlockType.COMMAND, - text: 'Clear file system', - arguments: {} - }, - { - blockIconURI: file, - opcode: 'in', - blockType: Scratch.BlockType.COMMAND, - text: 'Import file system from [STR]', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📁' - } - } - }, - { - blockIconURI: file, - opcode: 'out', - blockType: Scratch.BlockType.REPORTER, - text: 'Export file system', - arguments: {} - }, - { - blockIconURI: file, - opcode: 'list', - blockType: Scratch.BlockType.REPORTER, - text: 'List the contents under the same folder [STR]', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📁' - } - } - }, - { - blockIconURI: file, - opcode: 'search', - blockType: Scratch.BlockType.REPORTER, - text: 'Search [STR]', - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '📃' - } - } - } - ] - }; - } + "use strict"; + var rxFSfi = new Array(); + var rxFSsy = new Array(); + var Search, i, str, str2; - clean() { - rxFSfi = []; - rxFSsy = []; - } + const file = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzMC4zMTIxMiIgaGVpZ2h0PSIyNC4yNDk2NyIgdmlld0JveD0iMCwwLDMwLjMxMjEyLDI0LjI0OTY3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzA0Ljg0Mzk0LC0xNjcuODc1MTYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGw9IiNmZmI5MDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMzE5Ljk5OTk5LDE3MC45MDYzN2gxMi4xMjQ4M2MxLjY3NDEyLDAgMy4wMzEyNCwxLjM1NzEyIDMuMDMxMjQsMy4wMzEydjE1LjE1NjA3YzAsMS42NzQwOCAtMS4zNTcxMiwzLjAzMTIgLTMuMDMxMjQsMy4wMzEyaC0yNC4yNDk2NmMtMS42NzQxMiwwIC0zLjAzMTIyLC0xLjM1NzEyIC0zLjAzMTIyLC0zLjAzMTJ2LTE4LjE4NzI3YzAsLTEuNjgyMzIgMS4zNDg5LC0zLjAzMTIgMy4wMzEyMiwtMy4wMzEyaDkuMDkzNjN6Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTUuMTU2MDYwMDAwMDAwMDI1OjEyLjEyNDgzNTAwMDAwMDAxOS0tPg=="; + const wenj = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMS4zMzc2IiBoZWlnaHQ9IjI3LjEzNTI4IiB2aWV3Qm94PSIwLDAsMjEuMzM3NiwyNy4xMzUyOCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwOS4zMzEyLC0xNjYuNDMyMzYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMzMwLjU0OTY3LDE5MC41MzY0YzAsMS42NzQxMiAtMS4zNTcxMiwzLjAzMTI0IC0zLjAzMTIsMy4wMzEyNGgtMTUuMTU2MDdjLTEuNjc0MDgsMCAtMy4wMzEyLC0xLjM1NzEyIC0zLjAzMTIsLTMuMDMxMjR2LTIxLjA3MjgyYzAsLTEuNjc0MTIgMS4zNTcxMiwtMy4wMzEyMiAzLjAzMTIsLTMuMDMxMjJoMTQuMDQ5MzNsNC4xMzc5NCw0LjEwMTc3eiIgZmlsbD0iI2FmYWZhZiIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0zMzAuNjY4OCwxNzAuNzAxNDdsLTIuMTE5OTIsMC4wNTEzN2MtMS4xMzM3NCwwIC0yLjA1MjgyLC0wLjkxOTA4IC0yLjA1MjgyLC0yLjA1Mjg1di0yLjEwMjgyeiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMzEyLjgzMjY0LDE3My41MTk1OGgxMi4wMDE0MSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNMzEyLjc5MjMyLDE3Ni44NzI5MWgxMi4xNzc5IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMTIuODA1NzYsMTgwLjE3MTZoNi44ODMxNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjoxMC42Njg4MDAwMDAwMDAwMzM6MTMuNTY3NjM3NTAwMDAwMDE4LS0+"; - sync({ STR, STR2 }) { - str = btoa(unescape(encodeURIComponent(STR))); - str2 = btoa(unescape(encodeURIComponent(STR2))); - if (rxFSsy.indexOf(str) + 1 == 0) { - rxFSsy[((rxFSsy.indexOf(str) + 1) - 1)] = str2; - } - } + class rxFS { + getInfo() { + return { + id: "0832rxfs", + name: "rxFS", + color1: "#2bdab7", + blocks: [ + { + blockIconURI: wenj, + opcode: "start", + blockType: Scratch.BlockType.COMMAND, + text: "New [STR] ", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + }, + }, + { + blockIconURI: wenj, + opcode: "file", + blockType: Scratch.BlockType.COMMAND, + text: "Set [STR] to [STR2] ", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + STR2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "rxFS is good!", + }, + }, + }, + { + blockIconURI: wenj, + opcode: "sync", + blockType: Scratch.BlockType.COMMAND, + text: "Change the location of [STR] to [STR2] ", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + STR2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + }, + }, + { + blockIconURI: wenj, + opcode: "del", + blockType: Scratch.BlockType.COMMAND, + text: "Delete [STR] ", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + }, + }, + { + blockIconURI: wenj, + opcode: "webin", + blockType: Scratch.BlockType.REPORTER, + text: "Load [STR] from the network", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://0832k12.github.io/rxFS/hello.txt", + }, + }, + }, + { + blockIconURI: wenj, + opcode: "open", + blockType: Scratch.BlockType.REPORTER, + text: "Open [STR]", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + }, + }, + { + blockIconURI: file, + opcode: "clean", + blockType: Scratch.BlockType.COMMAND, + text: "Clear file system", + arguments: {}, + }, + { + blockIconURI: file, + opcode: "in", + blockType: Scratch.BlockType.COMMAND, + text: "Import file system from [STR]", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📁", + }, + }, + }, + { + blockIconURI: file, + opcode: "out", + blockType: Scratch.BlockType.REPORTER, + text: "Export file system", + arguments: {}, + }, + { + blockIconURI: file, + opcode: "list", + blockType: Scratch.BlockType.REPORTER, + text: "List the contents under the same folder [STR]", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📁", + }, + }, + }, + { + blockIconURI: file, + opcode: "search", + blockType: Scratch.BlockType.REPORTER, + text: "Search [STR]", + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "📃", + }, + }, + }, + ], + }; + } - start({ STR }) { - str = btoa(unescape(encodeURIComponent(STR))); - if (!(str.charAt((str.length - 1)) == '/') && rxFSsy.indexOf(str) + 1 == 0) { - rxFSfi.splice(((rxFSfi.length + 1) - 1), 0, null); - rxFSsy.splice(((rxFSsy.length + 1) - 1), 0, str); - } - } + clean() { + rxFSfi = []; + rxFSsy = []; + } - open({ STR }) { - return decodeURIComponent(escape(atob(rxFSfi[((rxFSsy.indexOf(btoa(unescape(encodeURIComponent(STR)))) + 1) - 1)]))); - } + sync({ STR, STR2 }) { + str = btoa(unescape(encodeURIComponent(STR))); + str2 = btoa(unescape(encodeURIComponent(STR2))); + if (rxFSsy.indexOf(str) + 1 == 0) { + rxFSsy[rxFSsy.indexOf(str) + 1 - 1] = str2; + } + } - del({ STR }) { - str = btoa(unescape(encodeURIComponent(STR))); - rxFSfi[((rxFSsy.indexOf(str) + 1) - 1)] = undefined; - rxFSsy[((rxFSsy.indexOf(str) + 1) - 1)] = undefined; - } + start({ STR }) { + str = btoa(unescape(encodeURIComponent(STR))); + if ( + !(str.charAt(str.length - 1) == "/") && + rxFSsy.indexOf(str) + 1 == 0 + ) { + rxFSfi.splice(rxFSfi.length + 1 - 1, 0, null); + rxFSsy.splice(rxFSsy.length + 1 - 1, 0, str); + } + } - file({ STR, STR2 }) { - rxFSfi[((rxFSsy.indexOf(btoa(unescape(encodeURIComponent(STR)))) + 1) - 1)] = btoa(unescape(encodeURIComponent(STR2))); - } + open({ STR }) { + return decodeURIComponent( + escape( + atob( + rxFSfi[ + rxFSsy.indexOf(btoa(unescape(encodeURIComponent(STR)))) + 1 - 1 + ] + ) + ) + ); + } - search({ STR }) { - Search = ''; - i = 0; - str = btoa(unescape(encodeURIComponent(STR))); - for (var i in rxFSsy) { - if (!(rxFSsy[(i)].indexOf(str) == undefined)) { - Search = [Search, 'LA==', rxFSsy[(i)]].join(''); - } - } - return decodeURIComponent(escape(atob(Search))); - } + del({ STR }) { + str = btoa(unescape(encodeURIComponent(STR))); + rxFSfi[rxFSsy.indexOf(str) + 1 - 1] = undefined; + rxFSsy[rxFSsy.indexOf(str) + 1 - 1] = undefined; + } - list({ STR }) { - Search = ''; - i = 0; - str = btoa(unescape(encodeURIComponent(STR))); - for (var i in rxFSsy) { - if (rxFSsy[(i)].slice(0, str.length) == str) { - Search = [Search, 'LA==', rxFSsy[(i)]].join(''); - } - } - return decodeURIComponent(escape(atob(Search))); - } + file({ STR, STR2 }) { + rxFSfi[rxFSsy.indexOf(btoa(unescape(encodeURIComponent(STR)))) + 1 - 1] = + btoa(unescape(encodeURIComponent(STR2))); + } - webin({ STR }) { - return Scratch.fetch(STR) - .then((response) => { - return response.text(); - }) - .catch((error) => { - console.error(error); - return 'undefined'; - }); + search({ STR }) { + Search = ""; + i = 0; + str = btoa(unescape(encodeURIComponent(STR))); + for (var i in rxFSsy) { + if (!(rxFSsy[i].indexOf(str) == undefined)) { + Search = [Search, "LA==", rxFSsy[i]].join(""); } + } + return decodeURIComponent(escape(atob(Search))); + } - in({ STR }) { - rxFSfi = STR.slice(0, STR.indexOf('|')).split(','); - rxFSsy = STR.slice(((STR.indexOf('|') + 1)), STR.length).split(','); + list({ STR }) { + Search = ""; + i = 0; + str = btoa(unescape(encodeURIComponent(STR))); + for (var i in rxFSsy) { + if (rxFSsy[i].slice(0, str.length) == str) { + Search = [Search, "LA==", rxFSsy[i]].join(""); } + } + return decodeURIComponent(escape(atob(Search))); + } - out() { - return [rxFSfi.join(','), '|', rxFSsy.join(',')].join(''); - } + webin({ STR }) { + return Scratch.fetch(STR) + .then((response) => { + return response.text(); + }) + .catch((error) => { + console.error(error); + return "undefined"; + }); + } + + in({ STR }) { + rxFSfi = STR.slice(0, STR.indexOf("|")).split(","); + rxFSsy = STR.slice(STR.indexOf("|") + 1, STR.length).split(","); + } + + out() { + return [rxFSfi.join(","), "|", rxFSsy.join(",")].join(""); } + } - Scratch.extensions.register(new rxFS()); + Scratch.extensions.register(new rxFS()); })(Scratch); diff --git a/extensions/0832/rxFS2.js b/extensions/0832/rxFS2.js index 0651762905..871ef0b1ca 100644 --- a/extensions/0832/rxFS2.js +++ b/extensions/0832/rxFS2.js @@ -1,291 +1,327 @@ +// Name: rxFS +// ID: 0832rxfs2 +// Description: Blocks for interacting with a virtual in-memory filesystem. +// By: 0832 + /*! * Made by 0832 * This file was originally under the rxLI Version 2.1 license: * https://0832k12.github.io/rxLi/2.1/ - * + * * However they have since claimed it to be "directly compatible with MIT license", * which is the license we use this file under. */ (function (Scratch) { - 'use strict'; - - Scratch.translate.setup({ - zh: { - start: '新建 [STR] ', - folder: '设置 [STR] 为 [STR2] ', - folder_default: '大主教大祭司主宰世界!', - sync: '将 [STR] 的位置更改为 [STR2] ', - del: '删除 [STR] ', - webin: '从网络加载 [STR]', - open: '打开 [STR]', - clean: '清空文件系统', - in: '从 [STR] 导入文件系统', - out: '导出文件系统', - list: '列出 [STR] 下的所有文件', - search: '搜索 [STR]' - }, - ru: { - start: 'Создать [STR]', - folder: 'Установить [STR] в [STR2]', - folder_default: 'Архиепископ Верховный жрец Правитель мира!', - sync: 'Изменить расположение [STR] на [STR2]', - del: 'Удалить [STR]', - webin: 'Загрузить [STR] из Интернета', - open: 'Открыть [STR]', - clean: 'Очистить файловую систему', - in: 'Импортировать файловую систему из [STR]', - out: 'Экспортировать файловую систему', - list: 'Список всех файлов в [STR]', - search: 'Поиск [STR]' - }, - jp: { - start: '新規作成 [STR]', - folder: '[STR] を [STR2] に設定する', - folder_default: '大主教大祭司世界の支配者!', - sync: '[STR] の位置を [STR2] に変更する', - del: '[STR] を削除する', - webin: '[STR] をウェブから読み込む', - open: '[STR] を開く', - clean: 'ファイルシステムをクリアする', - in: '[STR] からファイルシステムをインポートする', - out: 'ファイルシステムをエクスポートする', - list: '[STR] にあるすべてのファイルをリストする', - search: '[STR] を検索する' - } - }); + "use strict"; - var rxFSfi = new Array(); - var rxFSsy = new Array(); - var Search, i, str, str2; + Scratch.translate.setup({ + zh: { + start: "新建 [STR] ", + folder: "设置 [STR] 为 [STR2] ", + folder_default: "大主教大祭司主宰世界!", + sync: "将 [STR] 的位置更改为 [STR2] ", + del: "删除 [STR] ", + webin: "从网络加载 [STR]", + open: "打开 [STR]", + clean: "清空文件系统", + in: "从 [STR] 导入文件系统", + out: "导出文件系统", + list: "列出 [STR] 下的所有文件", + search: "搜索 [STR]", + }, + ru: { + start: "Создать [STR]", + folder: "Установить [STR] в [STR2]", + folder_default: "Архиепископ Верховный жрец Правитель мира!", + sync: "Изменить расположение [STR] на [STR2]", + del: "Удалить [STR]", + webin: "Загрузить [STR] из Интернета", + open: "Открыть [STR]", + clean: "Очистить файловую систему", + in: "Импортировать файловую систему из [STR]", + out: "Экспортировать файловую систему", + list: "Список всех файлов в [STR]", + search: "Поиск [STR]", + }, + jp: { + start: "新規作成 [STR]", + folder: "[STR] を [STR2] に設定する", + folder_default: "大主教大祭司世界の支配者!", + sync: "[STR] の位置を [STR2] に変更する", + del: "[STR] を削除する", + webin: "[STR] をウェブから読み込む", + open: "[STR] を開く", + clean: "ファイルシステムをクリアする", + in: "[STR] からファイルシステムをインポートする", + out: "ファイルシステムをエクスポートする", + list: "[STR] にあるすべてのファイルをリストする", + search: "[STR] を検索する", + }, + }); - const folder = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyOC40NjI1IiBoZWlnaHQ9IjI3LjciIHZpZXdCb3g9IjAsMCwyOC40NjI1LDI3LjciPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMjYuMDE5NTMsLTE2NC4xMTg3NSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzk5NjZmZiIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgZm9udC1mYW1pbHk9IlNhbnMgU2VyaWYiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGZvbnQtc2l6ZT0iNDAiIHRleHQtYW5jaG9yPSJzdGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0ZXh0IHRyYW5zZm9ybT0idHJhbnNsYXRlKDIyNi4yNjk1MywxODUuNzY4NzUpIHNjYWxlKDAuNSwwLjUpIiBmb250LXNpemU9IjQwIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmaWxsPSIjOTk2NmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBmb250LWZhbWlseT0iU2FucyBTZXJpZiIgZm9udC13ZWlnaHQ9Im5vcm1hbCIgdGV4dC1hbmNob3I9InN0YXJ0IiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHRzcGFuIHg9IjAiIGR5PSIwIj7wn5OBPC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTMuOTgwNDY4NzU6MTUuODgxMjQ5MjM3MDYwNTMtLT4='; - const file = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyOC40NjI1IiBoZWlnaHQ9IjI3LjciIHZpZXdCb3g9IjAsMCwyOC40NjI1LDI3LjciPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMjYuMDE5NTMsLTE2NC4xMTg3NSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzk5NjZmZiIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgZm9udC1mYW1pbHk9IlNhbnMgU2VyaWYiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGZvbnQtc2l6ZT0iNDAiIHRleHQtYW5jaG9yPSJzdGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0ZXh0IHRyYW5zZm9ybT0idHJhbnNsYXRlKDIyNi4yNjk1MywxODUuNzY4NzUpIHNjYWxlKDAuNSwwLjUpIiBmb250LXNpemU9IjQwIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmaWxsPSIjOTk2NmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBmb250LWZhbWlseT0iU2FucyBTZXJpZiIgZm9udC13ZWlnaHQ9Im5vcm1hbCIgdGV4dC1hbmNob3I9InN0YXJ0IiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHRzcGFuIHg9IjAiIGR5PSIwIj7wn5ODPC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTMuOTgwNDY4NzU6MTUuODgxMjQ5NjE4NTMwMjYyLS0+'; + var rxFSfi = new Array(); + var rxFSsy = new Array(); + var Search, i, str, str2; - class rxFS { - getInfo() { - return { - id: '0832rxfs2', - name: 'rxFS', - color1: '#192d50', - color2: '#192d50', - color3: '#192d50', - blocks: [ - { - blockIconURI: file, - opcode: 'start', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'start', default: 'Create [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - } - } - }, - { - blockIconURI: file, - opcode: 'folder', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'folder', default: 'Set [STR] to [STR2]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - }, - STR2: { - type: Scratch.ArgumentType.STRING, - defaultValue: Scratch.translate({ id: 'folder_default', default: 'rxFS is good!' }), - } - } - }, - { - blockIconURI: file, - opcode: 'sync', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'sync', default: 'Change the location of [STR] to [STR2]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - }, - STR2: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - } - } - }, - { - blockIconURI: file, - opcode: 'del', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'del', default: 'Delete [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - } - } - }, - { - blockIconURI: file, - opcode: 'webin', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'webin', default: 'Load [STR] from the web' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'https://0832k12.github.io/rxFS/hello.txt' - } - } - }, - { - blockIconURI: file, - opcode: 'open', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'open', default: 'Open [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - } - } - }, - '---', - { - blockIconURI: folder, - opcode: 'clean', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'clean', default: 'Clear the file system' }), - arguments: {} - }, - { - blockIconURI: folder, - opcode: 'in', - blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate({ id: 'in', default: 'Import file system from [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/' - } - } - }, - { - blockIconURI: folder, - opcode: 'out', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'out', default: 'Export file system' }), - arguments: {} - }, - { - blockIconURI: folder, - opcode: 'list', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'list', default: 'List all files under [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/' - } - } - }, - { - blockIconURI: folder, - opcode: 'search', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'search', default: 'Search [STR]' }), - arguments: { - STR: { - type: Scratch.ArgumentType.STRING, - defaultValue: '/rxFS/example' - } - } - } - ] - }; - } + const folder = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyOC40NjI1IiBoZWlnaHQ9IjI3LjciIHZpZXdCb3g9IjAsMCwyOC40NjI1LDI3LjciPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMjYuMDE5NTMsLTE2NC4xMTg3NSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzk5NjZmZiIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgZm9udC1mYW1pbHk9IlNhbnMgU2VyaWYiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGZvbnQtc2l6ZT0iNDAiIHRleHQtYW5jaG9yPSJzdGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0ZXh0IHRyYW5zZm9ybT0idHJhbnNsYXRlKDIyNi4yNjk1MywxODUuNzY4NzUpIHNjYWxlKDAuNSwwLjUpIiBmb250LXNpemU9IjQwIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmaWxsPSIjOTk2NmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBmb250LWZhbWlseT0iU2FucyBTZXJpZiIgZm9udC13ZWlnaHQ9Im5vcm1hbCIgdGV4dC1hbmNob3I9InN0YXJ0IiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHRzcGFuIHg9IjAiIGR5PSIwIj7wn5OBPC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTMuOTgwNDY4NzU6MTUuODgxMjQ5MjM3MDYwNTMtLT4="; + const file = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyOC40NjI1IiBoZWlnaHQ9IjI3LjciIHZpZXdCb3g9IjAsMCwyOC40NjI1LDI3LjciPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMjYuMDE5NTMsLTE2NC4xMTg3NSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzk5NjZmZiIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgZm9udC1mYW1pbHk9IlNhbnMgU2VyaWYiIGZvbnQtd2VpZ2h0PSJub3JtYWwiIGZvbnQtc2l6ZT0iNDAiIHRleHQtYW5jaG9yPSJzdGFydCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjx0ZXh0IHRyYW5zZm9ybT0idHJhbnNsYXRlKDIyNi4yNjk1MywxODUuNzY4NzUpIHNjYWxlKDAuNSwwLjUpIiBmb250LXNpemU9IjQwIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmaWxsPSIjOTk2NmZmIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBmb250LWZhbWlseT0iU2FucyBTZXJpZiIgZm9udC13ZWlnaHQ9Im5vcm1hbCIgdGV4dC1hbmNob3I9InN0YXJ0IiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHRzcGFuIHg9IjAiIGR5PSIwIj7wn5ODPC90c3Bhbj48L3RleHQ+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTMuOTgwNDY4NzU6MTUuODgxMjQ5NjE4NTMwMjYyLS0+"; + class rxFS { + getInfo() { + return { + id: "0832rxfs2", + name: "rxFS", + color1: "#192d50", + color2: "#192d50", + color3: "#192d50", + blocks: [ + { + blockIconURI: file, + opcode: "start", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ id: "start", default: "Create [STR]" }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + }, + }, + { + blockIconURI: file, + opcode: "folder", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ + id: "folder", + default: "Set [STR] to [STR2]", + }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + STR2: { + type: Scratch.ArgumentType.STRING, + defaultValue: Scratch.translate({ + id: "folder_default", + default: "rxFS is good!", + }), + }, + }, + }, + { + blockIconURI: file, + opcode: "sync", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ + id: "sync", + default: "Change the location of [STR] to [STR2]", + }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + STR2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + }, + }, + { + blockIconURI: file, + opcode: "del", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ id: "del", default: "Delete [STR]" }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + }, + }, + { + blockIconURI: file, + opcode: "webin", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "webin", + default: "Load [STR] from the web", + }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://0832k12.github.io/rxFS/hello.txt", + }, + }, + }, + { + blockIconURI: file, + opcode: "open", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ id: "open", default: "Open [STR]" }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + }, + }, + "---", + { + blockIconURI: folder, + opcode: "clean", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ + id: "clean", + default: "Clear the file system", + }), + arguments: {}, + }, + { + blockIconURI: folder, + opcode: "in", + blockType: Scratch.BlockType.COMMAND, + text: Scratch.translate({ + id: "in", + default: "Import file system from [STR]", + }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/", + }, + }, + }, + { + blockIconURI: folder, + opcode: "out", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "out", + default: "Export file system", + }), + arguments: {}, + }, + { + blockIconURI: folder, + opcode: "list", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "list", + default: "List all files under [STR]", + }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/", + }, + }, + }, + { + blockIconURI: folder, + opcode: "search", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ id: "search", default: "Search [STR]" }), + arguments: { + STR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "/rxFS/example", + }, + }, + }, + ], + }; + } - clean() { - rxFSfi = []; - rxFSsy = []; - } + clean() { + rxFSfi = []; + rxFSsy = []; + } - sync({ STR, STR2 }) { - str = encodeURIComponent(STR); - str2 = encodeURIComponent(STR2); - if (rxFSsy.indexOf(str) + 1 == 0) { - rxFSsy[((rxFSsy.indexOf(str) + 1) - 1)] = str2; - } - } + sync({ STR, STR2 }) { + str = encodeURIComponent(STR); + str2 = encodeURIComponent(STR2); + if (rxFSsy.indexOf(str) + 1 == 0) { + rxFSsy[rxFSsy.indexOf(str) + 1 - 1] = str2; + } + } - start({ STR }) { - str = encodeURIComponent(STR); - if (!(str.charAt((str.length - 1)) == '/') && rxFSsy.indexOf(str) + 1 == 0) { - rxFSfi.splice(((rxFSfi.length + 1) - 1), 0, null); - rxFSsy.splice(((rxFSsy.length + 1) - 1), 0, str); - } - } + start({ STR }) { + str = encodeURIComponent(STR); + if ( + !(str.charAt(str.length - 1) == "/") && + rxFSsy.indexOf(str) + 1 == 0 + ) { + rxFSfi.splice(rxFSfi.length + 1 - 1, 0, null); + rxFSsy.splice(rxFSsy.length + 1 - 1, 0, str); + } + } - open({ STR }) { - return decodeURIComponent(rxFSfi[((rxFSsy.indexOf(encodeURIComponent(STR)) + 1) - 1)]); - } + open({ STR }) { + return decodeURIComponent( + rxFSfi[rxFSsy.indexOf(encodeURIComponent(STR)) + 1 - 1] + ); + } - del({ STR }) { - str = encodeURIComponent(STR); - rxFSfi[((rxFSsy.indexOf(str) + 1) - 1)] = undefined; - rxFSsy[((rxFSsy.indexOf(str) + 1) - 1)] = undefined; - } + del({ STR }) { + str = encodeURIComponent(STR); + rxFSfi[rxFSsy.indexOf(str) + 1 - 1] = undefined; + rxFSsy[rxFSsy.indexOf(str) + 1 - 1] = undefined; + } - folder({ STR, STR2 }) { - rxFSfi[((rxFSsy.indexOf(encodeURIComponent(STR)) + 1) - 1)] = encodeURIComponent(STR2); - } + folder({ STR, STR2 }) { + rxFSfi[rxFSsy.indexOf(encodeURIComponent(STR)) + 1 - 1] = + encodeURIComponent(STR2); + } - search({ STR }) { - Search = ''; - i = 0; - str = encodeURIComponent(STR); - for (var i in rxFSsy) { - if (!(rxFSsy[(i)].indexOf(str) == undefined)) { - Search = [Search, ',"', rxFSsy[(i)], '"'].join(''); - } - } - return decodeURIComponent(Search); + search({ STR }) { + Search = ""; + i = 0; + str = encodeURIComponent(STR); + for (var i in rxFSsy) { + if (!(rxFSsy[i].indexOf(str) == undefined)) { + Search = [Search, ',"', rxFSsy[i], '"'].join(""); } + } + return decodeURIComponent(Search); + } - list({ STR }) { - Search = ''; - i = 0; - str = encodeURIComponent(STR); - for (var i in rxFSsy) { - if (rxFSsy[(i)].slice(0, str.length) == str) { - Search = [Search, ',"', rxFSsy[(i)], '"'].join(''); - } - } - return decodeURIComponent(Search); + list({ STR }) { + Search = ""; + i = 0; + str = encodeURIComponent(STR); + for (var i in rxFSsy) { + if (rxFSsy[i].slice(0, str.length) == str) { + Search = [Search, ',"', rxFSsy[i], '"'].join(""); } + } + return decodeURIComponent(Search); + } - webin({ STR }) { - return Scratch.fetch(STR) - .then((response) => { - return response.text(); - }) - .catch((error) => { - console.error(error); - return 'undefined'; - }); - } + webin({ STR }) { + return Scratch.fetch(STR) + .then((response) => { + return response.text(); + }) + .catch((error) => { + console.error(error); + return "undefined"; + }); + } - in({ STR }) { - rxFSfi = STR.slice(0, STR.indexOf('|')).split(','); - rxFSsy = STR.slice(((STR.indexOf('|') + 1)), STR.length).split(','); - } + in({ STR }) { + rxFSfi = STR.slice(0, STR.indexOf("|")).split(","); + rxFSsy = STR.slice(STR.indexOf("|") + 1, STR.length).split(","); + } - out() { - return [rxFSfi.join(','), '|', rxFSsy.join(',')].join(''); - } + out() { + return [rxFSfi.join(","), "|", rxFSsy.join(",")].join(""); } + } - Scratch.extensions.register(new rxFS()); + Scratch.extensions.register(new rxFS()); })(Scratch); diff --git a/extensions/Alestore/nfcwarp.js b/extensions/Alestore/nfcwarp.js index c731f04095..6e1face62c 100644 --- a/extensions/Alestore/nfcwarp.js +++ b/extensions/Alestore/nfcwarp.js @@ -1,68 +1,76 @@ -(function(Scratch) { - 'use strict'; +// Name: NFCWarp +// ID: alestorenfc +// Description: Allows reading data from NFC (NDEF) devices. Only works in Chrome on Android. +// By: Alestore Games + +(function (Scratch) { + "use strict"; /* globals NDEFReader */ - const extIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAACXBIWXMAAAsTAAALEwEAmpwYAAACN0lEQVR4nK2WzW7TQBDHDRIVHCr6CYV+PUG5hENvlpKdfyxX6q0SanmVqo+AkKqqPAG8CAVE1RJaKYn6cSiiIO70RBnYydi4ttcJjiPtIbsz/59nPZ4Zz9MfEz1nog8MXDHAhYvoGxPtMDAvvrXaHQZeJ2ysxnsGNiL9HgR4lRK6YOB8AOAPNmZJNJaX76lf2m73XyTZwwM2ZoGBswFgLfa8W6q16bBb9zTEDEgjnWei074wgMTemKeOh9nzHO9EQOIcBHNMdNInqheJ67vOOf/pOZxjkAjU67MMdAtAe4mkusyzKQTx6uooAxN6LY+ZqOMAXSYSq/v/oDBclMRoNCblf7P5iInaOaBrm+IKOiwLskKf2Pen9GoeMHCU8THmvoIOyoMGgdXrD6sBRd+MC2bMwvAg+y0RHcewIJjW1J+OsywMF6uIaF9Egc8Ka3MYzqjwfqWgRAStGGazsFKQvZ6b19XS/eO4Hg4F6hXWaO/IJoDs+/6UZGHSZ8iIZlJZ17EVQs5WVsYZ+FgNqCfGTpjvj8WwkqBDcVpbG2Hgdw6sbRNBbBqNSREvBSLqJIrkd5cNR5EBE7YAl4noawKU1xhZV9e2kNRY4ARlGx/RL/b9u+r4sgDE0hSDYK4QpI3vnUOkpineLARBS1Q0EeWD3tqDDYfzpjhubd2OS0/xOtPvLg/0LAp3N+fwi50BtBo8kdGqP+w8M3IR7aRnu3Xb+2+8M6I3cee0Qwqw7ZoJUutKtKJI/gr8AfOqgU5hKhA4AAAAAElFTkSuQmCC'; - const blocksIcon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMCIgd2lkdGg9IjE2MDAuMDAwMDAwcHQiIGhlaWdodD0iMTYwMC4wMDAwMDBwdCIgdmlld0JveD0iMCAwIDE2MDAuMDAwMDAwIDE2MDAuMDAwMDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPG1ldGFkYXRhIGZpbGw9IiNmZmZmZmYiPgpDcmVhdGVkIGJ5IHBvdHJhY2UgMS4xNSwgd3JpdHRlbiBieSBQZXRlciBTZWxpbmdlciAyMDAxLTIwMTcKPC9tZXRhZGF0YT4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTYwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0yNjk3IDE1OTg5IGMtNTY1IC01MCAtMTEwOCAtMjY0IC0xNTY3IC02MTkgLTExNCAtODggLTM5MSAtMzYyIC00ODQgLTQ4MCAtMTY0IC0yMDYgLTM0MCAtNTA5IC00MzQgLTc0NSAtODQgLTIxMSAtMTQzIC00MzAgLTE4NCAtNjgwIGwtMjMgLTE0MCAtMyAtNTEwNSBjLTIgLTM3MDkgMCAtNTE0NSA4IC01MjUwIDcwIC05MDUgNTQ5IC0xNzI0IDEzMDQgLTIyMjkgMzU3IC0yMzkgNzc4IC00MDIgMTIwMyAtNDY2IDIwOSAtMzIgNDA0IC0zNiAxNDM4IC0zMyBsMTAzMCAzIC01MCAyOSBjLTQ3MyAyNzIgLTkwMCA5ODAgLTExMzUgMTg4MCAtMTM2IDUyMSAtMjAwIDk1MiAtMjYyIDE3NjYgLTkgMTE2IC0xMyAxNTYzIC0xNSA1NzE5IGwtMyA1NTY0IDU5IC01NCBjMzMgLTMwIDE0NTAgLTE0MzMgMzE1MCAtMzExOSBsMzA5MSAtMzA2NSAwIC05NjAgMCAtOTYwIC0xNjcgMTY2IGMtMjc2IDI3MiAtMzY2MiAzNjM3IC00Mjg4IDQyNjEgbC01ODAgNTc4IC0zIC0zMDI4IGMtMyAtMzA1NiAwIC0zMzg3IDM0IC0zOTU3IDc2IC0xMjg5IDI1NyAtMjI1MSA1NTggLTI5NjggNTggLTEzOCAxOTUgLTQwNCAyNzEgLTUyNyAxNTYgLTI1MSA0MzQgLTU2MCA2NDggLTcxOSAzNTcgLTI2NiA3NzkgLTQ0OSAxMzIwIC01NzEgbDE3NyAtNDEgMjYzMyA0IGMyODk0IDMgMjY2NyAtMiAyOTgzIDYyIDk2MSAxOTQgMTc4NyA4OTMgMjE0OCAxODE2IDg2IDIyMSAxNDAgNDMwIDE4MyA3MDkgMTYgMTA4IDE4IDQxMyAyMSA1MTg1IDIgMzQ1NCAwIDUxMTcgLTggNTIxNyAtMTkgMjc4IC02MSA0OTAgLTE0NCA3NDMgLTM3NCAxMTI2IC0xMzc3IDE5MTQgLTI1NjEgMjAxNSAtNzIgNiAtNTU1IDEwIC0xMjAwIDEwIGwtMTA4MCAwIDU4IC0zMyBjMTAwIC01NiAxNzkgLTExOSAyOTcgLTIzNyA0NjUgLTQ2MyA4MDggLTEyNzMgOTc5IC0yMzEwIDU0IC0zMjkgODkgLTY1NCAxMjMgLTExNDUgOCAtMTI1IDEyIC0xNjI3IDE1IC01NzA0IGw0IC01NTMzIC0zOSAzMyBjLTIyIDE5IC0xMDc0IDEwNjAgLTIzMzggMjMxNCAtMTI2NCAxMjU0IC0yNjY1IDI2NDIgLTMxMTEgMzA4NSBsLTgxMyA4MDUgMCA5NjQgMCA5NjMgMTg1MyAtMTg0MyBjMTAxOCAtMTAxNCAyMTUyIC0yMTQzIDI1MjAgLTI1MDggbDY2NyAtNjY1IDAgMzAyNSBjMCAxNzQ5IC00IDMxNTMgLTEwIDMzMjkgLTU3IDE3OTQgLTI3NCAyOTkyIC02OTUgMzgzOCAtMTUzIDMwOCAtMzExIDUzNiAtNTI1IDc1NiAtMjIxIDIyNyAtNDQxIDM4NiAtNzM1IDUzMSAtMjY2IDEzMSAtNTM2IDIyMyAtODgzIDMwMSBsLTE1NCAzNCAtMjU5NiAtMSBjLTE0MjkgLTEgLTI2MzYgLTUgLTI2ODUgLTEweiIgZmlsbD0iI2ZmZmZmZiIvPgo8L2c+Cjwvc3ZnPg=='; + const extIcon = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAACXBIWXMAAAsTAAALEwEAmpwYAAACN0lEQVR4nK2WzW7TQBDHDRIVHCr6CYV+PUG5hENvlpKdfyxX6q0SanmVqo+AkKqqPAG8CAVE1RJaKYn6cSiiIO70RBnYydi4ttcJjiPtIbsz/59nPZ4Zz9MfEz1nog8MXDHAhYvoGxPtMDAvvrXaHQZeJ2ysxnsGNiL9HgR4lRK6YOB8AOAPNmZJNJaX76lf2m73XyTZwwM2ZoGBswFgLfa8W6q16bBb9zTEDEgjnWei074wgMTemKeOh9nzHO9EQOIcBHNMdNInqheJ67vOOf/pOZxjkAjU67MMdAtAe4mkusyzKQTx6uooAxN6LY+ZqOMAXSYSq/v/oDBclMRoNCblf7P5iInaOaBrm+IKOiwLskKf2Pen9GoeMHCU8THmvoIOyoMGgdXrD6sBRd+MC2bMwvAg+y0RHcewIJjW1J+OsywMF6uIaF9Egc8Ka3MYzqjwfqWgRAStGGazsFKQvZ6b19XS/eO4Hg4F6hXWaO/IJoDs+/6UZGHSZ8iIZlJZ17EVQs5WVsYZ+FgNqCfGTpjvj8WwkqBDcVpbG2Hgdw6sbRNBbBqNSREvBSLqJIrkd5cNR5EBE7YAl4noawKU1xhZV9e2kNRY4ARlGx/RL/b9u+r4sgDE0hSDYK4QpI3vnUOkpineLARBS1Q0EeWD3tqDDYfzpjhubd2OS0/xOtPvLg/0LAp3N+fwi50BtBo8kdGqP+w8M3IR7aRnu3Xb+2+8M6I3cee0Qwqw7ZoJUutKtKJI/gr8AfOqgU5hKhA4AAAAAElFTkSuQmCC"; + const blocksIcon = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMCIgd2lkdGg9IjE2MDAuMDAwMDAwcHQiIGhlaWdodD0iMTYwMC4wMDAwMDBwdCIgdmlld0JveD0iMCAwIDE2MDAuMDAwMDAwIDE2MDAuMDAwMDAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPG1ldGFkYXRhIGZpbGw9IiNmZmZmZmYiPgpDcmVhdGVkIGJ5IHBvdHJhY2UgMS4xNSwgd3JpdHRlbiBieSBQZXRlciBTZWxpbmdlciAyMDAxLTIwMTcKPC9tZXRhZGF0YT4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTYwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0yNjk3IDE1OTg5IGMtNTY1IC01MCAtMTEwOCAtMjY0IC0xNTY3IC02MTkgLTExNCAtODggLTM5MSAtMzYyIC00ODQgLTQ4MCAtMTY0IC0yMDYgLTM0MCAtNTA5IC00MzQgLTc0NSAtODQgLTIxMSAtMTQzIC00MzAgLTE4NCAtNjgwIGwtMjMgLTE0MCAtMyAtNTEwNSBjLTIgLTM3MDkgMCAtNTE0NSA4IC01MjUwIDcwIC05MDUgNTQ5IC0xNzI0IDEzMDQgLTIyMjkgMzU3IC0yMzkgNzc4IC00MDIgMTIwMyAtNDY2IDIwOSAtMzIgNDA0IC0zNiAxNDM4IC0zMyBsMTAzMCAzIC01MCAyOSBjLTQ3MyAyNzIgLTkwMCA5ODAgLTExMzUgMTg4MCAtMTM2IDUyMSAtMjAwIDk1MiAtMjYyIDE3NjYgLTkgMTE2IC0xMyAxNTYzIC0xNSA1NzE5IGwtMyA1NTY0IDU5IC01NCBjMzMgLTMwIDE0NTAgLTE0MzMgMzE1MCAtMzExOSBsMzA5MSAtMzA2NSAwIC05NjAgMCAtOTYwIC0xNjcgMTY2IGMtMjc2IDI3MiAtMzY2MiAzNjM3IC00Mjg4IDQyNjEgbC01ODAgNTc4IC0zIC0zMDI4IGMtMyAtMzA1NiAwIC0zMzg3IDM0IC0zOTU3IDc2IC0xMjg5IDI1NyAtMjI1MSA1NTggLTI5NjggNTggLTEzOCAxOTUgLTQwNCAyNzEgLTUyNyAxNTYgLTI1MSA0MzQgLTU2MCA2NDggLTcxOSAzNTcgLTI2NiA3NzkgLTQ0OSAxMzIwIC01NzEgbDE3NyAtNDEgMjYzMyA0IGMyODk0IDMgMjY2NyAtMiAyOTgzIDYyIDk2MSAxOTQgMTc4NyA4OTMgMjE0OCAxODE2IDg2IDIyMSAxNDAgNDMwIDE4MyA3MDkgMTYgMTA4IDE4IDQxMyAyMSA1MTg1IDIgMzQ1NCAwIDUxMTcgLTggNTIxNyAtMTkgMjc4IC02MSA0OTAgLTE0NCA3NDMgLTM3NCAxMTI2IC0xMzc3IDE5MTQgLTI1NjEgMjAxNSAtNzIgNiAtNTU1IDEwIC0xMjAwIDEwIGwtMTA4MCAwIDU4IC0zMyBjMTAwIC01NiAxNzkgLTExOSAyOTcgLTIzNyA0NjUgLTQ2MyA4MDggLTEyNzMgOTc5IC0yMzEwIDU0IC0zMjkgODkgLTY1NCAxMjMgLTExNDUgOCAtMTI1IDEyIC0xNjI3IDE1IC01NzA0IGw0IC01NTMzIC0zOSAzMyBjLTIyIDE5IC0xMDc0IDEwNjAgLTIzMzggMjMxNCAtMTI2NCAxMjU0IC0yNjY1IDI2NDIgLTMxMTEgMzA4NSBsLTgxMyA4MDUgMCA5NjQgMCA5NjMgMTg1MyAtMTg0MyBjMTAxOCAtMTAxNCAyMTUyIC0yMTQzIDI1MjAgLTI1MDggbDY2NyAtNjY1IDAgMzAyNSBjMCAxNzQ5IC00IDMxNTMgLTEwIDMzMjkgLTU3IDE3OTQgLTI3NCAyOTkyIC02OTUgMzgzOCAtMTUzIDMwOCAtMzExIDUzNiAtNTI1IDc1NiAtMjIxIDIyNyAtNDQxIDM4NiAtNzM1IDUzMSAtMjY2IDEzMSAtNTM2IDIyMyAtODgzIDMwMSBsLTE1NCAzNCAtMjU5NiAtMSBjLTE0MjkgLTEgLTI2MzYgLTUgLTI2ODUgLTEweiIgZmlsbD0iI2ZmZmZmZiIvPgo8L2c+Cjwvc3ZnPg=="; class NFCWarp { getInfo() { return { - id: 'alestorenfc', - name: 'NFCWarp', - color1: '#FF4646', - color2: '#FF0000', - color3: '#990033', + id: "alestorenfc", + name: "NFCWarp", + color1: "#FF4646", + color2: "#FF0000", + color3: "#990033", menuIconURI: extIcon, blockIconURI: blocksIcon, blocks: [ { blockType: Scratch.BlockType.LABEL, - text: 'Only works in Chrome on Android' + text: "Only works in Chrome on Android", }, { - opcode: 'supported', + opcode: "supported", blockType: Scratch.BlockType.BOOLEAN, - text: 'NFC supported?' + text: "NFC supported?", }, { - opcode: 'nfcRead', + opcode: "nfcRead", blockType: Scratch.BlockType.REPORTER, - text: 'read NFC tag', - disableMonitor: true - } - ] + text: "read NFC tag", + disableMonitor: true, + }, + ], }; } - supported () { - return typeof NDEFReader !== 'undefined'; + supported() { + return typeof NDEFReader !== "undefined"; } nfcRead() { if (!this.supported()) { - return 'NFC not supported'; + return "NFC not supported"; } return new Promise((resolve, reject) => { const ndef = new NDEFReader(); - ndef.scan() + ndef + .scan() .then(() => { - ndef.onreadingerror = event => { - console.log('Reading error', event); - resolve('Tag not supported'); + ndef.onreadingerror = (event) => { + console.log("Reading error", event); + resolve("Tag not supported"); }; - ndef.onreading = evt => { + ndef.onreading = (evt) => { const decoder = new TextDecoder(); const record = evt.message.records[0]; - console.log('Record type: ' + record.recordType); - console.log('Record encoding: ' + record.encoding); - console.log('Record data: ' + decoder.decode(record.data)); + console.log("Record type: " + record.recordType); + console.log("Record encoding: " + record.encoding); + console.log("Record data: " + decoder.decode(record.data)); resolve(decoder.decode(record.data)); }; }) - .catch(error => { - console.log('Scan error', error); + .catch((error) => { + console.log("Scan error", error); resolve(`Error: ${error}`); }); }); diff --git a/extensions/CST1229/images.js b/extensions/CST1229/images.js index a7a296b461..7524b64dea 100644 --- a/extensions/CST1229/images.js +++ b/extensions/CST1229/images.js @@ -202,7 +202,7 @@ case "image/bmp": case "image/jpeg": { - if (!await Scratch.canFetch(IMAGEURL)) return; + if (!(await Scratch.canFetch(IMAGEURL))) return; // eslint-disable-next-line no-restricted-syntax const image = new Image(); image.crossOrigin = "anonymous"; @@ -377,6 +377,7 @@ } } - if (!Scratch.extensions.unsandboxed) throw new Error("This extension cannot run in sandboxed mode."); + if (!Scratch.extensions.unsandboxed) + throw new Error("This extension cannot run in sandboxed mode."); Scratch.extensions.register(new ImagesExt(Scratch.vm)); })(globalThis.Scratch); diff --git a/extensions/CST1229/zip.js b/extensions/CST1229/zip.js index 9bbb0392ce..a362fd17d7 100644 --- a/extensions/CST1229/zip.js +++ b/extensions/CST1229/zip.js @@ -1,39 +1,16 @@ +// Name: Zip +// ID: cst1229zip +// Description: Create and edit .zip format files, including .sb3 files. +// By: CST1229 + (function (Scratch) { "use strict"; - // Tricking JSZip into thinking it's running as a CommonJS module - // is probably better than letting it overwrite globals - const exports = {}; - const module = { exports: null }; - - // jszip source code: - // https://github.com/Stuk/jszip - // using it under the MIT license - - // minified code from: https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js - // in a function for code folding - function jsZip() { - /*! - - JSZip v3.10.1 - A JavaScript class for generating and reading zip files - - - (c) 2009-2016 Stuart Knightley - Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. - - JSZip uses the library pako released under the MIT license : - https://github.com/nodeca/pako/blob/main/LICENSE - */ - /* eslint-disable */ - // @ts-ignore - !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e>2,s=(3&t)<<4|r>>4,a=1>6:64,o=2>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function A(e,t){var r,n="";for(r=0;r>>=8;return n}function n(e,t,r,n,i,s){var a,o,h=e.file,u=e.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),c=I.transformTo("string",O.utf8encode(h.name)),d=h.comment,p=I.transformTo("string",s(d)),m=I.transformTo("string",O.utf8encode(d)),_=c.length!==h.name.length,g=m.length!==d.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===i?(C=798,z|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(e){return 63&(e||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+c,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(n,4)+f+b+p}}var I=e("../utils"),i=e("../stream/GenericWorker"),O=e("../utf8"),B=e("../crc32"),R=e("../signature");function s(e,t,r,n){i.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,i),s.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,i.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},s.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=n(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=n(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return R.DATA_DESCRIPTOR+A(e.crc32,4)+A(e.compressedSize,4)+A(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),u=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,o){return new a.Promise(function(t,r){var n=[],i=e._internalType,s=e._outputType,a=e._mimeType;e.on("data",function(e,t){n.push(e),o&&o(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return u.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return h.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(h.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(h.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}(t),i=t;n!==t.length&&(h.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,n),l.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,a){"use strict";var o=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),u=e("./external");function n(e){return e}function l(e,t){for(var r=0;r>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return l(e,e.length)},r.binstring2buf=function(e){for(var t=new h.Buf8(e.length),r=0,n=t.length;r>10&1023,o[n++]=56320|1023&i)}return l(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var h,c=e("../utils/common"),u=e("./trees"),d=e("./adler32"),p=e("./crc32"),n=e("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,i=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=n[t],t}function T(e){return(e<<1)-(4e.avail_out&&(r=e.avail_out),0!==r&&(c.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){u._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&sh&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,i,s,a,o,h,u,l,f=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-z)){for(c.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,t=r=e.hash_size;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(t=r=f;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);i+=f}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,h=e.strstart+e.lookahead,u=i,l=void 0,l=a.avail_in,u=x)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x)if(n=u._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<=x&&(e.ins_h=(e.ins_h<=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=u._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===l)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?_:(e.state.gzhead=t,m):_},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5>8&255),U(n,n.gzhead.time>>16&255),U(n,n.gzhead.time>>24&255),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(U(n,255&n.gzhead.extra.length),U(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(U(n,0),U(n,0),U(n,0),U(n,0),U(n,0),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,3),n.status=E);else{var a=v+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=E,P(n,a),0!==n.strstart&&(P(n,e.adler>>>16),P(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending!==n.pending_buf_size));)U(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindexi&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&F(e),n.pending+2<=n.pending_buf_size&&(U(n,255&e.adler),U(n,e.adler>>8&255),e.adler=0,n.status=E)):n.status=E),0!==n.pending){if(F(e),0===e.avail_out)return n.last_flush=-1,m}else if(0===e.avail_in&&T(t)<=T(r)&&t!==f)return R(e,-5);if(666===n.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==l&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===l)return A;break}if(e.match_length=0,r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===l)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=u._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):h[n.level].func(n,t);if(o!==O&&o!==B||(n.status=666),o===A||o===O)return 0===e.avail_out&&(n.last_flush=-1),m;if(o===I&&(1===t?u._tr_align(n):5!==t&&(u._tr_stored_block(n,0,0,!1),3===t&&(D(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),F(e),0===e.avail_out))return n.last_flush=-1,m}return t!==f?m:n.wrap<=0?1:(2===n.wrap?(U(n,255&e.adler),U(n,e.adler>>8&255),U(n,e.adler>>16&255),U(n,e.adler>>24&255),U(n,255&e.total_in),U(n,e.total_in>>8&255),U(n,e.total_in>>16&255),U(n,e.total_in>>24&255)):(P(n,e.adler>>>16),P(n,65535&e.adler)),F(e),0=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new c.Buf8(r.w_size),c.arraySet(u,t,l-r.w_size,r.w_size,0),t=u,l=r.w_size),a=e.avail_in,o=e.next_in,h=e.input,e.avail_in=l,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,i=r.lookahead-(x-1);r.ins_h=(r.ins_h<>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<>>=y,p-=y),p<15&&(d+=z[n++]<>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<>>=y,p-=y,(y=s-a)>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){e.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l>>=_,l-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l>>=_)),u>>>=3,l-=3}else{for(z=_+7;l>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(hd?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u>=7;n>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){"use strict";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i="[object process]"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage("","*"),r.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",r.addEventListener?r.addEventListener("message",d,!1):r.attachEvent("onmessage",d),function(e){r.postMessage(a+e,"*")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(s=l.documentElement,function(e){var t=l.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),r=0;r parseInt(o, 16))); - } break; - case "binary": { - if (!/^(?:[01]{8})*$/i.test(DATA)) return; - const dataArr = this.splitIntoParts(DATA, 8); - DATA = Uint8Array.from(dataArr.map(o => parseInt(o, 2))); - } break; + case "URL": + { + if (TYPE === "base64") + DATA = "data:application/zip;base64," + DATA; + const resp = await Scratch.fetch(DATA); + DATA = await resp.blob(); + } + break; + case "hex": + { + if (!/^(?:[0-9A-F]{2})*$/i.test(DATA)) return; + const dataArr = this.splitIntoParts(DATA, 2); + DATA = Uint8Array.from(dataArr.map((o) => parseInt(o, 16))); + } + break; + case "binary": + { + if (!/^(?:[01]{8})*$/i.test(DATA)) return; + const dataArr = this.splitIntoParts(DATA, 8); + DATA = Uint8Array.from(dataArr.map((o) => parseInt(o, 2))); + } + break; } this.zip = await JSZip.loadAsync(DATA, { createFolders: true }); @@ -476,20 +460,23 @@ COMPRESSION = Math.max(Math.min(Math.round(COMPRESSION), 9), 0); const compType = COMPRESSION === 0 ? "STORE" : "DEFLATE"; - const options = { compression: compType, compressionOptions: { level: COMPRESSION } }; + const options = { + compression: compType, + compressionOptions: { level: COMPRESSION }, + }; switch (TYPE) { case "text": case "string": return await this.zip.generateAsync({ type: "binarystring", - ...options + ...options, }); case "base64": case "data: URL": { let data = await this.zip.generateAsync({ type: "base64", - ...options + ...options, }); if (TYPE === "data: URL") data = "data:application/zip;base64," + data; @@ -498,21 +485,29 @@ case "hex": { const data = await this.zip.generateAsync({ type: "array", - ...options + ...options, }); - return data.map(data => data.toString(16).padStart(2, "0")).join(""); + return data + .map((data) => data.toString(16).padStart(2, "0")) + .join(""); } case "binary": { const data = await this.zip.generateAsync({ type: "array", - ...options + ...options, }); - return data.map(data => data.toString(2).padStart(8, "0")).join(""); + return data + .map((data) => data.toString(2).padStart(8, "0")) + .join(""); } - default: return ""; + default: + return ""; } } catch (e) { - console.error(`Zip extension: Error creating zip with type ${TYPE} compression ${COMPRESSION}:`, e); + console.error( + `Zip extension: Error creating zip with type ${TYPE} compression ${COMPRESSION}:`, + e + ); } } close() { @@ -525,7 +520,9 @@ exists({ OBJECT }) { try { - return !!this.getObj(this.normalize(this.zipPath, Scratch.Cast.toString(OBJECT))); + return !!this.getObj( + this.normalize(this.zipPath, Scratch.Cast.toString(OBJECT)) + ); } catch (e) { return false; } @@ -553,16 +550,24 @@ } case "hex": { const data = await obj.async("array"); - return data.map(data => data.toString(16).padStart(2, "0")).join(""); + return data + .map((data) => data.toString(16).padStart(2, "0")) + .join(""); } case "binary": { const data = await obj.async("array"); - return data.map(data => data.toString(2).padStart(8, "0")).join(""); + return data + .map((data) => data.toString(2).padStart(8, "0")) + .join(""); } - default: return ""; + default: + return ""; } } catch (e) { - console.error(`Zip extension: Error getting file ${FILE} with type ${TYPE}:`, e); + console.error( + `Zip extension: Error getting file ${FILE} with type ${TYPE}:`, + e + ); return ""; } } @@ -588,7 +593,8 @@ }); break; case "base64": - case "data: URL": { // compatibility + case "data: URL": { + // compatibility if (TYPE === "data: URL") CONTENT = CONTENT.substring(CONTENT.indexOf(",")); this.zip.file(path, CONTENT, { @@ -597,33 +603,43 @@ }); break; } - case "URL": { - const resp = await Scratch.fetch(CONTENT); - this.zip.file(path, await resp.blob(), { - base64: true, - createFolders: true, - }); - } break; - case "hex": { - if (!/^(?:[0-9A-F]{2})*$/i.test(CONTENT)) return ""; - const dataArr = this.splitIntoParts(CONTENT, 2); - const data = Uint8Array.from(dataArr.map(o => parseInt(o, 16))); - this.zip.file(path, data, { - createFolders: true, - }); - } break; - case "binary": { - if (!/^(?:[01]{8})*$/i.test(CONTENT)) return ""; - const dataArr = this.splitIntoParts(CONTENT, 8); - const data = Uint8Array.from(dataArr.map(o => parseInt(o, 2))); - this.zip.file(path, data, { - createFolders: true, - }); - } break; - default: return ""; + case "URL": + { + const resp = await Scratch.fetch(CONTENT); + this.zip.file(path, await resp.blob(), { + base64: true, + createFolders: true, + }); + } + break; + case "hex": + { + if (!/^(?:[0-9A-F]{2})*$/i.test(CONTENT)) return ""; + const dataArr = this.splitIntoParts(CONTENT, 2); + const data = Uint8Array.from(dataArr.map((o) => parseInt(o, 16))); + this.zip.file(path, data, { + createFolders: true, + }); + } + break; + case "binary": + { + if (!/^(?:[01]{8})*$/i.test(CONTENT)) return ""; + const dataArr = this.splitIntoParts(CONTENT, 8); + const data = Uint8Array.from(dataArr.map((o) => parseInt(o, 2))); + this.zip.file(path, data, { + createFolders: true, + }); + } + break; + default: + return ""; } } catch (e) { - console.error(`Zip extension: Error writing to file ${FILE} type ${TYPE}:`, e); + console.error( + `Zip extension: Error writing to file ${FILE} type ${TYPE}:`, + e + ); } } renameFile({ FROM, TO }) { @@ -649,7 +665,10 @@ let toPath = this.normalize(this.zipPath, TO); const replacedTo = TO.replaceAll(/\\/g, "/"); const slashes = replacedTo.split("/").length - 1; - if (slashes <= +fromObj.dir && (slashes === 0 || replacedTo.endsWith("/"))) { + if ( + slashes <= +fromObj.dir && + (slashes === 0 || replacedTo.endsWith("/")) + ) { // this is a name-only change toPath = this.normalize(fromPath, "../" + replacedTo); if (fromObj.dir) { @@ -675,7 +694,7 @@ // Move current directory if (this.zipPath.substring(1).startsWith(fromPath)) { this.zipPath = - "/" + toPath + (this.zipPath.substring(1).substring(fromPath.length)); + "/" + toPath + this.zipPath.substring(1).substring(fromPath.length); } for (const path in this.zip.files) { @@ -697,10 +716,10 @@ if (!this.getObj(path)) return; if (path === "/") return; - const shouldGoBack = this.getObj(path).dir && this.zipPath.startsWith(path); + const shouldGoBack = + this.getObj(path).dir && this.zipPath.startsWith(path); if (path.startsWith("/")) path = path.substring(1); - this.zip.remove(path); if (shouldGoBack) { @@ -737,18 +756,23 @@ const obj = this.getObj(normalized); if (!obj) return ""; switch (META) { - case "modified days since 2000": { - const msPerDay = 24 * 60 * 60 * 1000; - const start = +(new Date(2000, 0, 1)); - obj.date = new Date(start + (Scratch.Cast.toNumber(VALUE) * msPerDay)); - } break; + case "modified days since 2000": + { + const msPerDay = 24 * 60 * 60 * 1000; + const start = +new Date(2000, 0, 1); + obj.date = new Date( + start + Scratch.Cast.toNumber(VALUE) * msPerDay + ); + } + break; case "unix modified timestamp": obj.date = new Date(Scratch.Cast.toNumber(VALUE)); break; case "comment": obj.comment = VALUE; break; - default: return; + default: + return; } } catch (e) { console.error(`Zip extension: Error getting ${META} of ${FILE}:`, e); @@ -776,28 +800,29 @@ case "folder": { /** @type {Array} */ const splitPath = obj.name.split("/"); - const folders = (splitPath.slice( - 0, splitPath.length - 1 - +obj.dir - ).join("/")); + const folders = splitPath + .slice(0, splitPath.length - 1 - +obj.dir) + .join("/"); return "/" + folders + (folders === "" ? "" : "/"); } case "modification date": return obj.date.toLocaleString(navigator.language); case "long modification date": - return new Date().toLocaleString( - navigator.language, - { dateStyle: "full", timeStyle: "medium" } - ); + return new Date().toLocaleString(navigator.language, { + dateStyle: "full", + timeStyle: "medium", + }); case "modified days since 2000": { const msPerDay = 24 * 60 * 60 * 1000; - const start = +(new Date(2000, 0, 1)); + const start = +new Date(2000, 0, 1); return (+obj.date - start) / msPerDay; } case "unix modified timestamp": return +obj.date; case "comment": return obj.comment || ""; - default: return ""; + default: + return ""; } } catch (e) { console.error(`Zip extension: Error getting ${META} of ${FILE}:`, e); @@ -824,7 +849,7 @@ try { let newPath = this.normalize(this.zipPath, DIR); if (!newPath.endsWith("/")) newPath += "/"; - if (!this.getObj(newPath)) return; + if (!this.getObj(newPath) && newPath !== "/") return; this.zipPath = newPath; } catch (e) { console.error(`Error going to directory ${DIR}:`, e); @@ -841,15 +866,20 @@ const dir = normalized.substring(1); const length = dir.length; - return JSON.stringify(Object.values(this.zip.files).filter((obj) => { - // Above the current directory - if (!obj.name.startsWith(dir)) return false; - // Below the current directory - if (obj.name.substring(length).split("/").length > (obj.dir + 1)) return false; - // Is the current directory - if (obj.name === dir) return false; - return true; - }).map(obj => obj.name.substring(length))); + return JSON.stringify( + Object.values(this.zip.files) + .filter((obj) => { + // Above the current directory + if (!obj.name.startsWith(dir)) return false; + // Below the current directory + if (obj.name.substring(length).split("/").length > obj.dir + 1) + return false; + // Is the current directory + if (obj.name === dir) return false; + return true; + }) + .map((obj) => obj.name.substring(length)) + ); } catch (e) { console.error(`Zip extension: Could not get directory ${DIR}:`, e); return ""; diff --git a/extensions/CubesterYT/TurboHook.js b/extensions/CubesterYT/TurboHook.js index 864b3c474c..a87d1192a4 100644 --- a/extensions/CubesterYT/TurboHook.js +++ b/extensions/CubesterYT/TurboHook.js @@ -1,7 +1,13 @@ +// Name: TurboHook +// ID: cubesterTurboHook +// Description: Allows you to use webhooks. +// By: CubesterYT + (function (Scratch) { "use strict"; - const icon = "data:image/png;base64,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cy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDpjMzJjNzQ0My00ZjNhLTQ4N2QtODRmZS02YjNiZGYwZTA1YWQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTU1MTE5ODgtZmUwOS00NTlkLWIxYmItMzM2ZjVkMTI4MGJkIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MDAyMTI1ZDctYWQyMS00NTBmLTljYTktZjAzNmY1ZjM2ZDA1IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjg2OTc0Njc2NjY2MDAxIgogICBHSU1QOlZlcnNpb249IjIuMTAuMzAiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjJiMWVlNjctMDQzOC00M2NjLWFlOTctNGI2ZTNhMjAyN2YwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wNi0xNlQyMzowNDozNi0wNTowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg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"; + const icon = + "data:image/png;base64,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cy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDpjMzJjNzQ0My00ZjNhLTQ4N2QtODRmZS02YjNiZGYwZTA1YWQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTU1MTE5ODgtZmUwOS00NTlkLWIxYmItMzM2ZjVkMTI4MGJkIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MDAyMTI1ZDctYWQyMS00NTBmLTljYTktZjAzNmY1ZjM2ZDA1IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjg2OTc0Njc2NjY2MDAxIgogICBHSU1QOlZlcnNpb249IjIuMTAuMzAiCiAgIHRpZmY6T3JpZW50YXRpb249IjEiCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjEwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjJiMWVlNjctMDQzOC00M2NjLWFlOTctNGI2ZTNhMjAyN2YwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMy0wNi0xNlQyMzowNDozNi0wNTowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg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"; const parseOrEmptyObject = (str) => { try { @@ -21,7 +27,7 @@ color2: "#2f39a1", color3: "#28318f", menuIconURI: icon, - docsURI: "https://extensions.turbowarp.org/CubesterYT/TurboHook.html", + docsURI: "https://extensions.turbowarp.org/CubesterYT/TurboHook", blocks: [ { @@ -30,9 +36,9 @@ blockType: Scratch.BlockType.COMMAND, arguments: { hookURL: { - type: Scratch.ArgumentType.STRING - } - } + type: Scratch.ArgumentType.STRING, + }, + }, }, { opcode: "params", @@ -41,29 +47,29 @@ arguments: { MENU: { type: Scratch.ArgumentType.STRING, - menu: "PARAMS" + menu: "PARAMS", }, DATA: { - type: Scratch.ArgumentType.STRING - } - } + type: Scratch.ArgumentType.STRING, + }, + }, }, { opcode: "connector", blockType: Scratch.BlockType.REPORTER, - text: "[STRING1] , [STRING2]" - } + text: "[STRING1] , [STRING2]", + }, ], menus: { PARAMS: { acceptReporters: true, - items: ["content", "name", "icon"] - } - } + items: ["content", "name", "icon"], + }, + }, }; } - webhook ({hookDATA, hookURL}) { + webhook({ hookDATA, hookURL }) { const data = parseOrEmptyObject(hookDATA); if (!data.content) { // Typically this can't be empty, so put something there if they forgot to @@ -72,12 +78,12 @@ Scratch.fetch(hookURL, { method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify(data) + body: JSON.stringify(data), }); } - params ({MENU, DATA}) { + params({ MENU, DATA }) { DATA = Scratch.Cast.toString(DATA); if (MENU == "content") { return JSON.stringify({ content: DATA }); @@ -88,13 +94,13 @@ } return "{}"; } - connector ({STRING1, STRING2}) { + connector({ STRING1, STRING2 }) { return JSON.stringify({ ...parseOrEmptyObject(STRING1), - ...parseOrEmptyObject(STRING2) + ...parseOrEmptyObject(STRING2), }); } } Scratch.extensions.register(new TurboHook()); -})(Scratch); \ No newline at end of file +})(Scratch); diff --git a/extensions/DNin/wake-lock.js b/extensions/DNin/wake-lock.js new file mode 100644 index 0000000000..41b98c8aa2 --- /dev/null +++ b/extensions/DNin/wake-lock.js @@ -0,0 +1,118 @@ +// Name: Wake Lock +// ID: dninwakelock +// Description: Prevent the computer from falling asleep. +// By: D-ScratchNinja + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Wake Lock extension must run unsandboxed"); + } + + /** @type {WakeLockSentinel} */ + let wakeLock = null; + let latestEnabled = false; + let promise = Promise.resolve(); + + class WakeLock { + constructor(runtime) { + this.runtime = runtime; + this.runtime.on("PROJECT_STOP_ALL", this.stopAll.bind(this)); + } + + getInfo() { + return { + id: "dninwakelock", + name: "Wake Lock", + docsURI: "https://extensions.turbowarp.org/DNin/wake-lock", + blocks: [ + { + opcode: "setWakeLock", + blockType: Scratch.BlockType.COMMAND, + text: "turn wake lock [enabled]", + arguments: { + enabled: { + type: Scratch.ArgumentType.STRING, + menu: "state", + defaultValue: "true", + }, + }, + }, + { + opcode: "isLocked", + blockType: Scratch.BlockType.BOOLEAN, + text: "is wake lock active?", + }, + ], + menus: { + state: { + acceptReporters: true, + items: [ + { + text: "on", + value: "true", + }, + { + text: "off", + value: "false", + }, + ], + }, + }, + }; + } + + stopAll() { + this.setWakeLock({ + enabled: false, + }); + } + + setWakeLock(args) { + if (!navigator.wakeLock) { + // Not supported in this browser. + return; + } + + const previousEnabled = latestEnabled; + latestEnabled = Scratch.Cast.toBoolean(args.enabled); + if (latestEnabled && !previousEnabled) { + promise = promise + .then(() => navigator.wakeLock.request("screen")) + .then((sentinel) => { + wakeLock = sentinel; + }) + .catch((error) => { + console.error(error); + // Allow to retry + latestEnabled = false; + }); + return promise; + } else if (!latestEnabled && previousEnabled) { + promise = promise + .then(() => { + if (wakeLock) { + return wakeLock.release(); + } else { + // Attempt to enable in the first place didn't work + } + }) + .then(() => { + wakeLock = null; + }) + .catch((error) => { + console.error(error); + wakeLock = null; + }); + return promise; + } + } + + isLocked() { + return !!wakeLock; + } + } + + Scratch.extensions.register(new WakeLock(Scratch.vm.runtime)); +})(Scratch); diff --git a/extensions/DT/cameracontrols.js b/extensions/DT/cameracontrols.js index 0c5f0cc4ab..4dc52493d6 100644 --- a/extensions/DT/cameracontrols.js +++ b/extensions/DT/cameracontrols.js @@ -1,13 +1,21 @@ -(Scratch => { - 'use strict'; +// Name: Camera Controls +// ID: DTcameracontrols +// Description: Move the visible part of the stage. +// By: DT + +((Scratch) => { + "use strict"; if (!Scratch.extensions.unsandboxed) { - throw new Error('Camera extension must be run unsandboxed'); + throw new Error("Camera extension must be run unsandboxed"); } - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0OCIgaGVpZ2h0PSIyMjUuMzU0OCIgdmlld0JveD0iMCwwLDIyNS4zNTQ4LDIyNS4zNTQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTg3LjMyMjkzLC0zNy4zMjI1OSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTg3LjMyMjk0LDE1MGMwLC02Mi4yMzAwMSA1MC40NDczOSwtMTEyLjY3NzQgMTEyLjY3NzQsLTExMi42Nzc0YzYyLjIzMDAxLDAgMTEyLjY3NzQsNTAuNDQ3MzkgMTEyLjY3NzQsMTEyLjY3NzRjMCw2Mi4yMzAwMSAtNTAuNDQ3MzksMTEyLjY3NzQgLTExMi42Nzc0LDExMi42Nzc0Yy02Mi4yMzAwMSwwIC0xMTIuNjc3NCwtNTAuNDQ3MzkgLTExMi42Nzc0LC0xMTIuNjc3NHoiIGZpbGw9IiNmZjRkYTciIHN0cm9rZS13aWR0aD0iMCIvPjxnPjxwYXRoIGQ9Ik0zMTcuMTAyOSw4MC44MTA4N2MyMS44OTI0LDAgMzkuNjYyMDcsMTcuNzM3MjMgMzkuNjYyMDcsMzkuNjM0NGMwLDEyLjMwNTE3IC01LjYxMTQ4LDIzLjI5NjIyIC0xNC40MDA4OCwzMC41NjgyNGg4Ljc3MDMydjY4LjE3NTYzaC0xMTQuMTMzMjV2LTU1Ljc5ODg5Yy0xNC4zMzQwOCwtMy41MjgxNyAtMjQuOTYxNTMsLTE2LjQ1NzQ3IC0yNC45NjE1MywtMzEuODgwNDRjMCwtMTguMTM5IDE0LjY5NjczLC0zMi44MzQ3OCAzMi44MzQ3OCwtMzIuODM0NzhjMTIuMDM3OTUsMCAyMi41NTY2MSw2LjQ3ODAxIDI4LjI3MjExLDE2LjEzMzk1bDQuODYxMzcsLTAuOTI0NzVjMy4xMjkyNiwtMTguNzY2OTYgMTkuNDM5NzYsLTMzLjA3MzM2IDM5LjA5OTAyLC0zMy4wNzMzNnpNMjc2LjIxODUxLDE0MS4yOTE3MWMtMS4xMDAzNSwzLjUzMzg5IC0yLjc2OTQ3LDYuODEyMDMgLTQuOTIwNTQsOS43MjE3OWgyMC41NDc3NGMtMy42ODc1NCwtMy4wNDgxNCAtNi44MDI0OCwtNi43NjUyNyAtOS4xODU0NSwtMTAuOTQ0Mjl6IiBmaWxsPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMzM2LjU3NTI5LDExOS41MjgxNWMwLDExLjA2ODM1IC04Ljk3MjY0LDIwLjA0MDk5IC0yMC4wNDA5OSwyMC4wNDA5OWMtMTEuMDY4MzUsMCAtMjAuMDQwOTksLTguOTcyNjQgLTIwLjA0MDk5LC0yMC4wNDA5OWMwLC0xMS4wNjgzNSA4Ljk3MjY0LC0yMC4wNDA5OSAyMC4wNDA5OSwtMjAuMDQwOTljMTEuMDY4MzUsMCAyMC4wNDA5OSw4Ljk3MjY0IDIwLjA0MDk5LDIwLjA0MDk5eiIgZmlsbD0iI2ZmNGRhNyIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjYxLjE4MywxMzAuMDI1ODFjMCw4Ljk2MDA0IC03LjI2MzYyLDE2LjIyMzY2IC0xNi4yMjM2NiwxNi4yMjM2NmMtOC45NjAwNCwwIC0xNi4yMjM2NiwtNy4yNjM2MiAtMTYuMjIzNjYsLTE2LjIyMzY2YzAsLTguOTYwMDQgNy4yNjM2MiwtMTYuMjIzNjYgMTYuMjIzNjYsLTE2LjIyMzY2YzguOTYwMDQsMCAxNi4yMjM2Niw3LjI2MzYyIDE2LjIyMzY2LDE2LjIyMzY2eiIgZmlsbD0iI2ZmNGRhNyIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMzg3Ljk2MDM5LDE0NS44NTcyNHY2MC41MTA0M2wtMjEuNjAzMjYsLTEzLjQ3MjE3aC0xNi44OTgzNHYtMzMuNTY2MDdoMTYuODk4MzRsMjEuNTk5MTcsLTEzLjQ3MTEyeiIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjExMi42NzcwNjU6MTEyLjY3NzQwNS0tPg=='; - const CW = 'data:image/png;base64,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'; - const CCW = 'data:image/png;base64,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'; + const icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0OCIgaGVpZ2h0PSIyMjUuMzU0OCIgdmlld0JveD0iMCwwLDIyNS4zNTQ4LDIyNS4zNTQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTg3LjMyMjkzLC0zNy4zMjI1OSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTg3LjMyMjk0LDE1MGMwLC02Mi4yMzAwMSA1MC40NDczOSwtMTEyLjY3NzQgMTEyLjY3NzQsLTExMi42Nzc0YzYyLjIzMDAxLDAgMTEyLjY3NzQsNTAuNDQ3MzkgMTEyLjY3NzQsMTEyLjY3NzRjMCw2Mi4yMzAwMSAtNTAuNDQ3MzksMTEyLjY3NzQgLTExMi42Nzc0LDExMi42Nzc0Yy02Mi4yMzAwMSwwIC0xMTIuNjc3NCwtNTAuNDQ3MzkgLTExMi42Nzc0LC0xMTIuNjc3NHoiIGZpbGw9IiNmZjRkYTciIHN0cm9rZS13aWR0aD0iMCIvPjxnPjxwYXRoIGQ9Ik0zMTcuMTAyOSw4MC44MTA4N2MyMS44OTI0LDAgMzkuNjYyMDcsMTcuNzM3MjMgMzkuNjYyMDcsMzkuNjM0NGMwLDEyLjMwNTE3IC01LjYxMTQ4LDIzLjI5NjIyIC0xNC40MDA4OCwzMC41NjgyNGg4Ljc3MDMydjY4LjE3NTYzaC0xMTQuMTMzMjV2LTU1Ljc5ODg5Yy0xNC4zMzQwOCwtMy41MjgxNyAtMjQuOTYxNTMsLTE2LjQ1NzQ3IC0yNC45NjE1MywtMzEuODgwNDRjMCwtMTguMTM5IDE0LjY5NjczLC0zMi44MzQ3OCAzMi44MzQ3OCwtMzIuODM0NzhjMTIuMDM3OTUsMCAyMi41NTY2MSw2LjQ3ODAxIDI4LjI3MjExLDE2LjEzMzk1bDQuODYxMzcsLTAuOTI0NzVjMy4xMjkyNiwtMTguNzY2OTYgMTkuNDM5NzYsLTMzLjA3MzM2IDM5LjA5OTAyLC0zMy4wNzMzNnpNMjc2LjIxODUxLDE0MS4yOTE3MWMtMS4xMDAzNSwzLjUzMzg5IC0yLjc2OTQ3LDYuODEyMDMgLTQuOTIwNTQsOS43MjE3OWgyMC41NDc3NGMtMy42ODc1NCwtMy4wNDgxNCAtNi44MDI0OCwtNi43NjUyNyAtOS4xODU0NSwtMTAuOTQ0Mjl6IiBmaWxsPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMzM2LjU3NTI5LDExOS41MjgxNWMwLDExLjA2ODM1IC04Ljk3MjY0LDIwLjA0MDk5IC0yMC4wNDA5OSwyMC4wNDA5OWMtMTEuMDY4MzUsMCAtMjAuMDQwOTksLTguOTcyNjQgLTIwLjA0MDk5LC0yMC4wNDA5OWMwLC0xMS4wNjgzNSA4Ljk3MjY0LC0yMC4wNDA5OSAyMC4wNDA5OSwtMjAuMDQwOTljMTEuMDY4MzUsMCAyMC4wNDA5OSw4Ljk3MjY0IDIwLjA0MDk5LDIwLjA0MDk5eiIgZmlsbD0iI2ZmNGRhNyIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjYxLjE4MywxMzAuMDI1ODFjMCw4Ljk2MDA0IC03LjI2MzYyLDE2LjIyMzY2IC0xNi4yMjM2NiwxNi4yMjM2NmMtOC45NjAwNCwwIC0xNi4yMjM2NiwtNy4yNjM2MiAtMTYuMjIzNjYsLTE2LjIyMzY2YzAsLTguOTYwMDQgNy4yNjM2MiwtMTYuMjIzNjYgMTYuMjIzNjYsLTE2LjIyMzY2YzguOTYwMDQsMCAxNi4yMjM2Niw3LjI2MzYyIDE2LjIyMzY2LDE2LjIyMzY2eiIgZmlsbD0iI2ZmNGRhNyIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMzg3Ljk2MDM5LDE0NS44NTcyNHY2MC41MTA0M2wtMjEuNjAzMjYsLTEzLjQ3MjE3aC0xNi44OTgzNHYtMzMuNTY2MDdoMTYuODk4MzRsMjEuNTk5MTcsLTEzLjQ3MTEyeiIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjExMi42NzcwNjU6MTEyLjY3NzQwNS0tPg=="; + const CW = + "data:image/png;base64,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"; + const CCW = + "data:image/png;base64,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"; const vm = Scratch.vm; @@ -15,268 +23,284 @@ let cameraY = 0; let cameraZoom = 100; let cameraDirection = 90; - let cameraBG = '#ffffff'; + let cameraBG = "#ffffff"; vm.runtime.runtimeOptions.fencing = false; vm.renderer.offscreenTouching = true; + vm.setInterpolation(false); - function updateCamera(x = cameraX, y = cameraY, scale = cameraZoom / 100, rot = -cameraDirection + 90) { - rot = rot / 180 * Math.PI; + function updateCamera( + x = cameraX, + y = cameraY, + scale = cameraZoom / 100, + rot = -cameraDirection + 90 + ) { + rot = (rot / 180) * Math.PI; let s = Math.sin(rot) * scale; let c = Math.cos(rot) * scale; let w = vm.runtime.stageWidth / 2; let h = vm.runtime.stageHeight / 2; vm.renderer._projection = [ - c / w, -s / h, 0, 0, - s / w, c / h, 0, 0, - 0, 0, -1, 0, - (c * -x + s * -y) / w, (c * -y - s * -x) / h, 0, 1 + c / w, + -s / h, + 0, + 0, + s / w, + c / h, + 0, + 0, + 0, + 0, + -1, + 0, + (c * -x + s * -y) / w, + (c * -y - s * -x) / h, + 0, + 1, ]; vm.renderer.dirty = true; } // tell resize to update camera as well - vm.runtime.on('STAGE_SIZE_CHANGED', _ => updateCamera()); + vm.runtime.on("STAGE_SIZE_CHANGED", (_) => updateCamera()); // fix mouse positions let oldSX = vm.runtime.ioDevices.mouse.getScratchX; let oldSY = vm.runtime.ioDevices.mouse.getScratchY; - vm.runtime.ioDevices.mouse.getScratchX = function(...a) { - return (oldSX.apply(this, a) + cameraX) / cameraZoom * 100; + vm.runtime.ioDevices.mouse.getScratchX = function (...a) { + return ((oldSX.apply(this, a) + cameraX) / cameraZoom) * 100; }; - vm.runtime.ioDevices.mouse.getScratchY = function(...a) { - return (oldSY.apply(this, a) + cameraY) / cameraZoom * 100; + vm.runtime.ioDevices.mouse.getScratchY = function (...a) { + return ((oldSY.apply(this, a) + cameraY) / cameraZoom) * 100; }; class Camera { - getInfo() { return { + id: "DTcameracontrols", + name: "Camera", - id: 'DTcameracontrols', - name: 'Camera', - - color1: '#ff4da7', - color2: '#de4391', - color3: '#c83c82', + color1: "#ff4da7", + color2: "#de4391", + color3: "#c83c82", menuIconURI: icon, blocks: [ { - opcode: 'moveSteps', + opcode: "moveSteps", blockType: Scratch.BlockType.COMMAND, - text: 'move camera [val] steps', + text: "move camera [val] steps", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 10 + defaultValue: 10, }, - } + }, }, { - opcode: 'rotateCW', + opcode: "rotateCW", blockType: Scratch.BlockType.COMMAND, - text: 'turn camera [image] [val] degrees', + text: "turn camera [image] [val] degrees", arguments: { image: { type: Scratch.ArgumentType.IMAGE, - dataURI: CW + dataURI: CW, }, val: { type: Scratch.ArgumentType.ANGLE, - defaultValue: 15 - } - } + defaultValue: 15, + }, + }, }, { - opcode: 'rotateCCW', + opcode: "rotateCCW", blockType: Scratch.BlockType.COMMAND, - text: 'turn camera [image] [val] degrees', + text: "turn camera [image] [val] degrees", arguments: { image: { type: Scratch.ArgumentType.IMAGE, - dataURI: CCW + dataURI: CCW, }, val: { type: Scratch.ArgumentType.ANGLE, - defaultValue: 15 - } - } + defaultValue: 15, + }, + }, }, - '---', + "---", { - opcode: 'goTo', + opcode: "goTo", blockType: Scratch.BlockType.COMMAND, - text: 'move camera to [sprite]', + text: "move camera to [sprite]", arguments: { sprite: { type: Scratch.ArgumentType.STRING, menu: "sprites", }, - } + }, }, { - opcode: 'setBoth', + opcode: "setBoth", blockType: Scratch.BlockType.COMMAND, - text: 'set camera to x: [x] y: [y]', + text: "set camera to x: [x] y: [y]", arguments: { x: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 + defaultValue: 0, }, y: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 + defaultValue: 0, }, - } + }, }, - '---', + "---", { - opcode: 'setDirection', + opcode: "setDirection", blockType: Scratch.BlockType.COMMAND, - text: 'set camera direction to [val]', + text: "set camera direction to [val]", arguments: { val: { type: Scratch.ArgumentType.ANGLE, - defaultValue: 90 - } - } + defaultValue: 90, + }, + }, }, { - opcode: 'pointTowards', + opcode: "pointTowards", blockType: Scratch.BlockType.COMMAND, - text: 'point camera towards [sprite]', + text: "point camera towards [sprite]", arguments: { sprite: { type: Scratch.ArgumentType.STRING, menu: "sprites", }, - } + }, }, - '---', + "---", { - opcode: 'changeX', + opcode: "changeX", blockType: Scratch.BlockType.COMMAND, - text: 'change camera x by [val]', + text: "change camera x by [val]", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 10 - } - } + defaultValue: 10, + }, + }, }, { - opcode: 'setX', + opcode: "setX", blockType: Scratch.BlockType.COMMAND, - text: 'set camera x to [val]', + text: "set camera x to [val]", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - } - } + defaultValue: 0, + }, + }, }, { - opcode: 'changeY', + opcode: "changeY", blockType: Scratch.BlockType.COMMAND, - text: 'change camera y by [val]', + text: "change camera y by [val]", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 10 - } - } + defaultValue: 10, + }, + }, }, { - opcode: 'setY', + opcode: "setY", blockType: Scratch.BlockType.COMMAND, - text: 'set camera y to [val]', + text: "set camera y to [val]", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - } - } + defaultValue: 0, + }, + }, }, - '---', + "---", { - opcode: 'getX', + opcode: "getX", blockType: Scratch.BlockType.REPORTER, - text: 'camera x', + text: "camera x", }, { - opcode: 'getY', + opcode: "getY", blockType: Scratch.BlockType.REPORTER, - text: 'camera y', + text: "camera y", }, { - opcode: 'getDirection', + opcode: "getDirection", blockType: Scratch.BlockType.REPORTER, - text: 'camera direction', + text: "camera direction", }, - '---', + "---", { - opcode: 'changeZoom', + opcode: "changeZoom", blockType: Scratch.BlockType.COMMAND, - text: 'change camera zoom by [val]', + text: "change camera zoom by [val]", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 10 - } - } + defaultValue: 10, + }, + }, }, { - opcode: 'setZoom', + opcode: "setZoom", blockType: Scratch.BlockType.COMMAND, - text: 'set camera zoom to [val] %', + text: "set camera zoom to [val] %", arguments: { val: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 100 - } - } + defaultValue: 100, + }, + }, }, { - opcode: 'getZoom', + opcode: "getZoom", blockType: Scratch.BlockType.REPORTER, - text: 'camera zoom', + text: "camera zoom", }, - '---', + "---", { - opcode: 'setCol', + opcode: "setCol", blockType: Scratch.BlockType.COMMAND, - text: 'set background color to [val]', + text: "set background color to [val]", arguments: { val: { - type: Scratch.ArgumentType.COLOR - } - } + type: Scratch.ArgumentType.COLOR, + }, + }, }, { - opcode: 'getCol', + opcode: "getCol", blockType: Scratch.BlockType.REPORTER, - text: 'background color', + text: "background color", }, ], menus: { sprites: { - items: 'getSprites', + items: "getSprites", acceptReporters: true, - } + }, }, }; } - getSprites(){ + getSprites() { let sprites = []; - Scratch.vm.runtime.targets.forEach(e=>{ + Scratch.vm.runtime.targets.forEach((e) => { if (e.isOriginal && !e.isStage) sprites.push(e.sprite.name); }); if (sprites.length === 0) { - sprites.push('no sprites exist'); + sprites.push("no sprites exist"); } return sprites; } @@ -346,13 +370,18 @@ } setCol(args, util) { const rgb = Scratch.Cast.toRgbColorList(args.val); - Scratch.vm.renderer.setBackgroundColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); + Scratch.vm.renderer.setBackgroundColor( + rgb[0] / 255, + rgb[1] / 255, + rgb[2] / 255 + ); + cameraBG = args.val; } getCol() { return cameraBG; } moveSteps(args) { - let dir = (-cameraDirection + 90) * Math.PI / 180; + let dir = ((-cameraDirection + 90) * Math.PI) / 180; cameraX += args.val * Math.cos(dir); cameraY += args.val * Math.sin(dir); updateCamera(); @@ -380,7 +409,7 @@ vm.runtime.requestRedraw(); } radToDeg(rad) { - return rad * 180 / Math.PI; + return (rad * 180) / Math.PI; } } diff --git a/extensions/JeremyGamer13/tween.js b/extensions/JeremyGamer13/tween.js index 5b6fe513e4..e19e1b5d94 100644 --- a/extensions/JeremyGamer13/tween.js +++ b/extensions/JeremyGamer13/tween.js @@ -1,304 +1,313 @@ -(function (Scratch) { - 'use strict'; - - const EasingMethods = [ - "linear", - "sine", - "quad", - "cubic", - "quart", - "quint", - "expo", - "circ", - "back", - "elastic", - "bounce" - ]; - - const BlockType = Scratch.BlockType; - const ArgumentType = Scratch.ArgumentType; - const Cast = Scratch.Cast; - - class Tween { - getInfo() { - return { - id: 'jeremygamerTweening', - name: 'Tweening', - blocks: [ - { - opcode: 'tweenValue', - text: '[MODE] ease [DIRECTION] [START] to [END] by [AMOUNT]%', - disableMonitor: true, - blockType: BlockType.REPORTER, - arguments: { - MODE: { type: ArgumentType.STRING, menu: 'modes' }, - DIRECTION: { type: ArgumentType.STRING, menu: 'direction' }, - START: { type: ArgumentType.NUMBER, defaultValue: 0 }, - END: { type: ArgumentType.NUMBER, defaultValue: 100 }, - AMOUNT: { type: ArgumentType.NUMBER, defaultValue: 50 }, - } - } - ], - menus: { - modes: { - acceptReporters: true, - items: EasingMethods.map(item => ({ text: item, value: item })) - }, - direction: { - acceptReporters: true, - items: [ - "in", - "out", - "in out" - ].map(item => ({ text: item, value: item })) - } - } - }; - } - - // utilities - multiplierToNormalNumber(mul, start, end) { - const multiplier = end - start; - const result = (mul * multiplier) + start; - return result; - } - - // blocks - tweenValue(args) { - const easeMethod = Cast.toString(args.MODE); - const easeDirection = Cast.toString(args.DIRECTION); - - const start = Cast.toNumber(args.START); - const end = Cast.toNumber(args.END); - - // easing method does not exist, return starting number - if (!EasingMethods.includes(easeMethod)) return start; - // easing method is not implemented, return starting number - if (!this[easeMethod]) return start; - - const progress = Cast.toNumber(args.AMOUNT) / 100; - - const tweened = this[easeMethod](progress, easeDirection); - return this.multiplierToNormalNumber(tweened, start, end); - } - - // easing functions (placed below blocks for organization) - linear(x) { - // lol - return x; - } - - sine(x, dir) { - switch (dir) { - case "in": { - return 1 - Math.cos((x * Math.PI) / 2); - } - case "out": { - return Math.sin((x * Math.PI) / 2); - } - case "in out": { - return -(Math.cos(Math.PI * x) - 1) / 2; - } - default: - return 0; - } - } - - quad(x, dir) { - switch (dir) { - case "in": { - return x * x; - } - case "out": { - return 1 - (1 - x) * (1 - x); - } - case "in out": { - return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; - } - default: - return 0; - } - } - - cubic(x, dir) { - switch (dir) { - case "in": { - return x * x * x; - } - case "out": { - return 1 - Math.pow(1 - x, 3); - } - case "in out": { - return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; - } - default: - return 0; - } - } - - quart(x, dir) { - switch (dir) { - case "in": { - return x * x * x * x; - } - case "out": { - return 1 - Math.pow(1 - x, 4); - } - case "in out": { - return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; - } - default: - return 0; - } - } - - quint(x, dir) { - switch (dir) { - case "in": { - return x * x * x * x * x; - } - case "out": { - return 1 - Math.pow(1 - x, 5); - } - case "in out": { - return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; - } - default: - return 0; - } - } - - expo(x, dir) { - switch (dir) { - case "in": { - return x === 0 ? 0 : Math.pow(2, 10 * x - 10); - } - case "out": { - return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); - } - case "in out": { - return x === 0 - ? 0 - : x === 1 - ? 1 - : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 - : (2 - Math.pow(2, -20 * x + 10)) / 2; - } - default: - return 0; - } - } - - circ(x, dir) { - switch (dir) { - case "in": { - return 1 - Math.sqrt(1 - Math.pow(x, 2)); - } - case "out": { - return Math.sqrt(1 - Math.pow(x - 1, 2)); - } - case "in out": { - return x < 0.5 - ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 - : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; - } - default: - return 0; - } - } - - back(x, dir) { - switch (dir) { - case "in": { - const c1 = 1.70158; - const c3 = c1 + 1; - - return c3 * x * x * x - c1 * x * x; - } - case "out": { - const c1 = 1.70158; - const c3 = c1 + 1; - - return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); - } - case "in out": { - const c1 = 1.70158; - const c2 = c1 * 1.525; - - return x < 0.5 - ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 - : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; - } - default: - return 0; - } - } - - elastic(x, dir) { - switch (dir) { - case "in": { - const c4 = (2 * Math.PI) / 3; - - return x === 0 - ? 0 - : x === 1 - ? 1 - : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); - } - case "out": { - const c4 = (2 * Math.PI) / 3; - - return x === 0 - ? 0 - : x === 1 - ? 1 - : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; - } - case "in out": { - const c5 = (2 * Math.PI) / 4.5; - - return x === 0 - ? 0 - : x === 1 - ? 1 - : x < 0.5 - ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 - : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1; - } - default: - return 0; - } - } - - bounce(x, dir) { - switch (dir) { - case "in": { - return 1 - this.bounce(1 - x, "out"); - } - case "out": { - const n1 = 7.5625; - const d1 = 2.75; - - if (x < 1 / d1) { - return n1 * x * x; - } else if (x < 2 / d1) { - return n1 * (x -= 1.5 / d1) * x + 0.75; - } else if (x < 2.5 / d1) { - return n1 * (x -= 2.25 / d1) * x + 0.9375; - } else { - return n1 * (x -= 2.625 / d1) * x + 0.984375; - } - } - case "in out": { - return x < 0.5 - ? (1 - this.bounce(1 - 2 * x, "out")) / 2 - : (1 + this.bounce(2 * x - 1, "out")) / 2; - } - default: - return 0; - } - } - } - - Scratch.extensions.register(new Tween()); -})(Scratch); +// Name: Tween +// ID: jeremygamerTweening +// Description: Easing methods for smooth animations. +// By: JeremyGamer13 + +(function (Scratch) { + "use strict"; + + const EasingMethods = [ + "linear", + "sine", + "quad", + "cubic", + "quart", + "quint", + "expo", + "circ", + "back", + "elastic", + "bounce", + ]; + + const BlockType = Scratch.BlockType; + const ArgumentType = Scratch.ArgumentType; + const Cast = Scratch.Cast; + + class Tween { + getInfo() { + return { + id: "jeremygamerTweening", + name: "Tweening", + blocks: [ + { + opcode: "tweenValue", + text: "[MODE] ease [DIRECTION] [START] to [END] by [AMOUNT]%", + disableMonitor: true, + blockType: BlockType.REPORTER, + arguments: { + MODE: { type: ArgumentType.STRING, menu: "modes" }, + DIRECTION: { type: ArgumentType.STRING, menu: "direction" }, + START: { type: ArgumentType.NUMBER, defaultValue: 0 }, + END: { type: ArgumentType.NUMBER, defaultValue: 100 }, + AMOUNT: { type: ArgumentType.NUMBER, defaultValue: 50 }, + }, + }, + ], + menus: { + modes: { + acceptReporters: true, + items: EasingMethods.map((item) => ({ text: item, value: item })), + }, + direction: { + acceptReporters: true, + items: ["in", "out", "in out"].map((item) => ({ + text: item, + value: item, + })), + }, + }, + }; + } + + // utilities + multiplierToNormalNumber(mul, start, end) { + const multiplier = end - start; + const result = mul * multiplier + start; + return result; + } + + // blocks + tweenValue(args) { + const easeMethod = Cast.toString(args.MODE); + const easeDirection = Cast.toString(args.DIRECTION); + + const start = Cast.toNumber(args.START); + const end = Cast.toNumber(args.END); + + // easing method does not exist, return starting number + if (!EasingMethods.includes(easeMethod)) return start; + // easing method is not implemented, return starting number + if (!this[easeMethod]) return start; + + const progress = Cast.toNumber(args.AMOUNT) / 100; + + const tweened = this[easeMethod](progress, easeDirection); + return this.multiplierToNormalNumber(tweened, start, end); + } + + // easing functions (placed below blocks for organization) + linear(x) { + // lol + return x; + } + + sine(x, dir) { + switch (dir) { + case "in": { + return 1 - Math.cos((x * Math.PI) / 2); + } + case "out": { + return Math.sin((x * Math.PI) / 2); + } + case "in out": { + return -(Math.cos(Math.PI * x) - 1) / 2; + } + default: + return 0; + } + } + + quad(x, dir) { + switch (dir) { + case "in": { + return x * x; + } + case "out": { + return 1 - (1 - x) * (1 - x); + } + case "in out": { + return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; + } + default: + return 0; + } + } + + cubic(x, dir) { + switch (dir) { + case "in": { + return x * x * x; + } + case "out": { + return 1 - Math.pow(1 - x, 3); + } + case "in out": { + return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + } + default: + return 0; + } + } + + quart(x, dir) { + switch (dir) { + case "in": { + return x * x * x * x; + } + case "out": { + return 1 - Math.pow(1 - x, 4); + } + case "in out": { + return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; + } + default: + return 0; + } + } + + quint(x, dir) { + switch (dir) { + case "in": { + return x * x * x * x * x; + } + case "out": { + return 1 - Math.pow(1 - x, 5); + } + case "in out": { + return x < 0.5 + ? 16 * x * x * x * x * x + : 1 - Math.pow(-2 * x + 2, 5) / 2; + } + default: + return 0; + } + } + + expo(x, dir) { + switch (dir) { + case "in": { + return x === 0 ? 0 : Math.pow(2, 10 * x - 10); + } + case "out": { + return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); + } + case "in out": { + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? Math.pow(2, 20 * x - 10) / 2 + : (2 - Math.pow(2, -20 * x + 10)) / 2; + } + default: + return 0; + } + } + + circ(x, dir) { + switch (dir) { + case "in": { + return 1 - Math.sqrt(1 - Math.pow(x, 2)); + } + case "out": { + return Math.sqrt(1 - Math.pow(x - 1, 2)); + } + case "in out": { + return x < 0.5 + ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 + : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2; + } + default: + return 0; + } + } + + back(x, dir) { + switch (dir) { + case "in": { + const c1 = 1.70158; + const c3 = c1 + 1; + + return c3 * x * x * x - c1 * x * x; + } + case "out": { + const c1 = 1.70158; + const c3 = c1 + 1; + + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + } + case "in out": { + const c1 = 1.70158; + const c2 = c1 * 1.525; + + return x < 0.5 + ? (Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2 + : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2; + } + default: + return 0; + } + } + + elastic(x, dir) { + switch (dir) { + case "in": { + const c4 = (2 * Math.PI) / 3; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4); + } + case "out": { + const c4 = (2 * Math.PI) / 3; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1; + } + case "in out": { + const c5 = (2 * Math.PI) / 4.5; + + return x === 0 + ? 0 + : x === 1 + ? 1 + : x < 0.5 + ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / + 2 + + 1; + } + default: + return 0; + } + } + + bounce(x, dir) { + switch (dir) { + case "in": { + return 1 - this.bounce(1 - x, "out"); + } + case "out": { + const n1 = 7.5625; + const d1 = 2.75; + + if (x < 1 / d1) { + return n1 * x * x; + } else if (x < 2 / d1) { + return n1 * (x -= 1.5 / d1) * x + 0.75; + } else if (x < 2.5 / d1) { + return n1 * (x -= 2.25 / d1) * x + 0.9375; + } else { + return n1 * (x -= 2.625 / d1) * x + 0.984375; + } + } + case "in out": { + return x < 0.5 + ? (1 - this.bounce(1 - 2 * x, "out")) / 2 + : (1 + this.bounce(2 * x - 1, "out")) / 2; + } + default: + return 0; + } + } + } + + Scratch.extensions.register(new Tween()); +})(Scratch); diff --git a/extensions/Lily/AllMenus.js b/extensions/Lily/AllMenus.js new file mode 100644 index 0000000000..827b03aed3 --- /dev/null +++ b/extensions/Lily/AllMenus.js @@ -0,0 +1,56 @@ +// Name: All Menus +// ID: lmsAllMenus +// Description: Special category with every menu from every Scratch category and extensions. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + var blockXML; + const blacklist = ["looks_costumenumbername", "extension_wedo_tilt_menu"]; + + Scratch.vm.addListener("BLOCKSINFO_UPDATE", refreshMenus); + + function refreshMenus() { + if (!window.ScratchBlocks) return; + Scratch.vm.removeListener("BLOCKSINFO_UPDATE", refreshMenus); + + let allBlocks = Object.keys(ScratchBlocks.Blocks); + + allBlocks = allBlocks.filter( + (item) => item.includes("menu") && !blacklist.includes(item) + ); + + const menuBlocks = allBlocks.map( + (item) => '' + ); + + blockXML = menuBlocks.join(""); + Scratch.vm.runtime.extensionManager.refreshBlocks(); + } + + class AllMenus { + constructor() { + Scratch.vm.runtime.on("EXTENSION_ADDED", () => { + refreshMenus(); + }); + } + + getInfo() { + return { + id: "lmsAllMenus", + name: "All Menus", + blocks: [ + { + blockType: Scratch.BlockType.XML, + xml: blockXML, + }, + ], + }; + } + } + + refreshMenus(); + + Scratch.extensions.register(new AllMenus()); +})(Scratch); diff --git a/extensions/Lily/Cast.js b/extensions/Lily/Cast.js index 5035acae5a..3baf507a99 100644 --- a/extensions/Lily/Cast.js +++ b/extensions/Lily/Cast.js @@ -1,73 +1,86 @@ -(function (Scratch) { - 'use strict'; - - const Cast = Scratch.Cast; - - class CastUtil { - getInfo() { - return { - id: 'lmsCast', - name: 'Cast', - blocks: [ - { - opcode: 'toType', - blockType: Scratch.BlockType.REPORTER, - text: 'cast [INPUT] to [TYPE]', - allowDropAnywhere: true, - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - TYPE: { - type: Scratch.ArgumentType.STRING, - menu: 'type' - } - } - }, - { - opcode: 'typeOf', - blockType: Scratch.BlockType.REPORTER, - text: 'type of [INPUT]', - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - } - } - } - ], - menus: { - type: { - acceptReporters: true, - items: ['number', 'string', 'boolean', 'default'] - } - } - }; - } - - toType(args) { - const input = args.INPUT; - switch (args.TYPE) { - case ('number'): return Cast.toNumber(input); - case ('string'): return Cast.toString(input); - case ('boolean'): return Cast.toBoolean(input); - default: return input; - } - } - - typeOf(args) { - const input = args.INPUT; - switch (typeof input) { - case ('number'): return 'number'; - case ('string'): return 'string'; - case ('boolean'): return 'boolean'; - default: return ''; - } - } - } - - Scratch.extensions.register(new CastUtil()); -})(Scratch); +// Name: Cast +// ID: lmsCast +// Description: Convert values between types. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const Cast = Scratch.Cast; + + class CastUtil { + getInfo() { + return { + id: "lmsCast", + name: "Cast", + blocks: [ + { + opcode: "toType", + blockType: Scratch.BlockType.REPORTER, + text: "cast [INPUT] to [TYPE]", + allowDropAnywhere: true, + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "type", + }, + }, + }, + { + opcode: "typeOf", + blockType: Scratch.BlockType.REPORTER, + text: "type of [INPUT]", + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + }, + }, + ], + menus: { + type: { + acceptReporters: true, + items: ["number", "string", "boolean", "default"], + }, + }, + }; + } + + toType(args) { + const input = args.INPUT; + switch (args.TYPE) { + case "number": + return Cast.toNumber(input); + case "string": + return Cast.toString(input); + case "boolean": + return Cast.toBoolean(input); + default: + return input; + } + } + + typeOf(args) { + const input = args.INPUT; + switch (typeof input) { + case "number": + return "number"; + case "string": + return "string"; + case "boolean": + return "boolean"; + default: + return ""; + } + } + } + + Scratch.extensions.register(new CastUtil()); +})(Scratch); diff --git a/extensions/Lily/ClonesPlus.js b/extensions/Lily/ClonesPlus.js index f98fb470bf..009e997692 100644 --- a/extensions/Lily/ClonesPlus.js +++ b/extensions/Lily/ClonesPlus.js @@ -1,611 +1,656 @@ -(function (Scratch) { - 'use strict'; - - const menuIconURI = "data:image/png;base64,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"; - - /** - * @param {VM.Target|null} target - * @param {string|unknown} thing - * @returns {string|number|boolean} - */ - const getThingOfTarget = (target, thing) => { - if (!target) { - return ''; - } - if (thing === 'x position') { - return target.x; - } - if (thing === 'y position') { - return target.y; - } - if (thing === 'direction') { - return target.direction; - } - if (thing === 'costume num') { - return (target.currentCostume + 1); - } - if (thing === 'costume name') { - return target.getCostumes()[target.currentCostume].name; - } - if (thing === 'size') { - return target.size; - } - if (thing === 'volume') { - return target.volume; - } - // this should never happen - return ''; - }; - - class ClonesPlus { - getInfo() { - return { - id: 'lmsclonesplus', - name: 'Clones+', - color1: '#FFAB19', - color2: '#EC9C13', - color3: '#CF8B17', - menuIconURI: menuIconURI, - blocks: [ - { - opcode: 'whenCloneStartsWithVar', - blockType: Scratch.BlockType.HAT, - text: 'when I start as a clone with [INPUTA] set to [INPUTB]', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'createCloneWithVar', - blockType: Scratch.BlockType.COMMAND, - text: 'create clone with [INPUTA] set to [INPUTB]', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - - '---', - - { - opcode: 'touchingCloneWithVar', - blockType: Scratch.BlockType.BOOLEAN, - text: 'touching clone with [INPUTA] set to [INPUTB]?', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'touchingMainSprite', - blockType: Scratch.BlockType.BOOLEAN, - text: 'touching main sprite?', - filter: [Scratch.TargetType.SPRITE] - }, - - '---', - - { - opcode: 'setVariableOfClone', - blockType: Scratch.BlockType.COMMAND, - text: 'set variable [INPUTA] to [INPUTB] for clones with [INPUTC] set to [INPUTD]', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '0' - }, - INPUTC: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTD: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'getVariableOfClone', - blockType: Scratch.BlockType.REPORTER, - text: 'variable [INPUTA] of clone with [INPUTB] set to [INPUTC]', - filter: [Scratch.TargetType.SPRITE], - disableMonitor: true, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTC: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'setVariableOfMainSprite', - blockType: Scratch.BlockType.COMMAND, - text: 'set variable [INPUTA] to [INPUTB] for main sprite', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'getVariableOfMainSprite', - blockType: Scratch.BlockType.REPORTER, - text: 'variable [INPUT] of main sprite', - filter: [Scratch.TargetType.SPRITE], - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - } - } - }, - - '---', - - { - opcode: 'cloneExists', - blockType: Scratch.BlockType.BOOLEAN, - text: 'clone with [INPUTA] set to [INPUTB] exists?', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'getThingOfClone', - blockType: Scratch.BlockType.REPORTER, - text: '[INPUTA] of clone with [INPUTB] set to [INPUTC]', - filter: [Scratch.TargetType.SPRITE], - disableMonitor: true, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'x position', - menu: 'thingOfMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTC: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'getThingOfMainSprite', - blockType: Scratch.BlockType.REPORTER, - text: '[INPUT] of main sprite', - filter: [Scratch.TargetType.SPRITE], - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'x position', - menu: 'thingOfMenu' - } - } - }, - - '---', - - { - opcode: 'stopScriptsInSprite', - blockType: Scratch.BlockType.COMMAND, - text: 'stop scripts in [INPUT]', - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } - }, - { - opcode: 'stopScriptsInClone', - blockType: Scratch.BlockType.COMMAND, - text: 'stop scripts in clones with [INPUTA] set to [INPUTB]', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'stopScriptsInMainSprite', - blockType: Scratch.BlockType.COMMAND, - text: 'stop scripts in main sprite', - filter: [Scratch.TargetType.SPRITE] - }, - - '---', - - { - opcode: 'deleteClonesInSprite', - blockType: Scratch.BlockType.COMMAND, - text: 'delete clones in [INPUT]', - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } - }, - { - opcode: 'deleteCloneWithVar', - blockType: Scratch.BlockType.COMMAND, - text: 'delete clones with [INPUTA] set to [INPUTB]', - filter: [Scratch.TargetType.SPRITE], - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - menu: 'variablesMenu' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - - '---', - - { - opcode: 'isClone', - blockType: Scratch.BlockType.BOOLEAN, - text: 'is clone?', - filter: [Scratch.TargetType.SPRITE] - }, - - '---', - - { - opcode: 'cloneCount', - blockType: Scratch.BlockType.REPORTER, - text: 'clone count' - }, - { - opcode: 'spriteCloneCount', - blockType: Scratch.BlockType.REPORTER, - text: 'clone count of [INPUT]', - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } - } - ], - menus: { - spriteMenu: { - acceptReporters: true, - items: 'getSprites' - }, - // menus use acceptReporters: false for Scratch parity - variablesMenu: { - acceptReporters: false, - items: 'getVariables' - }, - thingOfMenu: { - acceptReporters: false, - items: [ - { - text: 'x position', - value: 'x position' - }, - { - text: 'y position', - value: 'y position' - }, - { - text: 'direction', - value: 'direction' - }, - { - text: 'costume #', - value: 'costume num' - }, - { - text: 'costume name', - value: 'costume name' - }, - { - text: 'size', - value: 'size' - }, - { - text: 'volume', - value: 'volume' - }, - ] - } - } - }; - } - - whenCloneStartsWithVar(args, util) { - // TODO: this is really not ideal. this should be an event-based hat ideally, but we don't have a good - // way to do that right now... - if (util.target.isOriginal) { - return false; - } - const variable = util.target.lookupVariableById(args.INPUTA); - const expectedValue = args.INPUTB; - if (variable) { - return Scratch.Cast.compare(variable.value, expectedValue) === 0; - } - return false; - } - - createCloneWithVar(args, util) { - // @ts-expect-error - not typed yet - Scratch.vm.runtime.ext_scratch3_control._createClone(util.target.sprite.name, util.target); - const clones = util.target.sprite.clones; - const cloneNum = clones.length - 1; - const cloneVariable = clones[cloneNum].lookupVariableById(args.INPUTA); - if (cloneVariable) { - cloneVariable.value = args.INPUTB; - } - } - - touchingCloneWithVar(args, util) { - const drawableCandidates = util.target.sprite.clones - .filter(clone => { - const variable = clone.lookupVariableById(args.INPUTA); - return variable && Scratch.Cast.compare(variable.value, args.INPUTB) === 0; - }) - .map(clone => clone.drawableID); - if (drawableCandidates.length === 0) { - return false; - } - return Scratch.vm.renderer.isTouchingDrawables(util.target.drawableID, drawableCandidates); - } - - touchingMainSprite(args, util) { - if (util.target.isOriginal) { - return false; - } - const main = util.target.sprite.clones[0]; - const drawableCandidates = [main.drawableID]; - return Scratch.vm.renderer.isTouchingDrawables(util.target.drawableID, drawableCandidates); - } - - setVariableOfClone(args, util) { - const newVariableValue = args.INPUTB; - const expectedVarValue = args.INPUTD; - const clones = util.target.sprite.clones; - for (let index = 1; index < clones.length; index++) { - const checkVar = clones[index].lookupVariableById(args.INPUTC); - if (checkVar && Scratch.Cast.compare(checkVar.value, expectedVarValue) === 0) { - const editVar = clones[index].lookupVariableById(args.INPUTA); - if (editVar) { - editVar.value = newVariableValue; - } - } - } - } - - getVariableOfClone(args, util) { - const clone = this.getCloneFromVariable(args.INPUTB, args.INPUTC, util.target.sprite.clones); - if (!clone) { - return ''; - } - // guaranteed to exist by getCloneFromVariable - const cloneVar = clone.lookupVariableById(args.INPUTA); - return cloneVar.value; - } - - setVariableOfMainSprite(args, util) { - const main = util.target.sprite.clones[0]; - const variableObj = main.lookupVariableById(args.INPUTA); - if (variableObj) { - variableObj.value = args.INPUTB; - } - } - - getVariableOfMainSprite(args, util) { - const main = util.target.sprite.clones[0]; - const variableObj = main.lookupVariableById(args.INPUT); - if (variableObj) { - return variableObj.value; - } - return ''; - } - - cloneExists(args, util) { - const clone = this.getCloneFromVariable(args.INPUTA, args.INPUTB, util.target.sprite.clones); - return !!clone; - } - - getThingOfClone(args, util) { - const clone = this.getCloneFromVariable(args.INPUTB, args.INPUTC, util.target.sprite.clones); - return getThingOfTarget(clone, args.INPUTA); - } - - getThingOfMainSprite(args, util) { - const main = util.target.sprite.clones[0]; - return getThingOfTarget(main, args.INPUT); - } - - stopScriptsInSprite(args) { - const targetObj = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); - if (targetObj) { - Scratch.vm.runtime.stopForTarget(targetObj); - } - } - - stopScriptsInMainSprite(args, util) { - Scratch.vm.runtime.stopForTarget(util.target.sprite.clones[0]); - } - - stopScriptsInClone(args, util) { - const clones = util.target.sprite.clones; - let expectedValue = args.INPUTB; - for (let index = 1; index < clones.length; index++) { - const cloneVariable = clones[index].lookupVariableById(args.INPUTA); - if (cloneVariable && Scratch.Cast.compare(cloneVariable.value, expectedValue) === 0) { - Scratch.vm.runtime.stopForTarget(clones[index]); - } - } - } - - deleteClonesInSprite(args, util) { - const target = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); - if (!target) { - return; - } - const clones = target.sprite.clones; - for (let index = clones.length - 1; index > 0; index--) { - Scratch.vm.runtime.disposeTarget(clones[index]); - } - } - - deleteCloneWithVar(args, util) { - const clones = util.target.sprite.clones; - const expectedValue = args.INPUTB; - for (let index = clones.length - 1; index > 0; index--) { - const cloneVar = clones[index].lookupVariableById(args.INPUTA); - if (cloneVar && Scratch.Cast.compare(cloneVar.value, expectedValue) === 0) { - Scratch.vm.runtime.disposeTarget(clones[index]); - } - } - } - - isClone(args, util) { - return !util.target.isOriginal; - } - - cloneCount(args, util) { - return Scratch.vm.runtime._cloneCounter; - } - - spriteCloneCount(args, util) { - const target = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); - if (target) { - return (target.sprite.clones.length - 1); - } - return 0; - } - - /** - * @param {string} variableId - * @param {unknown} expectedValue - * @param {VM.Target[]} clones - * @returns {VM.Target|null} - */ - getCloneFromVariable(variableId, expectedValue, clones) { - for (let index = 1; index < clones.length; index++) { - const cloneVar = clones[index].lookupVariableById(variableId); - if (cloneVar && Scratch.Cast.compare(cloneVar.value, expectedValue) === 0) { - return clones[index]; - } - } - return null; - } - - getSprites() { - let spriteNames = []; - const targets = Scratch.vm.runtime.targets; - const myself = Scratch.vm.runtime.getEditingTarget().sprite.name; - for (let index = 1; index < targets.length; index++) { - const curTarget = targets[index].sprite; - let display = curTarget.name; - if (myself === curTarget.name) { - display = 'myself'; - } - if (targets[index].isOriginal) { - const jsonOBJ = { - text: display, - value: curTarget.name - }; - spriteNames.push(jsonOBJ); - } - } - if (spriteNames.length > 0) { - return spriteNames; - } else { - return [{ text: "", value: 0 }]; //this should never happen but it's a failsafe - } - } - - getSpriteObj(name) { //This is unused but I'm leaving it in for potential future blocks - const spriteObj = Scratch.vm.runtime.getSpriteTargetByName(name); - return JSON.stringify(spriteObj); - } - - getVariables() { - // @ts-expect-error - Blockly not typed yet - // eslint-disable-next-line no-undef - const variables = typeof Blockly === 'undefined' ? [] : Blockly.getMainWorkspace() - .getVariableMap() - .getVariablesOfType('') - .filter(model => model.isLocal) - .map(model => ({ - text: model.name, - value: model.getId() - })); - if (variables.length > 0) { - return variables; - } else { - return [{ text: "", value: "" }]; - } - } - } - Scratch.extensions.register(new ClonesPlus()); -})(Scratch); +// Name: Clones Plus +// ID: lmsclonesplus +// Description: Expansion of Scratch's clone features. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const menuIconURI = + "data:image/png;base64,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"; + + /** + * @param {VM.Target|null} target + * @param {string|unknown} thing + * @returns {string|number|boolean} + */ + const getThingOfTarget = (target, thing) => { + if (!target) { + return ""; + } + if (thing === "x position") { + return target.x; + } + if (thing === "y position") { + return target.y; + } + if (thing === "direction") { + return target.direction; + } + if (thing === "costume num") { + return target.currentCostume + 1; + } + if (thing === "costume name") { + return target.getCostumes()[target.currentCostume].name; + } + if (thing === "size") { + return target.size; + } + if (thing === "volume") { + return target.volume; + } + // this should never happen + return ""; + }; + + class ClonesPlus { + getInfo() { + return { + id: "lmsclonesplus", + name: "Clones+", + color1: "#FFAB19", + color2: "#EC9C13", + color3: "#CF8B17", + menuIconURI: menuIconURI, + blocks: [ + { + opcode: "whenCloneStartsWithVar", + blockType: Scratch.BlockType.HAT, + text: "when I start as a clone with [INPUTA] set to [INPUTB]", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "createCloneWithVar", + blockType: Scratch.BlockType.COMMAND, + text: "create clone with [INPUTA] set to [INPUTB]", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + + "---", + + { + opcode: "touchingCloneWithVar", + blockType: Scratch.BlockType.BOOLEAN, + text: "touching clone with [INPUTA] set to [INPUTB]?", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "touchingMainSprite", + blockType: Scratch.BlockType.BOOLEAN, + text: "touching main sprite?", + filter: [Scratch.TargetType.SPRITE], + }, + + "---", + + { + opcode: "setVariableOfClone", + blockType: Scratch.BlockType.COMMAND, + text: "set variable [INPUTA] to [INPUTB] for clones with [INPUTC] set to [INPUTD]", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "0", + }, + INPUTC: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTD: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "getVariableOfClone", + blockType: Scratch.BlockType.REPORTER, + text: "variable [INPUTA] of clone with [INPUTB] set to [INPUTC]", + filter: [Scratch.TargetType.SPRITE], + disableMonitor: true, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTC: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "setVariableOfMainSprite", + blockType: Scratch.BlockType.COMMAND, + text: "set variable [INPUTA] to [INPUTB] for main sprite", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "getVariableOfMainSprite", + blockType: Scratch.BlockType.REPORTER, + text: "variable [INPUT] of main sprite", + filter: [Scratch.TargetType.SPRITE], + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + }, + }, + + "---", + + { + opcode: "cloneExists", + blockType: Scratch.BlockType.BOOLEAN, + text: "clone with [INPUTA] set to [INPUTB] exists?", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "getThingOfClone", + blockType: Scratch.BlockType.REPORTER, + text: "[INPUTA] of clone with [INPUTB] set to [INPUTC]", + filter: [Scratch.TargetType.SPRITE], + disableMonitor: true, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x position", + menu: "thingOfMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTC: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "getThingOfMainSprite", + blockType: Scratch.BlockType.REPORTER, + text: "[INPUT] of main sprite", + filter: [Scratch.TargetType.SPRITE], + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x position", + menu: "thingOfMenu", + }, + }, + }, + + "---", + + { + opcode: "stopScriptsInSprite", + blockType: Scratch.BlockType.COMMAND, + text: "stop scripts in [INPUT]", + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + menu: "spriteMenu", + }, + }, + }, + { + opcode: "stopScriptsInClone", + blockType: Scratch.BlockType.COMMAND, + text: "stop scripts in clones with [INPUTA] set to [INPUTB]", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "stopScriptsInMainSprite", + blockType: Scratch.BlockType.COMMAND, + text: "stop scripts in main sprite", + filter: [Scratch.TargetType.SPRITE], + }, + + "---", + + { + opcode: "deleteClonesInSprite", + blockType: Scratch.BlockType.COMMAND, + text: "delete clones in [INPUT]", + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + menu: "spriteMenu", + }, + }, + }, + { + opcode: "deleteCloneWithVar", + blockType: Scratch.BlockType.COMMAND, + text: "delete clones with [INPUTA] set to [INPUTB]", + filter: [Scratch.TargetType.SPRITE], + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + menu: "variablesMenu", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + + "---", + + { + opcode: "isClone", + blockType: Scratch.BlockType.BOOLEAN, + text: "is clone?", + filter: [Scratch.TargetType.SPRITE], + }, + + "---", + + { + opcode: "cloneCount", + blockType: Scratch.BlockType.REPORTER, + text: "clone count", + }, + { + opcode: "spriteCloneCount", + blockType: Scratch.BlockType.REPORTER, + text: "clone count of [INPUT]", + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + menu: "spriteMenu", + }, + }, + }, + ], + menus: { + spriteMenu: { + acceptReporters: true, + items: "getSprites", + }, + // menus use acceptReporters: false for Scratch parity + variablesMenu: { + acceptReporters: false, + items: "getVariables", + }, + thingOfMenu: { + acceptReporters: false, + items: [ + { + text: "x position", + value: "x position", + }, + { + text: "y position", + value: "y position", + }, + { + text: "direction", + value: "direction", + }, + { + text: "costume #", + value: "costume num", + }, + { + text: "costume name", + value: "costume name", + }, + { + text: "size", + value: "size", + }, + { + text: "volume", + value: "volume", + }, + ], + }, + }, + }; + } + + whenCloneStartsWithVar(args, util) { + // TODO: this is really not ideal. this should be an event-based hat ideally, but we don't have a good + // way to do that right now... + if (util.target.isOriginal) { + return false; + } + const variable = util.target.lookupVariableById(args.INPUTA); + const expectedValue = args.INPUTB; + if (variable) { + return Scratch.Cast.compare(variable.value, expectedValue) === 0; + } + return false; + } + + createCloneWithVar(args, util) { + // @ts-expect-error - not typed yet + Scratch.vm.runtime.ext_scratch3_control._createClone( + util.target.sprite.name, + util.target + ); + const clones = util.target.sprite.clones; + const cloneNum = clones.length - 1; + const cloneVariable = clones[cloneNum].lookupVariableById(args.INPUTA); + if (cloneVariable) { + cloneVariable.value = args.INPUTB; + } + } + + touchingCloneWithVar(args, util) { + const drawableCandidates = util.target.sprite.clones + .filter((clone) => { + const variable = clone.lookupVariableById(args.INPUTA); + return ( + variable && Scratch.Cast.compare(variable.value, args.INPUTB) === 0 + ); + }) + .map((clone) => clone.drawableID); + if (drawableCandidates.length === 0) { + return false; + } + return Scratch.vm.renderer.isTouchingDrawables( + util.target.drawableID, + drawableCandidates + ); + } + + touchingMainSprite(args, util) { + if (util.target.isOriginal) { + return false; + } + const main = util.target.sprite.clones[0]; + const drawableCandidates = [main.drawableID]; + return Scratch.vm.renderer.isTouchingDrawables( + util.target.drawableID, + drawableCandidates + ); + } + + setVariableOfClone(args, util) { + const newVariableValue = args.INPUTB; + const expectedVarValue = args.INPUTD; + const clones = util.target.sprite.clones; + for (let index = 1; index < clones.length; index++) { + const checkVar = clones[index].lookupVariableById(args.INPUTC); + if ( + checkVar && + Scratch.Cast.compare(checkVar.value, expectedVarValue) === 0 + ) { + const editVar = clones[index].lookupVariableById(args.INPUTA); + if (editVar) { + editVar.value = newVariableValue; + } + } + } + } + + getVariableOfClone(args, util) { + const clone = this.getCloneFromVariable( + args.INPUTB, + args.INPUTC, + util.target.sprite.clones + ); + if (!clone) { + return ""; + } + // guaranteed to exist by getCloneFromVariable + const cloneVar = clone.lookupVariableById(args.INPUTA); + return cloneVar.value; + } + + setVariableOfMainSprite(args, util) { + const main = util.target.sprite.clones[0]; + const variableObj = main.lookupVariableById(args.INPUTA); + if (variableObj) { + variableObj.value = args.INPUTB; + } + } + + getVariableOfMainSprite(args, util) { + const main = util.target.sprite.clones[0]; + const variableObj = main.lookupVariableById(args.INPUT); + if (variableObj) { + return variableObj.value; + } + return ""; + } + + cloneExists(args, util) { + const clone = this.getCloneFromVariable( + args.INPUTA, + args.INPUTB, + util.target.sprite.clones + ); + return !!clone; + } + + getThingOfClone(args, util) { + const clone = this.getCloneFromVariable( + args.INPUTB, + args.INPUTC, + util.target.sprite.clones + ); + return getThingOfTarget(clone, args.INPUTA); + } + + getThingOfMainSprite(args, util) { + const main = util.target.sprite.clones[0]; + return getThingOfTarget(main, args.INPUT); + } + + stopScriptsInSprite(args) { + const targetObj = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); + if (targetObj) { + Scratch.vm.runtime.stopForTarget(targetObj); + } + } + + stopScriptsInMainSprite(args, util) { + Scratch.vm.runtime.stopForTarget(util.target.sprite.clones[0]); + } + + stopScriptsInClone(args, util) { + const clones = util.target.sprite.clones; + let expectedValue = args.INPUTB; + for (let index = 1; index < clones.length; index++) { + const cloneVariable = clones[index].lookupVariableById(args.INPUTA); + if ( + cloneVariable && + Scratch.Cast.compare(cloneVariable.value, expectedValue) === 0 + ) { + Scratch.vm.runtime.stopForTarget(clones[index]); + } + } + } + + deleteClonesInSprite(args, util) { + const target = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); + if (!target) { + return; + } + const clones = target.sprite.clones; + for (let index = clones.length - 1; index > 0; index--) { + Scratch.vm.runtime.disposeTarget(clones[index]); + } + } + + deleteCloneWithVar(args, util) { + const clones = util.target.sprite.clones; + const expectedValue = args.INPUTB; + for (let index = clones.length - 1; index > 0; index--) { + const cloneVar = clones[index].lookupVariableById(args.INPUTA); + if ( + cloneVar && + Scratch.Cast.compare(cloneVar.value, expectedValue) === 0 + ) { + Scratch.vm.runtime.disposeTarget(clones[index]); + } + } + } + + isClone(args, util) { + return !util.target.isOriginal; + } + + cloneCount(args, util) { + return Scratch.vm.runtime._cloneCounter; + } + + spriteCloneCount(args, util) { + const target = Scratch.vm.runtime.getSpriteTargetByName(args.INPUT); + if (target) { + return target.sprite.clones.length - 1; + } + return 0; + } + + /** + * @param {string} variableId + * @param {unknown} expectedValue + * @param {VM.Target[]} clones + * @returns {VM.Target|null} + */ + getCloneFromVariable(variableId, expectedValue, clones) { + for (let index = 1; index < clones.length; index++) { + const cloneVar = clones[index].lookupVariableById(variableId); + if ( + cloneVar && + Scratch.Cast.compare(cloneVar.value, expectedValue) === 0 + ) { + return clones[index]; + } + } + return null; + } + + getSprites() { + let spriteNames = []; + const targets = Scratch.vm.runtime.targets; + const myself = Scratch.vm.runtime.getEditingTarget().sprite.name; + for (let index = 1; index < targets.length; index++) { + const curTarget = targets[index].sprite; + let display = curTarget.name; + if (myself === curTarget.name) { + display = "myself"; + } + if (targets[index].isOriginal) { + const jsonOBJ = { + text: display, + value: curTarget.name, + }; + spriteNames.push(jsonOBJ); + } + } + if (spriteNames.length > 0) { + return spriteNames; + } else { + return [{ text: "", value: 0 }]; //this should never happen but it's a failsafe + } + } + + getSpriteObj(name) { + //This is unused but I'm leaving it in for potential future blocks + const spriteObj = Scratch.vm.runtime.getSpriteTargetByName(name); + return JSON.stringify(spriteObj); + } + + getVariables() { + // @ts-expect-error - Blockly not typed yet + // eslint-disable-next-line no-undef + const variables = + typeof Blockly === "undefined" + ? [] + : Blockly.getMainWorkspace() + .getVariableMap() + .getVariablesOfType("") + .filter((model) => model.isLocal) + .map((model) => ({ + text: model.name, + value: model.getId(), + })); + if (variables.length > 0) { + return variables; + } else { + return [{ text: "", value: "" }]; + } + } + } + Scratch.extensions.register(new ClonesPlus()); +})(Scratch); diff --git a/extensions/Lily/CommentBlocks.js b/extensions/Lily/CommentBlocks.js index bd831f0b4f..d2a25c4851 100644 --- a/extensions/Lily/CommentBlocks.js +++ b/extensions/Lily/CommentBlocks.js @@ -1,84 +1,89 @@ +// Name: Comment Blocks +// ID: lmscomments +// Description: Annotate your scripts. +// By: LilyMakesThings + (function (Scratch) { - 'use strict'; + "use strict"; class CommentBlocks { getInfo() { return { - id: 'lmscomments', - name: 'Comment Blocks', - color1: '#e4db8c', - color2: '#c6be79', - color3: '#a8a167', + id: "lmscomments", + name: "Comment Blocks", + color1: "#e4db8c", + color2: "#c6be79", + color3: "#a8a167", blocks: [ { - opcode: 'commentHat', + opcode: "commentHat", blockType: Scratch.BlockType.HAT, - text: '// [COMMENT]', + text: "// [COMMENT]", isEdgeActivated: false, arguments: { COMMENT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' - } - } + defaultValue: "comment", + }, + }, }, { - opcode: 'commentCommand', + opcode: "commentCommand", blockType: Scratch.BlockType.COMMAND, - text: '// [COMMENT]', + text: "// [COMMENT]", arguments: { COMMENT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' - } - } + defaultValue: "comment", + }, + }, }, { - opcode: 'commentReporter', + opcode: "commentReporter", blockType: Scratch.BlockType.REPORTER, - text: '[INPUT] // [COMMENT]', + text: "[INPUT] // [COMMENT]", arguments: { COMMENT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' + defaultValue: "comment", }, INPUT: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'commentBoolean', + opcode: "commentBoolean", blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUT] // [COMMENT]', + text: "[INPUT] // [COMMENT]", arguments: { COMMENT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' + defaultValue: "comment", }, INPUT: { - type: Scratch.ArgumentType.BOOLEAN - } - } - } - ] + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + ], }; } - commentHat () { + commentHat() { // no-op } - commentCommand () { + commentCommand() { // no-op } - commentReporter (args) { + commentReporter(args) { return args.INPUT; } - commentBoolean (args) { + commentBoolean(args) { return args.INPUT || false; } } diff --git a/extensions/Lily/LooksPlus.js b/extensions/Lily/LooksPlus.js index 6c4c193c99..49e17834f9 100644 --- a/extensions/Lily/LooksPlus.js +++ b/extensions/Lily/LooksPlus.js @@ -1,11 +1,19 @@ +// Name: Looks Plus +// ID: lmsLooksPlus +// Description: Expands upon the looks category, allowing you to show/hide, get costume data and edit SVG skins on sprites. +// By: LilyMakesThings + (function (Scratch) { - 'use strict'; + "use strict"; - const menuIconURI = 'data:image/png;base64,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'; + const menuIconURI = + "data:image/png;base64,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"; const requireNonPackagedRuntime = (blockName) => { if (Scratch.vm.runtime.isPackaged) { - alert(`To use the Looks+ ${blockName} block, the creator of the packaged project must uncheck "Remove raw asset data after loading to save RAM" under advanced settings in the packager.`); + alert( + `To use the Looks+ ${blockName} block, the creator of the packaged project must uncheck "Remove raw asset data after loading to save RAM" under advanced settings in the packager.` + ); return false; } return true; @@ -27,169 +35,169 @@ class LooksPlus { getInfo() { return { - id: 'lmsLooksPlus', - name: 'Looks+', - color1: '#9966FF', - color2: '#855CD6', - color3: '#774DCB', + id: "lmsLooksPlus", + name: "Looks+", + color1: "#9966FF", + color2: "#855CD6", + color3: "#774DCB", menuIconURI: menuIconURI, blocks: [ { - opcode: 'showSprite', + opcode: "showSprite", blockType: Scratch.BlockType.COMMAND, - text: 'show [TARGET]', + text: "show [TARGET]", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, { - opcode: 'hideSprite', + opcode: "hideSprite", blockType: Scratch.BlockType.COMMAND, - text: 'hide [TARGET]', + text: "hide [TARGET]", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, { - opcode: 'spriteVisible', + opcode: "spriteVisible", blockType: Scratch.BlockType.BOOLEAN, - text: '[TARGET] visible?', + text: "[TARGET] visible?", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, - '---', + "---", { - opcode: 'setLayerTo', + opcode: "setLayerTo", blockType: Scratch.BlockType.COMMAND, - text: 'set layer # of [TARGET] to [LAYER]', + text: "set layer # of [TARGET] to [LAYER]", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' + menu: "spriteMenu", }, LAYER: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - } - } + defaultValue: "1", + }, + }, }, { - opcode: 'spriteLayerNumber', + opcode: "spriteLayerNumber", blockType: Scratch.BlockType.REPORTER, - text: 'layer # of [TARGET]', + text: "layer # of [TARGET]", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, { - opcode: 'effectValue', + opcode: "effectValue", blockType: Scratch.BlockType.REPORTER, - text: '[EFFECT] effect of [TARGET]', + text: "[EFFECT] effect of [TARGET]", arguments: { EFFECT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'color', - menu: 'effectMenu' + defaultValue: "color", + menu: "effectMenu", }, TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, - '---', + "---", { - opcode: 'targetCostumeNumber', + opcode: "targetCostumeNumber", blockType: Scratch.BlockType.REPORTER, - text: '# of costumes in [TARGET]', + text: "# of costumes in [TARGET]", arguments: { TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, { - opcode: 'costumeAttribute', + opcode: "costumeAttribute", blockType: Scratch.BlockType.REPORTER, - text: '[ATTRIBUTE] of [COSTUME]', + text: "[ATTRIBUTE] of [COSTUME]", arguments: { ATTRIBUTE: { type: Scratch.ArgumentType.STRING, - menu: 'costumeAttribute' + menu: "costumeAttribute", }, COSTUME: { - type: Scratch.ArgumentType.COSTUME - } - } + type: Scratch.ArgumentType.COSTUME, + }, + }, }, - '---', + "---", { - opcode: 'snapshotStage', + opcode: "snapshotStage", blockType: Scratch.BlockType.REPORTER, - text: 'snapshot stage', - disableMonitor: true + text: "snapshot stage", + disableMonitor: true, }, - '---', + "---", { - opcode: 'replaceCostumeContent', + opcode: "replaceCostumeContent", blockType: Scratch.BlockType.COMMAND, - text: 'set [TYPE] for [COSTUME] to [CONTENT]', + text: "set [TYPE] for [COSTUME] to [CONTENT]", arguments: { TYPE: { type: Scratch.ArgumentType.STRING, - menu: 'SVGPNG', - defaultValue: 'SVG' + menu: "SVGPNG", + defaultValue: "SVG", }, COSTUME: { - type: Scratch.ArgumentType.COSTUME + type: Scratch.ArgumentType.COSTUME, }, CONTENT: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'restoreCostumeContent', + opcode: "restoreCostumeContent", blockType: Scratch.BlockType.COMMAND, - text: 'restore content for [COSTUME]', + text: "restore content for [COSTUME]", arguments: { COSTUME: { - type: Scratch.ArgumentType.COSTUME - } - } + type: Scratch.ArgumentType.COSTUME, + }, + }, }, { - opcode: 'costumeContent', + opcode: "costumeContent", blockType: Scratch.BlockType.REPORTER, - text: '[CONTENT] of costume # [COSTUME] of [TARGET]', + text: "[CONTENT] of costume # [COSTUME] of [TARGET]", arguments: { CONTENT: { type: Scratch.ArgumentType.STRING, - menu: 'contentType', - defaultValue: 'content' + menu: "contentType", + defaultValue: "content", }, COSTUME: { type: Scratch.ArgumentType.NUMBER, @@ -197,43 +205,43 @@ }, TARGET: { type: Scratch.ArgumentType.STRING, - menu: 'spriteMenu' - } - } + menu: "spriteMenu", + }, + }, }, - '---', + "---", { - opcode: 'replaceColors', + opcode: "replaceColors", blockType: Scratch.BlockType.REPORTER, - text: 'replace [COLOR1] with [COLOR2] in [SVG]', + text: "replace [COLOR1] with [COLOR2] in [SVG]", arguments: { COLOR1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#FCB1E3' + defaultValue: "#FCB1E3", }, COLOR2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#8ECAFF' + defaultValue: "#8ECAFF", }, SVG: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'colorHex', + opcode: "colorHex", blockType: Scratch.BlockType.REPORTER, - text: 'hex of [COLOR]', + text: "hex of [COLOR]", arguments: { COLOR: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#FFD983' - } - } - } + defaultValue: "#FFD983", + }, + }, + }, ], menus: { effectMenu: { @@ -241,87 +249,87 @@ acceptReporters: false, items: [ { - text: 'color', - value: 'color' + text: "color", + value: "color", }, { - text: 'fisheye', - value: 'fisheye' + text: "fisheye", + value: "fisheye", }, { - text: 'whirl', - value: 'whirl' + text: "whirl", + value: "whirl", }, { - text: 'pixelate', - value: 'pixelate' + text: "pixelate", + value: "pixelate", }, { - text: 'mosaic', - value: 'mosaic' + text: "mosaic", + value: "mosaic", }, { - text: 'brightness', - value: 'brightness' + text: "brightness", + value: "brightness", }, { - text: 'ghost', - value: 'ghost' - } - ] + text: "ghost", + value: "ghost", + }, + ], }, costumeAttribute: { acceptReporters: false, items: [ { - text: 'width', - value: 'width' + text: "width", + value: "width", }, { - text: 'height', - value: 'height' + text: "height", + value: "height", }, { - text: 'format', - value: 'format' + text: "format", + value: "format", }, { - text: 'rotation center x', - value: 'rotationCenterX' + text: "rotation center x", + value: "rotationCenterX", }, { - text: 'rotation center y', - value: 'rotationCenterY' - } - ] + text: "rotation center y", + value: "rotationCenterY", + }, + ], }, contentType: { acceptReporters: false, items: [ { - text: 'content', - value: 'content' + text: "content", + value: "content", }, { - text: 'dataURI', - value: 'dataURI' - } - ] + text: "dataURI", + value: "dataURI", + }, + ], }, SVGPNG: { acceptReporters: false, items: [ { - text: 'SVG', - value: 'SVG' - } - ] + text: "SVG", + value: "SVG", + }, + ], }, spriteMenu: { acceptReporters: true, - items: 'getSprites' - } - } + items: "getSprites", + }, + }, }; } @@ -354,8 +362,8 @@ } const drawableID = target.drawableID; const layerOrder = target.getLayerOrder(); - const newLayer = (args.LAYER - layerOrder); - target.renderer.setDrawableOrder(drawableID, newLayer, 'sprite', true); + const newLayer = args.LAYER - layerOrder; + target.renderer.setDrawableOrder(drawableID, newLayer, "sprite", true); } spriteLayerNumber(args, util) { @@ -384,26 +392,26 @@ const costumeIndex = this.getCostumeInput(args.COSTUME, util.target); const costume = util.target.sprite.costumes[costumeIndex]; if (!costume) { - console.error('Costume doesn\'t exist'); + console.error("Costume doesn't exist"); return 0; } const attribute = args.ATTRIBUTE; - if (attribute === 'width') { + if (attribute === "width") { return Math.ceil(Scratch.Cast.toNumber(costume.size[0])); - } else if (attribute === 'height') { + } else if (attribute === "height") { return Math.ceil(Scratch.Cast.toNumber(costume.size[1])); - } else if (attribute === 'format') { - if (!requireNonPackagedRuntime('costume format')) { - return 'unknown'; + } else if (attribute === "format") { + if (!requireNonPackagedRuntime("costume format")) { + return "unknown"; } return costume.asset.assetType.runtimeFormat; - } else if (attribute === 'rotationCenterX') { + } else if (attribute === "rotationCenterX") { return costume.rotationCenterX; - } else if (attribute === 'rotationCenterY') { + } else if (attribute === "rotationCenterY") { return costume.rotationCenterY; } else { - return ''; + return ""; } } @@ -416,8 +424,8 @@ } snapshotStage(args, util) { - return new Promise(resolve => { - Scratch.vm.runtime.renderer.requestSnapshot(uri => { + return new Promise((resolve) => { + Scratch.vm.runtime.renderer.requestSnapshot((uri) => { resolve(uri); }); }); @@ -427,24 +435,27 @@ const costumeIndex = this.getCostumeInput(args.COSTUME, util.target); const costume = util.target.sprite.costumes[costumeIndex]; if (!costume) { - console.error('Costume doesn\'t exist'); + console.error("Costume doesn't exist"); return; } //This is here to ensure no changes are made to bitmap costumes, as changes are irreversible //Check will be removed when it's possible to edit bitmap skins const format = costume.asset.assetType.runtimeFormat; - if (format !== 'svg') { - console.error('Costume is not vector'); + if (format !== "svg") { + console.error("Costume is not vector"); return; } const contentType = args.TYPE; const content = args.CONTENT; - if (contentType === 'SVG') { - Scratch.vm.runtime.renderer.updateSVGSkin(costume.skinId, Scratch.Cast.toString(content)); + if (contentType === "SVG") { + Scratch.vm.runtime.renderer.updateSVGSkin( + costume.skinId, + Scratch.Cast.toString(content) + ); } else { - console.error('Options other than SVG are currently unavailable'); + console.error("Options other than SVG are currently unavailable"); return; } Scratch.vm.emitTargetsUpdate(); @@ -454,47 +465,50 @@ const costumeIndex = this.getCostumeInput(args.COSTUME, util.target); const costume = util.target.sprite.costumes[costumeIndex]; if (!costume) { - console.error('Costume doesn\'t exist'); + console.error("Costume doesn't exist"); return; } - if (!requireNonPackagedRuntime('restore costume content')) { + if (!requireNonPackagedRuntime("restore costume content")) { return; } //This is here to ensure no changes are made to bitmap costumes, as changes are irreversible //Check will be removed when it's possible to edit bitmap skins const format = costume.asset.assetType.runtimeFormat; - if (format !== 'svg') { - console.error('Costume is not vector'); + if (format !== "svg") { + console.error("Costume is not vector"); return; } const content = costume.asset.decodeText(); const rotationCenterX = costume.rotationCenterX; const rotationCenterY = costume.rotationCenterY; - util.target.renderer.updateSVGSkin(costume.skinId, content, [rotationCenterX, rotationCenterY]); + util.target.renderer.updateSVGSkin(costume.skinId, content, [ + rotationCenterX, + rotationCenterY, + ]); } costumeContent(args, util) { const target = getSpriteTargetByName(util, args.TARGET); if (!target) { - console.error('Target does not exist'); - return ''; + console.error("Target does not exist"); + return ""; } - if (!requireNonPackagedRuntime('costume content')) { - return ''; + if (!requireNonPackagedRuntime("costume content")) { + return ""; } - const costume = target.sprite.costumes[(args.COSTUME - 1)]; + const costume = target.sprite.costumes[args.COSTUME - 1]; if (!costume) { - console.error('Target costume does not exist'); - return ''; + console.error("Target costume does not exist"); + return ""; } const format = args.CONTENT; - if (format === 'content') { + if (format === "content") { return costume.asset.decodeText(); } else { return costume.asset.encodeDataURI(); @@ -506,7 +520,7 @@ const color1 = args.COLOR1; const color2 = args.COLOR2; try { - return svg.replace(new RegExp(color1, 'gi'), color2); + return svg.replace(new RegExp(color1, "gi"), color2); } catch (e) { // regex was invalid, don't replace anything return svg; @@ -518,7 +532,7 @@ } getCostumeInput(costume, target) { - if (typeof costume === 'number') { + if (typeof costume === "number") { costume = Math.round(costume - 1); if (costume === Infinity || costume === -Infinity || !costume) { costume = 0; @@ -531,8 +545,8 @@ } wrapClamp(n, min, max) { - const range = (max - min) + 1; - return n - (Math.floor((n - min) / range) * range); + const range = max - min + 1; + return n - Math.floor((n - min) / range) * range; } getSprites() { @@ -545,13 +559,13 @@ const targetName = target.getName(); if (targetName === myself) { spriteNames.unshift({ - text: 'this sprite', - value: targetName + text: "this sprite", + value: targetName, }); } else { spriteNames.push({ text: targetName, - value: targetName + value: targetName, }); } } @@ -559,7 +573,7 @@ if (spriteNames.length > 0) { return spriteNames; } else { - return [{text: "", value: 0}]; //this should never happen but it's a failsafe + return [{ text: "", value: 0 }]; //this should never happen but it's a failsafe } } } diff --git a/extensions/Lily/McUtils.js b/extensions/Lily/McUtils.js index 5303bc39e7..9e4d80758e 100644 --- a/extensions/Lily/McUtils.js +++ b/extensions/Lily/McUtils.js @@ -1,81 +1,86 @@ +// Name: McUtils +// ID: lmsmcutils +// Description: Helpful utilities for any fast food employee. +// By: LilyMakesThings + /*! * Credit to NexusKitten (NamelessCat) for the idea */ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; class lmsmcutils { getInfo() { return { - id: 'lmsmcutils', - name: 'McUtils', - color1: '#ec2020', - color3: '#ffe427', + id: "lmsmcutils", + name: "McUtils", + color1: "#ec2020", + color3: "#ffe427", blocks: [ { - opcode: 'managerReporter', + opcode: "managerReporter", blockType: Scratch.BlockType.REPORTER, - text: 'if [INPUTA] is manager then [INPUTB] else [INPUTC]', + text: "if [INPUTA] is manager then [INPUTB] else [INPUTC]", arguments: { INPUTA: { - type: Scratch.ArgumentType.BOOLEAN + type: Scratch.ArgumentType.BOOLEAN, }, INPUTB: { - type: Scratch.ArgumentType.STRING + type: Scratch.ArgumentType.STRING, }, INPUTC: { - type: Scratch.ArgumentType.STRING - } - } + type: Scratch.ArgumentType.STRING, + }, + }, }, { - opcode: 'icecreammachine', + opcode: "icecreammachine", blockType: Scratch.BlockType.BOOLEAN, - text: 'is ice cream machine [INPUT]', + text: "is ice cream machine [INPUT]", arguments: { INPUT: { type: Scratch.ArgumentType.STRING, - menu: 'iceCreamMenu' - } - } + menu: "iceCreamMenu", + }, + }, }, { - opcode: 'talkToManager', + opcode: "talkToManager", blockType: Scratch.BlockType.BOOLEAN, - text: 'talk to manager [INPUT]', + text: "talk to manager [INPUT]", arguments: { INPUT: { type: Scratch.ArgumentType.STRING, - } - } + }, + }, }, { - opcode: 'placeOrder', + opcode: "placeOrder", blockType: Scratch.BlockType.REPORTER, - text: 'place order [INPUT]', + text: "place order [INPUT]", arguments: { INPUT: { type: Scratch.ArgumentType.STRING, - } - } - } + }, + }, + }, ], menus: { iceCreamMenu: { acceptReporters: true, items: [ { - text: 'working', - value: 'working' + text: "working", + value: "working", }, { - text: 'broken', - value: 'broken' - } - ] - } - } + text: "broken", + value: "broken", + }, + ], + }, + }, }; } @@ -88,7 +93,7 @@ } icecreammachine(args, util) { - if (args.INPUT === 'working') { + if (args.INPUT === "working") { return false; } else { return true; @@ -100,7 +105,7 @@ } placeOrder(args, util) { - if (args.INPUT.includes('ice cream')) { + if (args.INPUT.includes("ice cream")) { return false; } else { return args.INPUT; diff --git a/extensions/Lily/MoreEvents.js b/extensions/Lily/MoreEvents.js new file mode 100644 index 0000000000..cd1f329b65 --- /dev/null +++ b/extensions/Lily/MoreEvents.js @@ -0,0 +1,595 @@ +// Name: More Events +// ID: lmsMoreEvents +// Description: Start your scripts in new ways. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const vm = Scratch.vm; + const runtime = vm.runtime; + + const stopIcon = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAQlBMVEUAAAC/UFC8Q0OzTU24SEi4SEi3SEi4R0e4SEi4SEi4SEi4SEi7SUm8SUnMTk7MT0/OT0/PT0/gVVXiVVXsWVn///+CoOd2AAAAC3RSTlMAEBMUu7zLz9D8/dIXnJwAAAABYktHRBXl2PmjAAAAxklEQVRIx+3WwRKDIBAD0JWqVEOtWv7/W3twOqKwELzW3N9wYhORMMYiztgZUZMUAKxqmh5Kno/MG256nzI59Z2mB+BWH+XzUt5RhWoyQjFZkTQFkTBFERlCnAwlDoYUgaHFblpaeL86AK0MvNjMIABmT2cGIAAWniw3ucm/k9ovduEjXzgXtUfJmtrTt9VZzYH9FSB/xvfKZMsiLFmuko61zBTfucjL9RpXf6nEU2MhPxXS86J+kORmjz6V6seViOnG8oT7ApMcjsYZwhXCAAAAAElFTkSuQmCC"; + + // Source: + // https://github.com/TurboWarp/scratch-vm/blob/develop/src/io/keyboard.js + // https://github.com/TurboWarp/scratch-blocks/blob/develop/blocks_vertical/event.js + const validKeyboardInputs = [ + // Special Inputs + { text: "space", value: "space" }, + { text: "up arrow", value: "up arrow" }, + { text: "down arrow", value: "down arrow" }, + { text: "right arrow", value: "right arrow" }, + { text: "left arrow", value: "left arrow" }, + { text: "enter", value: "enter" }, + // TW: Extra Special Inputs + { text: "backspace", value: "backspace" }, + { text: "delete", value: "delete" }, + { text: "shift", value: "shift" }, + { text: "caps lock", value: "caps lock" }, + { text: "scroll lock", value: "scroll lock" }, + { text: "control", value: "control" }, + { text: "escape", value: "escape" }, + { text: "insert", value: "insert" }, + { text: "home", value: "home" }, + { text: "end", value: "end" }, + { text: "page up", value: "page up" }, + { text: "page down", value: "page down" }, + // Letter Keyboard Inputs + { text: "a", value: "a" }, + { text: "b", value: "b" }, + { text: "c", value: "c" }, + { text: "d", value: "d" }, + { text: "e", value: "e" }, + { text: "f", value: "f" }, + { text: "g", value: "g" }, + { text: "h", value: "h" }, + { text: "i", value: "i" }, + { text: "j", value: "j" }, + { text: "k", value: "k" }, + { text: "l", value: "l" }, + { text: "m", value: "m" }, + { text: "n", value: "n" }, + { text: "o", value: "o" }, + { text: "p", value: "p" }, + { text: "q", value: "q" }, + { text: "r", value: "r" }, + { text: "s", value: "s" }, + { text: "t", value: "t" }, + { text: "u", value: "u" }, + { text: "v", value: "v" }, + { text: "w", value: "w" }, + { text: "x", value: "x" }, + { text: "y", value: "y" }, + { text: "z", value: "z" }, + // Number Keyboard Inputs + { text: "0", value: "0" }, + { text: "1", value: "1" }, + { text: "2", value: "2" }, + { text: "3", value: "3" }, + { text: "4", value: "4" }, + { text: "5", value: "5" }, + { text: "6", value: "6" }, + { text: "7", value: "7" }, + { text: "8", value: "8" }, + { text: "9", value: "9" }, + ]; + + var lastValues = {}; + var runTimer = 0; + + class MoreEvents { + constructor() { + // Stop Sign Clicked contributed by @CST1229 + runtime.shouldExecuteStopClicked = true; + runtime.on("BEFORE_EXECUTE", () => { + runTimer++; + runtime.shouldExecuteStopClicked = false; + + runtime.startHats("lmsMoreEvents_forever"); + runtime.startHats("lmsMoreEvents_whileTrueFalse"); + runtime.startHats("lmsMoreEvents_whenValueChanged"); + runtime.startHats("lmsMoreEvents_everyDuration"); + runtime.startHats("lmsMoreEvents_whileKeyPressed"); + }); + runtime.on("PROJECT_START", () => { + runTimer = 0; + }); + runtime.on("PROJECT_STOP_ALL", () => { + runTimer = 0; + if (runtime.shouldExecuteStopClicked) + queueMicrotask(() => + runtime.startHats("lmsMoreEvents_whenStopClicked") + ); + }); + runtime.on("AFTER_EXECUTE", () => { + runtime.shouldExecuteStopClicked = true; + }); + const originalGreenFlag = vm.greenFlag; + vm.greenFlag = function () { + runtime.shouldExecuteStopClicked = false; + originalGreenFlag.call(this); + }; + } + + getInfo() { + return { + id: "lmsMoreEvents", + name: "More Events", + color1: "#FFBF00", + color2: "#E6AC00", + color3: "#CC9900", + blocks: [ + { + opcode: "whenStopClicked", + blockType: Scratch.BlockType.EVENT, + text: "when [STOP] clicked", + isEdgeActivated: false, + arguments: { + STOP: { + type: Scratch.ArgumentType.IMAGE, + dataURI: stopIcon, + }, + }, + }, + { + opcode: "forever", + blockType: Scratch.BlockType.EVENT, + text: "forever", + isEdgeActivated: false, + }, + + "---", + + { + opcode: "whenTrueFalse", + blockType: Scratch.BlockType.HAT, + text: "when [CONDITION] becomes [STATE]", + isEdgeActivated: true, + arguments: { + CONDITION: { + type: Scratch.ArgumentType.BOOLEAN, + }, + STATE: { + type: Scratch.ArgumentType.STRING, + menu: "boolean", + }, + }, + }, + { + opcode: "whileTrueFalse", + blockType: Scratch.BlockType.HAT, + text: "while [CONDITION] is [STATE]", + isEdgeActivated: false, + arguments: { + CONDITION: { + type: Scratch.ArgumentType.BOOLEAN, + }, + STATE: { + type: Scratch.ArgumentType.STRING, + menu: "boolean", + }, + }, + }, + + "---", + + { + opcode: "whenValueChanged", + blockType: Scratch.BlockType.HAT, + text: "when [INPUT] is changed", + isEdgeActivated: false, + arguments: { + INPUT: { + // Intentional: + // Encourages people to place a block + // (as opposed to typing a value) + type: null, + }, + }, + }, + { + opcode: "everyDuration", + blockType: Scratch.BlockType.HAT, + text: "every [DURATION] frames", + isEdgeActivated: false, + arguments: { + DURATION: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 3, + }, + }, + }, + + "---", + + { + opcode: "whenKeyAction", + blockType: Scratch.BlockType.HAT, + text: "when [KEY_OPTION] key [ACTION]", + isEdgeActivated: true, + arguments: { + KEY_OPTION: { + type: Scratch.ArgumentType.STRING, + defaultValue: "space", + menu: "keyboardButtons", + }, + ACTION: { + type: Scratch.ArgumentType.STRING, + menu: "action", + }, + }, + }, + { + opcode: "whileKeyPressed", + blockType: Scratch.BlockType.HAT, + text: "while [KEY_OPTION] key pressed", + isEdgeActivated: false, + arguments: { + KEY_OPTION: { + type: Scratch.ArgumentType.STRING, + defaultValue: "space", + menu: "keyboardButtons", + }, + }, + }, + + "---", + + { + opcode: "broadcastToTarget", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] to [TARGET]", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + }, + hideFromPalette: true, + }, + { + opcode: "broadcastToTargetAndWait", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] to [TARGET] and wait", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + }, + hideFromPalette: true, + }, + + "---", + + { + opcode: "broadcastData", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] with data [DATA]", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + DATA: { + type: Scratch.ArgumentType.STRING, + }, + }, + hideFromPalette: true, + }, + { + opcode: "broadcastDataAndWait", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] with data [DATA] and wait", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + DATA: { + type: Scratch.ArgumentType.STRING, + }, + }, + hideFromPalette: true, + }, + { + blockType: Scratch.BlockType.XML, + xml: '', + }, + { + opcode: "receivedData", + blockType: Scratch.BlockType.REPORTER, + text: "received data", + disableMonitor: true, + allowDropAnywhere: true, + }, + + "---", + + { + opcode: "broadcastDataToTarget", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] to [TARGET] with data [DATA]", + func: "broadcastToTarget", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + DATA: { + type: Scratch.ArgumentType.STRING, + }, + }, + hideFromPalette: true, + }, + { + opcode: "broadcastDataToTargetAndWait", + blockType: Scratch.BlockType.COMMAND, + text: "broadcast [BROADCAST_OPTION] to [TARGET] with data [DATA] and wait", + func: "broadcastToTargetAndWait", + arguments: { + BROADCAST_OPTION: { + type: null, + }, + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + DATA: { + type: Scratch.ArgumentType.STRING, + }, + }, + hideFromPalette: true, + }, + { + blockType: Scratch.BlockType.XML, + xml: '', + }, + ], + menus: { + // Targets have acceptReporters: true + targetMenu: { + acceptReporters: true, + items: "_getTargets", + }, + keyboardButtons: { + acceptReporters: true, + items: validKeyboardInputs, + }, + // Attributes have acceptReporters: false + action: { + acceptReporters: false, + items: ["hit", "released"], + }, + boolean: { + acceptReporters: false, + items: ["true", "false"], + }, + state: { + acceptReporters: false, + items: ["enabled", "disabled"], + }, + }, + }; + } + + whenTrueFalse(args) { + return args.STATE === "true" ? args.CONDITION : !args.CONDITION; + } + + whileTrueFalse(args) { + return args.STATE === "true" ? args.CONDITION : !args.CONDITION; + } + + whenValueChanged(args, util) { + const blockId = util.thread.peekStack(); + if (!lastValues[blockId]) + lastValues[blockId] = Scratch.Cast.toString(args.INPUT); + if (lastValues[blockId] !== Scratch.Cast.toString(args.INPUT)) { + lastValues[blockId] = Scratch.Cast.toString(args.INPUT); + return true; + } + return false; + } + + everyDuration(args, util) { + const duration = Math.max( + Math.round(Scratch.Cast.toNumber(args.DURATION)), + 0 + ); + return !!(runTimer % duration === 0); + } + + whenKeyAction(args, util) { + const key = Scratch.Cast.toString(args.KEY_OPTION).toLowerCase(); + const pressed = util.ioQuery("keyboard", "getKeyIsDown", [key]); + return args.ACTION === "released" ? !pressed : pressed; + } + + whileKeyPressed(args, util) { + const key = Scratch.Cast.toString(args.KEY_OPTION).toLowerCase(); + return util.ioQuery("keyboard", "getKeyIsDown", [key]); + } + + broadcastToTarget(args, util) { + const broadcastOption = Scratch.Cast.toString(args.BROADCAST_OPTION); + if (!broadcastOption) return; + + const data = Scratch.Cast.toString(args.DATA); + console.log(data); + + const cloneTargets = this._getTargetFromMenu(args.TARGET).sprite.clones; + let startedThreads = []; + + for (const clone of cloneTargets) { + startedThreads = [ + ...startedThreads, + ...util.startHats( + "event_whenbroadcastreceived", + { + BROADCAST_OPTION: broadcastOption, + }, + clone + ), + ]; + if (data) { + startedThreads.forEach((thread) => (thread.receivedData = args.DATA)); + } + } + } + + broadcastToTargetAndWait(args, util) { + if (!util.stackFrame.broadcastVar) { + util.stackFrame.broadcastVar = Scratch.Cast.toString( + args.BROADCAST_OPTION + ); + } + + const spriteTarget = this._getTargetFromMenu(args.TARGET); + if (!spriteTarget) return; + const cloneTargets = spriteTarget.sprite.clones; + + const data = Scratch.Cast.toString(args.DATA); + + if (util.stackFrame.broadcastVar) { + const broadcastOption = util.stackFrame.broadcastVar; + if (!util.stackFrame.startedThreads) { + util.stackFrame.startedThreads = []; + for (const clone of cloneTargets) { + util.stackFrame.startedThreads = [ + ...util.stackFrame.startedThreads, + ...util.startHats( + "event_whenbroadcastreceived", + { + BROADCAST_OPTION: broadcastOption, + }, + clone + ), + ]; + if (data) { + util.stackFrame.startedThreads.forEach( + (thread) => (thread.receivedData = args.DATA) + ); + } + } + if (util.stackFrame.startedThreads.length === 0) { + return; + } + } + + const waiting = util.stackFrame.startedThreads.some( + (thread) => runtime.threads.indexOf(thread) !== -1 + ); + if (waiting) { + if ( + util.stackFrame.startedThreads.every((thread) => + runtime.isWaitingThread(thread) + ) + ) { + util.yieldTick(); + } else { + util.yield(); + } + } + } + } + + broadcastData(args, util) { + const broadcast = Scratch.Cast.toString(args.BROADCAST_OPTION); + if (!broadcast) return; + + const data = Scratch.Cast.toString(args.DATA); + + let threads = util.startHats("event_whenbroadcastreceived", { + BROADCAST_OPTION: broadcast, + }); + threads.forEach((thread) => (thread.receivedData = data)); + } + + broadcastDataAndWait(args, util) { + const data = Scratch.Cast.toString(args.DATA); + + if (!util.stackFrame.broadcastVar) { + util.stackFrame.broadcastVar = Scratch.Cast.toString( + args.BROADCAST_OPTION + ); + } + + if (util.stackFrame.broadcastVar) { + const broadcastOption = util.stackFrame.broadcastVar; + if (!util.stackFrame.startedThreads) { + util.stackFrame.startedThreads = util.startHats( + "event_whenbroadcastreceived", + { + BROADCAST_OPTION: broadcastOption, + } + ); + if (util.stackFrame.startedThreads.length === 0) { + return; + } else { + util.stackFrame.startedThreads.forEach( + (thread) => (thread.receivedData = data) + ); + } + } + + const waiting = util.stackFrame.startedThreads.some( + (thread) => runtime.threads.indexOf(thread) !== -1 + ); + if (waiting) { + if ( + util.stackFrame.startedThreads.every((thread) => + runtime.isWaitingThread(thread) + ) + ) { + util.yieldTick(); + } else { + util.yield(); + } + } + } + } + + receivedData(args, util) { + const received = util.thread.receivedData; + return received ? received : ""; + } + + _getTargetFromMenu(targetName) { + let target = Scratch.vm.runtime.getSpriteTargetByName(targetName); + if (targetName === "_stage_") target = runtime.getTargetForStage(); + return target; + } + + _getTargets() { + const spriteNames = [{ text: "Stage", value: "_stage_" }]; + const targets = Scratch.vm.runtime.targets; + for (let index = 1; index < targets.length; index++) { + const target = targets[index]; + if (target.isOriginal) { + const targetName = target.getName(); + spriteNames.push({ + text: targetName, + value: targetName, + }); + } + } + if (spriteNames.length > 0) { + return spriteNames; + } else { + return [{ text: "", value: 0 }]; //this should never happen but it's a failsafe + } + } + } + + Scratch.extensions.register(new MoreEvents()); +})(Scratch); diff --git a/extensions/Lily/MoreTimers.js b/extensions/Lily/MoreTimers.js index 6878a4bcde..c6417906b9 100644 --- a/extensions/Lily/MoreTimers.js +++ b/extensions/Lily/MoreTimers.js @@ -1,279 +1,287 @@ -(function (Scratch) { - 'use strict'; - - const vm = Scratch.vm; - - /** - * @typedef Timer - * @property {number} startTime - * @property {number} pauseTime - * @property {boolean} paused - */ - - /** @type {Record} */ - let timers = Object.create(null); - - /** - * @param {Timer} timer - * @return {number} - */ - const timerValue = timer => { - return ((timer.paused ? 0 : (Date.now() - timer.startTime)) + timer.pauseTime) / 1000; - }; - - class Timers { - constructor() { - vm.runtime.on('PROJECT_START', () => { - timers = Object.create(null); - }); - } - - getInfo() { - return { - id: 'lmsTimers', - name: 'More Timers', - color1: '#5cb1d6', - color2: '#428faf', - color3: '#3281a3', - blocks: [ - { - opcode: 'whenTimerOp', - blockType: Scratch.BlockType.HAT, - extensions: ['colours_sensing'], - text: 'when timer [TIMER] [OP] [NUM]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - }, - OP: { - type: Scratch.ArgumentType.STRING, - menu: 'operation' - }, - NUM: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - } - } - }, - - '---', - - { - opcode: 'startResetTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'start/reset timer [TIMER]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - } - } - }, - { - opcode: 'valueOfTimer', - blockType: Scratch.BlockType.REPORTER, - extensions: ['colours_sensing'], - text: 'timer [TIMER]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - } - } - }, - - '---', - - { - opcode: 'pauseTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'pause timer [TIMER]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - } - } - }, - { - opcode: 'resumeTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'resume timer [TIMER]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - } - } - }, - - '---', - - { - opcode: 'setTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'set timer [TIMER] to [NUM]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - }, - NUM: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - } - } - }, - { - opcode: 'changeTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'change timer [TIMER] by [NUM]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - }, - NUM: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - } - } - }, - - '---', - - { - opcode: 'removeTimer', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'remove timer [TIMER]', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - }, - } - }, - { - opcode: 'removeTimers', - blockType: Scratch.BlockType.COMMAND, - extensions: ['colours_sensing'], - text: 'remove all timers' - }, - { - opcode: 'timerExists', - blockType: Scratch.BlockType.BOOLEAN, - extensions: ['colours_sensing'], - text: 'timer [TIMER] exists?', - arguments: { - TIMER: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'timer' - }, - } - }, - { - opcode: 'listExistingTimers', - blockType: Scratch.BlockType.REPORTER, - extensions: ['colours_sensing'], - text: 'list existing timers', - disableMonitor: false - } - ], - menus: { - operation: { - // false for Scratch parity - acceptReporters: false, - items: ['>','<'] - } - } - }; - } - - whenTimerOp(args) { - if (!timers[args.TIMER]) return false; - const value = timerValue(timers[args.TIMER]); - if (args.OP === '>') return value > args.NUM; - if (args.OP === '<') return value < args.NUM; - return false; - } - - startResetTimer(args) { - timers[args.TIMER] = { - startTime: Date.now(), - pauseTime: 0, - paused: false - }; - } - - pauseTimer(args) { - const timer = timers[args.TIMER]; - if (!timer) return; - timer.pauseTime = timerValue(timer) * 1000; - timer.paused = true; - } - - resumeTimer(args) { - const timer = timers[args.TIMER]; - if (!timer) return; - if (timer.paused === false) return; - timer.paused = false; - timer.startTime = Date.now(); - } - - valueOfTimer(args) { - if (!timers[args.TIMER]) return ''; - return timerValue(timers[args.TIMER]); - } - - setTimer(args) { - timers[args.TIMER] = { - paused: false, - startTime: Date.now(), - pauseTime: Scratch.Cast.toNumber(args.NUM) * 1000 - }; - } - - changeTimer(args) { - if (!timers[args.TIMER]) this.startResetTimer(args); - timers[args.TIMER].pauseTime += Scratch.Cast.toNumber(args.NUM) * 1000; - } - - removeTimers(args) { - timers = Object.create(null); - } - - removeTimer(args) { - Reflect.deleteProperty(timers, args.TIMER); - } - - timerExists(args) { - return !!timers[args.TIMER]; - } - - listExistingTimers(args) { - return Object.keys(timers).join(','); - } - } - - // "Extension" option reimplementation by Xeltalliv - // https://github.com/Xeltalliv/extensions/blob/examples/examples/extension-colors.js - - // const cbfsb = Scratch.vm.runtime._convertBlockForScratchBlocks.bind(Scratch.vm.runtime); - // Scratch.vm.runtime._convertBlockForScratchBlocks = function(blockInfo, categoryInfo) { - // const res = cbfsb(blockInfo, categoryInfo); - // if (blockInfo.extensions) { - // if (!res.json.extensions) res.json.extensions = []; - // res.json.extensions.push(...blockInfo.extensions); - // } - // return res; - // }; - - Scratch.extensions.register(new Timers()); -})(Scratch); +// Name: More Timers +// ID: lmsTimers +// Description: Control several timers at once. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const vm = Scratch.vm; + + /** + * @typedef Timer + * @property {number} startTime + * @property {number} pauseTime + * @property {boolean} paused + */ + + /** @type {Record} */ + let timers = Object.create(null); + + /** + * @param {Timer} timer + * @return {number} + */ + const timerValue = (timer) => { + return ( + ((timer.paused ? 0 : Date.now() - timer.startTime) + timer.pauseTime) / + 1000 + ); + }; + + class Timers { + constructor() { + vm.runtime.on("PROJECT_START", () => { + timers = Object.create(null); + }); + } + + getInfo() { + return { + id: "lmsTimers", + name: "More Timers", + color1: "#5cb1d6", + color2: "#428faf", + color3: "#3281a3", + blocks: [ + { + opcode: "whenTimerOp", + blockType: Scratch.BlockType.HAT, + extensions: ["colours_sensing"], + text: "when timer [TIMER] [OP] [NUM]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + OP: { + type: Scratch.ArgumentType.STRING, + menu: "operation", + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + }, + }, + + "---", + + { + opcode: "startResetTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "start/reset timer [TIMER]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + { + opcode: "valueOfTimer", + blockType: Scratch.BlockType.REPORTER, + extensions: ["colours_sensing"], + text: "timer [TIMER]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + + "---", + + { + opcode: "pauseTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "pause timer [TIMER]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + { + opcode: "resumeTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "resume timer [TIMER]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + + "---", + + { + opcode: "setTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "set timer [TIMER] to [NUM]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + }, + }, + { + opcode: "changeTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "change timer [TIMER] by [NUM]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + }, + }, + + "---", + + { + opcode: "removeTimer", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "remove timer [TIMER]", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + { + opcode: "removeTimers", + blockType: Scratch.BlockType.COMMAND, + extensions: ["colours_sensing"], + text: "remove all timers", + }, + { + opcode: "timerExists", + blockType: Scratch.BlockType.BOOLEAN, + extensions: ["colours_sensing"], + text: "timer [TIMER] exists?", + arguments: { + TIMER: { + type: Scratch.ArgumentType.STRING, + defaultValue: "timer", + }, + }, + }, + { + opcode: "listExistingTimers", + blockType: Scratch.BlockType.REPORTER, + extensions: ["colours_sensing"], + text: "list existing timers", + disableMonitor: false, + }, + ], + menus: { + operation: { + // false for Scratch parity + acceptReporters: false, + items: [">", "<"], + }, + }, + }; + } + + whenTimerOp(args) { + if (!timers[args.TIMER]) return false; + const value = timerValue(timers[args.TIMER]); + if (args.OP === ">") return value > args.NUM; + if (args.OP === "<") return value < args.NUM; + return false; + } + + startResetTimer(args) { + timers[args.TIMER] = { + startTime: Date.now(), + pauseTime: 0, + paused: false, + }; + } + + pauseTimer(args) { + const timer = timers[args.TIMER]; + if (!timer) return; + timer.pauseTime = timerValue(timer) * 1000; + timer.paused = true; + } + + resumeTimer(args) { + const timer = timers[args.TIMER]; + if (!timer) return; + if (timer.paused === false) return; + timer.paused = false; + timer.startTime = Date.now(); + } + + valueOfTimer(args) { + if (!timers[args.TIMER]) return ""; + return timerValue(timers[args.TIMER]); + } + + setTimer(args) { + timers[args.TIMER] = { + paused: false, + startTime: Date.now(), + pauseTime: Scratch.Cast.toNumber(args.NUM) * 1000, + }; + } + + changeTimer(args) { + if (!timers[args.TIMER]) this.startResetTimer(args); + timers[args.TIMER].pauseTime += Scratch.Cast.toNumber(args.NUM) * 1000; + } + + removeTimers(args) { + timers = Object.create(null); + } + + removeTimer(args) { + Reflect.deleteProperty(timers, args.TIMER); + } + + timerExists(args) { + return !!timers[args.TIMER]; + } + + listExistingTimers(args) { + return Object.keys(timers).join(","); + } + } + + // "Extension" option reimplementation by Xeltalliv + // https://github.com/Xeltalliv/extensions/blob/examples/examples/extension-colors.js + + // const cbfsb = Scratch.vm.runtime._convertBlockForScratchBlocks.bind(Scratch.vm.runtime); + // Scratch.vm.runtime._convertBlockForScratchBlocks = function(blockInfo, categoryInfo) { + // const res = cbfsb(blockInfo, categoryInfo); + // if (blockInfo.extensions) { + // if (!res.json.extensions) res.json.extensions = []; + // res.json.extensions.push(...blockInfo.extensions); + // } + // return res; + // }; + + Scratch.extensions.register(new Timers()); +})(Scratch); diff --git a/extensions/Lily/Skins.js b/extensions/Lily/Skins.js new file mode 100644 index 0000000000..2c28f6cc94 --- /dev/null +++ b/extensions/Lily/Skins.js @@ -0,0 +1,459 @@ +// Name: Skins +// ID: lmsSkins +// Description: Have your sprites render as other images or costumes. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const requireNonPackagedRuntime = (blockName) => { + if (Scratch.vm.runtime.isPackaged) { + alert( + `To use the Skins ${blockName} block, the creator of the packaged project must uncheck "Remove raw asset data after loading to save RAM" under advanced settings in the packager.` + ); + return false; + } + return true; + }; + + /** + * @param {RenderWebGL.SVGSkin} svgSkin + * @returns {Promise} + */ + const svgSkinFinishedLoading = (svgSkin) => + new Promise((resolve) => { + if (svgSkin._svgImageLoaded) { + resolve(); + } else { + svgSkin._svgImage.addEventListener("load", () => { + resolve(); + }); + svgSkin._svgImage.addEventListener("error", () => { + resolve(); + }); + } + }); + + const vm = Scratch.vm; + const runtime = vm.runtime; + const renderer = runtime.renderer; + const Cast = Scratch.Cast; + + var createdSkins = []; + + class Skins { + constructor() { + runtime.on("PROJECT_START", () => { + this._refreshTargets(); + }); + + runtime.on("PROJECT_STOP_ALL", () => { + this._refreshTargets(); + }); + } + + getInfo() { + return { + id: "lmsSkins", + name: "Skins", + color1: "#6b56ff", + color2: "#604de6", + color3: "#5645cc", + docsURI: "https://extensions.turbowarp.org/Lily/Skins", + blocks: [ + { + opcode: "registerSVGSkin", + blockType: Scratch.BlockType.COMMAND, + text: "create SVG skin [SVG] as [NAME]", + arguments: { + SVG: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + + "---", + + { + opcode: "registerCostumeSkin", + blockType: Scratch.BlockType.COMMAND, + text: "load skin from [COSTUME] as [NAME]", + arguments: { + COSTUME: { + type: Scratch.ArgumentType.COSTUME, + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + { + opcode: "registerURLSkin", + blockType: Scratch.BlockType.COMMAND, + text: "load skin from URL [URL] as [NAME]", + arguments: { + URL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://extensions.turbowarp.org/dango.png", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + { + opcode: "getSkinLoaded", + blockType: Scratch.BlockType.BOOLEAN, + text: "skin [NAME] is loaded?", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + + "---", + + { + opcode: "setSkin", + blockType: Scratch.BlockType.COMMAND, + text: "set skin of [TARGET] to [NAME]", + arguments: { + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + { + opcode: "restoreSkin", + blockType: Scratch.BlockType.COMMAND, + text: "restore skin of [TARGET]", + arguments: { + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + }, + }, + { + opcode: "restoreTargets", + blockType: Scratch.BlockType.COMMAND, + text: "restore targets with skin [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + + "---", + + { + opcode: "getCurrentSkin", + blockType: Scratch.BlockType.REPORTER, + text: "current skin of [TARGET]", + arguments: { + TARGET: { + type: Scratch.ArgumentType.STRING, + menu: "targetMenu", + }, + }, + }, + { + opcode: "getSkinAttribute", + blockType: Scratch.BlockType.REPORTER, + text: "[ATTRIBUTE] of skin [NAME]", + arguments: { + ATTRIBUTE: { + type: Scratch.ArgumentType.STRING, + menu: "skinAttributes", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + + "---", + + { + opcode: "deleteSkin", + blockType: Scratch.BlockType.COMMAND, + text: "delete skin [NAME]", + arguments: { + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my skin", + }, + }, + }, + { + opcode: "deleteAllSkins", + blockType: Scratch.BlockType.COMMAND, + text: "delete all skins", + }, + ], + menus: { + targetMenu: { + acceptReporters: true, + items: "_getTargets", + }, + skinAttributes: { + acceptReporters: true, + items: ["width", "height"], + }, + }, + }; + } + + async registerSVGSkin(args) { + const skinName = Cast.toString(args.NAME); + const svgData = Cast.toString(args.SVG); + + let oldSkinId = null; + if (createdSkins[skinName]) { + oldSkinId = createdSkins[skinName]; + } + + // This generally takes a few frames, so yield the block + const skinId = renderer.createSVGSkin(svgData); + createdSkins[skinName] = skinId; + + await svgSkinFinishedLoading(renderer._allSkins[skinId]); + + if (oldSkinId) { + this._refreshTargetsFromID(oldSkinId, false, skinId); + renderer.destroySkin(oldSkinId); + } + } + + async registerCostumeSkin(args, util) { + if (!requireNonPackagedRuntime("add costume skin")) { + return; + } + + const skinName = Cast.toString(args.NAME); + const costumeIndex = util.target.getCostumeIndexByName(args.COSTUME); + if (costumeIndex === -1) return; + const costume = util.target.sprite.costumes[costumeIndex]; + + const url = costume.asset.encodeDataURI(); + const rotationCenterX = costume.rotationCenterX; + const rotationCenterY = costume.rotationCenterY; + + let rotationCenter = [rotationCenterX, rotationCenterY]; + if (!rotationCenterX || !rotationCenterY) rotationCenter = null; + + let oldSkinId = null; + if (createdSkins[skinName]) { + oldSkinId = createdSkins[skinName]; + } + + const skinId = await this._createURLSkin(url, rotationCenter); + createdSkins[skinName] = skinId; + + if (oldSkinId) { + this._refreshTargetsFromID(oldSkinId, false, skinId); + renderer.destroySkin(oldSkinId); + } + } + + async registerURLSkin(args) { + const skinName = Cast.toString(args.NAME); + const url = Cast.toString(args.URL); + + let oldSkinId = null; + if (createdSkins[skinName]) { + oldSkinId = createdSkins[skinName]; + } + + const skinId = await this._createURLSkin(url); + if (!skinId) return; + createdSkins[skinName] = skinId; + + if (oldSkinId) { + this._refreshTargetsFromID(oldSkinId, false, skinId); + renderer.destroySkin(oldSkinId); + } + } + + getSkinLoaded(args) { + const skinName = Cast.toString(args.NAME); + return !!createdSkins[skinName]; + } + + setSkin(args, util) { + const skinName = Cast.toString(args.NAME); + if (!createdSkins[skinName]) return; + + const targetName = Cast.toString(args.TARGET); + const target = this._getTargetFromMenu(targetName, util); + if (!target) return; + const drawableID = target.drawableID; + + const skinId = createdSkins[skinName]; + renderer._allDrawables[drawableID].skin = renderer._allSkins[skinId]; + } + + restoreSkin(args, util) { + const targetName = Cast.toString(args.TARGET); + const target = this._getTargetFromMenu(targetName, util); + if (!target) return; + target.updateAllDrawableProperties(); + } + + getCurrentSkin(args, util) { + const targetName = Cast.toString(args.TARGET); + const target = this._getTargetFromMenu(targetName, util); + if (!target) return; + const drawableID = target.drawableID; + + const skinId = renderer._allDrawables[drawableID].skin._id; + const skinName = this._getSkinNameFromID(skinId); + return skinName ? skinName : ""; + } + + getSkinAttribute(args) { + const skins = renderer._allSkins; + const skinName = Cast.toString(args.NAME); + + if (!createdSkins[skinName]) return 0; + const skinId = createdSkins[skinName]; + if (!skins[skinId]) return 0; + + const size = skins[skinId].size; + const attribute = Cast.toString(args.ATTRIBUTE).toLowerCase(); + + switch (attribute) { + case "width": + return Math.ceil(size[0]); + case "height": + return Math.ceil(size[1]); + default: + return 0; + } + } + + deleteSkin(args) { + const skinName = Cast.toString(args.NAME); + if (!createdSkins[skinName]) return; + const skinId = createdSkins[skinName]; + + this._refreshTargetsFromID(skinId, true); + renderer.destroySkin(skinId); + Reflect.deleteProperty(createdSkins, skinName); + } + + deleteAllSkins() { + this._refreshTargets(); + for (let i = 0; i < createdSkins.length; i++) + renderer.destroySkin(createdSkins[i]); + createdSkins = []; + } + + restoreTargets(args) { + const skinName = Cast.toString(args.NAME); + if (!createdSkins[skinName]) return; + const skinId = createdSkins[skinName]; + + this._refreshTargetsFromID(skinId, true); + } + + // Utility Functions + + _refreshTargetsFromID(skinId, reset, newId) { + const drawables = renderer._allDrawables; + const skins = renderer._allSkins; + + for (const target of runtime.targets) { + const drawableID = target.drawableID; + const targetSkin = drawables[drawableID].skin.id; + + if (targetSkin === skinId) { + target.updateAllDrawableProperties(); + if (!reset) + drawables[drawableID].skin = newId ? skins[newId] : skins[skinId]; + } + } + } + + _refreshTargets() { + for (const target of runtime.targets) { + target.updateAllDrawableProperties(); + } + } + + _getSkinNameFromID(skinId) { + for (const skinName in createdSkins) { + if (createdSkins[skinName] === skinId) return skinName; + } + } + + _getTargetFromMenu(targetName, util) { + let target = Scratch.vm.runtime.getSpriteTargetByName(targetName); + if (targetName === "_myself_") target = util.target; + if (targetName === "_stage_") target = runtime.getTargetForStage(); + return target; + } + + async _createURLSkin(URL, rotationCenter) { + let imageData; + if (await Scratch.canFetch(URL)) { + imageData = await Scratch.fetch(URL); + } else { + return; + } + + const contentType = imageData.headers.get("Content-Type"); + if (contentType === "image/svg+xml") { + return renderer.createSVGSkin(await imageData.text(), rotationCenter); + } else if ( + contentType === "image/png" || + contentType === "image/jpeg" || + contentType === "image/bmp" + ) { + // eslint-disable-next-line no-restricted-syntax + const output = new Image(); + output.src = URL; + output.crossOrigin = "anonymous"; + await output.decode(); + return renderer.createBitmapSkin(output); + } + } + + _getTargets() { + const spriteNames = [ + { text: "myself", value: "_myself_" }, + { text: "Stage", value: "_stage_" }, + ]; + const targets = Scratch.vm.runtime.targets; + for (let index = 1; index < targets.length; index++) { + const target = targets[index]; + if (target.isOriginal) { + const targetName = target.getName(); + spriteNames.push({ + text: targetName, + value: targetName, + }); + } + } + return spriteNames; + } + } + Scratch.extensions.register(new Skins()); +})(Scratch); diff --git a/extensions/Lily/TempVariables.js b/extensions/Lily/TempVariables.js index 0e40763ee1..a64d63d8d4 100644 --- a/extensions/Lily/TempVariables.js +++ b/extensions/Lily/TempVariables.js @@ -1,7 +1,7 @@ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; - const menuIconURI = ''; + const menuIconURI = ""; // Object.create(null) prevents "variable [toString]" from returning a function let variables = Object.create(null); @@ -9,114 +9,114 @@ class TempVars { getInfo() { return { - id: 'lmstempvars', - name: 'Temporary Variables', - color1: '#FF791A', - color2: '#E15D00', + id: "lmstempvars", + name: "Temporary Variables", + color1: "#FF791A", + color2: "#E15D00", menuIconURI: menuIconURI, blocks: [ { - opcode: 'setVariableTo', + opcode: "setVariableTo", blockType: Scratch.BlockType.COMMAND, - text: 'set variable [INPUTA] to [INPUTB]', + text: "set variable [INPUTA] to [INPUTB]", arguments: { INPUTA: { type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' + defaultValue: "my variable", }, INPUTB: { type: Scratch.ArgumentType.STRING, - defaultValue: '0' - } - } + defaultValue: "0", + }, + }, }, { - opcode: 'changeVariableBy', + opcode: "changeVariableBy", blockType: Scratch.BlockType.COMMAND, - text: 'change variable [INPUTA] by [INPUTB]', + text: "change variable [INPUTA] by [INPUTB]", arguments: { INPUTA: { type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' + defaultValue: "my variable", }, INPUTB: { type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } + defaultValue: "1", + }, + }, }, { - opcode: 'getVariable', + opcode: "getVariable", blockType: Scratch.BlockType.REPORTER, - text: 'variable [INPUT]', + text: "variable [INPUT]", disableMonitor: true, arguments: { INPUT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - } - } + defaultValue: "my variable", + }, + }, }, - '---', + "---", { - opcode: 'deleteVariable', + opcode: "deleteVariable", blockType: Scratch.BlockType.COMMAND, - text: 'delete variable [INPUT]', + text: "delete variable [INPUT]", arguments: { INPUT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - } - } + defaultValue: "my variable", + }, + }, }, { - opcode: 'deleteAllVariables', + opcode: "deleteAllVariables", blockType: Scratch.BlockType.COMMAND, - text: 'delete all variables', + text: "delete all variables", }, { - opcode: 'listVariables', + opcode: "listVariables", blockType: Scratch.BlockType.REPORTER, - text: 'list active variables', + text: "list active variables", disableMonitor: true, - } - ] + }, + ], }; } - getVariable (args) { + getVariable(args) { if (args.INPUT in variables) { - return (variables[args.INPUT]); + return variables[args.INPUT]; } else { - return ''; + return ""; } } - setVariableTo (args) { + setVariableTo(args) { variables[args.INPUTA] = args.INPUTB; } - changeVariableBy (args) { + changeVariableBy(args) { if (args.INPUTA in variables) { const prev = Scratch.Cast.toNumber(variables[args.INPUTA]); const next = Scratch.Cast.toNumber(args.INPUTB); - variables[args.INPUTA] = (prev + next); + variables[args.INPUTA] = prev + next; } else { variables[args.INPUTA] = args.INPUTB; } } - listVariables (args, util) { - return Object.keys(variables).join(','); + listVariables(args, util) { + return Object.keys(variables).join(","); } - deleteVariable (args) { + deleteVariable(args) { Reflect.deleteProperty(variables, args.INPUT); } - deleteAllVariables () { + deleteAllVariables() { variables = Object.create(null); } } diff --git a/extensions/Lily/TempVariables2.js b/extensions/Lily/TempVariables2.js index 13912281f8..9b28309e35 100644 --- a/extensions/Lily/TempVariables2.js +++ b/extensions/Lily/TempVariables2.js @@ -1,302 +1,306 @@ -(function(Scratch) { - 'use strict'; - - const menuIconURI = ''; - - // Object.create(null) prevents "variable [toString]" from returning a function - let runtimeVariables = Object.create(null); - - // Credit to skyhigh173 for the idea of this - const label = (name, hidden) => ({ - blockType: Scratch.BlockType.LABEL, - text: name, - hideFromPalette: hidden - }); - - function resetRuntimeVariables() { - runtimeVariables = Object.create(null); - } - - class TempVars { - constructor() { - Scratch.vm.runtime.on('PROJECT_START', () => { - resetRuntimeVariables(); - }); - - Scratch.vm.runtime.on('PROJECT_STOP_ALL', () => { - resetRuntimeVariables(); - }); - } - - getInfo() { - return { - id: 'lmsTempVars2', - name: 'Temporary Variables', - color1: '#FF791A', - color2: '#E15D00', - menuIconURI: menuIconURI, // I intend on making one later - blocks: [ - - label('Thread Variables', false), - - { - opcode: 'setThreadVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'set thread var [VAR] to [STRING]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - }, - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: '0' - } - }, - }, - { - opcode: 'changeThreadVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'change thread var [VAR] by [NUM]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - }, - NUM: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - } - }, - }, - - '---', - - { - opcode: 'getThreadVariable', - blockType: Scratch.BlockType.REPORTER, - text: 'thread var [VAR]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - } - } - }, - { - opcode: 'threadVariableExists', - blockType: Scratch.BlockType.BOOLEAN, - text: 'thread var [VAR] exists?', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - } - } - }, - - '---', - - /* Add this when the compiler supports it - { - opcode: 'forEachThreadVariable', - blockType: Scratch.BlockType.LOOP, - text: 'for each [VAR] in [NUM]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'thread variable' - }, - NUM: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - } - } - }, - */ - { - opcode: 'listThreadVariables', - blockType: Scratch.BlockType.REPORTER, - text: 'list active thread variables', - disableMonitor: true - }, - - '---', - - label('Runtime Variables', false), - - { - opcode: 'setRuntimeVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'set runtime var [VAR] to [STRING]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - }, - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: '0' - } - } - }, - { - opcode: 'changeRuntimeVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'change runtime var [VAR] by [NUM]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - }, - NUM: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - - '---', - - { - opcode: 'getRuntimeVariable', - blockType: Scratch.BlockType.REPORTER, - text: 'runtime var [VAR]', - disableMonitor: true, - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - } - } - }, - { - opcode: 'runtimeVariableExists', - blockType: Scratch.BlockType.BOOLEAN, - text: 'runtime var [VAR] exists?', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - } - } - }, - - '---', - - { - opcode: 'deleteRuntimeVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'delete runtime var [VAR]', - arguments: { - VAR: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'variable' - } - } - }, - { - opcode: 'deleteAllRuntimeVariables', - blockType: Scratch.BlockType.COMMAND, - text: 'delete all runtime variables', - }, - { - opcode: 'listRuntimeVariables', - blockType: Scratch.BlockType.REPORTER, - text: 'list active runtime variables' - } - ] - }; - } - - /* THREAD VARIABLES */ - - setThreadVariable(args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - vars[args.VAR] = args.STRING; - } - - changeThreadVariable(args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - const prev = Scratch.Cast.toNumber(vars[args.VAR]); - const next = Scratch.Cast.toNumber(args.NUM); - vars[args.VAR] = prev + next; - } - - getThreadVariable (args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - const varValue = vars[args.VAR]; - if (typeof varValue === 'undefined') return ''; - return varValue; - } - - threadVariableExists (args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - const varValue = vars[args.VAR]; - return !(typeof varValue === 'undefined'); - } - - forEachThreadVariable(args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - if (typeof util.stackFrame.index === 'undefined') { - util.stackFrame.index = 0; - } - if (util.stackFrame.index < Number(args.NUM)) { - util.stackFrame.index++; - vars[args.VAR] = util.stackFrame.index; - util.startBranch(1, true); - } - } - - listThreadVariables(args, util) { - const thread = util.thread; - if (!thread.variables) thread.variables = Object.create(null); - const vars = thread.variables; - return Object.keys(vars).join(','); - } - - /* RUNTIME VARIABLES */ - - setRuntimeVariable (args) { - runtimeVariables[args.VAR] = args.STRING; - } - - changeRuntimeVariable (args) { - const prev = Scratch.Cast.toNumber(runtimeVariables[args.VAR]); - const next = Scratch.Cast.toNumber(args.NUM); - runtimeVariables[args.VAR] = prev + next; - } - - getRuntimeVariable (args) { - if (!(args.VAR in runtimeVariables)) return ''; - return runtimeVariables[args.VAR]; - } - - runtimeVariableExists (args) { - return (args.VAR in runtimeVariables); - } - - listRuntimeVariables (args, util) { - return Object.keys(runtimeVariables).join(','); - } - - deleteRuntimeVariable (args) { - Reflect.deleteProperty(runtimeVariables, args.VAR); - } - - deleteAllRuntimeVariables () { - runtimeVariables = Object.create(null); - } - } - Scratch.extensions.register(new TempVars()); -})(Scratch); +// Name: Temporary Variables +// ID: lmsTempVars2 +// Description: Create disposable runtime or thread variables. +// By: LilyMakesThings + +(function (Scratch) { + "use strict"; + + const menuIconURI = ""; + + // Object.create(null) prevents "variable [toString]" from returning a function + let runtimeVariables = Object.create(null); + + // Credit to skyhigh173 for the idea of this + const label = (name, hidden) => ({ + blockType: Scratch.BlockType.LABEL, + text: name, + hideFromPalette: hidden, + }); + + function resetRuntimeVariables() { + runtimeVariables = Object.create(null); + } + + class TempVars { + constructor() { + Scratch.vm.runtime.on("PROJECT_START", () => { + resetRuntimeVariables(); + }); + + Scratch.vm.runtime.on("PROJECT_STOP_ALL", () => { + resetRuntimeVariables(); + }); + } + + getInfo() { + return { + id: "lmsTempVars2", + name: "Temporary Variables", + color1: "#FF791A", + color2: "#E15D00", + menuIconURI: menuIconURI, // I intend on making one later + blocks: [ + label("Thread Variables", false), + + { + opcode: "setThreadVariable", + blockType: Scratch.BlockType.COMMAND, + text: "set thread var [VAR] to [STRING]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "0", + }, + }, + }, + { + opcode: "changeThreadVariable", + blockType: Scratch.BlockType.COMMAND, + text: "change thread var [VAR] by [NUM]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + + "---", + + { + opcode: "getThreadVariable", + blockType: Scratch.BlockType.REPORTER, + text: "thread var [VAR]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + }, + }, + { + opcode: "threadVariableExists", + blockType: Scratch.BlockType.BOOLEAN, + text: "thread var [VAR] exists?", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + }, + }, + + "---", + + /* Add this when the compiler supports it + { + opcode: 'forEachThreadVariable', + blockType: Scratch.BlockType.LOOP, + text: 'for each [VAR] in [NUM]', + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'thread variable' + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '10' + } + } + }, + */ + { + opcode: "listThreadVariables", + blockType: Scratch.BlockType.REPORTER, + text: "list active thread variables", + disableMonitor: true, + }, + + "---", + + label("Runtime Variables", false), + + { + opcode: "setRuntimeVariable", + blockType: Scratch.BlockType.COMMAND, + text: "set runtime var [VAR] to [STRING]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "0", + }, + }, + }, + { + opcode: "changeRuntimeVariable", + blockType: Scratch.BlockType.COMMAND, + text: "change runtime var [VAR] by [NUM]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + NUM: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + + "---", + + { + opcode: "getRuntimeVariable", + blockType: Scratch.BlockType.REPORTER, + text: "runtime var [VAR]", + disableMonitor: true, + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + }, + }, + { + opcode: "runtimeVariableExists", + blockType: Scratch.BlockType.BOOLEAN, + text: "runtime var [VAR] exists?", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + }, + }, + + "---", + + { + opcode: "deleteRuntimeVariable", + blockType: Scratch.BlockType.COMMAND, + text: "delete runtime var [VAR]", + arguments: { + VAR: { + type: Scratch.ArgumentType.STRING, + defaultValue: "variable", + }, + }, + }, + { + opcode: "deleteAllRuntimeVariables", + blockType: Scratch.BlockType.COMMAND, + text: "delete all runtime variables", + }, + { + opcode: "listRuntimeVariables", + blockType: Scratch.BlockType.REPORTER, + text: "list active runtime variables", + }, + ], + }; + } + + /* THREAD VARIABLES */ + + setThreadVariable(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + vars[args.VAR] = args.STRING; + } + + changeThreadVariable(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + const prev = Scratch.Cast.toNumber(vars[args.VAR]); + const next = Scratch.Cast.toNumber(args.NUM); + vars[args.VAR] = prev + next; + } + + getThreadVariable(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + const varValue = vars[args.VAR]; + if (typeof varValue === "undefined") return ""; + return varValue; + } + + threadVariableExists(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + const varValue = vars[args.VAR]; + return !(typeof varValue === "undefined"); + } + + forEachThreadVariable(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + if (typeof util.stackFrame.index === "undefined") { + util.stackFrame.index = 0; + } + if (util.stackFrame.index < Number(args.NUM)) { + util.stackFrame.index++; + vars[args.VAR] = util.stackFrame.index; + util.startBranch(1, true); + } + } + + listThreadVariables(args, util) { + const thread = util.thread; + if (!thread.variables) thread.variables = Object.create(null); + const vars = thread.variables; + return Object.keys(vars).join(","); + } + + /* RUNTIME VARIABLES */ + + setRuntimeVariable(args) { + runtimeVariables[args.VAR] = args.STRING; + } + + changeRuntimeVariable(args) { + const prev = Scratch.Cast.toNumber(runtimeVariables[args.VAR]); + const next = Scratch.Cast.toNumber(args.NUM); + runtimeVariables[args.VAR] = prev + next; + } + + getRuntimeVariable(args) { + if (!(args.VAR in runtimeVariables)) return ""; + return runtimeVariables[args.VAR]; + } + + runtimeVariableExists(args) { + return args.VAR in runtimeVariables; + } + + listRuntimeVariables(args, util) { + return Object.keys(runtimeVariables).join(","); + } + + deleteRuntimeVariable(args) { + Reflect.deleteProperty(runtimeVariables, args.VAR); + } + + deleteAllRuntimeVariables() { + runtimeVariables = Object.create(null); + } + } + Scratch.extensions.register(new TempVars()); +})(Scratch); diff --git a/extensions/Lily/lmsutils.js b/extensions/Lily/lmsutils.js index f72f40e4ae..91c42fdc38 100644 --- a/extensions/Lily/lmsutils.js +++ b/extensions/Lily/lmsutils.js @@ -1,1354 +1,1377 @@ -(function(Scratch) { - 'use strict'; - const menuIconURI = 'data:image/png;base64,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'; - - let hideLegacyBlocks = true; - - var vars = {}; - vars['variables'] = Object.create(null); - - - if (!Scratch.extensions.unsandboxed) { - throw new Error('This extension must run unsandboxed'); - } - - class LMSUtils { - constructor(runtime) { - this.runtime = runtime; - } - getInfo() { - return { - id: 'lmsutilsblocks', - name: 'LMS Utilities', - color1: '#3bb2ed', - color2: '#37a1de', - color3: '#3693d9', - menuIconURI: menuIconURI, - blocks: [ - { - opcode: 'whenBooleanHat', - blockType: Scratch.BlockType.HAT, - text: 'when [INPUT] is true', - isEdgeActivated: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - } - } - }, - { - opcode: 'whenKeyString', - blockType: Scratch.BlockType.HAT, - text: 'when key [KEY_OPTION] pressed', - isEdgeActivated: true, - arguments: { - KEY_OPTION: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'enter' - } - } - }, - - '---', - - { - opcode: 'keyStringPressed', - blockType: Scratch.BlockType.BOOLEAN, - text: 'key [KEY_OPTION] pressed?', - arguments: { - KEY_OPTION: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'enter' - } - } - }, - { - opcode: 'trueFalseBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[TRUEFALSE]', - arguments: { - TRUEFALSE: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'true', - menu: 'trueFalseMenu' - } - } - }, - { - opcode: 'stringIf', - blockType: Scratch.BlockType.REPORTER, - text: 'if [BOOLEAN] then [INPUTA]', - disableMonitor: true, - arguments: { - BOOLEAN: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - } - } - }, - { - opcode: 'stringIfElse', - blockType: Scratch.BlockType.REPORTER, - text: 'if [BOOLEAN] then [INPUTA] else [INPUTB]', - disableMonitor: true, - arguments: { - BOOLEAN: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'banana' - } - } - }, - - '---', - - { - opcode: 'getEffectValue', - blockType: Scratch.BlockType.REPORTER, - text: 'effect [INPUT]', - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'color', - menu: 'colorMenu' - } - } - }, - { - opcode: 'clonesBeingUsed', - blockType: Scratch.BlockType.REPORTER, - text: 'clone count', - }, - { - opcode: 'isClone', - blockType: Scratch.BlockType.BOOLEAN, - text: 'is clone?', - filter: [Scratch.TargetType.SPRITE] - }, - { - opcode: 'spriteClicked', - blockType: Scratch.BlockType.BOOLEAN, - text: 'sprite clicked?', - filter: [Scratch.TargetType.SPRITE] - }, - - '---', - - { - opcode: 'lettersToOf', - blockType: Scratch.BlockType.REPORTER, - text: 'letters [INPUTA] to [INPUTB] of [STRING]', - disableMonitor: true, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3' - }, - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'suspicious' - } - } - }, - { - opcode: 'replaceWords', - blockType: Scratch.BlockType.REPORTER, - text: 'replace first [INPUTA] with [INPUTB] in [STRING]', - disableMonitor: true, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Scratch' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Turbowarp' - }, - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Scratch is brilliant!' - } - } - }, - { - opcode: 'findIndexOfString', - blockType: Scratch.BlockType.REPORTER, - text: 'index of [INPUTA] in [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'brilliant' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Turbowarp is brilliant!' - } - } - }, - { - opcode: 'itemOfFromString', - blockType: Scratch.BlockType.REPORTER, - text: 'item [INPUTA] of [INPUTB] split by [INPUTC]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '2' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple|banana' - }, - INPUTC: { - type: Scratch.ArgumentType.STRING, - defaultValue: '|' - } - } - }, - { - opcode: 'stringToUpperCase', - blockType: Scratch.BlockType.REPORTER, - text: '[STRING] to uppercase', - disableMonitor: true, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - } - } - }, - { - opcode: 'stringToLowerCase', - blockType: Scratch.BlockType.REPORTER, - text: '[STRING] to lowercase', - disableMonitor: true, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'APPLE' - } - } - }, - { - opcode: 'reverseString', - blockType: Scratch.BlockType.REPORTER, - text: 'reverse [STRING]', - disableMonitor: true, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'prawobrut' - } - } - }, - - '---', - - { - opcode: 'norBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] nor [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - } - } - }, - { - opcode: 'xorBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] xor [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - } - } - }, - { - opcode: 'xnorBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] xnor [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - } - } - }, - { - opcode: 'nandBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] nand [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.BOOLEAN, - defaultValue: '' - } - } - }, - - '---', - - { - opcode: 'stringReporter', - blockType: Scratch.BlockType.REPORTER, - text: '[STRING]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - } - } - }, - { - opcode: 'colourHex', - blockType: Scratch.BlockType.REPORTER, - text: 'color [COLOUR]', - hideFromPalette: hideLegacyBlocks, - arguments: { - COLOUR: { - type: Scratch.ArgumentType.COLOR, - defaultValue: '#0088ff' - } - } - }, - { - opcode: 'angleReporter', - blockType: Scratch.BlockType.REPORTER, - text: 'angle [ANGLE]', - hideFromPalette: hideLegacyBlocks, - arguments: { - ANGLE: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '90' - } - } - }, - { - opcode: 'matrixReporter', - blockType: Scratch.BlockType.REPORTER, - text: 'matrix [MATRIX]', - hideFromPalette: hideLegacyBlocks, - arguments: { - MATRIX: { - type: Scratch.ArgumentType.MATRIX, - defaultValue: '0101001010000001000101110' - } - } - }, - { - opcode: 'noteReporter', - blockType: Scratch.BlockType.REPORTER, - text: 'note [NOTE]', - hideFromPalette: hideLegacyBlocks, - arguments: { - NOTE: { - type: Scratch.ArgumentType.NOTE, - defaultValue: '' - } - } - }, - { - opcode: 'newlineCharacter', - blockType: Scratch.BlockType.REPORTER, - text: 'newline character', - hideFromPalette: hideLegacyBlocks, - disableMonitor: true - }, - - '---', - - { - opcode: 'equalsExactly', - blockType: Scratch.BlockType.BOOLEAN, - text: '[ONE] === [TWO]', - arguments: { - ONE: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - TWO: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'banana' - } - } - }, - { - opcode: 'notEqualTo', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] ≠ [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'banana' - } - } - }, - { - opcode: 'moreThanEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] ≥ [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '16' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '25' - } - } - }, - { - opcode: 'lessThanEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUTA] ≤ [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '16' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '25' - } - } - }, - { - opcode: 'stringCheckBoolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[INPUT] is [DROPDOWN]', - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'text', - menu: 'stringCheckMenu' - } - } - }, - - '---', - - { - opcode: 'encodeToBlock', - blockType: Scratch.BlockType.REPORTER, - text: 'encode [STRING] to [DROPDOWN]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'base64', - menu: 'conversionMenu' - } - } - }, - { - opcode: 'decodeFromBlock', - blockType: Scratch.BlockType.REPORTER, - text: 'decode [STRING] from [DROPDOWN]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'base64', - menu: 'conversionMenu' - } - } - }, - - '---', - - { - opcode: 'negativeReporter', - blockType: Scratch.BlockType.REPORTER, - text: '- [INPUT]', - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } - }, - { - opcode: 'exponentBlock', - blockType: Scratch.BlockType.REPORTER, - text: '[INPUTA] ^ [INPUTB]', - disableMonitor: true, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } - }, - { - opcode: 'rootBlock', - blockType: Scratch.BlockType.REPORTER, - text: '[INPUTA] √ [INPUTB]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } - }, - { - opcode: 'normaliseValue', - blockType: Scratch.BlockType.REPORTER, - text: 'normalise [INPUT]', - disableMonitor: true, - arguments: { - INPUT: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - } - } - }, - { - opcode: 'clampNumber', - blockType: Scratch.BlockType.REPORTER, - text: 'clamp [INPUTA] between [INPUTB] and [INPUTC]', - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - INPUTB: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '25' - }, - INPUTC: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - } - } - }, - - '---', - - { - opcode: 'setVariableTo', - blockType: Scratch.BlockType.COMMAND, - text: 'set variable [INPUTA] to [INPUTB]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '0' - } - } - }, - { - opcode: 'changeVariableBy', - blockType: Scratch.BlockType.COMMAND, - text: 'change variable [INPUTA] by [INPUTB]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1' - } - } - }, - { - opcode: 'getVariable', - blockType: Scratch.BlockType.REPORTER, - text: 'variable [INPUT]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - } - } - }, - { - opcode: 'deleteVariable', - blockType: Scratch.BlockType.COMMAND, - text: 'delete variable [INPUT]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'my variable' - } - } - }, - { - opcode: 'deleteAllVariables', - blockType: Scratch.BlockType.COMMAND, - text: 'delete all variables', - hideFromPalette: hideLegacyBlocks - }, - { - opcode: 'listVariables', - blockType: Scratch.BlockType.REPORTER, - text: 'list active variables', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks - }, - - '---', - - { - opcode: 'greenFlag', - blockType: Scratch.BlockType.COMMAND, - text: 'green flag', - hideFromPalette: hideLegacyBlocks - }, - { - opcode: 'setUsername', - blockType: Scratch.BlockType.COMMAND, - text: 'set username to [INPUT]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'LilyMakesThings' - } - } - }, - - '---', - - { - opcode: 'setSpriteSVG', - blockType: Scratch.BlockType.COMMAND, - text: 'replace SVG data for costume [INPUTA] with [INPUTB]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, - - '---', - - { - opcode: 'alertBlock', - blockType: Scratch.BlockType.COMMAND, - text: 'alert [STRING]', - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'A red spy is in the base!' - } - } - }, - { - opcode: 'inputPromptBlock', - blockType: Scratch.BlockType.REPORTER, - text: 'prompt [STRING]', - hideFromPalette: hideLegacyBlocks, - disableMonitor: true, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'The code is 1, 1, 1.. err... 1!' - } - } - }, - { - opcode: 'confirmationBlock', - blockType: Scratch.BlockType.BOOLEAN, - text: 'confirm [STRING]', - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Are you the red spy?' - } - } - }, - - '---', - - { - opcode: 'goToLink', - blockType: Scratch.BlockType.COMMAND, - text: 'open link [INPUT] in new tab', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, - { - opcode: 'redirectToLink', - blockType: Scratch.BlockType.COMMAND, - text: 'redirect to link [INPUT]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, - - '---', - - { - opcode: 'setClipboard', - blockType: Scratch.BlockType.COMMAND, - text: 'set [STRING] to clipboard', - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple', - } - } - }, - { - opcode: 'readClipboard', - blockType: Scratch.BlockType.REPORTER, - text: 'clipboard', - hideFromPalette: hideLegacyBlocks - }, - - '---', - - { - opcode: 'isUserMobile', - blockType: Scratch.BlockType.BOOLEAN, - text: 'is mobile?' - }, - { - opcode: 'screenReporter', - blockType: Scratch.BlockType.REPORTER, - text: 'screen [DROPDOWN]', - disableMonitor: true, - arguments: { - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'width', - menu: 'screenReporterMenu' - } - } - }, - { - opcode: 'windowReporter', - blockType: Scratch.BlockType.REPORTER, - text: 'window [DROPDOWN]', - disableMonitor: true, - arguments: { - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'width', - menu: 'screenReporterMenu' - } - } - }, - { - opcode: 'osBrowserDetails', - blockType: Scratch.BlockType.REPORTER, - text: 'get [DROPDOWN] of user', - disableMonitor: true, - arguments: { - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'operating system', - menu: 'osBrowserMenu' - } - } - }, - { - opcode: 'projectURL', - blockType: Scratch.BlockType.REPORTER, - text: 'project URL', - disableMonitor: true, - }, - - '---', - - { - opcode: 'consoleLog', - blockType: Scratch.BlockType.COMMAND, - text: 'console [DROPDOWN] [INPUT]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - DROPDOWN: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'log', - menu: 'consoleLogMenu' - }, - INPUT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Apple' - } - } - }, - { - opcode: 'clearConsole', - blockType: Scratch.BlockType.COMMAND, - text: 'clear console', - hideFromPalette: hideLegacyBlocks - }, - - '---', - - { - opcode: 'commentHat', - blockType: Scratch.BlockType.HAT, - text: '// [STRING]', - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'comment', - } - } - }, - { - opcode: 'commentCommand', - blockType: Scratch.BlockType.COMMAND, - text: '// [STRING]', - hideFromPalette: hideLegacyBlocks, - arguments: { - STRING: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'comment', - } - } - }, - { - opcode: 'commentString', - blockType: Scratch.BlockType.REPORTER, - text: '// [INPUTA] [INPUTB]', - disableMonitor: true, - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' - }, - INPUTB: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'input' - } - } - }, - { - opcode: 'commentBool', - blockType: Scratch.BlockType.BOOLEAN, - text: '// [INPUTA] [INPUTB]', - hideFromPalette: hideLegacyBlocks, - arguments: { - INPUTA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'comment' - }, - INPUTB: { - type: Scratch.ArgumentType.BOOLEAN, - } - } - }, - - '---', - - { - func: 'showLegacyBlocks', - blockType: Scratch.BlockType.BUTTON, - text: 'Show Legacy Blocks', - hideFromPalette: !hideLegacyBlocks - }, - { - func: 'hideLegacyBlocks', - blockType: Scratch.BlockType.BUTTON, - text: 'Hide Legacy Blocks', - hideFromPalette: hideLegacyBlocks - }, - ], - menus: { - conversionMenu: { - acceptReporters: true, - items: ['base64', 'binary'] - }, - trueFalseMenu: { - acceptReporters: true, - items: ['true', 'false', 'random'] - }, - screenReporterMenu: { - acceptReporters: true, - items: ['width', 'height'] - }, - windowReporterMenu: { - acceptReporters: true, - items: ['width', 'height'] - }, - stringCheckMenu: { - acceptReporters: true, - items: ['text', 'number', 'uppercase', 'lowercase'] - }, - osBrowserMenu: { - acceptReporters: true, - items: ['operating system', 'browser'] - }, - consoleLogMenu: { - acceptReporters: false, - items: ['log', 'error', 'warn'] - }, - colorMenu: { - acceptReporters: true, - items: ['color', 'fisheye', 'whirl', 'pixelate', 'mosaic', 'brightness', 'ghost'] - }, - } - }; - } - - showLegacyBlocks() { - if (confirm('Are you sure you want to show legacy blocks? \n \n These blocks were removed because they were buggy or implemented better in other extensions.')) { - hideLegacyBlocks = false; - Scratch.vm.extensionManager.refreshBlocks(); - } else { - // - } - } - - hideLegacyBlocks() { - hideLegacyBlocks = true; - Scratch.vm.extensionManager.refreshBlocks(); - } - - whenBooleanHat(args) { - return args.INPUT; - } - - whenKeyString(args, util) { - return util.ioQuery('keyboard', 'getKeyIsDown', [args.KEY_OPTION]); - } - - keyStringPressed(args, util) { - return util.ioQuery('keyboard', 'getKeyIsDown', [args.KEY_OPTION]); - } - - trueFalseBoolean(args) { - if (args.TRUEFALSE === 'random') return Math.random() > 0.5; - if (args.TRUEFALSE === 'true') return true; - return false; - } - - stringIf(args) { - if (args.BOOLEAN) return args.INPUTA; - return ''; - } - - stringIfElse(args) { - if (args.BOOLEAN) return args.INPUTA; - return args.INPUTB; - } - - getEffectValue (args, util) { - return util.target.effects[args.INPUT]; - } - - clonesBeingUsed(args, util) { - return Scratch.vm.runtime._cloneCounter; - } - - isClone(args, util) { - return util.target.isOriginal ? false : true; - } - - spriteClicked(args, util) { - return (util.ioQuery('mouse', 'getIsDown') && util.target.isTouchingObject('_mouse_')); - } - - lettersToOf(args) { - var string = args.STRING.toString(); - var input1 = args.INPUTA - 1; - var input2 = args.INPUTB; - return string.slice(input1, input2); - } - - replaceWords(args) { - var input1 = args.INPUTA; - var input2 = args.INPUTB; - var string = args.STRING; - return string.replace(input1, input2); - } - - findIndexOfString (args) { - var input1 = args.INPUTA; - var input2 = args.INPUTB; - if (input2.includes(input1)) return (input2.indexOf(input1) + 1); - return ''; - } - - itemOfFromString (args, util) { - var input1 = (args.INPUTA - 1); - var input2 = String(args.INPUTB); - var input3 = args.INPUTC; - var output = input2.split(input3)[input1] || ''; - return output; - } - - stringToUpperCase(args) { - return args.STRING.toUpperCase(); - } - - stringToLowerCase(args) { - return args.STRING.toLowerCase(); - } - - reverseString(args) { - var input = args.STRING; - var splitInput = input.split(''); - var reversedInput = splitInput.reverse(); - var joinedArray = reversedInput.join(''); - return joinedArray; - } - - norBoolean(args) { - return !(args.INPUTA || args.INPUTB); - } - - xorBoolean(args) { - return (args.INPUTA !== args.INPUTB); - } - - xnorBoolean(args) { - return (args.INPUTA === args.INPUTB); - } - - nandBoolean(args) { - return !(args.INPUTA && args.INPUTB); - } - - stringReporter(args) { - return args.STRING; - } - - colourHex(args) { - return args.COLOUR; - } - - angleReporter(args) { - return args.ANGLE; - } - - matrixReporter(args) { - return args.MATRIX; - } - - noteReporter(args) { - return args.NOTE; - } - - newlineCharacter() { - return '\n'; - } - - equalsExactly(args) { - return args.ONE === args.TWO; - } - - notEqualTo(args) { - return (args.INPUTA != args.INPUTB); - } - - moreThanEqual(args) { - return (args.INPUTA >= args.INPUTB); - } - - lessThanEqual(args) { - return (args.INPUTA <= args.INPUTB); - } - - stringCheckBoolean(args) { - const input = args.INPUT; - const dropdown = args.DROPDOWN; - if (dropdown === 'text') return isNaN(input); - if (dropdown === 'number') return !isNaN(input); - if (dropdown === 'uppercase') return (input == input.toUpperCase()); - if (dropdown === 'lowercase') return (input == input.toLowerCase()); - return false; - } - - encodeToBlock(args) { - if (args.STRING === '') return ''; - if (args.DROPDOWN === 'base64') return btoa(args.STRING); - if (args.DROPDOWN === 'binary') { - return args.STRING.split('').map(function (char) { - return char.charCodeAt(0).toString(2); - }).join(' '); - } - return ''; - } - - decodeFromBlock(args) { - if (args.STRING === '') return ''; - if (args.DROPDOWN === 'base64') return atob(args.STRING); - if (args.DROPDOWN === 'binary') { - var output = args.STRING.toString(); - return output.split(' ').map((x) => x = String.fromCharCode(parseInt(x, 2))).join(''); - } - return ''; - } - - negativeReporter (args) { - return (args.INPUT * -1); - } - - exponentBlock(args) { - return Math.pow(args.INPUTA, args.INPUTB); - } - - rootBlock(args) { - return Math.pow(args.INPUTB, 1 / args.INPUTA); - } - - normaliseValue(args) { - var input1 = args.INPUT; - var input2 = Math.abs(input1); - var output = (input1 / input2); - if (isNaN(output)) return '0'; - return output; - } - - clampNumber (args) { - var input1 = args.INPUTA; - var input2 = args.INPUTB; - var input3 = args.INPUTC; - return Math.min(Math.max(input1, input2), input3); - } - - setVariableTo (args) { - vars['variables'][args.INPUTA] = args.INPUTB; - } - - changeVariableBy (args) { - if (args.INPUTA in vars['variables']) { - var prev = vars['variables'][args.INPUTA]; - var next = args.INPUTB; - vars['variables'][args.INPUTA] = (prev + next); - } else { - vars['variables'][args.INPUTA] = args.INPUTB; - } - } - - getVariable (args) { - if (args.INPUT in vars['variables']) return (vars['variables'][args.INPUT]); - return ''; - } - - deleteVariable (args) { - Reflect.deleteProperty(vars['variables'], args.INPUT); - } - - deleteAllVariables () { - Reflect.deleteProperty(vars, 'variables'); - vars['variables'] = {}; - } - - greenFlag(args, util) { - util.runtime.greenFlag(); - } - - setUsername(args, util) { - util.runtime.ioDevices.userData._username = args.INPUT; - } - - setSpriteSVG (args, util) { - try { - Scratch.vm.runtime.renderer.updateSVGSkin(util.target.sprite.costumes[args.INPUTA - 1].skinId,args.INPUTB); - } catch (error){ - return; - } - Scratch.vm.emitTargetsUpdate(); - } - - alertBlock(args) { - alert(args.STRING); - } - - inputPromptBlock(args) { - return prompt(args.STRING); - } - - confirmationBlock(args) { - return confirm(args.STRING); - } - - goToLink(args) { - Scratch.openWindow(args.INPUT); - } - - redirectToLink(args) { - Scratch.redirect(args.INPUT); - } - - setClipboard(args) { - if (navigator.clipboard && navigator.clipboard.writeText) { - navigator.clipboard.writeText(args.STRING); - } - } - - readClipboard(args) { - if (navigator.clipboard && navigator.clipboard.readText) { - return Scratch.canReadClipboard().then(allowed => { - if (allowed) { - return navigator.clipboard.readText(); - } - return ''; - }); - } - return ''; - } - - isUserMobile (args, util) { - return navigator.userAgent.includes('Mobile'); - } - - screenReporter(args) { - if (args.DROPDOWN === 'width') return screen.width; - if (args.DROPDOWN === 'height') return screen.height; - return ''; - } - - windowReporter(args) { - if (args.DROPDOWN === 'width') return window.innerWidth; - if (args.DROPDOWN === 'height') return window.innerHeight; - return ''; - } - - osBrowserDetails(args) { - var user = navigator.userAgent; - if (args.DROPDOWN === 'operating system') { - if (user.includes('Mac OS')) return 'macOS'; - if (user.includes('CrOS')) return 'ChromeOS'; - if (user.includes('Linux')) return 'Linux'; - if (user.includes('Windows')) return 'Windows'; - if (user.includes('iPad')) return 'iOS'; - if (user.includes('iPod')) return 'iOS'; - if (user.includes('iPhone')) return 'iOS'; - if (user.includes('Android')) return 'Android'; - return 'Other'; - } - if (args.DROPDOWN === 'browser') { - if (user.includes('Chrome')) return 'Chrome'; - if (user.includes('MSIE')) return 'Internet Explorer'; - if (user.includes('Firefox')) return 'Firefox'; - if (user.includes('Safari')) return 'Safari'; - return 'Other'; - } - } - - projectURL() { - return window.location.href; - } - - consoleLog(args) { - if (args.DROPDOWN === 'log') { - console.log(args.INPUT); - } else if (args.DROPDOWN === 'error') { - console.error(args.INPUT); - } else if (args.DROPDOWN === 'warn') { - console.warn(args.INPUT); - } - } - - clearConsole() { - console.clear(); - } - - commentHat () { - // no-op - } - - commentCommand () { - // no-op - } - - commentString (args) { - return args.INPUT; - } - - commentBool (args) { - return args.INPUT || false; - } - } - Scratch.extensions.register(new LMSUtils()); -})(Scratch); +(function (Scratch) { + "use strict"; + const menuIconURI = + "data:image/png;base64,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"; + + let hideLegacyBlocks = true; + + var vars = {}; + vars["variables"] = Object.create(null); + + if (!Scratch.extensions.unsandboxed) { + throw new Error("This extension must run unsandboxed"); + } + + class LMSUtils { + constructor(runtime) { + this.runtime = runtime; + } + getInfo() { + return { + id: "lmsutilsblocks", + name: "LMS Utilities", + color1: "#3bb2ed", + color2: "#37a1de", + color3: "#3693d9", + menuIconURI: menuIconURI, + blocks: [ + { + opcode: "whenBooleanHat", + blockType: Scratch.BlockType.HAT, + text: "when [INPUT] is true", + isEdgeActivated: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + }, + }, + { + opcode: "whenKeyString", + blockType: Scratch.BlockType.HAT, + text: "when key [KEY_OPTION] pressed", + isEdgeActivated: true, + arguments: { + KEY_OPTION: { + type: Scratch.ArgumentType.STRING, + defaultValue: "enter", + }, + }, + }, + + "---", + + { + opcode: "keyStringPressed", + blockType: Scratch.BlockType.BOOLEAN, + text: "key [KEY_OPTION] pressed?", + arguments: { + KEY_OPTION: { + type: Scratch.ArgumentType.STRING, + defaultValue: "enter", + }, + }, + }, + { + opcode: "trueFalseBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[TRUEFALSE]", + arguments: { + TRUEFALSE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "true", + menu: "trueFalseMenu", + }, + }, + }, + { + opcode: "stringIf", + blockType: Scratch.BlockType.REPORTER, + text: "if [BOOLEAN] then [INPUTA]", + disableMonitor: true, + arguments: { + BOOLEAN: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + }, + }, + { + opcode: "stringIfElse", + blockType: Scratch.BlockType.REPORTER, + text: "if [BOOLEAN] then [INPUTA] else [INPUTB]", + disableMonitor: true, + arguments: { + BOOLEAN: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "banana", + }, + }, + }, + + "---", + + { + opcode: "getEffectValue", + blockType: Scratch.BlockType.REPORTER, + text: "effect [INPUT]", + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "color", + menu: "colorMenu", + }, + }, + }, + { + opcode: "clonesBeingUsed", + blockType: Scratch.BlockType.REPORTER, + text: "clone count", + }, + { + opcode: "isClone", + blockType: Scratch.BlockType.BOOLEAN, + text: "is clone?", + filter: [Scratch.TargetType.SPRITE], + }, + { + opcode: "spriteClicked", + blockType: Scratch.BlockType.BOOLEAN, + text: "sprite clicked?", + filter: [Scratch.TargetType.SPRITE], + }, + + "---", + + { + opcode: "lettersToOf", + blockType: Scratch.BlockType.REPORTER, + text: "letters [INPUTA] to [INPUTB] of [STRING]", + disableMonitor: true, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3", + }, + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "suspicious", + }, + }, + }, + { + opcode: "replaceWords", + blockType: Scratch.BlockType.REPORTER, + text: "replace first [INPUTA] with [INPUTB] in [STRING]", + disableMonitor: true, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Scratch", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Turbowarp", + }, + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Scratch is brilliant!", + }, + }, + }, + { + opcode: "findIndexOfString", + blockType: Scratch.BlockType.REPORTER, + text: "index of [INPUTA] in [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "brilliant", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Turbowarp is brilliant!", + }, + }, + }, + { + opcode: "itemOfFromString", + blockType: Scratch.BlockType.REPORTER, + text: "item [INPUTA] of [INPUTB] split by [INPUTC]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "2", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple|banana", + }, + INPUTC: { + type: Scratch.ArgumentType.STRING, + defaultValue: "|", + }, + }, + }, + { + opcode: "stringToUpperCase", + blockType: Scratch.BlockType.REPORTER, + text: "[STRING] to uppercase", + disableMonitor: true, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + }, + }, + { + opcode: "stringToLowerCase", + blockType: Scratch.BlockType.REPORTER, + text: "[STRING] to lowercase", + disableMonitor: true, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "APPLE", + }, + }, + }, + { + opcode: "reverseString", + blockType: Scratch.BlockType.REPORTER, + text: "reverse [STRING]", + disableMonitor: true, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "prawobrut", + }, + }, + }, + + "---", + + { + opcode: "norBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] nor [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + }, + }, + { + opcode: "xorBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] xor [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + }, + }, + { + opcode: "xnorBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] xnor [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + }, + }, + { + opcode: "nandBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] nand [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.BOOLEAN, + defaultValue: "", + }, + }, + }, + + "---", + + { + opcode: "stringReporter", + blockType: Scratch.BlockType.REPORTER, + text: "[STRING]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + }, + }, + { + opcode: "colourHex", + blockType: Scratch.BlockType.REPORTER, + text: "color [COLOUR]", + hideFromPalette: hideLegacyBlocks, + arguments: { + COLOUR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#0088ff", + }, + }, + }, + { + opcode: "angleReporter", + blockType: Scratch.BlockType.REPORTER, + text: "angle [ANGLE]", + hideFromPalette: hideLegacyBlocks, + arguments: { + ANGLE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "90", + }, + }, + }, + { + opcode: "matrixReporter", + blockType: Scratch.BlockType.REPORTER, + text: "matrix [MATRIX]", + hideFromPalette: hideLegacyBlocks, + arguments: { + MATRIX: { + type: Scratch.ArgumentType.MATRIX, + defaultValue: "0101001010000001000101110", + }, + }, + }, + { + opcode: "noteReporter", + blockType: Scratch.BlockType.REPORTER, + text: "note [NOTE]", + hideFromPalette: hideLegacyBlocks, + arguments: { + NOTE: { + type: Scratch.ArgumentType.NOTE, + defaultValue: "", + }, + }, + }, + { + opcode: "newlineCharacter", + blockType: Scratch.BlockType.REPORTER, + text: "newline character", + hideFromPalette: hideLegacyBlocks, + disableMonitor: true, + }, + + "---", + + { + opcode: "equalsExactly", + blockType: Scratch.BlockType.BOOLEAN, + text: "[ONE] === [TWO]", + arguments: { + ONE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + TWO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "banana", + }, + }, + }, + { + opcode: "notEqualTo", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] ≠ [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "banana", + }, + }, + }, + { + opcode: "moreThanEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] ≥ [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "16", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "25", + }, + }, + }, + { + opcode: "lessThanEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUTA] ≤ [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "16", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "25", + }, + }, + }, + { + opcode: "stringCheckBoolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[INPUT] is [DROPDOWN]", + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "text", + menu: "stringCheckMenu", + }, + }, + }, + + "---", + + { + opcode: "encodeToBlock", + blockType: Scratch.BlockType.REPORTER, + text: "encode [STRING] to [DROPDOWN]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "base64", + menu: "conversionMenu", + }, + }, + }, + { + opcode: "decodeFromBlock", + blockType: Scratch.BlockType.REPORTER, + text: "decode [STRING] from [DROPDOWN]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "base64", + menu: "conversionMenu", + }, + }, + }, + + "---", + + { + opcode: "negativeReporter", + blockType: Scratch.BlockType.REPORTER, + text: "- [INPUT]", + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + }, + }, + { + opcode: "exponentBlock", + blockType: Scratch.BlockType.REPORTER, + text: "[INPUTA] ^ [INPUTB]", + disableMonitor: true, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + }, + }, + { + opcode: "rootBlock", + blockType: Scratch.BlockType.REPORTER, + text: "[INPUTA] √ [INPUTB]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + }, + }, + { + opcode: "normaliseValue", + blockType: Scratch.BlockType.REPORTER, + text: "normalise [INPUT]", + disableMonitor: true, + arguments: { + INPUT: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + { + opcode: "clampNumber", + blockType: Scratch.BlockType.REPORTER, + text: "clamp [INPUTA] between [INPUTB] and [INPUTC]", + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + INPUTB: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "25", + }, + INPUTC: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + }, + }, + + "---", + + { + opcode: "setVariableTo", + blockType: Scratch.BlockType.COMMAND, + text: "set variable [INPUTA] to [INPUTB]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "0", + }, + }, + }, + { + opcode: "changeVariableBy", + blockType: Scratch.BlockType.COMMAND, + text: "change variable [INPUTA] by [INPUTB]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1", + }, + }, + }, + { + opcode: "getVariable", + blockType: Scratch.BlockType.REPORTER, + text: "variable [INPUT]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + }, + }, + { + opcode: "deleteVariable", + blockType: Scratch.BlockType.COMMAND, + text: "delete variable [INPUT]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + }, + }, + { + opcode: "deleteAllVariables", + blockType: Scratch.BlockType.COMMAND, + text: "delete all variables", + hideFromPalette: hideLegacyBlocks, + }, + { + opcode: "listVariables", + blockType: Scratch.BlockType.REPORTER, + text: "list active variables", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + }, + + "---", + + { + opcode: "greenFlag", + blockType: Scratch.BlockType.COMMAND, + text: "green flag", + hideFromPalette: hideLegacyBlocks, + }, + { + opcode: "setUsername", + blockType: Scratch.BlockType.COMMAND, + text: "set username to [INPUT]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "LilyMakesThings", + }, + }, + }, + + "---", + + { + opcode: "setSpriteSVG", + blockType: Scratch.BlockType.COMMAND, + text: "replace SVG data for costume [INPUTA] with [INPUTB]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + + "---", + + { + opcode: "alertBlock", + blockType: Scratch.BlockType.COMMAND, + text: "alert [STRING]", + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "A red spy is in the base!", + }, + }, + }, + { + opcode: "inputPromptBlock", + blockType: Scratch.BlockType.REPORTER, + text: "prompt [STRING]", + hideFromPalette: hideLegacyBlocks, + disableMonitor: true, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "The code is 1, 1, 1.. err... 1!", + }, + }, + }, + { + opcode: "confirmationBlock", + blockType: Scratch.BlockType.BOOLEAN, + text: "confirm [STRING]", + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Are you the red spy?", + }, + }, + }, + + "---", + + { + opcode: "goToLink", + blockType: Scratch.BlockType.COMMAND, + text: "open link [INPUT] in new tab", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "redirectToLink", + blockType: Scratch.BlockType.COMMAND, + text: "redirect to link [INPUT]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + + "---", + + { + opcode: "setClipboard", + blockType: Scratch.BlockType.COMMAND, + text: "set [STRING] to clipboard", + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + }, + }, + { + opcode: "readClipboard", + blockType: Scratch.BlockType.REPORTER, + text: "clipboard", + hideFromPalette: hideLegacyBlocks, + }, + + "---", + + { + opcode: "isUserMobile", + blockType: Scratch.BlockType.BOOLEAN, + text: "is mobile?", + }, + { + opcode: "screenReporter", + blockType: Scratch.BlockType.REPORTER, + text: "screen [DROPDOWN]", + disableMonitor: true, + arguments: { + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "width", + menu: "screenReporterMenu", + }, + }, + }, + { + opcode: "windowReporter", + blockType: Scratch.BlockType.REPORTER, + text: "window [DROPDOWN]", + disableMonitor: true, + arguments: { + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "width", + menu: "screenReporterMenu", + }, + }, + }, + { + opcode: "osBrowserDetails", + blockType: Scratch.BlockType.REPORTER, + text: "get [DROPDOWN] of user", + disableMonitor: true, + arguments: { + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "operating system", + menu: "osBrowserMenu", + }, + }, + }, + { + opcode: "projectURL", + blockType: Scratch.BlockType.REPORTER, + text: "project URL", + disableMonitor: true, + }, + + "---", + + { + opcode: "consoleLog", + blockType: Scratch.BlockType.COMMAND, + text: "console [DROPDOWN] [INPUT]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + DROPDOWN: { + type: Scratch.ArgumentType.STRING, + defaultValue: "log", + menu: "consoleLogMenu", + }, + INPUT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Apple", + }, + }, + }, + { + opcode: "clearConsole", + blockType: Scratch.BlockType.COMMAND, + text: "clear console", + hideFromPalette: hideLegacyBlocks, + }, + + "---", + + { + opcode: "commentHat", + blockType: Scratch.BlockType.HAT, + text: "// [STRING]", + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "comment", + }, + }, + }, + { + opcode: "commentCommand", + blockType: Scratch.BlockType.COMMAND, + text: "// [STRING]", + hideFromPalette: hideLegacyBlocks, + arguments: { + STRING: { + type: Scratch.ArgumentType.STRING, + defaultValue: "comment", + }, + }, + }, + { + opcode: "commentString", + blockType: Scratch.BlockType.REPORTER, + text: "// [INPUTA] [INPUTB]", + disableMonitor: true, + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "comment", + }, + INPUTB: { + type: Scratch.ArgumentType.STRING, + defaultValue: "input", + }, + }, + }, + { + opcode: "commentBool", + blockType: Scratch.BlockType.BOOLEAN, + text: "// [INPUTA] [INPUTB]", + hideFromPalette: hideLegacyBlocks, + arguments: { + INPUTA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "comment", + }, + INPUTB: { + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + + "---", + + { + func: "showLegacyBlocks", + blockType: Scratch.BlockType.BUTTON, + text: "Show Legacy Blocks", + hideFromPalette: !hideLegacyBlocks, + }, + { + func: "hideLegacyBlocks", + blockType: Scratch.BlockType.BUTTON, + text: "Hide Legacy Blocks", + hideFromPalette: hideLegacyBlocks, + }, + ], + menus: { + conversionMenu: { + acceptReporters: true, + items: ["base64", "binary"], + }, + trueFalseMenu: { + acceptReporters: true, + items: ["true", "false", "random"], + }, + screenReporterMenu: { + acceptReporters: true, + items: ["width", "height"], + }, + windowReporterMenu: { + acceptReporters: true, + items: ["width", "height"], + }, + stringCheckMenu: { + acceptReporters: true, + items: ["text", "number", "uppercase", "lowercase"], + }, + osBrowserMenu: { + acceptReporters: true, + items: ["operating system", "browser"], + }, + consoleLogMenu: { + acceptReporters: false, + items: ["log", "error", "warn"], + }, + colorMenu: { + acceptReporters: true, + items: [ + "color", + "fisheye", + "whirl", + "pixelate", + "mosaic", + "brightness", + "ghost", + ], + }, + }, + }; + } + + showLegacyBlocks() { + if ( + confirm( + "Are you sure you want to show legacy blocks? \n \n These blocks were removed because they were buggy or implemented better in other extensions." + ) + ) { + hideLegacyBlocks = false; + Scratch.vm.extensionManager.refreshBlocks(); + } else { + // + } + } + + hideLegacyBlocks() { + hideLegacyBlocks = true; + Scratch.vm.extensionManager.refreshBlocks(); + } + + whenBooleanHat(args) { + return args.INPUT; + } + + whenKeyString(args, util) { + return util.ioQuery("keyboard", "getKeyIsDown", [args.KEY_OPTION]); + } + + keyStringPressed(args, util) { + return util.ioQuery("keyboard", "getKeyIsDown", [args.KEY_OPTION]); + } + + trueFalseBoolean(args) { + if (args.TRUEFALSE === "random") return Math.random() > 0.5; + if (args.TRUEFALSE === "true") return true; + return false; + } + + stringIf(args) { + if (args.BOOLEAN) return args.INPUTA; + return ""; + } + + stringIfElse(args) { + if (args.BOOLEAN) return args.INPUTA; + return args.INPUTB; + } + + getEffectValue(args, util) { + return util.target.effects[args.INPUT]; + } + + clonesBeingUsed(args, util) { + return Scratch.vm.runtime._cloneCounter; + } + + isClone(args, util) { + return util.target.isOriginal ? false : true; + } + + spriteClicked(args, util) { + return ( + util.ioQuery("mouse", "getIsDown") && + util.target.isTouchingObject("_mouse_") + ); + } + + lettersToOf(args) { + var string = args.STRING.toString(); + var input1 = args.INPUTA - 1; + var input2 = args.INPUTB; + return string.slice(input1, input2); + } + + replaceWords(args) { + var input1 = args.INPUTA; + var input2 = args.INPUTB; + var string = args.STRING; + return string.replace(input1, input2); + } + + findIndexOfString(args) { + var input1 = args.INPUTA; + var input2 = args.INPUTB; + if (input2.includes(input1)) return input2.indexOf(input1) + 1; + return ""; + } + + itemOfFromString(args, util) { + var input1 = args.INPUTA - 1; + var input2 = String(args.INPUTB); + var input3 = args.INPUTC; + var output = input2.split(input3)[input1] || ""; + return output; + } + + stringToUpperCase(args) { + return args.STRING.toUpperCase(); + } + + stringToLowerCase(args) { + return args.STRING.toLowerCase(); + } + + reverseString(args) { + var input = args.STRING; + var splitInput = input.split(""); + var reversedInput = splitInput.reverse(); + var joinedArray = reversedInput.join(""); + return joinedArray; + } + + norBoolean(args) { + return !(args.INPUTA || args.INPUTB); + } + + xorBoolean(args) { + return args.INPUTA !== args.INPUTB; + } + + xnorBoolean(args) { + return args.INPUTA === args.INPUTB; + } + + nandBoolean(args) { + return !(args.INPUTA && args.INPUTB); + } + + stringReporter(args) { + return args.STRING; + } + + colourHex(args) { + return args.COLOUR; + } + + angleReporter(args) { + return args.ANGLE; + } + + matrixReporter(args) { + return args.MATRIX; + } + + noteReporter(args) { + return args.NOTE; + } + + newlineCharacter() { + return "\n"; + } + + equalsExactly(args) { + return args.ONE === args.TWO; + } + + notEqualTo(args) { + return args.INPUTA != args.INPUTB; + } + + moreThanEqual(args) { + return args.INPUTA >= args.INPUTB; + } + + lessThanEqual(args) { + return args.INPUTA <= args.INPUTB; + } + + stringCheckBoolean(args) { + const input = args.INPUT; + const dropdown = args.DROPDOWN; + if (dropdown === "text") return isNaN(input); + if (dropdown === "number") return !isNaN(input); + if (dropdown === "uppercase") return input == input.toUpperCase(); + if (dropdown === "lowercase") return input == input.toLowerCase(); + return false; + } + + encodeToBlock(args) { + if (args.STRING === "") return ""; + if (args.DROPDOWN === "base64") return btoa(args.STRING); + if (args.DROPDOWN === "binary") { + return args.STRING.split("") + .map(function (char) { + return char.charCodeAt(0).toString(2); + }) + .join(" "); + } + return ""; + } + + decodeFromBlock(args) { + if (args.STRING === "") return ""; + if (args.DROPDOWN === "base64") return atob(args.STRING); + if (args.DROPDOWN === "binary") { + var output = args.STRING.toString(); + return output + .split(" ") + .map((x) => (x = String.fromCharCode(parseInt(x, 2)))) + .join(""); + } + return ""; + } + + negativeReporter(args) { + return args.INPUT * -1; + } + + exponentBlock(args) { + return Math.pow(args.INPUTA, args.INPUTB); + } + + rootBlock(args) { + return Math.pow(args.INPUTB, 1 / args.INPUTA); + } + + normaliseValue(args) { + var input1 = args.INPUT; + var input2 = Math.abs(input1); + var output = input1 / input2; + if (isNaN(output)) return "0"; + return output; + } + + clampNumber(args) { + var input1 = args.INPUTA; + var input2 = args.INPUTB; + var input3 = args.INPUTC; + return Math.min(Math.max(input1, input2), input3); + } + + setVariableTo(args) { + vars["variables"][args.INPUTA] = args.INPUTB; + } + + changeVariableBy(args) { + if (args.INPUTA in vars["variables"]) { + var prev = vars["variables"][args.INPUTA]; + var next = args.INPUTB; + vars["variables"][args.INPUTA] = prev + next; + } else { + vars["variables"][args.INPUTA] = args.INPUTB; + } + } + + getVariable(args) { + if (args.INPUT in vars["variables"]) return vars["variables"][args.INPUT]; + return ""; + } + + deleteVariable(args) { + Reflect.deleteProperty(vars["variables"], args.INPUT); + } + + deleteAllVariables() { + Reflect.deleteProperty(vars, "variables"); + vars["variables"] = {}; + } + + greenFlag(args, util) { + util.runtime.greenFlag(); + } + + setUsername(args, util) { + util.runtime.ioDevices.userData._username = args.INPUT; + } + + setSpriteSVG(args, util) { + try { + Scratch.vm.runtime.renderer.updateSVGSkin( + util.target.sprite.costumes[args.INPUTA - 1].skinId, + args.INPUTB + ); + } catch (error) { + return; + } + Scratch.vm.emitTargetsUpdate(); + } + + alertBlock(args) { + alert(args.STRING); + } + + inputPromptBlock(args) { + return prompt(args.STRING); + } + + confirmationBlock(args) { + return confirm(args.STRING); + } + + goToLink(args) { + Scratch.openWindow(args.INPUT); + } + + redirectToLink(args) { + Scratch.redirect(args.INPUT); + } + + setClipboard(args) { + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(args.STRING); + } + } + + readClipboard(args) { + if (navigator.clipboard && navigator.clipboard.readText) { + return Scratch.canReadClipboard().then((allowed) => { + if (allowed) { + return navigator.clipboard.readText(); + } + return ""; + }); + } + return ""; + } + + isUserMobile(args, util) { + return navigator.userAgent.includes("Mobile"); + } + + screenReporter(args) { + if (args.DROPDOWN === "width") return screen.width; + if (args.DROPDOWN === "height") return screen.height; + return ""; + } + + windowReporter(args) { + if (args.DROPDOWN === "width") return window.innerWidth; + if (args.DROPDOWN === "height") return window.innerHeight; + return ""; + } + + osBrowserDetails(args) { + var user = navigator.userAgent; + if (args.DROPDOWN === "operating system") { + if (user.includes("Mac OS")) return "macOS"; + if (user.includes("CrOS")) return "ChromeOS"; + if (user.includes("Linux")) return "Linux"; + if (user.includes("Windows")) return "Windows"; + if (user.includes("iPad")) return "iOS"; + if (user.includes("iPod")) return "iOS"; + if (user.includes("iPhone")) return "iOS"; + if (user.includes("Android")) return "Android"; + return "Other"; + } + if (args.DROPDOWN === "browser") { + if (user.includes("Chrome")) return "Chrome"; + if (user.includes("MSIE")) return "Internet Explorer"; + if (user.includes("Firefox")) return "Firefox"; + if (user.includes("Safari")) return "Safari"; + return "Other"; + } + } + + projectURL() { + return window.location.href; + } + + consoleLog(args) { + if (args.DROPDOWN === "log") { + console.log(args.INPUT); + } else if (args.DROPDOWN === "error") { + console.error(args.INPUT); + } else if (args.DROPDOWN === "warn") { + console.warn(args.INPUT); + } + } + + clearConsole() { + console.clear(); + } + + commentHat() { + // no-op + } + + commentCommand() { + // no-op + } + + commentString(args) { + return args.INPUT; + } + + commentBool(args) { + return args.INPUT || false; + } + } + Scratch.extensions.register(new LMSUtils()); +})(Scratch); diff --git a/extensions/Longboost/color_channels.js b/extensions/Longboost/color_channels.js index 71c8aea841..0d0c6e2523 100644 --- a/extensions/Longboost/color_channels.js +++ b/extensions/Longboost/color_channels.js @@ -1,91 +1,97 @@ -(function(Scratch) { - 'use strict'; +// Name: RGB Channels +// ID: lbdrawtest +// Description: Only render or stamp certain RGB channels. + +(function (Scratch) { + "use strict"; const renderer = Scratch.vm.renderer; const gl = renderer._gl; let channel_array = [true, true, true, true]; class LBdrawtest { getInfo() { return { - id: 'lbdrawtest', - name: 'RGB Channels', - menuIconURI: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzMyIgaGVpZ2h0PSIzMyIgdmlld0JveD0iMCwwLDMzLDMzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjIzLjUsLTE2My41KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjI0LDE4MGMwLC04LjgzNjU2IDcuMTYzNDQsLTE2IDE2LC0xNmM4LjgzNjU2LDAgMTYsNy4xNjM0NCAxNiwxNmMwLDguODM2NTYgLTcuMTYzNDQsMTYgLTE2LDE2Yy04LjgzNjU2LDAgLTE2LC03LjE2MzQ0IC0xNiwtMTZ6IiBmaWxsPSIjYWFhYWFhIiBzdHJva2U9IiM4ODg4ODgiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0yMzMuOTAyMDQsMTgxLjQ4NjkyYzAsLTQuNDE4MjggMy41ODE3MiwtOCA4LC04YzQuNDE4MjgsMCA4LDMuNTgxNzIgOCw4YzAsNC40MTgyOCAtMy41ODE3Miw4IC04LDhjLTQuNDE4MjgsMCAtOCwtMy41ODE3MiAtOCwtOHoiIGZpbGw9IiMwMDAwZmYiIHN0cm9rZT0iIzNjMDBmZiIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjMxLjk2MjQ1LDE3OS40NDEzNWMwLC00LjQxODI4IDMuNTgxNzIsLTggOCwtOGM0LjQxODI4LDAgOCwzLjU4MTcyIDgsOGMwLDQuNDE4MjggLTMuNTgxNzIsOCAtOCw4Yy00LjQxODI4LDAgLTgsLTMuNTgxNzIgLTgsLTh6IiBmaWxsPSIjMDBmZjAwIiBzdHJva2U9IiMwMGZmM2QiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0yMzAuMjI1OSwxNzcuNjIwOThjMCwtNC40MTgyOCAzLjU4MTcyLC04IDgsLThjNC40MTgyOCwwIDgsMy41ODE3MiA4LDhjMCw0LjQxODI4IC0zLjU4MTcyLDggLTgsOGMtNC40MTgyOCwwIC04LC0zLjU4MTcyIC04LC04eiIgZmlsbD0iI2ZmMDAwMCIgc3Ryb2tlPSIjZmYzZDAwIiBzdHJva2Utd2lkdGg9IjEiLz48L2c+PC9nPjwvc3ZnPgo=', - blockIconURI: 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMC40MjYxNCIgaGVpZ2h0PSIyMC42MTU5NCIgdmlld0JveD0iMCwwLDIwLjQyNjE0LDIwLjYxNTk0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI5LjcyNTksLTE2OS4xMjA5OCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIzMy45MDIwNCwxODEuNDg2OTJjMCwtNC40MTgyOCAzLjU4MTcyLC04IDgsLThjNC40MTgyOCwwIDgsMy41ODE3MiA4LDhjMCw0LjQxODI4IC0zLjU4MTcyLDggLTgsOGMtNC40MTgyOCwwIC04LC0zLjU4MTcyIC04LC04eiIgZmlsbD0iIzAwMDBmZiIgc3Ryb2tlPSIjM2MwMGZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjxwYXRoIGQ9Ik0yMzEuOTYyNDUsMTc5LjQ0MTM1YzAsLTQuNDE4MjggMy41ODE3MiwtOCA4LC04YzQuNDE4MjgsMCA4LDMuNTgxNzIgOCw4YzAsNC40MTgyOCAtMy41ODE3Miw4IC04LDhjLTQuNDE4MjgsMCAtOCwtMy41ODE3MiAtOCwtOHoiIGZpbGw9IiMwMGZmMDAiIHN0cm9rZT0iIzAwZmYzZCIgc3Ryb2tlLXdpZHRoPSIxIi8+PHBhdGggZD0iTTIzMC4yMjU5LDE3Ny42MjA5OGMwLC00LjQxODI4IDMuNTgxNzIsLTggOCwtOGM0LjQxODI4LDAgOCwzLjU4MTcyIDgsOGMwLDQuNDE4MjggLTMuNTgxNzIsOCAtOCw4Yy00LjQxODI4LDAgLTgsLTMuNTgxNzIgLTgsLTh6IiBmaWxsPSIjZmYwMDAwIiBzdHJva2U9IiNmZjNkMDAiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+Cg==', - color1: '#aaaaaa', - color2: '#888888', - color3: '#888888', + id: "lbdrawtest", + name: "RGB Channels", + menuIconURI: + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzMyIgaGVpZ2h0PSIzMyIgdmlld0JveD0iMCwwLDMzLDMzIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjIzLjUsLTE2My41KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjI0LDE4MGMwLC04LjgzNjU2IDcuMTYzNDQsLTE2IDE2LC0xNmM4LjgzNjU2LDAgMTYsNy4xNjM0NCAxNiwxNmMwLDguODM2NTYgLTcuMTYzNDQsMTYgLTE2LDE2Yy04LjgzNjU2LDAgLTE2LC03LjE2MzQ0IC0xNiwtMTZ6IiBmaWxsPSIjYWFhYWFhIiBzdHJva2U9IiM4ODg4ODgiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0yMzMuOTAyMDQsMTgxLjQ4NjkyYzAsLTQuNDE4MjggMy41ODE3MiwtOCA4LC04YzQuNDE4MjgsMCA4LDMuNTgxNzIgOCw4YzAsNC40MTgyOCAtMy41ODE3Miw4IC04LDhjLTQuNDE4MjgsMCAtOCwtMy41ODE3MiAtOCwtOHoiIGZpbGw9IiMwMDAwZmYiIHN0cm9rZT0iIzNjMDBmZiIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjMxLjk2MjQ1LDE3OS40NDEzNWMwLC00LjQxODI4IDMuNTgxNzIsLTggOCwtOGM0LjQxODI4LDAgOCwzLjU4MTcyIDgsOGMwLDQuNDE4MjggLTMuNTgxNzIsOCAtOCw4Yy00LjQxODI4LDAgLTgsLTMuNTgxNzIgLTgsLTh6IiBmaWxsPSIjMDBmZjAwIiBzdHJva2U9IiMwMGZmM2QiIHN0cm9rZS13aWR0aD0iMSIvPjxwYXRoIGQ9Ik0yMzAuMjI1OSwxNzcuNjIwOThjMCwtNC40MTgyOCAzLjU4MTcyLC04IDgsLThjNC40MTgyOCwwIDgsMy41ODE3MiA4LDhjMCw0LjQxODI4IC0zLjU4MTcyLDggLTgsOGMtNC40MTgyOCwwIC04LC0zLjU4MTcyIC04LC04eiIgZmlsbD0iI2ZmMDAwMCIgc3Ryb2tlPSIjZmYzZDAwIiBzdHJva2Utd2lkdGg9IjEiLz48L2c+PC9nPjwvc3ZnPgo=", + blockIconURI: + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMC40MjYxNCIgaGVpZ2h0PSIyMC42MTU5NCIgdmlld0JveD0iMCwwLDIwLjQyNjE0LDIwLjYxNTk0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI5LjcyNTksLTE2OS4xMjA5OCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIzMy45MDIwNCwxODEuNDg2OTJjMCwtNC40MTgyOCAzLjU4MTcyLC04IDgsLThjNC40MTgyOCwwIDgsMy41ODE3MiA4LDhjMCw0LjQxODI4IC0zLjU4MTcyLDggLTgsOGMtNC40MTgyOCwwIC04LC0zLjU4MTcyIC04LC04eiIgZmlsbD0iIzAwMDBmZiIgc3Ryb2tlPSIjM2MwMGZmIiBzdHJva2Utd2lkdGg9IjAuNSIvPjxwYXRoIGQ9Ik0yMzEuOTYyNDUsMTc5LjQ0MTM1YzAsLTQuNDE4MjggMy41ODE3MiwtOCA4LC04YzQuNDE4MjgsMCA4LDMuNTgxNzIgOCw4YzAsNC40MTgyOCAtMy41ODE3Miw4IC04LDhjLTQuNDE4MjgsMCAtOCwtMy41ODE3MiAtOCwtOHoiIGZpbGw9IiMwMGZmMDAiIHN0cm9rZT0iIzAwZmYzZCIgc3Ryb2tlLXdpZHRoPSIxIi8+PHBhdGggZD0iTTIzMC4yMjU5LDE3Ny42MjA5OGMwLC00LjQxODI4IDMuNTgxNzIsLTggOCwtOGM0LjQxODI4LDAgOCwzLjU4MTcyIDgsOGMwLDQuNDE4MjggLTMuNTgxNzIsOCAtOCw4Yy00LjQxODI4LDAgLTgsLTMuNTgxNzIgLTgsLTh6IiBmaWxsPSIjZmYwMDAwIiBzdHJva2U9IiNmZjNkMDAiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+Cg==", + color1: "#aaaaaa", + color2: "#888888", + color3: "#888888", blocks: [ { - opcode: 'true', + opcode: "true", blockType: Scratch.BlockType.BOOLEAN, - text: 'true' + text: "true", }, { - opcode: 'false', + opcode: "false", blockType: Scratch.BlockType.BOOLEAN, - text: 'false', - hideFromPalette: true + text: "false", + hideFromPalette: true, }, { - opcode: 'enabledCheck', + opcode: "enabledCheck", blockType: Scratch.BlockType.BOOLEAN, - text: '[COLOR] channel enabled?', + text: "[COLOR] channel enabled?", arguments: { COLOR: { type: Scratch.ArgumentType.STRING, - menu: 'COLOR_MENU' - } - } + menu: "COLOR_MENU", + }, + }, }, { - opcode: 'draw', + opcode: "draw", blockType: Scratch.BlockType.COMMAND, - text: 'only draw colors:[R]green:[G]blue:[B]', + text: "only draw colors:[R]green:[G]blue:[B]", arguments: { R: { - type: Scratch.ArgumentType.BOOLEAN + type: Scratch.ArgumentType.BOOLEAN, }, G: { - type: Scratch.ArgumentType.BOOLEAN + type: Scratch.ArgumentType.BOOLEAN, }, B: { - type: Scratch.ArgumentType.BOOLEAN - } - } + type: Scratch.ArgumentType.BOOLEAN, + }, + }, }, { - opcode: 'drawOneColor', + opcode: "drawOneColor", blockType: Scratch.BlockType.COMMAND, - text: 'only draw [COLOR]', + text: "only draw [COLOR]", arguments: { COLOR: { type: Scratch.ArgumentType.STRING, - menu: 'COLOR_MENU' - } - } + menu: "COLOR_MENU", + }, + }, }, { - opcode: 'drawDepth', + opcode: "drawDepth", blockType: Scratch.BlockType.COMMAND, - text: 'enable depth mask?[DRAW]', + text: "enable depth mask?[DRAW]", hideFromPalette: true, arguments: { DRAW: { - type: Scratch.ArgumentType.BOOLEAN - } - } + type: Scratch.ArgumentType.BOOLEAN, + }, + }, }, { - opcode: 'clearEffects', + opcode: "clearEffects", blockType: Scratch.BlockType.COMMAND, - text: 'clear color draw effects', - } + text: "clear color draw effects", + }, ], menus: { COLOR_MENU: { acceptReporters: true, - items: ['red', 'green', 'blue'] - } - } + items: ["red", "green", "blue"], + }, + }, }; } @@ -97,44 +103,63 @@ return false; } - enabledCheck({COLOR}) { - if ((COLOR == 'red' && channel_array[0]) || (COLOR == 'green' && channel_array[1]) || (COLOR == 'blue' && channel_array[2])) { + enabledCheck({ COLOR }) { + if ( + (COLOR == "red" && channel_array[0]) || + (COLOR == "green" && channel_array[1]) || + (COLOR == "blue" && channel_array[2]) + ) { return true; } else { return false; } } - draw({R, G, B}) { + draw({ R, G, B }) { channel_array = [R, G, B, true]; - gl.colorMask(channel_array[0], channel_array[1], channel_array[2], channel_array[3]); + gl.colorMask( + channel_array[0], + channel_array[1], + channel_array[2], + channel_array[3] + ); Scratch.vm.renderer.dirty = true; } - drawOneColor({COLOR}) { - if (COLOR == 'red') { + drawOneColor({ COLOR }) { + if (COLOR == "red") { channel_array = [true, false, false, true]; - } else if (COLOR == 'green') { + } else if (COLOR == "green") { channel_array = [false, true, false, true]; } else { channel_array = [false, false, true, true]; } - gl.colorMask(channel_array[0], channel_array[1], channel_array[2], channel_array[3]); + gl.colorMask( + channel_array[0], + channel_array[1], + channel_array[2], + channel_array[3] + ); Scratch.vm.renderer.dirty = true; } - drawDepth({DRAW}) { + drawDepth({ DRAW }) { gl.depthMask(DRAW); Scratch.vm.renderer.dirty = true; } clearEffects() { channel_array = [true, true, true, true]; - gl.colorMask(channel_array[0], channel_array[1], channel_array[2], channel_array[3]); + gl.colorMask( + channel_array[0], + channel_array[1], + channel_array[2], + channel_array[3] + ); gl.depthMask(true); Scratch.vm.renderer.dirty = true; } } Scratch.extensions.register(new LBdrawtest()); -})(Scratch); \ No newline at end of file +})(Scratch); diff --git a/extensions/Medericoder/textcase.js b/extensions/Medericoder/textcase.js index 2745ad484c..c08c3577be 100644 --- a/extensions/Medericoder/textcase.js +++ b/extensions/Medericoder/textcase.js @@ -15,332 +15,384 @@ */ (function (Scratch) { - 'use strict'; - function funchangecase (text, format) { - if (format === 'uppercase') { - return text.toString().toUpperCase(); - } else if (format === 'lowercase') { - return text.toString().toLowerCase(); - } else if (format === 'invert') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (text.toString()[i] === text.toString()[i].toUpperCase()) { - x += text.toString()[i].toLowerCase(); - } else { - x += text.toString()[i].toUpperCase(); - } - } - return x; - } else if (format === 'begin') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i == 0) { - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i]; - } - } - return x; - } else if (format === 'begin words') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i == 0) { - x += text.toString()[i].toUpperCase(); - } else if (text.toString()[i - 1].toLowerCase() === text.toString()[i - 1].toUpperCase()) { - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i]; - } - } - return x; - } else if (format === 'begin sentences') { - let x = ""; - let mybool = true; - for (let i = 0; i < text.length; i++) { - if (mybool){ - x += text.toString()[i].toUpperCase(); - if (!(text.toString()[i].toLowerCase() === text.toString()[i].toUpperCase())){ - mybool = false; - } - } else if (text.toString()[i] == '.' || text.toString()[i] == '!' || text.toString()[i] == '?') { - x += text.toString()[i]; - mybool = true; - } else { - x += text.toString()[i]; - } - } - return x; - } else if (format === 'begin only') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i == 0) { - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === 'begin words only') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i == 0) { - x += text.toString()[i].toUpperCase(); - } else if (text.toString()[i - 1].toLowerCase() === text.toString()[i - 1].toUpperCase()) { - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === 'begin sentences only') { - let x = ""; - let mybool = true; - for (let i = 0; i < text.length; i++) { - if (mybool){ - x += text.toString()[i].toUpperCase(); - if (!(text.toString()[i].toLowerCase() === text.toString()[i].toUpperCase())){ - mybool = false; - } - } else if (text.toString()[i] == '.' || text.toString()[i] == '!' || text.toString()[i] == '?') { - x += text.toString()[i]; - mybool = true; - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === '1/2 up') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i % 2 == 0){ - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === '1/2 low') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (i % 2 == 1){ - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === '1/2 up letters only') { - let x = ""; - let noletters = 0; - for (let i = 0; i < text.length; i++) { - if (text.toString()[i].toUpperCase() === text.toString()[i].toLowerCase()){ - noletters += 1; - x += text.toString()[i]; - } else if ((i - noletters) % 2 == 0){ - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === '1/2 low letters only') { - let x = ""; - let noletters = 0; - for (let i = 0; i < text.length; i++) { - if (text.toString()[i].toUpperCase() === text.toString()[i].toLowerCase()){ - noletters += 1; - x += text.toString()[i]; - } else if ((i - noletters) % 2 == 1){ - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; - } else if (format === 'random') { - let x = ""; - for (let i = 0; i < text.length; i++) { - if (Math.random() < 0.5){ - x += text.toString()[i].toUpperCase(); - } else { - x += text.toString()[i].toLowerCase(); - } - } - return x; + "use strict"; + function funchangecase(text, format) { + if (format === "uppercase") { + return text.toString().toUpperCase(); + } else if (format === "lowercase") { + return text.toString().toLowerCase(); + } else if (format === "invert") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (text.toString()[i] === text.toString()[i].toUpperCase()) { + x += text.toString()[i].toLowerCase(); } else { - return text.toString(); + x += text.toString()[i].toUpperCase(); } - } - - function fungetcase (args) { - var low = 0; - var up = 0; - for (let i = 0; i < args.TEXT.length; i++) { - if (args.TEXT.toString()[i].toLowerCase() === args.TEXT.toString()[i].toUpperCase()) { - low += 0; - up += 0; - } else if (args.TEXT.toString()[i].toLowerCase() === args.TEXT.toString()[i]) { - low += 1; - } else { - up += 1; - } + } + return x; + } else if (format === "begin") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i == 0) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i]; + } + } + return x; + } else if (format === "begin words") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i == 0) { + x += text.toString()[i].toUpperCase(); + } else if ( + text.toString()[i - 1].toLowerCase() === + text.toString()[i - 1].toUpperCase() + ) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i]; + } + } + return x; + } else if (format === "begin sentences") { + let x = ""; + let mybool = true; + for (let i = 0; i < text.length; i++) { + if (mybool) { + x += text.toString()[i].toUpperCase(); + if ( + !( + text.toString()[i].toLowerCase() === + text.toString()[i].toUpperCase() + ) + ) { + mybool = false; + } + } else if ( + text.toString()[i] == "." || + text.toString()[i] == "!" || + text.toString()[i] == "?" + ) { + x += text.toString()[i]; + mybool = true; + } else { + x += text.toString()[i]; + } + } + return x; + } else if (format === "begin only") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i == 0) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else if (format === "begin words only") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i == 0) { + x += text.toString()[i].toUpperCase(); + } else if ( + text.toString()[i - 1].toLowerCase() === + text.toString()[i - 1].toUpperCase() + ) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else if (format === "begin sentences only") { + let x = ""; + let mybool = true; + for (let i = 0; i < text.length; i++) { + if (mybool) { + x += text.toString()[i].toUpperCase(); + if ( + !( + text.toString()[i].toLowerCase() === + text.toString()[i].toUpperCase() + ) + ) { + mybool = false; + } + } else if ( + text.toString()[i] == "." || + text.toString()[i] == "!" || + text.toString()[i] == "?" + ) { + x += text.toString()[i]; + mybool = true; + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else if (format === "1/2 up") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i % 2 == 0) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else if (format === "1/2 low") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (i % 2 == 1) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else if (format === "1/2 up letters only") { + let x = ""; + let noletters = 0; + for (let i = 0; i < text.length; i++) { + if ( + text.toString()[i].toUpperCase() === text.toString()[i].toLowerCase() + ) { + noletters += 1; + x += text.toString()[i]; + } else if ((i - noletters) % 2 == 0) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); } - if (up == 0 && low == 0) { - return ''; - } else if (up > 0 && low > 0) { - if (up == low) { - return 'mixed'; - } else if (up > low) { - return 'mixed (upper)'; - } else { - return 'mixed (lower)'; - } - } else if (up > low) { - return 'upper'; + } + return x; + } else if (format === "1/2 low letters only") { + let x = ""; + let noletters = 0; + for (let i = 0; i < text.length; i++) { + if ( + text.toString()[i].toUpperCase() === text.toString()[i].toLowerCase() + ) { + noletters += 1; + x += text.toString()[i]; + } else if ((i - noletters) % 2 == 1) { + x += text.toString()[i].toUpperCase(); } else { - return 'lower'; + x += text.toString()[i].toLowerCase(); } + } + return x; + } else if (format === "random") { + let x = ""; + for (let i = 0; i < text.length; i++) { + if (Math.random() < 0.5) { + x += text.toString()[i].toUpperCase(); + } else { + x += text.toString()[i].toLowerCase(); + } + } + return x; + } else { + return text.toString(); } + } - class TextCase { - getInfo() { - return { - id: 'medericodertextcase', - name: 'Text Case', - blocks: [ - { - opcode: 'strictlyequal', - blockType: Scratch.BlockType.BOOLEAN, - text: '[TEXT1] ≡ [TEXT2]', - arguments: { - TEXT1: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello' - }, - TEXT2: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'hello' - } - } - }, - { - opcode: 'quasiequal', - blockType: Scratch.BlockType.BOOLEAN, - text: '[TEXT1] ≈ [TEXT2]', - arguments: { - TEXT1: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello' - }, - TEXT2: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'hello' - } - } - }, - '---', - { - opcode: 'changecase', - blockType: Scratch.BlockType.REPORTER, - text: 'convert [TEXT] to case [FORMAT]', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Where is the apple? It is here!' - }, - FORMAT: { - type: Scratch.ArgumentType.STRING, - menu: 'FORMAT_MENU' - } - } - }, - { - opcode: 'getcase', - blockType: Scratch.BlockType.REPORTER, - text: 'get case from [TEXT]', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'hello world' - } - } - }, - { - opcode: 'iscase', - blockType: Scratch.BlockType.BOOLEAN, - text: 'is [TEXT] [FORMAT]', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'hello world' - }, - FORMAT: { - type: Scratch.ArgumentType.STRING, - menu: 'FORMAT_MENU' - } - } - }, - '---', - { - opcode: 'glitch', - blockType: Scratch.BlockType.REPORTER, - text: 'glitch [TEXT] level [PROBA]%', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello' - }, - PROBA: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 10 - } - } - } - ], - menus: { - FORMAT_MENU: { - acceptReporters: true, - items: ['uppercase', 'lowercase', 'invert', 'begin', 'begin words', 'begin sentences', 'begin only', 'begin words only', 'begin sentences only', '1/2 up', '1/2 low', '1/2 up letters only', '1/2 low letters only', 'random', 'identity'] - } - } - }; - } + function fungetcase(args) { + var low = 0; + var up = 0; + for (let i = 0; i < args.TEXT.length; i++) { + if ( + args.TEXT.toString()[i].toLowerCase() === + args.TEXT.toString()[i].toUpperCase() + ) { + low += 0; + up += 0; + } else if ( + args.TEXT.toString()[i].toLowerCase() === args.TEXT.toString()[i] + ) { + low += 1; + } else { + up += 1; + } + } + if (up == 0 && low == 0) { + return ""; + } else if (up > 0 && low > 0) { + if (up == low) { + return "mixed"; + } else if (up > low) { + return "mixed (upper)"; + } else { + return "mixed (lower)"; + } + } else if (up > low) { + return "upper"; + } else { + return "lower"; + } + } - changecase (args) { - return funchangecase(args.TEXT, args.FORMAT); - } + class TextCase { + getInfo() { + return { + id: "medericodertextcase", + name: "Text Case", + blocks: [ + { + opcode: "strictlyequal", + blockType: Scratch.BlockType.BOOLEAN, + text: "[TEXT1] ≡ [TEXT2]", + arguments: { + TEXT1: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello", + }, + TEXT2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "hello", + }, + }, + }, + { + opcode: "quasiequal", + blockType: Scratch.BlockType.BOOLEAN, + text: "[TEXT1] ≈ [TEXT2]", + arguments: { + TEXT1: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello", + }, + TEXT2: { + type: Scratch.ArgumentType.STRING, + defaultValue: "hello", + }, + }, + }, + "---", + { + opcode: "changecase", + blockType: Scratch.BlockType.REPORTER, + text: "convert [TEXT] to case [FORMAT]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Where is the apple? It is here!", + }, + FORMAT: { + type: Scratch.ArgumentType.STRING, + menu: "FORMAT_MENU", + }, + }, + }, + { + opcode: "getcase", + blockType: Scratch.BlockType.REPORTER, + text: "get case from [TEXT]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "hello world", + }, + }, + }, + { + opcode: "iscase", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [TEXT] [FORMAT]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "hello world", + }, + FORMAT: { + type: Scratch.ArgumentType.STRING, + menu: "FORMAT_MENU", + }, + }, + }, + "---", + { + opcode: "glitch", + blockType: Scratch.BlockType.REPORTER, + text: "glitch [TEXT] level [PROBA]%", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello", + }, + PROBA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 10, + }, + }, + }, + ], + menus: { + FORMAT_MENU: { + acceptReporters: true, + items: [ + "uppercase", + "lowercase", + "invert", + "begin", + "begin words", + "begin sentences", + "begin only", + "begin words only", + "begin sentences only", + "1/2 up", + "1/2 low", + "1/2 up letters only", + "1/2 low letters only", + "random", + "identity", + ], + }, + }, + }; + } - getcase (args) { - return fungetcase(args); - } + changecase(args) { + return funchangecase(args.TEXT, args.FORMAT); + } - iscase (args) { - if (args.FORMAT == 'random'){ - return fungetcase(args).includes('mixed'); - } else { - return args.TEXT.toString() === funchangecase(args.TEXT, args.FORMAT); - } - } + getcase(args) { + return fungetcase(args); + } - strictlyequal (args) { - return args.TEXT1 === args.TEXT2; - } + iscase(args) { + if (args.FORMAT == "random") { + return fungetcase(args).includes("mixed"); + } else { + return args.TEXT.toString() === funchangecase(args.TEXT, args.FORMAT); + } + } - quasiequal (args) { - return args.TEXT1.toString().toLowerCase() === args.TEXT2.toString().toLowerCase(); - } + strictlyequal(args) { + return args.TEXT1 === args.TEXT2; + } + + quasiequal(args) { + return ( + args.TEXT1.toString().toLowerCase() === + args.TEXT2.toString().toLowerCase() + ); + } - glitch (args) { - var x = ''; - for (let i = 0; i < args.TEXT.toString().length; i++){ - if (Math.random() * 100 < args.PROBA){ - x += funchangecase(args.TEXT.toString()[i],'invert'); - } else { - x += args.TEXT.toString()[i]; - } - } - return x; + glitch(args) { + var x = ""; + for (let i = 0; i < args.TEXT.toString().length; i++) { + if (Math.random() * 100 < args.PROBA) { + x += funchangecase(args.TEXT.toString()[i], "invert"); + } else { + x += args.TEXT.toString()[i]; } + } + return x; } - Scratch.extensions.register(new TextCase()); + } + Scratch.extensions.register(new TextCase()); })(Scratch); diff --git a/extensions/NOname-awa/cn-number.js b/extensions/NOname-awa/cn-number.js index de029ed77d..a3db7e1208 100644 --- a/extensions/NOname-awa/cn-number.js +++ b/extensions/NOname-awa/cn-number.js @@ -1,32 +1,46 @@ (function (Scratch) { - 'use strict'; + "use strict"; - let Number2, null2, units, Number_in, uppercase, i, N_Z, o, j, After_decimal_point, unit, k, C_Number, m, n; + let Number2, + null2, + units, + Number_in, + uppercase, + i, + N_Z, + o, + j, + After_decimal_point, + unit, + k, + C_Number, + m, + n; class CNNUMBER { getInfo() { return { - id: 'nonameawacnnumber', - name: '中文数字', - color1: '#cb0000', - color2: '#a20000', - color3: '#a20000', + id: "nonameawacnnumber", + name: "中文数字", + color1: "#cb0000", + color2: "#a20000", + color3: "#a20000", blocks: [ { - opcode: 'CN_number', + opcode: "CN_number", blockType: Scratch.BlockType.REPORTER, disableMonitor: true, - text: '中文数字 [a] 大写 [u]', + text: "中文数字 [a] 大写 [u]", arguments: { a: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '2020' + defaultValue: "2020", }, u: { type: Scratch.ArgumentType.STRING, - menu: 'uppercase' + menu: "uppercase", }, - } + }, }, ], menus: { @@ -34,16 +48,16 @@ acceptReporters: true, items: [ { - text: '关闭', - value: '0' + text: "关闭", + value: "0", }, { - text: '打开', - value: '1' - } - ] - } - } + text: "打开", + value: "1", + }, + ], + }, + }, }; } CN_number(args) { @@ -54,12 +68,11 @@ } } - const main = (Number2, null2, units) => { if (Number2 == 0) { - return null2 ? '零' : ''; + return null2 ? "零" : ""; } - i = ''; + i = ""; let j_start = String(Number2).length; let j_inc = 1; if (j_start > 1) { @@ -67,18 +80,51 @@ } for (j = j_start; j_inc >= 0 ? j <= 1 : j >= 1; j += j_inc) { if (units) { - k = String(Number2).charAt(((String(Number2).length - (j - 1)) - 1)); + k = String(Number2).charAt(String(Number2).length - (j - 1) - 1); if (Number2 >= 10000) { - return ''; + return ""; } - i = String(i) + String(String(Number2).slice((-j)).charAt(0) == '2' && j >= 3 ? '两' : (k == '0' ? (j == 1 ? '' : (i.slice(-1) == '零' ? '' : (String(Number2).slice(-1) == '0' && (j != 3 || String(Number2).charAt(2) == '0') ? ' ' : '零'))) : C_Number[(k - 1)])); - i = String(i) + String(i.slice(-1) == '零' || i.slice(-1) == ' ' ? '' : (unit[(j - 1)] == '个' ? '' : unit[(j - 1)])); + i = + String(i) + + String( + String(Number2).slice(-j).charAt(0) == "2" && j >= 3 + ? "两" + : k == "0" + ? j == 1 + ? "" + : i.slice(-1) == "零" + ? "" + : String(Number2).slice(-1) == "0" && + (j != 3 || String(Number2).charAt(2) == "0") + ? " " + : "零" + : C_Number[k - 1] + ); + i = + String(i) + + String( + i.slice(-1) == "零" || i.slice(-1) == " " + ? "" + : unit[j - 1] == "个" + ? "" + : unit[j - 1] + ); } else { if (j != 1) { - k = String(Number2).slice((-(j - 1))).charAt(0); - i = String(i) + String(k == 0 ? '零' : C_Number[([k].reduce(function (x, y) { - return x + y; - }) - 1)]); + k = String(Number2) + .slice(-(j - 1)) + .charAt(0); + i = + String(i) + + String( + k == 0 + ? "零" + : C_Number[ + [k].reduce(function (x, y) { + return x + y; + }) - 1 + ] + ); } } } @@ -89,33 +135,57 @@ }; const CN_NUMBER = (Number_in, uppercase) => { - if (String(Math.abs(Number_in >= 0 ? Math.floor(Number_in) : Math.ceil(Number_in))).length > 20) { - return ''; + if ( + String( + Math.abs(Number_in >= 0 ? Math.floor(Number_in) : Math.ceil(Number_in)) + ).length > 20 + ) { + return ""; } - N_Z = Math.abs(Number_in >= 0 ? Math.floor(Number_in) : Math.ceil(Number_in)); - After_decimal_point = '.' + String(String(Number_in).split('.')[1]); - unit = (uppercase ? '个、拾、佰、仟、万、亿、兆、京' : '个、十、百、千、万、亿、兆、京').split('、'); - C_Number = (uppercase ? '壹、贰、叁、肆、伍、陆、柒、捌、玖、拾' : '一、二、三、四、五、六、七、八、九、十').split('、'); - m = ''; - o = ''; + N_Z = Math.abs( + Number_in >= 0 ? Math.floor(Number_in) : Math.ceil(Number_in) + ); + After_decimal_point = "." + String(String(Number_in).split(".")[1]); + unit = ( + uppercase + ? "个、拾、佰、仟、万、亿、兆、京" + : "个、十、百、千、万、亿、兆、京" + ).split("、"); + C_Number = ( + uppercase + ? "壹、贰、叁、肆、伍、陆、柒、捌、玖、拾" + : "一、二、三、四、五、六、七、八、九、十" + ).split("、"); + m = ""; + o = ""; let n_end = String(N_Z).length; let n_inc = 1; if (1 > n_end) { n_inc = -n_inc; } for (n = 1; n_inc >= 0 ? n <= n_end : n >= n_end; n += n_inc) { - m = [n % 4 == 0 ? ' ' : '', String(N_Z).slice((-n)).charAt(0), m].join(''); + m = [n % 4 == 0 ? " " : "", String(N_Z).slice(-n).charAt(0), m].join(""); } - m = m.trim().split(' '); + m = m.trim().split(" "); let n_end2 = m.length; let n_inc2 = 1; if (1 > n_end2) { n_inc2 = -n_inc2; } for (n = 1; n_inc2 >= 0 ? n <= n_end2 : n >= n_end2; n += n_inc2) { - o = [m.length == n ? '' : unit[((n + 4) - 1)], main(m.slice((-n))[0], m.length == n, true), o].join(''); + o = [ + m.length == n ? "" : unit[n + 4 - 1], + main(m.slice(-n)[0], m.length == n, true), + o, + ].join(""); } - return [Number_in < 0 ? '负' : '', o, Number_in % 1 != 0 ? '点' + String(main(After_decimal_point, false, false)) : ''].join(''); + return [ + Number_in < 0 ? "负" : "", + o, + Number_in % 1 != 0 + ? "点" + String(main(After_decimal_point, false, false)) + : "", + ].join(""); }; Scratch.extensions.register(new CNNUMBER()); diff --git a/extensions/NOname-awa/global-coordinate.js b/extensions/NOname-awa/global-coordinate.js index d546845192..3613073ce8 100644 --- a/extensions/NOname-awa/global-coordinate.js +++ b/extensions/NOname-awa/global-coordinate.js @@ -1,474 +1,511 @@ (function (Scratch) { - 'use strict'; - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSItMjcuNTEzODgsLTI3LjUxMzg4LDIwMCwyMDAiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNjcuNTEzNzgsLTEwNy41MTM3OCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTMxMC40ODYyMiwxMDkuNjM4Nzd2MzEuNzQ1MTdsLTEwLjA0MDU2LC0xMi4zODA2MmwtMTQuNzc1MSwxNS4yNzg5OWwtMTAuMDc3ODIsLTEwLjA3NzgybDE1LjI2Mjg3LC0xNC43NjAwMWwtMTIuMTE0NTUsLTkuODA1NzF6TTIwNC4yODIzMSwyMjUuNjcwNTVsLTE1LjI3MDkzLDE0Ljc2NzA1bDEyLjI0NzU3LDkuOTIzNjRoLTMxLjc0NTE3di0zMS43NDUxN2w5LjkyMzY3LDEyLjI0NzYxbDE0Ljc2NzA1LC0xNS4yNzA5M3pNMTY5LjYzODc3LDEwOS41MTM3OGgzMS43NDUxN2wtMTIuMzgwNjIsMTAuMDQwNTdsMTUuMjc4OTksMTQuNzc1MWwtMTAuMDc3ODIsMTAuMDc3ODJsLTE0Ljc2LC0xNS4yNjI4N2wtOS44MDU3MSwxMi4xMTQ1NnpNMjg1LjY3MDU1LDIxNS43MTc2OGwxNC43NjcwNCwxNS4yNzA5NGw5LjkyMzY0LC0xMi4yNDc1N3YzMS43NDUxN2gtMzEuNzQ1MTdsMTIuMjQ3NiwtOS45MjM2NmwtMTUuMjcwOTMsLTE0Ljc2NzA1eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzFhNjI2MiIgc3Ryb2tlLXdpZHRoPSI0Ii8+PHBhdGggZD0iTTI3OS45NTE2NCwyMDcuODA0OWMtMC44MjY0OSwwLjgzNDc5IC0xLjY3MzE0LDEuNjg5OTQgLTIuNDk5NjMsMi41MjQ3MmMtMS4yNzM0LDEuMDk2NjYgLTIuNjE0OTMsMi4xMTE2MiAtNC4wMTY1NSwzLjAzODhjLTAuODA5NzcsMC41NjE0MiAtMS42NTI5NCwxLjA3MzA5IC0yLjUyNDg1LDEuNTMyMTFsLTEuMzczOCwwLjc3Njc0Yy01LjYwODg3LDIuODUxNTIgLTExLjc3OTcyLDQuNDI1MjcgLTE4LjA2OTE1LDQuNjA4MjVjLTUuMzM4NDMsMC4xOTYxMyAtMTAuNjY1MTUsLTAuNjI5OTggLTE1LjY5MzQsLTIuNDMzODRjLTEuOTk0NDEsLTAuNzc4NzUgLTMuOTM0NywtMS42ODk1NCAtNS44MDc5OCwtMi43MjYzNWMtMS42MDU0NiwtMC45ODQ3MyAtMi45ODc1NSwtMS44NjkyNiAtNC4xMjY1NCwtMi43NTUwMWMtMC42NDA0MSwtMC40NTg0OSAtMS4yNTE4NiwtMC45NTYxNCAtMS44MzA4NSwtMS40OTAwOWwtMC40MDcyMSwtMC40MDMxNmwtMC42NzE5MSwtMC42NjUyMmMtMi4xNjQ5NCwtMS44NjUzOSAtMi40MjUxOSwtNS4xMjU3NyAtMC41ODM1NCwtNy4zMTA5NGwwLjIyMTc0LC0wLjIyMzk3YzEuNzUzMTYsLTEuNzg3OTEgNC41MzcyOCwtMi4wNTgxNSA2LjYwMTU2LC0wLjY0MDc5YzAsMCAwLjI4NTA1LDAuMjgyMjMgMC44MzM1OCwwLjU4MzM4YzAsMCAwLjI2NDY4LDAuMjYyMDUgMC41MDkwMSwwLjUwMzk1YzAuNDIxMTYsMC4zMDQzMiAwLjg2MjUyLDAuNTc5NjUgMS4zMjEwMiwwLjgyNDA2YzAuODUzMzMsMC40ODE5OSAxLjkyOTgyLDEuMDIzNjIgMy4xODg3MywxLjU4NDYyYzEuMzcyMDUsMC41NzY1MiAyLjc4MjUzLDEuMDU2ODggNC4yMjEyNiwxLjQzNzYzYzMuNjExOSwwLjg1NjQ3IDcuMzU1ODMsMS4wMDI5MSAxMS4wMjM2LDAuNDMxMTdjNC4wNzgyLC0wLjU5NjU5IDcuOTY5NDQsLTIuMTA0OTMgMTEuMzg0MzMsLTQuNDEyNzh2MGwwLjk2ODgxLC0wLjczNDJjMC40ODcwNSwtMC4zMjk3OCAwLjk1MjIsLTAuNjkwNzYgMS4zOTI1NCwtMS4wODA3NGwxLjM3MDc2LC0xLjM4NDUzbDAuODA2MzMsLTAuODE0NDNjMS4zMDExLC0xLjQzNDcxIDIuNDQ3NjksLTMuMDAyMyAzLjQyMDgzLC00LjY3Njg5YzEuNjk4OTgsLTIuOTA4MzMgMi44MTE3NCwtNi4xMjEzNSAzLjI3NTM4LC05LjQ1NzUyYzAuMDc3MzksLTAuNzI5NzQgMC4xNTQ5OSwtMS40MTg5OCAwLjE3MTcxLC0yLjEyODE2bC04LjEwMzgsMC4wNDA0OGMtMS42NDk5MywwLjAwNjQzIC0zLjE0MjcyLC0wLjk3NzQzIC0zLjc4NzQyLC0yLjQ5NjE5Yy0wLjY0NDcsLTEuNTE4NzYgLTAuMzE1NSwtMy4yNzYwOCAwLjgzNTI0LC00LjQ1ODQ4bDE3LjgxOTkxLC0xNy45OTg4NmMwLjc2NjA1LC0wLjc4NDY4IDEuODE0NjgsLTEuMjI5NTMgMi45MTEyNywtMS4yMzVjMS4wOTY1OCwtMC4wMDU0OCAyLjE0OTYxLDAuNDI4ODggMi45MjM0NywxLjIwNTg2bDE3Ljk3ODUsMTcuNzk5NzdjMS42MDAyMiwxLjYwNjQ3IDEuNjA0MTYsNC4yMDMyNyAwLjAwODc4LDUuODE0NTdjLTAuNzYwMTEsMC43NjM0MiAtMS43OTM2NywxLjE5MTYxIC0yLjg3MDk4LDEuMTg5NDNsLTguNjkxNDIsMC4wMjMxN2MtMC4yMjM5NiwxLjk2MDIgLTAuNTcyOSwzLjkwNDExIC0xLjA0NDcsNS44MTk4M2MtMS4zNzg4NSw1LjU0NTA5IC0zLjgxNTUyLDEwLjc3MTc0IC03LjE3NjE4LDE1LjM5MjkyYy0xLjE4NzcxLDEuNTYzNjYgLTIuNDk0NDQsMy4wMzMyNyAtMy45MDg0OCw0LjM5NTY4eiIgZmlsbD0iIzFhNjI2MiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMjIzLjcyODU0LDIwMy40MjU5OGwtMC4xNDExMSwwLjE0MjUzYy0xLjM2NDc3LDEuNTc0NTUgLTEuMTk4ODUsMy45NTYzNyAwLjM3MTA0LDUuMzI2NTJsMC42NzE5LDAuNjY1MjJjMC43MDQ5OCwwLjY1MTA3IDEuNDQ1MjcsMS4yNjI4NiAyLjIxNzQ5LDEuODMyNTljMS4wOTgyNywwLjg0NTQzIDIuNDM5NjQsMS42ODk2NSAzLjk2Mzg2LDIuNjM0MjZjMS43NzY0MSwwLjk4MDUxIDMuNjE0NzYsMS44NDQzNSA1LjUwMzM4LDIuNTg2MDRjNC44Mzk0OSwxLjgwOTQ3IDkuOTgxOSwyLjY3MTA5IDE1LjE0Njg5LDIuNTM3ODhjNi4wNTE3NCwtMC4yMTEyOCAxMS45ODExNSwtMS43NjMyNSAxNy4zNjAzNywtNC41NDM5M2MxLjMzMTI5LC0wLjcxMDMgMi42MjU3MiwtMS40ODc2MiAzLjg3ODI4LC0yLjMyOTAyYzEuMzM4LC0wLjg4MzY2IDIuNjE4ODgsLTEuODUwOTQgMy44MzQ5MywtMi44OTYwOGMwLjgwNjMzLC0wLjgxNDQzIDEuNjMyODIsLTEuNjQ5MjEgMi40MzkxNSwtMi40NjM2NGMxLjM2MDAyLC0xLjM1MTk4IDIuNjEyNjgsLTIuODA3ODQgMy43NDY2MSwtNC4zNTQzNWMzLjI2NDM5LC00LjQzMzE3IDUuNjQ2NjIsLTkuNDUyMjYgNy4wMTcxNCwtMTQuNzg0MzRjMC40OTIzOSwtMi4xOTY3NCAwLjgyNjU3LC00LjQyNTk3IDAuOTk5OTQsLTYuNjcwNTNsMC4wMTg1NCwtMC4zNDQ1MWw5Ljk4ODAzLC0wLjAyOTY0YzAuNjcxMTUsLTAuMDIwODMgMS4zMDg0MSwtMC4yOTk3OSAxLjc3OSwtMC43Nzg3NmMwLjUwNjEzLC0wLjUwMDMzIDAuNzkxMDcsLTEuMTgyMzUgMC43OTEzMiwtMS44OTQwNGMwLjAwMDI1LC0wLjcxMTY5IC0wLjI4NDIyLC0xLjM5MzkgLTAuNzg5OTksLTEuODk0NThsLTE3Ljk3ODUsLTE3Ljc5OTc2Yy0xLjAzNzgyLC0xLjAyNzYyIC0yLjcwOTQ1LC0xLjAyODMyIC0zLjc0ODEsLTAuMDAxNTNsLTE3LjgxOTkzLDE3Ljk5ODg2Yy0wLjc1MzMxLDAuNzY5NDQgLTAuOTY5NDgsMS45MTY2NCAtMC41NDc4NSwyLjkwNzQ2YzAuNDIxNjMsMC45OTA4MyAxLjM5ODE1LDEuNjMwNDggMi40NzQ5MiwxLjYyMTE2bDkuNDgxNDUsLTAuMDQ3MzZjMC4wMTM0NywxLjI2NzYxIC0wLjA2ODI0LDIuNTM0NDYgLTAuMjQ0NDUsMy43ODk4NGMtMC40NTcwMSwzLjUyNzYyIC0xLjU5NjAzLDYuOTMyNjIgLTMuMzUzNTksMTAuMDI1MTljLTEuMDE3OTYsMS43NjEzNyAtMi4yMTg0NiwzLjQxMDczIC0zLjU4MTY5LDQuOTIwODFsLTAuOTQ3NDUsMC45NTY5NmMtMC40NDM0OSwwLjQ0NzkzIC0wLjg2NjgsMC44NzU1MSAtMS4yNDk4MiwxLjI2MjM2djBjLTAuNTQ0MjYsMC41NDk3NCAtMi4wOTk0OSwxLjUwOTczIC0yLjU4MzMsMS45OTgzOGMtMy41OTM1MywyLjQyMTIyIC03LjY4NDAyLDQuMDA2MjUgLTExLjk3MDc0LDQuNjM4NTZjLTMuODQ0MDYsMC41NjI2MyAtNy43NjEwNCwwLjM2ODg2IC0xMS41MzA3OSwtMC41NzA0NWMtMS41MDk1MiwtMC40MTQ5OSAtMi45ODgwMSwtMC45MzU2NiAtNC40MjQ0NiwtMS41NTgxOGMtMS4yOTk2NCwtMC42MDEzMSAtMi4zOTY2OSwtMS4yMDM2MyAtMy4yMDkxLC0xLjYwNDc4Yy0wLjgxMjQxLC0wLjQwMTE0IC0xLjU4NTUxLC0xLjA0NTYgLTIuMTM0MDIsLTEuMzQ2NzZjLTAuNTQ4NTIsLTAuMzAxMTUgLTAuNzkyODUsLTAuNTQzMDYgLTAuNzkyODUsLTAuNTQzMDZjLTEuNDk3ODMsLTEuMDE2MjkgLTMuNTA3NjQsLTAuODE4ODIgLTQuNzc5MDIsMC40Njk1OU0yMjEuNTQ5OTMsMjAxLjI2OTA0djBjMi4xOTA0NywtMi4zODA3IDUuNzg1MTMsLTIuODE5MjggOC40ODM3NywtMS4wMzUxMmMwLjE0MjMzLDAuMTAwNTkgMC41Njk5LDAuNTIzOSAwLjgzMzU3LDAuNTgzMzdjMC4yNjM2NywwLjA1OTQ3IDAuNDQ2OTMsMC4yNDA4OSAwLjY1MDUzLDAuNDQyNDdjMC4zNjM2MSwwLjI2NTIgMC43NDM3NiwwLjUwNjkxIDEuMTM4MTcsMC43MjM2OGwwLjE0MjUzLDAuMTQxMTFjMC43OTI0NSwwLjQ2MjAxIDEuOTkwNywxLjA0MzU4IDIuODg0MTMsMS40NDQzMWMxLjMxMjUxLDAuNTUyNjggMi42NjIsMS4wMTMwNSA0LjAzODYyLDEuMzc3NzZjMy40MTkyMSwwLjg0ODE2IDYuOTcwNzUsMS4wMjMxIDEwLjQ1NjczLDAuNTE1MDRjMy44MTY3NiwtMC41Njk1MiA3LjQ2MTY4LC0xLjk3MTA1IDEwLjY3Njc2LC00LjEwNTM0bDEuMDA5OTQsLTAuNjEyODRsMS4xNzA3OSwtMC44NTY3N2wwLjM2Mjg0LC0wLjM2NjQ5YzAuMzIyNTMsLTAuMzI1NzcgMC42ODUzOCwtMC42OTIyNiAxLjA0ODIyLC0xLjA1ODc1bDAuNzI1NywtMC43MzI5OGMxLjIxNTg2LC0xLjM1NjQzIDIuMjg4MDIsLTIuODM1MTMgMy4xOTkzLC00LjQxMjRjMS42MTQyMiwtMi43ODM3MiAyLjY1OTM4LC01Ljg2MDM2IDMuMDc0ODMsLTkuMDUxMzFsMC4wNTgyNSwtMC41MDY3OGwtNi40ODMwNCwwLjAzMjM5Yy0yLjI0MDY5LDAuMDE5ODggLTQuMjcxOTEsLTEuMzE0NDIgLTUuMTQzNDQsLTMuMzc4NzhjLTAuODcxNTMsLTIuMDY0MzYgLTAuNDExMDksLTQuNDUwNjMgMS4xNjU5MywtNi4wNDI1bDE3LjkyMDcxLC0xOC4xMDA2NmMyLjE1MjAxLC0yLjE1MTIxIDUuNjM3NjksLTIuMTU5NTggNy44LC0wLjAxODcxbDE3LjkxNzQyLDE3LjczOTI5YzIuMTYyNDYsMi4xNjMxNiAyLjE3MDkyLDUuNjY2OTcgMC4wMTg5MSw3Ljg0MDUyYy0xLjAzMTM5LDEuMDQwNTkgLTIuNDM2OTcsMS42MjQxNCAtMy45MDIwOSwxLjYyMDAzbC03LjQzNTMzLDAuMDE2ODhjLTAuMjM1MDIsMS42MjE5OCAtMC41Mzc3LDMuMjE3MjcgLTAuOTA4MDUsNC43ODU5Yy0xLjQ3MjIsNS42NzA1OSAtNC4wMTc2OSwxMS4wMDY0MyAtNy40OTg3MSwxNS43MTg2OWMtMS4yMDcyNywxLjY0NzM4IC0yLjU0MDcxLDMuMTk4NDQgLTMuOTg4Myw0LjYzOTJjLTAuODI2NDksMC44MzQ3OSAtMS42OTMzLDEuNzEwMyAtMi42NDA3MywyLjY2NzI2Yy0xLjI3NDIxLDEuMDk1NjcgLTIuNjE1NjgsMi4xMTA1OCAtNC4wMTY1NiwzLjAzODhjLTAuODczMTUsMC42MjA2IC0xLjc4Mzc2LDEuMTg2NzUgLTIuNzI2NjMsMS42OTUybC0xLjUxNDQ5LDEuMDAwMzFsLTAuMTIwOTUsMC4xMjIxNmMtNS43NjQ2MiwyLjk2ODk3IC0xMi4xMTQ0Miw0LjYyNjU0IC0xOC41OTQ2OSw0Ljg1Mzk5Yy01LjU1MjUxLDAuMTM5MjMgLTExLjA4MDEsLTAuNzg5MTkgLTE2LjI4MjQ0LC0yLjczNDhjLTIuMDAwMTYsLTAuNzg4NDQgLTMuOTQ3MDQsLTEuNzA1ODUgLTUuODI4MzQsLTIuNzQ2NTF2MGMtMS41ODUzLC0xLjAwNTA4IC0zLjAwNzkyLC0xLjg4OTQyIC00LjEyNjU0LC0yLjc1NTAxYy0wLjY5NjQyLC0wLjQ5NjIgLTEuMzYyMTYsLTEuMDM0MTQgLTEuOTkzNTMsLTEuNjEwODRjLTAuMTIyMTYsLTAuMTIwOTUgLTAuMjQ0MzMsLTAuMjQxOSAtMC4zNDYxNCwtMC4zNDI2OWwtMC43NTMzNSwtMC43NDU4NmMtMS4zMjUxMiwtMS4xNTc2MiAtMi4xMzQ3MywtMi43OTUyMyAtMi4yNDk5NSwtNC41NTEwMmMtMC4xMTUyMiwtMS43NTU3OSAwLjQ3MzQ1LC0zLjQ4NTE2IDEuNjM1OTMsLTQuODA2MDNsMC4yODIyMiwtMC4yODUwNXoiIGZpbGw9IiMxYTYyNjIiIHN0cm9rZT0iIzFhNjI2MiIgc3Ryb2tlLXdpZHRoPSIzIi8+PHBhdGggZD0iTTIyMy41NDQ1MiwyMDMuMzAwMzNjMS4yNzEzOCwtMS4yODg0MSAzLjM2MzY0LC0xLjUxODEzIDQuODYxNDgsLTAuNTAxODRjMCwwIDAuMjQzOTYsMC4yNDEwMyAwLjc5MjQ5LDAuNTQyMmMwLjU0ODUyLDAuMzAxMTUgMS4zMjA1NSwwLjk0MzMzIDIuMTMyOTUsMS4zNDQ0NmMwLjgxMjQxLDAuNDAxMTQgMS45MDc2NiwxLjAwMDIzIDMuMjA3MywxLjYwMTU1YzEuNDM2NDUsMC42MjI1MiAyLjkxMjAzLDEuMTM5MDEgNC40MjE1NiwxLjU1Mzk5YzMuNzY5NzYsMC45MzkzMiA3LjY3NjQzLDEuMTIzNTggMTEuNTIwNSwwLjU2MDk1YzQuMjg2NzIsLTAuNjMyMyA4LjM1NjgsLTIuMjI0NTcgMTEuOTUwMzIsLTQuNjQ1NzljMC40ODM4LC0wLjQ4ODY2IDIuMDMxMjIsLTEuNDQ4NjcgMi41NzU0OSwtMS45OTg0MXYwYzAuMzgzLC0wLjM4Njg2IDAuODAxNTksLTAuODEzNzYgMS4yNDUwNiwtMS4yNjE3MWwwLjk0MzM4LC0wLjk1NTk2YzEuMzYzMjMsLTEuNTEwMDggMi41NDM3MiwtMy4xNDcwMiAzLjU2MTY5LC00LjkwODM5YzEuNzU3NTUsLTMuMDkyNTcgMi44ODczNiwtNi40NTI3NCAzLjM0NDM3LC05Ljk4MDM2YzAuMTc2MjEsLTEuMjU1MzggMC4yNjM1LC0yLjUxMTQ2IDAuMjUwMDIsLTMuNzc5MDdsLTkuNDUxNCwwLjA2NTY3Yy0xLjA3Njc3LDAuMDA5MzIgLTIuMDQwMTUsLTAuNjMwNDcgLTIuNDYxNzgsLTEuNjIxMjljLTAuNDIxNjMsLTAuOTkwODMgLTAuMjE1ODEsLTIuMjI1ODkgMC41Mzc1MSwtMi45OTUzMmwxNy44NDUwNiwtMTcuOTkzMjRjMS4wMzg2NiwtMS4wMjY3NyAyLjcxMzY2LC0xLjAyMzk3IDMuNzUxNDgsMC4wMDM2NWwxNy45OTU1NywxNy44MjI5NWMwLjUwNTc5LDAuNTAwNjkgMC43OTA4NiwxLjE4NTA0IDAuNzkwNiwxLjg5NjczYy0wLjAwMDI1LDAuNzExNjkgLTAuMjg0NzgsMS4zOTU0OCAtMC43OTA5LDEuODk1ODNjLTAuNDcwNTksMC40Nzg5NyAtMS4xOTQ0OSwwLjc3NjEyIC0xLjg2NTY2LDAuNzk2OTRsLTkuOTg3MjksMC4wMzI2OGwwLjA2Njg5LDAuMzIxMTljLTAuMTczMzgsMi4yNDQ1NiAtMC41MDYwNyw0LjQ4NDM2IC0wLjk5ODQ1LDYuNjgxMDljLTEuMzcwNTIsNS4zMzIwNyAtMy43NjE3MywxMC4zNzg3NyAtNy4wMjYxMiwxNC44MTE5M2MtMS4xMzM5MiwxLjU0NjUyIC0yLjM5NCwzLjAxMDAzIC0zLjc1NDAyLDQuMzYyMDFjLTAuODA2MzMsMC44MTQ0MyAtMS42Mzc5MSwxLjY1Mjc3IC0yLjQ0NDI0LDIuNDY3MmMtMS4yMTYwNiwxLjA0NTE0IC0yLjUwNDQxLDIuMDE2IC0zLjg0MjQzLDIuODk5NjdjLTEuMjUyNTcsMC44NDE0IC0yLjU1NDA5LDEuNjIwNjggLTMuODg1MzgsMi4zMzA5N2MtNS4zNzkyNCwyLjc4MDY4IC0xMS4zMzMzMiw0LjMzMDIxIC0xNy4zODUwNiw0LjU0MTQ5Yy01LjE2NSwwLjEzMzIxIC0xMC4zMjE3NiwtMC43Mzc3NCAtMTUuMTYxMjUsLTIuNTQ3MjFjLTEuODg4NjMsLTAuNzQxNjkgLTMuNzMxMDIsLTEuNjEwMDcgLTUuNTA3NDQsLTIuNTkwNThjLTEuNTI0MjEsLTAuOTQ0NiAtMi44NjgyMiwtMS43OTI1OCAtMy45NjY0OCwtMi42MzgwMmMtMC43NzIyMSwtMC41Njk3MyAtMS41MTM4NiwtMS4xODM4NSAtMi4yMTg4NiwtMS44MzQ5MmwtMC42NzIzMiwtMC42NjYwMWMtMS41Njk4OSwtMS4zNzAxNCAtMS43Mzc4NiwtMy43NTY1NyAtMC4zNzMwOSwtNS4zMzExM2wwLjE0MTA3LC0wLjE0MjYxIiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtub0hvdmVyJnF1b3Q7OmZhbHNlLCZxdW90O29yaWdJdGVtJnF1b3Q7OlsmcXVvdDtQYXRoJnF1b3Q7LHsmcXVvdDthcHBseU1hdHJpeCZxdW90Ozp0cnVlLCZxdW90O3NlZ21lbnRzJnF1b3Q7OltbMjA0LjMzNDA1LDE2MC4xMDgwNF0sW1syMDQuMTQwNTEsMTU5Ljk5MDM5XSxbMCwwXSxbLTIuMDg3MjUsLTEuMDg2NTFdXSxbWzE5OC4zODY3NiwxNjEuNzkzODddLFsxLjA5MzYzLC0yLjA4MzUzXSxbMCwwXV0sW1sxOTcuODMyMTMsMTYyLjcwNjI2XSxbMCwwXSxbLTAuNTMwNDIsMC45NDUwM11dLFtbMTk2LjQwMDI0LDE2NS42MjIzNV0sWzAuNDIzNDUsLTAuOTk3NTZdLFstMC42NDA4NSwxLjQyNzk2XV0sW1sxOTQuNTQ1NDEsMTcwLjY2Njg2XSxbMC42MzgxNiwtMS45MjE4NF0sWy0wLjYxMTUsMi4yMDgyOF1dLFtbMTkzLjE0NzAxLDE3Ny4zODk4M10sWzAuMzE5NzcsLTIuMjY4OTZdLFstMC43MTkzNSw1Ljc5MDJdXSxbWzE5NC4zMjg3OCwxOTQuNjkzMjFdLFstMS40OTk5MSwtNS42Mzg2NF0sWzEuODE4MDYsNi41OTIyNF1dLFtbMjAzLjg2OTczLDIxMi41NzIwOV0sWy00LjQ2NDIsLTUuMTgwMTZdLFsxLjEyOTEyLDEuMjc2MjNdXSxbWzIwNy40NDQ0NCwyMTYuMjIxODhdLFstMS4yNTI1LC0xLjE1NTM5XSxbMS4zMjEzMiwxLjIzODE3XV0sW1syMTEuNjMwNjksMjE5LjY3NTQzXSxbLTEuNDY2NzQsLTEuMDYxOV0sWzEuMTA1OTMsMC42NzIyOV1dLFtbMjE0Ljk3NjEzLDIyMS43MDkxXSxbLTEuMTA1OTMsLTAuNjcyMjldLFsxLjg0MTUyLDEuMTM5NjJdXSxbWzIyMC43NDEwOSwyMjQuNjgzNDZdLFstMS45OTU5NiwtMC44NDAyOF0sWzUuNzI1MTYsMi40MjQwMV1dLFtbMjM4LjgyMDA5LDIyOC41MTddLFstNi4yMTYyNSwtMC4xMDgxXSxbMi41NDIwNywtMC4wMzQ4M11dLFtbMjQ2LjQwOTQ3LDIyNy44NjcyNl0sWy0yLjUxMSwwLjM5Nzc4XSxbMCwwXV0sWzI0Ni43OTI3NiwyMjcuNzk3MzNdLFtbMjQ5LjQ0Mjg3LDIzOC43NjEwM10sWzAsMF0sWzAuMTk4NzcsMC43MzE3OF1dLFtbMjUwLjc2NDUzLDI0MC41MTEwOV0sWy0wLjY0OTQ2LC0wLjM5MTRdLFswLjY4MjI0LDAuNDI0ODRdXSxbWzI1My4wNTI0NCwyNDAuODgzOTddLFstMC43ODE4MywwLjE4NjIzXSxbMC43ODE4MywtMC4xODYyM11dLFtbMjU0LjkyNjU0LDIzOS41MTk2N10sWy0wLjQxNzQ0LDAuNjg2OF0sWzAsMF1dLFtbMjY5Ljc2NzI5LDIxNS4xMDYyMl0sWzAsMF0sWzAuODU2ODIsLTEuNDA5MzFdXSxbWzI2OC43ODY3MSwyMTAuOTg4NjddLFsxLjQwMDA3LDAuODcxODRdLFswLDBdXSxbWzI0NC4zNDU2MSwxOTYuMTMxMTFdLFswLDBdLFstMS4wNDI2MSwtMC42MjU4NF1dLFtbMjQxLjAwODMxLDE5Ni4yOTEyN10sWzAuOTc3ODksLTAuNzIyODFdLFstMC45Nzc4OSwwLjcyMjgxXV0sW1syMzkuODc2MTIsMTk5LjQzNDcyXSxbLTAuMjkyNDIsLTEuMTgwMzRdLFswLDBdXSxbWzI0Mi40MTI5NSwyMDkuODM3MzFdLFswLDBdLFstMS4zODg4OSwwLjM0N11dLFtbMjM4LjE4NTg5LDIxMC41NjE5OV0sWzEuNDI1MTcsLTAuMTM1NDNdLFstMy45OTQ3MywwLjQyMjQ4XV0sW1syMjYuMjk0NzMsMjA5LjUwNTQ5XSxbMy44NTc2NywxLjEyMDE0XSxbLTIuMjAxNTgsLTAuNjU2NTldXSxbWzIxOS45NTA3NiwyMDYuODYwNzNdLFsyLjAxNjAyLDEuMTAxNzFdLFswLDBdXSxbWzIxOC42NTEyOCwyMDYuMDcwNzldLFswLDBdLFstMC42MDgyNiwtMC4zNjk3Nl1dLFtbMjE2LjkzNzA5LDIwNS4wMjg3NF0sWzAuNTI1MzIsMC4zMTkzM10sWzAsMF1dLFtbMjE2LjkzNzA5LDIwNS4wMjg3NF0sWzAsMF0sWy0wLjc0NjUsLTAuNDUzNzldXSxbWzIxNC4wNjQ5NCwyMDIuNzE0OF0sWzAuNjYzNTYsMC40MDMzN10sWy0zLjYwMTM3LC0zLjMxMjgyXV0sW1syMDUuODMyNSwxOTAuNzgxMDNdLFsxLjgxNzk4LDQuNTQzMDldLFstMS42MjU0NCwtNC4wNzUxMV1dLFtbMjAzLjQzNzI2LDE3Ny45NjU0XSxbLTAuMDQzODcsNC4zODcxXSxbMC4wNjAyNSwtMS43NjY5MV1dLFtbMjAzLjk4OTM0LDE3Mi42OTY5NV0sWy0wLjMwNzM2LDEuNzQxMDNdLFswLjMxOTkyLC0xLjU4NTE5XV0sW1syMDQuOTExMTIsMTY4Ljc1MTMyXSxbLTAuMjI3NzMsMC45OTc1Ml0sWzAuMjI3NzMsLTAuOTk3NTJdXSxbWzIwNS44MzEyMSwxNjYuMDU0MjNdLFstMC4xODcwNiwwLjY4MTQ1XSxbMC4xODcwNiwtMC42ODE0NV1dLFtbMjA2LjIxOTk2LDE2NS4wNDA5OV0sWzAsMF0sWzAuNzIzODIsLTEuOTExNjVdXSxbWzIwNC40NTE3LDE1OS45MTQ1XSxbMS43NDg0NSwxLjA1ODkxXSxbMCwwXV1dLCZxdW90O2ZpbGxDb2xvciZxdW90OzpbMCwwLDAsMV19XX0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjU3LjM4NTY5LDE1Ny42MjIwNmMtMS43NTg0NywxLjc3Njc3IC00LjUzNDIsMi4wNDYyMiAtNi42MDE1NiwwLjY0MDc5bC0xLjMwMTA3LC0wLjg4NDk0Yy0wLjQ2MjY4LC0wLjM0NjI3IC0wLjk1MTk4LC0wLjY1NTQ1IC0xLjQ2MzM0LC0wLjkyNDY1Yy0wLjg1MzMzLC0wLjQ4MTk5IC0xLjkyOTgyLC0xLjAyMzYxIC0zLjE4ODc0LC0xLjU4NDZjLTEuMzc2MzIsLTAuNTY1MTkgLTIuNzg2MDgsLTEuMDQ1MzEgLTQuMjIxMjYsLTEuNDM3NjNjLTMuNTcyOTYsLTAuODc3MjkgLTcuMjgxNDEsLTEuMDU4MjUgLTEwLjkyMjgsLTAuNTMyOThjLTQuMDg3MTQsMC41OTg4NiAtNy45ODU5MSwyLjExNDM5IC0xMS40MDQ1LDQuNDMzMTNsLTAuOTI4NDksMC42OTM0OGMtMC40OTQxMywwLjMzNjIgLTAuOTY2MDIsMC43MDQgLTEuNDEyNywxLjEwMTFsLTEuMzUwNiwxLjM2NDE3bC0wLjgyNjQ5LDAuODM0NzljLTEuMjk3NDIsMS40Mzc3MSAtMi40NDM3LDMuMDA0OTEgLTMuNDIwODMsNC42NzY4OWMtMS42ODA5OCwyLjkwMjA1IC0yLjc5MjczLDYuMDk4MiAtMy4yNzU1OSw5LjQxNzAxYy0wLjA3NzU5LDAuNjg5MjIgLTAuMTM1MDMsMS4zNTgxIC0wLjE1MTc1LDIuMDY3MjhsOC4xMDM4LC0wLjA0MDQ4YzEuNjQ5OTMsLTAuMDA2NDMgMy4xNDI3MiwwLjk3NzQzIDMuNzg3NDIsMi40OTYyMWMwLjY0NDcsMS41MTg3NiAwLjMxNTUsMy4yNzYwNyAtMC44MzUyNCw0LjQ1ODQ4bC0xNy44ODA0LDE4LjA1OTk0Yy0wLjc1OTEsMC43Nzc1NiAtMS43OTYwMSwxLjIyMTczIC0yLjg4MjYxLDEuMjM0NzVjLTEuMDg2NiwwLjAxMzAyIC0yLjEzMzg3LC0wLjQwNjE1IC0yLjkxMTQsLTEuMTY1MjlsLTE3Ljk3ODQ5LC0xNy43OTk3NWMtMS4xNjI0OSwtMS4xNzA4NSAtMS41MDkyMywtMi45MjQ3OSAtMC44Nzk3NCwtNC40NDk5MmMwLjYyOTQ5LC0xLjUyNTEzIDIuMTEyMzgsLTIuNTIzODcgMy43NjIyOSwtMi41MzM5Mmw4LjczMTU0LC0wLjEwNDRjMC4yMjA5LC0xLjk2NTg5IDAuNTYyOTksLTMuOTE2MjggMS4wMjQzNCwtNS44Mzk5OWMxLjM3NTg3LC01LjUzNjI0IDMuODA1NDgsLTEwLjc1NTUzIDcuMTU2MDIsLTE1LjM3MjU2YzEuMTMxNCwtMS41NDg5NCAyLjM3Njk1LC0zLjAxMTE5IDMuNzI2MjUsLTQuMzc0NTNjMC44MjY0OSwtMC44MzQ3OSAxLjY3MzE0LC0xLjY4OTk0IDIuNDk5NjMsLTIuNTI0NzJjMS4yNzg0NiwtMS4xMDUzNyAyLjYyNjgzLC0yLjEyNzIyIDQuMDM2NzIsLTMuMDU5MTZjMC44MDk3LC0wLjU2MTU1IDEuNjUyODgsLTEuMDczMjEgMi41MjQ4NCwtMS41MzIxMmwxLjM3MzgsLTAuNzc2NzRjNS41OTA3NCwtMi44OTg2IDExLjc1NDg3LC00LjUyMDg5IDE4LjA0ODE5LC00Ljc0OTk2YzUuMzUxOTMsLTAuMTMyODggMTAuNjc5NjYsMC43NjIxIDE1LjY5NDQxLDIuNjM2NDRjMi4wNzI2NSwwLjc2Mzc4IDQuMDgyNCwxLjY4ODU0IDYuMDEwNzksMi43NjU4NWMxLjQyOTQ4LDAuODE5ODkgMi44MDc1MSwxLjcyNjQxIDQuMTI2MzQsMi43MTQ0OWMwLjYzOTcyLDAuNDU5NCAxLjI1MTExLDAuOTU3MDEgMS44MzA4NSwxLjQ5MDA5bDAuMzY2NDksMC4zNjI4NGMwLjUwOTAxLDAuNTAzOTUgMC43NTMzNSwwLjc0NTg2IDAuNzUzMzUsMC43NDU4NmMyLjExNzIyLDEuODg5MTggMi4zMjI5OCw1LjEyOTE2IDAuNDYxNzgsNy4yNzEwM2wtMC4yMDE1OSwwLjIwMzYxeiIgZmlsbD0iIzFhNjI2MiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMjAwLjk3ODczLDE1Mi45NDAxNmMtMS4zNTkwOSwxLjM1Mjg0IC0yLjYxMTY4LDIuODA4NjIgLTMuNzQ2NjEsNC4zNTQzNWMtMy4yNTg2LDQuNDI2NDUgLTUuNjM0MTksOS40MzkwNiAtNi45OTY5OCwxNC43NjM5N2MtMC40OTIyOSwyLjE5Njc2IC0wLjgyNjQ1LDQuNDI1OTggLTAuOTk5OTQsNi42NzA1M2wtMC4wMTg1NCwwLjM0NDUxbC05Ljk4ODAzLDAuMDI5NjRjLTEuMDk0ODUsLTAuMDE1NTcgLTIuMDg3NzgsMC42NDAyNCAtMi41MDMyNCwxLjY1MzMzYy0wLjQxNTQ1LDEuMDEzMSAtMC4xNjg4MywyLjE3NzIzIDAuNjIxNzMsMi45MzQ4NGwxOC4wMzk1OCwxNy44NjAyM2MxLjA0NTc4LDEuMDM1MzggMi43MzI4OCwxLjAyNjk1IDMuNzY4MjYsLTAuMDE4ODJsMTcuNzc5NiwtMTcuOTU4MTRjMC43NjUyMywtMC43NjQ5NiAwLjk5MTg5LC0xLjkxNjczIDAuNTczNTksLTIuOTE0NjJjLTAuNDE4MzEsLTAuOTk3ODggLTEuMzk4NTIsLTEuNjQzNzIgLTIuNDgwNSwtMS42MzQzNmwtOS41MDE4MSwwLjAyNzJjMC4wMTA3NCwtMS4yNjA4MSAwLjA5OTE4LC0yLjUxOTc1IDAuMjY0ODEsLTMuNzY5NjljMC40Njk2MiwtMy41MjQ3IDEuNjA3ODgsLTYuOTI3MzQgMy4zNTM2LC0xMC4wMjUxOWMxLjAyNjYzLC0xLjc2NzExIDIuMjMzNywtMy40MjMwMyAzLjYwMTg2LC00Ljk0MTE3bDAuOTQ3NDMsLTAuOTU2OTVjMC40NDM0OSwtMC40NDc5MyAwLjg2NjgsLTAuODc1NTEgMS4yNDk4MiwtMS4yNjIzNnYwYzAuNTQ0MjYsLTAuNTQ5NzQgMi4wOTk1LC0xLjUwOTc0IDIuNTYzMTQsLTEuOTc4MDNjMy41OTcyNSwtMi40MzIxIDcuNjk1MjUsLTQuMDI0MzIgMTEuOTkwOTEsLTQuNjU4OTJjMy44NDQ1OSwtMC41NTE3MSA3Ljc1OTQzLC0wLjM1ODA0IDExLjUzMDc5LDAuNTcwNDVjMS41MTgzNiwwLjQxMjQ2IDMuMDA5NTQsMC45MTkyNSA0LjQ2NDc4LDEuNTE3NDVjMS4yOTk2NCwwLjYwMTMxIDIuMjc0NzMsMS4xMjMxOSAzLjA0NjYxLDEuNTI0NTRjMC43MzM5LDAuNDA4NzYgMS40NDAwNSwwLjg2NTQ2IDIuMTEzODYsMS4zNjcxMmMwLjU0ODUyLDAuMzAxMTUgMC43OTI4NSwwLjU0MzA2IDAuNzkyODUsMC41NDMwNmMxLjQ5MTg0LDAuOTk4NzQgMy40ODAyNiwwLjgxMTAyIDQuNzU4ODUsLTAuNDQ5MjRsMC4yMDE1OCwtMC4yMDM2YzAuNjU0MDIsLTAuNzU0NTIgMC45ODA0NCwtMS43Mzg1NiAwLjkwNzAzLC0yLjczNDM3Yy0wLjA3MzQxLC0wLjk5NTgyIC0wLjU0MDYyLC0xLjkyMTMyIC0xLjI5ODIyLC0yLjU3MTc5YzAsMCAtMC4yMjM5NywtMC4yMjE3NCAtMC42NzE5LC0wLjY2NTIyYy0wLjcwNTA2LC0wLjY1MDk5IC0xLjQ0NTM0LC0xLjI2Mjc3IC0yLjIxNzQ5LC0xLjgzMjU5Yy0xLjI2OTU0LC0wLjk0MTA5IC0yLjU5MzAyLC0xLjgwNzE1IC0zLjk2MzY2LC0yLjU5Mzc0Yy0xLjc2MDc2LC0xLjAwMzIxIC0zLjU5MzUzLC0xLjg3NDM5IC01LjQ4MzIzLC0yLjYwNjRjLTQuODM5NDksLTEuODA5NDcgLTkuOTgxOSwtMi42NzEwOSAtMTUuMTQ2ODksLTIuNTM3ODhjLTYuMDUxNzQsMC4yMTEyOCAtMTEuOTgxMTUsMS43NjMyNSAtMTcuMzYwMzgsNC41NDM5M2MtMS4zMzEzNSwwLjcxMDIgLTIuNjI1NzYsMS40ODc1NSAtMy44NzgyOCwyLjMyOTAyYy0xLjM0NjEsMC44ODg2OCAtMi42MzM3NiwxLjg2Mjg1IC0zLjg1NTA5LDIuOTE2NDRjLTAuODA2MzMsMC44MTQ0MyAtMS42MzI4MiwxLjY0OTIxIC0yLjQzOTE1LDIuNDYzNjRNMTk5LjE2NjQyLDE1MS4xMDU1NnYwYzAuODA2MzMsLTAuODE0NDMgMS42NzMxNCwtMS42ODk5NCAyLjYyMDU3LC0yLjY0NjljMS4yNzkzOSwtMS4xMDQyMiAyLjYyNzcsLTIuMTI2IDQuMDM2NzIsLTMuMDU5MTdjMC44OTQ1MSwtMC42MDIwOSAxLjgyNTUzLC0xLjE0ODA0IDIuNzg3NzEsLTEuNjM0NzNsMS4zMzMyOCwtMC43NzY1NGwwLjEyMDk1LC0wLjEyMjE2YzUuNzY0NjIsLTIuOTY4OTcgMTIuMTE0NDIsLTQuNjI2NTUgMTguNTk0NjksLTQuODUzOTljNS41NTI1MSwtMC4xMzkyMyAxMS4wODAxLDAuNzg5MTkgMTYuMjgyNDQsMi43MzQ4YzIuMDM3NjksMC43OTM2NiA0LjAxMzEzLDEuNzM4NjggNS45MDk3OCwyLjgyNzE0YzEuNDQxNzYsMC44MjA5OCAyLjgzMzE5LDEuNzI3MzUgNC4xNjY4NSwyLjcxNDI5YzAuNjk2NDIsMC40OTYyIDEuMzYyMTYsMS4wMzQxNCAxLjk5MzUzLDEuNjEwODRjMC4xMjIxNiwwLjEyMDk1IC0wLjA0MDcxLC0wLjA0MDMxIDAuMzQ2MTQsMC4zNDI2OWMwLjM4Njg2LDAuMzgzIDAuNjUxNTUsMC42NDUwNyAwLjc3MzcxLDAuNzY2MDFjMi42OTIxNiwyLjQxMTA3IDIuOTY0NzQsNi41MzEzMSAwLjYxMzYyLDkuMjc2MDFsLTAuNDgzODEsMC40ODg2NmMtMi4yNjcwNSwyLjI2MzMzIC01LjgyNTksMi41ODc3NiAtOC40NjQ4MywwLjc3MTY0bC0wLjcxMTYsLTAuNTAyOTVsLTAuNDY4MjksLTAuNDYzNjVjLTAuNDAwNTUsLTAuMjk3NDIgLTAuODIxNjQsLTAuNTY2MTQgLTEuMjYwMTQsLTAuODA0MDlsLTAuMTQyNTMsLTAuMTQxMTFjLTAuNzMxNTcsLTAuNDQyMDcgLTEuOTA5NDUsLTEuMDAzNDYgLTIuODg0MTMsLTEuNDQ0MzFjLTEuMzEwNjksLTAuNTMzNTEgLTIuNjUyNTgsLTAuOTg2OSAtNC4wMTgyNywtMS4zNTc2MWMtMy40MjAwNSwtMC44NDI0MSAtNi45NzA0NiwtMS4wMTcyOSAtMTAuNDU2NzMsLTAuNTE1MDRjLTMuODMwMDcsMC41NTUxIC03LjQ4NjA2LDEuOTY1MTcgLTEwLjY5NjkyLDQuMTI1N2wtMS4wMDkzNCwwLjczNDRsLTEuMTcwNzksMC44NTY3N2wtMC4zNjI4NCwwLjM2NjQ5bC0xLjA2ODM4LDEuMDc5MTJsLTAuODA2MzMsMC44MTQ0M2MtMS4xOTQ5OSwxLjM4MDcyIC0yLjI0NjQ4LDIuODc5MzggLTMuMTM4MjIsNC40NzI4N2MtMS42MDIyNywyLjc0OTA0IC0yLjY2MDE3LDUuNzgwODkgLTMuMTE1OTUsOC45Mjk5NmwtMC4wNTgyNSwwLjUwNjc4bDYuNTYzNDcsLTAuMTU0MzVjMi4yNDU5NSwtMC4wMTk5OSA0LjI4MDg3LDEuMzIwNTIgNS4xNDkxLDMuMzkxOTdjMC44NjgyMywyLjA3MTQ1IDAuMzk3MjQsNC40NjIyOSAtMS4xOTE3Niw2LjA0OTY3bC0xNy43OTk3NSwxNy45Nzg0OWMtMi4xNDg2OCwyLjE3MDI3IC01LjY0OTksMi4xODc3NiAtNy44MjAxNiwwLjAzOTA3bC0xNy45Nzg1LC0xNy43OTk3NmMtMS42MDQ3OCwtMS41NzE0MiAtMi4wOTk2NCwtMy45NTc0NSAtMS4yNTIxNSwtNi4wMzc0N2MwLjg0NzQ5LC0yLjA4MDAzIDIuODY4OTIsLTMuNDQwOCA1LjExNDk2LC0zLjQ0MzI1bDcuMzUzNDksLTAuMTc4NTZjMC4yMzUwMiwtMS42MjE5OCAwLjUzNzcsLTMuMjE3MjYgMC45MDgwNiwtNC43ODU4OWMxLjQ2ODUxLC01LjY2MTk4IDQuMDA3LC0xMC45OTA1OSA3LjQ3ODU1LC0xNS42OTgzNGMxLjIwOTM5LC0xLjY0NTY3IDIuNTQyNzEsLTMuMTk2NTkgMy45ODgzLC00LjYzOTJ6IiBmaWxsPSIjMWE2MjYyIiBzdHJva2U9IiMxYTYyNjIiIHN0cm9rZS13aWR0aD0iMyIvPjxwYXRoIGQ9Ik0yMDAuOTcxNjgsMTUzLjAwNjkxYzAuODA2MzMsLTAuODE0NDMgMS42Mzc5MywtMS42NTI3OCAyLjQ0NDI2LC0yLjQ2NzIxYzEuMjIxMzIsLTEuMDUzNiAyLjUxNjU1LC0yLjAzMTM1IDMuODYyNjUsLTIuOTIwMDRjMS4yNTI1MiwtMC44NDE0NiAyLjU1NDA2LC0xLjYyMDc1IDMuODg1NCwtMi4zMzA5NWM1LjM3OTI0LC0yLjc4MDY4IDExLjMzMzMsLTQuMzMwMTQgMTcuMzg1MDUsLTQuNTQxNDJjNS4xNjUsLTAuMTMzMjEgMTAuMzIxNywwLjczNzc2IDE1LjE2MTE5LDIuNTQ3MjNjMS44ODk2OSwwLjczMjAxIDMuNzI2NSwxLjYwNzczIDUuNDg3MjcsMi42MTA5NGMxLjM3MDY0LDAuNzg2NTggMi42OTY3MSwxLjY1NjM3IDMuOTY2MjUsMi41OTc0NmMwLjc3MjE1LDAuNTY5ODEgMS41MTM3OCwxLjE4MzkzIDIuMjE4ODYsMS44MzQ5MmMwLjQ0NzkzLDAuNDQzNDkgMC42NzIzMiwwLjY2NjAxIDAuNjcyMzIsMC42NjYwMWMwLjc1NzYsMC42NTA0NyAxLjIyNjAyLDEuNTc4NTQgMS4yOTk0MywyLjU3NDM1YzAuMDczNDEsMC45OTU4MiAtMC4yNTIxNywxLjk4MTg5IC0wLjkwNjIsMi43MzY0MmwtMC4yMDE1NCwwLjIwMzczYy0xLjI3ODU5LDEuMjYwMjYgLTMuMzQ5MzcsMS40ODA0OSAtNC44NDEyLDAuNDgxNzVjMCwwIC0wLjI0Mzk2LC0wLjI0MTAzIC0wLjc5MjQ5LC0wLjU0MjJjLTAuNjczODEsLTAuNTAxNjYgLTEuMzc4ODcsLTAuOTU2MDYgLTIuMTEyNzgsLTEuMzY0ODJjLTAuNzcxODgsLTAuNDAxMzQgLTEuNzQ1MjYsLTAuOTIwMTYgLTMuMDQ0ODksLTEuNTIxNDhjLTEuNDU1MjQsLTAuNTk4MiAtMi45NDM1MSwtMS4xMDA4NCAtNC40NjE4OSwtMS41MTMzYy0zLjc3MTM2LC0wLjkyODUgLTcuNjc1OTUsLTEuMTEyNjYgLTExLjUyMDUzLC0wLjU2MDk1Yy00LjI5NTY2LDAuNjM0NiAtOC4zNzMyNywyLjIzNDA3IC0xMS45NzA1Myw0LjY2NjE2Yy0wLjQ2MzY0LDAuNDY4MyAtMi4wMTExMywxLjQyODM1IC0yLjU1NTQsMS45NzgwOXYwYy0wLjM4MywwLjM4Njg2IC0wLjgwMTYsMC44MTM3OCAtMS4yNDUwOSwxLjI2MTcybC0wLjk0MzM4LDAuOTU1OThjLTEuMzY4MTUsMS41MTgxNSAtMi41NTUwOSwzLjE2MTY1IC0zLjU4MTcxLDQuOTI4NzZjLTEuNzQ1NzMsMy4wOTc4NSAtMi44NzQ3MSw2LjQ1NTU0IC0zLjM0NDM0LDkuOTgwMjVjLTAuMTY1NjMsMS4yNDk5NCAtMC4yNTk2LDIuNDk4MTggLTAuMjcwMzUsMy43NTlsOS40NzE1NywtMC4wNDU1NmMxLjA4MTk4LC0wLjAwOTM3IDIuMDQ5MDYsMC42MzY2NyAyLjQ2NzM3LDEuNjM0NTRjMC40MTgzMSwwLjk5Nzg4IDAuMjAyMTcsMi4yMzc0OCAtMC41NjMwNiwzLjAwMjQ2bC0xNy44MDQ3MywxNy45NTI1NWMtMS4wMzUzOCwxLjA0NTc4IC0yLjcyNTg2LDEuMDUyMDkgLTMuNzcxNjUsMC4wMTY3MWwtMTguMDU2NywtMTcuODgzNWMtMC43OTA1NiwtMC43NTc1OSAtMS4wMzgwNCwtMS45MjQ5MSAtMC42MjI1OCwtMi45MzhjMC40MTU0NSwtMS4wMTMxIDEuNDk0OTEsLTEuNjg3NjkgMi41ODk3NiwtMS42NzIxM2w5Ljk4NzMsLTAuMDMyNjdsLTAuMDY2OTQsLTAuMzIxMzZjMC4xNzM0OCwtMi4yNDQ1NCAwLjUwNjE5LC00LjQ4NDM4IDAuOTk4NDcsLTYuNjgxMTRjMS4zNjI4LC01LjMyNDkgMy43NDc0NiwtMTAuMzY1MTEgNy4wMDYwNSwtMTQuNzkxNTVjMS4xMzQ5MiwtMS41NDU3MyAyLjM5NDk1LC0zLjAwOTE0IDMuNzU0MDQsLTQuMzYxOTgiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O25vSG92ZXImcXVvdDs6ZmFsc2UsJnF1b3Q7b3JpZ0l0ZW0mcXVvdDs6WyZxdW90O1BhdGgmcXVvdDsseyZxdW90O2FwcGx5TWF0cml4JnF1b3Q7OnRydWUsJnF1b3Q7c2VnbWVudHMmcXVvdDs6W1tbMjY1LjEzOTExLDEzOC4zNzk3OV0sWzAsMF0sWy0xLjg0MjIyLC0xLjEzODM3XV0sW1syNTkuMzc0MTUsMTM1LjQwNTQzXSxbMS45OTUzNSwwLjg0MTU4XSxbLTUuNzE2MjYsLTIuNDE5NDFdXSxbWzI0MS4zMjI4LDEzMS41ODg3XSxbNi4yMDYzNSwwLjEwMTQ5XSxbLTIuNTQyMDcsMC4wMzQ5NF1dLFtbMjMzLjczMzQyLDEzMi4yMzg0NF0sWzIuNTExMDEsLTAuMzk3NjZdLFswLDBdXSxbMjMzLjM1MDEzLDEzMi4zMDgzN10sW1syMzAuNzAwMDIsMTIxLjM0NDY3XSxbMCwwXSxbLTAuMjY5ODIsLTEuMjA2NzRdXSxbWzIyOC4yMjc4OCwxMTkuMDI4MjRdLFsxLjIyMTcyLDAuMTkwODZdLFstMS4yMjE3MiwtMC4xOTA4Nl1dLFtbMjI1LjE2NzAyLDEyMC40ODAzMV0sWzAuNjI1MDEsLTEuMDY2OTRdLFswLDBdXSxbWzIxMC4yNzU4NSwxNDQuOTc2NzFdLFswLDBdLFstMC44NjMyNiwxLjQyMDA5XV0sW1syMTEuMjg0MDcsMTQ5LjExMTA3XSxbLTEuNDIwMDksLTAuODYzMjZdLFswLDBdXSxbWzIzNS42Njk4OCwxNjMuOTM1MDFdLFswLDBdLFsxLjA0MDgzLDAuNjQwMV1dLFtbMjM5LjAyMTc5LDE2My44MDEyNF0sWy0wLjk4NjUxLDAuNzIxMDFdLFswLjk4NjUxLC0wLjcyMTAxXV0sW1syNDAuMTY3MDEsMTYwLjY0ODJdLFswLjI5Mzg0LDEuMTg2MDVdLFswLDBdXSxbWzIzNy42NDcsMTUwLjIxNzk3XSxbMCwwXSxbMS4zODc3NywtMC4zMTg2Ml1dLFtbMjQxLjg1NzI1LDE0OS41MjA5M10sWy0xLjQxNjQxLDAuMTQ1NjNdLFszLjk5NDgzLC0wLjQwNzg1XV0sW1syNTMuNzQ4NCwxNTAuNTc3NDRdLFstMy44NjAzNywtMS4xMDU3Nl0sWzIuMjEwMTUsMC42NjQ2MV1dLFtbMjYwLjEyMDAzLDE1My4yMzkwMV0sWy0yLjAyNjE4LC0xLjEwNV0sWzAsMF1dLFtbMjYxLjQxOTUsMTU0LjAyODk0XSxbMCwwXSxbMC42MDgyNiwwLjM2OTc2XV0sW1syNjMuMTMzNjksMTU1LjA3MDk5XSxbLTAuNTI1MzIsLTAuMzE5MzNdLFswLDBdXSxbWzI2My4xMzM2OSwxNTUuMDcwOTldLFswLDBdLFswLjc0NjUsMC40NTM3OV1dLFtbMjY1Ljk3ODIsMTU3LjM2ODExXSxbLTAuNjM1OTEsLTAuMzg2NTZdLFszLjYxNDI5LDMuMzE0MDZdXSxbWzI3NC4yMzgyOSwxNjkuMzE4N10sWy0xLjgyMjg0LC00LjU1MjNdLFsxLjYxMzU4LDQuMDc4NTRdXSxbWzI3Ni42MzM1MywxODIuMTM0MzNdLFswLjAzMTU2LC00LjM4NjAyXSxbLTAuMDU1MTUsMS43NzU5Nl1dLFtbMjc2LjEzNjc0LDE4Ny40MzY0XSxbMC4yNzU3MywtMS43NTUzXSxbLTAuMzE5OTIsMS41ODUxOV1dLFtbMjc1LjI2MDUyLDE5MS4xODI1Ml0sWzAuMjM4NTgsLTAuOTUzMDZdLFstMC4yNTY2NywwLjkxMzI4XV0sW1syNzQuMzEyNzYsMTkzLjg2MjhdLFswLjM3NDQ3LC0wLjg3MTYzXSxbLTAuMTg3MDYsMC42ODE0NV1dLFtbMjczLjkyNDAyLDE5NC44NzYwNF0sWzAsMF0sWy0wLjcwNjExLDEuOTAwNDddXSxbWzI3NS42NjQ2MywxOTkuOTg1NzNdLFstMS43MTk0MywtMS4wNzQyMV0sWzAsMF1dLFtbMjc1Ljk0MTEyLDIwMC4xNTM4XSxbMCwwXSxbMS4wMDAyMSwwLjUyMDY4XV0sW1syNzkuMTgyNDIsMjAwLjQzMzU0XSxbLTEuMDc0NjMsMC4zNDE2MV0sWzEuMDc0NjMsLTAuMzQxNjFdXSxbWzI4MS42NjcyMiwxOTguMzMzNTJdLFstMC41MTU5NywxLjAwMjY2XSxbMCwwXV0sW1syODIuMjIxODUsMTk3LjQyMTEyXSxbLTAuMzY5NzYsMC42MDgyNl0sWzAuNTMwMzEsLTAuOTQ1MDldXSxbWzI4My42NTM3NCwxOTQuNTA1MDNdLFstMC40MjM1NiwwLjk5NzUxXSxbMC43MDEwNCwtMS42NDExN11dLFtbMjg1LjQ2NDExLDE4OS40NzEzN10sWy0wLjUwNDgzLDEuNzExNzNdLFswLjY0MDU1LC0yLjE5NzA0XV0sW1syODYuODkwMTYsMTgyLjc2NTJdLFstMC4zMDg4NSwyLjI2NzU5XSxbMC43MTkzNSwtNS43OTAyXV0sW1syODUuNzA4MzksMTY1LjQ2MTgyXSxbMS40OTk5MSw1LjYzODY0XSxbLTEuODE4MDYsLTYuNTkyMjRdXSxbWzI3Ni4xNjc0NCwxNDcuNTgyOTNdLFs0LjQ2NDIsNS4xODAxNl0sWy0xLjEyOTAzLC0xLjI3NjMxXV0sW1syNzIuNTkyNzMsMTQzLjkzMzE1XSxbMS4yNTI1NiwxLjE1NTMyXSxbLTEuMzI4OTUsLTEuMjQ1NzRdXSxbWzI2OC4zNzg4MywxNDAuNDYyOF0sWzEuNDc3NCwxLjA2NTQ2XSxbLTEuMTA1OTMsLTAuNjcyMjldXSxbWzI2NS4wMzMzOSwxMzguNDI5MTNdLFsxLjEwNTkzLDAuNjcyMjldLFswLDBdXV0sJnF1b3Q7ZmlsbENvbG9yJnF1b3Q7OlswLDAsMCwxXX1dfSIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjcyLjQ4NjIxOTk5OTk5OTk3OjcyLjQ4NjIyLS0+'; - const tr_icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9Ii0yLjI2MzMxLC0yLjQxMTU2LDIwLDIwIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMyLjI3NjYyLC0xNzIuNTczMTIpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0yNDMuOTE0MTksMTgzLjk1OTEzYzAuMzY1OSwwIDAuNjcyNDYsMC4yOTYxOSAwLjY3MjQ2LDAuNjcwMTFjMCwwLjM2NDA4IC0wLjMwNjU2LDAuNjU5MjkgLTAuNjcyNDYsMC42NTkyOWMtMC4zNjU5LDAgLTAuNjYyNTcsLTAuMjk1MjEgLTAuNjYyNTcsLTAuNjU5MjljMCwtMC4zNzM5MiAwLjI5NjY3LC0wLjY3MDExIDAuNjYyNTcsLTAuNjcwMTF6TTIzNS4wNDMwMiwxNzkuMDUwMDN2MC4wMDk4NGMtMC4xODc4OSwwLjQ4MjE2IC0wLjcwMTE0LDAuNzc3MzYgLTEuMjI1MjcsMC42NDk0NGMtMC41NjQ2NywtMC4xMjc5MiAtMC45MjA2OCwtMC42OTg2NCAtMC43OTExMywtMS4yNTk1MmMwLjU0MzksLTIuMzQxOTMgMi40NDI2MiwtNC4zODg2NiA0Ljg5NDE0LC01LjEyNjY3YzEuMjE3MzUsLTAuMzczOTIgMi41NDE1MiwtMC40MjMxMiAzLjgxNzIyLC0wLjE0ODU4YzEuMDk4NjgsMC4yMzYxNiAyLjE0NTk1LDAuNzM4MDEgMy4wNDU4NiwxLjQzNjY1bDAuOTg4OTEsLTAuOTg0YzAuNDU0OSwtMC40NDE4MiAxLjIyNzI1LC0wLjEyNzkyIDEuMjI3MjUsMC41MTI2NnY0LjY5MjcyYzAsMC4zOTQ1OSAtMC4zMjYzNCwwLjcxOTMxIC0wLjcyMjg5LDAuNzE5MzFoLTQuNzE2MTRjLTAuNjQzNzksMCAtMC45NjAyNCwtMC43Njg1MSAtMC41MTUyMywtMS4yMjExNWwxLjE1NzAzLC0xLjE1MDNjLTAuOTA5OCwtMC44MzY0MSAtMi4yNDM4NSwtMS4yNjkzNiAtMy41Nzk4NywtMS4wNDMwNWMtMS41MjI5MywwLjIyNTM0IC0yLjk3NjYzLDEuMzQ3MSAtMy41Nzk4NywyLjkxMjY2ek0yNDEuMjkzMzcsMTg1LjQwNjZjMC40MTUzNSwwIDAuNzQxNjksMC4zMjQ3MyAwLjc0MTY5LDAuNzM4MDFjMCwwLjQxMzI4IC0wLjMyNjM0LDAuNzQ3ODQgLTAuNzQxNjksMC43NDc4NGMtMC40MTUzNSwwIC0wLjc1MTU4LC0wLjMzNDU2IC0wLjc1MTU4LC0wLjc0Nzg0YzAsLTAuNDEzMjggMC4zMzYyMywtMC43MzgwMSAwLjc1MTU4LC0wLjczODAxek0yMzUuNjE2NTksMTgzLjgxMjMyYzAuNTA1MzQsMCAwLjkwOTgxLDAuNDAzNDQgMC45MDk4MSwwLjkwNTI4YzAsMC40OTIgLTAuNDA0NDcsMC44OTU0NCAtMC45MDk4MSwwLjg5NTQ0Yy0wLjUwNDM1LDAgLTAuOTA5OCwtMC40MDM0NSAtMC45MDk4LC0wLjg5NTQ0YzAsLTAuNTAxODUgMC40MDU0NSwtMC45MDUyOCAwLjkwOTgsLTAuOTA1Mjh6TTIzNC4wMzQ2MywxODEuMTU1NDFjMC41NDM5LDAgMC45ODg5MSwwLjQ0MjggMC45ODg5MSwwLjk4NGMwLDAuNTUxMDQgLTAuNDQ1MDEsMC45ODQgLTAuOTg4OTEsMC45ODRjLTAuNTQzOSwwIC0wLjk4ODkyLC0wLjQzMjk2IC0wLjk4ODkyLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMiwtMC45ODQgMC45ODg5MiwtMC45ODR6TTIzOC4yNjc2OCwxODUuMzQ2ODdjMC40NTQ5LDAgMC44MjA4LDAuMzczOTMgMC44MjA4LDAuODI2NTdjMCwwLjQ1MjY0IC0wLjM2NTksMC44MjY1NiAtMC44MjA4LDAuODI2NTZjLTAuNDU0OSwwIC0wLjgzMTY3LC0wLjM3MzkyIC0wLjgzMTY3LC0wLjgyNjU2YzAsLTAuNDUyNjQgMC4zNzY3NywtMC44MjY1NyAwLjgzMTY3LC0wLjgyNjU3eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIHN0cm9rZT0iIzI1ODM4MyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBkPSJNMjQzLjkxNDE5LDE4My45NTkxM2MwLjM2NTksMCAwLjY3MjQ2LDAuMjk2MTkgMC42NzI0NiwwLjY3MDExYzAsMC4zNjQwOCAtMC4zMDY1NiwwLjY1OTI4IC0wLjY3MjQ2LDAuNjU5MjhjLTAuMzY1OSwwIC0wLjY2MjU3LC0wLjI5NTIgLTAuNjYyNTcsLTAuNjU5MjhjMCwtMC4zNzM5MiAwLjI5NjY3LC0wLjY3MDExIDAuNjYyNTcsLTAuNjcwMTF6TTIzNS4wNDMwMiwxNzkuMDUwMDN2MC4wMDk4NGMtMC4xODc4OSwwLjQ4MjE2IC0wLjcwMTE0LDAuNzc3MzYgLTEuMjI1MjcsMC42NDk0NGMtMC41NjQ2NywtMC4xMjc5MiAtMC45MjA2OCwtMC42OTg2NCAtMC43OTExMywtMS4yNTk1M2MwLjU0MzksLTIuMzQxOTMgMi40NDI2MiwtNC4zODg2NiA0Ljg5NDE0LC01LjEyNjY2YzEuMjE3MzYsLTAuMzczOTIgMi41NDE1MSwtMC40MjMxMiAzLjgxNzIxLC0wLjE0ODU4YzEuMDk4NjksMC4yMzYxNiAyLjE0NTk1LDAuNzM4IDMuMDQ1ODYsMS40MzY2NWwwLjk4ODkyLC0wLjk4NGMwLjQ1NDksLTAuNDQxODIgMS4yMjcyNCwtMC4xMjc5MiAxLjIyNzI0LDAuNTEyNjd2NC42OTI3MmMwLDAuMzk0NTkgLTAuMzI2MzQsMC43MTkzMSAtMC43MjI5LDAuNzE5MzFoLTQuNzE2MTRjLTAuNjQzNzgsMCAtMC45NjAyNCwtMC43Njg1MSAtMC41MTUyMywtMS4yMjExNWwxLjE1NzAzLC0xLjE1MDNjLTAuOTA5OCwtMC44MzY0IC0yLjI0Mzg1LC0xLjI2OTM3IC0zLjU3OTg3LC0xLjA0MzA1Yy0xLjUyMjkzLDAuMjI1MzQgLTIuOTc2NjQsMS4zNDcxIC0zLjU3OTg4LDIuOTEyNjV6TTI0MS4yOTMzNiwxODUuNDA2NmMwLjQxNTM0LDAgMC43NDE2OSwwLjMyNDcyIDAuNzQxNjksMC43MzhjMCwwLjQxMzI4IC0wLjMyNjM0LDAuNzQ3ODQgLTAuNzQxNjksMC43NDc4NGMtMC40MTUzNCwwIC0wLjc1MTU4LC0wLjMzNDU2IC0wLjc1MTU4LC0wLjc0Nzg0YzAsLTAuNDEzMjggMC4zMzYyMywtMC43MzggMC43NTE1OCwtMC43Mzh6TTIzNS42MTY1OSwxODMuODEyMzJjMC41MDUzNCwwIDAuOTA5OCwwLjQwMzQ0IDAuOTA5OCwwLjkwNTI4YzAsMC40OTIgLTAuNDA0NDcsMC44OTU0NCAtMC45MDk4LDAuODk1NDRjLTAuNTA0MzUsMCAtMC45MDk4LC0wLjQwMzQ0IC0wLjkwOTgsLTAuODk1NDRjMCwtMC41MDE4NCAwLjQwNTQ2LC0wLjkwNTI4IDAuOTA5OCwtMC45MDUyOHpNMjM0LjAzNDYyLDE4MS4xNTU0MWMwLjU0MzksMCAwLjk4ODkyLDAuNDQyOCAwLjk4ODkyLDAuOTg0YzAsMC41NTEwNCAtMC40NDUwMSwwLjk4NCAtMC45ODg5MiwwLjk4NGMtMC41NDM5LDAgLTAuOTg4OTIsLTAuNDMyOTYgLTAuOTg4OTIsLTAuOTg0YzAsLTAuNTQxMiAwLjQ0NTAxLC0wLjk4NCAwLjk4ODkyLC0wLjk4NHpNMjM4LjI2NzY4LDE4NS4zNDY4N2MwLjQ1NDksMCAwLjgyMDgsMC4zNzM5MiAwLjgyMDgsMC44MjY1NmMwLDAuNDUyNjQgLTAuMzY1OSwwLjgyNjU2IC0wLjgyMDgsMC44MjY1NmMtMC40NTQ5LDAgLTAuODMxNjgsLTAuMzczOTIgLTAuODMxNjgsLTAuODI2NTZjMCwtMC40NTI2NCAwLjM3Njc4LC0wLjgyNjU2IDAuODMxNjgsLTAuODI2NTZ6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjo3LjcyMzM3OTk5OTk5OTk5Mjo3LjQyNjg4MDAwMDAwMDAxMS0tPg=='; - const tl_icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9Ii0yLjI2MzMxLC0yLjQxMTU2LDIwLDIwIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjM0LjUxMzMxLC0xNzUuMDgyNykiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIzOS4wMTE2OSwxODcuMDQwOGMwLDAuMzY0MDggLTAuMjk2NjcsMC42NTkyOSAtMC42NjI1NywwLjY1OTI5Yy0wLjM2NTksMCAtMC42NzI0NiwtMC4yOTUyMSAtMC42NzI0NiwtMC42NTkyOWMwLC0wLjM3MzkyIDAuMzA2NTYsLTAuNjcwMTEgMC42NzI0NiwtMC42NzAxMWMwLjM2NTksMCAwLjY2MjU3LDAuMjk2MTkgMC42NjI1NywwLjY3MDExek0yNDcuMjIwMjgsMTgxLjQ2MTZjLTAuNjAzMjQsLTEuNTY1NTYgLTIuMDU2OTQsLTIuNjg3MzIgLTMuNTc5ODcsLTIuOTEyNjZjLTEuMzM2MDIsLTAuMjI2MzEgLTIuNjcwMDcsMC4yMDY2NCAtMy41Nzk4NywxLjA0MzA1bDEuMTU3MDMsMS4xNTAzYzAuNDQ1MDEsMC40NTI2NCAwLjEyODU2LDEuMjIxMTUgLTAuNTE1MjMsMS4yMjExNWgtNC43MTYxNGMtMC4zOTY1NSwwIC0wLjcyMjg5LC0wLjMyNDcyIC0wLjcyMjg5LC0wLjcxOTMxdi00LjY5MjcyYzAsLTAuNjQwNTggMC43NzIzNSwtMC45NTQ0OCAxLjIyNzI1LC0wLjUxMjY2bDAuOTg4OTEsMC45ODRjMC44OTk5MSwtMC42OTg2NCAxLjk0NzE4LC0xLjIwMDQ5IDMuMDQ1ODYsLTEuNDM2NjVjMS4yNzU3LC0wLjI3NDU0IDIuNTk5ODcsLTAuMjI1MzQgMy44MTcyMiwwLjE0ODU4YzIuNDUxNTIsMC43MzgwMSA0LjM1MDI0LDIuNzg0NzQgNC44OTQxNCw1LjEyNjY3YzAuMTI5NTUsMC41NjA4OCAtMC4yMjY0NiwxLjEzMTYgLTAuNzkxMTMsMS4yNTk1MmMtMC41MjQxMywwLjEyNzkyIC0xLjAzNzM4LC0wLjE2NzI4IC0xLjIyNTI3LC0wLjY0OTQ0di0wLjAwOTg0ek0yNDEuNzIxNTIsMTg4LjU1NjE3YzAsMC40MTMyOCAtMC4zMzYyMywwLjc0Nzg0IC0wLjc1MTU4LDAuNzQ3ODRjLTAuNDE1MzUsMCAtMC43NDE2OSwtMC4zMzQ1NiAtMC43NDE2OSwtMC43NDc4NGMwLC0wLjQxMzI4IDAuMzI2MzQsLTAuNzM4MDEgMC43NDE2OSwtMC43MzgwMWMwLjQxNTM1LDAgMC43NTE1OCwwLjMyNDczIDAuNzUxNTgsMC43MzgwMXpNMjQ3LjU1NjUyLDE4Ny4xMjkxNmMwLDAuNDkxOTkgLTAuNDA1NDUsMC44OTU0NCAtMC45MDk4LDAuODk1NDRjLTAuNTA1MzQsMCAtMC45MDk4MSwtMC40MDM0NCAtMC45MDk4MSwtMC44OTU0NGMwLC0wLjUwMTg0IDAuNDA0NDcsLTAuOTA1MjggMC45MDk4MSwtMC45MDUyOGMwLjUwNDM1LDAgMC45MDk4LDAuNDAzNDMgMC45MDk4LDAuOTA1Mjh6TTI0OS4yMTc2LDE4NC41NTA5N2MwLDAuNTUxMDQgLTAuNDQ1MDIsMC45ODQgLTAuOTg4OTIsMC45ODRjLTAuNTQzOSwwIC0wLjk4ODkxLC0wLjQzMjk2IC0wLjk4ODkxLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMSwtMC45ODQgMC45ODg5MSwtMC45ODRjMC41NDM5LDAgMC45ODg5MiwwLjQ0MjggMC45ODg5MiwwLjk4NHpNMjQ0LjgyNzMsMTg4LjU4NWMwLDAuNDUyNjQgLTAuMzc2NzcsMC44MjY1NiAtMC44MzE2NywwLjgyNjU2Yy0wLjQ1NDksMCAtMC44MjA4LC0wLjM3MzkyIC0wLjgyMDgsLTAuODI2NTZjMCwtMC40NTI2NCAwLjM2NTksLTAuODI2NTcgMC44MjA4LC0wLjgyNjU3YzAuNDU0OSwwIDAuODMxNjcsMC4zNzM5MyAwLjgzMTY3LDAuODI2NTd6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgc3Ryb2tlPSIjMjU4MzgzIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxwYXRoIGQ9Ik0yMzkuMDExNjksMTg3LjA0MDhjMCwwLjM2NDA4IC0wLjI5NjY3LDAuNjU5MjggLTAuNjYyNTcsMC42NTkyOGMtMC4zNjU5LDAgLTAuNjcyNDYsLTAuMjk1MiAtMC42NzI0NiwtMC42NTkyOGMwLC0wLjM3MzkyIDAuMzA2NTYsLTAuNjcwMTEgMC42NzI0NiwtMC42NzAxMWMwLjM2NTksMCAwLjY2MjU3LDAuMjk2MTkgMC42NjI1NywwLjY3MDExek0yNDcuMjIwMzEsMTgxLjQ2MTZjLTAuNjAzMjQsLTEuNTY1NTUgLTIuMDU2OTUsLTIuNjg3MzEgLTMuNTc5ODgsLTIuOTEyNjVjLTEuMzM2MDIsLTAuMjI2MzIgLTIuNjcwMDcsMC4yMDY2NSAtMy41Nzk4NywxLjA0MzA1bDEuMTU3MDMsMS4xNTAzYzAuNDQ1MDEsMC40NTI2NCAwLjEyODU1LDEuMjIxMTUgLTAuNTE1MjMsMS4yMjExNWgtNC43MTYxNGMtMC4zOTY1NiwwIC0wLjcyMjksLTAuMzI0NzIgLTAuNzIyOSwtMC43MTkzMXYtNC42OTI3MmMwLC0wLjY0MDU5IDAuNzcyMzQsLTAuOTU0NDkgMS4yMjcyNCwtMC41MTI2N2wwLjk4ODkyLDAuOTg0YzAuODk5OTEsLTAuNjk4NjUgMS45NDcxNywtMS4yMDA0OSAzLjA0NTg2LC0xLjQzNjY1YzEuMjc1NywtMC4yNzQ1NCAyLjU5OTg1LC0wLjIyNTM0IDMuODE3MjEsMC4xNDg1OGMyLjQ1MTUyLDAuNzM4IDQuMzUwMjQsMi43ODQ3MyA0Ljg5NDE0LDUuMTI2NjZjMC4xMjk1NSwwLjU2MDg5IC0wLjIyNjQ2LDEuMTMxNjEgLTAuNzkxMTMsMS4yNTk1M2MtMC41MjQxMywwLjEyNzkyIC0xLjAzNzM4LC0wLjE2NzI4IC0xLjIyNTI3LC0wLjY0OTQ0di0wLjAwOTg0ek0yNDEuNzIxNTMsMTg4LjU1NjE2YzAsMC40MTMyOCAtMC4zMzYyNCwwLjc0Nzg0IC0wLjc1MTU4LDAuNzQ3ODRjLTAuNDE1MzUsMCAtMC43NDE2OSwtMC4zMzQ1NiAtMC43NDE2OSwtMC43NDc4NGMwLC0wLjQxMzI4IDAuMzI2MzUsLTAuNzM4IDAuNzQxNjksLTAuNzM4YzAuNDE1MzUsMCAwLjc1MTU4LDAuMzI0NzIgMC43NTE1OCwwLjczOHpNMjQ3LjU1NjUyLDE4Ny4xMjkxNmMwLDAuNDkyIC0wLjQwNTQ1LDAuODk1NDQgLTAuOTA5OCwwLjg5NTQ0Yy0wLjUwNTMzLDAgLTAuOTA5OCwtMC40MDM0NCAtMC45MDk4LC0wLjg5NTQ0YzAsLTAuNTAxODQgMC40MDQ0NiwtMC45MDUyOCAwLjkwOTgsLTAuOTA1MjhjMC41MDQzNCwwIDAuOTA5OCwwLjQwMzQ0IDAuOTA5OCwwLjkwNTI4ek0yNDkuMjE3NjEsMTg0LjU1MDk3YzAsMC41NTEwNCAtMC40NDUwMiwwLjk4NCAtMC45ODg5MiwwLjk4NGMtMC41NDM5MSwwIC0wLjk4ODkyLC0wLjQzMjk2IC0wLjk4ODkyLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMiwtMC45ODQgMC45ODg5MiwtMC45ODRjMC41NDM5MSwwIDAuOTg4OTIsMC40NDI4IDAuOTg4OTIsMC45ODR6TTI0NC44MjczMSwxODguNTg0OTljMCwwLjQ1MjY0IC0wLjM3Njc4LDAuODI2NTYgLTAuODMxNjgsMC44MjY1NmMtMC40NTQ5LDAgLTAuODIwOCwtMC4zNzM5MiAtMC44MjA4LC0wLjgyNjU2YzAsLTAuNDUyNjQgMC4zNjU5LC0wLjgyNjU2IDAuODIwOCwtMC44MjY1NmMwLjQ1NDksMCAwLjgzMTY4LDAuMzczOTIgMC44MzE2OCwwLjgyNjU2eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6NS40ODY2OTE0OTU5NDA0Mjo0LjkxNzMwNDExMjkyMTc1NS0tPg=='; - let gx = [0]; - let gy = [0]; - let gr = [90]; - let gs = [100]; - let rm = [0]; - class Global_Coordinate { - getInfo() { - return { - id: 'globalCoordinate', - color1: '#2ea4a4', - menuIconURI: icon, - name: 'Global Coordinate', - blocks: [ - { - opcode: 'SET', - filter: [Scratch.TargetType.SPRITE], - blockType: Scratch.BlockType.COMMAND, - blockIconURI: icon, - text: 'go to x: [x] y: [y] direction [r] size [s] - use screens [screen]', - arguments: { - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - r: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '90' - }, - s: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'rotation_mode', - filter: [Scratch.TargetType.SPRITE], - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s rotation mode to [m]', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - m: { - type: Scratch.ArgumentType.NUMBER, - menu: 'rotation_mode' - } - } - }, - { - opcode: 'set', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s x: [x] y: [y] direction: [r] size: [s]', - arguments: { - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - r: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '90' - }, - s: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - '---', - { - opcode: 'Set_Co', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s x [x] y: [y]', - arguments: { - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'Set_GX', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s x to [x]', - arguments: { - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'Set_GY', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s y to: [y]', - arguments: { - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - '---', - { - opcode: 'CX', - blockType: Scratch.BlockType.COMMAND, - text: 'change screens [screen] \'s x by [x]', - arguments: { - x: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'CY', - blockType: Scratch.BlockType.COMMAND, - text: 'change screens [screen] \'s y by [y]', - arguments: { - y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - '---', - { - opcode: 'Set_GR', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s direction [r]', - arguments: { - r: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '90' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'TR', - blockType: Scratch.BlockType.COMMAND, - text: 'turn [tr_icon] [r] degrees - screens [screen]', - arguments: { - r: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '15' - }, - tr_icon: { - type: Scratch.ArgumentType.IMAGE, - dataURI: tr_icon - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'TL', - blockType: Scratch.BlockType.COMMAND, - text: 'turn [tr_icon] [r] degrees - screens [screen]', - arguments: { - r: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '15' - }, - tr_icon: { - type: Scratch.ArgumentType.IMAGE, - dataURI: tl_icon - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - '---', - { - opcode: 'Set_si', - blockType: Scratch.BlockType.COMMAND, - text: 'set screens [screen] \'s size [s]', - arguments: { - s: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'CS', - blockType: Scratch.BlockType.COMMAND, - text: 'change screens [screen] \'s size by [s]', - arguments: { - s: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - '---', - { - opcode: 'x', - blockType: Scratch.BlockType.REPORTER, - text: 'screens [screen] x', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'y', - blockType: Scratch.BlockType.REPORTER, - text: 'screens [screen] y', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'r', - blockType: Scratch.BlockType.REPORTER, - text: 'screens [screen] direction', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 's', - blockType: Scratch.BlockType.REPORTER, - text: 'screens [screen] size', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'rm', - blockType: Scratch.BlockType.BOOLEAN, - text: 'screens [screen] rotation mode is screen?', - arguments: { - screen: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - ], - menus: { - rotation_mode: { - acceptReporters: true, - items: [ - { - text: 'center of stage', - value: '0' - }, - { - text: 'center of screen', - value: '1' - } - ] - } - } - }; - } - SET(args, { target }) { - if (isNaN(args.x)) { - args.x = 0; - } - if (isNaN(args.y)) { - args.y = 0; - } - if (isNaN(args.r)) { - args.r = 0; - } - if (isNaN(args.s)) { - args.s = 0; - } - target.setSize((args.s / 100) * gs[args.screen - 1]); - target.setDirection((- ((args.r - (gr[args.screen - 1] - 90) - 90))) + 90); - if (rm[args.screen - 1] == 1) { - target.setXY( - (gx[args.screen - 1] / 100 * gs[args.screen - 1]) + (gs[args.screen - 1] / 100) * ((- (args.x)) * Math.cos((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI) - (- (args.y)) * Math.sin((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI)), - (gy[args.screen - 1] / 100 * gs[args.screen - 1]) + (gs[args.screen - 1] / 100) * ((- (args.x)) * Math.sin((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI) + (- (args.y)) * Math.cos((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI)), - true - ); - } else { - target.setXY( - (gs[args.screen - 1] / 100) * ((- (args.x + gx[args.screen - 1])) * Math.cos((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI) - (- (args.y + gy[args.screen - 1])) * Math.sin((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI)), - (gs[args.screen - 1] / 100) * ((- (args.x + gx[args.screen - 1])) * Math.sin((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI) + (- (args.y + gy[args.screen - 1])) * Math.cos((((- ((gr[args.screen - 1] - 90 - 90))) + 90)) / 180 * Math.PI)), - true - ); - } - } - rotation_mode(args) { - rm[args.screen - 1] = args.m; - } - set({ x, y, r, s, screen }) { - if (!isNaN(x)) { - gx[screen - 1] = x; - } else { - gx[screen - 1] = 0; - } - if (!isNaN(y)) { - gy[screen - 1] = y; - } else { - gy[screen - 1] = 0; - } - if (!isNaN(r)) { - gr[screen - 1] = r; - } else { - gr[screen - 1] = 0; - } - if (!isNaN(s)) { - gs[screen - 1] = s; - } else { - gs[screen - 1] = 0; - } - } - Set_Co({ x, y, screen }) { - if (!isNaN(x + y)) { - gx[screen - 1] = x; - gy[screen - 1] = y; - } else { - if (isNaN(x)) { - x = 0; - } - if (isNaN(y)) { - y = 0; - } - } - } - Set_GX({ x, screen }) { - if (!isNaN(x)) { - gx[screen - 1] = x; - } else { - gx[screen - 1] = 0; - } - } - Set_GY({ y, screen }) { - if (!isNaN(y)) { - gy[screen - 1] = y; - } else { - gy[screen - 1] = 0; - } - } - CX({ x, screen }) { - if (!isNaN(x)) { - gx[screen - 1] += x; - } - } - CY({ y, screen }) { - if (!isNaN(y)) { - gy[screen - 1] += y; - } - } - Set_GR({ r, screen }) { - if (!isNaN(r)) { - gr[screen - 1] = r; - } else { - gr[screen - 1] = 0; - } - } - TR({ r, screen }) { - if (!isNaN(r)) { - gr[screen - 1] += r; - } - } - TL({ r, screen }) { - if (!isNaN(r)) { - gr[screen - 1] -= r; - } - } - Set_si({ s, screen }) { - if (!isNaN(s)) { - gs[screen - 1] = s; - } else { - gs[screen - 1] = 0; - } - if (gs[screen - 1] < 0) { - gs[screen - 1] = 0; - } - } - CS({ s, screen }) { - if (!isNaN(s)) { - gs[screen - 1] += s; - } - if (gs[screen - 1] < 0) { - gs[screen - 1] = 0; - } - } - x({ screen }) { - return gx[screen - 1]; - } - y({ screen }) { - return gy[screen - 1]; - } - r({ screen }) { - return gr[screen - 1]; - } - s({ screen }) { - return gs[screen - 1]; + "use strict"; + const icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSItMjcuNTEzODgsLTI3LjUxMzg4LDIwMCwyMDAiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNjcuNTEzNzgsLTEwNy41MTM3OCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTMxMC40ODYyMiwxMDkuNjM4Nzd2MzEuNzQ1MTdsLTEwLjA0MDU2LC0xMi4zODA2MmwtMTQuNzc1MSwxNS4yNzg5OWwtMTAuMDc3ODIsLTEwLjA3NzgybDE1LjI2Mjg3LC0xNC43NjAwMWwtMTIuMTE0NTUsLTkuODA1NzF6TTIwNC4yODIzMSwyMjUuNjcwNTVsLTE1LjI3MDkzLDE0Ljc2NzA1bDEyLjI0NzU3LDkuOTIzNjRoLTMxLjc0NTE3di0zMS43NDUxN2w5LjkyMzY3LDEyLjI0NzYxbDE0Ljc2NzA1LC0xNS4yNzA5M3pNMTY5LjYzODc3LDEwOS41MTM3OGgzMS43NDUxN2wtMTIuMzgwNjIsMTAuMDQwNTdsMTUuMjc4OTksMTQuNzc1MWwtMTAuMDc3ODIsMTAuMDc3ODJsLTE0Ljc2LC0xNS4yNjI4N2wtOS44MDU3MSwxMi4xMTQ1NnpNMjg1LjY3MDU1LDIxNS43MTc2OGwxNC43NjcwNCwxNS4yNzA5NGw5LjkyMzY0LC0xMi4yNDc1N3YzMS43NDUxN2gtMzEuNzQ1MTdsMTIuMjQ3NiwtOS45MjM2NmwtMTUuMjcwOTMsLTE0Ljc2NzA1eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzFhNjI2MiIgc3Ryb2tlLXdpZHRoPSI0Ii8+PHBhdGggZD0iTTI3OS45NTE2NCwyMDcuODA0OWMtMC44MjY0OSwwLjgzNDc5IC0xLjY3MzE0LDEuNjg5OTQgLTIuNDk5NjMsMi41MjQ3MmMtMS4yNzM0LDEuMDk2NjYgLTIuNjE0OTMsMi4xMTE2MiAtNC4wMTY1NSwzLjAzODhjLTAuODA5NzcsMC41NjE0MiAtMS42NTI5NCwxLjA3MzA5IC0yLjUyNDg1LDEuNTMyMTFsLTEuMzczOCwwLjc3Njc0Yy01LjYwODg3LDIuODUxNTIgLTExLjc3OTcyLDQuNDI1MjcgLTE4LjA2OTE1LDQuNjA4MjVjLTUuMzM4NDMsMC4xOTYxMyAtMTAuNjY1MTUsLTAuNjI5OTggLTE1LjY5MzQsLTIuNDMzODRjLTEuOTk0NDEsLTAuNzc4NzUgLTMuOTM0NywtMS42ODk1NCAtNS44MDc5OCwtMi43MjYzNWMtMS42MDU0NiwtMC45ODQ3MyAtMi45ODc1NSwtMS44NjkyNiAtNC4xMjY1NCwtMi43NTUwMWMtMC42NDA0MSwtMC40NTg0OSAtMS4yNTE4NiwtMC45NTYxNCAtMS44MzA4NSwtMS40OTAwOWwtMC40MDcyMSwtMC40MDMxNmwtMC42NzE5MSwtMC42NjUyMmMtMi4xNjQ5NCwtMS44NjUzOSAtMi40MjUxOSwtNS4xMjU3NyAtMC41ODM1NCwtNy4zMTA5NGwwLjIyMTc0LC0wLjIyMzk3YzEuNzUzMTYsLTEuNzg3OTEgNC41MzcyOCwtMi4wNTgxNSA2LjYwMTU2LC0wLjY0MDc5YzAsMCAwLjI4NTA1LDAuMjgyMjMgMC44MzM1OCwwLjU4MzM4YzAsMCAwLjI2NDY4LDAuMjYyMDUgMC41MDkwMSwwLjUwMzk1YzAuNDIxMTYsMC4zMDQzMiAwLjg2MjUyLDAuNTc5NjUgMS4zMjEwMiwwLjgyNDA2YzAuODUzMzMsMC40ODE5OSAxLjkyOTgyLDEuMDIzNjIgMy4xODg3MywxLjU4NDYyYzEuMzcyMDUsMC41NzY1MiAyLjc4MjUzLDEuMDU2ODggNC4yMjEyNiwxLjQzNzYzYzMuNjExOSwwLjg1NjQ3IDcuMzU1ODMsMS4wMDI5MSAxMS4wMjM2LDAuNDMxMTdjNC4wNzgyLC0wLjU5NjU5IDcuOTY5NDQsLTIuMTA0OTMgMTEuMzg0MzMsLTQuNDEyNzh2MGwwLjk2ODgxLC0wLjczNDJjMC40ODcwNSwtMC4zMjk3OCAwLjk1MjIsLTAuNjkwNzYgMS4zOTI1NCwtMS4wODA3NGwxLjM3MDc2LC0xLjM4NDUzbDAuODA2MzMsLTAuODE0NDNjMS4zMDExLC0xLjQzNDcxIDIuNDQ3NjksLTMuMDAyMyAzLjQyMDgzLC00LjY3Njg5YzEuNjk4OTgsLTIuOTA4MzMgMi44MTE3NCwtNi4xMjEzNSAzLjI3NTM4LC05LjQ1NzUyYzAuMDc3MzksLTAuNzI5NzQgMC4xNTQ5OSwtMS40MTg5OCAwLjE3MTcxLC0yLjEyODE2bC04LjEwMzgsMC4wNDA0OGMtMS42NDk5MywwLjAwNjQzIC0zLjE0MjcyLC0wLjk3NzQzIC0zLjc4NzQyLC0yLjQ5NjE5Yy0wLjY0NDcsLTEuNTE4NzYgLTAuMzE1NSwtMy4yNzYwOCAwLjgzNTI0LC00LjQ1ODQ4bDE3LjgxOTkxLC0xNy45OTg4NmMwLjc2NjA1LC0wLjc4NDY4IDEuODE0NjgsLTEuMjI5NTMgMi45MTEyNywtMS4yMzVjMS4wOTY1OCwtMC4wMDU0OCAyLjE0OTYxLDAuNDI4ODggMi45MjM0NywxLjIwNTg2bDE3Ljk3ODUsMTcuNzk5NzdjMS42MDAyMiwxLjYwNjQ3IDEuNjA0MTYsNC4yMDMyNyAwLjAwODc4LDUuODE0NTdjLTAuNzYwMTEsMC43NjM0MiAtMS43OTM2NywxLjE5MTYxIC0yLjg3MDk4LDEuMTg5NDNsLTguNjkxNDIsMC4wMjMxN2MtMC4yMjM5NiwxLjk2MDIgLTAuNTcyOSwzLjkwNDExIC0xLjA0NDcsNS44MTk4M2MtMS4zNzg4NSw1LjU0NTA5IC0zLjgxNTUyLDEwLjc3MTc0IC03LjE3NjE4LDE1LjM5MjkyYy0xLjE4NzcxLDEuNTYzNjYgLTIuNDk0NDQsMy4wMzMyNyAtMy45MDg0OCw0LjM5NTY4eiIgZmlsbD0iIzFhNjI2MiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMjIzLjcyODU0LDIwMy40MjU5OGwtMC4xNDExMSwwLjE0MjUzYy0xLjM2NDc3LDEuNTc0NTUgLTEuMTk4ODUsMy45NTYzNyAwLjM3MTA0LDUuMzI2NTJsMC42NzE5LDAuNjY1MjJjMC43MDQ5OCwwLjY1MTA3IDEuNDQ1MjcsMS4yNjI4NiAyLjIxNzQ5LDEuODMyNTljMS4wOTgyNywwLjg0NTQzIDIuNDM5NjQsMS42ODk2NSAzLjk2Mzg2LDIuNjM0MjZjMS43NzY0MSwwLjk4MDUxIDMuNjE0NzYsMS44NDQzNSA1LjUwMzM4LDIuNTg2MDRjNC44Mzk0OSwxLjgwOTQ3IDkuOTgxOSwyLjY3MTA5IDE1LjE0Njg5LDIuNTM3ODhjNi4wNTE3NCwtMC4yMTEyOCAxMS45ODExNSwtMS43NjMyNSAxNy4zNjAzNywtNC41NDM5M2MxLjMzMTI5LC0wLjcxMDMgMi42MjU3MiwtMS40ODc2MiAzLjg3ODI4LC0yLjMyOTAyYzEuMzM4LC0wLjg4MzY2IDIuNjE4ODgsLTEuODUwOTQgMy44MzQ5MywtMi44OTYwOGMwLjgwNjMzLC0wLjgxNDQzIDEuNjMyODIsLTEuNjQ5MjEgMi40MzkxNSwtMi40NjM2NGMxLjM2MDAyLC0xLjM1MTk4IDIuNjEyNjgsLTIuODA3ODQgMy43NDY2MSwtNC4zNTQzNWMzLjI2NDM5LC00LjQzMzE3IDUuNjQ2NjIsLTkuNDUyMjYgNy4wMTcxNCwtMTQuNzg0MzRjMC40OTIzOSwtMi4xOTY3NCAwLjgyNjU3LC00LjQyNTk3IDAuOTk5OTQsLTYuNjcwNTNsMC4wMTg1NCwtMC4zNDQ1MWw5Ljk4ODAzLC0wLjAyOTY0YzAuNjcxMTUsLTAuMDIwODMgMS4zMDg0MSwtMC4yOTk3OSAxLjc3OSwtMC43Nzg3NmMwLjUwNjEzLC0wLjUwMDMzIDAuNzkxMDcsLTEuMTgyMzUgMC43OTEzMiwtMS44OTQwNGMwLjAwMDI1LC0wLjcxMTY5IC0wLjI4NDIyLC0xLjM5MzkgLTAuNzg5OTksLTEuODk0NThsLTE3Ljk3ODUsLTE3Ljc5OTc2Yy0xLjAzNzgyLC0xLjAyNzYyIC0yLjcwOTQ1LC0xLjAyODMyIC0zLjc0ODEsLTAuMDAxNTNsLTE3LjgxOTkzLDE3Ljk5ODg2Yy0wLjc1MzMxLDAuNzY5NDQgLTAuOTY5NDgsMS45MTY2NCAtMC41NDc4NSwyLjkwNzQ2YzAuNDIxNjMsMC45OTA4MyAxLjM5ODE1LDEuNjMwNDggMi40NzQ5MiwxLjYyMTE2bDkuNDgxNDUsLTAuMDQ3MzZjMC4wMTM0NywxLjI2NzYxIC0wLjA2ODI0LDIuNTM0NDYgLTAuMjQ0NDUsMy43ODk4NGMtMC40NTcwMSwzLjUyNzYyIC0xLjU5NjAzLDYuOTMyNjIgLTMuMzUzNTksMTAuMDI1MTljLTEuMDE3OTYsMS43NjEzNyAtMi4yMTg0NiwzLjQxMDczIC0zLjU4MTY5LDQuOTIwODFsLTAuOTQ3NDUsMC45NTY5NmMtMC40NDM0OSwwLjQ0NzkzIC0wLjg2NjgsMC44NzU1MSAtMS4yNDk4MiwxLjI2MjM2djBjLTAuNTQ0MjYsMC41NDk3NCAtMi4wOTk0OSwxLjUwOTczIC0yLjU4MzMsMS45OTgzOGMtMy41OTM1MywyLjQyMTIyIC03LjY4NDAyLDQuMDA2MjUgLTExLjk3MDc0LDQuNjM4NTZjLTMuODQ0MDYsMC41NjI2MyAtNy43NjEwNCwwLjM2ODg2IC0xMS41MzA3OSwtMC41NzA0NWMtMS41MDk1MiwtMC40MTQ5OSAtMi45ODgwMSwtMC45MzU2NiAtNC40MjQ0NiwtMS41NTgxOGMtMS4yOTk2NCwtMC42MDEzMSAtMi4zOTY2OSwtMS4yMDM2MyAtMy4yMDkxLC0xLjYwNDc4Yy0wLjgxMjQxLC0wLjQwMTE0IC0xLjU4NTUxLC0xLjA0NTYgLTIuMTM0MDIsLTEuMzQ2NzZjLTAuNTQ4NTIsLTAuMzAxMTUgLTAuNzkyODUsLTAuNTQzMDYgLTAuNzkyODUsLTAuNTQzMDZjLTEuNDk3ODMsLTEuMDE2MjkgLTMuNTA3NjQsLTAuODE4ODIgLTQuNzc5MDIsMC40Njk1OU0yMjEuNTQ5OTMsMjAxLjI2OTA0djBjMi4xOTA0NywtMi4zODA3IDUuNzg1MTMsLTIuODE5MjggOC40ODM3NywtMS4wMzUxMmMwLjE0MjMzLDAuMTAwNTkgMC41Njk5LDAuNTIzOSAwLjgzMzU3LDAuNTgzMzdjMC4yNjM2NywwLjA1OTQ3IDAuNDQ2OTMsMC4yNDA4OSAwLjY1MDUzLDAuNDQyNDdjMC4zNjM2MSwwLjI2NTIgMC43NDM3NiwwLjUwNjkxIDEuMTM4MTcsMC43MjM2OGwwLjE0MjUzLDAuMTQxMTFjMC43OTI0NSwwLjQ2MjAxIDEuOTkwNywxLjA0MzU4IDIuODg0MTMsMS40NDQzMWMxLjMxMjUxLDAuNTUyNjggMi42NjIsMS4wMTMwNSA0LjAzODYyLDEuMzc3NzZjMy40MTkyMSwwLjg0ODE2IDYuOTcwNzUsMS4wMjMxIDEwLjQ1NjczLDAuNTE1MDRjMy44MTY3NiwtMC41Njk1MiA3LjQ2MTY4LC0xLjk3MTA1IDEwLjY3Njc2LC00LjEwNTM0bDEuMDA5OTQsLTAuNjEyODRsMS4xNzA3OSwtMC44NTY3N2wwLjM2Mjg0LC0wLjM2NjQ5YzAuMzIyNTMsLTAuMzI1NzcgMC42ODUzOCwtMC42OTIyNiAxLjA0ODIyLC0xLjA1ODc1bDAuNzI1NywtMC43MzI5OGMxLjIxNTg2LC0xLjM1NjQzIDIuMjg4MDIsLTIuODM1MTMgMy4xOTkzLC00LjQxMjRjMS42MTQyMiwtMi43ODM3MiAyLjY1OTM4LC01Ljg2MDM2IDMuMDc0ODMsLTkuMDUxMzFsMC4wNTgyNSwtMC41MDY3OGwtNi40ODMwNCwwLjAzMjM5Yy0yLjI0MDY5LDAuMDE5ODggLTQuMjcxOTEsLTEuMzE0NDIgLTUuMTQzNDQsLTMuMzc4NzhjLTAuODcxNTMsLTIuMDY0MzYgLTAuNDExMDksLTQuNDUwNjMgMS4xNjU5MywtNi4wNDI1bDE3LjkyMDcxLC0xOC4xMDA2NmMyLjE1MjAxLC0yLjE1MTIxIDUuNjM3NjksLTIuMTU5NTggNy44LC0wLjAxODcxbDE3LjkxNzQyLDE3LjczOTI5YzIuMTYyNDYsMi4xNjMxNiAyLjE3MDkyLDUuNjY2OTcgMC4wMTg5MSw3Ljg0MDUyYy0xLjAzMTM5LDEuMDQwNTkgLTIuNDM2OTcsMS42MjQxNCAtMy45MDIwOSwxLjYyMDAzbC03LjQzNTMzLDAuMDE2ODhjLTAuMjM1MDIsMS42MjE5OCAtMC41Mzc3LDMuMjE3MjcgLTAuOTA4MDUsNC43ODU5Yy0xLjQ3MjIsNS42NzA1OSAtNC4wMTc2OSwxMS4wMDY0MyAtNy40OTg3MSwxNS43MTg2OWMtMS4yMDcyNywxLjY0NzM4IC0yLjU0MDcxLDMuMTk4NDQgLTMuOTg4Myw0LjYzOTJjLTAuODI2NDksMC44MzQ3OSAtMS42OTMzLDEuNzEwMyAtMi42NDA3MywyLjY2NzI2Yy0xLjI3NDIxLDEuMDk1NjcgLTIuNjE1NjgsMi4xMTA1OCAtNC4wMTY1NiwzLjAzODhjLTAuODczMTUsMC42MjA2IC0xLjc4Mzc2LDEuMTg2NzUgLTIuNzI2NjMsMS42OTUybC0xLjUxNDQ5LDEuMDAwMzFsLTAuMTIwOTUsMC4xMjIxNmMtNS43NjQ2MiwyLjk2ODk3IC0xMi4xMTQ0Miw0LjYyNjU0IC0xOC41OTQ2OSw0Ljg1Mzk5Yy01LjU1MjUxLDAuMTM5MjMgLTExLjA4MDEsLTAuNzg5MTkgLTE2LjI4MjQ0LC0yLjczNDhjLTIuMDAwMTYsLTAuNzg4NDQgLTMuOTQ3MDQsLTEuNzA1ODUgLTUuODI4MzQsLTIuNzQ2NTF2MGMtMS41ODUzLC0xLjAwNTA4IC0zLjAwNzkyLC0xLjg4OTQyIC00LjEyNjU0LC0yLjc1NTAxYy0wLjY5NjQyLC0wLjQ5NjIgLTEuMzYyMTYsLTEuMDM0MTQgLTEuOTkzNTMsLTEuNjEwODRjLTAuMTIyMTYsLTAuMTIwOTUgLTAuMjQ0MzMsLTAuMjQxOSAtMC4zNDYxNCwtMC4zNDI2OWwtMC43NTMzNSwtMC43NDU4NmMtMS4zMjUxMiwtMS4xNTc2MiAtMi4xMzQ3MywtMi43OTUyMyAtMi4yNDk5NSwtNC41NTEwMmMtMC4xMTUyMiwtMS43NTU3OSAwLjQ3MzQ1LC0zLjQ4NTE2IDEuNjM1OTMsLTQuODA2MDNsMC4yODIyMiwtMC4yODUwNXoiIGZpbGw9IiMxYTYyNjIiIHN0cm9rZT0iIzFhNjI2MiIgc3Ryb2tlLXdpZHRoPSIzIi8+PHBhdGggZD0iTTIyMy41NDQ1MiwyMDMuMzAwMzNjMS4yNzEzOCwtMS4yODg0MSAzLjM2MzY0LC0xLjUxODEzIDQuODYxNDgsLTAuNTAxODRjMCwwIDAuMjQzOTYsMC4yNDEwMyAwLjc5MjQ5LDAuNTQyMmMwLjU0ODUyLDAuMzAxMTUgMS4zMjA1NSwwLjk0MzMzIDIuMTMyOTUsMS4zNDQ0NmMwLjgxMjQxLDAuNDAxMTQgMS45MDc2NiwxLjAwMDIzIDMuMjA3MywxLjYwMTU1YzEuNDM2NDUsMC42MjI1MiAyLjkxMjAzLDEuMTM5MDEgNC40MjE1NiwxLjU1Mzk5YzMuNzY5NzYsMC45MzkzMiA3LjY3NjQzLDEuMTIzNTggMTEuNTIwNSwwLjU2MDk1YzQuMjg2NzIsLTAuNjMyMyA4LjM1NjgsLTIuMjI0NTcgMTEuOTUwMzIsLTQuNjQ1NzljMC40ODM4LC0wLjQ4ODY2IDIuMDMxMjIsLTEuNDQ4NjcgMi41NzU0OSwtMS45OTg0MXYwYzAuMzgzLC0wLjM4Njg2IDAuODAxNTksLTAuODEzNzYgMS4yNDUwNiwtMS4yNjE3MWwwLjk0MzM4LC0wLjk1NTk2YzEuMzYzMjMsLTEuNTEwMDggMi41NDM3MiwtMy4xNDcwMiAzLjU2MTY5LC00LjkwODM5YzEuNzU3NTUsLTMuMDkyNTcgMi44ODczNiwtNi40NTI3NCAzLjM0NDM3LC05Ljk4MDM2YzAuMTc2MjEsLTEuMjU1MzggMC4yNjM1LC0yLjUxMTQ2IDAuMjUwMDIsLTMuNzc5MDdsLTkuNDUxNCwwLjA2NTY3Yy0xLjA3Njc3LDAuMDA5MzIgLTIuMDQwMTUsLTAuNjMwNDcgLTIuNDYxNzgsLTEuNjIxMjljLTAuNDIxNjMsLTAuOTkwODMgLTAuMjE1ODEsLTIuMjI1ODkgMC41Mzc1MSwtMi45OTUzMmwxNy44NDUwNiwtMTcuOTkzMjRjMS4wMzg2NiwtMS4wMjY3NyAyLjcxMzY2LC0xLjAyMzk3IDMuNzUxNDgsMC4wMDM2NWwxNy45OTU1NywxNy44MjI5NWMwLjUwNTc5LDAuNTAwNjkgMC43OTA4NiwxLjE4NTA0IDAuNzkwNiwxLjg5NjczYy0wLjAwMDI1LDAuNzExNjkgLTAuMjg0NzgsMS4zOTU0OCAtMC43OTA5LDEuODk1ODNjLTAuNDcwNTksMC40Nzg5NyAtMS4xOTQ0OSwwLjc3NjEyIC0xLjg2NTY2LDAuNzk2OTRsLTkuOTg3MjksMC4wMzI2OGwwLjA2Njg5LDAuMzIxMTljLTAuMTczMzgsMi4yNDQ1NiAtMC41MDYwNyw0LjQ4NDM2IC0wLjk5ODQ1LDYuNjgxMDljLTEuMzcwNTIsNS4zMzIwNyAtMy43NjE3MywxMC4zNzg3NyAtNy4wMjYxMiwxNC44MTE5M2MtMS4xMzM5MiwxLjU0NjUyIC0yLjM5NCwzLjAxMDAzIC0zLjc1NDAyLDQuMzYyMDFjLTAuODA2MzMsMC44MTQ0MyAtMS42Mzc5MSwxLjY1Mjc3IC0yLjQ0NDI0LDIuNDY3MmMtMS4yMTYwNiwxLjA0NTE0IC0yLjUwNDQxLDIuMDE2IC0zLjg0MjQzLDIuODk5NjdjLTEuMjUyNTcsMC44NDE0IC0yLjU1NDA5LDEuNjIwNjggLTMuODg1MzgsMi4zMzA5N2MtNS4zNzkyNCwyLjc4MDY4IC0xMS4zMzMzMiw0LjMzMDIxIC0xNy4zODUwNiw0LjU0MTQ5Yy01LjE2NSwwLjEzMzIxIC0xMC4zMjE3NiwtMC43Mzc3NCAtMTUuMTYxMjUsLTIuNTQ3MjFjLTEuODg4NjMsLTAuNzQxNjkgLTMuNzMxMDIsLTEuNjEwMDcgLTUuNTA3NDQsLTIuNTkwNThjLTEuNTI0MjEsLTAuOTQ0NiAtMi44NjgyMiwtMS43OTI1OCAtMy45NjY0OCwtMi42MzgwMmMtMC43NzIyMSwtMC41Njk3MyAtMS41MTM4NiwtMS4xODM4NSAtMi4yMTg4NiwtMS44MzQ5MmwtMC42NzIzMiwtMC42NjYwMWMtMS41Njk4OSwtMS4zNzAxNCAtMS43Mzc4NiwtMy43NTY1NyAtMC4zNzMwOSwtNS4zMzExM2wwLjE0MTA3LC0wLjE0MjYxIiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtub0hvdmVyJnF1b3Q7OmZhbHNlLCZxdW90O29yaWdJdGVtJnF1b3Q7OlsmcXVvdDtQYXRoJnF1b3Q7LHsmcXVvdDthcHBseU1hdHJpeCZxdW90Ozp0cnVlLCZxdW90O3NlZ21lbnRzJnF1b3Q7OltbMjA0LjMzNDA1LDE2MC4xMDgwNF0sW1syMDQuMTQwNTEsMTU5Ljk5MDM5XSxbMCwwXSxbLTIuMDg3MjUsLTEuMDg2NTFdXSxbWzE5OC4zODY3NiwxNjEuNzkzODddLFsxLjA5MzYzLC0yLjA4MzUzXSxbMCwwXV0sW1sxOTcuODMyMTMsMTYyLjcwNjI2XSxbMCwwXSxbLTAuNTMwNDIsMC45NDUwM11dLFtbMTk2LjQwMDI0LDE2NS42MjIzNV0sWzAuNDIzNDUsLTAuOTk3NTZdLFstMC42NDA4NSwxLjQyNzk2XV0sW1sxOTQuNTQ1NDEsMTcwLjY2Njg2XSxbMC42MzgxNiwtMS45MjE4NF0sWy0wLjYxMTUsMi4yMDgyOF1dLFtbMTkzLjE0NzAxLDE3Ny4zODk4M10sWzAuMzE5NzcsLTIuMjY4OTZdLFstMC43MTkzNSw1Ljc5MDJdXSxbWzE5NC4zMjg3OCwxOTQuNjkzMjFdLFstMS40OTk5MSwtNS42Mzg2NF0sWzEuODE4MDYsNi41OTIyNF1dLFtbMjAzLjg2OTczLDIxMi41NzIwOV0sWy00LjQ2NDIsLTUuMTgwMTZdLFsxLjEyOTEyLDEuMjc2MjNdXSxbWzIwNy40NDQ0NCwyMTYuMjIxODhdLFstMS4yNTI1LC0xLjE1NTM5XSxbMS4zMjEzMiwxLjIzODE3XV0sW1syMTEuNjMwNjksMjE5LjY3NTQzXSxbLTEuNDY2NzQsLTEuMDYxOV0sWzEuMTA1OTMsMC42NzIyOV1dLFtbMjE0Ljk3NjEzLDIyMS43MDkxXSxbLTEuMTA1OTMsLTAuNjcyMjldLFsxLjg0MTUyLDEuMTM5NjJdXSxbWzIyMC43NDEwOSwyMjQuNjgzNDZdLFstMS45OTU5NiwtMC44NDAyOF0sWzUuNzI1MTYsMi40MjQwMV1dLFtbMjM4LjgyMDA5LDIyOC41MTddLFstNi4yMTYyNSwtMC4xMDgxXSxbMi41NDIwNywtMC4wMzQ4M11dLFtbMjQ2LjQwOTQ3LDIyNy44NjcyNl0sWy0yLjUxMSwwLjM5Nzc4XSxbMCwwXV0sWzI0Ni43OTI3NiwyMjcuNzk3MzNdLFtbMjQ5LjQ0Mjg3LDIzOC43NjEwM10sWzAsMF0sWzAuMTk4NzcsMC43MzE3OF1dLFtbMjUwLjc2NDUzLDI0MC41MTEwOV0sWy0wLjY0OTQ2LC0wLjM5MTRdLFswLjY4MjI0LDAuNDI0ODRdXSxbWzI1My4wNTI0NCwyNDAuODgzOTddLFstMC43ODE4MywwLjE4NjIzXSxbMC43ODE4MywtMC4xODYyM11dLFtbMjU0LjkyNjU0LDIzOS41MTk2N10sWy0wLjQxNzQ0LDAuNjg2OF0sWzAsMF1dLFtbMjY5Ljc2NzI5LDIxNS4xMDYyMl0sWzAsMF0sWzAuODU2ODIsLTEuNDA5MzFdXSxbWzI2OC43ODY3MSwyMTAuOTg4NjddLFsxLjQwMDA3LDAuODcxODRdLFswLDBdXSxbWzI0NC4zNDU2MSwxOTYuMTMxMTFdLFswLDBdLFstMS4wNDI2MSwtMC42MjU4NF1dLFtbMjQxLjAwODMxLDE5Ni4yOTEyN10sWzAuOTc3ODksLTAuNzIyODFdLFstMC45Nzc4OSwwLjcyMjgxXV0sW1syMzkuODc2MTIsMTk5LjQzNDcyXSxbLTAuMjkyNDIsLTEuMTgwMzRdLFswLDBdXSxbWzI0Mi40MTI5NSwyMDkuODM3MzFdLFswLDBdLFstMS4zODg4OSwwLjM0N11dLFtbMjM4LjE4NTg5LDIxMC41NjE5OV0sWzEuNDI1MTcsLTAuMTM1NDNdLFstMy45OTQ3MywwLjQyMjQ4XV0sW1syMjYuMjk0NzMsMjA5LjUwNTQ5XSxbMy44NTc2NywxLjEyMDE0XSxbLTIuMjAxNTgsLTAuNjU2NTldXSxbWzIxOS45NTA3NiwyMDYuODYwNzNdLFsyLjAxNjAyLDEuMTAxNzFdLFswLDBdXSxbWzIxOC42NTEyOCwyMDYuMDcwNzldLFswLDBdLFstMC42MDgyNiwtMC4zNjk3Nl1dLFtbMjE2LjkzNzA5LDIwNS4wMjg3NF0sWzAuNTI1MzIsMC4zMTkzM10sWzAsMF1dLFtbMjE2LjkzNzA5LDIwNS4wMjg3NF0sWzAsMF0sWy0wLjc0NjUsLTAuNDUzNzldXSxbWzIxNC4wNjQ5NCwyMDIuNzE0OF0sWzAuNjYzNTYsMC40MDMzN10sWy0zLjYwMTM3LC0zLjMxMjgyXV0sW1syMDUuODMyNSwxOTAuNzgxMDNdLFsxLjgxNzk4LDQuNTQzMDldLFstMS42MjU0NCwtNC4wNzUxMV1dLFtbMjAzLjQzNzI2LDE3Ny45NjU0XSxbLTAuMDQzODcsNC4zODcxXSxbMC4wNjAyNSwtMS43NjY5MV1dLFtbMjAzLjk4OTM0LDE3Mi42OTY5NV0sWy0wLjMwNzM2LDEuNzQxMDNdLFswLjMxOTkyLC0xLjU4NTE5XV0sW1syMDQuOTExMTIsMTY4Ljc1MTMyXSxbLTAuMjI3NzMsMC45OTc1Ml0sWzAuMjI3NzMsLTAuOTk3NTJdXSxbWzIwNS44MzEyMSwxNjYuMDU0MjNdLFstMC4xODcwNiwwLjY4MTQ1XSxbMC4xODcwNiwtMC42ODE0NV1dLFtbMjA2LjIxOTk2LDE2NS4wNDA5OV0sWzAsMF0sWzAuNzIzODIsLTEuOTExNjVdXSxbWzIwNC40NTE3LDE1OS45MTQ1XSxbMS43NDg0NSwxLjA1ODkxXSxbMCwwXV1dLCZxdW90O2ZpbGxDb2xvciZxdW90OzpbMCwwLDAsMV19XX0iIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwLjUiLz48cGF0aCBkPSJNMjU3LjM4NTY5LDE1Ny42MjIwNmMtMS43NTg0NywxLjc3Njc3IC00LjUzNDIsMi4wNDYyMiAtNi42MDE1NiwwLjY0MDc5bC0xLjMwMTA3LC0wLjg4NDk0Yy0wLjQ2MjY4LC0wLjM0NjI3IC0wLjk1MTk4LC0wLjY1NTQ1IC0xLjQ2MzM0LC0wLjkyNDY1Yy0wLjg1MzMzLC0wLjQ4MTk5IC0xLjkyOTgyLC0xLjAyMzYxIC0zLjE4ODc0LC0xLjU4NDZjLTEuMzc2MzIsLTAuNTY1MTkgLTIuNzg2MDgsLTEuMDQ1MzEgLTQuMjIxMjYsLTEuNDM3NjNjLTMuNTcyOTYsLTAuODc3MjkgLTcuMjgxNDEsLTEuMDU4MjUgLTEwLjkyMjgsLTAuNTMyOThjLTQuMDg3MTQsMC41OTg4NiAtNy45ODU5MSwyLjExNDM5IC0xMS40MDQ1LDQuNDMzMTNsLTAuOTI4NDksMC42OTM0OGMtMC40OTQxMywwLjMzNjIgLTAuOTY2MDIsMC43MDQgLTEuNDEyNywxLjEwMTFsLTEuMzUwNiwxLjM2NDE3bC0wLjgyNjQ5LDAuODM0NzljLTEuMjk3NDIsMS40Mzc3MSAtMi40NDM3LDMuMDA0OTEgLTMuNDIwODMsNC42NzY4OWMtMS42ODA5OCwyLjkwMjA1IC0yLjc5MjczLDYuMDk4MiAtMy4yNzU1OSw5LjQxNzAxYy0wLjA3NzU5LDAuNjg5MjIgLTAuMTM1MDMsMS4zNTgxIC0wLjE1MTc1LDIuMDY3MjhsOC4xMDM4LC0wLjA0MDQ4YzEuNjQ5OTMsLTAuMDA2NDMgMy4xNDI3MiwwLjk3NzQzIDMuNzg3NDIsMi40OTYyMWMwLjY0NDcsMS41MTg3NiAwLjMxNTUsMy4yNzYwNyAtMC44MzUyNCw0LjQ1ODQ4bC0xNy44ODA0LDE4LjA1OTk0Yy0wLjc1OTEsMC43Nzc1NiAtMS43OTYwMSwxLjIyMTczIC0yLjg4MjYxLDEuMjM0NzVjLTEuMDg2NiwwLjAxMzAyIC0yLjEzMzg3LC0wLjQwNjE1IC0yLjkxMTQsLTEuMTY1MjlsLTE3Ljk3ODQ5LC0xNy43OTk3NWMtMS4xNjI0OSwtMS4xNzA4NSAtMS41MDkyMywtMi45MjQ3OSAtMC44Nzk3NCwtNC40NDk5MmMwLjYyOTQ5LC0xLjUyNTEzIDIuMTEyMzgsLTIuNTIzODcgMy43NjIyOSwtMi41MzM5Mmw4LjczMTU0LC0wLjEwNDRjMC4yMjA5LC0xLjk2NTg5IDAuNTYyOTksLTMuOTE2MjggMS4wMjQzNCwtNS44Mzk5OWMxLjM3NTg3LC01LjUzNjI0IDMuODA1NDgsLTEwLjc1NTUzIDcuMTU2MDIsLTE1LjM3MjU2YzEuMTMxNCwtMS41NDg5NCAyLjM3Njk1LC0zLjAxMTE5IDMuNzI2MjUsLTQuMzc0NTNjMC44MjY0OSwtMC44MzQ3OSAxLjY3MzE0LC0xLjY4OTk0IDIuNDk5NjMsLTIuNTI0NzJjMS4yNzg0NiwtMS4xMDUzNyAyLjYyNjgzLC0yLjEyNzIyIDQuMDM2NzIsLTMuMDU5MTZjMC44MDk3LC0wLjU2MTU1IDEuNjUyODgsLTEuMDczMjEgMi41MjQ4NCwtMS41MzIxMmwxLjM3MzgsLTAuNzc2NzRjNS41OTA3NCwtMi44OTg2IDExLjc1NDg3LC00LjUyMDg5IDE4LjA0ODE5LC00Ljc0OTk2YzUuMzUxOTMsLTAuMTMyODggMTAuNjc5NjYsMC43NjIxIDE1LjY5NDQxLDIuNjM2NDRjMi4wNzI2NSwwLjc2Mzc4IDQuMDgyNCwxLjY4ODU0IDYuMDEwNzksMi43NjU4NWMxLjQyOTQ4LDAuODE5ODkgMi44MDc1MSwxLjcyNjQxIDQuMTI2MzQsMi43MTQ0OWMwLjYzOTcyLDAuNDU5NCAxLjI1MTExLDAuOTU3MDEgMS44MzA4NSwxLjQ5MDA5bDAuMzY2NDksMC4zNjI4NGMwLjUwOTAxLDAuNTAzOTUgMC43NTMzNSwwLjc0NTg2IDAuNzUzMzUsMC43NDU4NmMyLjExNzIyLDEuODg5MTggMi4zMjI5OCw1LjEyOTE2IDAuNDYxNzgsNy4yNzEwM2wtMC4yMDE1OSwwLjIwMzYxeiIgZmlsbD0iIzFhNjI2MiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48cGF0aCBkPSJNMjAwLjk3ODczLDE1Mi45NDAxNmMtMS4zNTkwOSwxLjM1Mjg0IC0yLjYxMTY4LDIuODA4NjIgLTMuNzQ2NjEsNC4zNTQzNWMtMy4yNTg2LDQuNDI2NDUgLTUuNjM0MTksOS40MzkwNiAtNi45OTY5OCwxNC43NjM5N2MtMC40OTIyOSwyLjE5Njc2IC0wLjgyNjQ1LDQuNDI1OTggLTAuOTk5OTQsNi42NzA1M2wtMC4wMTg1NCwwLjM0NDUxbC05Ljk4ODAzLDAuMDI5NjRjLTEuMDk0ODUsLTAuMDE1NTcgLTIuMDg3NzgsMC42NDAyNCAtMi41MDMyNCwxLjY1MzMzYy0wLjQxNTQ1LDEuMDEzMSAtMC4xNjg4MywyLjE3NzIzIDAuNjIxNzMsMi45MzQ4NGwxOC4wMzk1OCwxNy44NjAyM2MxLjA0NTc4LDEuMDM1MzggMi43MzI4OCwxLjAyNjk1IDMuNzY4MjYsLTAuMDE4ODJsMTcuNzc5NiwtMTcuOTU4MTRjMC43NjUyMywtMC43NjQ5NiAwLjk5MTg5LC0xLjkxNjczIDAuNTczNTksLTIuOTE0NjJjLTAuNDE4MzEsLTAuOTk3ODggLTEuMzk4NTIsLTEuNjQzNzIgLTIuNDgwNSwtMS42MzQzNmwtOS41MDE4MSwwLjAyNzJjMC4wMTA3NCwtMS4yNjA4MSAwLjA5OTE4LC0yLjUxOTc1IDAuMjY0ODEsLTMuNzY5NjljMC40Njk2MiwtMy41MjQ3IDEuNjA3ODgsLTYuOTI3MzQgMy4zNTM2LC0xMC4wMjUxOWMxLjAyNjYzLC0xLjc2NzExIDIuMjMzNywtMy40MjMwMyAzLjYwMTg2LC00Ljk0MTE3bDAuOTQ3NDMsLTAuOTU2OTVjMC40NDM0OSwtMC40NDc5MyAwLjg2NjgsLTAuODc1NTEgMS4yNDk4MiwtMS4yNjIzNnYwYzAuNTQ0MjYsLTAuNTQ5NzQgMi4wOTk1LC0xLjUwOTc0IDIuNTYzMTQsLTEuOTc4MDNjMy41OTcyNSwtMi40MzIxIDcuNjk1MjUsLTQuMDI0MzIgMTEuOTkwOTEsLTQuNjU4OTJjMy44NDQ1OSwtMC41NTE3MSA3Ljc1OTQzLC0wLjM1ODA0IDExLjUzMDc5LDAuNTcwNDVjMS41MTgzNiwwLjQxMjQ2IDMuMDA5NTQsMC45MTkyNSA0LjQ2NDc4LDEuNTE3NDVjMS4yOTk2NCwwLjYwMTMxIDIuMjc0NzMsMS4xMjMxOSAzLjA0NjYxLDEuNTI0NTRjMC43MzM5LDAuNDA4NzYgMS40NDAwNSwwLjg2NTQ2IDIuMTEzODYsMS4zNjcxMmMwLjU0ODUyLDAuMzAxMTUgMC43OTI4NSwwLjU0MzA2IDAuNzkyODUsMC41NDMwNmMxLjQ5MTg0LDAuOTk4NzQgMy40ODAyNiwwLjgxMTAyIDQuNzU4ODUsLTAuNDQ5MjRsMC4yMDE1OCwtMC4yMDM2YzAuNjU0MDIsLTAuNzU0NTIgMC45ODA0NCwtMS43Mzg1NiAwLjkwNzAzLC0yLjczNDM3Yy0wLjA3MzQxLC0wLjk5NTgyIC0wLjU0MDYyLC0xLjkyMTMyIC0xLjI5ODIyLC0yLjU3MTc5YzAsMCAtMC4yMjM5NywtMC4yMjE3NCAtMC42NzE5LC0wLjY2NTIyYy0wLjcwNTA2LC0wLjY1MDk5IC0xLjQ0NTM0LC0xLjI2Mjc3IC0yLjIxNzQ5LC0xLjgzMjU5Yy0xLjI2OTU0LC0wLjk0MTA5IC0yLjU5MzAyLC0xLjgwNzE1IC0zLjk2MzY2LC0yLjU5Mzc0Yy0xLjc2MDc2LC0xLjAwMzIxIC0zLjU5MzUzLC0xLjg3NDM5IC01LjQ4MzIzLC0yLjYwNjRjLTQuODM5NDksLTEuODA5NDcgLTkuOTgxOSwtMi42NzEwOSAtMTUuMTQ2ODksLTIuNTM3ODhjLTYuMDUxNzQsMC4yMTEyOCAtMTEuOTgxMTUsMS43NjMyNSAtMTcuMzYwMzgsNC41NDM5M2MtMS4zMzEzNSwwLjcxMDIgLTIuNjI1NzYsMS40ODc1NSAtMy44NzgyOCwyLjMyOTAyYy0xLjM0NjEsMC44ODg2OCAtMi42MzM3NiwxLjg2Mjg1IC0zLjg1NTA5LDIuOTE2NDRjLTAuODA2MzMsMC44MTQ0MyAtMS42MzI4MiwxLjY0OTIxIC0yLjQzOTE1LDIuNDYzNjRNMTk5LjE2NjQyLDE1MS4xMDU1NnYwYzAuODA2MzMsLTAuODE0NDMgMS42NzMxNCwtMS42ODk5NCAyLjYyMDU3LC0yLjY0NjljMS4yNzkzOSwtMS4xMDQyMiAyLjYyNzcsLTIuMTI2IDQuMDM2NzIsLTMuMDU5MTdjMC44OTQ1MSwtMC42MDIwOSAxLjgyNTUzLC0xLjE0ODA0IDIuNzg3NzEsLTEuNjM0NzNsMS4zMzMyOCwtMC43NzY1NGwwLjEyMDk1LC0wLjEyMjE2YzUuNzY0NjIsLTIuOTY4OTcgMTIuMTE0NDIsLTQuNjI2NTUgMTguNTk0NjksLTQuODUzOTljNS41NTI1MSwtMC4xMzkyMyAxMS4wODAxLDAuNzg5MTkgMTYuMjgyNDQsMi43MzQ4YzIuMDM3NjksMC43OTM2NiA0LjAxMzEzLDEuNzM4NjggNS45MDk3OCwyLjgyNzE0YzEuNDQxNzYsMC44MjA5OCAyLjgzMzE5LDEuNzI3MzUgNC4xNjY4NSwyLjcxNDI5YzAuNjk2NDIsMC40OTYyIDEuMzYyMTYsMS4wMzQxNCAxLjk5MzUzLDEuNjEwODRjMC4xMjIxNiwwLjEyMDk1IC0wLjA0MDcxLC0wLjA0MDMxIDAuMzQ2MTQsMC4zNDI2OWMwLjM4Njg2LDAuMzgzIDAuNjUxNTUsMC42NDUwNyAwLjc3MzcxLDAuNzY2MDFjMi42OTIxNiwyLjQxMTA3IDIuOTY0NzQsNi41MzEzMSAwLjYxMzYyLDkuMjc2MDFsLTAuNDgzODEsMC40ODg2NmMtMi4yNjcwNSwyLjI2MzMzIC01LjgyNTksMi41ODc3NiAtOC40NjQ4MywwLjc3MTY0bC0wLjcxMTYsLTAuNTAyOTVsLTAuNDY4MjksLTAuNDYzNjVjLTAuNDAwNTUsLTAuMjk3NDIgLTAuODIxNjQsLTAuNTY2MTQgLTEuMjYwMTQsLTAuODA0MDlsLTAuMTQyNTMsLTAuMTQxMTFjLTAuNzMxNTcsLTAuNDQyMDcgLTEuOTA5NDUsLTEuMDAzNDYgLTIuODg0MTMsLTEuNDQ0MzFjLTEuMzEwNjksLTAuNTMzNTEgLTIuNjUyNTgsLTAuOTg2OSAtNC4wMTgyNywtMS4zNTc2MWMtMy40MjAwNSwtMC44NDI0MSAtNi45NzA0NiwtMS4wMTcyOSAtMTAuNDU2NzMsLTAuNTE1MDRjLTMuODMwMDcsMC41NTUxIC03LjQ4NjA2LDEuOTY1MTcgLTEwLjY5NjkyLDQuMTI1N2wtMS4wMDkzNCwwLjczNDRsLTEuMTcwNzksMC44NTY3N2wtMC4zNjI4NCwwLjM2NjQ5bC0xLjA2ODM4LDEuMDc5MTJsLTAuODA2MzMsMC44MTQ0M2MtMS4xOTQ5OSwxLjM4MDcyIC0yLjI0NjQ4LDIuODc5MzggLTMuMTM4MjIsNC40NzI4N2MtMS42MDIyNywyLjc0OTA0IC0yLjY2MDE3LDUuNzgwODkgLTMuMTE1OTUsOC45Mjk5NmwtMC4wNTgyNSwwLjUwNjc4bDYuNTYzNDcsLTAuMTU0MzVjMi4yNDU5NSwtMC4wMTk5OSA0LjI4MDg3LDEuMzIwNTIgNS4xNDkxLDMuMzkxOTdjMC44NjgyMywyLjA3MTQ1IDAuMzk3MjQsNC40NjIyOSAtMS4xOTE3Niw2LjA0OTY3bC0xNy43OTk3NSwxNy45Nzg0OWMtMi4xNDg2OCwyLjE3MDI3IC01LjY0OTksMi4xODc3NiAtNy44MjAxNiwwLjAzOTA3bC0xNy45Nzg1LC0xNy43OTk3NmMtMS42MDQ3OCwtMS41NzE0MiAtMi4wOTk2NCwtMy45NTc0NSAtMS4yNTIxNSwtNi4wMzc0N2MwLjg0NzQ5LC0yLjA4MDAzIDIuODY4OTIsLTMuNDQwOCA1LjExNDk2LC0zLjQ0MzI1bDcuMzUzNDksLTAuMTc4NTZjMC4yMzUwMiwtMS42MjE5OCAwLjUzNzcsLTMuMjE3MjYgMC45MDgwNiwtNC43ODU4OWMxLjQ2ODUxLC01LjY2MTk4IDQuMDA3LC0xMC45OTA1OSA3LjQ3ODU1LC0xNS42OTgzNGMxLjIwOTM5LC0xLjY0NTY3IDIuNTQyNzEsLTMuMTk2NTkgMy45ODgzLC00LjYzOTJ6IiBmaWxsPSIjMWE2MjYyIiBzdHJva2U9IiMxYTYyNjIiIHN0cm9rZS13aWR0aD0iMyIvPjxwYXRoIGQ9Ik0yMDAuOTcxNjgsMTUzLjAwNjkxYzAuODA2MzMsLTAuODE0NDMgMS42Mzc5MywtMS42NTI3OCAyLjQ0NDI2LC0yLjQ2NzIxYzEuMjIxMzIsLTEuMDUzNiAyLjUxNjU1LC0yLjAzMTM1IDMuODYyNjUsLTIuOTIwMDRjMS4yNTI1MiwtMC44NDE0NiAyLjU1NDA2LC0xLjYyMDc1IDMuODg1NCwtMi4zMzA5NWM1LjM3OTI0LC0yLjc4MDY4IDExLjMzMzMsLTQuMzMwMTQgMTcuMzg1MDUsLTQuNTQxNDJjNS4xNjUsLTAuMTMzMjEgMTAuMzIxNywwLjczNzc2IDE1LjE2MTE5LDIuNTQ3MjNjMS44ODk2OSwwLjczMjAxIDMuNzI2NSwxLjYwNzczIDUuNDg3MjcsMi42MTA5NGMxLjM3MDY0LDAuNzg2NTggMi42OTY3MSwxLjY1NjM3IDMuOTY2MjUsMi41OTc0NmMwLjc3MjE1LDAuNTY5ODEgMS41MTM3OCwxLjE4MzkzIDIuMjE4ODYsMS44MzQ5MmMwLjQ0NzkzLDAuNDQzNDkgMC42NzIzMiwwLjY2NjAxIDAuNjcyMzIsMC42NjYwMWMwLjc1NzYsMC42NTA0NyAxLjIyNjAyLDEuNTc4NTQgMS4yOTk0MywyLjU3NDM1YzAuMDczNDEsMC45OTU4MiAtMC4yNTIxNywxLjk4MTg5IC0wLjkwNjIsMi43MzY0MmwtMC4yMDE1NCwwLjIwMzczYy0xLjI3ODU5LDEuMjYwMjYgLTMuMzQ5MzcsMS40ODA0OSAtNC44NDEyLDAuNDgxNzVjMCwwIC0wLjI0Mzk2LC0wLjI0MTAzIC0wLjc5MjQ5LC0wLjU0MjJjLTAuNjczODEsLTAuNTAxNjYgLTEuMzc4ODcsLTAuOTU2MDYgLTIuMTEyNzgsLTEuMzY0ODJjLTAuNzcxODgsLTAuNDAxMzQgLTEuNzQ1MjYsLTAuOTIwMTYgLTMuMDQ0ODksLTEuNTIxNDhjLTEuNDU1MjQsLTAuNTk4MiAtMi45NDM1MSwtMS4xMDA4NCAtNC40NjE4OSwtMS41MTMzYy0zLjc3MTM2LC0wLjkyODUgLTcuNjc1OTUsLTEuMTEyNjYgLTExLjUyMDUzLC0wLjU2MDk1Yy00LjI5NTY2LDAuNjM0NiAtOC4zNzMyNywyLjIzNDA3IC0xMS45NzA1Myw0LjY2NjE2Yy0wLjQ2MzY0LDAuNDY4MyAtMi4wMTExMywxLjQyODM1IC0yLjU1NTQsMS45NzgwOXYwYy0wLjM4MywwLjM4Njg2IC0wLjgwMTYsMC44MTM3OCAtMS4yNDUwOSwxLjI2MTcybC0wLjk0MzM4LDAuOTU1OThjLTEuMzY4MTUsMS41MTgxNSAtMi41NTUwOSwzLjE2MTY1IC0zLjU4MTcxLDQuOTI4NzZjLTEuNzQ1NzMsMy4wOTc4NSAtMi44NzQ3MSw2LjQ1NTU0IC0zLjM0NDM0LDkuOTgwMjVjLTAuMTY1NjMsMS4yNDk5NCAtMC4yNTk2LDIuNDk4MTggLTAuMjcwMzUsMy43NTlsOS40NzE1NywtMC4wNDU1NmMxLjA4MTk4LC0wLjAwOTM3IDIuMDQ5MDYsMC42MzY2NyAyLjQ2NzM3LDEuNjM0NTRjMC40MTgzMSwwLjk5Nzg4IDAuMjAyMTcsMi4yMzc0OCAtMC41NjMwNiwzLjAwMjQ2bC0xNy44MDQ3MywxNy45NTI1NWMtMS4wMzUzOCwxLjA0NTc4IC0yLjcyNTg2LDEuMDUyMDkgLTMuNzcxNjUsMC4wMTY3MWwtMTguMDU2NywtMTcuODgzNWMtMC43OTA1NiwtMC43NTc1OSAtMS4wMzgwNCwtMS45MjQ5MSAtMC42MjI1OCwtMi45MzhjMC40MTU0NSwtMS4wMTMxIDEuNDk0OTEsLTEuNjg3NjkgMi41ODk3NiwtMS42NzIxM2w5Ljk4NzMsLTAuMDMyNjdsLTAuMDY2OTQsLTAuMzIxMzZjMC4xNzM0OCwtMi4yNDQ1NCAwLjUwNjE5LC00LjQ4NDM4IDAuOTk4NDcsLTYuNjgxMTRjMS4zNjI4LC01LjMyNDkgMy43NDc0NiwtMTAuMzY1MTEgNy4wMDYwNSwtMTQuNzkxNTVjMS4xMzQ5MiwtMS41NDU3MyAyLjM5NDk1LC0zLjAwOTE0IDMuNzU0MDQsLTQuMzYxOTgiIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O25vSG92ZXImcXVvdDs6ZmFsc2UsJnF1b3Q7b3JpZ0l0ZW0mcXVvdDs6WyZxdW90O1BhdGgmcXVvdDsseyZxdW90O2FwcGx5TWF0cml4JnF1b3Q7OnRydWUsJnF1b3Q7c2VnbWVudHMmcXVvdDs6W1tbMjY1LjEzOTExLDEzOC4zNzk3OV0sWzAsMF0sWy0xLjg0MjIyLC0xLjEzODM3XV0sW1syNTkuMzc0MTUsMTM1LjQwNTQzXSxbMS45OTUzNSwwLjg0MTU4XSxbLTUuNzE2MjYsLTIuNDE5NDFdXSxbWzI0MS4zMjI4LDEzMS41ODg3XSxbNi4yMDYzNSwwLjEwMTQ5XSxbLTIuNTQyMDcsMC4wMzQ5NF1dLFtbMjMzLjczMzQyLDEzMi4yMzg0NF0sWzIuNTExMDEsLTAuMzk3NjZdLFswLDBdXSxbMjMzLjM1MDEzLDEzMi4zMDgzN10sW1syMzAuNzAwMDIsMTIxLjM0NDY3XSxbMCwwXSxbLTAuMjY5ODIsLTEuMjA2NzRdXSxbWzIyOC4yMjc4OCwxMTkuMDI4MjRdLFsxLjIyMTcyLDAuMTkwODZdLFstMS4yMjE3MiwtMC4xOTA4Nl1dLFtbMjI1LjE2NzAyLDEyMC40ODAzMV0sWzAuNjI1MDEsLTEuMDY2OTRdLFswLDBdXSxbWzIxMC4yNzU4NSwxNDQuOTc2NzFdLFswLDBdLFstMC44NjMyNiwxLjQyMDA5XV0sW1syMTEuMjg0MDcsMTQ5LjExMTA3XSxbLTEuNDIwMDksLTAuODYzMjZdLFswLDBdXSxbWzIzNS42Njk4OCwxNjMuOTM1MDFdLFswLDBdLFsxLjA0MDgzLDAuNjQwMV1dLFtbMjM5LjAyMTc5LDE2My44MDEyNF0sWy0wLjk4NjUxLDAuNzIxMDFdLFswLjk4NjUxLC0wLjcyMTAxXV0sW1syNDAuMTY3MDEsMTYwLjY0ODJdLFswLjI5Mzg0LDEuMTg2MDVdLFswLDBdXSxbWzIzNy42NDcsMTUwLjIxNzk3XSxbMCwwXSxbMS4zODc3NywtMC4zMTg2Ml1dLFtbMjQxLjg1NzI1LDE0OS41MjA5M10sWy0xLjQxNjQxLDAuMTQ1NjNdLFszLjk5NDgzLC0wLjQwNzg1XV0sW1syNTMuNzQ4NCwxNTAuNTc3NDRdLFstMy44NjAzNywtMS4xMDU3Nl0sWzIuMjEwMTUsMC42NjQ2MV1dLFtbMjYwLjEyMDAzLDE1My4yMzkwMV0sWy0yLjAyNjE4LC0xLjEwNV0sWzAsMF1dLFtbMjYxLjQxOTUsMTU0LjAyODk0XSxbMCwwXSxbMC42MDgyNiwwLjM2OTc2XV0sW1syNjMuMTMzNjksMTU1LjA3MDk5XSxbLTAuNTI1MzIsLTAuMzE5MzNdLFswLDBdXSxbWzI2My4xMzM2OSwxNTUuMDcwOTldLFswLDBdLFswLjc0NjUsMC40NTM3OV1dLFtbMjY1Ljk3ODIsMTU3LjM2ODExXSxbLTAuNjM1OTEsLTAuMzg2NTZdLFszLjYxNDI5LDMuMzE0MDZdXSxbWzI3NC4yMzgyOSwxNjkuMzE4N10sWy0xLjgyMjg0LC00LjU1MjNdLFsxLjYxMzU4LDQuMDc4NTRdXSxbWzI3Ni42MzM1MywxODIuMTM0MzNdLFswLjAzMTU2LC00LjM4NjAyXSxbLTAuMDU1MTUsMS43NzU5Nl1dLFtbMjc2LjEzNjc0LDE4Ny40MzY0XSxbMC4yNzU3MywtMS43NTUzXSxbLTAuMzE5OTIsMS41ODUxOV1dLFtbMjc1LjI2MDUyLDE5MS4xODI1Ml0sWzAuMjM4NTgsLTAuOTUzMDZdLFstMC4yNTY2NywwLjkxMzI4XV0sW1syNzQuMzEyNzYsMTkzLjg2MjhdLFswLjM3NDQ3LC0wLjg3MTYzXSxbLTAuMTg3MDYsMC42ODE0NV1dLFtbMjczLjkyNDAyLDE5NC44NzYwNF0sWzAsMF0sWy0wLjcwNjExLDEuOTAwNDddXSxbWzI3NS42NjQ2MywxOTkuOTg1NzNdLFstMS43MTk0MywtMS4wNzQyMV0sWzAsMF1dLFtbMjc1Ljk0MTEyLDIwMC4xNTM4XSxbMCwwXSxbMS4wMDAyMSwwLjUyMDY4XV0sW1syNzkuMTgyNDIsMjAwLjQzMzU0XSxbLTEuMDc0NjMsMC4zNDE2MV0sWzEuMDc0NjMsLTAuMzQxNjFdXSxbWzI4MS42NjcyMiwxOTguMzMzNTJdLFstMC41MTU5NywxLjAwMjY2XSxbMCwwXV0sW1syODIuMjIxODUsMTk3LjQyMTEyXSxbLTAuMzY5NzYsMC42MDgyNl0sWzAuNTMwMzEsLTAuOTQ1MDldXSxbWzI4My42NTM3NCwxOTQuNTA1MDNdLFstMC40MjM1NiwwLjk5NzUxXSxbMC43MDEwNCwtMS42NDExN11dLFtbMjg1LjQ2NDExLDE4OS40NzEzN10sWy0wLjUwNDgzLDEuNzExNzNdLFswLjY0MDU1LC0yLjE5NzA0XV0sW1syODYuODkwMTYsMTgyLjc2NTJdLFstMC4zMDg4NSwyLjI2NzU5XSxbMC43MTkzNSwtNS43OTAyXV0sW1syODUuNzA4MzksMTY1LjQ2MTgyXSxbMS40OTk5MSw1LjYzODY0XSxbLTEuODE4MDYsLTYuNTkyMjRdXSxbWzI3Ni4xNjc0NCwxNDcuNTgyOTNdLFs0LjQ2NDIsNS4xODAxNl0sWy0xLjEyOTAzLC0xLjI3NjMxXV0sW1syNzIuNTkyNzMsMTQzLjkzMzE1XSxbMS4yNTI1NiwxLjE1NTMyXSxbLTEuMzI4OTUsLTEuMjQ1NzRdXSxbWzI2OC4zNzg4MywxNDAuNDYyOF0sWzEuNDc3NCwxLjA2NTQ2XSxbLTEuMTA1OTMsLTAuNjcyMjldXSxbWzI2NS4wMzMzOSwxMzguNDI5MTNdLFsxLjEwNTkzLDAuNjcyMjldLFswLDBdXV0sJnF1b3Q7ZmlsbENvbG9yJnF1b3Q7OlswLDAsMCwxXX1dfSIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAuNSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjcyLjQ4NjIxOTk5OTk5OTk3OjcyLjQ4NjIyLS0+"; + const tr_icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9Ii0yLjI2MzMxLC0yLjQxMTU2LDIwLDIwIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMyLjI3NjYyLC0xNzIuNTczMTIpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0yNDMuOTE0MTksMTgzLjk1OTEzYzAuMzY1OSwwIDAuNjcyNDYsMC4yOTYxOSAwLjY3MjQ2LDAuNjcwMTFjMCwwLjM2NDA4IC0wLjMwNjU2LDAuNjU5MjkgLTAuNjcyNDYsMC42NTkyOWMtMC4zNjU5LDAgLTAuNjYyNTcsLTAuMjk1MjEgLTAuNjYyNTcsLTAuNjU5MjljMCwtMC4zNzM5MiAwLjI5NjY3LC0wLjY3MDExIDAuNjYyNTcsLTAuNjcwMTF6TTIzNS4wNDMwMiwxNzkuMDUwMDN2MC4wMDk4NGMtMC4xODc4OSwwLjQ4MjE2IC0wLjcwMTE0LDAuNzc3MzYgLTEuMjI1MjcsMC42NDk0NGMtMC41NjQ2NywtMC4xMjc5MiAtMC45MjA2OCwtMC42OTg2NCAtMC43OTExMywtMS4yNTk1MmMwLjU0MzksLTIuMzQxOTMgMi40NDI2MiwtNC4zODg2NiA0Ljg5NDE0LC01LjEyNjY3YzEuMjE3MzUsLTAuMzczOTIgMi41NDE1MiwtMC40MjMxMiAzLjgxNzIyLC0wLjE0ODU4YzEuMDk4NjgsMC4yMzYxNiAyLjE0NTk1LDAuNzM4MDEgMy4wNDU4NiwxLjQzNjY1bDAuOTg4OTEsLTAuOTg0YzAuNDU0OSwtMC40NDE4MiAxLjIyNzI1LC0wLjEyNzkyIDEuMjI3MjUsMC41MTI2NnY0LjY5MjcyYzAsMC4zOTQ1OSAtMC4zMjYzNCwwLjcxOTMxIC0wLjcyMjg5LDAuNzE5MzFoLTQuNzE2MTRjLTAuNjQzNzksMCAtMC45NjAyNCwtMC43Njg1MSAtMC41MTUyMywtMS4yMjExNWwxLjE1NzAzLC0xLjE1MDNjLTAuOTA5OCwtMC44MzY0MSAtMi4yNDM4NSwtMS4yNjkzNiAtMy41Nzk4NywtMS4wNDMwNWMtMS41MjI5MywwLjIyNTM0IC0yLjk3NjYzLDEuMzQ3MSAtMy41Nzk4NywyLjkxMjY2ek0yNDEuMjkzMzcsMTg1LjQwNjZjMC40MTUzNSwwIDAuNzQxNjksMC4zMjQ3MyAwLjc0MTY5LDAuNzM4MDFjMCwwLjQxMzI4IC0wLjMyNjM0LDAuNzQ3ODQgLTAuNzQxNjksMC43NDc4NGMtMC40MTUzNSwwIC0wLjc1MTU4LC0wLjMzNDU2IC0wLjc1MTU4LC0wLjc0Nzg0YzAsLTAuNDEzMjggMC4zMzYyMywtMC43MzgwMSAwLjc1MTU4LC0wLjczODAxek0yMzUuNjE2NTksMTgzLjgxMjMyYzAuNTA1MzQsMCAwLjkwOTgxLDAuNDAzNDQgMC45MDk4MSwwLjkwNTI4YzAsMC40OTIgLTAuNDA0NDcsMC44OTU0NCAtMC45MDk4MSwwLjg5NTQ0Yy0wLjUwNDM1LDAgLTAuOTA5OCwtMC40MDM0NSAtMC45MDk4LC0wLjg5NTQ0YzAsLTAuNTAxODUgMC40MDU0NSwtMC45MDUyOCAwLjkwOTgsLTAuOTA1Mjh6TTIzNC4wMzQ2MywxODEuMTU1NDFjMC41NDM5LDAgMC45ODg5MSwwLjQ0MjggMC45ODg5MSwwLjk4NGMwLDAuNTUxMDQgLTAuNDQ1MDEsMC45ODQgLTAuOTg4OTEsMC45ODRjLTAuNTQzOSwwIC0wLjk4ODkyLC0wLjQzMjk2IC0wLjk4ODkyLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMiwtMC45ODQgMC45ODg5MiwtMC45ODR6TTIzOC4yNjc2OCwxODUuMzQ2ODdjMC40NTQ5LDAgMC44MjA4LDAuMzczOTMgMC44MjA4LDAuODI2NTdjMCwwLjQ1MjY0IC0wLjM2NTksMC44MjY1NiAtMC44MjA4LDAuODI2NTZjLTAuNDU0OSwwIC0wLjgzMTY3LC0wLjM3MzkyIC0wLjgzMTY3LC0wLjgyNjU2YzAsLTAuNDUyNjQgMC4zNzY3NywtMC44MjY1NyAwLjgzMTY3LC0wLjgyNjU3eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIHN0cm9rZT0iIzI1ODM4MyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBkPSJNMjQzLjkxNDE5LDE4My45NTkxM2MwLjM2NTksMCAwLjY3MjQ2LDAuMjk2MTkgMC42NzI0NiwwLjY3MDExYzAsMC4zNjQwOCAtMC4zMDY1NiwwLjY1OTI4IC0wLjY3MjQ2LDAuNjU5MjhjLTAuMzY1OSwwIC0wLjY2MjU3LC0wLjI5NTIgLTAuNjYyNTcsLTAuNjU5MjhjMCwtMC4zNzM5MiAwLjI5NjY3LC0wLjY3MDExIDAuNjYyNTcsLTAuNjcwMTF6TTIzNS4wNDMwMiwxNzkuMDUwMDN2MC4wMDk4NGMtMC4xODc4OSwwLjQ4MjE2IC0wLjcwMTE0LDAuNzc3MzYgLTEuMjI1MjcsMC42NDk0NGMtMC41NjQ2NywtMC4xMjc5MiAtMC45MjA2OCwtMC42OTg2NCAtMC43OTExMywtMS4yNTk1M2MwLjU0MzksLTIuMzQxOTMgMi40NDI2MiwtNC4zODg2NiA0Ljg5NDE0LC01LjEyNjY2YzEuMjE3MzYsLTAuMzczOTIgMi41NDE1MSwtMC40MjMxMiAzLjgxNzIxLC0wLjE0ODU4YzEuMDk4NjksMC4yMzYxNiAyLjE0NTk1LDAuNzM4IDMuMDQ1ODYsMS40MzY2NWwwLjk4ODkyLC0wLjk4NGMwLjQ1NDksLTAuNDQxODIgMS4yMjcyNCwtMC4xMjc5MiAxLjIyNzI0LDAuNTEyNjd2NC42OTI3MmMwLDAuMzk0NTkgLTAuMzI2MzQsMC43MTkzMSAtMC43MjI5LDAuNzE5MzFoLTQuNzE2MTRjLTAuNjQzNzgsMCAtMC45NjAyNCwtMC43Njg1MSAtMC41MTUyMywtMS4yMjExNWwxLjE1NzAzLC0xLjE1MDNjLTAuOTA5OCwtMC44MzY0IC0yLjI0Mzg1LC0xLjI2OTM3IC0zLjU3OTg3LC0xLjA0MzA1Yy0xLjUyMjkzLDAuMjI1MzQgLTIuOTc2NjQsMS4zNDcxIC0zLjU3OTg4LDIuOTEyNjV6TTI0MS4yOTMzNiwxODUuNDA2NmMwLjQxNTM0LDAgMC43NDE2OSwwLjMyNDcyIDAuNzQxNjksMC43MzhjMCwwLjQxMzI4IC0wLjMyNjM0LDAuNzQ3ODQgLTAuNzQxNjksMC43NDc4NGMtMC40MTUzNCwwIC0wLjc1MTU4LC0wLjMzNDU2IC0wLjc1MTU4LC0wLjc0Nzg0YzAsLTAuNDEzMjggMC4zMzYyMywtMC43MzggMC43NTE1OCwtMC43Mzh6TTIzNS42MTY1OSwxODMuODEyMzJjMC41MDUzNCwwIDAuOTA5OCwwLjQwMzQ0IDAuOTA5OCwwLjkwNTI4YzAsMC40OTIgLTAuNDA0NDcsMC44OTU0NCAtMC45MDk4LDAuODk1NDRjLTAuNTA0MzUsMCAtMC45MDk4LC0wLjQwMzQ0IC0wLjkwOTgsLTAuODk1NDRjMCwtMC41MDE4NCAwLjQwNTQ2LC0wLjkwNTI4IDAuOTA5OCwtMC45MDUyOHpNMjM0LjAzNDYyLDE4MS4xNTU0MWMwLjU0MzksMCAwLjk4ODkyLDAuNDQyOCAwLjk4ODkyLDAuOTg0YzAsMC41NTEwNCAtMC40NDUwMSwwLjk4NCAtMC45ODg5MiwwLjk4NGMtMC41NDM5LDAgLTAuOTg4OTIsLTAuNDMyOTYgLTAuOTg4OTIsLTAuOTg0YzAsLTAuNTQxMiAwLjQ0NTAxLC0wLjk4NCAwLjk4ODkyLC0wLjk4NHpNMjM4LjI2NzY4LDE4NS4zNDY4N2MwLjQ1NDksMCAwLjgyMDgsMC4zNzM5MiAwLjgyMDgsMC44MjY1NmMwLDAuNDUyNjQgLTAuMzY1OSwwLjgyNjU2IC0wLjgyMDgsMC44MjY1NmMtMC40NTQ5LDAgLTAuODMxNjgsLTAuMzczOTIgLTAuODMxNjgsLTAuODI2NTZjMCwtMC40NTI2NCAwLjM3Njc4LC0wLjgyNjU2IDAuODMxNjgsLTAuODI2NTZ6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjo3LjcyMzM3OTk5OTk5OTk5Mjo3LjQyNjg4MDAwMDAwMDAxMS0tPg=="; + const tl_icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMHB4IiBoZWlnaHQ9IjIwcHgiIHZpZXdCb3g9Ii0yLjI2MzMxLC0yLjQxMTU2LDIwLDIwIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjM0LjUxMzMxLC0xNzUuMDgyNykiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iI2ZmZmZmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIzOS4wMTE2OSwxODcuMDQwOGMwLDAuMzY0MDggLTAuMjk2NjcsMC42NTkyOSAtMC42NjI1NywwLjY1OTI5Yy0wLjM2NTksMCAtMC42NzI0NiwtMC4yOTUyMSAtMC42NzI0NiwtMC42NTkyOWMwLC0wLjM3MzkyIDAuMzA2NTYsLTAuNjcwMTEgMC42NzI0NiwtMC42NzAxMWMwLjM2NTksMCAwLjY2MjU3LDAuMjk2MTkgMC42NjI1NywwLjY3MDExek0yNDcuMjIwMjgsMTgxLjQ2MTZjLTAuNjAzMjQsLTEuNTY1NTYgLTIuMDU2OTQsLTIuNjg3MzIgLTMuNTc5ODcsLTIuOTEyNjZjLTEuMzM2MDIsLTAuMjI2MzEgLTIuNjcwMDcsMC4yMDY2NCAtMy41Nzk4NywxLjA0MzA1bDEuMTU3MDMsMS4xNTAzYzAuNDQ1MDEsMC40NTI2NCAwLjEyODU2LDEuMjIxMTUgLTAuNTE1MjMsMS4yMjExNWgtNC43MTYxNGMtMC4zOTY1NSwwIC0wLjcyMjg5LC0wLjMyNDcyIC0wLjcyMjg5LC0wLjcxOTMxdi00LjY5MjcyYzAsLTAuNjQwNTggMC43NzIzNSwtMC45NTQ0OCAxLjIyNzI1LC0wLjUxMjY2bDAuOTg4OTEsMC45ODRjMC44OTk5MSwtMC42OTg2NCAxLjk0NzE4LC0xLjIwMDQ5IDMuMDQ1ODYsLTEuNDM2NjVjMS4yNzU3LC0wLjI3NDU0IDIuNTk5ODcsLTAuMjI1MzQgMy44MTcyMiwwLjE0ODU4YzIuNDUxNTIsMC43MzgwMSA0LjM1MDI0LDIuNzg0NzQgNC44OTQxNCw1LjEyNjY3YzAuMTI5NTUsMC41NjA4OCAtMC4yMjY0NiwxLjEzMTYgLTAuNzkxMTMsMS4yNTk1MmMtMC41MjQxMywwLjEyNzkyIC0xLjAzNzM4LC0wLjE2NzI4IC0xLjIyNTI3LC0wLjY0OTQ0di0wLjAwOTg0ek0yNDEuNzIxNTIsMTg4LjU1NjE3YzAsMC40MTMyOCAtMC4zMzYyMywwLjc0Nzg0IC0wLjc1MTU4LDAuNzQ3ODRjLTAuNDE1MzUsMCAtMC43NDE2OSwtMC4zMzQ1NiAtMC43NDE2OSwtMC43NDc4NGMwLC0wLjQxMzI4IDAuMzI2MzQsLTAuNzM4MDEgMC43NDE2OSwtMC43MzgwMWMwLjQxNTM1LDAgMC43NTE1OCwwLjMyNDczIDAuNzUxNTgsMC43MzgwMXpNMjQ3LjU1NjUyLDE4Ny4xMjkxNmMwLDAuNDkxOTkgLTAuNDA1NDUsMC44OTU0NCAtMC45MDk4LDAuODk1NDRjLTAuNTA1MzQsMCAtMC45MDk4MSwtMC40MDM0NCAtMC45MDk4MSwtMC44OTU0NGMwLC0wLjUwMTg0IDAuNDA0NDcsLTAuOTA1MjggMC45MDk4MSwtMC45MDUyOGMwLjUwNDM1LDAgMC45MDk4LDAuNDAzNDMgMC45MDk4LDAuOTA1Mjh6TTI0OS4yMTc2LDE4NC41NTA5N2MwLDAuNTUxMDQgLTAuNDQ1MDIsMC45ODQgLTAuOTg4OTIsMC45ODRjLTAuNTQzOSwwIC0wLjk4ODkxLC0wLjQzMjk2IC0wLjk4ODkxLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMSwtMC45ODQgMC45ODg5MSwtMC45ODRjMC41NDM5LDAgMC45ODg5MiwwLjQ0MjggMC45ODg5MiwwLjk4NHpNMjQ0LjgyNzMsMTg4LjU4NWMwLDAuNDUyNjQgLTAuMzc2NzcsMC44MjY1NiAtMC44MzE2NywwLjgyNjU2Yy0wLjQ1NDksMCAtMC44MjA4LC0wLjM3MzkyIC0wLjgyMDgsLTAuODI2NTZjMCwtMC40NTI2NCAwLjM2NTksLTAuODI2NTcgMC44MjA4LC0wLjgyNjU3YzAuNDU0OSwwIDAuODMxNjcsMC4zNzM5MyAwLjgzMTY3LDAuODI2NTd6IiBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpbmRleCZxdW90OzpudWxsfSIgc3Ryb2tlPSIjMjU4MzgzIiBzdHJva2Utd2lkdGg9IjEuNSIvPjxwYXRoIGQ9Ik0yMzkuMDExNjksMTg3LjA0MDhjMCwwLjM2NDA4IC0wLjI5NjY3LDAuNjU5MjggLTAuNjYyNTcsMC42NTkyOGMtMC4zNjU5LDAgLTAuNjcyNDYsLTAuMjk1MiAtMC42NzI0NiwtMC42NTkyOGMwLC0wLjM3MzkyIDAuMzA2NTYsLTAuNjcwMTEgMC42NzI0NiwtMC42NzAxMWMwLjM2NTksMCAwLjY2MjU3LDAuMjk2MTkgMC42NjI1NywwLjY3MDExek0yNDcuMjIwMzEsMTgxLjQ2MTZjLTAuNjAzMjQsLTEuNTY1NTUgLTIuMDU2OTUsLTIuNjg3MzEgLTMuNTc5ODgsLTIuOTEyNjVjLTEuMzM2MDIsLTAuMjI2MzIgLTIuNjcwMDcsMC4yMDY2NSAtMy41Nzk4NywxLjA0MzA1bDEuMTU3MDMsMS4xNTAzYzAuNDQ1MDEsMC40NTI2NCAwLjEyODU1LDEuMjIxMTUgLTAuNTE1MjMsMS4yMjExNWgtNC43MTYxNGMtMC4zOTY1NiwwIC0wLjcyMjksLTAuMzI0NzIgLTAuNzIyOSwtMC43MTkzMXYtNC42OTI3MmMwLC0wLjY0MDU5IDAuNzcyMzQsLTAuOTU0NDkgMS4yMjcyNCwtMC41MTI2N2wwLjk4ODkyLDAuOTg0YzAuODk5OTEsLTAuNjk4NjUgMS45NDcxNywtMS4yMDA0OSAzLjA0NTg2LC0xLjQzNjY1YzEuMjc1NywtMC4yNzQ1NCAyLjU5OTg1LC0wLjIyNTM0IDMuODE3MjEsMC4xNDg1OGMyLjQ1MTUyLDAuNzM4IDQuMzUwMjQsMi43ODQ3MyA0Ljg5NDE0LDUuMTI2NjZjMC4xMjk1NSwwLjU2MDg5IC0wLjIyNjQ2LDEuMTMxNjEgLTAuNzkxMTMsMS4yNTk1M2MtMC41MjQxMywwLjEyNzkyIC0xLjAzNzM4LC0wLjE2NzI4IC0xLjIyNTI3LC0wLjY0OTQ0di0wLjAwOTg0ek0yNDEuNzIxNTMsMTg4LjU1NjE2YzAsMC40MTMyOCAtMC4zMzYyNCwwLjc0Nzg0IC0wLjc1MTU4LDAuNzQ3ODRjLTAuNDE1MzUsMCAtMC43NDE2OSwtMC4zMzQ1NiAtMC43NDE2OSwtMC43NDc4NGMwLC0wLjQxMzI4IDAuMzI2MzUsLTAuNzM4IDAuNzQxNjksLTAuNzM4YzAuNDE1MzUsMCAwLjc1MTU4LDAuMzI0NzIgMC43NTE1OCwwLjczOHpNMjQ3LjU1NjUyLDE4Ny4xMjkxNmMwLDAuNDkyIC0wLjQwNTQ1LDAuODk1NDQgLTAuOTA5OCwwLjg5NTQ0Yy0wLjUwNTMzLDAgLTAuOTA5OCwtMC40MDM0NCAtMC45MDk4LC0wLjg5NTQ0YzAsLTAuNTAxODQgMC40MDQ0NiwtMC45MDUyOCAwLjkwOTgsLTAuOTA1MjhjMC41MDQzNCwwIDAuOTA5OCwwLjQwMzQ0IDAuOTA5OCwwLjkwNTI4ek0yNDkuMjE3NjEsMTg0LjU1MDk3YzAsMC41NTEwNCAtMC40NDUwMiwwLjk4NCAtMC45ODg5MiwwLjk4NGMtMC41NDM5MSwwIC0wLjk4ODkyLC0wLjQzMjk2IC0wLjk4ODkyLC0wLjk4NGMwLC0wLjU0MTIgMC40NDUwMiwtMC45ODQgMC45ODg5MiwtMC45ODRjMC41NDM5MSwwIDAuOTg4OTIsMC40NDI4IDAuOTg4OTIsMC45ODR6TTI0NC44MjczMSwxODguNTg0OTljMCwwLjQ1MjY0IC0wLjM3Njc4LDAuODI2NTYgLTAuODMxNjgsMC44MjY1NmMtMC40NTQ5LDAgLTAuODIwOCwtMC4zNzM5MiAtMC44MjA4LC0wLjgyNjU2YzAsLTAuNDUyNjQgMC4zNjU5LC0wLjgyNjU2IDAuODIwOCwtMC44MjY1NmMwLjQ1NDksMCAwLjgzMTY4LDAuMzczOTIgMC44MzE2OCwwLjgyNjU2eiIgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aW5kZXgmcXVvdDs6bnVsbH0iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6NS40ODY2OTE0OTU5NDA0Mjo0LjkxNzMwNDExMjkyMTc1NS0tPg=="; + let gx = [0]; + let gy = [0]; + let gr = [90]; + let gs = [100]; + let rm = [0]; + class Global_Coordinate { + getInfo() { + return { + id: "globalCoordinate", + color1: "#2ea4a4", + menuIconURI: icon, + name: "Global Coordinate", + blocks: [ + { + opcode: "SET", + filter: [Scratch.TargetType.SPRITE], + blockType: Scratch.BlockType.COMMAND, + blockIconURI: icon, + text: "go to x: [x] y: [y] direction [r] size [s] - use screens [screen]", + arguments: { + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + r: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "90", + }, + s: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "rotation_mode", + filter: [Scratch.TargetType.SPRITE], + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's rotation mode to [m]", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + m: { + type: Scratch.ArgumentType.NUMBER, + menu: "rotation_mode", + }, + }, + }, + { + opcode: "set", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's x: [x] y: [y] direction: [r] size: [s]", + arguments: { + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + r: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "90", + }, + s: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + "---", + { + opcode: "Set_Co", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's x [x] y: [y]", + arguments: { + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "Set_GX", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's x to [x]", + arguments: { + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "Set_GY", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's y to: [y]", + arguments: { + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + "---", + { + opcode: "CX", + blockType: Scratch.BlockType.COMMAND, + text: "change screens [screen] 's x by [x]", + arguments: { + x: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "CY", + blockType: Scratch.BlockType.COMMAND, + text: "change screens [screen] 's y by [y]", + arguments: { + y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + "---", + { + opcode: "Set_GR", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's direction [r]", + arguments: { + r: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "90", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "TR", + blockType: Scratch.BlockType.COMMAND, + text: "turn [tr_icon] [r] degrees - screens [screen]", + arguments: { + r: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "15", + }, + tr_icon: { + type: Scratch.ArgumentType.IMAGE, + dataURI: tr_icon, + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "TL", + blockType: Scratch.BlockType.COMMAND, + text: "turn [tr_icon] [r] degrees - screens [screen]", + arguments: { + r: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "15", + }, + tr_icon: { + type: Scratch.ArgumentType.IMAGE, + dataURI: tl_icon, + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + "---", + { + opcode: "Set_si", + blockType: Scratch.BlockType.COMMAND, + text: "set screens [screen] 's size [s]", + arguments: { + s: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "CS", + blockType: Scratch.BlockType.COMMAND, + text: "change screens [screen] 's size by [s]", + arguments: { + s: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + "---", + { + opcode: "x", + blockType: Scratch.BlockType.REPORTER, + text: "screens [screen] x", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "y", + blockType: Scratch.BlockType.REPORTER, + text: "screens [screen] y", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "r", + blockType: Scratch.BlockType.REPORTER, + text: "screens [screen] direction", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "s", + blockType: Scratch.BlockType.REPORTER, + text: "screens [screen] size", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "rm", + blockType: Scratch.BlockType.BOOLEAN, + text: "screens [screen] rotation mode is screen?", + arguments: { + screen: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + ], + menus: { + rotation_mode: { + acceptReporters: true, + items: [ + { + text: "center of stage", + value: "0", + }, + { + text: "center of screen", + value: "1", + }, + ], + }, + }, + }; + } + SET(args, { target }) { + if (isNaN(args.x)) { + args.x = 0; + } + if (isNaN(args.y)) { + args.y = 0; + } + if (isNaN(args.r)) { + args.r = 0; + } + if (isNaN(args.s)) { + args.s = 0; + } + target.setSize((args.s / 100) * gs[args.screen - 1]); + target.setDirection(-(args.r - (gr[args.screen - 1] - 90) - 90) + 90); + if (rm[args.screen - 1] == 1) { + target.setXY( + (gx[args.screen - 1] / 100) * gs[args.screen - 1] + + (gs[args.screen - 1] / 100) * + (-args.x * + Math.cos( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + ) - + -args.y * + Math.sin( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + )), + (gy[args.screen - 1] / 100) * gs[args.screen - 1] + + (gs[args.screen - 1] / 100) * + (-args.x * + Math.sin( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + ) + + -args.y * + Math.cos( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + )), + true + ); + } else { + target.setXY( + (gs[args.screen - 1] / 100) * + (-(args.x + gx[args.screen - 1]) * + Math.cos( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + ) - + -(args.y + gy[args.screen - 1]) * + Math.sin( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + )), + (gs[args.screen - 1] / 100) * + (-(args.x + gx[args.screen - 1]) * + Math.sin( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + ) + + -(args.y + gy[args.screen - 1]) * + Math.cos( + ((-(gr[args.screen - 1] - 90 - 90) + 90) / 180) * Math.PI + )), + true + ); + } + } + rotation_mode(args) { + rm[args.screen - 1] = args.m; + } + set({ x, y, r, s, screen }) { + if (!isNaN(x)) { + gx[screen - 1] = x; + } else { + gx[screen - 1] = 0; + } + if (!isNaN(y)) { + gy[screen - 1] = y; + } else { + gy[screen - 1] = 0; + } + if (!isNaN(r)) { + gr[screen - 1] = r; + } else { + gr[screen - 1] = 0; + } + if (!isNaN(s)) { + gs[screen - 1] = s; + } else { + gs[screen - 1] = 0; + } + } + Set_Co({ x, y, screen }) { + if (!isNaN(x + y)) { + gx[screen - 1] = x; + gy[screen - 1] = y; + } else { + if (isNaN(x)) { + x = 0; } - rm({ screen }) { - return rm[screen - 1] == 1; + if (isNaN(y)) { + y = 0; } + } + } + Set_GX({ x, screen }) { + if (!isNaN(x)) { + gx[screen - 1] = x; + } else { + gx[screen - 1] = 0; + } + } + Set_GY({ y, screen }) { + if (!isNaN(y)) { + gy[screen - 1] = y; + } else { + gy[screen - 1] = 0; + } + } + CX({ x, screen }) { + if (!isNaN(x)) { + gx[screen - 1] += x; + } + } + CY({ y, screen }) { + if (!isNaN(y)) { + gy[screen - 1] += y; + } + } + Set_GR({ r, screen }) { + if (!isNaN(r)) { + gr[screen - 1] = r; + } else { + gr[screen - 1] = 0; + } + } + TR({ r, screen }) { + if (!isNaN(r)) { + gr[screen - 1] += r; + } + } + TL({ r, screen }) { + if (!isNaN(r)) { + gr[screen - 1] -= r; + } + } + Set_si({ s, screen }) { + if (!isNaN(s)) { + gs[screen - 1] = s; + } else { + gs[screen - 1] = 0; + } + if (gs[screen - 1] < 0) { + gs[screen - 1] = 0; + } + } + CS({ s, screen }) { + if (!isNaN(s)) { + gs[screen - 1] += s; + } + if (gs[screen - 1] < 0) { + gs[screen - 1] = 0; + } + } + x({ screen }) { + return gx[screen - 1]; + } + y({ screen }) { + return gy[screen - 1]; + } + r({ screen }) { + return gr[screen - 1]; + } + s({ screen }) { + return gs[screen - 1]; + } + rm({ screen }) { + return rm[screen - 1] == 1; } - Scratch.extensions.register(new Global_Coordinate()); + } + Scratch.extensions.register(new Global_Coordinate()); })(Scratch); diff --git a/extensions/NOname-awa/graphics2d.js b/extensions/NOname-awa/graphics2d.js index 3eec30f6ed..cd6efa33e6 100644 --- a/extensions/NOname-awa/graphics2d.js +++ b/extensions/NOname-awa/graphics2d.js @@ -1,360 +1,420 @@ -(function (Scratch) { - 'use strict'; - Scratch.translate.setup({ - zh: { - name: '图形 2D', - line_section: '线段([x1],[y1])到([x2],[y2])', - triangle: '三角形([x1],[y1])([x2],[y2])([x3],[y3])的 [CS]', - triangle_s: '三角形 [s1] [s2] [s3] 的面积', - quadrilateral: '四边形([x1],[y1])([x2],[y2])([x3],[y3])([x4],[y4])的 [CS]', - graph: '图形 [graph] 的 [CS]', - round: '[rd] 为 [a] 的圆的 [CS]', - pi: '派', - radius: '半径', - diameter: '直径', - area: '面积', - circumference: '周长', - } - }); - class graph { - getInfo() { - return { - id: 'nonameawagraph', - name: Scratch.translate({ id: 'name', default: 'Graphics 2D' }), - color1: '#ff976c', - color2: '#cc7956', - color3: '#e58861', - blocks: [ - { - opcode: 'line_section', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'line_section', default: 'length from ([x1],[y1]) to ([x2],[y2])' }), - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - } - }, - { - opcode: 'vertical', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ⊥ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '0' - }, - b: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '90' - }, - } - }, - '---', - { - opcode: 'triangle', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'triangle', default: 'triangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) \'s [CS]' }), - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - { - opcode: 'triangle_s', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'triangle_s', default: 'triangle [s1] [s2] [s3] \'s area' }), - arguments: { - s1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3' - }, - s2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '4' - }, - s3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - }, - }, - { - opcode: 'quadrilateral', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'quadrilateral', default: 'quadrangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) \'s [CS]' }), - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - { - opcode: 'graph', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'graph', default: 'graph [graph] \'s [CS]' }), - arguments: { - graph: { - type: Scratch.ArgumentType.STRING, - defaultValue: '[[0,0], [0,2], [2,4], [4,2], [4,0]]' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - '---', - { - opcode: 'round', - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'round', default: 'circle of [rd][a]\'s [CS]' }), - arguments: { - rd: { - type: Scratch.ArgumentType.STRING, - menu: 'rd' - }, - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - '---', - { - opcode: 'pi', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate({ id: 'pi', default: 'pi' }), - }, - ], - menus: { - rd: { - acceptReporters: true, - items: [ - { - text: Scratch.translate({ id: 'radius', default: 'radius' }), - value: 'r' - }, - { - text: Scratch.translate({ id: 'diameter', default: 'diameter' }), - value: 'd' - } - ] - }, - cs: { - acceptReporters: true, - items: [ - { - text: Scratch.translate({ id: 'area', default: 'area' }), - value: 's' - }, - { - text: Scratch.translate({ id: 'circumference', default: 'circumference' }), - value: 'c' - } - ] - } - } - }; - } - line_section(args) { - return Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - } - vertical(args) { - if (isNaN(args.a) || isNaN(args.b)) { - return false; - } else { - return ((args.a - (args.b - 90)) % 180) == 0; - } - } - triangle(args) { - if (args.CS == 's') { - let points = [[args.x1, args.y1], [args.x2, args.y2], [args.x3, args.y3]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let i = 0; - i += Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - i += Math.sqrt(Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2)); - i += Math.sqrt(Math.pow(args.x3 - args.x1, 2) + Math.pow(args.y3 - args.y1, 2)); - return i; - } - return 0; - } - triangle_s(args) { - const s = (args.s1 + args.s2 + args.s3) / 2; - const area = Math.sqrt(s * (s - args.s1) * (s - args.s2) * (s - args.s3)); - return area; - } - quadrilateral(args) { - if (args.CS == 's') { - let points = [[args.x1, args.y1], [args.x2, args.y2], [args.x3, args.y3], [args.x4, args.y4]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let i = 0; - i += Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - i += Math.sqrt(Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2)); - i += Math.sqrt(Math.pow(args.x3 - args.x4, 2) + Math.pow(args.y3 - args.y4, 2)); - i += Math.sqrt(Math.pow(args.x4 - args.x1, 2) + Math.pow(args.y4 - args.y1, 2)); - return i; - } - return 0; - } - graph(args) { - let points; - try { - points = JSON.parse(args.graph); - } catch (error) { - return 0; - } - if (!Array.isArray(points)) { - return 0; - } - let n = points.length; - if (args.CS == 's') { - let area = 0; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let x1, x2, y1, y2; - let j = 0; - j = 0; - var i_end = n - 1; - var i_inc = 1; - if (0 > i_end) { - i_inc = -i_inc; - } - for (let i = 0; i_inc >= 0 ? i <= i_end : i >= i_end; i += i_inc) { - x1 = points[((i + 1) - 1)][0]; - x2 = i == n - 1 ? points[0][0] : points[((i + 2) - 1)][0]; - y1 = points[((i + 1) - 1)][1]; - y2 = i == n - 1 ? points[0][1] : points[((i + 2) - 1)][1]; - j = (typeof j == 'number' ? j : 0) + Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)); - } - return j; - } - return 0; - } - round(args) { - if (args.CS == 'c') { - return 2 * Math.PI * (args.rd == 'r' ? args.a : args.a / 2); - } - if (args.CS == 's') { - return Math.PI * ((args.rd == 'r' ? args.a : args.a / 2) ** 2); - } - } - pi() { - return Math.PI; - } - } - Scratch.extensions.register(new graph()); -})(Scratch); +// Name: Graphics 2D +// ID: nonameawagraph +// Description: Blocks to compute lengths, angles, and areas in two dimensions. +// By: NOname-awa + +(function (Scratch) { + "use strict"; + Scratch.translate.setup({ + zh: { + name: "图形 2D", + line_section: "线段([x1],[y1])到([x2],[y2])", + triangle: "三角形([x1],[y1])([x2],[y2])([x3],[y3])的 [CS]", + triangle_s: "三角形 [s1] [s2] [s3] 的面积", + quadrilateral: + "四边形([x1],[y1])([x2],[y2])([x3],[y3])([x4],[y4])的 [CS]", + graph: "图形 [graph] 的 [CS]", + round: "[rd] 为 [a] 的圆的 [CS]", + pi: "派", + radius: "半径", + diameter: "直径", + area: "面积", + circumference: "周长", + }, + }); + class graph { + getInfo() { + return { + id: "nonameawagraph", + name: Scratch.translate({ id: "name", default: "Graphics 2D" }), + color1: "#ff976c", + color2: "#cc7956", + color3: "#e58861", + blocks: [ + { + opcode: "line_section", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "line_section", + default: "length from ([x1],[y1]) to ([x2],[y2])", + }), + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + opcode: "vertical", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ⊥ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "0", + }, + b: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "90", + }, + }, + }, + "---", + { + opcode: "triangle", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "triangle", + default: "triangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) 's [CS]", + }), + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + { + opcode: "triangle_s", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "triangle_s", + default: "triangle [s1] [s2] [s3] 's area", + }), + arguments: { + s1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3", + }, + s2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "4", + }, + s3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + }, + }, + { + opcode: "quadrilateral", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "quadrilateral", + default: + "quadrangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) 's [CS]", + }), + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + { + opcode: "graph", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "graph", + default: "graph [graph] 's [CS]", + }), + arguments: { + graph: { + type: Scratch.ArgumentType.STRING, + defaultValue: "[[0,0], [0,2], [2,4], [4,2], [4,0]]", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + "---", + { + opcode: "round", + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + id: "round", + default: "circle of [rd][a]'s [CS]", + }), + arguments: { + rd: { + type: Scratch.ArgumentType.STRING, + menu: "rd", + }, + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + "---", + { + opcode: "pi", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ id: "pi", default: "pi" }), + }, + ], + menus: { + rd: { + acceptReporters: true, + items: [ + { + text: Scratch.translate({ id: "radius", default: "radius" }), + value: "r", + }, + { + text: Scratch.translate({ + id: "diameter", + default: "diameter", + }), + value: "d", + }, + ], + }, + cs: { + acceptReporters: true, + items: [ + { + text: Scratch.translate({ id: "area", default: "area" }), + value: "s", + }, + { + text: Scratch.translate({ + id: "circumference", + default: "circumference", + }), + value: "c", + }, + ], + }, + }, + }; + } + line_section(args) { + return Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + } + vertical(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return (args.a - (args.b - 90)) % 180 == 0; + } + } + triangle(args) { + if (args.CS == "s") { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; + } + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let i = 0; + i += Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + i += Math.sqrt( + Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2) + ); + i += Math.sqrt( + Math.pow(args.x3 - args.x1, 2) + Math.pow(args.y3 - args.y1, 2) + ); + return i; + } + return 0; + } + triangle_s(args) { + const s = (args.s1 + args.s2 + args.s3) / 2; + const area = Math.sqrt(s * (s - args.s1) * (s - args.s2) * (s - args.s3)); + return area; + } + quadrilateral(args) { + if (args.CS == "s") { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + [args.x4, args.y4], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; + } + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let i = 0; + i += Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + i += Math.sqrt( + Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2) + ); + i += Math.sqrt( + Math.pow(args.x3 - args.x4, 2) + Math.pow(args.y3 - args.y4, 2) + ); + i += Math.sqrt( + Math.pow(args.x4 - args.x1, 2) + Math.pow(args.y4 - args.y1, 2) + ); + return i; + } + return 0; + } + graph(args) { + let points; + try { + points = JSON.parse(args.graph); + } catch (error) { + return 0; + } + if (!Array.isArray(points)) { + return 0; + } + let n = points.length; + if (args.CS == "s") { + let area = 0; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; + } + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let x1, x2, y1, y2; + let j = 0; + j = 0; + var i_end = n - 1; + var i_inc = 1; + if (0 > i_end) { + i_inc = -i_inc; + } + for (let i = 0; i_inc >= 0 ? i <= i_end : i >= i_end; i += i_inc) { + x1 = points[i + 1 - 1][0]; + x2 = i == n - 1 ? points[0][0] : points[i + 2 - 1][0]; + y1 = points[i + 1 - 1][1]; + y2 = i == n - 1 ? points[0][1] : points[i + 2 - 1][1]; + j = + (typeof j == "number" ? j : 0) + + Math.sqrt( + Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2) + ); + } + return j; + } + return 0; + } + round(args) { + if (args.CS == "c") { + return 2 * Math.PI * (args.rd == "r" ? args.a : args.a / 2); + } + if (args.CS == "s") { + return Math.PI * (args.rd == "r" ? args.a : args.a / 2) ** 2; + } + } + pi() { + return Math.PI; + } + } + Scratch.extensions.register(new graph()); +})(Scratch); diff --git a/extensions/NOname-awa/math-and-string.js b/extensions/NOname-awa/math-and-string.js index 7ea26b9b7a..2793a40fc8 100644 --- a/extensions/NOname-awa/math-and-string.js +++ b/extensions/NOname-awa/math-and-string.js @@ -1,1123 +1,1202 @@ (function (Scratch) { - 'use strict'; - class MathAndString { - getInfo() { - return { - color1: '#5ac900', - color2: '#48a100', - color3: '#48a100', - id: 'nonameawamathandstring', - name: 'Math And String', - blocks: [ - { - opcode: 'exponent', - blockType: Scratch.BlockType.REPORTER, - text: '[A] ^ [B]', - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '2' - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - } - } - }, - { - opcode: 'negative', - blockType: Scratch.BlockType.REPORTER, - text: '- [A]', - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '2' - }, - } - }, - { - opcode: 'n_th_Root', - blockType: Scratch.BlockType.REPORTER, - text: '[A] √ [B]', - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3' - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '8' - } - } - }, '---', - { - opcode: 'astrict', - blockType: Scratch.BlockType.REPORTER, - text: 'constrain [A] low [B] high [C]', - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - } - } - }, - { - opcode: 'round', - blockType: Scratch.BlockType.REPORTER, - text: 'Round [A] to [B] decimal places', - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3.14' - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - } - } - }, "---", - { - opcode: 'boolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - } - }, - { - opcode: 'booleanToInt', - blockType: Scratch.BlockType.REPORTER, - text: '[a]', - arguments: { - a: { - type: Scratch.ArgumentType.BOOLEAN, - }, - } - }, - '---', - { - opcode: 'equal', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ⩵ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'A' - }, - b: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'a' - }, - } - }, - { - opcode: 'equalNegative', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] =- [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-5' - }, - } - }, - { - opcode: 'equalPlusMinus', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] =± [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-5' - }, - } - }, - { - opcode: 'notEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≠ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - b: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - } - }, - { - opcode: 'almostEqual2n', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≈ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5.5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '6' - }, - } - }, - { - opcode: 'almostEqual3n', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≈ [b] ± [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '6' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'xor', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ^ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.BOOLEAN, - }, - b: { - type: Scratch.ArgumentType.BOOLEAN, - }, - } - }, - '---', - { - opcode: 'equalOrGreater', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≥ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - }, - } - }, - { - opcode: 'equalOrLess', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≤ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - }, - } - }, - { - opcode: 'between', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] < [b] < [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - } - }, - { - opcode: 'betweenEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≤ [b] ≤ [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - }, - } - }, '---', - { - opcode: 'vertical', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ⊥ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '0' - }, - b: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '90' - }, - } - }, '---', { - opcode: 'text', - blockType: Scratch.BlockType.REPORTER, - text: '[a]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - } - }, '---', { - opcode: 'repeat', - blockType: Scratch.BlockType.REPORTER, - text: 'repeat [text] [n] times', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Text ' - }, - n: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3' - }, - } - }, { - opcode: 'trim', - blockType: Scratch.BlockType.REPORTER, - text: 'trim spaces from both sides of [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: ' Text ' - }, - } - }, { - opcode: 'intercept', - blockType: Scratch.BlockType.REPORTER, - text: 'in text [text] get substring from [h] to [e]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'this is text test' - }, - h: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '6' - }, - e: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '7' - }, - } - }, { - opcode: "replace", - blockType: Scratch.BlockType.REPORTER, - text: "replace [o] of [text] with [n]", - arguments: { - o: { - type: Scratch.ArgumentType.STRING, - defaultValue: "world" - }, - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: "Hello world!" - }, - n: { - type: Scratch.ArgumentType.STRING, - defaultValue: "Scratch" - } - } - }, { - opcode: 'Split', - blockType: Scratch.BlockType.REPORTER, - text: 'divide [text] according to [symbol] to take the [n] th item', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'a_b_c' - }, - symbol: { - type: Scratch.ArgumentType.STRING, - defaultValue: '_' - }, - n: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '2' - }, - } - }, '---', { - opcode: 'toUpperCase', - blockType: Scratch.BlockType.REPORTER, - text: 'UPPER CASE [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Text' - }, - } - }, { - opcode: 'toLowerCase', - blockType: Scratch.BlockType.REPORTER, - text: 'lower case [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Text' - }, - } - }, { - opcode: 'textToTitleCase', - blockType: Scratch.BlockType.REPORTER, - text: 'Title Case [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'the text test' - }, - } - }, '---', { - opcode: 'indexOf', - blockType: Scratch.BlockType.REPORTER, - text: '[a] the first appearance in [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'The text test' - }, - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'te' - }, - } - }, { - opcode: 'lastIndexOf', - blockType: Scratch.BlockType.REPORTER, - text: '[a] the position of last occurrence in [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'The text test' - }, - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'te' - }, - } - }, { - opcode: 'countKeyword', - blockType: Scratch.BlockType.REPORTER, - text: '[a] the number of occurrences in [text]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'The text test' - }, - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'te' - }, - } - }, '---', { - opcode: 'startsWith', - blockType: Scratch.BlockType.BOOLEAN, - text: 'does [a] begin with a text?', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Abc123' - }, - } - }, - { - opcode: 'matchTextWithPattern', - blockType: Scratch.BlockType.BOOLEAN, - text: 'match [text] as [pattern] - [flags]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'abc' - }, - pattern: { - type: Scratch.ArgumentType.STRING, - defaultValue: '[ -~]' - }, - flags: { - type: Scratch.ArgumentType.STRING, - menu: 'flags' - }, - } - }, '---', { - opcode: 'ascii', - blockType: Scratch.BlockType.REPORTER, - text: '[a]\'s ascii', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'a' - }, - } - }, { - opcode: 'ascii_', - blockType: Scratch.BlockType.REPORTER, - text: 'ascii is [a] \'s text', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '97' - }, - } - }, '---', { - opcode: 'line_segment', - blockType: Scratch.BlockType.REPORTER, - text: 'line segment ([x1],[y1]) to ([x2],[y2])', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - } - }, - '---', - { - opcode: 'triangle', - blockType: Scratch.BlockType.REPORTER, - text: 'triangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) \'s [CS]', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - { - opcode: 'triangle_s', - blockType: Scratch.BlockType.REPORTER, - text: 'triangle [s1] [s2] [s3] \'s square', - arguments: { - s1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '3' - }, - s2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '4' - }, - s3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - }, - }, - { - opcode: 'rectangle', - blockType: Scratch.BlockType.REPORTER, - text: 'rectangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) \'s [CS]', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - { - opcode: 'graph', - blockType: Scratch.BlockType.REPORTER, - text: 'graph [graph] \'s [CS]', - arguments: { - graph: { - type: Scratch.ArgumentType.STRING, - defaultValue: '[[0,0], [0,2], [2,4], [4,2], [4,0]]' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - '---', - { - opcode: 'circle', - blockType: Scratch.BlockType.REPORTER, - text: 'circle: [rd][a] \'s [CS]', - arguments: { - rd: { - type: Scratch.ArgumentType.STRING, - menu: 'rd' - }, - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - CS: { - type: Scratch.ArgumentType.STRING, - menu: 'cs' - }, - }, - }, - '---', - { - opcode: 'words', - blockType: Scratch.BlockType.REPORTER, - disableMonitor: true, - text: 'sort unique words in [text] as [language]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'movie dog restaurant book school' - }, - language: { - type: Scratch.ArgumentType.STRING, - menu: 'language' - } - } - }, '---', - { - opcode: 'true', - blockType: Scratch.BlockType.BOOLEAN, - text: 'true', - }, - { - opcode: 'false', - blockType: Scratch.BlockType.BOOLEAN, - text: 'false', - }, - { - opcode: 'new_line', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: '\\n', - }, - { - opcode: 'pi', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: 'π', - }, - { - opcode: 'phi', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: 'φ', - }, - { - opcode: 'e', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: 'e', - }, - { - opcode: 'infinity', - disableMonitor: true, - blockType: Scratch.BlockType.REPORTER, - text: '∞', - }, - ], - menus: { - rd: { - acceptReporters: true, - items: [ - { - text: 'radius (r)', - value: 'r' - }, - { - text: 'diameter (d)', - value: 'd' - } - ] - }, - cs: { - acceptReporters: true, - items: [ - { - text: 'square (s)', - value: 's' - }, - { - text: 'circumference (c)', - value: 'c' - } - ] - }, - language: { - acceptReporters: true, - items: [ - { - text: 'English (en)', - value: 'en' - }, - { - text: 'Chinese (zh)', - value: 'zh' - } - ] - }, - flags: { - acceptReporters: false, - items: [ - { - text: 'global (g)', - value: 'g' - }, - { - text: 'ignoring case (i)', - value: 'i' - } - ] - } - } - }; - } - exponent({ A, B }) { - return A ** B; - } - negative({ A }) { - return 0 - A; - } - n_th_Root({ A, B }) { - return Math.pow(B, 1 / A); - } - astrict({ A, B, C }) { - return Math.min(Math.max(A, B), C); - } - round(args) { - return args.A.toFixed(args.B); - } + "use strict"; + class MathAndString { + getInfo() { + return { + color1: "#5ac900", + color2: "#48a100", + color3: "#48a100", + id: "nonameawamathandstring", + name: "Math And String", + blocks: [ + { + opcode: "exponent", + blockType: Scratch.BlockType.REPORTER, + text: "[A] ^ [B]", + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "2", + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + }, + }, + { + opcode: "negative", + blockType: Scratch.BlockType.REPORTER, + text: "- [A]", + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "2", + }, + }, + }, + { + opcode: "n_th_Root", + blockType: Scratch.BlockType.REPORTER, + text: "[A] √ [B]", + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3", + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "8", + }, + }, + }, + "---", + { + opcode: "astrict", + blockType: Scratch.BlockType.REPORTER, + text: "constrain [A] low [B] high [C]", + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + { + opcode: "round", + blockType: Scratch.BlockType.REPORTER, + text: "Round [A] to [B] decimal places", + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3.14", + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + "---", + { + opcode: "boolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "booleanToInt", + blockType: Scratch.BlockType.REPORTER, + text: "[a]", + arguments: { + a: { + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + "---", + { + opcode: "equal", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ⩵ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "A", + }, + b: { + type: Scratch.ArgumentType.STRING, + defaultValue: "a", + }, + }, + }, + { + opcode: "equalNegative", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] =- [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-5", + }, + }, + }, + { + opcode: "equalPlusMinus", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] =± [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-5", + }, + }, + }, + { + opcode: "notEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≠ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + b: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "almostEqual2n", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≈ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5.5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "6", + }, + }, + }, + { + opcode: "almostEqual3n", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≈ [b] ± [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "6", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "xor", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ^ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.BOOLEAN, + }, + b: { + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + "---", + { + opcode: "equalOrGreater", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≥ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + }, + }, + { + opcode: "equalOrLess", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≤ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + }, + }, + { + opcode: "between", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] < [b] < [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + }, + }, + { + opcode: "betweenEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≤ [b] ≤ [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "", + }, + }, + }, + "---", + { + opcode: "vertical", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ⊥ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "0", + }, + b: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "90", + }, + }, + }, + "---", + { + opcode: "text", + blockType: Scratch.BlockType.REPORTER, + text: "[a]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + "---", + { + opcode: "repeat", + blockType: Scratch.BlockType.REPORTER, + text: "repeat [text] [n] times", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Text ", + }, + n: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3", + }, + }, + }, + { + opcode: "trim", + blockType: Scratch.BlockType.REPORTER, + text: "trim spaces from both sides of [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: " Text ", + }, + }, + }, + { + opcode: "intercept", + blockType: Scratch.BlockType.REPORTER, + text: "in text [text] get substring from [h] to [e]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "this is text test", + }, + h: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "6", + }, + e: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "7", + }, + }, + }, + { + opcode: "replace", + blockType: Scratch.BlockType.REPORTER, + text: "replace [o] of [text] with [n]", + arguments: { + o: { + type: Scratch.ArgumentType.STRING, + defaultValue: "world", + }, + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello world!", + }, + n: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Scratch", + }, + }, + }, + { + opcode: "Split", + blockType: Scratch.BlockType.REPORTER, + text: "divide [text] according to [symbol] to take the [n] th item", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "a_b_c", + }, + symbol: { + type: Scratch.ArgumentType.STRING, + defaultValue: "_", + }, + n: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "2", + }, + }, + }, + "---", + { + opcode: "toUpperCase", + blockType: Scratch.BlockType.REPORTER, + text: "UPPER CASE [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Text", + }, + }, + }, + { + opcode: "toLowerCase", + blockType: Scratch.BlockType.REPORTER, + text: "lower case [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Text", + }, + }, + }, + { + opcode: "textToTitleCase", + blockType: Scratch.BlockType.REPORTER, + text: "Title Case [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "the text test", + }, + }, + }, + "---", + { + opcode: "indexOf", + blockType: Scratch.BlockType.REPORTER, + text: "[a] the first appearance in [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "The text test", + }, + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "te", + }, + }, + }, + { + opcode: "lastIndexOf", + blockType: Scratch.BlockType.REPORTER, + text: "[a] the position of last occurrence in [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "The text test", + }, + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "te", + }, + }, + }, + { + opcode: "countKeyword", + blockType: Scratch.BlockType.REPORTER, + text: "[a] the number of occurrences in [text]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "The text test", + }, + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "te", + }, + }, + }, + "---", + { + opcode: "startsWith", + blockType: Scratch.BlockType.BOOLEAN, + text: "does [a] begin with a text?", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Abc123", + }, + }, + }, + { + opcode: "matchTextWithPattern", + blockType: Scratch.BlockType.BOOLEAN, + text: "match [text] as [pattern] - [flags]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "abc", + }, + pattern: { + type: Scratch.ArgumentType.STRING, + defaultValue: "[ -~]", + }, + flags: { + type: Scratch.ArgumentType.STRING, + menu: "flags", + }, + }, + }, + "---", + { + opcode: "ascii", + blockType: Scratch.BlockType.REPORTER, + text: "[a]'s ascii", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "a", + }, + }, + }, + { + opcode: "ascii_", + blockType: Scratch.BlockType.REPORTER, + text: "ascii is [a] 's text", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "97", + }, + }, + }, + "---", + { + opcode: "line_segment", + blockType: Scratch.BlockType.REPORTER, + text: "line segment ([x1],[y1]) to ([x2],[y2])", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + "---", + { + opcode: "triangle", + blockType: Scratch.BlockType.REPORTER, + text: "triangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) 's [CS]", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + { + opcode: "triangle_s", + blockType: Scratch.BlockType.REPORTER, + text: "triangle [s1] [s2] [s3] 's square", + arguments: { + s1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "3", + }, + s2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "4", + }, + s3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + }, + }, + { + opcode: "rectangle", + blockType: Scratch.BlockType.REPORTER, + text: "rectangle ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) 's [CS]", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + { + opcode: "graph", + blockType: Scratch.BlockType.REPORTER, + text: "graph [graph] 's [CS]", + arguments: { + graph: { + type: Scratch.ArgumentType.STRING, + defaultValue: "[[0,0], [0,2], [2,4], [4,2], [4,0]]", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + "---", + { + opcode: "circle", + blockType: Scratch.BlockType.REPORTER, + text: "circle: [rd][a] 's [CS]", + arguments: { + rd: { + type: Scratch.ArgumentType.STRING, + menu: "rd", + }, + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + CS: { + type: Scratch.ArgumentType.STRING, + menu: "cs", + }, + }, + }, + "---", + { + opcode: "words", + blockType: Scratch.BlockType.REPORTER, + disableMonitor: true, + text: "sort unique words in [text] as [language]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "movie dog restaurant book school", + }, + language: { + type: Scratch.ArgumentType.STRING, + menu: "language", + }, + }, + }, + "---", + { + opcode: "true", + blockType: Scratch.BlockType.BOOLEAN, + text: "true", + }, + { + opcode: "false", + blockType: Scratch.BlockType.BOOLEAN, + text: "false", + }, + { + opcode: "new_line", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: "\\n", + }, + { + opcode: "pi", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: "π", + }, + { + opcode: "phi", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: "φ", + }, + { + opcode: "e", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: "e", + }, + { + opcode: "infinity", + disableMonitor: true, + blockType: Scratch.BlockType.REPORTER, + text: "∞", + }, + ], + menus: { + rd: { + acceptReporters: true, + items: [ + { + text: "radius (r)", + value: "r", + }, + { + text: "diameter (d)", + value: "d", + }, + ], + }, + cs: { + acceptReporters: true, + items: [ + { + text: "square (s)", + value: "s", + }, + { + text: "circumference (c)", + value: "c", + }, + ], + }, + language: { + acceptReporters: true, + items: [ + { + text: "English (en)", + value: "en", + }, + { + text: "Chinese (zh)", + value: "zh", + }, + ], + }, + flags: { + acceptReporters: false, + items: [ + { + text: "global (g)", + value: "g", + }, + { + text: "ignoring case (i)", + value: "i", + }, + ], + }, + }, + }; + } + exponent({ A, B }) { + return A ** B; + } + negative({ A }) { + return 0 - A; + } + n_th_Root({ A, B }) { + return Math.pow(B, 1 / A); + } + astrict({ A, B, C }) { + return Math.min(Math.max(A, B), C); + } + round(args) { + return args.A.toFixed(args.B); + } - true() { - return true; - } - false() { - return false; - } - boolean(args) { - return Scratch.Cast.toBoolean(args.a); - } - booleanToInt(args) { - if (Scratch.Cast.toBoolean(args.a)) { - return 1; - } - return 0; - } - equal(args) { - return (args.a == args.b); - } - equalNegative(args) { - if (isNaN(args.a) || isNaN(args.b)) { - return false; - } else { - return (args.a == (0 - args.b)); - } - } - equalPlusMinus(args) { - if (isNaN(args.a) || isNaN(args.b)) { - return false; - } else { - return (args.a == (0 - args.b)) || (args.a == args.b); - } - } - almostEqual2n(args) { - return (Math.round(args.a) == Math.round(args.b)); - } - almostEqual3n(args) { - return (Math.abs(args.a - args.b) <= args.c); - } - between(args) { - return (args.a < args.b) && (args.b < args.c); - } - betweenEqual(args) { - return (args.a <= args.b) && (args.b <= args.c); - } - notEqual(args) { - return (args.a != args.b); - } - xor(args) { - return Scratch.Cast.toBoolean(args.a) !== Scratch.Cast.toBoolean(args.b); - } - equalOrGreater(args) { - return (args.a >= args.b); - } - equalOrLess(args) { - return (args.a <= args.b); - } - vertical(args) { - if (isNaN(args.a) || isNaN(args.b)) { - return false; - } else { - return ((args.a - (args.b - 90)) % 180) == 0; - } - } - segment_one(args) { - return Math.round(Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2))) == args.n; - } - segment_two(args) { - return Math.round(Math.sqrt(Math.pow(args.x11 - args.x12, 2) + Math.pow(args.y11 - args.y12, 2))) - == Math.round(Math.sqrt(Math.pow(args.x21 - args.x22, 2) + Math.pow(args.y21 - args.y22, 2))); - } + true() { + return true; + } + false() { + return false; + } + boolean(args) { + return Scratch.Cast.toBoolean(args.a); + } + booleanToInt(args) { + if (Scratch.Cast.toBoolean(args.a)) { + return 1; + } + return 0; + } + equal(args) { + return args.a == args.b; + } + equalNegative(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return args.a == 0 - args.b; + } + } + equalPlusMinus(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return args.a == 0 - args.b || args.a == args.b; + } + } + almostEqual2n(args) { + return Math.round(args.a) == Math.round(args.b); + } + almostEqual3n(args) { + return Math.abs(args.a - args.b) <= args.c; + } + between(args) { + return args.a < args.b && args.b < args.c; + } + betweenEqual(args) { + return args.a <= args.b && args.b <= args.c; + } + notEqual(args) { + return args.a != args.b; + } + xor(args) { + return Scratch.Cast.toBoolean(args.a) !== Scratch.Cast.toBoolean(args.b); + } + equalOrGreater(args) { + return args.a >= args.b; + } + equalOrLess(args) { + return args.a <= args.b; + } + vertical(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return (args.a - (args.b - 90)) % 180 == 0; + } + } + segment_one(args) { + return ( + Math.round( + Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ) + ) == args.n + ); + } + segment_two(args) { + return ( + Math.round( + Math.sqrt( + Math.pow(args.x11 - args.x12, 2) + Math.pow(args.y11 - args.y12, 2) + ) + ) == + Math.round( + Math.sqrt( + Math.pow(args.x21 - args.x22, 2) + Math.pow(args.y21 - args.y22, 2) + ) + ) + ); + } - ascii(args) { - return args.a.charCodeAt(); - } - ascii_(args) { - return String.fromCharCode(args.a); - } - text(args) { - return args.a; - } - repeat(args) { - if (args.n > 0) { - let repeat_i; - let repeat_j = ''; - let repeat_i_inc = 1; - if (1 > args.n) { - repeat_i_inc = -repeat_i_inc; - } - for (repeat_i = 1; repeat_i_inc >= 0 ? repeat_i <= args.n : repeat_i >= args.n; repeat_i += repeat_i_inc) { - repeat_j = String(repeat_j) + String(args.text); - } - return repeat_j; - } - return ''; - } - intercept(args) { - return args.text.slice((args.h - 1), args.e); - } - toUpperCase(args) { - return args.text.toUpperCase(); - } - toLowerCase(args) { - return args.text.toLowerCase(); - } - textToTitleCase(args) { - return textToTitleCase(args.text); - } - 'trim'(args) { - return args.text.trim(); - } - new_line() { - return '\n'; - } - 'Split'(args) { - const symbol = args.symbol === '.' ? '\\.' : args.symbol; - if (args.text && typeof args.text.split === 'function' && args.text.split(symbol)[(args.n - 1)] != undefined) { - return args.text.split(symbol)[(args.n - 1)]; - } - return ''; - } - indexOf(args) { - return args.text.indexOf(args.a) + 1; - } - lastIndexOf(args) { - return args.text.lastIndexOf(args.a) + 1; - } - replace(args) { - return replaceText(args.text, args.o, args.n); - } - startsWith(args) { - if (typeof args.a === 'string' && args.a.startsWith(args.a)) { - return true; - } else { - return false; - } + ascii(args) { + return args.a.charCodeAt(); + } + ascii_(args) { + return String.fromCharCode(args.a); + } + text(args) { + return args.a; + } + repeat(args) { + if (args.n > 0) { + let repeat_i; + let repeat_j = ""; + let repeat_i_inc = 1; + if (1 > args.n) { + repeat_i_inc = -repeat_i_inc; } - 'countKeyword'(args) { - return countKeyword(args.text, args.a); + for ( + repeat_i = 1; + repeat_i_inc >= 0 ? repeat_i <= args.n : repeat_i >= args.n; + repeat_i += repeat_i_inc + ) { + repeat_j = String(repeat_j) + String(args.text); } + return repeat_j; + } + return ""; + } + intercept(args) { + return args.text.slice(args.h - 1, args.e); + } + toUpperCase(args) { + return args.text.toUpperCase(); + } + toLowerCase(args) { + return args.text.toLowerCase(); + } + textToTitleCase(args) { + return textToTitleCase(args.text); + } + trim(args) { + return args.text.trim(); + } + new_line() { + return "\n"; + } + Split(args) { + const symbol = args.symbol === "." ? "\\." : args.symbol; + if ( + args.text && + typeof args.text.split === "function" && + args.text.split(symbol)[args.n - 1] != undefined + ) { + return args.text.split(symbol)[args.n - 1]; + } + return ""; + } + indexOf(args) { + return args.text.indexOf(args.a) + 1; + } + lastIndexOf(args) { + return args.text.lastIndexOf(args.a) + 1; + } + replace(args) { + return replaceText(args.text, args.o, args.n); + } + startsWith(args) { + if (typeof args.a === "string" && args.a.startsWith(args.a)) { + return true; + } else { + return false; + } + } + countKeyword(args) { + return countKeyword(args.text, args.a); + } - line_segment(args) { - return Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - } - triangle(args) { - if (args.CS == 's') { - let points = [[args.x1, args.y1], [args.x2, args.y2], [args.x3, args.y3]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let i = 0; - i += Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - i += Math.sqrt(Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2)); - i += Math.sqrt(Math.pow(args.x3 - args.x1, 2) + Math.pow(args.y3 - args.y1, 2)); - return i; - } - return 0; - } - triangle_s(args) { - const s = (args.s1 + args.s2 + args.s3) / 2; - const area = Math.sqrt(s * (s - args.s1) * (s - args.s2) * (s - args.s3)); - return area; + line_segment(args) { + return Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + } + triangle(args) { + if (args.CS == "s") { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; } - rectangle(args) { - if (args.CS == 's') { - let points = [[args.x1, args.y1], [args.x2, args.y2], [args.x3, args.y3], [args.x4, args.y4]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let i = 0; - i += Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - i += Math.sqrt(Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2)); - i += Math.sqrt(Math.pow(args.x3 - args.x4, 2) + Math.pow(args.y3 - args.y4, 2)); - i += Math.sqrt(Math.pow(args.x4 - args.x1, 2) + Math.pow(args.y4 - args.y1, 2)); - return i; - } - return 0; + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let i = 0; + i += Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + i += Math.sqrt( + Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2) + ); + i += Math.sqrt( + Math.pow(args.x3 - args.x1, 2) + Math.pow(args.y3 - args.y1, 2) + ); + return i; + } + return 0; + } + triangle_s(args) { + const s = (args.s1 + args.s2 + args.s3) / 2; + const area = Math.sqrt(s * (s - args.s1) * (s - args.s2) * (s - args.s3)); + return area; + } + rectangle(args) { + if (args.CS == "s") { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + [args.x4, args.y4], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; } - graph(args) { - let points = JSON.parse(args.graph); - let n = points.length; - if (args.CS == 's') { - let area = 0; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - if (args.CS == 'c') { - let x1, x2, y1, y2; - let j = 0; - j = 0; - var i_end = n - 1; - var i_inc = 1; - if (0 > i_end) { - i_inc = -i_inc; - } - for (let i = 0; i_inc >= 0 ? i <= i_end : i >= i_end; i += i_inc) { - x1 = points[((i + 1) - 1)][0]; - x2 = i == n - 1 ? points[0][0] : points[((i + 2) - 1)][0]; - y1 = points[((i + 1) - 1)][1]; - y2 = i == n - 1 ? points[0][1] : points[((i + 2) - 1)][1]; - j = (typeof j == 'number' ? j : 0) + Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)); - } - return j; - } - return 0; + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let i = 0; + i += Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + i += Math.sqrt( + Math.pow(args.x2 - args.x3, 2) + Math.pow(args.y2 - args.y3, 2) + ); + i += Math.sqrt( + Math.pow(args.x3 - args.x4, 2) + Math.pow(args.y3 - args.y4, 2) + ); + i += Math.sqrt( + Math.pow(args.x4 - args.x1, 2) + Math.pow(args.y4 - args.y1, 2) + ); + return i; + } + return 0; + } + graph(args) { + let points = JSON.parse(args.graph); + let n = points.length; + if (args.CS == "s") { + let area = 0; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; } - circle(args) { - if (args.CS == 'c') { - return 2 * Math.PI * (args.rd == 'r' ? args.a : args.a / 2); - } - if (args.CS == 's') { - return Math.PI * ((args.rd == 'r' ? args.a : args.a / 2) ** 2); - } + area = Math.abs(area) / 2; + return area; + } + if (args.CS == "c") { + let x1, x2, y1, y2; + let j = 0; + j = 0; + var i_end = n - 1; + var i_inc = 1; + if (0 > i_end) { + i_inc = -i_inc; } - pi() { - return Math.PI; + for (let i = 0; i_inc >= 0 ? i <= i_end : i >= i_end; i += i_inc) { + x1 = points[i + 1 - 1][0]; + x2 = i == n - 1 ? points[0][0] : points[i + 2 - 1][0]; + y1 = points[i + 1 - 1][1]; + y2 = i == n - 1 ? points[0][1] : points[i + 2 - 1][1]; + j = + (typeof j == "number" ? j : 0) + + Math.sqrt( + Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2) + ); } + return j; + } + return 0; + } + circle(args) { + if (args.CS == "c") { + return 2 * Math.PI * (args.rd == "r" ? args.a : args.a / 2); + } + if (args.CS == "s") { + return Math.PI * (args.rd == "r" ? args.a : args.a / 2) ** 2; + } + } + pi() { + return Math.PI; + } - words(args) { - const text = Scratch.Cast.toString(args.text); - const words = parse(text, args.language); - return words.join(' '); - } + words(args) { + const text = Scratch.Cast.toString(args.text); + const words = parse(text, args.language); + return words.join(" "); + } - phi() { - return (1 + Math.sqrt(5)) / 2; - } - e() { - return Math.E; - } - infinity() { - return 'Infinity'; - } + phi() { + return (1 + Math.sqrt(5)) / 2; + } + e() { + return Math.E; + } + infinity() { + return "Infinity"; + } - matchTextWithPattern({ text, pattern, flags }) { - const regex = new RegExp(pattern, flags); - return regex.test(text); - } + matchTextWithPattern({ text, pattern, flags }) { + const regex = new RegExp(pattern, flags); + return regex.test(text); } + } - const textToTitleCase = (str) => { - return str.replace(/\S+/g, - function (txt) { - return txt[0].toUpperCase() + txt.substring(1).toLowerCase(); -}); - }; + const textToTitleCase = (str) => { + return str.replace(/\S+/g, function (txt) { + return txt[0].toUpperCase() + txt.substring(1).toLowerCase(); + }); + }; - const replaceText = (text, oldStr, newStr) => { - return text.replace(new RegExp(oldStr, 'g'), newStr); - }; + const replaceText = (text, oldStr, newStr) => { + return text.replace(new RegExp(oldStr, "g"), newStr); + }; - const sortAndUniqueWords_en = (text) => { - let words = text.toLowerCase().match(/\b\w+\b/g); - words = Array.from(new Set(words)); - words.sort(); - return words.join(' '); - }; + const sortAndUniqueWords_en = (text) => { + let words = text.toLowerCase().match(/\b\w+\b/g); + words = Array.from(new Set(words)); + words.sort(); + return words.join(" "); + }; - const sortAndUniqueWords_cn = (text) => { - let words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); - words = Array.from(new Set(words)); - words.sort(function (a, b) { - return a.localeCompare(b, 'zh-Hans-CN', { sensitivity: 'accent' }); - }); - return words.join(' '); - }; + const sortAndUniqueWords_cn = (text) => { + let words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); + words = Array.from(new Set(words)); + words.sort(function (a, b) { + return a.localeCompare(b, "zh-Hans-CN", { sensitivity: "accent" }); + }); + return words.join(" "); + }; - const countKeyword = (sentence, keyword) => { - const count = (sentence.match(new RegExp(keyword, 'gi')) || []).length; - return count; - }; + const countKeyword = (sentence, keyword) => { + const count = (sentence.match(new RegExp(keyword, "gi")) || []).length; + return count; + }; - const parseEnglish = (text) => { - const words = text.toLowerCase().match(/\b\w+\b/g); - const uniques = Array.from(new Set(words)); - uniques.sort(); - return uniques; - }; + const parseEnglish = (text) => { + const words = text.toLowerCase().match(/\b\w+\b/g); + const uniques = Array.from(new Set(words)); + uniques.sort(); + return uniques; + }; - const parseChinese = (text) => { - const words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); - const uniques = Array.from(new Set(words)); - uniques.sort(function (a, b) { - return a.localeCompare(b, 'zh-Hans-CN', { sensitivity: 'accent' }); - }); - return uniques; - }; + const parseChinese = (text) => { + const words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); + const uniques = Array.from(new Set(words)); + uniques.sort(function (a, b) { + return a.localeCompare(b, "zh-Hans-CN", { sensitivity: "accent" }); + }); + return uniques; + }; - const parse = (text, language) => { - if (language === 'zh') { - return parseChinese(text); - } - return parseEnglish(text); - }; + const parse = (text, language) => { + if (language === "zh") { + return parseChinese(text); + } + return parseEnglish(text); + }; - Scratch.extensions.register(new MathAndString()); + Scratch.extensions.register(new MathAndString()); })(Scratch); diff --git a/extensions/NOname-awa/more-comparisons.js b/extensions/NOname-awa/more-comparisons.js index 38dc0e5aa7..19a08b5e6b 100644 --- a/extensions/NOname-awa/more-comparisons.js +++ b/extensions/NOname-awa/more-comparisons.js @@ -1,534 +1,568 @@ -(function(Scratch) { - 'use strict'; - const quadrilateral = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3Ny4wMjc4MSIgaGVpZ2h0PSI1NC44MDY1NCIgdmlld0JveD0iMCwwLDc3LjAyNzgxLDU0LjgwNjU0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAxLjUwNDMsLTE1Mi4yMTk3MykiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjI4LjE3ODc4LDE1NS40NzM3NGw0Ni40MDEwMywxOS44ODYxNmwtMjIuNTM3NjQsMjkuMTY2MzZoLTQ2LjYyMTk5eiIvPjwvZz48L2c+PC9zdmc+"; - class MoreComparisons { - getInfo() { - return { - id: 'nonameawacomparisons', - name: 'More Comparisons', - color1: '#00a889', - color2: '#1e8c76', - color3: '#1e8c76', - blocks: [ - { - opcode: 'true', - blockType: Scratch.BlockType.BOOLEAN, - text: 'true', - arguments: {} - }, - { - opcode: 'false', - blockType: Scratch.BlockType.BOOLEAN, - text: 'false', - arguments: {}, - }, - { - opcode: 'boolean', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - } - }, - { - opcode: 'booleanToInt', - blockType: Scratch.BlockType.REPORTER, - text: '[a]', - arguments: { - a: { - type: Scratch.ArgumentType.BOOLEAN, - }, - } - }, - '---', - { - opcode: 'equal', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ⩵ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'A' - }, - b: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'a' - }, - } - }, - { - opcode: 'equalNegative', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] =- [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-5' - }, - } - }, - { - opcode: 'equalPlusMinus', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] =± [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-5' - }, - } - }, - { - opcode: 'notEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≠ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.STRING, - defaultValue: '\n' - }, - b: { - type: Scratch.ArgumentType.STRING, - defaultValue: '\n' - }, - } - }, - { - opcode: 'almostEqual2n', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≈ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5.5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '6' - }, - } - }, - { - opcode: 'almostEqual3n', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≈ [b] ± [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '5' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '6' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '1' - }, - } - }, - { - opcode: 'xor', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ^ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.BOOLEAN, - }, - b: { - type: Scratch.ArgumentType.BOOLEAN, - }, - } - }, - '---', - { - opcode: 'equalOrGreater', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≥ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - }, - } - }, - { - opcode: 'equalOrLess', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≤ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '50' - }, - } - }, - { - opcode: 'between', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] < [b] < [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - } - }, - { - opcode: 'betweenEqual', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≤ [b] ≤ [c]', - arguments: { - a: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - b: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - c: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '\n' - }, - } - }, - '---', - { - opcode: 'vertical', - blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ⊥ [b]', - arguments: { - a: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '0' - }, - b: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: '90' - }, - } - }, - { - opcode: 'segment_one', - blockType: Scratch.BlockType.BOOLEAN, - text: '⎵ ([x1],[y1]) ([x2],[y2]) = [n]', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - - n: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - } - }, - { - opcode: 'segment_two', - blockType: Scratch.BlockType.BOOLEAN, - text: '⎵ ([x11],[y11]) ([x12],[y12]) = ⎵ ([x21],[y21]) ([x22],[y22])', - arguments: { - x11: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - y11: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x12: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y12: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - - x21: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - y21: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x22: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y22: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - } - }, - { - opcode: 'segment', - blockType: Scratch.BlockType.REPORTER, - text: '⎵ ([x1],[y1]) ([x2],[y2])', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '-100' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - } - }, - '---', - { - opcode: 'Squadrilateral_one', - blockType: Scratch.BlockType.BOOLEAN, - text: '[IMAGE] ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) = [n]', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - - n: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - }, - - IMAGE: { - type: Scratch.ArgumentType.IMAGE, - dataURI: quadrilateral, - flipRTL: true - } - }, - }, - { - opcode: 'Squadrilateral', - blockType: Scratch.BlockType.REPORTER, - text: '[IMAGE] ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4])', - arguments: { - x1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y1: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y2: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - x3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '10' - }, - y3: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - x4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - y4: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' - }, - - IMAGE: { - type: Scratch.ArgumentType.IMAGE, - dataURI: quadrilateral, - flipRTL: true - } - }, - } - ] - }; - } - true(){ - return true; - } - false(){ - return false; - } - boolean(args){ - return Scratch.Cast.toBoolean(args.a); - } - booleanToInt(args){ - if (Scratch.Cast.toBoolean(args.a)) { - return 1; - } - return 0; - } - equal(args) { - return (args.a == args.b); - } - equalNegative(args) { - if (isNaN(args.a) || isNaN(args.b)){ - return false; - } else { - return (args.a == (0 - args.b)); - } - } - equalPlusMinus(args) { - if (isNaN(args.a) || isNaN(args.b)){ - return false; - } else { - return (args.a == (0 - args.b)) || (args.a == args.b); - } - } - almostEqual2n(args) { - return (Math.round(args.a) == Math.round(args.b)); - } - almostEqual3n(args) { - return (Math.abs(args.a - args.b) <= args.c); - } - between(args) { - return (args.a < args.b) && (args.b < args.c); - } - betweenEqual(args) { - return (args.a <= args.b) && (args.b <= args.c); - } - notEqual(args){ - return (args.a != args.b); - } - xor(args){ - return Scratch.Cast.toBoolean(args.a) !== Scratch.Cast.toBoolean(args.b); - } - equalOrGreater(args) { - return (args.a >= args.b); - } - equalOrLess(args) { - return (args.a <= args.b); - } - vertical(args) { - if (isNaN(args.a) || isNaN(args.b)){ - return false; - } else { - return ((args.a - (args.b - 90)) % 180) == 0; - } - } - segment_one(args) { - return Math.round(Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2))) == args.n; - } - segment_two(args) { - return Math.round(Math.sqrt(Math.pow(args.x11 - args.x12, 2) + Math.pow(args.y11 - args.y12, 2))) - == Math.round(Math.sqrt(Math.pow(args.x21 - args.x22, 2) + Math.pow(args.y21 - args.y22, 2))); - } - segment(args) { - return Math.sqrt(Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2)); - } - Squadrilateral_one(args) { - let points = [[args.x1,args.y1], [args.x2,args.y2], [args.x3,args.y3], [args.x4,args.y4]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return Math.round(area) == args.n; - } - Squadrilateral(args) { - let points = [[args.x1,args.y1], [args.x2,args.y2], [args.x3,args.y3], [args.x4,args.y4]]; - let area = 0; - let n = points.length; - for (let i = 0; i < n; i++) { - let x1 = points[i][0]; - let y1 = points[i][1]; - let x2 = points[(i + 1) % n][0]; - let y2 = points[(i + 1) % n][1]; - area += x1 * y2; - area -= x2 * y1; - } - area = Math.abs(area) / 2; - return (area); - } - } - Scratch.extensions.register(new MoreComparisons()); -})(Scratch); +// Name: More Comparisons +// ID: nonameawacomparisons +// Description: More comparison blocks. +// By: NOname-awa + +(function (Scratch) { + "use strict"; + const quadrilateral = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3Ny4wMjc4MSIgaGVpZ2h0PSI1NC44MDY1NCIgdmlld0JveD0iMCwwLDc3LjAyNzgxLDU0LjgwNjU0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjAxLjUwNDMsLTE1Mi4yMTk3MykiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMjI4LjE3ODc4LDE1NS40NzM3NGw0Ni40MDEwMywxOS44ODYxNmwtMjIuNTM3NjQsMjkuMTY2MzZoLTQ2LjYyMTk5eiIvPjwvZz48L2c+PC9zdmc+"; + class MoreComparisons { + getInfo() { + return { + id: "nonameawacomparisons", + name: "More Comparisons", + color1: "#00a889", + color2: "#1e8c76", + color3: "#1e8c76", + blocks: [ + { + opcode: "true", + blockType: Scratch.BlockType.BOOLEAN, + text: "true", + arguments: {}, + }, + { + opcode: "false", + blockType: Scratch.BlockType.BOOLEAN, + text: "false", + arguments: {}, + }, + { + opcode: "boolean", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "booleanToInt", + blockType: Scratch.BlockType.REPORTER, + text: "[a]", + arguments: { + a: { + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + "---", + { + opcode: "equal", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ⩵ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "A", + }, + b: { + type: Scratch.ArgumentType.STRING, + defaultValue: "a", + }, + }, + }, + { + opcode: "equalNegative", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] =- [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-5", + }, + }, + }, + { + opcode: "equalPlusMinus", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] =± [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-5", + }, + }, + }, + { + opcode: "notEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≠ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.STRING, + defaultValue: "\n", + }, + b: { + type: Scratch.ArgumentType.STRING, + defaultValue: "\n", + }, + }, + }, + { + opcode: "almostEqual2n", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≈ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5.5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "6", + }, + }, + }, + { + opcode: "almostEqual3n", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≈ [b] ± [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "5", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "6", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "1", + }, + }, + }, + { + opcode: "xor", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ^ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.BOOLEAN, + }, + b: { + type: Scratch.ArgumentType.BOOLEAN, + }, + }, + }, + "---", + { + opcode: "equalOrGreater", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≥ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + }, + }, + { + opcode: "equalOrLess", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≤ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "50", + }, + }, + }, + { + opcode: "between", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] < [b] < [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + }, + }, + { + opcode: "betweenEqual", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ≤ [b] ≤ [c]", + arguments: { + a: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + b: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + c: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "\n", + }, + }, + }, + "---", + { + opcode: "vertical", + blockType: Scratch.BlockType.BOOLEAN, + text: "[a] ⊥ [b]", + arguments: { + a: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "0", + }, + b: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "90", + }, + }, + }, + { + opcode: "segment_one", + blockType: Scratch.BlockType.BOOLEAN, + text: "⎵ ([x1],[y1]) ([x2],[y2]) = [n]", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + + n: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + { + opcode: "segment_two", + blockType: Scratch.BlockType.BOOLEAN, + text: "⎵ ([x11],[y11]) ([x12],[y12]) = ⎵ ([x21],[y21]) ([x22],[y22])", + arguments: { + x11: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + y11: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x12: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y12: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + + x21: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + y21: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x22: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y22: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + { + opcode: "segment", + blockType: Scratch.BlockType.REPORTER, + text: "⎵ ([x1],[y1]) ([x2],[y2])", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + "---", + { + opcode: "Squadrilateral_one", + blockType: Scratch.BlockType.BOOLEAN, + text: "[IMAGE] ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4]) = [n]", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + + n: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + + IMAGE: { + type: Scratch.ArgumentType.IMAGE, + dataURI: quadrilateral, + flipRTL: true, + }, + }, + }, + { + opcode: "Squadrilateral", + blockType: Scratch.BlockType.REPORTER, + text: "[IMAGE] ([x1],[y1]) ([x2],[y2]) ([x3],[y3]) ([x4],[y4])", + arguments: { + x1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + x3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + y3: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + x4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + y4: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + + IMAGE: { + type: Scratch.ArgumentType.IMAGE, + dataURI: quadrilateral, + flipRTL: true, + }, + }, + }, + ], + }; + } + true() { + return true; + } + false() { + return false; + } + boolean(args) { + return Scratch.Cast.toBoolean(args.a); + } + booleanToInt(args) { + if (Scratch.Cast.toBoolean(args.a)) { + return 1; + } + return 0; + } + equal(args) { + return args.a == args.b; + } + equalNegative(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return args.a == 0 - args.b; + } + } + equalPlusMinus(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return args.a == 0 - args.b || args.a == args.b; + } + } + almostEqual2n(args) { + return Math.round(args.a) == Math.round(args.b); + } + almostEqual3n(args) { + return Math.abs(args.a - args.b) <= args.c; + } + between(args) { + return args.a < args.b && args.b < args.c; + } + betweenEqual(args) { + return args.a <= args.b && args.b <= args.c; + } + notEqual(args) { + return args.a != args.b; + } + xor(args) { + return Scratch.Cast.toBoolean(args.a) !== Scratch.Cast.toBoolean(args.b); + } + equalOrGreater(args) { + return args.a >= args.b; + } + equalOrLess(args) { + return args.a <= args.b; + } + vertical(args) { + if (isNaN(args.a) || isNaN(args.b)) { + return false; + } else { + return (args.a - (args.b - 90)) % 180 == 0; + } + } + segment_one(args) { + return ( + Math.round( + Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ) + ) == args.n + ); + } + segment_two(args) { + return ( + Math.round( + Math.sqrt( + Math.pow(args.x11 - args.x12, 2) + Math.pow(args.y11 - args.y12, 2) + ) + ) == + Math.round( + Math.sqrt( + Math.pow(args.x21 - args.x22, 2) + Math.pow(args.y21 - args.y22, 2) + ) + ) + ); + } + segment(args) { + return Math.sqrt( + Math.pow(args.x1 - args.x2, 2) + Math.pow(args.y1 - args.y2, 2) + ); + } + Squadrilateral_one(args) { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + [args.x4, args.y4], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; + } + area = Math.abs(area) / 2; + return Math.round(area) == args.n; + } + Squadrilateral(args) { + let points = [ + [args.x1, args.y1], + [args.x2, args.y2], + [args.x3, args.y3], + [args.x4, args.y4], + ]; + let area = 0; + let n = points.length; + for (let i = 0; i < n; i++) { + let x1 = points[i][0]; + let y1 = points[i][1]; + let x2 = points[(i + 1) % n][0]; + let y2 = points[(i + 1) % n][1]; + area += x1 * y2; + area -= x2 * y1; + } + area = Math.abs(area) / 2; + return area; + } + } + Scratch.extensions.register(new MoreComparisons()); +})(Scratch); diff --git a/extensions/NOname-awa/regular-expression.js b/extensions/NOname-awa/regular-expression.js index 9e8aadcdcc..0d79427d1c 100644 --- a/extensions/NOname-awa/regular-expression.js +++ b/extensions/NOname-awa/regular-expression.js @@ -1,241 +1,246 @@ (function (Scratch) { - 'use strict'; - let args = ["", "", "",""]; - class RegularExpression { - getInfo() { - return { - color1: '#0081d3', - color2: '#0067a9', - color3: '#0067a9', - id: 'nonameawaregex', - name: 'Regular Expression', - blocks: [ - { - opcode: 'set', - blockType: Scratch.BlockType.COMMAND, - text: 'set a: [one] b: [two] c: [three]', - arguments: { - one: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - two: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - three: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, - { - opcode: 'matchText', - blockType: Scratch.BlockType.BOOLEAN, - text: 'matchText: b in a [flags]', - arguments: { - flags: { - type: Scratch.ArgumentType.STRING, - menu: 'flags' - } - } - }, - { - opcode: 'searchText', - blockType: Scratch.BlockType.REPORTER, - text: 'searchText: b in a', - }, - { - opcode: 'replaceText', - blockType: Scratch.BlockType.REPORTER, - text: 'replaceText: b in a with c', - }, '---', - { - opcode: 'matchTextWithPattern', - blockType: Scratch.BlockType.BOOLEAN, - text: 'match [pattern] in [text] - [flags]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - pattern: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - flags: { - type: Scratch.ArgumentType.STRING, - menu: 'flags' - } - } - }, - { - opcode: 'searchTextWithPattern', - blockType: Scratch.BlockType.REPORTER, - text: 'search [pattern] in [text] ', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - pattern: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, - { - opcode: 'replaceTextWithPattern', - blockType: Scratch.BlockType.REPORTER, - text: 'replace [pattern] in [text] with [replacement]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - pattern: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - }, - replacement: { - type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } - }, '---', - { - opcode: 'constant', - blockType: Scratch.BlockType.REPORTER, - text: '[constant]', - arguments: { - constant: { - type: Scratch.ArgumentType.STRING, - menu: 'constant' - } - } - }, '---', - { - opcode: 'text', - blockType: Scratch.BlockType.REPORTER, - text: 'a', - }, - { - opcode: 'pattern', - blockType: Scratch.BlockType.REPORTER, - text: 'b', - }, - { - opcode: 'attach', - blockType: Scratch.BlockType.REPORTER, - text: 'c', - } - ], - menus: { - flags: { - acceptReporters: false, - items: [ - { - text: 'global (g)', - value: 'g' - }, - { - text: 'ignore case (i)', - value: 'i' - } - ] - }, - constant: { - acceptReporters: true, - items: [ - { - text: 'english', - value: '[a-zA-Z]' - }, - { - text: 'english uppercase', - value: '[A-Z]' - }, - { - text: 'english lowercase', - value: '[a-z]' - }, - { - text: 'number', - value: '[0-9]' - }, - { - text: 'numeric integer', - value: '^-?[1-9]\\d*$' - }, - { - text: 'positive integer', - value: '^[1-9]\\d*$' - }, - { - text: 'negative integer', - value: '^-[1-9]\\d*$' - }, - { - text: 'non-negative integers', - value: '^[1-9]\\d*|0$' - }, - { - text: 'non-positive integer', - value: '^-[1-9]\\d*|0$' - }, - { - text: 'chinese', - value: '[\u4e00-\u9fa5]' - }, - { - text: 'double-byte', - value: '[^\x00-\xff]' - } - ] - } - } - }; - } - set({ one, two, three }) { - args[1] = one; - args[2] = two; - args[3] = three; - } - matchText({ flags }) { - const regex = new RegExp(args[2], flags); - return regex.test(args[1]); - } - searchText() { - return args[1].toString().search(args[2].toString()); - } - replaceText() { - return args[1].toString().replace(args[2].toString(), args[3].toString()); - } + "use strict"; + let args = ["", "", "", ""]; + class RegularExpression { + getInfo() { + return { + color1: "#0081d3", + color2: "#0067a9", + color3: "#0067a9", + id: "nonameawaregex", + name: "Regular Expression", + blocks: [ + { + opcode: "set", + blockType: Scratch.BlockType.COMMAND, + text: "set a: [one] b: [two] c: [three]", + arguments: { + one: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + two: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + three: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "matchText", + blockType: Scratch.BlockType.BOOLEAN, + text: "matchText: b in a [flags]", + arguments: { + flags: { + type: Scratch.ArgumentType.STRING, + menu: "flags", + }, + }, + }, + { + opcode: "searchText", + blockType: Scratch.BlockType.REPORTER, + text: "searchText: b in a", + }, + { + opcode: "replaceText", + blockType: Scratch.BlockType.REPORTER, + text: "replaceText: b in a with c", + }, + "---", + { + opcode: "matchTextWithPattern", + blockType: Scratch.BlockType.BOOLEAN, + text: "match [pattern] in [text] - [flags]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + pattern: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + flags: { + type: Scratch.ArgumentType.STRING, + menu: "flags", + }, + }, + }, + { + opcode: "searchTextWithPattern", + blockType: Scratch.BlockType.REPORTER, + text: "search [pattern] in [text] ", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + pattern: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "replaceTextWithPattern", + blockType: Scratch.BlockType.REPORTER, + text: "replace [pattern] in [text] with [replacement]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + pattern: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + replacement: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + "---", + { + opcode: "constant", + blockType: Scratch.BlockType.REPORTER, + text: "[constant]", + arguments: { + constant: { + type: Scratch.ArgumentType.STRING, + menu: "constant", + }, + }, + }, + "---", + { + opcode: "text", + blockType: Scratch.BlockType.REPORTER, + text: "a", + }, + { + opcode: "pattern", + blockType: Scratch.BlockType.REPORTER, + text: "b", + }, + { + opcode: "attach", + blockType: Scratch.BlockType.REPORTER, + text: "c", + }, + ], + menus: { + flags: { + acceptReporters: false, + items: [ + { + text: "global (g)", + value: "g", + }, + { + text: "ignore case (i)", + value: "i", + }, + ], + }, + constant: { + acceptReporters: true, + items: [ + { + text: "english", + value: "[a-zA-Z]", + }, + { + text: "english uppercase", + value: "[A-Z]", + }, + { + text: "english lowercase", + value: "[a-z]", + }, + { + text: "number", + value: "[0-9]", + }, + { + text: "numeric integer", + value: "^-?[1-9]\\d*$", + }, + { + text: "positive integer", + value: "^[1-9]\\d*$", + }, + { + text: "negative integer", + value: "^-[1-9]\\d*$", + }, + { + text: "non-negative integers", + value: "^[1-9]\\d*|0$", + }, + { + text: "non-positive integer", + value: "^-[1-9]\\d*|0$", + }, + { + text: "chinese", + value: "[\u4e00-\u9fa5]", + }, + { + text: "double-byte", + value: "[^\x00-\xff]", + }, + ], + }, + }, + }; + } + set({ one, two, three }) { + args[1] = one; + args[2] = two; + args[3] = three; + } + matchText({ flags }) { + const regex = new RegExp(args[2], flags); + return regex.test(args[1]); + } + searchText() { + return args[1].toString().search(args[2].toString()); + } + replaceText() { + return args[1].toString().replace(args[2].toString(), args[3].toString()); + } - matchTextWithPattern({ text, pattern, flags }) { - const regex = new RegExp(pattern, flags); - return regex.test(text); - } - searchTextWithPattern({ text, pattern }) { - return text.toString().search(pattern.toString()); - } - replaceTextWithPattern({ text, pattern, replacement }) { - return text.toString().replace(pattern.toString(), replacement.toString()); - } + matchTextWithPattern({ text, pattern, flags }) { + const regex = new RegExp(pattern, flags); + return regex.test(text); + } + searchTextWithPattern({ text, pattern }) { + return text.toString().search(pattern.toString()); + } + replaceTextWithPattern({ text, pattern, replacement }) { + return text + .toString() + .replace(pattern.toString(), replacement.toString()); + } - constant({ constant }) { - return constant; - } + constant({ constant }) { + return constant; + } - text() { - return args[1]; - } - pattern() { - return args[2]; - } - attach() { - return args[3]; - } + text() { + return args[1]; + } + pattern() { + return args[2]; + } + attach() { + return args[3]; } - Scratch.extensions.register(new RegularExpression()); + } + Scratch.extensions.register(new RegularExpression()); })(Scratch); diff --git a/extensions/NOname-awa/sort-unique-words.js b/extensions/NOname-awa/sort-unique-words.js index f900d9ee26..d4bcc388b7 100644 --- a/extensions/NOname-awa/sort-unique-words.js +++ b/extensions/NOname-awa/sort-unique-words.js @@ -1,78 +1,78 @@ -(function(Scratch) { - 'use strict'; - - const parseEnglish = (text) => { - const words = text.toLowerCase().match(/\b\w+\b/g); - const uniques = Array.from(new Set(words)); - uniques.sort(); - return uniques; - }; - - const parseChinese = (text) => { - const words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); - const uniques = Array.from(new Set(words)); - uniques.sort(function(a, b) { - return a.localeCompare(b, 'zh-Hans-CN', {sensitivity: 'accent'}); - }); - return uniques; - }; - - const parse = (text, language) => { - if (language === 'zh') { - return parseChinese(text); - } - return parseEnglish(text); - }; - - class SortUniqueWords { - getInfo() { - return { - id: 'nonameawasortuniquewords', - name: 'Sort Unique Words', - color1: '#5a8b9e', - color2: '#427081', - color3: '#427081', - blocks: [ - { - opcode: 'words', - blockType: Scratch.BlockType.REPORTER, - disableMonitor: true, - text: 'sort unique words in [text] as [language]', - arguments: { - text: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'movie dog restaurant book school' - }, - language: { - type: Scratch.ArgumentType.STRING, - menu: 'language' - } - } - } - ], - menus: { - language: { - acceptReporters: true, - items: [ - { - text: 'English (en)', - value: 'en' - }, - { - text: 'Chinese (zh)', - value: 'zh' - } - ] - } - } - }; - } - words(args) { - const text = Scratch.Cast.toString(args.text); - const words = parse(text, args.language); - return words.join(' '); - } - } - - Scratch.extensions.register(new SortUniqueWords()); -})(Scratch); +(function (Scratch) { + "use strict"; + + const parseEnglish = (text) => { + const words = text.toLowerCase().match(/\b\w+\b/g); + const uniques = Array.from(new Set(words)); + uniques.sort(); + return uniques; + }; + + const parseChinese = (text) => { + const words = text.match(/[^\u4e00-\u9fa5]+|[\u4e00-\u9fa5]+/g); + const uniques = Array.from(new Set(words)); + uniques.sort(function (a, b) { + return a.localeCompare(b, "zh-Hans-CN", { sensitivity: "accent" }); + }); + return uniques; + }; + + const parse = (text, language) => { + if (language === "zh") { + return parseChinese(text); + } + return parseEnglish(text); + }; + + class SortUniqueWords { + getInfo() { + return { + id: "nonameawasortuniquewords", + name: "Sort Unique Words", + color1: "#5a8b9e", + color2: "#427081", + color3: "#427081", + blocks: [ + { + opcode: "words", + blockType: Scratch.BlockType.REPORTER, + disableMonitor: true, + text: "sort unique words in [text] as [language]", + arguments: { + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "movie dog restaurant book school", + }, + language: { + type: Scratch.ArgumentType.STRING, + menu: "language", + }, + }, + }, + ], + menus: { + language: { + acceptReporters: true, + items: [ + { + text: "English (en)", + value: "en", + }, + { + text: "Chinese (zh)", + value: "zh", + }, + ], + }, + }, + }; + } + words(args) { + const text = Scratch.Cast.toString(args.text); + const words = parse(text, args.language); + return words.join(" "); + } + } + + Scratch.extensions.register(new SortUniqueWords()); +})(Scratch); diff --git a/extensions/NexusKitten/controlcontrols.js b/extensions/NexusKitten/controlcontrols.js new file mode 100644 index 0000000000..3a4910012e --- /dev/null +++ b/extensions/NexusKitten/controlcontrols.js @@ -0,0 +1,165 @@ +// Name: Control Controls +// ID: nkcontrols +// Description: Show and hide the project's controls. +// By: NamelessCat + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Control Controls must run unsandboxed"); + } + + var fullScreen; + var greenFlag; + var pauseButton; + var stopButton; + + const getButtons = () => { + fullScreen = undefined; + greenFlag = undefined; + pauseButton = undefined; + stopButton = undefined; + + const rightButtons = document.querySelectorAll( + '[class*="stage-header_stage-button_"]' + ); + fullScreen = rightButtons[rightButtons.length - 1]; + if (!fullScreen) { + fullScreen = + document.querySelector(".fullscreen-button") || + document.querySelector(".standalone-fullscreen-button"); + } + + greenFlag = + document.querySelector('[class*="green-flag_green-flag_"]') || + document.querySelector(".green-flag-button"); + pauseButton = + document.querySelector(".pause-btn") || + document.querySelector(".pause-button"); + stopButton = + document.querySelector('[class*="stop-all_stop-all_"]') || + document.querySelector(".stop-all-button"); + }; + + class controlcontrols { + getInfo() { + return { + id: "nkcontrols", + name: "Control Controls", + color1: "#ffab19", + color2: "#ec9c13", + color3: "#b87d17", + blocks: [ + { + opcode: "showOption", + blockType: Scratch.BlockType.COMMAND, + text: "show [OPTION]", + arguments: { + OPTION: { + type: Scratch.ArgumentType.STRING, + menu: "OPTION", + }, + }, + }, + { + opcode: "hideOption", + blockType: Scratch.BlockType.COMMAND, + text: "hide [OPTION]", + arguments: { + OPTION: { + type: Scratch.ArgumentType.STRING, + menu: "OPTION", + }, + }, + }, + "---", + { + opcode: "optionShown", + blockType: Scratch.BlockType.BOOLEAN, + text: "[OPTION] shown?", + arguments: { + OPTION: { + type: Scratch.ArgumentType.STRING, + menu: "OPTION", + }, + }, + }, + "---", + { + opcode: "optionExists", + blockType: Scratch.BlockType.BOOLEAN, + text: "[OPTION] exists?", + arguments: { + OPTION: { + type: Scratch.ArgumentType.STRING, + menu: "OPTION", + }, + }, + }, + ], + menus: { + OPTION: { + acceptReporters: true, + items: ["green flag", "pause", "stop", "fullscreen"], + }, + }, + }; + } + + showOption(args) { + getButtons(); + if (args.OPTION === "green flag" && greenFlag) { + greenFlag.style.display = "block"; + } else if (args.OPTION === "pause" && pauseButton) { + pauseButton.style.display = "block"; + } else if (args.OPTION === "stop" && stopButton) { + stopButton.style.display = "block"; + } else if (args.OPTION === "fullscreen" && fullScreen) { + fullScreen.style.display = "block"; + } + } + + hideOption(args) { + getButtons(); + if (args.OPTION === "green flag" && greenFlag) { + greenFlag.style.display = "none"; + } else if (args.OPTION === "pause" && pauseButton) { + pauseButton.style.display = "none"; + } else if (args.OPTION === "stop" && stopButton) { + stopButton.style.display = "none"; + } else if (args.OPTION === "fullscreen" && fullScreen) { + fullScreen.style.display = "none"; + } + } + + optionShown(args) { + getButtons(); + if (args.OPTION === "green flag" && greenFlag) { + return greenFlag.style.display !== "none"; + } else if (args.OPTION === "pause" && pauseButton) { + return pauseButton.style.display !== "none"; + } else if (args.OPTION === "stop" && stopButton) { + return stopButton.style.display !== "none"; + } else if (args.OPTION === "fullscreen" && fullScreen) { + return fullScreen.style.display !== "none"; + } + return false; + } + + optionExists(args) { + getButtons(); + if (args.OPTION === "green flag" && greenFlag) { + return true; + } else if (args.OPTION === "pause" && pauseButton) { + return true; + } else if (args.OPTION === "stop" && stopButton) { + return true; + } else if (args.OPTION === "fullscreen" && fullScreen) { + return true; + } + return false; + } + } + Scratch.extensions.register(new controlcontrols()); +})(Scratch); diff --git a/extensions/NexusKitten/moremotion.js b/extensions/NexusKitten/moremotion.js new file mode 100644 index 0000000000..d3aee71d53 --- /dev/null +++ b/extensions/NexusKitten/moremotion.js @@ -0,0 +1,392 @@ +// Name: More Motion +// ID: nkmoremotion +// Description: More motion-related blocks. +// By: NamelessCat + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("More Motion must run unsandboxed"); + } + + // @ts-expect-error - not typed yet + const Rectangle = Scratch.vm.renderer.exports.Rectangle; + + class nkmoremotion { + getInfo() { + return { + id: "nkmoremotion", + name: "More Motion", + color1: "#4c97ff", + color2: "#3373cc", + blocks: [ + { + filter: [Scratch.TargetType.STAGE], + blockType: Scratch.BlockType.LABEL, + text: "Stage selected: no motion blocks", + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "changexy", + blockType: Scratch.BlockType.COMMAND, + text: "change x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "pointto", + blockType: Scratch.BlockType.COMMAND, + text: "point towards x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "rotationStyle", + blockType: Scratch.BlockType.REPORTER, + text: "rotation style", + disableMonitor: true, + }, + "---", + { + filter: [Scratch.TargetType.SPRITE], + opcode: "fence", + blockType: Scratch.BlockType.COMMAND, + text: "manually fence", + }, + "---", + { + filter: [Scratch.TargetType.SPRITE], + opcode: "steptowards", + blockType: Scratch.BlockType.COMMAND, + text: "move [STEPS] steps towards x: [X] y: [Y]", + arguments: { + STEPS: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "tweentowards", + blockType: Scratch.BlockType.COMMAND, + text: "move [PERCENT]% of the way to x: [X] y: [Y]", + arguments: { + PERCENT: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "10", + }, + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + "---", + { + filter: [Scratch.TargetType.SPRITE], + opcode: "directionto", + blockType: Scratch.BlockType.REPORTER, + text: "direction to x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "distanceto", + blockType: Scratch.BlockType.REPORTER, + text: "distance from x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "spritewh", + blockType: Scratch.BlockType.REPORTER, + text: "sprite [WHAT]", + disableMonitor: true, + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT", + }, + }, + }, + "---", + { + filter: [Scratch.TargetType.SPRITE], + opcode: "touchingxy", + blockType: Scratch.BlockType.BOOLEAN, + text: "touching x: [X] y: [Y]?", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + }, + }, + { + filter: [Scratch.TargetType.SPRITE], + opcode: "touchingrect", + blockType: Scratch.BlockType.BOOLEAN, + text: "touching rectangle x1: [X1] y1: [Y1] x2: [X2] y2: [Y2]?", + arguments: { + X1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + Y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "-100", + }, + X2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + Y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + }, + ], + menus: { + WHAT: { + acceptreporters: true, + items: ["width", "height", "costume width", "costume height"], + }, + }, + }; + } + + changexy(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + util.target.setXY(util.target.x + x, util.target.y + y); + } + + // LORAX APPROVED + pointto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + if (util.target.y > y) { + util.target.setDirection( + (180 / Math.PI) * + Math.atan((x - util.target.x) / (y - util.target.y)) + + 180 + ); + } else { + util.target.setDirection( + (180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y)) + ); + } + } + + rotationStyle(args, util) { + return util.target.rotationStyle; + } + + fence(args, util) { + const newpos = Scratch.vm.renderer.getFencedPositionOfDrawable( + util.target.drawableID, + [util.target.x, util.target.y] + ); + util.target.setXY(newpos[0], newpos[1]); + } + + directionto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + if (util.target.y > y) { + return ( + (180 / Math.PI) * + Math.atan((x - util.target.x) / (y - util.target.y)) + + 180 + ); + } else { + return ( + (180 / Math.PI) * Math.atan((x - util.target.x) / (y - util.target.y)) + ); + } + } + + distanceto(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + // Shoutout to Pythagoras! + return Math.sqrt((x - util.target.x) ** 2 + (y - util.target.y) ** 2); + } + + steptowards(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const steps = Scratch.Cast.toNumber(args.STEPS); + const val = + steps / Math.sqrt((x - util.target.x) ** 2 + (y - util.target.y) ** 2); + if (val >= 1) { + util.target.setXY(x, y); + } else { + util.target.setXY( + (x - util.target.x) * val + util.target.x, + (y - util.target.y) * val + util.target.y + ); + } + } + + tweentowards(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const val = Scratch.Cast.toNumber(args.PERCENT); + // Essentially a smooth glide script. + util.target.setXY( + (x - util.target.x) * (val / 100) + util.target.x, + (y - util.target.y) * (val / 100) + util.target.y + ); + } + + touchingrect(args, util) { + let left = Scratch.Cast.toNumber(args.X1); + let right = Scratch.Cast.toNumber(args.X2); + let bottom = Scratch.Cast.toNumber(args.Y1); + let top = Scratch.Cast.toNumber(args.Y2); + + // Fix argument order if they got it backwards + if (left > right) { + let temp = left; + left = right; + right = temp; + } + if (bottom > top) { + let temp = bottom; + bottom = top; + bottom = temp; + } + + const drawable = + Scratch.vm.renderer._allDrawables[util.target.drawableID]; + if (!drawable) { + return false; + } + + // See renderer.isTouchingDrawables + + const drawableBounds = drawable.getFastBounds(); + drawableBounds.snapToInt(); + + const containsBounds = new Rectangle(); + containsBounds.initFromBounds(left, right, bottom, top); + containsBounds.snapToInt(); + + if (!containsBounds.intersects(drawableBounds)) { + return false; + } + + drawable.updateCPURenderAttributes(); + + const intersectingBounds = Rectangle.intersect( + drawableBounds, + containsBounds + ); + for (let x = intersectingBounds.left; x < intersectingBounds.right; x++) { + for ( + let y = intersectingBounds.bottom; + y < intersectingBounds.top; + y++ + ) { + // technically should be a twgl vec3, but does not actually need to be + if (drawable.isTouching([x, y])) { + return true; + } + } + } + return false; + } + + touchingxy(args, util) { + const x = Scratch.Cast.toNumber(args.X); + const y = Scratch.Cast.toNumber(args.Y); + const drawable = + Scratch.vm.renderer._allDrawables[util.target.drawableID]; + if (!drawable) { + return false; + } + // Position should technically be a twgl vec3, but it doesn't actually need to be + drawable.updateCPURenderAttributes(); + return drawable.isTouching([x, y]); + } + + spritewh(args, util) { + if (args.WHAT === "width" || args.WHAT === "height") { + const bounds = Scratch.vm.renderer.getBounds(util.target.drawableID); + if (args.WHAT === "width") { + return Math.ceil(bounds.width); + } else { + return Math.ceil(bounds.height); + } + } else if ( + args.WHAT === "costume width" || + args.WHAT === "costume height" + ) { + const costume = util.target.sprite.costumes[util.target.currentCostume]; + if (args.WHAT === "costume width") { + return Math.ceil(costume.size[0]); + } else { + return Math.ceil(costume.size[1]); + } + } + } + } + + Scratch.extensions.register(new nkmoremotion()); +})(Scratch); diff --git a/extensions/NexusKitten/sgrab.js b/extensions/NexusKitten/sgrab.js index 6443180b21..6066bea0d2 100644 --- a/extensions/NexusKitten/sgrab.js +++ b/extensions/NexusKitten/sgrab.js @@ -1,249 +1,269 @@ -(function(Scratch) { - 'use strict'; - - if (!Scratch.extensions.unsandboxed) { - throw new Error('This Extension must run unsandboxed'); - } - - const icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAswAAALMCAYAAADw0eQaAAAAAXNSR0IArs4c6QAAIABJREFUeF7svef7BTd1Lrp/HxLAGHAFjE1L+XRzcC85yUlybnpCekLvLaGlnfJHnJwkgOm4gOnpvd9eDqaT5H5KoRoMxjbFpiTPk30fSVszGu3RHmlGWlpaeveX3zO/mdFI71paWnq1tHS2ww8IAAEgAAQ2I/D13/r2/eZCChTwgCf9j7MCxaJIIAAEgEBXCMCQdiVuNBYIAIEYBLg6vzF1z/EMnOwcKKIMIAAEJCEAh1mSNNEWIAAETiLw9Xd/+353ttvt9/vd2dnZbr/f7c70tfm7Uxwx7kfjA8caHQ4IAIFeEIDD3Iuk0U4g0AECvTPD3EQMh5qbRFAfIAAE1iIAh3ktcngPCACBKggop3hghA81wPWBIW8Ijwc+GbHVVToQPgoEgMAqBOAwr4INLwEBIECBABhjCpT5fAOMNB9ZoCZAAAhMEYDDDI0AAkCgOgJfe/cNKopYBxGb2OJDMDGugcfZ2Q6OdPUuigoAge4RgMPcvQoAACBAiwBYY1q8pX4NTrRUyaJdQIAnAnCYecoFtQICYhAwMccqOcWZSUJhs1DgGnhk1IcHPvk9GM/EWA00BAjwQwAGhp9MUCMg0CwCYI+bFZ3IioOFFilWNAoIVEEADnMV2PFRICADATf22MYg46+T0NkmdsZfk+C6Mg7IzCHD7qAVQKAGAnCYa6CObwKBRhEYGGS7J8+2A9fGHwQeBoFG9AEMdKOGCNUGAhUQgMNcAXR8Egi0ggAY5DFbBweGtDZDK/37YKBbsUyoJxCgRwAOMz3m+CIQYI0A4pBZiweVI0IA7DMR0PgMEGgEATjMjQgK1QQCpRD42ru/fW+YQ/uzsaa4NggAD+jHfocsHKUsEMoFAm0gAIe5DTmhlkAgKwLKSTbp3WyaN/wFHtCH2P4A9jmrOUJhQKAJBOAwNyEmVBIIbEMAsciIRUYMdrksHYh93maf8DYQaAEBOMwtSAl1BAIrEDAHhoA5BnMM5piyH8B5XmGs8AoQaAABOMwNCAlVBAKxCBgmGT8gAAQ4IIC4Zw5SQB2AQB4E4DDnwRGlAIFqCJhNe/gBASDAGQEwz5ylg7oBgWUE4DAvY4QngABLBEY2ORSbaauN+/P5g4GPQQD6QakfYJ1ZmlNUCggsIgCHeREiPAAE+CCgs1scDlLz3T1cT90/4AE83OkAR30A68zHtqImQGAJATjMSwjhPhCojIBmkr2jhu0mpuH/uD85mhr4mM2e0A+PQD/0ZY768cCnvAfjcWVbi88DgVMIoINCP4AAQwQQl8xQKKgSECBCAMwzEdD4DBBIQAAOcwJYeBQIlEZgiEv2GGOfYcb1gTm0AgFeE4Yd+iFDPxDvXNrionwgEI8AHOZ4rPAkECiCANjkIrCiUCAgCgGwzqLEicY0iAAc5gaFhirLQECxyZQHKuAADxzgAX1r/yCfByHWWcYAgFY0hwAc5uZEhgq3jMDIJiOGADEErvmFPkAf0vUBrHPLowHq3hoCcJhbkxjq2yQCX33XDXswvGB4wfC2z/By7MeIdW5yWEClG0MADnNjAkN120IA8cltyQu1BQItIwDGuWXpoe7cEYDDzF1CqF9zCCg2ublKo8JAAAiIQgCxzqLEicYwQAAOMwMhoAoyEDBsso1Fxd/wkcuho5jxf+gP+k3ufoNwDRnjC1pRHwE4zPVlgBo0joDPKPtHV/vNw33jFoZ+wAf4QD/y9w8wzo0PNKh+dQTgMFcXASrQKgLzjLJtTYgpw32DAPCZZxKhH9CPsv0Dcc6tjjiod20E4DDXlgC+3xwCiFFuTmSoMBAAAh4CYJyhEkAgDQE4zGl44elOETBp4c6GvGjqxBFcA4/dIU8c9AH9oWV7ANa504ENzU5CAA5zElx4uDcEwCb3JnG0Fwj0iwBY535lj5YvIwCHeRkjPNEpAjpGGQyiPmkCDCoY1JYZVOhvvP4+EEdvdzriodlLCMBhXkII97tDAKxydyJHg4EAEPAQANsMlQACUwTgMEMjgMABga+96wbFJ+/Odme7vZP47Dg7MO4DnzHxGfTDz3mC/iGpf8BxxhAJBAwCcJihCd0jAEa5exUAAEAACCwgAMcZKtI7AnCYe9eAjtv/tXd9+14xQZZRxl/DDAIH4AA9QD8I2QE4zh0Pmp03HQ5z5wrQY/PBKPcodbQZCACBnAjAcc6JJspqAQE4zC1ICXXMggAc5SwwohAgAASAwIAAHGcoQy8IwGHuRdIdtxOOcsfCR9OBABAgQQCOMwnM+EhFBOAwVwQfny6LwOgo2zwG9nu4NnkNgIdBAPoAfUB/yGUP4DiXHddQej0E4DDXwx5fLojAV96pjrIeTrK2548M+a8O55EM/7fX2o+ceQ/39fklwA/6gf4B+3BkN337CKe54OCGoqshAIe5GvT4cAkEEH5RAlWUCQSAABBIRwCOczpmeIMvAnCY+coGNUtAwDLKPkOM6yljDjyAh7uCAn2APlDoAxznhMEMj7JFAA4zW9GgYjEIgFGOQQnPAAEgAATqIwDHub4MUIP1CMBhXo8d3qyMwFffaY+yVqG19qABu4UL1+bgAeBhQq+hD9AH9AcO9uCcp74HfkflsROfX4cAFHcdbnirIgJglSuCj08DASAABDIgALY5A4goghQBOMykcONjWxCYMspjMjAneYPDqOI+cBmSekAvnOR50Avoxcg017eTYJy3jIp4lxIBOMyUaONbqxEAq7waOrwIBIAAEGCNANhm1uJB5Q4IwGGGKrBG4CvvvH5fnwMBJwdOjhMnB32EPsrUR7DNrIfj7isHh7l7FeALgGaVZY4LGO8hV8wD4ffDDszYgQdhUyDfQbnzmsFh7lwBODZf5VQeRxJbQ2tZcW0QAB7G44Q+QB/QHyTag3Oeejv8E44DdMd1gkJ2LHyOTVcb+7QfBAYSDCT0AP0AdqBrO4DYZo6jdL91gsPcr+xZtRyxyvAMuvYMMENEHg8wBUGmBLHNrIbrbisDh7lb0fNouD3Seu9u7Tvb7XDtuI/AA/qA/jG6k+gP3fYHxDfzGLd7rQUc5l4lz6Dd01hlMKxgWBGDgRgM2AHYgdN2ALHNDAbvTqsAh7lTwddstsp+McsgW+YoxCDhvmGWgM88wwb9gH6gf3RjHxCmUXMU7/PbcJj7lHu1Vk9jlW01LKOAa4MA8DBMK/QB+oD+AHsQtodgm6sN5V1+GA5zl2Kv02gTgoEfEAACQAAIAIE8CIBpzoMjSllGAA7zMkZ4YiMCiFVGbC5icxGbi9hc2IGSdgBs88aBGq8vIgCHeREiPLAFAZ0F45AsCAvsWGDHAjsW2GEPEHBUKuAKbPOW0RrvLiEAh3kJIdxfhcBX3nE9DiABoVSSUAJhCf2CfmHhYtYOgG1eNWzjpQUE4DBDRbIjgFjl7JCiQCAABIAAEEhAAGxzAlh4NAoBOMxRMOGhGAR0BgzNeOBkgW5PFoD8of/o/zh5idHJU+c87Xb4OTEDOJ5ZRACKtAgRHohBAKxyDEp4BggAASAABKgRANtMjbjM78FhlilX0lapeGWE0iGkFiG1CKmFHYAd4GoHHgymmdQvkPgxOMwSpUrUJrDKREDjM0AACAABIJAFAbDNWWDsshA4zF2KfXuj73/H9UO6OIQsImSRUcgiQuixhQBbCNQR4Yd0nrDPx/YZWTS2+wA9lgCHuUepb2wzmOWNAOJ1IAAEgAAQqIoAmOaq8Df5cTjMTYqtXqVdZtnWAgcR4CCCUgcRKB2DfkG/oF+jzUd/yNcfENdcz5do8ctwmFuUWoU6m4NIHHXRa/C4HkQBPKAP6A+jZUJ/QH9oqD+Aba7gVDT4STjMDQqNusqhLBg+w+zvjsZ9g0Bo1zjwAT7QD/QP2If57DLU9hFsM7Vn0d734DC3JzPSGg/xyjZflP06rs1IDzwMAtAH6AP6A+xB4/YQTDOpe9Hcx+AwNycymgqrWGWaL+ErQAAIAAEgAAT4IAC2mY8sONUEDjMnaTCpi2KV9/v97swGE5yd7XANPKAPh8Vz9AfYA9hH8eMDnGYmDgmjasBhZiQMDlUBs8xBCqgDEAACQAAI1EYATnNtCfD6PhxmXvKoWhsdr3yIRdWM8pDx/sAw62uziw33gQ/0w54Qgv4x2gPYB9hHWePDOThSu6pfwunjcJg5SaNSXcAqVwIenwUCQAAIAIEmEADb3ISYilYSDnNRePkXbg4iOdspahl/gQP0AP0AdgB2AHZg3g6c87T3wGfi79YUqyGEXwxa/gWDWeYvI9QQCAABIAAE+CAAppmPLKhrAoeZGnEm3wOzDCYRTCKYRDCJsAOwA+l2AEwzE0eGuBpwmIkB5/A5zSzbgybwN3zUWOgILvx//mgu4AJcYE9gTzqxAw9++u3wnzg4NIR1gMAJwebwKROGYS1aqEa4bzwf4DOPAPQD+oH+AfsA+4jwDA5eDV0d4DDTYV31S/e//Xq19jj+QkyQfQL355ki4GMQgH5AP+aYRPQP9I8O7QMc56ruDdnH4TCTQV3vQ/e/44Y91oqxVoy14k7WiocVJLQXdg92j8ruwWmu5+NQfRkOMxXSlb4zMMue3dwfAjN0tWbGVdw3sAAf6Af6x/GKAuwD7APs4/H4AKe5kqND9Fk4zERA1/gM0sbVQB3fBAJAAAgAgV4RgNMsV/JwmIXKNsQsIzvGPGMKXIALIhlOxKZjZZ9qZR/fERBJBKdZpmMFh1mgXMeYZT+mANfTGAvgATzcmCToA/QB+nCcRcnPioPraZaceTzgNMtzruAwC5PpfW9XR11jqwsIMRBV6AewA7ADsAM17QByNctysOAwC5LnJGbZjhS2fbh2duk4MwrgYxCAfkA/5tJOon+gf8A+bLKPYJrlOFlwmIXIEswymJSaTAqYPOgf9A+MPuzAvB0A0yzD0YLDLECOs9kwfMbQbyfuTxlF4DNFAPoB/Tg1OkA/oB/Qj7D3MNM/wDS372zBYW5chopZxpoh1gw3rRkiJgMxKYjJOaSJQQwKxpNy48m5T78dPlfDPheE16jwkDYOadCQBg27W5EOEXYAdqA9OwC2uU3HCw5zg3JTzvKBVp6tvZ/kxn8I96dJgYDPFIFT+vHgp71nVY+5/x03DO9B/6B/sF/hboT+0Uf/ANu8aiip+hIc5qrwp38czDIYpRKM0jlPXecIp2vw8htfeecN2D2F3VPYRYldlOLtAJjm5fGA0xNwmDlJY6EuJl4ZIylG0vUj6VqGuHY3uf8d12OmVGKmBHsCvYJeVY3pOPfp74UfVnuAifw+BBUJVO3HkDYObnKqm9yqcxzb177yjhvg7sHdg7tX1d2DXU61y3PPI+1crNWv+xwc5rr4R319yizbV3DSBE6amJ408eCn3R6lT1IfcuOkkTUE9gH2ASfRjLaOf38A08x/ZILDzFxGYJbBYIQYDOkM8pauqZznHMwPAqDQ/6BHfAMBz0nYhNzCihSY5i1Wv/y7cJjLY7z6CxNmWeXFOHNMN667xANOcnp30swz+kuX/WVw9SB/1vLnaNfMitVhykyoP2Ca02081RtwmKmQTvzOeCDJyPDYIvy0Q7iepiGSiEcKk5Koal097jLP6E8GAYn9xU1bh/bxsY8cHeMUA0hlP8A0p0iF7lk4zHRYR38J2TCwCKqGuN5jkqM7zMoHJwwStg9i+yCCeLJvH2zdQV4yLSWz94BpXkKf/j4cZnrMT34RMct9x0yCSabvkC3ENiKWum+70Ir8Yb92u5z2BEwz/Xhw6otwmBnJ4763X7c3RNfZIebSTvhxbWJQ8+PBgcVVTKd0JoZRNwtWRbNF6H+wP7C/0eMPB/vJ2bbc//brN/enc5+BPM1cZAyHmYskdrudG7fMqFpNVgUOaJNiY1HpaXo6FlVCJYAAGwRgW9eJYq1dwRHa6/Au8RYc5hKorijzvrddtx+ZZcso4++Q3cDuUvb+wnivUDa8EoXAJLtGQP+W9BP3D9l9gJ/DNLZn12Fno0xG1ENr7Aqc5ihoiz8Eh7k4xMsfALO8jJF6AkY7Dic8lReBtcxQ3lqgNIkI1LBpsfpco24SZXyqTbGyUGXAaa6vHXCYK8vgy2+7bo+cEMeJ8R/89L5Prauslvj8DAIqHrGVzVeoZ71NgufCdsF+rEAgxr4gnnkFsBlfgcOcEczUosAsTxEDo5GqQXi+BgIprFCN+uGb5RGArSqPca9fWLIvYJrraQYc5krYW2bZfP5sp9JjjMLo4xpMTCXlw2ezIKB3wHfcf3uwV1jpytJVUMgKBE7Zl3OfcTt8txWYbn0FoG9FcMX7PTPLYGZWKAxeYY3AEiPEuvKo3IAAbBOUgSMCIfsCppleWnCYiTF3Y5btpyUf3QoWmVjB8LlqCNx3YJwl92ezHsbnqOUt9YFtqtZV8OEVCMzZl4cgR/MKJNe/Aod5PXbJb2pmuYPdOA9++nuSscELQEACAve//YZ6u82we/h497Bnb8EiS+hlfbfBpKUbZ64Iz6DTBzjMRFjPZcPwGWZ/vGvtPhgbImXCZ9gjYNkglwFtvX+3WH/YJPZdBRVciYDLOCN7xkoQE1+Dw5wI2JrHJTPLYGzWaATe6QEBnwkaj3a3R7zjb+4VN9ijHnoW2ugiYO0MmObyegGHuTDGilku/IkqxYO5qQI7PtogAi7b3GD12VcZtoi9iFDBwghYG4OY5rJAw2Eui+/uvrddf3CYbdCR/WCb14hPLqwwKF4kAiZFlGtu2+z/o3Dq1R82SGQXQaMyIHB2dgafLgOOoSIAbkFwpbDLYHAKKgmK7gYBMM3bRA07tA0/vN0PAnCcy8gaDnMZXB1mud08TGByCikHiu0WgUkWDYuClDxtBdoDG9RtV0HDNyIAp3kjgDOvw2HOj+lOM8v+iqX/Hcb3weQUUAoUCQQcBO572/XTCI2G7IOuakH7BfuDrgIE8iAApzkPji4XkLfEzksz6ePs0dbt/AWT07niovnkCCi2Wc2sW7QXJeoNR5lcBfHBThCA45xH0GCY8+CoS2kxZhmDVEYFQFFAIBGB3uOaYX8SFQaPA4GVCMBpXgmc8xoc5u0YDs5yS0wRGOVMgkcxQGAjAj0yzbA/G5UGrwOBFQjAaV4BGhzmbaDNvf3lt16n1lYXj2bNnah/TXnnPuP2/ACgRCAABDYhoNlm7yhnaddglDepCF4GAlkQgOO8DkYwzOtwm7z1ZZ1rmf9Ih8Eqg7BRBBAoiIAJ0Whk5p1Qz3Of/p6CqKFoIAAEUhGA05yK2Ol92umldfhGC8wyGOUOFRNNbhYBSUwzJunNqiEq3gECcJrThAyGOQ2vWWbZxi5bZojLNQarDcLFq0CgIgIu08zFnqTYN8QoV1QefBoIJCIAxzkOMDjMcTgdPfWlt6r0ceFfwTSl+qNL5YNVXilYvAYEmCCgczUHfkv9v9Z92B0myoNqAIFEBOA0LwMGh3kZo9knuKaQA6u8UqB4DQgwRKCVtHOwOwyVB1UCAokIwGk+DRgc5kSFUo/ruGWGP7A7DIWCKgGBjQicYpo3Fr35ddiczRCiACDACgE4zWFxwGFOVFXDLPPaxQ52J1GIeBwINIYAt+wZyHrRmAKhukAgAQE4zfNgwWFOUCLFLHNLHgeGJ0GAeBQINIyAYppr2x/Ym4YVCFUHAgkIwGk+BgsOc4ICaXa59oh1+D5Y5QTB4VEgIACBmunmYG8EKBCaAAQSEYDTPAUMDnOkAtmsGBz85YfgpL5IqeExICALgS+/7XrSgDAwyrL0B60BAqkIwGkeEYPDHKE9+iQ/FYwxoHW2G661B+1c6/LK3QfTEyEwPAIEBCNw39tvKG5/EKMsWIHQNCCQiACcZgMYHOYFxVHMcqJuFXsczHIxaFEwEGgKAcU0l/rBzpRCFuUCgXYRgNMMh/mk9gbTx9kkGaG3M9/Hsmi7RgY1BwKlEAimm1tpf2BnSkkK5QIBGQj07jSDYT6hxyF2eeV4NHwp5X2wPTIMDVoBBEogMMc0p9gXWyfYmRLSQZlAQB4CPTvNcJgD+jxhl/0RiOgajI88Y4MWAYGcCGiWeYM9go3JKQ2UBQT6QKBXpxkO84x+G2bZ5sOwD9Beg/Hpw/CglUBgKwKGZU63Tw95xnu3fhrvAwEg0CkCPTrNcJg9ZbfMcvrwMw0I3/I+WJ9OLRCaDQRWImDTzcVM72FfVoKM14AAEBgQgMMMZdjVzooBZhlKCASAwBoEYjJnwL6sQRbvAAEgMIdAb04zGGZHC9y45S0MsSpyzfsYzGCUgAAQWIuAmzXDtz9gldeiiveAABA4hUBPTjMc5oMmfOm2a/fjAST24BG6v3CWYZRaQOBf/+b5upr7veouo/lYuv6G77u5heY1X8cvv/U65yClsx3sSvMiRQOAAHsEenGa4TBbh9k5oGTDpnNdWur7GNTY24PuKmgdY4qGw5nOh7J7dDbsSj5cURIQAAKnEejBaYbDvNuZuGV99LVilK3Ha6/9v3nvY1CDGaqNgHKOB4b40A9qXn/D999SG5Kmv6+cZtiVpkWIygOB5hCAw9ycyNIrrJxlywhT/8Wgli4vvLEdAUr2eGttwT5vRRDvAwEgAARoEJDuNHfPME9il20whWaaB6p5jAnMeB/OMk0HxldGBP7lr5+nL1JDhrg8/41gnqHOQAAIAAHWCEh2mrt2mGulkIOzzLq/i6rcvyon2U4ABf0F8yxKTdEYILAKgTWrZbAdq6COfgkOczRU7TyomeWMjHEsI/2QZ97eDkioaZMIWCa5yconVhqscyJgeBwINILA1Bn2EiXaPUdDW7bf/4bvw96JXKoh1WnukmEGs5yrW6AcTgj86988z9m06sReUAfnV/geBjtOmoi6AIE0BNYwxWlfSH8aTHQ6Zu4bEp3mPh3m267b73f7IZbzeHw/2+W+/5Bnvneb9uFtIBBAwDDKTsy90uyBgfH+b58Teh+MM7oJEOCPgLsKxmWPhEVtrj7I3JOuU3CY0zFj98bALgf8iKMVHv85+0DC+wjDYKcGIiqk4pNDatj7/zHAiVBxNEIQAoZFPlgmf8Le0DVWs+KVUprT3BXD/KXbrtvbDmsY5JFbLnkNdjm+g9V4UrMd2mBbe24Z2rLX3/gDt65u7r/81XPJ60uNz9bvbcF3tWDwIhAAAhME7MT+FIOr3OgW72NFa1nZJTnNfTnMzml+AzXnM8aZr5ERY7lDUT3hM7K+gfZDc2rdP2WElXMfWsLkUn9u9cOgRtXD8B0gYBAw2Xl8wsEJFZslKNq9j3jnsObDYW7QKnzxtmvJDyh5KOKWq2iKZl/xAwIOAmCboQ5AoDwCPWXomUMTk/N5HZPiNHfDMOtwDMLd+2CWyxtn+4VaIRVbQwbwftmQlzl8v/H714fB0Gk0vtQLAu6KEdXwVMKpq9EOKrzWfAd7KI57sASnuQuHWTnL06wXfhaMvNdglssPd2CRy2Ms9Qtgm6VKll+7/uWv1WrXjMvFNEtNqjPdWvuCmw4LZRlKxZOfBuerERzmfFgWKwnMcjFoSQuebnI7zqKGdBF95V3eKm8wzaTdV/zHemBYfecPWXri0t7DaR67f+tOs3iGWcUuU1prsMv50P66jkU22UxsFhP3r2VucB/4rNWPB2zIVJJP01FSSwhgdaslafGpK1a2jCxadppFO8wmjVzo5x2lefRY+n3kW95unDAYbccQJaQhgIEsDa/enh73SDihFQ3lDdbEAuqrPLXqeaDBNsNhZmk/wSyzFMtspf7lr56j7bkN9VN2DdfAg1IfvvEH3txOh0FNiyKASXtReFH4brfreZIOhplhFzjNLuevMNjldEwxMKVjhjfKIdDzIFYO1TZKxh4J7IGgzKKl9mD0bG9adZpFhmRodjnh6OpJR7H2PeH9hz7rvW2MCkxq+fW/fA6TmqAaQGCKwAN+EExzLzqhnOS52PfQngj8f36vBHBZj0uvTjMcZiZW9ou3XXc4oMRuFrMr/WWuwSzHCV45yTrUwhIZ6iRqXAMPhvrwAIRnxHXqBp/CqlaDQhNeZTjN7QhYHMM8xC77e/YKXSMrxmllB5vcjjFATUcEwDTL0YaRSbYT9rPdfr93JuwzefhxH/gMhE55/ejRaW6RZRblMJsDSmbTxG9N2zr7/kOfebucUSVzSwZG2TLJ+GsGIODQDA5wmjMbBcLiwCQTgo1PZUOgN8e5NadZlMP8xbccci7b7DFWjQtcg1metxFf/8tnO1OWggLQUxiUbxAooODAd/eAH3xLtoEQBdEggBUtGpzxlXII9DRRh8NcTo9OlmxCMdac+r6Okwa7PBUHBqpKio/PFkWgp8GrKJCFC8ceCewJkbQnppc0l3CYCxvGueKDWTF8AtL3p1feR1YM31l+9ribz94qFDM+fBnlm/kh8DYIFNQHMM0VjHrEJzFJjwAJjzSLQC+T9ZacZhEhGSZ2eWSKbZob/+jkHNdglkf7YwYsn6H3PTjcn658AJ+ph9uGfvQyeLXgXdiDjrAnAHsiJO8J6cXmwGEmtLpD3DLRN8EuG6C/9hfPJgyAOXbL1wXSoBzgti5w6wE/hHhmIhMb/Mz8BB0avU6jgVsLuMFprm11pt9vnmGmjF3GRr/dzmzqm1kC9/XaXyLH/SkCwGcaUtKAfiA0o87ghdCLOrjjqzwQ6MFpboVlbtphBrtM26G//hfPLpKej26rJhhm8ErbeCUwzXQ2Z5icz30SE87mJpwTMUJ+SfLrYbLegtPctsN823V7Kheo99hl5SwjBmMmZBseaFd60cPARecSz39pGnrhx/zjen6Jr+Cu19klRXyPete1dNsDh7mg5f3CW649HIFNM173HLv8tb94VkFJomgg0BYCD/yh29qqcCO1xQoWFf2D77TKc0he4YLDXMhQB9PIFVrb79VZnhvAfH6njRwH4axjqD9yeLhmI1a/JQ9chcx2sFgdegHCkpqwxPcaTMsJlpnaOk2/12RIhopdppwhPuxjclT2AAAgAElEQVRZ760rpQpf/9qfPyspxuqoiohRA36nrEvj+gGWOY9RQrYdmhVSyvGyEG+FiMBDROADBWfs4c4yN+cw641+QwJOG1N6FkhIuf1+j5kxDLO839l81vh7BjygD0f9AU7zeqcZeyKcE+3hYSJGJHFGI5lp5uw0N+cwu7HL1lxbe1Piujd2WTPL+AEBILCIwAN/GLHMiyDNPIA9EWtQwztAYERA8mQdDnMmTUcauUxABorBQFYWX5QuDwHJA1duaWlW+fBDyDJCiBsMIWalv1JDM+AwZ7K8fuyyzyj7K1tb7/fELh8zy40HmR7tIvKVEO1DkPX2IGuwzMvGfXYiju6H7re9+4WVrxP9kjhhh8O8bFMXn5iyyyWDMFRVznYPfdbti3WS8oAe0BJjqPA8YhCxC8fskYDTHLaEyrZgDwT2QGBPTJk9QVKz9XB1mpuJYf7Cm9VmP8cwF17T64tdfib8X/i/8H9XnkvzoB9+q5S5c7Z2YC9ENihREBA4iYDECTsc5g1K74ZiUMTA9ZR3+Wt//kzPVfQRRqZiZCpek6k4tGQhT78eCId5Yt0nK1YUBlt9ozCBgvIPwwTkaRBgpG8Iy9jgXCa+2gTDrDJjHLWrYIxSL+yycpYRiYE8qMhqtT0iCU6zsdDHE3BYGFgYWJjtFuZ0P5LGMoNhTvTk7eM2dplqQtcLu4yBDQM5BvJ8A7m0ASvVXA8HHaFboVvl61al/Uwx5UtjmeEwp1rgw/ND7LI6208fWGIjCMpc98Auf1WFYbicffk9lPge8B4tgEB96zmOGStVYvwurDg2vJdFmg3i6DSzDsnAEdgrZxkLrxl2GT8gAARyItBjWAZWqkCpg1LnQalLsz9wmBNHJx27TGiPHvbs9ybWsL3Hv/pnz2h4Ds3DMGGAgBzmOMUH/cjb2jMIG2p8kln2FwB9q4P70wVT4DPN0gP9SNYPMMwbjFnkq2wZZvcIbKrhWXo4xoRZpgoKx65qdruque3yllKfB/5IH+nlTMo4xBiJjjGCfB0Xqp0YMkksMxjmSC9exy1TUsu7s510dtkwy/gBASBQEgHpLPNX/8yGc/WTNhBpJZFWMpxHjlfa1QcJm7Rzc5pZMsyzaeRKjnK73U46u+yGYtiTt+xAgGtzEhfwMAMj9GG9PkgbsFyz665Q2T3YwwLSYQkd14cFJeChQwqgD7T6IIlhVsjBYV5wfA27rH60DIZkhhnscuHZFooHAgcEpDLM2PtAuJkGZ26uPHOTKniT93ck2SA4zEsO89whJYWHY8ns8nSgM0CODKIFdhqjhfuWYQQ+VmPcmFXoR1g/JA1WVvv15j67CQt/zWYs4AAcmOqBpFUuOMwnnF83dlktkauB2TLNJa8f9uz3FXbJ6xX/1T99xiHSwNnlpzu6xdfJa63xNte4D3xM/4N+GDMU338e9KNysmVgdaqe7caXgcBaBCRN3Dk5zaximKnTyFl/XGo4Bga7teYG7wGB9QhIGazMBj+EIiCNJO8QBMjnWD5gmdfb71Nv8nKYkR0jq5Q1u3wY8Eoy9MpgoXy6FRHgzVvfHvSjb8/aj6kL0xNtP+2kXwncN35a6Ad8gE9F/ZAyadcLnGfu9lFqazj9HhuHeTwCm/ZcDanxy0MoBgz6PAIY0DCgFRrQWh6ssCpVd0DG14FADgRatkF++79423W78579Pha+KotKKIDuffM1+3FhwTJIoQXBfPelxi9/5U+fnqPfoQwgEETgnAUmtVcdXMKFq0qpSbZdKcLfMb2i3eSKv+PKDvSDt35I2kfxxbdcuzvvOe9n4auyqMSYSo52KBEbu+yEYiAGETGYOWP81hhiNzSoF31cgxOt9Zt+7WhFCieBTldggAfwcL2lBvShNRsUsn9ffMt1+tZ5z6nPMrNxmGvMWM8Tmh2jV2avpsMh/ds5WNOe9DIHXlQ69dU/fTq29tFGAgJv4F0823ZLNmjJ1n3hLdfuzmfAMld3mGfZZf/odh/NTPclMsxq8MMPCOREIOcmtl70MydmOWXplzWmnYQHU9yDQbKJXhaYWLRTCsOsbBYXlrm6w6xil0sOCKGyxbLLf/I0ta0UmfVxskAWPSjBUmimWbh8SuCW2072xPjnxg7lAQHuCLRgg2IxVAyz+tVmmas6zLVilxXwUtllROzmjNhlQRRUI75KGdweQgBaYJh7YftjB2U81x4CMf2sZz2PwacFqVuGuXYsc1WHeWSX/RiL8tfnPfu9LehJUh2/8icqHAMuM1zmPGu/5zzxHUn6l/LwV9RKSLWpQB58TtX/nCfyzsWsbUWhtHpaD5C2EfgW0q+1E/nJikon+rkWqxRbTvGsZZhrs8zVHGawy3nVTBmD8m4A3PFepiOlmQnp+loavy3Ww2X4Q+V04k8EYUT7+fn7ufpUT/qfC7Mt9ibHuy7DXJNlruYw33vrNSqRYRUP7LznvC+HDFmVYRg7/IBAHgRKssuqhtL1tTR+a6U8ZfZtKeVX9Iyhx/cMAsA7VR9yr9hM7Y9ceXC1Q6n26wtvvnayoFcrlrmew6wPKlGJ0EMLmzYxeP770sIxxlAMDEiUA5I1RhLxzz1A+QayhwGL22Bll6S1e3AijyzuAx9O+lEqrCAUoiFJ/0thl+rwbn3+C2+5bsKv1srJXMVh1rHLlda2JWbHkM7Wbe1sKe+XdnJakVVpHMAyp2jl9mdb0bvtLUUJ0hAoZYt66BOlsKPWMZ9hVv7j+c+lP/2visOs4pcr+csis2P00PFLdFAuxoSj/Ciw4djunHpGgWFMfXtI4yc9TWGv7SvNkGobJDgNa2n8YuxPjmd8hln5j+dXOPmP3GHWscsVf9Lil+//46fuzs7Odvv9Hn8jcODixIS6ABd5UuCkBivJevvgH3tnRUtnPm0nJQ2c5DtJ2oH64iRq5ZyUtkPS+0dp/KgMnGaYZ37ULDO9w/zma/cmxH40iZTX8uKXsdnvVKdt2WDUZGApcKvZPgpDT4HhqXZIx5dChhTfoNKTFvWhNDYtYpKik6XxS6nLlmdNWjnjrrr+IjXLTOowD+xypU2p0thlpTyGkTwcnGaTjhyYVqugPd6XYigMS/hUsyJLKF8K/JTuagMoVH9rM8yawfc2VVubMBy0iPuTzUSl8aHoV6mOiasnpdufWn5pvO7/k6eRyj+1/Vv7b2n8UnVt7fMThtnzHylZZlKHuWbsslI8sMtr1bWN96QYhxDalGwIBZaU7amhwRQYctCVGti28M2a8t+CD6d+WRpDTm3dIrPQu6XxK1HnuTLnYpit30zJMpM6zIhfzqtelqHLW2p7pdVm8qgRo5A7BaYU7aCWjfs9Cgzn2icd15oyXfp2LZkv1WvL/Zr6RIFnzfZtkUvMuxT4xdRj6zOhGGZVrkiG2RxUYtde6f9KY5eVonzlj586n53PX+L2z4cRcl+KMVhrTErK/5wnlt+wVrL+kxCWSvpfQz8HxszGXFgLMVwftA33TSxQBnwo+spaG5H7PWr9omBIdZuE9g8K/HLr2CzDrA8uCfuNVCwzGcMMdjmvWkmeFS8hVcMRWapTrfsl9aA0ziXrXksetRlm6ZhykKuqQ+m+waWdoXpQ6RkFzlRtqSFTCvwo2nWKYaZkmUkc5nvffO2+dh5JaQyz3uxX52Txqt89h0GqLgoDkfqNEvpQ2tgGGWYhel0aP19HJkzZsKtP7RalX9Grbe9LfB+2Z94qldI7KrxL1Z9Dv+uFYVb9nSI0g8ZhvvWaageVHBJcp/of7J+//4+e4qZNGJeUJgPlkFZBxP0H//i72MulZgU1U5JR/qXxvv+Pn3JYEQ85dG3rb2n8XF2TzJLV7FPUk56abd367dw6SIV97npvxTHn+1QY5qzzXFn3vvnaRaLuAoKT/2gcZssw2yZbxoPoWlo6OckdPNTxpHT80oYlp26UxjxnXUvjuqb80vjZOoWYens/tBKF+waBOXyo2M01esX9HauPW/UL/Sesn+7pb6f6NxWGpXXShGQcWhrwH0UwzDZ2uebJTfIc5gMz51skwdcP/jGwy7FGaWBuN+pDaYZUr5K4HsvG+g74VMrz7n+/NH72e2bi4WVs9Tf14f7UNQ7gI8XBiLUVJZ+b1cujTZZ+puHD9X6/Q/85EXQZ2b8l6bMbxxzyJ0s7zcUZZmTHyG+SpobIll9zSqLqUO77kjp9fm2YL1E7zRsPGy49SZkyzOX0xyBEX35p/FSr3Nh1YfMN0vkPGOVylinEOC/pK5XdX1u/pfpzuE+FYTntGUtWuZiX9iaUzpZR1GG+R6WSc+b1NRTo/Oe8j0KWZN+474+eshjLI2kzIBXLQCZA4g+t1RcK3BXDHOCXRPz/3MIx9wNDX2U6sHU6xuN9Cj0n7vJsP7dGXynk49ohafQTBX4UCqdimGP9x5KxzEUd5tqp5BTA4sIx3M1+sRrEZIk6WuMP9ZXS2SkMSugbk82hCfpCwo4iJGO1ahi5zuURHpe0cT+MjyTmbbUSVXhxWPkK5gGf6i+FnCabpUMhI5H1Pc7rXbc/UuBHoUZLaeXcOpQMyyjqMPsMs898+uN3iftgmOc3tbTA7JVm6Cg6OodvrGFyKSYra+rVgt4qO1YaP7tyII0RK92e0nLh0N9bqEOs/paWl2W96QO2aFZYSuNHpWtzWTJC/mOTDPOUXa6zT1sau6wUxF3WolLWGt+R0tFrYDf3zVS9KY1/an244Bhbj5L4jen4hCSsJoohKymTWL3AcyMCMXpceqVrmp5VXn8qjR+VPk+yZAxBqWHK9fznvq8IGVykUNWMe25RR2HXVcDznystfvnJR5vhRQR7zujJuT/+bqq+2M137vujeP0pjf99f/jkdpc+IuxaSfwMMx/i2i2suG+RKCmLboxHwYYqttndlGu6l9Hf0quM2iZOHBVZ/ac0fgXVYlL0vbeqo7Hj/clSLHMxhxnxy/lVac2midJLnCXKBxOUX3dsibFLkKVlELskW0K/VJmll2BL4Sedmc+t+aXkkLuevZcX0uvS8pPen0rjR6W3KTHMqk6l4piLOMw6djlMcBiMC9+XFrusIEthCFtmns/9CZ7scir+LbejNCsHhjl9qJkwcZF5WIOGVvj7Upi1dC1p9405/S4tx2OGObBJr9E85qXxo9K2gWH2GRTfj3TuX/C892f3b7MXqOp7763X7o2hDv38tA3+c9vvn/ec91LJkuQ7qmOfOWsSakairv2/doBs+f6DGYVjWNwtnmvx5TTTt0v6p/SndH19XLfiy+39EvhJZ8NyGdIS2Oeq21w5W+TaWltjcLR4ULRtC/Yxban9DAWGFG2cMsxx/mEJlrmIwzwwzPX85Z00hlkzcp38ajOzJbGu3TarQkttLF3Ppe+3ruq58ZOOVw5558Y8R51i+1vOb7llccbkVJuVvlPUXXq/osCwlO665WqG2f7i/OVdEwyzcpYTYrOLRQ5Iy5AhfQnbKkLpUIClzk2Fc+12KhyGwaLCpsvJQFUnic6xAc6Y1zKnfPWKwH6v0yqbFcgzXHt4cGPSlH5zlBen1bslW0xxf1jpEtq/pMh7Lq1cjJ+Zm2XOzjBPsmOEZgSFD9IQlx1Ds8ultynxKL/WjPi+P3zS5qOk12TWrNXeicOs++lU/qXrVQvvcRAuq+858ZPOgm11jHJivaUuLcqJC3ZbcN/ybosyS2mvFPmeZJhP+JO5WeasDrPKjMHhYAGEY6R0KV7P1ujgtY1mjTafWiqmqE9tzEtrfS4MB6bSMayaucS1np/mZPLX6sT9f/RkEfLIpbNrcazxnvT+xaF/5JDrWoZZmcmcKeayOsyaXR5+ZRmcU4yrPIb5Sdogz52Ea/8v4f5DfvK3cvStpDK+/AdPMkunlfE99yfo2z46zVP9OpdADj7utfHP/f1c8hyZeA5URMwiKE09azt3kuWSS3eTDHGlhyXLUeexZppxKlXc9956zeoV4Jwsc16HWaWTs79K/rI0dnlcOq8EKKFAaxjqkemsj2+N9rtOsw0poTCyCMlYHjKks/DLCMw/QaGfobr1JpOaWK/Vj5T3pMtTivyCaeUi/E2WDPOUXU5R2bzPSmOXQ0yczyxLuKZmmJXTxg232k6zwoNCDtL1eqsc7/uDJ01D4n0z6c/vOrm/Fde1o42Z4Dm/zvCvhftaecW+dyTXkAPWaP+SIrdJDHOscJ3ncrHM2RhmnUru8Cu8py94krj6vDSGWfqSkQ2toZ4Jc8a1ppFTuFB8nzP+SWewBvL8bNVn6czXijGv2vIyZGGktVWn18i85Ds9yFWKzFQM8xb/Mle2jHwO8y1XqyjQYbe9f5CAf+JUqfsSGeaSRoNL2RSspttWxXBy/lHjQY0Fd/y34rFFfopddo99qklAKBw4fJ8irt6XudVRDu3npA9bdHtrv8r5vnT5SpGTkvkYw2yWdlL9R1YM88mjsGn2gGh/XJqzrFk4Qvyc+Q75dylYTWtsW8CVEo+cg1BsWUPIgVD93iK/iX6Glojrh9yvyaI4qkdC/bdgGauP7nPAfzmLKbVM1shx6Z1JOEaCPupyG3i+xiRzCfO194cY5g3jRQ6nOQvDbOKXQxrkt9AfAfLdP/+5718rD5bvmRnwBg2p6QEn1PshP/nbpPi3gis1LpRC+PIf/JzDXfLJvpCjv22Rm3TmPVXHqFky4J8mIWr5pNXu9NPSZd2ybFzJmfjl7f7lBc/7wGZ/d3MBqmFgmHN247EszcIdfg1MaLV7vqa+1DNhu+S9tr6UBAM1NmU0eVpqS/hT67N05j1hHr2j1v0WVp5S8KPiS6jllMtGSZd3q3Lx5avjlzPwhiwY5rtvuXqvj2rd7c2RrRX/XiCOYVYsnHyPeQsjl2o8DbPZxpKaGvAosUnFcu3zWgaCZ4BrZTboJouo4bpRs9Ts2Ii90mpELZsOan/LeFDLa63tcd+T3t/W2qEc2OYsQzHMufzKrU7zZoYZ6eRyqsZYlrtkbSci1pBLuqY2tArX1vCTYvisdhuGeaQMWpPHUn3Xystl3jMQKlQEY5HvULFjOr1hF4FvOXK/nCb6qG351pFXen9rTR4heW5NKeeWW91h5sIwS9vw9+Xf/7nyFo7BSLHWuVhrLFvE9SE/RRvjvRbb2PcmDLNAz3CNvKYM58wKiA+u4DzAVDbhCHMXY8H4zq6wZdIvKtnF2ppTz0mX/xo7lAPX3GWAYc6NqMq//Nz3FSi1XpFuZx4ZLVMfSdfUBnbKMLeDJxXjRqHxI8PcDv6GEY+r7xqddvWydmhbze9T6TnwLhdCuUb/KeyO+40e5N+CHGLkLoZhVuzyfDD22c4coRZa68p/X1L8smJBxzyDRqWkXlPOgjW7fPi1hiclTjFGbMsz0vU7VVauXm7BtfV3U3Fb217gvRa5+PeoZBlfo+mTPegAdxnEyu6eW6/xYoG2+Y8XPn99toxNMcyIX44Vedpz0pes7USKukO3jis1XmlaG/9063JYCnpNkVNrm1CdGWfWPMxUbBjwHhiDrPKb28Sb0g/irUeeJ4fQvJFBKY7H6jRShjFLqh9Vf8ojjdOl5GSY1Ze2xDFvcpjvdvMva0bZoZQJryWxy0qgLgPn7w61PSe0a7Sl+w/9qd+h6G/DNwyrMD0pqCV8qfEqJRzp+v2QBL0ema7EETF1BGX8PJVjNWUV+8Xb9Gua9lPJNtVWffn3fzbNAyXCa2zHNvlwxT1VTur5e1Qe5oz+5IUb8jGvdpi5sMsKUJHxy2lZfVKzALF4nrJTTwbL5SxJLPAZjItTX0rM1hi3mHcGhtk+3LA8dBO8+sfKSDrTvsTEU640tbjZNxY/7s/F9ocY25HrGen6AIb5tKasZZlXO8x333y12jWQJaH01nIueJ6cE/6+9Ps/a+BwJpiz/kTj91NYuBxGUuE68c8axU8Cy6zYHcn6HSsjjYPY8w7jkvxQ2AHgXH+YppBzyjgxjLNC+x83vFNk4z57zy3XxBmSREO6lmVe7zDfcs1+zl/2Q4LyHXw9H8Kjyr9AUIaMcakoUQMaG3qpWQdJuLZuDN3QmPpDef5+FiOfsD7WsKCuJaf7fgxOawda972pvtG1b36k7/P71PZ+SW+k9z9ueC/JI3RfhWOUsE6VGWZ/1yLdtbT45S/93s8WmVFx86djWbi1Hc1/TxKuD/1p2tjvXDKw5Wi2P7+fyqbfxMhn0MfQuXKNroAMESoL9adyll0mUcIKUyy+HFdwqG3+Kbslvf9R9a/cY4NfnmGY8/uTFz7v/avI4lUvqfjlbSHp+bYciItfdkIHSitjrfKpO7NhE2T9qDHMhZ5EWfjYxMhG0orHmpkKBQMmfSVjDe61Z6oUco+xVdL7HxecY2Rx6hmbIaOEv7nGaV7lMOv4ZSY/UfHLil2WTL0dFlce+tO/S6o9mk0QhmsMi0kKcuTHvvR7PyM0atCY9Bi5TPXRX6KXfx2DUaQ6BR8b9Uw+nqaFJVwK1z3IVz6F/Jf0R7p+cMB4SQYx9zXDXOi3Jh/zKodZM8xDmg87zx07lNm0ZtPMxd4fUZl/f/6+tPhlC5vkv9Sd2W4yG1Z27ApP43+pccxht5SzKE0Obnti2eWhf9vpw8FcDpEqQq9j8NmqZ5P+3hm+LegPh9CMwQ4J1A8O+G7tw/Z9N4bZ/m+df3nsn5I4zJzYZQWgLIZZsW/qJyzPltceSoZ5ymhOupzTp9vEmxLHXAZQsjwURksykc6wL63kUEzyJK4oLeHa2n0KPThlsyT3wyUblMuWU5RTkmFW9U91mpMZZnVYCZNscqKyYyjh+Uzo4N4FmNAW71MwTG5HPsVotoifu/JgHLS2NgC68pCAv57eOv1zSb+lM+ynVsYodLVnfFtauVnqJyWdMakrjlb+FP2spHzcsv0sGbn3iqdmy0h3mBG/XERXzIxX/o969isdV2o8t2poz/KQ3vZTukGhpz3ju7Vf1nifQifm2iVdT2rhmluHSrPLNAzzzdccDiyxQXb+Xz8oqNx9meEYskMyKDvzaBjbDLkYDdDp+lNiutUo9hyS0XOoAAXr1TO+rYVk2PpS6IVvs6TrSUvjwanxZHIkdnDTxzb/MjVTRhLDjPjlre5C+P0v/e7PtJglKMlOP/RnqLNj/ExS/baeOFnrfWpct/QC6XoeGqyOWC0/6YAPqqD7FAN4z/jO9sdG9IdCN44dZmdcCBmzRvCbq34NTLeMCaF3KRjmVJY5zWG+5Zo9p+xcFzzvfSXkVKVM6ctEClTqjtwDpjVw3dJBpMsk2mHeAmJj71L0e+l61ZjIo6tLoRuzDnN0Ddt7sAamJVAyDHP5LKQXPj/+EJM0h/nmq4cDSwjasUjYXfi895eQE3mZinVTM5Gz3dnkr6Wc/f/b69buP+xnfo8U2y8q1n4G11bxO6UfrbDMX/zdnx70vDX9jemfITm47Q71Z4n/p9DLkP2UiOfcONFyOyn048hhDoy3LePo6gX1OFtqUFcMM4WfedHzPxDtB0c/qEC5W8UvkzRhOQ+HOHaZQjOWYS0qXkrjKH3p3++GrbAK0uUyJwfNfuY798GMTw2UR9HfB2a5ATw0A2R/qK+ZLxOvPPagL62MBUuO9j23XEty4FhKarloh/nzN1+t08lx6e+iNvz97k/r8c/+JG5Reyg5u/zTovGc05cWmAXDtBp/T6K+z8lgZECtn2tWksb225UlWfcp9HHK3MvCT7p+2PZR6In91nSFy/x3ZJZl6A8lnktO75b7bgxz6fllrNMc7TCbDX+VKUrn+2CYy8f25BQ39axXOpM5t9BDwehtMYATplXgisoc/j3qobIbFP1duj7ltL9MFoZnVzAp7VYP/ZESzy3jwdK7VAyz8mtj45ijHWbLMHMZ5yQxzGrWK/1HOevtAc+QvlDinKqz0uUyh730NtfUw16xTe133J+ntFk96AwlniV1iyqGWTnBYJhLSjJj2e4SNR/+Pn8oM2VIxpcOIS6S8QxNXClxTu0G0uXiD1Rje+dDLkb9lHWfYsCe2k1Z+IVCBKAvqRZn+nwP/ZGz/U+VXrMMswrH4MIs23pIYZiHGW/pIJ2K5VMMoG5n1JhWbK+uS8XvU+Mdawgn7E5FfErJ52E/O80Cc5LNajjP6yx+ByWg0L3Z/u0roVB8h2YKax+Z3lgAheFnm0WBY6y93/ocJcOs1CEmW0ZUSAan7BjWE5ESw9wDw0zdiaUzmUvMOTXesYZRulzCDHP+FRtuBIatDwXDJV2Plvq3xPvQmzw7xChwjLX3W5+jZJiVhY4Jy4hymFX8MrdNA1JyMH/xd37K2b1nVUxWHoGH/ezvb+07Se9/8XdUTLjUPAwKimX98NnOJAALPWx0Pa7+LcrP1fOxrW57fVfH7+9t36fq5+H+3TZ+xy6TLP1Yal9p/Rn1JmQ/29cfjnZ/zXBy983X5JlBJDALF71gOR9zlMOMI7HXiDzuHd2J3X7qLxn5Am/wPmUnPgpxEYDfGv3gyDLPhmQIko+r50O/Di0BdxCSEmcB057qIYRtNuRFoL7M5aUuZbd66Y+l8EvrpdufpjoW262pWIb5wufLOOFPCWvKRG1XNG4llGYN/PZKxzNFvtTYL9VNumzCDPMSMu3fp9I16TrUviZsb0FuXepJZ3Jjt12a60polmGe5l/2l4jqXIva8Ke2U56d7XbDX7vi7v/fXrd1n3rGOzL2MvAb9SJd/tyM52Sz1pHep7dv2m/qv2/xNu0M1cfvvzKuKfr5vP7IwK83fVlqby59Oh4P5OoLN3u/zlU2b00Z5uUQxFwhfEss82JIxudvUgeW8PpJYZjVzDchxKbJPPbUnfgLv/NTTeJUSg+o8T9lKaTr+3mHWH3p7fQjPW27S48SveEqcXNfqp1ba796HAeo+mHpfq7K1wxzhd9SHPOyw3w4EjtV0Us+L2vDXwWtIPzkWoO3poo9Lb3F4kOJ/1KdpMtnYJiHzY1LiMi4T6Fj0nVHhiaUa0WMjvWuIwwJWQkAACAASURBVDEYlZNQ3pLvvuWaKsQXGOa8csxa2knGxF/R9U/CbuD+eT9HnR3DYewbwEdH4oROOM9Ufy5G9IjxydS+0vjFlu/HLx9FZDTYf2P0k0q/pOvPkZ4J6x9on5f0oYB8wTBvd882MczhA0v8k5X8gb/sfTDM2xWDogSqwdS2pXeGISRTajnM1UO6bMAul7Mo0nWnHHIouScEONj5XHifZpjL+penDjA5GZLBMX5ZCURKDPMXfvsnc+kXy3LO+7k/IK2XdDzXgkkth7l6SpeNxVh6O13ZUulVT5iu7eN4r28EqPoiJcoc45hPO8wMDyxRa9hSHOZJSMZhicYq5H6/352dqZnUYSmnwfvUndhloiTgl0v+HJbqvvDbP6WTWkjSb1c+lt0ZQgeO+q/fn9u/pujfPeE57e/t6wfa447fZeUpiV12nXJu6eXAMFNOmbxvSWdOKAZUF1LpeG5RVWpZ+HWVLhswzFu0M/yudL0pgxpK7Q2B2va9FN41WOZTccxRDLNNE2y3Lda+FsUwWyZK4F/KTqwGVs1oCMQxR7tqMxDS5aPw1StGnegflT5J15te9AXtPDDMBewD5ThbyjE+Va7Kyaw2UVP5n6sc5s/fdJUKAnDawedsTikOs3T2hLIjS8dyq6GilEWvMcw96SCFPvWE59b+jff7RYCiL9ZG9+6brz54zENQX9HrkNMcZJgnG/7oDloxaCx8Dw5zbfWN+z5lR8bgelomlLKAwxzXP1p9ikqX0Kdb1RDUmxIBqv5I2Sb/W5PQDAJ/NJQpI85hronUzLdlOsx8GHwD+fb6nPdzf0imOV/47Z8oOuPMgUftFRtKefiCly4fNWhNHDybiPko0/YBmYbvUwzQX/itn9iZXaJ+Bt/28TPm1e7iRvumGYoh31T9qGnXyQZ44tP/wDBTSjbyW9IZFIqB1UItHctIlTr5GKU8jh1m2SkUc8inhTKodAj9uQVtQB1rI0DVH2u3035/kjXD/rMA45zMMN9101X7s51NEM3r70XP/wAX+W2qh2JRSh4hPsfPUH7v/CfRMcz3/tZPVDlKkxLPrfKklIffMSAf76Sv0AmPzP9/HlGfhr7I0JeW7ONW+1rjfar+uMnRyfjy52++ekfhl25jmDM2OEdRskIy5JoUyqWicclfLp45htAajIRhC4XLxV9iF3pNoT9aX4TiN/QDtG8acgM8VuFB0R9z+Gw5y6BINZfkMKsNf/vdnsSTX/MdMMxtuB+UjCYYqTh3ugYjIX0lpQazVGP6QdWfoS9t2Pde9J5rO6n6Y05nN1dZFEzzxS/44NEev9lNf1yPxLZgi2KY7cxa4F8q52zCSAnEUW9+ytSuGoxEP/KxRLqVl6xr+v4sC78xZkymfqB9tPpK1R9zObm5yynNNM+xzEGHWSUatwO1PTiBy/VFL5ARw6xYUck/qhmwdBxz6giVTNw69yGf7VllamdRWfr++U/6o5yqOFvWqCvy8VzCG/d5ngNhFLe+flL0x+IdfsMHPn/zNZpIKuWfXvxCMMwbxJP/VZ06SfCPagYsHcecKkIlE7fOPcrHH059GbZ2n0pvrK60hk/r8kX9pwhw1z+q/phz7ClRVkmWGQxzCYltKNOwKTWiEWmisqhmwNJxjItOjtOjegxzXP3E9Idgnt0DDo3dp9Kbe3/rx02ekMbwOdJb1D+QR7tN/ecmX6r+uMG9IXn18zddXZdh5h6/rKQgJoYZDHOWTtUjg7kFOGp2ogf51F+gLbuAT6Ezrp5IxxPtK6uv0vGl6I9bxhjKdylZ5qMY5rvedJXSNdY/MTHM71ZsyszPT8TtP9LI/fOfXD7mUUFzr49jI/gEO1nh+lPJxbavO/mwtp7rKkehM0d6sq6qeAsIdIMARb/kDqZimUv9/DjmI4fZMsyhBVRbsZr3pTDMNpTAJuK2S+9SrqlCMkwaqlEjpeBXSh+o5GJthXT5zIYQ6CV5i8BMiEFD96n0ZRJa1RA+0uWP9vHuv1T9s5RTmqNctQFwboto4OD5YCCs79/6ccwBhpkmxnVtbKZ4hjmHBjEog2r2C2YqTdhUcgkyzGnVxdOVEaDQF/ThykLG55tHgKKfcgbp8zddk31P2MUv/MDER15kmH2Pm8O1HIbZhmQ4eTk1wDKuqTqwZqZmGSmf4cO1QoCakRAtnw72MlL0Y+0w8+ZpUD/Ih/2eZGrbzsmBdmOZc8WwRzLMnGA4rotMhnkqYnsC4tj69u5f8OQ/JlOke9/9Y2aicfhJwE+FlpSQP4UD5AresIft6W8M/v5JpbadoRNMW7xP0Y/vefeP6ZNlW8THhoCh/pAfB/09n3DcJRvgIz+UO555MYb5rpuu3mu1H88tmR40dnBLat6/6Pnvj4SP92PSlyEpHTPpWObUZEq5qHpDNjmlR1sWla5AR2jliq/JRoCq33JD0cYy5/JPTzLMY4YMf5s+r2tZDLNh3kZGVM41BTNlO2yIofJn/Lg2+kVtUGXKx2qT7b/OtT6Bqv37FGyVcpaN/ZOHn+ltcvUD7eMr3wueQrfCy8lxHmOZnRBMvcK57tplmScxzCpDxhCSZxlmn1FmcA2GmZN6hutC6ZSBoUrTCcgmDa9en6bQE/TdXrUL7S6NAEX/Ld2G1PJVLPPAMGfwVy9+wbjxb+Iwt5CDWYEnhWG+510q7tZKdPUEaNyMsm4CVex9yhnugKVgPFdOkGflS83+b5jgF9PPbHimWvSGnqfow5O+2xA2qCoQaAEBij7MDYecscxBhvmuN11teGsbxGw3HTG7vugFkmKY5W6zp1jOdTvqdHOZXFxzbNenZB4kymV/CLkY9M/blj2cjGwfaPA+1UCrN+w2iI8KuZEsf7RPjnypx+LaDrRxmA8ddKP/6qaWA8NcUbLSmRWqAdeKUDqeOVWVUjaQS07J0ZVFoSPQDTp54kt9I0DRn7kgTMMw33S1e2Da2PZQ/kU/BMB/rtB9OQzzISRD4ZQrcWCA0apR/vnEmw5MarnDTyCeJ7LMJesPJeMwxyDW0Mec+A0M8kHPJF5TDLD3qrC00EFqzngiEV/N4ArWH7SPn3wp7X5t59ls/nNCXlf6p1EMM2d/Q1YMs9zQgQue8iekfcawVXLxzBGKYfGhcIamzL9EufiMgJxrKv24511PlLuJY7B+vLJM8d8UALyMnS5jT6jHZVInwPmYyzJv8WeXY5j1R0MDnE/hbT2tO/19MQzzu35MtHtHNehajVRslUS3rMQBW5Tsv1S5lBnOeLgzVPqhJrlwj0q6Rzz0qZz7h/at6T9U/buWs2y/O8Yyh1zmZf9z1mFuJUOGAgIMcwkXKr+rST2TBcMcrxeUkxl5cpkJofJHBt8+N3afqu8ahnnm1zh+RyFHjckf9Z8cHHusoAL084Kn0q4A13Cec8UyW6d52PQ3ZMio0arEb4JhbiPwgNIpc5f/493GNnAs0R5KhkEaw2x0je8KXI76UfTd+YlU/RXMHPhJ1w+0T0b/p+jnie5d1seHOOaNpdo45sFh/twbr9rrk6lCR2Iz+v/FL/zAxubzeF2zK/mJ3ZyhrpvqV2MGe887n8im/c7RZZtwLFEOFYOoepo4PZe5R3fYE0nVb3VfPfy2xBi60xeUN3X3gQfwONU/qPp6TY9Lscxb/doZhvkq5RI3sWlKTkiGHTDWRCHxjwqjdMpGhhmbiGKj+qjkMzKJVkqt67tsj5lOL5xwDHjMQ9rYyQIGPF54vIVnhFT9vZbTPB6Vvd6/BcNcS3rOd12GhUF1slehxuxVOqY5hUQlH0kyGReE9ruz3dmJBaJ2719IENt49zt/VCx+ZlhuV/6of3/yoxoLco5fKWXd9aard1siKMAwp6Bd6NnghpdC36MuttbMVTquueRIIR9xshAcQmUXGCkGT4ROncgPu54IYxf6JTnksJEF+ajAAYqxINe4taac42wZaYZ8lmEeKsJ8xVRMDPM7n+gwEZaRkPP3wqf+6Rrd3vyOGoxHhkcOnobRzNue0s7RyCTmrXduHGLK26yYjRRA0W+VXuAHBIAAHwQo+n2t1iqGeYt/+/AXflDv93OyZCCGmVqYwaVqf8LiV6yR+6WdsZC8hk1moQcawS+ojxnrX1JGsvS7jyDbkvrg6vM92mEehh/ZQeG64X3ozyhjtLdF/abq/9S+lvrelljmeYa5kaUgKQyzZVmOYTdMov21fL/WrNVlsFrGz85qpwtI+fSjlHwk4j8x8gLysE781UPjSumDi53WDaH4De1E+6b+ou8hAR+W+FD0/xrOsvrmXTddvTpkCQxzLak535W0GSoEZ60Zaw/Y5lThnHKSiX1azNtqy1w56DOnHgRXgHQ6uT7wRDsh56ggYkb94YJKoZQ5x7O5srIxzCoHc+nK5ixfFMMs3J7UnLFOmCzhOOeyt1vkJR7vnEaMaVkXPq38voO734H4ZabiR7WAgEaAwg7UgHoSy5xYgYe/6IMq0cZup47FHhI728279qAShtcXv0DGwSUKe7MpSi4PUXO2quIk4Sdv4zeW5Cddfwf9cZaQdX8Ver1lwhQz/gz6IhS/IYQK7RtCDiT3F6nyXrL7MX2d4zMqLMPqo04zl+DfDg5zawyzEoQYlnmObcm4qWtWaYnLrzlbnWWziNt/JAN831iq0I8lPvKnXhc+7c+Kj3F3v+NHkE9NNEUiv59soyDawYfCHhQ3ON4HMjHMV+8VxTx63Gc77tcXveD91FgX+V4PDF1p1uqUYHrAV/IKBYfhReuXUxGbAD/k97R6n2JiqyawreJj5Y36OyssMx0U+MjAh8IeFHGqThRqGOZ1/i0YZmppzXzvNONiXwi5RG3crz1TNSzznOvVBn5hRgP1NwiU7h8MDAVBFSj6qbF3+AEBINACAhQ2gRqHtSzzUQxzaAbtzxg5XF8kJI5ZO3PMD4rJUb+aLLPqkJPQjA7wnoQ8oL3TEJBUPHyKWeiSeunBMTxxxRqJ3F0sHNaIoF9r9Usiy6xO/Rv2oNi9eidWEK2/C4aZemoTZJgZVKRwFUoPxjHVB7sVgxKemUPg6BiGg6G1z7Z+n6J/uv3PbjKXgl/r8kf9vWNGhPXvLfKlsA2Uo042htl60mdnToyH44Fzui8mhnnY9Hek0uJOwuLQ8UamWT7eLZ40NRpOjvLxQ2DkXFMwSaPDnErx4/ltSyTAD/i5u6zT9IHDuJ3ToVb5mIc9egn+7QLD7A9YfpXr3peSJcOEC/yIXiIYJyRm04C064ueXn4XfkzH6gVvafpTpz19LClT9M3Pv13FL/eBJ9oJOUvJpnHR0/88Zlht5hnDMKf7rwjJYCLinkIFOM1We8Kdiaq3Ww2OhLfOon/4bagfVZ+c9LcN9Z2ElGdoP8qb8R8gHyzQOfaFykZQDRBrwjLgMFNJZ+E7A+Np7ZZKqK0YZqHXFGxWimh7w39gaoXqV972nWLK/JAMf3NRG/epGKRjhrkNfMpnYSmd5QXlI0vStixR3MbslPF97lk4zFsRrPh+b0wn19lqb3KoqPLtfFr4yjJVX7xbhWMgWQEiNYT3J8kRRxcyCanMMXhsdphNEPSZc1Qg72tJMcxKAT7/9h/OoQfNlEHFaq0BpDdZrMEI78hAgKofok/J0Be0ol8EqGwFBcKfe+NVyf7uI37+Q2dnOBabQjzL31DLlWop2c5YhrRLhxm5xOtWlnncTYI9yac3fZxrryYS1MlQO/XXdk851xcSbehRDrNE/Ix5lqMPaA/kGdJnSQ6z8sjWsMyDw7wlT5/6OOX70tjlHhlmO4VosROCLVueAIp/In2T9RQSJu9T9b+jPsOk/UE9Rf2mm958oIBPl/hQ2QuK8cN1mGP91+YYZonOsnWY1czOTj0Mo2WZi/GvtPuS4qJ0nOZh6tiL/Px2ymy/3NDTOgyzXDwRootQdcmh+lT2gtphjv3e2WffcOXeHlRiE/8O1wcHjtP9h7/wA7Fta+45zcJ0aHEveoasPI8xivf5t/0wRhbuI4sWpOwOSdX3Pv+2HzocxCQbT+n6gvb1rb9U9iJmDN36zOfeaPIxp/i3TTHMUtllK/iel/olLfVs7cju+z3rRE4cN5clNC8tRb+bEAFWEELxHPQM7UMe40x50ifz9sr9h8JebLbVkQWsimE+yTAfEpoGPXDi+w9/0QcjoWjzsSnD7FhczcTZo/8sUSPv/kXP+Is2BVex1iNTLV8/zAoYpf6HBSshhPNiov5m2OXpTwJ+rj+E9kG+LgKS9VvKOP05deKfzQ7nHK18yt9thmGWzi6DZTYISJrB1vKjwUpnRD64AutPUPyIA/73qQa++VAz/vhMJ2hl5JvL3h2HeAFfCvmFI7bk4i8lLGM1w9xCMOXDXyQ3dnmyBH9gY0LjtL8i4z8n4T7VQJ7RrWJf1F1v+yG9I0GCfqg2UPQPi5UNtZZ2TckwK3lJwy+1PVR4xxgjZQ9S64/nDQJS7UGsfDnpcYyun3pG5WNO8X/BMG9FPPP7J5fY/SVpwde5mJfM4hFTnF4mF6w/syFMqe0VI+35hlBNTOdCMoRDq5tHhS8Flr3KkALb1r4hSa9TWWadJSPFw66xS1Z67LLtMMoo9b0H91i7JHVOroYRejdv1U4yLv4WAp9BbeD+xc+k2TNg9WuCZwP42CWZWUbxRP0lMXBcbVaNellW3m4pWqsfrb8vSb9FMszdxC+rVGPKZT5spgxOZDq7D6eZbngYmecTU7du9I8O9xpfoupXPbGTVJjW0Bd8cx6BnvTbIiBFz1cyzHy7Qi/s8l1vVUvk4tO+rl6gkDSr5dvbpjXTjEq3Sx5u1G0rEkur58XP/Mu0F1Y+fddbf3Dlm+28RoVlO4j0VdMedNyVqBR9Nwxz/O/ss2+4am8JI45/e2CXe5yhxqvo9EkpM9u17a/xXo/6OZfFMTUEmvvzpCEZM1kPueMTWz9M5mtYJX7fnIS2CdZ3xetJGYfV4SUpfu9CDPMk6mxmN2H5+9IZZj0zTZGYky+w1/ekzG75mfzTNTKMsxMyJF1vWxNQQn2p+pBk5o0KwwSx4tHKCEjWd7kMc2hp/9i/Zc0wS08l5zJ3OBgq/WAoMDv1RodQmjpbo+b12UlobxPZS/pLxRApB0ISbvuDXlDhV6+H48trEdBMM+zHWvhI31vJMJPWMfpjXbDL0WjgwTkEwPLU1YteGJW6KOf/OlW/kagfVNjllzpKpEBAos77uEnpA6timCmUaM03JDPMbigGl6PHbYhHi/UB47Omh+V5xzIqLeuPYoSO6p8HHpalUA14Ep0HKuxYKg4qFYWARL2XGZJxdZQ8h+CMaR5mP2aj3rV4ZzlJTHg4FgEMZrFI5X9O1CBhHegBJi/IpPH7VP1k1AkZ+FHhlr93okRKBO667QfMBBz2gxL25G9N8zAv+7s6htn/ypBmNfB5ivuSs2MMjNwB34HRxbVGIAceYJyTbUeWF9yY1cH8nJ3pmL5WrrMAwbwQKsdP0iSKCjPmqoPqRSAgSe/nmiulL6gY5hT/95AlY9z8bl/2CRTKa8mxy7Yj2U0BI9577Sji2iCQCx8pHTvCRrN65HO3/UC7+rzf6/TTPt/g76Vu+f7Fz/orEn3RehBIL98afg8nwoxEMPhIUQSU3rem3yn2jcp+FBXSbrezMcyx/q3DMPPZ1y41HEP6rLO0cm8pH47zFvTWvdu0vg8W9GAXBV1f/EwaZ1lpzV1vVY7DmcPItIunFCdhXW/GW6kIGPvXrr6bkJL5+ksZT0eGOc7/HRjmVGUo+bxUhll3oMPAG0o7g/tm81VJfDDwley9x2UrvR9WDFrQ/8Mw4TNEUq4pmVKXYW4ZP9gMWpsh4WuWZbZuZ8v6P7fiRmlHSupDSqaM2RjmkpVbKhvs8hJCuJ8bASmz5dy45CyvObZZO/aWYHEY0snBLW3ep3T+hs1PswfetIMfbEROa9BHWXp1RROXsuyHao+U/jAXw3xKO8/ufP2VrI7Glsouz8U0SZtxSmmPlJkzt2GpFcaFG26560Ol367Ny90G6vKoMKNuF75XBgFJuj+HkJT+8Nk3XJV0YPLC0dih7Rrl/i+RYXZDMbo6Wlj40cmUTF0Zs05f6mnGMcTEEP8/sEmtnNUbIgVtxGDRv1SDnZK1jQxs/S/6Or2taPmLpza7SrAjUvrDNK3csmTAMBP0SumzTQIIm/4ElYPSCkhN9Ad/k5/v+jV8/+HP+msSVTFyDrjKjeGHPkyiMmI+8rm3fP8hD7MM/ff7sZT+sIJhPs7DXEtrRbLLOoG5cMoK7dtMEVJmLqjVv93vGqaZab9gWq1ccFENdpIYZuX2UOHGoX+iDusRkKb3c3ZHDsOceNKfimFerxp533zEz38wb4EMSvvcbd8vJq3SqTQzQ6iJ0DQ6tdoneZCeMJCc0raxsYhlDNjDn03EMCuWTdCPCjdBkHXZFM0uC/9J6QuKYU75sYphlrbhr4eZZuuxia3WX8wM/8TBFrkY1bRyZOZNtRM+ygkY2wnRyrzQUpyEFAcBz6Yh0AtBRmlH0iSQ9vSqGOa0T5R7WhrDrDvPJK2Mn0YJ18DHTTu0Xh9aNmDasZpN47Yej03llTNx1UumdPokMm2U+FVXFlQgCQGJ+h4CQEo/AMOcpOJlH/bTaKUcPSn9aF60b9S9EkcHt8ZAz+0qtwiVwCeofxPHXV4wM9WGPyW7kTCQgyMlfmVHJ5SeEwGzyU+Oni+lt5HSD5plmMWxyx3EMeU0OCirHAKtsAEcGJr9br87253tpP6l1AUlT4k4PuLZf1Ous6LkphD47Fu+T7S9CPVfSjtSUiGSGWZzcMl4FHGto5mlZciYzjhtHtlKS8xDPmR8v/cQEM7MAJsQppIWunLZlAMdhwlQKbgpcSzVBpS7HgHJuh2DihT9N2nl4v1fnYc5BqDSz0himHvvTKV1BeXnQYCj0avddwZGZb83J0DpHDczjHOj9yll/rm3fJ84/Fz9eMRzwDTnsUT5S/nsm78vqf8u9YsjJrnR/p/DvklaYUlmmD+rHGbXwz6YuL1WiPGEreG60H1JDLPE2L2lmCbcN9kDW8RhabDIP5ydLrF6/6FuMOH3KGVde/JTGlZKLEu3RVr50nWvprwk6f3nVFq5BP+XBcMsiV1Wimxj96wHFYqJxH3jYQKf+ZhZav3gwhxU7T9q05/gHyUrqli+Hn6UmFLhmVN2NfDJWX8qzFv5Tg15lsAmlV1WdTBHYx+IMVspSywP14XvS3KYMbMtodookwoBLuxBrX5kliwPWe6GkAw51w8n3LA2hmTIwW/I2743Jx/ba0pct9oCLRev/iWva0zEpyEZ8vWvpPx8e1hDnlt1fu596zCn+L9gmDNLYuiozokYwwTkIBlcGwNmQxiABy88uDAIKm5QeSTU+pHZJLAqjlK2vbF8lNguKRUn7Gvgwqn9S7Jq7X4NeZbAaBXDrCry2TdctdcxykMShcOuQaJrMMwl1AFlAoFtCNRmm2uwzGavhsNIOQ67hP9TMqETJlMYjgOjF2gXBc4t4EuBwyx7aDf9daZ3S3q59X4teW4byebfVvHL5ly5OH/3kb/wITUE7Ha1M2VIcpgxsy2h2iizFgK12QT6/tTqYelx9aaUp5FdXL2kPrcV78+++Xvb3U28O9ttbf9au9c6blz7Qy15rtWDU++lMswsHGZJznJPMXuUMVOSY0pDMZGc8K0Zs2bSORHGIMre80fqwNBPdkoMq2XK9B0PqVjVcLCkYllGE9NKrSHPtBrGPw2HOR6rIk+ioxaBFYUyQKCmoaTtVweP2c5kQtg3ev8Rz/lfyLTJsHyBX6P4Da1B/c0elAX5UuqbrYrWO8gnSj5L8nPv15BlSWMFh7kkuhFltxBTtjWGKfb92Fgnd5OkhBjRWHxafK6W00zdr0xX90dcGdeUMpxOdGTgNw4DaM/UI5vHg1LfRofZDQWy/4W8YuR1Sr/hMDsxzMcDuDm4JDyw57kvKSRjZFSOEpX4ifnEXecwjD3jZxw0++OrPzWM5hgLWx4fbe8ER94+4rmEDPOt39t5BDPXSFS6elHq2+AwQ++K9LtHEtqOCH5y0yN3vv7KZP/2khcz2PQny2HuI1H/dJkm//GwtEvwm/pdly/nmBylAkenE4GQDCEEFeWEZxKSIQS/YEgG2jdLYFLq28mQDMhnE8FcQ46pY0TK86nhGKrsyaY/yyTbqQnVtRSH+bO3fu8mhWxtBbh0B+oNT8j/tLmj0gcwzCnDzrLM+s6RQcfkcsUZDLOMFasacsxniY5LuvP1V+n0oSn+LhjmjBIZ09j4MVO+KWv/PhXDOJ+Wqn38TAtCQ1wb7aPSAbeLzsfEZu5fwj3m0hPdqbyczVdcPTrUq5hHV8vJmmz6g3yzyJfSbmR0y4JFbWOYX3fl3hwJe3ZI5KzSODnXengvc/8Rv/BBCnyKf+POW79XdOyjtTuURlCxirB3WexdERypY9po9EF2XrlHPvd/LW4L7QfuvPV/JvsWPsQPAUpdg86Vk38NOZZrjT6ob5V/e8mLP1z/4BIxIRl+CiWhaW2oZ5rB1FRC8Q3GSPoWhEn7q+lDqfbLDmHeUU94/fUSXE/XlyTjQT2hVljq0C2xOW7GQYAyJJvSZpR0lG3Za9hl9e5xDPNwFPbhoACCazDMfBlEP1tADQPYC3PfKpNObUzL60PAY2Y6YQkOMIEJBSVbdJJhLjXhCXmgQuTX0oT6kc+jW804YpihX1nyMFPaCwpnWU+qNMPs+F1ncf4uYpgzSmjYlNSq5xNRb2rnaJgRuonoI+rZRWwMIxyo9aJkjOIYimZD0GT9pZSVCaGRhR/aEy/PGs6WmqSZ0NL4euL5MF6U9iKjO3ayqM0Msyr9M6+7okoG0keKiWGWHa9Xw/ghLo3KhGz/DqV+FI2NlR3CvKNk/e68RbZN3N5rZJdAqWvDWAGdy6ZUNeSXrfKBglT+5TVr+opdVkUOh1ve+bor1ZSMPA+OJZMBLwAAIABJREFUlBhmu1Q8rgj6myb9rHNt3a8503Q3e0nF9zgmDfoRMp5T5nJq/rbqh2Km9I8ySJDwe9UmNkLxDIZIoL2kk7PFkAzIIzntrUSH+bOvv2qNv6zjlycOMxjmbXMbzaYM0w95Ay7lQOtLQjOKMHjs9YvSwBbrbyq4rQZzUCSPyTEDQi4jonaRMz1o12I/eeTz/rdtg2ri22ZFQ3b/pWofpZ1IFPOmx/MzzCNFU3yAlrLhT3qsXk1nWamjdHylxNBR6kmxWEXheZipZQT/ZdGvFDs/o3a6JsQK/OZNekVpJzZ5wIkvD/HLiQQcGOZEoJcelx6vR238XLylY7ukW63cp9SRUjphNgtR8Tj036Fk/e685T/CX+7XX95dQs4wQ99yzBOo5UY1vqnT/dZa3KMY5s+87sq9yspsj8SeTbtR4L4UlrnoRiQqjTrxnZozTunYMhBvtipQ6kkRvUBIRjZdwBK55KnXsmtGOTlTSgt9y6Nv1HLLZnAWChrSyYXSyJ3wb8EwZ5aSYlOKx7BUDpKu1ZGmhpAwZqgy3i3qUz2WOXGNLRAUPzDMKmeQMqAhhrDR+9R9+DO3/Md5xr5R/AZ9QP0X+8clz6eOYXYYZshnUT5z9o3aPmR2w04WV55h9j3uAtdismTYTX/LE+9NsUU11zgpnaFJSIa76U8wvlJiAaj0pEzMoqtgVgtDzE1796lkY5EZiYS5jtseftMpFOpvEJjvH5TO17yeQT6n5DOOsUZ+lPKidJT16sPrrzITCDuRSvwbYJipm6GPG6T/aIEvBpkUQTFstWKbEAu5NvKqzntUelJEL4Y8zHkY69HU8CiPmvX7zM1q5U39eLSfmzwk1+eS5//vBUba+SI/c/P3iF/hLb3iSCkvMsU4fMhkx1j/u+TFH/bTyl1ZJWW/mINLhpAM2RQoNUOlZ4dIF7R6s0INl5mKqSinFyMTYU2sZSZav6aSzZRhlotn6/pQsv6UkzM1MVMMYsn2SC6f2i6sd13XvWnCMdb/jjb9qaLUxj/qJScwzG1FaNToWEWYREHMf56tHvn0sG2GWfaEl9KJ0WPKwPzJxrXGxDRfjy1jQSgZS+jZ+v5FKaf1Luv6N8Oxy7bM0yF3ll1WT7tHbRwc5rEbhqpoi89xHwzzekWvYTDBMLclr1oDOYWeFGGYD+mBQrFuNrKg1fvUE17L/A0M/aH7tIpf6/Knqn8tPaNqnxT9pZbTerd3/Zs+u5zqv1p2ecZhvmIMywj5BcOaR4CiS7wvhmEeYvUcXHLMKObKSJW4X8aG96kZKlv1MRZSNr66vRvkw+V9CpZ5ohOujm/Cr0pk2vrRIPFNajbJMH/49YYApZ5Bx9ZrF6Wc1tdy25s6fjm0hcL3c2f810teYuKXgwzztuqlvS2LYU5re4tP15qRml3Q+LWCAIWelNCJvfA8zNQTXiyV97kiRalnZuLcJ85b2t2Hs7wtdlmNtxEMs5PZ2frV9iSTzNdSGGYFSw8dl9IQus4hBt62BgQKPSnX32aSOvgzFZ+xaOR+jUFylgFsFL9BzKi/F9A57QCUegb9mqFRFvSTUj41SR6THWObP7sYw0zZQCkMs8JswngJzaJEwRzO6d+dikVwI+6F4hsckBtrL4WelOhvhmGW+6sxUGLJXK4+hVpGqWfQrzT9opRNWs3yP701O8Zphvm1V5gFydDRgZZ4yXj/kS+WkYfZMMzy4/VqdbYesM1vLuqWSKErRfRC5wra7852Z+L+XvICuty4Vvs+c9P3iMNRqn7kahelnkG/4u0UpVzqjj673Z2vu9IE6mz0Vx8VF8NMQ2lJYpjtErEdaO3uLUnXFEvtcx3NDcmQhKdyzaS2h8phzo2f1j8a8zeqOuH3KORyFE5F2D7p8uPePugXT/vRk7OsV/wnuZfXG6BgDLP6yKdfe8V+q0ee+v4lYJlrT8aiv09tDAeWqgP2PloIjTxIoStlGGbFs9VI2rhlC098fR/1gv+DVIM+c9N3i8azTBbjeHly+z70i6Yfp8j9EuI+T2pgZj72mdddOR7YvoFhdjNkqM9M8jDrsAJ9eAntTxbLbI/odGH081y1e03hBIUZZnunXfxMC/qoP4WuTDeD5tIP2ZvuqZkmLSPJM5AUz6UDHKBfvOwHtTxovcf5r+WIXVYlu+zyrMOsGeaDvHMNP0vliWKYb/qew6ZMs9Q++kfOkoCe8bR3v/YsVcWqWTxbxG+YnzYq/9T6UzBNWie0gcnXvwy/LPdHIRcXvU/f9N1ywUTLjhCAfvFRCmpZcGi5jV3O4b+68csBhlkdXmI57BNTZ//oJp9CSLgvK7Wc3I1/FIzhqQ5XZPmdQw8XWgcKfSmiE9IzZVRYnlVhGfjJR6AGqQLdmterGrLgoOGaXU7wP2eDnw7vg2EuKFHDgBqmy+42lnRde7YqHV+zIiFHfyj0xQyWefub5pcF59mlkItvZo9YZsH46rZ32r4auqXghn6NPa6WDAq6VtFFu7HLRAyzjWGm6/FSYpgnbNf6TZlGzgzfrx0LJR1faXmmKfRl0Imc/WU/bvoLWWo66zhfgy3fr8E8+Szglvp37I8OysAVvxq6pUCBfhnVqIV/tEdb+MExdjlPD4limAu36ah4KTHM0mP1OMxcpWNM3fdKfo9CX4row0Ax5+AoVBlLuzjo7z/qhbSZMj79pu9yVI2+vYaBkCtPDu171Av/z5Lm5GTZ0K/9rib+1QTvfFixy7l/izHMerb2uiv3elPV2ZnZnOb8tbEhOe9LcZjdkAGJ28I5zF7tErxEfNtNJDW/14FCX8row3QPob/i4+8xbPE+hWzcwevTb/pud8/oZI9mi/i5e0xRf7MiSj0Jg34ZBCiIidyOaIny3NjlHP6p7yxb6uOo7ipTRokGhcqU4jAXYbsoBbHwLQ4dUzrGjMS9uSoU+lJMH4Rv/KvBRk1ZwM3qhQKYIVBDpywEvepWTcw5qR8Fuxx0mF2G2WeUS1xLOR5b+m5dalZqrkNKx5iTEdpaFwp9KaUPiqEYz1R1sgYJ+T/FZMbXHz25EYIf2jHtHzX0acowf5fo/urrW228t44NOd9XzLJllHP6p2CYc0pppizpAwKX2ax0nKU4ahT6opml4WjRfI6tyfM9/nLuKVSl1i6PQjZHDrMTx1y7/fj+9NSyrXjU0CdXv+5403eJ7q+ufGpjXdiNSi6+BLusKhHtMH/mtVfqfeJjZOKYtsmmS8t5X0pIhvQjYLnMaqXjLOXgMAp9UZOnInghJCN54Fp6oddl8yVcJNyv7cT1oFu1MeaopyaNXBn/NNphVsBQxjFLcZj1xpbJAS5WxUJDelv3OXXY+V3RPs5t4Xu86a/t+lPoi9GD/P3LLPGdYJgPK9L2iSOGroH7l76IPquB7bfDuQIHAFvET7p+pLSPoq8vOWzumCBRvy6tmIVkCfta9ynZZdVGd9Vx0mZzRHbIc8/3/0e++IO1sM7+XenMJwVjGCuUYsziCferCJMp+HsU+lJMD4QzzHrJscIA3AMTGGvDpDxXQ4/msJOuW1xw5qK3d77uquGAuGnkw3b/9JKXfGjWNw47zK+5wo3JWB90Z6mDQJCUFHZZs/KTXKNc1CpfPbh1WOl455NcnZIo9KWUDuhNf8J/l77o/yJv4R1v/A/k38QHyyJQQ4/mWiRdt7jgXFab4kvX7PKCf7n2/lw4xiLDHF/19U/KdJj9xPw+Pm3ep3CAUjQp7Cy1ie/Ydhn1p9CXeR3IgN/+38xmQvs7WuP1Yy7au35UBYd5kJdAPKXrS6h9FP08ZlyY2AKB+lWjv8bgXuOZUqEYti3pDrNlmIcBwwvg2Lqt9vC+TIe5hgqV/yYXw2hbWopdLI+k/C9Q6Epp+UtnmWswVtJZQPk9+7iFNfQIDHOPmmbaPGGWC/mn6Q7z5PCSckeZSnGYSw/eHLoHhROU2s4ecE/FhMPzFLpCInvheYMp5OTro5abcFx7aV8N/QnZN+l6BYbZOsxXHZIrWE3I758+KjWGWVVFZ8o4MMHDCkfmaykOs2JO5o4SDx3R2OL/uTAJrsHsAfcW9YpiIFUDZMl+xGHiUboONfo0WObSUqUrv4b+hFrXg15xwptOy8Yvfea1V+pUFaX8UX3E+0s/HNzbF7wxOMyFUZHiMJOwXYVlsVQ8hRO0VIe5+z1gvwaXmu9Q6Epxuets9OGkdRKyptRgrT79xv8gHlfpemPbV0N/ggxzB3rFCW/q8aV03LJtTygcQ91fdpgL93wpDrNmOm0W5sMMyF8waP0+59ltD/i3oj9UemIZpSPHNWv/CxhAn+LwXcBG7lPJyh9c73jDdx42VcrG93gbf2CJtkH9ufTn/29qn2nxe3e88TuNW9NI/0vVj1r9dRF4ggdms2IU8E/BMBMIszjbRdCGpU9QsIZLdQiyC8JT+q3FpcZ7VHpSus/ZTX/5I+TMMOlPqGtd1xiE3eVz6fhKbV8NvVmyZy5xUqs/lZQ3R8yXZJLjPhW7rOq6nmF+zRU6G+n80qNK06wSRG+7f8lLPpQDz+plSI+faqGjSpdBdSWPrACVrpDI+5CPOVNSoAFBTuXVYAo/9YbvnCxvcsJDCQn1mS4/z+FxGUeGWa1cCJZfjb4aafaLPabilo/9zDz+55z/eunaGGaFQOkjskWGZHgHZCPWsVhfOip4EpohUA4FVqCCB0uv0VuqGDs6OWfe5Vx6F/WK8i99UZ2ldTPhkY9v8V1KpXdBzZRPNSlOHTmCIRkld4kR4s8V91Q5xT5PySwvscvq/skYZvXAHa9RR2QHI202b94AwxyrOnWfa6WjkrCOdUXB+utUekImZzDMxfRNxzEffmB0lxldd7CujRdXptPqVG18Sn2fK+4ljMQ8s1x2E/ap+OUohxkMc5wq0DFeZRUmxGBSMYdxaJ9+Crvwy01wlxhuKj0h62+amSrIGCwBSnS/Bsus2UCi9uE7edO91NCXmLHBbCaV21+54h4jm5RnqJllW7dT8ctRDvMdr7ncGzF8TsC3eGn3HyUlhtnu+p4k5PfSZqijdhu9T8UcpnSqU89ODaePu72WI5/5gxJo20fFfmh2eeLIlpOvzvOs92qMnJEZj0e7J+E+lezcPqvimBWSEvCTrh9u+zjGLlu9srHx0vpnK/jnGr8//dorAjO8NP/yeEZ++v1T8ctRDrN6yD3AJPdMXVIMcy5l4VhOcw6zjo/EjwoBSv0gC8cYbKub04IKUdrv1HCYVQvdsAzaFuNraxGopSux9ZWuU9zxj5VT6Dmq9HH+SsRSOEa0w6zimLeCEHpfFMNsG1kyr4yVGnFeqhaXggbDKVAerPKSne12VPoxWTkg6m+mq3kraQLzvF728/9PKTMfLPdTb/iOeSZJIL6TWIFG28eZXbZKplcuGsU3uAnWsT81+imVYfi0Osmv0m+JXY52mMEwL0tw2J07PFoq7H+gvbw9m2W/R8kgLqMd/8TIRpbFp+dEVJS6UUWeh41/8VrX5pM1mCvpbGCbmhCudQ0dScVQuk61IINUmannazHLlmnOzDCX2Z0hhWEeY6dk7jlouZMqA1pGe7FniVIvaslRs1Ud/C77BXqGWcH6qdcrlhk/7gjU0o9UXKTrUytySJFbOGaZbuS+9KUfWcwat/iAavSnTx5gss1BFJNWzkmRNMTGWI0REBJAteSe0slinx124wuSB4eQDEpnWYmuWoiNCshQm//U3kK9rnMm9vrSCmEZeiLUCb4t608rjpp0fZIWklEjfVzqgSWu6xDldxxny8jj+YNh3jbhyCOFZaaU2jmKUsqEh6SvAFDpgf0OtT5Ulx9Y5oTelv6odFYwHRFeb7TiLPewYtGSLGK02MQtU49g0+/FxC+rtkQxzJrdKXSAiUiGOUZLGnuG2kEqAY/02LYSmPll1tKD2rJTDKhdObLZISVe12KvtMN8WImTjK/yC1prX2sO2h1v+A4TRSVUn1qTx9K4xIFhjolfTnSY/XzMeWYEYhhmx+BLTJxeayBd6myp992BWaKcSk7UaxlqnUkhj7nZUE71CuQ9eeIEoJf9wv+b2q2yPG9Y5n5wbmNtcb+rpQ9rlaoHPapli9fKZOm92jHMRRjmEiGgYhzmN3znhK6XlpOhFrO41NHW3Hc3j9n3pckrZ3tqyp7TUbfY/Lemt8W/g7CMeKyonmzVMZOuS63KJaS3bjq5Glu+sjPMNiwjNwMgxmEGw0xlw7N8B0xzHGFZe2WBlZzcnZb+jMTXyobv12IUP/X6fz+i2DB+uhEC6n/Zi+usNOQw8J963b8/HXDauHxq9dEcspkroybDHJMdwyWLozHws2X4jLN/hHvMfTEO8xu+Q++et5ZSHWVpj3x1/7Z6vybLGK2giQ8apnkqp1blY9uRq/4c5M1NPpPMJIm61trjtRgs6cxgS3pQSwdyYCRdj1qWTUi+fixzjP+onvH9zhBDHfJPY8Mx7Lei9VNnysgcYvaol3w4+vucH0QH5SydcN2kyy1VKlwMMUe56I1/joH2Dbqk65rsomIH3czXNZZoe/9+Tfmn2qy55z+pGObDT6L+tC6fIMuc2b+M8Vcvfdly/uVVDHOJI7LFMMxzIRlUU6StU6yI92svzecwoqEygpvKBMlv1nA47eMk30EeLPE/uFI5g8RVOxmWV2tQ1qEZDPEQvUnFwVvCcv9RSIYwfZIgI388rnUsdjGGWTXwjldffsjgP6ZZGvLk2Hw5w187EKiM/04+Hee+HIZZzWjdLH2yeigX5rGU42ziJ+XKL+SBcJPryCzz7D+WZS6lh5zKfXTFGFaXIeSESQ91qSn3XPj2oD8S5HTkNL/mijHv4kDkhf1HM7Fefz8lftmuLibpaG6WWRTDnIRkWw9zc6xKoMcxDKBEO7nKsgn8nXzM0tMS1mKYrc5PmOYKS7XS5TvXPinM5cAwC9ab2v2zxNhEzTKnsMurHOZPvfryfWgFf83/H/VSGTHMakYbnOiEJkAN/V+KIV3q5JLlyJmRaAd3dwSWHCVpQk8e/eL/sdRlit3/5Ou+/VC2xChUflHSNWWdW4lG3bFuDj+8xzav029J8rJY3PGaK0jnqSnxy6scZjDM811b+pI+V1Yyt6FV5U0T39sv8AwRGNs/X78W5NYc3vvse59j9qaQDiRWm2qzWHoiheNMSPSD84R6jZ23m0cl60/t/rlGLkvvULLMxRlm1VjlNKtYvmDE59nZLvY+GOYTITiMGOheGGa3M6tJ0GzoPSO5zNWvpYGvHWbZOdJYuS+N53FNqX9tJmvKFh56aEf4zzoYmdtfW8ZLTtSa+7N6E+I//A9kxveo/pnKlyg35TDH+o+z4oz0Py9LyI7hriUm62JOlllODLPdNCYzaKoFpjJZkSNeGFcOeMu1Rfk0e4St9pfn86xL/D+HQVk5P3N57SXiTd1ODvKNMMXJjxiGWXY/BcOcrBbDC6ns8qqQDM0wv/ryQ0ZS8+11ETjm3UsFxTCvFx3/N1tiLUugyXHXdcsy4Yhnkt5MLGDSm00+/OiX1ItltoB98rU2prlJCNlWmoNsS4DTi75Ik9+nX3MFSR52MobZOs1ml5v1mG3auLRrMQyzPYqTNxG5+mRzypAMxepSfi/WWNfetS+BTZCye33/b4e0mqEl3jZD3seu4NWfCws5LLMLw7dW3mkuco21wSnPTUIyBOuLNBnqGOYh1jDNn4z1R9ewy6sZZvWiypZhlRcM825nOqfc7QWUbKZhH/dVd+ifMsy2fqXlLc0QUuFWWi66/P2/dZe3+9EveU+Kv1LsWcMcCvaANHLl28dFnqUUZcowl8ezVh5/aQyzJmRVPubDb4t/aZ1cP0dKanYMty6r9PWOV19hdv1tZFSlMMx2N7dUwomS3XR3N1N+d01H0IxphuGNezvXYOO+I7F/aOO30f619D6ngVk7QxnGn5bwz9leaZPxOfukSaxO+qc0eX76NVcW7d9VGeYtg6mYGGZrwIV6zJQdcmLoKueCTdXtU0vGnByO1HatfV70ErreUlTUrrMrnxMr+cnX3sAOn1b0gZMc19qWpfd60g9p8nQZ5iU5r7m/Jn55U0iGps0Vy6xMlj2xw06Bh7QeBwrgxP1HvfRDa9rL7h03JMPucrZDqYRrSgbU7m528aP8PjvlarRCNgRDgv67rvHQHr3O57tI/oxZ3n1OEz/lFE2nLPLxNy0MuebL7eckv9KmrRf9kCZTc4DJsv+4xv9cG46x2WF245jXKr44hrkVimFFPak6ZWgpjer7a3UZ7xkEeloKtflCj7qTEzI5GznQ8P3HMIlldvvbJ157wzRioWF8tb4Uqr80JnLJ5h7phbOHzB4kIaV/cuyXS/IJ3S/JMK9llzc7zIZhDq1JxvV4WQyzEf/IqMm6Jg/LOPQmF0/KOqzt7L2+Z8MvpOq/leukfYNns2IGmjMolTg4gePkdboZsC95xATr9uYs68n70QqEXL2QJl8dx7zRv5x7/9KXfdg9cy9puF79ov3KVpZZCsNsZrIjnDZhujVk47VBrsX7lDPYEU9j4Fz8OA7WSb1O2MPKUZGg32v6r2KY53/+rnz/qfbvP+alt7PU5E+8RoVptI/vNOvCNv15zEt5ZDihVhijC3M/mfohSc4lWOYt7LL13TfpsHKYt5xMJIZh7mDXNqWjurQLHkzzpm6b5eWeQi9OErhqurAuDX1s2lCW5XMenJWjlOGYgKblY9tPSXRkMSyZC9Gb/zrpn5JYZsUw5z6pcQu7nM1h3qLfUhhms/wz5gcNCdpnnP3nON+nNLyWYT6FD2V9tui4tHddRrkl/R02kTgrFjnqb46lkrvUeyoPCHenuVe52HZzkI+avNSsx3TFQXY/rYlz7nEODPPMQCWFYR4d5o1nhdtpjJ9pm8E1OcNse+BC5nLKeuU2Ci2VNxwEUCKTPAP9HmSxon3q5D+bDKinvy0wWhOG0UnqJF1OHJwny/TX1JPe5M9B7rnGNZstIxfTXJ1hVsDoOOaVEzdJDLNiRQd/90SIlL8L2lcurvcpGV3NCgQi7OfwoaxbLmPQQjlWp926ctXPyfyKuv8FY5lbkPK2OnKNZfZb9YnXXL+toQ29zUEmLt616wPZN6S8TlXvePUV2RLdX/byj2zes7e5AOMwH/IxH+369ilCP4GLuS/FaZa+S5uSJVizu5myfm2an/har8E/m2VrNHuE8ZlXMgeNv9cSqyV9ib62c6p6gXFQx/G+tn749ZHcTznIP36kOf2kCcsIHekZ51/a97du+LNk6Oa2bWGY1Thx6cs+vLkOHArQbJzg8ZLS6A0M8wo8KevJQe9y1UG6/hb3w00ws/nJ3IR/sn2tDdQnWcdG5cdFBp949fVHK4S16zaRd6PyjbEvtXHONR6pckQyzIZlvlxFh+mRwj/Zy58h+PflMMyhFDY5VaheWZQMrmE41/8o67q+lvXf3Ipz/RbwqUE4xRyfOpasSasDtYTlek7Yh/CsXUcJco7pv7Vxjqlj7DNzDHOqf6n8z8s25F5265olJEMV+MkbVXq5MAHhT+jcazEM8yT/5wpqtDgFFlraiPs/ZUfMtYQGtvnYNElfkq4ZGrLf/9tAGGxJt5lrkwt1OY95Gc/czLED9MdffV1T8uOG9yn8Hvuy98aKochzivWm7g+1vsdNL7YIVLHMp/zHuQU9//kc8cvqO9kc5i0HmEhimId8jwJ3Y1M6n0Me1Qw4UtZ7i2Eo+W5OPKVnF9jWvkNYxlYL3/D7lBPrsn3G2STISB5c8T0Z8rDf7Tg4cUMdGclT63Dm+nDVkTX9NUd6uRzxy1kdZlXYp25UYRnpadXkMMzWwK7IS+XGPw5Ms1UvHuVRdsLp8lme9lPWf41hyPlOCfzG+uWRh9jy9uYkz7H32pMq7bhoriXf5+Ac5exPhjmtJz/ueMbi85jKLLPLgKP/5ewh5crSDvOGtKO52OX8DrOOY073mKUyzEOHPNuNJw05jGlr9ymZWvdI0xDjtxY/ynaUMyPTkucY5LX4WLzx/sGaJfZfbd3lRWQlR7pwd/K29M1hU1shObc0udeT80gcarertNxicSj9XG2ct/Qt/907XnPlYaq6jkDMxS7nd5hvvFxnVoqLiB2fu0xKlgzheT4pOyHFBg3K9uQ0IBTY5Kxvb2X1vvnPlXftuNVauqeYzJhf6/jEttNiwaG9qXWOkSPHZzhgnQMXN4Z5jX/56Az5l11XPUebhjKGWOaEmBwpDPPoyMhcsqZ2MCnx5Mw6j5v01s2wx6kp3l+zArYGP7P5b7qE3+u1lIE760ApoLBPvPq6A1E6H3IU0vfa+jBu/pPdP2vjnEvFh5CMFVklcrLL2RlmVaDNlpEyExDFMKc0PJWKr/w89RJrylJfriUu6kmBa1RqtDcXbihnXFpT/eTjN153eku14DywWqe99kkZvHM5Aa2Xs0W/a+uCZpg76H+PfXndrCS5dHwLw5wzfrmIw6wKnRxkYlE7wTjLY5hzqQqvcqidSQ6hByXazKFdvDRLVm2szowMXHSIp+h5R21HSZaW1WvNVr3moAfTTYoy+ycHnHNo6VqGObezXMxhVixzSp4UMQyzl+fRP8jFz8va2n3qDvhxdVqUcxAON/xCeNglv9bkyw3fFvFzV2E+fuO1h/Eiz9Gux7tD/BAb/zv87kthvXI4Ai2WYXQ6FHIYr3+19WDsm9YNmlsa5td/pq796fo99uXva1HFjur8qVer47ETYnwPS1yPfvnfZkub7CKdHdSBYfblGbgWlVYOIRnZ9AkhCo6dEKxXybuEK4cmheo7F7I0bDBKt/fTkA5B71NPvLMZpM4LmmyW26iPtXXgKKRkY3tW+HPF+3ftSUmu7rI2rVwzDLMCyrDMcT8pDLM1KDK3/O121EZOOp6R80mmWbnHvi1V31PkE8ovO2Wy4uxhD09JYb96kJVqY2495iD/3G3ipgscMM6BiWGY0345M2O4X85OWduMkQ3eAAAgAElEQVTC9SEmkUwQGOY2mMQeNv2JDiKN7I+x/RbPjf32VKy72WRkE7DbFW1c787OyCfhacMunh6Y2AL6W9uhm2z+K9A+pd/jeELf32vjm6v3TGKYI1dac2fHKBqSYRjmJygNmfEE7afHll/2so/kwrZqOdLzO9ZimKsKFR8HAgsIxPSLj914LeYXAV7gcUJiLaV1FMXARvonq56rLffS7avNT9TGN1d/OB3D7Guo+WpzDLOqtGKZ55szDV2XEpKxdfdw7Q526vs1jjSVjGfJgYizHklrd2y/kL78m2NwlMKI5cCidhkU+lpb3hRtrCnH2vjmarubVu6Ybp3Ssup+idjl4gyzYZmX45ilOMuqvTk3RXDbRBDDouXqILYcyXhyky/qcwiV8C3yievUTTUTRstfofVPSO34vhRmLLc9pCpP6ymB/j3uF+tmcTjJMBO0fxKxUaD/18Y3l76mxjCXYpdVe4rFMFuGeWn8kRK/rNopmRGNZdJydRLpeEpjWntrz5r+IJ3Rytn3pbBjOTEpXRa1ftaWMXV7S8vPL782vrnae4phnvMvm2WYNcv8qifsTfC7nbJN/4pimI/yrrpEvps0pL28AqlsWo7OcpzHVg6eJigJ7TEItNUftgxEH3vVNTm6RjdlPO4X399NW2s1tKZO1pRvzXZTyLomtjnbpxnmgP/o/78ku1ycYdYO842X708Nh1Ic5mBex5ggbjfo1J8yMXm/WkgGk/YHQxZQv+MgMqXDIcqZqX7HyndrPxg2/+0Vj3DmbKLH9RIeUhyAnM7E1rJq6+OWyefWtk9DpOT1Pyn9xTLMMfRS8w6zdZqHmYAdIg4zBjkOsz3Va2s35vl+DcMmfcmMp6RRqxACufqAdGartAZJcQRK43SqfC46WFOWXDAopQc1sc3ZpgnD7PmPLsP86F/Mf7Kf346iMcz2Y3PZMuyCtCiHeTbvoh+K4udhbeP+VmZtTQea5q8N5bFsA79wHl7UfxqyxbN/5NR/s6nqwGjhr2HaV+AgxSFYYxvXvqOcxLV4l3gv1yR0DR7S+6GU/uHGMLsLlz7jXJpdJgnJOGKY3ViU3dnuspd/eI2us3tHep7VGoZNep5MpH8LR25w2kRYImuDdHaL0kBLcQxKYsZV32rJjiseOXWgFrY526DKCsYw270vhz1yYhjmwWmeQVIUw5xbUxiVV8thZgQBqtIhAiX1vodBm1plpDgJuXBrQceoZdYCJjnkT41rjjrPlRGTVo6CXSZjmLXDfCJbxmUvF3LSn3sykoA8jm6ESUnHIdTRpOfJLJ2HE+WfYLAj+mfpPKbTgdty6ofeYFfihs6B+5MsqBH4PO6XPlBqDGddrtarCHx0KBQD/aJ27FrDx4govf9L0f+YLBniHGawzKxt7GLlajnMixXDA0CgAAJU+t4L21VARElFUjtlSZXL9HCrukQpm1YxWqMilLiuqV/sO0sMM5WzTMowW5Z5Ljbx0UIYZumdkboDSscz1mDgOVoEyPX8lVcHs/BxiuWWEnP/eCHM88eE6A2VPKTgFdsPqXAtbZ0/dePlJ+3jYwiyY9g2kmTJsB+bHJXtrDBIimMeBrijPKs7kwxgSALg533kf7/0ErXf8U4eJSwQ39b1Q0L9qZhlV9enS8QHCzEsqeNah2IUxONxv9hO6MbHXnW1GUEK4lGj/JKT1B77V0k8SzvIfvkmJMM7l9q5FsswW5ZZdcj9bj+cyy0lhlk6I0rdCaXjSW148L3TCFDrt1ubj77y6uG8w1gGCc+VzbJSm6EDI7rdYvXar6TELysN+NSNV0z8Rdd/pMiM4WohKcOsHeYbL3fPBNZ1EcUwr8gnWiK/5Zq8pkv1oHYopOfJXMIb99fl503FjVqvQ26AZg81c2IZRLvXB9eGUeWDR06dGRlQPu2rhXcOth94nu1y6uf2acv6EjjFL5PHMA+hGa96gjJ/g/1DDPN6haJ8k7oTgmGmlG6f36LW6VMoKzZs/J1Yg9QP4f4kawbwCK9ZN6ovj/+lDyYZJfSfkf+svTqSJLgTD3/yxsuDK2+Uscu2iuQM88AyOx6zmMNLVDofwT9q5wIOs2BlYtA0an2OabJahp9zh/13fXcZ96cIAJ/pdAL60Zd+SAnJUOEYIY/50b/4EXL/lfyDVm0/8aon7O0Km0yGWR4DlGO5LMZpsM+czFMLRkkco0TJGHJ0lq3ea6bM39Tl7xPHfRO6Eto/D3yAT6f6kcrMp4zJ1M/OM8xnuxrOsmp7NYf5k6+63Oz62+/kHI8NhjlrfwLDnBVOFLbbNRHb5272skKTN/2mnB4hgAX604++SWGXle0LMcyUmTHcgbOaw6wYZlsRMQyzkLyYp3bfU3XGXnc3I+9ucAVucxaJluL6PvqKqzDBAQJAAAgkI/D4X06L/U7+AOELimGe+9WIX67KMKuPa5ZZZcl4+YcJRVDuU4YRle3yUCxl94CjdD3h1D7qUKJcFuajr7ROcyjvrv0S7s/nJQY+BgHoR0/6ISkkQzPM+jeukdQKx6juMFuWGQwzJ/dimeErydSBWV7GH/l34/sL1YpILifZLwdMcylkUS4QkIeAJHZZk6ozDHMtdrm6w2xZZikMs2qP3uU+5FF1ZvZ2E4qAvyWckB5wk64XnNpHsRJCNdxqpln2whXaB/mCKcjAhEhil5V9HRlmY21rssssHGbFMkthmBWg01yQVEMq/Xdyscy94EUvoX6/mEs3uSCoWGZzMuo4otpr/6/1PHHf4AV8pjhAP8zMRGr/kMQwT9jlw4SyJrvMwmHW0SnqWDohP5tHVUhzgs3YyjL3gpN0PeDUvq06yaktCM3gLA3UDQjwQ0CSs2zZZXdiU9tZhsNcQOfNRp0MayuNrMGmsnm94YO15vJrzdKWIUNm6aOvuPJgFeyR4Va7LGPmXO/3JkJGWyPcNwMv8Bn0Afohrn980y9/qIBHU69IP34ZDrMjCyksc4/M6RKz1yMm9cxMP19e0juJSGAToESpok1AYDsCMhlmQz/Wjl220qmWh9lXDykOMwa07R0fJQCBJQSkDQ5L7XXv//NvXpnyOJ4FAkBAOALf9Cuy2GUlLs0wM4ldhsNcsAOZk7rGpWi7JGpDNXBtloiBh1kkhj7E60NqCFDBbl61aOU065N/7c87ym04GRr3DQLAZ3LUHfTjcHK4kP7xeJHhGGZ85BCKAYe54HAHlrkguCi6SwR6ZpRDAgfT3GVXQKOBwAQBkezyqy4ftoI95pf+lk0kBJuKSAnJUJqsU6XpKbxlNmw+ZmeegvvAB/pxYP7C/aOXDX1rfYCPqvCM0EFuzt7jgVE8MK24PjCMwAP603j/ebzUcIyDAwWGeWZ0EOUwT/Kmzq/5jHlVcX9uzRT42Ly7feoHGOV4F9owzXPZSNyFRNw/zl4EfAwCoaxOwIc7Pt/0Kx+ONxSNPPlJxS4ffpzYZdtT2MAozWlmAywqAgQaQQCO8jpBueEZ1v3x3R1cT90f4AE83OlCi/ogMhzjxisOBMCOVfwyHOZ1Y1PUW//8iitnT+byT1rC9fTkJeDRHx7SNqxEGYgCD/3zb6qB5vDzNrkdfQ73J5vggI+HAPSDvX5806/KY5eVFlqGmRu7DIe5wKBli8TGv4LgomgRCIBNzi9GnT1D8zO+xzNe4z7wgX603z8khmNoh/nAMHOKXXZXH/Jb7ZUlSgrJmOxgxxqpIY5bXPNqfc2OWf0lLiGuNHfFXvvn37jihLtsPht2F3Af+EA/uPcPqezyJ151uZ7wc2SXwTAXG7JMwYZlLn80cE9HcQPPNvUJbHJhY+MVr5xmPfLYH3cPAPWDvKCv0f1VLLv8qsvZOstwmAuPYciTWhhgFM8aAbDJdcXjMs3H0yx7YE5ouo375kAh4DOfQwP6UUs/vllo7LKylophfiyjvMu+BWeTh3kgQvYqQ6iM30df4RxhCwYFDEoHDAo28PGyXQPT3ObCBBaUIDcsoDozFqnMsrKa6ihsjnHLrkWHw1x4fDM710N5LvF/jIgyRkSwyYUNycbi/+k3xtymG4vC60AACFRC4Jt/9SOVvlz+s9zZZXYhGapCkjb+qfaMu9bhNmN6IGt6ACe5/CCS8wuabR5+WPLCklcHS16C9P2bBDvL2hk9O2NH4CIkI+cIFFkWTuLCSVISTmKTvBwY2ZWbfwxMc/MiRAM6REAys6zE2QK7DIaZqON99DevRK4MBKY0GZjz+F/5EFEvwWeoEDBMs4xQILQDcuxh7VZqGjlr81pgl1k6zDLDMq6QQDAe26VB2wPjL+4bBBoi2MEiU7mt9b/zT7+OuOb6UkANgMBpBL75P8mNW4bDnEH7pcUxK0gmx9YOHlQILP+kk7lImlPJRPD+9KQU4DdFYNQPOMgZDFbDRagQDa0NTkgzroEH9OFAdFi+o1L/kB6KofmkBmKXXe6PnbkX6TBPNtywgxwV6gQB6Ut7nYgxezP/6defgKCpJoOmEJIhNSTjm//T32bv5xwLhMOcQSoinWadYg4/IECHABhkOqxb/5LeELhXjI/DsM74Y7gPfAYGGvpx5K/n6B/SM2K4thIOc4aRQ6TD/BtX7PY7e0IS/pqTkoBDDhzAHGcwOihCI2DYZvyAABCogUAvzHJr4Ri6vjUUIuabEh1m1e5pLtQYJPAMEJgiAOcYGlEaAYRoINRBaqgD93bBYS5t3daXz9ZhVk2S6DQPm2wcmTWURGGSjMo2AfUvkwSlhw0f600X3qRAwLLNR8ecHEI3bB1w3zsGBfjo0B7oh0Egtn/AWaawauu/AYd5PXar3wTLvBo6cS+CLRYnUpENmoZp+Fl4cD3NygM8gIebxSpOH3pylpWRbCl22SUH2Rp4iQyzAttursGmbJnnJ/SQN5Ot0UDFiiLwj//93yGbBgy3TMNdSa7f8p//rmif5Vh4i86ydvI5gunWSbLTHAplwP/bPb+rp93N3G0H6lcGAc02H60xe6MJ7nsxGsBn4m1AP7R+9MYqD0xtQ7mXXSsKh7nMmBJVKk7aioKpmYfALDcjKlQ0AwL/+N9VNg1sjuO+iQz140lB9cgstxqK0URIhqqkVIZZte2ff+NyDDdCFnjBLGfwwFBEswj8k3KeefolqBfkwm5e1yuzDIeZwMRLdpqn6ZvceUz6poFRFHGbDPB8Hrx7Nn4E3R+faAgBE+OMHxAAAiEEemWWW3eWdf1bUGvJDrPCf9gEmMd/GxkVlGcQKDh/QBhGCxYEdaRGQDnOI7EZOqDIEn+4P3+AE/AxAT9y9OOb/3Mfx12H7E2rm/1cV4raliZ/T7zDjJO1knWCywtgl7lIAvXgiAAYZ45SQZ2oEeiZVR6czUY3+rm6AoaZuucEvjeGZtgHsI2Y+zZzOMtMOg+qwR6BKePMLqQUe0mE7CXhuAUVzvJhoRcOM52dls4y69CMX3/Cbn84IQp/VWJztemT5184y3R9H1+ShcA//pqKc+bo2mB3HOSSTy+/5b/8vayOu6E1rYdiNBWSoSrbg8NsneYNeolXCRCAs0wAMj4hHoFJuAYW1LgvqKF+7nr8gr6CVR7NlxRnWbWoiZCMIUhhr/hG+b+BabY8jGVacW14qYp49L5pQ37vQwtrIPCPv/Ztk8/6/ohfJ9w/PXgDnzr4gFU+th5wmGtY1I5YZjDNlRTsxGfBKvOTCWokEwHNPA+xWKHgWhurhfvz+a+346OcPx0+Mxupsr18w3zIkB8Y5XlbJMlZbo5h7ik0Q7UVO8x5OAQwhjzkgFr0h8A//Nq3Bc9DsWiEIo9x3yCQgk+IIZ2TA/Dd7b4VcconjRIc5so2u5dYZs0y66NnjcWzhIudketrx2LhvgnVyI0PQjAqd3h8HggcEPDDNgBMHgRSwgggA4N5CmZ5pNReKdKc5SYZ5t5YZjDNdQwFWOU6uOOrQCAWAcV6ugzqhPF0gnhnzy3q/P5Wh+8f/tu3DYxNT/iCUY7tnYrA0hSWqF+TDeqJZbbaZvKYhk48wv/nT8pahwucZVE2Do3pBAGwn2FBb3WQQyX3gnkp/KR2TYnOcrMMc48s88A050sTGQ5u6zgdKZxlqSY8T7s+/oon6H7z2F/q+4jbPGiWLeUf/tv/VPYDjEv/1v/6/5HWTiLW1BiSCqzgx6Q6y3CYCypNqaKxEbAMsnCUy+AqqdRPvPJyZ1P/Hk5zg8IdNq/5SR4avuYWJqBY56MkJ43gCyZ5e6eGw7wdw+wl9BiW4YZnaItkt/1pRnjGIuG+gewEPt/yX/4uu26iQFkIfPyVTwimvwLTLEfWnFnSVtlOzphazW0VW449T7Kz3DTD3GtYhttJwDZvMxlglbfhJ/3tKaMcjmB67C99RDoUaJ+DgGFQ92pT06a/vTpqufBbi3+vuJfuxNKdZTjMpTWIoPxTeUo7DkUO58PfIXcmgVo2/YmBUbatmE0D4DQRMc1NyxuVr49ACSYajjGdXHtwlpt3mMEymw4xfzJWI0FjwybG8vVFfBqdAW31S8pZttloLKcce43wjFaljnoDASCwBQE4zFvQI3y351hmH2a91HXiZKdeGWdum2IIuwc+FYmAzn6RwCjrjjbz/GN/GdkzIiHHY0AACAhAoBdnWQTDDJb5uMcNjHPoUFSVT3zYNDjjYgu5D0ZZgDUu3ISPv/LyQy/ZH5hlyyuPm2rNRDP+PmKaCwsNxQMBIMACgZ6cZTEOM5zm474znMTUIeX8rf/171kYE1SCLwITRrlQNcE2FwIWxQIBIFAdgd6cZTjM1VWOpgJzpzE5SelmK9HifTDKNPrU8lf0hj7FKQ9pGG3s/OGvDWrKdB9sc8vagroDASAwh0CPzrIohxks83LHVhk1go5CyEFg/n/EJy/LvfcnKNjkUxiDae5dA9F+ICAHgV6dZTjMcnQ4qSXu5kD7os8oc74Gk5wk7m4fVvHJw4kjduLnM8hE12Cau1VDNBwIiEIADrMgcSJrRpowS+S/TKtB3NPIqRmHU+9PfewVKi1c8GC+6llkwDb3rqFoPxBoF4GenWVxDPPAlqpjmPBLRsCGbNgTlHaHbBm1ruEkJ4uw2xd02MXgKdsYZes587qG09ytmqLhQKBZBHp3luEwN6u6NBWnZJ/hHNPIVNpXfEZ5mDQH8pFzuQ+nWZomoj1AQC4CcJaNbBUnI/KH0IxyYtUx0A6JPzDQh0+euoZjXE4uvZSsY5OZb0aNrR9im3vRWrQTCLSJgLK3j/vlvxXrK6ZIRTQIcJpTVAHPAgHeCCg22c7yT8Vc2ciMUGu43X8cTgfkrXioHRDoFAFlcx//K38n2k9MEa1oIOAwp6gCngUC/BConRKOChGEaFAhje8AASAQg4CyvY+DszyBSrTDrFoKpzmma+AZIMALAcsmzzHKPkMs5RpMMy8dRG2AQK8IWPsLdnmqAeIdZjjNvXZ5tLs1BEY2WYoLbCWQ1h6wza1pLuoLBOQg8PFXmPz1YJePZQqHWY6eoyVAoDkEBiaZ80k5etbtbZEufA22uTlVRoWBQPMIgFk+LcIuHGawzM33YzRAEAKKSfb9Tb95uG9SGIFtFqT4aAoQYIyAXeEDsxwWUjcOM5xmxj0VVRONgGYtCjOy0ssH4yy6i6BxQKAqAh/7zcPBT7sdsmKckERXDjOc5qp9Eh/vCIFesltQixSMMzXi+B4QkI2Aa6vBLiMk4wgBZM6QbQDQOnoEPvab/24M8tV73BxKGddZ8Xjcr/wtvYDxRSAABEQhoFll9TvY58f/6t93R6CmCrRbgOA0p6oKngcCIwL+kdTWPcbfw/gTOJo7Jz4I00CPBAJAYA0Cvv0GsxyHYrcOM8Iz4hQETwEBhcAQ45bT47PZ1vB3jPFegS8YZ/RRIAAEYhGY7CnZ73aP/1Wc5BeLHRzmWKTwHBDoBAE3i8UK/82s8BEwrPjOiDNimzvpnGgmENiAwNzKIA4niQe0a4cZLHO8ouBJuQiYWDa4uHTBFOVcfbDNcvspWgYEtiAwZ+fBLv//7d1tkts2FoVhaSl2Mp6q2J69xc7HLGGSOMneJnGqJnHspWiKYlOiZFGiSADCx9N/utQkQeC9F+DpIxC4j2jzgplovi9hnF0+gfG20+W3RgsuETC/WV4ggEBHYGq85yzfnx8E8xMzLwHenzyuyJfA4Q3ofKuoZgkIcJwTQHYLBDIlMPUc4CwvCxjBPOJGNC9LIlc9jgC3+HHsS7ozx7mkaKkrAusI3HoucJeX8SWYz7gRzcsSyVVpCAzrHe82u8129Hqdz3jMyQeOc5p+6i4IPIpA5ypfex5Yb3l5ZAjmC+yI5uUJ5cp4BM6XA7IchXcVl76ryXGO10+VjMAjCMx5PnCW10WGYJ7gRzSvSyxXhyVw3EnPahY1rGaRy6okHOew/VRpCDyCwJznA2d5fWQI5isMieb1CaaEdQRsGGJB51TfJBDP6/qqqxF4BAHOcjrqBPMN1kRzumR0p1MC47low5w0v/u5yjjE4/Ds6990RQQQyJzAPc8Hq2KECSbBPIMj0TwDklOCErAsXFCcCltAgOO8AJpLEEhA4J7nA7EcLiAE8x0sCec7YDl1EYFPv77moHKQs3LQOc6LurKLEAhO4N7nA7EcNgQE8508ieY7gTl9NoF7XIPZhToRgYAEuM4BYSoKgZkEljwbiOWZcO84jWC+A9ZwKtG8AJpLrhL49MvrfvGHQ5I9vezmc09gWBwEjyx4cJ0NaAjEJ9A5ykvGv+dvfqftIoQH1IVQieaF4Fx2QmCJcwAhAjkR4DrnFA11qYHAmucCZzleBhDMK9gSzSvguXRzcA8O64YNULabzW43cpy7btpZrI73BPDJNT84zwY2BJYT6J8Jy8c3zvJy9nOuJJjnULpyDtG8EmCDl69xDxrEpcmFEuA8Fxo41U5OINQzgbscN3QEcyC+hHMgkBUXs3cPbNRno77hC4OGfnOeKx7YNG0xgcO7KwGeC8/fmre8OBAzLySYZ4KacxrRPIdSm+d8+vVfqTZsc5/RO4IBnkN4Bub57Ov/tjkIaDUCTwRCPw84y2lSi2COwJlwjgC10CJDOgiUW2Dl1pDDm/M3G8/e2Fmw0OFNte8gEOsbRs7yHUFYeSrBvBLg1OVEcySwhRQ7OAhDda2KZtU8qwYeO++1/mDucyGDnGreJDDMTY41/nOWb4Yg6AkEc1CcnxdGOEcGnFnxe0fZDwIIBCPAgQ6GUkGJCKR4DnCWEwVzdBuCORFzwjkR6AfepnOVu5/+m/6jp+AzHvIhXH8wBzrcIHecS/t5fLyoOZ/zYTm4ROM/Z3l+bEKeSTCHpHmjLKI5IeyEt0rhJiRsjlshUBwBLvTtkK0Zp/D9nO8anrejNX0GZ3kNvXXXEszr+C26mnBehC27i04d5c9XSxsqPPVumeM9AXwurzYoP9bnR4tudDdv9mybo4vvfK7JrxbnmQ9cL2wjFZzvVPw4y4+VAQTzA/kTzg+Ev/DWj3IVFlbXZQggcIVAic5prmNQiSynUiNHxpzlxw9lBPPjY7AhnDMIwo0q9MvDDVuW+t1vzYwDDm3nwRoHe/8NVeX9aA2fVE+FEuLw/M1vtFqqhLhyH0HIIAhDFQjnjILxVJUcnYb8KKkRAgggsIxALGe6lrGbs7wsr2JcRTDHoLqwzI+/vN6/TP/sjZ2wFiIMdtnRUR4m2Q5Oms/9pGM8eoddPsgH/cF4EG88fP6Wuxzswb6yIIJ5JcDQl//986vd8FLBcztghcZ7s7yPv7y2oZ4N9XLeGE9+yk/5eeVl4Zo28OQu33xkJz2BYE6Ke97NPv78ejd+bz7WV1bzalP/WZ9+6dZPPliFjQzF2ju9PkdNj1xxFmf5XOL4Tiznpz0I5vxisq/R2GkeD3fEc5iATTnJh/nkE7LZ8Z7AlAzDBx/5oX8YHy7/mzZ3fCSWwzznQ5dCMIcmGrC8c6f50AWfVicgnu+DfXCSh9Udpr7cdryfo4zP5X8N5If80D+MD5HGR2L5vud6yrMJ5pS0F9xrymk+X9iceL4M99qc5OEKG2fYOOPSvwfyoyegf+gf+keajamI5QUiKeElBHNC2EtvdXSazx/hlz+3Lp6Pc5Ln8ToOhc4/lUh44DGWzPJBPsiHWM8LYnmpQkp3HcGcjvWqO11zmm+90lH7ahvdEnBebfJq061+4HiJrz7Ja3lbf94Sy6vkUbKLCeZkqNffaHJO850LTZXsQPeL0XuE1P8I8S+QPNfP9fP6xwFieb02SlUCwZyKdKD7/P3u1S6WXnz+9rdAtQxTTDf/mHXs/4NY+a5cqykaX4wvjxwHvnj7Ow0WRi4kKUWwkmAOe5OPP786vKM9VfLwf3mM40sd6vFWpTHr17VZ+b0/GSP++Mov/Uv/Mr6sG1+J5bC6KEVpBHMKyhHuEdNpfuR/3Bwfjo/84/waB4wDNY8DxHIEUZSgSII5AeSYt+jc5lhv7R7rPcwlHP7ic+8x4tETkA/yQX8wHhgPb42H5ivHVEPxyyaY4zOOfoe92+wHAQQQQAABBLIk8MU35itnGZg7KkUw3wEr51PXLDvnXXTvFtb/Lro1J/Rz/Vw/f8w4wFnOWT3NrxvBPJ9V9mcenGbfkPuG3DfkviH3Dfmtb8gdN6Mq+owyznL20ml2BQnm2ajKOJHT/BgHgXODOweXg2scMA6MxwHOchm6aW4tCea5pAo6r3eazxd+8vl0ISg88BgPf/JBPsiH42NOf1jbH7745j19VZBumlNVAZ1DqcBzLDtnWaaal2Wy7Jj8lt+WH8x1HLBsXIGiaUaVCeYZkEo9xeoZpUZOvRFAAAEESiRgznKJUZtXZ4J5Hqdiz/rMaR5aMjXZzvGeAD6XJyPKD/mhfxgfjI8Xx0fOcrFSaVbFCeZZmMo+6XROs9dSvJbi9TSvpxkHjAPGgZDjgDnLZeukObUnmOdQquCci3Oah3ZNjZuO9wTwufxckR/yQ/8wPhgfN5zlCkTSjCYQzP5AV2UAAAvwSURBVDMg1XTKcV7z+VvQ5610/PQtaXxOCcgP+XHt8SE/5Ef9+WG+ck3q6HZbCObbjKo7o1urOde3i9XL6gdWP7D6gXHAOJD7OEAsVyeNbjaIYL6JqM4TPrx7udtutptOOfuNgzzQD4wDxgHjwLxxgFiuUxfdahXBfItQxcctO1dxcDUNAQQQQCA4AWI5ONJiCiSYiwlVnIpymuc5CpwXnDiwHFjjQNvjALEcR4eUUirBXEqkItaT0xwRrqIRQAABBIonQCwXH8LVDSCYVyOsp4AP77qXAXeb7Xa72e12o+WStpvu75tt/9txfOSH/nF8Kcv4YHys9/nw5bfv6aR6ZM6qlkiEVfjqu5jbXF9MtQgBBBBA4H4CXOX7mdV8BcFcc3QXtq0Tzb2DeO4o9yvU7+dyOo6P/Dj7xkX/MD4YH2t5PnCWFwqIii8jmCsO7pqmcZrX0HMtAggggECpBDjLpUYubr0J5rh8iy/9w08vuy27Tn+GrVCnWud4v5U0PpcJyA/5oX8YHzIcH7nKxUuWqA0gmKPiraPw/RSNp42nhhYNn89/O94TwGf0zuiIh/yQH/qH8SHH8fHLb36nh+qQLNFaIUGioa2r4BOn+Wnu6qGFPveriAw/eOAhH/QH40FPoIDxkFiuS6/Eag3BHItspeUOc5unHOdzB9HnU0cRDzzGDqt8kA/y4ei4p+4P5ipXKlQiNYtgjgS25mIvzmuuucHahgACCCBQFQHzlasKZ5LGEMxJMNd3kw8/vdrtv2l7mtt8WGXO534/h2FVPjzwkA/6g/Ggn5mRyXjIWa5Pk6RoEcGcgnKl9+A0VxpYzUIAAQQqJcBZrjSwCZpFMCeAXPstxqtoTL397O+XV43ABZdhlT2/p1eP0E/0k7X9w4t9tSuR+O0jmOMzbuIO3OYmwqyRCCCAQHEEuMrFhSzLChPMWYalzEp9ePdql80kNZOIn5ZzymTSoHiIR06TWOVjM/n45bfWVy5TUeRXa4I5v5gUXyNuc/Eh1AAEEECgaAJc5aLDl2XlCeYsw1J+pbpVNPrlEQaH02885IP+YBwwDsQfB8xXLl9D5NgCgjnHqFRUp8FtttFJ/9LO8IMHHvJBfzAe9ARCjYdc5YrEQ4ZNIZgzDEptVdq7zaw11hprLb61pp/pZ432M2K5NuWQX3sI5vxiUm2NxnObzx2F80Y7furA4nNKQH7Ij2sPL/nRTn4QytVKhuwaRjBnF5K6K9S7zSG/hPOlJp7yKdyX2vqT/lROf7ICRt16IbfWEcy5RaSB+nRO82FVp2HL2KmtYx3vt5TF5/LWuvJDfugfTY4P//juPf3SgF7IqYkSLqdoNFYXc5stI2Juu2Vk7GFnHLhnHDAFozGhkFFzCeaMgtFiVf766eVu/7iYclDtu9G/woPPZYdZfsgP/aOZ8YFYblEl5NNmgjmfWDRdk/6FQE7LPU4LXvJFvnDoWxgHzFVuWh5k03iCOZtQqEhH4K8fX9rwhA6kA+nAFnSgPL+R5//41jxlyiAfAgRzPrFQkycCx7nNA5JBQfrcE8CjVxryQT7oD7WOB6ZfkAS5ESCYc4uI+hwIdPOb7cNgHwYzdex3YhxoZxyw+gURkCsBgjnXyKjXnoC5zb6b9928OTrmLrQxDpir7MGfMwGCOefoqNvRbe7mNvtBAAEEEKiOAFe5upBW2SCCucqw1tmo/YYnU8uInX9jeb4Mm+OnMxvwOV2mTn7Ij/HMF/0jWf/wYl+dz+saW0Uw1xjVytu0X0nDDwIIIIBAsQS4ysWGrtmKE8zNhr78hh8d5+2mezvwONPTZzzkg/4wzHw2HuQ0HnCUy3/2ttoCgrnVyFfSbm5zJYHUDAQQqJ4AV7n6EFfdQIK56vC207jxhifbbb+V9GA5+4yHfNAfjAf9JO1HjIfWVG7nWVxzSwnmmqPbYNs4zg0GXZMRQCBLAhzlLMOiUgsJEMwLwbksbwKXHOfBWZlyWBzvnWh8Ljvy8kN+6B/zxgeOct7PR7VbRoBgXsbNVQUQOHWbhzkaQ8V97ues4NETkA/yQX8IMR5wlQt4OKriIgIE8yJsLiqJgGkaJUVLXRFAoEQChHKJUVPnewgQzPfQcm7RBAjnosOn8gggkCEBQjnDoKhSFAIEcxSsCs2ZAOGcc3TUDQEESiBAKJcQJXUMSYBgDklTWUUR6IRzv6D/sLGB33jIB/3BOHBtHHjx3R90Q1FPOpUNRUDihyKpnGIJcJyLDZ2KI4BAIgIc5USg3SZbAgRztqFRsdQExsL56LAc9j85W1NicCIdP27BfIwYfvKjd6r1j9L7x4vv3tMJqR9G7pclAR0hy7Co1CMJcJwfSd+9EUAgBwIc5RyioA45ESCYc4qGumRD4M8fvtoddvA427Fiu91uduMdDBw/2dECH/mhf4x2ACpwfCCWs3kUqUhGBAjmjIKhKvkR4DbnFxM1QgCBOAQI5ThclVoHAYK5jjhqRWQCY8f53EH1+dRRxQOPscMsH/LPB0I58gNE8VUQIJirCKNGpCTAdU5J270QQCAGASI5BlVl1kyAYK45utoWlcDedd7/jN+DH68LMNze8c1n6yU8ocNP/ug/m5T948X31lGO+mBQeLUECOZqQ6thqQhccpwH2TxVB8f7fzPwuUxAfsiP0P2Do5zqieA+tRIgmGuNrHYlJ/DnDy93l52iKYfZ3/E6X6nY5+lvbPSXJf2Fo5z8UeCGlRIgmCsNrGY9joA5zo9j784IINAT4CjLBATCEiCYw/JUGgInBI7znIFBAAEE4hLgJsflq/S2CRDMbcdf6xMR6KZrnO1fMLUvir9vT/ZBwQOPzZV9guTHlpucaBh3m8YJEMyNJ4DmpyXQO87DXMzh3j73czPx6AnIB/kwrz9wlNOO3+7WNgGCue34a/0DCXRznTlnGw4hB5mDPL2T9mf948X37z23Hzhuu3W7BHS8dmOv5ZkQMM85k0CoBgIZE+AmZxwcVWuCAMHcRJg1shQC+6XpfCPvG/l538ibwVH5DBZucikjt3q2QIBgbiHK2lgcAa5zcSFTYQSCEeAmB0OpIASCESCYg6FUEAJxCPz548udd+JGbDnwHPgKHXhucpzxU6kIhCJAMIciqRwEEhAYnOfzrZN9Pt1KGQ88xg+3XPOBk5xg0HQLBAIRIJgDgVQMAqkJXN6Ke6jF1BbLjvcE8Lm8BbX8iJ0fnOTUI6X7IRCGAMEchqNSEHgYgUvznc8dtfPKOX7qwOJzSkB+hM8PbvLDhkg3RiAIAYI5CEaFIJAHgd519oMAAjkQ4CbnEAV1QCAMAYI5DEelIJAdgc55tjGKjVFsyZ52q/V//vsPz9XsRkMVQmA9AR17PUMlIJA9gdMtuYdlJvyenss8NcfZ3y/PfW6bCyc5+yFQBRFYTYBgXo1QAQiUR+B///lqx3lM6zziXQ9v85HLG/PUGIG1BAjmtQRdj0DhBE5X2xgak+tCXOrXExCf1AvnEcmFD3Sqj8BKAgTzSoAuR6A2Ap37XFubtAeBewmYi3wvMecjUDcBgrnu+GodAqsImPvc9tzc1uZ4m4u8arhwMQJVEyCYqw6vxiEQlsDefT7fmvr8Fo6fbl2NzymBjPKDixx2fFAaAjUTIJhrjq62IRCZAAeaA13Sqhkc5MgDguIRqJgAwVxxcDUNgdQEzH9OTdz9rhHgIMsPBBAIRYBgDkVSOQggcJFAtwpHt4PKflm1TedHbjeHz7tuY5HRZ8fxWZgfxLEBCAEEYhIgmGPSVTYCCHxGgAstKUIQIJBDUFQGAgjMJUAwzyXlPAQQiEag30jluPNg70D7PHjyrfMw9zha11MwAgjMJEAwzwTlNAQQSE+AG52e+SPvyDV+JH33RgCBawQIZvmBAAJFEehW5tiNtlYZtpweGuFzvwV17jyI46K6ncoi0DwBgrn5FAAAgXoIcKTziiVRnFc81AYBBJYTIJiXs3MlAggURmCYKz22qAdH+vi7X7Xj3KF1vHeuh1VNOj4EcWEdQHURQGAxAYJ5MToXIoBArQRad6oJ4VozW7sQQGApAYJ5KTnXIYAAAiMCuYps4leaIoAAAusJ/B9Lvf8/QrCq1AAAAABJRU5ErkJggg=="; - - class nexuskittensgrab { - getInfo() { - return { - id: 'nexuskittensgrab', - name: 'S-Grab', - menuIconURI: icon, - color1: '#ECA90B', - color2: '#EBAF00', - blocks: [ - { - opcode: 'usergrab', - blockType: Scratch.BlockType.REPORTER, - text: 'grab [WHAT] count of user [WHO]', - arguments: { - WHAT: { - type: Scratch.ArgumentType.STRING, - menu: 'WHAT' - }, - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'john' - } - } - }, - { - opcode: 'rankusergrab', - blockType: Scratch.BlockType.REPORTER, - text: 'global [WHAT] ranking for [WHO]', - arguments: { - WHAT: { - type: Scratch.ArgumentType.STRING, - menu: 'WHAT2' - }, - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'john' - } - } - }, - { - opcode: 'usergrab2', - blockType: Scratch.BlockType.REPORTER, - text: '[WHAT] of user [WHO]', - arguments: { - WHAT: { - type: Scratch.ArgumentType.STRING, - menu: 'WHAT5' - }, - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'john' - } - } - }, - '---', - { - opcode: 'projectgrab', - blockType: Scratch.BlockType.REPORTER, - text: 'grab [WHAT] count of project id [WHO]', - arguments: { - WHAT: { - type: Scratch.ArgumentType.STRING, - menu: 'WHAT3' - }, - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: '717954208' - } - } - }, - { - opcode: 'rankprojectgrab', - blockType: Scratch.BlockType.REPORTER, - text: 'global [WHAT] ranking for project id [WHO]', - arguments: { - WHAT: { - type: Scratch.ArgumentType.STRING, - menu: 'WHAT4' - }, - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: '717954208' - } - } - }, - { - opcode: 'idtoname', - blockType: Scratch.BlockType.REPORTER, - text: 'name of project id [WHO]', - arguments: { - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: '717954208' - } - } - }, - { - opcode: 'idtoowner', - blockType: Scratch.BlockType.REPORTER, - text: 'creator of project id [WHO]', - arguments: { - WHO: { - type: Scratch.ArgumentType.STRING, - defaultValue: '717954208' - } - } - }, - ], - menus: { - WHAT: { - acceptReporters: true, - items: ['follower', 'following'] - }, - WHAT2: { - acceptReporters: true, - items: ['follower', 'love', 'favorite', 'view'] - }, - WHAT3: { - acceptReporters: true, - items: ['love', 'favorite', 'view'] - }, - WHAT4: { - acceptReporters: true, - items: ['love', 'favorite', 'view'] - }, - WHAT5: { - acceptReporters: true, - items: ['about me', 'wiwo', 'location', 'status'] - } - } - }; - } - async usergrab(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/user/info/' + args.WHO); - const jsonData = await response.json(); - if (args.WHAT === 'follower') { - return jsonData.statistics.followers; - } else if (args.WHAT === 'following') { - return jsonData.statistics.following; - } else { - return ''; - } - } catch (error){ - return ''; - } - } - async rankusergrab(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/user/info/' + args.WHO); - const jsonData = await response.json(); - if (args.WHAT === 'follower') { - return jsonData.statistics.ranks.followers; - } else if (args.WHAT === 'love') { - return jsonData.statistics.ranks.loves; - } else if (args.WHAT === 'favorite') { - return jsonData.statistics.ranks.favorites; - } else if (args.WHAT === 'view') { - return jsonData.statistics.ranks.views; - } else { - return ''; - } - } catch (error){ - return ''; - } - } - async usergrab2(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/user/info/' + args.WHO); - const jsonData = await response.json(); - if (args.WHAT === 'about me') { - return jsonData.bio; - } else if (args.WHAT === 'wiwo') { - return jsonData.work; - } else if (args.WHAT === 'location') { - return jsonData.country; - } else if (args.WHAT === 'status') { - return jsonData.status; - } else { - return ''; - } - } catch (error){ - return ''; - } - } - async projectgrab(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/project/info/' + args.WHO); - const jsonData = await response.json(); - if (args.WHAT === 'love') { - return jsonData.statistics.loves; - } else if (args.WHAT === 'favorite') { - return jsonData.statistics.favorites; - } else if (args.WHAT === 'view') { - return jsonData.statistics.views; - } else { - return ''; - } - } catch (error){ - return ''; - } - } - async rankprojectgrab(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/project/info/' + args.WHO); - const jsonData = await response.json(); - if (args.WHAT === 'love') { - return jsonData.statistics.ranks.loves; - } else if (args.WHAT === 'favorite') { - return jsonData.statistics.ranks.favorites; - } else if (args.WHAT === 'view') { - return jsonData.statistics.ranks.views; - } else { - return ''; - } - } catch (error){ - return ''; - } - } - async idtoname(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/project/info/' + args.WHO); - const jsonData = await response.json(); - return jsonData.title; - } catch (error){ - return ''; - } - } - async idtoowner(args) { - try { - const response = await Scratch.fetch('https://scratchdb.lefty.one/v3/project/info/' + args.WHO); - const jsonData = await response.json(); - return jsonData.username; - } catch (error){ - return ''; - } - } - } - Scratch.extensions.register(new nexuskittensgrab()); -})(Scratch); +// Name: S-Grab +// ID: nexuskittensgrab +// Description: Get information about Scratch projects and Scratch users. +// By: NamelessCat + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("This Extension must run unsandboxed"); + } + + const icon = + "data:image/png;base64,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"; + + class nexuskittensgrab { + getInfo() { + return { + id: "nexuskittensgrab", + name: "S-Grab", + menuIconURI: icon, + color1: "#ECA90B", + color2: "#EBAF00", + blocks: [ + { + opcode: "usergrab", + blockType: Scratch.BlockType.REPORTER, + text: "grab [WHAT] count of user [WHO]", + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT", + }, + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "john", + }, + }, + }, + { + opcode: "rankusergrab", + blockType: Scratch.BlockType.REPORTER, + text: "global [WHAT] ranking for [WHO]", + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT2", + }, + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "john", + }, + }, + }, + { + opcode: "usergrab2", + blockType: Scratch.BlockType.REPORTER, + text: "[WHAT] of user [WHO]", + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT5", + }, + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "john", + }, + }, + }, + "---", + { + opcode: "projectgrab", + blockType: Scratch.BlockType.REPORTER, + text: "grab [WHAT] count of project id [WHO]", + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT3", + }, + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "717954208", + }, + }, + }, + { + opcode: "rankprojectgrab", + blockType: Scratch.BlockType.REPORTER, + text: "global [WHAT] ranking for project id [WHO]", + arguments: { + WHAT: { + type: Scratch.ArgumentType.STRING, + menu: "WHAT4", + }, + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "717954208", + }, + }, + }, + { + opcode: "idtoname", + blockType: Scratch.BlockType.REPORTER, + text: "name of project id [WHO]", + arguments: { + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "717954208", + }, + }, + }, + { + opcode: "idtoowner", + blockType: Scratch.BlockType.REPORTER, + text: "creator of project id [WHO]", + arguments: { + WHO: { + type: Scratch.ArgumentType.STRING, + defaultValue: "717954208", + }, + }, + }, + ], + menus: { + WHAT: { + acceptReporters: true, + items: ["follower", "following"], + }, + WHAT2: { + acceptReporters: true, + items: ["follower", "love", "favorite", "view"], + }, + WHAT3: { + acceptReporters: true, + items: ["love", "favorite", "view"], + }, + WHAT4: { + acceptReporters: true, + items: ["love", "favorite", "view"], + }, + WHAT5: { + acceptReporters: true, + items: ["about me", "wiwo", "location", "status"], + }, + }, + }; + } + async usergrab(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/user/info/" + args.WHO + ); + const jsonData = await response.json(); + if (args.WHAT === "follower") { + return jsonData.statistics.followers; + } else if (args.WHAT === "following") { + return jsonData.statistics.following; + } else { + return ""; + } + } catch (error) { + return ""; + } + } + async rankusergrab(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/user/info/" + args.WHO + ); + const jsonData = await response.json(); + if (args.WHAT === "follower") { + return jsonData.statistics.ranks.followers; + } else if (args.WHAT === "love") { + return jsonData.statistics.ranks.loves; + } else if (args.WHAT === "favorite") { + return jsonData.statistics.ranks.favorites; + } else if (args.WHAT === "view") { + return jsonData.statistics.ranks.views; + } else { + return ""; + } + } catch (error) { + return ""; + } + } + async usergrab2(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/user/info/" + args.WHO + ); + const jsonData = await response.json(); + if (args.WHAT === "about me") { + return jsonData.bio; + } else if (args.WHAT === "wiwo") { + return jsonData.work; + } else if (args.WHAT === "location") { + return jsonData.country; + } else if (args.WHAT === "status") { + return jsonData.status; + } else { + return ""; + } + } catch (error) { + return ""; + } + } + async projectgrab(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/project/info/" + args.WHO + ); + const jsonData = await response.json(); + if (args.WHAT === "love") { + return jsonData.statistics.loves; + } else if (args.WHAT === "favorite") { + return jsonData.statistics.favorites; + } else if (args.WHAT === "view") { + return jsonData.statistics.views; + } else { + return ""; + } + } catch (error) { + return ""; + } + } + async rankprojectgrab(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/project/info/" + args.WHO + ); + const jsonData = await response.json(); + if (args.WHAT === "love") { + return jsonData.statistics.ranks.loves; + } else if (args.WHAT === "favorite") { + return jsonData.statistics.ranks.favorites; + } else if (args.WHAT === "view") { + return jsonData.statistics.ranks.views; + } else { + return ""; + } + } catch (error) { + return ""; + } + } + async idtoname(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/project/info/" + args.WHO + ); + const jsonData = await response.json(); + return jsonData.title; + } catch (error) { + return ""; + } + } + async idtoowner(args) { + try { + const response = await Scratch.fetch( + "https://scratchdb.lefty.one/v3/project/info/" + args.WHO + ); + const jsonData = await response.json(); + return jsonData.username; + } catch (error) { + return ""; + } + } + } + Scratch.extensions.register(new nexuskittensgrab()); +})(Scratch); diff --git a/extensions/Skyhigh173/bigint.js b/extensions/Skyhigh173/bigint.js index 14f137d6ca..acea113026 100644 --- a/extensions/Skyhigh173/bigint.js +++ b/extensions/Skyhigh173/bigint.js @@ -1,14 +1,19 @@ -(function(Scratch){ - 'use strict'; +// Name: BigInt +// ID: skyhigh173BigInt +// Description: Math blocks that work on infinitely large integers (no decimals). +// By: Skyhigh173 + +(function (Scratch) { + "use strict"; /** * @param {unknown} x * @returns {bigint} */ - const bi = x => { - if (typeof x === 'string') { + const bi = (x) => { + if (typeof x === "string") { // Try to parse things like '8n' - if (x.charAt(x.length - 1) === 'n') { + if (x.charAt(x.length - 1) === "n") { try { return BigInt(x.slice(0, -1)); } catch (e) { @@ -16,9 +21,10 @@ } } // Must remove decimal using string operations. Math.trunc will convert to float - // which ruins the point of using bigints. - const decimalIndex = x.indexOf('.'); - const withoutDecimal = decimalIndex === -1 ? x : x.substring(0, decimalIndex); + // which ruins the point of using bigints. + const decimalIndex = x.indexOf("."); + const withoutDecimal = + decimalIndex === -1 ? x : x.substring(0, decimalIndex); try { return BigInt(withoutDecimal); } catch (e) { @@ -35,335 +41,335 @@ }; const makeLabel = (text) => ({ - blockType: 'label', - text: text + blockType: "label", + text: text, }); class BigIntExtension { getInfo() { return { - id: 'skyhigh173BigInt', - name: 'BigInt', - color1: '#59C093', + id: "skyhigh173BigInt", + name: "BigInt", + color1: "#59C093", blocks: [ { - opcode: 'from', + opcode: "from", blockType: Scratch.BlockType.REPORTER, - text: 'To BigInt [text]', + text: "To BigInt [text]", arguments: { text: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'to', + opcode: "to", blockType: Scratch.BlockType.REPORTER, - text: 'To Number [text]', + text: "To Number [text]", arguments: { text: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, - makeLabel('Arithmetic'), + makeLabel("Arithmetic"), { - opcode: 'add', + opcode: "add", blockType: Scratch.BlockType.REPORTER, - text: '[a] + [b]', + text: "[a] + [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'sub', + opcode: "sub", blockType: Scratch.BlockType.REPORTER, - text: '[a] - [b]', + text: "[a] - [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'mul', + opcode: "mul", blockType: Scratch.BlockType.REPORTER, - text: '[a] * [b]', + text: "[a] * [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'div', + opcode: "div", blockType: Scratch.BlockType.REPORTER, - text: '[a] / [b]', + text: "[a] / [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'pow', + opcode: "pow", blockType: Scratch.BlockType.REPORTER, - text: '[a] ** [b]', + text: "[a] ** [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'mod', + opcode: "mod", blockType: Scratch.BlockType.REPORTER, - text: '[a] mod [b]', + text: "[a] mod [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'select', + opcode: "select", blockType: Scratch.BlockType.REPORTER, - text: '[a] [sel] [b]', + text: "[a] [sel] [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, sel: { type: Scratch.ArgumentType.STRING, - defaultValue: '+', - menu: 'op' - } - } + defaultValue: "+", + menu: "op", + }, + }, }, - makeLabel('Logic'), + makeLabel("Logic"), { - opcode: 'lt', + opcode: "lt", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] < [b]', + text: "[a] < [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'le', + opcode: "le", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≤ [b]', + text: "[a] ≤ [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'eq', + opcode: "eq", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] = [b]', + text: "[a] = [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'neq', + opcode: "neq", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≠ [b]', + text: "[a] ≠ [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'ge', + opcode: "ge", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] ≥ [b]', + text: "[a] ≥ [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'gt', + opcode: "gt", blockType: Scratch.BlockType.BOOLEAN, - text: '[a] > [b]', + text: "[a] > [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, - makeLabel('Bitwise'), + makeLabel("Bitwise"), { - opcode: 'and', + opcode: "and", blockType: Scratch.BlockType.REPORTER, - text: '[a] & [b]', + text: "[a] & [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'or', + opcode: "or", blockType: Scratch.BlockType.REPORTER, - text: '[a] | [b]', + text: "[a] | [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'xor', + opcode: "xor", blockType: Scratch.BlockType.REPORTER, - text: '[a] ^ [b]', + text: "[a] ^ [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'ls', + opcode: "ls", blockType: Scratch.BlockType.REPORTER, - text: '[a] << [b]', + text: "[a] << [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'rs', + opcode: "rs", blockType: Scratch.BlockType.REPORTER, - text: '[a] >> [b]', + text: "[a] >> [b]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, b: { type: Scratch.ArgumentType.STRING, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'not', + opcode: "not", blockType: Scratch.BlockType.REPORTER, - text: '~ [a]', + text: "~ [a]", arguments: { a: { type: Scratch.ArgumentType.STRING, - defaultValue: '' + defaultValue: "", }, - } + }, }, ], menus: { op: { - items: ['+', '-', '*', '/', '%', '^'], - acceptReporters: true - } - } + items: ["+", "-", "*", "/", "%", "^"], + acceptReporters: true, + }, + }, }; } from({ text }) { @@ -382,14 +388,14 @@ return (bi(a) * bi(b)).toString(); } div({ a, b }) { - if (Number(b) == 0) return 'NaN'; + if (Number(b) == 0) return "NaN"; return (bi(a) / bi(b)).toString(); } pow({ a, b }) { return (bi(a) ** bi(b)).toString(); } mod({ a, b }) { - if (Number(b) == 0) return 'NaN'; + if (Number(b) == 0) return "NaN"; return (bi(a) % bi(b)).toString(); } @@ -414,19 +420,25 @@ select({ a, sel, b }) { switch (sel) { - case '+': return (bi(a) + bi(b)).toString(); - case '-': return (bi(a) - bi(b)).toString(); - case '*': return (bi(a) * bi(b)).toString(); - case '/': { - if (Number(b) == 0) return 'NaN'; - return (bi(a) / bi(b)).toString(); - } - case '%': { - if (Number(b) == 0) return 'NaN'; - return (bi(a) % bi(b)).toString(); - } - case '^': case '**': return (bi(a) ** bi(b)).toString(); - default: return '0'; + case "+": + return (bi(a) + bi(b)).toString(); + case "-": + return (bi(a) - bi(b)).toString(); + case "*": + return (bi(a) * bi(b)).toString(); + case "/": { + if (Number(b) == 0) return "NaN"; + return (bi(a) / bi(b)).toString(); + } + case "%": { + if (Number(b) == 0) return "NaN"; + return (bi(a) % bi(b)).toString(); + } + case "^": + case "**": + return (bi(a) ** bi(b)).toString(); + default: + return "0"; } } diff --git a/extensions/Skyhigh173/json.js b/extensions/Skyhigh173/json.js index 65496e698d..5bceb7485a 100644 --- a/extensions/Skyhigh173/json.js +++ b/extensions/Skyhigh173/json.js @@ -1,525 +1,535 @@ -(function(Scratch) { - 'use strict'; +// Name: JSON +// ID: skyhigh173JSON +// Description: Handle JSON strings and arrays. +// By: Skyhigh173 + +(function (Scratch) { + "use strict"; /* - * JSON extension v2.5 by skyhigh173 (English Version) - * Do not remove this comment - */ + * JSON extension v2.5 by skyhigh173 (English Version) + * Do not remove this comment + */ const vm = Scratch.vm; - const hasOwn = (obj, property) => Object.prototype.hasOwnProperty.call(obj, property); + const hasOwn = (obj, property) => + Object.prototype.hasOwnProperty.call(obj, property); const makeLabel = (text) => ({ - blockType: 'label', - text: text + blockType: "label", + text: text, }); class JSONS { getInfo() { return { - id: 'skyhigh173JSON', - name: 'JSON', - color1: '#3271D0', + id: "skyhigh173JSON", + name: "JSON", + color1: "#3271D0", blocks: [ - makeLabel('General Utils'), + makeLabel("General Utils"), { - opcode: 'json_is_valid', + opcode: "json_is_valid", blockType: Scratch.BlockType.BOOLEAN, - text: 'is JSON [json] valid', + text: "is JSON [json] valid", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value"}' - } - } + defaultValue: '{"key":"value"}', + }, + }, }, { - opcode: 'json_is', + opcode: "json_is", blockType: Scratch.BlockType.BOOLEAN, - text: 'is [json] [types]', + text: "is [json] [types]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value"}' + defaultValue: '{"key":"value"}', }, types: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Object', - menu: 'types' + defaultValue: "Object", + menu: "types", }, - } + }, }, - '---', + "---", { - opcode: 'json_get_all', + opcode: "json_get_all", blockType: Scratch.BlockType.REPORTER, - text: 'get all [Stype] of [json]', + text: "get all [Stype] of [json]", arguments: { Stype: { type: Scratch.ArgumentType.STRING, - menu: 'get_all' + menu: "get_all", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value","key2":"value2"}' - } - } + defaultValue: '{"key":"value","key2":"value2"}', + }, + }, }, { - opcode: 'json_new', + opcode: "json_new", blockType: Scratch.BlockType.REPORTER, - text: 'new [json]', + text: "new [json]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Object', - menu: 'types' - } - } + defaultValue: "Object", + menu: "types", + }, + }, }, - '---', + "---", { - opcode: 'json_has_key', + opcode: "json_has_key", blockType: Scratch.BlockType.BOOLEAN, - text: '[json] contains key [key]?', + text: "[json] contains key [key]?", arguments: { key: { type: Scratch.ArgumentType.STRING, - defaultValue: 'key2' + defaultValue: "key2", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value"}' - } - } + defaultValue: '{"key":"value"}', + }, + }, }, { - opcode: 'json_has_value', + opcode: "json_has_value", blockType: Scratch.BlockType.BOOLEAN, - text: '[json] contains value [value]?', + text: "[json] contains value [value]?", arguments: { value: { type: Scratch.ArgumentType.STRING, - defaultValue: 'scratch' + defaultValue: "scratch", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["TurboWarp","scratch"]' - } - } + defaultValue: '["TurboWarp","scratch"]', + }, + }, }, { - opcode: 'json_equal', + opcode: "json_equal", blockType: Scratch.BlockType.BOOLEAN, - text: '[json1] [equal] [json2]', + text: "[json1] [equal] [json2]", arguments: { json1: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"a":0,"b":1}' + defaultValue: '{"a":0,"b":1}', }, json2: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"b":1,"a":0}' + defaultValue: '{"b":1,"a":0}', }, equal: { type: Scratch.ArgumentType.STRING, - defaultValue: '=', - menu: 'equal' - } - } + defaultValue: "=", + menu: "equal", + }, + }, }, - makeLabel('JSON Strings'), + makeLabel("JSON Strings"), { - opcode: 'json_jlength', + opcode: "json_jlength", blockType: Scratch.BlockType.REPORTER, - text: 'length of json [json]', + text: "length of json [json]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value","key2":"value2"}' - } - } + defaultValue: '{"key":"value","key2":"value2"}', + }, + }, }, { - opcode: 'json_get', + opcode: "json_get", blockType: Scratch.BlockType.REPORTER, - text: 'get [item] in [json]', + text: "get [item] in [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'key' + defaultValue: "key", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value"}' - } - } + defaultValue: '{"key":"value"}', + }, + }, }, { - opcode: 'json_set', + opcode: "json_set", blockType: Scratch.BlockType.REPORTER, - text: 'set [item] to [value] in [json]', + text: "set [item] to [value] in [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'key' + defaultValue: "key", }, value: { type: Scratch.ArgumentType.STRING, - defaultValue: 'new value' + defaultValue: "new value", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value"}' - } - } + defaultValue: '{"key":"value"}', + }, + }, }, { - opcode: 'json_delete', + opcode: "json_delete", blockType: Scratch.BlockType.REPORTER, - text: 'delete [item] in [json]', + text: "delete [item] in [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'key2' + defaultValue: "key2", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '{"key":"value","key2":"value2"}' - } - } + defaultValue: '{"key":"value","key2":"value2"}', + }, + }, }, - makeLabel('Array'), + makeLabel("Array"), { - opcode: 'json_length', + opcode: "json_length", blockType: Scratch.BlockType.REPORTER, - text: 'length of array [json]', + text: "length of array [json]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '[1,2,3]' - } - } + defaultValue: "[1,2,3]", + }, + }, }, { - opcode: 'json_array_get', + opcode: "json_array_get", blockType: Scratch.BlockType.REPORTER, - text: 'item [item] of array [json]', + text: "item [item] of array [json]", arguments: { item: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 + defaultValue: 1, }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["scratch","TurboWarp"]' - } - } + defaultValue: '["scratch","TurboWarp"]', + }, + }, }, { - opcode: 'json_array_push', + opcode: "json_array_push", blockType: Scratch.BlockType.REPORTER, - text: 'add [item] to array [json]', + text: "add [item] to array [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'TurboWarp' + defaultValue: "TurboWarp", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["scratch"]' - } - } + defaultValue: '["scratch"]', + }, + }, }, { - opcode: 'json_array_set', + opcode: "json_array_set", blockType: Scratch.BlockType.REPORTER, - text: 'replace item [pos] of [json] to [item]', + text: "replace item [pos] of [json] to [item]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'fav' + defaultValue: "fav", }, pos: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 + defaultValue: 2, }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["love","heart","follow"]' - } - } + defaultValue: '["love","heart","follow"]', + }, + }, }, { - opcode: 'json_array_insert', + opcode: "json_array_insert", blockType: Scratch.BlockType.REPORTER, - text: 'insert [item] at [pos] of array [json]', + text: "insert [item] at [pos] of array [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'fav' + defaultValue: "fav", }, pos: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 + defaultValue: 2, }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["love","follow"]' - } - } + defaultValue: '["love","follow"]', + }, + }, }, - '---', + "---", { - opcode: 'json_array_delete', + opcode: "json_array_delete", blockType: Scratch.BlockType.REPORTER, - text: 'delete item [item] of array [json]', + text: "delete item [item] of array [json]", arguments: { item: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 + defaultValue: 2, }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["scratch","a","TurboWarp"]' - } - } + defaultValue: '["scratch","a","TurboWarp"]', + }, + }, }, { - opcode: 'json_array_remove_all', + opcode: "json_array_remove_all", blockType: Scratch.BlockType.REPORTER, - text: 'delete all [item] in array [json]', + text: "delete all [item] in array [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'a' + defaultValue: "a", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["scratch","a","TurboWarp","a","a"]' - } - } + defaultValue: '["scratch","a","TurboWarp","a","a"]', + }, + }, }, - '---', + "---", { - opcode: 'json_array_itemH', + opcode: "json_array_itemH", blockType: Scratch.BlockType.REPORTER, - text: 'item # of [item] in array [json]', + text: "item # of [item] in array [json]", arguments: { item: { type: Scratch.ArgumentType.STRING, - defaultValue: 'scratch' + defaultValue: "scratch", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["scratch","TurboWarp"]' - } - } + defaultValue: '["scratch","TurboWarp"]', + }, + }, }, - makeLabel('Advanced'), + makeLabel("Advanced"), { - opcode: 'json_array_from', + opcode: "json_array_from", blockType: Scratch.BlockType.REPORTER, - text: 'array from text [json]', + text: "array from text [json]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: 'abcd' - } - } + defaultValue: "abcd", + }, + }, }, { - opcode: 'json_array_fromto', + opcode: "json_array_fromto", blockType: Scratch.BlockType.REPORTER, - text: 'array [json] from item [item] to [item2]', + text: "array [json] from item [item] to [item2]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '[1,2,3,4]' + defaultValue: "[1,2,3,4]", }, item: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 + defaultValue: 2, }, item2: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 3 - } - } + defaultValue: 3, + }, + }, }, { - opcode: 'json_array_reverse', + opcode: "json_array_reverse", blockType: Scratch.BlockType.REPORTER, - text: 'reverse array [json]', + text: "reverse array [json]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["a","b","c","d","e","f"]' - } - } + defaultValue: '["a","b","c","d","e","f"]', + }, + }, }, { - opcode: 'json_array_flat', + opcode: "json_array_flat", blockType: Scratch.BlockType.REPORTER, - text: 'flat array [json] by depth [depth]', + text: "flat array [json] by depth [depth]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '[[1],2,[3,4],[5,[6]]]' + defaultValue: "[[1],2,[3,4],[5,[6]]]", }, depth: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 - } - } + defaultValue: 2, + }, + }, }, { - opcode: 'json_array_concat', + opcode: "json_array_concat", blockType: Scratch.BlockType.REPORTER, - text: 'array concat [json] [json2]', + text: "array concat [json] [json2]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["a","b"]' + defaultValue: '["a","b"]', }, json2: { type: Scratch.ArgumentType.STRING, - defaultValue: '["c","d"]' - } - } + defaultValue: '["c","d"]', + }, + }, }, { - opcode: 'json_array_filter', + opcode: "json_array_filter", blockType: Scratch.BlockType.REPORTER, - text: 'get all value with key [key] in array [json]', + text: "get all value with key [key] in array [json]", arguments: { key: { type: Scratch.ArgumentType.STRING, - defaultValue: 'id' + defaultValue: "id", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '[{"id":12},{"id":24}]' - } - } + defaultValue: '[{"id":12},{"id":24}]', + }, + }, }, { - opcode: 'json_array_setlen', + opcode: "json_array_setlen", blockType: Scratch.BlockType.REPORTER, - text: 'set length of array [json] to [len]', + text: "set length of array [json] to [len]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["a","b","c"]' + defaultValue: '["a","b","c"]', }, len: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 - } - } + defaultValue: 2, + }, + }, }, - '---', + "---", { - opcode: 'json_array_create', + opcode: "json_array_create", blockType: Scratch.BlockType.REPORTER, - text: 'create array by [text] with delimiter [d]', + text: "create array by [text] with delimiter [d]", arguments: { text: { type: Scratch.ArgumentType.STRING, - defaultValue: 'a,b,c' + defaultValue: "a,b,c", }, d: { type: Scratch.ArgumentType.STRING, - defaultValue: ',' - } - } + defaultValue: ",", + }, + }, }, { - opcode: 'json_array_join', + opcode: "json_array_join", blockType: Scratch.BlockType.REPORTER, - text: 'join string by array [json] with delimiter [d]', + text: "join string by array [json] with delimiter [d]", arguments: { json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["a","b","c"]' + defaultValue: '["a","b","c"]', }, d: { type: Scratch.ArgumentType.STRING, - defaultValue: ',' - } - } + defaultValue: ",", + }, + }, }, - makeLabel('Lists'), + makeLabel("Lists"), { - opcode: 'json_vm_getlist', + opcode: "json_vm_getlist", blockType: Scratch.BlockType.REPORTER, - text: 'get list [list] as array', + text: "get list [list] as array", arguments: { list: { type: Scratch.ArgumentType.STRING, - menu: 'get_list' + menu: "get_list", }, - } + }, }, { - opcode: 'json_vm_setlist', + opcode: "json_vm_setlist", blockType: Scratch.BlockType.COMMAND, - text: 'set list [list] to content [json]', + text: "set list [list] to content [json]", arguments: { list: { type: Scratch.ArgumentType.STRING, - menu: 'get_list' + menu: "get_list", }, json: { type: Scratch.ArgumentType.STRING, - defaultValue: '["apple","banana"]' + defaultValue: '["apple","banana"]', }, - } + }, }, ], menus: { get_all: { - items: ['keys','values','datas'] + items: ["keys", "values", "datas"], }, get_list: { acceptReporters: true, - items: 'getLists' + items: "getLists", }, types: { acceptReporters: true, - items: ['Object', 'Array'] + items: ["Object", "Array"], }, equal: { acceptReporters: true, - items: ['=','≠'] - } - } + items: ["=", "≠"], + }, + }, }; } - getLists () { - const globalLists = Object.values(vm.runtime.getTargetForStage().variables).filter(x => x.type == 'list'); - const localLists = Object.values(vm.editingTarget.variables).filter(x => x.type == 'list'); + getLists() { + const globalLists = Object.values( + vm.runtime.getTargetForStage().variables + ).filter((x) => x.type == "list"); + const localLists = Object.values(vm.editingTarget.variables).filter( + (x) => x.type == "list" + ); const uniqueLists = [...new Set([...globalLists, ...localLists])]; if (uniqueLists.length === 0) { return [ { - text: 'select a list', - value: 'select a list' - } + text: "select a list", + value: "select a list", + }, ]; } - return uniqueLists.map(i => ({ + return uniqueLists.map((i) => ({ text: i.name, - value: i.id + value: i.id, })); } lookupList(list, util) { const byId = util.target.lookupVariableById(list); - if (byId && byId.type === 'list') { + if (byId && byId.type === "list") { return byId; } - const byName = util.target.lookupVariableByNameAndType(list, 'list'); + const byName = util.target.lookupVariableByNameAndType(list, "list"); if (byName) { return byName; } @@ -527,9 +537,12 @@ } json_is_valid({ json }) { - if (typeof json != 'string') { + if (typeof json != "string") { return false; - } else if ((json.slice(0,1) != '[' || json.slice(-1) != ']') && (json.slice(0,1) != '{' || json.slice(-1) != '}')) { + } else if ( + (json.slice(0, 1) != "[" || json.slice(-1) != "]") && + (json.slice(0, 1) != "{" || json.slice(-1) != "}") + ) { return false; } else { try { @@ -542,10 +555,13 @@ } // return object if its json else string - json_valid_return(json){ - if (typeof json != 'string') { + json_valid_return(json) { + if (typeof json != "string") { return json; - } else if ((json.slice(0,1) != '[' || json.slice(-1) != ']') && (json.slice(0,1) != '{' || json.slice(-1) != '}')) { + } else if ( + (json.slice(0, 1) != "[" || json.slice(-1) != "]") && + (json.slice(0, 1) != "{" || json.slice(-1) != "}") + ) { return json; } else { try { @@ -557,13 +573,16 @@ } json_is({ json, types }) { - if (!this.json_is_valid({json: json})) return false; + if (!this.json_is_valid({ json: json })) return false; try { json = JSON.parse(json); switch (types) { - case 'Object': return !Array.isArray(json); - case 'Array': return Array.isArray(json); - default: return false; + case "Object": + return !Array.isArray(json); + case "Array": + return Array.isArray(json); + default: + return false; } } catch { return false; @@ -575,21 +594,27 @@ json = JSON.parse(json); return Object.keys(json).length; } catch { - return ' '; + return " "; } } json_new({ json }) { switch (json) { - case 'Object': return '{}'; - case 'Array': return '[]'; - default: return ''; + case "Object": + return "{}"; + case "Array": + return "[]"; + default: + return ""; } } json_has_key({ json, key }) { try { - return this._fixInvalidJSONValues(this.json_valid_return(key)) in JSON.parse(json); + return ( + this._fixInvalidJSONValues(this.json_valid_return(key)) in + JSON.parse(json) + ); } catch { return false; } @@ -612,30 +637,34 @@ const keys1 = Object.keys(json1); const keys2 = Object.keys(json2); - const result = keys1.length === keys2.length && Object.keys(json1).every(key=>json1[key] === json2[key]); - if (equal === '=') return result; - if (equal === '≠') return !result; + const result = + keys1.length === keys2.length && + Object.keys(json1).every((key) => json1[key] === json2[key]); + if (equal === "=") return result; + if (equal === "≠") return !result; } catch { // ignore } return false; } - - json_get_all({ Stype,json }) { + json_get_all({ Stype, json }) { try { json = JSON.parse(json); switch (Stype) { - case 'keys': - return JSON.stringify(Object.keys(json).map(key => key)); - case 'values': - return JSON.stringify(Object.keys(json).map(key => json[key])); - case 'datas': - return JSON.stringify(Object.keys(json).map(key => [key, json[key]])); - default: return ''; + case "keys": + return JSON.stringify(Object.keys(json).map((key) => key)); + case "values": + return JSON.stringify(Object.keys(json).map((key) => json[key])); + case "datas": + return JSON.stringify( + Object.keys(json).map((key) => [key, json[key]]) + ); + default: + return ""; } } catch { - return ''; + return ""; } } @@ -644,7 +673,7 @@ json = JSON.parse(json); if (hasOwn(json, item)) { const result = json[item]; - if (typeof result === 'object') { + if (typeof result === "object") { return JSON.stringify(result); } else { return result; @@ -653,14 +682,14 @@ } catch { // ignore } - return ''; + return ""; } _fixInvalidJSONValues(value) { // JSON does not support these values, so convert to string. - if (Number.isNaN(value)) return 'NaN'; - if (value === Infinity) return 'Infinity'; - if (value === -Infinity) return '-Infinity'; + if (Number.isNaN(value)) return "NaN"; + if (value === Infinity) return "Infinity"; + if (value === -Infinity) return "-Infinity"; return value; } @@ -672,7 +701,7 @@ json[item] = value; return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -682,7 +711,7 @@ delete json[item]; return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -695,7 +724,7 @@ // 1...length : array content, -1...-length : reverse array content, 0 : ERROR try { item = Scratch.Cast.toNumber(item); - if (item == 0) return ''; + if (item == 0) return ""; if (item > 0) { item--; } @@ -706,13 +735,13 @@ } else { result = json[json.length + item]; } - if (typeof result == 'object') { + if (typeof result == "object") { return JSON.stringify(result); } else { return result; } } catch { - return ''; + return ""; } } @@ -723,7 +752,7 @@ let result = JSON.stringify(json.indexOf(item) + 1); return result; } catch { - return ''; + return ""; } } @@ -731,7 +760,7 @@ try { return JSON.stringify(Array.from(String(json))); } catch { - return ''; + return ""; } } @@ -741,7 +770,7 @@ json2 = JSON.parse(json2); return JSON.stringify(json.concat(json2)); } catch { - return ''; + return ""; } } @@ -752,7 +781,7 @@ json.push(item); return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -763,17 +792,19 @@ json.splice(pos - 1, 0, item); return JSON.stringify(json); } catch { - return ''; + return ""; } } json_array_set({ item, pos, json }) { try { json = JSON.parse(json); - json[pos - 1] = this._fixInvalidJSONValues(this.json_valid_return(item)); + json[pos - 1] = this._fixInvalidJSONValues( + this.json_valid_return(item) + ); return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -783,7 +814,7 @@ json.splice(item - 1, 1); return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -801,15 +832,15 @@ } return JSON.stringify(json); } catch { - return ''; + return ""; } } json_array_fromto({ json, item, item2 }) { try { - return JSON.stringify(JSON.parse(json).slice(item - 1,item2)); + return JSON.stringify(JSON.parse(json).slice(item - 1, item2)); } catch { - return ''; + return ""; } } @@ -817,7 +848,7 @@ try { return JSON.stringify(JSON.parse(json).reverse()); } catch { - return ''; + return ""; } } @@ -825,7 +856,7 @@ try { return JSON.stringify(JSON.parse(json).flat(depth)); } catch { - return ''; + return ""; } } @@ -837,21 +868,23 @@ try { return JSON.parse(json).join(d); } catch { - return ''; + return ""; } } json_array_filter({ key, json }) { try { json = JSON.parse(json); - return JSON.stringify(json.map(x => { - if (hasOwn(x, key)) { - return x[key]; - } - return null; - })); + return JSON.stringify( + json.map((x) => { + if (hasOwn(x, key)) { + return x[key]; + } + return null; + }) + ); } catch (e) { - return ''; + return ""; } } @@ -861,7 +894,7 @@ json.length = len; return JSON.stringify(json); } catch { - return ''; + return ""; } } @@ -874,7 +907,7 @@ } catch (e) { // ignore } - return ''; + return ""; } json_vm_setlist({ list, json }, util) { try { @@ -882,8 +915,8 @@ if (listVariable) { const array = JSON.parse(json); if (Array.isArray(array)) { - const safeArray = array.map(i => { - if (typeof i === 'object') return JSON.stringify(i); + const safeArray = array.map((i) => { + if (typeof i === "object") return JSON.stringify(i); return i; }); listVariable.value = safeArray; @@ -892,7 +925,7 @@ } catch (e) { // ignore } - return ''; + return ""; } } Scratch.extensions.register(new JSONS()); diff --git a/extensions/TheShovel/CanvasEffects.js b/extensions/TheShovel/CanvasEffects.js index 4d53b09cc2..e14c905bf5 100644 --- a/extensions/TheShovel/CanvasEffects.js +++ b/extensions/TheShovel/CanvasEffects.js @@ -1,165 +1,275 @@ -(function(Scratch) { - 'use strict'; - if (!Scratch.extensions.unsandboxed) { - throw new Error('This extension must run unsandboxed'); - } +// Name: Canvas Effects +// ID: theshovelcanvaseffects +// Description: Apply visual effects to the entire stage. +// By: TheShovel + +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) { + throw new Error("This extension must run unsandboxed"); + } - const canvas = Scratch.renderer.canvas; + const canvas = Scratch.renderer.canvas; - const updateStyle = () => { - const filter = `blur(${blur}px) contrast(${contrast / 100}) saturate(${saturation}%) hue-rotate(${color}deg) brightness(${brightness}%) invert(${invert}%)`; - if (canvas.style.filter !== filter) { - canvas.style.filter = filter; - } - const imageRendering = resizeMode === 'pixelated' ? 'pixelated' : ''; - if (canvas.style.imageRendering !== imageRendering) { - canvas.style.imageRendering = imageRendering; - } - }; - // scratch-gui will sometimes reset the cursor when resizing the window or going in/out of fullscreen - new MutationObserver(updateStyle).observe(canvas, { - attributeFilter: ['style'], - attributes: true - }); + const updateStyle = () => { + // Gotta keep the translation to % because of the stage size, window size and so on + const transform = `rotate(${rotation}deg) scale(${scale}%) skew(${skewX}deg, ${skewY}deg) translate(${offsetX}%, ${ + 0 - offsetY + }%)`; + if (canvas.style.transform !== transform) { + canvas.style.transform = transform; + } + const filter = `blur(${blur}px) contrast(${ + contrast / 100 + }) saturate(${saturation}%) hue-rotate(${color}deg) brightness(${brightness}%) invert(${invert}%) sepia(${sepia}%) opacity(${ + 100 - transparency + }%)`; + if (canvas.style.filter !== filter) { + canvas.style.filter = filter; + } + const cssBorderRadius = borderRadius === 0 ? "" : `${borderRadius}%`; + if (canvas.style.borderRadius !== cssBorderRadius) { + canvas.style.borderRadius = cssBorderRadius; + } + const imageRendering = resizeMode === "pixelated" ? "pixelated" : ""; + if (canvas.style.imageRendering !== imageRendering) { + canvas.style.imageRendering = imageRendering; + } + }; + // scratch-gui may reset canvas styles when resizing the window or going in/out of fullscreen + new MutationObserver(updateStyle).observe(canvas, { + attributeFilter: ["style"], + attributes: true, + }); - let blur = 0; - let contrast = 100; - let saturation = 100; - let color = 0; - let brightness = 100; - let invert = 0; - let resizeMode = 'default'; + let borderRadius = 0; + let rotation = 0; + let offsetY = 0; + let offsetX = 0; + let skewY = 0; + let skewX = 0; + let scale = 100; + // Thanks SharkPool for telling me about these + let transparency = 0; + let sepia = 0; + let blur = 0; + let contrast = 100; + let saturation = 100; + let color = 0; + let brightness = 100; + let invert = 0; + let resizeMode = "default"; - class CanvasEffects { - getInfo() { - return { - id: 'theshovelcanvaseffects', - name: 'Canvas Effects', - blocks: [ - { - opcode: 'seteffect', - blockType: Scratch.BlockType.COMMAND, - text: 'set canvas [EFFECT] to [NUMBER]', - arguments: { - EFFECT: { - type: Scratch.ArgumentType.STRING, - menu: 'EFFECTMENU' - }, - NUMBER: { - type: Scratch.ArgumentType.NUMBER - } - }, - }, - { - opcode: 'geteffect', - blockType: Scratch.BlockType.REPORTER, - text: 'get canvas [EFFECT]', - arguments: { - EFFECT: { - type: Scratch.ArgumentType.STRING, - menu: 'EFFECTGETMENU' - } - } - }, - { - opcode: 'cleareffects', - blockType: Scratch.BlockType.COMMAND, - text: 'clear canvas effects' - }, - { - opcode: 'renderscale', - blockType: Scratch.BlockType.COMMAND, - text: 'set canvas render size to width:[X] height:[Y]', - arguments: { - X: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100 - }, - Y: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 100 - } - } - }, - { - opcode: 'setrendermode', - blockType: Scratch.BlockType.COMMAND, - text: 'set canvas resize rendering mode [EFFECT]', - arguments: { - EFFECT: { - type: Scratch.ArgumentType.STRING, - menu: 'RENDERMODE' - } - }, - }, - ], - menus: { - EFFECTMENU: { - acceptReporters: true, - items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert'] - }, - RENDERMODE: { - acceptReporters: true, - items: ['pixelated', 'default'] - }, - EFFECTGETMENU: { - acceptReporters: true, - items: ['blur', 'contrast', 'saturation', 'color shift', 'brightness', 'invert', 'resize rendering mode'] - } - } - }; - } - geteffect({EFFECT}) { - if (EFFECT === 'blur') { - return blur; - } else if (EFFECT === 'contrast') { - return contrast; - } else if (EFFECT === 'saturation') { - return saturation; - } else if (EFFECT === 'color shift') { - return color; - } else if (EFFECT === 'brightness') { - return brightness; - } else if (EFFECT === 'invert') { - return invert; - } else if (EFFECT === 'resize rendering mode') { - return resizeMode; - } - return ''; - } - seteffect({EFFECT, NUMBER}) { - if (EFFECT === 'blur') { - blur = NUMBER; - } else if (EFFECT === 'contrast') { - contrast = NUMBER; - } else if (EFFECT === 'saturation') { - saturation = NUMBER; - } else if (EFFECT === 'color shift') { - color = NUMBER; - } else if (EFFECT === 'brightness') { - brightness = NUMBER; - } else if (EFFECT === 'invert') { - invert = NUMBER; - } - updateStyle(); - } - cleareffects() { - blur = 0; - contrast = 100; - saturation = 100; - color = 0; - brightness = 100; - invert = 0; - resizeMode = 'default'; - updateStyle(); - } - setrendermode({EFFECT}) { - resizeMode = EFFECT; - updateStyle(); - } - renderscale({X, Y}) { - Scratch.vm.renderer.resize(X, Y); - } + class CanvasEffects { + getInfo() { + return { + id: "theshovelcanvaseffects", + name: "Canvas Effects", + blocks: [ + { + opcode: "seteffect", + blockType: Scratch.BlockType.COMMAND, + text: "set canvas [EFFECT] to [NUMBER]", + arguments: { + EFFECT: { + type: Scratch.ArgumentType.STRING, + menu: "EFFECTMENU", + }, + NUMBER: { + type: Scratch.ArgumentType.NUMBER, + }, + }, + }, + { + opcode: "geteffect", + blockType: Scratch.BlockType.REPORTER, + text: "get canvas [EFFECT]", + arguments: { + EFFECT: { + type: Scratch.ArgumentType.STRING, + menu: "EFFECTGETMENU", + }, + }, + }, + { + opcode: "cleareffects", + blockType: Scratch.BlockType.COMMAND, + text: "clear canvas effects", + }, + { + opcode: "renderscale", + blockType: Scratch.BlockType.COMMAND, + text: "set canvas render size to width:[X] height:[Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 100, + }, + }, + }, + { + opcode: "setrendermode", + blockType: Scratch.BlockType.COMMAND, + text: "set canvas resize rendering mode [EFFECT]", + arguments: { + EFFECT: { + type: Scratch.ArgumentType.STRING, + menu: "RENDERMODE", + }, + }, + }, + ], + menus: { + EFFECTMENU: { + acceptReporters: true, + items: [ + "blur", + "contrast", + "saturation", + "color shift", + "brightness", + "invert", + "sepia", + "transparency", + "scale", + "skew X", + "skew Y", + "offset X", + "offset Y", + "rotation", + "border radius", + ], + }, + RENDERMODE: { + acceptReporters: true, + items: ["pixelated", "default"], + }, + EFFECTGETMENU: { + acceptReporters: true, + // this contains 'resize rendering mode', EFFECTMENU does not + items: [ + "blur", + "contrast", + "saturation", + "color shift", + "brightness", + "invert", + "resize rendering mode", + "sepia", + "transparency", + "scale", + "skew X", + "skew Y", + "offset X", + "offset Y", + "rotation", + "border radius", + ], + }, + }, + }; + } + geteffect({ EFFECT }) { + if (EFFECT === "blur") { + return blur; + } else if (EFFECT === "contrast") { + return contrast; + } else if (EFFECT === "saturation") { + return saturation; + } else if (EFFECT === "color shift") { + return color; + } else if (EFFECT === "brightness") { + return brightness; + } else if (EFFECT === "invert") { + return invert; + } else if (EFFECT === "resize rendering mode") { + return resizeMode; + } else if (EFFECT === "sepia") { + return sepia; + } else if (EFFECT === "transparency") { + return transparency; + } else if (EFFECT === "scale") { + return scale; + } else if (EFFECT === "skew X") { + return skewX; + } else if (EFFECT === "skew Y") { + return skewY; + } else if (EFFECT === "offset X") { + return offsetX; + } else if (EFFECT === "offset Y") { + return offsetY; + } else if (EFFECT === "rotation") { + return rotation; + } else if (EFFECT === "border radius") { + return borderRadius; + } + return ""; + } + seteffect({ EFFECT, NUMBER }) { + NUMBER = Scratch.Cast.toNumber(NUMBER); + if (EFFECT === "blur") { + blur = NUMBER; + } else if (EFFECT === "contrast") { + contrast = NUMBER; + } else if (EFFECT === "saturation") { + saturation = NUMBER; + } else if (EFFECT === "color shift") { + color = NUMBER; + } else if (EFFECT === "brightness") { + brightness = NUMBER; + } else if (EFFECT === "invert") { + invert = NUMBER; + } else if (EFFECT === "sepia") { + sepia = NUMBER; + } else if (EFFECT === "transparency") { + transparency = NUMBER; + } else if (EFFECT === "scale") { + scale = NUMBER; + } else if (EFFECT === "skew X") { + skewX = NUMBER; + } else if (EFFECT === "skew Y") { + skewY = NUMBER; + } else if (EFFECT === "offset X") { + offsetX = NUMBER; + } else if (EFFECT === "offset Y") { + offsetY = NUMBER; + } else if (EFFECT === "rotation") { + rotation = NUMBER; + } else if (EFFECT === "border radius") { + borderRadius = NUMBER; + } + updateStyle(); + } + cleareffects() { + borderRadius = 0; + rotation = 0; + offsetY = 0; + offsetX = 0; + skewY = 0; + skewX = 0; + scale = 100; + transparency = 0; + sepia = 0; + blur = 0; + contrast = 100; + saturation = 100; + color = 0; + brightness = 100; + invert = 0; + resizeMode = "default"; + updateStyle(); + } + setrendermode({ EFFECT }) { + resizeMode = EFFECT; + updateStyle(); + } + renderscale({ X, Y }) { + Scratch.vm.renderer.resize(X, Y); } - Scratch.extensions.register(new CanvasEffects()); + } + Scratch.extensions.register(new CanvasEffects()); })(Scratch); diff --git a/extensions/TheShovel/CustomStyles.js b/extensions/TheShovel/CustomStyles.js new file mode 100644 index 0000000000..fda63171b0 --- /dev/null +++ b/extensions/TheShovel/CustomStyles.js @@ -0,0 +1,760 @@ +// Name: Custom Styles +// ID: shovelcss +// Description: Customize the appearance of variable monitors and prompts in your project. +// By: TheShovel + +// Thanks LilyMakesThings for the awesome banner! +(function (Scratch) { + "use strict"; + + // Styles + let monitorText = ""; + let monitorBorder = ""; + let monitorBackgroundColor = ""; + let variableValueBackground = ""; + let variableValueTextColor = ""; + let listFooterBackground = ""; + let listHeaderBackground = ""; + let listValueText = ""; + let listValueBackground = ""; + let variableValueRoundness = -1; + let listValueRoundness = -1; + let monitorBackgroundRoundness = -1; + let monitorBackgroundBorderWidth = -1; + let allowScrolling = ""; + let askBackground = ""; + let askBackgroundRoundness = -1; + let askBackgroundBorderWidth = -1; + let askButtonBackground = ""; + let askButtonRoundness = -1; + let askInputBackground = ""; + let askInputRoundness = -1; + let askInputBorderWidth = -1; + let askBoxIcon = ""; + let askInputText = ""; + let askButtonImage = ""; + let askInputBorder = ""; + + // CSS selectors + let monitorRoot; + let monitorValue; + let monitorListHeader; + let monitorListFooter; + let monitorRowValueOuter; + let monitorRowsInner; + let monitorRowsScroller; + let monitorRowIndex; + let monitorValueLarge; + let askBoxBG; + let askBoxButton; + let askBoxInner; + let askBoxBorderMain; + let askBoxBorderOuter; + if (typeof scaffolding !== "undefined") { + monitorRoot = ".sc-monitor-root"; + monitorValue = ".sc-monitor-value"; + monitorListHeader = ".sc-monitor-list-label"; + monitorListFooter = ".sc-monitor-list-footer"; + monitorRowValueOuter = ".sc-monitor-row-value-outer"; + monitorRowsInner = ".sc-monitor-rows-inner"; + monitorRowsScroller = monitorRowsInner; + monitorRowIndex = ".sc-monitor-row-index"; + monitorValueLarge = ".sc-monitor-large-value"; + askBoxBG = ".sc-question-inner"; + askBoxButton = ".sc-question-submit-button"; + askBoxInner = ".sc-question-input"; + askBoxBorderMain = ".sc-question-input:hover"; + askBoxBorderOuter = ".sc-question-input:focus"; + } else { + monitorRoot = 'div[class^="monitor_monitor-container_"]'; + monitorValue = 'div[class^="monitor_value_"]'; + monitorListHeader = 'div[class^="monitor_list-header_"]'; + monitorListFooter = 'div[class^="monitor_list-footer_"]'; + monitorRowValueOuter = 'div[class^="monitor_list-value_"]'; + monitorRowsInner = 'div[class^="monitor_list-body_"]'; + monitorRowsScroller = + 'div[class^="monitor_list-body_"] > .ReactVirtualized__List'; + monitorRowIndex = 'div[class^="monitor_list-index_"]'; + monitorValueLarge = 'div[class^="monitor_large-value_"]'; + askBoxBG = 'div[class^="question_question-container_"]'; + askBoxButton = 'button[class^="question_question-submit-button_"]'; + askBoxInner = + '[class^="question_question-container_"] input[class^="input_input-form_"]'; + askBoxIcon = 'img[class^="question_question-submit-button-icon_"]'; + askBoxBorderMain = + '[class^="question_question-input_"] input:focus, [class^="question_question-input_"] input:hover'; + askBoxBorderOuter = '[class^="question_question-input_"] > input:focus'; + } + + const ColorIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMTYuNDE5NjQiIGhlaWdodD0iMTE4LjM0OTk0IiB2aWV3Qm94PSIwLDAsMTE2LjQxOTY0LDExOC4zNDk5NCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOC45MjgwOSw3LjY1MTk0KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTMxLjc3NzE4LDEwLjU1MDMzYy0xMC4yOTkxNywxLjEwODUyIC0xNi4zNDU2NSw0LjY2NTg3IC0xOS4xNDcxOSw5LjE4MDU3Yy0xLjg5MjIsMy4yNDc5NyAtMi4zNDE1Niw3LjEzODcyIC0xLjIzOTUzLDEwLjczMjVsMC4wOTA3LDAuMzQyNjRsMjAuMjY1NzksLTIwLjI1NTcxek03OC4yODQ2OSwyMS43NTY0OGMtNy4zNDY0NywtNy4zNTY1NSAtMTQuNzczNTcsLTEzLjEwMDcgLTIwLjg5MDU5LC0xNi4yOTUyN2MtNC4zNDMzOSwtMi4yMzcyIC03LjcyOTQyLC0zLjEzNDA5IC05LjM5MjIsLTIuMTg2ODFsLTAuODI2MzYsMC44MjYzNmMtMS4yMjk0NSwyLjAxNTUgLTAuNTEzOTYsNi4zMDg0OSAxLjc5Mzc5LDExLjY2OTdjMy41NjMzNSw3LjczMjE5IDguNTIwMTUsMTQuNzQyMDggMTQuNjIyNCwyMC42Nzg5N2M2Ljk5Mzc3LDYuOTkzNzcgMTQuNTAxNDgsMTIuMjM0MDQgMjAuODkwNTksMTUuMTE2MmM0LjU0NDk0LDIuMDE1NSA4LjI3MzYsMi45MjI0NyAxMC40MzAxOCwyLjMzNzk3bDEuOTI0OCwtMS45MzQ4OGMwLjQ1MzQ4LC0xLjkyNDggLTAuNTAzODgsLTUuMTc5ODIgLTIuNTA5MjksLTkuMjAwNzNjLTMuMTEzOTQsLTYuMjU4MTEgLTguNzg3NTUsLTEzLjc2NTgyIC0xNi4wNDMzMywtMjEuMDExNTJ6TTYwLjI4NjMzLC0wLjE1MTk0YzYuNzExNTksMy40NTY1NyAxNC42OTI5NSw5LjY0NDE0IDIyLjQ4MjgzLDE3LjQzNDAyYzcuNzg5ODksNy43ODk4OSAxMy44NTY1MiwxNS44MjE2MyAxNy4yMjIzOSwyMi43MjQ2OWMzLjc3OTA1LDcuNTY4MTggNC4zODM3LDE0LjAyNzg0IDAuNjk1MzQsMTcuNzE2MTljLTAuNjA1OCwwLjU3NzQ5IC0xLjI5NTUyLDEuMDU5OTYgLTIuMDQ1NzIsMS40MzFsLTM5LjQyMzA2LDM5LjQwMjljLTIuMzY4MiwyLjM4ODM2IC0zLjM1NTgsMy4zOTYxMSAtNy40MzcxNyw0LjMxMzE1Yy0yLjAzMTMyLDAuNDQwMzcgLTQuMTE4NywwLjU2Mjc2IC02LjE4NzU3LDAuMzYyNzhjLTIuMTQxODMsLTAuMjA3OCAtNC4yNTIxNSwtMC42NjQzNyAtNi4yODgzNCwtMS4zNjA0NmMtOC4zOTQ1MywtMi44MjE2OSAtMTcuMTMxNjksLTguNzI3MDkgLTI0LjQwNzYyLC0xNS45ODI4NmMtNy4yNzU5MywtNy4yNTU3NyAtMTMuMzcyOCwtMTYuMDUzNDEgLTE2LjMyNTUsLTI0LjQ2ODFjLTAuNTUwNDYsLTEuNTI1NTIgLTAuOTc4NDMsLTMuMDkyNDggLTEuMjc5ODQsLTQuNjg2MDJjLTAuMjg2MDMsLTEuNDc3NzUgLTAuNDMxMTIsLTIuOTc5MyAtMC40MzMzNCwtNC40ODQ0N2MtMC4xMTgxNywtMi4zNTIzNCAwLjMwNTkyLC00LjcwMDM3IDEuMjM5NTMsLTYuODYyNzVjMC45Njc0LC0xLjg2MTczIDIuMjUzMiwtMy41Mzk3NiAzLjc5OTIxLC00Ljk1ODExbDAuMTYxMjQsLTAuMTcxMzJsNC43NDY0OSwtNC43MzY0MWMtMC40OTc0NywtMS4xMjYwMiAtMC45MDg0OCwtMi4yODgyOCAtMS4yMjk0NSwtMy40NzY3M2MtMS41NTYxLC01LjE5NTg5IC0wLjg1MzgyLC0xMC44MDY3NSAxLjkzNDg4LC0xNS40NTg4NGM0LjMwMzA4LC03LjAzNDA3IDEzLjk1NzMsLTEyLjI2NDI4IDMwLjY2NTczLC0xMi40MzU2bDQuMjAyMzEsLTQuMTkyMjNjMC40NDM2OCwtMC42MDgzMiAwLjk4MTExLC0xLjE0MjM1IDEuNTkyMjQsLTEuNTgyMTZjMC4xOTg1NywtMC4xODczMSAwLjQyNjcsLTAuMzQwNTMgMC42NzUxOSwtMC40NTM0OGMzLjczODc0LC0yLjI1NzM1IDkuMjcxMjcsLTEuMzYwNDYgMTUuNjQwMjMsMS45MjQ4ek04OS4wNDc0Myw1OS43Njg2OWMtMi40NjcyLC0wLjU1OTY2IC00Ljg2ODc0LC0xLjM3NzA3IC03LjE2NTA4LC0yLjQzODc1Yy03LjA1NDIzLC0zLjE4NDQ4IC0xNS4yMjcwNiwtOC44NzgyNSAtMjIuNzY1LC0xNi40MDYxMmMtNi42NjcyNywtNi41MDY3NiAtMTIuMDc4NTEsLTE0LjE4NjM2IC0xNS45NjI3MSwtMjIuNjU0MTVjLTEuMDk4MjYsLTIuNDc4NDUgLTEuOTA2NDYsLTUuMDc1NTEgLTIuNDA4NTIsLTcuNzM5NDlsLTI2LjQxODk4LDI2LjMwMjJjMTMuODgzNTcsMTUuMDM3NTkgNTUuMzcyMzcsMjAuMzU1OTQgNjguMjQxMywxNC44NDM1NmMxLjUyOTk3LC0wLjYyNjEyIDMuMjc5MTUsMC4wOTI3NiAzLjkyNjY5LDEuNjEzOGMwLjY0NzU0LDEuNTIxMDMgLTAuMDQ2NzEsMy4yODAxNCAtMS41NTg0OSwzLjk0ODk3Yy0xNS4zMjc4Myw2LjU2MDQzIC02Mi41NTg3NiwtMS41MjU3NiAtNzQuODY2MzQsLTE2LjA4MzA5bC0zLjY5ODQzLDMuNzM4NzRjLTEuMDE3OSwwLjkxNDA1IC0xLjg4MjE3LDEuOTg1ODcgLTIuNTU5NjgsMy4xNzQ0Yy0wLjUxNTM2LDEuMzI3MzcgLTAuNzM1NDUsMi43NTEwNSAtMC42NDQ5Niw0LjE3MjA3YzAuMDA3NywxLjExNjQyIDAuMTE5MDQsMi4yMjk3MiAwLjMzMjU2LDMuMzI1NTZjMC4yMzkxMywxLjI4MDIzIDAuNTc2MDMsMi41NDAyNCAxLjAwNzc0LDMuNzY4OTdjMi42MzAyMiw3LjQ4NzU2IDguMDYxOTcsMTUuMzg4MjkgMTQuODIzOTYsMjIuMDU5NThjNi43NjE5OSw2LjY3MTI5IDE0LjUyMTYzLDEyLjAyMjQyIDIxLjk3ODk2LDE0LjQ5MTRjMS41NjYyMSwwLjU0IDMuMTg5MjMsMC44OTg0MSA0LjgzNzE5LDEuMDY4MjF2MGMxLjQxNDg2LDAuMTM5NCAyLjg0Mjc3LDAuMDU3ODEgNC4yMzI1MywtMC4yNDE4NmMxLjcwOTMzLC0wLjI5ODk0IDMuMjQ4NTEsLTEuMjE3NDEgNC4zMjMyMywtMi41Nzk4M2MwLjA2MDQ2LC0wLjA3MDU0IDAuNjQ0OTYsLTAuNjQ0OTYgMC42MzQ4OCwtMC42NTUwNHoiIGZpbGw9IiMwMDAwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLW9wYWNpdHk9IjAuMTI5NDEiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxNSIvPjxwYXRoIGQ9Ik0zLjI5NzAyLDQ5LjQyMTZjMi40NDg2OSwwLjQ4ODgxIDE0LjYwMzczLDI0LjI5Nzc5IDMxLjc5OSwyMS40MjkyM2MxNS42MzU4MywtMi42MDg0MSA5LjA4MDY3LDE0LjMzMjQ1IDQ4LjU0ODY1LC01LjUwNjgybC0yOC41MDkxNiwyOC40Nzg5M2MwLDAgLTAuNTc0NDIsMC41ODQ1IC0wLjYzNDg4LDAuNjU1MDRjLTEuMDc0NzIsMS4zNjI0MSAtMi42MTM5LDIuMjgwOSAtNC4zMjMyMywyLjU3OTgzYy0xLjM4OTc2LDAuMjk5NjYgLTIuODE3NjgsMC4zODEyNiAtNC4yMzI1MywwLjI0MTg2djBjLTEuNjQ3OTYsLTAuMTY5NzkgLTMuMjcwOTcsLTAuNTI4MjEgLTQuODM3MTksLTEuMDY4MjFjLTcuNDU3MzMsLTIuNDk5MjEgLTE1LjI4NzUyLC03Ljg2MDQzIC0yMS45NTg4LC0xNC40ODEzMmMtNi42NzEyOSwtNi42MjA4OSAtMTIuMTYzNSwtMTQuNTcyMDIgLTE0Ljc5MzcyLC0yMi4wNTk1OGMtMC40MzE3MiwtMS4yMjg3NCAtMC43Njg2MiwtMi40ODg3NSAtMS4wMDc3NCwtMy43Njg5N2MtMC4yMTM1MiwtMS4wOTU4NSAtMC4zMjQ4NiwtMi4yMDkxNCAtMC4zMzI1NiwtMy4zMjU1NmMtMC4wMzY5LC0xLjA2NTcxIDAuMDU3ODcsLTIuMTMxOSAwLjI4MjE2LC0zLjE3NDR6IiBmaWxsPSIjZjU0MjQyIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIi8+PHBhdGggZD0iTTMxLjc3NzE4LDEwLjU1MDMzYy0xMC4yOTkxNywxLjEwODUyIC0xNi4zNDU2NSw0LjY2NTg3IC0xOS4xNDcxOSw5LjE4MDU3Yy0xLjg5MjIsMy4yNDc5NyAtMi4zNDE1Niw3LjEzODcyIC0xLjIzOTUzLDEwLjczMjVsMC4wOTA3LDAuMzQyNjRsMjAuMjY1NzksLTIwLjI1NTcxek03OC4yODQ2OSwyMS43NTY0OGMtNy4zNDY0NywtNy4zNTY1NSAtMTQuNzczNTcsLTEzLjEwMDcgLTIwLjg5MDU5LC0xNi4yOTUyN2MtNC4zNDMzOSwtMi4yMzcyIC03LjcyOTQyLC0zLjEzNDA5IC05LjM5MjIsLTIuMTg2ODFsLTAuODI2MzYsMC44MjYzNmMtMS4yMjk0NSwyLjAxNTUgLTAuNTEzOTYsNi4zMDg0OSAxLjc5Mzc5LDExLjY2OTdjMy41NjMzNSw3LjczMjE5IDguNTIwMTUsMTQuNzQyMDggMTQuNjIyNCwyMC42Nzg5N2M2Ljk5Mzc3LDYuOTkzNzcgMTQuNTAxNDgsMTIuMjM0MDQgMjAuODkwNTksMTUuMTE2MmM0LjU0NDk0LDIuMDE1NSA4LjI3MzYsMi45MjI0NyAxMC40MzAxOCwyLjMzNzk3bDEuOTI0OCwtMS45MzQ4OGMwLjQ1MzQ4LC0xLjkyNDggLTAuNTAzODgsLTUuMTc5ODIgLTIuNTA5MjksLTkuMjAwNzNjLTMuMTEzOTQsLTYuMjU4MTEgLTguNzg3NTUsLTEzLjc2NTgyIC0xNi4wNDMzMywtMjEuMDExNTJ6TTYwLjI4NjMzLC0wLjE1MTk0YzYuNzExNTksMy40NTY1NyAxNC42OTI5NSw5LjY0NDE0IDIyLjQ4MjgzLDE3LjQzNDAyYzcuNzg5ODksNy43ODk4OSAxMy44NTY1MiwxNS44MjE2MyAxNy4yMjIzOSwyMi43MjQ2OWMzLjc3OTA1LDcuNTY4MTggNC4zODM3LDE0LjAyNzg0IDAuNjk1MzQsMTcuNzE2MTljLTAuNjA1OCwwLjU3NzQ5IC0xLjI5NTUyLDEuMDU5OTYgLTIuMDQ1NzIsMS40MzFsLTM5LjQyMzA2LDM5LjQwMjljLTIuMzY4MiwyLjM4ODM2IC0zLjM1NTgsMy4zOTYxMSAtNy40MzcxNyw0LjMxMzE1Yy0yLjAzMTMyLDAuNDQwMzcgLTQuMTE4NywwLjU2Mjc2IC02LjE4NzU3LDAuMzYyNzhjLTIuMTQxODMsLTAuMjA3OCAtNC4yNTIxNSwtMC42NjQzNyAtNi4yODgzNCwtMS4zNjA0NmMtOC4zOTQ1MywtMi44MjE2OSAtMTcuMTMxNjksLTguNzI3MDkgLTI0LjQwNzYyLC0xNS45ODI4NmMtNy4yNzU5MywtNy4yNTU3NyAtMTMuMzcyOCwtMTYuMDUzNDEgLTE2LjMyNTUsLTI0LjQ2ODFjLTAuNTUwNDYsLTEuNTI1NTIgLTAuOTc4NDMsLTMuMDkyNDggLTEuMjc5ODQsLTQuNjg2MDJjLTAuMjg2MDMsLTEuNDc3NzUgLTAuNDMxMTIsLTIuOTc5MyAtMC40MzMzNCwtNC40ODQ0N2MtMC4xMTgxNywtMi4zNTIzNCAwLjMwNTkyLC00LjcwMDM3IDEuMjM5NTMsLTYuODYyNzVjMC45Njc0LC0xLjg2MTczIDIuMjUzMiwtMy41Mzk3NiAzLjc5OTIxLC00Ljk1ODExbDAuMTYxMjQsLTAuMTcxMzJsNC43NDY0OSwtNC43MzY0MWMtMC40OTc0NywtMS4xMjYwMiAtMC45MDg0OCwtMi4yODgyOCAtMS4yMjk0NSwtMy40NzY3M2MtMS41NTYxLC01LjE5NTg5IC0wLjg1MzgyLC0xMC44MDY3NSAxLjkzNDg4LC0xNS40NTg4NGM0LjMwMzA4LC03LjAzNDA3IDEzLjk1NzMsLTEyLjI2NDI4IDMwLjY2NTczLC0xMi40MzU2bDQuMjAyMzEsLTQuMTkyMjNjMC40NDM2OCwtMC42MDgzMiAwLjk4MTExLC0xLjE0MjM1IDEuNTkyMjQsLTEuNTgyMTZjMC4xOTg1NywtMC4xODczMSAwLjQyNjcsLTAuMzQwNTMgMC42NzUxOSwtMC40NTM0OGMzLjczODc0LC0yLjI1NzM1IDkuMjcxMjcsLTEuMzYwNDYgMTUuNjQwMjMsMS45MjQ4ek04OS4wNDc0Myw1OS43Njg2OWMtMi40NjcyLC0wLjU1OTY2IC00Ljg2ODc0LC0xLjM3NzA3IC03LjE2NTA4LC0yLjQzODc1Yy03LjA1NDIzLC0zLjE4NDQ4IC0xNS4yMjcwNiwtOC44NzgyNSAtMjIuNzY1LC0xNi40MDYxMmMtNi42NjcyNywtNi41MDY3NiAtMTIuMDc4NTEsLTE0LjE4NjM2IC0xNS45NjI3MSwtMjIuNjU0MTVjLTEuMDk4MjYsLTIuNDc4NDUgLTEuOTA2NDYsLTUuMDc1NTEgLTIuNDA4NTIsLTcuNzM5NDlsLTI2LjQxODk4LDI2LjMwMjJjMTMuODgzNTcsMTUuMDM3NTkgNTUuMzcyMzcsMjAuMzU1OTQgNjguMjQxMywxNC44NDM1NmMxLjUyOTk3LC0wLjYyNjEyIDMuMjc5MTUsMC4wOTI3NiAzLjkyNjY5LDEuNjEzOGMwLjY0NzU0LDEuNTIxMDMgLTAuMDQ2NzEsMy4yODAxNCAtMS41NTg0OSwzLjk0ODk3Yy0xNS4zMjc4Myw2LjU2MDQzIC02Mi41NTg3NiwtMS41MjU3NiAtNzQuODY2MzQsLTE2LjA4MzA5bC0zLjY5ODQzLDMuNzM4NzRjLTEuMDE3OSwwLjkxNDA1IC0xLjg4MjE3LDEuOTg1ODcgLTIuNTU5NjgsMy4xNzQ0Yy0wLjUxNTM2LDEuMzI3MzcgLTAuNzM1NDUsMi43NTEwNSAtMC42NDQ5Niw0LjE3MjA3YzAuMDA3NywxLjExNjQyIDAuMTE5MDQsMi4yMjk3MiAwLjMzMjU2LDMuMzI1NTZjMC4yMzkxMywxLjI4MDIzIDAuNTc2MDMsMi41NDAyNCAxLjAwNzc0LDMuNzY4OTdjMi42MzAyMiw3LjQ4NzU2IDguMDYxOTcsMTUuMzg4MjkgMTQuODIzOTYsMjIuMDU5NThjNi43NjE5OSw2LjY3MTI5IDE0LjUyMTYzLDEyLjAyMjQyIDIxLjk3ODk2LDE0LjQ5MTRjMS41NjYyMSwwLjU0IDMuMTg5MjMsMC44OTg0MSA0LjgzNzE5LDEuMDY4MjF2MGMxLjQxNDg2LDAuMTM5NCAyLjg0Mjc3LDAuMDU3ODEgNC4yMzI1MywtMC4yNDE4NmMxLjcwOTMzLC0wLjI5ODk0IDMuMjQ4NTEsLTEuMjE3NDEgNC4zMjMyMywtMi41Nzk4M2MwLjA2MDQ2LC0wLjA3MDU0IDAuNjQ0OTYsLTAuNjQ0OTYgMC42MzQ4OCwtMC42NTUwNHoiIGZpbGw9IiMwMDAwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjEwIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6NTguOTI4MDk6NTcuNjUxOTM5OTk5OTk5OTk2LS0+"; + const BorderIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjkuMDA4NDIiIGhlaWdodD0iMTI5LjAwODQzIiB2aWV3Qm94PSIwLDAsMTI5LjAwODQyLDEyOS4wMDg0MyI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTQuNTA0MjEsMTQuNTA0MjIpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0tMi4wMDQyMSw4Ny40NTM4OGMwLC0yMi44MTE2IDAsLTY1LjYwODczIDAsLTc3LjM5MjMxYzAsLTUuOTA0NzkgNy4wNTUxNiwtMTIuMDY1NzkgMTMuMTA3OTYsLTEyLjA2NTc5YzExLjkwNTQsMCA1NC4yNjQ2NSwwIDc2LjcwNzQyLDBjOC41Mzc2NywwIDE0LjE5MzA0LDcuMTQ4NzcgMTQuMTkzMDQsMTcuNTQ0ODljMCwyMy44MjMyNSAwLDY0LjY5OTA0IDAsNzUuNjgwMDljMCw1LjM2NDQ4IC02LjcyOTAyLDEwLjc4MzQ1IC0xNi41OTAxNSwxMC43ODM0NWMtMjIuODg5MjQsMCAtNjIuNjUzNTUsMCAtNzQuMzEwMzEsMGMtNi4zMzM1NSwwIC0xMy4xMDc5NiwtNS44MDcyNCAtMTMuMTA3OTYsLTE0LjU1MDMzeiIgc3Ryb2tlLW9wYWNpdHk9IjAuMTI5NDEiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyNSIvPjxwYXRoIGQ9Ik0tMi4wMDQyMSw4Ny40NTM4OGMwLC0yMi44MTE2IDAsLTY1LjYwODczIDAsLTc3LjM5MjMxYzAsLTUuOTA0NzkgNy4wNTUxNiwtMTIuMDY1NzkgMTMuMTA3OTYsLTEyLjA2NTc5YzExLjkwNTQsMCA1NC4yNjQ2NSwwIDc2LjcwNzQyLDBjOC41Mzc2NywwIDE0LjE5MzA0LDcuMTQ4NzcgMTQuMTkzMDQsMTcuNTQ0ODljMCwyMy44MjMyNSAwLDY0LjY5OTA0IDAsNzUuNjgwMDljMCw1LjM2NDQ4IC02LjcyOTAyLDEwLjc4MzQ1IC0xNi41OTAxNSwxMC43ODM0NWMtMjIuODg5MjQsMCAtNjIuNjUzNTUsMCAtNzQuMzEwMzEsMGMtNi4zMzM1NSwwIC0xMy4xMDc5NiwtNS44MDcyNCAtMTMuMTA3OTYsLTE0LjU1MDMzeiIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIwIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6NjQuNTA0MjE6NjQuNTA0MjItLT4="; + const extensionIcon = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICB2aWV3Qm94PSIwIDAgMjk2Mjk3IDMzMzMzMyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiBpbWFnZS1yZW5kZXJpbmc9Im9wdGltaXplUXVhbGl0eSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iaWQ0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjU0MTI4LjciIHkxPSI3OTM1NS41IiB4Mj0iMjQwMzE4IiB5Mj0iNzkzNTUuNSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZThlN2U1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImlkNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSI2MjAxOS4zIiB5MT0iMjAyODY4IiB4Mj0iMjMzNTE1IiB5Mj0iMjAyODY4Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlOGU3ZTUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iaWQ2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwNDk2MyIgeTE9Ijk5NjE2LjkiIHgyPSIxMDQ5NjMiIHkyPSIxNzEwMjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2QxZDNkNCIvPjxzdG9wIG9mZnNldD0iLjM4OCIgc3RvcC1jb2xvcj0iI2QxZDNkNCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2QxZDNkNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJpZDciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4bGluazpocmVmPSIjaWQ2IiB4MT0iMTk0MTc5IiB5MT0iNjExODUuOCIgeDI9IjE5NDE3OSIgeTI9IjEzNTQwNyIvPjxtYXNrIGlkPSJpZDAiPjxsaW5lYXJHcmFkaWVudCBpZD0iaWQxIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjEwNDk2MyIgeTE9Ijk5NjE2LjkiIHgyPSIxMDQ5NjMiIHkyPSIxNzEwMjEiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1vcGFjaXR5PSIwIiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIuMzg4IiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLW9wYWNpdHk9Ii44MzEiIHN0b3AtY29sb3I9IiNmZmYiLz48L2xpbmVhckdyYWRpZW50PjxwYXRoIGZpbGw9InVybCgjaWQxKSIgZD0iTTYxNzM3IDk5NDY3aDg2NDUzdjcxNzA0SDYxNzM3eiIvPjwvbWFzaz48bWFzayBpZD0iaWQyIj48bGluZWFyR3JhZGllbnQgaWQ9ImlkMyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHgxPSIxOTQxNzkiIHkxPSI2MTE4NS44IiB4Mj0iMTk0MTc5IiB5Mj0iMTM1NDA3Ij48c3RvcCBvZmZzZXQ9IjAiIHN0b3Atb3BhY2l0eT0iMCIgc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iLjM4OCIgc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1vcGFjaXR5PSIuODMxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48cGF0aCBmaWxsPSJ1cmwoI2lkMykiIGQ9Ik0xNDc4OTAgNjEwMzZoOTI1Nzh2NzQ1MjFoLTkyNTc4eiIvPjwvbWFzaz48c3R5bGU+LmZpbDZ7ZmlsbDojMDAwO2ZpbGwtb3BhY2l0eTouMDUwOTh9PC9zdHlsZT48L2RlZnM+PGcgaWQ9IkxheWVyX3gwMDIwXzEiPjxnIGlkPSJfNTEzMDg1MzA0Ij48cGF0aCBmaWxsPSIjMjA2MmFmIiBkPSJNMjY4NTE3IDMwMDkyMmwtMTIwMzY5IDMyNDExLTEyMDM3MS0zMjQxMUwwIDBoMjk2Mjk3eiIvPjxwYXRoIGZpbGw9IiMzYzljZDciIGQ9Ik0xNDgxNDYgMjQzNzR2MjgzMTA5bDI3MyA3NCA5NzQwOS0yNjIyOSAyMjQ4NS0yNTY5NTR6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0ODA0MCA5OTYxN2wtODYxNTMgMzU4ODAgMjg1NyAzNTUyNCA4MzI5Ni0zNTYxNCA4ODYwNC0zNzg4MyAzNjc0LTM2MzM5LTkyMjc4IDM4NDMyeiIvPjxwYXRoIG1hc2s9InVybCgjaWQwKSIgZmlsbD0idXJsKCNpZDYpIiBkPSJNNjE4ODcgMTM1NDk3bDI4NTcgMzU1MjQgODMyOTUtMzU2MTRWOTk2MTd6Ii8+PHBhdGggbWFzaz0idXJsKCNpZDIpIiBmaWxsPSJ1cmwoI2lkNykiIGQ9Ik0yNDAzMTggNjExODZsLTkyMjc4IDM4NDMxdjM1NzkwbDg4NjA0LTM3ODgzeiIvPjxwYXRoIGZpbGw9InVybCgjaWQ1KSIgZD0iTTYyMDE5IDEzNTQ5N2wyODU4IDM1NTI0IDEyNzgwNiA0MDctMjg1OSA0NzM2NS00MjA1NSAxMTg0MC00MDQyOC0xMDIwOC0yNDUwLTI5Mzk5SDY3MzI3bDQ5MDAgNTY3NTYgNzU5NTAgMjI0NTcgNzU1MzgtMjIwNTAgOTgwMC0xMTI2OTJ6Ii8+PHBhdGggY2xhc3M9ImZpbDYiIGQ9Ik0xNDgwNDAgMTM1NDk3SDYxODg4bDI4NTcgMzU1MjQgODMyOTUgMjY2di0zNTc5MHptMCA5NTAyMmwtNDA4IDExNC00MDQyMi0xMDIwOC0yNDUwLTI5Mzk5SDY3MTk3bDQ4OTkgNTY3NTYgNzU5NDQgMjI0NTd2LTM5NzIweiIvPjxwYXRoIGZpbGw9InVybCgjaWQ0KSIgZD0iTTU0MTI5IDYxMTg2aDE4NjE4OWwtMzY3NCAzNjMzOUg1ODYyMGwtNDQ5MS0zNjMzOXoiLz48cGF0aCBjbGFzcz0iZmlsNiIgZD0iTTE0ODA0MCA2MTE4Nkg1NDEyOWw0NDkxIDM2MzM5aDg5NDIweiIvPjwvZz48L2c+PC9zdmc+"; + const miscIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMzUuMzc5IiBoZWlnaHQ9IjEzNS4zNzciIHZpZXdCb3g9IjAsMCwxMzUuMzc5LDEzNS4zNzciPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzIuMzEsLTgyLjMxMSkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzAwMDAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTM0OC45NjcsMTEyLjA4YzIuMzIxLDIuMzIxIDIuMzIxLDYuMTE4IDAsOC40MzlsLTcuMTAxLDcuMTAxYzEuOTU5LDMuNjU4IDMuNDU0LDcuNjAxIDQuNDA1LDExLjc1Mmg5LjE5OWMzLjI4MywwIDUuOTY5LDIuNjg2IDUuOTY5LDUuOTY4djEyLjQ3MWMwLDMuMjgzIC0yLjY4Niw1Ljk2OSAtNS45NjksNS45NjloLTEwLjAzOWMtMS4yMzEsNC4wNjMgLTIuOTkyLDcuODk2IC01LjIwNCwxMS40MThsNi41MTIsNi41MWMyLjMyMSwyLjMyMyAyLjMyMSw2LjEyIDAsOC40NGwtOC44MTgsOC44MTljLTIuMzIxLDIuMzIgLTYuMTE5LDIuMzIgLTguNDM5LDBsLTcuMTAyLC03LjEwMmMtMy42NTcsMS45NiAtNy42MDEsMy40NTYgLTExLjc1Myw0LjQwNnY5LjE5OWMwLDMuMjgyIC0yLjY4NSw1Ljk2OCAtNS45NjgsNS45NjhoLTEyLjQ3Yy0zLjI4MywwIC01Ljk2OSwtMi42ODYgLTUuOTY5LC01Ljk2OHYtMTAuMDM5Yy00LjA2MywtMS4yMzIgLTcuODk2LC0yLjk5MyAtMTEuNDE3LC01LjIwNWwtNi41MTEsNi41MTJjLTIuMzIzLDIuMzIxIC02LjEyLDIuMzIxIC04LjQ0MSwwbC04LjgxOCwtOC44MThjLTIuMzIxLC0yLjMyMSAtMi4zMjEsLTYuMTE4IDAsLTguNDM5bDcuMTAyLC03LjEwMmMtMS45NiwtMy42NTcgLTMuNDU2LC03LjYgLTQuNDA1LC0xMS43NTFoLTkuMjAyYy0zLjI4MiwwIC01Ljk2OCwtMi42ODUgLTUuOTY4LC01Ljk2OHYtMTIuNDcxYzAsLTMuMjgzIDIuNjg2LC01Ljk2OCA1Ljk2OCwtNS45NjhoMTAuMDM5YzEuMjMyLC00LjA2MyAyLjk5MywtNy44OTYgNS4yMDQsLTExLjQxOGwtNi41MTEsLTYuNTFjLTIuMzIxLC0yLjMyMiAtMi4zMjEsLTYuMTIgMCwtOC40NGw4LjgxOSwtOC44MTljMi4zMjEsLTIuMzIxIDYuMTE4LC0yLjMyMSA4LjQzOSwwbDcuMTAxLDcuMTAxYzMuNjU4LC0xLjk2IDcuNjAxLC0zLjQ1NiAxMS43NTMsLTQuNDA2di05LjE5OWMwLC0zLjI4MyAyLjY4NiwtNS45NjkgNS45NjgsLTUuOTY5aDEyLjQ3MWMzLjI4MiwwIDUuOTY4LDIuNjg2IDUuOTY4LDUuOTY5djEwLjAzNmM0LjA2NCwxLjIzMSA3Ljg5OCwyLjk5MiAxMS40MjIsNS4yMDRsNi41MDcsLTYuNTA5YzIuMzIzLC0yLjMyMSA2LjEyLC0yLjMyMSA4LjQ0MSwwek0zMjQuNTE5LDE1MGMwLDEzLjUzOCAtMTAuOTc5LDI0LjUxOSAtMjQuNTE5LDI0LjUxOWMtMTMuNTM5LDAgLTI0LjUxOSwtMTAuOTggLTI0LjUxOSwtMjQuNTE5YzAsLTEzLjUzOSAxMC45OCwtMjQuNTE5IDI0LjUxOSwtMjQuNTE5YzEzLjU0LDAgMjQuNTE5LDEwLjk4IDI0LjUxOSwyNC41MTl6IiBzdHJva2Utb3BhY2l0eT0iMC4xMjk0MSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjEyLjUiLz48cGF0aCBkPSJNMzQ4Ljk2NywxMTIuMDhjMi4zMjEsMi4zMjEgMi4zMjEsNi4xMTggMCw4LjQzOWwtNy4xMDEsNy4xMDFjMS45NTksMy42NTggMy40NTQsNy42MDEgNC40MDUsMTEuNzUyaDkuMTk5YzMuMjgzLDAgNS45NjksMi42ODYgNS45NjksNS45Njh2MTIuNDcxYzAsMy4yODMgLTIuNjg2LDUuOTY5IC01Ljk2OSw1Ljk2OWgtMTAuMDM5Yy0xLjIzMSw0LjA2MyAtMi45OTIsNy44OTYgLTUuMjA0LDExLjQxOGw2LjUxMiw2LjUxYzIuMzIxLDIuMzIzIDIuMzIxLDYuMTIgMCw4LjQ0bC04LjgxOCw4LjgxOWMtMi4zMjEsMi4zMiAtNi4xMTksMi4zMiAtOC40MzksMGwtNy4xMDIsLTcuMTAyYy0zLjY1NywxLjk2IC03LjYwMSwzLjQ1NiAtMTEuNzUzLDQuNDA2djkuMTk5YzAsMy4yODIgLTIuNjg1LDUuOTY4IC01Ljk2OCw1Ljk2OGgtMTIuNDdjLTMuMjgzLDAgLTUuOTY5LC0yLjY4NiAtNS45NjksLTUuOTY4di0xMC4wMzljLTQuMDYzLC0xLjIzMiAtNy44OTYsLTIuOTkzIC0xMS40MTcsLTUuMjA1bC02LjUxMSw2LjUxMmMtMi4zMjMsMi4zMjEgLTYuMTIsMi4zMjEgLTguNDQxLDBsLTguODE4LC04LjgxOGMtMi4zMjEsLTIuMzIxIC0yLjMyMSwtNi4xMTggMCwtOC40MzlsNy4xMDIsLTcuMTAyYy0xLjk2LC0zLjY1NyAtMy40NTYsLTcuNiAtNC40MDUsLTExLjc1MWgtOS4yMDJjLTMuMjgyLDAgLTUuOTY4LC0yLjY4NSAtNS45NjgsLTUuOTY4di0xMi40NzFjMCwtMy4yODMgMi42ODYsLTUuOTY4IDUuOTY4LC01Ljk2OGgxMC4wMzljMS4yMzIsLTQuMDYzIDIuOTkzLC03Ljg5NiA1LjIwNCwtMTEuNDE4bC02LjUxMSwtNi41MWMtMi4zMjEsLTIuMzIyIC0yLjMyMSwtNi4xMiAwLC04LjQ0bDguODE5LC04LjgxOWMyLjMyMSwtMi4zMjEgNi4xMTgsLTIuMzIxIDguNDM5LDBsNy4xMDEsNy4xMDFjMy42NTgsLTEuOTYgNy42MDEsLTMuNDU2IDExLjc1MywtNC40MDZ2LTkuMTk5YzAsLTMuMjgzIDIuNjg2LC01Ljk2OSA1Ljk2OCwtNS45NjloMTIuNDcxYzMuMjgyLDAgNS45NjgsMi42ODYgNS45NjgsNS45Njl2MTAuMDM2YzQuMDY0LDEuMjMxIDcuODk4LDIuOTkyIDExLjQyMiw1LjIwNGw2LjUwNywtNi41MDljMi4zMjMsLTIuMzIxIDYuMTIsLTIuMzIxIDguNDQxLDB6TTMyNC41MTksMTUwYzAsMTMuNTM4IC0xMC45NzksMjQuNTE5IC0yNC41MTksMjQuNTE5Yy0xMy41MzksMCAtMjQuNTE5LC0xMC45OCAtMjQuNTE5LC0yNC41MTljMCwtMTMuNTM5IDEwLjk4LC0yNC41MTkgMjQuNTE5LC0yNC41MTljMTMuNTQsMCAyNC41MTksMTAuOTggMjQuNTE5LDI0LjUxOXoiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI3LjUiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjo2Ny42OTo2Ny42ODktLT4="; + const TransparentIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMTYuNDE5NjQiIGhlaWdodD0iMTE4LjM0OTk0IiB2aWV3Qm94PSIwLDAsMTE2LjQxOTY0LDExOC4zNDk5NCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOC45MjgwOSw3LjY1MTk0KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTQuNDk0OTYsNDYuMzAwNDRjMi40Mjk4NiwwLjQ4NTA1IDE0LjQ5MTQ3LDI0LjExMSAzMS41NTQ1NSwyMS4yNjQ1YzE1LjUxNTYzLC0yLjU4ODM2IDEuNjU1NjMsMjMuNDU1NDUgNDAuODIwMjEsMy43Njg2OWwtMjAuOTM0NzYsMTkuMDI2ODJjMCwwIC0wLjU3LDAuNTggLTAuNjMsMC42NWMtMS4wNjY0NiwxLjM1MTk0IC0yLjU5MzgxLDIuMjYzMzYgLTQuMjksMi41NmMtMS4zNzkwOCwwLjI5NzM2IC0yLjc5NjAyLDAuMzc4MzMgLTQuMiwwLjI0djBjLTEuNjM1MjksLTAuMTY4NDkgLTMuMjQ1ODMsLTAuNTI0MTUgLTQuOCwtMS4wNmMtNy40LC0yLjQ4IC0xNS4xNywtNy44IC0yMS43OSwtMTQuMzdjLTYuNjIsLTYuNTcgLTEyLjA3LC0xNC40NiAtMTQuNjgsLTIxLjg5Yy0wLjQyODQsLTEuMjE5MjkgLTAuNzYyNzEsLTIuNDY5NjIgLTEsLTMuNzRjLTAuMjExODgsLTEuMDg3NDIgLTAuMzIyMzYsLTIuMTkyMTYgLTAuMzMsLTMuM2MtMC4wMzY2MiwtMS4wNTc1MiAwLjA1NzQyLC0yLjExNTUyIDAuMjgsLTMuMTV6IiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjxwYXRoIGQ9Ik0zMS43NzcxOCwxMC41NTAzM2MtMTAuMjk5MTcsMS4xMDg1MiAtMTYuMzQ1NjUsNC42NjU4NyAtMTkuMTQ3MTksOS4xODA1N2MtMS44OTIyLDMuMjQ3OTcgLTIuMzQxNTYsNy4xMzg3MiAtMS4yMzk1MywxMC43MzI1bDAuMDkwNywwLjM0MjY0bDIwLjI2NTc5LC0yMC4yNTU3MXpNNzguMjg0NjksMjEuNzU2NDhjLTcuMzQ2NDcsLTcuMzU2NTUgLTE0Ljc3MzU3LC0xMy4xMDA3IC0yMC44OTA1OSwtMTYuMjk1MjdjLTQuMzQzMzksLTIuMjM3MiAtNy43Mjk0MiwtMy4xMzQwOSAtOS4zOTIyLC0yLjE4NjgxbC0wLjgyNjM2LDAuODI2MzZjLTEuMjI5NDUsMi4wMTU1IC0wLjUxMzk2LDYuMzA4NDkgMS43OTM3OSwxMS42Njk3YzMuNTYzMzUsNy43MzIxOSA4LjUyMDE1LDE0Ljc0MjA4IDE0LjYyMjQsMjAuNjc4OTdjNi45OTM3Nyw2Ljk5Mzc3IDE0LjUwMTQ4LDEyLjIzNDA0IDIwLjg5MDU5LDE1LjExNjJjNC41NDQ5NCwyLjAxNTUgOC4yNzM2LDIuOTIyNDcgMTAuNDMwMTgsMi4zMzc5N2wxLjkyNDgsLTEuOTM0ODhjMC40NTM0OCwtMS45MjQ4IC0wLjUwMzg4LC01LjE3OTgyIC0yLjUwOTI5LC05LjIwMDczYy0zLjExMzk0LC02LjI1ODExIC04Ljc4NzU1LC0xMy43NjU4MiAtMTYuMDQzMzMsLTIxLjAxMTUyek02MC4yODYzMywtMC4xNTE5NGM2LjcxMTU5LDMuNDU2NTcgMTQuNjkyOTUsOS42NDQxNCAyMi40ODI4MywxNy40MzQwMmM3Ljc4OTg5LDcuNzg5ODkgMTMuODU2NTIsMTUuODIxNjMgMTcuMjIyMzksMjIuNzI0NjljMy43NzkwNSw3LjU2ODE4IDQuMzgzNywxNC4wMjc4NCAwLjY5NTM0LDE3LjcxNjE5Yy0wLjYwNTgsMC41Nzc0OSAtMS4yOTU1MiwxLjA1OTk2IC0yLjA0NTcyLDEuNDMxbC0zOS40MjMwNiwzOS40MDI5Yy0yLjM2ODIsMi4zODgzNiAtMy4zNTU4LDMuMzk2MTEgLTcuNDM3MTcsNC4zMTMxNWMtMi4wMzEzMiwwLjQ0MDM3IC00LjExODcsMC41NjI3NiAtNi4xODc1NywwLjM2Mjc4Yy0yLjE0MTgzLC0wLjIwNzggLTQuMjUyMTUsLTAuNjY0MzcgLTYuMjg4MzQsLTEuMzYwNDZjLTguMzk0NTMsLTIuODIxNjkgLTE3LjEzMTY5LC04LjcyNzA5IC0yNC40MDc2MiwtMTUuOTgyODZjLTcuMjc1OTMsLTcuMjU1NzcgLTEzLjM3MjgsLTE2LjA1MzQxIC0xNi4zMjU1LC0yNC40NjgxYy0wLjU1MDQ2LC0xLjUyNTUyIC0wLjk3ODQzLC0zLjA5MjQ4IC0xLjI3OTg0LC00LjY4NjAyYy0wLjI4NjAzLC0xLjQ3Nzc1IC0wLjQzMTEyLC0yLjk3OTMgLTAuNDMzMzQsLTQuNDg0NDdjLTAuMTE4MTcsLTIuMzUyMzQgMC4zMDU5MiwtNC43MDAzNyAxLjIzOTUzLC02Ljg2Mjc1YzAuOTY3NCwtMS44NjE3MyAyLjI1MzIsLTMuNTM5NzYgMy43OTkyMSwtNC45NTgxMWwwLjE2MTI0LC0wLjE3MTMybDQuNzQ2NDksLTQuNzM2NDFjLTAuNDk3NDcsLTEuMTI2MDIgLTAuOTA4NDgsLTIuMjg4MjggLTEuMjI5NDUsLTMuNDc2NzNjLTEuNTU2MSwtNS4xOTU4OSAtMC44NTM4MiwtMTAuODA2NzUgMS45MzQ4OCwtMTUuNDU4ODRjNC4zMDMwOCwtNy4wMzQwNyAxMy45NTczLC0xMi4yNjQyOCAzMC42NjU3MywtMTIuNDM1Nmw0LjIwMjMxLC00LjE5MjIzYzAuNDQzNjgsLTAuNjA4MzIgMC45ODExMSwtMS4xNDIzNSAxLjU5MjI0LC0xLjU4MjE2YzAuMTk4NTcsLTAuMTg3MzEgMC40MjY3LC0wLjM0MDUzIDAuNjc1MTksLTAuNDUzNDhjMy43Mzg3NCwtMi4yNTczNSA5LjI3MTI3LC0xLjM2MDQ2IDE1LjY0MDIzLDEuOTI0OHpNODkuMDQ3NDMsNTkuNzY4NjljLTIuNDY3MiwtMC41NTk2NiAtNC44Njg3NCwtMS4zNzcwNyAtNy4xNjUwOCwtMi40Mzg3NWMtNy4wNTQyMywtMy4xODQ0OCAtMTUuMjI3MDYsLTguODc4MjUgLTIyLjc2NSwtMTYuNDA2MTJjLTYuNjY3MjcsLTYuNTA2NzYgLTEyLjA3ODUxLC0xNC4xODYzNiAtMTUuOTYyNzEsLTIyLjY1NDE1Yy0xLjA5ODI2LC0yLjQ3ODQ1IC0xLjkwNjQ2LC01LjA3NTUxIC0yLjQwODUyLC03LjczOTQ5bC0yNi40MTg5OCwyNi4zMDIyYzEzLjg4MzU3LDE1LjAzNzU5IDU1LjM3MjM3LDIwLjM1NTk0IDY4LjI0MTMsMTQuODQzNTZjMS41Mjk5NywtMC42MjYxMiAzLjI3OTE1LDAuMDkyNzYgMy45MjY2OSwxLjYxMzhjMC42NDc1NCwxLjUyMTAzIC0wLjA0NjcxLDMuMjgwMTQgLTEuNTU4NDksMy45NDg5N2MtMTUuMzI3ODMsNi41NjA0MyAtNjIuNTU4NzYsLTEuNTI1NzYgLTc0Ljg2NjM0LC0xNi4wODMwOWwtMy42OTg0MywzLjczODc0Yy0xLjAxNzksMC45MTQwNSAtMS44ODIxNywxLjk4NTg3IC0yLjU1OTY4LDMuMTc0NGMtMC41MTUzNiwxLjMyNzM3IC0wLjczNTQ1LDIuNzUxMDUgLTAuNjQ0OTYsNC4xNzIwN2MwLjAwNzcsMS4xMTY0MiAwLjExOTA0LDIuMjI5NzIgMC4zMzI1NiwzLjMyNTU2YzAuMjM5MTMsMS4yODAyMyAwLjU3NjAzLDIuNTQwMjQgMS4wMDc3NCwzLjc2ODk3YzIuNjMwMjIsNy40ODc1NiA4LjA2MTk3LDE1LjM4ODI5IDE0LjgyMzk2LDIyLjA1OTU4YzYuNzYxOTksNi42NzEyOSAxNC41MjE2MywxMi4wMjI0MiAyMS45Nzg5NiwxNC40OTE0YzEuNTY2MjEsMC41NCAzLjE4OTIzLDAuODk4NDEgNC44MzcxOSwxLjA2ODIxdjBjMS40MTQ4NiwwLjEzOTQgMi44NDI3NywwLjA1NzgxIDQuMjMyNTMsLTAuMjQxODZjMS43MDkzMywtMC4yOTg5NCAzLjI0ODUxLC0xLjIxNzQxIDQuMzIzMjMsLTIuNTc5ODNjMC4wNjA0NiwtMC4wNzA1NCAwLjY0NDk2LC0wLjY0NDk2IDAuNjM0ODgsLTAuNjU1MDR6IiBmaWxsPSIjMDAwMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS1vcGFjaXR5PSIwLjEyOTQxIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMTUiLz48cGF0aCBkPSJNMzEuNzc3MTgsMTAuNTUwMzNjLTEwLjI5OTE3LDEuMTA4NTIgLTE2LjM0NTY1LDQuNjY1ODcgLTE5LjE0NzE5LDkuMTgwNTdjLTEuODkyMiwzLjI0Nzk3IC0yLjM0MTU2LDcuMTM4NzIgLTEuMjM5NTMsMTAuNzMyNWwwLjA5MDcsMC4zNDI2NGwyMC4yNjU3OSwtMjAuMjU1NzF6TTc4LjI4NDY5LDIxLjc1NjQ4Yy03LjM0NjQ3LC03LjM1NjU1IC0xNC43NzM1NywtMTMuMTAwNyAtMjAuODkwNTksLTE2LjI5NTI3Yy00LjM0MzM5LC0yLjIzNzIgLTcuNzI5NDIsLTMuMTM0MDkgLTkuMzkyMiwtMi4xODY4MWwtMC44MjYzNiwwLjgyNjM2Yy0xLjIyOTQ1LDIuMDE1NSAtMC41MTM5Niw2LjMwODQ5IDEuNzkzNzksMTEuNjY5N2MzLjU2MzM1LDcuNzMyMTkgOC41MjAxNSwxNC43NDIwOCAxNC42MjI0LDIwLjY3ODk3YzYuOTkzNzcsNi45OTM3NyAxNC41MDE0OCwxMi4yMzQwNCAyMC44OTA1OSwxNS4xMTYyYzQuNTQ0OTQsMi4wMTU1IDguMjczNiwyLjkyMjQ3IDEwLjQzMDE4LDIuMzM3OTdsMS45MjQ4LC0xLjkzNDg4YzAuNDUzNDgsLTEuOTI0OCAtMC41MDM4OCwtNS4xNzk4MiAtMi41MDkyOSwtOS4yMDA3M2MtMy4xMTM5NCwtNi4yNTgxMSAtOC43ODc1NSwtMTMuNzY1ODIgLTE2LjA0MzMzLC0yMS4wMTE1MnpNNjAuMjg2MzMsLTAuMTUxOTRjNi43MTE1OSwzLjQ1NjU3IDE0LjY5Mjk1LDkuNjQ0MTQgMjIuNDgyODMsMTcuNDM0MDJjNy43ODk4OSw3Ljc4OTg5IDEzLjg1NjUyLDE1LjgyMTYzIDE3LjIyMjM5LDIyLjcyNDY5YzMuNzc5MDUsNy41NjgxOCA0LjM4MzcsMTQuMDI3ODQgMC42OTUzNCwxNy43MTYxOWMtMC42MDU4LDAuNTc3NDkgLTEuMjk1NTIsMS4wNTk5NiAtMi4wNDU3MiwxLjQzMWwtMzkuNDIzMDYsMzkuNDAyOWMtMi4zNjgyLDIuMzg4MzYgLTMuMzU1OCwzLjM5NjExIC03LjQzNzE3LDQuMzEzMTVjLTIuMDMxMzIsMC40NDAzNyAtNC4xMTg3LDAuNTYyNzYgLTYuMTg3NTcsMC4zNjI3OGMtMi4xNDE4MywtMC4yMDc4IC00LjI1MjE1LC0wLjY2NDM3IC02LjI4ODM0LC0xLjM2MDQ2Yy04LjM5NDUzLC0yLjgyMTY5IC0xNy4xMzE2OSwtOC43MjcwOSAtMjQuNDA3NjIsLTE1Ljk4Mjg2Yy03LjI3NTkzLC03LjI1NTc3IC0xMy4zNzI4LC0xNi4wNTM0MSAtMTYuMzI1NSwtMjQuNDY4MWMtMC41NTA0NiwtMS41MjU1MiAtMC45Nzg0MywtMy4wOTI0OCAtMS4yNzk4NCwtNC42ODYwMmMtMC4yODYwMywtMS40Nzc3NSAtMC40MzExMiwtMi45NzkzIC0wLjQzMzM0LC00LjQ4NDQ3Yy0wLjExODE3LC0yLjM1MjM0IDAuMzA1OTIsLTQuNzAwMzcgMS4yMzk1MywtNi44NjI3NWMwLjk2NzQsLTEuODYxNzMgMi4yNTMyLC0zLjUzOTc2IDMuNzk5MjEsLTQuOTU4MTFsMC4xNjEyNCwtMC4xNzEzMmw0Ljc0NjQ5LC00LjczNjQxYy0wLjQ5NzQ3LC0xLjEyNjAyIC0wLjkwODQ4LC0yLjI4ODI4IC0xLjIyOTQ1LC0zLjQ3NjczYy0xLjU1NjEsLTUuMTk1ODkgLTAuODUzODIsLTEwLjgwNjc1IDEuOTM0ODgsLTE1LjQ1ODg0YzQuMzAzMDgsLTcuMDM0MDcgMTMuOTU3MywtMTIuMjY0MjggMzAuNjY1NzMsLTEyLjQzNTZsNC4yMDIzMSwtNC4xOTIyM2MwLjQ0MzY4LC0wLjYwODMyIDAuOTgxMTEsLTEuMTQyMzUgMS41OTIyNCwtMS41ODIxNmMwLjE5ODU3LC0wLjE4NzMxIDAuNDI2NywtMC4zNDA1MyAwLjY3NTE5LC0wLjQ1MzQ4YzMuNzM4NzQsLTIuMjU3MzUgOS4yNzEyNywtMS4zNjA0NiAxNS42NDAyMywxLjkyNDh6TTg5LjA0NzQzLDU5Ljc2ODY5Yy0yLjQ2NzIsLTAuNTU5NjYgLTQuODY4NzQsLTEuMzc3MDcgLTcuMTY1MDgsLTIuNDM4NzVjLTcuMDU0MjMsLTMuMTg0NDggLTE1LjIyNzA2LC04Ljg3ODI1IC0yMi43NjUsLTE2LjQwNjEyYy02LjY2NzI3LC02LjUwNjc2IC0xMi4wNzg1MSwtMTQuMTg2MzYgLTE1Ljk2MjcxLC0yMi42NTQxNWMtMS4wOTgyNiwtMi40Nzg0NSAtMS45MDY0NiwtNS4wNzU1MSAtMi40MDg1MiwtNy43Mzk0OWwtMjYuNDE4OTgsMjYuMzAyMmMxMy44ODM1NywxNS4wMzc1OSA1NS4zNzIzNywyMC4zNTU5NCA2OC4yNDEzLDE0Ljg0MzU2YzEuNTI5OTcsLTAuNjI2MTIgMy4yNzkxNSwwLjA5Mjc2IDMuOTI2NjksMS42MTM4YzAuNjQ3NTQsMS41MjEwMyAtMC4wNDY3MSwzLjI4MDE0IC0xLjU1ODQ5LDMuOTQ4OTdjLTE1LjMyNzgzLDYuNTYwNDMgLTYyLjU1ODc2LC0xLjUyNTc2IC03NC44NjYzNCwtMTYuMDgzMDlsLTMuNjk4NDMsMy43Mzg3NGMtMS4wMTc5LDAuOTE0MDUgLTEuODgyMTcsMS45ODU4NyAtMi41NTk2OCwzLjE3NDRjLTAuNTE1MzYsMS4zMjczNyAtMC43MzU0NSwyLjc1MTA1IC0wLjY0NDk2LDQuMTcyMDdjMC4wMDc3LDEuMTE2NDIgMC4xMTkwNCwyLjIyOTcyIDAuMzMyNTYsMy4zMjU1NmMwLjIzOTEzLDEuMjgwMjMgMC41NzYwMywyLjU0MDI0IDEuMDA3NzQsMy43Njg5N2MyLjYzMDIyLDcuNDg3NTYgOC4wNjE5NywxNS4zODgyOSAxNC44MjM5NiwyMi4wNTk1OGM2Ljc2MTk5LDYuNjcxMjkgMTQuNTIxNjMsMTIuMDIyNDIgMjEuOTc4OTYsMTQuNDkxNGMxLjU2NjIxLDAuNTQgMy4xODkyMywwLjg5ODQxIDQuODM3MTksMS4wNjgyMXYwYzEuNDE0ODYsMC4xMzk0IDIuODQyNzcsMC4wNTc4MSA0LjIzMjUzLC0wLjI0MTg2YzEuNzA5MzMsLTAuMjk4OTQgMy4yNDg1MSwtMS4yMTc0MSA0LjMyMzIzLC0yLjU3OTgzYzAuMDYwNDYsLTAuMDcwNTQgMC42NDQ5NiwtMC42NDQ5NiAwLjYzNDg4LC0wLjY1NTA0eiIgZmlsbD0iIzAwMDAwMCIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMTAiLz48L2c+PC9nPjwvc3ZnPjwhLS1yb3RhdGlvbkNlbnRlcjo1OC45MjgwOTo1Ny42NTE5Mzk5OTk5OTk5OTYtLT4="; + const GradientIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMTYuNDE5NjQiIGhlaWdodD0iMTE4LjM0OTk0IiB2aWV3Qm94PSIwLDAsMTE2LjQxOTY0LDExOC4zNDk5NCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IHgxPSIzLjM2ODMzIiB5MT0iNzMuMjEzOCIgeDI9IjgzLjM4NjAzIiB5Mj0iNzMuMjEzOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmNTQyNDIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM0Mjk3ZjUiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4LjkyODA5LDcuNjUxOTQpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMy42NTYwMyw0OS40MjYwM2MyLjQyOTg2LDAuNDg1MDUgMTQuNDkxNDcsMjQuMTExIDMxLjU1NDU1LDIxLjI2NDVjMTUuNTE1NjMsLTIuNTg4MzYgOS4wMTA4NywxNC4yMjIyNyA0OC4xNzU0NSwtNS40NjQ0OWwtMjguMjksMjguMjZjMCwwIC0wLjU3LDAuNTggLTAuNjMsMC42NWMtMS4wNjY0NiwxLjM1MTk0IC0yLjU5MzgxLDIuMjYzMzYgLTQuMjksMi41NmMtMS4zNzkwOCwwLjI5NzM2IC0yLjc5NjAyLDAuMzc4MzMgLTQuMiwwLjI0djBjLTEuNjM1MjksLTAuMTY4NDkgLTMuMjQ1ODMsLTAuNTI0MTUgLTQuOCwtMS4wNmMtNy40LC0yLjQ4IC0xNS4xNywtNy44IC0yMS43OSwtMTQuMzdjLTYuNjIsLTYuNTcgLTEyLjA3LC0xNC40NiAtMTQuNjgsLTIxLjg5Yy0wLjQyODQsLTEuMjE5MjkgLTAuNzYyNzEsLTIuNDY5NjIgLTEsLTMuNzRjLTAuMjExODgsLTEuMDg3NDIgLTAuMzIyMzYsLTIuMTkyMTYgLTAuMzMsLTMuM2MtMC4wMzY2MiwtMS4wNTc1MiAwLjA1NzQyLC0yLjExNTUyIDAuMjgsLTMuMTV6IiBmaWxsPSJ1cmwoI2NvbG9yLTEpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIi8+PHBhdGggZD0iTTMxLjc3NzE4LDEwLjU1MDMzYy0xMC4yOTkxNywxLjEwODUyIC0xNi4zNDU2NSw0LjY2NTg3IC0xOS4xNDcxOSw5LjE4MDU3Yy0xLjg5MjIsMy4yNDc5NyAtMi4zNDE1Niw3LjEzODcyIC0xLjIzOTUzLDEwLjczMjVsMC4wOTA3LDAuMzQyNjRsMjAuMjY1NzksLTIwLjI1NTcxek03OC4yODQ2OSwyMS43NTY0OGMtNy4zNDY0NywtNy4zNTY1NSAtMTQuNzczNTcsLTEzLjEwMDcgLTIwLjg5MDU5LC0xNi4yOTUyN2MtNC4zNDMzOSwtMi4yMzcyIC03LjcyOTQyLC0zLjEzNDA5IC05LjM5MjIsLTIuMTg2ODFsLTAuODI2MzYsMC44MjYzNmMtMS4yMjk0NSwyLjAxNTUgLTAuNTEzOTYsNi4zMDg0OSAxLjc5Mzc5LDExLjY2OTdjMy41NjMzNSw3LjczMjE5IDguNTIwMTUsMTQuNzQyMDggMTQuNjIyNCwyMC42Nzg5N2M2Ljk5Mzc3LDYuOTkzNzcgMTQuNTAxNDgsMTIuMjM0MDQgMjAuODkwNTksMTUuMTE2MmM0LjU0NDk0LDIuMDE1NSA4LjI3MzYsMi45MjI0NyAxMC40MzAxOCwyLjMzNzk3bDEuOTI0OCwtMS45MzQ4OGMwLjQ1MzQ4LC0xLjkyNDggLTAuNTAzODgsLTUuMTc5ODIgLTIuNTA5MjksLTkuMjAwNzNjLTMuMTEzOTQsLTYuMjU4MTEgLTguNzg3NTUsLTEzLjc2NTgyIC0xNi4wNDMzMywtMjEuMDExNTJ6TTYwLjI4NjMzLC0wLjE1MTk0YzYuNzExNTksMy40NTY1NyAxNC42OTI5NSw5LjY0NDE0IDIyLjQ4MjgzLDE3LjQzNDAyYzcuNzg5ODksNy43ODk4OSAxMy44NTY1MiwxNS44MjE2MyAxNy4yMjIzOSwyMi43MjQ2OWMzLjc3OTA1LDcuNTY4MTggNC4zODM3LDE0LjAyNzg0IDAuNjk1MzQsMTcuNzE2MTljLTAuNjA1OCwwLjU3NzQ5IC0xLjI5NTUyLDEuMDU5OTYgLTIuMDQ1NzIsMS40MzFsLTM5LjQyMzA2LDM5LjQwMjljLTIuMzY4MiwyLjM4ODM2IC0zLjM1NTgsMy4zOTYxMSAtNy40MzcxNyw0LjMxMzE1Yy0yLjAzMTMyLDAuNDQwMzcgLTQuMTE4NywwLjU2Mjc2IC02LjE4NzU3LDAuMzYyNzhjLTIuMTQxODMsLTAuMjA3OCAtNC4yNTIxNSwtMC42NjQzNyAtNi4yODgzNCwtMS4zNjA0NmMtOC4zOTQ1MywtMi44MjE2OSAtMTcuMTMxNjksLTguNzI3MDkgLTI0LjQwNzYyLC0xNS45ODI4NmMtNy4yNzU5MywtNy4yNTU3NyAtMTMuMzcyOCwtMTYuMDUzNDEgLTE2LjMyNTUsLTI0LjQ2ODFjLTAuNTUwNDYsLTEuNTI1NTIgLTAuOTc4NDMsLTMuMDkyNDggLTEuMjc5ODQsLTQuNjg2MDJjLTAuMjg2MDMsLTEuNDc3NzUgLTAuNDMxMTIsLTIuOTc5MyAtMC40MzMzNCwtNC40ODQ0N2MtMC4xMTgxNywtMi4zNTIzNCAwLjMwNTkyLC00LjcwMDM3IDEuMjM5NTMsLTYuODYyNzVjMC45Njc0LC0xLjg2MTczIDIuMjUzMiwtMy41Mzk3NiAzLjc5OTIxLC00Ljk1ODExbDAuMTYxMjQsLTAuMTcxMzJsNC43NDY0OSwtNC43MzY0MWMtMC40OTc0NywtMS4xMjYwMiAtMC45MDg0OCwtMi4yODgyOCAtMS4yMjk0NSwtMy40NzY3M2MtMS41NTYxLC01LjE5NTg5IC0wLjg1MzgyLC0xMC44MDY3NSAxLjkzNDg4LC0xNS40NTg4NGM0LjMwMzA4LC03LjAzNDA3IDEzLjk1NzMsLTEyLjI2NDI4IDMwLjY2NTczLC0xMi40MzU2bDQuMjAyMzEsLTQuMTkyMjNjMC40NDM2OCwtMC42MDgzMiAwLjk4MTExLC0xLjE0MjM1IDEuNTkyMjQsLTEuNTgyMTZjMC4xOTg1NywtMC4xODczMSAwLjQyNjcsLTAuMzQwNTMgMC42NzUxOSwtMC40NTM0OGMzLjczODc0LC0yLjI1NzM1IDkuMjcxMjcsLTEuMzYwNDYgMTUuNjQwMjMsMS45MjQ4ek04OS4wNDc0Myw1OS43Njg2OWMtMi40NjcyLC0wLjU1OTY2IC00Ljg2ODc0LC0xLjM3NzA3IC03LjE2NTA4LC0yLjQzODc1Yy03LjA1NDIzLC0zLjE4NDQ4IC0xNS4yMjcwNiwtOC44NzgyNSAtMjIuNzY1LC0xNi40MDYxMmMtNi42NjcyNywtNi41MDY3NiAtMTIuMDc4NTEsLTE0LjE4NjM2IC0xNS45NjI3MSwtMjIuNjU0MTVjLTEuMDk4MjYsLTIuNDc4NDUgLTEuOTA2NDYsLTUuMDc1NTEgLTIuNDA4NTIsLTcuNzM5NDlsLTI2LjQxODk4LDI2LjMwMjJjMTMuODgzNTcsMTUuMDM3NTkgNTUuMzcyMzcsMjAuMzU1OTQgNjguMjQxMywxNC44NDM1NmMxLjUyOTk3LC0wLjYyNjEyIDMuMjc5MTUsMC4wOTI3NiAzLjkyNjY5LDEuNjEzOGMwLjY0NzU0LDEuNTIxMDMgLTAuMDQ2NzEsMy4yODAxNCAtMS41NTg0OSwzLjk0ODk3Yy0xNS4zMjc4Myw2LjU2MDQzIC02Mi41NTg3NiwtMS41MjU3NiAtNzQuODY2MzQsLTE2LjA4MzA5bC0zLjY5ODQzLDMuNzM4NzRjLTEuMDE3OSwwLjkxNDA1IC0xLjg4MjE3LDEuOTg1ODcgLTIuNTU5NjgsMy4xNzQ0Yy0wLjUxNTM2LDEuMzI3MzcgLTAuNzM1NDUsMi43NTEwNSAtMC42NDQ5Niw0LjE3MjA3YzAuMDA3NywxLjExNjQyIDAuMTE5MDQsMi4yMjk3MiAwLjMzMjU2LDMuMzI1NTZjMC4yMzkxMywxLjI4MDIzIDAuNTc2MDMsMi41NDAyNCAxLjAwNzc0LDMuNzY4OTdjMi42MzAyMiw3LjQ4NzU2IDguMDYxOTcsMTUuMzg4MjkgMTQuODIzOTYsMjIuMDU5NThjNi43NjE5OSw2LjY3MTI5IDE0LjUyMTYzLDEyLjAyMjQyIDIxLjk3ODk2LDE0LjQ5MTRjMS41NjYyMSwwLjU0IDMuMTg5MjMsMC44OTg0MSA0LjgzNzE5LDEuMDY4MjF2MGMxLjQxNDg2LDAuMTM5NCAyLjg0Mjc3LDAuMDU3ODEgNC4yMzI1MywtMC4yNDE4NmMxLjcwOTMzLC0wLjI5ODk0IDMuMjQ4NTEsLTEuMjE3NDEgNC4zMjMyMywtMi41Nzk4M2MwLjA2MDQ2LC0wLjA3MDU0IDAuNjQ0OTYsLTAuNjQ0OTYgMC42MzQ4OCwtMC42NTUwNHoiIGZpbGw9IiMwMDAwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLW9wYWNpdHk9IjAuMTI5NDEiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxNSIvPjxwYXRoIGQ9Ik0zMS43NzcxOCwxMC41NTAzM2MtMTAuMjk5MTcsMS4xMDg1MiAtMTYuMzQ1NjUsNC42NjU4NyAtMTkuMTQ3MTksOS4xODA1N2MtMS44OTIyLDMuMjQ3OTcgLTIuMzQxNTYsNy4xMzg3MiAtMS4yMzk1MywxMC43MzI1bDAuMDkwNywwLjM0MjY0bDIwLjI2NTc5LC0yMC4yNTU3MXpNNzguMjg0NjksMjEuNzU2NDhjLTcuMzQ2NDcsLTcuMzU2NTUgLTE0Ljc3MzU3LC0xMy4xMDA3IC0yMC44OTA1OSwtMTYuMjk1MjdjLTQuMzQzMzksLTIuMjM3MiAtNy43Mjk0MiwtMy4xMzQwOSAtOS4zOTIyLC0yLjE4NjgxbC0wLjgyNjM2LDAuODI2MzZjLTEuMjI5NDUsMi4wMTU1IC0wLjUxMzk2LDYuMzA4NDkgMS43OTM3OSwxMS42Njk3YzMuNTYzMzUsNy43MzIxOSA4LjUyMDE1LDE0Ljc0MjA4IDE0LjYyMjQsMjAuNjc4OTdjNi45OTM3Nyw2Ljk5Mzc3IDE0LjUwMTQ4LDEyLjIzNDA0IDIwLjg5MDU5LDE1LjExNjJjNC41NDQ5NCwyLjAxNTUgOC4yNzM2LDIuOTIyNDcgMTAuNDMwMTgsMi4zMzc5N2wxLjkyNDgsLTEuOTM0ODhjMC40NTM0OCwtMS45MjQ4IC0wLjUwMzg4LC01LjE3OTgyIC0yLjUwOTI5LC05LjIwMDczYy0zLjExMzk0LC02LjI1ODExIC04Ljc4NzU1LC0xMy43NjU4MiAtMTYuMDQzMzMsLTIxLjAxMTUyek02MC4yODYzMywtMC4xNTE5NGM2LjcxMTU5LDMuNDU2NTcgMTQuNjkyOTUsOS42NDQxNCAyMi40ODI4MywxNy40MzQwMmM3Ljc4OTg5LDcuNzg5ODkgMTMuODU2NTIsMTUuODIxNjMgMTcuMjIyMzksMjIuNzI0NjljMy43NzkwNSw3LjU2ODE4IDQuMzgzNywxNC4wMjc4NCAwLjY5NTM0LDE3LjcxNjE5Yy0wLjYwNTgsMC41Nzc0OSAtMS4yOTU1MiwxLjA1OTk2IC0yLjA0NTcyLDEuNDMxbC0zOS40MjMwNiwzOS40MDI5Yy0yLjM2ODIsMi4zODgzNiAtMy4zNTU4LDMuMzk2MTEgLTcuNDM3MTcsNC4zMTMxNWMtMi4wMzEzMiwwLjQ0MDM3IC00LjExODcsMC41NjI3NiAtNi4xODc1NywwLjM2Mjc4Yy0yLjE0MTgzLC0wLjIwNzggLTQuMjUyMTUsLTAuNjY0MzcgLTYuMjg4MzQsLTEuMzYwNDZjLTguMzk0NTMsLTIuODIxNjkgLTE3LjEzMTY5LC04LjcyNzA5IC0yNC40MDc2MiwtMTUuOTgyODZjLTcuMjc1OTMsLTcuMjU1NzcgLTEzLjM3MjgsLTE2LjA1MzQxIC0xNi4zMjU1LC0yNC40NjgxYy0wLjU1MDQ2LC0xLjUyNTUyIC0wLjk3ODQzLC0zLjA5MjQ4IC0xLjI3OTg0LC00LjY4NjAyYy0wLjI4NjAzLC0xLjQ3Nzc1IC0wLjQzMTEyLC0yLjk3OTMgLTAuNDMzMzQsLTQuNDg0NDdjLTAuMTE4MTcsLTIuMzUyMzQgMC4zMDU5MiwtNC43MDAzNyAxLjIzOTUzLC02Ljg2Mjc1YzAuOTY3NCwtMS44NjE3MyAyLjI1MzIsLTMuNTM5NzYgMy43OTkyMSwtNC45NTgxMWwwLjE2MTI0LC0wLjE3MTMybDQuNzQ2NDksLTQuNzM2NDFjLTAuNDk3NDcsLTEuMTI2MDIgLTAuOTA4NDgsLTIuMjg4MjggLTEuMjI5NDUsLTMuNDc2NzNjLTEuNTU2MSwtNS4xOTU4OSAtMC44NTM4MiwtMTAuODA2NzUgMS45MzQ4OCwtMTUuNDU4ODRjNC4zMDMwOCwtNy4wMzQwNyAxMy45NTczLC0xMi4yNjQyOCAzMC42NjU3MywtMTIuNDM1Nmw0LjIwMjMxLC00LjE5MjIzYzAuNDQzNjgsLTAuNjA4MzIgMC45ODExMSwtMS4xNDIzNSAxLjU5MjI0LC0xLjU4MjE2YzAuMTk4NTcsLTAuMTg3MzEgMC40MjY3LC0wLjM0MDUzIDAuNjc1MTksLTAuNDUzNDhjMy43Mzg3NCwtMi4yNTczNSA5LjI3MTI3LC0xLjM2MDQ2IDE1LjY0MDIzLDEuOTI0OHpNODkuMDQ3NDMsNTkuNzY4NjljLTIuNDY3MiwtMC41NTk2NiAtNC44Njg3NCwtMS4zNzcwNyAtNy4xNjUwOCwtMi40Mzg3NWMtNy4wNTQyMywtMy4xODQ0OCAtMTUuMjI3MDYsLTguODc4MjUgLTIyLjc2NSwtMTYuNDA2MTJjLTYuNjY3MjcsLTYuNTA2NzYgLTEyLjA3ODUxLC0xNC4xODYzNiAtMTUuOTYyNzEsLTIyLjY1NDE1Yy0xLjA5ODI2LC0yLjQ3ODQ1IC0xLjkwNjQ2LC01LjA3NTUxIC0yLjQwODUyLC03LjczOTQ5bC0yNi40MTg5OCwyNi4zMDIyYzEzLjg4MzU3LDE1LjAzNzU5IDU1LjM3MjM3LDIwLjM1NTk0IDY4LjI0MTMsMTQuODQzNTZjMS41Mjk5NywtMC42MjYxMiAzLjI3OTE1LDAuMDkyNzYgMy45MjY2OSwxLjYxMzhjMC42NDc1NCwxLjUyMTAzIC0wLjA0NjcxLDMuMjgwMTQgLTEuNTU4NDksMy45NDg5N2MtMTUuMzI3ODMsNi41NjA0MyAtNjIuNTU4NzYsLTEuNTI1NzYgLTc0Ljg2NjM0LC0xNi4wODMwOWwtMy42OTg0MywzLjczODc0Yy0xLjAxNzksMC45MTQwNSAtMS44ODIxNywxLjk4NTg3IC0yLjU1OTY4LDMuMTc0NGMtMC41MTUzNiwxLjMyNzM3IC0wLjczNTQ1LDIuNzUxMDUgLTAuNjQ0OTYsNC4xNzIwN2MwLjAwNzcsMS4xMTY0MiAwLjExOTA0LDIuMjI5NzIgMC4zMzI1NiwzLjMyNTU2YzAuMjM5MTMsMS4yODAyMyAwLjU3NjAzLDIuNTQwMjQgMS4wMDc3NCwzLjc2ODk3YzIuNjMwMjIsNy40ODc1NiA4LjA2MTk3LDE1LjM4ODI5IDE0LjgyMzk2LDIyLjA1OTU4YzYuNzYxOTksNi42NzEyOSAxNC41MjE2MywxMi4wMjI0MiAyMS45Nzg5NiwxNC40OTE0YzEuNTY2MjEsMC41NCAzLjE4OTIzLDAuODk4NDEgNC44MzcxOSwxLjA2ODIxdjBjMS40MTQ4NiwwLjEzOTQgMi44NDI3NywwLjA1NzgxIDQuMjMyNTMsLTAuMjQxODZjMS43MDkzMywtMC4yOTg5NCAzLjI0ODUxLC0xLjIxNzQxIDQuMzIzMjMsLTIuNTc5ODNjMC4wNjA0NiwtMC4wNzA1NCAwLjY0NDk2LC0wLjY0NDk2IDAuNjM0ODgsLTAuNjU1MDR6IiBmaWxsPSIjMDAwMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIxMCIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjU4LjkyODA4ODk1NTAxODk4OjU3LjY1MTk0MTAyMjI0MTgzLS0+"; + const PictureIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMzguNjA3MDUiIGhlaWdodD0iMTE0LjIxMjI3IiB2aWV3Qm94PSIwLDAsMTM4LjYwNzA1LDExNC4yMTIyNyI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTkuMzAzNTIsNy4xMDYxNCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0iIzAwMDAwMCIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2Utb3BhY2l0eT0iMC4xMjk0MSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjcuNSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0xMTUuNTUzNTIsNS43OTA3NnY4OC40MTg0OGMwLDUuMDUxMzcgLTQuMDk1NTIsOS4xNDY5IC05LjE0NjksOS4xNDY5aC0xMTIuODEzMjVjLTUuMDUxMzcsMCAtOS4xNDY5LC00LjA5NTUyIC05LjE0NjksLTkuMTQ2OXYtODguNDE4NDhjMCwtNS4wNTEzNyA0LjA5NTUyLC05LjE0NjkgOS4xNDY5LC05LjE0NjloMTEyLjgxMzI1YzUuMDUxMzcsMCA5LjE0NjksNC4wOTU1MiA5LjE0NjksOS4xNDY5ek0yLjc0MDI3LDU3LjU4OTY0bDE1LjE2MDk4LC0xMS4xNDc3OGMzLjgxODgzLC0yLjgxMjY3IDkuMTYwNjIsLTIuMjM0MTMgMTIuMjkzNDMsMS4zMzU0NWwxMC4yMjM5NCwxMS42NDg1N2wyNC41NDU3LC0xMi4yNzA1NmMzLjAxNjE5LC0xLjUxMTUyIDYuNjI0NjQsLTEuMjM3MTIgOS4zODAxNCwwLjcxNTc0bDIyLjkxNTI2LDE2LjIzMzQ2di00OS4xNjY4NmgtOTQuNTE5NDZ6TTM4LjU3NzgxLDg1LjA2MjM0bC0xNi41OTcwNCwtMTguOTA4OTJsLTE5LjE1NTg5LDE0LjA4MTY1Yy0wLjAyNzQ0LDAuMDIwNTggLTAuMDU3MTcsMC4wMjk3MyAtMC4wODQ2MSwwLjA1MDMxdjQuNzc2OTd6TTk1LjE5NzEsODUuMDYyMzRsLTI2Ljk1NTkxLC0xOS4wOTE4NmwtMTUuMzQ2MjEsNy42NzE5NmwxMC4wMjI3MSwxMS40MTk5ek0zMC4xODA5NiwyOS44MDEzNmMwLC01LjQ3MjEzIC00LjQzNjI1LC05LjkwODM4IC05LjkwODM4LC05LjkwODM4Yy01LjQ3MjEzLDAgLTkuOTA4MzgsNC40MzYyNSAtOS45MDgzOCw5LjkwODM4YzAsNS40NzIxMyA0LjQzNjI1LDkuOTA4MzggOS45MDgzOCw5LjkwODM4YzUuNDcyMTMsMCA5LjkwODM4LC00LjQzNjI1IDkuOTA4MzgsLTkuOTA4Mzh6Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6NjkuMzAzNTIzOTQ2MzExODo1Ny4xMDYxMzY4MjA3MDk5Ni0tPg=="; + const ResetIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzNDkuMzIzMjQiIGhlaWdodD0iMzI3LjM0OTEyIiB2aWV3Qm94PSIwLDAsMzQ5LjMyMzI0LDMyNy4zNDkxMiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTY1LjMzODM4LC0xNi4zMjU0NCkiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTM2LjQxNTc0LDk3LjkwMTk0Yy0zOC4yNDgwNiw4Mi43NzIyNSAtMzAuNTkzMDYsMjMyLjc5NzI4IDEzOS43MzA3NywyMjAuMDA0MTZjMTQyLjQzOSwtMTAuNjk4NjggMTQzLjc0MTk3LC0xOTIuNTE4ODQgNDMuMzc5NjksLTI0OS41MzgxIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iNTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik05NS42NDc3MSw0OS43ODA2N2w5OS44NTIyMiwtMC40MDI2MyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjAuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTkxLjQ1MzI4LDY1LjUxNDIxbDEwOS42ODM0NywtMjQuMTg4NzciIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI1MCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTIwNC4wMjE1Miw0My4yOTQzNWwtOS44MjI0LDEwNi4wMTExMyIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjUwIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNOTAuMzM4MzgsNjcuNDI2MDhsMTAzLjUyMzY2LDgyLjIwODIiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI1MCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTE1My45MTUxOCwxMDMuODQ5ODV2LTMyLjA5NTloMTQuNjk0NTF2MzIuMDk1OXoiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSI1MCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMTk1LjQ5OTkyLDQ5LjM3ODA0IiBzdHJva2U9IiNmZjAwMDAiIHN0cm9rZS13aWR0aD0iNTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjE3NC42NjE2MjoxNjMuNjc0NTU5OTk5OTk5OTktLT4="; + + const stylesheet = document.createElement("style"); + stylesheet.className = "shovelcss-style"; + // end of for higher precedence than other sheets + document.body.appendChild(stylesheet); + + const applyCSS = () => { + let css = ""; + + // We assume all values are sanitized when they are set, so then we can just use them as-is here. + + if (monitorText) { + css += `${monitorRoot}, ${monitorListFooter}, ${monitorListHeader}, ${monitorRowIndex} { color: ${monitorText}; }`; + } + if (monitorBackgroundColor) { + css += `${monitorRoot}, ${monitorRowsInner} { background: ${monitorBackgroundColor}; }`; + } + if (monitorBorder) { + css += `${monitorRoot} { border-color: ${monitorBorder}; }`; + } + if (monitorBackgroundRoundness >= 0) { + css += `${monitorRoot} { border-radius: ${monitorBackgroundRoundness}px; }`; + } + if (monitorBackgroundBorderWidth >= 0) { + css += `${monitorRoot} { border-width: ${monitorBackgroundBorderWidth}px; }`; + } + if (variableValueBackground) { + css += `${monitorValue}, ${monitorValueLarge} { background: ${variableValueBackground} !important; }`; + } + if (variableValueTextColor) { + css += `${monitorValue}, ${monitorValueLarge} { color: ${variableValueTextColor}; }`; + } + if (variableValueRoundness >= 0) { + css += `${monitorValue} { border-radius: ${variableValueRoundness}px; }`; + } + if (listHeaderBackground) { + css += `${monitorListHeader} { background: ${listHeaderBackground}; }`; + } + if (listFooterBackground) { + css += `${monitorListFooter} { background: ${listHeaderBackground}; }`; + } + if (listValueBackground) { + css += `${monitorRowValueOuter} { background: ${listValueBackground} !important; }`; + } + if (listValueText) { + css += `${monitorRowValueOuter} { color: ${listValueText}; }`; + } + if (listValueRoundness >= 0) { + css += `${monitorRowValueOuter} { border-radius: ${listValueRoundness}px; }`; + } + if (allowScrolling) { + css += `${monitorRowsScroller} { overflow: ${allowScrolling} !important; }`; + } + if (askBackground) { + css += `${askBoxBG} { background: ${askBackground} !important; border: none !important; }`; + } + if (askBackgroundRoundness >= 0) { + css += `${askBoxBG} { border-radius: ${askBackgroundRoundness}px !important; }`; + } + if (askBackgroundBorderWidth >= 0) { + css += `${askBoxBG} { border-width: ${askBackgroundBorderWidth}px !important; }`; + } + if (askButtonBackground) { + css += `${askBoxButton} { background-color: ${askButtonBackground}; }`; + } + if (askButtonRoundness >= 0) { + css += `${askBoxButton} { border-radius: ${askButtonRoundness}px !important; }`; + } + if (askInputBackground) { + css += `${askBoxInner} { background: ${askInputBackground} !important; }`; + css += `${askBoxInner} { border: none !important; }`; + } + if (askInputText) { + css += `${askBoxInner} { color: ${askInputText} !important; }`; + } + if (askInputRoundness >= 0) { + css += `${askBoxInner} { border-radius: ${askInputRoundness}px !important; }`; + } + if (askInputBorderWidth >= 0) { + css += `${askBoxInner} { border-width: ${askInputBorderWidth}px !important; }`; + } + if (askButtonImage) { + css += `${askBoxButton} { background-image: url("${encodeURI( + askButtonImage + )}") !important; background-repeat: no-repeat; background-size: contain; }`; + css += `${askBoxIcon} { visibility: hidden; }`; + } + if (askInputBorder) { + css += `${askBoxBorderMain}, ${askBoxBorderOuter} { border-color: ${askInputBorder} !important; }`; + css += `${askBoxBorderOuter} { box-shadow: none !important; }`; + } + + stylesheet.textContent = css; + }; + + const getMonitorRoot = (id) => { + const allMonitors = document.querySelectorAll(monitorRoot); + for (const monitor of allMonitors) { + if (monitor.dataset.id === id) { + return monitor; + } + } + return null; + }; + + /** + * @param {string} id + * @param {number} x + * @param {number} y + */ + const setMonitorPosition = (id, x, y) => { + const root = getMonitorRoot(id); + if (root) { + root.style.transform = `translate(${x}px, ${y}px)`; + root.style.left = "0px"; + root.style.top = "0px"; + } + }; + + /** + * @param {VM.Target} target + * @param {string} name + * @param {VM.VariableType} type + * @param {number} x + * @param {number} y + */ + const setVariableMonitorPosition = (target, name, type, x, y) => { + // @ts-expect-error + const variable = target.lookupVariableByNameAndType(name, type); + if (variable) { + // @ts-expect-error + setMonitorPosition(variable.id, x, y); + } + }; + + const parseColor = (color, callback) => { + color = Scratch.Cast.toString(color); + + // These might have some exponential backtracking/ReDoS, but that's not really a concern here. + // If a project wanted to get stuck in an infinite loop, there are so many other ways to do that. + + // Simple color code or name + if (/^#?[a-z0-9]+$/.test(color)) { + callback(color); + return; + } + + // Simple linear gradient + if (/^linear-gradient\(\d+deg,#?[a-z0-9]+,#?[a-z0-9]+\)$/.test(color)) { + callback(color); + return; + } + + // URL + // see list of non-escaped characters: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#description + const match = color.match(/^url\("([A-Za-z0-9\-_.!~*'();/?:@&=+$,#]+)"\)$/); + if (match) { + const url = match[1]; + return Scratch.canFetch(url).then((allowed) => { + if (allowed) { + callback(color); + } + }); + } + + console.error("Invalid color", color); + }; + + class MonitorStyles { + getInfo() { + return { + id: "shovelcss", + name: "Custom Styles", + menuIconURI: extensionIcon, + color1: "#0072d6", + color2: "#0064bc", + color3: "#01539b", + blocks: [ + { + blockIconURI: ColorIcon, + opcode: "changecss", + blockType: Scratch.BlockType.COMMAND, + text: "set [COLORABLE] to [COLOR]", + arguments: { + COLORABLE: { + type: Scratch.ArgumentType.STRING, + menu: "COLORABLE_MENU", + }, + COLOR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#ff0000", + }, + }, + }, + { + blockIconURI: GradientIcon, + opcode: "gradientAngle", + blockType: Scratch.BlockType.REPORTER, + text: "make a gradient with [COLOR1] and [COLOR2] at angle [ANGLE]", + arguments: { + COLOR1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#ff0000", + }, + COLOR2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#6ed02d", + }, + ANGLE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: "90", + }, + }, + }, + { + blockIconURI: TransparentIcon, + disableMonitor: true, + opcode: "transparentinput", + blockType: Scratch.BlockType.REPORTER, + text: "transparent", + }, + { + blockIconURI: PictureIcon, + disableMonitor: true, + opcode: "pictureinput", + blockType: Scratch.BlockType.REPORTER, + text: "image [URL]", + arguments: { + URL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://extensions.turbowarp.org/dango.png", + }, + }, + }, + "---", + { + blockIconURI: PictureIcon, + disableMonitor: true, + opcode: "setAskURI", + blockType: Scratch.BlockType.COMMAND, + text: "set ask prompt button image to [URL]", + arguments: { + URL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "https://extensions.turbowarp.org/dango.png", + }, + }, + }, + "---", + { + blockIconURI: BorderIcon, + opcode: "setbordersize", + blockType: Scratch.BlockType.COMMAND, + text: "set border width of [BORDER] to [SIZE]", + arguments: { + BORDER: { + type: Scratch.ArgumentType.STRING, + menu: "BORDER_WIDTH_MENU", + }, + SIZE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "2", + }, + }, + }, + { + blockIconURI: BorderIcon, + opcode: "setborderradius", + blockType: Scratch.BlockType.COMMAND, + text: "set roundness of [CORNER] to [SIZE]", + arguments: { + SIZE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "4", + }, + CORNER: { + type: Scratch.ArgumentType.STRING, + menu: "BORDER_ROUNDNESS_MENU", + }, + }, + }, + "---", + { + blockIconURI: ResetIcon, + opcode: "clearCSS", + blockType: Scratch.BlockType.COMMAND, + text: "reset styles", + }, + "---", + { + blockIconURI: miscIcon, + opcode: "allowscrollrule", + blockType: Scratch.BlockType.COMMAND, + text: "set list scrolling to [SCROLLRULE]", + arguments: { + SCROLLRULE: { + type: Scratch.ArgumentType.STRING, + menu: "SCROLL_MENU", + }, + }, + }, + { + blockIconURI: miscIcon, + opcode: "getValue", + blockType: Scratch.BlockType.REPORTER, + text: "get [ITEM]", + arguments: { + ITEM: { + type: Scratch.ArgumentType.STRING, + menu: "VALUEGET_LIST", + }, + }, + }, + "---", + { + blockIconURI: miscIcon, + opcode: "setvarpos", + blockType: Scratch.BlockType.COMMAND, + text: "set position of variable [NAME] to x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + }, + }, + { + blockIconURI: miscIcon, + opcode: "setlistpos", + blockType: Scratch.BlockType.COMMAND, + text: "set position of list [NAME] to x: [X] y: [Y]", + arguments: { + X: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + NAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "my variable", + }, + }, + }, + ], + // Accepting reporters because there can't be errors in case the value is not correct + menus: { + COLORABLE_MENU: { + acceptReporters: true, + items: [ + "monitor text", + "monitor background", + "monitor border", + "variable value background", + "variable value text", + "list header background", + "list footer background", + "list value background", + "list value text", + "ask prompt background", + "ask prompt button background", + "ask prompt input background", + "ask prompt input text", + "ask prompt input border", + ], + }, + BORDER_WIDTH_MENU: { + acceptReporters: true, + items: [ + "monitor background", + "ask prompt background", + "ask prompt input", + ], + }, + BORDER_ROUNDNESS_MENU: { + acceptReporters: true, + items: [ + "monitor background", + "variable value", + "list value", + "ask prompt background", + "ask prompt button", + "ask prompt input", + ], + }, + SCROLL_MENU: { + acceptReporters: true, + items: ["enabled", "disabled"], + }, + VALUEGET_LIST: { + acceptReporters: true, + items: [ + "monitor text", + "monitor background", + "monitor border color", + "variable value background", + "variable value text", + "list header background", + "list footer background", + "list value background", + "list value text", + "ask prompt background", + "ask prompt button background", + "ask prompt input background", + "ask prompt input text", + "ask prompt input border", + "monitor background border width", + "ask prompt background border width", + "ask prompt input border width", + "monitor background roundness", + "variable value roundness", + "list value roundness", + "ask prompt background roundness", + "ask prompt button roundness", + "ask prompt input roundness", + "ask prompt button image", + "list scroll rule", + ], + }, + }, + }; + } + + changecss(args) { + return parseColor(args.COLOR, (color) => { + if (args.COLORABLE === "monitor text") { + monitorText = color; + } else if (args.COLORABLE === "monitor background") { + monitorBackgroundColor = color; + } else if (args.COLORABLE === "monitor border") { + monitorBorder = color; + } else if (args.COLORABLE === "variable value background") { + variableValueBackground = color; + } else if (args.COLORABLE === "variable value text") { + variableValueTextColor = color; + } else if (args.COLORABLE === "list header background") { + listHeaderBackground = color; + } else if (args.COLORABLE === "list footer background") { + listFooterBackground = color; + } else if (args.COLORABLE === "list value background") { + listValueBackground = color; + } else if (args.COLORABLE === "list value text") { + listValueText = color; + } else if (args.COLORABLE === "ask prompt background") { + askBackground = color; + } else if (args.COLORABLE === "ask prompt button background") { + askButtonBackground = color; + } else if (args.COLORABLE === "ask prompt input background") { + askInputBackground = color; + } else if (args.COLORABLE === "ask prompt input text") { + askInputText = color; + } else if (args.COLORABLE === "ask prompt input border") { + askInputBorder = color; + } + + applyCSS(); + }); + } + + gradientAngle(args) { + return ( + "linear-gradient(" + + args.ANGLE + + "deg," + + args.COLOR1 + + "," + + args.COLOR2 + + ")" + ); + } + + setbordersize(args) { + const size = Scratch.Cast.toNumber(args.SIZE); + if (args.BORDER === "monitor background") { + monitorBackgroundBorderWidth = size; + } else if (args.BORDER === "ask prompt background") { + askBackgroundBorderWidth = size; + } else if (args.BORDER === "ask prompt input") { + askInputBorderWidth = size; + } + applyCSS(); + } + + setborderradius(args) { + const size = Scratch.Cast.toNumber(args.SIZE); + if (args.CORNER === "monitor background") { + monitorBackgroundRoundness = size; + } else if (args.CORNER === "variable value") { + variableValueRoundness = size; + } else if (args.CORNER === "list value") { + listValueRoundness = size; + } else if (args.CORNER === "ask prompt background") { + askBackgroundRoundness = size; + } else if (args.CORNER === "ask prompt button") { + askButtonRoundness = size; + } else if (args.CORNER === "ask prompt input") { + askInputRoundness = size; + } + applyCSS(); + } + + allowscrollrule(args) { + if (args.SCROLLRULE === "enabled") { + allowScrolling = "auto"; + } else { + allowScrolling = "hidden"; + } + applyCSS(); + } + + setvarpos(args, util) { + setVariableMonitorPosition( + util.target, + args.NAME, + "", + Scratch.Cast.toNumber(args.X) + Scratch.vm.runtime.stageWidth / 2, + Scratch.vm.runtime.stageHeight / 2 - Scratch.Cast.toNumber(args.Y) + ); + } + + setlistpos(args, util) { + setVariableMonitorPosition( + util.target, + args.NAME, + "list", + Scratch.Cast.toNumber(args.X) + Scratch.vm.runtime.stageWidth / 2, + Scratch.vm.runtime.stageHeight / 2 - Scratch.Cast.toNumber(args.Y) + ); + } + + help() { + alert( + "\nThis is a short introduction to how to use the Monitor Styles extension!\n\n𝗟𝗼𝗼𝗸𝘀 𝗯𝗹𝗼𝗰𝗸𝘀\nThese blocks change the appearance of the variable and list didsplays. You can use the drop-down menu to select what component you want to modify. 𝙏𝙝𝙚 𝙘𝙤𝙡𝙤𝙧 𝙗𝙡𝙤𝙘𝙠 modifieas the color of a component. You can use the 𝙜𝙧𝙖𝙙𝙞𝙚𝙣𝙩 block inside the color input, to create gradients or the 𝙄𝙢𝙖𝙜𝙚 block to use a image instead of solid colors. 𝙏𝙝𝙚𝙨𝙚 𝙩𝙬𝙤 𝙤𝙣𝙡𝙮 𝙬𝙤𝙧𝙠 𝙤𝙣 𝙘𝙚𝙧𝙩𝙖𝙞𝙣 𝙘𝙤𝙢𝙥𝙤𝙣𝙚𝙣𝙩𝙨! You can also use the 𝙩𝙧𝙖𝙣𝙨𝙥𝙖𝙧𝙚𝙣𝙩 𝙗𝙡𝙤𝙘𝙠 as a color input, to make components invisible. The 𝙗𝙤𝙧𝙙𝙚𝙧 𝙗𝙡𝙤𝙘𝙠𝙨 modify the borders of components.\n\n𝗦𝗲𝗻𝘀𝗶𝗻𝗴 𝗯𝗹𝗼𝗰𝗸𝘀\nThese blocks can change the behaviour of certain components. The 𝙨𝙘𝙧𝙤𝙡𝙡 𝙧𝙪𝙡𝙚 block change the behaviour for lists. On 'auto' they will show the scroll bar, and allow you to school, but on 'hidden', they won't let you do that, and the scroll bar will be hidden.\n\n𝗠𝗼𝘁𝗶𝗼𝗻 𝗯𝗹𝗼𝗰𝗸𝘀\nThese blocks allow you to move variable and list displays around. You need to use their 𝙡𝙖𝙗𝙚𝙡 𝙣𝙖𝙢𝙚. The label name is the text that displays on the monitor. For example, a 'for this sprite only' variable will be like 'Sprite1: my variable'." + ); + } + + transparentinput() { + return "transparent"; + } + + pictureinput(args) { + return `url("${encodeURI(args.URL)}")`; + } + + clearCSS() { + monitorText = ""; + monitorBorder = ""; + monitorBackgroundColor = ""; + variableValueBackground = ""; + variableValueTextColor = ""; + listFooterBackground = ""; + listHeaderBackground = ""; + listValueText = ""; + listValueBackground = ""; + variableValueRoundness = -1; + listValueRoundness = -1; + monitorBackgroundRoundness = -1; + monitorBackgroundBorderWidth = -1; + allowScrolling = ""; + askBackground = ""; + askBackgroundRoundness = -1; + askBackgroundBorderWidth = -1; + askButtonBackground = ""; + askButtonRoundness = -1; + askInputBackground = ""; + askInputRoundness = -1; + askInputBorderWidth = -1; + askBoxIcon = ""; + askInputText = ""; + askButtonImage = ""; + askInputBorder = ""; + applyCSS(); + } + + getValue(args) { + if (args.ITEM === "monitor text") { + return monitorText; + } else if (args.ITEM === "monitor background") { + return monitorBackgroundColor; + } else if (args.ITEM === "monitor border color") { + return monitorBorder; + } else if (args.ITEM === "variable value background") { + return variableValueBackground; + } else if (args.ITEM === "variable value text") { + return variableValueTextColor; + } else if (args.ITEM === "list header background") { + return listHeaderBackground; + } else if (args.ITEM === "list footer background") { + return listFooterBackground; + } else if (args.ITEM === "list value background") { + return listValueBackground; + } else if (args.ITEM === "list value text") { + return listValueText; + } else if (args.ITEM === "ask prompt background") { + return askBackground; + } else if (args.ITEM === "ask prompt button background") { + return askButtonBackground; + } else if (args.ITEM === "ask prompt input background") { + return askInputBackground; + } else if (args.ITEM === "ask prompt input text") { + return askInputText; + } else if (args.ITEM === "ask prompt input border") { + return askInputBorder; + } else if (args.ITEM === "monitor background border width") { + return monitorBackgroundBorderWidth; + } else if (args.ITEM === "ask prompt background border width") { + return askBackgroundBorderWidth; + } else if (args.ITEM === "ask prompt input border width") { + return askInputBorderWidth; + } else if (args.ITEM === "monitor background roundness") { + return monitorBackgroundRoundness; + } else if (args.ITEM === "variable value roundness") { + return variableValueRoundness; + } else if (args.ITEM === "list value roundness") { + return listValueRoundness; + } else if (args.ITEM === "ask prompt background roundness") { + return askBackgroundRoundness; + } else if (args.ITEM === "ask prompt button roundness") { + return askButtonRoundness; + } else if (args.ITEM === "ask prompt input roundness") { + return askInputRoundness; + } else if (args.ITEM === "ask prompt button image") { + return askButtonImage; + } else if (args.ITEM === "list scrolling") { + if (allowScrolling === "auto") { + return "enabled"; + } else { + return "disabled"; + } + } + return ""; + } + + setAskURI(args) { + return Scratch.canFetch(args.URL).then((allowed) => { + if (allowed) { + askButtonImage = args.URL; + applyCSS(); + } + }); + } + } + + Scratch.extensions.register(new MonitorStyles()); +})(Scratch); diff --git a/extensions/TheShovel/LZ-String.js b/extensions/TheShovel/LZ-String.js new file mode 100644 index 0000000000..635ed972ef --- /dev/null +++ b/extensions/TheShovel/LZ-String.js @@ -0,0 +1,435 @@ +// Name: LZ Compress +// ID: shovellzcompress +// Description: Compress and decompress text using lz-string. + +(function (Scratch) { + "use strict"; + + /* eslint-disable */ + // Code from https://github.com/pieroxy/lz-string/ + // MIT License + + // Copyright (c) 2013 pieroxy + + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in all + // copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + // SOFTWARE. + var LZString = (function () { + var r = String.fromCharCode, + o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + n = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$", + e = {}; + + function t(r, o) { + if (!e[r]) { + e[r] = {}; + for (var n = 0; n < r.length; n++) e[r][r.charAt(n)] = n; + } + return e[r][o]; + } + var i = { + compressToBase64: function (r) { + if (null == r) return ""; + var n = i._compress(r, 6, function (r) { + return o.charAt(r); + }); + switch (n.length % 4) { + default: + case 0: + return n; + case 1: + return n + "==="; + case 2: + return n + "=="; + case 3: + return n + "="; + } + }, + decompressFromBase64: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 32, function (n) { + return t(o, r.charAt(n)); + }); + }, + compressToUTF16: function (o) { + return null == o + ? "" + : i._compress(o, 15, function (o) { + return r(o + 32); + }) + " "; + }, + decompressFromUTF16: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 16384, function (o) { + return r.charCodeAt(o) - 32; + }); + }, + compressToUint8Array: function (r) { + for ( + var o = i.compress(r), + n = new Uint8Array(2 * o.length), + e = 0, + t = o.length; + e < t; + e++ + ) { + var s = o.charCodeAt(e); + (n[2 * e] = s >>> 8), (n[2 * e + 1] = s % 256); + } + return n; + }, + decompressFromUint8Array: function (o) { + if (null == o) return i.decompress(o); + for (var n = new Array(o.length / 2), e = 0, t = n.length; e < t; e++) + n[e] = 256 * o[2 * e] + o[2 * e + 1]; + var s = []; + return ( + n.forEach(function (o) { + s.push(r(o)); + }), + i.decompress(s.join("")) + ); + }, + compressToEncodedURIComponent: function (r) { + return null == r + ? "" + : i._compress(r, 6, function (r) { + return n.charAt(r); + }); + }, + decompressFromEncodedURIComponent: function (r) { + return null == r + ? "" + : "" == r + ? null + : ((r = r.replace(/ /g, "+")), + i._decompress(r.length, 32, function (o) { + return t(n, r.charAt(o)); + })); + }, + compress: function (o) { + return i._compress(o, 16, function (o) { + return r(o); + }); + }, + _compress: function (r, o, n) { + if (null == r) return ""; + var e, + t, + i, + s = {}, + u = {}, + a = "", + p = "", + c = "", + l = 2, + f = 3, + h = 2, + d = [], + m = 0, + v = 0; + for (i = 0; i < r.length; i += 1) + if ( + ((a = r.charAt(i)), + Object.prototype.hasOwnProperty.call(s, a) || + ((s[a] = f++), (u[a] = !0)), + (p = c + a), + Object.prototype.hasOwnProperty.call(s, p)) + ) + c = p; + else { + if (Object.prototype.hasOwnProperty.call(u, c)) { + if (c.charCodeAt(0) < 256) { + for (e = 0; e < h; e++) + (m <<= 1), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++; + for (t = c.charCodeAt(0), e = 0; e < 8; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } else { + for (t = 1, e = 0; e < h; e++) + (m = (m << 1) | t), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t = 0); + for (t = c.charCodeAt(0), e = 0; e < 16; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } + 0 == --l && ((l = Math.pow(2, h)), h++), delete u[c]; + } else + for (t = s[c], e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + 0 == --l && ((l = Math.pow(2, h)), h++), + (s[p] = f++), + (c = String(a)); + } + if ("" !== c) { + if (Object.prototype.hasOwnProperty.call(u, c)) { + if (c.charCodeAt(0) < 256) { + for (e = 0; e < h; e++) + (m <<= 1), v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++; + for (t = c.charCodeAt(0), e = 0; e < 8; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } else { + for (t = 1, e = 0; e < h; e++) + (m = (m << 1) | t), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t = 0); + for (t = c.charCodeAt(0), e = 0; e < 16; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + } + 0 == --l && ((l = Math.pow(2, h)), h++), delete u[c]; + } else + for (t = s[c], e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + 0 == --l && ((l = Math.pow(2, h)), h++); + } + for (t = 2, e = 0; e < h; e++) + (m = (m << 1) | (1 & t)), + v == o - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, + (t >>= 1); + for (;;) { + if (((m <<= 1), v == o - 1)) { + d.push(n(m)); + break; + } + v++; + } + return d.join(""); + }, + decompress: function (r) { + return null == r + ? "" + : "" == r + ? null + : i._decompress(r.length, 32768, function (o) { + return r.charCodeAt(o); + }); + }, + _decompress: function (o, n, e) { + var t, + i, + s, + u, + a, + p, + c, + l = [], + f = 4, + h = 4, + d = 3, + m = "", + v = [], + g = { + val: e(0), + position: n, + index: 1, + }; + for (t = 0; t < 3; t += 1) l[t] = t; + for (s = 0, a = Math.pow(2, 2), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + switch (s) { + case 0: + for (s = 0, a = Math.pow(2, 8), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + c = r(s); + break; + case 1: + for (s = 0, a = Math.pow(2, 16), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + c = r(s); + break; + case 2: + return ""; + } + for (l[3] = c, i = c, v.push(c); ; ) { + if (g.index > o) return ""; + for (s = 0, a = Math.pow(2, d), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + switch ((c = s)) { + case 0: + for (s = 0, a = Math.pow(2, 8), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + (l[h++] = r(s)), (c = h - 1), f--; + break; + case 1: + for (s = 0, a = Math.pow(2, 16), p = 1; p != a; ) + (u = g.val & g.position), + (g.position >>= 1), + 0 == g.position && ((g.position = n), (g.val = e(g.index++))), + (s |= (u > 0 ? 1 : 0) * p), + (p <<= 1); + (l[h++] = r(s)), (c = h - 1), f--; + break; + case 2: + return v.join(""); + } + // @ts-ignore + if ((0 == f && ((f = Math.pow(2, d)), d++), l[c])) m = l[c]; + else { + if (c !== h) return null; + m = i + i.charAt(0); + } + v.push(m), + (l[h++] = i + m.charAt(0)), + (i = m), + 0 == --f && ((f = Math.pow(2, d)), d++); + } + }, + }; + return i; + })(); + // @ts-ignore + "function" == typeof define && define.amd + ? define(function () { + return LZString; + // @ts-ignore + }) + : "undefined" != typeof module && null != module + ? (module.exports = LZString) + : "undefined" != typeof angular && + null != angular && + angular.module("LZString", []).factory("LZString", function () { + return LZString; + }); + /* eslint-enable */ + + class lzcompress { + getInfo() { + return { + id: "shovellzcompress", + name: "LZ Compress", + blocks: [ + { + opcode: "compress", + blockType: Scratch.BlockType.REPORTER, + text: "compress [TEXT] to [TYPE]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello world!", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "COMPRESSIONTYPES", + }, + }, + }, + { + opcode: "decompress", + blockType: Scratch.BlockType.REPORTER, + text: "decompress [TEXT] from [TYPE]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "҅〶惶@✰Ӏ葀", + }, + TYPE: { + type: Scratch.ArgumentType.STRING, + menu: "COMPRESSIONTYPES", + }, + }, + }, + ], + menus: { + COMPRESSIONTYPES: { + acceptReporters: true, + items: [ + "Raw", + "Base64", + "EncodedURIComponent", + "Uint8Array", + "UTF16", + ], + }, + }, + }; + } + compress(args) { + const text = Scratch.Cast.toString(args.TEXT); + if (args.TYPE == "Raw") { + return LZString.compress(text); + } else if (args.TYPE == "Base64") { + return LZString.compressToBase64(text); + } else if (args.TYPE == "EncodedURIComponent") { + return LZString.compressToEncodedURIComponent(text); + } else if (args.TYPE == "Uint8Array") { + return LZString.compressToUint8Array(text); + } else if (args.TYPE == "UTF16") { + return LZString.compressToUTF16(text); + } + return ""; + } + + decompress(args) { + try { + const text = Scratch.Cast.toString(args.TEXT); + if (args.TYPE == "Raw") { + return LZString.decompress(text) || ""; + } else if (args.TYPE == "Base64") { + return LZString.decompressFromBase64(text) || ""; + } else if (args.TYPE == "EncodedURIComponent") { + return LZString.decompressFromEncodedURIComponent(text) || ""; + } else if (args.TYPE == "Uint8Array") { + return LZString.decompressFromUint8Array(text) || ""; + } else if (args.TYPE == "UTF16") { + return LZString.decompressFromUTF16(text) || ""; + } + } catch (e) { + console.error("decompress error", e); + } + return ""; + } + } + Scratch.extensions.register(new lzcompress()); +})(Scratch); diff --git a/extensions/TheShovel/ShovelUtils.js b/extensions/TheShovel/ShovelUtils.js index bbbba655e0..0c8211915e 100644 --- a/extensions/TheShovel/ShovelUtils.js +++ b/extensions/TheShovel/ShovelUtils.js @@ -1,6 +1,14 @@ +// Name: ShovelUtils +// ID: ShovelUtils +// Description: A bunch of miscellaneous blocks. +// By: TheShovel + (function (Scratch) { - 'use strict'; - console.log("ShovelUtils v1.3"); + "use strict"; + if (!Scratch.extensions.unsandboxed) { + throw new Error("ShovelUtils must be run unsandboxed"); + } + console.log("ShovelUtils v1.4"); const vm = Scratch.vm; // Based on from https://www.growingwiththeweb.com/2017/12/fast-simple-js-fps-counter.html @@ -20,154 +28,175 @@ class ShovelUtils { getInfo() { return { - id: 'ShovelUtils', - name: 'ShovelUtils', - color1: '#f54242', - color2: '#f54242', - color3: '#f54242', + id: "ShovelUtils", + name: "ShovelUtils", + color1: "#f54242", + color2: "#f54242", + color3: "#f54242", blocks: [ { - opcode: 'importImage', + opcode: "importImage", blockType: Scratch.BlockType.COMMAND, - text: 'Import image from [TEXT] name [NAME]', + text: "Import image from [TEXT] name [NAME]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'https://extensions.turbowarp.org/dango.png', + defaultValue: "https://extensions.turbowarp.org/dango.png", }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Dango', - } - } + defaultValue: "Dango", + }, + }, }, { - opcode: 'getlist', + opcode: "getlist", blockType: Scratch.BlockType.REPORTER, - text: 'Get list [TEXT]', + text: "Get list [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'MyList', - } - } + defaultValue: "MyList", + }, + }, }, { - opcode: 'setlist', + opcode: "setlist", blockType: Scratch.BlockType.COMMAND, - text: 'Set list [NAME] to [TEXT]', + text: "Set list [NAME] to [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: '[1,2]', + defaultValue: "[1,2]", }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: 'MyList', - } - } + defaultValue: "MyList", + }, + }, }, { - opcode: 'importSprite', + opcode: "importSprite", blockType: Scratch.BlockType.COMMAND, - text: 'Import sprite from [TEXT]', + text: "Import sprite from [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Link or data uri here', - } - } + defaultValue: "Link or data uri here", + }, + }, }, { - opcode: 'importSound', + opcode: "importSound", blockType: Scratch.BlockType.COMMAND, - text: 'Import sound from [TEXT] name [NAME]', + text: "Import sound from [TEXT] name [NAME]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'https://extensions.turbowarp.org/meow.mp3', + defaultValue: "https://extensions.turbowarp.org/meow.mp3", }, NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Meow', - } - } + defaultValue: "Meow", + }, + }, }, { - opcode: 'importProject', + opcode: "importProject", blockType: Scratch.BlockType.COMMAND, - text: 'Import project from [TEXT]', + text: "Import project from [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'https://theshovel.github.io/Bullet-Hell/Bullet%20Hell', - } - } + defaultValue: + "https://theshovel.github.io/Bullet-Hell/Bullet%20Hell", + }, + }, }, { - opcode: 'loadExtension', + opcode: "loadExtension", blockType: Scratch.BlockType.COMMAND, - text: 'Load extension from [TEXT]', + text: "Load extension from [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'https://extensions.turbowarp.org/utilities.js', - } - } + defaultValue: "https://extensions.turbowarp.org/utilities.js", + }, + }, }, { - opcode: 'restartProject', + opcode: "restartProject", blockType: Scratch.BlockType.COMMAND, - text: 'Restart project', + text: "Restart project", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: '0', - } - } + defaultValue: "0", + }, + }, }, { - opcode: 'deleteSprite', + opcode: "deleteSprite", blockType: Scratch.BlockType.COMMAND, - text: 'Delete sprite [SPRITE]', + text: "Delete sprite [SPRITE]", arguments: { SPRITE: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Sprite1', - } - } + defaultValue: "Sprite1", + }, + }, + }, + { + opcode: "deleteImage", + blockType: Scratch.BlockType.COMMAND, + text: "Delete costume [COSNAME] in [SPRITE]", + arguments: { + COSNAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: "costume1", + }, + SPRITE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Sprite1", + }, + }, }, { - opcode: 'setedtarget', + opcode: "setedtarget", blockType: Scratch.BlockType.COMMAND, - text: 'Set editing target to [NAME]', + text: "Set editing target to [NAME]", arguments: { NAME: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Sprite1', - } - } + defaultValue: "Sprite1", + }, + }, }, { - opcode: 'brightnessByColor', + opcode: "brightnessByColor", blockType: Scratch.BlockType.REPORTER, - text: 'Get brightness of [color]', + text: "Get brightness of [color]", arguments: { color: { type: Scratch.ArgumentType.STRING, - defaultValue: '#ffffff', - } - } + defaultValue: "#ffffff", + }, + }, }, { - opcode: 'getfps', + opcode: "getAllSprites", blockType: Scratch.BlockType.REPORTER, - text: 'Fps' + text: "get all sprites", }, - ] + { + opcode: "getfps", + blockType: Scratch.BlockType.REPORTER, + text: "Fps", + }, + ], }; } @@ -176,23 +205,23 @@ .then((r) => r.arrayBuffer()) .then((arrayBuffer) => { const storage = vm.runtime.storage; - vm.addCostume(NAME + '.PNG', { - name: NAME + '', + vm.addCostume(NAME + ".PNG", { + name: NAME + "", asset: new storage.Asset( storage.AssetType.ImageBitmap, null, // asset id, doesn't need to be set here because of `true` at the end will make Scratch generate it for you storage.DataFormat.PNG, new Uint8Array(arrayBuffer), true - ) + ), }); }); } importSprite({ TEXT }) { Scratch.fetch(TEXT) - .then(r => r.arrayBuffer()) - .then(buffer => vm.addSprite(buffer)) + .then((r) => r.arrayBuffer()) + .then((buffer) => vm.addSprite(buffer)) .then(() => { console.log("Done"); }) @@ -207,8 +236,12 @@ return; } // @ts-expect-error - if (typeof ScratchBlocks !== 'undefined') { - if (!confirm(`Do you want to delete the sprite "${SPRITE}"? This cannot be undone.`)) { + if (typeof ScratchBlocks !== "undefined") { + if ( + !confirm( + `Do you want to delete the sprite "${SPRITE}"? This cannot be undone.` + ) + ) { return; } } @@ -228,24 +261,28 @@ true ); vm.addSound({ - md5: asset.assetId + '.' + asset.dataFormat, + md5: asset.assetId + "." + asset.dataFormat, asset: asset, - name: NAME + '' + name: NAME + "", }); }); } importProject({ TEXT }) { // @ts-ignore - if (typeof ScratchBlocks !== 'undefined') { + if (typeof ScratchBlocks !== "undefined") { // We are in the editor. Ask before loading a new project to avoid unrecoverable data loss. - if (!confirm(`Do you want to import a project from "${TEXT}"? Everything in the current project will be permanently deleted.`)) { + if ( + !confirm( + `Do you want to import a project from "${TEXT}"? Everything in the current project will be permanently deleted.` + ) + ) { return; } } Scratch.fetch(TEXT) - .then(r => r.arrayBuffer()) - .then(buffer => vm.loadProject(buffer)) + .then((r) => r.arrayBuffer()) + .then((buffer) => vm.loadProject(buffer)) .then(() => { console.log("Done"); vm.greenFlag(); @@ -266,7 +303,9 @@ } getlist({ TEXT }) { - const list = vm.runtime.getTargetForStage().lookupVariableByNameAndType(TEXT, 'list'); + const list = vm.runtime + .getTargetForStage() + .lookupVariableByNameAndType(TEXT, "list"); if (list) { return JSON.stringify(list.value); } else { @@ -292,7 +331,9 @@ } } - const list = vm.runtime.getTargetForStage().lookupVariableByNameAndType(NAME, 'list'); + const list = vm.runtime + .getTargetForStage() + .lookupVariableByNameAndType(NAME, "list"); if (!list) { return; // List was not found } @@ -321,15 +362,32 @@ */ brightnessByColor({ color }) { // https://www.w3.org/TR/AERT/#color-contrast - const {r, g, b} = Scratch.Cast.toRgbColorObject(color); - return ((r * 299) + (g * 587) + (b * 114)) / 1000; + const { r, g, b } = Scratch.Cast.toRgbColorObject(color); + return (r * 299 + g * 587 + b * 114) / 1000; } - getfps(){ + getfps() { return fps; } - } + deleteImage({ SPRITE, COSNAME }) { + // 0znzw, since shovel did not add it yet. + const target = vm.runtime.getSpriteTargetByName(SPRITE); + if (!target) { + return; + } + target.deleteCostume(target.getCostumeIndexByName(COSNAME)); + } + + getAllSprites() { + // 0znzw, since shovel did not add it yet. + let sprites = []; + for (const target of vm.runtime.targets) { + if (target.isOriginal) sprites.push(target.sprite.name); + } + return JSON.stringify(sprites); + } + } Scratch.extensions.register(new ShovelUtils()); -// @ts-ignore + // @ts-ignore })(Scratch); diff --git a/extensions/TheShovel/profanity.js b/extensions/TheShovel/profanity.js index 028023e4db..337dc397dc 100644 --- a/extensions/TheShovel/profanity.js +++ b/extensions/TheShovel/profanity.js @@ -1,8 +1,18 @@ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; - const encode = (str) => btoa(str).split('').map((i) => String.fromCharCode(i.charCodeAt(0) + 1)).join(''); - const decode = (str) => atob(str.split('').map((i) => String.fromCharCode(i.charCodeAt(0) - 1)).join('')); + const encode = (str) => + btoa(str) + .split("") + .map((i) => String.fromCharCode(i.charCodeAt(0) + 1)) + .join(""); + const decode = (str) => + atob( + str + .split("") + .map((i) => String.fromCharCode(i.charCodeAt(0) - 1)) + .join("") + ); // A forewarning for the reader: // This list contains some very bad naughty words, so we've encoded it in a way that @@ -214,44 +224,44 @@ "e3iwdnV>", "e3m{[XG{dx>>", "e3m{[XG{d3W{", - "e3:x" + "e3:x", ].map(decode); // Put the longest words first so that if "test" and "tests" are in the word list in // that order, redacting "tests" will give "***" instead of "***s" NAUGHTY_WORDS.sort((a, b) => b.length - a.length); - const regex = new RegExp(NAUGHTY_WORDS.join('|'), 'gi'); + const regex = new RegExp(NAUGHTY_WORDS.join("|"), "gi"); class Profanity { - getInfo () { + getInfo() { return { - id: 'theshovelprofanity', - name: 'Bad Word Remover', - color1: '#cf6a3c', - color2: '#cf6a3c', - color3: '#cf6a3c', + id: "theshovelprofanity", + name: "Bad Word Remover", + color1: "#cf6a3c", + color2: "#cf6a3c", + color3: "#cf6a3c", blocks: [ { - opcode: 'checkProfanity', + opcode: "checkProfanity", blockType: Scratch.BlockType.REPORTER, - text: 'replace bad words in [TEXT] with [REPLACEMENT]', + text: "replace bad words in [TEXT] with [REPLACEMENT]", arguments: { REPLACEMENT: { type: Scratch.ArgumentType.STRING, - defaultValue: '***', + defaultValue: "***", }, TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello!', - } - } + defaultValue: "Hello!", + }, + }, }, - ] + ], }; } - checkProfanity({TEXT, REPLACEMENT}) { + checkProfanity({ TEXT, REPLACEMENT }) { // Use a function as the second argument so that replacing with "$&" does not allow // bypass. return String(TEXT).replace(regex, () => REPLACEMENT); diff --git a/extensions/TheShovel/profanityAPI.js b/extensions/TheShovel/profanityAPI.js index ce2cef49fc..773158a22b 100644 --- a/extensions/TheShovel/profanityAPI.js +++ b/extensions/TheShovel/profanityAPI.js @@ -1,33 +1,35 @@ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; class profanityAPI { - getInfo () { + getInfo() { return { - id: 'profanityAPI', - name: 'profanityAPI', - color1: '#cf6a3c', - color2: '#cf6a3c', - color3: '#cf6a3c', + id: "profanityAPI", + name: "profanityAPI", + color1: "#cf6a3c", + color2: "#cf6a3c", + color3: "#cf6a3c", blocks: [ { - opcode: 'checkProfanity', + opcode: "checkProfanity", blockType: Scratch.BlockType.REPORTER, text: "Remove profanity from [TEXT]", arguments: { TEXT: { type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello, I love pizza!', - } - } + defaultValue: "Hello, I love pizza!", + }, + }, }, - ] + ], }; } - checkProfanity({TEXT}) { - return Scratch.fetch("https://www.purgomalum.com/service/plain?text=" + TEXT) - .then(r => r.text()) - .catch(() => ''); + checkProfanity({ TEXT }) { + return Scratch.fetch( + "https://www.purgomalum.com/service/plain?text=" + TEXT + ) + .then((r) => r.text()) + .catch(() => ""); } } diff --git a/extensions/WP-Studio01/text2speech.js b/extensions/WP-Studio01/text2speech.js index 74b72c3c86..a51c7cb015 100644 --- a/extensions/WP-Studio01/text2speech.js +++ b/extensions/WP-Studio01/text2speech.js @@ -1,40 +1,40 @@ -(function(Scratch) { - 'use strict'; - - class Tools { - getInfo () { - return { - id: 'wpstudio01tts', - name: 'System Text To Speech', - blocks: [ - { - opcode: 'speak', - blockType: Scratch.BlockType.COMMAND, - text: 'speak [TEXT]', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello' - } - } - } - ] - }; - } - - speak (args) { - return new Promise((resolve, reject) => { - const utterance = new SpeechSynthesisUtterance(args.TEXT); - utterance.onend = () => { - resolve(); - }; - utterance.onerror = () => { - reject(new Error('Utterance error')); - }; - speechSynthesis.speak(utterance); - }); - } - } - - Scratch.extensions.register(new Tools()); -})(Scratch); \ No newline at end of file +(function (Scratch) { + "use strict"; + + class Tools { + getInfo() { + return { + id: "wpstudio01tts", + name: "System Text To Speech", + blocks: [ + { + opcode: "speak", + blockType: Scratch.BlockType.COMMAND, + text: "speak [TEXT]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello", + }, + }, + }, + ], + }; + } + + speak(args) { + return new Promise((resolve, reject) => { + const utterance = new SpeechSynthesisUtterance(args.TEXT); + utterance.onend = () => { + resolve(); + }; + utterance.onerror = () => { + reject(new Error("Utterance error")); + }; + speechSynthesis.speak(utterance); + }); + } + } + + Scratch.extensions.register(new Tools()); +})(Scratch); diff --git a/extensions/Xeltalliv/clippingblending.js b/extensions/Xeltalliv/clippingblending.js index 9eb79bb8e5..3b79908c01 100644 --- a/extensions/Xeltalliv/clippingblending.js +++ b/extensions/Xeltalliv/clippingblending.js @@ -1,13 +1,19 @@ -(function(Scratch) { - 'use strict'; +// Name: Clipping & Blending +// ID: xeltallivclipblend +// Description: Clipping outside of a specified rectangular area and additive color blending. +// By: Vadik1 + +(function (Scratch) { + "use strict"; if (!Scratch.extensions.unsandboxed) { throw new Error("Effects extension must be run unsandboxed"); } - // Simplified remake of an icon by True-Fantom - const icon = 'data:image/svg+xml,' + encodeURIComponent(` + const icon = + "data:image/svg+xml," + + encodeURIComponent(` @@ -18,7 +24,6 @@ `); - let toCorrectThing = null; let active = false; let flipY = false; @@ -34,7 +39,6 @@ let scratchUnitHeight = 360; let penDirty = false; - renderer._drawThese = function (drawables, drawMode, projection, opts) { active = true; [scratchUnitWidth, scratchUnitHeight] = renderer.getNativeSize(); @@ -70,40 +74,40 @@ }; // Getting Drawable - const dr = renderer.createDrawable('background'); + const dr = renderer.createDrawable("background"); const DrawableProto = renderer._allDrawables[dr].__proto__; - renderer.destroyDrawable(dr, 'background'); + renderer.destroyDrawable(dr, "background"); function setupModes(clipbox, blendMode, flipY) { if (clipbox) { gl.enable(gl.SCISSOR_TEST); - let x = (clipbox.x_min / scratchUnitWidth + 0.5) * width | 0; - let y = (clipbox.y_min / scratchUnitHeight + 0.5) * height | 0; - let x2 = (clipbox.x_max / scratchUnitWidth + 0.5) * width | 0; - let y2 = (clipbox.y_max / scratchUnitHeight + 0.5) * height | 0; + let x = ((clipbox.x_min / scratchUnitWidth + 0.5) * width) | 0; + let y = ((clipbox.y_min / scratchUnitHeight + 0.5) * height) | 0; + let x2 = ((clipbox.x_max / scratchUnitWidth + 0.5) * width) | 0; + let y2 = ((clipbox.y_max / scratchUnitHeight + 0.5) * height) | 0; let w = x2 - x; let h = y2 - y; if (flipY) { - y = (-(clipbox.y_max) / scratchUnitHeight + 0.5) * height | 0; + y = ((-clipbox.y_max / scratchUnitHeight + 0.5) * height) | 0; } gl.scissor(x, y, w, h); } else { gl.disable(gl.SCISSOR_TEST); } switch (blendMode) { - case 'additive': + case "additive": gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.ONE, gl.ONE); break; - case 'subtract': + case "subtract": gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT); gl.blendFunc(gl.ONE, gl.ONE); break; - case 'multiply': + case "multiply": gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); break; - case 'invert': + case "invert": gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.ONE_MINUS_SRC_COLOR); break; @@ -128,7 +132,6 @@ this.blendMode = blendMode; }; - // Expanding renderer renderer.updateDrawableClipBox = function (drawableID, clipbox) { const drawable = this._allDrawables[drawableID]; @@ -141,16 +144,23 @@ drawable.updateBlendMode(blendMode); }; - // Reset on stop & clones inherit effects const regTargetStuff = function (args) { if (args.editingTarget) { - vm.removeListener('targetsUpdate', regTargetStuff); + vm.removeListener("targetsUpdate", regTargetStuff); const proto = vm.runtime.targets[0].__proto__; const osa = proto.onStopAll; proto.onStopAll = function () { - this.renderer.updateDrawableClipBox.call(renderer, this.drawableID, null); - this.renderer.updateDrawableBlendMode.call(renderer, this.drawableID, null); + this.renderer.updateDrawableClipBox.call( + renderer, + this.drawableID, + null + ); + this.renderer.updateDrawableBlendMode.call( + renderer, + this.drawableID, + null + ); osa.call(this); }; const mc = proto.makeClone; @@ -159,15 +169,22 @@ if (this.clipbox || this.blendMode) { newTarget.clipbox = Object.assign({}, this.clipbox); newTarget.blendMode = this.blendMode; - renderer.updateDrawableClipBox.call(renderer, newTarget.drawableID, this.clipbox); - renderer.updateDrawableBlendMode.call(renderer, newTarget.drawableID, this.blendMode); + renderer.updateDrawableClipBox.call( + renderer, + newTarget.drawableID, + this.clipbox + ); + renderer.updateDrawableBlendMode.call( + renderer, + newTarget.drawableID, + this.blendMode + ); } return newTarget; }; } }; - vm.on('targetsUpdate', regTargetStuff); - + vm.on("targetsUpdate", regTargetStuff); // Pen lines support let emptyObject = {}; @@ -184,14 +201,20 @@ lastClipbox = null; lastBlendMode = "default"; }; - const willDrawPenWithTarget = function(target) { + const willDrawPenWithTarget = function (target) { if (!penDirty && target == lastTarget) return; penDirty = false; const clipbox = target.clipbox; - if ((!lastClipbox ^ !clipbox) || - (lastBlendMode != target.blendMode) || - (clipbox && (clipbox.x_min != lastClipbox.x_min || clipbox.y_min != lastClipbox.y_min || clipbox.x_max != lastClipbox.x_max || clipbox.y_max != lastClipbox.y_max))) { + if ( + !lastClipbox ^ !clipbox || + lastBlendMode != target.blendMode || + (clipbox && + (clipbox.x_min != lastClipbox.x_min || + clipbox.y_min != lastClipbox.y_min || + clipbox.x_max != lastClipbox.x_max || + clipbox.y_max != lastClipbox.y_max)) + ) { if (skin.a_lineColorIndex) { skin._flushLines(); } @@ -201,7 +224,7 @@ x_min: clipbox.x_min, y_min: clipbox.y_min, x_max: clipbox.x_max, - y_max: clipbox.y_max + y_max: clipbox.y_max, }; } else { lastClipbox = null; @@ -213,7 +236,7 @@ // When drawing a line it is important to know the target. // This saves target. const onTargetMoved = ext_pen._onTargetMoved; - ext_pen._onTargetMoved = function(target, oldX, oldY, isForce) { + ext_pen._onTargetMoved = function (target, oldX, oldY, isForce) { willDrawPenWithTarget(target); onTargetMoved.call(this, target, oldX, oldY, isForce); }; @@ -226,13 +249,13 @@ // When drawing a dot it is important to know the target. // This saves target. const penDown = ext_pen._penDown; - ext_pen._penDown = function(target) { + ext_pen._penDown = function (target) { willDrawPenWithTarget(target); penDown.call(this, target); }; // Set up correct clipping/blending before drawing const flushLines = skin.__proto__._flushLines; - skin.__proto__._flushLines = function() { + skin.__proto__._flushLines = function () { setupModes(lastClipbox, lastBlendMode, true); flushLines.call(this); }; @@ -244,7 +267,7 @@ // If pen skin does not exist, wait until it will, // trigger code once, and return everything as it was const createPenSkin = renderer.createPenSkin; - renderer.createPenSkin = function() { + renderer.createPenSkin = function () { let skinId = createPenSkin.call(this); patchPen(renderer._allSkins[skinId]); renderer.createPenSkin = createPenSkin; @@ -255,128 +278,132 @@ class Extension { getInfo() { return { - id: 'xeltallivclipblend', - name: 'Clipping & Blending', - color1: '#9966FF', - color2: '#855CD6', - color3: '#774DCB', + id: "xeltallivclipblend", + name: "Clipping & Blending", + color1: "#9966FF", + color2: "#855CD6", + color3: "#774DCB", menuIconURI: icon, blocks: [ { - opcode: 'setClipbox', + opcode: "setClipbox", blockType: Scratch.BlockType.COMMAND, - text: 'set clipping box x1:[X1] y1:[Y1] x2:[X2] y2:[Y2]', + text: "set clipping box x1:[X1] y1:[Y1] x2:[X2] y2:[Y2]", arguments: { X1: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' + defaultValue: "0", }, Y1: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0' + defaultValue: "0", }, X2: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' + defaultValue: "100", }, Y2: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - } + defaultValue: "100", + }, }, - filter: [Scratch.TargetType.SPRITE] + filter: [Scratch.TargetType.SPRITE], }, { - opcode: 'clearClipbox', + opcode: "clearClipbox", blockType: Scratch.BlockType.COMMAND, - text: 'clear clipping box', - filter: [Scratch.TargetType.SPRITE] + text: "clear clipping box", + filter: [Scratch.TargetType.SPRITE], }, { - opcode: 'getClipbox', + opcode: "getClipbox", blockType: Scratch.BlockType.REPORTER, - text: 'clipping box [PROP]', + text: "clipping box [PROP]", arguments: { PROP: { type: Scratch.ArgumentType.STRING, - defaultValue: 'width', - menu: 'props' - } + defaultValue: "width", + menu: "props", + }, }, - filter: [Scratch.TargetType.SPRITE] + filter: [Scratch.TargetType.SPRITE], }, - '---', + "---", { - opcode: 'setBlend', + opcode: "setBlend", blockType: Scratch.BlockType.COMMAND, - text: 'use [BLENDMODE] blending ', + text: "use [BLENDMODE] blending ", arguments: { BLENDMODE: { type: Scratch.ArgumentType.STRING, - defaultValue: 'default', - menu: 'blends' - } + defaultValue: "default", + menu: "blends", + }, }, - filter: [Scratch.TargetType.SPRITE] + filter: [Scratch.TargetType.SPRITE], }, { - opcode: 'getBlend', + opcode: "getBlend", blockType: Scratch.BlockType.REPORTER, - text: 'blending', + text: "blending", filter: [Scratch.TargetType.SPRITE], - disableMonitor: true + disableMonitor: true, }, - '---', + "---", { - opcode: 'setAdditiveBlend', + opcode: "setAdditiveBlend", blockType: Scratch.BlockType.COMMAND, - text: 'turn additive blending [STATE]', + text: "turn additive blending [STATE]", arguments: { STATE: { type: Scratch.ArgumentType.STRING, - defaultValue: 'on', - menu: 'states' - } + defaultValue: "on", + menu: "states", + }, }, filter: [Scratch.TargetType.SPRITE], - hideFromPalette: true + hideFromPalette: true, }, { - opcode: 'getAdditiveBlend', + opcode: "getAdditiveBlend", blockType: Scratch.BlockType.BOOLEAN, - text: 'is additive blending on?', + text: "is additive blending on?", filter: [Scratch.TargetType.SPRITE], - hideFromPalette: true + hideFromPalette: true, }, ], menus: { states: { acceptReporters: true, - items: ['on', 'off'] + items: ["on", "off"], }, blends: { acceptReporters: true, - items: ['default', 'additive', 'subtract', 'multiply', 'invert'] + items: ["default", "additive", "subtract", "multiply", "invert"], }, props: { acceptReporters: true, - items: ['width', 'height', 'min x', 'min y', 'max x', 'max y'] + items: ["width", "height", "min x", "min y", "max x", "max y"], }, - } + }, }; } - setClipbox ({X1, Y1, X2, Y2}, {target}) { + setClipbox({ X1, Y1, X2, Y2 }, { target }) { if (target.isStage) return; const newClipbox = { x_min: Math.min(X1, X2), y_min: Math.min(Y1, Y2), x_max: Math.max(X1, X2), - y_max: Math.max(Y1, Y2) + y_max: Math.max(Y1, Y2), }; penDirty = true; target.clipbox = newClipbox; - renderer.updateDrawableClipBox.call(renderer, target.drawableID, newClipbox); + renderer.updateDrawableClipBox.call( + renderer, + target.drawableID, + newClipbox + ); if (target.visible) { renderer.dirty = true; target.emitVisualChange(); @@ -385,7 +412,7 @@ } } - clearClipbox (args, {target}) { + clearClipbox(args, { target }) { if (target.isStage) return; target.clipbox = null; penDirty = true; @@ -398,28 +425,35 @@ } } - getClipbox ({PROP}, {target}) { + getClipbox({ PROP }, { target }) { const clipbox = target.clipbox; - if (!clipbox) return ''; + if (!clipbox) return ""; switch (PROP) { - case 'width': return clipbox.x_max - clipbox.x_min; - case 'height': return clipbox.y_max - clipbox.y_min; - case 'min x': return clipbox.x_min; - case 'min y': return clipbox.y_min; - case 'max x': return clipbox.x_max; - case 'max y': return clipbox.y_max; - default: return ''; + case "width": + return clipbox.x_max - clipbox.x_min; + case "height": + return clipbox.y_max - clipbox.y_min; + case "min x": + return clipbox.x_min; + case "min y": + return clipbox.y_min; + case "max x": + return clipbox.x_max; + case "max y": + return clipbox.y_max; + default: + return ""; } } - setBlend ({BLENDMODE}, {target}) { + setBlend({ BLENDMODE }, { target }) { let newValue = null; switch (BLENDMODE) { - case 'default': - case 'additive': - case 'subtract': - case 'multiply': - case 'invert': + case "default": + case "additive": + case "subtract": + case "multiply": + case "invert": newValue = BLENDMODE; break; default: @@ -428,7 +462,11 @@ if (target.isStage) return; penDirty = true; target.blendMode = newValue; - renderer.updateDrawableBlendMode.call(renderer, target.drawableID, newValue); + renderer.updateDrawableBlendMode.call( + renderer, + target.drawableID, + newValue + ); if (target.visible) { renderer.dirty = true; target.emitVisualChange(); @@ -437,19 +475,19 @@ } } - getBlend (args, {target}) { - return target.blendMode ?? 'default'; + getBlend(args, { target }) { + return target.blendMode ?? "default"; } - setAdditiveBlend ({STATE}, util) { - if (STATE === 'on') this.setBlend ({BLENDMODE: 'additive'}, util); - if (STATE === 'off') this.setBlend ({BLENDMODE: 'default'}, util); + setAdditiveBlend({ STATE }, util) { + if (STATE === "on") this.setBlend({ BLENDMODE: "additive" }, util); + if (STATE === "off") this.setBlend({ BLENDMODE: "default" }, util); } - getAdditiveBlend (args, {target}) { - return target.blendMode === 'additive'; + getAdditiveBlend(args, { target }) { + return target.blendMode === "additive"; } } Scratch.extensions.register(new Extension()); -})(Scratch); \ No newline at end of file +})(Scratch); diff --git a/extensions/XeroName/Deltatime.js b/extensions/XeroName/Deltatime.js new file mode 100644 index 0000000000..0d6f3a7de0 --- /dev/null +++ b/extensions/XeroName/Deltatime.js @@ -0,0 +1,61 @@ +// Name: Deltatime +// ID: dtbyxeroname +// Description: Precise delta timing blocks. +// By: XeroName + +(function (Scratch) { + "use strict"; + + const icon = + "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAwIiBoZWlnaHQ9IjYwMCIgdmlld0JveD0iMCAwIDYwMCA2MDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxjaXJjbGUgY3g9IjMwMCIgY3k9IjMwMCIgcj0iMzAwIiBmaWxsPSIjMjAyMDIwIi8+CjxwYXRoIGQ9Ik04Ny44NjggNTEyLjEzMkM2MC4wMTA0IDQ4NC4yNzQgMzcuOTEyNSA0NTEuMjAzIDIyLjgzNjEgNDE0LjgwNUM3Ljc1OTcyIDM3OC40MDcgLTMuNDQ0MTZlLTA2IDMzOS4zOTcgMCAzMDBDMy40NDQxNmUtMDYgMjYwLjYwMyA3Ljc1OTc0IDIyMS41OTMgMjIuODM2MiAxODUuMTk1QzM3LjkxMjYgMTQ4Ljc5NyA2MC4wMTA0IDExNS43MjYgODcuODY4IDg3Ljg2NzlDMTE1LjcyNiA2MC4wMTA0IDE0OC43OTcgMzcuOTEyNSAxODUuMTk1IDIyLjgzNjFDMjIxLjU5MyA3Ljc1OTcxIDI2MC42MDQgLTkuODYyNjZlLTA2IDMwMCAwQzMzOS4zOTcgOS44NjI2OGUtMDYgMzc4LjQwNyA3Ljc1OTc1IDQxNC44MDUgMjIuODM2MkM0NTEuMjAzIDM3LjkxMjYgNDg0LjI3NSA2MC4wMTA0IDUxMi4xMzIgODcuODY4TDMwMCAzMDBMODcuODY4IDUxMi4xMzJaIiBmaWxsPSIjMzAzMDMwIi8+CjxwYXRoIGQ9Ik0zMzAgNDM1TDIzMCAxODUiIHN0cm9rZT0iIzYxMjM2MSIgc3Ryb2tlLXdpZHRoPSIzMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMjAgMTg1SDQyME01MjAgMTg1SDQyME00MjAgMTg1VjQzNU0yOTkuNDUxIDQzMy42MjlMMjAwLjkyOCAxODcuMzIxQzIwMC41OTMgMTg2LjQ4MyAxOTkuNDA3IDE4Ni40ODMgMTk5LjA3MiAxODcuMzIxTDEwMC41NDkgNDMzLjYyOUMxMDAuMjg2IDQzNC4yODUgMTAwLjc3IDQzNSAxMDEuNDc3IDQzNUgyOTguNTIzQzI5OS4yMyA0MzUgMjk5LjcxNCA0MzQuMjg1IDI5OS40NTEgNDMzLjYyOVoiIHN0cm9rZT0iIzYwNjA2MCIgc3Ryb2tlLXdpZHRoPSIzMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMTAgNDE1TDIxMCAxNjUiIHN0cm9rZT0iI0ZGNUNGRiIgc3Ryb2tlLXdpZHRoPSIzMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMDAgMTY1SDQwME01MDAgMTY1SDQwME00MDAgMTY1VjQxNU0yNzkuNDUxIDQxMy42MjlMMTgwLjkyOCAxNjcuMzIxQzE4MC41OTMgMTY2LjQ4MyAxNzkuNDA3IDE2Ni40ODMgMTc5LjA3MiAxNjcuMzIxTDgwLjU0ODYgNDEzLjYyOUM4MC4yODU4IDQxNC4yODUgODAuNzY5NiA0MTUgODEuNDc3IDQxNUgyNzguNTIzQzI3OS4yMyA0MTUgMjc5LjcxNCA0MTQuMjg1IDI3OS40NTEgNDEzLjYyOVoiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMzIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("DeltaTime must be run unsandboxed"); + } + + const vm = Scratch.vm; + + let deltaTime = 0; + let previousTime = 0; + + vm.runtime.on("BEFORE_EXECUTE", () => { + const now = performance.now(); + deltaTime = previousTime === 0 ? 0 : (now - previousTime) / 1000; + previousTime = now; + }); + + class Dt { + getInfo() { + return { + id: "dtbyxeroname", + name: "Deltatime", + color1: "#333333", + color2: "#444444", + color3: "#ffffff", + menuIconURI: icon, + blocks: [ + { + opcode: "dt", + blockType: Scratch.BlockType.REPORTER, + text: "ΔT", + }, + { + opcode: "fps", + blockType: Scratch.BlockType.REPORTER, + text: "fps", + }, + ], + }; + } + + dt() { + return deltaTime; + } + + fps() { + return +(1 / deltaTime).toFixed(2); + } + } + + Scratch.extensions.register(new Dt()); +})(Scratch); diff --git a/extensions/ZXMushroom63/searchApi.js b/extensions/ZXMushroom63/searchApi.js index 060096f0a5..6d25fe08b5 100644 --- a/extensions/ZXMushroom63/searchApi.js +++ b/extensions/ZXMushroom63/searchApi.js @@ -1,210 +1,203 @@ -(function (Scratch) { - "use strict"; - if (!Scratch.extensions.unsandboxed) { - throw new Error("SearchParams must be run unsandboxed."); - } - - // Disable in desktop app editor for security reasons. - // @ts-ignore - const disabled = typeof TWD !== 'undefined'; - - class SearchApi { - getInfo() { - return { - id: "zxmushroom63searchparams", - name: "Search Params", - color1: "#b4b4b4", - color2: "#9c9c9c", - color3: "#646464", - blocks: [ - { - blockType: Scratch.BlockType.LABEL, - text: 'Disabled in desktop editor for security', - hideFromPalette: !disabled - }, - { - opcode: "searchparam", - blockType: Scratch.BlockType.REPORTER, - text: "value of search parameter [ID]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - }, - }, - { - opcode: "occurencesofsearchparam", - blockType: Scratch.BlockType.REPORTER, - text: "occurences of search parameter [ID]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - }, - }, - { - opcode: "indexedsearchparam", - blockType: Scratch.BlockType.REPORTER, - text: "index [I] of search parameters [ID]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - I: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1, - }, - }, - }, - { - opcode: "setsearchparam", - blockType: Scratch.BlockType.COMMAND, - text: "set search parameter [ID] to [VAL]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - VAL: { - type: Scratch.ArgumentType.STRING, - defaultValue: "15", - }, - }, - }, - { - opcode: "deletesearchparam", - blockType: Scratch.BlockType.COMMAND, - text: "delete search parameter [ID]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - }, - }, - { - opcode: "appendsearchparam", - blockType: Scratch.BlockType.COMMAND, - text: "append search parameter [ID] with value [VAL]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - VAL: { - type: Scratch.ArgumentType.STRING, - defaultValue: "15", - }, - }, - }, - { - opcode: "hassearchparam", - blockType: Scratch.BlockType.BOOLEAN, - text: "has search parameter [ID]", - arguments: { - ID: { - type: Scratch.ArgumentType.STRING, - defaultValue: "x", - }, - }, - }, - { - opcode: "searchparamslength", - blockType: Scratch.BlockType.REPORTER, - text: "length of search parameters", - }, - { - opcode: "searchparamatindex", - blockType: Scratch.BlockType.REPORTER, - text: "search parameter [PARAM] at index [I]", - arguments: { - PARAM: { - type: Scratch.ArgumentType.STRING, - menu: "PARAM", - }, - I: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1, - }, - }, - }, - ], - menus: { - PARAM: { - acceptReporters: true, - items: ["value", "name"], - }, - }, - }; - } - - searchparam({ ID }) { - if (disabled) return ""; - return new URLSearchParams(location.search).get(ID.toString()) || ""; - } - - occurencesofsearchparam({ ID }) { - if (disabled) return 0; - return new URLSearchParams(location.search).getAll(ID.toString()).length || 0; - } - - indexedsearchparam({ ID, I }) { - if (disabled) return ""; - return new URLSearchParams(location.search).getAll(ID.toString())[parseInt(I) - 1] || ""; - } - - setsearchparam({ ID, VAL }) { - if (disabled) return; - var s = new URLSearchParams(location.search); - s.set(ID.toString(), VAL.toString()); - history.replaceState("", "", "?" + s.toString()); - } - - searchparamslength() { - if (disabled) return 0; - var s = new URLSearchParams(location.search); - // @ts-ignore - return typeof s.size !== "object" ? s.size : 0; - } - - deletesearchparam({ ID }) { - if (disabled) return; - var s = new URLSearchParams(location.search); - s.delete(ID.toString()); - history.replaceState("", "", "?" + s.toString()); - } - - appendsearchparam({ ID, VAL }) { - if (disabled) return; - var s = new URLSearchParams(location.search); - s.append(ID.toString(), VAL.toString()); - history.replaceState("", "", "?" + s.toString()); - } - - hassearchparam({ ID }) { - if (disabled) return false; - var s = new URLSearchParams(location.search); - return s.has(ID.toString()) || false; - } - - searchparamatindex({ PARAM, I }) { - if (disabled) return ""; - var index = parseInt(I) - 1 || 0; - index = Math.max(0, index); - var s = new URLSearchParams(location.search); - var values = PARAM.toString() === "value" ? s.values() : s.keys(); - var i = 0; - for (const value of values) { - if (i === index) { - return value; - } - i++; - } - return ""; - } - } - Scratch.extensions.register(new SearchApi()); -})(Scratch); +// Name: Search Params +// ID: zxmushroom63searchparams +// Description: Interact with URL search parameters: the part of the URL after a question mark. +// By: ZXMushroom63 + +(function (Scratch) { + "use strict"; + if (!Scratch.extensions.unsandboxed) { + throw new Error("SearchParams must be run unsandboxed."); + } + + class SearchApi { + getInfo() { + return { + id: "zxmushroom63searchparams", + name: "Search Params", + color1: "#b4b4b4", + color2: "#9c9c9c", + color3: "#646464", + blocks: [ + { + opcode: "searchparam", + blockType: Scratch.BlockType.REPORTER, + text: "value of search parameter [ID]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + }, + }, + { + opcode: "occurencesofsearchparam", + blockType: Scratch.BlockType.REPORTER, + text: "occurences of search parameter [ID]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + }, + }, + { + opcode: "indexedsearchparam", + blockType: Scratch.BlockType.REPORTER, + text: "index [I] of search parameters [ID]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + I: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + }, + }, + { + opcode: "setsearchparam", + blockType: Scratch.BlockType.COMMAND, + text: "set search parameter [ID] to [VAL]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + VAL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "15", + }, + }, + }, + { + opcode: "deletesearchparam", + blockType: Scratch.BlockType.COMMAND, + text: "delete search parameter [ID]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + }, + }, + { + opcode: "appendsearchparam", + blockType: Scratch.BlockType.COMMAND, + text: "append search parameter [ID] with value [VAL]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + VAL: { + type: Scratch.ArgumentType.STRING, + defaultValue: "15", + }, + }, + }, + { + opcode: "hassearchparam", + blockType: Scratch.BlockType.BOOLEAN, + text: "has search parameter [ID]", + arguments: { + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "x", + }, + }, + }, + { + opcode: "searchparamslength", + blockType: Scratch.BlockType.REPORTER, + text: "length of search parameters", + }, + { + opcode: "searchparamatindex", + blockType: Scratch.BlockType.REPORTER, + text: "search parameter [PARAM] at index [I]", + arguments: { + PARAM: { + type: Scratch.ArgumentType.STRING, + menu: "PARAM", + }, + I: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + }, + }, + }, + ], + menus: { + PARAM: { + acceptReporters: true, + items: ["value", "name"], + }, + }, + }; + } + + searchparam({ ID }) { + return new URLSearchParams(location.search).get(ID.toString()) || ""; + } + + occurencesofsearchparam({ ID }) { + return ( + new URLSearchParams(location.search).getAll(ID.toString()).length || 0 + ); + } + + indexedsearchparam({ ID, I }) { + return ( + new URLSearchParams(location.search).getAll(ID.toString())[ + parseInt(I) - 1 + ] || "" + ); + } + + setsearchparam({ ID, VAL }) { + var s = new URLSearchParams(location.search); + s.set(ID.toString(), VAL.toString()); + history.replaceState("", "", "?" + s.toString()); + } + + searchparamslength() { + var s = new URLSearchParams(location.search); + // @ts-ignore + return typeof s.size !== "object" ? s.size : 0; + } + + deletesearchparam({ ID }) { + var s = new URLSearchParams(location.search); + s.delete(ID.toString()); + history.replaceState("", "", "?" + s.toString()); + } + + appendsearchparam({ ID, VAL }) { + var s = new URLSearchParams(location.search); + s.append(ID.toString(), VAL.toString()); + history.replaceState("", "", "?" + s.toString()); + } + + hassearchparam({ ID }) { + var s = new URLSearchParams(location.search); + return s.has(ID.toString()) || false; + } + + searchparamatindex({ PARAM, I }) { + var index = parseInt(I) - 1 || 0; + index = Math.max(0, index); + var s = new URLSearchParams(location.search); + var values = PARAM.toString() === "value" ? s.values() : s.keys(); + var i = 0; + for (const value of values) { + if (i === index) { + return value; + } + i++; + } + return ""; + } + } + Scratch.extensions.register(new SearchApi()); +})(Scratch); diff --git a/extensions/ar.js b/extensions/ar.js index ed507084e9..0d9ad60a71 100644 --- a/extensions/ar.js +++ b/extensions/ar.js @@ -1,805 +1,845 @@ -(function(Scratch) { - "use strict"; - - /* globals XRWebGLLayer, XRRigidTransform, XRWebGLLayer */ - - if (!Scratch.extensions.unsandboxed) { - throw new Error("AR extension must be run unsandboxed"); +// Name: Augmented Reality +// ID: AR +// Description: Shows image from camera and performs motion tracking, allowing 3D projects to correctly overlay virtual objects on real world. +// By: Vadik1 + +(function (Scratch) { + "use strict"; + + /* globals XRWebGLLayer, XRRigidTransform, XRWebGLLayer */ + + if (!Scratch.extensions.unsandboxed) { + throw new Error("AR extension must be run unsandboxed"); + } + + const ArgumentType = Scratch.ArgumentType; + const BlockType = Scratch.BlockType; + + const vm = Scratch.vm; + const renderer = vm.renderer; + const runtime = vm.runtime; + const frameLoop = runtime.frameLoop; + const mouse = runtime.ioDevices.mouse; + const video = runtime.ioDevices.video; + + let arResolution = 1; + let isPackaged = false; + + let arFail = "uninitialized"; + let xrSession = null; + let xrState = false; + let xrRefSpace; + let xrViewSpace; + let xrProjectionMatrix; + let xrTransform; + let xrCombinedMatrix; + let xrHitTestSource; + let hitPosition; + let hitPositionAvailible; + let oldWidth = 0; + let oldHeight = 0; + let xrNeedsResize = false; + let poseAvailible = false; + let enterARDone = []; + + let stageWrapper; + let stageWrapperParent; + let scLayers; + let scControlsBar; + const div = document.createElement("div"); + document.body.append(div); + const canvas = Scratch.vm.renderer.canvas; + const gl = Scratch.vm.renderer.gl; + const enableVideoOriginal = video.enableVideo; + + // Checking whether AR is supported. + // If not, extension should still load, to let people + // develop AR projects on non-AR-capable devices and then + // test them on AR-capable mobile devices + if (!window.isSecureContext) { + console.error( + (arFail = + "Window is not secure context. WebXR only works in secure context") + ); + } else if (!navigator.xr) { + console.error( + (arFail = "navigator.xr is not defined in the browser you are using") + ); + } else { + gl.makeXRCompatible().catch((error) => { + console.error((arFail = "gl.makeXRCompatible rejected with: " + error)); + }); + navigator.xr.isSessionSupported("immersive-ar").then((supported) => { + if (!supported) { + console.error( + (arFail = + "WebXR exists in the browser you are using, but 'immersive-ar' session type is not supported") + ); + } else { + arFail = null; + } + }); + } + + const onSuccess = function (session) { + xrSession = session; + xrRefSpace = null; + xrViewSpace = null; + xrHitTestSource = null; + hitPosition = null; + hitPositionAvailible = false; + poseAvailible = false; + + session.updateRenderState({ + baseLayer: new XRWebGLLayer(session, gl, { + framebufferScaleFactor: arResolution, + }), + }); + session.addEventListener("end", () => { + xrSession = null; + updateState(); + }); + session.requestReferenceSpace("local").then((refSpace) => { + xrRefSpace = refSpace; + }); + session + .requestReferenceSpace("viewer") + .then((viewSpace) => { + xrViewSpace = viewSpace; + return session.requestHitTestSource({ space: viewSpace }); + }) + .then((hts) => { + xrHitTestSource = hts; + }); + updateState(); + + // [enter AR] blocks should continue after success + enterARDone.forEach((fn) => fn()); + enterARDone = []; + }; + const onError = function (error) { + // This shouldn't set arFail, because arFail is for cases when it permanently failed. + // This might fail once, but work on the next attempt. + console.error( + "Even though 'immersive-ar' is supported in your browser, requesting it failed" + ); + console.error(error); + + // [enter AR] blocks should continue after failure + enterARDone.forEach((fn) => fn()); + enterARDone = []; + }; + const onErrorTryTap = function (error) { + canvas.removeEventListener("pointerup", enterAR); + canvas.addEventListener("pointerup", enterAR, { once: true }); + }; + + const updateState = function () { + const state = !!xrSession; + if (state === xrState) return; + + xrState = state; + renderer.draw = state ? drawXR : drawOrig; + renderer.xr = xrSession; + frameLoop.inXR = state; + if (frameLoop.running) { + frameLoop.stop(); + frameLoop.start(); } - - - const ArgumentType = Scratch.ArgumentType; - const BlockType = Scratch.BlockType; - - const vm = Scratch.vm; - const renderer = vm.renderer; - const runtime = vm.runtime; - const frameLoop = runtime.frameLoop; - const mouse = runtime.ioDevices.mouse; - const video = runtime.ioDevices.video; - - let arResolution = 1; - let isPackaged = false; - - let arFail = "uninitialized"; - let xrSession = null; - let xrState = false; - let xrRefSpace; - let xrViewSpace; - let xrProjectionMatrix; - let xrTransform; - let xrCombinedMatrix; - let xrHitTestSource; - let hitPosition; - let hitPositionAvailible; - let oldWidth = 0; - let oldHeight = 0; - let xrNeedsResize = false; - let poseAvailible = false; - let enterARDone = []; - - let stageWrapper; - let stageWrapperParent; - let scLayers; - let scControlsBar; - const div = document.createElement("div"); - document.body.append(div); - const canvas = Scratch.vm.renderer.canvas; - const gl = Scratch.vm.renderer.gl; - const enableVideoOriginal = video.enableVideo; - - // Checking whether AR is supported. - // If not, extension should still load, to let people - // develop AR projects on non-AR-capable devices and then - // test them on AR-capable mobile devices - if (!window.isSecureContext) { - console.error(arFail = "Window is not secure context. WebXR only works in secure context"); - } else if (!navigator.xr) { - console.error(arFail = "navigator.xr is not defined in the browser you are using"); + canvas.removeEventListener("pointerup", enterAR); + if (state) { + video.disableVideo(); // Hiding it, since it freezes anyways + video.enableVideo = () => null; + + // css "transform" doesn't work directly on domOverlay element, + // but works on it's children. stageWrapper needs to have "transform: scale" + // on it, so that is why it is placed into another div + div.append(stageWrapper); + + xrNeedsResize = true; + oldWidth = runtime.stageWidth; + oldHeight = runtime.stageHeight; } else { - gl.makeXRCompatible().catch( - (error) => { - console.error(arFail = "gl.makeXRCompatible rejected with: " + error); - } - ); - navigator.xr.isSessionSupported("immersive-ar").then( - (supported) => { - if (!supported) { - console.error(arFail = "WebXR exists in the browser you are using, but 'immersive-ar' session type is not supported"); - } else { - arFail = null; - } - } - ); + video.enableVideo = enableVideoOriginal; // After exiting AR, video sensing can be used again + + if (!isPackaged) { + const borderThing = stageWrapper.children[0].children[0].style; + borderThing["border"] = ""; + borderThing["border-radius"] = ""; + } else { + scControlsBar.style["display"] = null; + scLayers.style["transform"] = null; + stageWrapper.style["align-items"] = null; + stageWrapper.style["justify-content"] = null; + runtime.setStageSize(oldWidth, oldHeight); + } + stageWrapper.style = ""; + stageWrapperParent.append(stageWrapper); + + canvas.style.opacity = ""; + gl.bindFramebuffer(gl.FRAMEBUFFER, null); } - - - const onSuccess = function(session) { - xrSession = session; - xrRefSpace = null; - xrViewSpace = null; - xrHitTestSource = null; - hitPosition = null; - hitPositionAvailible = false; - poseAvailible = false; - - session.updateRenderState({ - baseLayer: new XRWebGLLayer(session, gl, { framebufferScaleFactor: arResolution }) - }); - session.addEventListener("end", () => { - xrSession = null; - updateState(); - }); - session.requestReferenceSpace("local").then((refSpace) => { - xrRefSpace = refSpace; - }); - session.requestReferenceSpace("viewer").then((viewSpace) => { - xrViewSpace = viewSpace; - return session.requestHitTestSource({ space: viewSpace }); - }).then((hts) => { - xrHitTestSource = hts; - }); - updateState(); - - // [enter AR] blocks should continue after success - enterARDone.forEach(fn => fn()); - enterARDone = []; + }; + + // Code copied from tw-frame-loop.js because existing code can't be accesed + const _requestAnimationFrame = + typeof requestAnimationFrame === "function" + ? requestAnimationFrame + : (f) => setTimeout(f, 1000 / 60); + const _cancelAnimationFrame = + typeof requestAnimationFrame === "function" + ? cancelAnimationFrame + : clearTimeout; + + const animationFrameWrapper = (callback) => { + let id; + const handle = () => { + id = _requestAnimationFrame(handle); + callback(); }; - const onError = function(error) { - // This shouldn't set arFail, because arFail is for cases when it permanently failed. - // This might fail once, but work on the next attempt. - console.error("Even though 'immersive-ar' is supported in your browser, requesting it failed"); - console.error(error); - - // [enter AR] blocks should continue after failure - enterARDone.forEach(fn => fn()); - enterARDone = []; - }; - const onErrorTryTap = function(error) { - canvas.removeEventListener("pointerup", enterAR); - canvas.addEventListener("pointerup", enterAR, {once: true}); - }; - - const updateState = function() { - const state = !!xrSession; - if (state === xrState) return; - - xrState = state; - renderer.draw = state ? drawXR : drawOrig; - renderer.xr = xrSession; - frameLoop.inXR = state; - if (frameLoop.running) { - frameLoop.stop(); - frameLoop.start(); - } - canvas.removeEventListener("pointerup", enterAR); - if (state) { - video.disableVideo(); // Hiding it, since it freezes anyways - video.enableVideo = () => null; - - // css "transform" doesn't work directly on domOverlay element, - // but works on it's children. stageWrapper needs to have "transform: scale" - // on it, so that is why it is placed into another div - div.append(stageWrapper); - - xrNeedsResize = true; - oldWidth = runtime.stageWidth; - oldHeight = runtime.stageHeight; - } else { - video.enableVideo = enableVideoOriginal; // After exiting AR, video sensing can be used again - - if (!isPackaged) { - const borderThing = stageWrapper.children[0].children[0].style; - borderThing["border"] = ""; - borderThing["border-radius"] = ""; - } else { - scControlsBar.style["display"] = null; - scLayers.style["transform"] = null; - stageWrapper.style["align-items"] = null; - stageWrapper.style["justify-content"] = null; - runtime.setStageSize(oldWidth, oldHeight); - } - stageWrapper.style = ""; - stageWrapperParent.append(stageWrapper); - - canvas.style.opacity = ""; - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } + const cancel = () => _cancelAnimationFrame(id); + id = _requestAnimationFrame(handle); + return { + cancel, }; - - - - // Code copied from tw-frame-loop.js because existing code can't be accesed - const _requestAnimationFrame = typeof requestAnimationFrame === 'function' ? - requestAnimationFrame : - (f => setTimeout(f, 1000 / 60)); - const _cancelAnimationFrame = typeof requestAnimationFrame === 'function' ? - cancelAnimationFrame : - clearTimeout; - - const animationFrameWrapper = callback => { - let id; - const handle = () => { - id = _requestAnimationFrame(handle); - callback(); - }; - const cancel = () => _cancelAnimationFrame(id); - id = _requestAnimationFrame(handle); - return { - cancel - }; - }; - - - - // Patching frameLoop to use xrSession.requestAnimationFrame when in AR mode - const xrAnimationFrameWrapper = (callback, fps = 30) => { - const xrSessionBackup = xrSession; - let shouldTriggerAgain = false; - let id; - let idIsXR; - let interval; - const handle = (t, frame) => { - // If fps = 0, then run at screen's refresh rate - // and always use xr animation frame. - // In other cases keep using normal animation frame - // and waiting until shouldTriggerAgain gets set - // to true, and only then use xr animation frame - // once and resume waiting. shouldTriggerAgain is - // set to true by the interval located below. - if (fps === 0 || shouldTriggerAgain) { - shouldTriggerAgain = false; - id = xrSession.requestAnimationFrame(handle); - idIsXR = true; - } else { - id = window.requestAnimationFrame(handle); - idIsXR = false; - } - // Normal animation frames are just for waiting and - // shouldn't trigger callback() - if (!frame) return; - - if (xrNeedsResize) { - xrNeedsResize = false; - - // This needs to run before setStageSize - if (isPackaged) { - scControlsBar.style["display"] = "none"; - scLayers.style["transform"] = "translate(0px, 0px)"; - stageWrapper.style["align-items"] = "normal"; - stageWrapper.style["justify-content"] = "flex-start"; - } - - const bl = xrSession.renderState.baseLayer; - const newWidth = Math.round(bl.framebufferWidth / bl.framebufferHeight * oldHeight); - if (runtime.stageWidth !== newWidth) { - runtime.setStageSize(newWidth, oldHeight); - } - - const scale = div.clientHeight / canvas.clientHeight; - stageWrapper.style = "transform-origin: top left; transform: scale(" + scale + "," + scale + ")"; - canvas.style.opacity = "0"; - - if (!isPackaged) { - const borderThing = stageWrapper.children[0].children[0].style; - borderThing["border"] = "none"; - borderThing["border-radius"] = "0"; - borderThing["transform"] = ""; // Removes translateX - } - } - poseAvailible = false; - if (xrRefSpace) { - const pose = frame.getViewerPose(xrRefSpace); - if (pose) { - poseAvailible = true; - xrProjectionMatrix = pose.views[0].projectionMatrix; - xrTransform = pose.views[0].transform; - const inverseTransformMatrix = xrTransform.inverse.matrix; - const a00 = xrProjectionMatrix[0]; - const a01 = xrProjectionMatrix[1]; - const a02 = xrProjectionMatrix[2]; - const a03 = xrProjectionMatrix[3]; - const a10 = xrProjectionMatrix[4]; - const a11 = xrProjectionMatrix[5]; - const a12 = xrProjectionMatrix[6]; - const a13 = xrProjectionMatrix[7]; - const a20 = xrProjectionMatrix[8]; - const a21 = xrProjectionMatrix[9]; - const a22 = xrProjectionMatrix[10]; - const a23 = xrProjectionMatrix[11]; - const a30 = xrProjectionMatrix[12]; - const a31 = xrProjectionMatrix[13]; - const a32 = xrProjectionMatrix[14]; - const a33 = xrProjectionMatrix[15]; - const b00 = inverseTransformMatrix[0]; - const b01 = inverseTransformMatrix[1]; - const b02 = inverseTransformMatrix[2]; - const b03 = inverseTransformMatrix[3]; - const b10 = inverseTransformMatrix[4]; - const b11 = inverseTransformMatrix[5]; - const b12 = inverseTransformMatrix[6]; - const b13 = inverseTransformMatrix[7]; - const b20 = inverseTransformMatrix[8]; - const b21 = inverseTransformMatrix[9]; - const b22 = inverseTransformMatrix[10]; - const b23 = inverseTransformMatrix[11]; - const b30 = inverseTransformMatrix[12]; - const b31 = inverseTransformMatrix[13]; - const b32 = inverseTransformMatrix[14]; - const b33 = inverseTransformMatrix[15]; - xrCombinedMatrix = [ - b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, - b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, - b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, - b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, - b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, - b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, - b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, - b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, - b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, - b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, - b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, - b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, - b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, - b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, - b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, - b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33 - ]; - } - } - hitPositionAvailible = false; - if (xrHitTestSource) { - const hitTestResults = frame.getHitTestResults(xrHitTestSource); - if (hitTestResults.length > 0) { - hitPositionAvailible = true; - hitPosition = hitTestResults[0].getPose(xrRefSpace).transform.position; - } - } - callback(); - }; - const cancel = () => { - if (idIsXR) { - xrSessionBackup.cancelAnimationFrame(id); - } else { - cancelAnimationFrame(id); - } - if (interval) { - clearInterval(interval); - } - }; + }; + + // Patching frameLoop to use xrSession.requestAnimationFrame when in AR mode + const xrAnimationFrameWrapper = (callback, fps = 30) => { + const xrSessionBackup = xrSession; + let shouldTriggerAgain = false; + let id; + let idIsXR; + let interval; + const handle = (t, frame) => { + // If fps = 0, then run at screen's refresh rate + // and always use xr animation frame. + // In other cases keep using normal animation frame + // and waiting until shouldTriggerAgain gets set + // to true, and only then use xr animation frame + // once and resume waiting. shouldTriggerAgain is + // set to true by the interval located below. + if (fps === 0 || shouldTriggerAgain) { + shouldTriggerAgain = false; id = xrSession.requestAnimationFrame(handle); - if (fps > 0) { - interval = setInterval(() => { - shouldTriggerAgain = true; - }, 1000 / fps); + idIsXR = true; + } else { + id = window.requestAnimationFrame(handle); + idIsXR = false; + } + // Normal animation frames are just for waiting and + // shouldn't trigger callback() + if (!frame) return; + + if (xrNeedsResize) { + xrNeedsResize = false; + + // This needs to run before setStageSize + if (isPackaged) { + scControlsBar.style["display"] = "none"; + scLayers.style["transform"] = "translate(0px, 0px)"; + stageWrapper.style["align-items"] = "normal"; + stageWrapper.style["justify-content"] = "flex-start"; } - return { - cancel - }; - }; - const start = function() { - this.running = true; - if (this.inXR) { - if (this.framerate === 0) { - this._stepAnimation = this.xrAnimationFrameWrapper(this.stepCallback, 0); - this.runtime.currentStepTime = 1000 / 60; - } else { - // Interpolation should never be enabled when framerate === 0 as that's just redundant - if (this.interpolation) { - this._interpolationAnimation = animationFrameWrapper(this.interpolationCallback); - } - this._stepAnimation = this.xrAnimationFrameWrapper(this.stepCallback, this.framerate); - this.runtime.currentStepTime = 1000 / this.framerate; - } - } else { - if (this.framerate === 0) { - this._stepAnimation = animationFrameWrapper(this.stepCallback); - this.runtime.currentStepTime = 1000 / 60; - } else { - // Interpolation should never be enabled when framerate === 0 as that's just redundant - if (this.interpolation) { - this._interpolationAnimation = animationFrameWrapper(this.interpolationCallback); - } - this._stepInterval = setInterval(this.stepCallback, 1000 / this.framerate); - this.runtime.currentStepTime = 1000 / this.framerate; - } - } - }; - frameLoop.xrAnimationFrameWrapper = xrAnimationFrameWrapper.bind(frameLoop); - frameLoop.start = start.bind(frameLoop); - frameLoop.inXR = false; - - - // Patching renderer.draw() to draw to xr framebuffer instead of canvas - const drawOrig = renderer.draw.bind(renderer); - const drawXR = (function() { - const bl = this.xr.renderState.baseLayer; // ADDED - if (!bl) return; // Should fix very rare crash during exiting // ADDED - - this._doExitDrawRegion(); - - const gl = this._gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, bl.framebuffer); // CHANGED - gl.viewport(0, 0, bl.framebufferWidth, bl.framebufferHeight); // CHANGED - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - this._drawThese(this._drawList, "default" /*ShaderManager.DRAW_MODE.default*/, this._projection, { - framebufferWidth: bl.framebufferWidth, // CHANGED - framebufferHeight: bl.framebufferHeight // CHANGED - }); - if (this._snapshotCallbacks.length > 0) { - const snapshot = gl.canvas.toDataURL(); - this._snapshotCallbacks.forEach(cb => cb(snapshot)); - this._snapshotCallbacks = []; - } - }).bind(renderer); - renderer.draw = drawOrig; - - - - // Patching _pickTarget incorrect position bug: - // When the canvas is scaled using transform:scale, - // canvas.getBoundingClientRect is affected by it, but - // canvas.clientWidth and canvas.clientHeight are not. - // - // postData receives data.x and data.y, which are mouse position in - // screen units. To be able to rescale it to usable scratch units - // it also receives data.canvasWidth and data.canvasHeight - // which are based on getBoundingClientRect. Based of that it - // calculates this._scratchX and this._scratchY. - // Even when canvas is scaled, those are calculated correctly and - // as a result, blocks (mouse x) and (mouse y) report correct values. - // - // Later, postData calls _pickTarget, while only passing data.x and data.y - // without data.canvasWidth and data.canvasHeight. That method calls - // runtime renderer.pick, which calls clientSpaceToScratchBounds, which - // uses canvas.clientWidth and canvas.clientHeight to rescale mouse - // position from screen units to scratch units. This ignores - // transform:scale and as a result, sprites can't be clicked or dragged. - // - // WARNING: Makes _pickTarget only work correctly when called from postData. - // If something else calls it directly, it may cause problems. - const postDataOriginal = mouse.postData.bind(mouse); - mouse.postData = function(data) { - this._canvasWidth = data.canvasWidth; - this._canvasHeight = data.canvasHeight; - postDataOriginal(data); - }.bind(mouse); - - const _pickTargetOriginal = mouse._pickTarget.bind(mouse); - mouse._pickTarget = function (x, y) { - return _pickTargetOriginal( - x / this._canvasWidth * canvas.clientWidth, - y / this._canvasHeight * canvas.clientHeight + const bl = xrSession.renderState.baseLayer; + const newWidth = Math.round( + (bl.framebufferWidth / bl.framebufferHeight) * oldHeight ); - }.bind(mouse); - - // This is used by . - // It was also broken in a similar way. - mouse.getClientX = function() { - return this._clientX / this._canvasWidth * canvas.clientWidth; - }.bind(mouse); - - mouse.getClientY = function() { - return this._clientY / this._canvasHeight * canvas.clientHeight; - }.bind(mouse); - // END OF WARNING - - - const enterAR = function(event) { - if (!xrSession) { - // Entering and exiting editor recreates this element - stageWrapper = document.querySelector("[class*='stage-wrapper_stage-canvas-wrapper']"); - if (!stageWrapper) { - stageWrapper = document.querySelector("[class='sc-root']"); - scControlsBar = document.querySelector("[class='sc-controls-bar']"); - scLayers = document.querySelector("[class='sc-layers']"); - if (!stageWrapper) { - console.error(arFail = "Failed to get the div element of the stage"); - return; - } - isPackaged = true; - } - stageWrapperParent = stageWrapper.parentElement; - - const noop = () => {}; - navigator.xr.requestSession("immersive-ar", { - requiredFeatures: ["hit-test", "dom-overlay"], - domOverlay: {root: div} - }).then(onSuccess, event ? onError : onErrorTryTap); - // If (event) is defined, it was from click, so something went wrong. - // If (event) is null, it was called directly, and might've been rejected due to lack of user interaction. + if (runtime.stageWidth !== newWidth) { + runtime.setStageSize(newWidth, oldHeight); } - }; - - - class ARExtension { - getInfo() { - return { - id: "AR", - color1: "#d10000", - color2: "#bd0000", - color3: "#af0100", - docsURI: "https://extensions.turbowarp.org/ar.html", - blocks: [ - { - opcode: "enterAR", - blockType: BlockType.COMMAND, - text: "enter AR mode", - arguments: {} - }, - { - opcode: "exitAR", - blockType: BlockType.COMMAND, - text: "exit AR mode", - arguments: {} - }, - { - opcode: "isInAR", - blockType: BlockType.BOOLEAN, - text: "is in AR?", - arguments: {} - }, - { - opcode: "isFeatureAvailible", - blockType: BlockType.BOOLEAN, - text: "is [FEATURE] availible?", - arguments: { - FEATURE: { - type: ArgumentType.STRING, - menu: "xrFeature", - defaultValue: "ar" - } - } - }, - "---", - { - opcode: "getStageWidth", - blockType: BlockType.REPORTER, - text: "stage width", - arguments: {} - }, - { - opcode: "getStageHeight", - blockType: BlockType.REPORTER, - text: "stage height", - arguments: {} - }, - "---", - { - opcode: "getMatrixItem", - blockType: BlockType.REPORTER, - text: "item [ITEM] of [MATRIX] matrix", - arguments: { - MATRIX: { - type: ArgumentType.STRING, - menu: "xrMatrix", - defaultValue: "combined" - }, - ITEM: { - type: ArgumentType.NUMBER, - defaultValue: 1 - } - } - }, - { - opcode: "getPosition", - blockType: BlockType.REPORTER, - text: "position [POSITION_COMPONENT]", - arguments: { - POSITION_COMPONENT: { - type: ArgumentType.STRING, - menu: "positionComponent", - defaultValue: "x" - } - } - }, - { - opcode: "getOrientation", - blockType: BlockType.REPORTER, - text: "orientation [ORIENTATION_COMPONENT]", - arguments: { - ORIENTATION_COMPONENT: { - type: ArgumentType.STRING, - menu: "orientationComponent", - defaultValue: "w" - } - } - }, - "---", - { - opcode: "getHitPosition", - blockType: BlockType.REPORTER, - text: "hit position [POSITION_COMPONENT]", - arguments: { - POSITION_COMPONENT: { - type: ArgumentType.STRING, - menu: "positionComponent", - defaultValue: "x" - } - } - }, - "---", - { - opcode: "moveSpaceBy", - blockType: BlockType.COMMAND, - text: "move everything by x:[X] y:[Y] z:[Z]", - arguments: { - X: { - type: ArgumentType.NUMBER, - defaultValue: 0 - }, - Y: { - type: ArgumentType.NUMBER, - defaultValue: 0 - }, - Z: { - type: ArgumentType.NUMBER, - defaultValue: 0 - } - } - }, - { - opcode: "turnSpaceBy", - blockType: BlockType.COMMAND, - text: "turn everything by r:[R] i:[I] j:[J] k:[K]", - arguments: { - R: { - type: ArgumentType.NUMBER, - defaultValue: 1 - }, - I: { - type: ArgumentType.NUMBER, - defaultValue: 0 - }, - J: { - type: ArgumentType.NUMBER, - defaultValue: 0 - }, - K: { - type: ArgumentType.NUMBER, - defaultValue: 0 - } - } - }, - "---", - { - opcode: "setResolution", - blockType: BlockType.COMMAND, - text: "set resolution [RESOLUTION]", - arguments: { - RESOLUTION: { - type: ArgumentType.NUMBER, - defaultValue: 1 - } - } - }, - ], - menus: { - positionComponent: { - acceptReporters: false, - items: [ - { - text: "x", - value: "x" - }, - { - text: "y", - value: "y" - }, - { - text: "z", - value: "z" - } - ] - }, - orientationComponent: { - acceptReporters: false, - items: [ - { - text: "r", - value: "w" - }, - { - text: "i", - value: "x" - }, - { - text: "j", - value: "y" - }, - { - text: "k", - value: "z" - } - ] - }, - xrMatrix: { - acceptReporters: false, - items: [ - "combined", - "projection", - "view", - "inverse view" - ] - }, - xrFeature: { - acceptReporters: false, - items: [ - "ar", - "pose", - "hit position" - ] - } - } - }; - } - enterAR() { - if (arFail) { - if (arFail !== "shown") { - // AR is used on mobile, where accessing browser console to see what's wrong can be an issue - alert("Project attempted to start AR even though it's not avalible. The reason: " + arFail + ". This message will only be shown once."); - arFail = "shown"; - } - } else { - if (!xrSession) { - if (enterARDone.length === 0) enterAR(null); - return new Promise(resolve => enterARDone.push(resolve)); - } - } - } - exitAR() { - if (xrSession) { - xrSession.end(); - } - } - isInAR() { - return !!xrSession; + const scale = div.clientHeight / canvas.clientHeight; + stageWrapper.style = + "transform-origin: top left; transform: scale(" + + scale + + "," + + scale + + ")"; + canvas.style.opacity = "0"; + + if (!isPackaged) { + const borderThing = stageWrapper.children[0].children[0].style; + borderThing["border"] = "none"; + borderThing["border-radius"] = "0"; + borderThing["transform"] = ""; // Removes translateX } - getStageWidth() { - return runtime.stageWidth; + } + poseAvailible = false; + if (xrRefSpace) { + const pose = frame.getViewerPose(xrRefSpace); + if (pose) { + poseAvailible = true; + xrProjectionMatrix = pose.views[0].projectionMatrix; + xrTransform = pose.views[0].transform; + const inverseTransformMatrix = xrTransform.inverse.matrix; + const a00 = xrProjectionMatrix[0]; + const a01 = xrProjectionMatrix[1]; + const a02 = xrProjectionMatrix[2]; + const a03 = xrProjectionMatrix[3]; + const a10 = xrProjectionMatrix[4]; + const a11 = xrProjectionMatrix[5]; + const a12 = xrProjectionMatrix[6]; + const a13 = xrProjectionMatrix[7]; + const a20 = xrProjectionMatrix[8]; + const a21 = xrProjectionMatrix[9]; + const a22 = xrProjectionMatrix[10]; + const a23 = xrProjectionMatrix[11]; + const a30 = xrProjectionMatrix[12]; + const a31 = xrProjectionMatrix[13]; + const a32 = xrProjectionMatrix[14]; + const a33 = xrProjectionMatrix[15]; + const b00 = inverseTransformMatrix[0]; + const b01 = inverseTransformMatrix[1]; + const b02 = inverseTransformMatrix[2]; + const b03 = inverseTransformMatrix[3]; + const b10 = inverseTransformMatrix[4]; + const b11 = inverseTransformMatrix[5]; + const b12 = inverseTransformMatrix[6]; + const b13 = inverseTransformMatrix[7]; + const b20 = inverseTransformMatrix[8]; + const b21 = inverseTransformMatrix[9]; + const b22 = inverseTransformMatrix[10]; + const b23 = inverseTransformMatrix[11]; + const b30 = inverseTransformMatrix[12]; + const b31 = inverseTransformMatrix[13]; + const b32 = inverseTransformMatrix[14]; + const b33 = inverseTransformMatrix[15]; + xrCombinedMatrix = [ + b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, + b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, + b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, + b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, + b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, + b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, + b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, + b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, + b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, + b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, + b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, + b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, + b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, + b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, + b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, + b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, + ]; } - getStageHeight() { - return runtime.stageHeight; + } + hitPositionAvailible = false; + if (xrHitTestSource) { + const hitTestResults = frame.getHitTestResults(xrHitTestSource); + if (hitTestResults.length > 0) { + hitPositionAvailible = true; + hitPosition = + hitTestResults[0].getPose(xrRefSpace).transform.position; } - getMatrixItem(args) { - let item = args.ITEM | 0; - if (item < 1 && item > 16) return ""; - let matrix = null; - switch (args.MATRIX) { - case "combined": - matrix = xrCombinedMatrix; - break; - case "projection": - matrix = xrProjectionMatrix; - break; - case "view": - matrix = xrTransform?.matrix; - break; - case "inverse view": - matrix = xrTransform?.inverse?.matrix; - break; - } - if (!matrix) return 0; - return matrix[item - 1] || 0; - } - moveSpaceBy(args) { - if (!xrRefSpace) return; - const x = +args.X || 0; - const y = +args.Y || 0; - const z = +args.Z || 0; - if (!isFinite(x + y + z)) return; - const offsetTransform = new XRRigidTransform({x: x, y: y, z: z}, {x: 0, y: 0, z: 0, w: 1}); - xrRefSpace = xrRefSpace.getOffsetReferenceSpace(offsetTransform); - } - turnSpaceBy(args) { - if (!xrRefSpace) return; - const r = +args.R || 0; - const i = +args.I || 0; - const j = +args.J || 0; - const k = +args.K || 0; - const len = Math.sqrt(r * r + i * i + j * j + k * k); - if (!isFinite(len) || len === 0) return; - const offsetTransform = new XRRigidTransform({x: 0, y: 0, z: 0}, {x: i / len, y: j / len, z: k / len, w: r / len}); - xrRefSpace = xrRefSpace.getOffsetReferenceSpace(offsetTransform); - } - getPosition(args) { - if (!xrTransform) return 0; - return xrTransform.position[args.POSITION_COMPONENT] || 0; + } + callback(); + }; + const cancel = () => { + if (idIsXR) { + xrSessionBackup.cancelAnimationFrame(id); + } else { + cancelAnimationFrame(id); + } + if (interval) { + clearInterval(interval); + } + }; + id = xrSession.requestAnimationFrame(handle); + if (fps > 0) { + interval = setInterval(() => { + shouldTriggerAgain = true; + }, 1000 / fps); + } + return { + cancel, + }; + }; + const start = function () { + this.running = true; + if (this.inXR) { + if (this.framerate === 0) { + this._stepAnimation = this.xrAnimationFrameWrapper( + this.stepCallback, + 0 + ); + this.runtime.currentStepTime = 1000 / 60; + } else { + // Interpolation should never be enabled when framerate === 0 as that's just redundant + if (this.interpolation) { + this._interpolationAnimation = animationFrameWrapper( + this.interpolationCallback + ); } - getOrientation(args) { - if (!xrTransform) return 0; - return xrTransform.orientation[args.ORIENTATION_COMPONENT] || 0; + this._stepAnimation = this.xrAnimationFrameWrapper( + this.stepCallback, + this.framerate + ); + this.runtime.currentStepTime = 1000 / this.framerate; + } + } else { + if (this.framerate === 0) { + this._stepAnimation = animationFrameWrapper(this.stepCallback); + this.runtime.currentStepTime = 1000 / 60; + } else { + // Interpolation should never be enabled when framerate === 0 as that's just redundant + if (this.interpolation) { + this._interpolationAnimation = animationFrameWrapper( + this.interpolationCallback + ); } - getHitPosition(args) { - if (!hitPosition) return 0; - return hitPosition[args.POSITION_COMPONENT] || 0; + this._stepInterval = setInterval( + this.stepCallback, + 1000 / this.framerate + ); + this.runtime.currentStepTime = 1000 / this.framerate; + } + } + }; + frameLoop.xrAnimationFrameWrapper = xrAnimationFrameWrapper.bind(frameLoop); + frameLoop.start = start.bind(frameLoop); + frameLoop.inXR = false; + + // Patching renderer.draw() to draw to xr framebuffer instead of canvas + const drawOrig = renderer.draw.bind(renderer); + const drawXR = function () { + const bl = this.xr.renderState.baseLayer; // ADDED + if (!bl) return; // Should fix very rare crash during exiting // ADDED + + this._doExitDrawRegion(); + + const gl = this._gl; + + gl.bindFramebuffer(gl.FRAMEBUFFER, bl.framebuffer); // CHANGED + gl.viewport(0, 0, bl.framebufferWidth, bl.framebufferHeight); // CHANGED + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this._drawThese( + this._drawList, + "default" /*ShaderManager.DRAW_MODE.default*/, + this._projection, + { + framebufferWidth: bl.framebufferWidth, // CHANGED + framebufferHeight: bl.framebufferHeight, // CHANGED + } + ); + if (this._snapshotCallbacks.length > 0) { + const snapshot = gl.canvas.toDataURL(); + this._snapshotCallbacks.forEach((cb) => cb(snapshot)); + this._snapshotCallbacks = []; + } + }.bind(renderer); + renderer.draw = drawOrig; + + // Patching _pickTarget incorrect position bug: + // When the canvas is scaled using transform:scale, + // canvas.getBoundingClientRect is affected by it, but + // canvas.clientWidth and canvas.clientHeight are not. + // + // postData receives data.x and data.y, which are mouse position in + // screen units. To be able to rescale it to usable scratch units + // it also receives data.canvasWidth and data.canvasHeight + // which are based on getBoundingClientRect. Based of that it + // calculates this._scratchX and this._scratchY. + // Even when canvas is scaled, those are calculated correctly and + // as a result, blocks (mouse x) and (mouse y) report correct values. + // + // Later, postData calls _pickTarget, while only passing data.x and data.y + // without data.canvasWidth and data.canvasHeight. That method calls + // runtime renderer.pick, which calls clientSpaceToScratchBounds, which + // uses canvas.clientWidth and canvas.clientHeight to rescale mouse + // position from screen units to scratch units. This ignores + // transform:scale and as a result, sprites can't be clicked or dragged. + // + // WARNING: Makes _pickTarget only work correctly when called from postData. + // If something else calls it directly, it may cause problems. + const postDataOriginal = mouse.postData.bind(mouse); + mouse.postData = function (data) { + this._canvasWidth = data.canvasWidth; + this._canvasHeight = data.canvasHeight; + postDataOriginal(data); + }.bind(mouse); + + const _pickTargetOriginal = mouse._pickTarget.bind(mouse); + mouse._pickTarget = function (x, y) { + return _pickTargetOriginal( + (x / this._canvasWidth) * canvas.clientWidth, + (y / this._canvasHeight) * canvas.clientHeight + ); + }.bind(mouse); + + // This is used by . + // It was also broken in a similar way. + mouse.getClientX = function () { + return (this._clientX / this._canvasWidth) * canvas.clientWidth; + }.bind(mouse); + + mouse.getClientY = function () { + return (this._clientY / this._canvasHeight) * canvas.clientHeight; + }.bind(mouse); + // END OF WARNING + + const enterAR = function (event) { + if (!xrSession) { + // Entering and exiting editor recreates this element + stageWrapper = document.querySelector( + "[class*='stage-wrapper_stage-canvas-wrapper']" + ); + if (!stageWrapper) { + stageWrapper = document.querySelector("[class='sc-root']"); + scControlsBar = document.querySelector("[class='sc-controls-bar']"); + scLayers = document.querySelector("[class='sc-layers']"); + if (!stageWrapper) { + console.error( + (arFail = "Failed to get the div element of the stage") + ); + return; } - isFeatureAvailible(args) { - switch (args.FEATURE) { - case "ar": - return !arFail; - case "pose": - return poseAvailible; - case "hit position": - return hitPositionAvailible; - default: - return false; - } + isPackaged = true; + } + stageWrapperParent = stageWrapper.parentElement; + + const noop = () => {}; + navigator.xr + .requestSession("immersive-ar", { + requiredFeatures: ["hit-test", "dom-overlay"], + domOverlay: { root: div }, + }) + .then(onSuccess, event ? onError : onErrorTryTap); + // If (event) is defined, it was from click, so something went wrong. + // If (event) is null, it was called directly, and might've been rejected due to lack of user interaction. + } + }; + + class ARExtension { + getInfo() { + return { + id: "AR", + color1: "#d10000", + color2: "#bd0000", + color3: "#af0100", + docsURI: "https://extensions.turbowarp.org/ar", + blocks: [ + { + opcode: "enterAR", + blockType: BlockType.COMMAND, + text: "enter AR mode", + arguments: {}, + }, + { + opcode: "exitAR", + blockType: BlockType.COMMAND, + text: "exit AR mode", + arguments: {}, + }, + { + opcode: "isInAR", + blockType: BlockType.BOOLEAN, + text: "is in AR?", + arguments: {}, + }, + { + opcode: "isFeatureAvailible", + blockType: BlockType.BOOLEAN, + text: "is [FEATURE] availible?", + arguments: { + FEATURE: { + type: ArgumentType.STRING, + menu: "xrFeature", + defaultValue: "ar", + }, + }, + }, + "---", + { + opcode: "getStageWidth", + blockType: BlockType.REPORTER, + text: "stage width", + arguments: {}, + }, + { + opcode: "getStageHeight", + blockType: BlockType.REPORTER, + text: "stage height", + arguments: {}, + }, + "---", + { + opcode: "getMatrixItem", + blockType: BlockType.REPORTER, + text: "item [ITEM] of [MATRIX] matrix", + arguments: { + MATRIX: { + type: ArgumentType.STRING, + menu: "xrMatrix", + defaultValue: "combined", + }, + ITEM: { + type: ArgumentType.NUMBER, + defaultValue: 1, + }, + }, + }, + { + opcode: "getPosition", + blockType: BlockType.REPORTER, + text: "position [POSITION_COMPONENT]", + arguments: { + POSITION_COMPONENT: { + type: ArgumentType.STRING, + menu: "positionComponent", + defaultValue: "x", + }, + }, + }, + { + opcode: "getOrientation", + blockType: BlockType.REPORTER, + text: "orientation [ORIENTATION_COMPONENT]", + arguments: { + ORIENTATION_COMPONENT: { + type: ArgumentType.STRING, + menu: "orientationComponent", + defaultValue: "w", + }, + }, + }, + "---", + { + opcode: "getHitPosition", + blockType: BlockType.REPORTER, + text: "hit position [POSITION_COMPONENT]", + arguments: { + POSITION_COMPONENT: { + type: ArgumentType.STRING, + menu: "positionComponent", + defaultValue: "x", + }, + }, + }, + "---", + { + opcode: "moveSpaceBy", + blockType: BlockType.COMMAND, + text: "move everything by x:[X] y:[Y] z:[Z]", + arguments: { + X: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + Y: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + Z: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + }, + }, + { + opcode: "turnSpaceBy", + blockType: BlockType.COMMAND, + text: "turn everything by r:[R] i:[I] j:[J] k:[K]", + arguments: { + R: { + type: ArgumentType.NUMBER, + defaultValue: 1, + }, + I: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + J: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + K: { + type: ArgumentType.NUMBER, + defaultValue: 0, + }, + }, + }, + "---", + { + opcode: "setResolution", + blockType: BlockType.COMMAND, + text: "set resolution [RESOLUTION]", + arguments: { + RESOLUTION: { + type: ArgumentType.NUMBER, + defaultValue: 1, + }, + }, + }, + ], + menus: { + positionComponent: { + acceptReporters: false, + items: [ + { + text: "x", + value: "x", + }, + { + text: "y", + value: "y", + }, + { + text: "z", + value: "z", + }, + ], + }, + orientationComponent: { + acceptReporters: false, + items: [ + { + text: "r", + value: "w", + }, + { + text: "i", + value: "x", + }, + { + text: "j", + value: "y", + }, + { + text: "k", + value: "z", + }, + ], + }, + xrMatrix: { + acceptReporters: false, + items: ["combined", "projection", "view", "inverse view"], + }, + xrFeature: { + acceptReporters: false, + items: ["ar", "pose", "hit position"], + }, + }, + }; + } + enterAR() { + if (arFail) { + if (arFail !== "shown") { + // AR is used on mobile, where accessing browser console to see what's wrong can be an issue + alert( + "Project attempted to start AR even though it's not avalible. The reason: " + + arFail + + ". This message will only be shown once." + ); + arFail = "shown"; } - setResolution(args) { - arResolution = Math.max(0.1, Math.min(1, +args.RESOLUTION || 0)); - if (xrSession) { - xrSession.updateRenderState({ - baseLayer: new XRWebGLLayer(xrSession, gl, { framebufferScaleFactor: arResolution }) - }); - } + } else { + if (!xrSession) { + if (enterARDone.length === 0) enterAR(null); + return new Promise((resolve) => enterARDone.push(resolve)); } + } + } + exitAR() { + if (xrSession) { + xrSession.end(); + } + } + isInAR() { + return !!xrSession; + } + getStageWidth() { + return runtime.stageWidth; + } + getStageHeight() { + return runtime.stageHeight; + } + getMatrixItem(args) { + let item = args.ITEM | 0; + if (item < 1 && item > 16) return ""; + let matrix = null; + switch (args.MATRIX) { + case "combined": + matrix = xrCombinedMatrix; + break; + case "projection": + matrix = xrProjectionMatrix; + break; + case "view": + matrix = xrTransform?.matrix; + break; + case "inverse view": + matrix = xrTransform?.inverse?.matrix; + break; + } + if (!matrix) return 0; + return matrix[item - 1] || 0; + } + moveSpaceBy(args) { + if (!xrRefSpace) return; + const x = +args.X || 0; + const y = +args.Y || 0; + const z = +args.Z || 0; + if (!isFinite(x + y + z)) return; + const offsetTransform = new XRRigidTransform( + { x: x, y: y, z: z }, + { x: 0, y: 0, z: 0, w: 1 } + ); + xrRefSpace = xrRefSpace.getOffsetReferenceSpace(offsetTransform); + } + turnSpaceBy(args) { + if (!xrRefSpace) return; + const r = +args.R || 0; + const i = +args.I || 0; + const j = +args.J || 0; + const k = +args.K || 0; + const len = Math.sqrt(r * r + i * i + j * j + k * k); + if (!isFinite(len) || len === 0) return; + const offsetTransform = new XRRigidTransform( + { x: 0, y: 0, z: 0 }, + { x: i / len, y: j / len, z: k / len, w: r / len } + ); + xrRefSpace = xrRefSpace.getOffsetReferenceSpace(offsetTransform); + } + getPosition(args) { + if (!xrTransform) return 0; + return xrTransform.position[args.POSITION_COMPONENT] || 0; + } + getOrientation(args) { + if (!xrTransform) return 0; + return xrTransform.orientation[args.ORIENTATION_COMPONENT] || 0; + } + getHitPosition(args) { + if (!hitPosition) return 0; + return hitPosition[args.POSITION_COMPONENT] || 0; + } + isFeatureAvailible(args) { + switch (args.FEATURE) { + case "ar": + return !arFail; + case "pose": + return poseAvailible; + case "hit position": + return hitPositionAvailible; + default: + return false; + } + } + setResolution(args) { + arResolution = Math.max(0.1, Math.min(1, +args.RESOLUTION || 0)); + if (xrSession) { + xrSession.updateRenderState({ + baseLayer: new XRWebGLLayer(xrSession, gl, { + framebufferScaleFactor: arResolution, + }), + }); + } } + } - Scratch.extensions.register(new ARExtension()); -})(Scratch); \ No newline at end of file + Scratch.extensions.register(new ARExtension()); +})(Scratch); diff --git a/extensions/battery.js b/extensions/battery.js index bf67e4ead6..fcb432b803 100644 --- a/extensions/battery.js +++ b/extensions/battery.js @@ -1,5 +1,9 @@ +// Name: Battery +// ID: battery +// Description: Access information about the battery of phones or laptops. May not work on all devices and browsers. + (function (Scratch) { - 'use strict'; + "use strict"; /** @type {Promise|null} */ let getBatteryPromise = null; @@ -18,34 +22,35 @@ return callback(cachedBattery); } if (!getBatteryPromise) { - getBatteryPromise = navigator.getBattery() - .then(battery => { + getBatteryPromise = navigator + .getBattery() + .then((battery) => { getBatteryPromise = null; cachedBattery = battery; - cachedBattery.addEventListener('chargingchange', () => { - Scratch.vm.runtime.startHats('battery_chargingChanged'); + cachedBattery.addEventListener("chargingchange", () => { + Scratch.vm.runtime.startHats("battery_chargingChanged"); }); - cachedBattery.addEventListener('levelchange', () => { - Scratch.vm.runtime.startHats('battery_levelChanged'); + cachedBattery.addEventListener("levelchange", () => { + Scratch.vm.runtime.startHats("battery_levelChanged"); }); - cachedBattery.addEventListener('chargingtimechange', () => { - Scratch.vm.runtime.startHats('battery_chargeTimeChanged'); + cachedBattery.addEventListener("chargingtimechange", () => { + Scratch.vm.runtime.startHats("battery_chargeTimeChanged"); }); - cachedBattery.addEventListener('dischargingtimechange', () => { - Scratch.vm.runtime.startHats('battery_dischargeTimeChanged'); + cachedBattery.addEventListener("dischargingtimechange", () => { + Scratch.vm.runtime.startHats("battery_dischargeTimeChanged"); }); return cachedBattery; }) - .catch(error => { + .catch((error) => { getBatteryPromise = null; - console.error('Could not get battery', error); + console.error("Could not get battery", error); batteryError = true; return null; }); } - return getBatteryPromise.then(battery => { + return getBatteryPromise.then((battery) => { return callback(battery); }); }; @@ -54,78 +59,78 @@ withBattery(() => {}); class BatteryExtension { - getInfo () { + getInfo() { return { - name: 'Battery', - id: 'battery', + name: "Battery", + id: "battery", blocks: [ { - opcode: 'charging', + opcode: "charging", blockType: Scratch.BlockType.BOOLEAN, - text: 'charging?' + text: "charging?", }, { - opcode: 'level', + opcode: "level", blockType: Scratch.BlockType.REPORTER, - text: 'battery level' + text: "battery level", }, { - opcode: 'chargeTime', + opcode: "chargeTime", blockType: Scratch.BlockType.REPORTER, - text: 'seconds until charged' + text: "seconds until charged", }, { - opcode: 'dischargeTime', + opcode: "dischargeTime", blockType: Scratch.BlockType.REPORTER, - text: 'seconds until empty' + text: "seconds until empty", }, { - opcode: 'chargingChanged', - blockType: Scratch.BlockType.HAT, - text: 'when charging changed', - isEdgeActivated: false + opcode: "chargingChanged", + blockType: Scratch.BlockType.EVENT, + text: "when charging changed", + isEdgeActivated: false, }, { - opcode: 'levelChanged', - blockType: Scratch.BlockType.HAT, - text: 'when battery level changed', - isEdgeActivated: false + opcode: "levelChanged", + blockType: Scratch.BlockType.EVENT, + text: "when battery level changed", + isEdgeActivated: false, }, { - opcode: 'chargeTimeChanged', - blockType: Scratch.BlockType.HAT, - text: 'when time until charged changed', - isEdgeActivated: false + opcode: "chargeTimeChanged", + blockType: Scratch.BlockType.EVENT, + text: "when time until charged changed", + isEdgeActivated: false, }, { - opcode: 'dischargeTimeChanged', - blockType: Scratch.BlockType.HAT, - text: 'when time until empty changed', - isEdgeActivated: false + opcode: "dischargeTimeChanged", + blockType: Scratch.BlockType.EVENT, + text: "when time until empty changed", + isEdgeActivated: false, }, - ] + ], }; } - charging () { - return withBattery(battery => { + charging() { + return withBattery((battery) => { if (!battery) return true; return battery.charging; }); } - level () { - return withBattery(battery => { + level() { + return withBattery((battery) => { if (!battery) return 100; return battery.level * 100; }); } - chargeTime () { - return withBattery(battery => { + chargeTime() { + return withBattery((battery) => { if (!battery) return 0; return battery.chargingTime; }); } - dischargeTime () { - return withBattery(battery => { + dischargeTime() { + return withBattery((battery) => { if (!battery) return Infinity; return battery.dischargingTime; }); diff --git a/extensions/bitwise.js b/extensions/bitwise.js index 938e2180c1..d78d514037 100644 --- a/extensions/bitwise.js +++ b/extensions/bitwise.js @@ -1,16 +1,22 @@ -(Scratch => { - 'use strict'; +// Name: Bitwise +// ID: Bitwise +// Description: Blocks that operate on the binary representation of numbers in computers. +// By: TrueFantom - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0ODEiIGhlaWdodD0iMjI1LjM1NDgiIHZpZXdCb3g9IjAsMCwyMjUuMzU0ODEsMjI1LjM1NDgiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNjIuMzIyODcsLTM3LjMyMjY1KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTYyLjMyMjg4LDE1MC4wMDAwNWMwLC02Mi4yMzAwMSA1MC40NDczOSwtMTEyLjY3NzQgMTEyLjY3NzQsLTExMi42Nzc0YzYyLjIzMDAxLDAgMTEyLjY3NzQsNTAuNDQ3MzkgMTEyLjY3NzQsMTEyLjY3NzRjMCw2Mi4yMzAwMSAtNTAuNDQ3MzksMTEyLjY3NzQgLTExMi42Nzc0LDExMi42Nzc0Yy02Mi4yMzAwMSwwIC0xMTIuNjc3NCwtNTAuNDQ3MzkgLTExMi42Nzc0LC0xMTIuNjc3NHoiIGZpbGw9IiNlNjI4MmEiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTE2Mi4zMjI4NywxNTAuMDAwMDVjMCwtNjIuMjMwMDEgNTAuNDQ3MzksLTExMi42Nzc0IDExMi42Nzc0LC0xMTIuNjc3NGM2Mi4yMzAwMSwwIDExMi42Nzc0LDUwLjQ0NzM5IDExMi42Nzc0LDExMi42Nzc0YzAsNjIuMjMwMDEgLTUwLjQ0NzM5LDExMi42Nzc0IC0xMTIuNjc3NCwxMTIuNjc3NGMtNjIuMjMwMDEsMCAtMTEyLjY3NzQsLTUwLjQ0NzM5IC0xMTIuNjc3NCwtMTEyLjY3NzR6IiBmaWxsPSIjMTdjZGU2IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNDEuNjc3NzgsMTQ3LjA4OTUzdjAuNDU5NjJjMi42OTA2NCwwLjI5NzU3IDUuMzUzMTYsMC44MDk5MSA3Ljk2MjEsMS41MzIwNmMyLjc5MzQ0LDAuNzkxNTUgNS4yODQ1NywxLjgxNjUxIDcuNDczMzcsMy4wNzQ4NGMxLjY0NiwwLjkzNzAxIDMuMTc1ODMsMi4wNjQ3MiA0LjU1Nzg3LDMuMzU5OGMzLjUyOTU0LDMuMzQzOTcgNS44MTE1Miw3Ljc5MjgzIDYuNDY4MzQsMTIuNjEwMzZjMC4yMzgsMS41NTU5IDAuMzU0NzksMy4xMjc5MSAwLjM0OTMxLDQuNzAxODhjMC4wMzMzNCw0LjAxNjE1IC0wLjY2NDkzLDguMDA0ODEgLTIuMDYwNjIsMTEuNzcwNzljLTAuNjk4NjIsMS44NTAxMSAtMS41NzMzNSwzLjYyODgzIC0yLjYxMjE2LDUuMzExNjRjLTIuMTA2MjIsMy4zNjc4NyAtNC44OTQ3Miw2LjI1NzEgLTguMTg1NzgsOC40ODE0NmMtMS45NjE1NywxLjM0MzYgLTQuMDQyMTIsMi41MDQ2NSAtNi4yMTU1NSwzLjQ2ODU4Yy01LjA1ODg1LDIuMjY5NSAtMTEuMjUxNDIsMy42NTQ0NiAtMTguNTc3NzEsNC4xNTQ5NGMtMi4xNTQxMiwwLjE0MzgxIC00LjMxMjUxLDAuMjE0MzMgLTYuNDcxNDEsMC4yMTE0MmMtMTEuODQ3OTEsMCAtMjIuMzY4MDIsLTEuOTkxNjcgLTMxLjU2MDM2LC01Ljk3NTAydi0xOS42MTAzMmM0LjY5ODMxLDIuMzQ5MTUgOS42MjY0Miw0LjEzNjU1IDE0Ljc4NDM0LDUuMzYyMmM1LjE1NzkzLDEuMjI1NjUgOS45MzI4MywxLjgzODQ3IDE0LjMyNDczLDEuODM4NDdjOC4yNzMxLDAgMTQuMDY5MzksLTEuNDI5OTEgMTcuMzg4ODQsLTQuMjg5NzZjMi40NzQyNCwtMi4xNDA1NyA0LjExOTE2LC01LjA4MTI2IDQuNjQ4MjYsLTguMzA5ODdjMC4yMjYzMywtMS4yNTE2NCAwLjMzNzEsLTIuNTIxNDQgMC4zMzA5MiwtMy43OTMzN2MwLC0zLjA2NDExIC0wLjc2NjAzLC01LjY0MzA3IC0yLjI5ODA4LC03LjczNjg4Yy0wLjY4ODk0LC0wLjkxOSAtMS41MzI5OSwtMS43MTA3MSAtMi40OTQxOSwtMi4zMzk0NWMtMC44MTcwOSwtMC41NDk1IC0xLjc1ODgsLTEuMDQzODQgLTIuODI1MTEsLTEuNDgzMDNjLTAuODg5MDUsLTAuMzYyMDcgLTEuNzk4NjIsLTAuNjcxNTggLTIuNzI0LC0wLjkyNjg5Yy0xLjMyNjEsLTAuMzYyNjUgLTIuNjcyNiwtMC42NDYwOCAtNC4wMzIzNywtMC44NDg3NmMtMi44MTg5OCwtMC40MzYxMiAtNi4xNjM5OCwtMC42ODQ4MyAtMTAuMDM0OTcsLTAuNzQ2MTFjLTAuNjQ3NTEsLTAuMDA5NzMgLTEuMjk1MDYsLTAuMDE0MzIgLTEuOTQyNjUsLTAuMDEzNzloLTguMjczMXYtMTcuNzcxODVoOC40MjYzMWM2Ljc0MTA1LDAgMTEuODczNDQsLTAuNjM4MzUgMTUuMzk3MTcsLTEuOTE1MDdjMS4wMTMxOSwtMC4zNjEwNCAxLjk5NjY4LC0wLjgwMDU1IDIuOTQxNTUsLTEuMzE0NWMxLjk1Njk0LC0xLjA3ODU3IDMuMzc2NjUsLTIuMzc2NzIgNC4yNTkxMiwtMy44OTQ0OWMxLjI3NjcyLC0yLjE5NTk0IDEuOTE1MDcsLTQuNzIzODQgMS45MTUwNywtNy41ODM2OGMwLC0zLjg4MTIgLTEuMjAwMTEsLTYuOTE5NzkgLTMuNjAwMzMsLTkuMTE1NzNjLTAuOTc3NDIsLTAuODY3NDIgLTIuMTA5NDEsLTEuNTQzMDcgLTMuMzM2ODIsLTEuOTkxNjdjLTIuMjYwMywtMC44NjgxNyAtNS4xNTY5LC0xLjMwMjI1IC04LjY4OTgyLC0xLjMwMjI1Yy01LjE2MzAzLDAgLTkuNjQ4ODksMC44Nzc4NyAtMTMuNDU3NTgsMi42MzM2Yy0wLjAzMzc1LDAuMDE1NzUgLTAuMDY3NDYsMC4wMzE1OSAtMC4xMDExMiwwLjA0NzQ5Yy0zLjgzMDE0LDEuNzg3NCAtNy4wNzMsMy41NDkyNyAtOS43Mjg1Niw1LjI4NTU5bC0xMC43MjQzOSwtMTUuOTMzMzljMi44MDQ5MywtMS45ODcyIDUuNzg3OCwtMy43MTA0NiA4LjkxMDQ0LC01LjE0NzcxYzIuMDE5MSwtMC45MzUwNiA0LjA4MTU0LC0xLjc3MzUyIDYuMTgwMzEsLTIuNTEyNTdjMi44NzMzMSwtMC45OTk5IDUuODMxNjUsLTEuNzM2MjggOC44Mzg0MywtMi4yMDAwM2MyLjkyODI4LC0wLjQ2Njc2IDYuMDYwMzEsLTAuNzQ0NTggOS4zOTYxLC0wLjgzMzQ0YzAuNzkwMzcsLTAuMDIwNyAxLjU4MDk5LC0wLjAzMDkyIDIuMzcxNjIsLTAuMDMwNjRjMy41NjQ5OSwtMC4wMTkwMyA3LjEyNTksMC4yNDQyOSAxMC42NDkzMiwwLjc4NzQ4YzMuNzcxOTIsMC42MDI2IDcuMTg5NDMsMS41NTI0OCAxMC4yNTI1MiwyLjg0OTYyYzIuMDQ3MjEsMC44NTkyMSA0LjAwMTUsMS45MjQ5MSA1LjgzMjU0LDMuMTgwNTVjMy4wMjEwMywyLjAxNTE3IDUuNTA5ODksNC43MzEwOCA3LjI1NDI5LDcuOTE2MTNjMS43NTE2NSwzLjI3NDUxIDIuNjI3NDgsNy4wNDQ5IDIuNjI3NDgsMTEuMzExMTdjMC4wMjI4MywyLjY0OTMyIC0wLjI5OTU4LDUuMjkwMzYgLTAuOTU5MDcsNy44NTYzOGMtMC45OTAyNiwzLjg3NTMyIC0zLjAxNTMyLDcuNDA4NTggLTUuODU4NTgsMTAuMjIxODhjLTQuMjg3OSw0LjIxNDEgLTkuNTg4MjgsNy4yNTM2NiAtMTUuMzkxMDQsOC44MjYxOGMtMC40NTk1NCwwLjEyOTkzIC0wLjkyMTI3LDAuMjUyMDEgLTEuMzg0OTgsMC4zNjYxNnoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTM1Ny4xOTQ4MiwxODUuMjM3NzN2MTkuNDU3MTFoLTc2LjQ0OTYxdi0xNi4wODY1OWwyNy40MjM4MSwtMjcuNzMwMjJjNS41MTU0LC01LjcxOTY5IDkuOTU4MzcsLTEwLjQ5NDU5IDEzLjMyODg5LC0xNC4zMjQ3M2MxLjE0MDMxLC0xLjI5MDI0IDIuMjMzMzcsLTIuNjIxNDQgMy4yNzcwNywtMy45OTEwMWMxLjczNjMzLC0yLjI4ODg5IDMuMDk1MjYsLTQuNDMxMjMgNC4wNzY4LC02LjQyNjk4YzEuNTMyMDYsLTMuMTE1MTggMi4yOTgwOCwtNi40NjAxOCAyLjI5ODA4LC0xMC4wMzQ5N2MwLC00LjM5MTkgLTEuMjAwMTEsLTcuNjYwMjggLTMuNjAwMzMsLTkuODA1MTZjLTEuNDk5NjUsLTEuMzE5NjEgLTMuMzAxMzQsLTIuMjQ5NCAtNS4yNDU3NiwtMi43MDcxNGMtMS4yMzI3OCwtMC4zMDg0NiAtMi41NzUzOSwtMC40NzY5OSAtNC4wMjc3OCwtMC41MDU1OGMtMC4xNTE2NiwtMC4wMDMwNSAtMC4zMDMzMywtMC4wMDQ1NyAtMC40NTUwMiwtMC4wMDQ2Yy00LjExNSwwLjAxMzE5IC04LjE3NDk2LDAuOTQ2NTQgLTExLjg4MjYzLDIuNzMxNjZjLTAuMTI1LDAuMDU4OTQgLTAuMjQ5NiwwLjExODY5IC0wLjM3MzgyLDAuMTc5MjVjLTEuOTc2NDgsMC45NzE0NSAtMy44OTIwMiwyLjA2MjI1IC01LjczNjAyLDMuMjY2MzRjLTEuODcxMTYsMS4yMTMzOSAtMy43ODM2NywyLjU5MTIzIC01LjczNzU1LDQuMTMzNDljLTAuMzY1MjksMC4yODgzNSAtMC43Mjg0LDAuNTc5NDQgLTEuMDg5MjksMC44NzMyN2wtMTIuNTYyODYsLTE0Ljg2MDk1YzMuMTY2MjUsLTIuNzU3NyA2LjUxMTI0LC01LjMxMTEzIDEwLjAzNDk3LC03LjY2MDI4YzIuMjk2ODQsLTEuNTE5MDggNC43Mjg4LC0yLjgyMzI3IDcuMjY1MDEsLTMuODk2MDJjMS42MzM0MywtMC42OTU0OCAzLjI5OTE1LC0xLjMxMjU3IDQuOTkxNDQsLTEuODQ5MTljNC42NDcyNSwtMS40ODA5OSAxMC4yMzkyNSwtMi4yMjE0OCAxNi43NzYwMiwtMi4yMjE0OGMyLjk1MzkxLC0wLjAxNTA2IDUuOTAzMjMsMC4yMzUxNiA4LjgxMjM5LDAuNzQ3NjRjMy41MDMzMSwwLjYzNTI4IDYuNjk3MTIsMS42NzY1OSA5LjU4MTQ4LDMuMTIzODZjMC4wMjI0OSwwLjAxMTcyIDAuMDQ0OTcsMC4wMjM0NyAwLjA2NzQxLDAuMDM1MjRjMi44MTA3NCwxLjM5Mzg2IDUuMzk4NTgsMy4xOTc5MiA3LjY3ODY3LDUuMzUzYzEuNjMxNSwxLjU1ODQ4IDMuMDY1OTcsMy4zMTA5MyA0LjI3MTM3LDUuMjE4MThjMi44MDg3Niw0LjQ0Mjk2IDQuMjEzMTUsOS40NzMyMiA0LjIxMzE1LDE1LjA5MDc1YzAuMDIzMDQsMy43ODk2NiAtMC40OTkxMSw3LjU2MjkzIC0xLjU1MDQ0LDExLjIwMzkzYy0wLjUzODM4LDEuODMzMDMgLTEuMjIzOSwzLjYxOTYxIC0yLjA0OTg5LDUuMzQyMjhjLTEuNDI5ODcsMi45NDcyMiAtMy4xMjYyOCw1Ljc1NzQ3IC01LjA2ODA0LDguMzk1NjdjLTEuNjU1MDMsMi4yNTgxIC0zLjQ0MTQ0LDQuNDE2OTIgLTUuMzQ5OTQsNi40NjUyOGMtMi4xMjQ1NiwyLjI4NTM1IC00LjI5NDM4LDQuNTI4MjIgLTYuNTA4MTgsNi43MjcyNmMtMi4yNjY0MiwyLjI1NzIyIC00LjcwMDg1LDQuNjA0ODYgLTcuMzAzMzEsNy4wNDI4NmMtMC44ODI0MiwwLjgyNzExIC0xLjc2ODQ3LDEuNjUwMzUgLTIuNjU4MTIsMi40Njk2N2wtMTQuMDk0OTIsMTMuMTc1Njh2MS4wNzI0NHoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTEyLjY3NzEzOjExMi42NzczNDUwMDAwMDAwMy0tPg=='; +((Scratch) => { + "use strict"; - const isNumberBits = bits => { + const icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0ODEiIGhlaWdodD0iMjI1LjM1NDgiIHZpZXdCb3g9IjAsMCwyMjUuMzU0ODEsMjI1LjM1NDgiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNjIuMzIyODcsLTM3LjMyMjY1KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIHN0eWxlPSJtaXgtYmxlbmQtbW9kZTogbm9ybWFsIj48cGF0aCBkPSJNMTYyLjMyMjg4LDE1MC4wMDAwNWMwLC02Mi4yMzAwMSA1MC40NDczOSwtMTEyLjY3NzQgMTEyLjY3NzQsLTExMi42Nzc0YzYyLjIzMDAxLDAgMTEyLjY3NzQsNTAuNDQ3MzkgMTEyLjY3NzQsMTEyLjY3NzRjMCw2Mi4yMzAwMSAtNTAuNDQ3MzksMTEyLjY3NzQgLTExMi42Nzc0LDExMi42Nzc0Yy02Mi4yMzAwMSwwIC0xMTIuNjc3NCwtNTAuNDQ3MzkgLTExMi42Nzc0LC0xMTIuNjc3NHoiIGZpbGw9IiNlNjI4MmEiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1saW5lY2FwPSJidXR0Ii8+PHBhdGggZD0iTTE2Mi4zMjI4NywxNTAuMDAwMDVjMCwtNjIuMjMwMDEgNTAuNDQ3MzksLTExMi42Nzc0IDExMi42Nzc0LC0xMTIuNjc3NGM2Mi4yMzAwMSwwIDExMi42Nzc0LDUwLjQ0NzM5IDExMi42Nzc0LDExMi42Nzc0YzAsNjIuMjMwMDEgLTUwLjQ0NzM5LDExMi42Nzc0IC0xMTIuNjc3NCwxMTIuNjc3NGMtNjIuMjMwMDEsMCAtMTEyLjY3NzQsLTUwLjQ0NzM5IC0xMTIuNjc3NCwtMTEyLjY3NzR6IiBmaWxsPSIjMTdjZGU2IiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNDEuNjc3NzgsMTQ3LjA4OTUzdjAuNDU5NjJjMi42OTA2NCwwLjI5NzU3IDUuMzUzMTYsMC44MDk5MSA3Ljk2MjEsMS41MzIwNmMyLjc5MzQ0LDAuNzkxNTUgNS4yODQ1NywxLjgxNjUxIDcuNDczMzcsMy4wNzQ4NGMxLjY0NiwwLjkzNzAxIDMuMTc1ODMsMi4wNjQ3MiA0LjU1Nzg3LDMuMzU5OGMzLjUyOTU0LDMuMzQzOTcgNS44MTE1Miw3Ljc5MjgzIDYuNDY4MzQsMTIuNjEwMzZjMC4yMzgsMS41NTU5IDAuMzU0NzksMy4xMjc5MSAwLjM0OTMxLDQuNzAxODhjMC4wMzMzNCw0LjAxNjE1IC0wLjY2NDkzLDguMDA0ODEgLTIuMDYwNjIsMTEuNzcwNzljLTAuNjk4NjIsMS44NTAxMSAtMS41NzMzNSwzLjYyODgzIC0yLjYxMjE2LDUuMzExNjRjLTIuMTA2MjIsMy4zNjc4NyAtNC44OTQ3Miw2LjI1NzEgLTguMTg1NzgsOC40ODE0NmMtMS45NjE1NywxLjM0MzYgLTQuMDQyMTIsMi41MDQ2NSAtNi4yMTU1NSwzLjQ2ODU4Yy01LjA1ODg1LDIuMjY5NSAtMTEuMjUxNDIsMy42NTQ0NiAtMTguNTc3NzEsNC4xNTQ5NGMtMi4xNTQxMiwwLjE0MzgxIC00LjMxMjUxLDAuMjE0MzMgLTYuNDcxNDEsMC4yMTE0MmMtMTEuODQ3OTEsMCAtMjIuMzY4MDIsLTEuOTkxNjcgLTMxLjU2MDM2LC01Ljk3NTAydi0xOS42MTAzMmM0LjY5ODMxLDIuMzQ5MTUgOS42MjY0Miw0LjEzNjU1IDE0Ljc4NDM0LDUuMzYyMmM1LjE1NzkzLDEuMjI1NjUgOS45MzI4MywxLjgzODQ3IDE0LjMyNDczLDEuODM4NDdjOC4yNzMxLDAgMTQuMDY5MzksLTEuNDI5OTEgMTcuMzg4ODQsLTQuMjg5NzZjMi40NzQyNCwtMi4xNDA1NyA0LjExOTE2LC01LjA4MTI2IDQuNjQ4MjYsLTguMzA5ODdjMC4yMjYzMywtMS4yNTE2NCAwLjMzNzEsLTIuNTIxNDQgMC4zMzA5MiwtMy43OTMzN2MwLC0zLjA2NDExIC0wLjc2NjAzLC01LjY0MzA3IC0yLjI5ODA4LC03LjczNjg4Yy0wLjY4ODk0LC0wLjkxOSAtMS41MzI5OSwtMS43MTA3MSAtMi40OTQxOSwtMi4zMzk0NWMtMC44MTcwOSwtMC41NDk1IC0xLjc1ODgsLTEuMDQzODQgLTIuODI1MTEsLTEuNDgzMDNjLTAuODg5MDUsLTAuMzYyMDcgLTEuNzk4NjIsLTAuNjcxNTggLTIuNzI0LC0wLjkyNjg5Yy0xLjMyNjEsLTAuMzYyNjUgLTIuNjcyNiwtMC42NDYwOCAtNC4wMzIzNywtMC44NDg3NmMtMi44MTg5OCwtMC40MzYxMiAtNi4xNjM5OCwtMC42ODQ4MyAtMTAuMDM0OTcsLTAuNzQ2MTFjLTAuNjQ3NTEsLTAuMDA5NzMgLTEuMjk1MDYsLTAuMDE0MzIgLTEuOTQyNjUsLTAuMDEzNzloLTguMjczMXYtMTcuNzcxODVoOC40MjYzMWM2Ljc0MTA1LDAgMTEuODczNDQsLTAuNjM4MzUgMTUuMzk3MTcsLTEuOTE1MDdjMS4wMTMxOSwtMC4zNjEwNCAxLjk5NjY4LC0wLjgwMDU1IDIuOTQxNTUsLTEuMzE0NWMxLjk1Njk0LC0xLjA3ODU3IDMuMzc2NjUsLTIuMzc2NzIgNC4yNTkxMiwtMy44OTQ0OWMxLjI3NjcyLC0yLjE5NTk0IDEuOTE1MDcsLTQuNzIzODQgMS45MTUwNywtNy41ODM2OGMwLC0zLjg4MTIgLTEuMjAwMTEsLTYuOTE5NzkgLTMuNjAwMzMsLTkuMTE1NzNjLTAuOTc3NDIsLTAuODY3NDIgLTIuMTA5NDEsLTEuNTQzMDcgLTMuMzM2ODIsLTEuOTkxNjdjLTIuMjYwMywtMC44NjgxNyAtNS4xNTY5LC0xLjMwMjI1IC04LjY4OTgyLC0xLjMwMjI1Yy01LjE2MzAzLDAgLTkuNjQ4ODksMC44Nzc4NyAtMTMuNDU3NTgsMi42MzM2Yy0wLjAzMzc1LDAuMDE1NzUgLTAuMDY3NDYsMC4wMzE1OSAtMC4xMDExMiwwLjA0NzQ5Yy0zLjgzMDE0LDEuNzg3NCAtNy4wNzMsMy41NDkyNyAtOS43Mjg1Niw1LjI4NTU5bC0xMC43MjQzOSwtMTUuOTMzMzljMi44MDQ5MywtMS45ODcyIDUuNzg3OCwtMy43MTA0NiA4LjkxMDQ0LC01LjE0NzcxYzIuMDE5MSwtMC45MzUwNiA0LjA4MTU0LC0xLjc3MzUyIDYuMTgwMzEsLTIuNTEyNTdjMi44NzMzMSwtMC45OTk5IDUuODMxNjUsLTEuNzM2MjggOC44Mzg0MywtMi4yMDAwM2MyLjkyODI4LC0wLjQ2Njc2IDYuMDYwMzEsLTAuNzQ0NTggOS4zOTYxLC0wLjgzMzQ0YzAuNzkwMzcsLTAuMDIwNyAxLjU4MDk5LC0wLjAzMDkyIDIuMzcxNjIsLTAuMDMwNjRjMy41NjQ5OSwtMC4wMTkwMyA3LjEyNTksMC4yNDQyOSAxMC42NDkzMiwwLjc4NzQ4YzMuNzcxOTIsMC42MDI2IDcuMTg5NDMsMS41NTI0OCAxMC4yNTI1MiwyLjg0OTYyYzIuMDQ3MjEsMC44NTkyMSA0LjAwMTUsMS45MjQ5MSA1LjgzMjU0LDMuMTgwNTVjMy4wMjEwMywyLjAxNTE3IDUuNTA5ODksNC43MzEwOCA3LjI1NDI5LDcuOTE2MTNjMS43NTE2NSwzLjI3NDUxIDIuNjI3NDgsNy4wNDQ5IDIuNjI3NDgsMTEuMzExMTdjMC4wMjI4MywyLjY0OTMyIC0wLjI5OTU4LDUuMjkwMzYgLTAuOTU5MDcsNy44NTYzOGMtMC45OTAyNiwzLjg3NTMyIC0zLjAxNTMyLDcuNDA4NTggLTUuODU4NTgsMTAuMjIxODhjLTQuMjg3OSw0LjIxNDEgLTkuNTg4MjgsNy4yNTM2NiAtMTUuMzkxMDQsOC44MjYxOGMtMC40NTk1NCwwLjEyOTkzIC0wLjkyMTI3LDAuMjUyMDEgLTEuMzg0OTgsMC4zNjYxNnoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTM1Ny4xOTQ4MiwxODUuMjM3NzN2MTkuNDU3MTFoLTc2LjQ0OTYxdi0xNi4wODY1OWwyNy40MjM4MSwtMjcuNzMwMjJjNS41MTU0LC01LjcxOTY5IDkuOTU4MzcsLTEwLjQ5NDU5IDEzLjMyODg5LC0xNC4zMjQ3M2MxLjE0MDMxLC0xLjI5MDI0IDIuMjMzMzcsLTIuNjIxNDQgMy4yNzcwNywtMy45OTEwMWMxLjczNjMzLC0yLjI4ODg5IDMuMDk1MjYsLTQuNDMxMjMgNC4wNzY4LC02LjQyNjk4YzEuNTMyMDYsLTMuMTE1MTggMi4yOTgwOCwtNi40NjAxOCAyLjI5ODA4LC0xMC4wMzQ5N2MwLC00LjM5MTkgLTEuMjAwMTEsLTcuNjYwMjggLTMuNjAwMzMsLTkuODA1MTZjLTEuNDk5NjUsLTEuMzE5NjEgLTMuMzAxMzQsLTIuMjQ5NCAtNS4yNDU3NiwtMi43MDcxNGMtMS4yMzI3OCwtMC4zMDg0NiAtMi41NzUzOSwtMC40NzY5OSAtNC4wMjc3OCwtMC41MDU1OGMtMC4xNTE2NiwtMC4wMDMwNSAtMC4zMDMzMywtMC4wMDQ1NyAtMC40NTUwMiwtMC4wMDQ2Yy00LjExNSwwLjAxMzE5IC04LjE3NDk2LDAuOTQ2NTQgLTExLjg4MjYzLDIuNzMxNjZjLTAuMTI1LDAuMDU4OTQgLTAuMjQ5NiwwLjExODY5IC0wLjM3MzgyLDAuMTc5MjVjLTEuOTc2NDgsMC45NzE0NSAtMy44OTIwMiwyLjA2MjI1IC01LjczNjAyLDMuMjY2MzRjLTEuODcxMTYsMS4yMTMzOSAtMy43ODM2NywyLjU5MTIzIC01LjczNzU1LDQuMTMzNDljLTAuMzY1MjksMC4yODgzNSAtMC43Mjg0LDAuNTc5NDQgLTEuMDg5MjksMC44NzMyN2wtMTIuNTYyODYsLTE0Ljg2MDk1YzMuMTY2MjUsLTIuNzU3NyA2LjUxMTI0LC01LjMxMTEzIDEwLjAzNDk3LC03LjY2MDI4YzIuMjk2ODQsLTEuNTE5MDggNC43Mjg4LC0yLjgyMzI3IDcuMjY1MDEsLTMuODk2MDJjMS42MzM0MywtMC42OTU0OCAzLjI5OTE1LC0xLjMxMjU3IDQuOTkxNDQsLTEuODQ5MTljNC42NDcyNSwtMS40ODA5OSAxMC4yMzkyNSwtMi4yMjE0OCAxNi43NzYwMiwtMi4yMjE0OGMyLjk1MzkxLC0wLjAxNTA2IDUuOTAzMjMsMC4yMzUxNiA4LjgxMjM5LDAuNzQ3NjRjMy41MDMzMSwwLjYzNTI4IDYuNjk3MTIsMS42NzY1OSA5LjU4MTQ4LDMuMTIzODZjMC4wMjI0OSwwLjAxMTcyIDAuMDQ0OTcsMC4wMjM0NyAwLjA2NzQxLDAuMDM1MjRjMi44MTA3NCwxLjM5Mzg2IDUuMzk4NTgsMy4xOTc5MiA3LjY3ODY3LDUuMzUzYzEuNjMxNSwxLjU1ODQ4IDMuMDY1OTcsMy4zMTA5MyA0LjI3MTM3LDUuMjE4MThjMi44MDg3Niw0LjQ0Mjk2IDQuMjEzMTUsOS40NzMyMiA0LjIxMzE1LDE1LjA5MDc1YzAuMDIzMDQsMy43ODk2NiAtMC40OTkxMSw3LjU2MjkzIC0xLjU1MDQ0LDExLjIwMzkzYy0wLjUzODM4LDEuODMzMDMgLTEuMjIzOSwzLjYxOTYxIC0yLjA0OTg5LDUuMzQyMjhjLTEuNDI5ODcsMi45NDcyMiAtMy4xMjYyOCw1Ljc1NzQ3IC01LjA2ODA0LDguMzk1NjdjLTEuNjU1MDMsMi4yNTgxIC0zLjQ0MTQ0LDQuNDE2OTIgLTUuMzQ5OTQsNi40NjUyOGMtMi4xMjQ1NiwyLjI4NTM1IC00LjI5NDM4LDQuNTI4MjIgLTYuNTA4MTgsNi43MjcyNmMtMi4yNjY0MiwyLjI1NzIyIC00LjcwMDg1LDQuNjA0ODYgLTcuMzAzMzEsNy4wNDI4NmMtMC44ODI0MiwwLjgyNzExIC0xLjc2ODQ3LDEuNjUwMzUgLTIuNjU4MTIsMi40Njk2N2wtMTQuMDk0OTIsMTMuMTc1Njh2MS4wNzI0NHoiIGZpbGw9IiNmZmZmZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTEyLjY3NzEzOjExMi42NzczNDUwMDAwMDAwMy0tPg=="; + + const isNumberBits = (bits) => { return /^-?[01]+$/.test(bits); }; - const number2bits = number => { + const number2bits = (number) => { return Scratch.Cast.toNumber(number).toString(2); }; - const bits2number = bits => { - return /^-?[01]+$/.test(bits) ? (parseInt(bits, 2) || 0) : 0; + const bits2number = (bits) => { + return /^-?[01]+$/.test(bits) ? parseInt(bits, 2) || 0 : 0; }; const circularRightShift = (number, k) => { @@ -21,224 +27,222 @@ }; class Bitwise { - getInfo() { return { + id: "Bitwise", + name: "Bitwise", - id: 'Bitwise', - name: 'Bitwise', - - color1: '#17cde6', + color1: "#17cde6", - docsURI: "https://extensions.turbowarp.org/bitwise.html", + docsURI: "https://extensions.turbowarp.org/bitwise", menuIconURI: icon, blocks: [ { - opcode: 'isNumberBits', + opcode: "isNumberBits", blockType: Scratch.BlockType.BOOLEAN, - text: 'is [CENTRAL] binary?', + text: "is [CENTRAL] binary?", arguments: { CENTRAL: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0000000000100000' - } - } + defaultValue: "0000000000100000", + }, + }, }, - '---', + "---", { - opcode: 'toNumberBits', + opcode: "toNumberBits", blockType: Scratch.BlockType.REPORTER, - text: '[CENTRAL] to binary', + text: "[CENTRAL] to binary", arguments: { CENTRAL: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '32' - } - } + defaultValue: "32", + }, + }, }, { - opcode: 'ofNumberBits', + opcode: "ofNumberBits", blockType: Scratch.BlockType.REPORTER, - text: '[CENTRAL] to number', + text: "[CENTRAL] to number", arguments: { CENTRAL: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0000000000100000' - } - } + defaultValue: "0000000000100000", + }, + }, }, - '---', + "---", { - opcode: 'bitwiseRightShift', + opcode: "bitwiseRightShift", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] >> [RIGHT]', + text: "[LEFT] >> [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseLeftShift', + opcode: "bitwiseLeftShift", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] << [RIGHT]', + text: "[LEFT] << [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseLogicalRightShift', + opcode: "bitwiseLogicalRightShift", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] >>> [RIGHT]', + text: "[LEFT] >>> [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, - { - opcode: 'bitwiseCircularRightShift', + { + opcode: "bitwiseCircularRightShift", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] ↻ [RIGHT]', + text: "[LEFT] ↻ [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseCircularLeftShift', + opcode: "bitwiseCircularLeftShift", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] ↺ [RIGHT]', + text: "[LEFT] ↺ [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, - '---', + "---", { - opcode: 'bitwiseAnd', + opcode: "bitwiseAnd", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] and [RIGHT]', + text: "[LEFT] and [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseOr', + opcode: "bitwiseOr", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] or [RIGHT]', + text: "[LEFT] or [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseXor', + opcode: "bitwiseXor", blockType: Scratch.BlockType.REPORTER, - text: '[LEFT] xor [RIGHT]', + text: "[LEFT] xor [RIGHT]", arguments: { LEFT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' + defaultValue: "", }, RIGHT: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } + defaultValue: "", + }, + }, }, { - opcode: 'bitwiseNot', + opcode: "bitwiseNot", blockType: Scratch.BlockType.REPORTER, - text: 'not [CENTRAL]', + text: "not [CENTRAL]", arguments: { CENTRAL: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '' - } - } - } - ] + defaultValue: "", + }, + }, + }, + ], }; } - isNumberBits({CENTRAL}) { + isNumberBits({ CENTRAL }) { return isNumberBits(CENTRAL); } - toNumberBits({CENTRAL}) { + toNumberBits({ CENTRAL }) { return number2bits(CENTRAL); } - ofNumberBits({CENTRAL}) { + ofNumberBits({ CENTRAL }) { return bits2number(CENTRAL); } - bitwiseRightShift({LEFT, RIGHT}) { + bitwiseRightShift({ LEFT, RIGHT }) { return LEFT >> RIGHT; } - bitwiseLeftShift({LEFT, RIGHT}) { + bitwiseLeftShift({ LEFT, RIGHT }) { return LEFT << RIGHT; } - bitwiseLogicalRightShift({LEFT, RIGHT}) { + bitwiseLogicalRightShift({ LEFT, RIGHT }) { return LEFT >>> RIGHT; } - bitwiseCircularRightShift({LEFT, RIGHT}) { + bitwiseCircularRightShift({ LEFT, RIGHT }) { return circularRightShift(LEFT, RIGHT); } - bitwiseCircularLeftShift({LEFT, RIGHT}) { + bitwiseCircularLeftShift({ LEFT, RIGHT }) { return circularLeftShift(LEFT, RIGHT); } - bitwiseAnd({LEFT, RIGHT}) { + bitwiseAnd({ LEFT, RIGHT }) { return LEFT & RIGHT; } - bitwiseOr({LEFT, RIGHT}) { + bitwiseOr({ LEFT, RIGHT }) { return LEFT | RIGHT; } - bitwiseXor({LEFT, RIGHT}) { + bitwiseXor({ LEFT, RIGHT }) { return LEFT ^ RIGHT; } - bitwiseNot({CENTRAL}) { + bitwiseNot({ CENTRAL }) { return ~CENTRAL; } } diff --git a/extensions/box2d.js b/extensions/box2d.js index 8c5f411dc2..42df1ab034 100644 --- a/extensions/box2d.js +++ b/extensions/box2d.js @@ -1,14 +1,19 @@ +// Name: Box2D Physics +// ID: griffpatch +// Description: Two dimensional physics. +// Original: griffpatch + /*! * This is based on https://github.com/griffpatch/scratch-vm/tree/box2d/src/extensions/scratch3_griffpatch */ /* eslint-disable */ -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; if (!Scratch.extensions.unsandboxed) { - throw new Error('Box2D must be run unsandboxed'); + throw new Error("Box2D must be run unsandboxed"); } // First we need to load the Box2D physics library that this extension uses. @@ -16,22 +21,22 @@ // the source code below. Yes, this is really ugly. /*! - * Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ + * Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ var Box2D = {}; (function (a2j, undefined) { @@ -78,7 +83,8 @@ //package structure if (typeof Box2D === "undefined") Box2D = {}; if (typeof Box2D.Collision === "undefined") Box2D.Collision = {}; - if (typeof Box2D.Collision.Shapes === "undefined") Box2D.Collision.Shapes = {}; + if (typeof Box2D.Collision.Shapes === "undefined") + Box2D.Collision.Shapes = {}; if (typeof Box2D.Common === "undefined") Box2D.Common = {}; if (typeof Box2D.Common.Math === "undefined") Box2D.Common.Math = {}; if (typeof Box2D.Dynamics === "undefined") Box2D.Dynamics = {}; @@ -169,7 +175,8 @@ function b2Manifold() { b2Manifold.b2Manifold.apply(this, arguments); - if (this.constructor === b2Manifold) this.b2Manifold.apply(this, arguments); + if (this.constructor === b2Manifold) + this.b2Manifold.apply(this, arguments); } Box2D.Collision.b2Manifold = b2Manifold; @@ -515,13 +522,19 @@ Box2D.Dynamics.Controllers.b2BuoyancyController = b2BuoyancyController; function b2ConstantAccelController() { - b2ConstantAccelController.b2ConstantAccelController.apply(this, arguments); + b2ConstantAccelController.b2ConstantAccelController.apply( + this, + arguments + ); } Box2D.Dynamics.Controllers.b2ConstantAccelController = b2ConstantAccelController; function b2ConstantForceController() { - b2ConstantForceController.b2ConstantForceController.apply(this, arguments); + b2ConstantForceController.b2ConstantForceController.apply( + this, + arguments + ); } Box2D.Dynamics.Controllers.b2ConstantForceController = b2ConstantForceController; @@ -542,7 +555,10 @@ Box2D.Dynamics.Controllers.b2GravityController = b2GravityController; function b2TensorDampingController() { - b2TensorDampingController.b2TensorDampingController.apply(this, arguments); + b2TensorDampingController.b2TensorDampingController.apply( + this, + arguments + ); } Box2D.Dynamics.Controllers.b2TensorDampingController = b2TensorDampingController; @@ -602,7 +618,8 @@ function b2JointDef() { b2JointDef.b2JointDef.apply(this, arguments); - if (this.constructor === b2JointDef) this.b2JointDef.apply(this, arguments); + if (this.constructor === b2JointDef) + this.b2JointDef.apply(this, arguments); } Box2D.Dynamics.Joints.b2JointDef = b2JointDef; @@ -957,7 +974,13 @@ var separation = v2X * normal1WorldX + v2Y * normal1WorldY; return separation; }; - b2Collision.FindMaxSeparation = function (edgeIndex, poly1, xf1, poly2, xf2) { + b2Collision.FindMaxSeparation = function ( + edgeIndex, + poly1, + xf1, + poly2, + xf2 + ) { var count1 = parseInt(poly1.m_vertexCount); var normals1 = poly1.m_normals; var tVec; @@ -1003,7 +1026,8 @@ return s; } while (true) { - if (increment == -1) edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1; + if (increment == -1) + edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1; else edge = bestEdge + 1 < count1 ? bestEdge + 1 : 0; s = b2Collision.EdgeSeparation(poly1, xf1, edge, poly2, xf2); if (s > bestSeparation) { @@ -1049,16 +1073,20 @@ tClip = c[0]; tVec = vertices2[i1]; tMat = xf2.R; - tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); - tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + tClip.v.x = + xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + tClip.v.y = + xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); tClip.id.features.referenceEdge = edge1; tClip.id.features.incidentEdge = i1; tClip.id.features.incidentVertex = 0; tClip = c[1]; tVec = vertices2[i2]; tMat = xf2.R; - tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); - tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + tClip.v.x = + xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + tClip.v.y = + xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); tClip.id.features.referenceEdge = edge1; tClip.id.features.incidentEdge = i2; tClip.id.features.incidentVertex = 1; @@ -1156,13 +1184,17 @@ var v11 = b2Collision.s_v11; var v12 = b2Collision.s_v12; v11.x = - xf1.position.x + (tMat.col1.x * local_v11.x + tMat.col2.x * local_v11.y); + xf1.position.x + + (tMat.col1.x * local_v11.x + tMat.col2.x * local_v11.y); v11.y = - xf1.position.y + (tMat.col1.y * local_v11.x + tMat.col2.y * local_v11.y); + xf1.position.y + + (tMat.col1.y * local_v11.x + tMat.col2.y * local_v11.y); v12.x = - xf1.position.x + (tMat.col1.x * local_v12.x + tMat.col2.x * local_v12.y); + xf1.position.x + + (tMat.col1.x * local_v12.x + tMat.col2.x * local_v12.y); v12.y = - xf1.position.y + (tMat.col1.y * local_v12.x + tMat.col2.y * local_v12.y); + xf1.position.y + + (tMat.col1.y * local_v12.x + tMat.col2.y * local_v12.y); var frontOffset = normal.x * v11.x + normal.y * v11.y; var sideOffset1 = -tangent.x * v11.x - tangent.y * v11.y + totalRadius; var sideOffset2 = tangent.x * v12.x + tangent.y * v12.y + totalRadius; @@ -1203,7 +1235,13 @@ } manifold.m_pointCount = pointCount; }; - b2Collision.CollideCircles = function (manifold, circle1, xf1, circle2, xf2) { + b2Collision.CollideCircles = function ( + manifold, + circle1, + xf1, + circle2, + xf2 + ) { manifold.m_pointCount = 0; var tMat; var tVec; @@ -1489,7 +1527,9 @@ } b2Distance.b2_gjkMaxIters = b2Math.Max(b2Distance.b2_gjkMaxIters, iter); simplex.GetWitnessPoints(output.pointA, output.pointB); - output.distance = b2Math.SubtractVV(output.pointA, output.pointB).Length(); + output.distance = b2Math + .SubtractVV(output.pointA, output.pointB) + .Length(); output.iterations = iter; simplex.WriteCache(cache); if (input.useRadii) { @@ -1748,17 +1788,21 @@ var child2 = sibling.child2; var norm1 = Math.abs( - (child1.aabb.lowerBound.x + child1.aabb.upperBound.x) / 2 - center.x + (child1.aabb.lowerBound.x + child1.aabb.upperBound.x) / 2 - + center.x ) + Math.abs( - (child1.aabb.lowerBound.y + child1.aabb.upperBound.y) / 2 - center.y + (child1.aabb.lowerBound.y + child1.aabb.upperBound.y) / 2 - + center.y ); var norm2 = Math.abs( - (child2.aabb.lowerBound.x + child2.aabb.upperBound.x) / 2 - center.x + (child2.aabb.lowerBound.x + child2.aabb.upperBound.x) / 2 - + center.x ) + Math.abs( - (child2.aabb.lowerBound.y + child2.aabb.upperBound.y) / 2 - center.y + (child2.aabb.lowerBound.y + child2.aabb.upperBound.y) / 2 - + center.y ); if (norm1 < norm2) { sibling = child1; @@ -2275,15 +2319,19 @@ tVec = this.m_localPoint; tMat = transformB.R; pointBX = - transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + transformB.position.x + + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); pointBY = - transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + transformB.position.y + + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); tVec = localPointA; tMat = transformA.R; pointAX = - transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + transformA.position.x + + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); pointAY = - transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + transformA.position.y + + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); sgn = (pointAX - pointBX) * normalX + (pointAY - pointBY) * normalY; if (s < 0.0) { this.m_axis.NegativeSelf(); @@ -2302,15 +2350,19 @@ tVec = this.m_localPoint; tMat = transformA.R; pointAX = - transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + transformA.position.x + + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); pointAY = - transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + transformA.position.y + + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); tVec = localPointB; tMat = transformB.R; pointBX = - transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + transformB.position.x + + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); pointBY = - transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + transformB.position.y + + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); sgn = (pointBX - pointAX) * normalX + (pointBY - pointAY) * normalY; if (s < 0.0) { this.m_axis.NegativeSelf(); @@ -2318,7 +2370,10 @@ } } }; - b2SeparationFunction.prototype.Evaluate = function (transformA, transformB) { + b2SeparationFunction.prototype.Evaluate = function ( + transformA, + transformB + ) { var axisA; var axisB; var localPointA; @@ -3133,7 +3188,8 @@ }; b2CircleShape.prototype.ComputeMass = function (massData, density) { if (density === undefined) density = 0; - massData.mass = density * b2Settings.b2_pi * this.m_radius * this.m_radius; + massData.mass = + density * b2Settings.b2_pi * this.m_radius * this.m_radius; massData.center.SetV(this.m_p); massData.I = massData.mass * @@ -3395,7 +3451,10 @@ this.m_nextEdge = null; this.m_v1 = v1; this.m_v2 = v2; - this.m_direction.Set(this.m_v2.x - this.m_v1.x, this.m_v2.y - this.m_v1.y); + this.m_direction.Set( + this.m_v2.x - this.m_v1.x, + this.m_v2.y - this.m_v1.y + ); this.m_length = this.m_direction.Normalize(); this.m_normal.Set(this.m_direction.y, -this.m_direction.x); this.m_coreV1.Set( @@ -3413,13 +3472,23 @@ this.m_cornerDir1 = this.m_normal; this.m_cornerDir2.Set(-this.m_normal.x, -this.m_normal.y); }; - b2EdgeShape.prototype.SetPrevEdge = function (edge, core, cornerDir, convex) { + b2EdgeShape.prototype.SetPrevEdge = function ( + edge, + core, + cornerDir, + convex + ) { this.m_prevEdge = edge; this.m_coreV1 = core; this.m_cornerDir1 = cornerDir; this.m_cornerConvex1 = convex; }; - b2EdgeShape.prototype.SetNextEdge = function (edge, core, cornerDir, convex) { + b2EdgeShape.prototype.SetNextEdge = function ( + edge, + core, + cornerDir, + convex + ) { this.m_nextEdge = edge; this.m_coreV2 = core; this.m_cornerDir2 = cornerDir; @@ -3521,7 +3590,12 @@ polygonShape.SetAsBox(hx, hy); return polygonShape; }; - b2PolygonShape.prototype.SetAsOrientedBox = function (hx, hy, center, angle) { + b2PolygonShape.prototype.SetAsOrientedBox = function ( + hx, + hy, + center, + angle + ) { if (hx === undefined) hx = 0; if (hy === undefined) hy = 0; if (center === undefined) center = null; @@ -3646,8 +3720,10 @@ b2PolygonShape.prototype.ComputeAABB = function (aabb, xf) { var tMat = xf.R; var tVec = this.m_vertices[0]; - var lowerX = xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); - var lowerY = xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + var lowerX = + xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var lowerY = + xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); var upperX = lowerX; var upperY = lowerY; for (var i = 1; i < this.m_vertexCount; ++i) { @@ -3703,11 +3779,13 @@ var ey2 = e2Y; var intx2 = k_inv3 * - (0.25 * (ex1 * ex1 + ex2 * ex1 + ex2 * ex2) + (px * ex1 + px * ex2)) + + (0.25 * (ex1 * ex1 + ex2 * ex1 + ex2 * ex2) + + (px * ex1 + px * ex2)) + 0.5 * px * px; var inty2 = k_inv3 * - (0.25 * (ey1 * ey1 + ey2 * ey1 + ey2 * ey2) + (py * ey1 + py * ey2)) + + (0.25 * (ey1 * ey1 + ey2 * ey1 + ey2 * ey2) + + (py * ey1 + py * ey2)) + 0.5 * py * py; I += D * (intx2 + inty2); } @@ -3925,8 +4003,10 @@ var centerX = 0.5 * (lowerX + upperX); var centerY = 0.5 * (lowerY + upperY); var tMat = obb.R; - obb.center.x = root.x + (tMat.col1.x * centerX + tMat.col2.x * centerY); - obb.center.y = root.y + (tMat.col1.y * centerX + tMat.col2.y * centerY); + obb.center.x = + root.x + (tMat.col1.x * centerX + tMat.col2.x * centerY); + obb.center.y = + root.y + (tMat.col1.y * centerX + tMat.col2.y * centerY); obb.extents.x = 0.5 * (upperX - lowerX); obb.extents.y = 0.5 * (upperY - lowerY); } @@ -4404,8 +4484,14 @@ return C; }; b2Math.MulTMM = function (A, B) { - var c1 = new b2Vec2(b2Math.Dot(A.col1, B.col1), b2Math.Dot(A.col2, B.col1)); - var c2 = new b2Vec2(b2Math.Dot(A.col1, B.col2), b2Math.Dot(A.col2, B.col2)); + var c1 = new b2Vec2( + b2Math.Dot(A.col1, B.col1), + b2Math.Dot(A.col2, B.col1) + ); + var c2 = new b2Vec2( + b2Math.Dot(A.col1, B.col2), + b2Math.Dot(A.col2, B.col2) + ); var C = b2Mat22.FromVV(c1, c2); return C; }; @@ -4780,7 +4866,8 @@ b2CircleContact = Box2D.Dynamics.Contacts.b2CircleContact, b2Contact = Box2D.Dynamics.Contacts.b2Contact, b2ContactConstraint = Box2D.Dynamics.Contacts.b2ContactConstraint, - b2ContactConstraintPoint = Box2D.Dynamics.Contacts.b2ContactConstraintPoint, + b2ContactConstraintPoint = + Box2D.Dynamics.Contacts.b2ContactConstraintPoint, b2ContactEdge = Box2D.Dynamics.Contacts.b2ContactEdge, b2ContactFactory = Box2D.Dynamics.Contacts.b2ContactFactory, b2ContactRegister = Box2D.Dynamics.Contacts.b2ContactRegister, @@ -4791,7 +4878,8 @@ b2PolyAndCircleContact = Box2D.Dynamics.Contacts.b2PolyAndCircleContact, b2PolyAndEdgeContact = Box2D.Dynamics.Contacts.b2PolyAndEdgeContact, b2PolygonContact = Box2D.Dynamics.Contacts.b2PolygonContact, - b2PositionSolverManifold = Box2D.Dynamics.Contacts.b2PositionSolverManifold, + b2PositionSolverManifold = + Box2D.Dynamics.Contacts.b2PositionSolverManifold, b2Controller = Box2D.Dynamics.Controllers.b2Controller, b2DistanceJoint = Box2D.Dynamics.Joints.b2DistanceJoint, b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef, @@ -4984,7 +5072,8 @@ bd.angularDamping = this.m_angularDamping; bd.angularVelocity = this.m_angularVelocity; bd.fixedRotation = - (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag; + (this.m_flags & b2Body.e_fixedRotationFlag) == + b2Body.e_fixedRotationFlag; bd.bullet = (this.m_flags & b2Body.e_bulletFlag) == b2Body.e_bulletFlag; bd.awake = (this.m_flags & b2Body.e_awakeFlag) == b2Body.e_awakeFlag; bd.linearDamping = this.m_linearDamping; @@ -5126,7 +5215,10 @@ this.m_mass = 1.0; } this.m_invMass = 1.0 / this.m_mass; - if (massData.I > 0.0 && (this.m_flags & b2Body.e_fixedRotationFlag) == 0) { + if ( + massData.I > 0.0 && + (this.m_flags & b2Body.e_fixedRotationFlag) == 0 + ) { this.m_I = massData.I - this.m_mass * @@ -5313,7 +5405,8 @@ }; b2Body.prototype.IsFixedRotation = function () { return ( - (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag + (this.m_flags & b2Body.e_fixedRotationFlag) == + b2Body.e_fixedRotationFlag ); }; b2Body.prototype.SetActive = function (flag) { @@ -5347,7 +5440,9 @@ return (this.m_flags & b2Body.e_activeFlag) == b2Body.e_activeFlag; }; b2Body.prototype.IsSleepingAllowed = function () { - return (this.m_flags & b2Body.e_allowSleepFlag) == b2Body.e_allowSleepFlag; + return ( + (this.m_flags & b2Body.e_allowSleepFlag) == b2Body.e_allowSleepFlag + ); }; b2Body.prototype.GetFixtureList = function () { return this.m_fixtureList; @@ -5561,8 +5656,10 @@ proxyUserDataA, proxyUserDataB ) { - var fixtureA = proxyUserDataA instanceof b2Fixture ? proxyUserDataA : null; - var fixtureB = proxyUserDataB instanceof b2Fixture ? proxyUserDataB : null; + var fixtureA = + proxyUserDataA instanceof b2Fixture ? proxyUserDataA : null; + var fixtureB = + proxyUserDataB instanceof b2Fixture ? proxyUserDataB : null; var bodyA = fixtureA.GetBody(); var bodyB = fixtureB.GetBody(); if (bodyA == bodyB) return; @@ -5729,7 +5826,11 @@ if (xformScale === undefined) xformScale = 0; }; b2DebugDraw.prototype.GetXFormScale = function () {}; - b2DebugDraw.prototype.DrawPolygon = function (vertices, vertexCount, color) { + b2DebugDraw.prototype.DrawPolygon = function ( + vertices, + vertexCount, + color + ) { if (vertexCount === undefined) vertexCount = 0; }; b2DebugDraw.prototype.DrawSolidPolygon = function ( @@ -5979,8 +6080,10 @@ for (i = 0; i < this.m_bodyCount; ++i) { b = this.m_bodies[i]; if (b.GetType() != b2Body.b2_dynamicBody) continue; - b.m_linearVelocity.x += step.dt * (gravity.x + b.m_invMass * b.m_force.x); - b.m_linearVelocity.y += step.dt * (gravity.y + b.m_invMass * b.m_force.y); + b.m_linearVelocity.x += + step.dt * (gravity.x + b.m_invMass * b.m_force.x); + b.m_linearVelocity.y += + step.dt * (gravity.y + b.m_invMass * b.m_force.y); b.m_angularVelocity += step.dt * b.m_invI * b.m_torque; b.m_linearVelocity.Multiply( b2Math.Clamp(1.0 - step.dt * b.m_linearDamping, 0.0, 1.0) @@ -6063,7 +6166,8 @@ if (allowSleep) { var minSleepTime = Number.MAX_VALUE; var linTolSqr = - b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance; + b2Settings.b2_linearSleepTolerance * + b2Settings.b2_linearSleepTolerance; var angTolSqr = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance; @@ -6145,7 +6249,8 @@ } var k_toiBaumgarte = 0.75; for (i = 0; i < subStep.positionIterations; ++i) { - var contactsOkay = contactSolver.SolvePositionConstraints(k_toiBaumgarte); + var contactsOkay = + contactSolver.SolvePositionConstraints(k_toiBaumgarte); var jointsOkay = true; for (j = 0; j < this.m_jointCount; ++j) { var jointOkay = this.m_joints[j].SolvePositionConstraints( @@ -6870,7 +6975,8 @@ bA = fA.m_body; bB = fB.m_body; if ( - (bA.GetType() != b2Body.b2_dynamicBody || bA.IsAwake() == false) && + (bA.GetType() != b2Body.b2_dynamicBody || + bA.IsAwake() == false) && (bB.GetType() != b2Body.b2_dynamicBody || bB.IsAwake() == false) ) { continue; @@ -7113,7 +7219,8 @@ b2CircleContact = Box2D.Dynamics.Contacts.b2CircleContact, b2Contact = Box2D.Dynamics.Contacts.b2Contact, b2ContactConstraint = Box2D.Dynamics.Contacts.b2ContactConstraint, - b2ContactConstraintPoint = Box2D.Dynamics.Contacts.b2ContactConstraintPoint, + b2ContactConstraintPoint = + Box2D.Dynamics.Contacts.b2ContactConstraintPoint, b2ContactEdge = Box2D.Dynamics.Contacts.b2ContactEdge, b2ContactFactory = Box2D.Dynamics.Contacts.b2ContactFactory, b2ContactRegister = Box2D.Dynamics.Contacts.b2ContactRegister, @@ -7124,7 +7231,8 @@ b2PolyAndCircleContact = Box2D.Dynamics.Contacts.b2PolyAndCircleContact, b2PolyAndEdgeContact = Box2D.Dynamics.Contacts.b2PolyAndEdgeContact, b2PolygonContact = Box2D.Dynamics.Contacts.b2PolygonContact, - b2PositionSolverManifold = Box2D.Dynamics.Contacts.b2PositionSolverManifold, + b2PositionSolverManifold = + Box2D.Dynamics.Contacts.b2PositionSolverManifold, b2Body = Box2D.Dynamics.b2Body, b2BodyDef = Box2D.Dynamics.b2BodyDef, b2ContactFilter = Box2D.Dynamics.b2ContactFilter, @@ -7237,7 +7345,8 @@ }; b2Contact.prototype.IsContinuous = function () { return ( - (this.m_flags & b2Contact.e_continuousFlag) == b2Contact.e_continuousFlag + (this.m_flags & b2Contact.e_continuousFlag) == + b2Contact.e_continuousFlag ); }; b2Contact.prototype.SetSensor = function (sensor) { @@ -7258,7 +7367,9 @@ } }; b2Contact.prototype.IsEnabled = function () { - return (this.m_flags & b2Contact.e_enabledFlag) == b2Contact.e_enabledFlag; + return ( + (this.m_flags & b2Contact.e_enabledFlag) == b2Contact.e_enabledFlag + ); }; b2Contact.prototype.GetNext = function () { return this.m_next; @@ -7550,7 +7661,8 @@ var tMat; this.m_constraintCount = contactCount; while (this.m_constraints.length < this.m_constraintCount) { - this.m_constraints[this.m_constraints.length] = new b2ContactConstraint(); + this.m_constraints[this.m_constraints.length] = + new b2ContactConstraint(); } for (i = 0; i < contactCount; ++i) { contact = contacts[i]; @@ -7629,7 +7741,8 @@ var kEqualized = bodyA.m_mass * bodyA.m_invMass + bodyB.m_mass * bodyB.m_invMass; kEqualized += - bodyA.m_mass * bodyA.m_invI * rnA + bodyB.m_mass * bodyB.m_invI * rnB; + bodyA.m_mass * bodyA.m_invI * rnA + + bodyB.m_mass * bodyB.m_invI * rnB; ccp.equalizedMass = 1.0 / kEqualized; var tangentX = normalY; var tangentY = -normalX; @@ -7704,8 +7817,10 @@ var ccp = c.points[j]; ccp.normalImpulse *= step.dtRatio; ccp.tangentImpulse *= step.dtRatio; - var PX = ccp.normalImpulse * normalX + ccp.tangentImpulse * tangentX; - var PY = ccp.normalImpulse * normalY + ccp.tangentImpulse * tangentY; + var PX = + ccp.normalImpulse * normalX + ccp.tangentImpulse * tangentX; + var PY = + ccp.normalImpulse * normalY + ccp.tangentImpulse * tangentY; bodyA.m_angularVelocity -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX); bodyA.m_linearVelocity.x -= invMassA * PX; bodyA.m_linearVelocity.y -= invMassA * PY; @@ -7988,7 +8103,8 @@ var rAY = point.y - bodyA.m_sweep.c.y; var rBX = point.x - bodyB.m_sweep.c.x; var rBY = point.y - bodyB.m_sweep.c.y; - minSeparation = minSeparation < separation ? minSeparation : separation; + minSeparation = + minSeparation < separation ? minSeparation : separation; var C = b2Math.Clamp( baumgarte * (separation + b2Settings.b2_linearSlop), -b2Settings.b2_maxLinearCorrection, @@ -8051,7 +8167,8 @@ xf2 ) {}; Box2D.inherit(b2NullContact, Box2D.Dynamics.Contacts.b2Contact); - b2NullContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype; + b2NullContact.prototype.__super = + Box2D.Dynamics.Contacts.b2Contact.prototype; b2NullContact.b2NullContact = function () { Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments); }; @@ -8157,7 +8274,9 @@ b2PositionSolverManifold.b2PositionSolverManifold = function () {}; b2PositionSolverManifold.prototype.b2PositionSolverManifold = function () { this.m_normal = new b2Vec2(); - this.m_separations = new Vector_a2j_Number(b2Settings.b2_maxManifoldPoints); + this.m_separations = new Vector_a2j_Number( + b2Settings.b2_maxManifoldPoints + ); this.m_points = new Vector(b2Settings.b2_maxManifoldPoints); for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) { this.m_points[i] = new b2Vec2(); @@ -8324,11 +8443,17 @@ b2TensorDampingController = Box2D.Dynamics.Controllers.b2TensorDampingController; - Box2D.inherit(b2BuoyancyController, Box2D.Dynamics.Controllers.b2Controller); + Box2D.inherit( + b2BuoyancyController, + Box2D.Dynamics.Controllers.b2Controller + ); b2BuoyancyController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype; b2BuoyancyController.b2BuoyancyController = function () { - Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments); + Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply( + this, + arguments + ); this.normal = new b2Vec2(0, -1); this.offset = 0; this.density = 0; @@ -8418,7 +8543,10 @@ b2ConstantAccelController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype; b2ConstantAccelController.b2ConstantAccelController = function () { - Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments); + Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply( + this, + arguments + ); this.A = new b2Vec2(0, 0); }; b2ConstantAccelController.prototype.Step = function (step) { @@ -8441,7 +8569,10 @@ b2ConstantForceController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype; b2ConstantForceController.b2ConstantForceController = function () { - Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments); + Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply( + this, + arguments + ); this.F = new b2Vec2(0, 0); }; b2ConstantForceController.prototype.Step = function (step) { @@ -8501,7 +8632,10 @@ b2GravityController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype; b2GravityController.b2GravityController = function () { - Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments); + Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply( + this, + arguments + ); this.G = 1; this.invSqr = true; }; @@ -8564,7 +8698,10 @@ b2TensorDampingController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype; b2TensorDampingController.b2TensorDampingController = function () { - Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments); + Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply( + this, + arguments + ); this.T = new b2Mat22(); this.maxTimestep = 0; }; @@ -9216,7 +9353,9 @@ this.m_J.linearB.Set(-this.m_ratio * ugX, -this.m_ratio * ugY); this.m_J.angularB = -this.m_ratio * crug; K += - this.m_ratio * this.m_ratio * (bB.m_invMass + bB.m_invI * crug * crug); + this.m_ratio * + this.m_ratio * + (bB.m_invMass + bB.m_invI * crug * crug); } this.m_mass = K > 0.0 ? 1.0 / K : 0.0; if (step.warmStarting) { @@ -9282,7 +9421,8 @@ return linearError < b2Settings.b2_linearSlop; }; Box2D.inherit(b2GearJointDef, Box2D.Dynamics.Joints.b2JointDef); - b2GearJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype; + b2GearJointDef.prototype.__super = + Box2D.Dynamics.Joints.b2JointDef.prototype; b2GearJointDef.b2GearJointDef = function () { Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments); }; @@ -9375,7 +9515,9 @@ break; case b2Joint.e_mouseJoint: { - joint = new b2MouseJoint(def instanceof b2MouseJointDef ? def : null); + joint = new b2MouseJoint( + def instanceof b2MouseJointDef ? def : null + ); } break; case b2Joint.e_prismaticJoint: @@ -9673,7 +9815,8 @@ var i2 = this.m_invIB; this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2; - this.m_K.col1.y = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; + this.m_K.col1.y = + i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; this.m_K.col2.x = this.m_K.col1.y; this.m_K.col2.y = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2; @@ -9916,7 +10059,8 @@ i2 = this.m_invIB; this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2; - this.m_K.col1.y = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; + this.m_K.col1.y = + i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; this.m_K.col2.x = this.m_K.col1.y; this.m_K.col2.y = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2; @@ -9957,7 +10101,8 @@ ); }; Box2D.inherit(b2LineJointDef, Box2D.Dynamics.Joints.b2JointDef); - b2LineJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype; + b2LineJointDef.prototype.__super = + Box2D.Dynamics.Joints.b2JointDef.prototype; b2LineJointDef.b2LineJointDef = function () { Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments); this.localAnchorA = new b2Vec2(); @@ -10146,7 +10291,8 @@ this.dampingRatio = 0.7; }; Box2D.inherit(b2PrismaticJoint, Box2D.Dynamics.Joints.b2Joint); - b2PrismaticJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype; + b2PrismaticJoint.prototype.__super = + Box2D.Dynamics.Joints.b2Joint.prototype; b2PrismaticJoint.b2PrismaticJoint = function () { Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments); this.m_localAnchor1 = new b2Vec2(); @@ -10347,7 +10493,8 @@ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2; this.m_K.col1.y = i1 * this.m_s1 + i2 * this.m_s2; - this.m_K.col1.z = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; + this.m_K.col1.z = + i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; this.m_K.col2.x = this.m_K.col1.y; this.m_K.col2.y = i1 + i2; this.m_K.col2.z = i1 * this.m_a1 + i2 * this.m_a2; @@ -10595,7 +10742,8 @@ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2; this.m_K.col1.y = i1 * this.m_s1 + i2 * this.m_s2; - this.m_K.col1.z = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; + this.m_K.col1.z = + i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2; this.m_K.col2.x = this.m_K.col1.y; this.m_K.col2.y = i1 + i2; this.m_K.col2.z = i1 * this.m_a1 + i2 * this.m_a2; @@ -11370,11 +11518,13 @@ v1.x -= m1 * this.impulse3.x; v1.y -= m1 * this.impulse3.y; w1 -= - i1 * (r1X * this.impulse3.y - r1Y * this.impulse3.x + this.impulse3.z); + i1 * + (r1X * this.impulse3.y - r1Y * this.impulse3.x + this.impulse3.z); v2.x += m2 * this.impulse3.x; v2.y += m2 * this.impulse3.y; w2 += - i2 * (r2X * this.impulse3.y - r2Y * this.impulse3.x + this.impulse3.z); + i2 * + (r2X * this.impulse3.y - r2Y * this.impulse3.x + this.impulse3.z); } else { tMat = bA.m_xf.R; r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x; @@ -11729,7 +11879,8 @@ ); }; Box2D.inherit(b2WeldJointDef, Box2D.Dynamics.Joints.b2JointDef); - b2WeldJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype; + b2WeldJointDef.prototype.__super = + Box2D.Dynamics.Joints.b2JointDef.prototype; b2WeldJointDef.b2WeldJointDef = function () { Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments); this.localAnchorA = new b2Vec2(); @@ -11844,7 +11995,11 @@ b2DebugDraw.prototype.GetXFormScale = function () { return this.m_xformScale; }; - b2DebugDraw.prototype.DrawPolygon = function (vertices, vertexCount, color) { + b2DebugDraw.prototype.DrawPolygon = function ( + vertices, + vertexCount, + color + ) { if (!vertexCount) return; var s = this.m_ctx; var drawScale = this.m_drawScale; @@ -12003,16 +12158,16 @@ const prevPos = {}; /** - * Active b2Body/s in the world. - * @type {Object.} - */ + * Active b2Body/s in the world. + * @type {Object.} + */ const bodies = {}; // const joints = {}; const pinned = {}; // Map of IDs to pinned joints /** - * The runtime instantiating this block package. - * @type {Array} - */ + * The runtime instantiating this block package. + * @type {Array} + */ const stageBodies = []; // const categorySeq = 1; @@ -12208,12 +12363,12 @@ }; /** - * Set the X and Y coordinates (No Fencing) - * @param {!RenderedTarget} rt the renderedTarget. - * @param {!number} x New X coordinate, in Scratch coordinates. - * @param {!number} y New Y coordinate, in Scratch coordinates. - * @param {?boolean} force Force setting X/Y, in case of dragging - */ + * Set the X and Y coordinates (No Fencing) + * @param {!RenderedTarget} rt the renderedTarget. + * @param {!number} x New X coordinate, in Scratch coordinates. + * @param {!number} y New Y coordinate, in Scratch coordinates. + * @param {?boolean} force Force setting X/Y, in case of dragging + */ const _setXY = function (rt, x, y, force) { if (rt.isStage) return; if (rt.dragging && !force) return; @@ -12291,8 +12446,10 @@ } }; - const blockIconURI = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSItMy43IC0zLjcgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTMuNyAtMy43IDQwIDQwIg0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxkZWZzPg0KPC9kZWZzPg0KPHJlY3QgeD0iOC45IiB5PSIxLjUiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxLjUiIHk9IjE2LjMiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxNi4zIiB5PSIxNi4zIiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMxNjlGQjAiIHN0cm9rZS13aWR0aD0iMyIgd2lkdGg9IjE0LjgiIGhlaWdodD0iMTQuOCIvPg0KPC9zdmc+"; - const menuIconURI = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSItMy43IC0zLjcgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTMuNyAtMy43IDQwIDQwIg0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxkZWZzPg0KPC9kZWZzPg0KPHJlY3QgeD0iOC45IiB5PSIxLjUiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxLjUiIHk9IjE2LjMiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxNi4zIiB5PSIxNi4zIiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMxNjlGQjAiIHN0cm9rZS13aWR0aD0iMyIgd2lkdGg9IjE0LjgiIGhlaWdodD0iMTQuOCIvPg0KPC9zdmc+"; + const blockIconURI = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSItMy43IC0zLjcgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTMuNyAtMy43IDQwIDQwIg0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxkZWZzPg0KPC9kZWZzPg0KPHJlY3QgeD0iOC45IiB5PSIxLjUiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxLjUiIHk9IjE2LjMiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxNi4zIiB5PSIxNi4zIiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMxNjlGQjAiIHN0cm9rZS13aWR0aD0iMyIgd2lkdGg9IjE0LjgiIGhlaWdodD0iMTQuOCIvPg0KPC9zdmc+"; + const menuIconURI = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQoJIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOmE9Imh0dHA6Ly9ucy5hZG9iZS5jb20vQWRvYmVTVkdWaWV3ZXJFeHRlbnNpb25zLzMuMC8iDQoJIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSItMy43IC0zLjcgNDAgNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTMuNyAtMy43IDQwIDQwIg0KCSB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxkZWZzPg0KPC9kZWZzPg0KPHJlY3QgeD0iOC45IiB5PSIxLjUiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxLjUiIHk9IjE2LjMiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzE2OUZCMCIgc3Ryb2tlLXdpZHRoPSIzIiB3aWR0aD0iMTQuOCIgaGVpZ2h0PSIxNC44Ii8+DQo8cmVjdCB4PSIxNi4zIiB5PSIxNi4zIiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMxNjlGQjAiIHN0cm9rZS13aWR0aD0iMyIgd2lkdGg9IjE0LjgiIGhlaWdodD0iMTQuOCIvPg0KPC9zdmc+"; const vm = Scratch.vm; class Scratch3Griffpatch { @@ -12354,7 +12511,7 @@ default: "Physics", description: "Label for the Griffpatch extension category", }), - docsURI: "https://extensions.turbowarp.org/box2d.html", + docsURI: "https://extensions.turbowarp.org/box2d", menuIconURI: menuIconURI, blockIconURI: blockIconURI, blocks: [ @@ -12609,7 +12766,7 @@ filter: [Scratch.TargetType.SPRITE], }, - '---', + "---", { opcode: "setAngVelocity", @@ -12637,7 +12794,7 @@ }), filter: [Scratch.TargetType.SPRITE], }, - + "---", { @@ -12662,10 +12819,10 @@ text: formatMessage({ id: "griffpatch.getStatic", default: "fixed?", - description: "get whether this sprite is fixed" + description: "get whether this sprite is fixed", }), blockType: BlockType.BOOLEAN, - filter: [Scratch.TargetType.SPRITE] + filter: [Scratch.TargetType.SPRITE], }, "---", @@ -12676,7 +12833,7 @@ text: formatMessage({ id: "griffpatch.setDensity", default: "set density [density]", - description: "Set the density of the object" + description: "Set the density of the object", }), arguments: { density: { @@ -12694,13 +12851,13 @@ text: formatMessage({ id: "griffpatch.setDensityValue", default: "set density to [density]", - description: "Set the density of the object" + description: "Set the density of the object", }), arguments: { density: { type: ArgumentType.NUMBER, - defaultValue: 100 - } + defaultValue: 100, + }, }, filter: [Scratch.TargetType.SPRITE], }, @@ -12816,7 +12973,8 @@ blockType: BlockType.COMMAND, text: formatMessage({ id: "griffpatch.setProperties", - default: "set density [density] roughness [friction] bounce [restitution]", + default: + "set density [density] roughness [friction] bounce [restitution]", description: "Set the density of the object", }), arguments: { @@ -12938,7 +13096,7 @@ description: "get the y scroll", }), blockType: BlockType.REPORTER, - } + }, ], menus: { @@ -13104,7 +13262,8 @@ } const prev = prevPos[targetID]; - const fixedRotation = target.rotationStyle !== ROTATION_STYLE_ALL_AROUND; + const fixedRotation = + target.rotationStyle !== ROTATION_STYLE_ALL_AROUND; if (prev && (prev.x !== target.x || prev.y !== target.y)) { const pos = new b2Vec2( @@ -13350,7 +13509,7 @@ body.GetFixtureList().SetDensity(Cast.toNumber(args.density) / 100.0); body.ResetMassData(); } - + getDensity(args, util) { let body = bodies[util.target.id]; if (!body) { @@ -13385,7 +13544,9 @@ body = this.setPhysicsFor(util.target); } - body.GetFixtureList().SetRestitution(Cast.toNumber(args.restitution) / 100.0); + body + .GetFixtureList() + .SetRestitution(Cast.toNumber(args.restitution) / 100.0); body.ResetMassData(); } @@ -13406,7 +13567,9 @@ body.GetFixtureList().SetDensity(Cast.toNumber(args.density) / 100.0); body.GetFixtureList().SetFriction(Cast.toNumber(args.friction) / 100.0); - body.GetFixtureList().SetRestitution(Cast.toNumber(args.restitution) / 100.0); + body + .GetFixtureList() + .SetRestitution(Cast.toNumber(args.restitution) / 100.0); body.ResetMassData(); } @@ -13695,7 +13858,11 @@ position.x * zoom - _scroll.x, position.y * zoom - _scroll.y ); - prevPos[targetID] = { x: target.x, y: target.y, dir: target.direction }; + prevPos[targetID] = { + x: target.x, + y: target.y, + dir: target.direction, + }; } } } diff --git a/extensions/clipboard.js b/extensions/clipboard.js index 7064a0194f..07f1752857 100644 --- a/extensions/clipboard.js +++ b/extensions/clipboard.js @@ -1,118 +1,123 @@ -/*! - * Copyright 2023 tomyo-code + AdamMady - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -(function(Scratch) { - 'use strict'; - - if (!Scratch.extensions.unsandboxed) { - throw new Error('Clipboard must run unsandboxed'); - } - - const extensionicon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MC40NTQ1NCIgaGVpZ2h0PSI4MC40NTQ1NCIgdmlld0JveD0iMCwwLDgwLjQ1NDU0LDgwLjQ1NDU0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5Ljc3MjcyLC0xMzkuNzcyNzIpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTE5OS43NzI3MywxODBjMCwtMjIuMjE2OSAxOC4wMTAzNywtNDAuMjI3MjcgNDAuMjI3MjcsLTQwLjIyNzI3YzIyLjIxNjksMCA0MC4yMjcyNywxOC4wMTAzNyA0MC4yMjcyNyw0MC4yMjcyN2MwLDIyLjIxNjkgLTE4LjAxMDM3LDQwLjIyNzI3IC00MC4yMjcyNyw0MC4yMjcyN2MtMjIuMjE2OSwwIC00MC4yMjcyNywtMTguMDEwMzcgLTQwLjIyNzI3LC00MC4yMjcyN3oiIGZpbGw9IiMwMDgwODAiIHN0cm9rZS13aWR0aD0iMCIvPjxpbWFnZSB4PSI0MzQiIHk9IjMwMCIgdHJhbnNmb3JtPSJzY2FsZSgwLjUsMC41KSIgd2lkdGg9Ijk0IiBoZWlnaHQ9IjExOCIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFGNEFBQUIyQ0FZQUFBQkJMU1ExQUFBQUFYTlNSMElBcnM0YzZRQUFCckpKUkVGVWVGN3RuVnZJYmtNWXgyZTJuTUlPeVNHbDVCQjJTWExNc1lRTFo5cHNaMEl1aEhKQmlkeTZJaUVrNTFPT0VYZkVqZHk0c0pPemN0Z1hLRGtrNStTdmI3Ny9zMzNmM3U5YTYxbnZtdlhNdk85KzNwdXA5VDd6UERPLythOVpzMmF0bVJWRDVUOEFlN0tJMXpNOWx1bTJUTDlqK2dyVGV4ZlNHT09mTlZjdDFseTRoYkk1ZU9NV0FuQXpROTdLZEN0bEVlUU1XRTNsdjYzTVoycFdyZUlkdktrT1V0ZHlHME5LT3EwNHBJOVAxNFFZNDd2R1ZXa05OMjJsUnF1RGd4OE43V1RIQVBibFB4OHczVHhURVQ2bm4vMm8vSDh6K1Iza3BockZiN0xnQVd6QkpyeUo2UWxNdHh6VXRQOW5mcHFLdTN1U1B3Q1A4dmlsSGZIKzR2OC9NZDJaNllxR2ZPRHhreG4vOVliNGovRzRuSGxEcXkzbGU0SnhuMW5xY0wzaUhUeHN3UVBZbmkwaFYvMjloeloxUS80MzJQSW5OaWp1TXg3ZnB5SC9iengrTVAwa2V3RFg4TGljU1UzZFovby94bmhkUS94ZmVIeTd6UFdYTSs0cHhyODRwUTUrRVRNQWMvRFM5NnpKM05JYnVudVZMWDU2ZytLKzVmRmRHOHFSenNnWTQyRU4rZi9nOGFZNzNPZVkvN3lHL0QvdytJNGpjemhWRk8vZ0Z4VnZEcjVMS2JrRWNCa1ZKeGV4Wlg0QnlCekxMZzBCcFp4SDBjOTc3Q0p1cFAzdFRKdjYrT2VaNzl3R3hiL0c0NmZrcW5DRG4zV2llQWUvcUhoejhITFY3V3JvZjJnZzQrZ3VlMm5RQjZpMFc5b3lBT2pxNHlXN2xFTXVoanQwS0YzeWRmWHhLMm1ZN0VJSVJ6TGRyS09pY29adHJTeEhNbHNZMVRqNFJjVlhCLzVIdHVReFREL3Bram9WM210T0JNRDM5THVUeHY4VU51a0pWWXp4ekQ1NUFUVGRFWXNiK2Y4U0huaVFhV3MramVJZGZIdExqUVkrRFRkampCZjBVWXJXRnNCcHRIMVpveFN0M3dsMklxQzlXSitmQi9ocXpLbzljeldLZC9BOVdpZ24rTWVwa0s1WlExWHhBSnhFdzZ1WW5zRTAxL3g3Vnpua1J1bGhHcVpaMFJqalIxMFpOZjhyN2tlU0c0M2lIYnlHT0cxeWduK1Npa2l6YW4xL0FPUk84SDdtM1oxcExROWg1TDVBWm1mUFlYM2x2cUpYbFIyOEh0ZDhnUWR3Tk92K3BuRWZya2MrMlRMTnBZUVFWbEg1di9aeFdGenhEajQwVGZhcEw2NVQ5ZkVBWkpTd2Z4L0ZWR1I3SnhWL1E1OHkxYUI0QjkvU1lwcmhaQy9GQXppQThkNW4yalc3MTBkUWxyYmZVUEV5Q2xQRkxxWjRCOS81UUdlY1BuN0pVLzk3VkJLcDEwaUdtWHYwR2RlWFZMeThidUhnQy9YeHZlYTlLeFMrUEVGN2lJcVhKMTZ0UlMycGVMbTRPbmhMeFZlb1hOTWlGVk84YVMwckRPYmdDeldLZzNmd2hRZ1VDdXVLbjNmd0FPVFo2ZVhHZGIyTDQrdG5qZVBXTVk1MzhNdmJ3YXlyQVpEZWV3OGhwUGUrRFgvcHJlTVlZM29MdVphZmd5L1VFcGJnWlUyUjlQVldWYjZEaW4vRUtxQW1qb1BYVUJyQnhnejhDR1dmYVpjT3ZsRHo1UVF2NnpNdktsU1htUXJyNEFzMWw0TjM4SVVJRkFwcnBuZ0FCN0tPRTFkY0Y2cC9qckN5dzlOTHZGLzRYZVBVd1dzb3RkdFVELzVGbHYvczRYV3R5b01zUTVYZFF0WnFTcWRkcjZ0NWhhOTFPQW5Bd1M5cEVVdndhZVYyQ01GNlBsNGp3Q0UyZnkrOWRtblhTRG40SWNnWDgxWVBYdmI0emIyejBYQjB3enpJeXZTMDRqekdxRnFwYnFsNEIxK2lqeDhtcXZuTGJhYjQrVU0zckVZT2ZoaS9xWE03K0tuUkRjdG9CbjdKUnFHeVUrdXdrbytYTzYzd0dQdExDZzUrNHdhY08vQ3l3K21WNDRrMWkrY1BxZmhEc25ocmNHS3BlQWRmWWh3UElNMVhoeERPR2xOSkdYeW5mUzFqakx0bDhOWG93bEx4RHI2UTRnOW4zT1BHVkZJRzMxOVE4UzlrOEZXRjRoMThDY1dQcVo1WjlHM1d4ODhpbkRITDdPREhwTnZpMjhIUEFmaFJOL3dzeEdlMHNEa1Y3K0I3TkpNWmVBQlhzMXhWclVWU3NQcUs0L3J6RmJacUV3ZmZqV3Jtd1pkYTlkZU50dDBpdmFJWFk1UXZIUXoxbC9KYkt0N0JqM1RuMm5weEJYQXQ0MTZSUlRKMlRyNms0clBPcWxvcTNzR1hVTHlkUUdjamtwbmlad09IWFNrZHZCM3JaWkVjdklNdlJLQlFXRmY4dklNSGNCRHJlRVNodWtyWVR6a3VmNnRrT2N3VTcrQ1hON01sK0ZvV24zMU14Y3NXdTBXRTcrQ0xZTGVkSkx1dmtybWFkNmo0NHdzeE41K2RkUEFsNW1vQXlGZmhaUkZhS2NISi9IcXY3emJsTHF4bEgrL2dTeWcrdDJKbTNaK1o0bWNkVk83eU8vamNSSlgrSEx3U1ZHNHpCNSticU5LZmcxZUN5bTJXRTN6YW56M0d1Q1ozSWVmUlg4NDl5Ung4RDRYa0JKLzJhd2toSEVybGY5MmpISnVNS1lEVnJHeDZEeW1FMFBwVlQ4MmVaQTVlSVo4eHdFdFkyU3BLdm9HbktFNVZKbkdrMG9qZmJlaC9oU2FPUnZFT3ZwM2sxT0JGd1RMWnBXa3d0NW1ld0xwMHJRVGc0S2VIT0UzTzllRFRON2xEQ0JkTzQ4WHpxQW5JenEzcDYwRUxpbmZ3YW5hREREY0N2NUx1MGpQTEVNS3FRZTQ5ODRZRUJMaDhDVGw5UFdoQjhRNStYTEZNQlA4ZjVqR04yQ3N0cTkwQUFBQUFTVVZPUks1Q1lJST0iIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMC41Ii8+PC9nPjwvZz48L3N2Zz4="; - - let lastPastedText = ''; - - window.addEventListener('copy', (event) => { - Scratch.vm.runtime.startHats('clipboard_whenCopied') ; - }); - window.addEventListener('paste', (event) => { - Scratch.vm.runtime.startHats('clipboard_whenPasted'); - const clipboardData = event.clipboardData || window.clipboardData; - const pastedText = clipboardData.getData('Text'); - lastPastedText = pastedText; - }); - - class Clipboard { - getInfo() { - return { - id: 'clipboard', - name: 'Clipboard', - blockIconURI: extensionicon, - color1: '#008080', - color2: '#006666', - blocks: [ - { - opcode: 'whenCopied', - blockType: Scratch.BlockType.HAT, - text: 'when something is copied', - isEdgeActivated: false - }, - { - opcode: 'whenPasted', - blockType: Scratch.BlockType.HAT, - text: 'when something is pasted', - isEdgeActivated: false - }, - '---', - { - opcode: 'setClipboard', - blockType: Scratch.BlockType.COMMAND, - text: 'copy to clipboard: [TEXT]', - arguments: { - TEXT: { - type: Scratch.ArgumentType.STRING - } - } - }, - { - opcode: 'resetClipboard', - blockType: Scratch.BlockType.COMMAND, - text: 'reset clipboard' - }, - '---', - { - opcode: 'clipboard', - blockType: Scratch.BlockType.REPORTER, - text: 'clipboard', - disableMonitor: true - }, - { - opcode: 'getLastPastedText', - blockType: Scratch.BlockType.REPORTER, - text: 'last pasted text', - disableMonitor: true - } - ], - }; - } - - setClipboard(args) { - navigator.clipboard.writeText(args.TEXT); - } - - resetClipboard() { - navigator.clipboard.writeText(''); - } - - clipboard() { - if (navigator.clipboard && navigator.clipboard.readText) { - return Scratch.canReadClipboard().then(allowed => { - if (allowed) { - return navigator.clipboard.readText(); - } - return ''; - }); - } - return ''; - } - - getLastPastedText() { - return lastPastedText; - } - } - - Scratch.extensions.register(new Clipboard()); -})(Scratch); +// Name: Clipboard +// ID: clipboard +// Description: Read and write from the system clipboard. + +/*! + * Copyright 2023 tomyo-code + AdamMady + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Clipboard must run unsandboxed"); + } + + const extensionicon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MC40NTQ1NCIgaGVpZ2h0PSI4MC40NTQ1NCIgdmlld0JveD0iMCwwLDgwLjQ1NDU0LDgwLjQ1NDU0Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5Ljc3MjcyLC0xMzkuNzcyNzIpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTE5OS43NzI3MywxODBjMCwtMjIuMjE2OSAxOC4wMTAzNywtNDAuMjI3MjcgNDAuMjI3MjcsLTQwLjIyNzI3YzIyLjIxNjksMCA0MC4yMjcyNywxOC4wMTAzNyA0MC4yMjcyNyw0MC4yMjcyN2MwLDIyLjIxNjkgLTE4LjAxMDM3LDQwLjIyNzI3IC00MC4yMjcyNyw0MC4yMjcyN2MtMjIuMjE2OSwwIC00MC4yMjcyNywtMTguMDEwMzcgLTQwLjIyNzI3LC00MC4yMjcyN3oiIGZpbGw9IiMwMDgwODAiIHN0cm9rZS13aWR0aD0iMCIvPjxpbWFnZSB4PSI0MzQiIHk9IjMwMCIgdHJhbnNmb3JtPSJzY2FsZSgwLjUsMC41KSIgd2lkdGg9Ijk0IiBoZWlnaHQ9IjExOCIgeGxpbms6aHJlZj0iZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFGNEFBQUIyQ0FZQUFBQkJMU1ExQUFBQUFYTlNSMElBcnM0YzZRQUFCckpKUkVGVWVGN3RuVnZJYmtNWXgyZTJuTUlPeVNHbDVCQjJTWExNc1lRTFo5cHNaMEl1aEhKQmlkeTZJaUVrNTFPT0VYZkVqZHk0c0pPemN0Z1hLRGtrNStTdmI3Ny9zMzNmM3U5YTYxbnZtdlhNdk85KzNwdXA5VDd6UERPLythOVpzMmF0bVJWRDVUOEFlN0tJMXpNOWx1bTJUTDlqK2dyVGV4ZlNHT09mTlZjdDFseTRoYkk1ZU9NV0FuQXpROTdLZEN0bEVlUU1XRTNsdjYzTVoycFdyZUlkdktrT1V0ZHlHME5LT3EwNHBJOVAxNFFZNDd2R1ZXa05OMjJsUnF1RGd4OE43V1RIQVBibFB4OHczVHhURVQ2bm4vMm8vSDh6K1Iza3BockZiN0xnQVd6QkpyeUo2UWxNdHh6VXRQOW5mcHFLdTN1U1B3Q1A4dmlsSGZIKzR2OC9NZDJaNllxR2ZPRHhreG4vOVliNGovRzRuSGxEcXkzbGU0SnhuMW5xY0wzaUhUeHN3UVBZbmkwaFYvMjloeloxUS80MzJQSW5OaWp1TXg3ZnB5SC9iengrTVAwa2V3RFg4TGljU1UzZFovby94bmhkUS94ZmVIeTd6UFdYTSs0cHhyODRwUTUrRVRNQWMvRFM5NnpKM05JYnVudVZMWDU2ZytLKzVmRmRHOHFSenNnWTQyRU4rZi9nOGFZNzNPZVkvN3lHL0QvdytJNGpjemhWRk8vZ0Z4VnZEcjVMS2JrRWNCa1ZKeGV4Wlg0QnlCekxMZzBCcFp4SDBjOTc3Q0p1cFAzdFRKdjYrT2VaNzl3R3hiL0c0NmZrcW5DRG4zV2llQWUvcUhoejhITFY3V3JvZjJnZzQrZ3VlMm5RQjZpMFc5b3lBT2pxNHlXN2xFTXVoanQwS0YzeWRmWHhLMm1ZN0VJSVJ6TGRyS09pY29adHJTeEhNbHNZMVRqNFJjVlhCLzVIdHVReFREL3Bram9WM210T0JNRDM5THVUeHY4VU51a0pWWXp4ekQ1NUFUVGRFWXNiK2Y4U0huaVFhV3MramVJZGZIdExqUVkrRFRkampCZjBVWXJXRnNCcHRIMVpveFN0M3dsMklxQzlXSitmQi9ocXpLbzljeldLZC9BOVdpZ24rTWVwa0s1WlExWHhBSnhFdzZ1WW5zRTAxL3g3Vnpua1J1bGhHcVpaMFJqalIxMFpOZjhyN2tlU0c0M2lIYnlHT0cxeWduK1Npa2l6YW4xL0FPUk84SDdtM1oxcExROWg1TDVBWm1mUFlYM2x2cUpYbFIyOEh0ZDhnUWR3Tk92K3BuRWZya2MrMlRMTnBZUVFWbEg1di9aeFdGenhEajQwVGZhcEw2NVQ5ZkVBWkpTd2Z4L0ZWR1I3SnhWL1E1OHkxYUI0QjkvU1lwcmhaQy9GQXppQThkNW4yalc3MTBkUWxyYmZVUEV5Q2xQRkxxWjRCOS81UUdlY1BuN0pVLzk3VkJLcDEwaUdtWHYwR2RlWFZMeThidUhnQy9YeHZlYTlLeFMrUEVGN2lJcVhKMTZ0UlMycGVMbTRPbmhMeFZlb1hOTWlGVk84YVMwckRPYmdDeldLZzNmd2hRZ1VDdXVLbjNmd0FPVFo2ZVhHZGIyTDQrdG5qZVBXTVk1MzhNdmJ3YXlyQVpEZWV3OGhwUGUrRFgvcHJlTVlZM29MdVphZmd5L1VFcGJnWlUyUjlQVldWYjZEaW4vRUtxQW1qb1BYVUJyQnhnejhDR1dmYVpjT3ZsRHo1UVF2NnpNdktsU1htUXJyNEFzMWw0TjM4SVVJRkFwcnBuZ0FCN0tPRTFkY0Y2cC9qckN5dzlOTHZGLzRYZVBVd1dzb3RkdFVELzVGbHYvczRYV3R5b01zUTVYZFF0WnFTcWRkcjZ0NWhhOTFPQW5Bd1M5cEVVdndhZVYyQ01GNlBsNGp3Q0UyZnkrOWRtblhTRG40SWNnWDgxWVBYdmI0emIyejBYQjB3enpJeXZTMDRqekdxRnFwYnFsNEIxK2lqeDhtcXZuTGJhYjQrVU0zckVZT2ZoaS9xWE03K0tuUkRjdG9CbjdKUnFHeVUrdXdrbytYTzYzd0dQdExDZzUrNHdhY08vQ3l3K21WNDRrMWkrY1BxZmhEc25ocmNHS3BlQWRmWWh3UElNMVhoeERPR2xOSkdYeW5mUzFqakx0bDhOWG93bEx4RHI2UTRnOW4zT1BHVkZJRzMxOVE4UzlrOEZXRjRoMThDY1dQcVo1WjlHM1d4ODhpbkRITDdPREhwTnZpMjhIUEFmaFJOL3dzeEdlMHNEa1Y3K0I3TkpNWmVBQlhzMXhWclVWU3NQcUs0L3J6RmJacUV3ZmZqV3Jtd1pkYTlkZU50dDBpdmFJWFk1UXZIUXoxbC9KYkt0N0JqM1RuMm5weEJYQXQ0MTZSUlRKMlRyNms0clBPcWxvcTNzR1hVTHlkUUdjamtwbmlad09IWFNrZHZCM3JaWkVjdklNdlJLQlFXRmY4dklNSGNCRHJlRVNodWtyWVR6a3VmNnRrT2N3VTcrQ1hON01sK0ZvV24zMU14Y3NXdTBXRTcrQ0xZTGVkSkx1dmtybWFkNmo0NHdzeE41K2RkUEFsNW1vQXlGZmhaUkZhS2NISi9IcXY3emJsTHF4bEgrL2dTeWcrdDJKbTNaK1o0bWNkVk83eU8vamNSSlgrSEx3U1ZHNHpCNSticU5LZmcxZUN5bTJXRTN6YW56M0d1Q1ozSWVmUlg4NDl5Ung4RDRYa0JKLzJhd2toSEVybGY5MmpISnVNS1lEVnJHeDZEeW1FMFBwVlQ4MmVaQTVlSVo4eHdFdFkyU3BLdm9HbktFNVZKbkdrMG9qZmJlaC9oU2FPUnZFT3ZwM2sxT0JGd1RMWnBXa3d0NW1ld0xwMHJRVGc0S2VIT0UzTzllRFRON2xEQ0JkTzQ4WHpxQW5JenEzcDYwRUxpbmZ3YW5hREREY0N2NUx1MGpQTEVNS3FRZTQ5ODRZRUJMaDhDVGw5UFdoQjhRNStYTEZNQlA4ZjVqR04yQ3N0cTkwQUFBQUFTVVZPUks1Q1lJST0iIGZpbGw9Im5vbmUiIHN0cm9rZS13aWR0aD0iMC41Ii8+PC9nPjwvZz48L3N2Zz4="; + + let lastPastedText = ""; + + window.addEventListener("copy", (event) => { + Scratch.vm.runtime.startHats("clipboard_whenCopied"); + }); + window.addEventListener("paste", (event) => { + Scratch.vm.runtime.startHats("clipboard_whenPasted"); + const clipboardData = event.clipboardData || window.clipboardData; + const pastedText = clipboardData.getData("Text"); + lastPastedText = pastedText; + }); + + class Clipboard { + getInfo() { + return { + id: "clipboard", + name: "Clipboard", + blockIconURI: extensionicon, + color1: "#008080", + color2: "#006666", + blocks: [ + { + opcode: "whenCopied", + blockType: Scratch.BlockType.EVENT, + text: "when something is copied", + isEdgeActivated: false, + }, + { + opcode: "whenPasted", + blockType: Scratch.BlockType.EVENT, + text: "when something is pasted", + isEdgeActivated: false, + }, + "---", + { + opcode: "setClipboard", + blockType: Scratch.BlockType.COMMAND, + text: "copy to clipboard: [TEXT]", + arguments: { + TEXT: { + type: Scratch.ArgumentType.STRING, + }, + }, + }, + { + opcode: "resetClipboard", + blockType: Scratch.BlockType.COMMAND, + text: "reset clipboard", + }, + "---", + { + opcode: "clipboard", + blockType: Scratch.BlockType.REPORTER, + text: "clipboard", + disableMonitor: true, + }, + { + opcode: "getLastPastedText", + blockType: Scratch.BlockType.REPORTER, + text: "last pasted text", + disableMonitor: true, + }, + ], + }; + } + + setClipboard(args) { + navigator.clipboard.writeText(args.TEXT); + } + + resetClipboard() { + navigator.clipboard.writeText(""); + } + + clipboard() { + if (navigator.clipboard && navigator.clipboard.readText) { + return Scratch.canReadClipboard().then((allowed) => { + if (allowed) { + return navigator.clipboard.readText(); + } + return ""; + }); + } + return ""; + } + + getLastPastedText() { + return lastPastedText; + } + } + + Scratch.extensions.register(new Clipboard()); +})(Scratch); diff --git a/extensions/clouddata-ping.js b/extensions/clouddata-ping.js index 0947f40315..93a130de1e 100644 --- a/extensions/clouddata-ping.js +++ b/extensions/clouddata-ping.js @@ -1,3 +1,8 @@ +// Name: Ping Cloud Data +// ID: clouddataping +// Description: Determine whether a cloud variable server is probably up. +// Original: TheShovel + (function (Scratch) { "use strict"; @@ -17,10 +22,10 @@ * @returns {Promise} */ const pingWebSocket = async (uri) => { - if (!await Scratch.canFetch(uri)) { + if (!(await Scratch.canFetch(uri))) { return { expires: 0, - value: false + value: false, }; } @@ -33,7 +38,7 @@ } catch (e) { return { expires: 0, - value: false + value: false, }; } @@ -60,7 +65,7 @@ return { expires: Date.now() + 60000, - value: isUp + value: isUp, }; }; diff --git a/extensions/cloudlink.js b/extensions/cloudlink.js index 03ac77a836..1583cc1e23 100644 --- a/extensions/cloudlink.js +++ b/extensions/cloudlink.js @@ -1,1712 +1,1838 @@ +// Name: Cloudlink +// ID: cloudlink +// Description: Powerful WebSocket extension for Scratch 3. +// By: MikeDEV + // Copy of S4-0_nosuite.js as of 10/31/2022 /* eslint-disable */ -(function(Scratch) { - -var servers = {}; ; // Server list -let mWS = null; - -// Get the server URL list -try { - Scratch.fetch('https://mikedev101.github.io/cloudlink/serverlist.json').then(response => { - return response.text(); - }).then(data => { - servers = JSON.parse(data); - }).catch(err => { - console.log(err); - servers = {}; - }); -} catch(err) { - console.log(err); - servers = {}; -}; - -function find_id(ID, ulist) { - // Thanks StackOverflow! - if (jsonCheck(ID) && (!intCheck(ID))) { - return ulist.some(o => ((o.username === JSON.parse(ID).username) && (o.id == JSON.parse(ID).id))); - } else { - return ulist.some(o => ((o.username === String(ID)) || (o.id == ID))); - }; -} - -function jsonCheck(JSON_STRING) { - try { - JSON.parse(JSON_STRING); - return true; - } catch (err) { - return false; - } -} - -function intCheck(value) { - return !isNaN(value); -} - -function autoConvert(value) { - // Check if the value is JSON / Dict first - try { - JSON.parse(value); - return JSON.parse(value); - } catch (err) {}; - - // Check if the value is an array - try { - tmp = value; - tmp = tmp.replace(/'/g, '"'); - JSON.parse(tmp); - return JSON.parse(tmp); - } catch (err) {}; - - // Check if an int/float - if (!isNaN(value)) { - return Number(value); - }; - - // Leave as the original value if none of the above work - return value; -} - -class CloudLink { - constructor (runtime, extensionId) { - // Extension stuff - this.runtime = runtime; - this.cl_icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0OCIgaGVpZ2h0PSIyMjUuMzU0OCIgdmlld0JveD0iMCwwLDIyNS4zNTQ4LDIyNS4zNTQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTI3LjMyMjYsLTY3LjMyMjYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0xMjcuMzIyNiwxODBjMCwtNjIuMjMwMDEgNTAuNDQ3MzksLTExMi42Nzc0IDExMi42Nzc0LC0xMTIuNjc3NGM2Mi4yMzAwMSwwIDExMi42Nzc0LDUwLjQ0NzM5IDExMi42Nzc0LDExMi42Nzc0YzAsNjIuMjMwMDEgLTUwLjQ0NzM5LDExMi42Nzc0IC0xMTIuNjc3NCwxMTIuNjc3NGMtNjIuMjMwMDEsMCAtMTEyLjY3NzQsLTUwLjQ0NzM5IC0xMTIuNjc3NCwtMTEyLjY3NzR6IiBmaWxsPSIjMDBjMjhjIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS13aWR0aD0iMCIvPjxnIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLXdpZHRoPSIxIj48cGF0aCBkPSJNMjg2LjEyMDM3LDE1MC41NTc5NWMyMy4yNDA4NiwwIDQyLjA3ODksMTguODM5NDYgNDIuMDc4OSw0Mi4wNzg5YzAsMjMuMjM5NDQgLTE4LjgzODAzLDQyLjA3ODkgLTQyLjA3ODksNDIuMDc4OWgtOTIuMjQwNzRjLTIzLjI0MDg2LDAgLTQyLjA3ODksLTE4LjgzOTQ2IC00Mi4wNzg5LC00Mi4wNzg5YzAsLTIzLjIzOTQ0IDE4LjgzODAzLC00Mi4wNzg5IDQyLjA3ODksLTQyLjA3ODloNC4xODg4N2MxLjgxMTUzLC0yMS41NzA1NSAxOS44OTM1NywtMzguNTEyODkgNDEuOTMxNSwtMzguNTEyODljMjIuMDM3OTMsMCA0MC4xMTk5NywxNi45NDIzNCA0MS45MzE1LDM4LjUxMjg5eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yODkuMDg2NTUsMjEwLjM0MTE0djkuMDQ2NjdoLTI2LjkxNjYzaC05LjA0NjY3di05LjA0NjY3di01NC41MDMzOWg5LjA0NjY3djU0LjUwMzM5eiIgZmlsbD0iIzAwYzI4YyIvPjxwYXRoIGQ9Ik0yMjIuNDA5MjUsMjE5LjM4NzgxYy04LjM1MzIsMCAtMTYuMzY0MzEsLTMuMzE4MzQgLTIyLjI3MDksLTkuMjI0OTJjLTUuOTA2NjEsLTUuOTA2NTggLTkuMjI0OTEsLTEzLjkxNzY4IC05LjIyNDkxLC0yMi4yNzA4OWMwLC04LjM1MzIgMy4zMTgyOSwtMTYuMzY0MzEgOS4yMjQ5MSwtMjIuMjcwOWM1LjkwNjU5LC01LjkwNjYxIDEzLjkxNzcsLTkuMjI0OTEgMjIuMjcwOSwtOS4yMjQ5MWgyMS4xMDg5djguOTM0OThoLTIxLjEwODl2MC4xMDI1N2MtNS45NTYyOCwwIC0xMS42Njg2NCwyLjM2NjE2IC0xNS44ODAzNyw2LjU3Nzg5Yy00LjIxMTczLDQuMjExNzMgLTYuNTc3ODksOS45MjQwOCAtNi41Nzc4OSwxNS44ODAzN2MwLDUuOTU2MjggMi4zNjYxNiwxMS42Njg2NCA2LjU3Nzg5LDE1Ljg4MDM3YzQuMjExNzMsNC4yMTE3MyA5LjkyNDA4LDYuNTc3OTMgMTUuODgwMzcsNi41Nzc5M3YwLjEwMjUzaDIxLjEwODl2OC45MzQ5OHoiIGZpbGw9IiMwMGMyOGMiLz48L2c+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTEyLjY3NzQwNDA4NDA4MzkyOjExMi42Nzc0MDQwODQwODQwMy0tPg=='; - this.cl_block = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzYuMzk4NTQiIGhlaWdodD0iMTIyLjY3MDY5IiB2aWV3Qm94PSIwLDAsMTc2LjM5ODU0LDEyMi42NzA2OSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1MS44MDA3MywtMTE4LjY2NDY2KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PGc+PHBhdGggZD0iTTI4Ni4xMjAzNywxNTcuMTc3NTVjMjMuMjQwODYsMCA0Mi4wNzg5LDE4LjgzOTQ2IDQyLjA3ODksNDIuMDc4OWMwLDIzLjIzOTQ0IC0xOC44MzgwMyw0Mi4wNzg5IC00Mi4wNzg5LDQyLjA3ODloLTkyLjI0MDc0Yy0yMy4yNDA4NiwwIC00Mi4wNzg5LC0xOC44Mzk0NiAtNDIuMDc4OSwtNDIuMDc4OWMwLC0yMy4yMzk0NCAxOC44MzgwMywtNDIuMDc4OSA0Mi4wNzg5LC00Mi4wNzg5aDQuMTg4ODdjMS44MTE1MywtMjEuNTcwNTUgMTkuODkzNTcsLTM4LjUxMjg5IDQxLjkzMTUsLTM4LjUxMjg5YzIyLjAzNzkzLDAgNDAuMTE5OTcsMTYuOTQyMzQgNDEuOTMxNSwzOC41MTI4OXoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjg5LjA4NjU1LDIxNi45NjA3NHY5LjA0NjY3aC0yNi45MTY2M2gtOS4wNDY2N3YtOS4wNDY2N3YtNTQuNTAzMzloOS4wNDY2N3Y1NC41MDMzOXoiIGZpbGw9IiMwMGMyOGMiLz48cGF0aCBkPSJNMjIyLjQwOTI1LDIyNi4wMDc0MWMtOC4zNTMyLDAgLTE2LjM2NDMxLC0zLjMxODM0IC0yMi4yNzA5LC05LjIyNDkyYy01LjkwNjYxLC01LjkwNjU4IC05LjIyNDkxLC0xMy45MTc2OCAtOS4yMjQ5MSwtMjIuMjcwODljMCwtOC4zNTMyIDMuMzE4MjksLTE2LjM2NDMxIDkuMjI0OTEsLTIyLjI3MDljNS45MDY1OSwtNS45MDY2MSAxMy45MTc3LC05LjIyNDkxIDIyLjI3MDksLTkuMjI0OTFoMjEuMTA4OXY4LjkzNDk4aC0yMS4xMDg5djAuMTAyNTdjLTUuOTU2MjgsMCAtMTEuNjY4NjQsMi4zNjYxNiAtMTUuODgwMzcsNi41Nzc4OWMtNC4yMTE3Myw0LjIxMTczIC02LjU3Nzg5LDkuOTI0MDggLTYuNTc3ODksMTUuODgwMzdjMCw1Ljk1NjI4IDIuMzY2MTYsMTEuNjY4NjQgNi41Nzc4OSwxNS44ODAzN2M0LjIxMTczLDQuMjExNzMgOS45MjQwOCw2LjU3NzkzIDE1Ljg4MDM3LDYuNTc3OTN2MC4xMDI1M2gyMS4xMDg5djguOTM0OTh6IiBmaWxsPSIjMDBjMjhjIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg4LjE5OTI2OTk5OTk5OTk4OjYxLjMzNTM0NDk5OTk5OTk5LS0+'; - - // Socket data - this.socketData = { - "gmsg": [], - "pmsg": [], - "direct": [], - "statuscode": [], - "gvar": [], - "pvar": [], - "motd": "", - "client_ip": "", - "ulist": [], - "server_version": "" - }; - this.varData = { - "gvar": {}, - "pvar": {} - }; - - this.queueableCmds = ["gmsg", "pmsg", "gvar", "pvar", "direct", "statuscode"]; - this.varCmds = ["gvar", "pvar"]; - - // Listeners - this.socketListeners = {}; - this.socketListenersData = {}; - this.newSocketData = { - "gmsg": false, - "pmsg": false, - "direct": false, - "statuscode": false, - "gvar": false, - "pvar": false - }; - - // Edge-triggered hat blocks - this.connect_hat = 0; - this.packet_hat = 0; - this.close_hat = 0; - - // Status stuff - this.isRunning = false; - this.isLinked = false; - this.version = "S4.0"; - this.link_status = 0; - this.username = ""; - this.tmp_username = ""; - this.isUsernameSyncing = false; - this.isUsernameSet = false; - this.disconnectWasClean = false; - this.wasConnectionDropped = false; - this.didConnectionFail = false; - this.protocolOk = false; - - // Listeners stuff - this.enableListener = false; - this.setListener = ""; - - // Rooms stuff - this.enableRoom = false; - this.isRoomSetting = false; - this.selectRoom = ""; - - // Remapping stuff - this.menuRemap = { - "Global data": "gmsg", - "Private data": "pmsg", - "Global variables": "gvar", - "Private variables": "pvar", - "Direct data": "direct", - "Status code": "statuscode", - "All data": "all" - }; - } - - getInfo () { - return { - "id": 'cloudlink', - "name": 'CloudLink', - "blockIconURI": this.cl_block, - "menuIconURI": this.cl_icon, - "blocks": [ - { - "opcode": 'returnGlobalData', - "blockType": "reporter", - "text": "Global data" - }, - { - "opcode": 'returnPrivateData', - "blockType": "reporter", - "text": "Private data" - }, - { - "opcode": 'returnDirectData', - "blockType": "reporter", - "text": "Direct Data" - }, - { - "opcode": 'returnLinkData', - "blockType": "reporter", - "text": "Link status" - }, - { - "opcode": 'returnStatusCode', - "blockType": "reporter", - "text": "Status code" - }, - { - "opcode": 'returnUserListData', - "blockType": "reporter", - "text": "Usernames" - }, - { - "opcode": "returnUsernameData", - "blockType": "reporter", - "text": "My username" - }, - { - "opcode": "returnVersionData", - "blockType": "reporter", - "text": "Extension version" - }, - { - "opcode": "returnServerVersion", - "blockType": "reporter", - "text": "Server version" - }, - { - "opcode": "returnServerList", - "blockType": "reporter", - "text": "Server list" - }, - { - "opcode": "returnMOTD", - "blockType": "reporter", - "text": "Server MOTD" - }, - { - "opcode": "returnClientIP", - "blockType": "reporter", - "text": "My IP address" - }, - { - "opcode": 'returnListenerData', - "blockType": "reporter", - "text": "Response for listener [ID]", - "arguments": { - "ID": { - "type": "string", - "defaultValue": "example-listener", - }, - }, - }, - { - "opcode": "readQueueSize", - "blockType": "reporter", - "text": "Size of queue for [TYPE]", - "arguments": { - "TYPE": { - "type": "string", - "menu": "allmenu", - "defaultValue": "All data", - }, - }, - }, - { - "opcode": "readQueueData", - "blockType": "reporter", - "text": "Packet queue for [TYPE]", - "arguments": { - "TYPE": { - "type": "string", - "menu": "allmenu", - "defaultValue": "All data", - }, - }, - }, - { - "opcode": 'returnVarData', - "blockType": "reporter", - "text": "[TYPE] [VAR] data", - "arguments": { - "VAR": { - "type": "string", - "defaultValue": "Apple", - }, - "TYPE": { - "type": "string", - "menu": "varmenu", - "defaultValue": "Global variables", - }, - }, - }, - { - "opcode": 'parseJSON', - "blockType": "reporter", - "text": '[PATH] of [JSON_STRING]', - "arguments": { - "PATH": { - "type": "string", - "defaultValue": 'fruit/apples', - }, - "JSON_STRING": { - "type": "string", - "defaultValue": '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', - }, - }, - }, - { - "opcode": 'getFromJSONArray', - "blockType": "reporter", - "text": 'Get [NUM] from JSON array [ARRAY]', - "arguments": { - "NUM": { - "type": "number", - "defaultValue": 0, - }, - "ARRAY": { - "type": "string", - "defaultValue": '["foo","bar"]', - } - } - }, - { - "opcode": 'fetchURL', - "blockType": "reporter", - "blockAllThreads": "true", - "text": "Fetch data from URL [url]", - "arguments": { - "url": { - "type": "string", - "defaultValue": "https://mikedev101.github.io/cloudlink/fetch_test", - }, - }, - }, - { - "opcode": 'requestURL', - "blockType": "reporter", - "blockAllThreads": "true", - "text": 'Send request with method [method] for URL [url] with data [data] and headers [headers]', - "arguments": { - "method": { - "type": "string", - "defaultValue": 'GET', - }, - "url": { - "type": "string", - "defaultValue": 'https://mikedev101.github.io/cloudlink/fetch_test', - }, - "data": { - "type": "string", - "defaultValue": '{}' - }, - "headers": { - "type": "string", - "defaultValue": '{}' - }, - } - }, - { - "opcode": 'makeJSON', - "blockType": "reporter", - "text": 'Convert [toBeJSONified] to JSON', - "arguments": { - "toBeJSONified": { - "type": "string", - "defaultValue": '{"test": true}', - }, - } - }, - { - "opcode": 'onConnect', - "blockType": "hat", - "text": 'When connected', - "blockAllThreads": "true" - }, - { - "opcode": 'onClose', - "blockType": "hat", - "text": 'When disconnected', - "blockAllThreads": "true" - }, - { - "opcode": 'onListener', - "blockType": "hat", - "text": 'When I receive new packet with listener [ID]', - "blockAllThreads": "true", - "arguments": { - "ID": { - "type": "string", - "defaultValue": "example-listener", - }, - }, - }, - { - "opcode": 'onNewPacket', - "blockType": "hat", - "text": 'When I receive new [TYPE] packet', - "blockAllThreads": "true", - "arguments": { - "TYPE": { - "type": "string", - "menu": "almostallmenu", - "defaultValue": 'Global data' - }, - }, - }, - { - "opcode": 'onNewVar', - "blockType": "hat", - "text": 'When I receive new [TYPE] data for [VAR]', - "blockAllThreads": "true", - "arguments": { - "TYPE": { - "type": "string", - "menu": "varmenu", - "defaultValue": 'Global variables', - }, - "VAR": { - "type": "string", - "defaultValue": 'Apple', - }, - }, - }, - { - "opcode": 'getComState', - "blockType": "Boolean", - "text": 'Connected?', - }, - { - "opcode": 'getRoomState', - "blockType": "Boolean", - "text": 'Linked to rooms?', - }, - { - "opcode": 'getComLostConnectionState', - "blockType": "Boolean", - "text": 'Lost connection?', - }, - { - "opcode": 'getComFailedConnectionState', - "blockType": "Boolean", - "text": 'Failed to connnect?', - }, - { - "opcode": 'getUsernameState', - "blockType": "Boolean", - "text": 'Username synced?', - }, - { - "opcode": 'returnIsNewData', - "blockType": "Boolean", - "text": 'Got New [TYPE]?', - "arguments": { - "TYPE": { - "type": "string", - "menu": "datamenu", - "defaultValue": 'Global data', - }, - }, - }, - { - "opcode": 'returnIsNewVarData', - "blockType": "Boolean", - "text": 'Got New [TYPE] data for variable [VAR]?', - "arguments": { - "TYPE": { - "type": "string", - "menu": "varmenu", - "defaultValue": 'Global variables', - }, - "VAR": { - "type": "string", - "defaultValue": 'Apple', - }, - }, - }, - { - "opcode": 'returnIsNewListener', - "blockType": "Boolean", - "text": 'Got new packet with listener [ID]?', - "blockAllThreads": "true", - "arguments": { - "ID": { - "type": "string", - "defaultValue": "example-listener", - }, - }, - }, - { - "opcode": 'checkForID', - "blockType": "Boolean", - "text": 'ID [ID] connected?', - "arguments": { - "ID": { - "type": "string", - "defaultValue": 'Another name', - }, - }, - }, - { - "opcode": 'isValidJSON', - "blockType": "Boolean", - "text": 'Is [JSON_STRING] valid JSON?', - "arguments": { - "JSON_STRING": { - "type": "string", - "defaultValue": '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', - }, - }, - }, - { - "opcode": 'openSocket', - "blockType": "command", - "text": 'Connect to [IP]', - "blockAllThreads": "true", - "arguments": { - "IP": { - "type": "string", - "defaultValue": 'ws://127.0.0.1:3000/', - }, - }, - }, - { - "opcode": 'openSocketPublicServers', - "blockType": "command", - "text": 'Connect to server [ID]', - "blockAllThreads": "true", - "arguments": { - "ID": { - "type": "number", - "defaultValue": '', - }, - }, - }, - { - "opcode": 'closeSocket', - "blockType": "command", - "blockAllThreads": "true", - "text": 'Disconnect', - }, - { - "opcode": 'setMyName', - "blockType": "command", - "text": 'Set [NAME] as username', - "blockAllThreads": "true", - "arguments": { - "NAME": { - "type": "string", - "defaultValue": "A name", - }, - }, - }, - { - "opcode": 'createListener', - "blockType": "command", - "text": 'Attach listener [ID] to next packet', - "blockAllThreads": "true", - "arguments": { - "ID": { - "type": "string", - "defaultValue": "example-listener", - }, - }, - }, - { - "opcode": 'linkToRooms', - "blockType": "command", - "text": 'Link to room(s) [ROOMS]', - "blockAllThreads": "true", - "arguments": { - "ROOMS": { - "type": "string", - "defaultValue": '["test"]', - }, - } - }, - { - "opcode": 'selectRoomsInNextPacket', - "blockType": "command", - "text": 'Select room(s) [ROOMS] for next packet', - "blockAllThreads": "true", - "arguments": { - "ROOMS": { - "type": "string", - "defaultValue": '["test"]', - }, - }, - }, - { - "opcode": 'unlinkFromRooms', - "blockType": "command", - "text": 'Unlink from all rooms', - "blockAllThreads": "true" - }, - { - "opcode": 'sendGData', - "blockType": "command", - "text": 'Send [DATA]', - "blockAllThreads": "true", - "arguments": { - "DATA": { - "type": "string", - "defaultValue": 'Apple' - } - } - }, - { - "opcode": 'sendPData', - "blockType": "command", - "text": 'Send [DATA] to [ID]', - "blockAllThreads": "true", - "arguments": { - "DATA": { - "type": "string", - "defaultValue": 'Apple' - }, - "ID": { - "type": "string", - "defaultValue": 'Another name' - } - } - }, - { - "opcode": 'sendGDataAsVar', - "blockType": "command", - "text": 'Send variable [VAR] with data [DATA]', - "blockAllThreads": "true", - "arguments": { - "DATA": { - "type": "string", - "defaultValue": 'Banana' - }, - "VAR": { - "type": "string", - "defaultValue": 'Apple' - } - } - }, - { - "opcode": 'sendPDataAsVar', - "blockType": "command", - "text": 'Send variable [VAR] to [ID] with data [DATA]', - "blockAllThreads": "true", - "arguments": { - "DATA": { - "type": "string", - "defaultValue": 'Banana' - }, - "ID": { - "type": "string", - "defaultValue": 'Another name' - }, - "VAR": { - "type": "string", - "defaultValue": 'Apple' - } - } - }, - { - "opcode": 'runCMDnoID', - "blockType": "command", - "text": 'Send command without ID [CMD] [DATA]', - "blockAllThreads": "true", - "arguments": { - "CMD": { - "type": "string", - "defaultValue": 'direct' - }, - "DATA": { - "type": "string", - "defaultValue": 'val' - } - } - }, - { - "opcode": 'runCMD', - "blockType": "command", - "text": 'Send command [CMD] [ID] [DATA]', - "blockAllThreads": "true", - "arguments": { - "CMD": { - "type": "string", - "defaultValue": 'direct' - }, - "ID": { - "type": "string", - "defaultValue": 'id' - }, - "DATA": { - "type": "string", - "defaultValue": 'val' - } - } - }, - { - "opcode": 'resetNewData', - "blockType": "command", - "text": 'Reset got new [TYPE] status', - "blockAllThreads": "true", - "arguments": { - "TYPE": { - "type": "string", - "menu": "datamenu", - "defaultValue": 'Global data' - } - } - }, - { - "opcode": 'resetNewVarData', - "blockType": "command", - "text": 'Reset got new [TYPE] [VAR] status', - "blockAllThreads": "true", - "arguments": { - "TYPE": { - "type": "string", - "menu": "varmenu", - "defaultValue": 'Global variables' - }, - "VAR": { - "type": "string", - "defaultValue": 'Apple' - } - } - }, - { - "opcode": 'resetNewListener', - "blockType": "command", - "text": 'Reset got new [ID] listener status', - "blockAllThreads": "true", - "arguments": { - "ID": { - "type": "string", - "defaultValue": 'example-listener' - } - } - }, - { - "opcode": 'clearAllPackets', - "blockType": "command", - "text": "Clear all packets for [TYPE]", - "arguments": { - "TYPE": { - "type": "string", - "menu": "allmenu", - "defaultValue": "All data" - }, - }, - } - ], - "menus": { - "coms": { - "items": ["Connected", "Username synced"] - }, - "datamenu": { - "items": ['Global data', 'Private data', 'Direct data', 'Status code'] - }, - "varmenu": { - "items": ['Global variables', 'Private variables'] - }, - "allmenu": { - "items": ['Global data', 'Private data', 'Direct data', 'Status code', "Global variables", "Private variables", "All data"] - }, - "almostallmenu": { - "items": ['Global data', 'Private data', 'Direct data', 'Status code', "Global variables", "Private variables"] - }, - }, - }; - }; - - // Code for blocks go here - - returnGlobalData() { - if (this.socketData.gmsg.length != 0) { - - let data = (this.socketData.gmsg[this.socketData.gmsg.length - 1].val); - - if (typeof(data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - } else { - return ""; - }; - }; - - returnPrivateData() { - if (this.socketData.pmsg.length != 0) { - let data = (this.socketData.pmsg[this.socketData.pmsg.length - 1].val); - - if (typeof (data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - } else { - return ""; - }; - }; - - returnDirectData() { - if (this.socketData.direct.length != 0) { - let data = (this.socketData.direct[this.socketData.direct.length - 1].val); - - if (typeof (data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - } else { - return ""; - }; - }; - - returnLinkData() { - return String(this.link_status); - }; - - returnStatusCode() { - if (this.socketData.statuscode.length != 0) { - let data = (this.socketData.statuscode[this.socketData.statuscode.length - 1].code); - - if (typeof (data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - } else { - return ""; - }; - }; - - returnUserListData() { - return JSON.stringify(this.socketData.ulist); - }; - - returnUsernameData() { - let data = this.username; - - if (typeof (data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - }; - - returnVersionData() { - return String(this.version); - }; - - returnServerVersion() { - return String(this.socketData.server_version); - }; - - returnServerList() { - return JSON.stringify(servers); - }; - - returnMOTD() { - return String(this.socketData.motd); - }; - - returnClientIP() { - return String(this.socketData.client_ip); - }; - - returnListenerData({ID}) { - const self = this; - if ((this.isRunning) && (this.socketListeners.hasOwnProperty(String(ID)))) { - return JSON.stringify(this.socketListenersData[ID]); - } else { - return "{}"; - }; - }; - - readQueueSize({TYPE}) { - if (this.menuRemap[String(TYPE)] == "all") { - let tmp_size = 0; - tmp_size = tmp_size + this.socketData.gmsg.length; - tmp_size = tmp_size + this.socketData.pmsg.length; - tmp_size = tmp_size + this.socketData.direct.length; - tmp_size = tmp_size + this.socketData.statuscode.length; - tmp_size = tmp_size + this.socketData.gvar.length; - tmp_size = tmp_size + this.socketData.pvar.length; - return tmp_size; - } else { - return this.socketData[this.menuRemap[String(TYPE)]].length; - }; - }; - - readQueueData({TYPE}) { - if (this.menuRemap[String(TYPE)] == "all") { - let tmp_socketData = JSON.parse(JSON.stringify(this.socketData)); // Deep copy - - delete tmp_socketData.motd; - delete tmp_socketData.client_ip; - delete tmp_socketData.ulist; - delete tmp_socketData.server_version; - - return JSON.stringify(tmp_socketData); - } else { - return JSON.stringify(this.socketData[this.menuRemap[String(TYPE)]]); - }; - }; - - returnVarData({ TYPE, VAR }) { - if (this.isRunning) { - if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { - if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { - return this.varData[this.menuRemap[TYPE]][VAR].value; - } else { - return ""; - }; - } else { - return ""; - }; - } else { - return ""; - }; - }; - - parseJSON({PATH, JSON_STRING}) { - try { - const path = PATH.toString().split('/').map(prop => decodeURIComponent(prop)); - if (path[0] === '') path.splice(0, 1); - if (path[path.length - 1] === '') path.splice(-1, 1); - let json; - try { - json = JSON.parse(' ' + JSON_STRING); - } catch (e) { - return e.message; - }; - path.forEach(prop => json = json[prop]); - if (json === null) return 'null'; - else if (json === undefined) return ''; - else if (typeof json === 'object') return JSON.stringify(json); - else return json.toString(); - } catch (err) { - return ''; - }; - }; - - getFromJSONArray({NUM, ARRAY}) { - var json_array = JSON.parse(ARRAY); - if (json_array[NUM] == "undefined") { - return ""; - } else { - let data = json_array[NUM]; - - if (typeof (data) == "object") { - data = JSON.stringify(data); // Make the JSON safe for Scratch - } - - return data; - } - }; - - fetchURL(args) { - return Scratch.fetch(args.url, { - method: "GET" - }).then(response => response.text()); - }; - - requestURL(args) { - if (args.method == "GET" || args.method == "HEAD") { - return Scratch.fetch(args.url, { - method: args.method, - headers: JSON.parse(args.headers) - }).then(response => response.text()); - } else { - return Scratch.fetch(args.url, { - method: args.method, - headers: JSON.parse(args.headers), - body: JSON.parse(args.data) - }).then(response => response.text()); - } - }; - - isValidJSON({JSON_STRING}) { - return jsonCheck(JSON_STRING); - }; - - makeJSON({toBeJSONified}) { - if (typeof(toBeJSONified) == "string") { - try { - JSON.parse(toBeJSONified); - return String(toBeJSONified); - } catch(err) { - return "Not JSON!"; - } - } else if (typeof(toBeJSONified) == "object") { - return JSON.stringify(toBeJSONified); - } else { - return "Not JSON!"; - }; - }; - - onConnect() { - const self = this; - if (self.connect_hat == 0 && self.isRunning && self.protocolOk) { - self.connect_hat = 1; - return true; - } else { - return false; - }; - }; - - onClose() { - const self = this; - if (self.close_hat == 0 && !self.isRunning) { - self.close_hat = 1; - return true; - } else { - return false; - }; - }; - - onListener({ ID }) { - const self = this; - if ((this.isRunning) && (this.socketListeners.hasOwnProperty(String(ID)))) { - if (self.socketListeners[String(ID)]) { - self.socketListeners[String(ID)] = false; - return true; - } else { - return false; - }; - } else { - return false; - }; - }; - - onNewPacket({ TYPE }) { - const self = this; - if ((this.isRunning) && (this.newSocketData[this.menuRemap[String(TYPE)]])) { - self.newSocketData[this.menuRemap[String(TYPE)]] = false; - return true; - } else { - return false; - }; - }; - - onNewVar({ TYPE, VAR }) { - const self = this; - if (this.isRunning) { - if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { - if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { - if (this.varData[this.menuRemap[TYPE]][VAR].isNew) { - self.varData[this.menuRemap[TYPE]][VAR].isNew = false; - return true; - } else { - return false; - } - } else { - return false; - }; - } else { - return false; - }; - } else { - return false; - }; - }; - - getComState(){ - return String((this.link_status == 2) || this.protocolOk); - }; - - getRoomState() { - return this.isLinked; - }; - - getComLostConnectionState() { - return this.wasConnectionDropped; - }; - - getComFailedConnectionState() { - return this.didConnectionFail; - }; - - getUsernameState(){ - return this.isUsernameSet; - }; - - returnIsNewData({TYPE}){ - if (this.isRunning) { - return this.newSocketData[this.menuRemap[String(TYPE)]]; - } else { - return false; - }; - }; - - returnIsNewVarData({ TYPE, VAR }) { - if (this.isRunning) { - if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { - if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { - return this.varData[this.menuRemap[TYPE]][VAR].isNew; - } else { - return false; - }; - } else { - return false; - }; - } else { - return false; - }; - }; - - returnIsNewListener({ ID }) { - if (this.isRunning) { - if (this.socketListeners.hasOwnProperty(String(ID))) { - return this.socketListeners[ID]; - } else { - return false; - }; - } else { - return false; - }; - }; - - checkForID({ ID }) { - return find_id(ID, this.socketData.ulist); - }; - - async openSocket({IP}) { - const self = this; - if (!self.isRunning) { - if (!await Scratch.canFetch(IP)) { - return; - } - - console.log("Starting socket."); - self.link_status = 1; - - self.disconnectWasClean = false; - self.wasConnectionDropped = false; - self.didConnectionFail = false; - - mWS = new WebSocket(String(IP)); - - mWS.onerror = function(){ - self.isRunning = false; - }; - - mWS.onopen = function(){ - self.isRunning = true; - self.packet_queue = {}; - self.link_status = 2; - - // Send the handshake request to get server to detect client protocol - mWS.send(JSON.stringify({"cmd": "handshake", "listener": "setprotocol"})) - - console.log("Successfully opened socket."); - }; - - mWS.onmessage = function(event){ - let tmp_socketData = JSON.parse(event.data); - console.log("RX:", tmp_socketData); - - if (self.queueableCmds.includes(tmp_socketData.cmd)) { - self.socketData[tmp_socketData.cmd].push(tmp_socketData); - } else { - if (tmp_socketData.cmd == "ulist") { - // ulist functionality has been changed in server 0.1.9 - if (tmp_socketData.hasOwnProperty("mode")) { - if (tmp_socketData.mode == "set") { - self.socketData["ulist"] = tmp_socketData.val; - } else if (tmp_socketData.mode == "add") { - if (!self.socketData.ulist.some(o => ((o.username === tmp_socketData.val.username) && (o.id == tmp_socketData.val.id)))) { - self.socketData["ulist"].push(tmp_socketData.val); - } else { - console.log("Could not perform ulist method add, client", tmp_socketData.val, "already exists"); - }; - } else if (tmp_socketData.mode == "remove") { - if (self.socketData.ulist.some(o => ((o.username === tmp_socketData.val.username) && (o.id == tmp_socketData.val.id)))) { - // This is by far the fugliest thing I have ever written in JS, or in any programming language... thanks I hate it - self.socketData["ulist"] = self.socketData["ulist"].filter(user => ((!(user.username === tmp_socketData.val.username)) && (!(user.id == tmp_socketData.val.id)))); - } else { - console.log("Could not perform ulist method remove, client", tmp_socketData.val, "was not found"); - }; - } else { - console.log("Could not understand ulist method:", tmp_socketData.mode); - }; - } else { - // Retain compatibility wtih existing servers - self.socketData["ulist"] = tmp_socketData.val; - }; - } else { - self.socketData[tmp_socketData.cmd] = tmp_socketData.val; - }; - }; - - if (self.newSocketData.hasOwnProperty(tmp_socketData.cmd)) { - self.newSocketData[tmp_socketData.cmd] = true; - }; - - if (self.varCmds.includes(tmp_socketData.cmd)) { - self.varData[tmp_socketData.cmd][tmp_socketData.name] = { - "value": tmp_socketData.val, - "isNew": true - }; - }; - if (tmp_socketData.hasOwnProperty("listener")) { - if (tmp_socketData.listener == "setusername") { - self.socketListeners["setusername"] = true; - if (tmp_socketData.code == "I:100 | OK") { - self.username = tmp_socketData.val; - self.isUsernameSyncing = false; - self.isUsernameSet = true; - console.log("Username was accepted by the server, and has been set to:", self.username); - } else { - console.warn("Username was rejected by the server. Error code:", String(tmp_socketData.code)); - self.isUsernameSyncing = false; - }; - } else if (tmp_socketData.listener == "roomLink") { - self.isRoomSetting = false; - self.socketListeners["roomLink"] = true; - if (tmp_socketData.code == "I:100 | OK") { - console.log("Linking to room(s) was accepted by the server!"); - self.isLinked = true; - } else { - console.warn("Linking to room(s) was rejected by the server. Error code:", String(tmp_socketData.code)); - self.enableRoom = false; - self.isLinked = false; - self.selectRoom = ""; - }; - } else if ((tmp_socketData.listener == "setprotocol") && (!this.protocolOk)) { - console.log("Server successfully set client protocol to cloudlink!"); - self.socketData.statuscode = []; - self.protocolOk = true; - self.socketListeners["setprotocol"] = true; - } else { - if (self.socketListeners.hasOwnProperty(tmp_socketData.listener)) { - self.socketListeners[tmp_socketData.listener] = true; - }; - }; - self.socketListenersData[tmp_socketData.listener] = tmp_socketData; - }; - self.packet_hat = 0; - }; - - mWS.onclose = function() { - self.isRunning = false; - self.connect_hat = 0; - self.packet_hat = 0; - self.protocolOk = false; - if (self.close_hat == 1) { - self.close_hat = 0; - }; - self.socketData = { - "gmsg": [], - "pmsg": [], - "direct": [], - "statuscode": [], - "gvar": [], - "pvar": [], - "motd": "", - "client_ip": "", - "ulist": [], - "server_version": "" - }; - self.newSocketData = { - "gmsg": false, - "pmsg": false, - "direct": false, - "statuscode": false, - "gvar": false, - "pvar": false - }; - self.socketListeners = {}; - self.username = ""; - self.tmp_username = ""; - self.isUsernameSyncing = false; - self.isUsernameSet = false; - self.enableListener = false; - self.setListener = ""; - self.enableRoom = false; - self.selectRoom = ""; - self.isLinked = false; - self.isRoomSetting = false; - - if (self.link_status != 1) { - if (self.disconnectWasClean) { - self.link_status = 3; - console.log("Socket closed."); - self.wasConnectionDropped = false; - self.didConnectionFail = false; - } else { - self.link_status = 4; - console.error("Lost connection to the server."); - self.wasConnectionDropped = true; - self.didConnectionFail = false; - }; - } else { - self.link_status = 4; - console.error("Failed to connect to server."); - self.wasConnectionDropped = false; - self.didConnectionFail = true; - }; - }; - } else { - console.warn("Socket is already open."); - }; - } - - openSocketPublicServers({ ID }){ - if (servers.hasOwnProperty(ID)) { - console.log("Connecting to:", servers[ID].url) - this.openSocket({"IP": servers[ID].url}); - }; - }; - - closeSocket(){ - const self = this; - if (this.isRunning) { - console.log("Closing socket..."); - mWS.close(1000,'script closure'); - self.disconnectWasClean = true; - } else { - console.warn("Socket is not open."); - }; - } - - setMyName({NAME}) { - const self = this; - if (this.isRunning) { - if (!this.isUsernameSyncing) { - if (!this.isUsernameSet){ - if (String(NAME) != "") { - if ((!(String(NAME).length > 20))) { - if (!(String(NAME) == "%CA%" || String(NAME) == "%CC%" || String(NAME) == "%CD%" || String(NAME) == "%MS%")){ - let tmp_msg = { - cmd: "setid", - val: String(NAME), - listener: "setusername" - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - self.tmp_username = String(NAME); - self.isUsernameSyncing = true; - - } else { - console.log("Blocking attempt to use reserved usernames"); - }; - } else { - console.log("Blocking attempt to use username larger than 20 characters, username is " + String(NAME).length + " characters long"); - }; - } else { - console.log("Blocking attempt to use blank username"); - }; - } else { - console.warn("Username already has been set!"); - }; - } else { - console.warn("Username is still syncing!"); - }; - }; - }; - - createListener({ ID }) { - self = this; - if (this.isRunning) { - if (!this.enableListener) { - self.enableListener = true; - self.setListener = String(ID); - } else { - console.warn("Listeners were already created!"); - }; - } else { - console.log("Cannot assign a listener to a packet while disconnected"); - }; - }; - - linkToRooms({ ROOMS }) { - const self = this; - - if (this.isRunning) { - if (!this.isRoomSetting) { - if (!(String(ROOMS).length > 1000)) { - let tmp_msg = { - cmd: "link", - val: autoConvert(ROOMS), - listener: "roomLink" - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - self.isRoomSetting = true; - - } else { - console.warn("Blocking attempt to send a room ID / room list larger than 1000 bytes (1 KB), room ID / room list is " + String(ROOMS).length + " bytes"); - }; - } else { - console.warn("Still linking to rooms!"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - selectRoomsInNextPacket({ROOMS}) { - const self = this; - if (this.isRunning) { - if (this.isLinked) { - if (!this.enableRoom) { - if (!(String(ROOMS).length > 1000)) { - self.enableRoom = true; - self.selectRoom = ROOMS; - } else { - console.warn("Blocking attempt to select a room ID / room list larger than 1000 bytes (1 KB), room ID / room list is " + String(ROOMS).length + " bytes"); - }; - } else { - console.warn("Rooms were already selected!"); - }; - } else { - console.warn("Not linked to any room(s)!"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - unlinkFromRooms() { - const self = this; - if (this.isRunning) { - if (this.isLinked) { - let tmp_msg = { - cmd: "unlink", - val: "" - }; - - if (this.enableListener) { - tmp_msg["listener"] = autoConvert(this.setListener); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - - self.isLinked = false; - } else { - console.warn("Not linked to any rooms!"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - sendGData({DATA}){ - const self = this; - if (this.isRunning) { - if (!(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: "gmsg", - val: autoConvert(DATA) - }; - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - - if (this.enableRoom) { - tmp_msg["rooms"] = autoConvert(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + String(DATA).length + " bytes"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - sendPData({DATA, ID}) { - const self = this; - if (this.isRunning) { - if (!(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: "pmsg", - val: autoConvert(DATA), - id: autoConvert(ID) - } - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - if (this.enableRoom) { - tmp_msg["rooms"] = autoConvert(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + String(DATA).length + " bytes"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - sendGDataAsVar({VAR, DATA }) { - const self = this; - if (this.isRunning) { - if (!(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: "gvar", - name: VAR, - val: autoConvert(DATA) - } - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - if (this.enableRoom) { - tmp_msg["rooms"] = autoConvert(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + String(DATA).length + " bytes"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - sendPDataAsVar({VAR, ID, DATA}) { - const self = this; - if (this.isRunning) { - if (!(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: "pvar", - name: VAR, - val: autoConvert(DATA), - id: autoConvert(ID) - } - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - if (this.enableRoom) { - tmp_msg["rooms"] = autoConvert(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + String(DATA).length + " bytes"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - runCMDnoID({CMD, DATA}) { - const self = this; - if (this.isRunning) { - if (!(String(CMD).length > 100) || !(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: String(CMD), - val: autoConvert(DATA) - } - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - if (this.enableRoom) { - tmp_msg["rooms"] = String(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet with questionably long arguments"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - runCMD({CMD, ID, DATA}) { - const self = this; - if (this.isRunning) { - if (!(String(CMD).length > 100) || !(String(ID).length > 20) || !(String(DATA).length > 1000)) { - let tmp_msg = { - cmd: String(CMD), - id: autoConvert(ID), - val: autoConvert(DATA) - } - - if (this.enableListener) { - tmp_msg["listener"] = String(this.setListener); - }; - if (this.enableRoom) { - tmp_msg["rooms"] = String(this.selectRoom); - }; - - console.log("TX:", tmp_msg); - mWS.send(JSON.stringify(tmp_msg)); - - if (this.enableListener) { - if (!self.socketListeners.hasOwnProperty(this.setListener)) { - self.socketListeners[this.setListener] = false; - }; - self.enableListener = false; - }; - if (this.enableRoom) { - self.enableRoom = false; - self.selectRoom = ""; - }; - - } else { - console.warn("Blocking attempt to send packet with questionably long arguments"); - }; - } else { - console.warn("Socket is not open."); - }; - }; - - resetNewData({TYPE}){ - const self = this; - if (this.isRunning) { - self.newSocketData[this.menuRemap[String(TYPE)]] = false; - }; - }; - - resetNewVarData({ TYPE, VAR }) { - const self = this; - if (this.isRunning) { - if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { - if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { - self.varData[this.menuRemap[TYPE]][VAR].isNew = false; - }; - }; - }; - }; - - resetNewListener({ ID }) { - const self = this; - if (this.isRunning) { - if (this.socketListeners.hasOwnProperty(String(ID))) { - self.socketListeners[String(ID)] = false; - }; - }; - }; - - clearAllPackets({TYPE}){ - const self = this; - if (this.menuRemap[String(TYPE)] == "all") { - self.socketData.gmsg = []; - self.socketData.pmsg = []; - self.socketData.direct = []; - self.socketData.statuscode = []; - self.socketData.gvar = []; - self.socketData.pvar = []; - } else { - self.socketData[this.menuRemap[String(TYPE)]] = []; - }; - }; -}; - -console.log("CloudLink 4.0 loaded. Detecting unsandboxed mode."); -Scratch.extensions.register(new CloudLink(Scratch.vm.runtime)); - +(function (Scratch) { + var servers = {}; // Server list + let mWS = null; + + // Get the server URL list + try { + Scratch.fetch("https://mikedev101.github.io/cloudlink/serverlist.json") + .then((response) => { + return response.text(); + }) + .then((data) => { + servers = JSON.parse(data); + }) + .catch((err) => { + console.log(err); + servers = {}; + }); + } catch (err) { + console.log(err); + servers = {}; + } + + function find_id(ID, ulist) { + // Thanks StackOverflow! + if (jsonCheck(ID) && !intCheck(ID)) { + return ulist.some( + (o) => + o.username === JSON.parse(ID).username && o.id == JSON.parse(ID).id + ); + } else { + return ulist.some((o) => o.username === String(ID) || o.id == ID); + } + } + + function jsonCheck(JSON_STRING) { + try { + JSON.parse(JSON_STRING); + return true; + } catch (err) { + return false; + } + } + + function intCheck(value) { + return !isNaN(value); + } + + function autoConvert(value) { + // Check if the value is JSON / Dict first + try { + JSON.parse(value); + return JSON.parse(value); + } catch (err) {} + + // Check if the value is an array + try { + tmp = value; + tmp = tmp.replace(/'/g, '"'); + JSON.parse(tmp); + return JSON.parse(tmp); + } catch (err) {} + + // Check if an int/float + if (!isNaN(value)) { + return Number(value); + } + + // Leave as the original value if none of the above work + return value; + } + + class CloudLink { + constructor(runtime, extensionId) { + // Extension stuff + this.runtime = runtime; + this.cl_icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIyMjUuMzU0OCIgaGVpZ2h0PSIyMjUuMzU0OCIgdmlld0JveD0iMCwwLDIyNS4zNTQ4LDIyNS4zNTQ4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTI3LjMyMjYsLTY3LjMyMjYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0xMjcuMzIyNiwxODBjMCwtNjIuMjMwMDEgNTAuNDQ3MzksLTExMi42Nzc0IDExMi42Nzc0LC0xMTIuNjc3NGM2Mi4yMzAwMSwwIDExMi42Nzc0LDUwLjQ0NzM5IDExMi42Nzc0LDExMi42Nzc0YzAsNjIuMjMwMDEgLTUwLjQ0NzM5LDExMi42Nzc0IC0xMTIuNjc3NCwxMTIuNjc3NGMtNjIuMjMwMDEsMCAtMTEyLjY3NzQsLTUwLjQ0NzM5IC0xMTIuNjc3NCwtMTEyLjY3NzR6IiBmaWxsPSIjMDBjMjhjIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZS13aWR0aD0iMCIvPjxnIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLXdpZHRoPSIxIj48cGF0aCBkPSJNMjg2LjEyMDM3LDE1MC41NTc5NWMyMy4yNDA4NiwwIDQyLjA3ODksMTguODM5NDYgNDIuMDc4OSw0Mi4wNzg5YzAsMjMuMjM5NDQgLTE4LjgzODAzLDQyLjA3ODkgLTQyLjA3ODksNDIuMDc4OWgtOTIuMjQwNzRjLTIzLjI0MDg2LDAgLTQyLjA3ODksLTE4LjgzOTQ2IC00Mi4wNzg5LC00Mi4wNzg5YzAsLTIzLjIzOTQ0IDE4LjgzODAzLC00Mi4wNzg5IDQyLjA3ODksLTQyLjA3ODloNC4xODg4N2MxLjgxMTUzLC0yMS41NzA1NSAxOS44OTM1NywtMzguNTEyODkgNDEuOTMxNSwtMzguNTEyODljMjIuMDM3OTMsMCA0MC4xMTk5NywxNi45NDIzNCA0MS45MzE1LDM4LjUxMjg5eiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0yODkuMDg2NTUsMjEwLjM0MTE0djkuMDQ2NjdoLTI2LjkxNjYzaC05LjA0NjY3di05LjA0NjY3di01NC41MDMzOWg5LjA0NjY3djU0LjUwMzM5eiIgZmlsbD0iIzAwYzI4YyIvPjxwYXRoIGQ9Ik0yMjIuNDA5MjUsMjE5LjM4NzgxYy04LjM1MzIsMCAtMTYuMzY0MzEsLTMuMzE4MzQgLTIyLjI3MDksLTkuMjI0OTJjLTUuOTA2NjEsLTUuOTA2NTggLTkuMjI0OTEsLTEzLjkxNzY4IC05LjIyNDkxLC0yMi4yNzA4OWMwLC04LjM1MzIgMy4zMTgyOSwtMTYuMzY0MzEgOS4yMjQ5MSwtMjIuMjcwOWM1LjkwNjU5LC01LjkwNjYxIDEzLjkxNzcsLTkuMjI0OTEgMjIuMjcwOSwtOS4yMjQ5MWgyMS4xMDg5djguOTM0OThoLTIxLjEwODl2MC4xMDI1N2MtNS45NTYyOCwwIC0xMS42Njg2NCwyLjM2NjE2IC0xNS44ODAzNyw2LjU3Nzg5Yy00LjIxMTczLDQuMjExNzMgLTYuNTc3ODksOS45MjQwOCAtNi41Nzc4OSwxNS44ODAzN2MwLDUuOTU2MjggMi4zNjYxNiwxMS42Njg2NCA2LjU3Nzg5LDE1Ljg4MDM3YzQuMjExNzMsNC4yMTE3MyA5LjkyNDA4LDYuNTc3OTMgMTUuODgwMzcsNi41Nzc5M3YwLjEwMjUzaDIxLjEwODl2OC45MzQ5OHoiIGZpbGw9IiMwMGMyOGMiLz48L2c+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTEyLjY3NzQwNDA4NDA4MzkyOjExMi42Nzc0MDQwODQwODQwMy0tPg=="; + this.cl_block = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNzYuMzk4NTQiIGhlaWdodD0iMTIyLjY3MDY5IiB2aWV3Qm94PSIwLDAsMTc2LjM5ODU0LDEyMi42NzA2OSI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1MS44MDA3MywtMTE4LjY2NDY2KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PGc+PHBhdGggZD0iTTI4Ni4xMjAzNywxNTcuMTc3NTVjMjMuMjQwODYsMCA0Mi4wNzg5LDE4LjgzOTQ2IDQyLjA3ODksNDIuMDc4OWMwLDIzLjIzOTQ0IC0xOC44MzgwMyw0Mi4wNzg5IC00Mi4wNzg5LDQyLjA3ODloLTkyLjI0MDc0Yy0yMy4yNDA4NiwwIC00Mi4wNzg5LC0xOC44Mzk0NiAtNDIuMDc4OSwtNDIuMDc4OWMwLC0yMy4yMzk0NCAxOC44MzgwMywtNDIuMDc4OSA0Mi4wNzg5LC00Mi4wNzg5aDQuMTg4ODdjMS44MTE1MywtMjEuNTcwNTUgMTkuODkzNTcsLTM4LjUxMjg5IDQxLjkzMTUsLTM4LjUxMjg5YzIyLjAzNzkzLDAgNDAuMTE5OTcsMTYuOTQyMzQgNDEuOTMxNSwzOC41MTI4OXoiIGZpbGw9IiNmZmZmZmYiLz48cGF0aCBkPSJNMjg5LjA4NjU1LDIxNi45NjA3NHY5LjA0NjY3aC0yNi45MTY2M2gtOS4wNDY2N3YtOS4wNDY2N3YtNTQuNTAzMzloOS4wNDY2N3Y1NC41MDMzOXoiIGZpbGw9IiMwMGMyOGMiLz48cGF0aCBkPSJNMjIyLjQwOTI1LDIyNi4wMDc0MWMtOC4zNTMyLDAgLTE2LjM2NDMxLC0zLjMxODM0IC0yMi4yNzA5LC05LjIyNDkyYy01LjkwNjYxLC01LjkwNjU4IC05LjIyNDkxLC0xMy45MTc2OCAtOS4yMjQ5MSwtMjIuMjcwODljMCwtOC4zNTMyIDMuMzE4MjksLTE2LjM2NDMxIDkuMjI0OTEsLTIyLjI3MDljNS45MDY1OSwtNS45MDY2MSAxMy45MTc3LC05LjIyNDkxIDIyLjI3MDksLTkuMjI0OTFoMjEuMTA4OXY4LjkzNDk4aC0yMS4xMDg5djAuMTAyNTdjLTUuOTU2MjgsMCAtMTEuNjY4NjQsMi4zNjYxNiAtMTUuODgwMzcsNi41Nzc4OWMtNC4yMTE3Myw0LjIxMTczIC02LjU3Nzg5LDkuOTI0MDggLTYuNTc3ODksMTUuODgwMzdjMCw1Ljk1NjI4IDIuMzY2MTYsMTEuNjY4NjQgNi41Nzc4OSwxNS44ODAzN2M0LjIxMTczLDQuMjExNzMgOS45MjQwOCw2LjU3NzkzIDE1Ljg4MDM3LDYuNTc3OTN2MC4xMDI1M2gyMS4xMDg5djguOTM0OTh6IiBmaWxsPSIjMDBjMjhjIi8+PC9nPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjg4LjE5OTI2OTk5OTk5OTk4OjYxLjMzNTM0NDk5OTk5OTk5LS0+"; + + // Socket data + this.socketData = { + gmsg: [], + pmsg: [], + direct: [], + statuscode: [], + gvar: [], + pvar: [], + motd: "", + client_ip: "", + ulist: [], + server_version: "", + }; + this.varData = { + gvar: {}, + pvar: {}, + }; + + this.queueableCmds = [ + "gmsg", + "pmsg", + "gvar", + "pvar", + "direct", + "statuscode", + ]; + this.varCmds = ["gvar", "pvar"]; + + // Listeners + this.socketListeners = {}; + this.socketListenersData = {}; + this.newSocketData = { + gmsg: false, + pmsg: false, + direct: false, + statuscode: false, + gvar: false, + pvar: false, + }; + + // Edge-triggered hat blocks + this.connect_hat = 0; + this.packet_hat = 0; + this.close_hat = 0; + + // Status stuff + this.isRunning = false; + this.isLinked = false; + this.version = "S4.0"; + this.link_status = 0; + this.username = ""; + this.tmp_username = ""; + this.isUsernameSyncing = false; + this.isUsernameSet = false; + this.disconnectWasClean = false; + this.wasConnectionDropped = false; + this.didConnectionFail = false; + this.protocolOk = false; + + // Listeners stuff + this.enableListener = false; + this.setListener = ""; + + // Rooms stuff + this.enableRoom = false; + this.isRoomSetting = false; + this.selectRoom = ""; + + // Remapping stuff + this.menuRemap = { + "Global data": "gmsg", + "Private data": "pmsg", + "Global variables": "gvar", + "Private variables": "pvar", + "Direct data": "direct", + "Status code": "statuscode", + "All data": "all", + }; + } + + getInfo() { + return { + id: "cloudlink", + name: "CloudLink", + blockIconURI: this.cl_block, + menuIconURI: this.cl_icon, + docsURI: "https://hackmd.io/@MikeDEV/HJiNYwOfo", + blocks: [ + { + opcode: "returnGlobalData", + blockType: "reporter", + text: "Global data", + }, + { + opcode: "returnPrivateData", + blockType: "reporter", + text: "Private data", + }, + { + opcode: "returnDirectData", + blockType: "reporter", + text: "Direct Data", + }, + { + opcode: "returnLinkData", + blockType: "reporter", + text: "Link status", + }, + { + opcode: "returnStatusCode", + blockType: "reporter", + text: "Status code", + }, + { + opcode: "returnUserListData", + blockType: "reporter", + text: "Usernames", + }, + { + opcode: "returnUsernameData", + blockType: "reporter", + text: "My username", + }, + { + opcode: "returnVersionData", + blockType: "reporter", + text: "Extension version", + }, + { + opcode: "returnServerVersion", + blockType: "reporter", + text: "Server version", + }, + { + opcode: "returnServerList", + blockType: "reporter", + text: "Server list", + }, + { + opcode: "returnMOTD", + blockType: "reporter", + text: "Server MOTD", + }, + { + opcode: "returnClientIP", + blockType: "reporter", + text: "My IP address", + }, + { + opcode: "returnListenerData", + blockType: "reporter", + text: "Response for listener [ID]", + arguments: { + ID: { + type: "string", + defaultValue: "example-listener", + }, + }, + }, + { + opcode: "readQueueSize", + blockType: "reporter", + text: "Size of queue for [TYPE]", + arguments: { + TYPE: { + type: "string", + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + { + opcode: "readQueueData", + blockType: "reporter", + text: "Packet queue for [TYPE]", + arguments: { + TYPE: { + type: "string", + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + { + opcode: "returnVarData", + blockType: "reporter", + text: "[TYPE] [VAR] data", + arguments: { + VAR: { + type: "string", + defaultValue: "Apple", + }, + TYPE: { + type: "string", + menu: "varmenu", + defaultValue: "Global variables", + }, + }, + }, + { + opcode: "parseJSON", + blockType: "reporter", + text: "[PATH] of [JSON_STRING]", + arguments: { + PATH: { + type: "string", + defaultValue: "fruit/apples", + }, + JSON_STRING: { + type: "string", + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + }, + }, + }, + { + opcode: "getFromJSONArray", + blockType: "reporter", + text: "Get [NUM] from JSON array [ARRAY]", + arguments: { + NUM: { + type: "number", + defaultValue: 0, + }, + ARRAY: { + type: "string", + defaultValue: '["foo","bar"]', + }, + }, + }, + { + opcode: "fetchURL", + blockType: "reporter", + blockAllThreads: "true", + text: "Fetch data from URL [url]", + arguments: { + url: { + type: "string", + defaultValue: + "https://mikedev101.github.io/cloudlink/fetch_test", + }, + }, + }, + { + opcode: "requestURL", + blockType: "reporter", + blockAllThreads: "true", + text: "Send request with method [method] for URL [url] with data [data] and headers [headers]", + arguments: { + method: { + type: "string", + defaultValue: "GET", + }, + url: { + type: "string", + defaultValue: + "https://mikedev101.github.io/cloudlink/fetch_test", + }, + data: { + type: "string", + defaultValue: "{}", + }, + headers: { + type: "string", + defaultValue: "{}", + }, + }, + }, + { + opcode: "makeJSON", + blockType: "reporter", + text: "Convert [toBeJSONified] to JSON", + arguments: { + toBeJSONified: { + type: "string", + defaultValue: '{"test": true}', + }, + }, + }, + { + opcode: "onConnect", + blockType: "hat", + text: "When connected", + blockAllThreads: "true", + }, + { + opcode: "onClose", + blockType: "hat", + text: "When disconnected", + blockAllThreads: "true", + }, + { + opcode: "onListener", + blockType: "hat", + text: "When I receive new packet with listener [ID]", + blockAllThreads: "true", + arguments: { + ID: { + type: "string", + defaultValue: "example-listener", + }, + }, + }, + { + opcode: "onNewPacket", + blockType: "hat", + text: "When I receive new [TYPE] packet", + blockAllThreads: "true", + arguments: { + TYPE: { + type: "string", + menu: "almostallmenu", + defaultValue: "Global data", + }, + }, + }, + { + opcode: "onNewVar", + blockType: "hat", + text: "When I receive new [TYPE] data for [VAR]", + blockAllThreads: "true", + arguments: { + TYPE: { + type: "string", + menu: "varmenu", + defaultValue: "Global variables", + }, + VAR: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "getComState", + blockType: "Boolean", + text: "Connected?", + }, + { + opcode: "getRoomState", + blockType: "Boolean", + text: "Linked to rooms?", + }, + { + opcode: "getComLostConnectionState", + blockType: "Boolean", + text: "Lost connection?", + }, + { + opcode: "getComFailedConnectionState", + blockType: "Boolean", + text: "Failed to connnect?", + }, + { + opcode: "getUsernameState", + blockType: "Boolean", + text: "Username synced?", + }, + { + opcode: "returnIsNewData", + blockType: "Boolean", + text: "Got New [TYPE]?", + arguments: { + TYPE: { + type: "string", + menu: "datamenu", + defaultValue: "Global data", + }, + }, + }, + { + opcode: "returnIsNewVarData", + blockType: "Boolean", + text: "Got New [TYPE] data for variable [VAR]?", + arguments: { + TYPE: { + type: "string", + menu: "varmenu", + defaultValue: "Global variables", + }, + VAR: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "returnIsNewListener", + blockType: "Boolean", + text: "Got new packet with listener [ID]?", + blockAllThreads: "true", + arguments: { + ID: { + type: "string", + defaultValue: "example-listener", + }, + }, + }, + { + opcode: "checkForID", + blockType: "Boolean", + text: "ID [ID] connected?", + arguments: { + ID: { + type: "string", + defaultValue: "Another name", + }, + }, + }, + { + opcode: "isValidJSON", + blockType: "Boolean", + text: "Is [JSON_STRING] valid JSON?", + arguments: { + JSON_STRING: { + type: "string", + defaultValue: + '{"fruit": {"apples": 2, "bananas": 3}, "total_fruit": 5}', + }, + }, + }, + { + opcode: "openSocket", + blockType: "command", + text: "Connect to [IP]", + blockAllThreads: "true", + arguments: { + IP: { + type: "string", + defaultValue: "ws://127.0.0.1:3000/", + }, + }, + }, + { + opcode: "openSocketPublicServers", + blockType: "command", + text: "Connect to server [ID]", + blockAllThreads: "true", + arguments: { + ID: { + type: "number", + defaultValue: "", + }, + }, + }, + { + opcode: "closeSocket", + blockType: "command", + blockAllThreads: "true", + text: "Disconnect", + }, + { + opcode: "setMyName", + blockType: "command", + text: "Set [NAME] as username", + blockAllThreads: "true", + arguments: { + NAME: { + type: "string", + defaultValue: "A name", + }, + }, + }, + { + opcode: "createListener", + blockType: "command", + text: "Attach listener [ID] to next packet", + blockAllThreads: "true", + arguments: { + ID: { + type: "string", + defaultValue: "example-listener", + }, + }, + }, + { + opcode: "linkToRooms", + blockType: "command", + text: "Link to room(s) [ROOMS]", + blockAllThreads: "true", + arguments: { + ROOMS: { + type: "string", + defaultValue: '["test"]', + }, + }, + }, + { + opcode: "selectRoomsInNextPacket", + blockType: "command", + text: "Select room(s) [ROOMS] for next packet", + blockAllThreads: "true", + arguments: { + ROOMS: { + type: "string", + defaultValue: '["test"]', + }, + }, + }, + { + opcode: "unlinkFromRooms", + blockType: "command", + text: "Unlink from all rooms", + blockAllThreads: "true", + }, + { + opcode: "sendGData", + blockType: "command", + text: "Send [DATA]", + blockAllThreads: "true", + arguments: { + DATA: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "sendPData", + blockType: "command", + text: "Send [DATA] to [ID]", + blockAllThreads: "true", + arguments: { + DATA: { + type: "string", + defaultValue: "Apple", + }, + ID: { + type: "string", + defaultValue: "Another name", + }, + }, + }, + { + opcode: "sendGDataAsVar", + blockType: "command", + text: "Send variable [VAR] with data [DATA]", + blockAllThreads: "true", + arguments: { + DATA: { + type: "string", + defaultValue: "Banana", + }, + VAR: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "sendPDataAsVar", + blockType: "command", + text: "Send variable [VAR] to [ID] with data [DATA]", + blockAllThreads: "true", + arguments: { + DATA: { + type: "string", + defaultValue: "Banana", + }, + ID: { + type: "string", + defaultValue: "Another name", + }, + VAR: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "runCMDnoID", + blockType: "command", + text: "Send command without ID [CMD] [DATA]", + blockAllThreads: "true", + arguments: { + CMD: { + type: "string", + defaultValue: "direct", + }, + DATA: { + type: "string", + defaultValue: "val", + }, + }, + }, + { + opcode: "runCMD", + blockType: "command", + text: "Send command [CMD] [ID] [DATA]", + blockAllThreads: "true", + arguments: { + CMD: { + type: "string", + defaultValue: "direct", + }, + ID: { + type: "string", + defaultValue: "id", + }, + DATA: { + type: "string", + defaultValue: "val", + }, + }, + }, + { + opcode: "resetNewData", + blockType: "command", + text: "Reset got new [TYPE] status", + blockAllThreads: "true", + arguments: { + TYPE: { + type: "string", + menu: "datamenu", + defaultValue: "Global data", + }, + }, + }, + { + opcode: "resetNewVarData", + blockType: "command", + text: "Reset got new [TYPE] [VAR] status", + blockAllThreads: "true", + arguments: { + TYPE: { + type: "string", + menu: "varmenu", + defaultValue: "Global variables", + }, + VAR: { + type: "string", + defaultValue: "Apple", + }, + }, + }, + { + opcode: "resetNewListener", + blockType: "command", + text: "Reset got new [ID] listener status", + blockAllThreads: "true", + arguments: { + ID: { + type: "string", + defaultValue: "example-listener", + }, + }, + }, + { + opcode: "clearAllPackets", + blockType: "command", + text: "Clear all packets for [TYPE]", + arguments: { + TYPE: { + type: "string", + menu: "allmenu", + defaultValue: "All data", + }, + }, + }, + ], + menus: { + coms: { + items: ["Connected", "Username synced"], + }, + datamenu: { + items: [ + "Global data", + "Private data", + "Direct data", + "Status code", + ], + }, + varmenu: { + items: ["Global variables", "Private variables"], + }, + allmenu: { + items: [ + "Global data", + "Private data", + "Direct data", + "Status code", + "Global variables", + "Private variables", + "All data", + ], + }, + almostallmenu: { + items: [ + "Global data", + "Private data", + "Direct data", + "Status code", + "Global variables", + "Private variables", + ], + }, + }, + }; + } + + // Code for blocks go here + + returnGlobalData() { + if (this.socketData.gmsg.length != 0) { + let data = this.socketData.gmsg[this.socketData.gmsg.length - 1].val; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } else { + return ""; + } + } + + returnPrivateData() { + if (this.socketData.pmsg.length != 0) { + let data = this.socketData.pmsg[this.socketData.pmsg.length - 1].val; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } else { + return ""; + } + } + + returnDirectData() { + if (this.socketData.direct.length != 0) { + let data = + this.socketData.direct[this.socketData.direct.length - 1].val; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } else { + return ""; + } + } + + returnLinkData() { + return String(this.link_status); + } + + returnStatusCode() { + if (this.socketData.statuscode.length != 0) { + let data = + this.socketData.statuscode[this.socketData.statuscode.length - 1] + .code; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } else { + return ""; + } + } + + returnUserListData() { + return JSON.stringify(this.socketData.ulist); + } + + returnUsernameData() { + let data = this.username; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } + + returnVersionData() { + return String(this.version); + } + + returnServerVersion() { + return String(this.socketData.server_version); + } + + returnServerList() { + return JSON.stringify(servers); + } + + returnMOTD() { + return String(this.socketData.motd); + } + + returnClientIP() { + return String(this.socketData.client_ip); + } + + returnListenerData({ ID }) { + const self = this; + if (this.isRunning && this.socketListeners.hasOwnProperty(String(ID))) { + return JSON.stringify(this.socketListenersData[ID]); + } else { + return "{}"; + } + } + + readQueueSize({ TYPE }) { + if (this.menuRemap[String(TYPE)] == "all") { + let tmp_size = 0; + tmp_size = tmp_size + this.socketData.gmsg.length; + tmp_size = tmp_size + this.socketData.pmsg.length; + tmp_size = tmp_size + this.socketData.direct.length; + tmp_size = tmp_size + this.socketData.statuscode.length; + tmp_size = tmp_size + this.socketData.gvar.length; + tmp_size = tmp_size + this.socketData.pvar.length; + return tmp_size; + } else { + return this.socketData[this.menuRemap[String(TYPE)]].length; + } + } + + readQueueData({ TYPE }) { + if (this.menuRemap[String(TYPE)] == "all") { + let tmp_socketData = JSON.parse(JSON.stringify(this.socketData)); // Deep copy + + delete tmp_socketData.motd; + delete tmp_socketData.client_ip; + delete tmp_socketData.ulist; + delete tmp_socketData.server_version; + + return JSON.stringify(tmp_socketData); + } else { + return JSON.stringify(this.socketData[this.menuRemap[String(TYPE)]]); + } + } + + returnVarData({ TYPE, VAR }) { + if (this.isRunning) { + if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { + if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { + return this.varData[this.menuRemap[TYPE]][VAR].value; + } else { + return ""; + } + } else { + return ""; + } + } else { + return ""; + } + } + + parseJSON({ PATH, JSON_STRING }) { + try { + const path = PATH.toString() + .split("/") + .map((prop) => decodeURIComponent(prop)); + if (path[0] === "") path.splice(0, 1); + if (path[path.length - 1] === "") path.splice(-1, 1); + let json; + try { + json = JSON.parse(" " + JSON_STRING); + } catch (e) { + return e.message; + } + path.forEach((prop) => (json = json[prop])); + if (json === null) return "null"; + else if (json === undefined) return ""; + else if (typeof json === "object") return JSON.stringify(json); + else return json.toString(); + } catch (err) { + return ""; + } + } + + getFromJSONArray({ NUM, ARRAY }) { + var json_array = JSON.parse(ARRAY); + if (json_array[NUM] == "undefined") { + return ""; + } else { + let data = json_array[NUM]; + + if (typeof data == "object") { + data = JSON.stringify(data); // Make the JSON safe for Scratch + } + + return data; + } + } + + fetchURL(args) { + return Scratch.fetch(args.url, { + method: "GET", + }).then((response) => response.text()); + } + + requestURL(args) { + if (args.method == "GET" || args.method == "HEAD") { + return Scratch.fetch(args.url, { + method: args.method, + headers: JSON.parse(args.headers), + }).then((response) => response.text()); + } else { + return Scratch.fetch(args.url, { + method: args.method, + headers: JSON.parse(args.headers), + body: JSON.parse(args.data), + }).then((response) => response.text()); + } + } + + isValidJSON({ JSON_STRING }) { + return jsonCheck(JSON_STRING); + } + + makeJSON({ toBeJSONified }) { + if (typeof toBeJSONified == "string") { + try { + JSON.parse(toBeJSONified); + return String(toBeJSONified); + } catch (err) { + return "Not JSON!"; + } + } else if (typeof toBeJSONified == "object") { + return JSON.stringify(toBeJSONified); + } else { + return "Not JSON!"; + } + } + + onConnect() { + const self = this; + if (self.connect_hat == 0 && self.isRunning && self.protocolOk) { + self.connect_hat = 1; + return true; + } else { + return false; + } + } + + onClose() { + const self = this; + if (self.close_hat == 0 && !self.isRunning) { + self.close_hat = 1; + return true; + } else { + return false; + } + } + + onListener({ ID }) { + const self = this; + if (this.isRunning && this.socketListeners.hasOwnProperty(String(ID))) { + if (self.socketListeners[String(ID)]) { + self.socketListeners[String(ID)] = false; + return true; + } else { + return false; + } + } else { + return false; + } + } + + onNewPacket({ TYPE }) { + const self = this; + if (this.isRunning && this.newSocketData[this.menuRemap[String(TYPE)]]) { + self.newSocketData[this.menuRemap[String(TYPE)]] = false; + return true; + } else { + return false; + } + } + + onNewVar({ TYPE, VAR }) { + const self = this; + if (this.isRunning) { + if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { + if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { + if (this.varData[this.menuRemap[TYPE]][VAR].isNew) { + self.varData[this.menuRemap[TYPE]][VAR].isNew = false; + return true; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + getComState() { + return String(this.link_status == 2 || this.protocolOk); + } + + getRoomState() { + return this.isLinked; + } + + getComLostConnectionState() { + return this.wasConnectionDropped; + } + + getComFailedConnectionState() { + return this.didConnectionFail; + } + + getUsernameState() { + return this.isUsernameSet; + } + + returnIsNewData({ TYPE }) { + if (this.isRunning) { + return this.newSocketData[this.menuRemap[String(TYPE)]]; + } else { + return false; + } + } + + returnIsNewVarData({ TYPE, VAR }) { + if (this.isRunning) { + if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { + if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { + return this.varData[this.menuRemap[TYPE]][VAR].isNew; + } else { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + + returnIsNewListener({ ID }) { + if (this.isRunning) { + if (this.socketListeners.hasOwnProperty(String(ID))) { + return this.socketListeners[ID]; + } else { + return false; + } + } else { + return false; + } + } + + checkForID({ ID }) { + return find_id(ID, this.socketData.ulist); + } + + async openSocket({ IP }) { + const self = this; + if (!self.isRunning) { + if (!(await Scratch.canFetch(IP))) { + return; + } + + console.log("Starting socket."); + self.link_status = 1; + + self.disconnectWasClean = false; + self.wasConnectionDropped = false; + self.didConnectionFail = false; + + mWS = new WebSocket(String(IP)); + + mWS.onerror = function () { + self.isRunning = false; + }; + + mWS.onopen = function () { + self.isRunning = true; + self.packet_queue = {}; + self.link_status = 2; + + // Send the handshake request to get server to detect client protocol + mWS.send( + JSON.stringify({ cmd: "handshake", listener: "setprotocol" }) + ); + + console.log("Successfully opened socket."); + }; + + mWS.onmessage = function (event) { + let tmp_socketData = JSON.parse(event.data); + console.log("RX:", tmp_socketData); + + if (self.queueableCmds.includes(tmp_socketData.cmd)) { + self.socketData[tmp_socketData.cmd].push(tmp_socketData); + } else { + if (tmp_socketData.cmd == "ulist") { + // ulist functionality has been changed in server 0.1.9 + if (tmp_socketData.hasOwnProperty("mode")) { + if (tmp_socketData.mode == "set") { + self.socketData["ulist"] = tmp_socketData.val; + } else if (tmp_socketData.mode == "add") { + if ( + !self.socketData.ulist.some( + (o) => + o.username === tmp_socketData.val.username && + o.id == tmp_socketData.val.id + ) + ) { + self.socketData["ulist"].push(tmp_socketData.val); + } else { + console.log( + "Could not perform ulist method add, client", + tmp_socketData.val, + "already exists" + ); + } + } else if (tmp_socketData.mode == "remove") { + if ( + self.socketData.ulist.some( + (o) => + o.username === tmp_socketData.val.username && + o.id == tmp_socketData.val.id + ) + ) { + // This is by far the fugliest thing I have ever written in JS, or in any programming language... thanks I hate it + self.socketData["ulist"] = self.socketData["ulist"].filter( + (user) => + !(user.username === tmp_socketData.val.username) && + !(user.id == tmp_socketData.val.id) + ); + } else { + console.log( + "Could not perform ulist method remove, client", + tmp_socketData.val, + "was not found" + ); + } + } else { + console.log( + "Could not understand ulist method:", + tmp_socketData.mode + ); + } + } else { + // Retain compatibility wtih existing servers + self.socketData["ulist"] = tmp_socketData.val; + } + } else { + self.socketData[tmp_socketData.cmd] = tmp_socketData.val; + } + } + + if (self.newSocketData.hasOwnProperty(tmp_socketData.cmd)) { + self.newSocketData[tmp_socketData.cmd] = true; + } + + if (self.varCmds.includes(tmp_socketData.cmd)) { + self.varData[tmp_socketData.cmd][tmp_socketData.name] = { + value: tmp_socketData.val, + isNew: true, + }; + } + if (tmp_socketData.hasOwnProperty("listener")) { + if (tmp_socketData.listener == "setusername") { + self.socketListeners["setusername"] = true; + if (tmp_socketData.code == "I:100 | OK") { + self.username = tmp_socketData.val; + self.isUsernameSyncing = false; + self.isUsernameSet = true; + console.log( + "Username was accepted by the server, and has been set to:", + self.username + ); + } else { + console.warn( + "Username was rejected by the server. Error code:", + String(tmp_socketData.code) + ); + self.isUsernameSyncing = false; + } + } else if (tmp_socketData.listener == "roomLink") { + self.isRoomSetting = false; + self.socketListeners["roomLink"] = true; + if (tmp_socketData.code == "I:100 | OK") { + console.log("Linking to room(s) was accepted by the server!"); + self.isLinked = true; + } else { + console.warn( + "Linking to room(s) was rejected by the server. Error code:", + String(tmp_socketData.code) + ); + self.enableRoom = false; + self.isLinked = false; + self.selectRoom = ""; + } + } else if ( + tmp_socketData.listener == "setprotocol" && + !this.protocolOk + ) { + console.log( + "Server successfully set client protocol to cloudlink!" + ); + self.socketData.statuscode = []; + self.protocolOk = true; + self.socketListeners["setprotocol"] = true; + } else { + if ( + self.socketListeners.hasOwnProperty(tmp_socketData.listener) + ) { + self.socketListeners[tmp_socketData.listener] = true; + } + } + self.socketListenersData[tmp_socketData.listener] = tmp_socketData; + } + self.packet_hat = 0; + }; + + mWS.onclose = function () { + self.isRunning = false; + self.connect_hat = 0; + self.packet_hat = 0; + self.protocolOk = false; + if (self.close_hat == 1) { + self.close_hat = 0; + } + self.socketData = { + gmsg: [], + pmsg: [], + direct: [], + statuscode: [], + gvar: [], + pvar: [], + motd: "", + client_ip: "", + ulist: [], + server_version: "", + }; + self.newSocketData = { + gmsg: false, + pmsg: false, + direct: false, + statuscode: false, + gvar: false, + pvar: false, + }; + self.socketListeners = {}; + self.username = ""; + self.tmp_username = ""; + self.isUsernameSyncing = false; + self.isUsernameSet = false; + self.enableListener = false; + self.setListener = ""; + self.enableRoom = false; + self.selectRoom = ""; + self.isLinked = false; + self.isRoomSetting = false; + + if (self.link_status != 1) { + if (self.disconnectWasClean) { + self.link_status = 3; + console.log("Socket closed."); + self.wasConnectionDropped = false; + self.didConnectionFail = false; + } else { + self.link_status = 4; + console.error("Lost connection to the server."); + self.wasConnectionDropped = true; + self.didConnectionFail = false; + } + } else { + self.link_status = 4; + console.error("Failed to connect to server."); + self.wasConnectionDropped = false; + self.didConnectionFail = true; + } + }; + } else { + console.warn("Socket is already open."); + } + } + + openSocketPublicServers({ ID }) { + if (servers.hasOwnProperty(ID)) { + console.log("Connecting to:", servers[ID].url); + this.openSocket({ IP: servers[ID].url }); + } + } + + closeSocket() { + const self = this; + if (this.isRunning) { + console.log("Closing socket..."); + mWS.close(1000, "script closure"); + self.disconnectWasClean = true; + } else { + console.warn("Socket is not open."); + } + } + + setMyName({ NAME }) { + const self = this; + if (this.isRunning) { + if (!this.isUsernameSyncing) { + if (!this.isUsernameSet) { + if (String(NAME) != "") { + if (!(String(NAME).length > 20)) { + if ( + !( + String(NAME) == "%CA%" || + String(NAME) == "%CC%" || + String(NAME) == "%CD%" || + String(NAME) == "%MS%" + ) + ) { + let tmp_msg = { + cmd: "setid", + val: String(NAME), + listener: "setusername", + }; + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + self.tmp_username = String(NAME); + self.isUsernameSyncing = true; + } else { + console.log("Blocking attempt to use reserved usernames"); + } + } else { + console.log( + "Blocking attempt to use username larger than 20 characters, username is " + + String(NAME).length + + " characters long" + ); + } + } else { + console.log("Blocking attempt to use blank username"); + } + } else { + console.warn("Username already has been set!"); + } + } else { + console.warn("Username is still syncing!"); + } + } + } + + createListener({ ID }) { + self = this; + if (this.isRunning) { + if (!this.enableListener) { + self.enableListener = true; + self.setListener = String(ID); + } else { + console.warn("Listeners were already created!"); + } + } else { + console.log("Cannot assign a listener to a packet while disconnected"); + } + } + + linkToRooms({ ROOMS }) { + const self = this; + + if (this.isRunning) { + if (!this.isRoomSetting) { + if (!(String(ROOMS).length > 1000)) { + let tmp_msg = { + cmd: "link", + val: autoConvert(ROOMS), + listener: "roomLink", + }; + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + self.isRoomSetting = true; + } else { + console.warn( + "Blocking attempt to send a room ID / room list larger than 1000 bytes (1 KB), room ID / room list is " + + String(ROOMS).length + + " bytes" + ); + } + } else { + console.warn("Still linking to rooms!"); + } + } else { + console.warn("Socket is not open."); + } + } + + selectRoomsInNextPacket({ ROOMS }) { + const self = this; + if (this.isRunning) { + if (this.isLinked) { + if (!this.enableRoom) { + if (!(String(ROOMS).length > 1000)) { + self.enableRoom = true; + self.selectRoom = ROOMS; + } else { + console.warn( + "Blocking attempt to select a room ID / room list larger than 1000 bytes (1 KB), room ID / room list is " + + String(ROOMS).length + + " bytes" + ); + } + } else { + console.warn("Rooms were already selected!"); + } + } else { + console.warn("Not linked to any room(s)!"); + } + } else { + console.warn("Socket is not open."); + } + } + + unlinkFromRooms() { + const self = this; + if (this.isRunning) { + if (this.isLinked) { + let tmp_msg = { + cmd: "unlink", + val: "", + }; + + if (this.enableListener) { + tmp_msg["listener"] = autoConvert(this.setListener); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + + self.isLinked = false; + } else { + console.warn("Not linked to any rooms!"); + } + } else { + console.warn("Socket is not open."); + } + } + + sendGData({ DATA }) { + const self = this; + if (this.isRunning) { + if (!(String(DATA).length > 1000)) { + let tmp_msg = { + cmd: "gmsg", + val: autoConvert(DATA), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + + if (this.enableRoom) { + tmp_msg["rooms"] = autoConvert(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + + String(DATA).length + + " bytes" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + sendPData({ DATA, ID }) { + const self = this; + if (this.isRunning) { + if (!(String(DATA).length > 1000)) { + let tmp_msg = { + cmd: "pmsg", + val: autoConvert(DATA), + id: autoConvert(ID), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + if (this.enableRoom) { + tmp_msg["rooms"] = autoConvert(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + + String(DATA).length + + " bytes" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + sendGDataAsVar({ VAR, DATA }) { + const self = this; + if (this.isRunning) { + if (!(String(DATA).length > 1000)) { + let tmp_msg = { + cmd: "gvar", + name: VAR, + val: autoConvert(DATA), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + if (this.enableRoom) { + tmp_msg["rooms"] = autoConvert(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + + String(DATA).length + + " bytes" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + sendPDataAsVar({ VAR, ID, DATA }) { + const self = this; + if (this.isRunning) { + if (!(String(DATA).length > 1000)) { + let tmp_msg = { + cmd: "pvar", + name: VAR, + val: autoConvert(DATA), + id: autoConvert(ID), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + if (this.enableRoom) { + tmp_msg["rooms"] = autoConvert(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet larger than 1000 bytes (1 KB), packet is " + + String(DATA).length + + " bytes" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + runCMDnoID({ CMD, DATA }) { + const self = this; + if (this.isRunning) { + if (!(String(CMD).length > 100) || !(String(DATA).length > 1000)) { + let tmp_msg = { + cmd: String(CMD), + val: autoConvert(DATA), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + if (this.enableRoom) { + tmp_msg["rooms"] = String(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet with questionably long arguments" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + runCMD({ CMD, ID, DATA }) { + const self = this; + if (this.isRunning) { + if ( + !(String(CMD).length > 100) || + !(String(ID).length > 20) || + !(String(DATA).length > 1000) + ) { + let tmp_msg = { + cmd: String(CMD), + id: autoConvert(ID), + val: autoConvert(DATA), + }; + + if (this.enableListener) { + tmp_msg["listener"] = String(this.setListener); + } + if (this.enableRoom) { + tmp_msg["rooms"] = String(this.selectRoom); + } + + console.log("TX:", tmp_msg); + mWS.send(JSON.stringify(tmp_msg)); + + if (this.enableListener) { + if (!self.socketListeners.hasOwnProperty(this.setListener)) { + self.socketListeners[this.setListener] = false; + } + self.enableListener = false; + } + if (this.enableRoom) { + self.enableRoom = false; + self.selectRoom = ""; + } + } else { + console.warn( + "Blocking attempt to send packet with questionably long arguments" + ); + } + } else { + console.warn("Socket is not open."); + } + } + + resetNewData({ TYPE }) { + const self = this; + if (this.isRunning) { + self.newSocketData[this.menuRemap[String(TYPE)]] = false; + } + } + + resetNewVarData({ TYPE, VAR }) { + const self = this; + if (this.isRunning) { + if (this.varData.hasOwnProperty(this.menuRemap[TYPE])) { + if (this.varData[this.menuRemap[TYPE]].hasOwnProperty(VAR)) { + self.varData[this.menuRemap[TYPE]][VAR].isNew = false; + } + } + } + } + + resetNewListener({ ID }) { + const self = this; + if (this.isRunning) { + if (this.socketListeners.hasOwnProperty(String(ID))) { + self.socketListeners[String(ID)] = false; + } + } + } + + clearAllPackets({ TYPE }) { + const self = this; + if (this.menuRemap[String(TYPE)] == "all") { + self.socketData.gmsg = []; + self.socketData.pmsg = []; + self.socketData.direct = []; + self.socketData.statuscode = []; + self.socketData.gvar = []; + self.socketData.pvar = []; + } else { + self.socketData[this.menuRemap[String(TYPE)]] = []; + } + } + } + + console.log("CloudLink 4.0 loaded. Detecting unsandboxed mode."); + Scratch.extensions.register(new CloudLink(Scratch.vm.runtime)); })(Scratch); - diff --git a/extensions/codeGIO/ExtraUtilities.js b/extensions/codeGIO/ExtraUtilities.js index 3c7414dde4..a88fee3591 100644 --- a/extensions/codeGIO/ExtraUtilities.js +++ b/extensions/codeGIO/ExtraUtilities.js @@ -1,7 +1,7 @@ -(function(Scratch) { +(function (Scratch) { "use strict"; class codegioExtension { - getInfo () { + getInfo() { return { id: "utilitiesCodegio", name: "Utilities", @@ -11,7 +11,7 @@ { opcode: "newline", blockType: Scratch.BlockType.REPORTER, - text: "New Line" + text: "New Line", }, { @@ -21,25 +21,25 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "" + defaultValue: "", }, two: { type: Scratch.ArgumentType.STRING, - defaultValue: "" - } - } + defaultValue: "", + }, + }, }, { opcode: "returntrue", blockType: Scratch.BlockType.BOOLEAN, - text: "true" + text: "true", }, { opcode: "returnfalse", blockType: Scratch.BlockType.BOOLEAN, - text: "false" + text: "false", }, { @@ -49,13 +49,13 @@ arguments: { one: { type: Scratch.ArgumentType.NUMBER, - defaultValue: "" + defaultValue: "", }, two: { type: Scratch.ArgumentType.NUMBER, - defaultValue: "" - } - } + defaultValue: "", + }, + }, }, { @@ -65,33 +65,33 @@ arguments: { color: { type: Scratch.ArgumentType.COLOR, - defaultValue: "#96ccff" - } - } + defaultValue: "#96ccff", + }, + }, }, { opcode: "monitor_width", blockType: Scratch.BlockType.REPORTER, - text: "Screen | Width" + text: "Screen | Width", }, { opcode: "monitor_height", blockType: Scratch.BlockType.REPORTER, - text: "Screen | Height" + text: "Screen | Height", }, { opcode: "window_width", blockType: Scratch.BlockType.REPORTER, - text: "Window | Width" + text: "Window | Width", }, { opcode: "window_height", blockType: Scratch.BlockType.REPORTER, - text: "Window | Height" + text: "Window | Height", }, { @@ -101,9 +101,9 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "Alert..." - } - } + defaultValue: "Alert...", + }, + }, }, { @@ -113,9 +113,9 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "Confirm..." - } - } + defaultValue: "Confirm...", + }, + }, }, { @@ -125,13 +125,13 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "Enter Username:" + defaultValue: "Enter Username:", }, two: { type: Scratch.ArgumentType.STRING, - defaultValue: "griffpatch" + defaultValue: "griffpatch", }, - } + }, }, { @@ -141,9 +141,9 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "https://turbowarp.org/" - } - } + defaultValue: "https://turbowarp.org/", + }, + }, }, { @@ -153,21 +153,21 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "https://turbowarp.org/" - } - } + defaultValue: "https://turbowarp.org/", + }, + }, }, { opcode: "get_current_url", blockType: Scratch.BlockType.REPORTER, - text: "Current URL" + text: "Current URL", }, { opcode: "get_current_url_hash", blockType: Scratch.BlockType.REPORTER, - text: "Current URL hash (#)" + text: "Current URL hash (#)", }, { @@ -177,27 +177,27 @@ arguments: { one: { type: Scratch.ArgumentType.STRING, - defaultValue: "" - } - } + defaultValue: "", + }, + }, }, { opcode: "get_clipboard", blockType: Scratch.BlockType.REPORTER, - text: "Clipboard" + text: "Clipboard", }, { opcode: "get_browser", blockType: Scratch.BlockType.REPORTER, - text: "Browser" + text: "Browser", }, { opcode: "get_os", blockType: Scratch.BlockType.REPORTER, - text: "Operating System" + text: "Operating System", }, { @@ -212,7 +212,7 @@ font: { type: Scratch.ArgumentType.STRING, defaultValue: "Monospace", - menu: "consoleFonts" + menu: "consoleFonts", }, size: { type: Scratch.ArgumentType.NUMBER, @@ -222,25 +222,25 @@ type: Scratch.ArgumentType.COLOR, defaultValue: "#000000", }, - } + }, }, { opcode: "consoleClear", blockType: Scratch.BlockType.COMMAND, - text: "Console | Clear" + text: "Console | Clear", }, ], menus: { consoleFonts: { acceptReporters: true, items: [ - {text: "Serif (default)", value: "serif"}, - {text: "Monospace", value: "monospace"}, - {text: "Sans-serif", value: "sans-serif"} - ] - } - } + { text: "Serif (default)", value: "serif" }, + { text: "Monospace", value: "monospace" }, + { text: "Sans-serif", value: "sans-serif" }, + ], + }, + }, }; } @@ -257,7 +257,7 @@ } strict_equality(args) { - return (args.one == args.two); + return args.one == args.two; } exponent(args) { @@ -333,28 +333,28 @@ get_clipboard() { if (navigator.clipboard && navigator.clipboard.readText) { - return Scratch.canReadClipboard().then(allowed => { + return Scratch.canReadClipboard().then((allowed) => { if (allowed) { return navigator.clipboard.readText(); } - return ''; + return ""; }); } - return ''; + return ""; } get_browser() { let userAgent = navigator.userAgent; - if (userAgent.match(/chrome|chromium|crios/i)){ + if (userAgent.match(/chrome|chromium|crios/i)) { return "Chrome"; - } else if (userAgent.match(/firefox|fxios/i)){ + } else if (userAgent.match(/firefox|fxios/i)) { return "Firefox"; - } else if (userAgent.match(/safari/i)){ + } else if (userAgent.match(/safari/i)) { return "Safari"; - } else if (userAgent.match(/opr\//i)){ + } else if (userAgent.match(/opr\//i)) { return "Opera"; - } else if (userAgent.match(/edg/i)){ + } else if (userAgent.match(/edg/i)) { return "Edge"; } else { return "No browser detection"; @@ -366,7 +366,10 @@ } consoleLog(args) { - console.log(`%c${args.input}`, `color:${args.color}; font-family:${args.font}; font-size: ${args.size}px;`); + console.log( + `%c${args.input}`, + `color:${args.color}; font-family:${args.font}; font-size: ${args.size}px;` + ); } consoleClear() { diff --git a/extensions/cs2627883/numericalencoding.js b/extensions/cs2627883/numericalencoding.js index 0f23332264..5212283722 100644 --- a/extensions/cs2627883/numericalencoding.js +++ b/extensions/cs2627883/numericalencoding.js @@ -1,48 +1,54 @@ +// Name: Numerical Encoding +// ID: cs2627883NumericalEncoding +// Description: Encode strings as numbers for cloud variables. +// By: cs2627883 + // https://github.com/CS2627883/Turbowarp-Encoding-Extension/blob/main/Encoding.js -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; class NumericalEncodingExtension { maxcharlength = 6; // There are 149,186 unicode characters, so the maximum character code length is 6 encoded = 0; decoded = 0; getInfo() { return { - id: 'cs2627883NumericalEncoding', - name: 'Numerical Encoding', - blocks: [{ - opcode: 'NumericalEncode', - blockType: Scratch.BlockType.COMMAND, - text: 'Encode [DATA] to numbers', - arguments: { - DATA: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'Hello!' - } - } - }, + id: "cs2627883NumericalEncoding", + name: "Numerical Encoding", + blocks: [ { - opcode: 'NumericalDecode', + opcode: "NumericalEncode", blockType: Scratch.BlockType.COMMAND, - text: 'Decode [ENCODED] back to text', + text: "Encode [DATA] to numbers", + arguments: { + DATA: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello!", + }, + }, + }, + { + opcode: "NumericalDecode", + blockType: Scratch.BlockType.COMMAND, + text: "Decode [ENCODED] back to text", arguments: { ENCODED: { type: Scratch.ArgumentType.STRING, - defaultValue: '000072000101000108000108000111000033' //Encoded "Hello!" - } - } + defaultValue: "000072000101000108000108000111000033", //Encoded "Hello!" + }, + }, }, { - opcode: 'GetNumericalEncoded', + opcode: "GetNumericalEncoded", blockType: Scratch.BlockType.REPORTER, - text: 'encoded', + text: "encoded", }, { - opcode: 'GetNumericalDecoded', + opcode: "GetNumericalDecoded", blockType: Scratch.BlockType.REPORTER, - text: 'decoded', - } - ] + text: "decoded", + }, + ], }; } NumericalEncode(args) { @@ -52,7 +58,8 @@ // Get char code of character var encodedchar = String(toencode.charCodeAt(i)); // Pad encodedchar with 0s to ensure all encodedchars are the same length - encodedchar = "0".repeat(this.maxcharlength - encodedchar.length) + encodedchar; + encodedchar = + "0".repeat(this.maxcharlength - encodedchar.length) + encodedchar; encoded += encodedchar; } this.encoded = encoded; @@ -65,7 +72,7 @@ } var decoded = ""; // Create regex to split by char length - const regex = new RegExp('.{1,' + this.maxcharlength + '}', 'g'); + const regex = new RegExp(".{1," + this.maxcharlength + "}", "g"); // Split into array of characters var encodedchars = todecode.match(regex); for (let i = 0; i < encodedchars.length; i++) { diff --git a/extensions/cursor.js b/extensions/cursor.js index 4b9420e9f8..3fdf7c47ca 100644 --- a/extensions/cursor.js +++ b/extensions/cursor.js @@ -1,286 +1,323 @@ -(function (Scratch) { - 'use strict'; - - if (!Scratch.extensions.unsandboxed) { - throw new Error('MouseCursor extension must be run unsandboxed'); - } - - const lazilyCreatedCanvas = () => { - /** @type {HTMLCanvasElement} */ - let canvas = null; - /** @type {CanvasRenderingContext2D} */ - let ctx = null; - /** - * @param {number} width - * @param {number} height - * @returns {[HTMLCanvasElement, CanvasRenderingContext2D]} - */ - return (width, height) => { - if (!canvas) { - canvas = document.createElement('canvas'); - ctx = canvas.getContext('2d'); - if (!ctx) { - throw new Error('Could not get 2d rendering context'); - } - } - // Setting canvas size also clears it - canvas.width = width; - canvas.height = height; - return [canvas, ctx]; - }; - }; - const getRawSkinCanvas = lazilyCreatedCanvas(); - - /** - * @param {RenderWebGL.Skin} skin - * @returns {string} A data: URI for the skin. - */ - const encodeSkinToURL = (skin) => { - const svgSkin = /** @type {RenderWebGL.SVGSkin} */ (skin); - if (svgSkin._svgImage) { - // This is an SVG skin - return svgSkin._svgImage.src; - } - - // It's probably a bitmap skin. - // The most reliable way to get the bitmap in every runtime is through the silhouette. - // This is very slow and could involve reading the texture from the GPU. - const silhouette = skin._silhouette; - // unlazy() only exists in TW - if (silhouette.unlazy) { - silhouette.unlazy(); - } - const colorData = silhouette._colorData; - const width = silhouette._width; - const height = silhouette._height; - const imageData = new ImageData(colorData, silhouette._width, silhouette._height); - const [canvas, ctx] = getRawSkinCanvas(width, height); - ctx.putImageData(imageData, 0, 0); - return canvas.toDataURL(); - }; - - /** - * @param {VM.Costume} costume - * @param {number} maxWidth - * @param {number} maxHeight - * @returns {{uri: string, width: number, height: number}} - */ - const costumeToCursor = (costume, maxWidth, maxHeight) => { - const skin = Scratch.vm.renderer._allSkins[costume.skinId]; - const imageURI = encodeSkinToURL(skin); - - let width = skin.size[0]; - let height = skin.size[1]; - if (width > maxWidth) { - height = height * (maxWidth / width); - width = maxWidth; - } - if (height > maxHeight) { - width = width * (maxHeight / height); - height = maxHeight; - } - width = Math.round(width); - height = Math.round(height); - - // We wrap the encoded image in an . This lets us do some clever things: - // - We can resize the image without a canvas. - // - We can give the browser an image with more raw pixels than its DPI independent size. - // The latter is important so that cursors won't look horrible on high DPI displays. For - // example, if the cursor will display at 32x32 in DPI independent units on a 2x high DPI - // display, we actually need to send a 64x64 image for it to look good. This lets us do - // that automatically. - let svg = ``; - svg += ``; - svg += ''; - // URI encoding usually results in smaller string than base 64 for the types of data we get here. - const svgURI = `data:image/svg+xml;,${encodeURIComponent(svg)}`; - - return { - uri: svgURI, - width, - height - }; - }; - - /** @type {string} */ - let nativeCursor = 'default'; - /** @type {null|string} */ - let customCursorImageName = null; - - const canvas = Scratch.renderer.canvas; - /** @type {string} */ - let currentCanvasCursor = nativeCursor; - const updateCanvasCursor = () => { - if (canvas.style.cursor !== currentCanvasCursor) { - canvas.style.cursor = currentCanvasCursor; - } - }; - - // scratch-gui will sometimes reset the cursor when resizing the window or going in/out of fullscreen - new MutationObserver(updateCanvasCursor).observe(canvas, { - attributeFilter: ['style'], - attributes: true - }); - - /** - * Parse strings like "60x12" or "77,1" - * @param {string} string - * @returns {[number, number]} - */ - const parseTuple = (string) => { - const [a, b] = ('' + string).split(/[ ,x]/); - return [ - +a || 0, - +b || 0 - ]; - }; - - const cursors = [ - 'default', 'pointer', 'move', 'grab', 'grabbing', 'text', - 'vertical-text', 'wait', 'progress', 'help', 'context-menu', - 'zoom-in', 'zoom-out', 'crosshair', 'cell', 'not-allowed', - 'copy', 'alias', 'no-drop', 'all-scroll', 'col-resize', - 'row-resize', 'n-resize', 'e-resize', 's-resize', 'w-resize', - 'ne-resize', 'nw-resize', 'se-resize', 'sw-resize', - 'ew-resize', 'ns-resize', 'nesw-resize', 'nwse-resize' - ]; - - class MouseCursor { - getInfo() { - return { - id: 'MouseCursor', - name: 'Mouse Cursor', - blocks: [ - { - opcode: 'setCur', - blockType: Scratch.BlockType.COMMAND, - text: 'set cursor to [cur]', - arguments: { - cur: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'pointer', - menu: 'cursors', - }, - }, - }, - { - opcode: 'setCursorImage', - blockType: Scratch.BlockType.COMMAND, - text: "set cursor to current costume center: [position] max size: [size]", - arguments: { - position: { - type: Scratch.ArgumentType.STRING, - defaultValue: '0,0', - menu: 'imagePositions' - }, - size: { - type: Scratch.ArgumentType.STRING, - defaultValue: '32x32', - menu: 'imageSizes' - } - } - }, - { - opcode: 'hideCur', - blockType: Scratch.BlockType.COMMAND, - text: 'hide cursor', - }, - { - opcode: 'getCur', - blockType: Scratch.BlockType.REPORTER, - text: 'cursor', - }, - ], - menus: { - cursors: { - acceptReporters: true, - items: cursors, - }, - imagePositions: { - acceptReporters: true, - items: [ - // [x, y] where x is [0=left, 100=right] and y is [0=top, 100=bottom] - { text: 'top left', value: '0,0' }, - { text: 'top right', value: '100,0' }, - { text: 'bottom left', value: '0,100' }, - { text: 'bottom right', value: '100,100' }, - { text: 'center', value: '50,50' }, - ] - }, - imageSizes: { - acceptReporters: true, - items: [ - // Some important numbers to keep in mind: - // Browsers ignore cursor images >128 in any dimension (https://searchfox.org/mozilla-central/rev/43ee5e789b079e94837a21336e9ce2420658fd19/widget/gtk/nsWindow.cpp#3393-3402) - // Browsers may refuse to display a cursor near window borders for images >32 in any dimension - { text: '4x4', value: '4x4' }, - { text: '8x8', value: '8x4' }, - { text: '12x12', value: '12x12' }, - { text: '16x16', value: '16x16' }, - { text: '32x32', value: '32x32' }, - { text: '48x48 (unreliable)', value: '48x48' }, - { text: '64x64 (unreliable)', value: '64x64' }, - { text: '128x128 (unreliable)', value: '128x128' }, - ] - } - }, - }; - } - - setCur(args) { - const newCursor = Scratch.Cast.toString(args.cur); - // Prevent setting cursor to "url(...), default" from causing fetch. - if (cursors.includes(newCursor) || newCursor === 'none') { - nativeCursor = newCursor; - customCursorImageName = null; - currentCanvasCursor = newCursor; - updateCanvasCursor(); - } - } - - setCursorImage(args, util) { - const [maxWidth, maxHeight] = parseTuple(args.size).map(i => Math.max(0, i)); - - const currentCostume = util.target.getCostumes()[util.target.currentCostume]; - const costumeName = currentCostume.name; - - let encodedCostume; - try { - encodedCostume = costumeToCursor(currentCostume, maxWidth, maxHeight); - } catch (e) { - // This could happen for a variety of reasons. - console.error(e); - } - - if (encodedCostume) { - const [percentX, percentY] = parseTuple(args.position).map(i => Math.max(0, Math.min(100, i)) / 100); - const x = percentX * encodedCostume.width; - const y = percentY * encodedCostume.height; - - currentCanvasCursor = `url("${encodedCostume.uri}") ${x} ${y}, ${nativeCursor}`; - updateCanvasCursor(); - } else { - // If for some reason the costume couldn't be encoded, we'll leave the cursor unchanged. - // This is the same behavior that would happen if we successfully encode a cursor but the browser - // is unable to parse it for some reason. - } - - customCursorImageName = costumeName; - } - - hideCur() { - this.setCur({ - cur: 'none' - }); - } - - getCur() { - if (customCursorImageName !== null) { - return customCursorImageName; - } - return nativeCursor; - } - } - - Scratch.extensions.register(new MouseCursor()); -})(Scratch); +// Name: Mouse Cursor +// ID: MouseCursor +// Description: Use custom cursors or hide the cursor. Also allows replacing the cursor with any costume image. + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("MouseCursor extension must be run unsandboxed"); + } + + const lazilyCreatedCanvas = () => { + /** @type {HTMLCanvasElement} */ + let canvas = null; + /** @type {CanvasRenderingContext2D} */ + let ctx = null; + /** + * @param {number} width + * @param {number} height + * @returns {[HTMLCanvasElement, CanvasRenderingContext2D]} + */ + return (width, height) => { + if (!canvas) { + canvas = document.createElement("canvas"); + ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("Could not get 2d rendering context"); + } + } + // Setting canvas size also clears it + canvas.width = width; + canvas.height = height; + return [canvas, ctx]; + }; + }; + const getRawSkinCanvas = lazilyCreatedCanvas(); + + /** + * @param {RenderWebGL.Skin} skin + * @returns {string} A data: URI for the skin. + */ + const encodeSkinToURL = (skin) => { + const svgSkin = /** @type {RenderWebGL.SVGSkin} */ (skin); + if (svgSkin._svgImage) { + // This is an SVG skin + return svgSkin._svgImage.src; + } + + // It's probably a bitmap skin. + // The most reliable way to get the bitmap in every runtime is through the silhouette. + // This is very slow and could involve reading the texture from the GPU. + const silhouette = skin._silhouette; + // unlazy() only exists in TW + if (silhouette.unlazy) { + silhouette.unlazy(); + } + const colorData = silhouette._colorData; + const width = silhouette._width; + const height = silhouette._height; + const imageData = new ImageData( + colorData, + silhouette._width, + silhouette._height + ); + const [canvas, ctx] = getRawSkinCanvas(width, height); + ctx.putImageData(imageData, 0, 0); + return canvas.toDataURL(); + }; + + /** + * @param {VM.Costume} costume + * @param {number} maxWidth + * @param {number} maxHeight + * @returns {{uri: string, width: number, height: number}} + */ + const costumeToCursor = (costume, maxWidth, maxHeight) => { + const skin = Scratch.vm.renderer._allSkins[costume.skinId]; + const imageURI = encodeSkinToURL(skin); + + let width = skin.size[0]; + let height = skin.size[1]; + if (width > maxWidth) { + height = height * (maxWidth / width); + width = maxWidth; + } + if (height > maxHeight) { + width = width * (maxHeight / height); + height = maxHeight; + } + width = Math.round(width); + height = Math.round(height); + + // We wrap the encoded image in an . This lets us do some clever things: + // - We can resize the image without a canvas. + // - We can give the browser an image with more raw pixels than its DPI independent size. + // The latter is important so that cursors won't look horrible on high DPI displays. For + // example, if the cursor will display at 32x32 in DPI independent units on a 2x high DPI + // display, we actually need to send a 64x64 image for it to look good. This lets us do + // that automatically. + let svg = ``; + svg += ``; + svg += ""; + // URI encoding usually results in smaller string than base 64 for the types of data we get here. + const svgURI = `data:image/svg+xml;,${encodeURIComponent(svg)}`; + + return { + uri: svgURI, + width, + height, + }; + }; + + /** @type {string} */ + let nativeCursor = "default"; + /** @type {null|string} */ + let customCursorImageName = null; + + const canvas = Scratch.renderer.canvas; + /** @type {string} */ + let currentCanvasCursor = nativeCursor; + const updateCanvasCursor = () => { + if (canvas.style.cursor !== currentCanvasCursor) { + canvas.style.cursor = currentCanvasCursor; + } + }; + + // scratch-gui will sometimes reset the cursor when resizing the window or going in/out of fullscreen + new MutationObserver(updateCanvasCursor).observe(canvas, { + attributeFilter: ["style"], + attributes: true, + }); + + /** + * Parse strings like "60x12" or "77,1" + * @param {string} string + * @returns {[number, number]} + */ + const parseTuple = (string) => { + const [a, b] = ("" + string).split(/[ ,x]/); + return [+a || 0, +b || 0]; + }; + + const cursors = [ + "default", + "pointer", + "move", + "grab", + "grabbing", + "text", + "vertical-text", + "wait", + "progress", + "help", + "context-menu", + "zoom-in", + "zoom-out", + "crosshair", + "cell", + "not-allowed", + "copy", + "alias", + "no-drop", + "all-scroll", + "col-resize", + "row-resize", + "n-resize", + "e-resize", + "s-resize", + "w-resize", + "ne-resize", + "nw-resize", + "se-resize", + "sw-resize", + "ew-resize", + "ns-resize", + "nesw-resize", + "nwse-resize", + ]; + + class MouseCursor { + getInfo() { + return { + id: "MouseCursor", + name: "Mouse Cursor", + blocks: [ + { + opcode: "setCur", + blockType: Scratch.BlockType.COMMAND, + text: "set cursor to [cur]", + arguments: { + cur: { + type: Scratch.ArgumentType.STRING, + defaultValue: "pointer", + menu: "cursors", + }, + }, + }, + { + opcode: "setCursorImage", + blockType: Scratch.BlockType.COMMAND, + text: "set cursor to current costume center: [position] max size: [size]", + arguments: { + position: { + type: Scratch.ArgumentType.STRING, + defaultValue: "0,0", + menu: "imagePositions", + }, + size: { + type: Scratch.ArgumentType.STRING, + defaultValue: "32x32", + menu: "imageSizes", + }, + }, + }, + { + opcode: "hideCur", + blockType: Scratch.BlockType.COMMAND, + text: "hide cursor", + }, + { + opcode: "getCur", + blockType: Scratch.BlockType.REPORTER, + text: "cursor", + }, + ], + menus: { + cursors: { + acceptReporters: true, + items: cursors, + }, + imagePositions: { + acceptReporters: true, + items: [ + // [x, y] where x is [0=left, 100=right] and y is [0=top, 100=bottom] + { text: "top left", value: "0,0" }, + { text: "top right", value: "100,0" }, + { text: "bottom left", value: "0,100" }, + { text: "bottom right", value: "100,100" }, + { text: "center", value: "50,50" }, + ], + }, + imageSizes: { + acceptReporters: true, + items: [ + // Some important numbers to keep in mind: + // Browsers ignore cursor images >128 in any dimension (https://searchfox.org/mozilla-central/rev/43ee5e789b079e94837a21336e9ce2420658fd19/widget/gtk/nsWindow.cpp#3393-3402) + // Browsers may refuse to display a cursor near window borders for images >32 in any dimension + { text: "4x4", value: "4x4" }, + { text: "8x8", value: "8x4" }, + { text: "12x12", value: "12x12" }, + { text: "16x16", value: "16x16" }, + { text: "32x32", value: "32x32" }, + { text: "48x48 (unreliable)", value: "48x48" }, + { text: "64x64 (unreliable)", value: "64x64" }, + { text: "128x128 (unreliable)", value: "128x128" }, + ], + }, + }, + }; + } + + setCur(args) { + const newCursor = Scratch.Cast.toString(args.cur); + // Prevent setting cursor to "url(...), default" from causing fetch. + if (cursors.includes(newCursor) || newCursor === "none") { + nativeCursor = newCursor; + customCursorImageName = null; + currentCanvasCursor = newCursor; + updateCanvasCursor(); + } + } + + setCursorImage(args, util) { + const [maxWidth, maxHeight] = parseTuple(args.size).map((i) => + Math.max(0, i) + ); + + const currentCostume = + util.target.getCostumes()[util.target.currentCostume]; + const costumeName = currentCostume.name; + + let encodedCostume; + try { + encodedCostume = costumeToCursor(currentCostume, maxWidth, maxHeight); + } catch (e) { + // This could happen for a variety of reasons. + console.error(e); + } + + if (encodedCostume) { + const [percentX, percentY] = parseTuple(args.position).map( + (i) => Math.max(0, Math.min(100, i)) / 100 + ); + const x = percentX * encodedCostume.width; + const y = percentY * encodedCostume.height; + + currentCanvasCursor = `url("${encodedCostume.uri}") ${x} ${y}, ${nativeCursor}`; + updateCanvasCursor(); + } else { + // If for some reason the costume couldn't be encoded, we'll leave the cursor unchanged. + // This is the same behavior that would happen if we successfully encode a cursor but the browser + // is unable to parse it for some reason. + } + + customCursorImageName = costumeName; + } + + hideCur() { + this.setCur({ + cur: "none", + }); + } + + getCur() { + if (customCursorImageName !== null) { + return customCursorImageName; + } + return nativeCursor; + } + } + + Scratch.extensions.register(new MouseCursor()); +})(Scratch); diff --git a/extensions/docs-examples/unsandboxed/broadcast-5.js b/extensions/docs-examples/unsandboxed/broadcast-5.js index 40c08fd40a..1e08bff12b 100644 --- a/extensions/docs-examples/unsandboxed/broadcast-5.js +++ b/extensions/docs-examples/unsandboxed/broadcast-5.js @@ -8,7 +8,7 @@ blocks: [ { opcode: 'whenReceived', - blockType: Scratch.BlockType.HAT, + blockType: Scratch.BlockType.EVENT, text: 'when I receive [EVENT_OPTION]', isEdgeActivated: false, arguments: { @@ -22,7 +22,6 @@ opcode: 'broadcast', blockType: Scratch.BlockType.REPORTER, text: 'broadcast [EVENT]', - disableMonitor: true, arguments: { EVENT: { type: Scratch.ArgumentType.STRING, diff --git a/extensions/docs-examples/unsandboxed/every-second.js b/extensions/docs-examples/unsandboxed/every-second.js index 647ff595e2..56356bcbd0 100644 --- a/extensions/docs-examples/unsandboxed/every-second.js +++ b/extensions/docs-examples/unsandboxed/every-second.js @@ -10,13 +10,7 @@ opcode: 'everySecond', blockType: Scratch.BlockType.HAT, text: 'every second', - isEdgeActivated: false, - arguments: { - EVENT_OPTION: { - type: Scratch.ArgumentType.STRING, - menu: 'EVENT_FIELD' - } - } + isEdgeActivated: false } ] }; diff --git a/extensions/docs-examples/unsandboxed/when-key-pressed-restart.js b/extensions/docs-examples/unsandboxed/when-key-pressed-restart.js new file mode 100644 index 0000000000..6f909400e6 --- /dev/null +++ b/extensions/docs-examples/unsandboxed/when-key-pressed-restart.js @@ -0,0 +1,55 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('This example must run unsandboxed'); + } + + class WhenKeyPressed { + getInfo() { + return { + id: 'restartexampleunsandboxed', + name: 'Restart Threads Example', + blocks: [ + { + blockType: Scratch.BlockType.EVENT, + opcode: 'whenPressed', + text: 'when [KEY] key pressed', + isEdgeActivated: false, + // highlight-next-line + shouldRestartExistingThreads: true, + arguments: { + KEY: { + type: Scratch.ArgumentType.STRING, + menu: 'key' + } + } + } + ], + menus: { + key: { + acceptReporters: false, + items: [ + { + text: 'space', + value: ' ' + }, + 'a', + 'b', + 'c', + // ... + ] + } + } + }; + } + } + + document.addEventListener('keydown', (e) => { + Scratch.vm.runtime.startHats('restartexampleunsandboxed_whenPressed', { + KEY: e.key + }); + }); + + Scratch.extensions.register(new WhenKeyPressed()); +})(Scratch); diff --git a/extensions/docs-examples/unsandboxed/when-key-pressed-stage.js b/extensions/docs-examples/unsandboxed/when-key-pressed-stage.js new file mode 100644 index 0000000000..ee22bbe9f4 --- /dev/null +++ b/extensions/docs-examples/unsandboxed/when-key-pressed-stage.js @@ -0,0 +1,54 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('This example must run unsandboxed'); + } + + class WhenKeyPressedInStage { + getInfo() { + return { + id: 'eventexample3unsandboxed', + name: 'Event Block Example 3', + blocks: [ + { + blockType: Scratch.BlockType.EVENT, + opcode: 'whenPressed', + text: 'when [KEY] key pressed', + isEdgeActivated: false, + arguments: { + KEY: { + type: Scratch.ArgumentType.STRING, + menu: 'key' + } + } + } + ], + menus: { + key: { + acceptReporters: false, + items: [ + { + text: 'space', + value: ' ' + }, + 'a', + 'b', + 'c', + // ... + ] + } + } + }; + } + } + + document.addEventListener('keydown', (e) => { + Scratch.vm.runtime.startHats('eventexample3unsandboxed_whenPressed', { + KEY: e.key + // highlight-next-line + }, Scratch.vm.runtime.getTargetForStage()); + }); + + Scratch.extensions.register(new WhenKeyPressedInStage()); +})(Scratch); diff --git a/extensions/docs-examples/unsandboxed/when-key-pressed.js b/extensions/docs-examples/unsandboxed/when-key-pressed.js new file mode 100644 index 0000000000..e91b5103b3 --- /dev/null +++ b/extensions/docs-examples/unsandboxed/when-key-pressed.js @@ -0,0 +1,58 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('This example must run unsandboxed'); + } + + class WhenKeyPressed { + getInfo() { + return { + id: 'eventexample2unsandboxed', + name: 'Event Block Example 2', + blocks: [ + { + blockType: Scratch.BlockType.EVENT, + opcode: 'whenPressed', + text: 'when [KEY] key pressed', + isEdgeActivated: false, // required boilerplate + // highlight-start + arguments: { + KEY: { + type: Scratch.ArgumentType.STRING, + menu: 'key' + } + } + // highlight-end + } + ], + menus: { + key: { + acceptReporters: false, + items: [ + { + // startHats filters by *value*, not by text + text: 'space', + value: ' ' + }, + 'a', + 'b', + 'c', + // ... + ] + } + } + }; + } + } + + document.addEventListener('keydown', (e) => { + // highlight-start + Scratch.vm.runtime.startHats('eventexample2unsandboxed_whenPressed', { + KEY: e.key + }); + // highlight-end + }); + + Scratch.extensions.register(new WhenKeyPressed()); +})(Scratch); diff --git a/extensions/docs-examples/unsandboxed/when-space-key-pressed.js b/extensions/docs-examples/unsandboxed/when-space-key-pressed.js new file mode 100644 index 0000000000..da2d5f17ea --- /dev/null +++ b/extensions/docs-examples/unsandboxed/when-space-key-pressed.js @@ -0,0 +1,37 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('This example must run unsandboxed'); + } + + class WhenSpaceKeyPressed { + getInfo() { + return { + id: 'eventexampleunsandboxed', + name: 'Event Block Example', + blocks: [ + // highlight-start + { + blockType: Scratch.BlockType.EVENT, + opcode: 'whenSpacePressed', + text: 'when space key pressed', + isEdgeActivated: false // required boilerplate + } + // highlight-end + ] + }; + } + // Notice: whenSpacePressed does not have a function defined! + } + + // highlight-start + document.addEventListener('keydown', (e) => { + if (e.key === ' ') { + Scratch.vm.runtime.startHats('eventexampleunsandboxed_whenSpacePressed'); + } + }); + // highlight-end + + Scratch.extensions.register(new WhenSpaceKeyPressed()); +})(Scratch); diff --git a/extensions/docs-examples/unsandboxed/when.js b/extensions/docs-examples/unsandboxed/when.js new file mode 100644 index 0000000000..ea6fa2f2c3 --- /dev/null +++ b/extensions/docs-examples/unsandboxed/when.js @@ -0,0 +1,45 @@ +(function(Scratch) { + 'use strict'; + + if (!Scratch.extensions.unsandboxed) { + throw new Error('This example must run unsandboxed'); + } + + class When { + getInfo() { + return { + id: 'whenunsandboxed', + name: 'When', + blocks: [ + { + // highlight-start + blockType: Scratch.BlockType.HAT, + opcode: 'when', + text: 'when [CONDITION]', + isEdgeActivated: false, // required boilerplate + arguments: { + CONDITION: { + type: Scratch.BlockType.BOOLEAN + } + } + // highlight-end + } + ] + }; + } + // highlight-start + when(args) { + return Scratch.Cast.toBoolean(args.CONDITION); + } + // highlight-end + } + + // highlight-start + Scratch.vm.runtime.on('BEFORE_EXECUTE', () => { + // startHats is the same as before! + Scratch.vm.runtime.startHats('whenunsandboxed_when'); + }); + // highlight-end + + Scratch.extensions.register(new When()); +})(Scratch); diff --git a/extensions/encoding.js b/extensions/encoding.js index 9613e965c7..f506508052 100644 --- a/extensions/encoding.js +++ b/extensions/encoding.js @@ -1,618 +1,634 @@ -(function (Scratch) { - 'use strict'; - const icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMzcuNzk1MDYiIGhlaWdodD0iMTM0LjIzNzA3IiB2aWV3Qm94PSIwLDAsMTM3Ljc5NTA2LDEzNC4yMzcwNyI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1Mi44OTU4NiwtMTMwLjM3OTg5KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0xOTkuMzA5MDgsMjE5LjYyMDExdi03OS4yNDAyMmg4MS4zODE4NHY3OS4yNDAyMnoiLz48cGF0aCBkPSJNMTYyLjg5NTg2LDI1NC42MTY5NnYtNzkuMjQwMjJoODEuMzgxODR2NzkuMjQwMjJ6Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6ODcuMTA0MTQwMTg0NTE2NDQ6NDkuNjIwMTA4MzQwNzA3OTYtLT4='; - const icon2 = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS44ODUzOSIgaGVpZ2h0PSI4MC42MDMwNyIgdmlld0JveD0iMCwwLDgxLjg4NTM5LDgwLjYwMzA3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5LjA1NzMsLTEzOS42OTg0NikiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC45NDI3LDE4MGMwLDIyLjI1NzkyIC0xOC4zMzA2Nyw0MC4zMDE1NCAtNDAuOTQyNyw0MC4zMDE1NGMtMjIuNjEyMDMsMCAtNDAuOTQyNywtMTguMDQzNjEgLTQwLjk0MjcsLTQwLjMwMTU0YzAsLTIyLjI1NzkyIDE4LjMzMDY3LC00MC4zMDE1NCA0MC45NDI3LC00MC4zMDE1NGMyMi42MTIwMywwIDQwLjk0MjcsMTguMDQzNjEgNDAuOTQyNyw0MC4zMDE1NHoiIGZpbGw9IiM2NDk1ZWQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIi8+PHBhdGggZD0iTTIzMS44MTg3NiwxODcuOTc2MDh2LTI4Ljc2NzE1aDI5LjczNDExdjI4Ljc2NzE1eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNMjE4LjQ0NzEzLDIwMC43OTEwN3YtMjguNzY3MTVoMjkuNzM0MTF2MjguNzY3MTV6IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNiIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjk0MjY5NjA1MzgwMTE0OjQwLjMwMTUzNTI2NTQ4NjcwNi0tPg=='; - - /*! - This md5 function is based on https://github.com/blueimp/JavaScript-MD5/blob/master/js/md5.js - which is licensed under: - - MIT License - - Copyright © 2011 Sebastian Tschan, https://blueimp.net - - Permission is hereby granted, free of charge, to any person obtaining a copy of - this software and associated documentation files (the "Software"), to deal in - the Software without restriction, including without limitation the rights to - use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - /* eslint-disable */ - const md5 = (function () { - /** - * Add integers, wrapping at 2^32. - * This uses 16-bit operations internally to work around bugs in interpreters. - * - * @param {number} x First integer - * @param {number} y Second integer - * @returns {number} Sum - */ - function safeAdd(x, y) { - var lsw = (x & 0xffff) + (y & 0xffff) - var msw = (x >> 16) + (y >> 16) + (lsw >> 16) - return (msw << 16) | (lsw & 0xffff) - } - - /** - * Bitwise rotate a 32-bit number to the left. - * - * @param {number} num 32-bit number - * @param {number} cnt Rotation count - * @returns {number} Rotated number - */ - function bitRotateLeft(num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)) - } - - /** - * Basic operation the algorithm uses. - * - * @param {number} q q - * @param {number} a a - * @param {number} b b - * @param {number} x x - * @param {number} s s - * @param {number} t t - * @returns {number} Result - */ - function md5cmn(q, a, b, x, s, t) { - return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) - } - /** - * Basic operation the algorithm uses. - * - * @param {number} a a - * @param {number} b b - * @param {number} c c - * @param {number} d d - * @param {number} x x - * @param {number} s s - * @param {number} t t - * @returns {number} Result - */ - function md5ff(a, b, c, d, x, s, t) { - return md5cmn((b & c) | (~b & d), a, b, x, s, t) - } - /** - * Basic operation the algorithm uses. - * - * @param {number} a a - * @param {number} b b - * @param {number} c c - * @param {number} d d - * @param {number} x x - * @param {number} s s - * @param {number} t t - * @returns {number} Result - */ - function md5gg(a, b, c, d, x, s, t) { - return md5cmn((b & d) | (c & ~d), a, b, x, s, t) - } - /** - * Basic operation the algorithm uses. - * - * @param {number} a a - * @param {number} b b - * @param {number} c c - * @param {number} d d - * @param {number} x x - * @param {number} s s - * @param {number} t t - * @returns {number} Result - */ - function md5hh(a, b, c, d, x, s, t) { - return md5cmn(b ^ c ^ d, a, b, x, s, t) - } - /** - * Basic operation the algorithm uses. - * - * @param {number} a a - * @param {number} b b - * @param {number} c c - * @param {number} d d - * @param {number} x x - * @param {number} s s - * @param {number} t t - * @returns {number} Result - */ - function md5ii(a, b, c, d, x, s, t) { - return md5cmn(c ^ (b | ~d), a, b, x, s, t) - } - - /** - * Calculate the MD5 of an array of little-endian words, and a bit length. - * - * @param {Array} x Array of little-endian words - * @param {number} len Bit length - * @returns {Array} MD5 Array - */ - function binlMD5(x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << len % 32 - x[(((len + 64) >>> 9) << 4) + 14] = len - - var i - var olda - var oldb - var oldc - var oldd - var a = 1732584193 - var b = -271733879 - var c = -1732584194 - var d = 271733878 - - for (i = 0; i < x.length; i += 16) { - olda = a - oldb = b - oldc = c - oldd = d - - a = md5ff(a, b, c, d, x[i], 7, -680876936) - d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) - c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) - b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) - a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) - d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) - c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) - b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) - a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) - d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) - c = md5ff(c, d, a, b, x[i + 10], 17, -42063) - b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) - a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) - d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) - c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) - b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) - - a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) - d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) - c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) - b = md5gg(b, c, d, a, x[i], 20, -373897302) - a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) - d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) - c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) - b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) - a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) - d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) - c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) - b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) - a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) - d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) - c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) - b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) - - a = md5hh(a, b, c, d, x[i + 5], 4, -378558) - d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) - c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) - b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) - a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) - d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) - c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) - b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) - a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) - d = md5hh(d, a, b, c, x[i], 11, -358537222) - c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) - b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) - a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) - d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) - c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) - b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) - - a = md5ii(a, b, c, d, x[i], 6, -198630844) - d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) - c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) - b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) - a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) - d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) - c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) - b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) - a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) - d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) - c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) - b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) - a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) - d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) - c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) - b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) - - a = safeAdd(a, olda) - b = safeAdd(b, oldb) - c = safeAdd(c, oldc) - d = safeAdd(d, oldd) - } - return [a, b, c, d] - } - - /** - * Convert an array of little-endian words to a string - * - * @param {Array} input MD5 Array - * @returns {string} MD5 string - */ - function binl2rstr(input) { - var i - var output = '' - var length32 = input.length * 32 - for (i = 0; i < length32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff) - } - return output - } - - /** - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - * - * @param {string} input Raw input string - * @returns {Array} Array of little-endian words - */ - function rstr2binl(input) { - var i - var output = [] - output[(input.length >> 2) - 1] = undefined - for (i = 0; i < output.length; i += 1) { - output[i] = 0 - } - var length8 = input.length * 8 - for (i = 0; i < length8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32 - } - return output - } - - /** - * Calculate the MD5 of a raw string - * - * @param {string} s Input string - * @returns {string} Raw MD5 string - */ - function rstrMD5(s) { - return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) - } - - /** - * Calculates the HMAC-MD5 of a key and some data (raw strings) - * - * @param {string} key HMAC key - * @param {string} data Raw input string - * @returns {string} Raw MD5 string - */ - function rstrHMACMD5(key, data) { - var i - var bkey = rstr2binl(key) - var ipad = [] - var opad = [] - var hash - ipad[15] = opad[15] = undefined - if (bkey.length > 16) { - bkey = binlMD5(bkey, key.length * 8) - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636 - opad[i] = bkey[i] ^ 0x5c5c5c5c - } - hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) - return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) - } - - /** - * Convert a raw string to a hex string - * - * @param {string} input Raw input string - * @returns {string} Hex encoded string - */ - function rstr2hex(input) { - var hexTab = '0123456789abcdef' - var output = '' - var x - var i - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i) - output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f) - } - return output - } - - /** - * Encode a string as UTF-8 - * - * @param {string} input Input string - * @returns {string} UTF8 string - */ - function str2rstrUTF8(input) { - return unescape(encodeURIComponent(input)) - } - - /** - * Encodes input string as raw MD5 string - * - * @param {string} s Input string - * @returns {string} Raw MD5 string - */ - function rawMD5(s) { - return rstrMD5(str2rstrUTF8(s)) - } - /** - * Encodes input string as Hex encoded string - * - * @param {string} s Input string - * @returns {string} Hex encoded string - */ - function hexMD5(s) { - return rstr2hex(rawMD5(s)) - } - /** - * Calculates the raw HMAC-MD5 for the given key and data - * - * @param {string} k HMAC key - * @param {string} d Input string - * @returns {string} Raw MD5 string - */ - function rawHMACMD5(k, d) { - return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) - } - /** - * Calculates the Hex encoded HMAC-MD5 for the given key and data - * - * @param {string} k HMAC key - * @param {string} d Input string - * @returns {string} Raw MD5 string - */ - function hexHMACMD5(k, d) { - return rstr2hex(rawHMACMD5(k, d)) - } - - /** - * Calculates MD5 value for a given string. - * If a key is provided, calculates the HMAC-MD5 value. - * Returns a Hex encoded string unless the raw argument is given. - * - * @param {string} string Input string - * @param {string} [key] HMAC key - * @param {boolean} [raw] Raw output switch - * @returns {string} MD5 output - */ - function md5(string, key, raw) { - if (!key) { - if (!raw) { - return hexMD5(string) - } - return rawMD5(string) - } - if (!raw) { - return hexHMACMD5(key, string) - } - return rawHMACMD5(key, string) - } - - return md5; - })(); - /* eslint-enable */ - - class Encoding { - getInfo() { - return { - id: 'Encoding', - name: 'Encoding', - color1: '#6495ed', - color2: '#739fee', - color3: '#83aaf0', - menuIconURI: icon2, - blockIconURI: icon, - blocks: [ - { - opcode: 'encode', - blockType: Scratch.BlockType.REPORTER, - text: 'Encode [string] in [code]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - code: { - type: Scratch.ArgumentType.STRING, - menu: 'encode', - defaultValue: 'Base64' - } - } - }, - { - opcode: 'decode', - blockType: Scratch.BlockType.REPORTER, - text: 'Decode [string] with [code]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'VHVyYm9XYXJw' - }, - code: { - type: Scratch.ArgumentType.STRING, - menu: 'encode', - defaultValue: 'Base64' - } - } - }, - { - opcode: 'hash', - blockType: Scratch.BlockType.REPORTER, - text: 'Hash [string] with [hash]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'apple' - }, - hash: { - type: Scratch.ArgumentType.STRING, - menu: 'hash', - defaultValue: 'MD5' - } - } - }, - - '---', - - { - opcode: 'Conversioncodes', - blockType: Scratch.BlockType.REPORTER, - text: 'Convert the character [string] to [CodeList]', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: 'A' - }, - CodeList: { - type: Scratch.ArgumentType.STRING, - menu: 'Code', - defaultValue: 'UNICODE' - } - } - }, - { - opcode: 'Restorecode', - blockType: Scratch.BlockType.REPORTER, - text: '[string] corresponding to the [CodeList] character', - arguments: { - string: { - type: Scratch.ArgumentType.STRING, - defaultValue: '65' - }, - CodeList: { - type: Scratch.ArgumentType.STRING, - menu: 'Code', - defaultValue: 'UNICODE' - } - } - }, - - '---', - - { - opcode: 'Randomstrings', - blockType: Scratch.BlockType.REPORTER, - text: 'Randomly generated [position] character string', - arguments: { - position: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '8' - } - } - }, - { - opcode: 'Fontgenerationstring', - blockType: Scratch.BlockType.REPORTER, - text: 'Use [wordbank] to generate a random [position] character string', - arguments: { - wordbank: { - type: Scratch.ArgumentType.STRING, - defaultValue: '1234567890' - }, - position: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: '8' - } - } - }, - ], - menus: { - Code: { - acceptReporters: true, - items: [ - { - text: 'Unicode', - value: 'UNICODE' - } - ] - }, - encode: { - acceptReporters: true, - items: [ - { - text: 'Base 64', - value: 'Base64' - }, - 'URL' - ] - }, - hash: { - acceptReporters: true, - items: ['MD5'] - } - } - }; - } - encode({ string, code }) { - string = Scratch.Cast.toString(string); - switch (code) { - case 'Base64': return btoa(string); - case 'URL': return encodeURIComponent(string); - } - return ''; - } - decode({ string, code }) { - string = Scratch.Cast.toString(string); - switch (code) { - case 'Base64': - try { - return atob(string); - } catch (error) { - console.error('invalid base 64', error); - return ''; - } - case 'URL': return decodeURIComponent(string); - } - return ''; - } - hash({ string, hash }) { - string = Scratch.Cast.toString(string); - switch (hash) { - case 'MD5': return md5(string); - } - return ''; - } - Conversioncodes({ string, CodeList }) { - string = Scratch.Cast.toString(string); - switch (CodeList) { - case 'UNICODE': return string.charCodeAt(0); - } - return 0; - } - Restorecode({ string, CodeList}) { - switch (CodeList) { - case 'UNICODE': return String.fromCharCode(string); - } - return ''; - } - Randomstrings({ position }) { - position = Scratch.Cast.toNumber(position) || 32; - let t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; - let a = t.length; - let string = ''; - for (let i = 0; i < position; i++) { - string += t.charAt(Math.floor(Math.random() * a)); - } - return string; - } - Fontgenerationstring({ wordbank, position }) { - position = Scratch.Cast.toNumber(position) || 32; - let t = String(wordbank); - let a = t.length; - let string = ''; - for (let i = 0; i < position; i++) { - string += t.charAt(Math.floor(Math.random() * a)); - } - return string; - } - } - Scratch.extensions.register(new Encoding()); -})(Scratch); +// Name: Encoding +// ID: Encoding +// Description: Encode and decode strings into their unicode numbers, base 64, or URLs. +// By: -SIPC- + +(function (Scratch) { + "use strict"; + const icon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMzcuNzk1MDYiIGhlaWdodD0iMTM0LjIzNzA3IiB2aWV3Qm94PSIwLDAsMTM3Ljc5NTA2LDEzNC4yMzcwNyI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1Mi44OTU4NiwtMTMwLjM3OTg5KSI+PGcgZGF0YS1wYXBlci1kYXRhPSJ7JnF1b3Q7aXNQYWludGluZ0xheWVyJnF1b3Q7OnRydWV9IiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiIHN0cm9rZS1saW5lam9pbj0ibWl0ZXIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLWRhc2hhcnJheT0iIiBzdHJva2UtZGFzaG9mZnNldD0iMCIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0xOTkuMzA5MDgsMjE5LjYyMDExdi03OS4yNDAyMmg4MS4zODE4NHY3OS4yNDAyMnoiLz48cGF0aCBkPSJNMTYyLjg5NTg2LDI1NC42MTY5NnYtNzkuMjQwMjJoODEuMzgxODR2NzkuMjQwMjJ6Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6ODcuMTA0MTQwMTg0NTE2NDQ6NDkuNjIwMTA4MzQwNzA3OTYtLT4="; + const icon2 = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS44ODUzOSIgaGVpZ2h0PSI4MC42MDMwNyIgdmlld0JveD0iMCwwLDgxLjg4NTM5LDgwLjYwMzA3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5LjA1NzMsLTEzOS42OTg0NikiPjxnIGRhdGEtcGFwZXItZGF0YT0ieyZxdW90O2lzUGFpbnRpbmdMYXllciZxdW90Ozp0cnVlfSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC45NDI3LDE4MGMwLDIyLjI1NzkyIC0xOC4zMzA2Nyw0MC4zMDE1NCAtNDAuOTQyNyw0MC4zMDE1NGMtMjIuNjEyMDMsMCAtNDAuOTQyNywtMTguMDQzNjEgLTQwLjk0MjcsLTQwLjMwMTU0YzAsLTIyLjI1NzkyIDE4LjMzMDY3LC00MC4zMDE1NCA0MC45NDI3LC00MC4zMDE1NGMyMi42MTIwMywwIDQwLjk0MjcsMTguMDQzNjEgNDAuOTQyNyw0MC4zMDE1NHoiIGZpbGw9IiM2NDk1ZWQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIwIi8+PHBhdGggZD0iTTIzMS44MTg3NiwxODcuOTc2MDh2LTI4Ljc2NzE1aDI5LjczNDExdjI4Ljc2NzE1eiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjYiLz48cGF0aCBkPSJNMjE4LjQ0NzEzLDIwMC43OTEwN3YtMjguNzY3MTVoMjkuNzM0MTF2MjguNzY3MTV6IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNiIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjk0MjY5NjA1MzgwMTE0OjQwLjMwMTUzNTI2NTQ4NjcwNi0tPg=="; + + /*! + This md5 function is based on https://github.com/blueimp/JavaScript-MD5/blob/master/js/md5.js + which is licensed under: + + MIT License + + Copyright © 2011 Sebastian Tschan, https://blueimp.net + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + /* eslint-disable */ + const md5 = (function () { + /** + * Add integers, wrapping at 2^32. + * This uses 16-bit operations internally to work around bugs in interpreters. + * + * @param {number} x First integer + * @param {number} y Second integer + * @returns {number} Sum + */ + function safeAdd(x, y) { + var lsw = (x & 0xffff) + (y & 0xffff); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); + } + + /** + * Bitwise rotate a 32-bit number to the left. + * + * @param {number} num 32-bit number + * @param {number} cnt Rotation count + * @returns {number} Rotated number + */ + function bitRotateLeft(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); + } + + /** + * Basic operation the algorithm uses. + * + * @param {number} q q + * @param {number} a a + * @param {number} b b + * @param {number} x x + * @param {number} s s + * @param {number} t t + * @returns {number} Result + */ + function md5cmn(q, a, b, x, s, t) { + return safeAdd( + bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), + b + ); + } + /** + * Basic operation the algorithm uses. + * + * @param {number} a a + * @param {number} b b + * @param {number} c c + * @param {number} d d + * @param {number} x x + * @param {number} s s + * @param {number} t t + * @returns {number} Result + */ + function md5ff(a, b, c, d, x, s, t) { + return md5cmn((b & c) | (~b & d), a, b, x, s, t); + } + /** + * Basic operation the algorithm uses. + * + * @param {number} a a + * @param {number} b b + * @param {number} c c + * @param {number} d d + * @param {number} x x + * @param {number} s s + * @param {number} t t + * @returns {number} Result + */ + function md5gg(a, b, c, d, x, s, t) { + return md5cmn((b & d) | (c & ~d), a, b, x, s, t); + } + /** + * Basic operation the algorithm uses. + * + * @param {number} a a + * @param {number} b b + * @param {number} c c + * @param {number} d d + * @param {number} x x + * @param {number} s s + * @param {number} t t + * @returns {number} Result + */ + function md5hh(a, b, c, d, x, s, t) { + return md5cmn(b ^ c ^ d, a, b, x, s, t); + } + /** + * Basic operation the algorithm uses. + * + * @param {number} a a + * @param {number} b b + * @param {number} c c + * @param {number} d d + * @param {number} x x + * @param {number} s s + * @param {number} t t + * @returns {number} Result + */ + function md5ii(a, b, c, d, x, s, t) { + return md5cmn(c ^ (b | ~d), a, b, x, s, t); + } + + /** + * Calculate the MD5 of an array of little-endian words, and a bit length. + * + * @param {Array} x Array of little-endian words + * @param {number} len Bit length + * @returns {Array} MD5 Array + */ + function binlMD5(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << len % 32; + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var i; + var olda; + var oldb; + var oldc; + var oldd; + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for (i = 0; i < x.length; i += 16) { + olda = a; + oldb = b; + oldc = c; + oldd = d; + + a = md5ff(a, b, c, d, x[i], 7, -680876936); + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); + + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5gg(b, c, d, a, x[i], 20, -373897302); + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); + + a = md5hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5hh(d, a, b, c, x[i], 11, -358537222); + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); + + a = md5ii(a, b, c, d, x[i], 6, -198630844); + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); + + a = safeAdd(a, olda); + b = safeAdd(b, oldb); + c = safeAdd(c, oldc); + d = safeAdd(d, oldd); + } + return [a, b, c, d]; + } + + /** + * Convert an array of little-endian words to a string + * + * @param {Array} input MD5 Array + * @returns {string} MD5 string + */ + function binl2rstr(input) { + var i; + var output = ""; + var length32 = input.length * 32; + for (i = 0; i < length32; i += 8) { + output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); + } + return output; + } + + /** + * Convert a raw string to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + * + * @param {string} input Raw input string + * @returns {Array} Array of little-endian words + */ + function rstr2binl(input) { + var i; + var output = []; + output[(input.length >> 2) - 1] = undefined; + for (i = 0; i < output.length; i += 1) { + output[i] = 0; + } + var length8 = input.length * 8; + for (i = 0; i < length8; i += 8) { + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; + } + return output; + } + + /** + * Calculate the MD5 of a raw string + * + * @param {string} s Input string + * @returns {string} Raw MD5 string + */ + function rstrMD5(s) { + return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); + } + + /** + * Calculates the HMAC-MD5 of a key and some data (raw strings) + * + * @param {string} key HMAC key + * @param {string} data Raw input string + * @returns {string} Raw MD5 string + */ + function rstrHMACMD5(key, data) { + var i; + var bkey = rstr2binl(key); + var ipad = []; + var opad = []; + var hash; + ipad[15] = opad[15] = undefined; + if (bkey.length > 16) { + bkey = binlMD5(bkey, key.length * 8); + } + for (i = 0; i < 16; i += 1) { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5c5c5c5c; + } + hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); + return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)); + } + + /** + * Convert a raw string to a hex string + * + * @param {string} input Raw input string + * @returns {string} Hex encoded string + */ + function rstr2hex(input) { + var hexTab = "0123456789abcdef"; + var output = ""; + var x; + var i; + for (i = 0; i < input.length; i += 1) { + x = input.charCodeAt(i); + output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f); + } + return output; + } + + /** + * Encode a string as UTF-8 + * + * @param {string} input Input string + * @returns {string} UTF8 string + */ + function str2rstrUTF8(input) { + return unescape(encodeURIComponent(input)); + } + + /** + * Encodes input string as raw MD5 string + * + * @param {string} s Input string + * @returns {string} Raw MD5 string + */ + function rawMD5(s) { + return rstrMD5(str2rstrUTF8(s)); + } + /** + * Encodes input string as Hex encoded string + * + * @param {string} s Input string + * @returns {string} Hex encoded string + */ + function hexMD5(s) { + return rstr2hex(rawMD5(s)); + } + /** + * Calculates the raw HMAC-MD5 for the given key and data + * + * @param {string} k HMAC key + * @param {string} d Input string + * @returns {string} Raw MD5 string + */ + function rawHMACMD5(k, d) { + return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)); + } + /** + * Calculates the Hex encoded HMAC-MD5 for the given key and data + * + * @param {string} k HMAC key + * @param {string} d Input string + * @returns {string} Raw MD5 string + */ + function hexHMACMD5(k, d) { + return rstr2hex(rawHMACMD5(k, d)); + } + + /** + * Calculates MD5 value for a given string. + * If a key is provided, calculates the HMAC-MD5 value. + * Returns a Hex encoded string unless the raw argument is given. + * + * @param {string} string Input string + * @param {string} [key] HMAC key + * @param {boolean} [raw] Raw output switch + * @returns {string} MD5 output + */ + function md5(string, key, raw) { + if (!key) { + if (!raw) { + return hexMD5(string); + } + return rawMD5(string); + } + if (!raw) { + return hexHMACMD5(key, string); + } + return rawHMACMD5(key, string); + } + + return md5; + })(); + /* eslint-enable */ + + class Encoding { + getInfo() { + return { + id: "Encoding", + name: "Encoding", + color1: "#6495ed", + color2: "#739fee", + color3: "#83aaf0", + menuIconURI: icon2, + blockIconURI: icon, + blocks: [ + { + opcode: "encode", + blockType: Scratch.BlockType.REPORTER, + text: "Encode [string] in [code]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + code: { + type: Scratch.ArgumentType.STRING, + menu: "encode", + defaultValue: "Base64", + }, + }, + }, + { + opcode: "decode", + blockType: Scratch.BlockType.REPORTER, + text: "Decode [string] with [code]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "VHVyYm9XYXJw", + }, + code: { + type: Scratch.ArgumentType.STRING, + menu: "encode", + defaultValue: "Base64", + }, + }, + }, + { + opcode: "hash", + blockType: Scratch.BlockType.REPORTER, + text: "Hash [string] with [hash]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "apple", + }, + hash: { + type: Scratch.ArgumentType.STRING, + menu: "hash", + defaultValue: "MD5", + }, + }, + }, + + "---", + + { + opcode: "Conversioncodes", + blockType: Scratch.BlockType.REPORTER, + text: "Convert the character [string] to [CodeList]", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "A", + }, + CodeList: { + type: Scratch.ArgumentType.STRING, + menu: "Code", + defaultValue: "UNICODE", + }, + }, + }, + { + opcode: "Restorecode", + blockType: Scratch.BlockType.REPORTER, + text: "[string] corresponding to the [CodeList] character", + arguments: { + string: { + type: Scratch.ArgumentType.STRING, + defaultValue: "65", + }, + CodeList: { + type: Scratch.ArgumentType.STRING, + menu: "Code", + defaultValue: "UNICODE", + }, + }, + }, + + "---", + + { + opcode: "Randomstrings", + blockType: Scratch.BlockType.REPORTER, + text: "Randomly generated [position] character string", + arguments: { + position: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "8", + }, + }, + }, + { + opcode: "Fontgenerationstring", + blockType: Scratch.BlockType.REPORTER, + text: "Use [wordbank] to generate a random [position] character string", + arguments: { + wordbank: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1234567890", + }, + position: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "8", + }, + }, + }, + ], + menus: { + Code: { + acceptReporters: true, + items: [ + { + text: "Unicode", + value: "UNICODE", + }, + ], + }, + encode: { + acceptReporters: true, + items: [ + { + text: "Base 64", + value: "Base64", + }, + "URL", + ], + }, + hash: { + acceptReporters: true, + items: ["MD5"], + }, + }, + }; + } + encode({ string, code }) { + string = Scratch.Cast.toString(string); + switch (code) { + case "Base64": + return btoa(string); + case "URL": + return encodeURIComponent(string); + } + return ""; + } + decode({ string, code }) { + string = Scratch.Cast.toString(string); + switch (code) { + case "Base64": + try { + return atob(string); + } catch (error) { + console.error("invalid base 64", error); + return ""; + } + case "URL": + return decodeURIComponent(string); + } + return ""; + } + hash({ string, hash }) { + string = Scratch.Cast.toString(string); + switch (hash) { + case "MD5": + return md5(string); + } + return ""; + } + Conversioncodes({ string, CodeList }) { + string = Scratch.Cast.toString(string); + switch (CodeList) { + case "UNICODE": + return string.charCodeAt(0); + } + return 0; + } + Restorecode({ string, CodeList }) { + switch (CodeList) { + case "UNICODE": + return String.fromCharCode(string); + } + return ""; + } + Randomstrings({ position }) { + position = Scratch.Cast.toNumber(position) || 32; + let t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; + let a = t.length; + let string = ""; + for (let i = 0; i < position; i++) { + string += t.charAt(Math.floor(Math.random() * a)); + } + return string; + } + Fontgenerationstring({ wordbank, position }) { + position = Scratch.Cast.toNumber(position) || 32; + let t = String(wordbank); + let a = t.length; + let string = ""; + for (let i = 0; i < position; i++) { + string += t.charAt(Math.floor(Math.random() * a)); + } + return string; + } + } + Scratch.extensions.register(new Encoding()); +})(Scratch); diff --git a/extensions/extensions.json b/extensions/extensions.json new file mode 100644 index 0000000000..6418f9577d --- /dev/null +++ b/extensions/extensions.json @@ -0,0 +1,75 @@ +[ + "lab/text", + "stretch", + "gamepad", + "box2d", + "files", + "pointerlock", + "cursor", + "runtime-options", + "fetch", + "text", + "local-storage", + "true-fantom/base", + "bitwise", + "Skyhigh173/bigint", + "utilities", + "sound", + "Xeltalliv/clippingblending", + "clipboard", + "penplus", + "Lily/Skins", + "obviousAlexC/SensingPlus", + "Lily/ClonesPlus", + "Lily/LooksPlus", + "Lily/MoreEvents", + "NexusKitten/moremotion", + "navigator", + "battery", + "TheShovel/CustomStyles", + "NexusKitten/controlcontrols", + "mdwalters/notifications", + "XeroName/Deltatime", + "ar", + "encoding", + "Lily/TempVariables2", + "Lily/MoreTimers", + "clouddata-ping", + "cloudlink", + "true-fantom/network", + "true-fantom/math", + "true-fantom/regexp", + "true-fantom/couplers", + "Lily/AllMenus", + "Lily/Cast", + "-SIPC-/time", + "-SIPC-/consoles", + "ZXMushroom63/searchApi", + "TheShovel/ShovelUtils", + "DNin/wake-lock", + "Skyhigh173/json", + "cs2627883/numericalencoding", + "DT/cameracontrols", + "TheShovel/CanvasEffects", + "Longboost/color_channels", + "CST1229/zip", + "TheShovel/LZ-String", + "0832/rxFS2", + "NexusKitten/sgrab", + "NOname-awa/graphics2d", + "NOname-awa/more-comparisons", + "JeremyGamer13/tween", + "rixxyx", + "qxsck/data-analysis", + "qxsck/var-and-list", + "vercte/dictionaries", + "godslayerakp/http", + "Lily/CommentBlocks", + "veggiecan/LongmanDictionary", + "CubesterYT/TurboHook", + "Alestore/nfcwarp", + "itchio", + "gamejolt", + "obviousAlexC/newgroundsIO", + "Lily/McUtils" +] diff --git a/extensions/fetch.js b/extensions/fetch.js index 0340ad3aec..10ff8e13e4 100644 --- a/extensions/fetch.js +++ b/extensions/fetch.js @@ -1,31 +1,35 @@ -(function(Scratch) { - 'use strict'; +// Name: Fetch +// ID: fetch +// Description: Make requests to the broader internet. + +(function (Scratch) { + "use strict"; class Fetch { - getInfo () { + getInfo() { return { - id: 'fetch', - name: 'Fetch', + id: "fetch", + name: "Fetch", blocks: [ { - opcode: 'get', + opcode: "get", blockType: Scratch.BlockType.REPORTER, - text: 'GET [URL]', + text: "GET [URL]", arguments: { URL: { type: Scratch.ArgumentType.STRING, - defaultValue: 'https://extensions.turbowarp.org/hello.txt' - } - } - } - ] + defaultValue: "https://extensions.turbowarp.org/hello.txt", + }, + }, + }, + ], }; } - get (args) { + get(args) { return Scratch.fetch(args.URL) - .then(r => r.text()) - .catch(() => ''); + .then((r) => r.text()) + .catch(() => ""); } } diff --git a/extensions/files.js b/extensions/files.js index 2d1cea7860..c4212b34c8 100644 --- a/extensions/files.js +++ b/extensions/files.js @@ -1,175 +1,187 @@ -(function(Scratch) { - 'use strict'; +// Name: Files +// ID: files +// Description: Read and download files. + +(function (Scratch) { + "use strict"; if (!Scratch.extensions.unsandboxed) { - throw new Error('files extension must be run unsandboxed'); + throw new Error("files extension must be run unsandboxed"); } - const MODE_MODAL = 'modal'; - const MODE_IMMEDIATELY_SHOW_SELECTOR = 'selector'; - const MODE_ONLY_SELECTOR = 'only-selector'; - const ALL_MODES = [MODE_MODAL, MODE_IMMEDIATELY_SHOW_SELECTOR, MODE_ONLY_SELECTOR]; + const MODE_MODAL = "modal"; + const MODE_IMMEDIATELY_SHOW_SELECTOR = "selector"; + const MODE_ONLY_SELECTOR = "only-selector"; + const ALL_MODES = [ + MODE_MODAL, + MODE_IMMEDIATELY_SHOW_SELECTOR, + MODE_ONLY_SELECTOR, + ]; let openFileSelectorMode = MODE_MODAL; - const AS_TEXT = 'text'; - const AS_DATA_URL = 'url'; + const AS_TEXT = "text"; + const AS_DATA_URL = "url"; /** * @param {string} accept See MODE_ constants above * @param {string} as See AS_ constants above * @returns {Promise} format given by as parameter */ - const showFilePrompt = (accept, as) => new Promise((_resolve) => { - // We can't reliably show an - - - Zip docs - - - - -
-

Zip extension documentation

-
-
-

- The Zip extension allows you to read, create and edit .zip format files, - including Scratch project and sprite files - (.sb3, .sprite3). -

- -

- Paths - (link) -

-

Most blocks in this extension work with a path format:

-
    -
  • - The path is relative to the current directory by default -
  • -
  • - Directories (folders) and the filename are separated by slashes, like - folder1/folder2/file.txt -
  • -
  • - .. goes to the parent directory, like - ../file.txt -
  • -
  • - . goes to the current directory, like - ./file.txt -
  • -
  • - A / at the very start goes to the root directory, like - /file.txt -
  • -
  • - A / at the end denotes a directory, like - folder/ -
  • -
  • - - Multiple slashes in a row or trying to go above the root directory will - result in an error (usually the block doing nothing or returning the - empty value) -
  • -
- -

- - Archive management blocks - (link) -

-

- Blocks for creating and saving the current archive. - Only one archive can be open at a time. -

- -

- create empty archive - (link) -

-

- Creates and opens an empty archive with nothing in it. -

- -

- open zip from [URL] [https://extensions.turbowarp.org/hello.zip] - (link) -

-

- Opens a .zip (or .sb3 or .sprite3...) file. -

-

- The type can be one of the following: -

-
    -
  • - URL: A URL, which can be either a web URL or data: URL. Recommended. -
  • -
  • - base64: A base64 string without the data URL header. -
  • -
  • - hex: A sequence of hexadecimal bytes - (like 101A1B1C), - without a separator. -
  • -
  • - binary: A sequence of binary bytes - (like 000000010010101001101011), - without a separator. -
  • -
  • - string: Plain text. - Not recommended! - Text encoding behavior will likely break it, as it's a binary file. -
  • -
-

- If the file is not of zip format (e.g RAR or 7z) or is password-protected, - it won't be opened. - Make sure to check if it loaded successfully with the - archive is open? block. -

- -

- output zip type [data: URL] compression level [6] - (link) -

-

- Save the zip data into a string, which can be saved with e.g the Files extension. -

-

- The type can be one of the following: -

-
    -
  • - data: URL: A base64-encoded data URL. Recommended. -
  • -
  • - base64: A base64 string without the data URL header. -
  • -
  • - hex: A sequence of hexadecimal bytes - (like 101A1B1C), - without a separator. -
  • -
  • - binary: A sequence of binary bytes - (like 000000010010101001101011), - without a separator. -
  • -
  • - string: Plain text. - Not recommended! - Text encoding behavior will likely break it, as it's a binary file. -
  • -
-

- The compression level decides how much the zip is compressed. -
- Lower compression levels will result in a bigger file, - but are faster to create. Usually, high compression levels - provide diminishing returns (lesser gains the higher you go) - with much slower speeds. -
- A compression level of 0 (no compression) is the fastest, - but will often result in a very big file. -

- -

- close archive - (link) -

-

Closes the archive. Use after you're done working with it.

- -

- archive is open? - (link) -

-

Returns true if an archive is open.

- - -

- File blocks - (link) -

-

- Blocks for working with files - (and blocks that are general to both files and folders/directories.) -

- -

- [folder/] exists? - (link) -

-

- Returns if a file or directory exists or not. - The slash at the end matters! - If a directory named folder exists, - [folder] exists? will return false, - but [folder/] exists? will return true. -

- -

- write file [new file.txt] content [Hello, world?] type [text] - (link) -

-

- Writes content to a file, creating the file if it doesn't exist - and replacing its existing data if it does. -

-

- The type can be one of the following: -

-
    -
  • - text: Plain text. -
  • -
  • - URL: A URL, which can be either a web URL or data: URL. - Best for binary data (like other zip files). -
  • -
  • - base64: A base64 string without the data URL header. -
  • -
  • - hex: A sequence of hexadecimal bytes - (like 101A1B1C), - without a separator. -
  • -
  • - binary: A sequence of binary bytes - (like 000000010010101001101011), - without a separator. -
  • -
- -

- rename [hello.txt] to [hello renamed.txt] - (link) -

-

- Renames a file or directory to another name. - If the target file already exists, it will be overwritten. - The current directory will also be updated. - This block can also be used to move files to a different directory. -

- -

- delete [hello.txt] - (link) -

-

- Deletes a file or directory (including everything in it). -
- If the current directory is in that directory, it will - be set to the closest existing parent directory. -

- -

- file [hello.txt] as [text] - (link) -

-

- Get the contents of a file. -

-

- The type can be one of the following: -

-
    -
  • - text: Plain text. -
  • -
  • - data: URL: A base64-encoded data URL. - Best for binary data (like other zip files). -
  • -
  • - base64: A base64 string without the data URL header. -
  • -
  • - hex: A sequence of hexadecimal bytes - (like 101A1B1C), - without a separator. -
  • -
  • - binary: A sequence of binary bytes - (like 000000010010101001101011), - without a separator. -
  • -
- -

- File info blocks - (link) -

-

Blocks for getting and setting additional information on a file.

- -

- set [unix modified timestamp] of [folder/dango.png] to [0] - (link) -

-

- Set additional info on a file or directory. -

-

- Available options: -

-
    -
  • - modified days since 2000: - The modification date of the file, as days since 2000. -
  • -
  • - unix modified timestamp: - The modification date of the file, as a Unix timestamp (milliseconds since 1970). - Useful when combined with e.g the Time extension. -
  • -
  • - comment: A comment on the file. Can be any text. - Some programs may show this as metadata. -
  • -
- -

- [name] of [folder/dango.png] - (link) -

-

- Get additional info on a file or directory. -

-

- Available options: -

-
    -
  • - name: - Just the name of this file (without the directories it's in). - For example, the name of /folder1/folder2/dango.png - would be dango.png. -
  • -
  • - path: The full absolute path of this file - (its name and any directories it's in). -
  • -
  • - folder: - Just the folders this file is in (without its filename). - For example, the folder of /folder1/folder2/dango.png - would be /folder1/folder2/. -
  • -
  • - modification date: - A human-readable version of the file's modification date. - The output of this depends on the browser's language and possibly other factors. -
  • -
  • - long modification date: - A longer human-readable version of the file's modification date. - The output of this depends on the browser's language and possibly other factors. -
  • -
  • - modified days since 2000: - The modification date of the file, as days since 2000. -
  • -
  • - unix modified timestamp: - The modification date of the file, as a Unix timestamp (milliseconds since 1970). - Useful when combined with e.g the Time extension. -
  • -
  • - comment: A comment on the file. Can be any text. - Some programs may show this as metadata. -
  • -
- -

- Directory blocks - (link) -

-

Blocks that deal with directories and the current directory.

- - -

- create directory [new folder] - (link) -

-

- Creates a directory with a name. This can create multiple directories - at once (by having multiple directores in the path, like - /new folder1/new folder2/new folder3/). -

- -

- go to [folder] - (link) -

-

- Moves the current directory (the default origin of most file operations) - to the speficied directory. If it doesn't exist, this block will do nothing. -

- -

- contents of directory [.] - (link) -

-

- Returns a list of files in a directory, as JSON - (which you can parse with the JSON extension). -

- -

- current directory path - (link) -

-

- Returns the absolute path to the current directory. -

- -

- Other blocks - (link) -

-

- Miscellaneous stuff. -

- -

- set archive comment to [any text] - (link) -

-

- Sets the archive's comment to some text. Just like file comments, - this is saved and may be displayed as metadata by some programs. -

- -

- archive comment - (link) -

-

- Returns the archive's comment. -

- -

- path [../folder3/] from [/folder/folder2] - (link) -

-

- Returns an absolute path from an origin path and a target path. - Does not depend on the archive, so it works without one open. - This is mostly a utility used internally, but it might - be useful for users too. -

-
- - - \ No newline at end of file diff --git a/website/CubesterYT/TurboHook.html b/website/CubesterYT/TurboHook.html deleted file mode 100644 index 1222e12df7..0000000000 --- a/website/CubesterYT/TurboHook.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - TurboHook docs - - - -
- TurboHook extension documentation -
-
-

About this extension

-

This extension allows you to post to webhooks from some common third-party websites or programs.

- -
- -

Blocks

-

Webhook Block

-

Posts a message to a webhook.

- -

The empty area is where you insert the data reporter and/or connector reporter. The string area is where you put your webhook's URL.

- -
- -

Data Reporter

-

The Menu area of the Data Reporter currently has three options, ("content","name","icon").

-

Content

- -

When choosing "content", you can type in the text you want to send as the body of the webhook.

-

Name

- -

When choosing "name", you can type in the name of the name you want the webhook to display as on the third-party website.

-

Icon

- -

When choosing "icon", you can type in a URL to the image you want to set as the profile picture of the webhook.

- -
- -

Connector Reporter

- -

This reporter connects two different data reporters together and/or another connector reporter so you can send multiple pieces of data to the webhook.

-
- - diff --git a/website/CubesterYT/connector.png b/website/CubesterYT/connector.png deleted file mode 100644 index b97a6f3e53..0000000000 Binary files a/website/CubesterYT/connector.png and /dev/null differ diff --git a/website/CubesterYT/content.png b/website/CubesterYT/content.png deleted file mode 100644 index 0afb7e96f5..0000000000 Binary files a/website/CubesterYT/content.png and /dev/null differ diff --git a/website/CubesterYT/icon.png b/website/CubesterYT/icon.png deleted file mode 100644 index 1fba21595f..0000000000 Binary files a/website/CubesterYT/icon.png and /dev/null differ diff --git a/website/CubesterYT/name.png b/website/CubesterYT/name.png deleted file mode 100644 index b729b1b79e..0000000000 Binary files a/website/CubesterYT/name.png and /dev/null differ diff --git a/website/CubesterYT/webhook.png b/website/CubesterYT/webhook.png deleted file mode 100644 index 121c8415c2..0000000000 Binary files a/website/CubesterYT/webhook.png and /dev/null differ diff --git a/website/ar.html b/website/ar.html deleted file mode 100644 index 5c40f7c9ce..0000000000 --- a/website/ar.html +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - AR docs - - - - - - - -
- Augmented Reality extension documentation -
-
-

Requirements

- -

At the moment of writing, only Chromium-based browsers support immersive-ar session type.

- -

Other information

-
    -
  • It is generally recommended to use this extension with fps set to 0, which in turbowarp means running project at the screen refresh rate.
  • -
  • While exiting AR mode, there is a small chance TurboWarp will lose WebGL context. This problem has also been observed on other websites with AR and likely can't be fixed. If that happens, you can save the project, refresh the tab, load the project and continue.
  • -
  • It isn't possible to be in AR mode and have video sensing enabled at the same time. Entering AR disables video sensing and makes it not toggleable until AR session ends.
  • -
- -

Blocks

-
enterARmode
-

If AR is not supported and the error message haven't been shown yet, shows a popup with an error message.

-

If AR is supported and project is currently not in AR, attempts to enter AR mode. While doing so, it will pause the script it is in, in the same way ask block does. It will first try to enter AR mode directly. That may fail because entering AR can only be triggred by user interaction. If it fails, it will make it so after user clicks/taps the project will attempt to enter AR. If that fails as well or either of those 2 attempts succeed it will resume the script execution. After it resumes, project may or may not be in AR mode. Projects should handle both cases.

-

The origin of coordinate system is placed at or close to position of the device at the time this block was called.

- -
-
exitARmode
-

If the project is in AR mode, exits it.

- -
-
isinAR?
-

Tells if the project is currently in AR mode.

- -
-
isaravailible?
-

Tells if AR is supported on this device.

- -
-
isposeavailible?
-

Tells if AR engine knows what the current camera position and orientation is.

-

After entering AR mode, is is not immediately availible as the map of the environment needs to be built first. - After enough information about environment has been gathered and processed, it becomes availible. - It can temporarily become unavailible due to lack of detailed features in the view of camera that are used for motion tracking, - fast motion causing camera image to become too blurry or camera getting covered.

- -
-
ishit positionavailible?
-

Tells if AR engine knows where the point of ray intersection is.

-

Can become unavailible for the same reasons as [is [pose] availible?]

- -
-
stagewidth
-

Tells stage width in scratch units. Default is 480.
- This value may change when entering AR.

- -
-
stageheight
-

Tells stage height in scratch units. Default is 360.
- This value will not change when entering AR.

- -
-
item1ofviewmatrix
-

view matrix - is a matrix that can be used to transform points from the world space - (relatively to the world origin) to the view space (with origin is at the camera). It - includes rotation and translation of the camera.

- Also: - - -
-
item1ofinverse viewmatrix
-

inverse view matrix = view matrix-1
- Describes the opposite transformation to the view matrix.

- -
-
item1ofprojectionmatrix
-

For perspective projection in scratch you are likely used to doing something like this:

- - -

With this extension it's more complicated: projection is done by - first doing a 4x4 projection matrix multiplication by 4D vector x,y,z,1, - with result being X,Y,Z,W, then performing division of X,Y,Z by W to - get screen coordinates in range from -1 to 1, and then multiplying them - by half of stage width and height to get scratch coordinates.

- -

The matrix is a perspective projection matrix with the assumption camera faces negative z and that screen coordinates range from -1 to 1.

- -

It's calculated as:

- - -

Point is only visible if -W < X,Y,Z < W.

- - - - If the first codition was true, then -1 < screenX,screenY,screenZ < 1 is also always true. Note that the opposite is not always true (think of what happens when W is negative). - -

To get a better understanding of this topic, you may read:

- - -

Projection matrix contains a lot of 0s, and can be simplified. As the result of simplifications it is possible to make it look closer to how it is usually done on scratch:

- - -
-
item1ofcombinedmatrix
-

combined matrix = projection matrix * inverse view matrix

- -
-
positionx
-
positiony
-
positionz
-

Camera position relatively to the world origin.

- -
-
orientationr
-
orientationi
-
orientationj
-
orientationk
-

Camera orientation represented as quaternion.

- -
-
hitpositionx
-
hitpositiony
-
hitpositionz
-

A ray that originates from camera in the direction the camera is facing (center of the screen) intersects the first detected real world surface. - This block returns the coordinates of that intersection point.

- -
-
moveeverythingbyx:0y:0z:0
-

Moves the coordinate system by specified amount.
- It can also be understood as switching to a new coordinate system with origin at a given location in the current coordinate system.

- - Usage example: -

After starting the project, it may be a good idea to give user a way to pick location for AR content and then perform this before starting the main AR game/animation/etc.

-
moveeverythingbyx:hitpositionxy:hitpositionyz:hitpositionz
- -
-
turneverythingbyr:0i:0j:0k:0
-

Turns coodinate system around the origin by specified quaternion.
- Internally it also does quaternion normalization, so you don't have to worry about doing it yourself.

- - Usage example: -

This script can be used to rotate XZ around Y-axis:

-
turneverythingbyr:cosofangle/2i:0j:sinofangle/2k:0
- -
-
setresolution1
-

accepts values from 0.1 to 1

- -

changes the resolution at which the project is rendered
- 1 - is native screen resolution
- 0.5 - half the screen resolution
- 0.1 - one tenth of screen resolution
- Reduing resolution can improve performance and reduce memory usage.

- -

Examples

- Example 1: -
whenclickedenterARmodewhileisinAR?clearpointat0.10.5-0.3definepointatxyzsetXtox*item1ofcombinedmatrix+y*item5ofcombinedmatrix+z*item9ofcombinedmatrix+item13ofcombinedmatrixsetYtox*item2ofcombinedmatrix+y*item6ofcombinedmatrix+z*item10ofcombinedmatrix+item14ofcombinedmatrixsetZtox*item3ofcombinedmatrix+y*item7ofcombinedmatrix+z*item11ofcombinedmatrix+item15ofcombinedmatrixsetWtox*item4ofcombinedmatrix+y*item8ofcombinedmatrix+z*item12ofcombinedmatrix+item16ofcombinedmatrixifabsofX<WthenifabsofY<WthenifabsofZ<Wthengotox:X/W*stagewidth/2y:Y/W*stageheight/2pendownpenup
- Example 2: -
whenclickedenterARmodewhileisinAR?clearViewmatrixandcameracoordspointat0.10.5-0.3defineViewmatrixandcameracoordssetMXXtoitem1ofviewmatrixsetMXYtoitem5ofviewmatrixsetMXZto0-item9ofviewmatrixsetMYXtoitem2ofviewmatrixsetMYYtoitem6ofviewmatrixsetMYZto0-item10ofviewmatrixsetMZXtoitem3ofviewmatrixsetMZYtoitem7ofviewmatrixsetMZZto0-item11ofviewmatrixsetcamXtoitem13ofviewmatrixsetcamYtoitem14ofviewmatrixsetcamZtoitem15ofviewmatrixsetdisttoitem6ofprojectionmatrix*stageheight/2definepointatxyzsetx2tox-camXsety2toy-camYsetz2toz-camZsetrotXtox2*MXX+y2*MYX+z2*MZXsetrotYtox2*MXY+y2*MYY+z2*MZYsetrotZtox2*MXZ+y2*MYZ+z2*MZZifrotZ>0thengotox:rotX*dist/rotZy:rotY*dist/rotZpendownpenup
-

Credits

-

The block svg images have been generated with scratchblocks.

-
- - \ No newline at end of file diff --git a/website/bitwise.html b/website/bitwise.html deleted file mode 100644 index 04a35b6d65..0000000000 --- a/website/bitwise.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - Bitwise docs - - - - - - - -
- Bitwise extension documentation -
-
-

About this extension

-
    -
  • This extension allows you to perform bit shifts and logic operations on integers as if they were encoded in binary. These operations can be used to create programmer calculators and more.
  • -
  • The extension uses signed 32-bit integers, which can range from -2,147,483,648 to 2,147,483,647.
  • -
- -

Blocks

- -
is0000000000100000binary?
-

Returns true if the input is a binary number. Binary numbers can only contain 0s and 1s, so this essentially tells you if the input only contains 0s and 1s.

- -
- -
32tobinary
-

Converts the input from decimal to binary and returns the result.

- -

Note: All other blocks except for [is () binary?] and [() to number] will treat this result as a decimal number, so make sure to convert it back to decimal before you perform operations on it.

- -
- -
0000000000100000tonumber
-

Converts the input from binary to decimal and returns the result.

- -
- -
x>>y
-

Arithmatically shifts each bit of the binary representation of x to the right y times and returns the result.

- -

Example: 3210000001000016

- -

The sign will be preserved, so negative numbers will stay negative.

- -

This is essentially the same thing as dividing by 2^y and rounding down to the nearest integer.

- -
- -
x<<y
-

Arithmatically shifts each bit of the binary representation of x to the left y times and returns the result.

- -

Example: 320100000100000064

- -

The sign will be preserved, so negative numbers will stay negative.

- -

This is essentially the same thing as multiplying by 2^y.

- -
- -
x>>>y
-

Logically shifts each bit of the binary representation of x to the right y times and returns the result.

- -

This also moves the sign bit, so it can result in negative numbers becoming positive.

- -
- -
xy
-

Shifts each bit of the binary representation of x to the right y times. Numbers shifted past one end will reappear on the other (circle around).

- -

Example: 30000000000000000000000000000001110000000000000000000000000000001-2147483647

- -
- -
xy
-

Shifts each bit of the binary representation of x to the left y times. Numbers shifted past one end will reappear on the other (circle around).

- -

Example: -214748364710000000000000000000000000000001000000000000000000000000000000113

- -
- -
xandy
-

Logically ANDs the binary representation of the inputs together and returns the result.

- -

Example:

- - -
- -
xory
-

Logically ORs the binary representation of the inputs together and returns the result.

- -

Example:

- - -
- -
xxory
-

Logically XORs the binary representation of the inputs together and returns the result.

- -

Example:

- - -
- -
notx
-

Flips all bits of the binary representation of x and returns the result.

- -

Example:

- - -

Credits

-

The block SVGs were generated with scratchblocks.

-
- - diff --git a/website/box2d.html b/website/box2d.html deleted file mode 100644 index c470d09bbc..0000000000 --- a/website/box2d.html +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - Box2D Physics docs - - - - - - - -
- Box2D Physics extension documentation -
-
-

About this extension

-
    -
  • This extension allows you to easily implement proper physics using a physics library called Box2D.
  • -
- -

Blocks

- -

World

-

Adjust the physics of all sprites.

- -
setupstageboxed stage
-

Choose a type of containment to keep sprites within the stage.

-
    -
  • Boxed stage: Keeps sprites from going off the bottom and sides.
  • -
  • Open (with floor): Keeps sprites from going off the bottom.
  • -
  • Open (no floor): Removes all boundaries; sprites can go wherever they want.
  • -
- -
- -
setgravitytox:0y:-10
-

Change the direction and strength of gravity.

- -
- -
stepsimulation
-

Move forward in time by one step. Run this in a loop to keep the physics going.

- -

Sprites

-

Manipulate individual sprites.

- -
enableforthis costumemodenormal
-

Make physics apply to this sprite. It can also collide with other sprites that have physics enabled.

- -
    -
  • Enable for this costume: Enable physics only for the current sprite or clone.
  • -
  • Enable for this circle: Enable physics for the current sprite or clone as if it were shaped like a circle.
  • -
  • Enable for all sprites: Enable physics for all sprites.
  • -
-

Precision mode will make the sprite work extra hard to make sure it doesn't overlap with anything. Note that this can decrease performance and even cause the project to get stuck, so use with care.

- -
- -
gotox:0y:0in world
-

Make the sprite go to the specified location.

- -
    -
  • In world: Relative to the center of the world.
  • -
  • On stage: Relative to the center of the screen (if you've scrolled it).
  • -
  • Relative: Relative to itself.
  • -
- -
- -
setvelocitytosx:0sy:0
-

Set the velocity (speed) of the sprite to the specified value.

- -

You can get the velocity of the current sprite with the (x velocity) and (y velocity) reporters.

- -
- -
pushwithforce25indirection0
-

Directly send the sprite flying in a certain direction, adding on to its current velocity.

- -
- -
setangularvelocityto30
-

Set the angular velocity (rotational speed) of the sprite to the specified value.

- -

You can get the angular velocity of the current sprite with the (angular velocity) reporter.

- -
- -
spinwithforce500
-

Directly send the sprite spinning, adding on to its current angular (rotational) velocity.

- -
- -
setfixedfixed in place
-

Choose whether the sprite is fixed in place or can move around.

- -

You can tell if the sprite is currently fixed in place with the (fixed?) reporter.

- -
- -
setdensitynormal
-

Set the sprite's density, which affects how heavy it is.

- -

You can get the sprite's current density with the (density) reporter.

- -
- -
setfrictionnormal
-

Set the sprite's roughness. Smoother settings make the sprite slipperier.

- -

You can get the sprite's current friction with the (friction) reporter.

- -
- -
setbouncenormal
-

Set the sprite's bounciness.

- -

You can get the sprite's current bounciness with the (bounce) reporter.

- -
- -
touchingany
-

Returns what other sprites the sprite is touching. Also includes the edges of the stage.

- -

If there are multiple sprites touching, it will return a comma-separated list of them.

- -

Screen

-

Move the camera around the world.

- -
setscrollx:0y:0
-

Scroll to the desired location.

- -

You can get the current screen position with the (x scroll) and (y scroll) reporters.

- -

This will not affect the world boundaries set by the [setup stage] block.

- -

Example

- -
whenclickedsetupstageboxed stagesetgravitytox:0y:-10createcloneofmyselfrepeat20stepsimulationcreatecloneofmyselfforeverstepsimulationwhenIstartasacloneshowenableforthis costumemodenormalgotox:-150y:240in worldsetbouncequite bouncysetangularvelocityto-4.1
- -

Credits

-

The block SVGs were generated with scratchblocks.

-
- - diff --git a/website/index.ejs b/website/index.ejs deleted file mode 100644 index c785b75872..0000000000 --- a/website/index.ejs +++ /dev/null @@ -1,768 +0,0 @@ - - - - - - TurboWarp Extension Gallery - - - - - - -
-

- -
TurboWarp Extension Gallery
-

- -

Unlike custom extensions on other websites, these aren't limited by the extension sandbox, so they are a lot more powerful. All extensions are reviewed for safety.

-

- <% if (mode === 'desktop') { %> - To use these extensions in TurboWarp Desktop, hover over the extension and press the add button. - <% } else { %> - To use multiple of these extensions in TurboWarp, hover over the extension and press the button to copy its URL. Then go to the editor, open the extension chooser, then choose the "Custom Extension" option at the bottom, and enter the URL. - <% } %> -

- -
-
These extensions are not compatible with Scratch.
- Projects that use these extensions can't be uploaded to the Scratch website. - They can, however, be used in the packager. -
- -
- <% if (mode === 'desktop') { %> -
Some extensions may be outdated or missing.
- <% - const now = new Date(); - const date = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')}`; - %> - For compatibility, security, and offline support, TurboWarp Desktop includes an offline copy of extensions.turbowarp.org from this update's release date (<%= date %>). - Compared to the live website, some extensions may be missing or outdated. - <% } else { %> -
Some extensions may not work in TurboWarp Desktop.
- For compatibility, security, and offline support, each TurboWarp Desktop update contains an offline copy of these extensions from its release date, so some extensions may be outdated or missing. - Use the latest update for best results. - <% } %> -
-
- - <% - const getLinkToRun = (extensionPath) => `https://turbowarp.org/editor?extension=${host}${extensionPath}`; - %> - - <% if (mode === "development") { %> -
-
-

Development Server Tools

-

- Most recently modified extensions: - <% for (const extension of mostRecentExtensions) { %> - <%= extension %> - <% } %> -

-
-
- <% } %> - - - - <% if (mode === 'desktop') { %> - - <% } %> - - <% - const banner = (extensionFile, options = {}) => { - if (extensionFile.endsWith('.js')) { - return `Do not add .js when calling banner(): ${extensionFile}`; - } - const imageSource = extensionImages[extensionFile] ? `images/${extensionImages[extensionFile]}` : 'images/unknown.svg'; - const imageClasses = ["extension-image"]; - if (options.invertDark) imageClasses.push("invert-dark"); - return ` -
- -
- - ${mode === "desktop" ? (` - - `) : (` - Open Extension - `)} -
-
` - }; - - const img = (extensionFile) => { - if (extensionImages[extensionFile]) return `images/${extensionImages[extensionFile]}`; - return 'images/unknown.svg'; - }; - %> - -
-
-
- <%- banner('lab/text') %> -

Animated Text

-

An easy way to display and animate text. Compatible with Scratch Lab's Animated Text experiment.

-
- -
- <%- banner('stretch') %> -

Stretch

-

Stretch sprites horizontally or vertically.

-
- -
- <%- banner('gamepad') %> -

Gamepad

-

Directly access gamepads instead of just mapping buttons to keys.

-
- -
- <%- banner('box2d') %> -

Box2D Physics

-

Two dimensional physics. Originally created by griffpatch.

-
- -
- <%- banner('files') %> -

Files

-

Read and download files.

-
- -
- <%- banner('pointerlock') %> -

Pointerlock

-

Adds blocks for mouse locking. Mouse x & y blocks will report the change since the previous frame while the pointer is locked. Replaces the pointerlock experiment.

-
- -
- <%- banner('cursor') %> -

Mouse Cursor

-

Use custom cursors or hide the cursor. Also allows replacing the cursor with any costume image.

-
- -
- <%- banner('runtime-options') %> -

Runtime Options

-

Get and modify turbo mode, framerate, interpolation, clone limit, stage size, and more.

-
- -
- <%- banner('fetch') %> -

Fetch

-

Make requests to the broader internet.

-
- -
- <%- banner('text') %> -

Text

-

Manipulate characters and text. Originally created by CST1229.

-
- -
- <%- banner('local-storage') %> -

Local Storage

-

Store data persistently. Like cookies, but better.

-
- -
- <%- banner('true-fantom/base') %> -

Base

-

Convert numbers between bases. Created by TrueFantom.

-
- -
- <%- banner('bitwise') %> -

Bitwise

-

Blocks that operate on the binary representation of numbers in computers. Modified by TrueFantom.

-
- -
- <%- banner('Skyhigh173/bigint') %> -

BigInt

-

Math blocks that work on infinitely large integers (no decimals). Created by Skyhigh173.

-
- -
- <%- banner('utilities') %> -

Utilities

-

A bunch of interesting blocks. Originally created by Sheep_maker.

-
- -
- <%- banner('sound') %> -

Sound

-

Play sounds from URLs.

-
- -
- <%- banner('Xeltalliv/clippingblending') %> -

Clipping & Blending

-

Clipping outside of a specified rectangular area and additive color blending. Created by Vadik1.

-
- -
- <%- banner('clipboard') %> -

Clipboard

-

Read and write from the system clipboard.

-
- -
- <%- banner('penplus') %> -

Pen Plus

-

Advanced rendering capabilities. Created by ObviousAlexC.

-
- -
- <%- banner('obviousAlexC/SensingPlus') %> -

Sensing Plus

-

An extension to the sensing category. Created by ObviousAlexC.

-
- -
- <%- banner('Lily/ClonesPlus') %> -

Clones Plus

-

Expansion of Scratch's clone features. Created by LilyMakesThings.

-
- -
- <%- banner('Lily/LooksPlus') %> -

Looks Plus

-

Expands upon the looks category, allowing you to show/hide, get costume data and edit SVG skins on sprites. Created by LilyMakesThings.

-
- -
- <%- banner('navigator') %> -

Navigator

-

Details about the user's browser and operating system.

-
- -
- <%- banner('battery') %> -

Battery

-

Access information about the battery of phones or laptops. May not work on all devices and browsers.

-
- -
- <%- banner('mdwalters/notifications') %> -

Notifications

-

Display notifications.

-
- -
- <%- banner('ar') %> -

Augmented Reality

-

Shows image from camera and performs motion tracking, allowing 3D projects to correctly overlay virtual objects on real world. Created by Vadik1.

-
- -
- <%- banner('encoding') %> -

Encoding

-

Encode and decode strings into their unicode numbers, base 64, or URLs. Created by -SIPC-.

-
- -
- <%- banner('Lily/MoreTimers') %> -

More Timers

-

Control several timers at once. Created by LilyMakesThings.

-
- -
- <%- banner('clouddata-ping') %> -

Ping Cloud Data

-

Determine whether a cloud variable server is probably up. Originally created by TheShovel.

-
- -
- <%- banner('cloudlink') %> -

Cloudlink

-

Powerful WebSocket extension for Scratch 3. Created by MikeDEV.

-
- -
- <%- banner('true-fantom/network') %> -

Network

-

Various blocks for interacting with the network. Created by TrueFantom.

-
- -
- <%- banner('true-fantom/math') %> -

Math

-

A lot of operators blocks, from exponentiation to trigonometric functions. Created by TrueFantom.

-
- -
- <%- banner('true-fantom/regexp') %> -

RegExp

-

Full interface for working with Regular Expressions. Created by TrueFantom.

-
- -
- <%- banner('true-fantom/couplers') %> -

Couplers

-

A few adapter blocks. Created by TrueFantom.

-
- -
- <%- banner('Lily/Cast') %> -

Cast

-

Convert values between types. Created by LilyMakesThings

-
- -
- <%- banner('-SIPC-/time') %> -

Time

-

Blocks for interacting with unix timestamps and other date strings. Created by -SIPC-.

-
- -
- <%- banner('-SIPC-/consoles') %> -

Consoles

-

Blocks that interact the JavaScript console built in to your browser's developer tools. Created by -SIPC-.

-
- -
- <%- banner('ZXMushroom63/searchApi') %> -

Search Params

-

Interact with URL search parameters: the part of the URL after a question mark. Created by ZXMushroom63.

-
- -
- <%- banner('TheShovel/ShovelUtils') %> -

ShovelUtils

-

A bunch of miscellaneous blocks. Created by TheShovel.

-
- -
- <%- banner('Skyhigh173/json') %> -

JSON

-

Handle JSON strings and arrays. Created by Skyhigh173.

-
- -
- <%- banner('cs2627883/numericalencoding') %> -

Numerical Encoding

-

Encode strings as numbers for cloud variables. Created by cs2627883.

-
- -
- <%- banner('DT/cameracontrols') %> -

Camera Controls

-

Move the visible part of the stage. Created by DT.

-
- -
- <%- banner('TheShovel/CanvasEffects') %> -

Canvas Effects

-

Apply visual effects to the entire stage. Created by TheShovel.

-
- -
- <%- banner('Longboost/color_channels') %> -

RGB Channels

-

Only render or stamp certain RGB channels.

-
- -
- <%- banner('Lily/TempVariables2') %> -

Temporary Variables

-

Create disposable runtime or thread variables. Created by LilyMakesThings.

-
- -
- <%- banner('CST1229/zip') %> -

Zip

-

Create and edit .zip format files, including .sb3 files. Created by CST1229.

-
- -
- <%- banner('0832/rxFS2') %> -

rxFS

-

Blocks for interacting with a virtual in-memory filesystem. Created by 0832.

-
- -
- <%- banner('NexusKitten/sgrab') %> -

S-Grab

-

Get information about Scratch projects and Scratch users. Created by NamelessCat.

-
- -
- <%- banner('NOname-awa/graphics2d') %> -

Graphics 2D

-

Blocks to compute lengths, angles, and areas in two dimensions. Created by NOname-awa.

-
- -
- <%- banner('NOname-awa/more-comparisons') %> -

More Comparisons

-

More comparison blocks. Created by NOname-awa.

-
- -
- <%- banner('JeremyGamer13/tween') %> -

Tween

-

Easing methods for smooth animations. Created by JeremyGamer13

-
- -
- <%- banner('rixxyx') %> -

RixxyX

-

Various utility blocks. Created by RixTheTyrunt.

-
- -
- <%- banner('qxsck/data-analysis') %> -

Data Analysis

-

Blocks to compute means, medians, maximums, minimums, variances, and modes. Created by qxsck.

-
- -
- <%- banner('qxsck/var-and-list') %> -

Variable and list

-

More blocks related to variables and lists. Created by qxsck.

-
- -
- <%- banner('vercte/dictionaries') %> -

Dictionaries

-

Use the power of dictionaries in your project. Created by Vercte.

-
- -
- <%- banner('godslayerakp/http') %> -

HTTP

-

Comprehensive extension for interacting with external websites. Created by RedMan13.

-
- -
- <%- banner('Lily/CommentBlocks') %> -

Comment Blocks

-

Annotate your scripts. Created by LilyMakesThings.

-
- -
- <%- banner('CubesterYT/TurboHook') %> -

TurboHook

-

Allows you to use webhooks. Created by CubesterYT.

-
- -
- <%- banner('Alestore/nfcwarp') %> -

NFCWarp

-

Allows reading data from NFC (NDEF) devices. Only works in Chrome on Android. Created by Alestore Games.

-
- -
- <%- banner('itchio') %> -

itch.io

-

Blocks that interact with the itch.io website. Unofficial. Created by softed.

-
- -
- <%- banner('gamejolt') %> -

GameJolt

-

Blocks that allow games to interact with the GameJolt API. Unofficial. Created by softed.

-
- -
- <%- banner('obviousAlexC/newgroundsIO') %> -

Newgrounds

-

Blocks that allow games to interact with the Newgrounds API. Unofficial. Created by ObviousAlexC.

-
- -
- <%- banner('Lily/McUtils') %> -

McUtils

-

Helpful utilities for any fast food employee. Created by LilyMakesThings.

-
-
-
- - - - diff --git a/website/local-storage.html b/website/local-storage.html deleted file mode 100644 index 3f1262abf6..0000000000 --- a/website/local-storage.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - Local Storage docs - - - - - - - -
- How to use Local Storage -
-
-

About this extension

- -

This extension allows you to automatically save plain text in storage. Forget save codes! With this extension, we can make a game that doesn't require any user interaction to save progress.

- -

Features

- -

Namespaces

- -

The namespace is basically like the file you want to read and write to. Each project should set this to something unique, such as developer/project title so each project gets its own storage space. If two different projects use the same namespace, they will overwrite eachother's data and cause very bad things!

- -

You can set which namespace to use with this block:

- -
setstoragenamespaceIDtoproject title
- -

Some example namespaces:

-
    -
  • griffpatch/Paper Minecraft
  • -
  • Untitled-37 by TestMuffin
  • -
- -

The format isn't important -- it just needs to be unique. Don't include a version number like "v1.4" in the namespace unless you want to discard data from old versions of your project.

- -

Reading and writing

- -

After setting the namespace, you're ready to read from and write to storage.

- -

You can store data in keys, which are kind of like names of variables in any Scratch project, except they persist between sessions.

- -

You can store data in storage keys with this block:

- -
setkeyscoreto1000
- -

And read it with:

- -
getkeyscore
- -

If you want to delete a key and its value, use this block:

- -
deletekeyscore
- -

Or wipe everything stored in the namespace:

- -
deleteallkeys
- -

Loading data into memory

- -

Relying on the disk to read information that gets saved such as a player's progress or stats can be pretty slow. That's why it's very useful to store this data in variables while it's in use. Variables are like your project's random-access memory. One way of doing this is by getting all keys from storage and putting their values in variables as part of your project's initialization process.

- -

If you're unfamiliar with computer memory, think of reading local storage as opening up your dictionary to look for a definition for someone and reading memory as remembering the definition you just read and saying it to someone. Of course, you wouldn't want to be constantly running to get the dictionary every time the same definition was requested.

- -

While browsers actually hold this data in memory automatically for quicker access, it's still more efficient and a better practice to take some work off the browser by not constantly getting the same storage key over and over again for no reason, and is much more important to know and think about if you ever use other programming languages where you don't want the disk to have to spend time reading the same data over and over again when it could be kept in memory (that's what memory is for).

- -

For example, in a game, you can speed up code that needs to know how many coins the player has collected by getting the storage key for coins and then loading it into a variable, just once on startup.

- -
foreverifgetkeycoins>99thenbroadcast1-UPDon't do thissetcoinstogetkeycoinsDo this insteadforeverifcoins>99thenbroadcast1-UP
- -

So in general, we don't really need to load data from storage again once we have it in memory. If we already know what's in storage and what we're writing to storage because that data is already present in the project's variables, we really only need to read from storage once to initialize and then we're good.

- -

...Right?

- -

Handling interference from other windows

- -

Sometimes a user may open the same project in multiple tabs or windows, each of which could be trying to read and write data to the same space. If this causes a desync, it can result in unexpected behavior.

- -

Here's an example scenario. Suppose someone opens the same game twice by accident. They play in Window A for a while and save the game. Then they close that window and do something else. Later, they come back to the other window they had opened before and start playing in Window B, but all the progress is "gone" because that window had already been running the game and had already loaded the save data before Window A had saved the progress that was made, and it's too late because they had saved the game in Window B before they realized the problem. This is unfortunate for the player, but more importantly for you, the developer, what is the project supposed to do now? Mix the data?

- -

So you can see how it's a good idea to consider that people may have multiple windows of the same project open intentionally or accidentally.

- -

You don't have to do things like auto-refresh content if you didn't intend for your project to support multi-window usage, but it's nice to at least make sure no glitches happen if someone accidentally had multiple windows open. Here are a few ways you can deal with this problem:

- -

Initialize by loading from storage

- -

For games and other projects that are intended for use only in one window, we recommend you load all storage keys you need into variables only when the project starts so that nothing changes if a second instance of the project writes to storage while you're still using the first one. Then, when you need to save data, rewrite everything in the same group of data (like everything in the same save file in a game) to storage at the same time so nothing from other instances of the project gets mixed in.

- -

The worst that could happen with this implementation is that "data A" might be overwritten by "data B", if the user made a mistake.

- -

Reload data from storage as needed

- -

Sometimes you might want to respond to changes in local storage. There is a block to help with this:

- -
whenanotherwindowchangesstorage
- -

The code under this block will run whenever a different instance of the project writes to storage or if a different project that's using the same namespace writes to storage. This allows your project to properly respond to and handle these events as they happen.

- -

This may or may not be important. You probably wouldn't want to use this technique in a game - games don't need to respond to other instances of themselves writing to storage. Plus, if game save data from one window is mixed in with data from another, it can cause glitches like sequence breaks.

- -

This kind of thing is more useful if you made something like a file system simulator that you want to have auto-refresh if the user makes edits in other windows, you may want to use this so that the content being displayed stays up to date even if another window modifies it.

- -

You could do this by constantly getting the storage key, but it's better to only grab keys from storage when necessary. The block above is how you do that.

- -

Merge the data

- -

This one is more advanced and the way you would code it depends on the project, but you could make it so that when you're about to save data and another window wrote data that was not loaded into the first window, the two are merged - the data being written is merged with the data that was already present, so that if you collected 100 coins in one session and 100 XP in another, both of those changes in the save data would stay.

- -

Make sure to do this correctly because if data is merged incorrectly, it can cause glitches like sequence breaks in a game.

- -

I said that this is advanced, because sometimes these algorithms can get confused when merging changes to the same piece of data. (Like, what are we supposed to do if we're trying to merge two changes, one of which changes "A" to "B" and the other changes the same "A" to "C"?) This is known as a merge conflict. If you don't have any way to prioritize one change over another, you'll just be stuck with two branches of data.

- -

Local storage limits

- -

This extension uses the browser's local storage API, which limits each website to around 5 MiB or 5,242,800 bytes of local storage data, so if we want local storage to be able to hold data for many projects, each one should stay well below this limit. We recommend only storing small files such as game save data or settings in local storage.

- -

In rare instances, such as when a system is running out of disk space, the browser may delete our data to make room for something else. We, unfortunately, cannot influence when this happens.

- -

Credits

- -

This page was written by DNin01.

- -

The block SVGs were generated with scratchblocks.

-
- - diff --git a/website/test/loops.js b/website/test/loops.js new file mode 100644 index 0000000000..ae1bf2f6c3 --- /dev/null +++ b/website/test/loops.js @@ -0,0 +1,71 @@ +// From https://github.com/TurboWarp/scratch-vm/pull/141 +(function (Scratch) { + "use strict"; + + class LoopsAndThings { + getInfo() { + return { + id: "loopsAndThings", + name: "Loops and things test", + blocks: [ + { + opcode: "conditional", + blockType: Scratch.BlockType.CONDITIONAL, + text: "run branch [BRANCH] of", + arguments: { + BRANCH: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + branchCount: 3 + }, + { + opcode: "loop", + blockType: Scratch.BlockType.LOOP, + text: "my repeat [TIMES]", + arguments: { + TIMES: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 10 + } + }, + }, + '---', + { + opcode: "testPromise", + blockType: Scratch.BlockType.REPORTER, + text: "return [VALUE] in a Promise", + arguments: { + VALUE: { + type: Scratch.ArgumentType.STRING, + defaultValue: '' + } + } + } + ] + }; + } + + conditional({BRANCH}, util) { + return Scratch.Cast.toNumber(BRANCH); + } + + loop({TIMES}, util) { + const times = Math.round(Scratch.Cast.toNumber(TIMES)); + if (typeof util.stackFrame.loopCounter === "undefined") { + util.stackFrame.loopCounter = times; + } + util.stackFrame.loopCounter--; + if (util.stackFrame.loopCounter >= 0) { + return true; + } + } + + testPromise({VALUE}) { + return Promise.resolve(VALUE); + } + } + + Scratch.extensions.register(new LoopsAndThings()); +})(Scratch); \ No newline at end of file diff --git a/website/turbowarp.svg b/website/turbowarp.svg new file mode 100644 index 0000000000..6a1ec7f87d --- /dev/null +++ b/website/turbowarp.svg @@ -0,0 +1,68 @@ + +