diff --git a/HISTORY.md b/HISTORY.md
index 8ff29cd4..908ce65f 100755
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -68,6 +68,9 @@
* Chinese, thanks, Simon!
* Brazilian Portuguese, thank you, Cassiano D'Andrea!
+### 2021-11-30
+* blocks, threads: block-assembly support, experimental
+
### 2021-11-29
* renamed "r-g-b-a" option to "RGBA" and "r-g-b(-a)" to "RGB(A)"
diff --git a/snap.html b/snap.html
index 4f633cae..c92e171a 100755
--- a/snap.html
+++ b/snap.html
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/src/blocks.js b/src/blocks.js
index 10ef8c75..f0ca75fe 100644
--- a/src/blocks.js
+++ b/src/blocks.js
@@ -153,14 +153,14 @@ radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph, SymbolMorph,
fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph, Rectangle,
DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, WHITE, BLACK,
Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, CLEAR, Point,
-isSnapObject, PushButtonMorph, SpriteIconMorph, Process, AlignmentMorph,
+isSnapObject, PushButtonMorph, SpriteIconMorph, Process, AlignmentMorph, List,
CustomCommandBlockMorph, ToggleButtonMorph, DialMorph, SnapExtensions*/
/*jshint esversion: 6*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2021-November-27';
+modules.blocks = '2021-November-30';
var SyntaxElementMorph;
var BlockMorph;
@@ -3614,6 +3614,8 @@ BlockMorph.prototype.restoreInputs = function (oldInputs, offset = 0) {
return leftOver;
};
+// BlockMorph helpscreens
+
BlockMorph.prototype.showHelp = function () {
var myself = this,
ide = this.parentThatIsA(IDE_Morph),
@@ -3702,6 +3704,41 @@ BlockMorph.prototype.exportResultPic = function () {
}
};
+// BlockMorph components - EXPERIMENTAL
+
+BlockMorph.prototype.components = function () { // +++
+ throw new Error('subclass responsility');
+};
+
+BlockMorph.prototype.copyWithInputs = function (inputs) {
+ var cpy = this.fullCopy(),
+ slots = cpy.inputs(),
+ dta = inputs.itemsArray().map(inp =>
+ inp instanceof Context ? inp.expression : inp
+ );
+ slots.forEach(slt => {
+ if (slt instanceof BlockMorph) {
+ cpy.revertToDefaultInput(slt);
+ }
+ });
+ dta.forEach((inp, i) => {
+ if (inp instanceof BlockMorph) {
+ if (inp instanceof CommandBlockMorph && slots[i].nestedBlock) {
+ slots[i].nestedBlock(inp);
+ } else {
+ cpy.replaceInput(slots[i], inp);
+ }
+ } else {
+ if (inp instanceof List && inp.length() === 0) {
+ nop(); // ignore, i.e. leave slot as is
+ } else {
+ slots[i].setContents(inp);
+ }
+ }
+ });
+ return cpy;
+};
+
// BlockMorph code mapping
/*
@@ -5417,6 +5454,46 @@ CommandBlockMorph.prototype.extract = function () {
}
};
+// CommandBlockMorph components - EXPERIMENTAL
+
+CommandBlockMorph.prototype.components = function () { // +++
+ var seq = new List(this.blockSequence()).map(block => {
+ var expr = block.fullCopy(),
+ nb = expr.nextBlock(),
+ inputs, parts;
+ if (nb) {
+ nb.destroy();
+ }
+ expr.fixBlockColor(null, true);
+ inputs = expr.inputs();
+ if (!inputs.length) {
+ return expr;
+ }
+ parts = new List([expr]);
+ inputs.forEach(inp => {
+ var val;
+ if (inp instanceof BlockMorph) {
+ parts.add(inp.components());
+ } else {
+ val = inp.evaluate();
+ parts.add(val instanceof BlockMorph ? val.components() : val);
+
+ }
+ expr.revertToDefaultInput(inp, true); // empty
+ });
+ return parts;
+ });
+ return seq.length() === 1 ? seq.at(1) : seq;
+};
+
+CommandBlockMorph.prototype.copyWithNext = function (next) {
+ var exp = this.fullCopy(),
+ bottom = exp.bottomBlock(),
+ top = next.fullCopy().topBlock();
+ bottom.nextBlock(top);
+ return exp;
+};
+
// CommandBlockMorph drawing:
CommandBlockMorph.prototype.outlinePath = function(ctx, inset) {
@@ -6180,6 +6257,32 @@ ReporterBlockMorph.prototype.userDestroy = function () {
this.destroy();
};
+// ReporterBlockMorph components - EXPERIMENTAL
+
+ReporterBlockMorph.prototype.components = function () { // +++
+ var expr = this.fullCopy(),
+ inputs = expr.inputs(),
+ parts;
+ expr.fixBlockColor(null, true);
+ if (!inputs.length ||
+ inputs.every(slot => slot.isEmptySlot && slot.isEmptySlot())) {
+ return expr;
+ }
+ parts = new List([expr]);
+ inputs.forEach(inp => {
+ var val;
+ if (inp instanceof BlockMorph) {
+ parts.add(inp.components());
+ } else {
+ val = inp.evaluate();
+ parts.add(val instanceof BlockMorph ? val.components() : val);
+
+ }
+ expr.revertToDefaultInput(inp, true); // empty
+ });
+ return parts;
+};
+
// ReporterBlockMorph drawing:
ReporterBlockMorph.prototype.outlinePath = function (ctx, inset) {
diff --git a/src/threads.js b/src/threads.js
index 3be3dbdb..b0d9c8db 100644
--- a/src/threads.js
+++ b/src/threads.js
@@ -64,7 +64,7 @@ SnapExtensions, AlignmentMorph, TextMorph, Cloud, HatBlockMorph*/
/*jshint esversion: 6*/
-modules.threads = '2021-November-27';
+modules.threads = '2021-November-30';
var ThreadManager;
var Process;
@@ -7009,6 +7009,20 @@ Context.prototype.isInCustomBlock = function () {
return false;
};
+// Context components - EXPERIMENTAL
+
+Context.prototype.components = function () {
+ var expr = this.expression.components(),
+ parts;
+ if (!this.inputs.length) {
+ return expr;
+ }
+ parts = new List();
+ parts.add(expr); // blocks / other
+ this.inputs.forEach(name => parts.add(name));
+ return parts;
+};
+
// Variable /////////////////////////////////////////////////////////////////
function Variable(value, isTransient, isHidden) {