@protorobotics/jenga 1.1.2 → 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 +81 -77
  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,7 +51,12 @@ 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
  /**
@@ -100,23 +114,69 @@ function initBlocklyCategory(category) {
100
114
  function initBlocklyBlock(entry, category, generator) {
101
115
  const blockDefinition = {
102
116
  init: function () {
103
- const inputConn = entry.blocklyInput;
104
- const rootInput = !inputConn
105
- ? this.appendDummyInput("dummyInput")
106
- : this.appendValueInput(inputConn.name).setCheck(inputConn.type);
117
+ let rootInput = null;
107
118
 
108
119
  for (const field of entry.blocklyTemplate) {
109
120
  if (field.text) {
110
- // Plain text
111
- rootInput.appendField(field.text);
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
+ }
112
135
  } else if (field.field) {
136
+ if (!rootInput) {
137
+ rootInput = this.appendDummyInput()
138
+ }
113
139
  // Additional user input
114
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
+ }
115
173
  } else {
116
174
  console.error("unknown field type: ", field);
117
175
  }
118
176
  }
119
177
 
178
+ this.setInputsInline(entry.inputsInline);
179
+
120
180
  const outputConn = entry.blocklyOutput;
121
181
  if (!outputConn) {
122
182
  this.setPreviousStatement(true, null);
@@ -125,10 +185,21 @@ function initBlocklyBlock(entry, category, generator) {
125
185
  this.setOutput(true, outputConn.type);
126
186
  }
127
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
+
128
191
  this.setTooltip(entry.description || "");
129
192
  this.setHelpUrl("");
130
193
  this.setColour(category.color);
131
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
132
203
  };
133
204
 
134
205
  // Define the block globally
@@ -147,75 +218,8 @@ function initBlocklyBlock(entry, category, generator) {
147
218
  * @returns {{kind: string, contents: object[]}} The blockly toolbox.
148
219
  */
149
220
  function initBlocklyToolbox(blocklyCategories) {
150
- // TODO: filter default categories
151
- const defaultCategories = [
152
- {
153
- kind: "category",
154
- name: "Flow",
155
- colour: "#e9a719",
156
- contents: [
157
- {
158
- kind: "block",
159
- type: "controls_if",
160
- },
161
- {
162
- kind: "block",
163
- type: "logic_compare",
164
- },
165
- {
166
- kind: "block",
167
- type: "logic_operation",
168
- },
169
- {
170
- kind: "block",
171
- type: "logic_negate",
172
- },
173
- {
174
- kind: "block",
175
- type: "logic_boolean",
176
- },
177
- {
178
- kind: "block",
179
- type: "controls_repeat_ext",
180
- inputs: {
181
- TIMES: {
182
- block: {
183
- type: "math_number",
184
- fields: {
185
- NUM: 10,
186
- },
187
- },
188
- },
189
- },
190
- },
191
- {
192
- kind: "block",
193
- type: "controls_whileUntil",
194
- },
195
- ],
196
- },
197
- {
198
- kind: "category",
199
- name: "Math",
200
- colour: "#cc44cc",
201
- contents: [
202
- {
203
- kind: "block",
204
- type: "math_number",
205
- fields: {
206
- NUM: 123,
207
- },
208
- },
209
- {
210
- kind: "block",
211
- type: "math_arithmetic",
212
- },
213
- ],
214
- },
215
- ];
216
-
217
221
  return {
218
222
  kind: "categoryToolbox",
219
- contents: blocklyCategories.concat(defaultCategories),
223
+ contents: blocklyCategories,
220
224
  };
221
225
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protorobotics/jenga",
3
- "version": "1.1.2",
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",