@blockdia/scratch-blocks 0.1.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 (243) hide show
  1. package/.nvmrc +1 -0
  2. package/LICENSE +674 -0
  3. package/README.md +57 -0
  4. package/TRADEMARK +1 -0
  5. package/blockly_compressed_horizontal.js +2268 -0
  6. package/blockly_compressed_vertical.js +2296 -0
  7. package/blockly_uncompressed_horizontal.js +1212 -0
  8. package/blockly_uncompressed_vertical.js +1212 -0
  9. package/blocks_common/colour.js +61 -0
  10. package/blocks_common/math.js +159 -0
  11. package/blocks_common/matrix.js +54 -0
  12. package/blocks_common/note.js +58 -0
  13. package/blocks_common/text.js +57 -0
  14. package/blocks_compressed.js +37 -0
  15. package/blocks_compressed_horizontal.js +68 -0
  16. package/blocks_compressed_vertical.js +216 -0
  17. package/blocks_horizontal/control.js +212 -0
  18. package/blocks_horizontal/default_toolbox.js +139 -0
  19. package/blocks_horizontal/event.js +190 -0
  20. package/blocks_horizontal/wedo.js +325 -0
  21. package/blocks_vertical/control.js +532 -0
  22. package/blocks_vertical/data.js +666 -0
  23. package/blocks_vertical/default_toolbox.js +571 -0
  24. package/blocks_vertical/event.js +329 -0
  25. package/blocks_vertical/extensions.js +294 -0
  26. package/blocks_vertical/looks.js +591 -0
  27. package/blocks_vertical/motion.js +587 -0
  28. package/blocks_vertical/operators.js +470 -0
  29. package/blocks_vertical/procedures.js +1051 -0
  30. package/blocks_vertical/sensing.js +555 -0
  31. package/blocks_vertical/sound.js +246 -0
  32. package/blocks_vertical/vertical_extensions.js +289 -0
  33. package/build/gen_blocks.js +1 -0
  34. package/build/test_expect.js +1 -0
  35. package/build/test_input.js +1 -0
  36. package/build.py +647 -0
  37. package/cleanup.sh +101 -0
  38. package/core/block.js +1837 -0
  39. package/core/block_animations.js +107 -0
  40. package/core/block_drag_surface.js +299 -0
  41. package/core/block_dragger.js +424 -0
  42. package/core/block_events.js +531 -0
  43. package/core/block_render_svg_horizontal.js +892 -0
  44. package/core/block_render_svg_vertical.js +1734 -0
  45. package/core/block_svg.js +1355 -0
  46. package/core/blockly.js +620 -0
  47. package/core/blocks.js +37 -0
  48. package/core/bubble.js +664 -0
  49. package/core/bubble_dragger.js +285 -0
  50. package/core/colours.js +177 -0
  51. package/core/comment.js +293 -0
  52. package/core/comment_events.js +539 -0
  53. package/core/connection.js +776 -0
  54. package/core/connection_db.js +300 -0
  55. package/core/constants.js +408 -0
  56. package/core/contextmenu.js +534 -0
  57. package/core/css.js +1354 -0
  58. package/core/data_category.js +490 -0
  59. package/core/dragged_connection_manager.js +260 -0
  60. package/core/dropdowndiv.js +408 -0
  61. package/core/events.js +429 -0
  62. package/core/events_abstract.js +113 -0
  63. package/core/extensions.js +450 -0
  64. package/core/field.js +810 -0
  65. package/core/field_angle.js +410 -0
  66. package/core/field_checkbox.js +133 -0
  67. package/core/field_colour.js +253 -0
  68. package/core/field_colour_slider.js +387 -0
  69. package/core/field_date.js +353 -0
  70. package/core/field_dropdown.js +447 -0
  71. package/core/field_iconmenu.js +309 -0
  72. package/core/field_image.js +200 -0
  73. package/core/field_label.js +136 -0
  74. package/core/field_label_serializable.js +125 -0
  75. package/core/field_matrix.js +566 -0
  76. package/core/field_note.js +850 -0
  77. package/core/field_number.js +366 -0
  78. package/core/field_numberdropdown.js +77 -0
  79. package/core/field_textdropdown.js +164 -0
  80. package/core/field_textinput.js +675 -0
  81. package/core/field_textinput_removable.js +105 -0
  82. package/core/field_variable.js +385 -0
  83. package/core/field_variable_getter.js +185 -0
  84. package/core/field_vertical_separator.js +161 -0
  85. package/core/flyout_base.js +935 -0
  86. package/core/flyout_button.js +324 -0
  87. package/core/flyout_dragger.js +83 -0
  88. package/core/flyout_extension_category_header.js +159 -0
  89. package/core/flyout_horizontal.js +475 -0
  90. package/core/flyout_vertical.js +770 -0
  91. package/core/generator.js +426 -0
  92. package/core/gesture.js +1010 -0
  93. package/core/grid.js +227 -0
  94. package/core/icon.js +205 -0
  95. package/core/inject.js +496 -0
  96. package/core/input.js +285 -0
  97. package/core/insertion_marker_manager.js +678 -0
  98. package/core/intersection_observer.js +102 -0
  99. package/core/msg.js +62 -0
  100. package/core/mutator.js +426 -0
  101. package/core/names.js +198 -0
  102. package/core/options.js +244 -0
  103. package/core/procedures.js +739 -0
  104. package/core/rendered_connection.js +417 -0
  105. package/core/scratch_block_comment.js +646 -0
  106. package/core/scratch_blocks_utils.js +246 -0
  107. package/core/scratch_bubble.js +699 -0
  108. package/core/scratch_events.js +131 -0
  109. package/core/scratch_msgs.js +85 -0
  110. package/core/scrollbar.js +875 -0
  111. package/core/toolbox.js +803 -0
  112. package/core/tooltip.js +337 -0
  113. package/core/touch.js +226 -0
  114. package/core/trashcan.js +343 -0
  115. package/core/ui_events.js +91 -0
  116. package/core/ui_menu_utils.js +68 -0
  117. package/core/utils.js +825 -0
  118. package/core/variable_events.js +259 -0
  119. package/core/variable_map.js +415 -0
  120. package/core/variable_model.js +116 -0
  121. package/core/variables.js +674 -0
  122. package/core/warning.js +199 -0
  123. package/core/widgetdiv.js +344 -0
  124. package/core/workspace.js +673 -0
  125. package/core/workspace_audio.js +170 -0
  126. package/core/workspace_comment.js +426 -0
  127. package/core/workspace_comment_render_svg.js +706 -0
  128. package/core/workspace_comment_svg.js +609 -0
  129. package/core/workspace_drag_surface_svg.js +195 -0
  130. package/core/workspace_dragger.js +132 -0
  131. package/core/workspace_svg.js +2382 -0
  132. package/core/xml.js +930 -0
  133. package/core/zoom_controls.js +301 -0
  134. package/dist/horizontal.js +222 -0
  135. package/dist/vertical.js +222 -0
  136. package/dist/web/horizontal.js +232 -0
  137. package/dist/web/vertical.js +232 -0
  138. package/i18n/common.py +234 -0
  139. package/i18n/create_messages.py +162 -0
  140. package/i18n/create_scratch_msgs.js +69 -0
  141. package/i18n/dedup_json.py +73 -0
  142. package/i18n/js_to_json.js +46 -0
  143. package/i18n/js_to_json.py +136 -0
  144. package/i18n/json_to_js.js +52 -0
  145. package/i18n/json_to_js.py +185 -0
  146. package/i18n/sync_tx_translations.js +111 -0
  147. package/i18n/test_scratch_msgs.js +87 -0
  148. package/i18n/tests.py +47 -0
  149. package/i18n/xliff_to_json.py +232 -0
  150. package/local_build.sh +70 -0
  151. package/media/click.mp3 +0 -0
  152. package/media/click.ogg +0 -0
  153. package/media/click.wav +0 -0
  154. package/media/comment-arrow-down.svg +10 -0
  155. package/media/comment-arrow-up.svg +10 -0
  156. package/media/delete-x.svg +10 -0
  157. package/media/delete.mp3 +0 -0
  158. package/media/delete.ogg +0 -0
  159. package/media/delete.wav +0 -0
  160. package/media/dropdown-arrow-dark.svg +1 -0
  161. package/media/dropdown-arrow.svg +1 -0
  162. package/media/extensions/microbit-block-icon.svg +130 -0
  163. package/media/extensions/music-block-icon.svg +17 -0
  164. package/media/extensions/pen-block-icon.svg +19 -0
  165. package/media/extensions/wedo2-block-icon.svg +36 -0
  166. package/media/eyedropper.svg +22 -0
  167. package/media/green-flag.svg +17 -0
  168. package/media/handclosed.cur +0 -0
  169. package/media/handdelete.cur +0 -0
  170. package/media/handopen.cur +0 -0
  171. package/media/icons/arrow.svg +12 -0
  172. package/media/icons/arrow_button.svg +1 -0
  173. package/media/icons/control_forever.svg +1 -0
  174. package/media/icons/control_repeat.svg +1 -0
  175. package/media/icons/control_stop.svg +1 -0
  176. package/media/icons/control_wait.svg +1 -0
  177. package/media/icons/event_broadcast_blue.svg +1 -0
  178. package/media/icons/event_broadcast_coral.svg +1 -0
  179. package/media/icons/event_broadcast_green.svg +1 -0
  180. package/media/icons/event_broadcast_magenta.svg +1 -0
  181. package/media/icons/event_broadcast_orange.svg +1 -0
  182. package/media/icons/event_broadcast_purple.svg +1 -0
  183. package/media/icons/event_when-broadcast-received_blue.svg +1 -0
  184. package/media/icons/event_when-broadcast-received_coral.svg +1 -0
  185. package/media/icons/event_when-broadcast-received_green.svg +1 -0
  186. package/media/icons/event_when-broadcast-received_magenta.svg +1 -0
  187. package/media/icons/event_when-broadcast-received_orange.svg +1 -0
  188. package/media/icons/event_when-broadcast-received_purple.svg +1 -0
  189. package/media/icons/event_whenflagclicked.svg +1 -0
  190. package/media/icons/remove.svg +19 -0
  191. package/media/icons/set-led_blue.svg +1 -0
  192. package/media/icons/set-led_coral.svg +1 -0
  193. package/media/icons/set-led_green.svg +1 -0
  194. package/media/icons/set-led_magenta.svg +1 -0
  195. package/media/icons/set-led_mystery.svg +1 -0
  196. package/media/icons/set-led_orange.svg +1 -0
  197. package/media/icons/set-led_purple.svg +1 -0
  198. package/media/icons/set-led_white.svg +1 -0
  199. package/media/icons/set-led_yellow.svg +1 -0
  200. package/media/icons/wedo_motor-clockwise.svg +1 -0
  201. package/media/icons/wedo_motor-counterclockwise.svg +1 -0
  202. package/media/icons/wedo_motor-speed_fast.svg +1 -0
  203. package/media/icons/wedo_motor-speed_med.svg +1 -0
  204. package/media/icons/wedo_motor-speed_slow.svg +1 -0
  205. package/media/icons/wedo_when-distance_close.svg +1 -0
  206. package/media/icons/wedo_when-tilt-backward.svg +1 -0
  207. package/media/icons/wedo_when-tilt-forward.svg +1 -0
  208. package/media/icons/wedo_when-tilt-left.svg +1 -0
  209. package/media/icons/wedo_when-tilt-right.svg +1 -0
  210. package/media/icons/wedo_when-tilt.svg +1 -0
  211. package/media/repeat.svg +18 -0
  212. package/media/rotate-left.svg +1 -0
  213. package/media/rotate-right.svg +1 -0
  214. package/media/sprites.png +0 -0
  215. package/media/status-not-ready.svg +13 -0
  216. package/media/status-ready.svg +13 -0
  217. package/media/zoom-in.svg +1 -0
  218. package/media/zoom-out.svg +1 -0
  219. package/media/zoom-reset.svg +1 -0
  220. package/msg/js/en.js +290 -0
  221. package/msg/json/en.json +285 -0
  222. package/msg/messages.js +359 -0
  223. package/msg/scratch_msgs.js +22969 -0
  224. package/package.json +60 -0
  225. package/pull_from_blockly.sh +151 -0
  226. package/renovate.json5 +15 -0
  227. package/shim/blockly_compressed_horizontal-blocks_compressed.js +1 -0
  228. package/shim/blockly_compressed_horizontal.Blockly.js +1 -0
  229. package/shim/blockly_compressed_horizontal.goog.js +1 -0
  230. package/shim/blockly_compressed_horizontal.js +1 -0
  231. package/shim/blockly_compressed_vertical-blocks_compressed.js +1 -0
  232. package/shim/blockly_compressed_vertical.Blockly.js +1 -0
  233. package/shim/blockly_compressed_vertical.goog.js +1 -0
  234. package/shim/blockly_compressed_vertical.js +1 -0
  235. package/shim/blocks_compressed_horizontal-blockly_compressed_horizontal-messages.js +1 -0
  236. package/shim/blocks_compressed_horizontal.js +1 -0
  237. package/shim/blocks_compressed_vertical-blockly_compressed_vertical-messages.js +1 -0
  238. package/shim/blocks_compressed_vertical.js +1 -0
  239. package/shim/gh-pages.js +1 -0
  240. package/shim/horizontal.js +1 -0
  241. package/shim/index.js +17 -0
  242. package/shim/vertical.js +1 -0
  243. package/universal-python.js +82 -0
@@ -0,0 +1,850 @@
1
+ /**
2
+ * @license
3
+ * Visual Blocks Editor
4
+ *
5
+ * Copyright 2018 Massachusetts Institute of Technology
6
+ * All rights reserved.
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */
20
+
21
+ /**
22
+ * @fileoverview Note input field, for selecting a musical note on a piano.
23
+ * @author ericr@media.mit.edu (Eric Rosenbaum)
24
+ */
25
+ 'use strict';
26
+
27
+ goog.provide('Blockly.FieldNote');
28
+
29
+ goog.require('Blockly.DropDownDiv');
30
+ goog.require('Blockly.FieldTextInput');
31
+ goog.require('goog.math');
32
+ goog.require('goog.userAgent');
33
+
34
+ /**
35
+ * Class for a note input field, for selecting a musical note on a piano.
36
+ * @param {(string|number)=} opt_value The initial content of the field. The
37
+ * value should cast to a number, and if it does not, '0' will be used.
38
+ * @param {Function=} opt_validator An optional function that is called
39
+ * to validate any constraints on what the user entered. Takes the new
40
+ * text as an argument and returns the accepted text or null to abort
41
+ * the change.
42
+ * @extends {Blockly.FieldTextInput}
43
+ * @constructor
44
+ */
45
+ Blockly.FieldNote = function(opt_value, opt_validator) {
46
+ opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
47
+ Blockly.FieldNote.superClass_.constructor.call(
48
+ this, opt_value, opt_validator);
49
+ this.addArgType('note');
50
+
51
+ /**
52
+ * Width of the field. Computed when drawing it, and used for animation.
53
+ * @type {number}
54
+ * @private
55
+ */
56
+ this.fieldEditorWidth_ = 0;
57
+
58
+ /**
59
+ * Height of the field. Computed when drawing it.
60
+ * @type {number}
61
+ * @private
62
+ */
63
+ this.fieldEditorHeight_ = 0;
64
+
65
+ /**
66
+ * The piano SVG.
67
+ * @type {SVGElement}
68
+ * @private
69
+ */
70
+ this.pianoSVG_ = null;
71
+
72
+ /**
73
+ * Array of SVG elements representing the clickable piano keys.
74
+ * @type {!Array<SVGElement>}
75
+ * @private
76
+ */
77
+ this.keySVGs_ = [];
78
+
79
+ /**
80
+ * Note name indicator at the top of the field.
81
+ * @type {SVGElement}
82
+ * @private
83
+ */
84
+ this.noteNameText_ = null;
85
+
86
+ /**
87
+ * Note name indicator on the low C key.
88
+ * @type {SVGElement}
89
+ * @private
90
+ */
91
+ this.lowCText_ = null;
92
+
93
+ /**
94
+ * Note name indicator on the low C key.
95
+ * @type {SVGElement}
96
+ * @private
97
+ */
98
+ this.highCText_ = null;
99
+
100
+ /**
101
+ * Octave number of the currently displayed range of keys.
102
+ * @type {number}
103
+ * @private
104
+ */
105
+ this.displayedOctave_ = null;
106
+
107
+ /**
108
+ * Current animation position of the piano SVG, as it shifts left or right to
109
+ * change octaves.
110
+ * @type {number}
111
+ * @private
112
+ */
113
+ this.animationPos_ = 0;
114
+
115
+ /**
116
+ * Target position for the animation as the piano SVG shifts left or right.
117
+ * @type {number}
118
+ * @private
119
+ */
120
+ this.animationTarget_ = 0;
121
+
122
+ /**
123
+ * A flag indicating that the mouse is currently down. Used in combination with
124
+ * mouse enter events to update the key selection while dragging.
125
+ * @type {boolean}
126
+ * @private
127
+ */
128
+ this.mouseIsDown_ = false;
129
+
130
+ /**
131
+ * An array of wrappers for mouse down events on piano keys.
132
+ * @type {!Array.<!Array>}
133
+ * @private
134
+ */
135
+ this.mouseDownWrappers_ = [];
136
+
137
+ /**
138
+ * A wrapper for the mouse up event.
139
+ * @type {!Array.<!Array>}
140
+ * @private
141
+ */
142
+ this.mouseUpWrapper_ = null;
143
+
144
+ /**
145
+ * An array of wrappers for mouse enter events on piano keys.
146
+ * @type {!Array.<!Array>}
147
+ * @private
148
+ */
149
+ this.mouseEnterWrappers_ = [];
150
+
151
+ /**
152
+ * A wrapper for the mouse down event on the octave down button.
153
+ * @type {!Array.<!Array>}
154
+ * @private
155
+ */
156
+ this.octaveDownMouseDownWrapper_ = null;
157
+
158
+ /**
159
+ * A wrapper for the mouse down event on the octave up button.
160
+ * @type {!Array.<!Array>}
161
+ * @private
162
+ */
163
+ this.octaveUpMouseDownWrapper_ = null;
164
+ };
165
+ goog.inherits(Blockly.FieldNote, Blockly.FieldTextInput);
166
+
167
+ /**
168
+ * Inset in pixels of content displayed in the field, caused by parent properties.
169
+ * The inset is actually determined by the CSS property blocklyDropDownDiv- it is
170
+ * the sum of the padding and border thickness.
171
+ */
172
+ Blockly.FieldNote.INSET = 5;
173
+
174
+ /**
175
+ * Height of the top area of the field, in px.
176
+ * @type {number}
177
+ * @const
178
+ */
179
+ Blockly.FieldNote.TOP_MENU_HEIGHT = 32 - Blockly.FieldNote.INSET;
180
+
181
+ /**
182
+ * Padding on the top and sides of the field, in px.
183
+ * @type {number}
184
+ * @const
185
+ */
186
+ Blockly.FieldNote.EDGE_PADDING = 1;
187
+
188
+ /**
189
+ * Height of the drop shadow on the piano, in px.
190
+ * @type {number}
191
+ * @const
192
+ */
193
+ Blockly.FieldNote.SHADOW_HEIGHT = 4;
194
+
195
+ /**
196
+ * Color for the shadow on the piano.
197
+ * @type {string}
198
+ * @const
199
+ */
200
+ Blockly.FieldNote.SHADOW_COLOR = '#000';
201
+
202
+ /**
203
+ * Opacity for the shadow on the piano.
204
+ * @type {string}
205
+ * @const
206
+ */
207
+ Blockly.FieldNote.SHADOW_OPACITY = .2;
208
+
209
+ /**
210
+ * A color for the white piano keys.
211
+ * @type {string}
212
+ * @const
213
+ */
214
+ Blockly.FieldNote.WHITE_KEY_COLOR = '#FFFFFF';
215
+
216
+ /**
217
+ * A color for the black piano keys.
218
+ * @type {string}
219
+ * @const
220
+ */
221
+ Blockly.FieldNote.BLACK_KEY_COLOR = '#323133';
222
+
223
+ /**
224
+ * A color for stroke around black piano keys.
225
+ * @type {string}
226
+ * @const
227
+ */
228
+ Blockly.FieldNote.BLACK_KEY_STROKE = '#555555';
229
+
230
+ /**
231
+ * A color for the selected state of a piano key.
232
+ * @type {string}
233
+ * @const
234
+ */
235
+ Blockly.FieldNote.KEY_SELECTED_COLOR = '#b0d6ff';
236
+
237
+ /**
238
+ * The number of white keys in one octave on the piano.
239
+ * @type {number}
240
+ * @const
241
+ */
242
+ Blockly.FieldNote.NUM_WHITE_KEYS = 8;
243
+
244
+ /**
245
+ * Height of a white piano key, in px.
246
+ * @type {string}
247
+ * @const
248
+ */
249
+ Blockly.FieldNote.WHITE_KEY_HEIGHT = 72;
250
+
251
+ /**
252
+ * Width of a white piano key, in px.
253
+ * @type {string}
254
+ * @const
255
+ */
256
+ Blockly.FieldNote.WHITE_KEY_WIDTH = 40;
257
+
258
+ /**
259
+ * Height of a black piano key, in px.
260
+ * @type {string}
261
+ * @const
262
+ */
263
+ Blockly.FieldNote.BLACK_KEY_HEIGHT = 40;
264
+
265
+ /**
266
+ * Width of a black piano key, in px.
267
+ * @type {string}
268
+ * @const
269
+ */
270
+ Blockly.FieldNote.BLACK_KEY_WIDTH = 32;
271
+
272
+ /**
273
+ * Radius of the curved bottom corner of a piano key, in px.
274
+ * @type {string}
275
+ * @const
276
+ */
277
+ Blockly.FieldNote.KEY_RADIUS = 6;
278
+
279
+ /**
280
+ * Bottom padding for the labels on C keys.
281
+ * @type {string}
282
+ * @const
283
+ */
284
+ Blockly.FieldNote.KEY_LABEL_PADDING = 8;
285
+
286
+ /**
287
+ * An array of objects with data describing the keys on the piano.
288
+ * @type {Array.<{name: String, pitch: Number, isBlack: boolean}>}
289
+ * @const
290
+ */
291
+ Blockly.FieldNote.KEY_INFO = [
292
+ {name: 'C', pitch: 0},
293
+ {name: 'C♯', pitch: 1, isBlack: true},
294
+ {name: 'D', pitch: 2},
295
+ {name: 'E♭', pitch: 3, isBlack: true},
296
+ {name: 'E', pitch: 4},
297
+ {name: 'F', pitch: 5},
298
+ {name: 'F♯', pitch: 6, isBlack: true},
299
+ {name: 'G', pitch: 7},
300
+ {name: 'G♯', pitch: 8, isBlack: true},
301
+ {name: 'A', pitch: 9},
302
+ {name: 'B♭', pitch: 10, isBlack: true},
303
+ {name: 'B', pitch: 11},
304
+ {name: 'C', pitch: 12}
305
+ ];
306
+
307
+ /**
308
+ * The MIDI note number of the highest note selectable on the piano.
309
+ * @type {number}
310
+ * @const
311
+ */
312
+ Blockly.FieldNote.MAX_NOTE = 130;
313
+
314
+ /**
315
+ * The fraction of the distance to the target location to move the piano at each
316
+ * step of the animation.
317
+ * @type {number}
318
+ * @const
319
+ */
320
+ Blockly.FieldNote.ANIMATION_FRACTION = 0.2;
321
+
322
+ /**
323
+ * Path to the arrow svg icon, used on the octave buttons.
324
+ * @type {string}
325
+ * @const
326
+ */
327
+ Blockly.FieldNote.ARROW_SVG_PATH = 'icons/arrow_button.svg';
328
+
329
+ /**
330
+ * The size of the square octave buttons.
331
+ * @type {number}
332
+ * @const
333
+ */
334
+ Blockly.FieldNote.OCTAVE_BUTTON_SIZE = 32;
335
+
336
+ /**
337
+ * Construct a FieldNote from a JSON arg object.
338
+ * @param {!Object} options A JSON object with options.
339
+ * @returns {!Blockly.FieldNote} The new field instance.
340
+ * @package
341
+ * @nocollapse
342
+ */
343
+ Blockly.FieldNote.fromJson = function(options) {
344
+ return new Blockly.FieldNote(options['note']);
345
+ };
346
+
347
+ /**
348
+ * Clean up this FieldNote, as well as the inherited FieldTextInput.
349
+ * @return {!Function} Closure to call on destruction of the WidgetDiv.
350
+ * @private
351
+ */
352
+ Blockly.FieldNote.prototype.dispose_ = function() {
353
+ var thisField = this;
354
+ return function() {
355
+ Blockly.FieldNote.superClass_.dispose_.call(thisField)();
356
+ thisField.mouseDownWrappers_.forEach(function(wrapper) {
357
+ Blockly.unbindEvent_(wrapper);
358
+ });
359
+ thisField.mouseEnterWrappers_.forEach(function(wrapper) {
360
+ Blockly.unbindEvent_(wrapper);
361
+ });
362
+ if (thisField.mouseUpWrapper_) {
363
+ Blockly.unbindEvent_(thisField.mouseUpWrapper_);
364
+ }
365
+ if (thisField.octaveDownMouseDownWrapper_) {
366
+ Blockly.unbindEvent_(thisField.octaveDownMouseDownWrapper_);
367
+ }
368
+ if (thisField.octaveUpMouseDownWrapper_) {
369
+ Blockly.unbindEvent_(thisField.octaveUpMouseDownWrapper_);
370
+ }
371
+ this.pianoSVG_ = null;
372
+ this.keySVGs_.length = 0;
373
+ this.noteNameText_ = null;
374
+ this.lowCText_ = null;
375
+ this.highCText_ = null;
376
+ };
377
+ };
378
+
379
+ /**
380
+ * Show a field with piano keys.
381
+ * @private
382
+ */
383
+ Blockly.FieldNote.prototype.showEditor_ = function() {
384
+ // Mobile browsers have issues with in-line textareas (focus & keyboards).
385
+ Blockly.FieldNote.superClass_.showEditor_.call(this, this.useTouchInteraction_);
386
+
387
+ // If there is an existing drop-down someone else owns, hide it immediately and clear it.
388
+ Blockly.DropDownDiv.hideWithoutAnimation();
389
+ Blockly.DropDownDiv.clearContent();
390
+
391
+ // Build the SVG DOM.
392
+ var div = Blockly.DropDownDiv.getContentDiv();
393
+
394
+ this.fieldEditorWidth_ = Blockly.FieldNote.NUM_WHITE_KEYS * Blockly.FieldNote.WHITE_KEY_WIDTH +
395
+ Blockly.FieldNote.EDGE_PADDING;
396
+ this.fieldEditorHeight_ = Blockly.FieldNote.TOP_MENU_HEIGHT +
397
+ Blockly.FieldNote.WHITE_KEY_HEIGHT +
398
+ Blockly.FieldNote.EDGE_PADDING;
399
+
400
+ var svg = Blockly.utils.createSvgElement('svg', {
401
+ 'xmlns': 'http://www.w3.org/2000/svg',
402
+ 'xmlns:html': 'http://www.w3.org/1999/xhtml',
403
+ 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
404
+ 'version': '1.1',
405
+ 'height': this.fieldEditorHeight_ + 'px',
406
+ 'width': this.fieldEditorWidth_ + 'px'
407
+ }, div);
408
+
409
+ // Add the white and black keys
410
+ // Since we are adding the keys from left to right in order, they need
411
+ // to be in two groups in order to layer correctly.
412
+ this.pianoSVG_ = Blockly.utils.createSvgElement('g', {}, svg);
413
+ var whiteKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
414
+ var blackKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
415
+
416
+ // Add three piano octaves, so we can animate moving up or down an octave.
417
+ // Only the middle octave gets bound to events.
418
+ this.keySVGs_ = [];
419
+ this.addPianoOctave_(-this.fieldEditorWidth_ + Blockly.FieldNote.EDGE_PADDING,
420
+ whiteKeyGroup, blackKeyGroup, null);
421
+ this.addPianoOctave_(0, whiteKeyGroup, blackKeyGroup, this.keySVGs_);
422
+ this.addPianoOctave_(this.fieldEditorWidth_ - Blockly.FieldNote.EDGE_PADDING,
423
+ whiteKeyGroup, blackKeyGroup, null);
424
+
425
+ // Note name indicator at the top of the field
426
+ this.noteNameText_ = Blockly.utils.createSvgElement('text',
427
+ {
428
+ 'x': this.fieldEditorWidth_ / 2,
429
+ 'y': Blockly.FieldNote.TOP_MENU_HEIGHT / 2,
430
+ 'class': 'blocklyText',
431
+ 'text-anchor': 'middle',
432
+ 'dominant-baseline': 'middle',
433
+ }, svg);
434
+
435
+ // Note names on the low and high C keys
436
+ var lowCX = Blockly.FieldNote.WHITE_KEY_WIDTH / 2;
437
+ this.lowCText_ = this.addCKeyLabel_(lowCX, svg);
438
+ var highCX = lowCX + (Blockly.FieldNote.WHITE_KEY_WIDTH *
439
+ (Blockly.FieldNote.NUM_WHITE_KEYS - 1));
440
+ this.highCText_ = this.addCKeyLabel_(highCX, svg);
441
+
442
+ // Horizontal line at the top of the keys
443
+ Blockly.utils.createSvgElement('line',
444
+ {
445
+ 'stroke': this.sourceBlock_.getColourTertiary(),
446
+ 'x1': 0,
447
+ 'y1': Blockly.FieldNote.TOP_MENU_HEIGHT,
448
+ 'x2': this.fieldEditorWidth_,
449
+ 'y2': Blockly.FieldNote.TOP_MENU_HEIGHT
450
+ }, svg);
451
+
452
+ // Drop shadow at the top of the keys
453
+ Blockly.utils.createSvgElement('rect',
454
+ {
455
+ 'x': 0,
456
+ 'y': Blockly.FieldNote.TOP_MENU_HEIGHT,
457
+ 'width': this.fieldEditorWidth_,
458
+ 'height': Blockly.FieldNote.SHADOW_HEIGHT,
459
+ 'fill': Blockly.FieldNote.SHADOW_COLOR,
460
+ 'fill-opacity': Blockly.FieldNote.SHADOW_OPACITY
461
+ }, svg);
462
+
463
+ // Octave buttons
464
+ this.octaveDownButton = this.addOctaveButton_(0, true, svg);
465
+ this.octaveUpButton = this.addOctaveButton_(
466
+ (this.fieldEditorWidth_ + Blockly.FieldNote.INSET * 2) -
467
+ Blockly.FieldNote.OCTAVE_BUTTON_SIZE, false, svg);
468
+
469
+ this.octaveDownMouseDownWrapper_ =
470
+ Blockly.bindEvent_(this.octaveDownButton, 'mousedown', this, function() {
471
+ this.changeOctaveBy_(-1);
472
+ });
473
+ this.octaveUpMouseDownWrapper_ =
474
+ Blockly.bindEvent_(this.octaveUpButton, 'mousedown', this,function() {
475
+ this.changeOctaveBy_(1);
476
+ });
477
+ Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(),
478
+ this.sourceBlock_.getColourTertiary());
479
+ Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
480
+ Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
481
+
482
+ this.updateSelection_();
483
+ };
484
+
485
+ /**
486
+ * Add one octave of piano keys drawn using SVG.
487
+ * @param {number} x The x position of the left edge of this octave of keys.
488
+ * @param {SVGElement} whiteKeyGroup The group for all white piano keys.
489
+ * @param {SvgElement} blackKeyGroup The group for all black piano keys.
490
+ * @param {!Array.<SvgElement>} keySVGarray An array containing all the key SVGs.
491
+ * @private
492
+ */
493
+ Blockly.FieldNote.prototype.addPianoOctave_ = function(x, whiteKeyGroup, blackKeyGroup, keySVGarray) {
494
+ var xIncrement, width, height, fill, stroke, group;
495
+ x += Blockly.FieldNote.EDGE_PADDING / 2;
496
+ var y = Blockly.FieldNote.TOP_MENU_HEIGHT;
497
+ for (var i = 0; i < Blockly.FieldNote.KEY_INFO.length; i++) {
498
+ // Draw a black or white key
499
+ if (Blockly.FieldNote.KEY_INFO[i].isBlack) {
500
+ // Black keys are shifted back half a key
501
+ x -= Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
502
+ xIncrement = Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
503
+ width = Blockly.FieldNote.BLACK_KEY_WIDTH;
504
+ height = Blockly.FieldNote.BLACK_KEY_HEIGHT;
505
+ fill = Blockly.FieldNote.BLACK_KEY_COLOR;
506
+ stroke = Blockly.FieldNote.BLACK_KEY_STROKE;
507
+ group = blackKeyGroup;
508
+ } else {
509
+ xIncrement = Blockly.FieldNote.WHITE_KEY_WIDTH;
510
+ width = Blockly.FieldNote.WHITE_KEY_WIDTH;
511
+ height = Blockly.FieldNote.WHITE_KEY_HEIGHT;
512
+ fill = Blockly.FieldNote.WHITE_KEY_COLOR;
513
+ stroke = this.sourceBlock_.getColourTertiary();
514
+ group = whiteKeyGroup;
515
+ }
516
+ var attr = {
517
+ 'd': this.getPianoKeyPath_(x, y, width, height),
518
+ 'fill': fill,
519
+ 'stroke': stroke
520
+ };
521
+ x += xIncrement;
522
+
523
+ var keySVG = Blockly.utils.createSvgElement('path', attr, group);
524
+
525
+ if (keySVGarray) {
526
+ keySVGarray[i] = keySVG;
527
+ keySVG.setAttribute('data-pitch', Blockly.FieldNote.KEY_INFO[i].pitch);
528
+ keySVG.setAttribute('data-name', Blockly.FieldNote.KEY_INFO[i].name);
529
+ keySVG.setAttribute('data-isBlack', Blockly.FieldNote.KEY_INFO[i].isBlack);
530
+
531
+ this.mouseDownWrappers_[i] =
532
+ Blockly.bindEvent_(keySVG, 'mousedown', this, this.onMouseDownOnKey_);
533
+ this.mouseEnterWrappers_[i] =
534
+ Blockly.bindEvent_(keySVG, 'mouseenter', this, this.onMouseEnter_);
535
+ }
536
+ }
537
+ };
538
+
539
+ /**
540
+ * Construct the SVG path string for a piano key shape: a rectangle with rounded
541
+ * corners at the bottom.
542
+ * @param {number} x the x position for the key.
543
+ * @param {number} y the y position for the key.
544
+ * @param {number} width the width of the key.
545
+ * @param {number} height the height of the key.
546
+ * @returns {string} the SVG path as a string.
547
+ * @private
548
+ */
549
+ Blockly.FieldNote.prototype.getPianoKeyPath_ = function(x, y, width, height) {
550
+ return 'M' + x + ' ' + y + ' ' +
551
+ 'L' + x + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
552
+ 'Q' + x + ' ' + (y + height) + ' ' +
553
+ (x + Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
554
+ 'L' + (x + width - Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
555
+ 'Q' + (x + width) + ' ' + (y + height) + ' ' +
556
+ (x + width) + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
557
+ 'L' + (x + width) + ' ' + y + ' ' +
558
+ 'L' + x + ' ' + y;
559
+ };
560
+
561
+ /**
562
+ * Add a button for switching the displayed octave of the piano up or down.
563
+ * @param {number} x The x position of the button.
564
+ * @param {boolean} flipped If true, the icon should be flipped.
565
+ * @param {SvgElement} svg The svg element to add the buttons to.
566
+ * @returns {SvgElement} A group containing the button SVG elements.
567
+ * @private
568
+ */
569
+ Blockly.FieldNote.prototype.addOctaveButton_ = function(x, flipped, svg) {
570
+ var group = Blockly.utils.createSvgElement('g', {}, svg);
571
+ var imageSize = Blockly.FieldNote.OCTAVE_BUTTON_SIZE;
572
+ var arrow = Blockly.utils.createSvgElement('image',
573
+ {
574
+ 'width': imageSize,
575
+ 'height': imageSize,
576
+ 'x': x - Blockly.FieldNote.INSET,
577
+ 'y': -1 * Blockly.FieldNote.INSET
578
+ }, group);
579
+ arrow.setAttributeNS(
580
+ 'http://www.w3.org/1999/xlink',
581
+ 'xlink:href',
582
+ Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldNote.ARROW_SVG_PATH
583
+ );
584
+ Blockly.utils.createSvgElement('line',
585
+ {
586
+ 'stroke': this.sourceBlock_.getColourTertiary(),
587
+ 'x1': x - Blockly.FieldNote.INSET,
588
+ 'y1': 0,
589
+ 'x2': x - Blockly.FieldNote.INSET,
590
+ 'y2': Blockly.FieldNote.TOP_MENU_HEIGHT - Blockly.FieldNote.INSET
591
+ }, group);
592
+ if (flipped) {
593
+ var translateX = -1 * Blockly.FieldNote.OCTAVE_BUTTON_SIZE + (Blockly.FieldNote.INSET * 2);
594
+ group.setAttribute('transform', 'scale(-1, 1) ' +
595
+ 'translate(' + translateX + ', 0)');
596
+ }
597
+ return group;
598
+ };
599
+
600
+ /**
601
+ * Add an SVG text label for display on the C keys of the piano.
602
+ * @param {number} x The x position for the label.
603
+ * @param {SvgElement} svg The SVG element to add the label to.
604
+ * @returns {SvgElement} The SVG element containing the label.
605
+ * @private
606
+ */
607
+ Blockly.FieldNote.prototype.addCKeyLabel_ = function(x, svg) {
608
+ return Blockly.utils.createSvgElement('text',
609
+ {
610
+ 'x': x,
611
+ 'y': Blockly.FieldNote.TOP_MENU_HEIGHT + Blockly.FieldNote.WHITE_KEY_HEIGHT -
612
+ Blockly.FieldNote.KEY_LABEL_PADDING,
613
+ 'class': 'scratchNotePickerKeyLabel',
614
+ 'text-anchor': 'middle'
615
+ }, svg);
616
+ };
617
+
618
+ /**
619
+ * Set the visibility of the C key labels.
620
+ * @param {boolean} visible If true, set labels to be visible.
621
+ * @private
622
+ */
623
+ Blockly.FieldNote.prototype.setCKeyLabelsVisible_ = function(visible) {
624
+ if (visible) {
625
+ this.fadeSvgToOpacity_(this.lowCText_, 1);
626
+ this.fadeSvgToOpacity_(this.highCText_, 1);
627
+ } else {
628
+ this.fadeSvgToOpacity_(this.lowCText_, 0);
629
+ this.fadeSvgToOpacity_(this.highCText_, 0);
630
+ }
631
+ };
632
+
633
+ /**
634
+ * Animate an SVG to fade it in or out to a target opacity.
635
+ * @param {SvgElement} svg The SVG element to apply the fade to.
636
+ * @param {number} opacity The target opacity.
637
+ * @private
638
+ */
639
+ Blockly.FieldNote.prototype.fadeSvgToOpacity_ = function(svg, opacity) {
640
+ svg.setAttribute('style', 'opacity: ' + opacity + '; transition: opacity 0.1s;');
641
+ };
642
+
643
+ /**
644
+ * Handle the mouse down event on a piano key.
645
+ * @param {!Event} e Mouse down event.
646
+ * @private
647
+ */
648
+ Blockly.FieldNote.prototype.onMouseDownOnKey_ = function(e) {
649
+ this.mouseIsDown_ = true;
650
+ this.mouseUpWrapper_ = Blockly.bindEvent_(document.body, 'mouseup', this, this.onMouseUp_);
651
+ this.selectNoteWithMouseEvent_(e);
652
+ };
653
+
654
+ /**
655
+ * Handle the mouse up event following a mouse down on a piano key.
656
+ * @private
657
+ */
658
+ Blockly.FieldNote.prototype.onMouseUp_ = function() {
659
+ this.mouseIsDown_ = false;
660
+ Blockly.unbindEvent_(this.mouseUpWrapper_);
661
+ };
662
+
663
+ /**
664
+ * Handle the event when the mouse enters a piano key.
665
+ * @param {!Event} e Mouse enter event.
666
+ * @private
667
+ */
668
+ Blockly.FieldNote.prototype.onMouseEnter_ = function(e) {
669
+ if (this.mouseIsDown_) {
670
+ this.selectNoteWithMouseEvent_(e);
671
+ }
672
+ };
673
+
674
+ /**
675
+ * Use the data in a mouse event to select a new note, and play it.
676
+ * @param {!Event} e Mouse event.
677
+ * @private
678
+ */
679
+ Blockly.FieldNote.prototype.selectNoteWithMouseEvent_ = function(e) {
680
+ var newNoteNum = Number(e.target.getAttribute('data-pitch')) + this.displayedOctave_ * 12;
681
+ this.setNoteNum_(newNoteNum);
682
+ this.playNoteInternal_();
683
+ };
684
+
685
+ /**
686
+ * Play a note, by calling the externally overriden play note function.
687
+ * @private
688
+ */
689
+ Blockly.FieldNote.prototype.playNoteInternal_ = function() {
690
+ if (Blockly.FieldNote.playNote_) {
691
+ Blockly.FieldNote.playNote_(
692
+ this.getValue(),
693
+ this.sourceBlock_.parentBlock_.getCategory()
694
+ );
695
+ }
696
+ };
697
+
698
+ /**
699
+ * Function to play a musical note corresponding to the key selected.
700
+ * Overridden externally.
701
+ * @param {number} noteNum the MIDI note number to play.
702
+ * @param {string} id An id to select a scratch extension to play the note.
703
+ * @private
704
+ */
705
+ Blockly.FieldNote.playNote_ = function(/* noteNum, id*/) {
706
+ return;
707
+ };
708
+
709
+ /**
710
+ * Change the selected note by a number of octaves, and start the animation.
711
+ * @param {number} octaves The number of octaves to change by.
712
+ * @private
713
+ */
714
+ Blockly.FieldNote.prototype.changeOctaveBy_ = function(octaves) {
715
+ this.displayedOctave_ += octaves;
716
+ if (this.displayedOctave_ < 0) {
717
+ this.displayedOctave_ = 0;
718
+ return;
719
+ }
720
+ var maxOctave = Math.floor(Blockly.FieldNote.MAX_NOTE / 12);
721
+ if (this.displayedOctave_ > maxOctave) {
722
+ this.displayedOctave_ = maxOctave;
723
+ return;
724
+ }
725
+
726
+ var newNote = Number(this.getText()) + (octaves * 12);
727
+ this.setNoteNum_(newNote);
728
+
729
+ this.animationTarget_ = this.fieldEditorWidth_ * octaves * -1;
730
+ this.animationPos_ = 0;
731
+ this.stepOctaveAnimation_();
732
+ this.setCKeyLabelsVisible_(false);
733
+ };
734
+
735
+ /**
736
+ * Animate the piano up or down an octave by sliding it to the left or right.
737
+ * @private
738
+ */
739
+ Blockly.FieldNote.prototype.stepOctaveAnimation_ = function() {
740
+ var absDiff = Math.abs(this.animationPos_ - this.animationTarget_);
741
+ if (absDiff < 1) {
742
+ this.pianoSVG_.setAttribute('transform', 'translate(0, 0)');
743
+ this.setCKeyLabelsVisible_(true);
744
+ this.playNoteInternal_();
745
+ return;
746
+ }
747
+ this.animationPos_ += (this.animationTarget_ - this.animationPos_) *
748
+ Blockly.FieldNote.ANIMATION_FRACTION;
749
+ this.pianoSVG_.setAttribute('transform', 'translate(' + this.animationPos_ + ',0)');
750
+ requestAnimationFrame(this.stepOctaveAnimation_.bind(this));
751
+ };
752
+
753
+ /**
754
+ * Set the selected note number, and update the piano display and the input field.
755
+ * @param {number} noteNum The MIDI note number to select.
756
+ * @private
757
+ */
758
+ Blockly.FieldNote.prototype.setNoteNum_ = function(noteNum) {
759
+ noteNum = this.callValidator(noteNum);
760
+ this.setValue(noteNum);
761
+ Blockly.FieldTextInput.htmlInput_.value = noteNum;
762
+ };
763
+
764
+ /**
765
+ * Sets the text in this field. Triggers a rerender of the source block, and
766
+ * updates the selection on the field.
767
+ * @param {?string} text New text.
768
+ */
769
+ Blockly.FieldNote.prototype.setText = function(text) {
770
+ Blockly.FieldNote.superClass_.setText.call(this, text);
771
+ if (!this.textElement_) {
772
+ // Not rendered yet.
773
+ return;
774
+ }
775
+ this.updateSelection_();
776
+ // Cached width is obsolete. Clear it.
777
+ this.size_.width = 0;
778
+ };
779
+
780
+ /**
781
+ * For a MIDI note number, find the index of the corresponding piano key.
782
+ * @param {number} noteNum The note number.
783
+ * @returns {number} The index of the piano key.
784
+ * @private
785
+ */
786
+ Blockly.FieldNote.prototype.noteNumToKeyIndex_ = function(noteNum) {
787
+ return Math.floor(noteNum) - (this.displayedOctave_ * 12);
788
+ };
789
+
790
+ /**
791
+ * Update the selected note and labels on the field.
792
+ * @private
793
+ */
794
+ Blockly.FieldNote.prototype.updateSelection_ = function() {
795
+ var noteNum = Number(this.getText());
796
+
797
+ // If the note is outside the currently displayed octave, update it
798
+ if (this.displayedOctave_ == null ||
799
+ noteNum > ((this.displayedOctave_ * 12) + 12) ||
800
+ noteNum < (this.displayedOctave_ * 12)) {
801
+ this.displayedOctave_ = Math.floor(noteNum / 12);
802
+ }
803
+
804
+ var index = this.noteNumToKeyIndex_(noteNum);
805
+
806
+ // Clear the highlight on all keys
807
+ this.keySVGs_.forEach(function(svg) {
808
+ var isBlack = svg.getAttribute('data-isBlack');
809
+ if (isBlack === 'true') {
810
+ svg.setAttribute('fill', Blockly.FieldNote.BLACK_KEY_COLOR);
811
+ } else {
812
+ svg.setAttribute('fill', Blockly.FieldNote.WHITE_KEY_COLOR);
813
+ }
814
+ });
815
+ // Set the highlight on the selected key
816
+ if (this.keySVGs_[index]) {
817
+ this.keySVGs_[index].setAttribute('fill', Blockly.FieldNote.KEY_SELECTED_COLOR);
818
+ // Update the note name text
819
+ var noteName = Blockly.FieldNote.KEY_INFO[index].name;
820
+ this.noteNameText_.textContent = noteName + ' (' + Math.floor(noteNum) + ')';
821
+ // Update the low and high C note names
822
+ var lowCNum = this.displayedOctave_ * 12;
823
+ this.lowCText_.textContent = 'C(' + lowCNum + ')';
824
+ this.highCText_.textContent = 'C(' + (lowCNum + 12) + ')';
825
+ }
826
+ };
827
+
828
+ /**
829
+ * Ensure that only a valid MIDI note number may be entered.
830
+ * @param {string} text The user's text.
831
+ * @return {?string} A string representing a valid note number, or null if invalid.
832
+ */
833
+ Blockly.FieldNote.prototype.classValidator = function(text) {
834
+ if (text === null) {
835
+ return null;
836
+ }
837
+ var n = parseFloat(text || 0);
838
+ if (isNaN(n)) {
839
+ return null;
840
+ }
841
+ if (n < 0) {
842
+ n = 0;
843
+ }
844
+ if (n > Blockly.FieldNote.MAX_NOTE) {
845
+ n = Blockly.FieldNote.MAX_NOTE;
846
+ }
847
+ return String(n);
848
+ };
849
+
850
+ Blockly.Field.register('field_note', Blockly.FieldNote);