const BlockType = require('../../extension-support/block-type') const BlockShape = require('../../extension-support/block-shape') const ArgumentType = require('../../extension-support/argument-type') const Cast = require('../../util/cast') let arrayLimit = 2 ** 32 // credit to sharpool because i stole the for each code from his extension haha im soo evil /** * @param {number} x * @returns {string} */ function formatNumber(x) { if (x >= 1e6) { return x.toExponential(4) } else { x = Math.floor(x * 1000) / 1000 return x.toFixed(Math.min(3, (String(x).split('.')[1] || '').length)) } } function clampIndex(x) { return Math.min(Math.max(x, 0), arrayLimit) } function span(text) { let el = document.createElement('span') el.innerHTML = text el.style.display = 'hidden' el.style.whiteSpace = 'nowrap' el.style.width = '100%' el.style.textAlign = 'center' return el } class ArrayType { customId = "jwArray" array = [] constructor(array = []) { this.array = array } static toArray(x) { if (x instanceof ArrayType) return new ArrayType([...x.array]) if (x instanceof Array) return new ArrayType([...x]) if (x === "" || x === null || x === undefined) return new ArrayType() try { let parsed = JSON.parse(x) if (parsed instanceof Array) return new ArrayType(parsed) } catch {} return new ArrayType([x]) } static forArray(x) { if (x instanceof ArrayType) return new ArrayType([...x.array]) return x } static display(x) { try { switch (typeof x) { case "object": if (x === null) return "null" if (typeof x.jwArrayHandler == "function") { return x.jwArrayHandler() } return Cast.toString(x) case "undefined": return "null" case "number": return formatNumber(x) case "boolean": return x ? "true" : "false" case "string": return `"${Cast.toString(x)}"` } } catch {} return "?" } jwArrayHandler() { return `Array<${formatNumber(this.array.length)}>` } toString() { return JSON.stringify(this.array) } toMonitorContent = () => span(this.toString()) toReporterContent() { let root = document.createElement('div') root.style.display = 'flex' root.style.flexDirection = 'column' root.style.justifyContent = 'center' let arrayDisplay = span(`[${this.array.slice(0, 50).map(v => ArrayType.display(v)).join(', ')}]`) arrayDisplay.style.overflow = "hidden" arrayDisplay.style.whiteSpace = "nowrap" arrayDisplay.style.textOverflow = "ellipsis" arrayDisplay.style.maxWidth = "256px" root.appendChild(arrayDisplay) root.appendChild(span(`Length: ${this.array.length}`)) return root } get length() { return this.array.length } } const jwArray = { Type: ArrayType, Block: { blockType: BlockType.REPORTER, blockShape: BlockShape.SQUARE, forceOutputType: "Array", disableMonitor: true }, Argument: { shape: BlockShape.SQUARE, check: ["Array"] } } class Extension { constructor() { vm.jwArray = jwArray vm.runtime.registerSerializer( //this basically copies variable serialization "jwArray", v => v.array.map(w => { if (typeof w == "object" && w != null && w.customId) { return { customType: true, typeId: w.customId, serialized: vm.runtime.serializers[w.customId].serialize(w) }; } return w }), v => new jwArray.Type(v.map(w => { if (typeof w == "object" && w != null && w.customType) { return vm.runtime.serializers[w.typeId].deserialize(w.serialized) } return w })) ); } getInfo() { return { id: "jwArray", name: "Arrays", color1: "#ff513d", menuIconURI: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgeG1sbnM6Yng9Imh0dHBzOi8vYm94eS1zdmcuY29tIj4KICA8Y2lyY2xlIHN0eWxlPSJzdHJva2Utd2lkdGg6IDJweDsgcGFpbnQtb3JkZXI6IHN0cm9rZTsgZmlsbDogcmdiKDI1NSwgODEsIDYxKTsgc3Ryb2tlOiByZ2IoMjA1LCA1OSwgNDQpOyIgY3g9IjEwIiBjeT0iMTAiIHI9IjkiPjwvY2lyY2xlPgogIDxwYXRoIGQ9Ik0gOC4wNzMgNC4yMiBMIDYuMTQ3IDQuMjIgQyA1LjA4MyA0LjIyIDQuMjIgNS4wODMgNC4yMiA2LjE0NyBMIDQuMjIgMTMuODUzIEMgNC4yMiAxNC45MTkgNS4wODMgMTUuNzggNi4xNDcgMTUuNzggTCA4LjA3MyAxNS43OCBMIDguMDczIDEzLjg1MyBMIDYuMTQ3IDEzLjg1MyBMIDYuMTQ3IDYuMTQ3IEwgOC4wNzMgNi4xNDcgTCA4LjA3MyA0LjIyIFogTSAxMS45MjcgMTMuODUzIEwgMTMuODUzIDEzLjg1MyBMIDEzLjg1MyA2LjE0NyBMIDExLjkyNyA2LjE0NyBMIDExLjkyNyA0LjIyIEwgMTMuODUzIDQuMjIgQyAxNC45MTcgNC4yMiAxNS43OCA1LjA4MyAxNS43OCA2LjE0NyBMIDE1Ljc4IDEzLjg1MyBDIDE1Ljc4IDE0LjkxOSAxNC45MTcgMTUuNzggMTMuODUzIDE1Ljc4IEwgMTEuOTI3IDE1Ljc4IEwgMTEuOTI3IDEzLjg1MyBaIiBmaWxsPSIjZmZmIiBzdHlsZT0iIj48L3BhdGg+Cjwvc3ZnPg==", blocks: [ { opcode: 'blank', text: 'blank array', ...jwArray.Block }, { opcode: 'blankLength', text: 'blank array of length [LENGTH]', arguments: { LENGTH: { type: ArgumentType.NUMBER, defaultValue: 1 } }, ...jwArray.Block }, { opcode: 'fromList', text: 'array from list [LIST]', arguments: { LIST: { menu: "list" } }, hideFromPalette: true, //doesn't work for some reason ...jwArray.Block }, { opcode: 'split', text: 'split [STRING] by [DIVIDER]', arguments: { STRING: { type: ArgumentType.STRING, defaultValue: "foo" }, DIVIDER: { type: ArgumentType.STRING } }, ...jwArray.Block }, "---", { opcode: 'get', text: 'get [INDEX] in [ARRAY]', blockType: BlockType.REPORTER, allowDropAnywhere: true, arguments: { ARRAY: jwArray.Argument, INDEX: { type: ArgumentType.NUMBER, defaultValue: 1 } } }, { opcode: 'index', text: 'index of [VALUE] in [ARRAY]', blockType: BlockType.REPORTER, arguments: { ARRAY: jwArray.Argument, VALUE: { type: ArgumentType.STRING, defaultValue: "foo", exemptFromNormalization: true } } }, { opcode: 'has', text: '[ARRAY] has [VALUE]', blockType: BlockType.BOOLEAN, arguments: { ARRAY: jwArray.Argument, VALUE: { type: ArgumentType.STRING, exemptFromNormalization: true } } }, { opcode: 'length', text: 'length of [ARRAY]', blockType: BlockType.REPORTER, arguments: { ARRAY: jwArray.Argument } }, "---", { opcode: 'set', text: 'set [INDEX] in [ARRAY] to [VALUE]', arguments: { ARRAY: jwArray.Argument, INDEX: { type: ArgumentType.NUMBER, defaultValue: 1 }, VALUE: { type: ArgumentType.STRING, defaultValue: "foo", exemptFromNormalization: true } }, ...jwArray.Block }, { opcode: 'append', text: 'append [VALUE] to [ARRAY]', arguments: { ARRAY: jwArray.Argument, VALUE: { type: ArgumentType.STRING, defaultValue: "foo", exemptFromNormalization: true } }, ...jwArray.Block }, { opcode: 'concat', text: 'merge [ONE] with [TWO]', arguments: { ONE: jwArray.Argument, TWO: jwArray.Argument }, ...jwArray.Block }, { opcode: 'fill', text: 'fill [ARRAY] with [VALUE]', arguments: { ARRAY: jwArray.Argument, VALUE: { type: ArgumentType.STRING, defaultValue: "foo", exemptFromNormalization: true } }, ...jwArray.Block }, { opcode: 'splice', text: 'splice [ARRAY] at [INDEX] with [ITEMS] items', arguments: { ARRAY: jwArray.Argument, INDEX: { type: ArgumentType.NUMBER, defaultValue: 1 }, ITEMS: { type: ArgumentType.NUMBER, defaultValue: 1 } }, ...jwArray.Block }, "---", { opcode: 'reverse', text: 'reverse [ARRAY]', arguments: { ARRAY: jwArray.Argument }, ...jwArray.Block }, "---", { opcode: 'forEachI', text: 'index', blockType: BlockType.REPORTER, hideFromPalette: true, canDragDuplicate: true }, { opcode: 'forEachV', text: 'value', blockType: BlockType.REPORTER, hideFromPalette: true, allowDropAnywhere: true, canDragDuplicate: true }, { opcode: 'forEach', text: 'for [I] [V] of [ARRAY]', blockType: BlockType.LOOP, arguments: { ARRAY: jwArray.Argument, I: { fillIn: 'forEachI' }, V: { fillIn: 'forEachV' } } }, /*{ opcode: 'forEachBreak', text: 'break', blockType: BlockType.COMMAND, isTerminal: true }*/ ], menus: { list: { acceptReporters: false, items: "getLists", }, } }; } getLists() { const globalLists = Object.values(vm.runtime.getTargetForStage().variables) .filter((x) => x.type == "list"); const localLists = Object.values(vm.editingTarget.variables) .filter((x) => x.type == "list"); const uniqueLists = [...new Set([...globalLists, ...localLists])]; if (uniqueLists.length === 0) return [{ text: "", value: "" }]; return uniqueLists.map((v) => ({ text: v.name, value: new jwArray.Type(v.value) })); } blank() { return new jwArray.Type() } blankLength({LENGTH}) { LENGTH = clampIndex(Cast.toNumber(LENGTH)) return new jwArray.Type(Array(LENGTH).fill(undefined)) } fromList({LIST}) { return jwArray.Type.toArray(LIST) } split({STRING, DIVIDER}) { STRING = Cast.toString(STRING) DIVIDER = Cast.toString(DIVIDER) return new jwArray.Type(STRING.split(DIVIDER)) } get({ARRAY, INDEX}) { ARRAY = jwArray.Type.toArray(ARRAY) return ARRAY.array[Cast.toNumber(INDEX)-1] || "" } index({ARRAY, VALUE}) { ARRAY = jwArray.Type.toArray(ARRAY) return ARRAY.array.indexOf(VALUE) + 1 } has({ARRAY, VALUE}) { ARRAY = jwArray.Type.toArray(ARRAY) return ARRAY.array.includes(VALUE) } length({ARRAY}) { ARRAY = jwArray.Type.toArray(ARRAY) return ARRAY.length } set({ARRAY, INDEX, VALUE}) { ARRAY = jwArray.Type.toArray(ARRAY) INDEX = Cast.toNumber(INDEX) ARRAY.array[clampIndex(Cast.toNumber(INDEX)-1)] = jwArray.Type.forArray(VALUE) return ARRAY } append({ARRAY, VALUE}) { ARRAY = jwArray.Type.toArray(ARRAY) ARRAY.array.push(jwArray.Type.forArray(VALUE)) return ARRAY } concat({ONE, TWO}) { ONE = jwArray.Type.toArray(ONE) TWO = jwArray.Type.toArray(TWO) return new jwArray.Type(ONE.array.concat(TWO.array)) } fill({ARRAY, VALUE}) { ARRAY = jwArray.Type.toArray(ARRAY) ARRAY.array.fill(jwArray.Type.forArray(VALUE)) return ARRAY } splice({ARRAY, INDEX, ITEMS}) { ARRAY = jwArray.Type.toArray(ARRAY) INDEX = Cast.toNumber(INDEX) ITEMS = Cast.toNumber(ITEMS) ARRAY.array.splice(INDEX - 1, ITEMS) return ARRAY } reverse({ARRAY}) { ARRAY = jwArray.Type.toArray(ARRAY) ARRAY.array.reverse() return ARRAY } forEachI({}, util) { let arr = util.thread.stackFrames[0].jwArray return arr ? Cast.toNumber(arr[0]) + 1 : 0 } forEachV({}, util) { let arr = util.thread.stackFrames[0].jwArray return arr ? arr[1] : "" } forEach({ARRAY}, util) { ARRAY = jwArray.Type.toArray(ARRAY) if (util.stackFrame.execute) { util.stackFrame.index++; const { index, entry } = util.stackFrame; if (index > entry.length - 1) return; util.thread.stackFrames[0].jwArray = entry[index]; } else { const entry = Object.entries(ARRAY.array); if (entry.length === 0) return; util.stackFrame.entry = entry; util.stackFrame.execute = true; util.stackFrame.index = 0; util.thread.stackFrames[0].jwArray = entry[0]; } util.startBranch(1, true); } forEachBreak({}, util) { util.stackFrame.entry = [] } } module.exports = Extension