|
10 | 10 | throw new Error('\'Yet Another String Extension\' must run unsandboxed!'); |
11 | 11 | } |
12 | 12 |
|
| 13 | + if (Scratch.gui) { |
| 14 | + Scratch.gui.getBlockly().then(ScratchBlocks => { |
| 15 | + function recursiveRender(block) { |
| 16 | + if (!block) return; |
| 17 | + while (block.parentBlock_ !== null) { |
| 18 | + if (block.parentBlock_ !== null) { |
| 19 | + block.render(false); |
| 20 | + block = block.parentBlock_; |
| 21 | + } |
| 22 | + } |
| 23 | + block.render(false); |
| 24 | + } |
| 25 | + ScratchBlocks.FieldCustom.registerInput( |
| 26 | + "multiline", |
| 27 | + (() => { |
| 28 | + const container = document.createElement('textarea'); |
| 29 | + container.style.background = "#fff"; |
| 30 | + container.style.color = "#404040"; |
| 31 | + container.style.minWidth = "29px"; |
| 32 | + container.style.minHeight = "27px"; |
| 33 | + container.style.margin = "5px 0"; |
| 34 | + container.style.padding = "5px"; |
| 35 | + container.style.borderRadius = '8px'; |
| 36 | + container.style.display = 'block'; |
| 37 | + container.style.resize = "both"; |
| 38 | + container.style.overflow = "auto"; |
| 39 | + container.style.boxSizing = "border-box"; |
| 40 | + |
| 41 | + document.body.appendChild(container); |
| 42 | + |
| 43 | + return container; |
| 44 | + })(), |
| 45 | + (field, input) => { |
| 46 | + if (!input) return; |
| 47 | + |
| 48 | + let value; |
| 49 | + try { |
| 50 | + value = JSON.parse(field.getValue()); |
| 51 | + } catch { |
| 52 | + value = field.getValue(); |
| 53 | + } |
| 54 | + let str = ""; |
| 55 | + let width = 120; |
| 56 | + let height = 80; |
| 57 | + |
| 58 | + if (typeof value === "string") { |
| 59 | + str = value; |
| 60 | + } else if (value && typeof value === "object") { |
| 61 | + str = value['string'] || ""; |
| 62 | + if (Array.isArray(value['size']) && value['size'].length === 2) { |
| 63 | + width = value['size'][0] || 120; |
| 64 | + height = value['size'][1] || 80; |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + input.value = str; |
| 69 | + input.style.width = width + "px"; |
| 70 | + input.style.height = height + "px"; |
| 71 | + field.setValue(JSON.stringify({ 'string': str, 'size': [width, height] })); |
| 72 | + let isPaused = false; |
| 73 | + input.addEventListener("keydown", function(event) { |
| 74 | + if (event.key === "Tab") { |
| 75 | + event.preventDefault(); |
| 76 | + |
| 77 | + let textarea = this; |
| 78 | + let cursorStart = textarea.selectionStart; |
| 79 | + let cursorEnd = textarea.selectionEnd; |
| 80 | + |
| 81 | + textarea.value = |
| 82 | + textarea.value.substring(0, cursorStart) + |
| 83 | + "\t" + |
| 84 | + textarea.value.substring(cursorEnd); |
| 85 | + |
| 86 | + textarea.selectionStart = textarea.selectionEnd = cursorStart + 1; |
| 87 | + } |
| 88 | + }); |
| 89 | + const observer = new ResizeObserver(entries => { |
| 90 | + if (isPaused) return; |
| 91 | + for (const entry of entries) { |
| 92 | + isPaused = true; |
| 93 | + let { width, height } = entry.contentRect; |
| 94 | + //width = Math.max(100, width); |
| 95 | + //height = Math.max(32, height); |
| 96 | + |
| 97 | + const foreignObject = input.parentNode; |
| 98 | + foreignObject.setAttribute("width", width); |
| 99 | + foreignObject.setAttribute("height", height); |
| 100 | + |
| 101 | + field.size_.width = width + 15; |
| 102 | + field.size_.height = height + 25; |
| 103 | + input.style.border = `solid 1px ${field.sourceBlock_?.colourTertiary_}`; |
| 104 | + recursiveRender(field.sourceBlock_); |
| 105 | + requestAnimationFrame(() => { isPaused = false }); |
| 106 | + field.setValue(JSON.stringify({ 'string': input.value, 'size': [width, height] })); |
| 107 | + } |
| 108 | + }); |
| 109 | + |
| 110 | + observer.observe(input); |
| 111 | + |
| 112 | + input.addEventListener("change", () => { |
| 113 | + field.setValue(JSON.stringify({ 'string': input.value, 'size': [parseFloat(input.style.width), parseFloat(input.style.height)] })); |
| 114 | + }) |
| 115 | + }, |
| 116 | + (block) => { }, |
| 117 | + (block) => { } |
| 118 | + ); |
| 119 | + }); |
| 120 | + } |
| 121 | + |
13 | 122 | class YetAnotherStringExtension { |
14 | 123 | constructor() { |
15 | 124 | vm.runtime.registerCompiledExtensionBlocks('dogeiscutyetanotherstringextension', this.getCompileInfo()); |
|
144 | 253 | }, |
145 | 254 | extensions: ["colours_operators"], |
146 | 255 | }, |
| 256 | + '---', |
| 257 | + /*UNFINISHED { |
| 258 | + opcode: 'multiline', |
| 259 | + text: '[STRING]', |
| 260 | + blockType: Scratch.BlockType.REPORTER, |
| 261 | + blockShape: Scratch.BlockShape.SQUARE, |
| 262 | + disableMonitor: true, |
| 263 | + arguments: { |
| 264 | + STRING: { |
| 265 | + type: Scratch.ArgumentType.CUSTOM, |
| 266 | + id: "multiline", |
| 267 | + defaultValue: "Multiple\nLines!\nYay!" |
| 268 | + } |
| 269 | + } |
| 270 | + }*/ |
147 | 271 | ] |
148 | 272 | } |
149 | 273 | } |
150 | 274 |
|
151 | 275 | getCompileInfo() { |
152 | 276 | return { |
153 | 277 | ir: { |
154 | | - builder: (generator, block) => ({ |
155 | | - kind: 'input', |
156 | | - substack: generator.descendSubstack(block, 'SUBSTACK') |
157 | | - }), |
| 278 | + builder: (generator, block) => { |
| 279 | + generator.script.yields = true |
| 280 | + return { |
| 281 | + kind: 'input', |
| 282 | + substack: generator.descendSubstack(block, 'SUBSTACK') |
| 283 | + } |
| 284 | + }, |
158 | 285 | }, |
159 | 286 | js: { |
160 | 287 | builder: (node, compiler, imports) => { |
161 | 288 | const originalSource = compiler.source; |
162 | 289 |
|
163 | | - compiler.source = '(yield* (function*() {'; |
164 | | - compiler.source += ' const __inner = (yield* (function*() {'; |
165 | | - compiler.source += ` thread._dogeiscutyetanotherstringextensionBuilderIndex ??= [];`; |
166 | | - compiler.source += ` thread._dogeiscutyetanotherstringextensionBuilderIndex.push('');`; |
| 290 | + compiler.source = 'Scratch.Cast.toString(yield* (function*() {'; |
| 291 | + compiler.source += ` thread._dogeiscutyetanotherstringextensionBuilderIndex ??= [];`; |
| 292 | + compiler.source += ` thread._dogeiscutyetanotherstringextensionBuilderIndex.push('');`; |
167 | 293 | compiler.descendStack(node.substack, new imports.Frame(false, undefined, true)); |
168 | | - compiler.source += ` return new runtime.ext_dogeiscutyetanotherstringextension.BuilderReturnValue(thread._dogeiscutyetanotherstringextensionBuilderIndex.pop());`; |
169 | | - compiler.source += ' })());'; |
170 | | - compiler.source += ' const __result = __inner;'; |
171 | | - compiler.source += ' if (!(__result instanceof runtime.ext_dogeiscutyetanotherstringextension.BuilderReturnValue)) {'; |
172 | | - compiler.source += ' throw "Return statements are not supported in builders.";'; |
173 | | - compiler.source += ' }'; |
174 | | - compiler.source += ' return __result.value;'; |
175 | | - compiler.source += '})())'; |
| 294 | + compiler.source += ` return thread._dogeiscutyetanotherstringextensionBuilderIndex.pop();`; |
| 295 | + compiler.source += '})());'; |
176 | 296 |
|
177 | 297 | const stackSource = compiler.source; |
178 | 298 | compiler.source = originalSource; |
|
182 | 302 | }; |
183 | 303 | } |
184 | 304 |
|
185 | | - BuilderReturnValue = class { |
186 | | - constructor(value) { |
187 | | - this.value = value |
188 | | - } |
189 | | - } |
190 | | - |
191 | 305 | currentString({}, util) { |
192 | 306 | if (util.thread._dogeiscutyetanotherstringextensionBuilderIndex && util.thread._dogeiscutyetanotherstringextensionBuilderIndex.length > 0) { |
193 | 307 | return util.thread._dogeiscutyetanotherstringextensionBuilderIndex[util.thread._dogeiscutyetanotherstringextensionBuilderIndex.length-1] |
|
297 | 411 | } |
298 | 412 | return STRING.repeat(Math.round(INT)); |
299 | 413 | } |
| 414 | + |
| 415 | + multiline({ STRING }) { |
| 416 | + let value; |
| 417 | + try { |
| 418 | + value = JSON.parse(STRING)['string']; |
| 419 | + } catch { |
| 420 | + throw 'Failed to get multiline string, this should never happen!' |
| 421 | + } |
| 422 | + return value; |
| 423 | + } |
300 | 424 |
|
301 | 425 | } |
302 | 426 |
|
|
0 commit comments