pxt-core 7.5.8 → 7.5.11

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 (47) hide show
  1. package/built/pxt.js +1004 -4
  2. package/built/pxtblockly.js +439 -49
  3. package/built/pxtblocks.d.ts +34 -0
  4. package/built/pxtblocks.js +439 -49
  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 +130 -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.e30f6be4.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 +10 -4
  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/Button.less +21 -0
  39. package/react-common/styles/controls/DraggableGraph.less +13 -0
  40. package/react-common/styles/controls/Dropdown.less +68 -0
  41. package/react-common/styles/controls/RadioButtonGroup.less +36 -0
  42. package/react-common/styles/react-common-variables.less +38 -0
  43. package/react-common/styles/react-common.less +3 -0
  44. package/theme/pxt.less +1 -0
  45. package/theme/soundeffecteditor.less +239 -0
  46. package/webapp/public/skillmap.html +1 -1
  47. 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) {
@@ -10866,18 +10874,22 @@ var pxt;
10866
10874
  // by BlocklyLoader. The number makes it an invalid JS identifier
10867
10875
  const buttonAddName = "0_add_button";
10868
10876
  const buttonRemName = "0_rem_button";
10877
+ const buttonAddRemName = "0_add_rem_button";
10869
10878
  const numVisibleAttr = "_expanded";
10870
10879
  const inputInitAttr = "_input_init";
10871
10880
  const optionNames = def.parameters.map(p => p.name);
10872
10881
  const totalOptions = def.parameters.length;
10873
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;
10874
10885
  const state = new MutationState(b);
10875
10886
  state.setEventsEnabled(false);
10876
10887
  state.setValue(numVisibleAttr, 0);
10877
10888
  state.setValue(inputInitAttr, false);
10878
10889
  state.setEventsEnabled(true);
10879
10890
  Blockly.Extensions.apply('inline-svgs', b, false);
10880
- addPlusButton();
10891
+ let updatingInputs = false;
10892
+ let firstRender = true;
10881
10893
  appendMutation(b, {
10882
10894
  mutationToDom: (el) => {
10883
10895
  // The reason we store the inputsInitialized variable separately from visibleOptions
@@ -10892,8 +10904,8 @@ var pxt;
10892
10904
  state.setEventsEnabled(false);
10893
10905
  if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
10894
10906
  state.setValue(inputInitAttr, true);
10895
- initOptionalInputs();
10896
10907
  }
10908
+ initOptionalInputs();
10897
10909
  if (saved.hasAttribute(numVisibleAttr)) {
10898
10910
  const val = parseInt(saved.getAttribute(numVisibleAttr));
10899
10911
  if (!isNaN(val)) {
@@ -10904,6 +10916,7 @@ var pxt;
10904
10916
  }
10905
10917
  else {
10906
10918
  state.setValue(numVisibleAttr, addDelta(delta));
10919
+ updateButtons();
10907
10920
  }
10908
10921
  }
10909
10922
  else {
@@ -10914,14 +10927,32 @@ var pxt;
10914
10927
  state.setEventsEnabled(true);
10915
10928
  }
10916
10929
  });
10917
- // Blockly only lets you hide an input once it is rendered, so we can't
10918
- // hide the inputs in init() or domToMutation(). This will get executed after
10919
- // the block is rendered
10920
- setTimeout(() => {
10921
- if (b.rendered && !b.workspace.isDragging()) {
10930
+ initOptionalInputs();
10931
+ if (compileHiddenArguments) {
10932
+ // Make sure all inputs have shadow blocks attached
10933
+ let optIndex = 0;
10934
+ for (let i = 0; i < b.inputList.length; i++) {
10935
+ const input = b.inputList[i];
10936
+ if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
10937
+ if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
10938
+ const param = comp.definitionNameToParam[def.parameters[optIndex].name];
10939
+ attachShadowBlock(input, param);
10940
+ }
10941
+ ++optIndex;
10942
+ }
10943
+ }
10944
+ }
10945
+ b.render = (opt_bubble) => {
10946
+ if (updatingInputs)
10947
+ return;
10948
+ if (firstRender) {
10949
+ firstRender = false;
10950
+ updatingInputs = true;
10922
10951
  updateShape(0, undefined, true);
10952
+ updatingInputs = false;
10923
10953
  }
10924
- }, 1);
10954
+ Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
10955
+ };
10925
10956
  // Set skipRender to true if the block is still initializing. Otherwise
10926
10957
  // the inputs will render before their shadow blocks are created and
10927
10958
  // leave behind annoying artifacts
@@ -10951,25 +10982,14 @@ var pxt;
10951
10982
  setInputVisible(input, visible);
10952
10983
  if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
10953
10984
  const param = comp.definitionNameToParam[def.parameters[optIndex].name];
10954
- let shadow = blocks.createShadowValue(info, param);
10955
- if (shadow.tagName.toLowerCase() === "value") {
10956
- // Unwrap the block
10957
- shadow = shadow.firstElementChild;
10958
- }
10959
- Blockly.Events.disable();
10960
- try {
10961
- const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
10962
- if (nb) {
10963
- input.connection.connect(nb.outputConnection);
10964
- }
10965
- }
10966
- catch (e) { }
10967
- Blockly.Events.enable();
10985
+ attachShadowBlock(input, param);
10968
10986
  }
10969
10987
  ++optIndex;
10970
10988
  }
10971
10989
  }
10972
10990
  updateButtons();
10991
+ if (variableInlineInputs)
10992
+ b.setInputsInline(visibleOptions < 4);
10973
10993
  if (!skipRender)
10974
10994
  b.render();
10975
10995
  }
@@ -10978,31 +10998,32 @@ var pxt;
10978
10998
  .appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
10979
10999
  }
10980
11000
  function updateButtons() {
11001
+ if (updatingInputs)
11002
+ return;
10981
11003
  const visibleOptions = state.getNumber(numVisibleAttr);
10982
11004
  const showPlus = visibleOptions !== totalOptions;
10983
11005
  const showMinus = visibleOptions !== 0;
10984
- const hasMinus = !!b.getInput(buttonRemName);
10985
- const hasPlus = !!b.getInput(buttonAddName);
10986
- if (!showPlus) {
11006
+ if (b.inputList.some(i => i.name === buttonAddName))
10987
11007
  b.removeInput(buttonAddName, true);
10988
- }
10989
- if (!showMinus) {
11008
+ if (b.inputList.some(i => i.name === buttonRemName))
10990
11009
  b.removeInput(buttonRemName, true);
11010
+ if (b.inputList.some(i => i.name === buttonAddRemName))
11011
+ b.removeInput(buttonAddRemName, true);
11012
+ if (showPlus && showMinus) {
11013
+ addPlusAndMinusButtons();
10991
11014
  }
10992
- if (showMinus && !hasMinus) {
10993
- addMinusButton();
11015
+ else if (showPlus) {
11016
+ addPlusButton();
10994
11017
  }
10995
- if (showPlus) {
10996
- // make sure plus button is last in line.
10997
- if (hasPlus && b.inputList.findIndex(el => el.name === buttonAddName) !== b.inputList.length - 1) {
10998
- b.removeInput(buttonAddName, true);
10999
- addPlusButton();
11000
- }
11001
- else if (!hasPlus) {
11002
- addPlusButton();
11003
- }
11018
+ else if (showMinus) {
11019
+ addMinusButton();
11004
11020
  }
11005
11021
  }
11022
+ function addPlusAndMinusButtons() {
11023
+ b.appendDummyInput(buttonAddRemName)
11024
+ .appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
11025
+ .appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
11026
+ }
11006
11027
  function addPlusButton() {
11007
11028
  addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
11008
11029
  }
@@ -11019,12 +11040,23 @@ var pxt;
11019
11040
  }
11020
11041
  function setInputVisible(input, visible) {
11021
11042
  // If the block isn't rendered, Blockly will crash
11022
- if (b.rendered) {
11023
- let renderList = input.setVisible(visible);
11024
- renderList.forEach((block) => {
11025
- block.render();
11026
- });
11043
+ input.setVisible(visible);
11044
+ }
11045
+ function attachShadowBlock(input, param) {
11046
+ let shadow = blocks.createShadowValue(info, param);
11047
+ if (shadow.tagName.toLowerCase() === "value") {
11048
+ // Unwrap the block
11049
+ shadow = shadow.firstElementChild;
11050
+ }
11051
+ Blockly.Events.disable();
11052
+ try {
11053
+ const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
11054
+ if (nb) {
11055
+ input.connection.connect(nb.outputConnection);
11056
+ }
11027
11057
  }
11058
+ catch (e) { }
11059
+ Blockly.Events.enable();
11028
11060
  }
11029
11061
  }
11030
11062
  blocks.initExpandableBlock = initExpandableBlock;
@@ -11364,6 +11396,17 @@ var pxtblockly;
11364
11396
  this.loaded = true;
11365
11397
  this.valueText = this.onValueChanged(this.valueText);
11366
11398
  }
11399
+ getAnchorDimensions() {
11400
+ const boundingBox = this.getScaledBBox();
11401
+ if (this.sourceBlock_.RTL) {
11402
+ boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
11403
+ }
11404
+ else {
11405
+ boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
11406
+ }
11407
+ return boundingBox;
11408
+ }
11409
+ ;
11367
11410
  isInitialized() {
11368
11411
  return !!this.fieldGroup_;
11369
11412
  }
@@ -11373,6 +11416,23 @@ var pxtblockly;
11373
11416
  setBlockData(value) {
11374
11417
  pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
11375
11418
  }
11419
+ getSiblingBlock(inputName, useGrandparent = false) {
11420
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
11421
+ if (!block || !block.inputList)
11422
+ return undefined;
11423
+ for (const input of block.inputList) {
11424
+ if (input.name === inputName) {
11425
+ return input.connection.targetBlock();
11426
+ }
11427
+ }
11428
+ return undefined;
11429
+ }
11430
+ getSiblingField(fieldName, useGrandparent = false) {
11431
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
11432
+ if (!block)
11433
+ return undefined;
11434
+ return block.getField(fieldName);
11435
+ }
11376
11436
  }
11377
11437
  pxtblockly.FieldBase = FieldBase;
11378
11438
  })(pxtblockly || (pxtblockly = {}));
@@ -15075,6 +15135,319 @@ var pxtblockly;
15075
15135
  }
15076
15136
  pxtblockly.FieldProtractor = FieldProtractor;
15077
15137
  })(pxtblockly || (pxtblockly = {}));
15138
+ /// <reference path="../../built/pxtlib.d.ts" />
15139
+ /// <reference path="./field_base.ts" />
15140
+ var pxtblockly;
15141
+ (function (pxtblockly) {
15142
+ var svg = pxt.svgUtil;
15143
+ const MUSIC_ICON_WIDTH = 20;
15144
+ const TOTAL_WIDTH = 160;
15145
+ const TOTAL_HEIGHT = 40;
15146
+ const X_PADDING = 5;
15147
+ const Y_PADDING = 4;
15148
+ const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
15149
+ class FieldSoundEffect extends pxtblockly.FieldBase {
15150
+ onInit() {
15151
+ if (!this.options)
15152
+ this.options = {};
15153
+ if (!this.options.durationInputName)
15154
+ this.options.durationInputName = "duration";
15155
+ if (!this.options.startFrequencyInputName)
15156
+ this.options.startFrequencyInputName = "startFrequency";
15157
+ if (!this.options.endFrequencyInputName)
15158
+ this.options.endFrequencyInputName = "endFrequency";
15159
+ if (!this.options.startVolumeInputName)
15160
+ this.options.startVolumeInputName = "startVolume";
15161
+ if (!this.options.endVolumeInputName)
15162
+ this.options.endVolumeInputName = "endVolume";
15163
+ if (!this.options.waveFieldName)
15164
+ this.options.waveFieldName = "waveShape";
15165
+ if (!this.options.interpolationFieldName)
15166
+ this.options.interpolationFieldName = "interpolation";
15167
+ if (!this.options.effectFieldName)
15168
+ this.options.effectFieldName = "effect";
15169
+ this.redrawPreview();
15170
+ }
15171
+ onDispose() {
15172
+ }
15173
+ onValueChanged(newValue) {
15174
+ return newValue;
15175
+ }
15176
+ redrawPreview() {
15177
+ if (!this.fieldGroup_)
15178
+ return;
15179
+ pxsim.U.clear(this.fieldGroup_);
15180
+ const bg = new svg.Rect()
15181
+ .at(X_PADDING, Y_PADDING)
15182
+ .size(TOTAL_WIDTH, TOTAL_HEIGHT)
15183
+ .setClass("blocklySpriteField")
15184
+ .stroke("#fff", 1)
15185
+ .corner(TOTAL_HEIGHT / 2);
15186
+ const clipPathId = "preview-clip-" + pxt.U.guidGen();
15187
+ const clip = new svg.ClipPath()
15188
+ .id(clipPathId)
15189
+ .clipPathUnits(false);
15190
+ const clipRect = new svg.Rect()
15191
+ .size(PREVIEW_WIDTH, TOTAL_HEIGHT)
15192
+ .fill("#FFF")
15193
+ .at(0, 0);
15194
+ clip.appendChild(clipRect);
15195
+ const path = new svg.Path()
15196
+ .stroke("grey", 2)
15197
+ .fill("none")
15198
+ .setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
15199
+ .clipPath("url('#" + clipPathId + "')");
15200
+ const g = new svg.Group()
15201
+ .translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
15202
+ g.appendChild(clip);
15203
+ g.appendChild(path);
15204
+ const musicIcon = new svg.Text("\uf001")
15205
+ .appendClass("melody-editor-field-icon")
15206
+ .setAttribute("alignment-baseline", "middle")
15207
+ .anchor("middle")
15208
+ .at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
15209
+ this.fieldGroup_.appendChild(bg.el);
15210
+ this.fieldGroup_.appendChild(musicIcon.el);
15211
+ this.fieldGroup_.appendChild(g.el);
15212
+ }
15213
+ showEditor_() {
15214
+ const initialSound = this.readCurrentSound();
15215
+ Blockly.Events.disable();
15216
+ let bbox;
15217
+ // This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
15218
+ // which caused the widgetdiv to jump around if any fields underneath changed size
15219
+ let widgetOwner = {
15220
+ getScaledBBox: () => bbox
15221
+ };
15222
+ Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
15223
+ fv.hide();
15224
+ widgetDiv.classList.remove("sound-effect-editor-widget");
15225
+ widgetDiv.style.transform = "";
15226
+ widgetDiv.style.position = "";
15227
+ widgetDiv.style.left = "";
15228
+ widgetDiv.style.top = "";
15229
+ widgetDiv.style.width = "";
15230
+ widgetDiv.style.height = "";
15231
+ widgetDiv.style.opacity = "";
15232
+ widgetDiv.style.transition = "";
15233
+ Blockly.Events.enable();
15234
+ Blockly.Events.setGroup(true);
15235
+ this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
15236
+ this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
15237
+ this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
15238
+ this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
15239
+ this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
15240
+ this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
15241
+ this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
15242
+ this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
15243
+ Blockly.Events.setGroup(false);
15244
+ if (this.mostRecentValue)
15245
+ this.setBlockData(JSON.stringify(this.mostRecentValue));
15246
+ });
15247
+ const widgetDiv = Blockly.WidgetDiv.DIV;
15248
+ const opts = {
15249
+ onClose: () => {
15250
+ fv.hide();
15251
+ Blockly.WidgetDiv.hideIfOwner(widgetOwner);
15252
+ },
15253
+ onSoundChange: (newSound) => {
15254
+ this.mostRecentValue = newSound;
15255
+ this.updateSiblingBlocks(newSound);
15256
+ this.redrawPreview();
15257
+ },
15258
+ initialSound: initialSound
15259
+ };
15260
+ const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
15261
+ const block = this.sourceBlock_;
15262
+ const bounds = block.getBoundingRectangle();
15263
+ const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
15264
+ const animationDistance = 20;
15265
+ const left = coord.x + 20;
15266
+ const top = coord.y - animationDistance;
15267
+ widgetDiv.style.opacity = "0";
15268
+ widgetDiv.classList.add("sound-effect-editor-widget");
15269
+ widgetDiv.style.position = "absolute";
15270
+ widgetDiv.style.left = left + "px";
15271
+ widgetDiv.style.top = top + "px";
15272
+ widgetDiv.style.width = "30rem";
15273
+ widgetDiv.style.height = "40rem";
15274
+ widgetDiv.style.display = "block";
15275
+ widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
15276
+ fv.onHide(() => {
15277
+ // do nothing
15278
+ });
15279
+ fv.show();
15280
+ const divBounds = widgetDiv.getBoundingClientRect();
15281
+ const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
15282
+ if (divBounds.height > injectDivBounds.height) {
15283
+ widgetDiv.style.height = "";
15284
+ widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
15285
+ widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
15286
+ }
15287
+ else {
15288
+ if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
15289
+ // This editor is pretty tall, so just center vertically on the inject div
15290
+ widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
15291
+ }
15292
+ }
15293
+ const toolboxWidth = block.workspace.getToolbox().getWidth();
15294
+ if (divBounds.width > injectDivBounds.width - toolboxWidth) {
15295
+ widgetDiv.style.width = "";
15296
+ widgetDiv.style.left = "1rem";
15297
+ widgetDiv.style.right = "1rem";
15298
+ }
15299
+ else {
15300
+ // Check to see if we are bleeding off the right side of the canvas
15301
+ if (divBounds.left + divBounds.width >= injectDivBounds.right) {
15302
+ // If so, try and place to the left of the block instead of the right
15303
+ const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
15304
+ const workspaceLeft = injectDivBounds.left + toolboxWidth;
15305
+ if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
15306
+ widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
15307
+ }
15308
+ else {
15309
+ // As a last resort, just center on the inject div
15310
+ widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
15311
+ }
15312
+ }
15313
+ }
15314
+ const finalDimensions = widgetDiv.getBoundingClientRect();
15315
+ bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
15316
+ requestAnimationFrame(() => {
15317
+ widgetDiv.style.opacity = "1";
15318
+ widgetDiv.style.transform = `translateY(${animationDistance}px)`;
15319
+ });
15320
+ }
15321
+ render_() {
15322
+ super.render_();
15323
+ this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
15324
+ this.size_.width = TOTAL_WIDTH;
15325
+ }
15326
+ updateSiblingBlocks(sound) {
15327
+ this.setNumberInputValue(this.options.durationInputName, sound.duration);
15328
+ this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
15329
+ this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
15330
+ this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
15331
+ this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
15332
+ this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
15333
+ this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
15334
+ this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
15335
+ }
15336
+ setNumberInputValue(name, value) {
15337
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15338
+ if (!block)
15339
+ return;
15340
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15341
+ block.setFieldValue(Math.round(value), "NUM");
15342
+ }
15343
+ else if (block.type === "math_number_minmax") {
15344
+ block.setFieldValue(Math.round(value), "SLIDER");
15345
+ }
15346
+ }
15347
+ getNumberInputValue(name, defaultValue) {
15348
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15349
+ if (!block)
15350
+ return defaultValue;
15351
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15352
+ return parseInt(block.getFieldValue("NUM") + "");
15353
+ }
15354
+ else if (block.type === "math_number_minmax") {
15355
+ return parseInt(block.getFieldValue("SLIDER") + "");
15356
+ }
15357
+ return defaultValue;
15358
+ }
15359
+ fireNumberInputUpdate(name, oldValue) {
15360
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
15361
+ if (!block)
15362
+ return;
15363
+ let fieldName;
15364
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
15365
+ fieldName = "NUM";
15366
+ }
15367
+ else if (block.type === "math_number_minmax") {
15368
+ fieldName = "SLIDER";
15369
+ }
15370
+ if (!fieldName)
15371
+ return;
15372
+ Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
15373
+ }
15374
+ setFieldDropdownValue(name, value) {
15375
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15376
+ if (!field)
15377
+ return;
15378
+ field.setValue(value);
15379
+ }
15380
+ getFieldDropdownValue(name) {
15381
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15382
+ if (!field)
15383
+ return undefined;
15384
+ return field.getValue();
15385
+ }
15386
+ fireFieldDropdownUpdate(name, oldValue) {
15387
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
15388
+ if (!field)
15389
+ return;
15390
+ Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
15391
+ }
15392
+ readCurrentSound() {
15393
+ const savedSound = this.readBlockDataSound();
15394
+ return {
15395
+ duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
15396
+ startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
15397
+ endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
15398
+ startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
15399
+ endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
15400
+ wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
15401
+ interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
15402
+ effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
15403
+ };
15404
+ }
15405
+ // This stores the values of the fields in case a block (e.g. a variable) is placed in one
15406
+ // of the inputs.
15407
+ readBlockDataSound() {
15408
+ const data = this.getBlockData();
15409
+ let sound;
15410
+ try {
15411
+ sound = JSON.parse(data);
15412
+ }
15413
+ catch (e) {
15414
+ sound = {
15415
+ duration: 1000,
15416
+ startFrequency: 100,
15417
+ endFrequency: 1800,
15418
+ startVolume: 1023,
15419
+ endVolume: 0,
15420
+ wave: "sine",
15421
+ interpolation: "linear",
15422
+ effect: "none"
15423
+ };
15424
+ }
15425
+ return sound;
15426
+ }
15427
+ }
15428
+ pxtblockly.FieldSoundEffect = FieldSoundEffect;
15429
+ const waveformMapping = {
15430
+ "sine": "WaveShape.Sine",
15431
+ "square": "WaveShape.Square",
15432
+ "sawtooth": "WaveShape.Sawtooth",
15433
+ "triangle": "WaveShape.Triangle",
15434
+ "noise": "WaveShape.Noise",
15435
+ };
15436
+ const effectMapping = {
15437
+ "none": "SoundExpressionEffect.None",
15438
+ "vibrato": "SoundExpressionEffect.Vibrato",
15439
+ "tremolo": "SoundExpressionEffect.Tremolo",
15440
+ "warble": "SoundExpressionEffect.Warble",
15441
+ };
15442
+ const interpolationMapping = {
15443
+ "linear": "InterpolationCurve.Linear",
15444
+ "curve": "InterpolationCurve.Curve",
15445
+ "logarithmic": "InterpolationCurve.Logarithmic",
15446
+ };
15447
+ function reverseLookup(map, value) {
15448
+ return Object.keys(map).find(k => map[k] === value);
15449
+ }
15450
+ })(pxtblockly || (pxtblockly = {}));
15078
15451
  /// <reference path="../../localtypings/blockly.d.ts"/>
15079
15452
  /// <reference path="../../built/pxtsim.d.ts"/>
15080
15453
  var pxtblockly;
@@ -16540,4 +16913,21 @@ var pxtblockly;
16540
16913
  }
16541
16914
  }
16542
16915
  pxtblockly.getTemporaryAssets = getTemporaryAssets;
16916
+ function workspaceToScreenCoordinates(ws, wsCoordinates) {
16917
+ // The position in pixels relative to the origin of the
16918
+ // main workspace.
16919
+ const scaledWS = wsCoordinates.scale(ws.scale);
16920
+ // The offset in pixels between the main workspace's origin and the upper
16921
+ // left corner of the injection div.
16922
+ const mainOffsetPixels = ws.getOriginOffsetInPixels();
16923
+ // The client coordinates offset by the injection div's upper left corner.
16924
+ const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
16925
+ const injectionDiv = ws.getInjectionDiv();
16926
+ // Bounding rect coordinates are in client coordinates, meaning that they
16927
+ // are in pixels relative to the upper left corner of the visible browser
16928
+ // window. These coordinates change when you scroll the browser window.
16929
+ const boundingRect = injectionDiv.getBoundingClientRect();
16930
+ return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
16931
+ }
16932
+ pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
16543
16933
  })(pxtblockly || (pxtblockly = {}));