pxt-core 7.5.8 → 7.5.11
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 +1004 -4
- package/built/pxtblockly.js +439 -49
- package/built/pxtblocks.d.ts +34 -0
- package/built/pxtblocks.js +439 -49
- package/built/pxtlib.d.ts +20 -2
- package/built/pxtlib.js +127 -3
- 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 +130 -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.e30f6be4.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 +10 -4
- 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/Button.less +21 -0
- package/react-common/styles/controls/DraggableGraph.less +13 -0
- package/react-common/styles/controls/Dropdown.less +68 -0
- package/react-common/styles/controls/RadioButtonGroup.less +36 -0
- package/react-common/styles/react-common-variables.less +38 -0
- package/react-common/styles/react-common.less +3 -0
- package/theme/pxt.less +1 -0
- package/theme/soundeffecteditor.less +239 -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,22 @@ 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;
|
|
7330
|
+
let firstRender = true;
|
|
7319
7331
|
appendMutation(b, {
|
|
7320
7332
|
mutationToDom: (el) => {
|
|
7321
7333
|
// The reason we store the inputsInitialized variable separately from visibleOptions
|
|
@@ -7330,8 +7342,8 @@ var pxt;
|
|
|
7330
7342
|
state.setEventsEnabled(false);
|
|
7331
7343
|
if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
|
|
7332
7344
|
state.setValue(inputInitAttr, true);
|
|
7333
|
-
initOptionalInputs();
|
|
7334
7345
|
}
|
|
7346
|
+
initOptionalInputs();
|
|
7335
7347
|
if (saved.hasAttribute(numVisibleAttr)) {
|
|
7336
7348
|
const val = parseInt(saved.getAttribute(numVisibleAttr));
|
|
7337
7349
|
if (!isNaN(val)) {
|
|
@@ -7342,6 +7354,7 @@ var pxt;
|
|
|
7342
7354
|
}
|
|
7343
7355
|
else {
|
|
7344
7356
|
state.setValue(numVisibleAttr, addDelta(delta));
|
|
7357
|
+
updateButtons();
|
|
7345
7358
|
}
|
|
7346
7359
|
}
|
|
7347
7360
|
else {
|
|
@@ -7352,14 +7365,32 @@ var pxt;
|
|
|
7352
7365
|
state.setEventsEnabled(true);
|
|
7353
7366
|
}
|
|
7354
7367
|
});
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7368
|
+
initOptionalInputs();
|
|
7369
|
+
if (compileHiddenArguments) {
|
|
7370
|
+
// Make sure all inputs have shadow blocks attached
|
|
7371
|
+
let optIndex = 0;
|
|
7372
|
+
for (let i = 0; i < b.inputList.length; i++) {
|
|
7373
|
+
const input = b.inputList[i];
|
|
7374
|
+
if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
|
|
7375
|
+
if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
7376
|
+
const param = comp.definitionNameToParam[def.parameters[optIndex].name];
|
|
7377
|
+
attachShadowBlock(input, param);
|
|
7378
|
+
}
|
|
7379
|
+
++optIndex;
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
b.render = (opt_bubble) => {
|
|
7384
|
+
if (updatingInputs)
|
|
7385
|
+
return;
|
|
7386
|
+
if (firstRender) {
|
|
7387
|
+
firstRender = false;
|
|
7388
|
+
updatingInputs = true;
|
|
7360
7389
|
updateShape(0, undefined, true);
|
|
7390
|
+
updatingInputs = false;
|
|
7361
7391
|
}
|
|
7362
|
-
|
|
7392
|
+
Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
|
|
7393
|
+
};
|
|
7363
7394
|
// Set skipRender to true if the block is still initializing. Otherwise
|
|
7364
7395
|
// the inputs will render before their shadow blocks are created and
|
|
7365
7396
|
// leave behind annoying artifacts
|
|
@@ -7389,25 +7420,14 @@ var pxt;
|
|
|
7389
7420
|
setInputVisible(input, visible);
|
|
7390
7421
|
if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
7391
7422
|
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();
|
|
7423
|
+
attachShadowBlock(input, param);
|
|
7406
7424
|
}
|
|
7407
7425
|
++optIndex;
|
|
7408
7426
|
}
|
|
7409
7427
|
}
|
|
7410
7428
|
updateButtons();
|
|
7429
|
+
if (variableInlineInputs)
|
|
7430
|
+
b.setInputsInline(visibleOptions < 4);
|
|
7411
7431
|
if (!skipRender)
|
|
7412
7432
|
b.render();
|
|
7413
7433
|
}
|
|
@@ -7416,31 +7436,32 @@ var pxt;
|
|
|
7416
7436
|
.appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
|
|
7417
7437
|
}
|
|
7418
7438
|
function updateButtons() {
|
|
7439
|
+
if (updatingInputs)
|
|
7440
|
+
return;
|
|
7419
7441
|
const visibleOptions = state.getNumber(numVisibleAttr);
|
|
7420
7442
|
const showPlus = visibleOptions !== totalOptions;
|
|
7421
7443
|
const showMinus = visibleOptions !== 0;
|
|
7422
|
-
|
|
7423
|
-
const hasPlus = !!b.getInput(buttonAddName);
|
|
7424
|
-
if (!showPlus) {
|
|
7444
|
+
if (b.inputList.some(i => i.name === buttonAddName))
|
|
7425
7445
|
b.removeInput(buttonAddName, true);
|
|
7426
|
-
|
|
7427
|
-
if (!showMinus) {
|
|
7446
|
+
if (b.inputList.some(i => i.name === buttonRemName))
|
|
7428
7447
|
b.removeInput(buttonRemName, true);
|
|
7448
|
+
if (b.inputList.some(i => i.name === buttonAddRemName))
|
|
7449
|
+
b.removeInput(buttonAddRemName, true);
|
|
7450
|
+
if (showPlus && showMinus) {
|
|
7451
|
+
addPlusAndMinusButtons();
|
|
7429
7452
|
}
|
|
7430
|
-
if (
|
|
7431
|
-
|
|
7453
|
+
else if (showPlus) {
|
|
7454
|
+
addPlusButton();
|
|
7432
7455
|
}
|
|
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
|
-
}
|
|
7456
|
+
else if (showMinus) {
|
|
7457
|
+
addMinusButton();
|
|
7442
7458
|
}
|
|
7443
7459
|
}
|
|
7460
|
+
function addPlusAndMinusButtons() {
|
|
7461
|
+
b.appendDummyInput(buttonAddRemName)
|
|
7462
|
+
.appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
|
|
7463
|
+
.appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
|
|
7464
|
+
}
|
|
7444
7465
|
function addPlusButton() {
|
|
7445
7466
|
addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
|
|
7446
7467
|
}
|
|
@@ -7457,12 +7478,23 @@ var pxt;
|
|
|
7457
7478
|
}
|
|
7458
7479
|
function setInputVisible(input, visible) {
|
|
7459
7480
|
// If the block isn't rendered, Blockly will crash
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7481
|
+
input.setVisible(visible);
|
|
7482
|
+
}
|
|
7483
|
+
function attachShadowBlock(input, param) {
|
|
7484
|
+
let shadow = blocks.createShadowValue(info, param);
|
|
7485
|
+
if (shadow.tagName.toLowerCase() === "value") {
|
|
7486
|
+
// Unwrap the block
|
|
7487
|
+
shadow = shadow.firstElementChild;
|
|
7488
|
+
}
|
|
7489
|
+
Blockly.Events.disable();
|
|
7490
|
+
try {
|
|
7491
|
+
const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
|
|
7492
|
+
if (nb) {
|
|
7493
|
+
input.connection.connect(nb.outputConnection);
|
|
7494
|
+
}
|
|
7465
7495
|
}
|
|
7496
|
+
catch (e) { }
|
|
7497
|
+
Blockly.Events.enable();
|
|
7466
7498
|
}
|
|
7467
7499
|
}
|
|
7468
7500
|
blocks.initExpandableBlock = initExpandableBlock;
|
|
@@ -7802,6 +7834,17 @@ var pxtblockly;
|
|
|
7802
7834
|
this.loaded = true;
|
|
7803
7835
|
this.valueText = this.onValueChanged(this.valueText);
|
|
7804
7836
|
}
|
|
7837
|
+
getAnchorDimensions() {
|
|
7838
|
+
const boundingBox = this.getScaledBBox();
|
|
7839
|
+
if (this.sourceBlock_.RTL) {
|
|
7840
|
+
boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
7841
|
+
}
|
|
7842
|
+
else {
|
|
7843
|
+
boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
7844
|
+
}
|
|
7845
|
+
return boundingBox;
|
|
7846
|
+
}
|
|
7847
|
+
;
|
|
7805
7848
|
isInitialized() {
|
|
7806
7849
|
return !!this.fieldGroup_;
|
|
7807
7850
|
}
|
|
@@ -7811,6 +7854,23 @@ var pxtblockly;
|
|
|
7811
7854
|
setBlockData(value) {
|
|
7812
7855
|
pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
|
|
7813
7856
|
}
|
|
7857
|
+
getSiblingBlock(inputName, useGrandparent = false) {
|
|
7858
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
7859
|
+
if (!block || !block.inputList)
|
|
7860
|
+
return undefined;
|
|
7861
|
+
for (const input of block.inputList) {
|
|
7862
|
+
if (input.name === inputName) {
|
|
7863
|
+
return input.connection.targetBlock();
|
|
7864
|
+
}
|
|
7865
|
+
}
|
|
7866
|
+
return undefined;
|
|
7867
|
+
}
|
|
7868
|
+
getSiblingField(fieldName, useGrandparent = false) {
|
|
7869
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
7870
|
+
if (!block)
|
|
7871
|
+
return undefined;
|
|
7872
|
+
return block.getField(fieldName);
|
|
7873
|
+
}
|
|
7814
7874
|
}
|
|
7815
7875
|
pxtblockly.FieldBase = FieldBase;
|
|
7816
7876
|
})(pxtblockly || (pxtblockly = {}));
|
|
@@ -11513,6 +11573,319 @@ var pxtblockly;
|
|
|
11513
11573
|
}
|
|
11514
11574
|
pxtblockly.FieldProtractor = FieldProtractor;
|
|
11515
11575
|
})(pxtblockly || (pxtblockly = {}));
|
|
11576
|
+
/// <reference path="../../built/pxtlib.d.ts" />
|
|
11577
|
+
/// <reference path="./field_base.ts" />
|
|
11578
|
+
var pxtblockly;
|
|
11579
|
+
(function (pxtblockly) {
|
|
11580
|
+
var svg = pxt.svgUtil;
|
|
11581
|
+
const MUSIC_ICON_WIDTH = 20;
|
|
11582
|
+
const TOTAL_WIDTH = 160;
|
|
11583
|
+
const TOTAL_HEIGHT = 40;
|
|
11584
|
+
const X_PADDING = 5;
|
|
11585
|
+
const Y_PADDING = 4;
|
|
11586
|
+
const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
|
|
11587
|
+
class FieldSoundEffect extends pxtblockly.FieldBase {
|
|
11588
|
+
onInit() {
|
|
11589
|
+
if (!this.options)
|
|
11590
|
+
this.options = {};
|
|
11591
|
+
if (!this.options.durationInputName)
|
|
11592
|
+
this.options.durationInputName = "duration";
|
|
11593
|
+
if (!this.options.startFrequencyInputName)
|
|
11594
|
+
this.options.startFrequencyInputName = "startFrequency";
|
|
11595
|
+
if (!this.options.endFrequencyInputName)
|
|
11596
|
+
this.options.endFrequencyInputName = "endFrequency";
|
|
11597
|
+
if (!this.options.startVolumeInputName)
|
|
11598
|
+
this.options.startVolumeInputName = "startVolume";
|
|
11599
|
+
if (!this.options.endVolumeInputName)
|
|
11600
|
+
this.options.endVolumeInputName = "endVolume";
|
|
11601
|
+
if (!this.options.waveFieldName)
|
|
11602
|
+
this.options.waveFieldName = "waveShape";
|
|
11603
|
+
if (!this.options.interpolationFieldName)
|
|
11604
|
+
this.options.interpolationFieldName = "interpolation";
|
|
11605
|
+
if (!this.options.effectFieldName)
|
|
11606
|
+
this.options.effectFieldName = "effect";
|
|
11607
|
+
this.redrawPreview();
|
|
11608
|
+
}
|
|
11609
|
+
onDispose() {
|
|
11610
|
+
}
|
|
11611
|
+
onValueChanged(newValue) {
|
|
11612
|
+
return newValue;
|
|
11613
|
+
}
|
|
11614
|
+
redrawPreview() {
|
|
11615
|
+
if (!this.fieldGroup_)
|
|
11616
|
+
return;
|
|
11617
|
+
pxsim.U.clear(this.fieldGroup_);
|
|
11618
|
+
const bg = new svg.Rect()
|
|
11619
|
+
.at(X_PADDING, Y_PADDING)
|
|
11620
|
+
.size(TOTAL_WIDTH, TOTAL_HEIGHT)
|
|
11621
|
+
.setClass("blocklySpriteField")
|
|
11622
|
+
.stroke("#fff", 1)
|
|
11623
|
+
.corner(TOTAL_HEIGHT / 2);
|
|
11624
|
+
const clipPathId = "preview-clip-" + pxt.U.guidGen();
|
|
11625
|
+
const clip = new svg.ClipPath()
|
|
11626
|
+
.id(clipPathId)
|
|
11627
|
+
.clipPathUnits(false);
|
|
11628
|
+
const clipRect = new svg.Rect()
|
|
11629
|
+
.size(PREVIEW_WIDTH, TOTAL_HEIGHT)
|
|
11630
|
+
.fill("#FFF")
|
|
11631
|
+
.at(0, 0);
|
|
11632
|
+
clip.appendChild(clipRect);
|
|
11633
|
+
const path = new svg.Path()
|
|
11634
|
+
.stroke("grey", 2)
|
|
11635
|
+
.fill("none")
|
|
11636
|
+
.setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
|
|
11637
|
+
.clipPath("url('#" + clipPathId + "')");
|
|
11638
|
+
const g = new svg.Group()
|
|
11639
|
+
.translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
|
|
11640
|
+
g.appendChild(clip);
|
|
11641
|
+
g.appendChild(path);
|
|
11642
|
+
const musicIcon = new svg.Text("\uf001")
|
|
11643
|
+
.appendClass("melody-editor-field-icon")
|
|
11644
|
+
.setAttribute("alignment-baseline", "middle")
|
|
11645
|
+
.anchor("middle")
|
|
11646
|
+
.at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
|
|
11647
|
+
this.fieldGroup_.appendChild(bg.el);
|
|
11648
|
+
this.fieldGroup_.appendChild(musicIcon.el);
|
|
11649
|
+
this.fieldGroup_.appendChild(g.el);
|
|
11650
|
+
}
|
|
11651
|
+
showEditor_() {
|
|
11652
|
+
const initialSound = this.readCurrentSound();
|
|
11653
|
+
Blockly.Events.disable();
|
|
11654
|
+
let bbox;
|
|
11655
|
+
// This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
|
|
11656
|
+
// which caused the widgetdiv to jump around if any fields underneath changed size
|
|
11657
|
+
let widgetOwner = {
|
|
11658
|
+
getScaledBBox: () => bbox
|
|
11659
|
+
};
|
|
11660
|
+
Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
|
|
11661
|
+
fv.hide();
|
|
11662
|
+
widgetDiv.classList.remove("sound-effect-editor-widget");
|
|
11663
|
+
widgetDiv.style.transform = "";
|
|
11664
|
+
widgetDiv.style.position = "";
|
|
11665
|
+
widgetDiv.style.left = "";
|
|
11666
|
+
widgetDiv.style.top = "";
|
|
11667
|
+
widgetDiv.style.width = "";
|
|
11668
|
+
widgetDiv.style.height = "";
|
|
11669
|
+
widgetDiv.style.opacity = "";
|
|
11670
|
+
widgetDiv.style.transition = "";
|
|
11671
|
+
Blockly.Events.enable();
|
|
11672
|
+
Blockly.Events.setGroup(true);
|
|
11673
|
+
this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
|
|
11674
|
+
this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
|
|
11675
|
+
this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
|
|
11676
|
+
this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
|
|
11677
|
+
this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
|
|
11678
|
+
this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
|
|
11679
|
+
this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
|
|
11680
|
+
this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
|
|
11681
|
+
Blockly.Events.setGroup(false);
|
|
11682
|
+
if (this.mostRecentValue)
|
|
11683
|
+
this.setBlockData(JSON.stringify(this.mostRecentValue));
|
|
11684
|
+
});
|
|
11685
|
+
const widgetDiv = Blockly.WidgetDiv.DIV;
|
|
11686
|
+
const opts = {
|
|
11687
|
+
onClose: () => {
|
|
11688
|
+
fv.hide();
|
|
11689
|
+
Blockly.WidgetDiv.hideIfOwner(widgetOwner);
|
|
11690
|
+
},
|
|
11691
|
+
onSoundChange: (newSound) => {
|
|
11692
|
+
this.mostRecentValue = newSound;
|
|
11693
|
+
this.updateSiblingBlocks(newSound);
|
|
11694
|
+
this.redrawPreview();
|
|
11695
|
+
},
|
|
11696
|
+
initialSound: initialSound
|
|
11697
|
+
};
|
|
11698
|
+
const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
|
|
11699
|
+
const block = this.sourceBlock_;
|
|
11700
|
+
const bounds = block.getBoundingRectangle();
|
|
11701
|
+
const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
|
|
11702
|
+
const animationDistance = 20;
|
|
11703
|
+
const left = coord.x + 20;
|
|
11704
|
+
const top = coord.y - animationDistance;
|
|
11705
|
+
widgetDiv.style.opacity = "0";
|
|
11706
|
+
widgetDiv.classList.add("sound-effect-editor-widget");
|
|
11707
|
+
widgetDiv.style.position = "absolute";
|
|
11708
|
+
widgetDiv.style.left = left + "px";
|
|
11709
|
+
widgetDiv.style.top = top + "px";
|
|
11710
|
+
widgetDiv.style.width = "30rem";
|
|
11711
|
+
widgetDiv.style.height = "40rem";
|
|
11712
|
+
widgetDiv.style.display = "block";
|
|
11713
|
+
widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
|
|
11714
|
+
fv.onHide(() => {
|
|
11715
|
+
// do nothing
|
|
11716
|
+
});
|
|
11717
|
+
fv.show();
|
|
11718
|
+
const divBounds = widgetDiv.getBoundingClientRect();
|
|
11719
|
+
const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
|
|
11720
|
+
if (divBounds.height > injectDivBounds.height) {
|
|
11721
|
+
widgetDiv.style.height = "";
|
|
11722
|
+
widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
|
|
11723
|
+
widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
|
|
11724
|
+
}
|
|
11725
|
+
else {
|
|
11726
|
+
if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
|
|
11727
|
+
// This editor is pretty tall, so just center vertically on the inject div
|
|
11728
|
+
widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
|
|
11729
|
+
}
|
|
11730
|
+
}
|
|
11731
|
+
const toolboxWidth = block.workspace.getToolbox().getWidth();
|
|
11732
|
+
if (divBounds.width > injectDivBounds.width - toolboxWidth) {
|
|
11733
|
+
widgetDiv.style.width = "";
|
|
11734
|
+
widgetDiv.style.left = "1rem";
|
|
11735
|
+
widgetDiv.style.right = "1rem";
|
|
11736
|
+
}
|
|
11737
|
+
else {
|
|
11738
|
+
// Check to see if we are bleeding off the right side of the canvas
|
|
11739
|
+
if (divBounds.left + divBounds.width >= injectDivBounds.right) {
|
|
11740
|
+
// If so, try and place to the left of the block instead of the right
|
|
11741
|
+
const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
|
|
11742
|
+
const workspaceLeft = injectDivBounds.left + toolboxWidth;
|
|
11743
|
+
if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
|
|
11744
|
+
widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
|
|
11745
|
+
}
|
|
11746
|
+
else {
|
|
11747
|
+
// As a last resort, just center on the inject div
|
|
11748
|
+
widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
}
|
|
11752
|
+
const finalDimensions = widgetDiv.getBoundingClientRect();
|
|
11753
|
+
bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
|
|
11754
|
+
requestAnimationFrame(() => {
|
|
11755
|
+
widgetDiv.style.opacity = "1";
|
|
11756
|
+
widgetDiv.style.transform = `translateY(${animationDistance}px)`;
|
|
11757
|
+
});
|
|
11758
|
+
}
|
|
11759
|
+
render_() {
|
|
11760
|
+
super.render_();
|
|
11761
|
+
this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
|
|
11762
|
+
this.size_.width = TOTAL_WIDTH;
|
|
11763
|
+
}
|
|
11764
|
+
updateSiblingBlocks(sound) {
|
|
11765
|
+
this.setNumberInputValue(this.options.durationInputName, sound.duration);
|
|
11766
|
+
this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
|
|
11767
|
+
this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
|
|
11768
|
+
this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
|
|
11769
|
+
this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
|
|
11770
|
+
this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
|
|
11771
|
+
this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
|
|
11772
|
+
this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
|
|
11773
|
+
}
|
|
11774
|
+
setNumberInputValue(name, value) {
|
|
11775
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
11776
|
+
if (!block)
|
|
11777
|
+
return;
|
|
11778
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
11779
|
+
block.setFieldValue(Math.round(value), "NUM");
|
|
11780
|
+
}
|
|
11781
|
+
else if (block.type === "math_number_minmax") {
|
|
11782
|
+
block.setFieldValue(Math.round(value), "SLIDER");
|
|
11783
|
+
}
|
|
11784
|
+
}
|
|
11785
|
+
getNumberInputValue(name, defaultValue) {
|
|
11786
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
11787
|
+
if (!block)
|
|
11788
|
+
return defaultValue;
|
|
11789
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
11790
|
+
return parseInt(block.getFieldValue("NUM") + "");
|
|
11791
|
+
}
|
|
11792
|
+
else if (block.type === "math_number_minmax") {
|
|
11793
|
+
return parseInt(block.getFieldValue("SLIDER") + "");
|
|
11794
|
+
}
|
|
11795
|
+
return defaultValue;
|
|
11796
|
+
}
|
|
11797
|
+
fireNumberInputUpdate(name, oldValue) {
|
|
11798
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
11799
|
+
if (!block)
|
|
11800
|
+
return;
|
|
11801
|
+
let fieldName;
|
|
11802
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
11803
|
+
fieldName = "NUM";
|
|
11804
|
+
}
|
|
11805
|
+
else if (block.type === "math_number_minmax") {
|
|
11806
|
+
fieldName = "SLIDER";
|
|
11807
|
+
}
|
|
11808
|
+
if (!fieldName)
|
|
11809
|
+
return;
|
|
11810
|
+
Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
|
|
11811
|
+
}
|
|
11812
|
+
setFieldDropdownValue(name, value) {
|
|
11813
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
11814
|
+
if (!field)
|
|
11815
|
+
return;
|
|
11816
|
+
field.setValue(value);
|
|
11817
|
+
}
|
|
11818
|
+
getFieldDropdownValue(name) {
|
|
11819
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
11820
|
+
if (!field)
|
|
11821
|
+
return undefined;
|
|
11822
|
+
return field.getValue();
|
|
11823
|
+
}
|
|
11824
|
+
fireFieldDropdownUpdate(name, oldValue) {
|
|
11825
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
11826
|
+
if (!field)
|
|
11827
|
+
return;
|
|
11828
|
+
Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
|
|
11829
|
+
}
|
|
11830
|
+
readCurrentSound() {
|
|
11831
|
+
const savedSound = this.readBlockDataSound();
|
|
11832
|
+
return {
|
|
11833
|
+
duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
|
|
11834
|
+
startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
|
|
11835
|
+
endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
|
|
11836
|
+
startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
|
|
11837
|
+
endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
|
|
11838
|
+
wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
|
|
11839
|
+
interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
|
|
11840
|
+
effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
|
|
11841
|
+
};
|
|
11842
|
+
}
|
|
11843
|
+
// This stores the values of the fields in case a block (e.g. a variable) is placed in one
|
|
11844
|
+
// of the inputs.
|
|
11845
|
+
readBlockDataSound() {
|
|
11846
|
+
const data = this.getBlockData();
|
|
11847
|
+
let sound;
|
|
11848
|
+
try {
|
|
11849
|
+
sound = JSON.parse(data);
|
|
11850
|
+
}
|
|
11851
|
+
catch (e) {
|
|
11852
|
+
sound = {
|
|
11853
|
+
duration: 1000,
|
|
11854
|
+
startFrequency: 100,
|
|
11855
|
+
endFrequency: 1800,
|
|
11856
|
+
startVolume: 1023,
|
|
11857
|
+
endVolume: 0,
|
|
11858
|
+
wave: "sine",
|
|
11859
|
+
interpolation: "linear",
|
|
11860
|
+
effect: "none"
|
|
11861
|
+
};
|
|
11862
|
+
}
|
|
11863
|
+
return sound;
|
|
11864
|
+
}
|
|
11865
|
+
}
|
|
11866
|
+
pxtblockly.FieldSoundEffect = FieldSoundEffect;
|
|
11867
|
+
const waveformMapping = {
|
|
11868
|
+
"sine": "WaveShape.Sine",
|
|
11869
|
+
"square": "WaveShape.Square",
|
|
11870
|
+
"sawtooth": "WaveShape.Sawtooth",
|
|
11871
|
+
"triangle": "WaveShape.Triangle",
|
|
11872
|
+
"noise": "WaveShape.Noise",
|
|
11873
|
+
};
|
|
11874
|
+
const effectMapping = {
|
|
11875
|
+
"none": "SoundExpressionEffect.None",
|
|
11876
|
+
"vibrato": "SoundExpressionEffect.Vibrato",
|
|
11877
|
+
"tremolo": "SoundExpressionEffect.Tremolo",
|
|
11878
|
+
"warble": "SoundExpressionEffect.Warble",
|
|
11879
|
+
};
|
|
11880
|
+
const interpolationMapping = {
|
|
11881
|
+
"linear": "InterpolationCurve.Linear",
|
|
11882
|
+
"curve": "InterpolationCurve.Curve",
|
|
11883
|
+
"logarithmic": "InterpolationCurve.Logarithmic",
|
|
11884
|
+
};
|
|
11885
|
+
function reverseLookup(map, value) {
|
|
11886
|
+
return Object.keys(map).find(k => map[k] === value);
|
|
11887
|
+
}
|
|
11888
|
+
})(pxtblockly || (pxtblockly = {}));
|
|
11516
11889
|
/// <reference path="../../localtypings/blockly.d.ts"/>
|
|
11517
11890
|
/// <reference path="../../built/pxtsim.d.ts"/>
|
|
11518
11891
|
var pxtblockly;
|
|
@@ -12978,4 +13351,21 @@ var pxtblockly;
|
|
|
12978
13351
|
}
|
|
12979
13352
|
}
|
|
12980
13353
|
pxtblockly.getTemporaryAssets = getTemporaryAssets;
|
|
13354
|
+
function workspaceToScreenCoordinates(ws, wsCoordinates) {
|
|
13355
|
+
// The position in pixels relative to the origin of the
|
|
13356
|
+
// main workspace.
|
|
13357
|
+
const scaledWS = wsCoordinates.scale(ws.scale);
|
|
13358
|
+
// The offset in pixels between the main workspace's origin and the upper
|
|
13359
|
+
// left corner of the injection div.
|
|
13360
|
+
const mainOffsetPixels = ws.getOriginOffsetInPixels();
|
|
13361
|
+
// The client coordinates offset by the injection div's upper left corner.
|
|
13362
|
+
const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
|
|
13363
|
+
const injectionDiv = ws.getInjectionDiv();
|
|
13364
|
+
// Bounding rect coordinates are in client coordinates, meaning that they
|
|
13365
|
+
// are in pixels relative to the upper left corner of the visible browser
|
|
13366
|
+
// window. These coordinates change when you scroll the browser window.
|
|
13367
|
+
const boundingRect = injectionDiv.getBoundingClientRect();
|
|
13368
|
+
return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
|
|
13369
|
+
}
|
|
13370
|
+
pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
|
|
12981
13371
|
})(pxtblockly || (pxtblockly = {}));
|