From 2ac1380b0a808201da19cf3a7108fedb8e63cefd Mon Sep 17 00:00:00 2001 From: samllea1 Date: Mon, 9 Jun 2025 23:38:39 +1000 Subject: [PATCH 1/4] Html Elements --- extensions/extensions.json | 1 + extensions/samllea1/Html Elements.js | 493 +++++++++++++++++++++++++++ images/samllea1/Html Elements.png | Bin 0 -> 18395 bytes 3 files changed, 494 insertions(+) create mode 100644 extensions/samllea1/Html Elements.js create mode 100644 images/samllea1/Html Elements.png diff --git a/extensions/extensions.json b/extensions/extensions.json index d8ed71e33d..43b0923fe5 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -18,6 +18,7 @@ "sound", "Lily/Video", "iframe", + "samllea1/Html Elements", "Clay/htmlEncode", "Xeltalliv/clippingblending", "clipboard", diff --git a/extensions/samllea1/Html Elements.js b/extensions/samllea1/Html Elements.js new file mode 100644 index 0000000000..886a407942 --- /dev/null +++ b/extensions/samllea1/Html Elements.js @@ -0,0 +1,493 @@ +// Name: Html Elements +// ID: htmlelements +// Description: Lets you add HTML elements to the scratch canvas. +// By: samllea1 +// License: MPL-2.0 + +(function (Scratch) { + 'use strict'; + + const Cast = Scratch.Cast; + const runtime = Scratch.vm.runtime; + + class HtmlElements { + constructor() { + this.elements = {}; + this.zIndexCounter = 0; + } + + getInfo() { + return { + id: 'htmlElements', + name: 'HTML Elements', + color1: '#9596bf', + color2: '#7b7c9e', + blocks: this.getBlockDefinitions(), + }; + } + + getBlockDefinitions() { + return [ + { + opcode: 'createInput', + blockType: Scratch.BlockType.COMMAND, + text: 'create input with id [ID] at x: [X] y: [Y]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + }, + }, + { + opcode: 'createDropdown', + blockType: Scratch.BlockType.COMMAND, + text: 'create dropdown with id [ID] at x: [X] y: [Y] with options [OPTIONS]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'dropdown1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + OPTIONS: { type: Scratch.ArgumentType.STRING, defaultValue: 'option1,option2,option3' }, + }, + }, + { + opcode: 'createButton', + blockType: Scratch.BlockType.COMMAND, + text: 'create button with id [ID] at x: [X] y: [Y] with text [TEXT]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'Click me' }, + }, + }, + { + opcode: 'createToggleButton', + blockType: Scratch.BlockType.COMMAND, + text: 'create toggle button with id [ID] at x: [X] y: [Y] with text [TEXT]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'toggle1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'Toggle me' }, + }, + }, + { + opcode: 'createPanel', + blockType: Scratch.BlockType.COMMAND, + text: 'create panel with id [ID] at x: [X] y: [Y]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'panel1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + }, + }, + { blockType: Scratch.BlockType.LABEL, text: 'Element Actions' }, + { + opcode: 'whenButtonClicked', + blockType: Scratch.BlockType.HAT, + text: 'when button [ID] clicked', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + }, + }, + { + opcode: 'getInputValue', + blockType: Scratch.BlockType.REPORTER, + text: 'get value of element [ID]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + }, + }, + { + opcode: 'setInputValue', + blockType: Scratch.BlockType.COMMAND, + text: 'set value of element [ID] to [VALUE]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + VALUE: { type: Scratch.ArgumentType.STRING, defaultValue: 'Hello, World!' }, + }, + }, + { + opcode: 'setButtonText', + blockType: Scratch.BlockType.COMMAND, + text: 'set button [ID] text to [TEXT]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'New Text' }, + }, + }, + { + opcode: 'setToggleButtonText', + blockType: Scratch.BlockType.COMMAND, + text: 'set toggle button [ID] text to [TEXT]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'toggle1' }, + TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'New Text' }, + }, + }, + { blockType: Scratch.BlockType.LABEL, text: 'Styling' }, + { + opcode: 'setElementBackgroundColor', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] background color to [COLOR]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + }, + }, + { + opcode: 'setElementTextColor', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] text color to [COLOR]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + }, + }, + { + opcode: 'setElementBorder', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] border to width: [WIDTH] color: [COLOR] rounding: [ROUNDING]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + WIDTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, + COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + ROUNDING: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + }, + }, + { + opcode: 'setElementFont', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] font to base64 or data url [FONT_URL]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + FONT_URL: { type: Scratch.ArgumentType.STRING, defaultValue: '' }, + }, + }, + { + opcode: 'setElementTransparency', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] transparency to [ALPHA]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ALPHA: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, min: 0, max: 1 }, + }, + }, + { + opcode: 'setElementTextTransparency', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] text transparency to [ALPHA]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ALPHA: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, min: 0, max: 1 }, + }, + }, + { blockType: Scratch.BlockType.LABEL, text: 'Layering' }, + { + opcode: 'bringToFront', + blockType: Scratch.BlockType.COMMAND, + text: 'bring element [ID] to front', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + }, + }, + { + opcode: 'sendToBack', + blockType: Scratch.BlockType.COMMAND, + text: 'send element [ID] to back', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + }, + }, + { + opcode: 'setLayer', + blockType: Scratch.BlockType.COMMAND, + text: 'set element [ID] layer to [LAYER]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + LAYER: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + }, + }, + { blockType: Scratch.BlockType.LABEL, text: 'Element Management' }, + { + opcode: 'moveElement', + blockType: Scratch.BlockType.COMMAND, + text: 'move element [ID] to x: [X] y: [Y]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, + }, + }, + { + opcode: 'resizeElement', + blockType: Scratch.BlockType.COMMAND, + text: 'resize element [ID] to width: [WIDTH] height: [HEIGHT]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + WIDTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, + HEIGHT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 20 }, + }, + }, + { + opcode: 'removeElement', + blockType: Scratch.BlockType.COMMAND, + text: 'remove element [ID]', + arguments: { + ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + }, + }, + { + opcode: 'removeAllElements', + blockType: Scratch.BlockType.COMMAND, + text: 'remove all elements', + }, + { + opcode: 'listElements', + blockType: Scratch.BlockType.REPORTER, + text: 'list all elements', + }, + ]; + } + + createElement(tag, { ID, X, Y }, additionalSetup = () => {}) { + ID = Cast.toString(ID); + X = Cast.toNumber(X); + Y = Cast.toNumber(Y); + + if (this.elements[ID]) { + this.removeElement({ ID }); + } + + const element = document.createElement(tag); + element.id = ID; + element.style.position = 'absolute'; + element.style.left = X + runtime.stageWidth / 2 + 'px'; + element.style.top = -Y + runtime.stageHeight / 2 + 'px'; + element.style.pointerEvents = 'auto'; + element.style.transform = 'translate(-50%, -50%)'; + + additionalSetup(element); + + Scratch.renderer.addOverlay(element); + this.elements[ID] = { element, overlay: element, zIndex: this.zIndexCounter++ }; + this.updateZIndex(ID); + } + + createInput(args) { + this.createElement('input', args); + } + + createDropdown({ ID, X, Y, OPTIONS }) { + this.createElement('select', { ID, X, Y }, (select) => { + Cast.toString(OPTIONS).split(',').forEach(optionText => { + const option = document.createElement('option'); + option.textContent = optionText; + select.appendChild(option); + }); + }); + } + + createButton({ ID, X, Y, TEXT }) { + this.createElement('button', { ID, X, Y }, (button) => { + button.textContent = Cast.toString(TEXT); + button.addEventListener('click', () => { + runtime.startHats('htmlElements_whenButtonClicked', { ID }); + }); + }); + } + + createToggleButton({ ID, X, Y, TEXT }) { + this.createElement('button', { ID, X, Y }, (button) => { + button.textContent = Cast.toString(TEXT); + button.dataset.toggled = 'false'; + button.addEventListener('click', () => { + const isToggled = button.dataset.toggled === 'true'; + button.dataset.toggled = !isToggled; + runtime.startHats('htmlElements_whenButtonClicked', { ID }); + }); + }); + } + + createPanel(args) { + this.createElement('div', args, (panel) => { + panel.style.width = '50px'; + panel.style.height = '50px'; + panel.style.backgroundColor = '#ccc'; + }); + } + + whenButtonClicked(args) { + return args.ID; + } + + getInputValue({ ID }) { + ID = Cast.toString(ID); + const element = this.elements[ID]?.element; + if (element?.dataset.toggled !== undefined) { + return element.dataset.toggled === 'true'; + } else if (element && 'value' in element) { + return element.value || ''; + } + return ''; + } + + setInputValue({ ID, VALUE }) { + ID = Cast.toString(ID); + const element = this.elements[ID]?.element; + if (element?.dataset.toggled !== undefined) { + element.dataset.toggled = Cast.toBoolean(VALUE); + } else if (element && 'value' in element) { + element.value = Cast.toString(VALUE); + } + } + + setButtonText({ ID, TEXT }) { + ID = Cast.toString(ID); + const element = this.elements[ID]?.element; + if (element && element.tagName === 'BUTTON') { + element.textContent = Cast.toString(TEXT); + } + } + + setToggleButtonText({ ID, TEXT }) { + ID = Cast.toString(ID); + const element = this.elements[ID]?.element; + if (element && element.tagName === 'BUTTON') { + element.textContent = Cast.toString(TEXT); + } + } + + removeElement({ ID }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + Scratch.renderer.removeOverlay(this.elements[ID].overlay); + delete this.elements[ID]; + } + } + + setElementStyle({ ID, property, value }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + this.elements[ID].element.style[property] = value; + } + } + + setElementBackgroundColor({ ID, COLOR }) { + this.setElementStyle({ ID, property: 'backgroundColor', value: COLOR }); + } + + setElementTextColor({ ID, COLOR }) { + this.setElementStyle({ ID, property: 'color', value: COLOR }); + } + + setElementBorder({ ID, WIDTH, COLOR, ROUNDING }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + this.elements[ID].element.style.border = `${WIDTH}px solid ${COLOR}`; + this.elements[ID].element.style.borderRadius = `${ROUNDING}px`; + } + } + + setElementFont({ ID, FONT_URL }) { + ID = Cast.toString(ID); + if (this.elements[ID] && FONT_URL) { + const fontFace = new FontFace('CustomFont', `url(${FONT_URL})`); + document.fonts.add(fontFace); + fontFace.load().then(() => { + this.elements[ID].element.style.fontFamily = 'CustomFont'; + }); + } + } + + setElementTransparency({ ID, ALPHA }) { + this.setElementStyle({ ID, property: 'opacity', value: ALPHA }); + } + + setElementTextTransparency({ ID, ALPHA }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + const color = this.elements[ID].element.style.color || 'rgba(0, 0, 0, 1)'; + const rgbaColor = this.hexToRgba(color, ALPHA); + this.elements[ID].element.style.color = rgbaColor; + } + } + + hexToRgba(hex, alpha) { + let r = 0, g = 0, b = 0; + if (hex.length === 4) { + r = parseInt(hex[1] + hex[1], 16); + g = parseInt(hex[2] + hex[2], 16); + b = parseInt(hex[3] + hex[3], 16); + } else if (hex.length === 7) { + r = parseInt(hex[1] + hex[2], 16); + g = parseInt(hex[3] + hex[4], 16); + b = parseInt(hex[5] + hex[6], 16); + } + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } + + bringToFront({ ID }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + this.zIndexCounter++; + this.elements[ID].zIndex = this.zIndexCounter; + this.updateZIndex(ID); + } + } + + sendToBack({ ID }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + this.elements[ID].zIndex = -1; + this.updateZIndex(ID); + } + } + + setLayer({ ID, LAYER }) { + ID = Cast.toString(ID); + LAYER = Cast.toNumber(LAYER); + if (this.elements[ID]) { + this.elements[ID].zIndex = LAYER; + this.updateZIndex(ID); + } + } + + updateZIndex(ID) { + if (this.elements[ID]) { + this.elements[ID].overlay.style.zIndex = this.elements[ID].zIndex; + } + } + + moveElement({ ID, X, Y }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + X = Cast.toNumber(X) + runtime.stageWidth / 2; + Y = -Cast.toNumber(Y) + runtime.stageHeight / 2; + this.elements[ID].overlay.style.left = X + 'px'; + this.elements[ID].overlay.style.top = Y + 'px'; + } + } + + resizeElement({ ID, WIDTH, HEIGHT }) { + ID = Cast.toString(ID); + if (this.elements[ID]) { + WIDTH = Cast.toNumber(WIDTH); + HEIGHT = Cast.toNumber(HEIGHT); + this.elements[ID].overlay.style.width = WIDTH + 'px'; + this.elements[ID].overlay.style.height = HEIGHT + 'px'; + } + } + + removeAllElements() { + Object.keys(this.elements).forEach(id => this.removeElement({ ID: id })); + this.zIndexCounter = 0; + } + + listElements() { + return JSON.stringify(Object.keys(this.elements)); + } + } + + Scratch.extensions.register(new HtmlElements()); +})(Scratch); diff --git a/images/samllea1/Html Elements.png b/images/samllea1/Html Elements.png new file mode 100644 index 0000000000000000000000000000000000000000..622aea2cbe56b10413d163e04a594c2e9a226815 GIT binary patch literal 18395 zcmeEuWl&t*wk;7P5Tt`UH0~j|2WZ^gAq01KOGx9v9fG^NTOhawcMGnKyS$zAopbN~ z^QvCe`}gWq(bepx_ugx*z2;nFj5!t|it-Yu$OOo6aB!$nk`QG$xR;u6a4#50-x2XE{pwVdGKP%!_#UcjaNz=MO6_#y=nR(1bzl=emwd-nD@ zwq7cwR;rn5WP+VFOe(d^;!I}ZVwJi|Y9U`PC07d54I(C{q&s)52T{d=hhSl-V!=a{ zgfZafuYFn$@9FQ+p08JOVjY;t?%Uq4j-3k%E+wJIl%}JfObHJ^+L;m zceqSRG7;b{8v*B`E=Dm)k{cq!6{*-0M#`1lxF%nwS85}s}( z;HW@=r_48=Oho3#RnnR5mn$tV62f#&&fnRTZVV~2AvO~QTYUEZ+`G1x38qJh0|hG< z{k^dVNZHHM#%*^ITYKXT2kLqGar$1FvfuMZZSkkexv8&Tzw+U%G^_>{8@5l{cpkhL z3k(b-B-9aM7K@LQ-)=IU6{XpeDbu$5k)AvzCo4aqRj(bHe~%dGfXPs@KgF3m4xvdj zs$I>`+W({v_fJRIK8s5x_mR-p`C2cSik6i%=Wmq@1l>;R3CLewUC9~wm>;m7T3AiV z_03l^6d>8v*27kIikdr<4mm=4$VJ|Zk%{EJ1gpA=w@S>-&rdHbjG{{<8ci)M9G&MT zBnq{am3^Dy2*1qop89n#e52#Kg++djnfw;~8u`UPqiXi!n~CP6>kk!ArRrwRZ@tA{F{rzz$Kzm$bmU1mpZMMXztAtRrLvbw&&d;7&CH0}2tm2svV zob|O!=0n5GxvjDFCKg`rDM~U68@&(t0?xp>XCAjywVxF1NNq~;>&dw)lIYbb=?btC zIvC?i3rj}^7|QL;^DBPOO~s@dbV<9qx}tDGta79$ial+gEk!2`8By6~ly!v}3=dif z;&Yoj-Sbj}KiYGTWM4XMwKRJUGFY1DunSaV=9afhRf-1Z^o@-tYS)aj^A!ZAN_v@Y zX}?M#aXv=oif&&p>TQQkk(@9ZS$H(4 z>BDBvE5t{0)Ks@a%C=M7>l0+n{Ne`j<+zyClJNomPHw)7!@g1BEiuV0y2Nbw0oe%V zK2GZ@-BNJbbZ8xdCpFsa#>5Skg@berZC^7%T5gd|i>6pmJo|eWWYowlyWnC8LNK

okA6&MkW_99zI}`E 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 literal 0 HcmV?d00001 From b5fe682789d929c3a853dc9a8a08c667aa280e82 Mon Sep 17 00:00:00 2001 From: samllea1 <117500365+samllea1@users.noreply.github.com> Date: Mon, 9 Jun 2025 23:42:00 +1000 Subject: [PATCH 2/4] LICENSE --- LICENSE | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..d0a1fa1482 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. From 855d716f296c293def4a445edc6b893731fe8585 Mon Sep 17 00:00:00 2001 From: samllea1 Date: Mon, 9 Jun 2025 23:51:40 +1000 Subject: [PATCH 3/4] Fix --- .../{Html Elements.js => HtmlElements.js} | 321 ++++++++++-------- .../{Html Elements.png => HtmlElements.png} | Bin 2 files changed, 186 insertions(+), 135 deletions(-) rename extensions/samllea1/{Html Elements.js => HtmlElements.js} (59%) rename images/samllea1/{Html Elements.png => HtmlElements.png} (100%) diff --git a/extensions/samllea1/Html Elements.js b/extensions/samllea1/HtmlElements.js similarity index 59% rename from extensions/samllea1/Html Elements.js rename to extensions/samllea1/HtmlElements.js index 886a407942..ae86698ab3 100644 --- a/extensions/samllea1/Html Elements.js +++ b/extensions/samllea1/HtmlElements.js @@ -5,7 +5,7 @@ // License: MPL-2.0 (function (Scratch) { - 'use strict'; + "use strict"; const Cast = Scratch.Cast; const runtime = Scratch.vm.runtime; @@ -18,10 +18,10 @@ getInfo() { return { - id: 'htmlElements', - name: 'HTML Elements', - color1: '#9596bf', - color2: '#7b7c9e', + id: "htmlElements", + name: Scratch.translate({ default: "HTML Elements", id: "htmlElements.name" }), + color1: "#9596bf", + color2: "#7b7c9e", blocks: this.getBlockDefinitions(), }; } @@ -29,223 +29,263 @@ getBlockDefinitions() { return [ { - opcode: 'createInput', + opcode: "createInput", blockType: Scratch.BlockType.COMMAND, - text: 'create input with id [ID] at x: [X] y: [Y]', + text: "create input with id [ID] at x: [X] y: [Y]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { - opcode: 'createDropdown', + opcode: "createDropdown", blockType: Scratch.BlockType.COMMAND, - text: 'create dropdown with id [ID] at x: [X] y: [Y] with options [OPTIONS]', + text: "create dropdown with id [ID] at x: [X] y: [Y] with options [OPTIONS]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'dropdown1' }, + ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: "dropdown1", + }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - OPTIONS: { type: Scratch.ArgumentType.STRING, defaultValue: 'option1,option2,option3' }, + OPTIONS: { + type: Scratch.ArgumentType.STRING, + defaultValue: "option1,option2,option3", + }, }, }, { - opcode: 'createButton', + opcode: "createButton", blockType: Scratch.BlockType.COMMAND, - text: 'create button with id [ID] at x: [X] y: [Y] with text [TEXT]', + text: "create button with id [ID] at x: [X] y: [Y] with text [TEXT]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'Click me' }, + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Click me", + }, }, }, { - opcode: 'createToggleButton', + opcode: "createToggleButton", blockType: Scratch.BlockType.COMMAND, - text: 'create toggle button with id [ID] at x: [X] y: [Y] with text [TEXT]', + text: "create toggle button with id [ID] at x: [X] y: [Y] with text [TEXT]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'toggle1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "toggle1" }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, - TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'Toggle me' }, + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Toggle me", + }, }, }, { - opcode: 'createPanel', + opcode: "createPanel", blockType: Scratch.BlockType.COMMAND, - text: 'create panel with id [ID] at x: [X] y: [Y]', + text: "create panel with id [ID] at x: [X] y: [Y]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'panel1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "panel1" }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, - { blockType: Scratch.BlockType.LABEL, text: 'Element Actions' }, + { blockType: Scratch.BlockType.LABEL, text: "Element Actions" }, { - opcode: 'whenButtonClicked', + opcode: "whenButtonClicked", blockType: Scratch.BlockType.HAT, - text: 'when button [ID] clicked', + text: "when button [ID] clicked", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, }, }, { - opcode: 'getInputValue', + opcode: "getInputValue", blockType: Scratch.BlockType.REPORTER, - text: 'get value of element [ID]', + text: "get value of element [ID]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, }, }, { - opcode: 'setInputValue', + opcode: "setInputValue", blockType: Scratch.BlockType.COMMAND, - text: 'set value of element [ID] to [VALUE]', + text: "set value of element [ID] to [VALUE]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, - VALUE: { type: Scratch.ArgumentType.STRING, defaultValue: 'Hello, World!' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, + VALUE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "Hello, World!", + }, }, }, { - opcode: 'setButtonText', + opcode: "setButtonText", blockType: Scratch.BlockType.COMMAND, - text: 'set button [ID] text to [TEXT]', + text: "set button [ID] text to [TEXT]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, - TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'New Text' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "New Text", + }, }, }, { - opcode: 'setToggleButtonText', + opcode: "setToggleButtonText", blockType: Scratch.BlockType.COMMAND, - text: 'set toggle button [ID] text to [TEXT]', + text: "set toggle button [ID] text to [TEXT]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'toggle1' }, - TEXT: { type: Scratch.ArgumentType.STRING, defaultValue: 'New Text' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "toggle1" }, + TEXT: { + type: Scratch.ArgumentType.STRING, + defaultValue: "New Text", + }, }, }, - { blockType: Scratch.BlockType.LABEL, text: 'Styling' }, + { blockType: Scratch.BlockType.LABEL, text: "Styling" }, { - opcode: 'setElementBackgroundColor', + opcode: "setElementBackgroundColor", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] background color to [COLOR]', + text: "set element [ID] background color to [COLOR]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, - COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, + COLOR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#000000", + }, }, }, { - opcode: 'setElementTextColor', + opcode: "setElementTextColor", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] text color to [COLOR]', + text: "set element [ID] text color to [COLOR]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, - COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, + COLOR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#000000", + }, }, }, { - opcode: 'setElementBorder', + opcode: "setElementBorder", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] border to width: [WIDTH] color: [COLOR] rounding: [ROUNDING]', + text: "set element [ID] border to width: [WIDTH] color: [COLOR] rounding: [ROUNDING]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, WIDTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 }, - COLOR: { type: Scratch.ArgumentType.COLOR, defaultValue: '#000000' }, + COLOR: { + type: Scratch.ArgumentType.COLOR, + defaultValue: "#000000", + }, ROUNDING: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { - opcode: 'setElementFont', + opcode: "setElementFont", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] font to base64 or data url [FONT_URL]', + text: "set element [ID] font to base64 or data url [FONT_URL]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, - FONT_URL: { type: Scratch.ArgumentType.STRING, defaultValue: '' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, + FONT_URL: { type: Scratch.ArgumentType.STRING, defaultValue: "" }, }, }, { - opcode: 'setElementTransparency', + opcode: "setElementTransparency", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] transparency to [ALPHA]', + text: "set element [ID] transparency to [ALPHA]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, - ALPHA: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, min: 0, max: 1 }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, + ALPHA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + min: 0, + max: 1, + }, }, }, { - opcode: 'setElementTextTransparency', + opcode: "setElementTextTransparency", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] text transparency to [ALPHA]', + text: "set element [ID] text transparency to [ALPHA]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, - ALPHA: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1, min: 0, max: 1 }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, + ALPHA: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1, + min: 0, + max: 1, + }, }, }, - { blockType: Scratch.BlockType.LABEL, text: 'Layering' }, + { blockType: Scratch.BlockType.LABEL, text: "Layering" }, { - opcode: 'bringToFront', + opcode: "bringToFront", blockType: Scratch.BlockType.COMMAND, - text: 'bring element [ID] to front', + text: "bring element [ID] to front", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, }, }, { - opcode: 'sendToBack', + opcode: "sendToBack", blockType: Scratch.BlockType.COMMAND, - text: 'send element [ID] to back', + text: "send element [ID] to back", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, }, }, { - opcode: 'setLayer', + opcode: "setLayer", blockType: Scratch.BlockType.COMMAND, - text: 'set element [ID] layer to [LAYER]', + text: "set element [ID] layer to [LAYER]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'button1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "button1" }, LAYER: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, - { blockType: Scratch.BlockType.LABEL, text: 'Element Management' }, + { blockType: Scratch.BlockType.LABEL, text: "Element Management" }, { - opcode: 'moveElement', + opcode: "moveElement", blockType: Scratch.BlockType.COMMAND, - text: 'move element [ID] to x: [X] y: [Y]', + text: "move element [ID] to x: [X] y: [Y]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, X: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 }, }, }, { - opcode: 'resizeElement', + opcode: "resizeElement", blockType: Scratch.BlockType.COMMAND, - text: 'resize element [ID] to width: [WIDTH] height: [HEIGHT]', + text: "resize element [ID] to width: [WIDTH] height: [HEIGHT]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, WIDTH: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 }, HEIGHT: { type: Scratch.ArgumentType.NUMBER, defaultValue: 20 }, }, }, { - opcode: 'removeElement', + opcode: "removeElement", blockType: Scratch.BlockType.COMMAND, - text: 'remove element [ID]', + text: "remove element [ID]", arguments: { - ID: { type: Scratch.ArgumentType.STRING, defaultValue: 'input1' }, + ID: { type: Scratch.ArgumentType.STRING, defaultValue: "input1" }, }, }, { - opcode: 'removeAllElements', + opcode: "removeAllElements", blockType: Scratch.BlockType.COMMAND, - text: 'remove all elements', + text: "remove all elements", }, { - opcode: 'listElements', + opcode: "listElements", blockType: Scratch.BlockType.REPORTER, - text: 'list all elements', + text: "list all elements", }, ]; } @@ -261,59 +301,65 @@ const element = document.createElement(tag); element.id = ID; - element.style.position = 'absolute'; - element.style.left = X + runtime.stageWidth / 2 + 'px'; - element.style.top = -Y + runtime.stageHeight / 2 + 'px'; - element.style.pointerEvents = 'auto'; - element.style.transform = 'translate(-50%, -50%)'; + element.style.position = "absolute"; + element.style.left = X + runtime.stageWidth / 2 + "px"; + element.style.top = -Y + runtime.stageHeight / 2 + "px"; + element.style.pointerEvents = "auto"; + element.style.transform = "translate(-50%, -50%)"; additionalSetup(element); Scratch.renderer.addOverlay(element); - this.elements[ID] = { element, overlay: element, zIndex: this.zIndexCounter++ }; + this.elements[ID] = { + element, + overlay: element, + zIndex: this.zIndexCounter++, + }; this.updateZIndex(ID); } createInput(args) { - this.createElement('input', args); + this.createElement("input", args); } createDropdown({ ID, X, Y, OPTIONS }) { - this.createElement('select', { ID, X, Y }, (select) => { - Cast.toString(OPTIONS).split(',').forEach(optionText => { - const option = document.createElement('option'); - option.textContent = optionText; - select.appendChild(option); - }); + this.createElement("select", { ID, X, Y }, (select) => { + Cast.toString(OPTIONS) + .split(",") + .forEach((optionText) => { + const option = document.createElement("option"); + option.textContent = optionText; + select.appendChild(option); + }); }); } createButton({ ID, X, Y, TEXT }) { - this.createElement('button', { ID, X, Y }, (button) => { + this.createElement("button", { ID, X, Y }, (button) => { button.textContent = Cast.toString(TEXT); - button.addEventListener('click', () => { - runtime.startHats('htmlElements_whenButtonClicked', { ID }); + button.addEventListener("click", () => { + runtime.startHats("htmlElements_whenButtonClicked", { ID }); }); }); } createToggleButton({ ID, X, Y, TEXT }) { - this.createElement('button', { ID, X, Y }, (button) => { + this.createElement("button", { ID, X, Y }, (button) => { button.textContent = Cast.toString(TEXT); - button.dataset.toggled = 'false'; - button.addEventListener('click', () => { - const isToggled = button.dataset.toggled === 'true'; + button.dataset.toggled = "false"; + button.addEventListener("click", () => { + const isToggled = button.dataset.toggled === "true"; button.dataset.toggled = !isToggled; - runtime.startHats('htmlElements_whenButtonClicked', { ID }); + runtime.startHats("htmlElements_whenButtonClicked", { ID }); }); }); } createPanel(args) { - this.createElement('div', args, (panel) => { - panel.style.width = '50px'; - panel.style.height = '50px'; - panel.style.backgroundColor = '#ccc'; + this.createElement("div", args, (panel) => { + panel.style.width = "50px"; + panel.style.height = "50px"; + panel.style.backgroundColor = "#ccc"; }); } @@ -325,11 +371,11 @@ ID = Cast.toString(ID); const element = this.elements[ID]?.element; if (element?.dataset.toggled !== undefined) { - return element.dataset.toggled === 'true'; - } else if (element && 'value' in element) { - return element.value || ''; + return element.dataset.toggled === "true"; + } else if (element && "value" in element) { + return element.value || ""; } - return ''; + return ""; } setInputValue({ ID, VALUE }) { @@ -337,7 +383,7 @@ const element = this.elements[ID]?.element; if (element?.dataset.toggled !== undefined) { element.dataset.toggled = Cast.toBoolean(VALUE); - } else if (element && 'value' in element) { + } else if (element && "value" in element) { element.value = Cast.toString(VALUE); } } @@ -345,7 +391,7 @@ setButtonText({ ID, TEXT }) { ID = Cast.toString(ID); const element = this.elements[ID]?.element; - if (element && element.tagName === 'BUTTON') { + if (element && element.tagName === "BUTTON") { element.textContent = Cast.toString(TEXT); } } @@ -353,7 +399,7 @@ setToggleButtonText({ ID, TEXT }) { ID = Cast.toString(ID); const element = this.elements[ID]?.element; - if (element && element.tagName === 'BUTTON') { + if (element && element.tagName === "BUTTON") { element.textContent = Cast.toString(TEXT); } } @@ -374,11 +420,11 @@ } setElementBackgroundColor({ ID, COLOR }) { - this.setElementStyle({ ID, property: 'backgroundColor', value: COLOR }); + this.setElementStyle({ ID, property: "backgroundColor", value: COLOR }); } setElementTextColor({ ID, COLOR }) { - this.setElementStyle({ ID, property: 'color', value: COLOR }); + this.setElementStyle({ ID, property: "color", value: COLOR }); } setElementBorder({ ID, WIDTH, COLOR, ROUNDING }) { @@ -392,29 +438,32 @@ setElementFont({ ID, FONT_URL }) { ID = Cast.toString(ID); if (this.elements[ID] && FONT_URL) { - const fontFace = new FontFace('CustomFont', `url(${FONT_URL})`); + const fontFace = new FontFace("CustomFont", `url(${FONT_URL})`); document.fonts.add(fontFace); fontFace.load().then(() => { - this.elements[ID].element.style.fontFamily = 'CustomFont'; + this.elements[ID].element.style.fontFamily = "CustomFont"; }); } } setElementTransparency({ ID, ALPHA }) { - this.setElementStyle({ ID, property: 'opacity', value: ALPHA }); + this.setElementStyle({ ID, property: "opacity", value: ALPHA }); } setElementTextTransparency({ ID, ALPHA }) { ID = Cast.toString(ID); if (this.elements[ID]) { - const color = this.elements[ID].element.style.color || 'rgba(0, 0, 0, 1)'; + const color = + this.elements[ID].element.style.color || "rgba(0, 0, 0, 1)"; const rgbaColor = this.hexToRgba(color, ALPHA); this.elements[ID].element.style.color = rgbaColor; } } hexToRgba(hex, alpha) { - let r = 0, g = 0, b = 0; + let r = 0, + g = 0, + b = 0; if (hex.length === 4) { r = parseInt(hex[1] + hex[1], 16); g = parseInt(hex[2] + hex[2], 16); @@ -464,8 +513,8 @@ if (this.elements[ID]) { X = Cast.toNumber(X) + runtime.stageWidth / 2; Y = -Cast.toNumber(Y) + runtime.stageHeight / 2; - this.elements[ID].overlay.style.left = X + 'px'; - this.elements[ID].overlay.style.top = Y + 'px'; + this.elements[ID].overlay.style.left = X + "px"; + this.elements[ID].overlay.style.top = Y + "px"; } } @@ -474,13 +523,15 @@ if (this.elements[ID]) { WIDTH = Cast.toNumber(WIDTH); HEIGHT = Cast.toNumber(HEIGHT); - this.elements[ID].overlay.style.width = WIDTH + 'px'; - this.elements[ID].overlay.style.height = HEIGHT + 'px'; + this.elements[ID].overlay.style.width = WIDTH + "px"; + this.elements[ID].overlay.style.height = HEIGHT + "px"; } } removeAllElements() { - Object.keys(this.elements).forEach(id => this.removeElement({ ID: id })); + Object.keys(this.elements).forEach((id) => + this.removeElement({ ID: id }) + ); this.zIndexCounter = 0; } diff --git a/images/samllea1/Html Elements.png b/images/samllea1/HtmlElements.png similarity index 100% rename from images/samllea1/Html Elements.png rename to images/samllea1/HtmlElements.png From 83cd0a502d55539b715861dd003945acc39de41b Mon Sep 17 00:00:00 2001 From: samllea1 Date: Mon, 9 Jun 2025 23:55:10 +1000 Subject: [PATCH 4/4] Fix 2 --- extensions/samllea1/HtmlElements.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/samllea1/HtmlElements.js b/extensions/samllea1/HtmlElements.js index ae86698ab3..bd30c093e6 100644 --- a/extensions/samllea1/HtmlElements.js +++ b/extensions/samllea1/HtmlElements.js @@ -19,7 +19,10 @@ getInfo() { return { id: "htmlElements", - name: Scratch.translate({ default: "HTML Elements", id: "htmlElements.name" }), + name: Scratch.translate({ + default: "HTML Elements", + id: "htmlElements.name", + }), color1: "#9596bf", color2: "#7b7c9e", blocks: this.getBlockDefinitions(),