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