pxt-core 7.5.7 → 7.5.10
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 +432 -52
- package/built/pxtblocks.d.ts +34 -0
- package/built/pxtblocks.js +432 -52
- 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 +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) {
|
|
@@ -6271,9 +6279,11 @@ var pxt;
|
|
|
6271
6279
|
return pxt.Util.values(apis.byQName).filter(sym => sym.namespace === enumName && !sym.attributes.blockHidden);
|
|
6272
6280
|
}
|
|
6273
6281
|
function getFixedInstanceDropdownValues(apis, qName) {
|
|
6274
|
-
|
|
6282
|
+
const symbols = pxt.Util.values(apis.byQName).filter(sym => sym.kind === 4 /* Variable */
|
|
6275
6283
|
&& sym.attributes.fixedInstance
|
|
6276
|
-
&& isSubtype(apis, sym.retType, qName))
|
|
6284
|
+
&& isSubtype(apis, sym.retType, qName))
|
|
6285
|
+
.sort((l, r) => (r.attributes.weight || 50) - (l.attributes.weight || 50));
|
|
6286
|
+
return symbols;
|
|
6277
6287
|
}
|
|
6278
6288
|
blocks_4.getFixedInstanceDropdownValues = getFixedInstanceDropdownValues;
|
|
6279
6289
|
function generateIcons(instanceSymbols) {
|
|
@@ -7302,18 +7312,21 @@ var pxt;
|
|
|
7302
7312
|
// by BlocklyLoader. The number makes it an invalid JS identifier
|
|
7303
7313
|
const buttonAddName = "0_add_button";
|
|
7304
7314
|
const buttonRemName = "0_rem_button";
|
|
7315
|
+
const buttonAddRemName = "0_add_rem_button";
|
|
7305
7316
|
const numVisibleAttr = "_expanded";
|
|
7306
7317
|
const inputInitAttr = "_input_init";
|
|
7307
7318
|
const optionNames = def.parameters.map(p => p.name);
|
|
7308
7319
|
const totalOptions = def.parameters.length;
|
|
7309
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;
|
|
7310
7323
|
const state = new MutationState(b);
|
|
7311
7324
|
state.setEventsEnabled(false);
|
|
7312
7325
|
state.setValue(numVisibleAttr, 0);
|
|
7313
7326
|
state.setValue(inputInitAttr, false);
|
|
7314
7327
|
state.setEventsEnabled(true);
|
|
7315
7328
|
Blockly.Extensions.apply('inline-svgs', b, false);
|
|
7316
|
-
|
|
7329
|
+
let updatingInputs = false;
|
|
7317
7330
|
appendMutation(b, {
|
|
7318
7331
|
mutationToDom: (el) => {
|
|
7319
7332
|
// The reason we store the inputsInitialized variable separately from visibleOptions
|
|
@@ -7328,8 +7341,8 @@ var pxt;
|
|
|
7328
7341
|
state.setEventsEnabled(false);
|
|
7329
7342
|
if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
|
|
7330
7343
|
state.setValue(inputInitAttr, true);
|
|
7331
|
-
initOptionalInputs();
|
|
7332
7344
|
}
|
|
7345
|
+
initOptionalInputs();
|
|
7333
7346
|
if (saved.hasAttribute(numVisibleAttr)) {
|
|
7334
7347
|
const val = parseInt(saved.getAttribute(numVisibleAttr));
|
|
7335
7348
|
if (!isNaN(val)) {
|
|
@@ -7340,6 +7353,7 @@ var pxt;
|
|
|
7340
7353
|
}
|
|
7341
7354
|
else {
|
|
7342
7355
|
state.setValue(numVisibleAttr, addDelta(delta));
|
|
7356
|
+
updateButtons();
|
|
7343
7357
|
}
|
|
7344
7358
|
}
|
|
7345
7359
|
else {
|
|
@@ -7350,14 +7364,29 @@ var pxt;
|
|
|
7350
7364
|
state.setEventsEnabled(true);
|
|
7351
7365
|
}
|
|
7352
7366
|
});
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
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
|
+
}
|
|
7359
7380
|
}
|
|
7360
|
-
}
|
|
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
|
+
};
|
|
7361
7390
|
// Set skipRender to true if the block is still initializing. Otherwise
|
|
7362
7391
|
// the inputs will render before their shadow blocks are created and
|
|
7363
7392
|
// leave behind annoying artifacts
|
|
@@ -7387,25 +7416,14 @@ var pxt;
|
|
|
7387
7416
|
setInputVisible(input, visible);
|
|
7388
7417
|
if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
7389
7418
|
const param = comp.definitionNameToParam[def.parameters[optIndex].name];
|
|
7390
|
-
|
|
7391
|
-
if (shadow.tagName.toLowerCase() === "value") {
|
|
7392
|
-
// Unwrap the block
|
|
7393
|
-
shadow = shadow.firstElementChild;
|
|
7394
|
-
}
|
|
7395
|
-
Blockly.Events.disable();
|
|
7396
|
-
try {
|
|
7397
|
-
const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
|
|
7398
|
-
if (nb) {
|
|
7399
|
-
input.connection.connect(nb.outputConnection);
|
|
7400
|
-
}
|
|
7401
|
-
}
|
|
7402
|
-
catch (e) { }
|
|
7403
|
-
Blockly.Events.enable();
|
|
7419
|
+
attachShadowBlock(input, param);
|
|
7404
7420
|
}
|
|
7405
7421
|
++optIndex;
|
|
7406
7422
|
}
|
|
7407
7423
|
}
|
|
7408
7424
|
updateButtons();
|
|
7425
|
+
if (variableInlineInputs)
|
|
7426
|
+
b.setInputsInline(visibleOptions < 4);
|
|
7409
7427
|
if (!skipRender)
|
|
7410
7428
|
b.render();
|
|
7411
7429
|
}
|
|
@@ -7414,31 +7432,32 @@ var pxt;
|
|
|
7414
7432
|
.appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
|
|
7415
7433
|
}
|
|
7416
7434
|
function updateButtons() {
|
|
7435
|
+
if (updatingInputs)
|
|
7436
|
+
return;
|
|
7417
7437
|
const visibleOptions = state.getNumber(numVisibleAttr);
|
|
7418
7438
|
const showPlus = visibleOptions !== totalOptions;
|
|
7419
7439
|
const showMinus = visibleOptions !== 0;
|
|
7420
|
-
|
|
7421
|
-
const hasPlus = !!b.getInput(buttonAddName);
|
|
7422
|
-
if (!showPlus) {
|
|
7440
|
+
if (b.inputList.some(i => i.name === buttonAddName))
|
|
7423
7441
|
b.removeInput(buttonAddName, true);
|
|
7424
|
-
|
|
7425
|
-
if (!showMinus) {
|
|
7442
|
+
if (b.inputList.some(i => i.name === buttonRemName))
|
|
7426
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();
|
|
7427
7448
|
}
|
|
7428
|
-
if (
|
|
7429
|
-
|
|
7449
|
+
else if (showPlus) {
|
|
7450
|
+
addPlusButton();
|
|
7430
7451
|
}
|
|
7431
|
-
if (
|
|
7432
|
-
|
|
7433
|
-
if (hasPlus && b.inputList.findIndex(el => el.name === buttonAddName) !== b.inputList.length - 1) {
|
|
7434
|
-
b.removeInput(buttonAddName, true);
|
|
7435
|
-
addPlusButton();
|
|
7436
|
-
}
|
|
7437
|
-
else if (!hasPlus) {
|
|
7438
|
-
addPlusButton();
|
|
7439
|
-
}
|
|
7452
|
+
else if (showMinus) {
|
|
7453
|
+
addMinusButton();
|
|
7440
7454
|
}
|
|
7441
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
|
+
}
|
|
7442
7461
|
function addPlusButton() {
|
|
7443
7462
|
addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
|
|
7444
7463
|
}
|
|
@@ -7455,12 +7474,23 @@ var pxt;
|
|
|
7455
7474
|
}
|
|
7456
7475
|
function setInputVisible(input, visible) {
|
|
7457
7476
|
// If the block isn't rendered, Blockly will crash
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
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
|
+
}
|
|
7463
7491
|
}
|
|
7492
|
+
catch (e) { }
|
|
7493
|
+
Blockly.Events.enable();
|
|
7464
7494
|
}
|
|
7465
7495
|
}
|
|
7466
7496
|
blocks.initExpandableBlock = initExpandableBlock;
|
|
@@ -7800,6 +7830,17 @@ var pxtblockly;
|
|
|
7800
7830
|
this.loaded = true;
|
|
7801
7831
|
this.valueText = this.onValueChanged(this.valueText);
|
|
7802
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
|
+
;
|
|
7803
7844
|
isInitialized() {
|
|
7804
7845
|
return !!this.fieldGroup_;
|
|
7805
7846
|
}
|
|
@@ -7809,6 +7850,23 @@ var pxtblockly;
|
|
|
7809
7850
|
setBlockData(value) {
|
|
7810
7851
|
pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
|
|
7811
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
|
+
}
|
|
7812
7870
|
}
|
|
7813
7871
|
pxtblockly.FieldBase = FieldBase;
|
|
7814
7872
|
})(pxtblockly || (pxtblockly = {}));
|
|
@@ -11511,6 +11569,311 @@ var pxtblockly;
|
|
|
11511
11569
|
}
|
|
11512
11570
|
pxtblockly.FieldProtractor = FieldProtractor;
|
|
11513
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 = {}));
|
|
11514
11877
|
/// <reference path="../../localtypings/blockly.d.ts"/>
|
|
11515
11878
|
/// <reference path="../../built/pxtsim.d.ts"/>
|
|
11516
11879
|
var pxtblockly;
|
|
@@ -12976,4 +13339,21 @@ var pxtblockly;
|
|
|
12976
13339
|
}
|
|
12977
13340
|
}
|
|
12978
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;
|
|
12979
13359
|
})(pxtblockly || (pxtblockly = {}));
|