Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/workflows/develop-builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Build and Publish to develop-builds

on:
push:
branches:
- develop

jobs:
build-and-publish:
runs-on: ubuntu-latest

steps:
- name: Checkout develop branch
uses: actions/checkout@v4
with:
ref: develop
fetch-depth: 0 # Fetch all history for all branches
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies and build
run: |
npm ci # This triggers prepublish which builds compressed files

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Checkout develop-builds branch
run: |
git fetch origin develop-builds:develop-builds || git checkout -b develop-builds
git checkout develop-builds

- name: Merge develop into develop-builds
run: |
git merge origin/develop --no-edit || true

- name: Add compiled files
run: |
git add -f blockly_compressed*.js blocks_compressed*.js msg/*.js
git diff --staged --quiet || git commit -m "Auto-build: Update compiled files from develop ($(git rev-parse --short origin/develop))"

- name: Push to develop-builds
run: |
git push origin develop-builds
34 changes: 34 additions & 0 deletions blocks_common/custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @fileoverview Custom blocks for Blockly.
* @author @SharkPool-SP (SharkPool)
*/
'use strict';

goog.provide('Blockly.Blocks.customInput');

goog.require('Blockly.Blocks');

goog.require('Blockly.Colours');

goog.require('Blockly.constants');

Blockly.Blocks['customInput'] = {
/**
* Block for custom inputs.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1",
"args0": [
{
"type": "field_customInput",
"name": "CUSTOM"
}
],
"outputShape": Blockly.OUTPUT_SHAPE_SQUARE,
"output": "String",
"extensions": ["colours_pen"]
});
}
};
4 changes: 3 additions & 1 deletion blocks_vertical/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ Blockly.Blocks['data_variable'] = {
],
"category": Blockly.Categories.data,
"checkboxInFlyout": true,
"extensions": ["contextMenu_getVariableBlock", "colours_data", "output_string"]
// ob: allow variables in any input, like AmpMod and pang and joe's epic tw mod
"output": null,
"extensions": ["contextMenu_getVariableBlock", "colours_data", "shape_reporter"]
});
}
};
Expand Down
38 changes: 38 additions & 0 deletions blocks_vertical/operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,45 @@ Blockly.Blocks['operator_lt'] = {
});
}
};
Blockly.Blocks['operator_gtoreq'] = {
init: function() {
this.jsonInit({
"message0": "%1 ≥ %2",
"args0": [
{
"type": "input_value",
"name": "OPERAND1"
},
{
"type": "input_value",
"name": "OPERAND2"
}
],
"category": Blockly.Categories.operators,
"extensions": ["colours_operators", "output_boolean"]
});
}
};

Blockly.Blocks['operator_ltoreq'] = {
init: function() {
this.jsonInit({
"message0": "%1 ≤ %2",
"args0": [
{
"type": "input_value",
"name": "OPERAND1"
},
{
"type": "input_value",
"name": "OPERAND2"
}
],
"category": Blockly.Categories.operators,
"extensions": ["colours_operators", "output_boolean"]
});
}
};
Blockly.Blocks['operator_equals'] = {
/**
* Block for equals comparator.
Expand Down
13 changes: 13 additions & 0 deletions blocks_vertical/vertical_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_STRING = function() {
this.setOutput(true, 'String');
};

/**
* Extension to make represent a round, but generic, reporter in Scratch-Blocks.
* That means the block has inline inputs, and a round output shape.
* @this {Blockly.Block}
* @readonly
*/
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_REPORTER = function() {
this.setInputsInline(true);
this.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
};

/**
* Extension to make represent a boolean reporter in Scratch-Blocks.
* That means the block has inline inputs, a round output shape, and a 'Boolean'
Expand Down Expand Up @@ -258,6 +269,8 @@ Blockly.ScratchBlocks.VerticalExtensions.registerAll = function() {
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_HAT);
Blockly.Extensions.register('shape_end',
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_END);
Blockly.Extensions.register('shape_reporter',
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_REPORTER);

// Output shapes and types are related.
Blockly.Extensions.register('output_number',
Expand Down
1 change: 1 addition & 0 deletions core/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.math.Coordinate');
goog.require('goog.string');
goog.require('Blockly.FieldCustom');


/**
Expand Down
1 change: 1 addition & 0 deletions core/blockly.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ goog.require('Blockly.constants');
goog.require('Blockly.inject');
goog.require('Blockly.utils');
goog.require('goog.color');
goog.require('Blockly.FieldCustom');


// Turn off debugging when compiled.
Expand Down
195 changes: 195 additions & 0 deletions core/field_customInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* @fileoverview custom DOM-based input users can customize
* @author @SharkPool-SP (SharkPool)
*/
'use strict';

goog.provide('Blockly.FieldCustom');

const customInputs = new Map();

/**
* Class for a custom field.
* @param {object} options Object containing the default value, inputID, etc for the field
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldCustom = function(options) {
Blockly.FieldCustom.superClass_.constructor.call(this, options);
this.addArgType('text');

/**
* input ID used to identify input from 'customInputs'
* @type {string}
*/
this.inputID = options.id ? options.id : null;

/**
* value of the field
* @type {any}
*/
this.value_ = options.value ? options.value : '';
/**
* input parts stored in 'customInputs'
* @type {object}
*/
this.inputParts = {};

/**
* Touch event wrapper.
* Runs when the field is selected.
* @type {!Array}
* @private
*/
this.mouseDownWrapper_ = null;
};
goog.inherits(Blockly.FieldCustom, Blockly.Field);

/**
* Construct a FieldCustom from a JSON arg object.
* @param {!Object} options A JSON object with options.
* @returns {!Blockly.FieldCustom} The new field instance.
* @package
* @nocollapse
*/
Blockly.FieldCustom.fromJson = function(options) {
return new Blockly.FieldCustom(options);
};

Blockly.FieldCustom.registerInput = function(id, templateHTML, onInit, onClick, onUpdate, optOnDispose) {
if (!id || typeof id !== 'string') {
console.warn('Param 1 must be a non-empty string id!');
return;
}
if (customInputs.has && customInputs.has(id)) {
console.warn('An input with id "' + id + '" is already registered; overriding.');
}
if (!templateHTML || !(templateHTML instanceof Node)) {
console.warn('Param 2 must be a valid DOM element!');
return;
}
if (!onInit || typeof onInit !== 'function') {
console.warn('Param 3 must be a function!');
return;
}
if (!onClick || typeof onClick !== 'function') {
console.warn('Param 4 must be a function!');
return;
}
if (!onUpdate || typeof onUpdate !== 'function') {
console.warn('Param 5 must be a function!');
return;
}
if (optOnDispose && typeof optOnDispose !== 'function') {
console.warn('Param 6 must be a function!');
return;
}
customInputs.set(id, { templateHTML, onInit, onClick, onUpdate, optOnDispose });
};
Blockly.FieldCustom.unregisterInput = function(id) {
customInputs.delete(id);
};
Blockly.FieldCustom.registeredInputs = function() {
return customInputs;
};

/**
* Called when the field is placed on a block.
* @param {Block} block The owning block.
*/
Blockly.FieldCustom.prototype.init = function() {
if (this.fieldGroup_) {
// custom field has already been initialized
return;
}

this.inputParts = customInputs.get(this.inputID);
if (!this.inputParts) {
console.error(`No Custom Input found with ID '${this.inputID}', did you use 'registerInput'?`);
return;
}

// Build the DOM.
const htmlDOM = this.inputParts.templateHTML.cloneNode(true);
htmlDOM.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.inputParts.html = htmlDOM; // makes it easier for ext devs to find the input theyre editting

this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
const boundingBox = htmlDOM.getBoundingClientRect();
this.size_.width = htmlDOM.width ? htmlDOM.width : htmlDOM.style.width ? parseFloat(htmlDOM.style.width) :
boundingBox.width;
this.size_.height = htmlDOM.height ? htmlDOM.height : htmlDOM.style.height ? parseFloat(htmlDOM.style.height) :
Math.max(32, boundingBox.height);

this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);

this.inputSource = Blockly.utils.createSvgElement('foreignObject', {
'width': this.size_.width, 'height': this.size_.height,
'pointer-events': 'all', 'cursor': 'pointer', 'overflow': 'visible'
}, this.fieldGroup_);
this.inputSource.appendChild(htmlDOM);

this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
this.getClickTarget_(), 'mousedown', this, this.onMouseDown_
);
queueMicrotask(() => {
this.inputParts.onInit(this, this.inputParts.html);
});
};

/**
* Set the value for this field
* @param {any} value The new value of whatever the user chooses
* @override
*/
Blockly.FieldCustom.prototype.setValue = function(value) {
if (!value || value === this.value_) {
return; // No change
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
this.sourceBlock_, 'field', this.name, this.value_, value
));
}
this.value_ = value;
if (this.inputParts !== undefined && this.inputParts.onUpdate) {
const htmlDOM = this.inputParts.html;
this.inputParts.onUpdate(this, htmlDOM);
}
};

/**
* Get the value from this field menu.
* @return {any} Current value.
*/
Blockly.FieldCustom.prototype.getValue = function() {
return this.value_;
};

/**
* do whatever the user desires on-edit
* @private
*/
Blockly.FieldCustom.prototype.showEditor_ = function() {
const htmlDOM = this.inputParts.html;
this.inputParts.onClick(this, htmlDOM);
};

/**
* Clean up this FieldCustom, as well as the inherited Field.
* @return {!Function} Closure to call on destruction of the WidgetDiv.
* @private
*/
Blockly.FieldCustom.prototype.dispose_ = function() {
var thisField = this;
return function() {
if (thisField.inputParts.optOnDispose) {
const htmlDOM = this.inputParts.html;
thisField.inputParts.optOnDispose(thisField, htmlDOM);
}
Blockly.FieldCustom.superClass_.dispose_.call(thisField)();
if (thisField.mouseDownWrapper_) Blockly.unbindEvent_(thisField.mouseDownWrapper_);
};
};

Blockly.Field.register('field_customInput', Blockly.FieldCustom);
Loading