pxt-core 8.4.2 → 8.4.3

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 (77) hide show
  1. package/built/backendutils.js +1 -0
  2. package/built/cli.js +83 -75
  3. package/built/pxt.js +1264 -176
  4. package/built/pxtblockly.js +323 -40
  5. package/built/pxtblocks.d.ts +30 -7
  6. package/built/pxtblocks.js +324 -41
  7. package/built/pxtlib.d.ts +91 -5
  8. package/built/pxtlib.js +1173 -98
  9. package/built/pxtsim.js +8 -3
  10. package/built/server.js +4 -0
  11. package/built/target.js +1 -1
  12. package/built/web/main.js +1 -1
  13. package/built/web/multiplayer/css/main.2dd69ed8.css +4 -0
  14. package/built/web/multiplayer/js/main.f3b8f930.js +2 -0
  15. package/built/web/pxtapp.js +1 -1
  16. package/built/web/pxtasseteditor.js +1 -1
  17. package/built/web/pxtblockly.js +1 -1
  18. package/built/web/pxtblocks.js +1 -1
  19. package/built/web/pxtembed.js +2 -2
  20. package/built/web/pxtlib.js +1 -1
  21. package/built/web/pxtsim.js +1 -1
  22. package/built/web/pxtworker.js +2 -2
  23. package/built/web/react-common-authcode.css +4 -6993
  24. package/built/web/react-common-multiplayer.css +13 -0
  25. package/built/web/react-common-skillmap.css +1 -1
  26. package/built/web/rtlreact-common-authcode.css +13 -0
  27. package/built/web/rtlreact-common-multiplayer.css +13 -0
  28. package/built/web/rtlreact-common-skillmap.css +1 -1
  29. package/built/web/rtlsemantic.css +1 -1
  30. package/built/web/semantic.css +1 -1
  31. package/built/web/skillmap/js/main.a6cf40e1.chunk.js +1 -0
  32. package/common-docs/identity/sign-in.md +17 -3
  33. package/common-docs/static/music-editor/apple.png +0 -0
  34. package/common-docs/static/music-editor/burger.png +0 -0
  35. package/common-docs/static/music-editor/cake.png +0 -0
  36. package/common-docs/static/music-editor/car.png +0 -0
  37. package/common-docs/static/music-editor/cat.png +0 -0
  38. package/common-docs/static/music-editor/cherry.png +0 -0
  39. package/common-docs/static/music-editor/clam.png +0 -0
  40. package/common-docs/static/music-editor/computer.png +0 -0
  41. package/common-docs/static/music-editor/crab.png +0 -0
  42. package/common-docs/static/music-editor/dog.png +0 -0
  43. package/common-docs/static/music-editor/duck.png +0 -0
  44. package/common-docs/static/music-editor/egg.png +0 -0
  45. package/common-docs/static/music-editor/explosion.png +0 -0
  46. package/common-docs/static/music-editor/fish.png +0 -0
  47. package/common-docs/static/music-editor/ice-cream.png +0 -0
  48. package/common-docs/static/music-editor/lemon.png +0 -0
  49. package/common-docs/static/music-editor/metronomeWorker.js +35 -0
  50. package/common-docs/static/music-editor/snake.png +0 -0
  51. package/common-docs/static/music-editor/star.png +0 -0
  52. package/common-docs/static/music-editor/strawberry.png +0 -0
  53. package/common-docs/static/music-editor/taco.png +0 -0
  54. package/common-docs/static/music-editor/treble-clef.svg +1 -0
  55. package/package.json +4 -2
  56. package/react-common/components/controls/Input.tsx +7 -3
  57. package/react-common/styles/controls/Button.less +9 -0
  58. package/react-common/styles/react-common-authcode-core.less +1 -1
  59. package/react-common/styles/react-common-authcode.less +1 -1
  60. package/react-common/styles/react-common-multiplayer-core.less +10 -0
  61. package/react-common/styles/react-common-multiplayer.less +12 -0
  62. package/theme/highcontrast.less +6 -0
  63. package/theme/music-editor/EditControls.less +22 -0
  64. package/theme/music-editor/MusicEditor.less +25 -0
  65. package/theme/music-editor/Note.less +16 -0
  66. package/theme/music-editor/NoteGroup.less +7 -0
  67. package/theme/music-editor/PlaybackControls.less +55 -0
  68. package/theme/music-editor/ScrollableWorkspace.less +3 -0
  69. package/theme/music-editor/Staff.less +31 -0
  70. package/theme/music-editor/Track.less +0 -0
  71. package/theme/music-editor/TrackSelector.less +48 -0
  72. package/theme/music-editor/Workspace.less +3 -0
  73. package/theme/pxt.less +1 -0
  74. package/theme/tutorial-sidebar.less +3 -0
  75. package/webapp/public/multiplayer.html +1 -0
  76. package/webapp/public/skillmap.html +1 -1
  77. package/built/web/skillmap/js/main.6eec9e0f.chunk.js +0 -1
@@ -5963,6 +5963,7 @@ var pxt;
5963
5963
  registerFieldEditor('melody', pxtblockly.FieldCustomMelody);
5964
5964
  registerFieldEditor('soundeffect', pxtblockly.FieldSoundEffect);
5965
5965
  registerFieldEditor('autocomplete', pxtblockly.FieldAutoComplete);
5966
+ registerFieldEditor('musiceditor', pxtblockly.FieldMusicEditor);
5966
5967
  }
5967
5968
  blocks.initFieldEditors = initFieldEditors;
5968
5969
  function registerFieldEditor(selector, field, validator) {
@@ -11584,50 +11585,159 @@ var pxtblockly;
11584
11585
  const project = pxt.react.getTilemapProject();
11585
11586
  pxt.sprite.addMissingTilemapTilesAndReferences(project, this.asset);
11586
11587
  break;
11588
+ case "song" /* pxt.AssetType.Song */:
11589
+ editorKind = "music-editor";
11590
+ params.temporaryAssets = pxtblockly.getTemporaryAssets(this.sourceBlock_.workspace, "song" /* pxt.AssetType.Song */);
11591
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, true);
11592
+ break;
11593
+ }
11594
+ if (this.isFullscreen()) {
11595
+ this.showEditorFullscreen(editorKind, params);
11596
+ }
11597
+ else {
11598
+ this.showEditorInWidgetDiv(editorKind, params);
11587
11599
  }
11600
+ }
11601
+ showEditorFullscreen(editorKind, params) {
11588
11602
  const fv = pxt.react.getFieldEditorView(editorKind, this.asset, params);
11589
11603
  if (this.undoRedoState) {
11590
11604
  fv.restorePersistentData(this.undoRedoState);
11591
11605
  }
11592
11606
  pxt.react.getTilemapProject().pushUndo();
11593
11607
  fv.onHide(() => {
11594
- var _a;
11595
- const result = fv.getResult();
11596
- const project = pxt.react.getTilemapProject();
11597
- if (result) {
11598
- const old = this.getValue();
11599
- if (pxt.assetEquals(this.asset, result))
11600
- return;
11601
- const oldId = isTemporaryAsset(this.asset) ? null : this.asset.id;
11602
- let newId = isTemporaryAsset(result) ? null : result.id;
11603
- if (!oldId && newId === this.sourceBlock_.id) {
11604
- // The temporary assets we create just use the block id as the id; give it something
11605
- // a little nicer
11606
- result.id = project.generateNewID(result.type);
11607
- newId = result.id;
11608
+ this.onFieldEditorHide(fv);
11609
+ });
11610
+ fv.show();
11611
+ }
11612
+ showEditorInWidgetDiv(editorKind, params) {
11613
+ let bbox;
11614
+ // This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
11615
+ // which caused the widgetdiv to jump around if any fields underneath changed size
11616
+ let widgetOwner = {
11617
+ getScaledBBox: () => bbox
11618
+ };
11619
+ Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
11620
+ if (document.activeElement && document.activeElement.tagName === "INPUT")
11621
+ document.activeElement.blur();
11622
+ fv.hide();
11623
+ widgetDiv.classList.remove("sound-effect-editor-widget");
11624
+ widgetDiv.style.transform = "";
11625
+ widgetDiv.style.position = "";
11626
+ widgetDiv.style.left = "";
11627
+ widgetDiv.style.top = "";
11628
+ widgetDiv.style.width = "";
11629
+ widgetDiv.style.height = "";
11630
+ widgetDiv.style.opacity = "";
11631
+ widgetDiv.style.transition = "";
11632
+ widgetDiv.style.alignItems = "";
11633
+ this.onFieldEditorHide(fv);
11634
+ });
11635
+ const widgetDiv = Blockly.WidgetDiv.DIV;
11636
+ const fv = pxt.react.getFieldEditorView(editorKind, this.asset, params, widgetDiv);
11637
+ const block = this.sourceBlock_;
11638
+ const bounds = block.getBoundingRectangle();
11639
+ const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
11640
+ const animationDistance = 20;
11641
+ const left = coord.x - 400;
11642
+ const top = coord.y + 60 - animationDistance;
11643
+ widgetDiv.style.opacity = "0";
11644
+ widgetDiv.classList.add("sound-effect-editor-widget");
11645
+ widgetDiv.style.position = "absolute";
11646
+ widgetDiv.style.left = left + "px";
11647
+ widgetDiv.style.top = top + "px";
11648
+ widgetDiv.style.width = "50rem";
11649
+ widgetDiv.style.height = "34.25rem";
11650
+ widgetDiv.style.display = "flex";
11651
+ widgetDiv.style.alignItems = "center";
11652
+ widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
11653
+ widgetDiv.style.borderRadius = "";
11654
+ fv.onHide(() => {
11655
+ Blockly.WidgetDiv.hideIfOwner(widgetOwner);
11656
+ });
11657
+ fv.show();
11658
+ const divBounds = widgetDiv.getBoundingClientRect();
11659
+ const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
11660
+ if (divBounds.height > injectDivBounds.height) {
11661
+ widgetDiv.style.height = "";
11662
+ widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
11663
+ widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
11664
+ }
11665
+ else {
11666
+ if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
11667
+ // This editor is pretty tall, so just center vertically on the inject div
11668
+ widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
11669
+ }
11670
+ }
11671
+ const toolboxWidth = block.workspace.getToolbox().getWidth();
11672
+ const workspaceLeft = injectDivBounds.left + toolboxWidth;
11673
+ if (divBounds.width > injectDivBounds.width - toolboxWidth) {
11674
+ widgetDiv.style.width = "";
11675
+ widgetDiv.style.left = "1rem";
11676
+ widgetDiv.style.right = "1rem";
11677
+ }
11678
+ else {
11679
+ // Check to see if we are bleeding off the right side of the canvas
11680
+ if (divBounds.left + divBounds.width >= injectDivBounds.right) {
11681
+ // If so, try and place to the left of the block instead of the right
11682
+ const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
11683
+ if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
11684
+ widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
11608
11685
  }
11609
- this.pendingEdit = true;
11610
- if ((_a = result.meta) === null || _a === void 0 ? void 0 : _a.displayName)
11611
- this.disposeOfTemporaryAsset();
11612
- this.asset = result;
11613
- const lastRevision = project.revision();
11614
- this.onEditorClose(this.asset);
11615
- this.updateAssetListener();
11616
- this.updateAssetMeta();
11617
- this.redrawPreview();
11618
- this.undoRedoState = fv.getPersistentData();
11619
- if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
11620
- const event = new BlocklyTilemapChange(this.sourceBlock_, 'field', this.name, old, this.getValue(), lastRevision, project.revision());
11621
- if (oldId !== newId) {
11622
- event.oldAssetId = oldId;
11623
- event.newAssetId = newId;
11624
- }
11625
- Blockly.Events.fire(event);
11686
+ else {
11687
+ // As a last resort, just center on the inject div
11688
+ widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
11626
11689
  }
11627
- this.pendingEdit = false;
11628
11690
  }
11691
+ else if (divBounds.left < injectDivBounds.left) {
11692
+ widgetDiv.style.left = workspaceLeft + "px";
11693
+ }
11694
+ }
11695
+ const finalDimensions = widgetDiv.getBoundingClientRect();
11696
+ bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
11697
+ requestAnimationFrame(() => {
11698
+ widgetDiv.style.opacity = "1";
11699
+ widgetDiv.style.transform = `translateY(${animationDistance}px)`;
11629
11700
  });
11630
- fv.show();
11701
+ }
11702
+ onFieldEditorHide(fv) {
11703
+ var _a;
11704
+ const result = fv.getResult();
11705
+ const project = pxt.react.getTilemapProject();
11706
+ if (this.asset.type === "song" /* pxt.AssetType.Song */) {
11707
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, false);
11708
+ }
11709
+ if (result) {
11710
+ const old = this.getValue();
11711
+ if (pxt.assetEquals(this.asset, result))
11712
+ return;
11713
+ const oldId = isTemporaryAsset(this.asset) ? null : this.asset.id;
11714
+ let newId = isTemporaryAsset(result) ? null : result.id;
11715
+ if (!oldId && newId === this.sourceBlock_.id) {
11716
+ // The temporary assets we create just use the block id as the id; give it something
11717
+ // a little nicer
11718
+ result.id = project.generateNewID(result.type);
11719
+ newId = result.id;
11720
+ }
11721
+ this.pendingEdit = true;
11722
+ if ((_a = result.meta) === null || _a === void 0 ? void 0 : _a.displayName)
11723
+ this.disposeOfTemporaryAsset();
11724
+ this.asset = result;
11725
+ const lastRevision = project.revision();
11726
+ this.onEditorClose(this.asset);
11727
+ this.updateAssetListener();
11728
+ this.updateAssetMeta();
11729
+ this.redrawPreview();
11730
+ this.undoRedoState = fv.getPersistentData();
11731
+ if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
11732
+ const event = new BlocklyTilemapChange(this.sourceBlock_, 'field', this.name, old, this.getValue(), lastRevision, project.revision());
11733
+ if (oldId !== newId) {
11734
+ event.oldAssetId = oldId;
11735
+ event.newAssetId = newId;
11736
+ }
11737
+ Blockly.Events.fire(event);
11738
+ }
11739
+ this.pendingEdit = false;
11740
+ }
11631
11741
  }
11632
11742
  render_() {
11633
11743
  if (this.isGreyBlock && !this.textElement_) {
@@ -11725,12 +11835,17 @@ var pxtblockly;
11725
11835
  case "tilemap" /* pxt.AssetType.Tilemap */:
11726
11836
  dataURI = pxtblockly.tilemapToImageURI(this.asset.data, PREVIEW_WIDTH, this.lightMode);
11727
11837
  break;
11838
+ case "song" /* pxt.AssetType.Song */:
11839
+ dataURI = pxtblockly.songToDataURI(this.asset.song, 60, 20, this.lightMode);
11840
+ break;
11841
+ }
11842
+ if (dataURI) {
11843
+ const img = new svg.Image()
11844
+ .src(dataURI)
11845
+ .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
11846
+ .size(PREVIEW_WIDTH, PREVIEW_WIDTH);
11847
+ this.fieldGroup_.appendChild(img.el);
11728
11848
  }
11729
- const img = new svg.Image()
11730
- .src(dataURI)
11731
- .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
11732
- .size(PREVIEW_WIDTH, PREVIEW_WIDTH);
11733
- this.fieldGroup_.appendChild(img.el);
11734
11849
  }
11735
11850
  }
11736
11851
  parseValueText(newText) {
@@ -11821,6 +11936,9 @@ var pxtblockly;
11821
11936
  pxt.react.getTilemapProject().addChangeListener(this.asset, this.assetChangeListener);
11822
11937
  }
11823
11938
  }
11939
+ isFullscreen() {
11940
+ return true;
11941
+ }
11824
11942
  }
11825
11943
  pxtblockly.FieldAssetEditor = FieldAssetEditor;
11826
11944
  function isTemporaryAsset(asset) {
@@ -13887,13 +14005,13 @@ var pxtblockly;
13887
14005
  this.prevString = this.getValue();
13888
14006
  // The webapp listens to this event and stops the simulator so that you don't get the melody
13889
14007
  // playing twice (once in the editor and once when the code runs in the sim)
13890
- Blockly.Events.fire(new Blockly.Events.Ui(this.sourceBlock_, "melody-editor", false, true));
14008
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, true);
13891
14009
  Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_, () => {
13892
14010
  this.onEditorClose();
13893
14011
  // revert all style attributes for dropdown div
13894
14012
  pxt.BrowserUtils.removeClass(contentDiv, "melody-content-div");
13895
14013
  pxt.BrowserUtils.removeClass(contentDiv.parentElement, "melody-editor-dropdown");
13896
- Blockly.Events.fire(new Blockly.Events.Ui(this.sourceBlock_, "melody-editor", true, false));
14014
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, false);
13897
14015
  });
13898
14016
  }
13899
14017
  getValue() {
@@ -14561,6 +14679,116 @@ var pxtblockly;
14561
14679
  return "#DCDCDC";
14562
14680
  }
14563
14681
  })(pxtblockly || (pxtblockly = {}));
14682
+ /// <reference path="../../built/pxtlib.d.ts" />
14683
+ /// <reference path="./field_asset.ts" />
14684
+ var pxtblockly;
14685
+ (function (pxtblockly) {
14686
+ var svg = pxt.svgUtil;
14687
+ const PREVIEW_HEIGHT = 32;
14688
+ const X_PADDING = 5;
14689
+ const Y_PADDING = 1;
14690
+ const BG_PADDING = 4;
14691
+ const BG_HEIGHT = BG_PADDING * 2 + PREVIEW_HEIGHT;
14692
+ const TOTAL_HEIGHT = Y_PADDING * 2 + BG_PADDING * 2 + PREVIEW_HEIGHT;
14693
+ class FieldMusicEditor extends pxtblockly.FieldAssetEditor {
14694
+ getAssetType() {
14695
+ return "song" /* pxt.AssetType.Song */;
14696
+ }
14697
+ createNewAsset(text) {
14698
+ const project = pxt.react.getTilemapProject();
14699
+ if (text) {
14700
+ const asset = pxt.lookupProjectAssetByTSReference(text, project);
14701
+ if (asset)
14702
+ return asset;
14703
+ }
14704
+ if (this.getBlockData()) {
14705
+ return project.lookupAsset("song" /* pxt.AssetType.Song */, this.getBlockData());
14706
+ }
14707
+ let song;
14708
+ if (text) {
14709
+ const match = /^\s*hex\s*`([a-fA-F0-9]+)`\s*(?:;?)\s*$/.exec(text);
14710
+ if (match) {
14711
+ song = pxt.assets.music.decodeSongFromHex(match[1]);
14712
+ }
14713
+ }
14714
+ else {
14715
+ song = pxt.assets.music.getEmptySong(2);
14716
+ }
14717
+ if (!song) {
14718
+ this.isGreyBlock = true;
14719
+ this.valueText = text;
14720
+ return undefined;
14721
+ }
14722
+ else {
14723
+ // Restore all of the unused tracks
14724
+ pxt.assets.music.inflateSong(song);
14725
+ }
14726
+ const newAsset = {
14727
+ internalID: -1,
14728
+ id: this.sourceBlock_.id,
14729
+ type: "song" /* pxt.AssetType.Song */,
14730
+ meta: {},
14731
+ song
14732
+ };
14733
+ return newAsset;
14734
+ }
14735
+ render_() {
14736
+ super.render_();
14737
+ if (!this.isGreyBlock) {
14738
+ this.size_.height = TOTAL_HEIGHT;
14739
+ this.size_.width = X_PADDING * 2 + BG_PADDING * 2 + this.previewWidth();
14740
+ }
14741
+ }
14742
+ getValueText() {
14743
+ if (this.asset && !this.isTemporaryAsset()) {
14744
+ return pxt.getTSReferenceForAsset(this.asset);
14745
+ }
14746
+ return this.asset ? `hex\`${pxt.assets.music.encodeSongToHex(this.asset.song)}\`` : "";
14747
+ }
14748
+ parseFieldOptions(opts) {
14749
+ return {};
14750
+ }
14751
+ isFullscreen() {
14752
+ return false;
14753
+ }
14754
+ redrawPreview() {
14755
+ var _a;
14756
+ if (!this.fieldGroup_)
14757
+ return;
14758
+ pxsim.U.clear(this.fieldGroup_);
14759
+ if (this.isGreyBlock) {
14760
+ super.redrawPreview();
14761
+ return;
14762
+ }
14763
+ const totalWidth = X_PADDING * 2 + BG_PADDING * 2 + this.previewWidth();
14764
+ const bg = new svg.Rect()
14765
+ .at(X_PADDING, Y_PADDING)
14766
+ .size(BG_PADDING * 2 + this.previewWidth(), BG_HEIGHT)
14767
+ .setClass("blocklySpriteField")
14768
+ .stroke("#898989", 1)
14769
+ .corner(4);
14770
+ this.fieldGroup_.appendChild(bg.el);
14771
+ if (this.asset) {
14772
+ const dataURI = pxtblockly.songToDataURI(this.asset.song, this.previewWidth(), PREVIEW_HEIGHT, this.lightMode);
14773
+ if (dataURI) {
14774
+ const img = new svg.Image()
14775
+ .src(dataURI)
14776
+ .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
14777
+ .size(this.previewWidth(), PREVIEW_HEIGHT);
14778
+ this.fieldGroup_.appendChild(img.el);
14779
+ }
14780
+ }
14781
+ if (((_a = this.size_) === null || _a === void 0 ? void 0 : _a.width) != totalWidth) {
14782
+ this.forceRerender();
14783
+ }
14784
+ }
14785
+ previewWidth() {
14786
+ const measures = this.asset ? this.asset.song.measures : 2;
14787
+ return measures * PREVIEW_HEIGHT;
14788
+ }
14789
+ }
14790
+ pxtblockly.FieldMusicEditor = FieldMusicEditor;
14791
+ })(pxtblockly || (pxtblockly = {}));
14564
14792
  /// <reference path="../../localtypings/pxtblockly.d.ts" />
14565
14793
  var pxtblockly;
14566
14794
  (function (pxtblockly) {
@@ -17096,6 +17324,54 @@ var pxtblockly;
17096
17324
  return canvas.toDataURL();
17097
17325
  }
17098
17326
  pxtblockly.tilemapToImageURI = tilemapToImageURI;
17327
+ function songToDataURI(song, width, height, lightMode, maxMeasures) {
17328
+ const colors = pxt.appTarget.runtime.palette.slice();
17329
+ const canvas = document.createElement("canvas");
17330
+ canvas.width = width;
17331
+ canvas.height = height;
17332
+ let context;
17333
+ if (lightMode) {
17334
+ context = canvas.getContext("2d", { alpha: false });
17335
+ context.fillStyle = "#dedede";
17336
+ context.fillRect(0, 0, width, height);
17337
+ }
17338
+ else {
17339
+ context = canvas.getContext("2d");
17340
+ }
17341
+ const trackColors = [
17342
+ 5,
17343
+ 11,
17344
+ 5,
17345
+ 4,
17346
+ 2,
17347
+ 6,
17348
+ 14,
17349
+ 2,
17350
+ 5,
17351
+ 1, // explosion
17352
+ ];
17353
+ maxMeasures = maxMeasures || song.measures;
17354
+ const cellWidth = Math.max(Math.floor(width / (song.beatsPerMeasure * maxMeasures * 2)), 1);
17355
+ const cellsShown = Math.floor(width / cellWidth);
17356
+ const cellHeight = Math.max(Math.floor(height / 12), 1);
17357
+ const notesShown = Math.floor(height / cellHeight);
17358
+ for (const track of song.tracks) {
17359
+ for (const noteEvent of track.notes) {
17360
+ const col = Math.floor(noteEvent.startTick / (song.ticksPerBeat / 2));
17361
+ if (col > cellsShown)
17362
+ break;
17363
+ for (const note of noteEvent.notes) {
17364
+ const row = 12 - (note % 12);
17365
+ if (row > notesShown)
17366
+ continue;
17367
+ context.fillStyle = colors[trackColors[track.id || song.tracks.indexOf(track)]];
17368
+ context.fillRect(col * cellWidth, row * cellHeight, cellWidth, cellHeight);
17369
+ }
17370
+ }
17371
+ }
17372
+ return canvas.toDataURL();
17373
+ }
17374
+ pxtblockly.songToDataURI = songToDataURI;
17099
17375
  function deleteTilesetTileIfExists(ws, tile) {
17100
17376
  const existing = ws.getVariablesOfType(pxt.sprite.BLOCKLY_TILESET_TYPE);
17101
17377
  for (const model of existing) {
@@ -17231,10 +17507,17 @@ var pxtblockly;
17231
17507
  case "animation" /* pxt.AssetType.Animation */:
17232
17508
  return getAllFields(workspace, field => field instanceof pxtblockly.FieldAnimationEditor && field.isTemporaryAsset())
17233
17509
  .map(f => f.ref.getAsset());
17510
+ case "song" /* pxt.AssetType.Song */:
17511
+ return getAllFields(workspace, field => field instanceof pxtblockly.FieldMusicEditor && field.isTemporaryAsset())
17512
+ .map(f => f.ref.getAsset());
17234
17513
  default: return [];
17235
17514
  }
17236
17515
  }
17237
17516
  pxtblockly.getTemporaryAssets = getTemporaryAssets;
17517
+ function setMelodyEditorOpen(block, isOpen) {
17518
+ Blockly.Events.fire(new Blockly.Events.Ui(block, "melody-editor", !isOpen, isOpen));
17519
+ }
17520
+ pxtblockly.setMelodyEditorOpen = setMelodyEditorOpen;
17238
17521
  function workspaceToScreenCoordinates(ws, wsCoordinates) {
17239
17522
  // The position in pixels relative to the origin of the
17240
17523
  // main workspace.
@@ -411,15 +411,15 @@ declare namespace pxtblockly {
411
411
  }
412
412
  declare namespace pxtblockly {
413
413
  export interface FieldAssetEditorOptions {
414
- initWidth: string;
415
- initHeight: string;
416
- disableResize: string;
414
+ initWidth?: string;
415
+ initHeight?: string;
416
+ disableResize?: string;
417
417
  }
418
418
  interface ParsedFieldAssetEditorOptions {
419
- initWidth: number;
420
- initHeight: number;
421
- disableResize: boolean;
422
- lightMode: boolean;
419
+ initWidth?: number;
420
+ initHeight?: number;
421
+ disableResize?: boolean;
422
+ lightMode?: boolean;
423
423
  }
424
424
  export abstract class FieldAssetEditor<U extends FieldAssetEditorOptions, V extends ParsedFieldAssetEditorOptions> extends FieldBase<U> {
425
425
  protected asset: pxt.Asset;
@@ -437,6 +437,9 @@ declare namespace pxtblockly {
437
437
  onInit(): void;
438
438
  onValueChanged(newValue: string): string;
439
439
  showEditor_(): void;
440
+ protected showEditorFullscreen(editorKind: string, params: any): void;
441
+ protected showEditorInWidgetDiv(editorKind: string, params: any): void;
442
+ protected onFieldEditorHide(fv: pxt.react.FieldEditorView<pxt.Asset>): void;
440
443
  render_(): void;
441
444
  getDisplayText_(): string;
442
445
  updateEditable(): void;
@@ -454,6 +457,7 @@ declare namespace pxtblockly {
454
457
  protected updateAssetMeta(): void;
455
458
  protected updateAssetListener(): void;
456
459
  protected assetChangeListener: () => void;
460
+ protected isFullscreen(): boolean;
457
461
  }
458
462
  export class BlocklyTilemapChange extends Blockly.Events.BlockChange {
459
463
  protected oldRevision: number;
@@ -981,6 +985,23 @@ declare namespace pxtblockly {
981
985
  cy: number;
982
986
  }
983
987
  }
988
+ declare namespace pxtblockly {
989
+ export interface FieldMusicEditorOptions {
990
+ }
991
+ interface ParsedFieldMusicEditorOptions {
992
+ }
993
+ export class FieldMusicEditor extends FieldAssetEditor<FieldMusicEditorOptions, ParsedFieldMusicEditorOptions> {
994
+ protected getAssetType(): pxt.AssetType;
995
+ protected createNewAsset(text?: string): pxt.Asset;
996
+ render_(): void;
997
+ protected getValueText(): string;
998
+ protected parseFieldOptions(opts: FieldMusicEditorOptions): ParsedFieldMusicEditorOptions;
999
+ protected isFullscreen(): boolean;
1000
+ protected redrawPreview(): void;
1001
+ protected previewWidth(): number;
1002
+ }
1003
+ export {};
1004
+ }
984
1005
  declare namespace pxtblockly {
985
1006
  interface FieldNoteOptions extends Blockly.FieldCustomOptions {
986
1007
  editorColour?: string;
@@ -1498,6 +1519,7 @@ declare namespace pxtblockly {
1498
1519
  */
1499
1520
  function bitmapToImageURI(frame: pxt.sprite.Bitmap, sideLength: number, lightMode: boolean): string;
1500
1521
  function tilemapToImageURI(data: pxt.sprite.TilemapData, sideLength: number, lightMode: boolean): string;
1522
+ function songToDataURI(song: pxt.assets.music.Song, width: number, height: number, lightMode: boolean, maxMeasures?: number): string;
1501
1523
  interface FieldEditorReference<U extends Blockly.Field> {
1502
1524
  block: Blockly.Block;
1503
1525
  field: string;
@@ -1511,5 +1533,6 @@ declare namespace pxtblockly {
1511
1533
  function getAllFields<U extends Blockly.Field>(ws: Blockly.Workspace, predicate: (field: Blockly.Field) => boolean): FieldEditorReference<U>[];
1512
1534
  function getAllReferencedTiles(workspace: Blockly.Workspace, excludeBlockID?: string): pxt.Tile[];
1513
1535
  function getTemporaryAssets(workspace: Blockly.Workspace, type: pxt.AssetType): pxt.Asset[];
1536
+ function setMelodyEditorOpen(block: Blockly.Block, isOpen: boolean): void;
1514
1537
  function workspaceToScreenCoordinates(ws: Blockly.WorkspaceSvg, wsCoordinates: Blockly.utils.Coordinate): Blockly.utils.Coordinate;
1515
1538
  }