@protorobotics/jenga 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/jenga.js +85 -78
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 --- 2026-02-16
4
+
5
+ ### Added
6
+
7
+ - Added shadow blocks
8
+ - Support for newlines using `\n`
9
+ - Toggle for inline inputs
10
+ - Help button
11
+
12
+ ### Changed
13
+
14
+ - Additional input types and removed one-input limit
15
+
16
+ ### Removed
17
+
18
+ - Removed default blocks
19
+
3
20
  ## 1.1.0 --- 2025-10-08
4
21
 
5
22
  ### Changed
package/jenga.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Block, CodeGenerator, common, Field, Toolbox } from "blockly";
1
+ import { Block, CodeGenerator, common, Field, Toolbox, FieldImage, utils } from "blockly";
2
2
 
3
3
  /**
4
4
  * @typedef {object} Category
@@ -12,8 +12,15 @@ import { Block, CodeGenerator, common, Field, Toolbox } from "blockly";
12
12
  * @property {string} name The name of the entry.
13
13
  * @property {string} description Description of the entry.
14
14
  * @property {BlocklyField[]} blocklyTemplate The template used to make the blockly block.
15
- * @property {BlocklyIOConnection?} blocklyInput The input connection for the block.
16
15
  * @property {BlocklyIOConnection?} blocklyOutput The output connection for the block.
16
+ * @property {boolean} inputsInline Whether the inputs are inline. Optional parameter only used in backend.
17
+ * @property {function(?): object} save Function that saves extra state for the block. Optional parameter only used in backend.
18
+ * @property {function(object): void} load Function that loads extra state for the block. Optional parameter only used in backend.
19
+ * @property {function(): void} update Function that updates the block shape based on state. Optional parameter only used in backend.
20
+ * @property {function(object): void} saveConnections Function that saves connections for the block. Optional parameter only used in backend.
21
+ * @property {function(object): object} compose Function that composes the block from saved connections. Optional parameter only used in backend.
22
+ * @property {function(object): object} decompose Function that decomposes the block to save connections. Optional parameter only used in backend.
23
+ * @property {function(object): void} onchange Function that handles block changes. Optional parameter
17
24
  * @property {CodeGenFunction} codeGenerator The code generator function used by blockly.
18
25
  */
19
26
 
@@ -27,12 +34,14 @@ import { Block, CodeGenerator, common, Field, Toolbox } from "blockly";
27
34
  * @property {string?} name The name of the field.
28
35
  * @property {FieldFunction?} field Function that generates the blockly field object.
29
36
  * @property {string?} text Text displayed in place of the field.
37
+ * @property {BlocklyIOConnection?} blocklyInput The input connection displayed in place of the field.
30
38
  */
31
39
 
32
40
  /**
33
41
  * @typedef {object} BlocklyIOConnection
34
42
  * @property {BlocklyType} type The type of the connection.
35
43
  * @property {string} name The name of the connection.
44
+ * @property {string?} shadow The name of the optional shadow block to use for this connection. Only used for block inputs.
36
45
  */
37
46
 
38
47
  // TODO: define vocab objects
@@ -42,12 +51,18 @@ import { Block, CodeGenerator, common, Field, Toolbox } from "blockly";
42
51
  */
43
52
  const BlocklyType = {
44
53
  bool: "Boolean",
45
- // TODO: Add other types
54
+ number: "Number",
55
+ string: "String",
56
+ any: "Any",
57
+ void: "Void",
58
+ array: "Array",
59
+ color: "Colour",
46
60
  };
47
61
 
48
62
  /**
49
63
  * @callback CodeGenFunction
50
64
  * @param {Block} block The blockly block.
65
+ * @param {CodeGenerator | undefined} generator The blockly generator.
51
66
  * @returns {string} The generated code.
52
67
  */
53
68
 
@@ -99,21 +114,69 @@ function initBlocklyCategory(category) {
99
114
  function initBlocklyBlock(entry, category, generator) {
100
115
  const blockDefinition = {
101
116
  init: function () {
102
- const inputConn = entry.blocklyInput;
103
- const rootInput = !inputConn
104
- ? this.appendDummyInput("dummyInput")
105
- : this.appendValueInput(inputConn.name).setCheck(inputConn.type);
117
+ let rootInput = null;
106
118
 
107
119
  for (const field of entry.blocklyTemplate) {
108
120
  if (field.text) {
109
- // Plain text
110
- rootInput.appendField(field.text);
111
- } else {
121
+ if (!rootInput) {
122
+ rootInput = this.appendDummyInput()
123
+ }
124
+ //split text by \n
125
+ //create a new dummy input for every new line
126
+ //then add the text as a field
127
+ const cleanText = field.text.split("\n")
128
+ for (let i = 0; i < cleanText.length; i++) {
129
+ rootInput.appendField(cleanText[i]);
130
+
131
+ if (i != cleanText.length - 1) {
132
+ rootInput = this.appendDummyInput()
133
+ }
134
+ }
135
+ } else if (field.field) {
136
+ if (!rootInput) {
137
+ rootInput = this.appendDummyInput()
138
+ }
112
139
  // Additional user input
113
140
  rootInput.appendField(field.field(), field.name);
141
+ } else if (field.blocklyInput) {
142
+ if (field.blocklyInput.type === "Any" || field.blocklyInput.type == null){
143
+ rootInput = this.appendValueInput(field.blocklyInput.name)
144
+ if (field.blocklyInput.shadow) {
145
+ const shadowXml = utils.xml.textToDom(`
146
+ <shadow type="${field.blocklyInput.shadow}">
147
+ </shadow>
148
+ `);
149
+ rootInput.connection.setShadowDom(shadowXml);
150
+ }
151
+
152
+ rootInput = this.appendDummyInput() //needed to make sure next inputs go to after this one otheriwise they get added in front of this input
153
+ } else if (field.blocklyInput.type === "Void") {
154
+ rootInput = this.appendStatementInput(field.blocklyInput.name)
155
+ if (field.blocklyInput.shadow) {
156
+ const shadowXml = utils.xml.textToDom(`
157
+ <shadow type="${field.blocklyInput.shadow}">
158
+ </shadow>
159
+ `);
160
+ rootInput.connection.setShadowDom(shadowXml);
161
+ }
162
+ } else {
163
+ rootInput = this.appendValueInput(field.blocklyInput.name).setCheck(field.blocklyInput.type || null)
164
+ if (field.blocklyInput.shadow) {
165
+ const shadowXml = utils.xml.textToDom(`
166
+ <shadow type="${field.blocklyInput.shadow}">
167
+ </shadow>
168
+ `);
169
+ rootInput.connection.setShadowDom(shadowXml);
170
+ }
171
+ rootInput = this.appendDummyInput() //needed to make sure next inputs go to after this one otheriwise they get added in front of this input
172
+ }
173
+ } else {
174
+ console.error("unknown field type: ", field);
114
175
  }
115
176
  }
116
177
 
178
+ this.setInputsInline(entry.inputsInline);
179
+
117
180
  const outputConn = entry.blocklyOutput;
118
181
  if (!outputConn) {
119
182
  this.setPreviousStatement(true, null);
@@ -122,10 +185,21 @@ function initBlocklyBlock(entry, category, generator) {
122
185
  this.setOutput(true, outputConn.type);
123
186
  }
124
187
 
188
+ //TODO: figure out where to put this since every block will have the help button
189
+ rootInput.appendField(new FieldImage("./images/help.svg", 15, 15, "Info", () => console.log("Clicked!" + entry.name)), "info_icon");
190
+
125
191
  this.setTooltip(entry.description || "");
126
192
  this.setHelpUrl("");
127
193
  this.setColour(category.color);
128
194
  },
195
+
196
+ saveExtraState: entry.save, //used for saving extra state
197
+ loadExtraState: entry.load, //used for loading extra state
198
+ updateShape_: entry.update, //used for updating shape based on state
199
+ saveConnections: entry.saveConnections, //used for saving connections
200
+ compose: entry.compose, //used for composing block from saved connections
201
+ decompose: entry.decompose, //used for decomposing block to save connections
202
+ onchange: entry.onchange, //used for handling block changes
129
203
  };
130
204
 
131
205
  // Define the block globally
@@ -144,75 +218,8 @@ function initBlocklyBlock(entry, category, generator) {
144
218
  * @returns {{kind: string, contents: object[]}} The blockly toolbox.
145
219
  */
146
220
  function initBlocklyToolbox(blocklyCategories) {
147
- // TODO: filter default categories
148
- const defaultCategories = [
149
- {
150
- kind: "category",
151
- name: "Flow",
152
- colour: "#e9a719",
153
- contents: [
154
- {
155
- kind: "block",
156
- type: "controls_if",
157
- },
158
- {
159
- kind: "block",
160
- type: "logic_compare",
161
- },
162
- {
163
- kind: "block",
164
- type: "logic_operation",
165
- },
166
- {
167
- kind: "block",
168
- type: "logic_negate",
169
- },
170
- {
171
- kind: "block",
172
- type: "logic_boolean",
173
- },
174
- {
175
- kind: "block",
176
- type: "controls_repeat_ext",
177
- inputs: {
178
- TIMES: {
179
- block: {
180
- type: "math_number",
181
- fields: {
182
- NUM: 10,
183
- },
184
- },
185
- },
186
- },
187
- },
188
- {
189
- kind: "block",
190
- type: "controls_whileUntil",
191
- },
192
- ],
193
- },
194
- {
195
- kind: "category",
196
- name: "Math",
197
- colour: "#cc44cc",
198
- contents: [
199
- {
200
- kind: "block",
201
- type: "math_number",
202
- fields: {
203
- NUM: 123,
204
- },
205
- },
206
- {
207
- kind: "block",
208
- type: "math_arithmetic",
209
- },
210
- ],
211
- },
212
- ];
213
-
214
221
  return {
215
222
  kind: "categoryToolbox",
216
- contents: blocklyCategories.concat(defaultCategories),
223
+ contents: blocklyCategories,
217
224
  };
218
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protorobotics/jenga",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Build a function vocabulary and a blockly instance from one JSON object",
5
5
  "keywords": [
6
6
  "blockly",