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
@@ -454,7 +454,7 @@ var pxt;
454
454
  const call = e.stdCallTable[b.type];
455
455
  if (call.attrs.shim === "ENUM_GET" || call.attrs.shim === "KIND_GET")
456
456
  return;
457
- visibleParams(call, countOptionals(b)).forEach((p, i) => {
457
+ visibleParams(call, countOptionals(b, call)).forEach((p, i) => {
458
458
  const isInstance = call.isExtensionMethod && i === 0;
459
459
  if (p.definitionName && !b.getFieldValue(p.definitionName)) {
460
460
  let i = b.inputList.find((i) => i.name == p.definitionName);
@@ -922,7 +922,7 @@ var pxt;
922
922
  let call = e.stdCallTable[b.type];
923
923
  if (call) {
924
924
  if (call.imageLiteral)
925
- 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)));
925
+ 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)));
926
926
  else
927
927
  expr = compileStdCall(e, b, call, comments);
928
928
  }
@@ -1135,12 +1135,12 @@ var pxt;
1135
1135
  return blocks_1.mkStmt(blocks_1.mkInfix(ref, "+=", expr));
1136
1136
  }
1137
1137
  function eventArgs(call, b) {
1138
- return visibleParams(call, countOptionals(b)).filter(ar => !!ar.definitionName);
1138
+ return visibleParams(call, countOptionals(b, call)).filter(ar => !!ar.definitionName);
1139
1139
  }
1140
1140
  function compileCall(e, b, comments) {
1141
1141
  const call = e.stdCallTable[b.type];
1142
1142
  if (call.imageLiteral)
1143
- 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))));
1143
+ 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))));
1144
1144
  else if (call.hasHandler)
1145
1145
  return compileEvent(e, b, call, eventArgs(call, b), call.namespace, comments);
1146
1146
  else
@@ -1219,7 +1219,7 @@ var pxt;
1219
1219
  return blocks_1.H.mkPropertyAccess(b.getFieldValue("MEMBER"), blocks_1.mkText(info.name));
1220
1220
  }
1221
1221
  else {
1222
- args = visibleParams(func, countOptionals(b)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
1222
+ args = visibleParams(func, countOptionals(b, func)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
1223
1223
  }
1224
1224
  let callNamespace = func.namespace;
1225
1225
  let callName = func.f;
@@ -1873,7 +1873,14 @@ var pxt;
1873
1873
  }
1874
1874
  return blocks_1.mkStmt(blocks_1.mkText("let " + v.escapedName + tp + " = "), defl);
1875
1875
  }
1876
- function countOptionals(b) {
1876
+ function countOptionals(b, func) {
1877
+ if (func.attrs.compileHiddenArguments) {
1878
+ return func.comp.parameters.reduce((prev, block) => {
1879
+ if (block.isOptional)
1880
+ prev++;
1881
+ return prev;
1882
+ }, 0);
1883
+ }
1877
1884
  if (b.mutationToDom) {
1878
1885
  const el = b.mutationToDom();
1879
1886
  if (el.hasAttribute("_expanded")) {
@@ -2392,6 +2399,7 @@ var pxt;
2392
2399
  registerFieldEditor('protractor', pxtblockly.FieldProtractor);
2393
2400
  registerFieldEditor('position', pxtblockly.FieldPosition);
2394
2401
  registerFieldEditor('melody', pxtblockly.FieldCustomMelody);
2402
+ registerFieldEditor('soundeffect', pxtblockly.FieldSoundEffect);
2395
2403
  }
2396
2404
  blocks.initFieldEditors = initFieldEditors;
2397
2405
  function registerFieldEditor(selector, field, validator) {
@@ -7304,18 +7312,22 @@ var pxt;
7304
7312
  // by BlocklyLoader. The number makes it an invalid JS identifier
7305
7313
  const buttonAddName = "0_add_button";
7306
7314
  const buttonRemName = "0_rem_button";
7315
+ const buttonAddRemName = "0_add_rem_button";
7307
7316
  const numVisibleAttr = "_expanded";
7308
7317
  const inputInitAttr = "_input_init";
7309
7318
  const optionNames = def.parameters.map(p => p.name);
7310
7319
  const totalOptions = def.parameters.length;
7311
7320
  const buttonDelta = toggle ? totalOptions : 1;
7321
+ const variableInlineInputs = info.blocksById[b.type].attributes.inlineInputMode === "variable";
7322
+ const compileHiddenArguments = info.blocksById[b.type].attributes.compileHiddenArguments;
7312
7323
  const state = new MutationState(b);
7313
7324
  state.setEventsEnabled(false);
7314
7325
  state.setValue(numVisibleAttr, 0);
7315
7326
  state.setValue(inputInitAttr, false);
7316
7327
  state.setEventsEnabled(true);
7317
7328
  Blockly.Extensions.apply('inline-svgs', b, false);
7318
- addPlusButton();
7329
+ let updatingInputs = false;
7330
+ let firstRender = true;
7319
7331
  appendMutation(b, {
7320
7332
  mutationToDom: (el) => {
7321
7333
  // The reason we store the inputsInitialized variable separately from visibleOptions
@@ -7330,8 +7342,8 @@ var pxt;
7330
7342
  state.setEventsEnabled(false);
7331
7343
  if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
7332
7344
  state.setValue(inputInitAttr, true);
7333
- initOptionalInputs();
7334
7345
  }
7346
+ initOptionalInputs();
7335
7347
  if (saved.hasAttribute(numVisibleAttr)) {
7336
7348
  const val = parseInt(saved.getAttribute(numVisibleAttr));
7337
7349
  if (!isNaN(val)) {
@@ -7342,6 +7354,7 @@ var pxt;
7342
7354
  }
7343
7355
  else {
7344
7356
  state.setValue(numVisibleAttr, addDelta(delta));
7357
+ updateButtons();
7345
7358
  }
7346
7359
  }
7347
7360
  else {
@@ -7352,14 +7365,32 @@ var pxt;
7352
7365
  state.setEventsEnabled(true);
7353
7366
  }
7354
7367
  });
7355
- // Blockly only lets you hide an input once it is rendered, so we can't
7356
- // hide the inputs in init() or domToMutation(). This will get executed after
7357
- // the block is rendered
7358
- setTimeout(() => {
7359
- if (b.rendered && !b.workspace.isDragging()) {
7368
+ initOptionalInputs();
7369
+ if (compileHiddenArguments) {
7370
+ // Make sure all inputs have shadow blocks attached
7371
+ let optIndex = 0;
7372
+ for (let i = 0; i < b.inputList.length; i++) {
7373
+ const input = b.inputList[i];
7374
+ if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
7375
+ if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
7376
+ const param = comp.definitionNameToParam[def.parameters[optIndex].name];
7377
+ attachShadowBlock(input, param);
7378
+ }
7379
+ ++optIndex;
7380
+ }
7381
+ }
7382
+ }
7383
+ b.render = (opt_bubble) => {
7384
+ if (updatingInputs)
7385
+ return;
7386
+ if (firstRender) {
7387
+ firstRender = false;
7388
+ updatingInputs = true;
7360
7389
  updateShape(0, undefined, true);
7390
+ updatingInputs = false;
7361
7391
  }
7362
- }, 1);
7392
+ Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
7393
+ };
7363
7394
  // Set skipRender to true if the block is still initializing. Otherwise
7364
7395
  // the inputs will render before their shadow blocks are created and
7365
7396
  // leave behind annoying artifacts
@@ -7389,25 +7420,14 @@ var pxt;
7389
7420
  setInputVisible(input, visible);
7390
7421
  if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
7391
7422
  const param = comp.definitionNameToParam[def.parameters[optIndex].name];
7392
- let shadow = blocks.createShadowValue(info, param);
7393
- if (shadow.tagName.toLowerCase() === "value") {
7394
- // Unwrap the block
7395
- shadow = shadow.firstElementChild;
7396
- }
7397
- Blockly.Events.disable();
7398
- try {
7399
- const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
7400
- if (nb) {
7401
- input.connection.connect(nb.outputConnection);
7402
- }
7403
- }
7404
- catch (e) { }
7405
- Blockly.Events.enable();
7423
+ attachShadowBlock(input, param);
7406
7424
  }
7407
7425
  ++optIndex;
7408
7426
  }
7409
7427
  }
7410
7428
  updateButtons();
7429
+ if (variableInlineInputs)
7430
+ b.setInputsInline(visibleOptions < 4);
7411
7431
  if (!skipRender)
7412
7432
  b.render();
7413
7433
  }
@@ -7416,31 +7436,32 @@ var pxt;
7416
7436
  .appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
7417
7437
  }
7418
7438
  function updateButtons() {
7439
+ if (updatingInputs)
7440
+ return;
7419
7441
  const visibleOptions = state.getNumber(numVisibleAttr);
7420
7442
  const showPlus = visibleOptions !== totalOptions;
7421
7443
  const showMinus = visibleOptions !== 0;
7422
- const hasMinus = !!b.getInput(buttonRemName);
7423
- const hasPlus = !!b.getInput(buttonAddName);
7424
- if (!showPlus) {
7444
+ if (b.inputList.some(i => i.name === buttonAddName))
7425
7445
  b.removeInput(buttonAddName, true);
7426
- }
7427
- if (!showMinus) {
7446
+ if (b.inputList.some(i => i.name === buttonRemName))
7428
7447
  b.removeInput(buttonRemName, true);
7448
+ if (b.inputList.some(i => i.name === buttonAddRemName))
7449
+ b.removeInput(buttonAddRemName, true);
7450
+ if (showPlus && showMinus) {
7451
+ addPlusAndMinusButtons();
7429
7452
  }
7430
- if (showMinus && !hasMinus) {
7431
- addMinusButton();
7453
+ else if (showPlus) {
7454
+ addPlusButton();
7432
7455
  }
7433
- if (showPlus) {
7434
- // make sure plus button is last in line.
7435
- if (hasPlus && b.inputList.findIndex(el => el.name === buttonAddName) !== b.inputList.length - 1) {
7436
- b.removeInput(buttonAddName, true);
7437
- addPlusButton();
7438
- }
7439
- else if (!hasPlus) {
7440
- addPlusButton();
7441
- }
7456
+ else if (showMinus) {
7457
+ addMinusButton();
7442
7458
  }
7443
7459
  }
7460
+ function addPlusAndMinusButtons() {
7461
+ b.appendDummyInput(buttonAddRemName)
7462
+ .appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
7463
+ .appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
7464
+ }
7444
7465
  function addPlusButton() {
7445
7466
  addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
7446
7467
  }
@@ -7457,12 +7478,23 @@ var pxt;
7457
7478
  }
7458
7479
  function setInputVisible(input, visible) {
7459
7480
  // If the block isn't rendered, Blockly will crash
7460
- if (b.rendered) {
7461
- let renderList = input.setVisible(visible);
7462
- renderList.forEach((block) => {
7463
- block.render();
7464
- });
7481
+ input.setVisible(visible);
7482
+ }
7483
+ function attachShadowBlock(input, param) {
7484
+ let shadow = blocks.createShadowValue(info, param);
7485
+ if (shadow.tagName.toLowerCase() === "value") {
7486
+ // Unwrap the block
7487
+ shadow = shadow.firstElementChild;
7488
+ }
7489
+ Blockly.Events.disable();
7490
+ try {
7491
+ const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
7492
+ if (nb) {
7493
+ input.connection.connect(nb.outputConnection);
7494
+ }
7465
7495
  }
7496
+ catch (e) { }
7497
+ Blockly.Events.enable();
7466
7498
  }
7467
7499
  }
7468
7500
  blocks.initExpandableBlock = initExpandableBlock;
@@ -7802,6 +7834,17 @@ var pxtblockly;
7802
7834
  this.loaded = true;
7803
7835
  this.valueText = this.onValueChanged(this.valueText);
7804
7836
  }
7837
+ getAnchorDimensions() {
7838
+ const boundingBox = this.getScaledBBox();
7839
+ if (this.sourceBlock_.RTL) {
7840
+ boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
7841
+ }
7842
+ else {
7843
+ boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
7844
+ }
7845
+ return boundingBox;
7846
+ }
7847
+ ;
7805
7848
  isInitialized() {
7806
7849
  return !!this.fieldGroup_;
7807
7850
  }
@@ -7811,6 +7854,23 @@ var pxtblockly;
7811
7854
  setBlockData(value) {
7812
7855
  pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
7813
7856
  }
7857
+ getSiblingBlock(inputName, useGrandparent = false) {
7858
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
7859
+ if (!block || !block.inputList)
7860
+ return undefined;
7861
+ for (const input of block.inputList) {
7862
+ if (input.name === inputName) {
7863
+ return input.connection.targetBlock();
7864
+ }
7865
+ }
7866
+ return undefined;
7867
+ }
7868
+ getSiblingField(fieldName, useGrandparent = false) {
7869
+ const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
7870
+ if (!block)
7871
+ return undefined;
7872
+ return block.getField(fieldName);
7873
+ }
7814
7874
  }
7815
7875
  pxtblockly.FieldBase = FieldBase;
7816
7876
  })(pxtblockly || (pxtblockly = {}));
@@ -11513,6 +11573,319 @@ var pxtblockly;
11513
11573
  }
11514
11574
  pxtblockly.FieldProtractor = FieldProtractor;
11515
11575
  })(pxtblockly || (pxtblockly = {}));
11576
+ /// <reference path="../../built/pxtlib.d.ts" />
11577
+ /// <reference path="./field_base.ts" />
11578
+ var pxtblockly;
11579
+ (function (pxtblockly) {
11580
+ var svg = pxt.svgUtil;
11581
+ const MUSIC_ICON_WIDTH = 20;
11582
+ const TOTAL_WIDTH = 160;
11583
+ const TOTAL_HEIGHT = 40;
11584
+ const X_PADDING = 5;
11585
+ const Y_PADDING = 4;
11586
+ const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
11587
+ class FieldSoundEffect extends pxtblockly.FieldBase {
11588
+ onInit() {
11589
+ if (!this.options)
11590
+ this.options = {};
11591
+ if (!this.options.durationInputName)
11592
+ this.options.durationInputName = "duration";
11593
+ if (!this.options.startFrequencyInputName)
11594
+ this.options.startFrequencyInputName = "startFrequency";
11595
+ if (!this.options.endFrequencyInputName)
11596
+ this.options.endFrequencyInputName = "endFrequency";
11597
+ if (!this.options.startVolumeInputName)
11598
+ this.options.startVolumeInputName = "startVolume";
11599
+ if (!this.options.endVolumeInputName)
11600
+ this.options.endVolumeInputName = "endVolume";
11601
+ if (!this.options.waveFieldName)
11602
+ this.options.waveFieldName = "waveShape";
11603
+ if (!this.options.interpolationFieldName)
11604
+ this.options.interpolationFieldName = "interpolation";
11605
+ if (!this.options.effectFieldName)
11606
+ this.options.effectFieldName = "effect";
11607
+ this.redrawPreview();
11608
+ }
11609
+ onDispose() {
11610
+ }
11611
+ onValueChanged(newValue) {
11612
+ return newValue;
11613
+ }
11614
+ redrawPreview() {
11615
+ if (!this.fieldGroup_)
11616
+ return;
11617
+ pxsim.U.clear(this.fieldGroup_);
11618
+ const bg = new svg.Rect()
11619
+ .at(X_PADDING, Y_PADDING)
11620
+ .size(TOTAL_WIDTH, TOTAL_HEIGHT)
11621
+ .setClass("blocklySpriteField")
11622
+ .stroke("#fff", 1)
11623
+ .corner(TOTAL_HEIGHT / 2);
11624
+ const clipPathId = "preview-clip-" + pxt.U.guidGen();
11625
+ const clip = new svg.ClipPath()
11626
+ .id(clipPathId)
11627
+ .clipPathUnits(false);
11628
+ const clipRect = new svg.Rect()
11629
+ .size(PREVIEW_WIDTH, TOTAL_HEIGHT)
11630
+ .fill("#FFF")
11631
+ .at(0, 0);
11632
+ clip.appendChild(clipRect);
11633
+ const path = new svg.Path()
11634
+ .stroke("grey", 2)
11635
+ .fill("none")
11636
+ .setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
11637
+ .clipPath("url('#" + clipPathId + "')");
11638
+ const g = new svg.Group()
11639
+ .translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
11640
+ g.appendChild(clip);
11641
+ g.appendChild(path);
11642
+ const musicIcon = new svg.Text("\uf001")
11643
+ .appendClass("melody-editor-field-icon")
11644
+ .setAttribute("alignment-baseline", "middle")
11645
+ .anchor("middle")
11646
+ .at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
11647
+ this.fieldGroup_.appendChild(bg.el);
11648
+ this.fieldGroup_.appendChild(musicIcon.el);
11649
+ this.fieldGroup_.appendChild(g.el);
11650
+ }
11651
+ showEditor_() {
11652
+ const initialSound = this.readCurrentSound();
11653
+ Blockly.Events.disable();
11654
+ let bbox;
11655
+ // This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
11656
+ // which caused the widgetdiv to jump around if any fields underneath changed size
11657
+ let widgetOwner = {
11658
+ getScaledBBox: () => bbox
11659
+ };
11660
+ Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
11661
+ fv.hide();
11662
+ widgetDiv.classList.remove("sound-effect-editor-widget");
11663
+ widgetDiv.style.transform = "";
11664
+ widgetDiv.style.position = "";
11665
+ widgetDiv.style.left = "";
11666
+ widgetDiv.style.top = "";
11667
+ widgetDiv.style.width = "";
11668
+ widgetDiv.style.height = "";
11669
+ widgetDiv.style.opacity = "";
11670
+ widgetDiv.style.transition = "";
11671
+ Blockly.Events.enable();
11672
+ Blockly.Events.setGroup(true);
11673
+ this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
11674
+ this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
11675
+ this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
11676
+ this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
11677
+ this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
11678
+ this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
11679
+ this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
11680
+ this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
11681
+ Blockly.Events.setGroup(false);
11682
+ if (this.mostRecentValue)
11683
+ this.setBlockData(JSON.stringify(this.mostRecentValue));
11684
+ });
11685
+ const widgetDiv = Blockly.WidgetDiv.DIV;
11686
+ const opts = {
11687
+ onClose: () => {
11688
+ fv.hide();
11689
+ Blockly.WidgetDiv.hideIfOwner(widgetOwner);
11690
+ },
11691
+ onSoundChange: (newSound) => {
11692
+ this.mostRecentValue = newSound;
11693
+ this.updateSiblingBlocks(newSound);
11694
+ this.redrawPreview();
11695
+ },
11696
+ initialSound: initialSound
11697
+ };
11698
+ const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
11699
+ const block = this.sourceBlock_;
11700
+ const bounds = block.getBoundingRectangle();
11701
+ const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
11702
+ const animationDistance = 20;
11703
+ const left = coord.x + 20;
11704
+ const top = coord.y - animationDistance;
11705
+ widgetDiv.style.opacity = "0";
11706
+ widgetDiv.classList.add("sound-effect-editor-widget");
11707
+ widgetDiv.style.position = "absolute";
11708
+ widgetDiv.style.left = left + "px";
11709
+ widgetDiv.style.top = top + "px";
11710
+ widgetDiv.style.width = "30rem";
11711
+ widgetDiv.style.height = "40rem";
11712
+ widgetDiv.style.display = "block";
11713
+ widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
11714
+ fv.onHide(() => {
11715
+ // do nothing
11716
+ });
11717
+ fv.show();
11718
+ const divBounds = widgetDiv.getBoundingClientRect();
11719
+ const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
11720
+ if (divBounds.height > injectDivBounds.height) {
11721
+ widgetDiv.style.height = "";
11722
+ widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
11723
+ widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
11724
+ }
11725
+ else {
11726
+ if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
11727
+ // This editor is pretty tall, so just center vertically on the inject div
11728
+ widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
11729
+ }
11730
+ }
11731
+ const toolboxWidth = block.workspace.getToolbox().getWidth();
11732
+ if (divBounds.width > injectDivBounds.width - toolboxWidth) {
11733
+ widgetDiv.style.width = "";
11734
+ widgetDiv.style.left = "1rem";
11735
+ widgetDiv.style.right = "1rem";
11736
+ }
11737
+ else {
11738
+ // Check to see if we are bleeding off the right side of the canvas
11739
+ if (divBounds.left + divBounds.width >= injectDivBounds.right) {
11740
+ // If so, try and place to the left of the block instead of the right
11741
+ const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
11742
+ const workspaceLeft = injectDivBounds.left + toolboxWidth;
11743
+ if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
11744
+ widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
11745
+ }
11746
+ else {
11747
+ // As a last resort, just center on the inject div
11748
+ widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
11749
+ }
11750
+ }
11751
+ }
11752
+ const finalDimensions = widgetDiv.getBoundingClientRect();
11753
+ bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
11754
+ requestAnimationFrame(() => {
11755
+ widgetDiv.style.opacity = "1";
11756
+ widgetDiv.style.transform = `translateY(${animationDistance}px)`;
11757
+ });
11758
+ }
11759
+ render_() {
11760
+ super.render_();
11761
+ this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
11762
+ this.size_.width = TOTAL_WIDTH;
11763
+ }
11764
+ updateSiblingBlocks(sound) {
11765
+ this.setNumberInputValue(this.options.durationInputName, sound.duration);
11766
+ this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
11767
+ this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
11768
+ this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
11769
+ this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
11770
+ this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
11771
+ this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
11772
+ this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
11773
+ }
11774
+ setNumberInputValue(name, value) {
11775
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
11776
+ if (!block)
11777
+ return;
11778
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
11779
+ block.setFieldValue(Math.round(value), "NUM");
11780
+ }
11781
+ else if (block.type === "math_number_minmax") {
11782
+ block.setFieldValue(Math.round(value), "SLIDER");
11783
+ }
11784
+ }
11785
+ getNumberInputValue(name, defaultValue) {
11786
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
11787
+ if (!block)
11788
+ return defaultValue;
11789
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
11790
+ return parseInt(block.getFieldValue("NUM") + "");
11791
+ }
11792
+ else if (block.type === "math_number_minmax") {
11793
+ return parseInt(block.getFieldValue("SLIDER") + "");
11794
+ }
11795
+ return defaultValue;
11796
+ }
11797
+ fireNumberInputUpdate(name, oldValue) {
11798
+ const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
11799
+ if (!block)
11800
+ return;
11801
+ let fieldName;
11802
+ if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
11803
+ fieldName = "NUM";
11804
+ }
11805
+ else if (block.type === "math_number_minmax") {
11806
+ fieldName = "SLIDER";
11807
+ }
11808
+ if (!fieldName)
11809
+ return;
11810
+ Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
11811
+ }
11812
+ setFieldDropdownValue(name, value) {
11813
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
11814
+ if (!field)
11815
+ return;
11816
+ field.setValue(value);
11817
+ }
11818
+ getFieldDropdownValue(name) {
11819
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
11820
+ if (!field)
11821
+ return undefined;
11822
+ return field.getValue();
11823
+ }
11824
+ fireFieldDropdownUpdate(name, oldValue) {
11825
+ const field = this.getSiblingField(name) || this.getSiblingField(name, true);
11826
+ if (!field)
11827
+ return;
11828
+ Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
11829
+ }
11830
+ readCurrentSound() {
11831
+ const savedSound = this.readBlockDataSound();
11832
+ return {
11833
+ duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
11834
+ startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
11835
+ endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
11836
+ startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
11837
+ endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
11838
+ wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
11839
+ interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
11840
+ effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
11841
+ };
11842
+ }
11843
+ // This stores the values of the fields in case a block (e.g. a variable) is placed in one
11844
+ // of the inputs.
11845
+ readBlockDataSound() {
11846
+ const data = this.getBlockData();
11847
+ let sound;
11848
+ try {
11849
+ sound = JSON.parse(data);
11850
+ }
11851
+ catch (e) {
11852
+ sound = {
11853
+ duration: 1000,
11854
+ startFrequency: 100,
11855
+ endFrequency: 1800,
11856
+ startVolume: 1023,
11857
+ endVolume: 0,
11858
+ wave: "sine",
11859
+ interpolation: "linear",
11860
+ effect: "none"
11861
+ };
11862
+ }
11863
+ return sound;
11864
+ }
11865
+ }
11866
+ pxtblockly.FieldSoundEffect = FieldSoundEffect;
11867
+ const waveformMapping = {
11868
+ "sine": "WaveShape.Sine",
11869
+ "square": "WaveShape.Square",
11870
+ "sawtooth": "WaveShape.Sawtooth",
11871
+ "triangle": "WaveShape.Triangle",
11872
+ "noise": "WaveShape.Noise",
11873
+ };
11874
+ const effectMapping = {
11875
+ "none": "SoundExpressionEffect.None",
11876
+ "vibrato": "SoundExpressionEffect.Vibrato",
11877
+ "tremolo": "SoundExpressionEffect.Tremolo",
11878
+ "warble": "SoundExpressionEffect.Warble",
11879
+ };
11880
+ const interpolationMapping = {
11881
+ "linear": "InterpolationCurve.Linear",
11882
+ "curve": "InterpolationCurve.Curve",
11883
+ "logarithmic": "InterpolationCurve.Logarithmic",
11884
+ };
11885
+ function reverseLookup(map, value) {
11886
+ return Object.keys(map).find(k => map[k] === value);
11887
+ }
11888
+ })(pxtblockly || (pxtblockly = {}));
11516
11889
  /// <reference path="../../localtypings/blockly.d.ts"/>
11517
11890
  /// <reference path="../../built/pxtsim.d.ts"/>
11518
11891
  var pxtblockly;
@@ -12978,4 +13351,21 @@ var pxtblockly;
12978
13351
  }
12979
13352
  }
12980
13353
  pxtblockly.getTemporaryAssets = getTemporaryAssets;
13354
+ function workspaceToScreenCoordinates(ws, wsCoordinates) {
13355
+ // The position in pixels relative to the origin of the
13356
+ // main workspace.
13357
+ const scaledWS = wsCoordinates.scale(ws.scale);
13358
+ // The offset in pixels between the main workspace's origin and the upper
13359
+ // left corner of the injection div.
13360
+ const mainOffsetPixels = ws.getOriginOffsetInPixels();
13361
+ // The client coordinates offset by the injection div's upper left corner.
13362
+ const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
13363
+ const injectionDiv = ws.getInjectionDiv();
13364
+ // Bounding rect coordinates are in client coordinates, meaning that they
13365
+ // are in pixels relative to the upper left corner of the visible browser
13366
+ // window. These coordinates change when you scroll the browser window.
13367
+ const boundingRect = injectionDiv.getBoundingClientRect();
13368
+ return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
13369
+ }
13370
+ pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
12981
13371
  })(pxtblockly || (pxtblockly = {}));