pxt-core 7.5.7 → 7.5.10

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 (46) hide show
  1. package/built/pxt.js +1004 -4
  2. package/built/pxtblockly.js +432 -52
  3. package/built/pxtblocks.d.ts +34 -0
  4. package/built/pxtblocks.js +432 -52
  5. package/built/pxtlib.d.ts +20 -2
  6. package/built/pxtlib.js +127 -3
  7. package/built/pxtsim.d.ts +222 -0
  8. package/built/pxtsim.js +877 -1
  9. package/built/target.js +1 -1
  10. package/built/web/icons.css +49 -10
  11. package/built/web/main.js +1 -1
  12. package/built/web/pxtapp.js +1 -1
  13. package/built/web/pxtasseteditor.js +1 -1
  14. package/built/web/pxtblockly.js +1 -1
  15. package/built/web/pxtblocks.js +1 -1
  16. package/built/web/pxtembed.js +2 -2
  17. package/built/web/pxtlib.js +1 -1
  18. package/built/web/pxtsim.js +1 -1
  19. package/built/web/pxtworker.js +1 -1
  20. package/built/web/react-common-authcode.css +104 -1
  21. package/built/web/react-common-skillmap.css +1 -1
  22. package/built/web/rtlreact-common-skillmap.css +1 -1
  23. package/built/web/rtlsemantic.css +2 -2
  24. package/built/web/semantic.css +2 -2
  25. package/built/web/skillmap/js/main.b5f3628d.chunk.js +1 -0
  26. package/docfiles/footer.html +1 -1
  27. package/docfiles/script.html +1 -1
  28. package/docfiles/thin-footer.html +1 -1
  29. package/localtypings/pxtarget.d.ts +1 -0
  30. package/package.json +1 -1
  31. package/react-common/components/controls/Button.tsx +7 -3
  32. package/react-common/components/controls/DraggableGraph.tsx +242 -0
  33. package/react-common/components/controls/Dropdown.tsx +121 -0
  34. package/react-common/components/controls/FocusList.tsx +17 -8
  35. package/react-common/components/controls/Input.tsx +13 -3
  36. package/react-common/components/controls/RadioButtonGroup.tsx +66 -0
  37. package/react-common/components/util.tsx +23 -0
  38. package/react-common/styles/controls/DraggableGraph.less +13 -0
  39. package/react-common/styles/controls/Dropdown.less +64 -0
  40. package/react-common/styles/controls/RadioButtonGroup.less +36 -0
  41. package/react-common/styles/react-common-variables.less +24 -0
  42. package/react-common/styles/react-common.less +3 -0
  43. package/theme/pxt.less +1 -0
  44. package/theme/soundeffecteditor.less +132 -0
  45. package/webapp/public/skillmap.html +1 -1
  46. package/built/web/skillmap/js/main.2485091f.chunk.js +0 -1
@@ -4016,7 +4016,7 @@ var pxt;
4016
4016
  const call = e.stdCallTable[b.type];
4017
4017
  if (call.attrs.shim === "ENUM_GET" || call.attrs.shim === "KIND_GET")
4018
4018
  return;
4019
- visibleParams(call, countOptionals(b)).forEach((p, i) => {
4019
+ visibleParams(call, countOptionals(b, call)).forEach((p, i) => {
4020
4020
  const isInstance = call.isExtensionMethod && i === 0;
4021
4021
  if (p.definitionName && !b.getFieldValue(p.definitionName)) {
4022
4022
  let i = b.inputList.find((i) => i.name == p.definitionName);
@@ -4484,7 +4484,7 @@ var pxt;
4484
4484
  let call = e.stdCallTable[b.type];
4485
4485
  if (call) {
4486
4486
  if (call.imageLiteral)
4487
- expr = compileImage(e, b, call.imageLiteral, call.imageLiteralColumns, call.imageLiteralRows, call.namespace, call.f, visibleParams(call, countOptionals(b)).map(ar => compileArgument(e, b, ar, comments)));
4487
+ expr = compileImage(e, b, call.imageLiteral, call.imageLiteralColumns, call.imageLiteralRows, call.namespace, call.f, visibleParams(call, countOptionals(b, call)).map(ar => compileArgument(e, b, ar, comments)));
4488
4488
  else
4489
4489
  expr = compileStdCall(e, b, call, comments);
4490
4490
  }
@@ -4697,12 +4697,12 @@ var pxt;
4697
4697
  return blocks_1.mkStmt(blocks_1.mkInfix(ref, "+=", expr));
4698
4698
  }
4699
4699
  function eventArgs(call, b) {
4700
- return visibleParams(call, countOptionals(b)).filter(ar => !!ar.definitionName);
4700
+ return visibleParams(call, countOptionals(b, call)).filter(ar => !!ar.definitionName);
4701
4701
  }
4702
4702
  function compileCall(e, b, comments) {
4703
4703
  const call = e.stdCallTable[b.type];
4704
4704
  if (call.imageLiteral)
4705
- return blocks_1.mkStmt(compileImage(e, b, call.imageLiteral, call.imageLiteralColumns, call.imageLiteralRows, call.namespace, call.f, visibleParams(call, countOptionals(b)).map(ar => compileArgument(e, b, ar, comments))));
4705
+ return blocks_1.mkStmt(compileImage(e, b, call.imageLiteral, call.imageLiteralColumns, call.imageLiteralRows, call.namespace, call.f, visibleParams(call, countOptionals(b, call)).map(ar => compileArgument(e, b, ar, comments))));
4706
4706
  else if (call.hasHandler)
4707
4707
  return compileEvent(e, b, call, eventArgs(call, b), call.namespace, comments);
4708
4708
  else
@@ -4781,7 +4781,7 @@ var pxt;
4781
4781
  return blocks_1.H.mkPropertyAccess(b.getFieldValue("MEMBER"), blocks_1.mkText(info.name));
4782
4782
  }
4783
4783
  else {
4784
- args = visibleParams(func, countOptionals(b)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
4784
+ args = visibleParams(func, countOptionals(b, func)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
4785
4785
  }
4786
4786
  let callNamespace = func.namespace;
4787
4787
  let callName = func.f;
@@ -5435,7 +5435,14 @@ var pxt;
5435
5435
  }
5436
5436
  return blocks_1.mkStmt(blocks_1.mkText("let " + v.escapedName + tp + " = "), defl);
5437
5437
  }
5438
- function countOptionals(b) {
5438
+ function countOptionals(b, func) {
5439
+ if (func.attrs.compileHiddenArguments) {
5440
+ return func.comp.parameters.reduce((prev, block) => {
5441
+ if (block.isOptional)
5442
+ prev++;
5443
+ return prev;
5444
+ }, 0);
5445
+ }
5439
5446
  if (b.mutationToDom) {
5440
5447
  const el = b.mutationToDom();
5441
5448
  if (el.hasAttribute("_expanded")) {
@@ -5954,6 +5961,7 @@ var pxt;
5954
5961
  registerFieldEditor('protractor', pxtblockly.FieldProtractor);
5955
5962
  registerFieldEditor('position', pxtblockly.FieldPosition);
5956
5963
  registerFieldEditor('melody', pxtblockly.FieldCustomMelody);
5964
+ registerFieldEditor('soundeffect', pxtblockly.FieldSoundEffect);
5957
5965
  }
5958
5966
  blocks.initFieldEditors = initFieldEditors;
5959
5967
  function registerFieldEditor(selector, field, validator) {
@@ -9833,9 +9841,11 @@ var pxt;
9833
9841
  return pxt.Util.values(apis.byQName).filter(sym => sym.namespace === enumName && !sym.attributes.blockHidden);
9834
9842
  }
9835
9843
  function getFixedInstanceDropdownValues(apis, qName) {
9836
- return pxt.Util.values(apis.byQName).filter(sym => sym.kind === 4 /* Variable */
9844
+ const symbols = pxt.Util.values(apis.byQName).filter(sym => sym.kind === 4 /* Variable */
9837
9845
  && sym.attributes.fixedInstance
9838
- && isSubtype(apis, sym.retType, qName));
9846
+ && isSubtype(apis, sym.retType, qName))
9847
+ .sort((l, r) => (r.attributes.weight || 50) - (l.attributes.weight || 50));
9848
+ return symbols;
9839
9849
  }
9840
9850
  blocks_4.getFixedInstanceDropdownValues = getFixedInstanceDropdownValues;
9841
9851
  function generateIcons(instanceSymbols) {
@@ -10864,18 +10874,21 @@ var pxt;
10864
10874
  // by BlocklyLoader. The number makes it an invalid JS identifier
10865
10875
  const buttonAddName = "0_add_button";
10866
10876
  const buttonRemName = "0_rem_button";
10877
+ const buttonAddRemName = "0_add_rem_button";
10867
10878
  const numVisibleAttr = "_expanded";
10868
10879
  const inputInitAttr = "_input_init";
10869
10880
  const optionNames = def.parameters.map(p => p.name);
10870
10881
  const totalOptions = def.parameters.length;
10871
10882
  const buttonDelta = toggle ? totalOptions : 1;
10883
+ const variableInlineInputs = info.blocksById[b.type].attributes.inlineInputMode === "variable";
10884
+ const compileHiddenArguments = info.blocksById[b.type].attributes.compileHiddenArguments;
10872
10885
  const state = new MutationState(b);
10873
10886
  state.setEventsEnabled(false);
10874
10887
  state.setValue(numVisibleAttr, 0);
10875
10888
  state.setValue(inputInitAttr, false);
10876
10889
  state.setEventsEnabled(true);
10877
10890
  Blockly.Extensions.apply('inline-svgs', b, false);
10878
- addPlusButton();
10891
+ let updatingInputs = false;
10879
10892
  appendMutation(b, {
10880
10893
  mutationToDom: (el) => {
10881
10894
  // The reason we store the inputsInitialized variable separately from visibleOptions
@@ -10890,8 +10903,8 @@ var pxt;
10890
10903
  state.setEventsEnabled(false);
10891
10904
  if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
10892
10905
  state.setValue(inputInitAttr, true);
10893
- initOptionalInputs();
10894
10906
  }
10907
+ initOptionalInputs();
10895
10908
  if (saved.hasAttribute(numVisibleAttr)) {
10896
10909
  const val = parseInt(saved.getAttribute(numVisibleAttr));
10897
10910
  if (!isNaN(val)) {
@@ -10902,6 +10915,7 @@ var pxt;
10902
10915
  }
10903
10916
  else {
10904
10917
  state.setValue(numVisibleAttr, addDelta(delta));
10918
+ updateButtons();
10905
10919
  }
10906
10920
  }
10907
10921
  else {
@@ -10912,14 +10926,29 @@ var pxt;
10912
10926
  state.setEventsEnabled(true);
10913
10927
  }
10914
10928
  });
10915
- // Blockly only lets you hide an input once it is rendered, so we can't
10916
- // hide the inputs in init() or domToMutation(). This will get executed after
10917
- // the block is rendered
10918
- setTimeout(() => {
10919
- if (b.rendered && !b.workspace.isDragging()) {
10920
- updateShape(0, undefined, true);
10929
+ initOptionalInputs();
10930
+ if (compileHiddenArguments) {
10931
+ // Make sure all inputs have shadow blocks attached
10932
+ let optIndex = 0;
10933
+ for (let i = 0; i < b.inputList.length; i++) {
10934
+ const input = b.inputList[i];
10935
+ if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
10936
+ if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
10937
+ const param = comp.definitionNameToParam[def.parameters[optIndex].name];
10938
+ attachShadowBlock(input, param);
10939
+ }
10940
+ ++optIndex;
10941
+ }
10921
10942
  }
10922
- }, 1);
10943
+ }
10944
+ b.render = (opt_bubble) => {
10945
+ if (updatingInputs)
10946
+ return;
10947
+ updatingInputs = true;
10948
+ updateShape(0, undefined, true);
10949
+ updatingInputs = false;
10950
+ Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
10951
+ };
10923
10952
  // Set skipRender to true if the block is still initializing. Otherwise
10924
10953
  // the inputs will render before their shadow blocks are created and
10925
10954
  // leave behind annoying artifacts
@@ -10949,25 +10978,14 @@ var pxt;
10949
10978
  setInputVisible(input, visible);
10950
10979
  if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
10951
10980
  const param = comp.definitionNameToParam[def.parameters[optIndex].name];
10952
- let shadow = blocks.createShadowValue(info, param);
10953
- if (shadow.tagName.toLowerCase() === "value") {
10954
- // Unwrap the block
10955
- shadow = shadow.firstElementChild;
10956
- }
10957
- Blockly.Events.disable();
10958
- try {
10959
- const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
10960
- if (nb) {
10961
- input.connection.connect(nb.outputConnection);
10962
- }
10963
- }
10964
- catch (e) { }
10965
- Blockly.Events.enable();
10981
+ attachShadowBlock(input, param);
10966
10982
  }
10967
10983
  ++optIndex;
10968
10984
  }
10969
10985
  }
10970
10986
  updateButtons();
10987
+ if (variableInlineInputs)
10988
+ b.setInputsInline(visibleOptions < 4);
10971
10989
  if (!skipRender)
10972
10990
  b.render();
10973
10991
  }
@@ -10976,31 +10994,32 @@ var pxt;
10976
10994
  .appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
10977
10995
  }
10978
10996
  function updateButtons() {
10997
+ if (updatingInputs)
10998
+ return;
10979
10999
  const visibleOptions = state.getNumber(numVisibleAttr);
10980
11000
  const showPlus = visibleOptions !== totalOptions;
10981
11001
  const showMinus = visibleOptions !== 0;
10982
- const hasMinus = !!b.getInput(buttonRemName);
10983
- const hasPlus = !!b.getInput(buttonAddName);
10984
- if (!showPlus) {
11002
+ if (b.inputList.some(i => i.name === buttonAddName))
10985
11003
  b.removeInput(buttonAddName, true);
10986
- }
10987
- if (!showMinus) {
11004
+ if (b.inputList.some(i => i.name === buttonRemName))
10988
11005
  b.removeInput(buttonRemName, true);
11006
+ if (b.inputList.some(i => i.name === buttonAddRemName))
11007
+ b.removeInput(buttonAddRemName, true);
11008
+ if (showPlus && showMinus) {
11009
+ addPlusAndMinusButtons();
10989
11010
  }
10990
- if (showMinus && !hasMinus) {
10991
- addMinusButton();
11011
+ else if (showPlus) {
11012
+ addPlusButton();
10992
11013
  }
10993
- if (showPlus) {
10994
- // make sure plus button is last in line.
10995
- if (hasPlus && b.inputList.findIndex(el => el.name === buttonAddName) !== b.inputList.length - 1) {
10996
- b.removeInput(buttonAddName, true);
10997
- addPlusButton();
10998
- }
10999
- else if (!hasPlus) {
11000
- addPlusButton();
11001
- }
11014
+ else if (showMinus) {
11015
+ addMinusButton();
11002
11016
  }
11003
11017
  }
11018
+ function addPlusAndMinusButtons() {
11019
+ b.appendDummyInput(buttonAddRemName)
11020
+ .appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
11021
+ .appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
11022
+ }
11004
11023
  function addPlusButton() {
11005
11024
  addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
11006
11025
  }
@@ -11017,12 +11036,23 @@ var pxt;
11017
11036
  }
11018
11037
  function setInputVisible(input, visible) {
11019
11038
  // If the block isn't rendered, Blockly will crash
11020
- if (b.rendered) {
11021
- let renderList = input.setVisible(visible);
11022
- renderList.forEach((block) => {
11023
- block.render();
11024
- });
11039
+ input.setVisible(visible);
11040
+ }
11041
+ function attachShadowBlock(input, param) {
11042
+ let shadow = blocks.createShadowValue(info, param);
11043
+ if (shadow.tagName.toLowerCase() === "value") {
11044
+ // Unwrap the block
11045
+ shadow = shadow.firstElementChild;
11046
+ }
11047
+ Blockly.Events.disable();
11048
+ try {
11049
+ const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
11050
+ if (nb) {
11051
+ input.connection.connect(nb.outputConnection);
11052
+ }
11025
11053
  }
11054
+ catch (e) { }
11055
+ Blockly.Events.enable();
11026
11056
  }
11027
11057
  }
11028
11058
  blocks.initExpandableBlock = initExpandableBlock;
@@ -11362,6 +11392,17 @@ var pxtblockly;
11362
11392
  this.loaded = true;
11363
11393
  this.valueText = this.onValueChanged(this.valueText);
11364
11394
  }
11395
+ getAnchorDimensions() {
11396
+ const boundingBox = this.getScaledBBox();
11397
+ if (this.sourceBlock_.RTL) {
11398
+ boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
11399
+ }
11400
+ else {
11401
+ boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
11402
+ }
11403
+ return boundingBox;
11404
+ }
11405
+ ;
11365
11406
  isInitialized() {
11366
11407
  return !!this.fieldGroup_;
11367
11408
  }
@@ -11371,6 +11412,23 @@ var pxtblockly;
11371
11412
  setBlockData(value) {
11372
11413
  pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
11373
11414
  }
11415
+ getSiblingBlock(inputName, useGrandparent = false) {
11416
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
11417
+ if (!block || !block.inputList)
11418
+ return undefined;
11419
+ for (const input of block.inputList) {
11420
+ if (input.name === inputName) {
11421
+ return input.connection.targetBlock();
11422
+ }
11423
+ }
11424
+ return undefined;
11425
+ }
11426
+ getSiblingField(fieldName, useGrandparent = false) {
11427
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
11428
+ if (!block)
11429
+ return undefined;
11430
+ return block.getField(fieldName);
11431
+ }
11374
11432
  }
11375
11433
  pxtblockly.FieldBase = FieldBase;
11376
11434
  })(pxtblockly || (pxtblockly = {}));
@@ -15073,6 +15131,311 @@ var pxtblockly;
15073
15131
  }
15074
15132
  pxtblockly.FieldProtractor = FieldProtractor;
15075
15133
  })(pxtblockly || (pxtblockly = {}));
15134
+ /// <reference path="../../built/pxtlib.d.ts" />
15135
+ /// <reference path="./field_base.ts" />
15136
+ var pxtblockly;
15137
+ (function (pxtblockly) {
15138
+ var svg = pxt.svgUtil;
15139
+ const MUSIC_ICON_WIDTH = 20;
15140
+ const TOTAL_WIDTH = 160;
15141
+ const TOTAL_HEIGHT = 40;
15142
+ const X_PADDING = 5;
15143
+ const Y_PADDING = 4;
15144
+ const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
15145
+ class FieldSoundEffect extends pxtblockly.FieldBase {
15146
+ onInit() {
15147
+ if (!this.options)
15148
+ this.options = {};
15149
+ if (!this.options.durationInputName)
15150
+ this.options.durationInputName = "duration";
15151
+ if (!this.options.startFrequencyInputName)
15152
+ this.options.startFrequencyInputName = "startFrequency";
15153
+ if (!this.options.endFrequencyInputName)
15154
+ this.options.endFrequencyInputName = "endFrequency";
15155
+ if (!this.options.startVolumeInputName)
15156
+ this.options.startVolumeInputName = "startVolume";
15157
+ if (!this.options.endVolumeInputName)
15158
+ this.options.endVolumeInputName = "endVolume";
15159
+ if (!this.options.waveFieldName)
15160
+ this.options.waveFieldName = "waveShape";
15161
+ if (!this.options.interpolationFieldName)
15162
+ this.options.interpolationFieldName = "interpolation";
15163
+ if (!this.options.effectFieldName)
15164
+ this.options.effectFieldName = "effect";
15165
+ this.redrawPreview();
15166
+ }
15167
+ onDispose() {
15168
+ }
15169
+ onValueChanged(newValue) {
15170
+ return newValue;
15171
+ }
15172
+ redrawPreview() {
15173
+ if (!this.fieldGroup_)
15174
+ return;
15175
+ pxsim.U.clear(this.fieldGroup_);
15176
+ const bg = new svg.Rect()
15177
+ .at(X_PADDING, Y_PADDING)
15178
+ .size(TOTAL_WIDTH, TOTAL_HEIGHT)
15179
+ .setClass("blocklySpriteField")
15180
+ .stroke("#fff", 1)
15181
+ .corner(TOTAL_HEIGHT / 2);
15182
+ const clipPathId = "preview-clip-" + pxt.U.guidGen();
15183
+ const clip = new svg.ClipPath()
15184
+ .id(clipPathId)
15185
+ .clipPathUnits(false);
15186
+ const clipRect = new svg.Rect()
15187
+ .size(PREVIEW_WIDTH, TOTAL_HEIGHT)
15188
+ .fill("#FFF")
15189
+ .at(0, 0);
15190
+ clip.appendChild(clipRect);
15191
+ const path = new svg.Path()
15192
+ .stroke("grey", 2)
15193
+ .fill("none")
15194
+ .setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
15195
+ .clipPath("url('#" + clipPathId + "')");
15196
+ const g = new svg.Group()
15197
+ .translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
15198
+ g.appendChild(clip);
15199
+ g.appendChild(path);
15200
+ const musicIcon = new svg.Text("\uf001")
15201
+ .appendClass("melody-editor-field-icon")
15202
+ .setAttribute("alignment-baseline", "middle")
15203
+ .anchor("middle")
15204
+ .at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
15205
+ this.fieldGroup_.appendChild(bg.el);
15206
+ this.fieldGroup_.appendChild(musicIcon.el);
15207
+ this.fieldGroup_.appendChild(g.el);
15208
+ }
15209
+ showEditor_() {
15210
+ const initialSound = this.readCurrentSound();
15211
+ Blockly.Events.disable();
15212
+ Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, () => {
15213
+ fv.hide();
15214
+ widgetDiv.classList.remove("sound-effect-editor-widget");
15215
+ widgetDiv.style.transform = "";
15216
+ widgetDiv.style.position = "";
15217
+ widgetDiv.style.left = "";
15218
+ widgetDiv.style.top = "";
15219
+ widgetDiv.style.width = "";
15220
+ widgetDiv.style.height = "";
15221
+ widgetDiv.style.opacity = "";
15222
+ widgetDiv.style.transition = "";
15223
+ Blockly.Events.enable();
15224
+ Blockly.Events.setGroup(true);
15225
+ this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
15226
+ this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
15227
+ this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
15228
+ this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
15229
+ this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
15230
+ this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
15231
+ this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
15232
+ this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
15233
+ Blockly.Events.setGroup(false);
15234
+ if (this.mostRecentValue)
15235
+ this.setBlockData(JSON.stringify(this.mostRecentValue));
15236
+ });
15237
+ const widgetDiv = Blockly.WidgetDiv.DIV;
15238
+ const opts = {
15239
+ onClose: () => {
15240
+ fv.hide();
15241
+ Blockly.WidgetDiv.hideIfOwner(this);
15242
+ },
15243
+ onSoundChange: (newSound) => {
15244
+ this.mostRecentValue = newSound;
15245
+ this.updateSiblingBlocks(newSound);
15246
+ this.redrawPreview();
15247
+ },
15248
+ initialSound: initialSound
15249
+ };
15250
+ const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
15251
+ const block = this.sourceBlock_;
15252
+ const bounds = block.getBoundingRectangle();
15253
+ const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
15254
+ const animationDistance = 20;
15255
+ const left = coord.x + 20;
15256
+ const top = coord.y - animationDistance;
15257
+ widgetDiv.style.opacity = "0";
15258
+ widgetDiv.classList.add("sound-effect-editor-widget");
15259
+ widgetDiv.style.position = "absolute";
15260
+ widgetDiv.style.left = left + "px";
15261
+ widgetDiv.style.top = top + "px";
15262
+ widgetDiv.style.width = "30rem";
15263
+ widgetDiv.style.height = "40rem";
15264
+ widgetDiv.style.display = "block";
15265
+ widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
15266
+ fv.onHide(() => {
15267
+ // do nothing
15268
+ });
15269
+ fv.show();
15270
+ const divBounds = widgetDiv.getBoundingClientRect();
15271
+ const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
15272
+ if (divBounds.height > injectDivBounds.height) {
15273
+ widgetDiv.style.height = "";
15274
+ widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
15275
+ widgetDiv.style.bottom = "1rem";
15276
+ }
15277
+ else {
15278
+ if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
15279
+ // This editor is pretty tall, so just center vertically on the inject div
15280
+ widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
15281
+ }
15282
+ }
15283
+ if (divBounds.width > injectDivBounds.width) {
15284
+ widgetDiv.style.width = "";
15285
+ widgetDiv.style.left = "1rem";
15286
+ widgetDiv.style.right = "1rem";
15287
+ }
15288
+ else {
15289
+ // Check to see if we are bleeding off the right side of the canvas
15290
+ if (divBounds.left + divBounds.width >= injectDivBounds.right) {
15291
+ // If so, try and place to the left of the block instead of the right
15292
+ const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
15293
+ const toolboxWidth = block.workspace.getToolbox().getWidth();
15294
+ const workspaceLeft = injectDivBounds.left + toolboxWidth;
15295
+ if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
15296
+ widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
15297
+ }
15298
+ else {
15299
+ // As a last resort, just center on the inject div
15300
+ widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
15301
+ }
15302
+ }
15303
+ }
15304
+ requestAnimationFrame(() => {
15305
+ widgetDiv.style.opacity = "1";
15306
+ widgetDiv.style.transform = `translateY(${animationDistance}px)`;
15307
+ });
15308
+ }
15309
+ render_() {
15310
+ super.render_();
15311
+ this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
15312
+ this.size_.width = TOTAL_WIDTH;
15313
+ }
15314
+ updateSiblingBlocks(sound) {
15315
+ this.setNumberInputValue(this.options.durationInputName, sound.duration);
15316
+ this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
15317
+ this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
15318
+ this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
15319
+ this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
15320
+ this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
15321
+ this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
15322
+ this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
15323
+ }
15324
+ setNumberInputValue(name, value) {
15325
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15326
+ if (!block)
15327
+ return;
15328
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15329
+ block.setFieldValue(Math.round(value), "NUM");
15330
+ }
15331
+ else if (block.type === "math_number_minmax") {
15332
+ block.setFieldValue(Math.round(value), "SLIDER");
15333
+ }
15334
+ }
15335
+ getNumberInputValue(name, defaultValue) {
15336
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15337
+ if (!block)
15338
+ return defaultValue;
15339
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15340
+ return parseInt(block.getFieldValue("NUM") + "");
15341
+ }
15342
+ else if (block.type === "math_number_minmax") {
15343
+ return parseInt(block.getFieldValue("SLIDER") + "");
15344
+ }
15345
+ return defaultValue;
15346
+ }
15347
+ fireNumberInputUpdate(name, oldValue) {
15348
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15349
+ if (!block)
15350
+ return;
15351
+ let fieldName;
15352
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15353
+ fieldName = "NUM";
15354
+ }
15355
+ else if (block.type === "math_number_minmax") {
15356
+ fieldName = "SLIDER";
15357
+ }
15358
+ if (!fieldName)
15359
+ return;
15360
+ Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
15361
+ }
15362
+ setFieldDropdownValue(name, value) {
15363
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15364
+ if (!field)
15365
+ return;
15366
+ field.setValue(value);
15367
+ }
15368
+ getFieldDropdownValue(name) {
15369
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15370
+ if (!field)
15371
+ return undefined;
15372
+ return field.getValue();
15373
+ }
15374
+ fireFieldDropdownUpdate(name, oldValue) {
15375
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15376
+ if (!field)
15377
+ return;
15378
+ Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
15379
+ }
15380
+ readCurrentSound() {
15381
+ const savedSound = this.readBlockDataSound();
15382
+ return {
15383
+ duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
15384
+ startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
15385
+ endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
15386
+ startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
15387
+ endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
15388
+ wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
15389
+ interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
15390
+ effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
15391
+ };
15392
+ }
15393
+ // This stores the values of the fields in case a block (e.g. a variable) is placed in one
15394
+ // of the inputs.
15395
+ readBlockDataSound() {
15396
+ const data = this.getBlockData();
15397
+ let sound;
15398
+ try {
15399
+ sound = JSON.parse(data);
15400
+ }
15401
+ catch (e) {
15402
+ sound = {
15403
+ duration: 1000,
15404
+ startFrequency: 100,
15405
+ endFrequency: 1800,
15406
+ startVolume: 1023,
15407
+ endVolume: 0,
15408
+ wave: "sine",
15409
+ interpolation: "linear",
15410
+ effect: "none"
15411
+ };
15412
+ }
15413
+ return sound;
15414
+ }
15415
+ }
15416
+ pxtblockly.FieldSoundEffect = FieldSoundEffect;
15417
+ const waveformMapping = {
15418
+ "sine": "WaveShape.Sine",
15419
+ "square": "WaveShape.Square",
15420
+ "sawtooth": "WaveShape.Sawtooth",
15421
+ "triangle": "WaveShape.Triangle",
15422
+ "noise": "WaveShape.Noise",
15423
+ };
15424
+ const effectMapping = {
15425
+ "none": "SoundExpressionEffect.None",
15426
+ "vibrato": "SoundExpressionEffect.Vibrato",
15427
+ "tremolo": "SoundExpressionEffect.Tremolo",
15428
+ "warble": "SoundExpressionEffect.Warble",
15429
+ };
15430
+ const interpolationMapping = {
15431
+ "linear": "InterpolationCurve.Linear",
15432
+ "curve": "InterpolationCurve.Curve",
15433
+ "logarithmic": "InterpolationCurve.Logarithmic",
15434
+ };
15435
+ function reverseLookup(map, value) {
15436
+ return Object.keys(map).find(k => map[k] === value);
15437
+ }
15438
+ })(pxtblockly || (pxtblockly = {}));
15076
15439
  /// <reference path="../../localtypings/blockly.d.ts"/>
15077
15440
  /// <reference path="../../built/pxtsim.d.ts"/>
15078
15441
  var pxtblockly;
@@ -16538,4 +16901,21 @@ var pxtblockly;
16538
16901
  }
16539
16902
  }
16540
16903
  pxtblockly.getTemporaryAssets = getTemporaryAssets;
16904
+ function workspaceToScreenCoordinates(ws, wsCoordinates) {
16905
+ // The position in pixels relative to the origin of the
16906
+ // main workspace.
16907
+ const scaledWS = wsCoordinates.scale(ws.scale);
16908
+ // The offset in pixels between the main workspace's origin and the upper
16909
+ // left corner of the injection div.
16910
+ const mainOffsetPixels = ws.getOriginOffsetInPixels();
16911
+ // The client coordinates offset by the injection div's upper left corner.
16912
+ const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
16913
+ const injectionDiv = ws.getInjectionDiv();
16914
+ // Bounding rect coordinates are in client coordinates, meaning that they
16915
+ // are in pixels relative to the upper left corner of the visible browser
16916
+ // window. These coordinates change when you scroll the browser window.
16917
+ const boundingRect = injectionDiv.getBoundingClientRect();
16918
+ return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
16919
+ }
16920
+ pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
16541
16921
  })(pxtblockly || (pxtblockly = {}));