pxt-core 7.5.8 → 7.5.9

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 +1002 -2
  2. package/built/pxtblockly.js +428 -50
  3. package/built/pxtblocks.d.ts +34 -0
  4. package/built/pxtblocks.js +428 -50
  5. package/built/pxtlib.d.ts +19 -1
  6. package/built/pxtlib.js +125 -1
  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) {
@@ -7304,18 +7312,21 @@ 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;
7319
7330
  appendMutation(b, {
7320
7331
  mutationToDom: (el) => {
7321
7332
  // The reason we store the inputsInitialized variable separately from visibleOptions
@@ -7330,8 +7341,8 @@ var pxt;
7330
7341
  state.setEventsEnabled(false);
7331
7342
  if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
7332
7343
  state.setValue(inputInitAttr, true);
7333
- initOptionalInputs();
7334
7344
  }
7345
+ initOptionalInputs();
7335
7346
  if (saved.hasAttribute(numVisibleAttr)) {
7336
7347
  const val = parseInt(saved.getAttribute(numVisibleAttr));
7337
7348
  if (!isNaN(val)) {
@@ -7342,6 +7353,7 @@ var pxt;
7342
7353
  }
7343
7354
  else {
7344
7355
  state.setValue(numVisibleAttr, addDelta(delta));
7356
+ updateButtons();
7345
7357
  }
7346
7358
  }
7347
7359
  else {
@@ -7352,14 +7364,29 @@ var pxt;
7352
7364
  state.setEventsEnabled(true);
7353
7365
  }
7354
7366
  });
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()) {
7360
- 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
+ }
7361
7380
  }
7362
- }, 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
+ };
7363
7390
  // Set skipRender to true if the block is still initializing. Otherwise
7364
7391
  // the inputs will render before their shadow blocks are created and
7365
7392
  // leave behind annoying artifacts
@@ -7389,25 +7416,14 @@ var pxt;
7389
7416
  setInputVisible(input, visible);
7390
7417
  if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
7391
7418
  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();
7419
+ attachShadowBlock(input, param);
7406
7420
  }
7407
7421
  ++optIndex;
7408
7422
  }
7409
7423
  }
7410
7424
  updateButtons();
7425
+ if (variableInlineInputs)
7426
+ b.setInputsInline(visibleOptions < 4);
7411
7427
  if (!skipRender)
7412
7428
  b.render();
7413
7429
  }
@@ -7416,31 +7432,32 @@ var pxt;
7416
7432
  .appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
7417
7433
  }
7418
7434
  function updateButtons() {
7435
+ if (updatingInputs)
7436
+ return;
7419
7437
  const visibleOptions = state.getNumber(numVisibleAttr);
7420
7438
  const showPlus = visibleOptions !== totalOptions;
7421
7439
  const showMinus = visibleOptions !== 0;
7422
- const hasMinus = !!b.getInput(buttonRemName);
7423
- const hasPlus = !!b.getInput(buttonAddName);
7424
- if (!showPlus) {
7440
+ if (b.inputList.some(i => i.name === buttonAddName))
7425
7441
  b.removeInput(buttonAddName, true);
7426
- }
7427
- if (!showMinus) {
7442
+ if (b.inputList.some(i => i.name === buttonRemName))
7428
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();
7429
7448
  }
7430
- if (showMinus && !hasMinus) {
7431
- addMinusButton();
7449
+ else if (showPlus) {
7450
+ addPlusButton();
7432
7451
  }
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
- }
7452
+ else if (showMinus) {
7453
+ addMinusButton();
7442
7454
  }
7443
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
+ }
7444
7461
  function addPlusButton() {
7445
7462
  addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
7446
7463
  }
@@ -7457,12 +7474,23 @@ var pxt;
7457
7474
  }
7458
7475
  function setInputVisible(input, visible) {
7459
7476
  // 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
- });
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
+ }
7465
7491
  }
7492
+ catch (e) { }
7493
+ Blockly.Events.enable();
7466
7494
  }
7467
7495
  }
7468
7496
  blocks.initExpandableBlock = initExpandableBlock;
@@ -7802,6 +7830,17 @@ var pxtblockly;
7802
7830
  this.loaded = true;
7803
7831
  this.valueText = this.onValueChanged(this.valueText);
7804
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
+ ;
7805
7844
  isInitialized() {
7806
7845
  return !!this.fieldGroup_;
7807
7846
  }
@@ -7811,6 +7850,23 @@ var pxtblockly;
7811
7850
  setBlockData(value) {
7812
7851
  pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
7813
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
+ }
7814
7870
  }
7815
7871
  pxtblockly.FieldBase = FieldBase;
7816
7872
  })(pxtblockly || (pxtblockly = {}));
@@ -11513,6 +11569,311 @@ var pxtblockly;
11513
11569
  }
11514
11570
  pxtblockly.FieldProtractor = FieldProtractor;
11515
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 = {}));
11516
11877
  /// <reference path="../../localtypings/blockly.d.ts"/>
11517
11878
  /// <reference path="../../built/pxtsim.d.ts"/>
11518
11879
  var pxtblockly;
@@ -12978,4 +13339,21 @@ var pxtblockly;
12978
13339
  }
12979
13340
  }
12980
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;
12981
13359
  })(pxtblockly || (pxtblockly = {}));
package/built/pxtlib.d.ts CHANGED
@@ -1904,7 +1904,7 @@ declare namespace pxt.react {
1904
1904
  getPersistentData(): any;
1905
1905
  restorePersistentData(value: any): void;
1906
1906
  }
1907
- let getFieldEditorView: <U>(fieldEditorId: string, value: U, options: any) => FieldEditorView<U>;
1907
+ let getFieldEditorView: <U>(fieldEditorId: string, value: U, options: any, container?: HTMLDivElement) => FieldEditorView<U>;
1908
1908
  let getTilemapProject: () => TilemapProject;
1909
1909
  }
1910
1910
  declare namespace pxt.semver {
@@ -2282,6 +2282,23 @@ declare namespace pxt.skillmap {
2282
2282
  saveUserStateAsync(user: U): Promise<void>;
2283
2283
  }
2284
2284
  }
2285
+ declare namespace pxt.assets {
2286
+ type SoundWaveForm = "square" | "sine" | "triangle" | "noise" | "sawtooth";
2287
+ type SoundInterpolation = "linear" | "curve" | "logarithmic";
2288
+ type SoundEffect = "vibrato" | "tremolo" | "warble" | "none";
2289
+ interface Sound {
2290
+ wave: SoundWaveForm;
2291
+ interpolation: SoundInterpolation;
2292
+ effect: SoundEffect;
2293
+ startFrequency: number;
2294
+ endFrequency: number;
2295
+ startVolume: number;
2296
+ endVolume: number;
2297
+ duration: number;
2298
+ }
2299
+ function renderSoundPath(sound: pxt.assets.Sound, width: number, height: number): string;
2300
+ function renderWaveSnapshot(frequency: number, volume: number, wave: SoundWaveForm, width: number, height: number, timeBase: number): string;
2301
+ }
2285
2302
  declare namespace pxt.streams {
2286
2303
  interface JsonStreamField {
2287
2304
  name: string;
@@ -2469,6 +2486,7 @@ declare namespace pxt.svgUtil {
2469
2486
  constructor();
2470
2487
  update(): this;
2471
2488
  path(cb: (d: PathContext) => void): this;
2489
+ setD(d: string): this;
2472
2490
  }
2473
2491
  class Image extends Drawable<SVGImageElement> {
2474
2492
  constructor();