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.
- package/built/pxt.js +1002 -2
- package/built/pxtblockly.js +428 -50
- package/built/pxtblocks.d.ts +34 -0
- package/built/pxtblocks.js +428 -50
- package/built/pxtlib.d.ts +19 -1
- package/built/pxtlib.js +125 -1
- package/built/pxtsim.d.ts +222 -0
- package/built/pxtsim.js +877 -1
- package/built/target.js +1 -1
- package/built/web/icons.css +49 -10
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtblockly.js +1 -1
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtsim.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-authcode.css +104 -1
- package/built/web/react-common-skillmap.css +1 -1
- package/built/web/rtlreact-common-skillmap.css +1 -1
- package/built/web/rtlsemantic.css +2 -2
- package/built/web/semantic.css +2 -2
- package/built/web/skillmap/js/main.b5f3628d.chunk.js +1 -0
- package/docfiles/footer.html +1 -1
- package/docfiles/script.html +1 -1
- package/docfiles/thin-footer.html +1 -1
- package/localtypings/pxtarget.d.ts +1 -0
- package/package.json +1 -1
- package/react-common/components/controls/Button.tsx +7 -3
- package/react-common/components/controls/DraggableGraph.tsx +242 -0
- package/react-common/components/controls/Dropdown.tsx +121 -0
- package/react-common/components/controls/FocusList.tsx +17 -8
- package/react-common/components/controls/Input.tsx +13 -3
- package/react-common/components/controls/RadioButtonGroup.tsx +66 -0
- package/react-common/components/util.tsx +23 -0
- package/react-common/styles/controls/DraggableGraph.less +13 -0
- package/react-common/styles/controls/Dropdown.less +64 -0
- package/react-common/styles/controls/RadioButtonGroup.less +36 -0
- package/react-common/styles/react-common-variables.less +24 -0
- package/react-common/styles/react-common.less +3 -0
- package/theme/pxt.less +1 -0
- package/theme/soundeffecteditor.less +132 -0
- package/webapp/public/skillmap.html +1 -1
- package/built/web/skillmap/js/main.2485091f.chunk.js +0 -1
package/built/pxtblocks.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
7431
|
-
|
|
7449
|
+
else if (showPlus) {
|
|
7450
|
+
addPlusButton();
|
|
7432
7451
|
}
|
|
7433
|
-
if (
|
|
7434
|
-
|
|
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
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
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();
|