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
@@ -2401,6 +2401,7 @@ var pxt;
2401
2401
  registerFieldEditor('melody', pxtblockly.FieldCustomMelody);
2402
2402
  registerFieldEditor('soundeffect', pxtblockly.FieldSoundEffect);
2403
2403
  registerFieldEditor('autocomplete', pxtblockly.FieldAutoComplete);
2404
+ registerFieldEditor('musiceditor', pxtblockly.FieldMusicEditor);
2404
2405
  }
2405
2406
  blocks.initFieldEditors = initFieldEditors;
2406
2407
  function registerFieldEditor(selector, field, validator) {
@@ -8022,50 +8023,159 @@ var pxtblockly;
8022
8023
  const project = pxt.react.getTilemapProject();
8023
8024
  pxt.sprite.addMissingTilemapTilesAndReferences(project, this.asset);
8024
8025
  break;
8026
+ case "song" /* pxt.AssetType.Song */:
8027
+ editorKind = "music-editor";
8028
+ params.temporaryAssets = pxtblockly.getTemporaryAssets(this.sourceBlock_.workspace, "song" /* pxt.AssetType.Song */);
8029
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, true);
8030
+ break;
8031
+ }
8032
+ if (this.isFullscreen()) {
8033
+ this.showEditorFullscreen(editorKind, params);
8034
+ }
8035
+ else {
8036
+ this.showEditorInWidgetDiv(editorKind, params);
8025
8037
  }
8038
+ }
8039
+ showEditorFullscreen(editorKind, params) {
8026
8040
  const fv = pxt.react.getFieldEditorView(editorKind, this.asset, params);
8027
8041
  if (this.undoRedoState) {
8028
8042
  fv.restorePersistentData(this.undoRedoState);
8029
8043
  }
8030
8044
  pxt.react.getTilemapProject().pushUndo();
8031
8045
  fv.onHide(() => {
8032
- var _a;
8033
- const result = fv.getResult();
8034
- const project = pxt.react.getTilemapProject();
8035
- if (result) {
8036
- const old = this.getValue();
8037
- if (pxt.assetEquals(this.asset, result))
8038
- return;
8039
- const oldId = isTemporaryAsset(this.asset) ? null : this.asset.id;
8040
- let newId = isTemporaryAsset(result) ? null : result.id;
8041
- if (!oldId && newId === this.sourceBlock_.id) {
8042
- // The temporary assets we create just use the block id as the id; give it something
8043
- // a little nicer
8044
- result.id = project.generateNewID(result.type);
8045
- newId = result.id;
8046
- }
8047
- this.pendingEdit = true;
8048
- if ((_a = result.meta) === null || _a === void 0 ? void 0 : _a.displayName)
8049
- this.disposeOfTemporaryAsset();
8050
- this.asset = result;
8051
- const lastRevision = project.revision();
8052
- this.onEditorClose(this.asset);
8053
- this.updateAssetListener();
8054
- this.updateAssetMeta();
8055
- this.redrawPreview();
8056
- this.undoRedoState = fv.getPersistentData();
8057
- if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
8058
- const event = new BlocklyTilemapChange(this.sourceBlock_, 'field', this.name, old, this.getValue(), lastRevision, project.revision());
8059
- if (oldId !== newId) {
8060
- event.oldAssetId = oldId;
8061
- event.newAssetId = newId;
8062
- }
8063
- Blockly.Events.fire(event);
8046
+ this.onFieldEditorHide(fv);
8047
+ });
8048
+ fv.show();
8049
+ }
8050
+ showEditorInWidgetDiv(editorKind, params) {
8051
+ let bbox;
8052
+ // This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
8053
+ // which caused the widgetdiv to jump around if any fields underneath changed size
8054
+ let widgetOwner = {
8055
+ getScaledBBox: () => bbox
8056
+ };
8057
+ Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
8058
+ if (document.activeElement && document.activeElement.tagName === "INPUT")
8059
+ document.activeElement.blur();
8060
+ fv.hide();
8061
+ widgetDiv.classList.remove("sound-effect-editor-widget");
8062
+ widgetDiv.style.transform = "";
8063
+ widgetDiv.style.position = "";
8064
+ widgetDiv.style.left = "";
8065
+ widgetDiv.style.top = "";
8066
+ widgetDiv.style.width = "";
8067
+ widgetDiv.style.height = "";
8068
+ widgetDiv.style.opacity = "";
8069
+ widgetDiv.style.transition = "";
8070
+ widgetDiv.style.alignItems = "";
8071
+ this.onFieldEditorHide(fv);
8072
+ });
8073
+ const widgetDiv = Blockly.WidgetDiv.DIV;
8074
+ const fv = pxt.react.getFieldEditorView(editorKind, this.asset, params, widgetDiv);
8075
+ const block = this.sourceBlock_;
8076
+ const bounds = block.getBoundingRectangle();
8077
+ const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
8078
+ const animationDistance = 20;
8079
+ const left = coord.x - 400;
8080
+ const top = coord.y + 60 - animationDistance;
8081
+ widgetDiv.style.opacity = "0";
8082
+ widgetDiv.classList.add("sound-effect-editor-widget");
8083
+ widgetDiv.style.position = "absolute";
8084
+ widgetDiv.style.left = left + "px";
8085
+ widgetDiv.style.top = top + "px";
8086
+ widgetDiv.style.width = "50rem";
8087
+ widgetDiv.style.height = "34.25rem";
8088
+ widgetDiv.style.display = "flex";
8089
+ widgetDiv.style.alignItems = "center";
8090
+ widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
8091
+ widgetDiv.style.borderRadius = "";
8092
+ fv.onHide(() => {
8093
+ Blockly.WidgetDiv.hideIfOwner(widgetOwner);
8094
+ });
8095
+ fv.show();
8096
+ const divBounds = widgetDiv.getBoundingClientRect();
8097
+ const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
8098
+ if (divBounds.height > injectDivBounds.height) {
8099
+ widgetDiv.style.height = "";
8100
+ widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
8101
+ widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
8102
+ }
8103
+ else {
8104
+ if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
8105
+ // This editor is pretty tall, so just center vertically on the inject div
8106
+ widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
8107
+ }
8108
+ }
8109
+ const toolboxWidth = block.workspace.getToolbox().getWidth();
8110
+ const workspaceLeft = injectDivBounds.left + toolboxWidth;
8111
+ if (divBounds.width > injectDivBounds.width - toolboxWidth) {
8112
+ widgetDiv.style.width = "";
8113
+ widgetDiv.style.left = "1rem";
8114
+ widgetDiv.style.right = "1rem";
8115
+ }
8116
+ else {
8117
+ // Check to see if we are bleeding off the right side of the canvas
8118
+ if (divBounds.left + divBounds.width >= injectDivBounds.right) {
8119
+ // If so, try and place to the left of the block instead of the right
8120
+ const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
8121
+ if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
8122
+ widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
8064
8123
  }
8065
- this.pendingEdit = false;
8124
+ else {
8125
+ // As a last resort, just center on the inject div
8126
+ widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
8127
+ }
8128
+ }
8129
+ else if (divBounds.left < injectDivBounds.left) {
8130
+ widgetDiv.style.left = workspaceLeft + "px";
8066
8131
  }
8132
+ }
8133
+ const finalDimensions = widgetDiv.getBoundingClientRect();
8134
+ bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
8135
+ requestAnimationFrame(() => {
8136
+ widgetDiv.style.opacity = "1";
8137
+ widgetDiv.style.transform = `translateY(${animationDistance}px)`;
8067
8138
  });
8068
- fv.show();
8139
+ }
8140
+ onFieldEditorHide(fv) {
8141
+ var _a;
8142
+ const result = fv.getResult();
8143
+ const project = pxt.react.getTilemapProject();
8144
+ if (this.asset.type === "song" /* pxt.AssetType.Song */) {
8145
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, false);
8146
+ }
8147
+ if (result) {
8148
+ const old = this.getValue();
8149
+ if (pxt.assetEquals(this.asset, result))
8150
+ return;
8151
+ const oldId = isTemporaryAsset(this.asset) ? null : this.asset.id;
8152
+ let newId = isTemporaryAsset(result) ? null : result.id;
8153
+ if (!oldId && newId === this.sourceBlock_.id) {
8154
+ // The temporary assets we create just use the block id as the id; give it something
8155
+ // a little nicer
8156
+ result.id = project.generateNewID(result.type);
8157
+ newId = result.id;
8158
+ }
8159
+ this.pendingEdit = true;
8160
+ if ((_a = result.meta) === null || _a === void 0 ? void 0 : _a.displayName)
8161
+ this.disposeOfTemporaryAsset();
8162
+ this.asset = result;
8163
+ const lastRevision = project.revision();
8164
+ this.onEditorClose(this.asset);
8165
+ this.updateAssetListener();
8166
+ this.updateAssetMeta();
8167
+ this.redrawPreview();
8168
+ this.undoRedoState = fv.getPersistentData();
8169
+ if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
8170
+ const event = new BlocklyTilemapChange(this.sourceBlock_, 'field', this.name, old, this.getValue(), lastRevision, project.revision());
8171
+ if (oldId !== newId) {
8172
+ event.oldAssetId = oldId;
8173
+ event.newAssetId = newId;
8174
+ }
8175
+ Blockly.Events.fire(event);
8176
+ }
8177
+ this.pendingEdit = false;
8178
+ }
8069
8179
  }
8070
8180
  render_() {
8071
8181
  if (this.isGreyBlock && !this.textElement_) {
@@ -8163,12 +8273,17 @@ var pxtblockly;
8163
8273
  case "tilemap" /* pxt.AssetType.Tilemap */:
8164
8274
  dataURI = pxtblockly.tilemapToImageURI(this.asset.data, PREVIEW_WIDTH, this.lightMode);
8165
8275
  break;
8276
+ case "song" /* pxt.AssetType.Song */:
8277
+ dataURI = pxtblockly.songToDataURI(this.asset.song, 60, 20, this.lightMode);
8278
+ break;
8279
+ }
8280
+ if (dataURI) {
8281
+ const img = new svg.Image()
8282
+ .src(dataURI)
8283
+ .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
8284
+ .size(PREVIEW_WIDTH, PREVIEW_WIDTH);
8285
+ this.fieldGroup_.appendChild(img.el);
8166
8286
  }
8167
- const img = new svg.Image()
8168
- .src(dataURI)
8169
- .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
8170
- .size(PREVIEW_WIDTH, PREVIEW_WIDTH);
8171
- this.fieldGroup_.appendChild(img.el);
8172
8287
  }
8173
8288
  }
8174
8289
  parseValueText(newText) {
@@ -8259,6 +8374,9 @@ var pxtblockly;
8259
8374
  pxt.react.getTilemapProject().addChangeListener(this.asset, this.assetChangeListener);
8260
8375
  }
8261
8376
  }
8377
+ isFullscreen() {
8378
+ return true;
8379
+ }
8262
8380
  }
8263
8381
  pxtblockly.FieldAssetEditor = FieldAssetEditor;
8264
8382
  function isTemporaryAsset(asset) {
@@ -10325,13 +10443,13 @@ var pxtblockly;
10325
10443
  this.prevString = this.getValue();
10326
10444
  // The webapp listens to this event and stops the simulator so that you don't get the melody
10327
10445
  // playing twice (once in the editor and once when the code runs in the sim)
10328
- Blockly.Events.fire(new Blockly.Events.Ui(this.sourceBlock_, "melody-editor", false, true));
10446
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, true);
10329
10447
  Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_, () => {
10330
10448
  this.onEditorClose();
10331
10449
  // revert all style attributes for dropdown div
10332
10450
  pxt.BrowserUtils.removeClass(contentDiv, "melody-content-div");
10333
10451
  pxt.BrowserUtils.removeClass(contentDiv.parentElement, "melody-editor-dropdown");
10334
- Blockly.Events.fire(new Blockly.Events.Ui(this.sourceBlock_, "melody-editor", true, false));
10452
+ pxtblockly.setMelodyEditorOpen(this.sourceBlock_, false);
10335
10453
  });
10336
10454
  }
10337
10455
  getValue() {
@@ -10999,6 +11117,116 @@ var pxtblockly;
10999
11117
  return "#DCDCDC";
11000
11118
  }
11001
11119
  })(pxtblockly || (pxtblockly = {}));
11120
+ /// <reference path="../../built/pxtlib.d.ts" />
11121
+ /// <reference path="./field_asset.ts" />
11122
+ var pxtblockly;
11123
+ (function (pxtblockly) {
11124
+ var svg = pxt.svgUtil;
11125
+ const PREVIEW_HEIGHT = 32;
11126
+ const X_PADDING = 5;
11127
+ const Y_PADDING = 1;
11128
+ const BG_PADDING = 4;
11129
+ const BG_HEIGHT = BG_PADDING * 2 + PREVIEW_HEIGHT;
11130
+ const TOTAL_HEIGHT = Y_PADDING * 2 + BG_PADDING * 2 + PREVIEW_HEIGHT;
11131
+ class FieldMusicEditor extends pxtblockly.FieldAssetEditor {
11132
+ getAssetType() {
11133
+ return "song" /* pxt.AssetType.Song */;
11134
+ }
11135
+ createNewAsset(text) {
11136
+ const project = pxt.react.getTilemapProject();
11137
+ if (text) {
11138
+ const asset = pxt.lookupProjectAssetByTSReference(text, project);
11139
+ if (asset)
11140
+ return asset;
11141
+ }
11142
+ if (this.getBlockData()) {
11143
+ return project.lookupAsset("song" /* pxt.AssetType.Song */, this.getBlockData());
11144
+ }
11145
+ let song;
11146
+ if (text) {
11147
+ const match = /^\s*hex\s*`([a-fA-F0-9]+)`\s*(?:;?)\s*$/.exec(text);
11148
+ if (match) {
11149
+ song = pxt.assets.music.decodeSongFromHex(match[1]);
11150
+ }
11151
+ }
11152
+ else {
11153
+ song = pxt.assets.music.getEmptySong(2);
11154
+ }
11155
+ if (!song) {
11156
+ this.isGreyBlock = true;
11157
+ this.valueText = text;
11158
+ return undefined;
11159
+ }
11160
+ else {
11161
+ // Restore all of the unused tracks
11162
+ pxt.assets.music.inflateSong(song);
11163
+ }
11164
+ const newAsset = {
11165
+ internalID: -1,
11166
+ id: this.sourceBlock_.id,
11167
+ type: "song" /* pxt.AssetType.Song */,
11168
+ meta: {},
11169
+ song
11170
+ };
11171
+ return newAsset;
11172
+ }
11173
+ render_() {
11174
+ super.render_();
11175
+ if (!this.isGreyBlock) {
11176
+ this.size_.height = TOTAL_HEIGHT;
11177
+ this.size_.width = X_PADDING * 2 + BG_PADDING * 2 + this.previewWidth();
11178
+ }
11179
+ }
11180
+ getValueText() {
11181
+ if (this.asset && !this.isTemporaryAsset()) {
11182
+ return pxt.getTSReferenceForAsset(this.asset);
11183
+ }
11184
+ return this.asset ? `hex\`${pxt.assets.music.encodeSongToHex(this.asset.song)}\`` : "";
11185
+ }
11186
+ parseFieldOptions(opts) {
11187
+ return {};
11188
+ }
11189
+ isFullscreen() {
11190
+ return false;
11191
+ }
11192
+ redrawPreview() {
11193
+ var _a;
11194
+ if (!this.fieldGroup_)
11195
+ return;
11196
+ pxsim.U.clear(this.fieldGroup_);
11197
+ if (this.isGreyBlock) {
11198
+ super.redrawPreview();
11199
+ return;
11200
+ }
11201
+ const totalWidth = X_PADDING * 2 + BG_PADDING * 2 + this.previewWidth();
11202
+ const bg = new svg.Rect()
11203
+ .at(X_PADDING, Y_PADDING)
11204
+ .size(BG_PADDING * 2 + this.previewWidth(), BG_HEIGHT)
11205
+ .setClass("blocklySpriteField")
11206
+ .stroke("#898989", 1)
11207
+ .corner(4);
11208
+ this.fieldGroup_.appendChild(bg.el);
11209
+ if (this.asset) {
11210
+ const dataURI = pxtblockly.songToDataURI(this.asset.song, this.previewWidth(), PREVIEW_HEIGHT, this.lightMode);
11211
+ if (dataURI) {
11212
+ const img = new svg.Image()
11213
+ .src(dataURI)
11214
+ .at(X_PADDING + BG_PADDING, Y_PADDING + BG_PADDING)
11215
+ .size(this.previewWidth(), PREVIEW_HEIGHT);
11216
+ this.fieldGroup_.appendChild(img.el);
11217
+ }
11218
+ }
11219
+ if (((_a = this.size_) === null || _a === void 0 ? void 0 : _a.width) != totalWidth) {
11220
+ this.forceRerender();
11221
+ }
11222
+ }
11223
+ previewWidth() {
11224
+ const measures = this.asset ? this.asset.song.measures : 2;
11225
+ return measures * PREVIEW_HEIGHT;
11226
+ }
11227
+ }
11228
+ pxtblockly.FieldMusicEditor = FieldMusicEditor;
11229
+ })(pxtblockly || (pxtblockly = {}));
11002
11230
  /// <reference path="../../localtypings/pxtblockly.d.ts" />
11003
11231
  var pxtblockly;
11004
11232
  (function (pxtblockly) {
@@ -13534,6 +13762,54 @@ var pxtblockly;
13534
13762
  return canvas.toDataURL();
13535
13763
  }
13536
13764
  pxtblockly.tilemapToImageURI = tilemapToImageURI;
13765
+ function songToDataURI(song, width, height, lightMode, maxMeasures) {
13766
+ const colors = pxt.appTarget.runtime.palette.slice();
13767
+ const canvas = document.createElement("canvas");
13768
+ canvas.width = width;
13769
+ canvas.height = height;
13770
+ let context;
13771
+ if (lightMode) {
13772
+ context = canvas.getContext("2d", { alpha: false });
13773
+ context.fillStyle = "#dedede";
13774
+ context.fillRect(0, 0, width, height);
13775
+ }
13776
+ else {
13777
+ context = canvas.getContext("2d");
13778
+ }
13779
+ const trackColors = [
13780
+ 5,
13781
+ 11,
13782
+ 5,
13783
+ 4,
13784
+ 2,
13785
+ 6,
13786
+ 14,
13787
+ 2,
13788
+ 5,
13789
+ 1, // explosion
13790
+ ];
13791
+ maxMeasures = maxMeasures || song.measures;
13792
+ const cellWidth = Math.max(Math.floor(width / (song.beatsPerMeasure * maxMeasures * 2)), 1);
13793
+ const cellsShown = Math.floor(width / cellWidth);
13794
+ const cellHeight = Math.max(Math.floor(height / 12), 1);
13795
+ const notesShown = Math.floor(height / cellHeight);
13796
+ for (const track of song.tracks) {
13797
+ for (const noteEvent of track.notes) {
13798
+ const col = Math.floor(noteEvent.startTick / (song.ticksPerBeat / 2));
13799
+ if (col > cellsShown)
13800
+ break;
13801
+ for (const note of noteEvent.notes) {
13802
+ const row = 12 - (note % 12);
13803
+ if (row > notesShown)
13804
+ continue;
13805
+ context.fillStyle = colors[trackColors[track.id || song.tracks.indexOf(track)]];
13806
+ context.fillRect(col * cellWidth, row * cellHeight, cellWidth, cellHeight);
13807
+ }
13808
+ }
13809
+ }
13810
+ return canvas.toDataURL();
13811
+ }
13812
+ pxtblockly.songToDataURI = songToDataURI;
13537
13813
  function deleteTilesetTileIfExists(ws, tile) {
13538
13814
  const existing = ws.getVariablesOfType(pxt.sprite.BLOCKLY_TILESET_TYPE);
13539
13815
  for (const model of existing) {
@@ -13669,10 +13945,17 @@ var pxtblockly;
13669
13945
  case "animation" /* pxt.AssetType.Animation */:
13670
13946
  return getAllFields(workspace, field => field instanceof pxtblockly.FieldAnimationEditor && field.isTemporaryAsset())
13671
13947
  .map(f => f.ref.getAsset());
13948
+ case "song" /* pxt.AssetType.Song */:
13949
+ return getAllFields(workspace, field => field instanceof pxtblockly.FieldMusicEditor && field.isTemporaryAsset())
13950
+ .map(f => f.ref.getAsset());
13672
13951
  default: return [];
13673
13952
  }
13674
13953
  }
13675
13954
  pxtblockly.getTemporaryAssets = getTemporaryAssets;
13955
+ function setMelodyEditorOpen(block, isOpen) {
13956
+ Blockly.Events.fire(new Blockly.Events.Ui(block, "melody-editor", !isOpen, isOpen));
13957
+ }
13958
+ pxtblockly.setMelodyEditorOpen = setMelodyEditorOpen;
13676
13959
  function workspaceToScreenCoordinates(ws, wsCoordinates) {
13677
13960
  // The position in pixels relative to the origin of the
13678
13961
  // main workspace.
package/built/pxtlib.d.ts CHANGED
@@ -99,6 +99,7 @@ declare namespace pxt.auth {
99
99
  protected abstract onProfileDeleted(userId: string): Promise<void>;
100
100
  protected abstract onApiError(err: any): Promise<void>;
101
101
  protected abstract onStateCleared(): Promise<void>;
102
+ authTokenAsync(): Promise<string>;
102
103
  /**
103
104
  * Starts the process of authenticating the user against the given identity
104
105
  * provider. Upon success the backend will write an http-only session cookie
@@ -177,6 +178,7 @@ declare namespace pxt.auth {
177
178
  function hasIdentity(): boolean;
178
179
  function enableAuth(enabled?: boolean): void;
179
180
  function userName(user: pxt.auth.UserProfile): string;
181
+ function firstName(user: pxt.auth.UserProfile): string;
180
182
  function userInitials(user: pxt.auth.UserProfile): string;
181
183
  function generateUserProfilePicDataUrl(profile: pxt.auth.UserProfile): void;
182
184
  /**
@@ -554,6 +556,7 @@ declare namespace pxt {
554
556
  asseteditorUrl?: string;
555
557
  skillmapUrl?: string;
556
558
  authcodeUrl?: string;
559
+ multiplayerUrl?: string;
557
560
  isStatic?: boolean;
558
561
  verprefix?: string;
559
562
  }
@@ -1613,6 +1616,8 @@ declare namespace pxt.sprite {
1613
1616
  const IMAGE_PREFIX = "image";
1614
1617
  const ANIMATION_NAMESPACE = "myAnimations";
1615
1618
  const ANIMATION_PREFIX = "anim";
1619
+ const SONG_NAMESPACE = "mySongs";
1620
+ const SONG_PREFIX = "song";
1616
1621
  interface Coord {
1617
1622
  x: number;
1618
1623
  y: number;
@@ -1764,6 +1769,66 @@ declare namespace pxt.storage.shared {
1764
1769
  function setAsync(container: string, key: string, val: any): Promise<void>;
1765
1770
  function delAsync(container: string, key: string): Promise<void>;
1766
1771
  }
1772
+ declare namespace pxt.assets.music {
1773
+ interface Instrument {
1774
+ waveform: number;
1775
+ ampEnvelope: Envelope;
1776
+ pitchEnvelope?: Envelope;
1777
+ ampLFO?: LFO;
1778
+ pitchLFO?: LFO;
1779
+ octave?: number;
1780
+ }
1781
+ interface Envelope {
1782
+ attack: number;
1783
+ decay: number;
1784
+ sustain: number;
1785
+ release: number;
1786
+ amplitude: number;
1787
+ }
1788
+ interface LFO {
1789
+ frequency: number;
1790
+ amplitude: number;
1791
+ }
1792
+ interface Song {
1793
+ measures: number;
1794
+ beatsPerMeasure: number;
1795
+ beatsPerMinute: number;
1796
+ ticksPerBeat: number;
1797
+ tracks: Track[];
1798
+ }
1799
+ interface Track {
1800
+ instrument: Instrument;
1801
+ id: number;
1802
+ name?: string;
1803
+ iconURI?: string;
1804
+ drums?: DrumInstrument[];
1805
+ notes: NoteEvent[];
1806
+ }
1807
+ interface NoteEvent {
1808
+ notes: number[];
1809
+ startTick: number;
1810
+ endTick: number;
1811
+ }
1812
+ interface DrumSoundStep {
1813
+ waveform: number;
1814
+ frequency: number;
1815
+ volume: number;
1816
+ duration: number;
1817
+ }
1818
+ interface DrumInstrument {
1819
+ startFrequency: number;
1820
+ startVolume: number;
1821
+ steps: DrumSoundStep[];
1822
+ }
1823
+ function renderInstrument(instrument: Instrument, noteFrequency: number, gateLength: number, volume: number): Uint8Array;
1824
+ function renderDrumInstrument(sound: DrumInstrument, volume: number): Uint8Array;
1825
+ function encodeSongToHex(song: Song): string;
1826
+ function decodeSongFromHex(hex: string): Song;
1827
+ function cloneSong(song: Song): Song;
1828
+ function songEquals(a: Song, b: Song): boolean;
1829
+ function inflateSong(song: pxt.assets.music.Song): void;
1830
+ function getEmptySong(measures: number): pxt.assets.music.Song;
1831
+ }
1767
1832
  declare namespace pxt {
1768
1833
  class Package {
1769
1834
  id: string;
@@ -2607,11 +2672,13 @@ declare namespace pxt {
2607
2672
  export const IMAGE_MIME_TYPE = "image/x-mkcd-f4";
2608
2673
  export const TILEMAP_MIME_TYPE = "application/mkcd-tilemap";
2609
2674
  export const ANIMATION_MIME_TYPE = "application/mkcd-animation";
2675
+ export const SONG_MIME_TYPE = "application/mkcd-song";
2610
2676
  export const enum AssetType {
2611
2677
  Image = "image",
2612
2678
  Tile = "tile",
2613
2679
  Tilemap = "tilemap",
2614
- Animation = "animation"
2680
+ Animation = "animation",
2681
+ Song = "song"
2615
2682
  }
2616
2683
  export interface AssetMetadata {
2617
2684
  displayName?: string;
@@ -2623,7 +2690,7 @@ declare namespace pxt {
2623
2690
  blockId: string;
2624
2691
  fieldName: string;
2625
2692
  }
2626
- export type Asset = ProjectImage | Tile | Animation | ProjectTilemap;
2693
+ export type Asset = ProjectImage | Tile | Animation | ProjectTilemap | Song;
2627
2694
  export interface BaseAsset {
2628
2695
  internalID: number;
2629
2696
  id: string;
@@ -2661,6 +2728,10 @@ declare namespace pxt {
2661
2728
  type: AssetType.Tilemap;
2662
2729
  data: pxt.sprite.TilemapData;
2663
2730
  }
2731
+ export interface Song extends BaseAsset {
2732
+ type: AssetType.Song;
2733
+ song: assets.music.Song;
2734
+ }
2664
2735
  export interface TilemapSnapshot {
2665
2736
  revision: number;
2666
2737
  projectTilemaps?: ProjectTilemap[];
@@ -2674,6 +2745,7 @@ declare namespace pxt {
2674
2745
  tilemaps: AssetCollection<ProjectTilemap>;
2675
2746
  images: AssetCollection<ProjectImage>;
2676
2747
  animations: AssetCollection<Animation>;
2748
+ songs: AssetCollection<Song>;
2677
2749
  }
2678
2750
  interface AssetSnapshotDiff {
2679
2751
  beforeRevision: number;
@@ -2682,6 +2754,7 @@ declare namespace pxt {
2682
2754
  tilemaps: AssetCollectionDiff<ProjectTilemap>;
2683
2755
  images: AssetCollectionDiff<ProjectImage>;
2684
2756
  animations: AssetCollectionDiff<Animation>;
2757
+ songs: AssetCollectionDiff<Song>;
2685
2758
  }
2686
2759
  interface AssetUpdateListener {
2687
2760
  internalID: number;
@@ -2734,6 +2807,7 @@ declare namespace pxt {
2734
2807
  getProjectTiles(tileWidth: number, createIfMissing: boolean): TileSet | null;
2735
2808
  createNewTile(data: pxt.sprite.BitmapData, id?: string, displayName?: string): Tile;
2736
2809
  createNewProjectImage(data: pxt.sprite.BitmapData, displayName?: string): ProjectImage;
2810
+ createNewSong(data: pxt.assets.music.Song, displayName?: string): Song;
2737
2811
  updateTile(tile: pxt.Tile): Tile;
2738
2812
  deleteTile(id: string): void;
2739
2813
  getProjectTilesetJRes(): Map<any>;
@@ -2775,6 +2849,10 @@ declare namespace pxt {
2775
2849
  * assets.animation`shortId`
2776
2850
  * assets.animation`displayName`
2777
2851
  *
2852
+ * SONGS:
2853
+ * assets.song`shortId`
2854
+ * assets.song`displayName`
2855
+ *
2778
2856
  * TILEMAPS:
2779
2857
  * tilemap`shortId`
2780
2858
  *
@@ -2787,36 +2865,43 @@ declare namespace pxt {
2787
2865
  lookupAsset(assetType: AssetType.Tile, name: string): Tile;
2788
2866
  lookupAsset(assetType: AssetType.Tilemap, name: string): ProjectTilemap;
2789
2867
  lookupAsset(assetType: AssetType.Animation, name: string): Animation;
2868
+ lookupAsset(assetType: AssetType.Song, name: string): Song;
2790
2869
  lookupAsset(assetType: AssetType, name: string): Asset;
2791
2870
  lookupAssetByName(assetType: AssetType.Image, name: string): ProjectImage;
2792
2871
  lookupAssetByName(assetType: AssetType.Tile, name: string): Tile;
2793
2872
  lookupAssetByName(assetType: AssetType.Tilemap, name: string): ProjectTilemap;
2794
2873
  lookupAssetByName(assetType: AssetType.Animation, name: string): Animation;
2874
+ lookupAssetByName(assetType: AssetType.Song, name: string): Song;
2795
2875
  lookupAssetByName(assetType: AssetType, name: string): Asset;
2796
2876
  getAssets(type: AssetType.Image): ProjectImage[];
2797
2877
  getAssets(type: AssetType.Tile): Tile[];
2798
2878
  getAssets(type: AssetType.Tilemap): ProjectTilemap[];
2799
2879
  getAssets(type: AssetType.Animation): Animation[];
2880
+ getAssets(type: AssetType.Song): Song[];
2800
2881
  getAssets(type: AssetType): Asset[];
2801
2882
  getGalleryAssets(type: AssetType.Image): ProjectImage[];
2802
2883
  getGalleryAssets(type: AssetType.Tile): Tile[];
2803
2884
  getGalleryAssets(type: AssetType.Tilemap): ProjectTilemap[];
2804
2885
  getGalleryAssets(type: AssetType.Animation): Animation[];
2886
+ getGalleryAssets(type: AssetType.Song): Song[];
2805
2887
  getGalleryAssets(type: AssetType): Asset[];
2806
2888
  lookupBlockAsset(assetType: AssetType.Image, blockID: string): ProjectImage;
2807
2889
  lookupBlockAsset(assetType: AssetType.Tile, blockID: string): Tile;
2808
2890
  lookupBlockAsset(assetType: AssetType.Tilemap, blockID: string): ProjectTilemap;
2809
2891
  lookupBlockAsset(assetType: AssetType.Animation, blockID: string): Animation;
2892
+ lookupBlockAsset(assetType: AssetType.Song, blockID: string): Song;
2810
2893
  lookupBlockAsset(assetType: AssetType, blockID: string): Asset;
2811
2894
  updateAsset(asset: ProjectImage): ProjectImage;
2812
2895
  updateAsset(asset: Tile): Tile;
2813
2896
  updateAsset(asset: ProjectTilemap): ProjectTilemap;
2814
2897
  updateAsset(asset: Animation): Animation;
2898
+ updateAsset(asset: Song): Song;
2815
2899
  updateAsset(asset: Asset): Asset;
2816
2900
  duplicateAsset(asset: ProjectImage, displayName?: string): ProjectImage;
2817
2901
  duplicateAsset(asset: Tile, displayName?: string): Tile;
2818
2902
  duplicateAsset(asset: ProjectTilemap, displayName?: string): ProjectTilemap;
2819
2903
  duplicateAsset(asset: Animation, displayName?: string): Animation;
2904
+ duplicateAsset(asset: Song, displayName?: string): Song;
2820
2905
  duplicateAsset(asset: Asset, displayName?: string): Asset;
2821
2906
  removeAsset(asset: Asset): void;
2822
2907
  addChangeListener(asset: Asset, listener: () => void): void;
@@ -2827,12 +2912,13 @@ declare namespace pxt {
2827
2912
  removeInactiveBlockAssets(activeBlockIDs: string[]): void;
2828
2913
  protected generateImage(entry: JRes, type: AssetType.Image): ProjectImage;
2829
2914
  protected generateImage(entry: JRes, type: AssetType.Tile): Tile;
2915
+ protected generateSong(entry: JRes): Song;
2830
2916
  protected generateAnimation(entry: JRes): [Animation, boolean];
2831
- protected inflateAnimation(animation: Animation, assets: (Tile | ProjectImage | Animation)[]): Animation;
2917
+ protected inflateAnimation(animation: Animation, assets: (Tile | ProjectImage | Animation | Song)[]): Animation;
2832
2918
  generateNewID(type: AssetType): string;
2833
2919
  protected generateNewIDInternal(type: AssetType, varPrefix: string, namespaceString?: string): string;
2834
2920
  protected onChange(): void;
2835
- protected readImages(allJRes: Map<JRes>, isProjectFile?: boolean): (Tile | ProjectImage | Animation)[];
2921
+ protected readImages(allJRes: Map<JRes>, isProjectFile?: boolean): (Tile | ProjectImage | Animation | Song)[];
2836
2922
  protected cleanupTemporaryAssets(): void;
2837
2923
  }
2838
2924
  export function emitTilemapsFromJRes(jres: pxt.Map<JRes>): string;
@@ -2845,7 +2931,7 @@ declare namespace pxt {
2845
2931
  type: string;
2846
2932
  name: string;
2847
2933
  };
2848
- export function lookupProjectAssetByTSReference(ts: string, project: TilemapProject): Tile | ProjectImage | Animation | ProjectTilemap;
2934
+ export function lookupProjectAssetByTSReference(ts: string, project: TilemapProject): Tile | ProjectImage | Animation | ProjectTilemap | Song;
2849
2935
  export function getDefaultAssetDisplayName(type: pxt.AssetType): string;
2850
2936
  export function getShortIDForAsset(asset: pxt.Asset): string;
2851
2937
  export {};