From 4795d627d5a07ade964b83db5b8946b65787d77a Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 12:17:31 +0800 Subject: [PATCH 01/18] Create keysPlusV2.js --- extensions/StackOverflow/keysPlusV2.js | 949 +++++++++++++++++++++++++ 1 file changed, 949 insertions(+) create mode 100644 extensions/StackOverflow/keysPlusV2.js diff --git a/extensions/StackOverflow/keysPlusV2.js b/extensions/StackOverflow/keysPlusV2.js new file mode 100644 index 0000000000..ee00a1d6f9 --- /dev/null +++ b/extensions/StackOverflow/keysPlusV2.js @@ -0,0 +1,949 @@ +// Name: Keys+ V2 +// ID: enderKeysPlusV2 +// Description: Even more powerful and flexible key press detection blocks with some additional features. +// By: StackOverflow +// Original: StackOverflow +// License: MIT & LGPL-3.0 + +(function(Scratch){ + "use strict"; + + if (!Scratch.extensions.unsandboxed) throw new Error("Keys+ V2 must run unsandboxed!"); + + const runtime = Scratch.vm.runtime; + const Cast = Scratch.Cast; + + const _format = { + toValue: [ + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", + "KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ", + "Backquote", "Minus", "Equal", "BracketLeft", "BracketRight", "Backslash", "Semicolon", "Quote", "Comma", "Period", "Slash" + ], + toCustomValue: { + "MetaLeft": "left windows key", + "MetaRight": "right windows key", + "ControlLeft": "left control", + "ControlRight": "right control", + "AltLeft": "left alt", + "AltRight": "right alt", + "ShiftLeft": "left shift", + "ShiftRight": "right shift", + "ArrowUp": "up arrow", + "ArrowDown": "down arrow", + "ArrowLeft": "left arrow", + "ArrowRight": "right arrow" + } + }; + const _filter = { + default: [ + "_a", "_b", "_c", "_d", "_e", "_f", "_g", "_h", "_i", "_j", "_k", "_l", "_m", + "_n", "_o", "_p", "_q", "_r", "_s", "_t", "_u", "_v", "_w", "_x", "_y", "_z", + "_A", "_B", "_C", "_D", "_E", "_F", "_G", "_H", "_I", "_J", "_K", "_L", "_M", + "_N", "_O", "_P", "_Q", "_R", "_S", "_T", "_U", "_V", "_W", "_X", "_Y", "_Z", + "_0", "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9", + "_`", "_~", "_!", "_@", "_#", "_$", "_%", "_^", "_&", "_*", + "_(", "_)", "_-", "__", "_=", "_+", "_[", "_{", "_]", "_}", + "_\\", "_|", "_;", "_:", "_'", "\"", "_,", "_<", "_.", "_>", + "_/", "_?", "_ " + ], + shift: [ "_left shift", "_right shift" ], + }; + function format(_key) { + const name = _key.code, value = _key.key; + if (_format.toValue.includes(name)) return "_" + value; + if (name.startsWith("Numpad")) return "_numpad: " + name.slice(6).toLowerCase(); + if (name in _format.toCustomValue) return "_" + _format.toCustomValue[name]; + return "_" + name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase(); + } + const createLabel = (text) => ({ blockType: Scratch.BlockType.LABEL, text: text }); + + function startKeyEvents(_trigger) { + runtime.startHats("enderKeysPlusV2_eventKeyPressed", { trigger: _trigger }); + runtime.startHats("enderKeysPlusV2_eventKeysPressed", { trigger: _trigger }); + runtime.startHats("enderKeysPlusV2_eventKeybindTriggered", { trigger: _trigger }); + } + + class enderKeysPlusV2 { + constructor() { + this._settings = { + "clearOnBlur": true, + "includeTags": true + }; + + this._tags = { + "#a": ["a", "A"], "#b": ["b", "B"], "#c": ["c", "C"], "#d": ["d", "D"], "#e": ["e", "E"], "#f": ["f", "F"], "#g": ["g", "G"], "#h": ["h", "H"], "#i": ["i", "I"], "#j": ["j", "J"], "#k": ["k", "K"], "#l": ["l", "L"], "#m": ["m", "M"], + "#n": ["n", "N"], "#o": ["o", "O"], "#p": ["p", "P"], "#q": ["q", "Q"], "#r": ["r", "R"], "#s": ["s", "S"], "#t": ["t", "T"], "#u": ["u", "U"], "#v": ["v", "V"], "#w": ["w", "W"], "#x": ["x", "X"], "#y": ["y", "Y"], "#z": ["z", "Z"], + "#letters": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], + "#uppercaseLetters": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], + "#lowercaseLetters": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"], + "#vowels": ["a", "e", "i", "o", "u"], + "#consonants": ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"], + "#numbers": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "#specialCharacters": ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "=", "{", "}", "[", "]", "|", "\\", ":", ";", "'", "\"", "<", ">", ",", ".", "?", "/", "~", "`"], + "#shift": ["left shift", "right shift"], + "#alt": ["left alt", "right alt"], + "#control": ["left control", "right control"], + "#windowsKey": ["left windows key", "right windows key"], + "#arrowKeys": ["up arrow", "down arrow", "right arrow", "left arrow"], + "#functionKeys": ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"], + "#navigationKeys": ["up arrow", "down arrow", "right arrow", "left arrow", "home", "end", "page up", "page down", "insert", "delete"], + "#numpad": ["numpad: divide", "numpad: multiply", "numpad: subtract", "numpad: add", "numpad: 0", "numpad: 1", "numpad: 2", "numpad: 3", "numpad: 4", "numpad: 5", "numpad: 6", "numpad: 7", "numpad: 8", "numpad: 9", "numpad: decimal", "numpad: enter"] + } + + this._keybinds = {}; + + this._mouseDown = {}; + this._scrollDeltaY = 0; + this._lastScrollTime = Date.now(); + + this._keysPressed = {}; + this._lastKeyPressed = ""; + + this._importError = "None"; + + runtime.on("BEFORE_EXECUTE", () => { + runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "while" }); + startKeyEvents("while"); + }); + // Keys + window.addEventListener("keydown", (event) => { + const key = format(event); + this._lastKeyPressed = key; + if (!this._keysPressed[key]) { + this._keysPressed[key] = { time: Date.now(), code: event.code, value: event.key }; + startKeyEvents("when"); + }; + }); + window.addEventListener("keyup", (event) => { + const key = format(event); + startKeyEvents("after"); + delete this._keysPressed[key]; + this._filter(); + }); + window.addEventListener("blur", () => { + if (this._settings["clearOnBlur"]) { + this._keysPressed = {}; + this._mouseDown = {}; + }; + }); + // Mouse + window.addEventListener("mousedown", (event) => { + if (!this._mouseDown[event.button]) { + this._mouseDown[event.button] = { time: Date.now() }; + runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "when" }); + } + }); + window.addEventListener("mouseup", (event) => { + runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "after" }); + delete this._mouseDown[event.button]; + }) + window.addEventListener("wheel", (event) => { + this._scrollDeltaY = event.deltaY; + this._lastScrollTime = Date.now() + runtime.startHats("enderKeysPlusV2_eventScroll") + }); + } + getInfo() { + return { + id: "enderKeysPlusV2", + name: "Keys+ V2", + docsURI: "https://github.com/Ender-Studio/EnderStudio-Extensions/wiki/Extensions#keys", + color1: "#647970", + color2: "#4D5E56", + blocks: [ + createLabel("Mouse"), + { + opcode: "eventMouseDown", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [button] mouse button is down", + isEdgeActivated: false, + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, + button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } + } + }, + "---", + { + opcode: "isMouseDown", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [button] mouse button down?", + arguments: { + button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } + } + }, + { + opcode: "isMouseHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [button] mouse button clicked?", + arguments: { + button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } + } + }, + "---", + { + opcode: "timeMouseDown", + blockType: Scratch.BlockType.REPORTER, + text: "time [button] mouse button down", + arguments: { + button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } + } + }, + createLabel("Scrolling"), + { + opcode: "eventScroll", + blockType: Scratch.BlockType.HAT, + text: "when scrolling [dir]", + isEdgeActivated: false, + arguments: { + dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" } + } + }, + { + opcode: "isScrolling", + blockType: Scratch.BlockType.BOOLEAN, + text: "is scrolling [dir]?", + disableMonitor: true, + arguments: { + dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" } + } + }, + "---", + { + opcode: "scrollDirection", + blockType: Scratch.BlockType.REPORTER, + text: "scroll direction" + }, + createLabel("Keys"), + { + opcode: "eventKeysPressed", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [[keys]] keys is pressed [ordered]", + isEdgeActivated: false, + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } + } + }, + { + opcode: "eventKeyPressed", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [key] key is pressed", + isEdgeActivated: false, + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, + key: { type: Scratch.ArgumentType.STRING, menu: "keys" } + } + }, + "---", + { + opcode: "isKeysPressed", + blockType: Scratch.BlockType.BOOLEAN, + text: "are [[keys]] keys pressed [ordered]?", + arguments: { + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } + } + }, + { + opcode: "isKeyPressed", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [key] key pressed?", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" } + } + }, + "---", + { + opcode: "isKeysHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "are [[keys]] keys hit [ordered]?", + arguments: { + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } + } + }, + { + opcode: "isKeyHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [key] key hit?", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" } + } + }, + "---", + { + opcode: "lastKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "last key pressed" + }, + { + opcode: "currentKeysPressed", + blockType: Scratch.BlockType.REPORTER, + text: "current keys pressed", + }, + { + opcode: "currentKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "current key pressed", + }, + { + opcode: "keyPressedProperty", + blockType: Scratch.BlockType.REPORTER, + text: "current key pressed [property]", + disableMonitor: true, + arguments: { + property: { type: Scratch.ArgumentType.STRING, menu: "keyProperty" } + } + }, + "---", + { + opcode: "timeKeysPressed", + blockType: Scratch.BlockType.REPORTER, + text: "time [[keys]] keys pressed [mode]", + disableMonitor: true, + arguments: { + mode: { type: Scratch.ArgumentType.STRING, menu: "returnMode" }, + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } + } + }, + { + opcode: "timeKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "time [key] key pressed", + disableMonitor: true, + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" } + } + }, + createLabel("Keybinding"), + { + opcode: "eventKeybindTriggered", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [event] is triggered", + isEdgeActivated: false, + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + "---", + { + opcode: "whileKeybindTriggered", + blockType: Scratch.BlockType.BOOLEAN, + text: "while [event] is triggered?", + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "whenKeybindTriggered", + blockType: Scratch.BlockType.BOOLEAN, + text: "when [event] is triggered?", + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + "---", + { + opcode: "causeKeybindTriggered", + blockType: Scratch.BlockType.REPORTER, + text: "cause of [event] triggered", + disableMonitor: true, + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "timeKeybindTriggered", + blockType: Scratch.BlockType.REPORTER, + text: "time [event] is triggered", + disableMonitor: true, + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + "---", + { + opcode: "keybindBindMultiple", + blockType: Scratch.BlockType.COMMAND, + text: "bind [[keys]] keys [mode] as [trigger] to [event]", + arguments: { + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' }, + mode: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "keybindBind", + blockType: Scratch.BlockType.COMMAND, + text: "bind key [key] as [trigger] to [event]", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "keybindUnbind", + blockType: Scratch.BlockType.COMMAND, + text: "unbind trigger [trigger] from [event]", + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + "---", + { + opcode: "keybindReset", + blockType: Scratch.BlockType.COMMAND, + text: "reset binds of [event]", + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "resetAllKeybinds", + blockType: Scratch.BlockType.COMMAND, + text: "reset all keybindings" + }, + "---", + { + opcode: "keybindListTriggers", + blockType: Scratch.BlockType.REPORTER, + text: "list triggers of [event]", + disableMonitor: true, + arguments: { + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + { + opcode: "keybindKeysInTrigger", + blockType: Scratch.BlockType.REPORTER, + text: "keys bound to [trigger] in [event]", + disableMonitor: true, + arguments: { + trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, + event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } + } + }, + "---", + { + opcode: "listAllKeybinds", + blockType: Scratch.BlockType.REPORTER, + text: "list all keybindings" + }, + { + opcode: "listActiveKeybinds", + blockType: Scratch.BlockType.REPORTER, + text: "list active keybindings" + }, + createLabel("Tags"), + { + opcode: "createTag", + blockType: Scratch.BlockType.COMMAND, + text: "set value of tag: #[tag] to [[keys]]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, + keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } + } + }, + "---", + { + opcode: "deleteTag", + blockType: Scratch.BlockType.COMMAND, + text: "delete tag: #[tag]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" } + } + }, + { + opcode: "deleteAllTags", + blockType: Scratch.BlockType.COMMAND, + text: "delete all tags", + }, + "---", + { + opcode: "valueOfTag", + blockType: Scratch.BlockType.REPORTER, + text: "value of tag: #[tag]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" } + } + }, + { + opcode: "listTags", + blockType: Scratch.BlockType.REPORTER, + text: "list all tags" + }, + createLabel("Storage"), + { + opcode: "import", + blockType: Scratch.BlockType.COMMAND, + text: "import [type] from [json]", + arguments: { + type: { type: Scratch.ArgumentType.STRING, menu: "data" }, + json: { type: Scratch.ArgumentType.STRING, defaultValue: "" } + } + }, + { + opcode: "importError", + blockType: Scratch.BlockType.REPORTER, + text: "import error" + }, + "---", + { + opcode: "export", + blockType: Scratch.BlockType.REPORTER, + text: "export [type]", + disableMonitor: true, + arguments: { + type: { type: Scratch.ArgumentType.STRING, menu: "data" } + } + }, + createLabel("Settings"), + { + opcode: "toggleSetting", + blockType: Scratch.BlockType.COMMAND, + text: "set [setting] to [toggle]", + arguments: { + setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, + toggle: { type: Scratch.ArgumentType.STRING, menu: "toggle" } + } + }, + { + opcode: "isSettingEnabled", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [setting] enabled?", + disableMonitor: true, + arguments: { + setting: { type: Scratch.ArgumentType.STRING, menu: "settings" } + } + }, + "---", + { + opcode: "resetSettings", + blockType: Scratch.BlockType.COMMAND, + text: "reset all settings" + } + ], + menus: { + mouseButtons: { + items: [ + { text: "left", value: "0" }, + { text: "scroll wheel", value: "1" }, + { text: "right", value: "2" }, + { text: "back", value: "3" }, + { text: "forward", value: "4" }, + ], + acceptReporters: true + }, + keys: { items: "_getKeysMenu", acceptReporters: true }, + settings: { + items: [ + { text: "clear on blur", value: "clearOnBlur" }, + { text: "include tags on menu", value: "includeTags" }, + ] + }, + eventTriggerCondition: { items: ["when", "while", "after"] }, + keyProperty: { items: ["time", "name", "code", "value"] }, + returnMode: { items: ["together & in order", "together & ignore order", "individually"] }, + orderMode: { items: ["together & in order", "together & ignore order"] }, + toggle: { items: ["enabled", "disabled"] }, + data: { items: ["all", "tags", "keybinds", "settings"] }, + upDown: { items: ["up", "down"] } + } + }; + }; + // Filter + _filter() { + const keysPressed = runtime.ioDevices.keyboard._keysPressed.map(k => k.toLowerCase()); + for (const key in this._keysPressed) { + const keyName = key.slice(1).toLowerCase(); + if (_filter.shift.includes(key) && !keysPressed.includes("shift")) { + delete this._keysPressed["_left shift"]; + delete this._keysPressed["_right shift"]; + continue; + } + if (!keysPressed.includes(keyName) && (/^_[a-zA-Z]$/.test(key) || _filter.default.includes(key))) { + delete this._keysPressed[key]; + } + } + } + // Helper Functions + _getKeysPressed() { + const keys = Object.keys(this._keysPressed); + return keys.map(key => key.slice(1)); + } + _parse(keys) { + if (Array.isArray(keys)) return keys.map(key => Cast.toString(key)); + try { + const parsed = JSON.parse(/^\[.*\]$/.test(keys) ? keys : `[${keys}]`); + return Array.isArray(parsed) ? parsed.map(key => Cast.toString(key)) : []; + } catch { + return []; + }; + } + _isKeyPressed(_key, _source) { + const key = Cast.toString(_key) + const keysPressed = this._getKeysPressed(); + if (keysPressed.length) keysPressed.unshift("any"); + if (key.startsWith("#")) { + const source = (this._tags[key] ?? []).find(currentKey => keysPressed.includes(currentKey)); + return _source + ? { source, isPressed: !!source } + : !!source; + } + return _source + ? { source: key, isPressed: keysPressed.includes(key) } + : keysPressed.includes(key); + } + _isKeysPressed(_keys, ordered) { + const keys = this._parse(_keys); + if (!keys.length) return false; + if (ordered) { + return this._getKeysPressed().filter(key => keys.includes(key)).join() === keys.join(); + } + return keys.every(key => this._isKeyPressed(key)); + } + _isKeybindTriggered(_event) { + const event = this._keybinds[_event]; + for (const trigger in event) { + const mode = event[trigger]["mode"]; + const keys = event[trigger]["keys"]; + const triggered = this._isKeysPressed(keys, mode === "together & in order"); + if (triggered) return { isTriggered: true, cause: trigger }; + } + return { isTriggered: false, cause: undefined }; + } + + // Mouse + eventMouseDown(args) { + return this.isMouseDown(args); + } + + isMouseDown(args) { + return Cast.toNumber(args.button) in this._mouseDown; + } + isMouseHit(args) { + const time = this.timeMouseDown(args); + return time !== 0 && time <= 0.075; + } + + timeMouseDown(args) { + const button = this._mouseDown[Cast.toNumber(args.button)]; + return button ? (Date.now() - button.time) / 1000 : 0; + } + // Scrolling + eventScroll(args) { + return args.dir === "up" ? this._scrollDeltaY < 0 : this._scrollDeltaY > 0; + } + isScrolling({ dir }) { + const withinRange = Date.now() - this._lastScrollTime < 50; + return dir === "up" ? this._scrollDeltaY < 0 && withinRange : this._scrollDeltaY > 0 && withinRange; + } + scrollDirection() { + if (this._scrollDeltaY && Date.now() - this._lastScrollTime < 50) { + return this._scrollDeltaY < 0 ? "up" : "down"; + } + return "none"; + } + // Keys + eventKeysPressed(args) { + const ordered = args.ordered === "together & in order"; + return this._isKeysPressed(args.keys, ordered); + } + eventKeyPressed(args) { + return this._isKeyPressed(args.key); + } + + isKeysPressed(args) { + const ordered = args.ordered === "together & in order"; + return this._isKeysPressed(args.keys, ordered); + } + isKeyPressed(args) { + return this._isKeyPressed(args.key); + } + + isKeysHit(args) { + const time = this.timeKeysPressed(args); + return time !== 0 && time <= 0.075; + } + isKeyHit(args) { + const time = this.timeKeyPressed(args); + return time !== 0 && time <= 0.075; + } + + lastKeyPressed() { + return this._lastKeyPressed.slice(1); + } + currentKeysPressed() { + return JSON.stringify(this._getKeysPressed()); + } + currentKeyPressed() { + return this._getKeysPressed().reverse()[0] || "None"; + } + keyPressedProperty(args) { + const key = this.currentKeyPressed(); + if (key === "None") return 0; + switch (args.property) { + case "time": + return this.timeKeyPressed({ key: key }); + case "name": + return key; + case "code": + return this._keysPressed["_" + key].code; + case "value": + return this._keysPressed["_" + key].value; + } + } + + timeKeysPressed(args) { + const keys = this._parse(args.keys); + if (!keys.length) return 0; + if (args.mode === "individually") { + return JSON.stringify(keys.map(key => this.timeKeyPressed({ key }))); + } + const ordered = args.mode === "together & in order"; + const isKeysPressed = this._isKeysPressed(keys, ordered); + console.log(isKeysPressed) + if (isKeysPressed) { + const key = ordered + ? keys[keys.length - 1] + : this._getKeysPressed() + .filter(key => keys.includes(key)) + .reverse()[0]; + return this.timeKeyPressed({ key: key }); + } + return 0; + } + timeKeyPressed(args) { + const data = this._isKeyPressed(args.key, true); + const key = this._keysPressed["_" + data.source]; + return key ? (Date.now() - key.time) / 1000 : 0; + } + // Keybinding + eventKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; + } + + whileKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; + } + whenKeybindTriggered(args) { + const time = this.timeKeybindTriggered(args); + return time !== 0 && time <= 0.075; + } + + timeKeybindTriggered(args) { + const event = Cast.toString(args.event); + const data = this._isKeybindTriggered(event); + const trigger = this._keybinds[event]?.[data.cause]; + if (!data.isTriggered) return 0; + return this.timeKeysPressed({ "mode": trigger.mode, "keys": trigger.keys }); + } + causeKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).cause ?? ""; + } + + keybindBindMultiple(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + this._keybinds[event] ??= {}; + this._keybinds[event][trigger] = { "mode": args.mode, "keys": this._parse(args.keys) }; + } + keybindBind(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + this._keybinds[event] ??= {}; + this._keybinds[event][trigger] = { "mode": "together & in order", "keys": [Cast.toString(args.key)] }; + }; + keybindUnbind(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + if (!this._keybinds[event]) return; + delete this._keybinds[event][trigger]; + if (!Object.keys(this._keybinds[event]).length) delete this._keybinds[event]; + }; + + keybindReset(args) { + delete this._keybinds[Cast.toString(args.event)]; + } + resetAllKeybinds() { + this._keybinds = {} + } + + keybindListTriggers(args) { + return JSON.stringify(Object.keys(this._keybinds[Cast.toString(args.event)] ?? {})); + } + keybindKeysInTrigger(args) { + const event = this._keybinds[Cast.toString(args.event)]; + return event?.[Cast.toString(args.trigger)] + ? JSON.stringify(event[args.trigger]["keys"]) + : "[]"; + } + + listAllKeybinds() { + return JSON.stringify(Object.keys(this._keybinds)); + } + listActiveKeybinds() { + return JSON.stringify(Object.keys(this._keybinds).filter(event => + this._isKeybindTriggered(event).isTriggered + )); + } + // Tags + createTag(args) { + this._tags["#" + args.tag] = this._parse(args.keys); + } + + deleteTag(args) { + delete this._tags["#" + args.tag]; + } + deleteAllTags() { + this._tags = {} + } + + valueOfTag(args) { + return JSON.stringify(this._tags["#" + args.tag] ?? []); + } + listTags() { + return JSON.stringify(Object.keys(this._tags)); + } + // Settings + toggleSetting(args) { + const boolean = (args.toggle === "enabled") ? true : false; + this._settings[args.setting] = boolean; + } + isSettingEnabled(args) { + return this._settings[args.setting]; + } + resetSettings() { + this._settings = { + "clearOnBlur": true, + "includeTags": true + } + } + // Storage + + import(args) { + const result = this.validate(args.type, args.json); + if (result.error) return this._importError = result.error; + this._importError = "None"; + switch (args.type) { + case "tags": + this._tags = result.output; + case "keybinds": + this._keybinds = result.output; + case "settings": + this._settings = result.output; + case "all": + this._tags = result.output["tags"]; + this._keybinds = result.output["keybinds"]; + this._settings = result.output["settings"]; + } + } + importError() { + return this._importError; + } + + export(args) { + switch (args.type) { + case "tags": + return JSON.stringify(this._tags); + case "keybinds": + return JSON.stringify(this._keybinds); + case "settings": + return JSON.stringify(this._settings); + case "all": + return JSON.stringify({ tags: this._tags, keybinds: this._keybinds, settings: this._settings }); + } + } + + validate(_type, _data) { + let data = typeof _data === "string" ? _data : JSON.stringify(_data); + try { data = JSON.parse(data); } + catch { return { error: "Invalid JSON" }; }; + + if (typeof data !== "object" || data === null) return { error: "Invalid JSON" }; + if (Array.isArray(data)) return { error: "Input can't be an Array" } + + if (_type === "tags") { + for (const tag in data) { + if (tag.startsWith("#")) { + if (Array.isArray(data[tag])) { + const index = data[tag].findIndex(item => typeof item !== "string"); + if (index !== -1) return { error: `Tags: Expected string at item #${index} of '${tag}'` }; + } + else { return { error: `Tags: Expected array at '${tag}'` }; } + } else { return { error: `Tags: Invalid tag id, reading '${tag}'`} } + } + } + if (_type === "keybinds") { + for (const event in data) { + if (typeof data[event] === "object" && data[event] !== null) { + for (const trigger in data[event]) { + const triggerData = data[event][trigger] + if (typeof triggerData === "object" && trigger) { + if (triggerData["mode"]) { + if (triggerData["mode"] === "together & in order" || triggerData["mode"] === "together & ignore order") { + if (triggerData["keys"]) { + if (Array.isArray(triggerData["keys"])) { + const index = triggerData["keys"].findIndex(item => typeof item !== "string"); + if (index !== -1) { + return { error: `Keybinds: Expected string at item #${index} of '${event}/${trigger}'` }; + } + } else { return { error: `Keybinds: Expected array at '${event}/${trigger}/keys'` } } + } else { return { error: `Missing 'keys' at '${event}/${trigger}'` } } + } else { return { error: `Keybinds: Invalid input in '${event}/${trigger}/mode'` } } + } else { return { error: `Missing 'mode' at '${event}/${trigger}'` } } + } else { return { error: `Keybinds: Expected JSON at '${event}/${trigger}'` }} + } + } else { return { error: `Keybinds: Expected JSON at '${event}'` }; } + } + } + if (_type === "settings") { + for (const setting in data) { + if (typeof data[setting] !== "boolean") { + return { error: `Settings: Expected boolean at '${setting}'` }; + } + } + } + if (_type === "all") { + if (!("settings" in data)) return { error: "Missing 'settings'" }; + if (!("tags" in data)) return { error: "Missing 'tags'" }; + if (!("keybinds" in data)) return { error: "Missing 'keybinds'" }; + + const tagsValidation = this.validate("tags", data["tags"]); + if (tagsValidation.error) return { error: `Tags: ${tagsValidation.error}` }; + + const keybindsValidation = this.validate("keybinds", data["keybinds"]); + if (keybindsValidation.error) return { error: `Keybinds: ${keybindsValidation.error}` }; + + const settingsValidation = this.validate("settings", data["settings"]); + if (settingsValidation.error) return { error: `Settings: ${settingsValidation.error}` }; + } + + return { output: data }; + } + + + // Menu + _getKeysMenu() { + const tags = this._settings["includeTags"] ? Object.keys(this._tags) : []; + return [ + "space", "up arrow", "down arrow", "right arrow", "left arrow", + "backspace", "enter", "any", "right shift", "left shift", "right control", "left control", "right alt", "left alt", "right windows key", "left windows key", "context menu", "escape", "tab", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "[", "]", "{", "}", "\\", "|", ";", ":", "'", "\"", ",", ".", "/", "?", "<", ">", + "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "caps lock", "scroll lock", "num lock", "insert", "delete", "home", "end", "page up", "page down", + "numpad: divide", "numpad: multiply", "numpad: subtract", "numpad: add", "numpad: 0", "numpad: 1", "numpad: 2", "numpad: 3", "numpad: 4", "numpad: 5", "numpad: 6", "numpad: 7", "numpad: 8", "numpad: 9", "numpad: decimal", "numpad: enter", + ...tags + ]; + } + } + + Scratch.extensions.register(new enderKeysPlusV2()); +})(Scratch); From d10cbfb872d611a6bfbc3d25997d1578bffa0e73 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 12:19:53 +0800 Subject: [PATCH 02/18] Create keysPlusV2.svg --- images/StackOverflow/keysPlusV2.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 images/StackOverflow/keysPlusV2.svg diff --git a/images/StackOverflow/keysPlusV2.svg b/images/StackOverflow/keysPlusV2.svg new file mode 100644 index 0000000000..842b5271db --- /dev/null +++ b/images/StackOverflow/keysPlusV2.svg @@ -0,0 +1 @@ +KeysPlus2 From 9d73979bfade14f69995ae9c2832e65f755f616c Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 12:24:47 +0800 Subject: [PATCH 03/18] Update keysPlusV2.js -temporarily remove wiki --- extensions/StackOverflow/keysPlusV2.js | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/StackOverflow/keysPlusV2.js b/extensions/StackOverflow/keysPlusV2.js index ee00a1d6f9..1af9cc9bac 100644 --- a/extensions/StackOverflow/keysPlusV2.js +++ b/extensions/StackOverflow/keysPlusV2.js @@ -148,7 +148,6 @@ return { id: "enderKeysPlusV2", name: "Keys+ V2", - docsURI: "https://github.com/Ender-Studio/EnderStudio-Extensions/wiki/Extensions#keys", color1: "#647970", color2: "#4D5E56", blocks: [ From 1772cee2b3629d2dd555613fa5e7d8795f831ad6 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 12:26:43 +0800 Subject: [PATCH 04/18] Update extensions.json --- extensions/extensions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/extensions.json b/extensions/extensions.json index 97cd039e69..5af8698d13 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -1,5 +1,6 @@ [ // This file supports comments + "StackOverflow/keysPlusV2", "lab/text", "stretch", "gamepad", From 6745caaa8c457a9a117c379fe13a01359c55902c Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Sat, 8 Feb 2025 04:56:11 +0000 Subject: [PATCH 05/18] [Automated] Format code --- extensions/StackOverflow/keysPlusV2.js | 2505 +++++++++++++++--------- 1 file changed, 1603 insertions(+), 902 deletions(-) diff --git a/extensions/StackOverflow/keysPlusV2.js b/extensions/StackOverflow/keysPlusV2.js index 1af9cc9bac..49161b6fee 100644 --- a/extensions/StackOverflow/keysPlusV2.js +++ b/extensions/StackOverflow/keysPlusV2.js @@ -5,944 +5,1645 @@ // Original: StackOverflow // License: MIT & LGPL-3.0 -(function(Scratch){ - "use strict"; +(function (Scratch) { + "use strict"; - if (!Scratch.extensions.unsandboxed) throw new Error("Keys+ V2 must run unsandboxed!"); + if (!Scratch.extensions.unsandboxed) + throw new Error("Keys+ V2 must run unsandboxed!"); - const runtime = Scratch.vm.runtime; - const Cast = Scratch.Cast; + const runtime = Scratch.vm.runtime; + const Cast = Scratch.Cast; - const _format = { - toValue: [ - "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", - "Digit0", "Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", - "KeyA", "KeyB", "KeyC", "KeyD", "KeyE", "KeyF", "KeyG", "KeyH", "KeyI", "KeyJ", "KeyK", "KeyL", "KeyM", "KeyN", "KeyO", "KeyP", "KeyQ", "KeyR", "KeyS", "KeyT", "KeyU", "KeyV", "KeyW", "KeyX", "KeyY", "KeyZ", - "Backquote", "Minus", "Equal", "BracketLeft", "BracketRight", "Backslash", "Semicolon", "Quote", "Comma", "Period", "Slash" + const _format = { + toValue: [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "Digit0", + "Digit1", + "Digit2", + "Digit3", + "Digit4", + "Digit5", + "Digit6", + "Digit7", + "Digit8", + "Digit9", + "KeyA", + "KeyB", + "KeyC", + "KeyD", + "KeyE", + "KeyF", + "KeyG", + "KeyH", + "KeyI", + "KeyJ", + "KeyK", + "KeyL", + "KeyM", + "KeyN", + "KeyO", + "KeyP", + "KeyQ", + "KeyR", + "KeyS", + "KeyT", + "KeyU", + "KeyV", + "KeyW", + "KeyX", + "KeyY", + "KeyZ", + "Backquote", + "Minus", + "Equal", + "BracketLeft", + "BracketRight", + "Backslash", + "Semicolon", + "Quote", + "Comma", + "Period", + "Slash", + ], + toCustomValue: { + MetaLeft: "left windows key", + MetaRight: "right windows key", + ControlLeft: "left control", + ControlRight: "right control", + AltLeft: "left alt", + AltRight: "right alt", + ShiftLeft: "left shift", + ShiftRight: "right shift", + ArrowUp: "up arrow", + ArrowDown: "down arrow", + ArrowLeft: "left arrow", + ArrowRight: "right arrow", + }, + }; + const _filter = { + default: [ + "_a", + "_b", + "_c", + "_d", + "_e", + "_f", + "_g", + "_h", + "_i", + "_j", + "_k", + "_l", + "_m", + "_n", + "_o", + "_p", + "_q", + "_r", + "_s", + "_t", + "_u", + "_v", + "_w", + "_x", + "_y", + "_z", + "_A", + "_B", + "_C", + "_D", + "_E", + "_F", + "_G", + "_H", + "_I", + "_J", + "_K", + "_L", + "_M", + "_N", + "_O", + "_P", + "_Q", + "_R", + "_S", + "_T", + "_U", + "_V", + "_W", + "_X", + "_Y", + "_Z", + "_0", + "_1", + "_2", + "_3", + "_4", + "_5", + "_6", + "_7", + "_8", + "_9", + "_`", + "_~", + "_!", + "_@", + "_#", + "_$", + "_%", + "_^", + "_&", + "_*", + "_(", + "_)", + "_-", + "__", + "_=", + "_+", + "_[", + "_{", + "_]", + "_}", + "_\\", + "_|", + "_;", + "_:", + "_'", + '"', + "_,", + "_<", + "_.", + "_>", + "_/", + "_?", + "_ ", + ], + shift: ["_left shift", "_right shift"], + }; + function format(_key) { + const name = _key.code, + value = _key.key; + if (_format.toValue.includes(name)) return "_" + value; + if (name.startsWith("Numpad")) + return "_numpad: " + name.slice(6).toLowerCase(); + if (name in _format.toCustomValue) return "_" + _format.toCustomValue[name]; + return "_" + name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase(); + } + const createLabel = (text) => ({ + blockType: Scratch.BlockType.LABEL, + text: text, + }); + + function startKeyEvents(_trigger) { + runtime.startHats("enderKeysPlusV2_eventKeyPressed", { trigger: _trigger }); + runtime.startHats("enderKeysPlusV2_eventKeysPressed", { + trigger: _trigger, + }); + runtime.startHats("enderKeysPlusV2_eventKeybindTriggered", { + trigger: _trigger, + }); + } + + class enderKeysPlusV2 { + constructor() { + this._settings = { + clearOnBlur: true, + includeTags: true, + }; + + this._tags = { + "#a": ["a", "A"], + "#b": ["b", "B"], + "#c": ["c", "C"], + "#d": ["d", "D"], + "#e": ["e", "E"], + "#f": ["f", "F"], + "#g": ["g", "G"], + "#h": ["h", "H"], + "#i": ["i", "I"], + "#j": ["j", "J"], + "#k": ["k", "K"], + "#l": ["l", "L"], + "#m": ["m", "M"], + "#n": ["n", "N"], + "#o": ["o", "O"], + "#p": ["p", "P"], + "#q": ["q", "Q"], + "#r": ["r", "R"], + "#s": ["s", "S"], + "#t": ["t", "T"], + "#u": ["u", "U"], + "#v": ["v", "V"], + "#w": ["w", "W"], + "#x": ["x", "X"], + "#y": ["y", "Y"], + "#z": ["z", "Z"], + "#letters": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", ], - toCustomValue: { - "MetaLeft": "left windows key", - "MetaRight": "right windows key", - "ControlLeft": "left control", - "ControlRight": "right control", - "AltLeft": "left alt", - "AltRight": "right alt", - "ShiftLeft": "left shift", - "ShiftRight": "right shift", - "ArrowUp": "up arrow", - "ArrowDown": "down arrow", - "ArrowLeft": "left arrow", - "ArrowRight": "right arrow" - } - }; - const _filter = { - default: [ - "_a", "_b", "_c", "_d", "_e", "_f", "_g", "_h", "_i", "_j", "_k", "_l", "_m", - "_n", "_o", "_p", "_q", "_r", "_s", "_t", "_u", "_v", "_w", "_x", "_y", "_z", - "_A", "_B", "_C", "_D", "_E", "_F", "_G", "_H", "_I", "_J", "_K", "_L", "_M", - "_N", "_O", "_P", "_Q", "_R", "_S", "_T", "_U", "_V", "_W", "_X", "_Y", "_Z", - "_0", "_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9", - "_`", "_~", "_!", "_@", "_#", "_$", "_%", "_^", "_&", "_*", - "_(", "_)", "_-", "__", "_=", "_+", "_[", "_{", "_]", "_}", - "_\\", "_|", "_;", "_:", "_'", "\"", "_,", "_<", "_.", "_>", - "_/", "_?", "_ " + "#uppercaseLetters": [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", ], - shift: [ "_left shift", "_right shift" ], - }; - function format(_key) { - const name = _key.code, value = _key.key; - if (_format.toValue.includes(name)) return "_" + value; - if (name.startsWith("Numpad")) return "_numpad: " + name.slice(6).toLowerCase(); - if (name in _format.toCustomValue) return "_" + _format.toCustomValue[name]; - return "_" + name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase(); - } - const createLabel = (text) => ({ blockType: Scratch.BlockType.LABEL, text: text }); - - function startKeyEvents(_trigger) { - runtime.startHats("enderKeysPlusV2_eventKeyPressed", { trigger: _trigger }); - runtime.startHats("enderKeysPlusV2_eventKeysPressed", { trigger: _trigger }); - runtime.startHats("enderKeysPlusV2_eventKeybindTriggered", { trigger: _trigger }); - } - - class enderKeysPlusV2 { - constructor() { - this._settings = { - "clearOnBlur": true, - "includeTags": true - }; - - this._tags = { - "#a": ["a", "A"], "#b": ["b", "B"], "#c": ["c", "C"], "#d": ["d", "D"], "#e": ["e", "E"], "#f": ["f", "F"], "#g": ["g", "G"], "#h": ["h", "H"], "#i": ["i", "I"], "#j": ["j", "J"], "#k": ["k", "K"], "#l": ["l", "L"], "#m": ["m", "M"], - "#n": ["n", "N"], "#o": ["o", "O"], "#p": ["p", "P"], "#q": ["q", "Q"], "#r": ["r", "R"], "#s": ["s", "S"], "#t": ["t", "T"], "#u": ["u", "U"], "#v": ["v", "V"], "#w": ["w", "W"], "#x": ["x", "X"], "#y": ["y", "Y"], "#z": ["z", "Z"], - "#letters": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], - "#uppercaseLetters": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"], - "#lowercaseLetters": ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"], - "#vowels": ["a", "e", "i", "o", "u"], - "#consonants": ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"], - "#numbers": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], - "#specialCharacters": ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+", "=", "{", "}", "[", "]", "|", "\\", ":", ";", "'", "\"", "<", ">", ",", ".", "?", "/", "~", "`"], - "#shift": ["left shift", "right shift"], - "#alt": ["left alt", "right alt"], - "#control": ["left control", "right control"], - "#windowsKey": ["left windows key", "right windows key"], - "#arrowKeys": ["up arrow", "down arrow", "right arrow", "left arrow"], - "#functionKeys": ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"], - "#navigationKeys": ["up arrow", "down arrow", "right arrow", "left arrow", "home", "end", "page up", "page down", "insert", "delete"], - "#numpad": ["numpad: divide", "numpad: multiply", "numpad: subtract", "numpad: add", "numpad: 0", "numpad: 1", "numpad: 2", "numpad: 3", "numpad: 4", "numpad: 5", "numpad: 6", "numpad: 7", "numpad: 8", "numpad: 9", "numpad: decimal", "numpad: enter"] - } + "#lowercaseLetters": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + ], + "#vowels": ["a", "e", "i", "o", "u"], + "#consonants": [ + "b", + "c", + "d", + "f", + "g", + "h", + "j", + "k", + "l", + "m", + "n", + "p", + "q", + "r", + "s", + "t", + "v", + "w", + "x", + "y", + "z", + ], + "#numbers": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "#specialCharacters": [ + "!", + "@", + "#", + "$", + "%", + "^", + "&", + "*", + "(", + ")", + "_", + "+", + "=", + "{", + "}", + "[", + "]", + "|", + "\\", + ":", + ";", + "'", + '"', + "<", + ">", + ",", + ".", + "?", + "/", + "~", + "`", + ], + "#shift": ["left shift", "right shift"], + "#alt": ["left alt", "right alt"], + "#control": ["left control", "right control"], + "#windowsKey": ["left windows key", "right windows key"], + "#arrowKeys": ["up arrow", "down arrow", "right arrow", "left arrow"], + "#functionKeys": [ + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + ], + "#navigationKeys": [ + "up arrow", + "down arrow", + "right arrow", + "left arrow", + "home", + "end", + "page up", + "page down", + "insert", + "delete", + ], + "#numpad": [ + "numpad: divide", + "numpad: multiply", + "numpad: subtract", + "numpad: add", + "numpad: 0", + "numpad: 1", + "numpad: 2", + "numpad: 3", + "numpad: 4", + "numpad: 5", + "numpad: 6", + "numpad: 7", + "numpad: 8", + "numpad: 9", + "numpad: decimal", + "numpad: enter", + ], + }; - this._keybinds = {}; + this._keybinds = {}; - this._mouseDown = {}; - this._scrollDeltaY = 0; - this._lastScrollTime = Date.now(); + this._mouseDown = {}; + this._scrollDeltaY = 0; + this._lastScrollTime = Date.now(); - this._keysPressed = {}; - this._lastKeyPressed = ""; + this._keysPressed = {}; + this._lastKeyPressed = ""; - this._importError = "None"; + this._importError = "None"; - runtime.on("BEFORE_EXECUTE", () => { - runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "while" }); - startKeyEvents("while"); - }); - // Keys - window.addEventListener("keydown", (event) => { - const key = format(event); - this._lastKeyPressed = key; - if (!this._keysPressed[key]) { - this._keysPressed[key] = { time: Date.now(), code: event.code, value: event.key }; - startKeyEvents("when"); - }; - }); - window.addEventListener("keyup", (event) => { - const key = format(event); - startKeyEvents("after"); - delete this._keysPressed[key]; - this._filter(); - }); - window.addEventListener("blur", () => { - if (this._settings["clearOnBlur"]) { - this._keysPressed = {}; - this._mouseDown = {}; - }; - }); - // Mouse - window.addEventListener("mousedown", (event) => { - if (!this._mouseDown[event.button]) { - this._mouseDown[event.button] = { time: Date.now() }; - runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "when" }); - } - }); - window.addEventListener("mouseup", (event) => { - runtime.startHats("enderKeysPlusV2_eventMouseDown", { trigger: "after" }); - delete this._mouseDown[event.button]; - }) - window.addEventListener("wheel", (event) => { - this._scrollDeltaY = event.deltaY; - this._lastScrollTime = Date.now() - runtime.startHats("enderKeysPlusV2_eventScroll") - }); - } - getInfo() { - return { - id: "enderKeysPlusV2", - name: "Keys+ V2", - color1: "#647970", - color2: "#4D5E56", - blocks: [ - createLabel("Mouse"), - { - opcode: "eventMouseDown", - blockType: Scratch.BlockType.HAT, - text: "[trigger] [button] mouse button is down", - isEdgeActivated: false, - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, - button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } - } - }, - "---", - { - opcode: "isMouseDown", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [button] mouse button down?", - arguments: { - button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } - } - }, - { - opcode: "isMouseHit", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [button] mouse button clicked?", - arguments: { - button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } - } - }, - "---", - { - opcode: "timeMouseDown", - blockType: Scratch.BlockType.REPORTER, - text: "time [button] mouse button down", - arguments: { - button: { type: Scratch.ArgumentType.STRING, menu: "mouseButtons" } - } - }, - createLabel("Scrolling"), - { - opcode: "eventScroll", - blockType: Scratch.BlockType.HAT, - text: "when scrolling [dir]", - isEdgeActivated: false, - arguments: { - dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" } - } - }, - { - opcode: "isScrolling", - blockType: Scratch.BlockType.BOOLEAN, - text: "is scrolling [dir]?", - disableMonitor: true, - arguments: { - dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" } - } - }, - "---", - { - opcode: "scrollDirection", - blockType: Scratch.BlockType.REPORTER, - text: "scroll direction" - }, - createLabel("Keys"), - { - opcode: "eventKeysPressed", - blockType: Scratch.BlockType.HAT, - text: "[trigger] [[keys]] keys is pressed [ordered]", - isEdgeActivated: false, - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, - ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } - } - }, - { - opcode: "eventKeyPressed", - blockType: Scratch.BlockType.HAT, - text: "[trigger] [key] key is pressed", - isEdgeActivated: false, - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, - key: { type: Scratch.ArgumentType.STRING, menu: "keys" } - } - }, - "---", - { - opcode: "isKeysPressed", - blockType: Scratch.BlockType.BOOLEAN, - text: "are [[keys]] keys pressed [ordered]?", - arguments: { - ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } - } - }, - { - opcode: "isKeyPressed", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [key] key pressed?", - arguments: { - key: { type: Scratch.ArgumentType.STRING, menu: "keys" } - } - }, - "---", - { - opcode: "isKeysHit", - blockType: Scratch.BlockType.BOOLEAN, - text: "are [[keys]] keys hit [ordered]?", - arguments: { - ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } - } - }, - { - opcode: "isKeyHit", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [key] key hit?", - arguments: { - key: { type: Scratch.ArgumentType.STRING, menu: "keys" } - } - }, - "---", - { - opcode: "lastKeyPressed", - blockType: Scratch.BlockType.REPORTER, - text: "last key pressed" - }, - { - opcode: "currentKeysPressed", - blockType: Scratch.BlockType.REPORTER, - text: "current keys pressed", - }, - { - opcode: "currentKeyPressed", - blockType: Scratch.BlockType.REPORTER, - text: "current key pressed", - }, - { - opcode: "keyPressedProperty", - blockType: Scratch.BlockType.REPORTER, - text: "current key pressed [property]", - disableMonitor: true, - arguments: { - property: { type: Scratch.ArgumentType.STRING, menu: "keyProperty" } - } - }, - "---", - { - opcode: "timeKeysPressed", - blockType: Scratch.BlockType.REPORTER, - text: "time [[keys]] keys pressed [mode]", - disableMonitor: true, - arguments: { - mode: { type: Scratch.ArgumentType.STRING, menu: "returnMode" }, - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } - } - }, - { - opcode: "timeKeyPressed", - blockType: Scratch.BlockType.REPORTER, - text: "time [key] key pressed", - disableMonitor: true, - arguments: { - key: { type: Scratch.ArgumentType.STRING, menu: "keys" } - } - }, - createLabel("Keybinding"), - { - opcode: "eventKeybindTriggered", - blockType: Scratch.BlockType.HAT, - text: "[trigger] [event] is triggered", - isEdgeActivated: false, - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, menu: "eventTriggerCondition" }, - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - "---", - { - opcode: "whileKeybindTriggered", - blockType: Scratch.BlockType.BOOLEAN, - text: "while [event] is triggered?", - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "whenKeybindTriggered", - blockType: Scratch.BlockType.BOOLEAN, - text: "when [event] is triggered?", - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - "---", - { - opcode: "causeKeybindTriggered", - blockType: Scratch.BlockType.REPORTER, - text: "cause of [event] triggered", - disableMonitor: true, - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "timeKeybindTriggered", - blockType: Scratch.BlockType.REPORTER, - text: "time [event] is triggered", - disableMonitor: true, - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - "---", - { - opcode: "keybindBindMultiple", - blockType: Scratch.BlockType.COMMAND, - text: "bind [[keys]] keys [mode] as [trigger] to [event]", - arguments: { - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' }, - mode: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, - trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "keybindBind", - blockType: Scratch.BlockType.COMMAND, - text: "bind key [key] as [trigger] to [event]", - arguments: { - key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, - trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "keybindUnbind", - blockType: Scratch.BlockType.COMMAND, - text: "unbind trigger [trigger] from [event]", - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - "---", - { - opcode: "keybindReset", - blockType: Scratch.BlockType.COMMAND, - text: "reset binds of [event]", - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "resetAllKeybinds", - blockType: Scratch.BlockType.COMMAND, - text: "reset all keybindings" - }, - "---", - { - opcode: "keybindListTriggers", - blockType: Scratch.BlockType.REPORTER, - text: "list triggers of [event]", - disableMonitor: true, - arguments: { - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - { - opcode: "keybindKeysInTrigger", - blockType: Scratch.BlockType.REPORTER, - text: "keys bound to [trigger] in [event]", - disableMonitor: true, - arguments: { - trigger: { type: Scratch.ArgumentType.STRING, defaultValue: "trigger" }, - event: { type: Scratch.ArgumentType.STRING, defaultValue: "event" } - } - }, - "---", - { - opcode: "listAllKeybinds", - blockType: Scratch.BlockType.REPORTER, - text: "list all keybindings" - }, - { - opcode: "listActiveKeybinds", - blockType: Scratch.BlockType.REPORTER, - text: "list active keybindings" - }, - createLabel("Tags"), - { - opcode: "createTag", - blockType: Scratch.BlockType.COMMAND, - text: "set value of tag: #[tag] to [[keys]]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, - keys: { type: Scratch.ArgumentType.STRING, defaultValue: '"a", "b", "c"' } - } - }, - "---", - { - opcode: "deleteTag", - blockType: Scratch.BlockType.COMMAND, - text: "delete tag: #[tag]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" } - } - }, - { - opcode: "deleteAllTags", - blockType: Scratch.BlockType.COMMAND, - text: "delete all tags", - }, - "---", - { - opcode: "valueOfTag", - blockType: Scratch.BlockType.REPORTER, - text: "value of tag: #[tag]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" } - } - }, - { - opcode: "listTags", - blockType: Scratch.BlockType.REPORTER, - text: "list all tags" - }, - createLabel("Storage"), - { - opcode: "import", - blockType: Scratch.BlockType.COMMAND, - text: "import [type] from [json]", - arguments: { - type: { type: Scratch.ArgumentType.STRING, menu: "data" }, - json: { type: Scratch.ArgumentType.STRING, defaultValue: "" } - } - }, - { - opcode: "importError", - blockType: Scratch.BlockType.REPORTER, - text: "import error" - }, - "---", - { - opcode: "export", - blockType: Scratch.BlockType.REPORTER, - text: "export [type]", - disableMonitor: true, - arguments: { - type: { type: Scratch.ArgumentType.STRING, menu: "data" } - } - }, - createLabel("Settings"), - { - opcode: "toggleSetting", - blockType: Scratch.BlockType.COMMAND, - text: "set [setting] to [toggle]", - arguments: { - setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, - toggle: { type: Scratch.ArgumentType.STRING, menu: "toggle" } - } - }, - { - opcode: "isSettingEnabled", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [setting] enabled?", - disableMonitor: true, - arguments: { - setting: { type: Scratch.ArgumentType.STRING, menu: "settings" } - } - }, - "---", - { - opcode: "resetSettings", - blockType: Scratch.BlockType.COMMAND, - text: "reset all settings" - } - ], - menus: { - mouseButtons: { - items: [ - { text: "left", value: "0" }, - { text: "scroll wheel", value: "1" }, - { text: "right", value: "2" }, - { text: "back", value: "3" }, - { text: "forward", value: "4" }, - ], - acceptReporters: true - }, - keys: { items: "_getKeysMenu", acceptReporters: true }, - settings: { - items: [ - { text: "clear on blur", value: "clearOnBlur" }, - { text: "include tags on menu", value: "includeTags" }, - ] - }, - eventTriggerCondition: { items: ["when", "while", "after"] }, - keyProperty: { items: ["time", "name", "code", "value"] }, - returnMode: { items: ["together & in order", "together & ignore order", "individually"] }, - orderMode: { items: ["together & in order", "together & ignore order"] }, - toggle: { items: ["enabled", "disabled"] }, - data: { items: ["all", "tags", "keybinds", "settings"] }, - upDown: { items: ["up", "down"] } - } - }; - }; - // Filter - _filter() { - const keysPressed = runtime.ioDevices.keyboard._keysPressed.map(k => k.toLowerCase()); - for (const key in this._keysPressed) { - const keyName = key.slice(1).toLowerCase(); - if (_filter.shift.includes(key) && !keysPressed.includes("shift")) { - delete this._keysPressed["_left shift"]; - delete this._keysPressed["_right shift"]; - continue; - } - if (!keysPressed.includes(keyName) && (/^_[a-zA-Z]$/.test(key) || _filter.default.includes(key))) { - delete this._keysPressed[key]; - } - } + runtime.on("BEFORE_EXECUTE", () => { + runtime.startHats("enderKeysPlusV2_eventMouseDown", { + trigger: "while", + }); + startKeyEvents("while"); + }); + // Keys + window.addEventListener("keydown", (event) => { + const key = format(event); + this._lastKeyPressed = key; + if (!this._keysPressed[key]) { + this._keysPressed[key] = { + time: Date.now(), + code: event.code, + value: event.key, + }; + startKeyEvents("when"); } - // Helper Functions - _getKeysPressed() { - const keys = Object.keys(this._keysPressed); - return keys.map(key => key.slice(1)); + }); + window.addEventListener("keyup", (event) => { + const key = format(event); + startKeyEvents("after"); + delete this._keysPressed[key]; + this._filter(); + }); + window.addEventListener("blur", () => { + if (this._settings["clearOnBlur"]) { + this._keysPressed = {}; + this._mouseDown = {}; } - _parse(keys) { - if (Array.isArray(keys)) return keys.map(key => Cast.toString(key)); - try { - const parsed = JSON.parse(/^\[.*\]$/.test(keys) ? keys : `[${keys}]`); - return Array.isArray(parsed) ? parsed.map(key => Cast.toString(key)) : []; - } catch { - return []; - }; - } - _isKeyPressed(_key, _source) { - const key = Cast.toString(_key) - const keysPressed = this._getKeysPressed(); - if (keysPressed.length) keysPressed.unshift("any"); - if (key.startsWith("#")) { - const source = (this._tags[key] ?? []).find(currentKey => keysPressed.includes(currentKey)); - return _source - ? { source, isPressed: !!source } - : !!source; - } - return _source - ? { source: key, isPressed: keysPressed.includes(key) } - : keysPressed.includes(key); - } - _isKeysPressed(_keys, ordered) { - const keys = this._parse(_keys); - if (!keys.length) return false; - if (ordered) { - return this._getKeysPressed().filter(key => keys.includes(key)).join() === keys.join(); - } - return keys.every(key => this._isKeyPressed(key)); + }); + // Mouse + window.addEventListener("mousedown", (event) => { + if (!this._mouseDown[event.button]) { + this._mouseDown[event.button] = { time: Date.now() }; + runtime.startHats("enderKeysPlusV2_eventMouseDown", { + trigger: "when", + }); } - _isKeybindTriggered(_event) { - const event = this._keybinds[_event]; - for (const trigger in event) { - const mode = event[trigger]["mode"]; - const keys = event[trigger]["keys"]; - const triggered = this._isKeysPressed(keys, mode === "together & in order"); - if (triggered) return { isTriggered: true, cause: trigger }; - } - return { isTriggered: false, cause: undefined }; + }); + window.addEventListener("mouseup", (event) => { + runtime.startHats("enderKeysPlusV2_eventMouseDown", { + trigger: "after", + }); + delete this._mouseDown[event.button]; + }); + window.addEventListener("wheel", (event) => { + this._scrollDeltaY = event.deltaY; + this._lastScrollTime = Date.now(); + runtime.startHats("enderKeysPlusV2_eventScroll"); + }); + } + getInfo() { + return { + id: "enderKeysPlusV2", + name: "Keys+ V2", + color1: "#647970", + color2: "#4D5E56", + blocks: [ + createLabel("Mouse"), + { + opcode: "eventMouseDown", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [button] mouse button is down", + isEdgeActivated: false, + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + menu: "eventTriggerCondition", + }, + button: { + type: Scratch.ArgumentType.STRING, + menu: "mouseButtons", + }, + }, + }, + "---", + { + opcode: "isMouseDown", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [button] mouse button down?", + arguments: { + button: { + type: Scratch.ArgumentType.STRING, + menu: "mouseButtons", + }, + }, + }, + { + opcode: "isMouseHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [button] mouse button clicked?", + arguments: { + button: { + type: Scratch.ArgumentType.STRING, + menu: "mouseButtons", + }, + }, + }, + "---", + { + opcode: "timeMouseDown", + blockType: Scratch.BlockType.REPORTER, + text: "time [button] mouse button down", + arguments: { + button: { + type: Scratch.ArgumentType.STRING, + menu: "mouseButtons", + }, + }, + }, + createLabel("Scrolling"), + { + opcode: "eventScroll", + blockType: Scratch.BlockType.HAT, + text: "when scrolling [dir]", + isEdgeActivated: false, + arguments: { + dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" }, + }, + }, + { + opcode: "isScrolling", + blockType: Scratch.BlockType.BOOLEAN, + text: "is scrolling [dir]?", + disableMonitor: true, + arguments: { + dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" }, + }, + }, + "---", + { + opcode: "scrollDirection", + blockType: Scratch.BlockType.REPORTER, + text: "scroll direction", + }, + createLabel("Keys"), + { + opcode: "eventKeysPressed", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [[keys]] keys is pressed [ordered]", + isEdgeActivated: false, + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + menu: "eventTriggerCondition", + }, + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + }, + }, + { + opcode: "eventKeyPressed", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [key] key is pressed", + isEdgeActivated: false, + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + menu: "eventTriggerCondition", + }, + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + }, + }, + "---", + { + opcode: "isKeysPressed", + blockType: Scratch.BlockType.BOOLEAN, + text: "are [[keys]] keys pressed [ordered]?", + arguments: { + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + }, + }, + { + opcode: "isKeyPressed", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [key] key pressed?", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + }, + }, + "---", + { + opcode: "isKeysHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "are [[keys]] keys hit [ordered]?", + arguments: { + ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + }, + }, + { + opcode: "isKeyHit", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [key] key hit?", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + }, + }, + "---", + { + opcode: "lastKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "last key pressed", + }, + { + opcode: "currentKeysPressed", + blockType: Scratch.BlockType.REPORTER, + text: "current keys pressed", + }, + { + opcode: "currentKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "current key pressed", + }, + { + opcode: "keyPressedProperty", + blockType: Scratch.BlockType.REPORTER, + text: "current key pressed [property]", + disableMonitor: true, + arguments: { + property: { + type: Scratch.ArgumentType.STRING, + menu: "keyProperty", + }, + }, + }, + "---", + { + opcode: "timeKeysPressed", + blockType: Scratch.BlockType.REPORTER, + text: "time [[keys]] keys pressed [mode]", + disableMonitor: true, + arguments: { + mode: { type: Scratch.ArgumentType.STRING, menu: "returnMode" }, + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + }, + }, + { + opcode: "timeKeyPressed", + blockType: Scratch.BlockType.REPORTER, + text: "time [key] key pressed", + disableMonitor: true, + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + }, + }, + createLabel("Keybinding"), + { + opcode: "eventKeybindTriggered", + blockType: Scratch.BlockType.HAT, + text: "[trigger] [event] is triggered", + isEdgeActivated: false, + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + menu: "eventTriggerCondition", + }, + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + "---", + { + opcode: "whileKeybindTriggered", + blockType: Scratch.BlockType.BOOLEAN, + text: "while [event] is triggered?", + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "whenKeybindTriggered", + blockType: Scratch.BlockType.BOOLEAN, + text: "when [event] is triggered?", + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + "---", + { + opcode: "causeKeybindTriggered", + blockType: Scratch.BlockType.REPORTER, + text: "cause of [event] triggered", + disableMonitor: true, + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "timeKeybindTriggered", + blockType: Scratch.BlockType.REPORTER, + text: "time [event] is triggered", + disableMonitor: true, + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + "---", + { + opcode: "keybindBindMultiple", + blockType: Scratch.BlockType.COMMAND, + text: "bind [[keys]] keys [mode] as [trigger] to [event]", + arguments: { + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + mode: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, + trigger: { + type: Scratch.ArgumentType.STRING, + defaultValue: "trigger", + }, + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "keybindBind", + blockType: Scratch.BlockType.COMMAND, + text: "bind key [key] as [trigger] to [event]", + arguments: { + key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, + trigger: { + type: Scratch.ArgumentType.STRING, + defaultValue: "trigger", + }, + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "keybindUnbind", + blockType: Scratch.BlockType.COMMAND, + text: "unbind trigger [trigger] from [event]", + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + defaultValue: "trigger", + }, + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + "---", + { + opcode: "keybindReset", + blockType: Scratch.BlockType.COMMAND, + text: "reset binds of [event]", + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "resetAllKeybinds", + blockType: Scratch.BlockType.COMMAND, + text: "reset all keybindings", + }, + "---", + { + opcode: "keybindListTriggers", + blockType: Scratch.BlockType.REPORTER, + text: "list triggers of [event]", + disableMonitor: true, + arguments: { + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + { + opcode: "keybindKeysInTrigger", + blockType: Scratch.BlockType.REPORTER, + text: "keys bound to [trigger] in [event]", + disableMonitor: true, + arguments: { + trigger: { + type: Scratch.ArgumentType.STRING, + defaultValue: "trigger", + }, + event: { + type: Scratch.ArgumentType.STRING, + defaultValue: "event", + }, + }, + }, + "---", + { + opcode: "listAllKeybinds", + blockType: Scratch.BlockType.REPORTER, + text: "list all keybindings", + }, + { + opcode: "listActiveKeybinds", + blockType: Scratch.BlockType.REPORTER, + text: "list active keybindings", + }, + createLabel("Tags"), + { + opcode: "createTag", + blockType: Scratch.BlockType.COMMAND, + text: "set value of tag: #[tag] to [[keys]]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, + keys: { + type: Scratch.ArgumentType.STRING, + defaultValue: '"a", "b", "c"', + }, + }, + }, + "---", + { + opcode: "deleteTag", + blockType: Scratch.BlockType.COMMAND, + text: "delete tag: #[tag]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, + }, + }, + { + opcode: "deleteAllTags", + blockType: Scratch.BlockType.COMMAND, + text: "delete all tags", + }, + "---", + { + opcode: "valueOfTag", + blockType: Scratch.BlockType.REPORTER, + text: "value of tag: #[tag]", + arguments: { + tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, + }, + }, + { + opcode: "listTags", + blockType: Scratch.BlockType.REPORTER, + text: "list all tags", + }, + createLabel("Storage"), + { + opcode: "import", + blockType: Scratch.BlockType.COMMAND, + text: "import [type] from [json]", + arguments: { + type: { type: Scratch.ArgumentType.STRING, menu: "data" }, + json: { type: Scratch.ArgumentType.STRING, defaultValue: "" }, + }, + }, + { + opcode: "importError", + blockType: Scratch.BlockType.REPORTER, + text: "import error", + }, + "---", + { + opcode: "export", + blockType: Scratch.BlockType.REPORTER, + text: "export [type]", + disableMonitor: true, + arguments: { + type: { type: Scratch.ArgumentType.STRING, menu: "data" }, + }, + }, + createLabel("Settings"), + { + opcode: "toggleSetting", + blockType: Scratch.BlockType.COMMAND, + text: "set [setting] to [toggle]", + arguments: { + setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, + toggle: { type: Scratch.ArgumentType.STRING, menu: "toggle" }, + }, + }, + { + opcode: "isSettingEnabled", + blockType: Scratch.BlockType.BOOLEAN, + text: "is [setting] enabled?", + disableMonitor: true, + arguments: { + setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, + }, + }, + "---", + { + opcode: "resetSettings", + blockType: Scratch.BlockType.COMMAND, + text: "reset all settings", + }, + ], + menus: { + mouseButtons: { + items: [ + { text: "left", value: "0" }, + { text: "scroll wheel", value: "1" }, + { text: "right", value: "2" }, + { text: "back", value: "3" }, + { text: "forward", value: "4" }, + ], + acceptReporters: true, + }, + keys: { items: "_getKeysMenu", acceptReporters: true }, + settings: { + items: [ + { text: "clear on blur", value: "clearOnBlur" }, + { text: "include tags on menu", value: "includeTags" }, + ], + }, + eventTriggerCondition: { items: ["when", "while", "after"] }, + keyProperty: { items: ["time", "name", "code", "value"] }, + returnMode: { + items: [ + "together & in order", + "together & ignore order", + "individually", + ], + }, + orderMode: { + items: ["together & in order", "together & ignore order"], + }, + toggle: { items: ["enabled", "disabled"] }, + data: { items: ["all", "tags", "keybinds", "settings"] }, + upDown: { items: ["up", "down"] }, + }, + }; + } + // Filter + _filter() { + const keysPressed = runtime.ioDevices.keyboard._keysPressed.map((k) => + k.toLowerCase() + ); + for (const key in this._keysPressed) { + const keyName = key.slice(1).toLowerCase(); + if (_filter.shift.includes(key) && !keysPressed.includes("shift")) { + delete this._keysPressed["_left shift"]; + delete this._keysPressed["_right shift"]; + continue; } - - // Mouse - eventMouseDown(args) { - return this.isMouseDown(args); + if ( + !keysPressed.includes(keyName) && + (/^_[a-zA-Z]$/.test(key) || _filter.default.includes(key)) + ) { + delete this._keysPressed[key]; } + } + } + // Helper Functions + _getKeysPressed() { + const keys = Object.keys(this._keysPressed); + return keys.map((key) => key.slice(1)); + } + _parse(keys) { + if (Array.isArray(keys)) return keys.map((key) => Cast.toString(key)); + try { + const parsed = JSON.parse(/^\[.*\]$/.test(keys) ? keys : `[${keys}]`); + return Array.isArray(parsed) + ? parsed.map((key) => Cast.toString(key)) + : []; + } catch { + return []; + } + } + _isKeyPressed(_key, _source) { + const key = Cast.toString(_key); + const keysPressed = this._getKeysPressed(); + if (keysPressed.length) keysPressed.unshift("any"); + if (key.startsWith("#")) { + const source = (this._tags[key] ?? []).find((currentKey) => + keysPressed.includes(currentKey) + ); + return _source ? { source, isPressed: !!source } : !!source; + } + return _source + ? { source: key, isPressed: keysPressed.includes(key) } + : keysPressed.includes(key); + } + _isKeysPressed(_keys, ordered) { + const keys = this._parse(_keys); + if (!keys.length) return false; + if (ordered) { + return ( + this._getKeysPressed() + .filter((key) => keys.includes(key)) + .join() === keys.join() + ); + } + return keys.every((key) => this._isKeyPressed(key)); + } + _isKeybindTriggered(_event) { + const event = this._keybinds[_event]; + for (const trigger in event) { + const mode = event[trigger]["mode"]; + const keys = event[trigger]["keys"]; + const triggered = this._isKeysPressed( + keys, + mode === "together & in order" + ); + if (triggered) return { isTriggered: true, cause: trigger }; + } + return { isTriggered: false, cause: undefined }; + } - isMouseDown(args) { - return Cast.toNumber(args.button) in this._mouseDown; - } - isMouseHit(args) { - const time = this.timeMouseDown(args); - return time !== 0 && time <= 0.075; - } + // Mouse + eventMouseDown(args) { + return this.isMouseDown(args); + } - timeMouseDown(args) { - const button = this._mouseDown[Cast.toNumber(args.button)]; - return button ? (Date.now() - button.time) / 1000 : 0; - } - // Scrolling - eventScroll(args) { - return args.dir === "up" ? this._scrollDeltaY < 0 : this._scrollDeltaY > 0; - } - isScrolling({ dir }) { - const withinRange = Date.now() - this._lastScrollTime < 50; - return dir === "up" ? this._scrollDeltaY < 0 && withinRange : this._scrollDeltaY > 0 && withinRange; - } - scrollDirection() { - if (this._scrollDeltaY && Date.now() - this._lastScrollTime < 50) { - return this._scrollDeltaY < 0 ? "up" : "down"; - } - return "none"; - } - // Keys - eventKeysPressed(args) { - const ordered = args.ordered === "together & in order"; - return this._isKeysPressed(args.keys, ordered); - } - eventKeyPressed(args) { - return this._isKeyPressed(args.key); - } + isMouseDown(args) { + return Cast.toNumber(args.button) in this._mouseDown; + } + isMouseHit(args) { + const time = this.timeMouseDown(args); + return time !== 0 && time <= 0.075; + } - isKeysPressed(args) { - const ordered = args.ordered === "together & in order"; - return this._isKeysPressed(args.keys, ordered); - } - isKeyPressed(args) { - return this._isKeyPressed(args.key); - } + timeMouseDown(args) { + const button = this._mouseDown[Cast.toNumber(args.button)]; + return button ? (Date.now() - button.time) / 1000 : 0; + } + // Scrolling + eventScroll(args) { + return args.dir === "up" + ? this._scrollDeltaY < 0 + : this._scrollDeltaY > 0; + } + isScrolling({ dir }) { + const withinRange = Date.now() - this._lastScrollTime < 50; + return dir === "up" + ? this._scrollDeltaY < 0 && withinRange + : this._scrollDeltaY > 0 && withinRange; + } + scrollDirection() { + if (this._scrollDeltaY && Date.now() - this._lastScrollTime < 50) { + return this._scrollDeltaY < 0 ? "up" : "down"; + } + return "none"; + } + // Keys + eventKeysPressed(args) { + const ordered = args.ordered === "together & in order"; + return this._isKeysPressed(args.keys, ordered); + } + eventKeyPressed(args) { + return this._isKeyPressed(args.key); + } - isKeysHit(args) { - const time = this.timeKeysPressed(args); - return time !== 0 && time <= 0.075; - } - isKeyHit(args) { - const time = this.timeKeyPressed(args); - return time !== 0 && time <= 0.075; - } + isKeysPressed(args) { + const ordered = args.ordered === "together & in order"; + return this._isKeysPressed(args.keys, ordered); + } + isKeyPressed(args) { + return this._isKeyPressed(args.key); + } - lastKeyPressed() { - return this._lastKeyPressed.slice(1); - } - currentKeysPressed() { - return JSON.stringify(this._getKeysPressed()); - } - currentKeyPressed() { - return this._getKeysPressed().reverse()[0] || "None"; - } - keyPressedProperty(args) { - const key = this.currentKeyPressed(); - if (key === "None") return 0; - switch (args.property) { - case "time": - return this.timeKeyPressed({ key: key }); - case "name": - return key; - case "code": - return this._keysPressed["_" + key].code; - case "value": - return this._keysPressed["_" + key].value; - } - } + isKeysHit(args) { + const time = this.timeKeysPressed(args); + return time !== 0 && time <= 0.075; + } + isKeyHit(args) { + const time = this.timeKeyPressed(args); + return time !== 0 && time <= 0.075; + } - timeKeysPressed(args) { - const keys = this._parse(args.keys); - if (!keys.length) return 0; - if (args.mode === "individually") { - return JSON.stringify(keys.map(key => this.timeKeyPressed({ key }))); - } - const ordered = args.mode === "together & in order"; - const isKeysPressed = this._isKeysPressed(keys, ordered); - console.log(isKeysPressed) - if (isKeysPressed) { - const key = ordered - ? keys[keys.length - 1] - : this._getKeysPressed() - .filter(key => keys.includes(key)) - .reverse()[0]; - return this.timeKeyPressed({ key: key }); - } - return 0; - } - timeKeyPressed(args) { - const data = this._isKeyPressed(args.key, true); - const key = this._keysPressed["_" + data.source]; - return key ? (Date.now() - key.time) / 1000 : 0; - } - // Keybinding - eventKeybindTriggered(args) { - return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; - } + lastKeyPressed() { + return this._lastKeyPressed.slice(1); + } + currentKeysPressed() { + return JSON.stringify(this._getKeysPressed()); + } + currentKeyPressed() { + return this._getKeysPressed().reverse()[0] || "None"; + } + keyPressedProperty(args) { + const key = this.currentKeyPressed(); + if (key === "None") return 0; + switch (args.property) { + case "time": + return this.timeKeyPressed({ key: key }); + case "name": + return key; + case "code": + return this._keysPressed["_" + key].code; + case "value": + return this._keysPressed["_" + key].value; + } + } - whileKeybindTriggered(args) { - return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; - } - whenKeybindTriggered(args) { - const time = this.timeKeybindTriggered(args); - return time !== 0 && time <= 0.075; - } + timeKeysPressed(args) { + const keys = this._parse(args.keys); + if (!keys.length) return 0; + if (args.mode === "individually") { + return JSON.stringify(keys.map((key) => this.timeKeyPressed({ key }))); + } + const ordered = args.mode === "together & in order"; + const isKeysPressed = this._isKeysPressed(keys, ordered); + console.log(isKeysPressed); + if (isKeysPressed) { + const key = ordered + ? keys[keys.length - 1] + : this._getKeysPressed() + .filter((key) => keys.includes(key)) + .reverse()[0]; + return this.timeKeyPressed({ key: key }); + } + return 0; + } + timeKeyPressed(args) { + const data = this._isKeyPressed(args.key, true); + const key = this._keysPressed["_" + data.source]; + return key ? (Date.now() - key.time) / 1000 : 0; + } + // Keybinding + eventKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; + } - timeKeybindTriggered(args) { - const event = Cast.toString(args.event); - const data = this._isKeybindTriggered(event); - const trigger = this._keybinds[event]?.[data.cause]; - if (!data.isTriggered) return 0; - return this.timeKeysPressed({ "mode": trigger.mode, "keys": trigger.keys }); - } - causeKeybindTriggered(args) { - return this._isKeybindTriggered(Cast.toString(args.event)).cause ?? ""; - } + whileKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).isTriggered; + } + whenKeybindTriggered(args) { + const time = this.timeKeybindTriggered(args); + return time !== 0 && time <= 0.075; + } - keybindBindMultiple(args) { - const event = Cast.toString(args.event); - const trigger = Cast.toString(args.trigger); - this._keybinds[event] ??= {}; - this._keybinds[event][trigger] = { "mode": args.mode, "keys": this._parse(args.keys) }; - } - keybindBind(args) { - const event = Cast.toString(args.event); - const trigger = Cast.toString(args.trigger); - this._keybinds[event] ??= {}; - this._keybinds[event][trigger] = { "mode": "together & in order", "keys": [Cast.toString(args.key)] }; - }; - keybindUnbind(args) { - const event = Cast.toString(args.event); - const trigger = Cast.toString(args.trigger); - if (!this._keybinds[event]) return; - delete this._keybinds[event][trigger]; - if (!Object.keys(this._keybinds[event]).length) delete this._keybinds[event]; - }; - - keybindReset(args) { - delete this._keybinds[Cast.toString(args.event)]; - } - resetAllKeybinds() { - this._keybinds = {} - } + timeKeybindTriggered(args) { + const event = Cast.toString(args.event); + const data = this._isKeybindTriggered(event); + const trigger = this._keybinds[event]?.[data.cause]; + if (!data.isTriggered) return 0; + return this.timeKeysPressed({ mode: trigger.mode, keys: trigger.keys }); + } + causeKeybindTriggered(args) { + return this._isKeybindTriggered(Cast.toString(args.event)).cause ?? ""; + } - keybindListTriggers(args) { - return JSON.stringify(Object.keys(this._keybinds[Cast.toString(args.event)] ?? {})); - } - keybindKeysInTrigger(args) { - const event = this._keybinds[Cast.toString(args.event)]; - return event?.[Cast.toString(args.trigger)] - ? JSON.stringify(event[args.trigger]["keys"]) - : "[]"; - } + keybindBindMultiple(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + this._keybinds[event] ??= {}; + this._keybinds[event][trigger] = { + mode: args.mode, + keys: this._parse(args.keys), + }; + } + keybindBind(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + this._keybinds[event] ??= {}; + this._keybinds[event][trigger] = { + mode: "together & in order", + keys: [Cast.toString(args.key)], + }; + } + keybindUnbind(args) { + const event = Cast.toString(args.event); + const trigger = Cast.toString(args.trigger); + if (!this._keybinds[event]) return; + delete this._keybinds[event][trigger]; + if (!Object.keys(this._keybinds[event]).length) + delete this._keybinds[event]; + } - listAllKeybinds() { - return JSON.stringify(Object.keys(this._keybinds)); - } - listActiveKeybinds() { - return JSON.stringify(Object.keys(this._keybinds).filter(event => - this._isKeybindTriggered(event).isTriggered - )); - } - // Tags - createTag(args) { - this._tags["#" + args.tag] = this._parse(args.keys); - } + keybindReset(args) { + delete this._keybinds[Cast.toString(args.event)]; + } + resetAllKeybinds() { + this._keybinds = {}; + } - deleteTag(args) { - delete this._tags["#" + args.tag]; - } - deleteAllTags() { - this._tags = {} - } + keybindListTriggers(args) { + return JSON.stringify( + Object.keys(this._keybinds[Cast.toString(args.event)] ?? {}) + ); + } + keybindKeysInTrigger(args) { + const event = this._keybinds[Cast.toString(args.event)]; + return event?.[Cast.toString(args.trigger)] + ? JSON.stringify(event[args.trigger]["keys"]) + : "[]"; + } - valueOfTag(args) { - return JSON.stringify(this._tags["#" + args.tag] ?? []); - } - listTags() { - return JSON.stringify(Object.keys(this._tags)); - } - // Settings - toggleSetting(args) { - const boolean = (args.toggle === "enabled") ? true : false; - this._settings[args.setting] = boolean; - } - isSettingEnabled(args) { - return this._settings[args.setting]; - } - resetSettings() { - this._settings = { - "clearOnBlur": true, - "includeTags": true - } - } - // Storage - - import(args) { - const result = this.validate(args.type, args.json); - if (result.error) return this._importError = result.error; - this._importError = "None"; - switch (args.type) { - case "tags": - this._tags = result.output; - case "keybinds": - this._keybinds = result.output; - case "settings": - this._settings = result.output; - case "all": - this._tags = result.output["tags"]; - this._keybinds = result.output["keybinds"]; - this._settings = result.output["settings"]; - } - } - importError() { - return this._importError; - } + listAllKeybinds() { + return JSON.stringify(Object.keys(this._keybinds)); + } + listActiveKeybinds() { + return JSON.stringify( + Object.keys(this._keybinds).filter( + (event) => this._isKeybindTriggered(event).isTriggered + ) + ); + } + // Tags + createTag(args) { + this._tags["#" + args.tag] = this._parse(args.keys); + } - export(args) { - switch (args.type) { - case "tags": - return JSON.stringify(this._tags); - case "keybinds": - return JSON.stringify(this._keybinds); - case "settings": - return JSON.stringify(this._settings); - case "all": - return JSON.stringify({ tags: this._tags, keybinds: this._keybinds, settings: this._settings }); - } - } + deleteTag(args) { + delete this._tags["#" + args.tag]; + } + deleteAllTags() { + this._tags = {}; + } - validate(_type, _data) { - let data = typeof _data === "string" ? _data : JSON.stringify(_data); - try { data = JSON.parse(data); } - catch { return { error: "Invalid JSON" }; }; - - if (typeof data !== "object" || data === null) return { error: "Invalid JSON" }; - if (Array.isArray(data)) return { error: "Input can't be an Array" } - - if (_type === "tags") { - for (const tag in data) { - if (tag.startsWith("#")) { - if (Array.isArray(data[tag])) { - const index = data[tag].findIndex(item => typeof item !== "string"); - if (index !== -1) return { error: `Tags: Expected string at item #${index} of '${tag}'` }; - } - else { return { error: `Tags: Expected array at '${tag}'` }; } - } else { return { error: `Tags: Invalid tag id, reading '${tag}'`} } - } + valueOfTag(args) { + return JSON.stringify(this._tags["#" + args.tag] ?? []); + } + listTags() { + return JSON.stringify(Object.keys(this._tags)); + } + // Settings + toggleSetting(args) { + const boolean = args.toggle === "enabled" ? true : false; + this._settings[args.setting] = boolean; + } + isSettingEnabled(args) { + return this._settings[args.setting]; + } + resetSettings() { + this._settings = { + clearOnBlur: true, + includeTags: true, + }; + } + // Storage + + import(args) { + const result = this.validate(args.type, args.json); + if (result.error) return (this._importError = result.error); + this._importError = "None"; + switch (args.type) { + case "tags": + this._tags = result.output; + case "keybinds": + this._keybinds = result.output; + case "settings": + this._settings = result.output; + case "all": + this._tags = result.output["tags"]; + this._keybinds = result.output["keybinds"]; + this._settings = result.output["settings"]; + } + } + importError() { + return this._importError; + } + + export(args) { + switch (args.type) { + case "tags": + return JSON.stringify(this._tags); + case "keybinds": + return JSON.stringify(this._keybinds); + case "settings": + return JSON.stringify(this._settings); + case "all": + return JSON.stringify({ + tags: this._tags, + keybinds: this._keybinds, + settings: this._settings, + }); + } + } + + validate(_type, _data) { + let data = typeof _data === "string" ? _data : JSON.stringify(_data); + try { + data = JSON.parse(data); + } catch { + return { error: "Invalid JSON" }; + } + + if (typeof data !== "object" || data === null) + return { error: "Invalid JSON" }; + if (Array.isArray(data)) return { error: "Input can't be an Array" }; + + if (_type === "tags") { + for (const tag in data) { + if (tag.startsWith("#")) { + if (Array.isArray(data[tag])) { + const index = data[tag].findIndex( + (item) => typeof item !== "string" + ); + if (index !== -1) + return { + error: `Tags: Expected string at item #${index} of '${tag}'`, + }; + } else { + return { error: `Tags: Expected array at '${tag}'` }; } - if (_type === "keybinds") { - for (const event in data) { - if (typeof data[event] === "object" && data[event] !== null) { - for (const trigger in data[event]) { - const triggerData = data[event][trigger] - if (typeof triggerData === "object" && trigger) { - if (triggerData["mode"]) { - if (triggerData["mode"] === "together & in order" || triggerData["mode"] === "together & ignore order") { - if (triggerData["keys"]) { - if (Array.isArray(triggerData["keys"])) { - const index = triggerData["keys"].findIndex(item => typeof item !== "string"); - if (index !== -1) { - return { error: `Keybinds: Expected string at item #${index} of '${event}/${trigger}'` }; - } - } else { return { error: `Keybinds: Expected array at '${event}/${trigger}/keys'` } } - } else { return { error: `Missing 'keys' at '${event}/${trigger}'` } } - } else { return { error: `Keybinds: Invalid input in '${event}/${trigger}/mode'` } } - } else { return { error: `Missing 'mode' at '${event}/${trigger}'` } } - } else { return { error: `Keybinds: Expected JSON at '${event}/${trigger}'` }} + } else { + return { error: `Tags: Invalid tag id, reading '${tag}'` }; + } + } + } + if (_type === "keybinds") { + for (const event in data) { + if (typeof data[event] === "object" && data[event] !== null) { + for (const trigger in data[event]) { + const triggerData = data[event][trigger]; + if (typeof triggerData === "object" && trigger) { + if (triggerData["mode"]) { + if ( + triggerData["mode"] === "together & in order" || + triggerData["mode"] === "together & ignore order" + ) { + if (triggerData["keys"]) { + if (Array.isArray(triggerData["keys"])) { + const index = triggerData["keys"].findIndex( + (item) => typeof item !== "string" + ); + if (index !== -1) { + return { + error: `Keybinds: Expected string at item #${index} of '${event}/${trigger}'`, + }; } - } else { return { error: `Keybinds: Expected JSON at '${event}'` }; } - } - } - if (_type === "settings") { - for (const setting in data) { - if (typeof data[setting] !== "boolean") { - return { error: `Settings: Expected boolean at '${setting}'` }; + } else { + return { + error: `Keybinds: Expected array at '${event}/${trigger}/keys'`, + }; + } + } else { + return { + error: `Missing 'keys' at '${event}/${trigger}'`, + }; } + } else { + return { + error: `Keybinds: Invalid input in '${event}/${trigger}/mode'`, + }; + } + } else { + return { error: `Missing 'mode' at '${event}/${trigger}'` }; } + } else { + return { + error: `Keybinds: Expected JSON at '${event}/${trigger}'`, + }; + } } - if (_type === "all") { - if (!("settings" in data)) return { error: "Missing 'settings'" }; - if (!("tags" in data)) return { error: "Missing 'tags'" }; - if (!("keybinds" in data)) return { error: "Missing 'keybinds'" }; - - const tagsValidation = this.validate("tags", data["tags"]); - if (tagsValidation.error) return { error: `Tags: ${tagsValidation.error}` }; - - const keybindsValidation = this.validate("keybinds", data["keybinds"]); - if (keybindsValidation.error) return { error: `Keybinds: ${keybindsValidation.error}` }; - - const settingsValidation = this.validate("settings", data["settings"]); - if (settingsValidation.error) return { error: `Settings: ${settingsValidation.error}` }; - } - - return { output: data }; - } - - - // Menu - _getKeysMenu() { - const tags = this._settings["includeTags"] ? Object.keys(this._tags) : []; - return [ - "space", "up arrow", "down arrow", "right arrow", "left arrow", - "backspace", "enter", "any", "right shift", "left shift", "right control", "left control", "right alt", "left alt", "right windows key", "left windows key", "context menu", "escape", "tab", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", - "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", - "`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "[", "]", "{", "}", "\\", "|", ";", ":", "'", "\"", ",", ".", "/", "?", "<", ">", - "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "caps lock", "scroll lock", "num lock", "insert", "delete", "home", "end", "page up", "page down", - "numpad: divide", "numpad: multiply", "numpad: subtract", "numpad: add", "numpad: 0", "numpad: 1", "numpad: 2", "numpad: 3", "numpad: 4", "numpad: 5", "numpad: 6", "numpad: 7", "numpad: 8", "numpad: 9", "numpad: decimal", "numpad: enter", - ...tags - ]; + } else { + return { error: `Keybinds: Expected JSON at '${event}'` }; + } } + } + if (_type === "settings") { + for (const setting in data) { + if (typeof data[setting] !== "boolean") { + return { error: `Settings: Expected boolean at '${setting}'` }; + } + } + } + if (_type === "all") { + if (!("settings" in data)) return { error: "Missing 'settings'" }; + if (!("tags" in data)) return { error: "Missing 'tags'" }; + if (!("keybinds" in data)) return { error: "Missing 'keybinds'" }; + + const tagsValidation = this.validate("tags", data["tags"]); + if (tagsValidation.error) + return { error: `Tags: ${tagsValidation.error}` }; + + const keybindsValidation = this.validate("keybinds", data["keybinds"]); + if (keybindsValidation.error) + return { error: `Keybinds: ${keybindsValidation.error}` }; + + const settingsValidation = this.validate("settings", data["settings"]); + if (settingsValidation.error) + return { error: `Settings: ${settingsValidation.error}` }; + } + + return { output: data }; + } + + // Menu + _getKeysMenu() { + const tags = this._settings["includeTags"] ? Object.keys(this._tags) : []; + return [ + "space", + "up arrow", + "down arrow", + "right arrow", + "left arrow", + "backspace", + "enter", + "any", + "right shift", + "left shift", + "right control", + "left control", + "right alt", + "left alt", + "right windows key", + "left windows key", + "context menu", + "escape", + "tab", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "`", + "~", + "!", + "@", + "#", + "$", + "%", + "^", + "&", + "*", + "(", + ")", + "-", + "+", + "=", + "[", + "]", + "{", + "}", + "\\", + "|", + ";", + ":", + "'", + '"', + ",", + ".", + "/", + "?", + "<", + ">", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "caps lock", + "scroll lock", + "num lock", + "insert", + "delete", + "home", + "end", + "page up", + "page down", + "numpad: divide", + "numpad: multiply", + "numpad: subtract", + "numpad: add", + "numpad: 0", + "numpad: 1", + "numpad: 2", + "numpad: 3", + "numpad: 4", + "numpad: 5", + "numpad: 6", + "numpad: 7", + "numpad: 8", + "numpad: 9", + "numpad: decimal", + "numpad: enter", + ...tags, + ]; } + } - Scratch.extensions.register(new enderKeysPlusV2()); + Scratch.extensions.register(new enderKeysPlusV2()); })(Scratch); From b7fca2df7c5b64a35efde64e5248f312b43ca4dc Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 13:04:35 +0800 Subject: [PATCH 06/18] Update extensions.json --- extensions/extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extensions.json b/extensions/extensions.json index 5af8698d13..4a9d595a43 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -1,6 +1,5 @@ [ // This file supports comments - "StackOverflow/keysPlusV2", "lab/text", "stretch", "gamepad", @@ -32,6 +31,7 @@ "Lily/LooksPlus", "Lily/MoreEvents", "Lily/ListTools", + "StackOverflow/keysPlusV2", "veggiecan/mobilekeyboard", "NexusKitten/moremotion", "CubesterYT/WindowControls", From af6d86a3e13798c5f19efb54df5934ee3b79a9d8 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:08:21 +0800 Subject: [PATCH 07/18] Update keysPlusV2.svg --- images/StackOverflow/keysPlusV2.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/StackOverflow/keysPlusV2.svg b/images/StackOverflow/keysPlusV2.svg index 842b5271db..171cef5b48 100644 --- a/images/StackOverflow/keysPlusV2.svg +++ b/images/StackOverflow/keysPlusV2.svg @@ -1 +1 @@ -KeysPlus2 + From 6f3dca95c35c962b244fa89ac3971a413588830a Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:08:38 +0800 Subject: [PATCH 08/18] Rename keysPlusV2.svg to Keys-Plus-V2.svg --- images/StackOverflow/{keysPlusV2.svg => Keys-Plus-V2.svg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename images/StackOverflow/{keysPlusV2.svg => Keys-Plus-V2.svg} (100%) diff --git a/images/StackOverflow/keysPlusV2.svg b/images/StackOverflow/Keys-Plus-V2.svg similarity index 100% rename from images/StackOverflow/keysPlusV2.svg rename to images/StackOverflow/Keys-Plus-V2.svg From 1f9afb195434cd0707389c5a79346d170fadb9ed Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:09:30 +0800 Subject: [PATCH 09/18] Update extensions.json --- extensions/extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extensions.json b/extensions/extensions.json index 4a9d595a43..c926bebebc 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -31,7 +31,7 @@ "Lily/LooksPlus", "Lily/MoreEvents", "Lily/ListTools", - "StackOverflow/keysPlusV2", + "StackOverflow/Keys-Plus-V2", "veggiecan/mobilekeyboard", "NexusKitten/moremotion", "CubesterYT/WindowControls", From 0f04dc8c85bd4ae41aa54b6b19d4ae6d49499e3d Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:11:00 +0800 Subject: [PATCH 10/18] Rename keysPlusV2.js to Keys-Plus-V2.js --- extensions/StackOverflow/{keysPlusV2.js => Keys-Plus-V2.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename extensions/StackOverflow/{keysPlusV2.js => Keys-Plus-V2.js} (100%) diff --git a/extensions/StackOverflow/keysPlusV2.js b/extensions/StackOverflow/Keys-Plus-V2.js similarity index 100% rename from extensions/StackOverflow/keysPlusV2.js rename to extensions/StackOverflow/Keys-Plus-V2.js From b9421a84e6a294accc5089cc37f0cb6a4342579f Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:18:15 +0800 Subject: [PATCH 11/18] Update Keys-Plus-V2.js --- extensions/StackOverflow/Keys-Plus-V2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/StackOverflow/Keys-Plus-V2.js b/extensions/StackOverflow/Keys-Plus-V2.js index 49161b6fee..821e7f8225 100644 --- a/extensions/StackOverflow/Keys-Plus-V2.js +++ b/extensions/StackOverflow/Keys-Plus-V2.js @@ -1,7 +1,7 @@ // Name: Keys+ V2 // ID: enderKeysPlusV2 // Description: Even more powerful and flexible key press detection blocks with some additional features. -// By: StackOverflow +// By: StackOverflow // Original: StackOverflow // License: MIT & LGPL-3.0 From d4b2f1acb627a2520467ecfc6640c9d520e242b3 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:29:00 +0800 Subject: [PATCH 12/18] Update and rename Keys-Plus-V2.js to Keys-Plus-V2.js --- .../Keys-Plus-V2.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) rename extensions/{StackOverflow => Ender-Studio}/Keys-Plus-V2.js (99%) diff --git a/extensions/StackOverflow/Keys-Plus-V2.js b/extensions/Ender-Studio/Keys-Plus-V2.js similarity index 99% rename from extensions/StackOverflow/Keys-Plus-V2.js rename to extensions/Ender-Studio/Keys-Plus-V2.js index 821e7f8225..c28b3cf214 100644 --- a/extensions/StackOverflow/Keys-Plus-V2.js +++ b/extensions/Ender-Studio/Keys-Plus-V2.js @@ -1,8 +1,8 @@ // Name: Keys+ V2 // ID: enderKeysPlusV2 // Description: Even more powerful and flexible key press detection blocks with some additional features. -// By: StackOverflow -// Original: StackOverflow +// By: Ender-Studio +// Original: Ender-Studio // License: MIT & LGPL-3.0 (function (Scratch) { @@ -1253,7 +1253,9 @@ keybindBindMultiple(args) { const event = Cast.toString(args.event); const trigger = Cast.toString(args.trigger); - this._keybinds[event] ??= {}; + if (!this._keybinds[event]) { + this._keybinds[event] = {}; + }; this._keybinds[event][trigger] = { mode: args.mode, keys: this._parse(args.keys), @@ -1262,7 +1264,9 @@ keybindBind(args) { const event = Cast.toString(args.event); const trigger = Cast.toString(args.trigger); - this._keybinds[event] ??= {}; + if (!this._keybinds[event]) { + this._keybinds[event] = {}; + }; this._keybinds[event][trigger] = { mode: "together & in order", keys: [Cast.toString(args.key)], @@ -1347,14 +1351,18 @@ switch (args.type) { case "tags": this._tags = result.output; + break; case "keybinds": this._keybinds = result.output; + break; case "settings": this._settings = result.output; + break; case "all": this._tags = result.output["tags"]; this._keybinds = result.output["keybinds"]; this._settings = result.output["settings"]; + break; } } importError() { From 7cd7e7a62a1fbc65c700306f7dca71601eba6877 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:30:03 +0800 Subject: [PATCH 13/18] Rename Keys-Plus-V2.svg to Keys-Plus-V2.svg --- images/{StackOverflow => Ender-Studio}/Keys-Plus-V2.svg | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename images/{StackOverflow => Ender-Studio}/Keys-Plus-V2.svg (100%) diff --git a/images/StackOverflow/Keys-Plus-V2.svg b/images/Ender-Studio/Keys-Plus-V2.svg similarity index 100% rename from images/StackOverflow/Keys-Plus-V2.svg rename to images/Ender-Studio/Keys-Plus-V2.svg From 2114aec6bd81e94b3945d8ffc76789a34abee04e Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 14:33:14 +0800 Subject: [PATCH 14/18] Update extensions.json --- extensions/extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/extensions.json b/extensions/extensions.json index c926bebebc..147452e648 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -31,7 +31,7 @@ "Lily/LooksPlus", "Lily/MoreEvents", "Lily/ListTools", - "StackOverflow/Keys-Plus-V2", + "Ender-Studio/Keys-Plus-V2", "veggiecan/mobilekeyboard", "NexusKitten/moremotion", "CubesterYT/WindowControls", From 6de2fe028f65c655a661cf8a74fa73b022ca3e18 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Sat, 8 Feb 2025 06:35:15 +0000 Subject: [PATCH 15/18] [Automated] Format code --- extensions/Ender-Studio/Keys-Plus-V2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/Ender-Studio/Keys-Plus-V2.js b/extensions/Ender-Studio/Keys-Plus-V2.js index c28b3cf214..0b3dbb4a3b 100644 --- a/extensions/Ender-Studio/Keys-Plus-V2.js +++ b/extensions/Ender-Studio/Keys-Plus-V2.js @@ -1255,7 +1255,7 @@ const trigger = Cast.toString(args.trigger); if (!this._keybinds[event]) { this._keybinds[event] = {}; - }; + } this._keybinds[event][trigger] = { mode: args.mode, keys: this._parse(args.keys), @@ -1266,7 +1266,7 @@ const trigger = Cast.toString(args.trigger); if (!this._keybinds[event]) { this._keybinds[event] = {}; - }; + } this._keybinds[event][trigger] = { mode: "together & in order", keys: [Cast.toString(args.key)], From bb6256ca5e63b90ab066dc46563ce7095b7935bc Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 15:11:46 +0800 Subject: [PATCH 16/18] Update Keys-Plus-V2.js --- extensions/Ender-Studio/Keys-Plus-V2.js | 267 +++++++++--------------- 1 file changed, 104 insertions(+), 163 deletions(-) diff --git a/extensions/Ender-Studio/Keys-Plus-V2.js b/extensions/Ender-Studio/Keys-Plus-V2.js index 0b3dbb4a3b..b2aed7cb93 100644 --- a/extensions/Ender-Studio/Keys-Plus-V2.js +++ b/extensions/Ender-Studio/Keys-Plus-V2.js @@ -3,7 +3,7 @@ // Description: Even more powerful and flexible key press detection blocks with some additional features. // By: Ender-Studio // Original: Ender-Studio -// License: MIT & LGPL-3.0 +// License: MIT AND LGPL-3.0 (function (Scratch) { "use strict"; @@ -201,7 +201,7 @@ return "_" + name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase(); } const createLabel = (text) => ({ - blockType: Scratch.BlockType.LABEL, + blockType: Scratch.translate(Scratch.BlockType.LABEL), text: text, }); @@ -534,7 +534,7 @@ getInfo() { return { id: "enderKeysPlusV2", - name: "Keys+ V2", + name: Scratch.translate("Keys+ V2"), color1: "#647970", color2: "#4D5E56", blocks: [ @@ -542,7 +542,7 @@ { opcode: "eventMouseDown", blockType: Scratch.BlockType.HAT, - text: "[trigger] [button] mouse button is down", + text: Scratch.translate("[trigger] [button] mouse button is down"), isEdgeActivated: false, arguments: { trigger: { @@ -559,7 +559,7 @@ { opcode: "isMouseDown", blockType: Scratch.BlockType.BOOLEAN, - text: "is [button] mouse button down?", + text: Scratch.translate("is [button] mouse button down?"), arguments: { button: { type: Scratch.ArgumentType.STRING, @@ -570,7 +570,7 @@ { opcode: "isMouseHit", blockType: Scratch.BlockType.BOOLEAN, - text: "is [button] mouse button clicked?", + text: Scratch.translate("is [button] mouse button clicked?"), arguments: { button: { type: Scratch.ArgumentType.STRING, @@ -582,7 +582,7 @@ { opcode: "timeMouseDown", blockType: Scratch.BlockType.REPORTER, - text: "time [button] mouse button down", + text: Scratch.translate("time [button] mouse button down"), arguments: { button: { type: Scratch.ArgumentType.STRING, @@ -594,7 +594,7 @@ { opcode: "eventScroll", blockType: Scratch.BlockType.HAT, - text: "when scrolling [dir]", + text: Scratch.translate("when scrolling [dir]"), isEdgeActivated: false, arguments: { dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" }, @@ -603,7 +603,7 @@ { opcode: "isScrolling", blockType: Scratch.BlockType.BOOLEAN, - text: "is scrolling [dir]?", + text: Scratch.translate("is scrolling [dir]?"), disableMonitor: true, arguments: { dir: { type: Scratch.ArgumentType.STRING, menu: "upDown" }, @@ -613,13 +613,13 @@ { opcode: "scrollDirection", blockType: Scratch.BlockType.REPORTER, - text: "scroll direction", + text: Scratch.translate("scroll direction"), }, createLabel("Keys"), { opcode: "eventKeysPressed", blockType: Scratch.BlockType.HAT, - text: "[trigger] [[keys]] keys is pressed [ordered]", + text: Scratch.translate("[trigger] [[keys]] keys is pressed [ordered]"), isEdgeActivated: false, arguments: { trigger: { @@ -636,7 +636,7 @@ { opcode: "eventKeyPressed", blockType: Scratch.BlockType.HAT, - text: "[trigger] [key] key is pressed", + text: Scratch.translate("[trigger] [key] key is pressed"), isEdgeActivated: false, arguments: { trigger: { @@ -650,7 +650,7 @@ { opcode: "isKeysPressed", blockType: Scratch.BlockType.BOOLEAN, - text: "are [[keys]] keys pressed [ordered]?", + text: Scratch.translate("are [[keys]] keys pressed [ordered]?"), arguments: { ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, keys: { @@ -662,7 +662,7 @@ { opcode: "isKeyPressed", blockType: Scratch.BlockType.BOOLEAN, - text: "is [key] key pressed?", + text: Scratch.translate("is [key] key pressed?"), arguments: { key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, }, @@ -671,7 +671,7 @@ { opcode: "isKeysHit", blockType: Scratch.BlockType.BOOLEAN, - text: "are [[keys]] keys hit [ordered]?", + text: Scratch.translate("are [[keys]] keys hit [ordered]?"), arguments: { ordered: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, keys: { @@ -683,7 +683,7 @@ { opcode: "isKeyHit", blockType: Scratch.BlockType.BOOLEAN, - text: "is [key] key hit?", + text: Scratch.translate("is [key] key hit?"), arguments: { key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, }, @@ -692,22 +692,22 @@ { opcode: "lastKeyPressed", blockType: Scratch.BlockType.REPORTER, - text: "last key pressed", + text: Scratch.translate("last key pressed"), }, { opcode: "currentKeysPressed", blockType: Scratch.BlockType.REPORTER, - text: "current keys pressed", + text: Scratch.translate("current keys pressed"), }, { opcode: "currentKeyPressed", blockType: Scratch.BlockType.REPORTER, - text: "current key pressed", + text: Scratch.translate("current key pressed"), }, { opcode: "keyPressedProperty", blockType: Scratch.BlockType.REPORTER, - text: "current key pressed [property]", + text: Scratch.translate("current key pressed [property]"), disableMonitor: true, arguments: { property: { @@ -720,7 +720,7 @@ { opcode: "timeKeysPressed", blockType: Scratch.BlockType.REPORTER, - text: "time [[keys]] keys pressed [mode]", + text: Scratch.translate("time [[keys]] keys pressed [mode]"), disableMonitor: true, arguments: { mode: { type: Scratch.ArgumentType.STRING, menu: "returnMode" }, @@ -733,7 +733,7 @@ { opcode: "timeKeyPressed", blockType: Scratch.BlockType.REPORTER, - text: "time [key] key pressed", + text: Scratch.translate("time [key] key pressed"), disableMonitor: true, arguments: { key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, @@ -743,7 +743,7 @@ { opcode: "eventKeybindTriggered", blockType: Scratch.BlockType.HAT, - text: "[trigger] [event] is triggered", + text: Scratch.translate("[trigger] [event] is triggered"), isEdgeActivated: false, arguments: { trigger: { @@ -752,7 +752,7 @@ }, event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, @@ -760,22 +760,22 @@ { opcode: "whileKeybindTriggered", blockType: Scratch.BlockType.BOOLEAN, - text: "while [event] is triggered?", + text: Scratch.translate("while [event] is triggered?"), arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "whenKeybindTriggered", blockType: Scratch.BlockType.BOOLEAN, - text: "when [event] is triggered?", + text: Scratch.translate("when [event] is triggered?"), arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, @@ -783,24 +783,24 @@ { opcode: "causeKeybindTriggered", blockType: Scratch.BlockType.REPORTER, - text: "cause of [event] triggered", + text: Scratch.translate("cause of [event] triggered"), disableMonitor: true, arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "timeKeybindTriggered", blockType: Scratch.BlockType.REPORTER, - text: "time [event] is triggered", + text: Scratch.translate("time [event] is triggered"), disableMonitor: true, arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, @@ -808,7 +808,7 @@ { opcode: "keybindBindMultiple", blockType: Scratch.BlockType.COMMAND, - text: "bind [[keys]] keys [mode] as [trigger] to [event]", + text: Scratch.translate("bind [[keys]] keys [mode] as [trigger] to [event]"), arguments: { keys: { type: Scratch.ArgumentType.STRING, @@ -817,42 +817,42 @@ mode: { type: Scratch.ArgumentType.STRING, menu: "orderMode" }, trigger: { type: Scratch.ArgumentType.STRING, - defaultValue: "trigger", + defaultValue: Scratch.translate("trigger"), }, event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "keybindBind", blockType: Scratch.BlockType.COMMAND, - text: "bind key [key] as [trigger] to [event]", + text: Scratch.translate("bind key [key] as [trigger] to [event]"), arguments: { key: { type: Scratch.ArgumentType.STRING, menu: "keys" }, trigger: { type: Scratch.ArgumentType.STRING, - defaultValue: "trigger", + defaultValue: Scratch.translate("trigger"), }, event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "keybindUnbind", blockType: Scratch.BlockType.COMMAND, - text: "unbind trigger [trigger] from [event]", + text: Scratch.translate("unbind trigger [trigger] from [event]"), arguments: { trigger: { type: Scratch.ArgumentType.STRING, - defaultValue: "trigger", + defaultValue: Scratch.translate("trigger"), }, event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, @@ -860,45 +860,45 @@ { opcode: "keybindReset", blockType: Scratch.BlockType.COMMAND, - text: "reset binds of [event]", + text: Scratch.translate("reset binds of [event]"), arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "resetAllKeybinds", blockType: Scratch.BlockType.COMMAND, - text: "reset all keybindings", + text: Scratch.translate("reset all keybindings"), }, "---", { opcode: "keybindListTriggers", blockType: Scratch.BlockType.REPORTER, - text: "list triggers of [event]", + text: Scratch.translate("list triggers of [event]"), disableMonitor: true, arguments: { event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, { opcode: "keybindKeysInTrigger", blockType: Scratch.BlockType.REPORTER, - text: "keys bound to [trigger] in [event]", + text: Scratch.translate("keys bound to [trigger] in [event]"), disableMonitor: true, arguments: { trigger: { type: Scratch.ArgumentType.STRING, - defaultValue: "trigger", + defaultValue: Scratch.translate("trigger"), }, event: { type: Scratch.ArgumentType.STRING, - defaultValue: "event", + defaultValue: Scratch.translate("event"), }, }, }, @@ -906,139 +906,80 @@ { opcode: "listAllKeybinds", blockType: Scratch.BlockType.REPORTER, - text: "list all keybindings", - }, - { - opcode: "listActiveKeybinds", - blockType: Scratch.BlockType.REPORTER, - text: "list active keybindings", - }, - createLabel("Tags"), - { - opcode: "createTag", - blockType: Scratch.BlockType.COMMAND, - text: "set value of tag: #[tag] to [[keys]]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, - keys: { - type: Scratch.ArgumentType.STRING, - defaultValue: '"a", "b", "c"', - }, - }, - }, - "---", - { - opcode: "deleteTag", - blockType: Scratch.BlockType.COMMAND, - text: "delete tag: #[tag]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, - }, - }, - { - opcode: "deleteAllTags", - blockType: Scratch.BlockType.COMMAND, - text: "delete all tags", - }, - "---", - { - opcode: "valueOfTag", - blockType: Scratch.BlockType.REPORTER, - text: "value of tag: #[tag]", - arguments: { - tag: { type: Scratch.ArgumentType.STRING, defaultValue: "abc" }, - }, - }, - { - opcode: "listTags", - blockType: Scratch.BlockType.REPORTER, - text: "list all tags", - }, - createLabel("Storage"), - { - opcode: "import", - blockType: Scratch.BlockType.COMMAND, - text: "import [type] from [json]", - arguments: { - type: { type: Scratch.ArgumentType.STRING, menu: "data" }, - json: { type: Scratch.ArgumentType.STRING, defaultValue: "" }, - }, - }, - { - opcode: "importError", - blockType: Scratch.BlockType.REPORTER, - text: "import error", - }, - "---", - { - opcode: "export", - blockType: Scratch.BlockType.REPORTER, - text: "export [type]", - disableMonitor: true, - arguments: { - type: { type: Scratch.ArgumentType.STRING, menu: "data" }, - }, - }, - createLabel("Settings"), - { - opcode: "toggleSetting", - blockType: Scratch.BlockType.COMMAND, - text: "set [setting] to [toggle]", - arguments: { - setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, - toggle: { type: Scratch.ArgumentType.STRING, menu: "toggle" }, - }, - }, - { - opcode: "isSettingEnabled", - blockType: Scratch.BlockType.BOOLEAN, - text: "is [setting] enabled?", + text: Scratch.translate("list all keybindings"), disableMonitor: true, - arguments: { - setting: { type: Scratch.ArgumentType.STRING, menu: "settings" }, - }, - }, - "---", - { - opcode: "resetSettings", - blockType: Scratch.BlockType.COMMAND, - text: "reset all settings", }, ], menus: { mouseButtons: { items: [ - { text: "left", value: "0" }, - { text: "scroll wheel", value: "1" }, - { text: "right", value: "2" }, - { text: "back", value: "3" }, - { text: "forward", value: "4" }, + { text: Scratch.translate("left"), value: "0" }, + { text: Scratch.translate("scroll wheel"), value: "1" }, + { text: Scratch.translate("right"), value: "2" }, + { text: Scratch.translate("back"), value: "3" }, + { text: Scratch.translate("forward"), value: "4" }, ], acceptReporters: true, }, - keys: { items: "_getKeysMenu", acceptReporters: true }, + keys: { + items: "_getKeysMenu", + acceptReporters: true + }, settings: { items: [ - { text: "clear on blur", value: "clearOnBlur" }, - { text: "include tags on menu", value: "includeTags" }, + { text: Scratch.translate("clear on blur"), value: "clearOnBlur" }, + { text: Scratch.translate("include tags on menu"), value: "includeTags" }, ], }, - eventTriggerCondition: { items: ["when", "while", "after"] }, - keyProperty: { items: ["time", "name", "code", "value"] }, + eventTriggerCondition: { + items: [ + { text: Scratch.translate("when"), value: "when" }, + { text: Scratch.translate("while"), value: "while" }, + { text: Scratch.translate("after"), value: "after" }, + ] + }, + keyProperty: { + items: [ + { text: Scratch.translate("time"), value: "time" }, + { text: Scratch.translate("name"), value: "name" }, + { text: Scratch.translate("code"), value: "code" }, + { text: Scratch.translate("value"), value: "value" }, + ] + }, returnMode: { items: [ - "together & in order", - "together & ignore order", - "individually", + { text: Scratch.translate("together & in order"), value: "together & in order" }, + { text: Scratch.translate("together & ignore order"), value: "together & ignore order" }, + { text: Scratch.translate("individually"), value: "individually" }, ], }, orderMode: { - items: ["together & in order", "together & ignore order"], + items: [ + { text: Scratch.translate("together & in order"), value: "together & in order" }, + { text: Scratch.translate("together & ignore order"), value: "together & ignore order" }, + ], + }, + toggle: { + items: [ + { text: Scratch.translate("enabled"), value: "enabled" }, + { text: Scratch.translate("disabled"), value: "disabled" }, + ] + }, + data: { + items: [ + { text: Scratch.translate("all"), value: "all" }, + { text: Scratch.translate("tags"), value: "tags" }, + { text: Scratch.translate("keybinds"), value: "keybinds" }, + { text: Scratch.translate("settings"), value: "settings" }, + ] + }, + upDown: { + items: [ + { text: Scratch.translate("up"), value: "up" }, + { text: Scratch.translate("down"), value: "down" }, + ] }, - toggle: { items: ["enabled", "disabled"] }, - data: { items: ["all", "tags", "keybinds", "settings"] }, - upDown: { items: ["up", "down"] }, - }, + }, }; } // Filter From cdbf9aec91bac17fc5db981fdd80edd8c0220a45 Mon Sep 17 00:00:00 2001 From: "DangoCat[bot]" Date: Sat, 8 Feb 2025 07:12:33 +0000 Subject: [PATCH 17/18] [Automated] Format code --- extensions/Ender-Studio/Keys-Plus-V2.js | 71 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/extensions/Ender-Studio/Keys-Plus-V2.js b/extensions/Ender-Studio/Keys-Plus-V2.js index b2aed7cb93..e10da1cc06 100644 --- a/extensions/Ender-Studio/Keys-Plus-V2.js +++ b/extensions/Ender-Studio/Keys-Plus-V2.js @@ -619,7 +619,9 @@ { opcode: "eventKeysPressed", blockType: Scratch.BlockType.HAT, - text: Scratch.translate("[trigger] [[keys]] keys is pressed [ordered]"), + text: Scratch.translate( + "[trigger] [[keys]] keys is pressed [ordered]" + ), isEdgeActivated: false, arguments: { trigger: { @@ -808,7 +810,9 @@ { opcode: "keybindBindMultiple", blockType: Scratch.BlockType.COMMAND, - text: Scratch.translate("bind [[keys]] keys [mode] as [trigger] to [event]"), + text: Scratch.translate( + "bind [[keys]] keys [mode] as [trigger] to [event]" + ), arguments: { keys: { type: Scratch.ArgumentType.STRING, @@ -921,65 +925,86 @@ ], acceptReporters: true, }, - keys: { - items: "_getKeysMenu", - acceptReporters: true + keys: { + items: "_getKeysMenu", + acceptReporters: true, }, settings: { items: [ - { text: Scratch.translate("clear on blur"), value: "clearOnBlur" }, - { text: Scratch.translate("include tags on menu"), value: "includeTags" }, + { + text: Scratch.translate("clear on blur"), + value: "clearOnBlur", + }, + { + text: Scratch.translate("include tags on menu"), + value: "includeTags", + }, ], }, - eventTriggerCondition: { + eventTriggerCondition: { items: [ { text: Scratch.translate("when"), value: "when" }, { text: Scratch.translate("while"), value: "while" }, { text: Scratch.translate("after"), value: "after" }, - ] + ], }, - keyProperty: { + keyProperty: { items: [ { text: Scratch.translate("time"), value: "time" }, { text: Scratch.translate("name"), value: "name" }, { text: Scratch.translate("code"), value: "code" }, { text: Scratch.translate("value"), value: "value" }, - ] + ], }, returnMode: { items: [ - { text: Scratch.translate("together & in order"), value: "together & in order" }, - { text: Scratch.translate("together & ignore order"), value: "together & ignore order" }, - { text: Scratch.translate("individually"), value: "individually" }, + { + text: Scratch.translate("together & in order"), + value: "together & in order", + }, + { + text: Scratch.translate("together & ignore order"), + value: "together & ignore order", + }, + { + text: Scratch.translate("individually"), + value: "individually", + }, ], }, orderMode: { items: [ - { text: Scratch.translate("together & in order"), value: "together & in order" }, - { text: Scratch.translate("together & ignore order"), value: "together & ignore order" }, + { + text: Scratch.translate("together & in order"), + value: "together & in order", + }, + { + text: Scratch.translate("together & ignore order"), + value: "together & ignore order", + }, ], }, - toggle: { + toggle: { items: [ { text: Scratch.translate("enabled"), value: "enabled" }, { text: Scratch.translate("disabled"), value: "disabled" }, - ] + ], }, - data: { + data: { items: [ { text: Scratch.translate("all"), value: "all" }, { text: Scratch.translate("tags"), value: "tags" }, { text: Scratch.translate("keybinds"), value: "keybinds" }, { text: Scratch.translate("settings"), value: "settings" }, - ] + ], }, - upDown: { + upDown: { items: [ { text: Scratch.translate("up"), value: "up" }, { text: Scratch.translate("down"), value: "down" }, - ] + ], }, - }, + }, }; } // Filter From 2e4fcd502e46ca3490499742f6e2b0090d393355 Mon Sep 17 00:00:00 2001 From: Ender-Studio Date: Sat, 8 Feb 2025 15:15:13 +0800 Subject: [PATCH 18/18] Update Keys-Plus-V2.svg --- images/Ender-Studio/Keys-Plus-V2.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/Ender-Studio/Keys-Plus-V2.svg b/images/Ender-Studio/Keys-Plus-V2.svg index 171cef5b48..2ca99fbf3a 100644 --- a/images/Ender-Studio/Keys-Plus-V2.svg +++ b/images/Ender-Studio/Keys-Plus-V2.svg @@ -1 +1 @@ - +