diff --git a/static/extensions/Ashime/MoreFields.js b/static/extensions/Ashime/MoreFields.js index 2fc74a3bf..d06a6bee2 100644 --- a/static/extensions/Ashime/MoreFields.js +++ b/static/extensions/Ashime/MoreFields.js @@ -1,32 +1,40 @@ /**! * More Fields - * @author 0znzw https://scratch.mit.edu/users/0znzw/ - * @version 1.4 - * @copyright MIT & LGPLv3 License + * @author 0znzw (@link https://scratch.mit.edu/users/0znzw/) + * @version 1.8.1 + * @license MIT AND LGPL-3.0 * Do not remove this comment */ -(async function (Scratch) { +(function (Scratch) { 'use strict'; - if (!Scratch.extensions.unsandboxed) { throw new Error(`"More Fields" must be ran unsandboxed.`); } - const extId = '0znzwMoreFields'; const { BlockType, ArgumentType, vm } = Scratch, runtime = vm.runtime; const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); - - // Some checks + if (hasOwn(runtime, `ext_${extId}`)) { + // https://github.com/surv-is-a-dev/gallery/blob/main/site-files/extensions/0znzw/NicheToolbox.js + const MESSAGE = `Palette overload.
(MoreFields loaded twice)`; + const toString = Object.prototype.toString; + Object.prototype.toString = function() { + throw new Error(MESSAGE); + } + vm.editingTarget = {}; + vm.emitTargetsUpdate(); + setTimeout(function(){ + const err = document.querySelector('p[class^=crash-message_error-message]'); + err.innerHTML = MESSAGE; + Object.prototype.toString = toString; + }, 100); + throw new Error(MESSAGE); + } const DOOMcheck = (vm.runtime.ioDevices.userData._username === 'DOOM1997'); - const searchParams = new URLSearchParams(window.location.search); - const hideInlineTextarea = !searchParams.has('MoreFields_InlineTextarea'); - - // "Constants" - const padding = JSON.parse(localStorage['tw:addons'] || JSON.stringify(window.scratchAddons ? scratchAddons.globalState.addonSettings : {'custom-block-shape': { cornerSize: 100, notchSize: 100, paddingSize: 100 }}))['custom-block-shape'] || { cornerSize: 100, notchSize: 100, paddingSize: 100 }; - console.log(padding); + const searchParams = new URLSearchParams(globalThis.location.search); + const _hideInlineTextarea = !searchParams.has('MoreFields_InlineTextarea'); + const padding = JSON.parse(localStorage['tw:addons'] || JSON.stringify(globalThis.scratchAddons ? scratchAddons.globalState.addonSettings : {'custom-block-shape': { cornerSize: 100, notchSize: 100, paddingSize: 100 }}))['custom-block-shape'] || { cornerSize: 100, notchSize: 100, paddingSize: 100 }; const customFieldTypes = {}; - let Blockly = null; // Blockly is used cause Its easier than ScratchBlocks imo, it does not make a difference. - + let Blockly = null; // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors const _LDC = function _LightenDarkenColor(col, amt) { const num = parseInt(col.replace('#', ''), 16); @@ -36,8 +44,6 @@ const newColour = g | (b << 8) | (r << 16); return (col.at(0) === '#' ? '#' : '') + newColour.toString(16); }; - - // Me being lazy function _setCssNattr(node, attr, value) { node.setAttribute(attr, String(value)); node.style[attr] = value; @@ -46,8 +52,6 @@ node.removeAttribute(attr); delete node.style[attr]; } - - // These should NEVER be called without ScratchBlocks existing function _fixColours(doText, col1, textColour) { const LDA = -10; const self = this.sourceBlock_; @@ -69,14 +73,12 @@ toArgument ??= false; Blockly.DropDownDiv.showPositionedByBlock(this, (toArgument ? this.sourceBlock_ : this.sourceBlock_.parentBlock_)); } - const _cbfsb = runtime._convertBlockForScratchBlocks.bind(runtime); runtime._convertBlockForScratchBlocks = function(blockInfo, categoryInfo, ...args) { const res = _cbfsb(blockInfo, categoryInfo, ...args); if (hasOwn(blockInfo, 'blockShape')) res.json.outputShape = blockInfo.blockShape; return res; }; - // https://github.com/Xeltalliv/extensions/blob/examples/examples/custom-field-types.js const bcfi = runtime._buildCustomFieldInfo.bind(runtime); const bcftfsb = runtime._buildCustomFieldTypeForScratchBlocks.bind(runtime); @@ -86,7 +88,7 @@ return bcfi(fieldName, fieldInfo, extensionId, categoryInfo, ...args); }; runtime._buildCustomFieldTypeForScratchBlocks = function(fieldName, output, outputShape, categoryInfo, ...args) { - let res = bcftfsb(fieldName, output, outputShape, categoryInfo, ...args); + const res = bcftfsb(fieldName, output, outputShape, categoryInfo, ...args); if (fi) { if (fi.color1) res.json.colour = fi.color1; if (fi.color2) res.json.colourSecondary = fi.color2; @@ -97,17 +99,13 @@ } return res; }; - const toRegisterOnBlocklyGot = []; - // https://github.com/LLK/scratch-vm/blob/f405e59d01a8f9c0e3e986fb5276667a8a3c7d40/test/unit/extension_conversion.js#L85-L124 // https://github.com/LLK/scratch-vm/commit/ceaa3c7857b79459ccd1b14d548528e4511209e7 vm.addListener('EXTENSION_FIELD_ADDED', (fieldInfo) => { if (Blockly) Blockly.Field.register(fieldInfo.name, fieldInfo.implementation); else toRegisterOnBlocklyGot.push([fieldInfo.name, fieldInfo.implementation]); }); - - // ArgumentType additions ArgumentType.TEXTAREA = 'TextareaInput'; ArgumentType.INLINETEXTAREA = 'TextareaInputInline'; ArgumentType.SNAPBOOLEAN = 'SnapBoolean'; @@ -115,8 +113,7 @@ ArgumentType.HIDDENSTRING = 'StringHidden'; ArgumentType.INLINEDATE = 'DateInline'; ArgumentType.FILE = 'FileInput'; - - let implementations = { + const implementations = { FieldTextarea: null, FieldInlineTextarea: null, FieldSnapBoolean: null, @@ -125,77 +122,73 @@ FieldFileInput: null, FieldInlineDoom: null, }; - customFieldTypes[ArgumentType.TEXTAREA] = { output: ArgumentType.STRING, color1: '#9566d3', outputShape: 2, implementation: { - fromJson: () => new implementations.FieldTextarea() - } + fromJson: () => new implementations.FieldTextarea(), + }, }; customFieldTypes[ArgumentType.INLINETEXTAREA] = { output: ArgumentType.STRING, color1: '#9566d3', outputShape: 3, implementation: { - fromJson: () => new implementations.FieldInlineTextarea() - } + fromJson: () => new implementations.FieldInlineTextarea(), + }, }; customFieldTypes[ArgumentType.SNAPBOOLEAN] = { output: ArgumentType.BOOLEAN, color1: '#9566d3', outputShape: 1, implementation: { - fromJson: () => new implementations.FieldSnapBoolean() - } + fromJson: () => new implementations.FieldSnapBoolean(), + }, }; customFieldTypes[ArgumentType.INLINESLIDER] = { output: ArgumentType.NUMBER, color1: '#9566d3', outputShape: 3, implementation: { - fromJson: () => new implementations.FieldInlineSlider() - } + fromJson: () => new implementations.FieldInlineSlider(), + }, }; customFieldTypes[ArgumentType.HIDDENSTRING] = { output: ArgumentType.STRING, color1: '#9566d3', outputShape: 2, implementation: { - fromJson: () => new implementations.FieldHiddenTextInput() - } + fromJson: () => new implementations.FieldHiddenTextInput(), + }, }; customFieldTypes[ArgumentType.INLINEDATE] = { output: ArgumentType.NUMBER, color1: '#9566d3', outputShape: 3, implementation: { - fromJson: () => new implementations.FieldInlineDate() - } + fromJson: () => new implementations.FieldInlineDate(), + }, }; customFieldTypes[ArgumentType.FILE] = { output: null, color1: '#9566d3', outputShape: 3, implementation: { - fromJson: () => new implementations.FieldFileInput() - } + fromJson: () => new implementations.FieldFileInput(), + }, }; customFieldTypes['InlineDoom'] = { output: [], color1: '#9566d3', outputShape: 3, implementation: { - fromJson: () => new implementations.FieldInlineDoom() - } + fromJson: () => new implementations.FieldInlineDoom(), + }, }; - - // Main try thing - function tryUseScratchBlocks(_sb) { + function gotBlockly(_sb) { Blockly = _sb; const BlockSvg = Blockly.BlockSvg; - // Temporary fix for the annoying error: // ' attribute x: Expected length, "NaN".' const _setAttribute = SVGTextElement.prototype.setAttribute; @@ -207,7 +200,6 @@ } return _setAttribute.call(this, attr, val, ...args); }; - // Patch for a bug in size_.height const _endBlockDrag = Blockly.BlockDragger.prototype.endBlockDrag Blockly.BlockDragger.prototype.endBlockDrag = function (...a) { @@ -218,8 +210,6 @@ } return res; } - - // Fields const textInputs_trueToOriginal = true; implementations.FieldTextarea = class FieldTextarea extends Blockly.FieldTextInput { constructor(opt_value) { @@ -232,6 +222,7 @@ Blockly.DropDownDiv.clearContent(); const div = Blockly.DropDownDiv.getContentDiv(); const input = document.createElement('textarea'); + input.setAttribute('style', 'color-scheme: light; color: #575E75;'); input.value = this.getValue(); div.append(input); this._textarea = input; @@ -284,13 +275,14 @@ this._FakeWidth ??= 40; this._FakeHeight ??= 24; const textareaHolder = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'); + textareaHolder.setAttribute('style', 'color-scheme: light; color: #575E75;'); textareaHolder.setAttribute('x', '6'); textareaHolder.setAttribute('y', String(BlockSvg.NOTCH_START_PADDING / 2 - 0.375)); textareaHolder.addEventListener('mousedown', (e) => e.stopPropagation()); const textarea = document.createElement('textarea'); textarea.value = this.getValue() ?? ''; textarea.addEventListener('input', () => this._onInput()); - textarea.addEventListener('mouseup', (e) => this._resizeHolder()); + textarea.addEventListener('mouseup', () => this._resizeHolder()); if (this.fieldGroup_) { this.fieldGroup_.insertAdjacentElement('afterend', textareaHolder); textareaHolder.appendChild(textarea); @@ -350,7 +342,6 @@ this.sliderCircle_.setAttribute('transform', `translate(16, 16`); } } - // Colours <3 rerender() { this.updateCircle_(); const fg_ = this.fieldGroup_; @@ -369,62 +360,38 @@ if (circle) circle.setAttribute('cx', '0'); } } - // State management updateState(value, toggle) { let n = Number(value); - if (toggle) n = Number(!Boolean(Number(this.getValue()))); + if (toggle) n = Number(!(Number(this.getValue()))); this.setValue(n); } showEditor_() { this.updateState(0, true); this.render_(); } - // Rendering render_() { if (this.visible_ && this.textElement_) { - // Replace the text. - this.textElement_.textContent = this.slap; //this.getDisplayText_(); + this.textElement_.textContent = this.slap; this.updateWidth(); - - // Update text centering, based on newly calculated width. - var centerTextX = (this.size_.width - this.arrowWidth_) / 2; + let centerTextX = (this.size_.width - this.arrowWidth_) / 2; if (this.sourceBlock_.RTL) { centerTextX += this.arrowWidth_; } - - // In a text-editing shadow block's field, - // if half the text length is not at least center of - // visible field (FIELD_WIDTH), center it there instead, - // unless there is a drop-down arrow. if (this.sourceBlock_.isShadow() && !this.positionArrow) { - var minOffset = Blockly.BlockSvg.FIELD_WIDTH / 2; + const minOffset = Blockly.BlockSvg.FIELD_WIDTH / 2; if (this.sourceBlock_.RTL) { - // X position starts at the left edge of the block, in both RTL and LTR. - // First offset by the width of the block to move to the right edge, - // and then subtract to move to the same position as LTR. - var minCenter = this.size_.width - minOffset; + const minCenter = this.size_.width - minOffset; centerTextX = Math.min(minCenter, centerTextX); } else { - // (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2 - // if the text is longer. centerTextX = Math.max(minOffset, centerTextX); } } - - // Apply new text element x position. this.textElement_.setAttribute('x', centerTextX); } - - // Update any drawn box to the correct width and height. if (this.box_) { this.box_.setAttribute('width', this.size_.width); this.box_.setAttribute('height', this.size_.height); } - - if (this.textElement_) { - // this.textElement_.style.display = 'none'; - } - this.rerender(); } } @@ -484,7 +451,7 @@ this.setValue(val); } _onSliderInput() { - if (!!this._valInput) this._valInput.value = Number(this._slider.value); + if (this._valInput) this._valInput.value = Number(this._slider.value); const val = `${this._slider.value},${this._slider.min},${this._slider.max}`; this.setValue(val); } @@ -534,15 +501,11 @@ if (!!this.textNode__ && this.sourceBlock_.parentBlock_) _fixColours.call(this, true, this.sourceBlock_.parentBlock_.colour_, this.sourceBlock_.parentBlock_.colour_); } showEditor_(...showArgs) { - if (!!this.textNode__) _delCssNattr(this.textNode__, 'fill'); + if (this.textNode__) _delCssNattr(this.textNode__, 'fill'); Blockly.FieldTextInput.prototype.showEditor_.call(this, ...showArgs); } } implementations.FieldInlineDate = class FieldInlineDate extends Blockly.Field { - // For help with date related stuff look at these at they helped me alot. - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date - // https://stackoverflow.com/questions/847185/convert-a-unix-timestamp-to-time-in-javascript - // https://stackoverflow.com/questions/7556591/is-the-javascript-date-object-always-one-day-off constructor(opt_value) { opt_value = ArgumentType.INLINEDATE; super(opt_value); @@ -575,7 +538,7 @@ init(...initArgs) { Blockly.FieldNumber.prototype.init.call(this, ...initArgs); this.textNode__ = this.sourceBlock_.svgPath_.parentNode.querySelector('g.blocklyEditableText text'); - if (!!this.textNode__) { + if (this.textNode__) { this.textNode__.style.display = 'none'; if (this.sourceBlock_.parentBlock_) _fixColours.call(this, false, this.sourceBlock_.parentBlock_.colour_); } @@ -619,7 +582,6 @@ // TODO: add min and max date along with "step" } } - // icons from: https://fonts.google.com/icons const _fileIconColour = `style="fill:#FFFFFF;stroke:#FFFFFF;" fill="#FFFFFF" stroke="#FFFFFF"`; const settingsIcon = `data:image/svg+xml;base64,${btoa(``)}`; @@ -645,11 +607,13 @@ this._delim = '\n'; Blockly.FieldTextInput.prototype.init.call(this, ...initArgs); this.textNode__ = this.sourceBlock_.svgPath_.parentNode.querySelector('g.blocklyEditableText text'); - if (!!this.textNode__) { + if (this.textNode__) { this.textNode__.style.display = 'none'; if (this.sourceBlock_.parentBlock_) _fixColours.call(this, false, this.sourceBlock_.parentBlock_.colour_); } - this._fileData = this.getValue() ?? null; + this._fileData = this.getValue() ?? ''; + if (this._fileData === ArgumentType.FILE) this._fileData = ''; + this.setValue(this._fileData); const fg_ = this.fieldGroup_; if (!fg_) return; const path = fg_?.previousElementSibling; @@ -679,7 +643,23 @@ _loadData(item) { const value = this.getValue(); const cr1 = value.indexOf(this._delim); - const cr2 = value.indexOf(this._delim, cr1 + 1); + const cr2 = value.indexOf(this._delim, cr1 === -1 ? Infinity : cr1 + 1); + if (cr1 === -1 || cr2 === -1) { + switch (item) { + case 1: + this._fileData = ''; + break; + case 2: + this._outputOptions.value = 'dataURL'; + break; + case 3: + this._fileLimiter.value = '*.*'; + break; + default: + break; + } + return; + } switch (item) { case 1: this._fileData = value.substr(cr2 + 1); @@ -695,11 +675,11 @@ } } _saveFileData(skipLoad) { - if (!Boolean(skipLoad ?? false)) this._loadData(1); + if (!(skipLoad ?? false)) this._loadData(1); this.showEditor_(true); this._onInput(this._fileData); Blockly.DropDownDiv.hideWithoutAnimation(); - this._fileData = null; + this._fileData = ''; } _onSettingsClick(e) { e.stopPropagation(); @@ -710,7 +690,7 @@ const fileInput = document.createElement('input'); this.showEditor_(true); fileInput.type = 'file'; - fileInput.accept = this._fileLimiter.value.replaceAll(this._delim, '').trim() || '*.*'; + fileInput.accept = this._fileLimiter.value.trim() || '*.*'; const loadType = this._outputOptions.value; Blockly.DropDownDiv.hideWithoutAnimation(); const fiErr = (c, alr) => { @@ -731,7 +711,7 @@ if (fileList.length < 0) noFileErr = () => fiErr(true, 'No file uploaded?'); return true; }; - window.addEventListener('focus', unfe, {once: true}); + globalThis.addEventListener('focus', unfe, {once: true}); if (unfe() && noFileErr()) return; // Ok done crying. const reader = new FileReader(); @@ -759,15 +739,15 @@ fileInput.click(); } _getFileData() { - if (!!this._fileData) return this._fileData; + if (this._fileData && this._fileData !== '') return this._fileData; this._loadData(1); const fileData = this._fileData ?? ''; - this._fileData = null; + this._fileData = ''; return fileData; } _onInput(fileData) { if (this._fileLimiter.value.trim().length < 1) this._fileLimiter.value = '*.*'; - this.setValue(`${this._outputOptions.value}${this._delim}${this._fileLimiter.value.replaceAll(this._delim, '')}${this._delim}${fileData ?? this._getFileData()}`); + this.setValue(`${this._outputOptions.value.trim() || 'dataURL'}${this._delim}${this._fileLimiter.value.trim() || '*.*'}${this._delim}${fileData ?? (this._getFileData() ?? '')}`); } _optDropdown(selected, ...optValues) { optValues = (optValues ?? []).map(opt => ``).join('\n'); @@ -775,7 +755,7 @@ return { select, optValues }; } showEditor_(forceShow) { - if (!Boolean(forceShow ?? false)) return; + if (!(forceShow ?? false)) return; Blockly.DropDownDiv.clearContent(); const div = Blockly.DropDownDiv.getContentDiv(); if (!div) return; @@ -785,9 +765,9 @@ const fileLimiter = document.createElement('input'); fileLimiter.addEventListener('input', () => this._onInput()); const clearBtn = document.createElement('button'); - if (!!this._getFileData().at(0)) { + if (this._getFileData().at(0)) { clearBtn.addEventListener('click', () => { - this._fileData = null; + this._fileData = ''; this._onInput(''); clearBtn.nextElementSibling.remove(); clearBtn.remove(); @@ -814,10 +794,8 @@ this._loadData(3); } } - implementations.FieldInlineDoom = class FieldInlineDoom extends Blockly.Field { - showEditor_() { - } + showEditor_() {} constructor(opt_value) { opt_value = 'InlineDoom'; super(opt_value); @@ -837,7 +815,7 @@ this.inlineDblRender = true; Blockly.Field.prototype.init.call(this, ...initArgs); this.textNode__ = this.sourceBlock_.svgPath_.parentNode.querySelector('g.blocklyEditableText text'); - if (!!this.textNode__) { + if (this.textNode__) { this.textNode__.style.display = 'none'; if (this.sourceBlock_.parentBlock_) _fixColours.call(this, false, this.sourceBlock_.parentBlock_.colour_); } @@ -856,54 +834,249 @@ this._addDOOM(); } _addDOOM() { - let frame = document.createElement('iframe'); + const frame = document.createElement('iframe'); frame.width = 640; frame.height = 400; frame.id = 'DOOM'; this._fObj.appendChild(frame); - /** + /**! * * ORIGINAL: https://diekmann.github.io/wasm-fizzbuzz/doom/ * Ported for use in turbowarp blocks * */ - frame.srcdoc = atob(`PCEtLSBPUklHSU5BTDogaHR0cHM6Ly9kaWVrbWFubi5naXRodWIuaW8vd2FzbS1maXp6YnV6ei9kb29tLyAtLT48IWRvY3R5cGVodG1sPjxodG1sPjxib2R5PjxET09NPjxzdHlsZT4jb3V0cHV0e2JvcmRlcjozcHggZ3Jvb3ZlICM3ZmZmZDQ7YmFja2dyb3VuZC1jb2xvcjpiaXNxdWU7d2lkdGg6NTUwcHg7aGVpZ2h0OjQwMHB4O2ZvbnQtZmFtaWx5Om1vbm9zcGFjZSxzZXJpZjtmb250LXNpemU6MTBweDtvdmVyZmxvdy15OnNjcm9sbH0jb3V0cHV0IHNwYW4ubG9ne2NvbG9yOiM0ODNkOGJ9I291dHB1dCBzcGFuLnN0ZG91dHtjb2xvcjojMDAwfSNvdXRwdXQgc3Bhbi5zdGRlcnJ7Zm9udC13ZWlnaHQ6NzAwO2NvbG9yOmJyb3dufS5jb250YWluZXJ7ZGlzcGxheTpmbGV4fSp7bWFyZ2luOjBweDtwYWRkaW5nOjBweH08L3N0eWxlPjxzcGFuIGhpZGRlbj48cCBpZD1mb2N1c2hpbnQ+PC9wPjxwPjxidXR0b24gaWQ9ZW50ZXJCdXR0b24+PC9idXR0b24+PGJ1dHRvbiBpZD1sZWZ0QnV0dG9uPjwvYnV0dG9uPjxidXR0b24gaWQ9dXBCdXR0b24+PC9idXR0b24+PGJ1dHRvbiBpZD1kb3duQnV0dG9uPjwvYnV0dG9uPjxidXR0b24gaWQ9cmlnaHRCdXR0b24+PC9idXR0b24+IDxidXR0b24gaWQ9Y3RybEJ1dHRvbj48L2J1dHRvbj48YnV0dG9uIGlkPXNwYWNlQnV0dG9uPjwvYnV0dG9uPiA8YnV0dG9uIGlkPWFsdEJ1dHRvbj48L2J1dHRvbj48L3A+PC9zcGFuPjxkaXYgY2xhc3M9Y29udGFpbmVyPjxjYW52YXMgaGVpZ2h0PTQwMCBpZD1zY3JlZW4gdGFiaW5kZXg9MCB3aWR0aD02NDA+VGhpcyBpcyB3aGVyZSB0aGUgRG9vTSBzY3JlZW4gc2hvdWxkIHJlbmRlci48L2NhbnZhcz48ZGl2IGhpZGRlbiBpZD1vdXRwdXQ+PC9kaXY+PC9kaXY+PHNwYW4gaGlkZGVuPjxzcGFuIGlkPWdldG1zcHNfc3RhdHM+PC9zcGFuPjxzcGFuIGlkPWdldG1zX3N0YXRzPjwvc3Bhbj4gPHNwYW4gaWQ9ZnBzX3N0YXRzPjwvc3Bhbj48c3BhbiBpZD1kcmF3ZnJhbWVzX3N0YXRzPjwvc3Bhbj4gPHNwYW4gaWQ9YW5pbWF0aW9uZnBzX3N0YXRzPjwvc3Bhbj48L3NwYW4+PHNjcmlwdCBkZWZlcj4idXNlIHN0cmljdCI7dmFyIG1lbW9yeT1uZXcgV2ViQXNzZW1ibHkuTWVtb3J5KHtpbml0aWFsOjEwOH0pO2NvbnN0IG91dHB1dD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgib3V0cHV0Iik7ZnVuY3Rpb24gcmVhZFdhc21TdHJpbmcodCxlKXtsZXQgbj1uZXcgVWludDhBcnJheShtZW1vcnkuYnVmZmVyLHQsZSk7cmV0dXJuIG5ldyBUZXh0RGVjb2RlcigidXRmOCIpLmRlY29kZShuKX1mdW5jdGlvbiBjb25zb2xlTG9nU3RyaW5nKHQsZSl7bGV0IG49cmVhZFdhc21TdHJpbmcodCxlKTtjb25zb2xlLmxvZygnIicrbisnIicpfWZ1bmN0aW9uIGFwcGVuZE91dHB1dCh0KXtyZXR1cm4gZnVuY3Rpb24oZSxuKXtsZXQgcz1yZWFkV2FzbVN0cmluZyhlLG4pLnNwbGl0KCJcbiIpO2Zvcih2YXIgYT0wO2E8cy5sZW5ndGg7KythKWlmKDAhPXNbYV0ubGVuZ3RoKXt2YXIgcj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7ci5jbGFzc0xpc3QuYWRkKHQpLHIuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoc1thXSkpLG91dHB1dC5hcHBlbmRDaGlsZChyKSxvdXRwdXQuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnIiKSksci5zY3JvbGxJbnRvVmlldyh7YmVoYXZpb3I6InNtb290aCIsYmxvY2s6ImVuZCIsaW5saW5lOiJuZWFyZXN0In0pfX19Y29uc3QgZ2V0bXNwc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZ2V0bXNwc19zdGF0cyIpLGdldG1zX3N0YXRzPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJnZXRtc19zdGF0cyIpO3ZhciBnZXRtc19jYWxsc190b3RhbD0wLGdldG1zX2NhbGxzPTA7ZnVuY3Rpb24gZ2V0TWlsbGlzZWNvbmRzKCl7cmV0dXJuKytnZXRtc19jYWxscyxwZXJmb3JtYW5jZS5ub3coKX13aW5kb3cuc2V0SW50ZXJ2YWwoZnVuY3Rpb24oKXtnZXRtc19jYWxsc190b3RhbCs9Z2V0bXNfY2FsbHMsZ2V0bXNwc19zdGF0cy5pbm5lclRleHQ9Z2V0bXNfY2FsbHMvMWUzKyJrIixnZXRtc19zdGF0cy5pbm5lclRleHQ9Z2V0bXNfY2FsbHNfdG90YWwsZ2V0bXNfY2FsbHM9MH0sMWUzKTtjb25zdCBjYW52YXM9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjcmVlbiIpLGRvb21fc2NyZWVuX3dpZHRoPTY0MCxkb29tX3NjcmVlbl9oZWlnaHQ9NDAwLGZwc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZnBzX3N0YXRzIiksZHJhd2ZyYW1lc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZHJhd2ZyYW1lc19zdGF0cyIpO3ZhciBudW1iZXJfb2ZfZHJhd3NfdG90YWw9MCxudW1iZXJfb2ZfZHJhd3M9MDtmdW5jdGlvbiBkcmF3Q2FudmFzKHQpe3ZhciBlPW5ldyBVaW50OENsYW1wZWRBcnJheShtZW1vcnkuYnVmZmVyLHQsMTAyNGUzKSxuPW5ldyBJbWFnZURhdGEoZSw2NDAsNDAwKTtjYW52YXMuZ2V0Q29udGV4dCgiMmQiKS5wdXRJbWFnZURhdGEobiwwLDApLCsrbnVtYmVyX29mX2RyYXdzfXdpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbigpe251bWJlcl9vZl9kcmF3c190b3RhbCs9bnVtYmVyX29mX2RyYXdzLGRyYXdmcmFtZXNfc3RhdHMuaW5uZXJUZXh0PW51bWJlcl9vZl9kcmF3c190b3RhbCxmcHNfc3RhdHMuaW5uZXJUZXh0PW51bWJlcl9vZl9kcmF3cyxudW1iZXJfb2ZfZHJhd3M9MH0sMWUzKTt2YXIgaW1wb3J0T2JqZWN0PXtqczp7anNfY29uc29sZV9sb2c6YXBwZW5kT3V0cHV0KCJsb2ciKSxqc19zdGRvdXQ6YXBwZW5kT3V0cHV0KCJzdGRvdXQiKSxqc19zdGRlcnI6YXBwZW5kT3V0cHV0KCJzdGRlcnIiKSxqc19taWxsaXNlY29uZHNfc2luY2Vfc3RhcnQ6Z2V0TWlsbGlzZWNvbmRzLGpzX2RyYXdfc2NyZWVuOmRyYXdDYW52YXN9LGVudjp7bWVtb3J5Om1lbW9yeX19O1dlYkFzc2VtYmx5Lmluc3RhbnRpYXRlU3RyZWFtaW5nKGZldGNoKCJodHRwczovL3N1cnYuaXMtYS5kZXYvZG9vbS53YXNtIiksaW1wb3J0T2JqZWN0KS50aGVuKHQ9Pnt0Lmluc3RhbmNlLmV4cG9ydHMubWFpbigpO2xldCBlPWZ1bmN0aW9uKHQpe3N3aXRjaCh0KXtjYXNlIDg6cmV0dXJuIDEyNztjYXNlIDE3OnJldHVybiAxNTc7Y2FzZSAxODpyZXR1cm4gMTg0O2Nhc2UgMzc6cmV0dXJuIDE3MjtjYXNlIDM4OnJldHVybiAxNzM7Y2FzZSAzOTpyZXR1cm4gMTc0O2Nhc2UgNDA6cmV0dXJuIDE3NTtkZWZhdWx0OmlmKHQ+PTY1JiZ0PD05MClyZXR1cm4gdCszMjtpZih0Pj0xMTImJnQ8PTEyMylyZXR1cm4gdCs3NTtyZXR1cm4gdH19LG49ZnVuY3Rpb24oZSl7dC5pbnN0YW5jZS5leHBvcnRzLmFkZF9icm93c2VyX2V2ZW50KDAsZSl9LHM9ZnVuY3Rpb24oZSl7dC5pbnN0YW5jZS5leHBvcnRzLmFkZF9icm93c2VyX2V2ZW50KDEsZSl9O2NhbnZhcy5hZGRFdmVudExpc3RlbmVyKCJrZXlkb3duIixmdW5jdGlvbih0KXtuKGUodC5rZXlDb2RlKSksdC5wcmV2ZW50RGVmYXVsdCgpfSwhMSksY2FudmFzLmFkZEV2ZW50TGlzdGVuZXIoImtleXVwIixmdW5jdGlvbih0KXtzKGUodC5rZXlDb2RlKSksdC5wcmV2ZW50RGVmYXVsdCgpfSwhMSksW1siZW50ZXJCdXR0b24iLDEzXSxbImxlZnRCdXR0b24iLDE3Ml0sWyJyaWdodEJ1dHRvbiIsMTc0XSxbInVwQnV0dG9uIiwxNzNdLFsiZG93bkJ1dHRvbiIsMTc1XSxbImN0cmxCdXR0b24iLDE1N10sWyJzcGFjZUJ1dHRvbiIsMzJdLFsiYWx0QnV0dG9uIiwxODRdXS5mb3JFYWNoKChbdCxlXSk9Pntjb25zb2xlLmxvZyh0KyIgZm9yICIrZSk7dmFyIGE9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodCk7YS5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaHN0YXJ0IiwoKT0+bihlKSksYS5hZGRFdmVudExpc3RlbmVyKCJ0b3VjaGVuZCIsKCk9PnMoZSkpLGEuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hjYW5jZWwiLCgpPT5zKGUpKX0pO2xldCBhPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmb2N1c2hpbnQiKSxyPWZ1bmN0aW9uKHQpe2EuaW5uZXJUZXh0PSJLZXlib2FyZCBldmVudHMgd2lsbCBiZSBjYXB0dXJlZCBhcyBsb25nIGFzIHRoZSB0aGUgRE9PTSBjYW52YXMgaGFzIGZvY3VzLiIsYS5zdHlsZS5mb250V2VpZ2h0PSJub3JtYWwifTtjYW52YXMuYWRkRXZlbnRMaXN0ZW5lcigiZm9jdXNpbiIsciwhMSksY2FudmFzLmFkZEV2ZW50TGlzdGVuZXIoImZvY3Vzb3V0IixmdW5jdGlvbih0KXthLmlubmVyVGV4dD0iQ2xpY2sgb24gdGhlIGNhbnZhcyB0byBjYXB1dGUgaW5wdXQgYW5kIHN0YXJ0IHBsYXlpbmcuIixhLnN0eWxlLmZvbnRXZWlnaHQ9ImJvbGQifSwhMSksY2FudmFzLmZvY3VzKCkscigpO2xldCBvPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJhbmltYXRpb25mcHNfc3RhdHMiKTt2YXIgdT0wO2Z1bmN0aW9uIGMoZSl7Kyt1LHQuaW5zdGFuY2UuZXhwb3J0cy5kb29tX2xvb3Bfc3RlcCgpLHdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoYyl9d2luZG93LnNldEludGVydmFsKGZ1bmN0aW9uKCl7by5pbm5lclRleHQ9dSx1PTB9LDFlMyksd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZShjKX0pOzwvc2NyaXB0PjwvRE9PTT48L2JvZHk+PC9odG1sPg==`); + frame.srcdoc = atob(`PCEtLSBPUklHSU5BTDogaHR0cHM6Ly9kaWVrbWFubi5naXRodWIuaW8vd2FzbS1maXp6YnV6ei9kb29tLyAtLT48IWRvY3R5cGVodG1sPjxodG1sPjxib2R5PjxET09NPjxzdHlsZT4jb3V0cHV0e2JvcmRlcjozcHggZ3Jvb3ZlICM3ZmZmZDQ7YmFja2dyb3VuZC1jb2xvcjpiaXNxdWU7d2lkdGg6NTUwcHg7aGVpZ2h0OjQwMHB4O2ZvbnQtZmFtaWx5Om1vbm9zcGFjZSxzZXJpZjtmb250LXNpemU6MTBweDtvdmVyZmxvdy15OnNjcm9sbH0jb3V0cHV0IHNwYW4ubG9ne2NvbG9yOiM0ODNkOGJ9I291dHB1dCBzcGFuLnN0ZG91dHtjb2xvcjojMDAwfSNvdXRwdXQgc3Bhbi5zdGRlcnJ7Zm9udC13ZWlnaHQ6NzAwO2NvbG9yOmJyb3dufS5jb250YWluZXJ7ZGlzcGxheTpmbGV4fSp7bWFyZ2luOjBweDtwYWRkaW5nOjBweH08L3N0eWxlPjxzcGFuIGhpZGRlbj48cCBpZD1mb2N1c2hpbnQ+PC9wPjxwPjxidXR0b24gaWQ9ZW50ZXJCdXR0b24+PC9idXR0b24+PGJ1dHRvbiBpZD1sZWZ0QnV0dG9uPjwvYnV0dG9uPjxidXR0b24gaWQ9dXBCdXR0b24+PC9idXR0b24+PGJ1dHRvbiBpZD1kb3duQnV0dG9uPjwvYnV0dG9uPjxidXR0b24gaWQ9cmlnaHRCdXR0b24+PC9idXR0b24+IDxidXR0b24gaWQ9Y3RybEJ1dHRvbj48L2J1dHRvbj48YnV0dG9uIGlkPXNwYWNlQnV0dG9uPjwvYnV0dG9uPiA8YnV0dG9uIGlkPWFsdEJ1dHRvbj48L2J1dHRvbj48L3A+PC9zcGFuPjxkaXYgY2xhc3M9Y29udGFpbmVyPjxjYW52YXMgaGVpZ2h0PTQwMCBpZD1zY3JlZW4gdGFiaW5kZXg9MCB3aWR0aD02NDA+VGhpcyBpcyB3aGVyZSB0aGUgRG9vTSBzY3JlZW4gc2hvdWxkIHJlbmRlci48L2NhbnZhcz48ZGl2IGhpZGRlbiBpZD1vdXRwdXQ+PC9kaXY+PC9kaXY+PHNwYW4gaGlkZGVuPjxzcGFuIGlkPWdldG1zcHNfc3RhdHM+PC9zcGFuPjxzcGFuIGlkPWdldG1zX3N0YXRzPjwvc3Bhbj4gPHNwYW4gaWQ9ZnBzX3N0YXRzPjwvc3Bhbj48c3BhbiBpZD1kcmF3ZnJhbWVzX3N0YXRzPjwvc3Bhbj4gPHNwYW4gaWQ9YW5pbWF0aW9uZnBzX3N0YXRzPjwvc3Bhbj48L3NwYW4+PHNjcmlwdCBkZWZlcj4idXNlIHN0cmljdCI7dmFyIG1lbW9yeT1uZXcgV2ViQXNzZW1ibHkuTWVtb3J5KHtpbml0aWFsOjEwOH0pO2NvbnN0IG91dHB1dD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgib3V0cHV0Iik7ZnVuY3Rpb24gcmVhZFdhc21TdHJpbmcodCxlKXtsZXQgbj1uZXcgVWludDhBcnJheShtZW1vcnkuYnVmZmVyLHQsZSk7cmV0dXJuIG5ldyBUZXh0RGVjb2RlcigidXRmOCIpLmRlY29kZShuKX1mdW5jdGlvbiBjb25zb2xlTG9nU3RyaW5nKHQsZSl7bGV0IG49cmVhZFdhc21TdHJpbmcodCxlKTtjb25zb2xlLmxvZygnIicrbisnIicpfWZ1bmN0aW9uIGFwcGVuZE91dHB1dCh0KXtyZXR1cm4gZnVuY3Rpb24oZSxuKXtsZXQgcz1yZWFkV2FzbVN0cmluZyhlLG4pLnNwbGl0KCJcbiIpO2Zvcih2YXIgYT0wO2E8cy5sZW5ndGg7KythKWlmKDAhPXNbYV0ubGVuZ3RoKXt2YXIgcj1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzcGFuIik7ci5jbGFzc0xpc3QuYWRkKHQpLHIuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoc1thXSkpLG91dHB1dC5hcHBlbmRDaGlsZChyKSxvdXRwdXQuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiYnIiKSksci5zY3JvbGxJbnRvVmlldyh7YmVoYXZpb3I6InNtb290aCIsYmxvY2s6ImVuZCIsaW5saW5lOiJuZWFyZXN0In0pfX19Y29uc3QgZ2V0bXNwc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZ2V0bXNwc19zdGF0cyIpLGdldG1zX3N0YXRzPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJnZXRtc19zdGF0cyIpO3ZhciBnZXRtc19jYWxsc190b3RhbD0wLGdldG1zX2NhbGxzPTA7ZnVuY3Rpb24gZ2V0TWlsbGlzZWNvbmRzKCl7cmV0dXJuKytnZXRtc19jYWxscyxwZXJmb3JtYW5jZS5ub3coKX13aW5kb3cuc2V0SW50ZXJ2YWwoZnVuY3Rpb24oKXtnZXRtc19jYWxsc190b3RhbCs9Z2V0bXNfY2FsbHMsZ2V0bXNwc19zdGF0cy5pbm5lclRleHQ9Z2V0bXNfY2FsbHMvMWUzKyJrIixnZXRtc19zdGF0cy5pbm5lclRleHQ9Z2V0bXNfY2FsbHNfdG90YWwsZ2V0bXNfY2FsbHM9MH0sMWUzKTtjb25zdCBjYW52YXM9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjcmVlbiIpLGRvb21fc2NyZWVuX3dpZHRoPTY0MCxkb29tX3NjcmVlbl9oZWlnaHQ9NDAwLGZwc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZnBzX3N0YXRzIiksZHJhd2ZyYW1lc19zdGF0cz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZHJhd2ZyYW1lc19zdGF0cyIpO3ZhciBudW1iZXJfb2ZfZHJhd3NfdG90YWw9MCxudW1iZXJfb2ZfZHJhd3M9MDtmdW5jdGlvbiBkcmF3Q2FudmFzKHQpe3ZhciBlPW5ldyBVaW50OENsYW1wZWRBcnJheShtZW1vcnkuYnVmZmVyLHQsMTAyNGUzKSxuPW5ldyBJbWFnZURhdGEoZSw2NDAsNDAwKTtjYW52YXMuZ2V0Q29udGV4dCgiMmQiKS5wdXRJbWFnZURhdGEobiwwLDApLCsrbnVtYmVyX29mX2RyYXdzfXdpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbigpe251bWJlcl9vZl9kcmF3c190b3RhbCs9bnVtYmVyX29mX2RyYXdzLGRyYXdmcmFtZXNfc3RhdHMuaW5uZXJUZXh0PW51bWJlcl9vZl9kcmF3c190b3RhbCxmcHNfc3RhdHMuaW5uZXJUZXh0PW51bWJlcl9vZl9kcmF3cyxudW1iZXJfb2ZfZHJhd3M9MH0sMWUzKTt2YXIgaW1wb3J0T2JqZWN0PXtqczp7anNfY29uc29sZV9sb2c6YXBwZW5kT3V0cHV0KCJsb2ciKSxqc19zdGRvdXQ6YXBwZW5kT3V0cHV0KCJzdGRvdXQiKSxqc19zdGRlcnI6YXBwZW5kT3V0cHV0KCJzdGRlcnIiKSxqc19taWxsaXNlY29uZHNfc2luY2Vfc3RhcnQ6Z2V0TWlsbGlzZWNvbmRzLGpzX2RyYXdfc2NyZWVuOmRyYXdDYW52YXN9LGVudjp7bWVtb3J5Om1lbW9yeX19O1dlYkFzc2VtYmx5Lmluc3RhbnRpYXRlU3RyZWFtaW5nKGZldGNoKCJodHRwczovL21peW8ubG9sL2Rvb20ud2FzbSIpLGltcG9ydE9iamVjdCkudGhlbih0PT57dC5pbnN0YW5jZS5leHBvcnRzLm1haW4oKTtsZXQgZT1mdW5jdGlvbih0KXtzd2l0Y2godCl7Y2FzZSA4OnJldHVybiAxMjc7Y2FzZSAxNzpyZXR1cm4gMTU3O2Nhc2UgMTg6cmV0dXJuIDE4NDtjYXNlIDM3OnJldHVybiAxNzI7Y2FzZSAzODpyZXR1cm4gMTczO2Nhc2UgMzk6cmV0dXJuIDE3NDtjYXNlIDQwOnJldHVybiAxNzU7ZGVmYXVsdDppZih0Pj02NSYmdDw9OTApcmV0dXJuIHQrMzI7aWYodD49MTEyJiZ0PD0xMjMpcmV0dXJuIHQrNzU7cmV0dXJuIHR9fSxuPWZ1bmN0aW9uKGUpe3QuaW5zdGFuY2UuZXhwb3J0cy5hZGRfYnJvd3Nlcl9ldmVudCgwLGUpfSxzPWZ1bmN0aW9uKGUpe3QuaW5zdGFuY2UuZXhwb3J0cy5hZGRfYnJvd3Nlcl9ldmVudCgxLGUpfTtjYW52YXMuYWRkRXZlbnRMaXN0ZW5lcigia2V5ZG93biIsZnVuY3Rpb24odCl7bihlKHQua2V5Q29kZSkpLHQucHJldmVudERlZmF1bHQoKX0sITEpLGNhbnZhcy5hZGRFdmVudExpc3RlbmVyKCJrZXl1cCIsZnVuY3Rpb24odCl7cyhlKHQua2V5Q29kZSkpLHQucHJldmVudERlZmF1bHQoKX0sITEpLFtbImVudGVyQnV0dG9uIiwxM10sWyJsZWZ0QnV0dG9uIiwxNzJdLFsicmlnaHRCdXR0b24iLDE3NF0sWyJ1cEJ1dHRvbiIsMTczXSxbImRvd25CdXR0b24iLDE3NV0sWyJjdHJsQnV0dG9uIiwxNTddLFsic3BhY2VCdXR0b24iLDMyXSxbImFsdEJ1dHRvbiIsMTg0XV0uZm9yRWFjaCgoW3QsZV0pPT57Y29uc29sZS5sb2codCsiIGZvciAiK2UpO3ZhciBhPWRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHQpO2EuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hzdGFydCIsKCk9Pm4oZSkpLGEuYWRkRXZlbnRMaXN0ZW5lcigidG91Y2hlbmQiLCgpPT5zKGUpKSxhLmFkZEV2ZW50TGlzdGVuZXIoInRvdWNoY2FuY2VsIiwoKT0+cyhlKSl9KTtsZXQgYT1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZm9jdXNoaW50Iikscj1mdW5jdGlvbih0KXthLmlubmVyVGV4dD0iS2V5Ym9hcmQgZXZlbnRzIHdpbGwgYmUgY2FwdHVyZWQgYXMgbG9uZyBhcyB0aGUgdGhlIERPT00gY2FudmFzIGhhcyBmb2N1cy4iLGEuc3R5bGUuZm9udFdlaWdodD0ibm9ybWFsIn07Y2FudmFzLmFkZEV2ZW50TGlzdGVuZXIoImZvY3VzaW4iLHIsITEpLGNhbnZhcy5hZGRFdmVudExpc3RlbmVyKCJmb2N1c291dCIsZnVuY3Rpb24odCl7YS5pbm5lclRleHQ9IkNsaWNrIG9uIHRoZSBjYW52YXMgdG8gY2FwdXRlIGlucHV0IGFuZCBzdGFydCBwbGF5aW5nLiIsYS5zdHlsZS5mb250V2VpZ2h0PSJib2xkIn0sITEpLGNhbnZhcy5mb2N1cygpLHIoKTtsZXQgbz1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYW5pbWF0aW9uZnBzX3N0YXRzIik7dmFyIHU9MDtmdW5jdGlvbiBjKGUpeysrdSx0Lmluc3RhbmNlLmV4cG9ydHMuZG9vbV9sb29wX3N0ZXAoKSx3aW5kb3cucmVxdWVzdEFuaW1hdGlvbkZyYW1lKGMpfXdpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbigpe28uaW5uZXJUZXh0PXUsdT0wfSwxZTMpLHdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWUoYyl9KTs8L3NjcmlwdD48L0RPT00+PC9ib2R5PjwvaHRtbD4=`); } } - + gotBlockly._registerFields(); + } + gotBlockly._on = new Set(); + gotBlockly.when = function(callback) { + if (Blockly) { + callback(Blockly); + return; + } + gotBlockly._on.add(() => callback(Blockly)); + }; + gotBlockly._registerFields = function() { while (toRegisterOnBlocklyGot.length > 0) { const [name, impl] = toRegisterOnBlocklyGot.shift(); Blockly.Field.register(name, impl); } - - // Attempt to reload the workspace and what not. - // https://github.com/TurboWarp/addons/blob/tw/addons/custom-block-shape/update-all-blocks.js + gotBlockly._hardRefresh(); + }; + gotBlockly._hardRefresh = function() { + vm.extensionManager.refreshBlocks(); const eventsOriginallyEnabled = Blockly.Events.isEnabled(), workspace = Blockly.getMainWorkspace(); - Blockly.Events.disable(); - if (workspace) { - if (vm.editingTarget) vm.emitWorkspaceUpdate(); - const flyout = workspace.getFlyout(); - if (flyout) { - const flyoutWorkspace = flyout.getWorkspace(); - Blockly.Xml.clearWorkspaceAndLoadFromXml( - Blockly.Xml.workspaceToDom(flyoutWorkspace), - flyoutWorkspace - ); - workspace.getToolbox().refreshSelection(); - workspace.toolboxRefreshEnabled_ = true; + try { + // https://github.com/TurboWarp/addons/blob/tw/addons/custom-block-shape/update-all-blocks.js + Blockly.Events.disable(); + if (workspace) { + if (vm.editingTarget) vm.emitWorkspaceUpdate(); + const flyout = workspace.getFlyout(); + if (flyout) { + const flyoutWorkspace = flyout.getWorkspace(); + Blockly.Xml.clearWorkspaceAndLoadFromXml( + Blockly.Xml.workspaceToDom(flyoutWorkspace), + flyoutWorkspace + ); + workspace.getToolbox().refreshSelection(); + workspace.toolboxRefreshEnabled_ = true; + } } + } catch(err) { + console.error('Error while refreshing toolbox and workspace.', err); + } finally { + if (eventsOriginallyEnabled) Blockly.Events.enable(); } - if (eventsOriginallyEnabled) Blockly.Events.enable(); } - - // Passes "Blockly" to tryUseScratchBlocks if Scratch.gui is a object. - if (typeof Scratch?.gui === 'object') Scratch.gui.getBlockly().then((Blockly) => tryUseScratchBlocks(Blockly)); - - // Actual "extension" part - class extension { + gotBlockly._badRefresh = function(ws) { + ws.resetDragSurface(); + try { + ws.getFlyout().clearOldBlocks_(); + vm.extensionManager.refreshBlocks(); + ws.refreshToolboxSelection_(); + } catch {/**/} + }; + if (typeof Scratch?.gui === 'object') Scratch.gui.getBlockly().then((Blockly) => gotBlockly(Blockly)); + const xmlEscape = (unsafe) => unsafe.replace(/[<>&'"]/g, c => { + switch (c) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + case '\'': return '''; + case '"': return '"'; + } + }); + // The MoreFields API + class extensionAPI { static get customFieldTypes() { return customFieldTypes; } + static get fieldInfo() { + return fi; + } + static PRIV_extensionField(args, util, blockJSON) { + return ( + this._registered[blockJSON.fieldInfo.name.toUpperCase()] + ).getValue(args, util, blockJSON); + } + static hideDropdown() { + if (!Blockly) return; + if (!Blockly.DropDownDiv.isVisible()) return; + Blockly.DropDownDiv.clearContent(); + Blockly.DropDownDiv.hide(); + } + static fixDropdown(self) { + if (!Blockly) return; + if (Blockly.DropDownDiv.isVisible()) return; + if (self.sourceBlock_.parentBlock) { + Blockly.DropDownDiv.setColour(self.sourceBlock_.parentBlock_.getColour(), self.sourceBlock_.parentBlock_.getColourTertiary()); + Blockly.DropDownDiv.setCategory(self.sourceBlock_.parentBlock_.getCategory()); + } + _moveDropdown.call(self, true); + } + static setPathColour(self, colour) { + const fg_ = self.fieldGroup_; + if (!fg_) return; + const path = fg_?.previousElementSibling; + if (path?.nodeName !== 'path') return; + path.setAttribute('stroke', colour); + path.setAttribute('fill', colour); + } + static fixTextNode(self, textColour) { + if (!self.sourceBlock_) return; + if (!(self.textNode__ = ( + self.sourceBlock_.svgPath_.parentNode.querySelector('g.blocklyEditableText text') + ))) return + _setCssNattr(self.textNode__, 'fill', textColour ?? '#FFFFFF'); + } + static _registered = Object.create(null); + static _register = new Set(); + static _patch = new Set(); + constructor() { + vm.on('CREATE_UNSANDBOXED_EXTENSION_API', (Scratch) => { + const register = Scratch.extensions.register; + Scratch.extensions.register = (clss) => { + if (this.constructor._patch.has(clss.getInfo().id)) { + clss['extensionField'] = this.constructor.PRIV_extensionField.bind(this.constructor); + } + return register(clss); + }; + }); + } + static register(newExtId, opts, getValue, getField) { + const onu = opts.name.toUpperCase(); + if (this._register.has(onu)) { + throw new Error(`"${opts.name}" already exists.`); + } + this._register.add(onu); + this._patch.add(newExtId); + ArgumentType[onu] = onu; + opts.blockType ??= BlockType.REPORTER; + opts.defaultValue ??= ''; + opts.text ??= '[FIELD]'; + opts.xml ??= ''; + opts.output ??= null; + opts.outputShape ??= 3; + opts.color1 ??= '#9566d3'; + opts.color2 ??= opts.color1; + opts.color3 ??= opts.color1; + opts.color4 ??= opts.color1; + implementations[`ceb${onu}`] = null; + const fi = customFieldTypes[onu] = opts.fi = { + name: onu, + output: opts.output, + color1: opts.color1, + color2: opts.color2, + color3: opts.color3, + color4: opts.color4, + outputShape: opts.outputShape, + implementation: { + fromJson: (...args) => new implementations[`ceb${onu}`](...args), + }, + }; + this._registered[onu] = { + getValue, + getField, + opts, + fi, + }; + gotBlockly.when((Blockly) => { + Blockly.defineBlocksWithJsonArray([{ + type: `${extId}_${onu}`, + message0: '%1', + inputsInline: true, + output: opts.output, + colour: opts.color1, + colourSecondary: opts.color2, + colourTertiary: opts.color3, + outputShape: opts.outputShape, + args0: [{ + name: `field_${extId}_${onu}`, + type: `field_${extId}_${onu}`, + }], + }]); + const field = getField(Blockly); + implementations[`ceb${onu}`] = field; + this._register.delete(onu); + if (this._register.size === 0) { + runtime.emit('BLOCKINFO_UPDATE', runtime[`ext_${extId}`].getInfo()); + gotBlockly._registerFields(); + if (globalThis.ReduxStore) { setTimeout(() => { + const ws = Blockly.getMainWorkspace(); + vm.clearFlyoutBlocks(); + runtime.flyoutBlocks.resetCache(); + ws.updateToolbox(ReduxStore.getState().scratchGui.toolbox.toolboxXML); + gotBlockly._badRefresh(ws); + setTimeout(() => gotBlockly._badRefresh(ws), 100); + }, 250); } + } + }); + vm.emit('EXTENSION_FIELD_ADDED', Object.assign(fi, { + name: `field_${extId}_${onu}`, + })); + return [{ + fieldInfo: opts, + blockType: opts.blockType, + outputShape: opts.outputShape, + blockShape: opts.outputShape, + func: 'extensionField', + opcode: `ceb${onu}`, + text: opts.text, + arguments: { + FIELD: { + type: onu, + defaultValue: opts.defaultValue, + exemptFromNormalization: true, + }, + }, + allowDropAnywhere: (opts.output === null), + hideFromPalette: true, + }, { + blockType: BlockType.XML, + xml: (`${ + opts.xml + }${ + xmlEscape(opts.defaultValue) + }`), + }]; + } + } + class extension extends extensionAPI { + static exports = { + hasOwn, + _LDC, + _setCssNattr, + _delCssNattr, + _fixColours, + _moveDropdown, + extensionAPI, + xmlEscape, + get padding() { return padding; }, + }; getInfo() { const getInfo = ({ id: extId, @@ -921,15 +1094,18 @@ arguments: { FILE: { type: ArgumentType.FILE, - defaultValue: 'dataURL\n*/*\n', + defaultValue: '', + exemptFromNormalization: true, }, BOOL: { type: ArgumentType.SNAPBOOLEAN, defaultValue: 0, + exemptFromNormalization: true, }, NUM: { type: ArgumentType.INLINESLIDER, defaultValue: '10,0,20', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -943,6 +1119,7 @@ TEXT: { type: ArgumentType.TEXTAREA, defaultValue: ':D', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -957,6 +1134,7 @@ TEXT: { type: ArgumentType.INLINETEXTAREA, defaultValue: ':D', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -975,6 +1153,7 @@ BOOL: { type: ArgumentType.SNAPBOOLEAN, defaultValue: 0, + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -988,6 +1167,7 @@ NUM: { type: ArgumentType.INLINESLIDER, defaultValue: '10,0,20', + exemptFromNormalization: true, } }, allowDropAnywhere: true, @@ -1001,6 +1181,7 @@ TEXT: { type: ArgumentType.HIDDENSTRING, defaultValue: 'oo a secret ;)', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -1014,6 +1195,7 @@ DATE: { type: ArgumentType.INLINEDATE, defaultValue: '2024-03-14', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -1026,7 +1208,8 @@ arguments: { FILE: { type: ArgumentType.FILE, - defaultValue: 'dataURL\n*/*\n', + defaultValue: '', + exemptFromNormalization: true, }, }, allowDropAnywhere: true, @@ -1041,6 +1224,7 @@ _a: { type: 'InlineDoom', defaultValue: '', + exemptFromNormalization: true, }, }, }, @@ -1071,7 +1255,7 @@ } date(args) { try { - let date = new Date(Scratch.Cast.toString(args.DATE).replaceAll('-', '/')); + const date = new Date(Scratch.Cast.toString(args.DATE).replaceAll('-', '/')); return (date.getTime()); } catch { return ''; @@ -1082,14 +1266,19 @@ try { const cr1 = args.FILE.indexOf('\n'); const cr2 = args.FILE.indexOf('\n', cr1 + 1); + if (cr1 === -1 || cr2 === -1) return ''; return args.FILE.substr(cr2 + 1); } catch { return ''; } } - DOOM(args) { + DOOM() { return ''; } } - Scratch.extensions.register(runtime[`ext_${extId}`] = new extension()); + const inst = runtime[`ext_${extId}`] = new extension(); + Scratch.extensions.register(inst); + vm._events['MOREFIELDS_REGISTERED'] = (() => {}); + vm.emit('MOREFIELDS_REGISTERED', inst, extension); })(Scratch); +