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/pxtblockly.js
CHANGED
|
@@ -4016,7 +4016,7 @@ var pxt;
|
|
|
4016
4016
|
const call = e.stdCallTable[b.type];
|
|
4017
4017
|
if (call.attrs.shim === "ENUM_GET" || call.attrs.shim === "KIND_GET")
|
|
4018
4018
|
return;
|
|
4019
|
-
visibleParams(call, countOptionals(b)).forEach((p, i) => {
|
|
4019
|
+
visibleParams(call, countOptionals(b, call)).forEach((p, i) => {
|
|
4020
4020
|
const isInstance = call.isExtensionMethod && i === 0;
|
|
4021
4021
|
if (p.definitionName && !b.getFieldValue(p.definitionName)) {
|
|
4022
4022
|
let i = b.inputList.find((i) => i.name == p.definitionName);
|
|
@@ -4484,7 +4484,7 @@ var pxt;
|
|
|
4484
4484
|
let call = e.stdCallTable[b.type];
|
|
4485
4485
|
if (call) {
|
|
4486
4486
|
if (call.imageLiteral)
|
|
4487
|
-
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)));
|
|
4487
|
+
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)));
|
|
4488
4488
|
else
|
|
4489
4489
|
expr = compileStdCall(e, b, call, comments);
|
|
4490
4490
|
}
|
|
@@ -4697,12 +4697,12 @@ var pxt;
|
|
|
4697
4697
|
return blocks_1.mkStmt(blocks_1.mkInfix(ref, "+=", expr));
|
|
4698
4698
|
}
|
|
4699
4699
|
function eventArgs(call, b) {
|
|
4700
|
-
return visibleParams(call, countOptionals(b)).filter(ar => !!ar.definitionName);
|
|
4700
|
+
return visibleParams(call, countOptionals(b, call)).filter(ar => !!ar.definitionName);
|
|
4701
4701
|
}
|
|
4702
4702
|
function compileCall(e, b, comments) {
|
|
4703
4703
|
const call = e.stdCallTable[b.type];
|
|
4704
4704
|
if (call.imageLiteral)
|
|
4705
|
-
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))));
|
|
4705
|
+
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))));
|
|
4706
4706
|
else if (call.hasHandler)
|
|
4707
4707
|
return compileEvent(e, b, call, eventArgs(call, b), call.namespace, comments);
|
|
4708
4708
|
else
|
|
@@ -4781,7 +4781,7 @@ var pxt;
|
|
|
4781
4781
|
return blocks_1.H.mkPropertyAccess(b.getFieldValue("MEMBER"), blocks_1.mkText(info.name));
|
|
4782
4782
|
}
|
|
4783
4783
|
else {
|
|
4784
|
-
args = visibleParams(func, countOptionals(b)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
|
|
4784
|
+
args = visibleParams(func, countOptionals(b, func)).map((p, i) => compileArgument(e, b, p, comments, func.isExtensionMethod && i === 0 && !func.isExpression));
|
|
4785
4785
|
}
|
|
4786
4786
|
let callNamespace = func.namespace;
|
|
4787
4787
|
let callName = func.f;
|
|
@@ -5435,7 +5435,14 @@ var pxt;
|
|
|
5435
5435
|
}
|
|
5436
5436
|
return blocks_1.mkStmt(blocks_1.mkText("let " + v.escapedName + tp + " = "), defl);
|
|
5437
5437
|
}
|
|
5438
|
-
function countOptionals(b) {
|
|
5438
|
+
function countOptionals(b, func) {
|
|
5439
|
+
if (func.attrs.compileHiddenArguments) {
|
|
5440
|
+
return func.comp.parameters.reduce((prev, block) => {
|
|
5441
|
+
if (block.isOptional)
|
|
5442
|
+
prev++;
|
|
5443
|
+
return prev;
|
|
5444
|
+
}, 0);
|
|
5445
|
+
}
|
|
5439
5446
|
if (b.mutationToDom) {
|
|
5440
5447
|
const el = b.mutationToDom();
|
|
5441
5448
|
if (el.hasAttribute("_expanded")) {
|
|
@@ -5954,6 +5961,7 @@ var pxt;
|
|
|
5954
5961
|
registerFieldEditor('protractor', pxtblockly.FieldProtractor);
|
|
5955
5962
|
registerFieldEditor('position', pxtblockly.FieldPosition);
|
|
5956
5963
|
registerFieldEditor('melody', pxtblockly.FieldCustomMelody);
|
|
5964
|
+
registerFieldEditor('soundeffect', pxtblockly.FieldSoundEffect);
|
|
5957
5965
|
}
|
|
5958
5966
|
blocks.initFieldEditors = initFieldEditors;
|
|
5959
5967
|
function registerFieldEditor(selector, field, validator) {
|
|
@@ -10866,18 +10874,22 @@ var pxt;
|
|
|
10866
10874
|
// by BlocklyLoader. The number makes it an invalid JS identifier
|
|
10867
10875
|
const buttonAddName = "0_add_button";
|
|
10868
10876
|
const buttonRemName = "0_rem_button";
|
|
10877
|
+
const buttonAddRemName = "0_add_rem_button";
|
|
10869
10878
|
const numVisibleAttr = "_expanded";
|
|
10870
10879
|
const inputInitAttr = "_input_init";
|
|
10871
10880
|
const optionNames = def.parameters.map(p => p.name);
|
|
10872
10881
|
const totalOptions = def.parameters.length;
|
|
10873
10882
|
const buttonDelta = toggle ? totalOptions : 1;
|
|
10883
|
+
const variableInlineInputs = info.blocksById[b.type].attributes.inlineInputMode === "variable";
|
|
10884
|
+
const compileHiddenArguments = info.blocksById[b.type].attributes.compileHiddenArguments;
|
|
10874
10885
|
const state = new MutationState(b);
|
|
10875
10886
|
state.setEventsEnabled(false);
|
|
10876
10887
|
state.setValue(numVisibleAttr, 0);
|
|
10877
10888
|
state.setValue(inputInitAttr, false);
|
|
10878
10889
|
state.setEventsEnabled(true);
|
|
10879
10890
|
Blockly.Extensions.apply('inline-svgs', b, false);
|
|
10880
|
-
|
|
10891
|
+
let updatingInputs = false;
|
|
10892
|
+
let firstRender = true;
|
|
10881
10893
|
appendMutation(b, {
|
|
10882
10894
|
mutationToDom: (el) => {
|
|
10883
10895
|
// The reason we store the inputsInitialized variable separately from visibleOptions
|
|
@@ -10892,8 +10904,8 @@ var pxt;
|
|
|
10892
10904
|
state.setEventsEnabled(false);
|
|
10893
10905
|
if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
|
|
10894
10906
|
state.setValue(inputInitAttr, true);
|
|
10895
|
-
initOptionalInputs();
|
|
10896
10907
|
}
|
|
10908
|
+
initOptionalInputs();
|
|
10897
10909
|
if (saved.hasAttribute(numVisibleAttr)) {
|
|
10898
10910
|
const val = parseInt(saved.getAttribute(numVisibleAttr));
|
|
10899
10911
|
if (!isNaN(val)) {
|
|
@@ -10904,6 +10916,7 @@ var pxt;
|
|
|
10904
10916
|
}
|
|
10905
10917
|
else {
|
|
10906
10918
|
state.setValue(numVisibleAttr, addDelta(delta));
|
|
10919
|
+
updateButtons();
|
|
10907
10920
|
}
|
|
10908
10921
|
}
|
|
10909
10922
|
else {
|
|
@@ -10914,14 +10927,32 @@ var pxt;
|
|
|
10914
10927
|
state.setEventsEnabled(true);
|
|
10915
10928
|
}
|
|
10916
10929
|
});
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10930
|
+
initOptionalInputs();
|
|
10931
|
+
if (compileHiddenArguments) {
|
|
10932
|
+
// Make sure all inputs have shadow blocks attached
|
|
10933
|
+
let optIndex = 0;
|
|
10934
|
+
for (let i = 0; i < b.inputList.length; i++) {
|
|
10935
|
+
const input = b.inputList[i];
|
|
10936
|
+
if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
|
|
10937
|
+
if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
10938
|
+
const param = comp.definitionNameToParam[def.parameters[optIndex].name];
|
|
10939
|
+
attachShadowBlock(input, param);
|
|
10940
|
+
}
|
|
10941
|
+
++optIndex;
|
|
10942
|
+
}
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
b.render = (opt_bubble) => {
|
|
10946
|
+
if (updatingInputs)
|
|
10947
|
+
return;
|
|
10948
|
+
if (firstRender) {
|
|
10949
|
+
firstRender = false;
|
|
10950
|
+
updatingInputs = true;
|
|
10922
10951
|
updateShape(0, undefined, true);
|
|
10952
|
+
updatingInputs = false;
|
|
10923
10953
|
}
|
|
10924
|
-
|
|
10954
|
+
Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
|
|
10955
|
+
};
|
|
10925
10956
|
// Set skipRender to true if the block is still initializing. Otherwise
|
|
10926
10957
|
// the inputs will render before their shadow blocks are created and
|
|
10927
10958
|
// leave behind annoying artifacts
|
|
@@ -10951,25 +10982,14 @@ var pxt;
|
|
|
10951
10982
|
setInputVisible(input, visible);
|
|
10952
10983
|
if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
10953
10984
|
const param = comp.definitionNameToParam[def.parameters[optIndex].name];
|
|
10954
|
-
|
|
10955
|
-
if (shadow.tagName.toLowerCase() === "value") {
|
|
10956
|
-
// Unwrap the block
|
|
10957
|
-
shadow = shadow.firstElementChild;
|
|
10958
|
-
}
|
|
10959
|
-
Blockly.Events.disable();
|
|
10960
|
-
try {
|
|
10961
|
-
const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
|
|
10962
|
-
if (nb) {
|
|
10963
|
-
input.connection.connect(nb.outputConnection);
|
|
10964
|
-
}
|
|
10965
|
-
}
|
|
10966
|
-
catch (e) { }
|
|
10967
|
-
Blockly.Events.enable();
|
|
10985
|
+
attachShadowBlock(input, param);
|
|
10968
10986
|
}
|
|
10969
10987
|
++optIndex;
|
|
10970
10988
|
}
|
|
10971
10989
|
}
|
|
10972
10990
|
updateButtons();
|
|
10991
|
+
if (variableInlineInputs)
|
|
10992
|
+
b.setInputsInline(visibleOptions < 4);
|
|
10973
10993
|
if (!skipRender)
|
|
10974
10994
|
b.render();
|
|
10975
10995
|
}
|
|
@@ -10978,31 +10998,32 @@ var pxt;
|
|
|
10978
10998
|
.appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
|
|
10979
10999
|
}
|
|
10980
11000
|
function updateButtons() {
|
|
11001
|
+
if (updatingInputs)
|
|
11002
|
+
return;
|
|
10981
11003
|
const visibleOptions = state.getNumber(numVisibleAttr);
|
|
10982
11004
|
const showPlus = visibleOptions !== totalOptions;
|
|
10983
11005
|
const showMinus = visibleOptions !== 0;
|
|
10984
|
-
|
|
10985
|
-
const hasPlus = !!b.getInput(buttonAddName);
|
|
10986
|
-
if (!showPlus) {
|
|
11006
|
+
if (b.inputList.some(i => i.name === buttonAddName))
|
|
10987
11007
|
b.removeInput(buttonAddName, true);
|
|
10988
|
-
|
|
10989
|
-
if (!showMinus) {
|
|
11008
|
+
if (b.inputList.some(i => i.name === buttonRemName))
|
|
10990
11009
|
b.removeInput(buttonRemName, true);
|
|
11010
|
+
if (b.inputList.some(i => i.name === buttonAddRemName))
|
|
11011
|
+
b.removeInput(buttonAddRemName, true);
|
|
11012
|
+
if (showPlus && showMinus) {
|
|
11013
|
+
addPlusAndMinusButtons();
|
|
10991
11014
|
}
|
|
10992
|
-
if (
|
|
10993
|
-
|
|
11015
|
+
else if (showPlus) {
|
|
11016
|
+
addPlusButton();
|
|
10994
11017
|
}
|
|
10995
|
-
if (
|
|
10996
|
-
|
|
10997
|
-
if (hasPlus && b.inputList.findIndex(el => el.name === buttonAddName) !== b.inputList.length - 1) {
|
|
10998
|
-
b.removeInput(buttonAddName, true);
|
|
10999
|
-
addPlusButton();
|
|
11000
|
-
}
|
|
11001
|
-
else if (!hasPlus) {
|
|
11002
|
-
addPlusButton();
|
|
11003
|
-
}
|
|
11018
|
+
else if (showMinus) {
|
|
11019
|
+
addMinusButton();
|
|
11004
11020
|
}
|
|
11005
11021
|
}
|
|
11022
|
+
function addPlusAndMinusButtons() {
|
|
11023
|
+
b.appendDummyInput(buttonAddRemName)
|
|
11024
|
+
.appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
|
|
11025
|
+
.appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
|
|
11026
|
+
}
|
|
11006
11027
|
function addPlusButton() {
|
|
11007
11028
|
addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
|
|
11008
11029
|
}
|
|
@@ -11019,12 +11040,23 @@ var pxt;
|
|
|
11019
11040
|
}
|
|
11020
11041
|
function setInputVisible(input, visible) {
|
|
11021
11042
|
// If the block isn't rendered, Blockly will crash
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11043
|
+
input.setVisible(visible);
|
|
11044
|
+
}
|
|
11045
|
+
function attachShadowBlock(input, param) {
|
|
11046
|
+
let shadow = blocks.createShadowValue(info, param);
|
|
11047
|
+
if (shadow.tagName.toLowerCase() === "value") {
|
|
11048
|
+
// Unwrap the block
|
|
11049
|
+
shadow = shadow.firstElementChild;
|
|
11050
|
+
}
|
|
11051
|
+
Blockly.Events.disable();
|
|
11052
|
+
try {
|
|
11053
|
+
const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
|
|
11054
|
+
if (nb) {
|
|
11055
|
+
input.connection.connect(nb.outputConnection);
|
|
11056
|
+
}
|
|
11027
11057
|
}
|
|
11058
|
+
catch (e) { }
|
|
11059
|
+
Blockly.Events.enable();
|
|
11028
11060
|
}
|
|
11029
11061
|
}
|
|
11030
11062
|
blocks.initExpandableBlock = initExpandableBlock;
|
|
@@ -11364,6 +11396,17 @@ var pxtblockly;
|
|
|
11364
11396
|
this.loaded = true;
|
|
11365
11397
|
this.valueText = this.onValueChanged(this.valueText);
|
|
11366
11398
|
}
|
|
11399
|
+
getAnchorDimensions() {
|
|
11400
|
+
const boundingBox = this.getScaledBBox();
|
|
11401
|
+
if (this.sourceBlock_.RTL) {
|
|
11402
|
+
boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
11403
|
+
}
|
|
11404
|
+
else {
|
|
11405
|
+
boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
11406
|
+
}
|
|
11407
|
+
return boundingBox;
|
|
11408
|
+
}
|
|
11409
|
+
;
|
|
11367
11410
|
isInitialized() {
|
|
11368
11411
|
return !!this.fieldGroup_;
|
|
11369
11412
|
}
|
|
@@ -11373,6 +11416,23 @@ var pxtblockly;
|
|
|
11373
11416
|
setBlockData(value) {
|
|
11374
11417
|
pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
|
|
11375
11418
|
}
|
|
11419
|
+
getSiblingBlock(inputName, useGrandparent = false) {
|
|
11420
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
11421
|
+
if (!block || !block.inputList)
|
|
11422
|
+
return undefined;
|
|
11423
|
+
for (const input of block.inputList) {
|
|
11424
|
+
if (input.name === inputName) {
|
|
11425
|
+
return input.connection.targetBlock();
|
|
11426
|
+
}
|
|
11427
|
+
}
|
|
11428
|
+
return undefined;
|
|
11429
|
+
}
|
|
11430
|
+
getSiblingField(fieldName, useGrandparent = false) {
|
|
11431
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
11432
|
+
if (!block)
|
|
11433
|
+
return undefined;
|
|
11434
|
+
return block.getField(fieldName);
|
|
11435
|
+
}
|
|
11376
11436
|
}
|
|
11377
11437
|
pxtblockly.FieldBase = FieldBase;
|
|
11378
11438
|
})(pxtblockly || (pxtblockly = {}));
|
|
@@ -15075,6 +15135,319 @@ var pxtblockly;
|
|
|
15075
15135
|
}
|
|
15076
15136
|
pxtblockly.FieldProtractor = FieldProtractor;
|
|
15077
15137
|
})(pxtblockly || (pxtblockly = {}));
|
|
15138
|
+
/// <reference path="../../built/pxtlib.d.ts" />
|
|
15139
|
+
/// <reference path="./field_base.ts" />
|
|
15140
|
+
var pxtblockly;
|
|
15141
|
+
(function (pxtblockly) {
|
|
15142
|
+
var svg = pxt.svgUtil;
|
|
15143
|
+
const MUSIC_ICON_WIDTH = 20;
|
|
15144
|
+
const TOTAL_WIDTH = 160;
|
|
15145
|
+
const TOTAL_HEIGHT = 40;
|
|
15146
|
+
const X_PADDING = 5;
|
|
15147
|
+
const Y_PADDING = 4;
|
|
15148
|
+
const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
|
|
15149
|
+
class FieldSoundEffect extends pxtblockly.FieldBase {
|
|
15150
|
+
onInit() {
|
|
15151
|
+
if (!this.options)
|
|
15152
|
+
this.options = {};
|
|
15153
|
+
if (!this.options.durationInputName)
|
|
15154
|
+
this.options.durationInputName = "duration";
|
|
15155
|
+
if (!this.options.startFrequencyInputName)
|
|
15156
|
+
this.options.startFrequencyInputName = "startFrequency";
|
|
15157
|
+
if (!this.options.endFrequencyInputName)
|
|
15158
|
+
this.options.endFrequencyInputName = "endFrequency";
|
|
15159
|
+
if (!this.options.startVolumeInputName)
|
|
15160
|
+
this.options.startVolumeInputName = "startVolume";
|
|
15161
|
+
if (!this.options.endVolumeInputName)
|
|
15162
|
+
this.options.endVolumeInputName = "endVolume";
|
|
15163
|
+
if (!this.options.waveFieldName)
|
|
15164
|
+
this.options.waveFieldName = "waveShape";
|
|
15165
|
+
if (!this.options.interpolationFieldName)
|
|
15166
|
+
this.options.interpolationFieldName = "interpolation";
|
|
15167
|
+
if (!this.options.effectFieldName)
|
|
15168
|
+
this.options.effectFieldName = "effect";
|
|
15169
|
+
this.redrawPreview();
|
|
15170
|
+
}
|
|
15171
|
+
onDispose() {
|
|
15172
|
+
}
|
|
15173
|
+
onValueChanged(newValue) {
|
|
15174
|
+
return newValue;
|
|
15175
|
+
}
|
|
15176
|
+
redrawPreview() {
|
|
15177
|
+
if (!this.fieldGroup_)
|
|
15178
|
+
return;
|
|
15179
|
+
pxsim.U.clear(this.fieldGroup_);
|
|
15180
|
+
const bg = new svg.Rect()
|
|
15181
|
+
.at(X_PADDING, Y_PADDING)
|
|
15182
|
+
.size(TOTAL_WIDTH, TOTAL_HEIGHT)
|
|
15183
|
+
.setClass("blocklySpriteField")
|
|
15184
|
+
.stroke("#fff", 1)
|
|
15185
|
+
.corner(TOTAL_HEIGHT / 2);
|
|
15186
|
+
const clipPathId = "preview-clip-" + pxt.U.guidGen();
|
|
15187
|
+
const clip = new svg.ClipPath()
|
|
15188
|
+
.id(clipPathId)
|
|
15189
|
+
.clipPathUnits(false);
|
|
15190
|
+
const clipRect = new svg.Rect()
|
|
15191
|
+
.size(PREVIEW_WIDTH, TOTAL_HEIGHT)
|
|
15192
|
+
.fill("#FFF")
|
|
15193
|
+
.at(0, 0);
|
|
15194
|
+
clip.appendChild(clipRect);
|
|
15195
|
+
const path = new svg.Path()
|
|
15196
|
+
.stroke("grey", 2)
|
|
15197
|
+
.fill("none")
|
|
15198
|
+
.setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
|
|
15199
|
+
.clipPath("url('#" + clipPathId + "')");
|
|
15200
|
+
const g = new svg.Group()
|
|
15201
|
+
.translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
|
|
15202
|
+
g.appendChild(clip);
|
|
15203
|
+
g.appendChild(path);
|
|
15204
|
+
const musicIcon = new svg.Text("\uf001")
|
|
15205
|
+
.appendClass("melody-editor-field-icon")
|
|
15206
|
+
.setAttribute("alignment-baseline", "middle")
|
|
15207
|
+
.anchor("middle")
|
|
15208
|
+
.at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
|
|
15209
|
+
this.fieldGroup_.appendChild(bg.el);
|
|
15210
|
+
this.fieldGroup_.appendChild(musicIcon.el);
|
|
15211
|
+
this.fieldGroup_.appendChild(g.el);
|
|
15212
|
+
}
|
|
15213
|
+
showEditor_() {
|
|
15214
|
+
const initialSound = this.readCurrentSound();
|
|
15215
|
+
Blockly.Events.disable();
|
|
15216
|
+
let bbox;
|
|
15217
|
+
// This is due to the changes in https://github.com/microsoft/pxt-blockly/pull/289
|
|
15218
|
+
// which caused the widgetdiv to jump around if any fields underneath changed size
|
|
15219
|
+
let widgetOwner = {
|
|
15220
|
+
getScaledBBox: () => bbox
|
|
15221
|
+
};
|
|
15222
|
+
Blockly.WidgetDiv.show(widgetOwner, this.sourceBlock_.RTL, () => {
|
|
15223
|
+
fv.hide();
|
|
15224
|
+
widgetDiv.classList.remove("sound-effect-editor-widget");
|
|
15225
|
+
widgetDiv.style.transform = "";
|
|
15226
|
+
widgetDiv.style.position = "";
|
|
15227
|
+
widgetDiv.style.left = "";
|
|
15228
|
+
widgetDiv.style.top = "";
|
|
15229
|
+
widgetDiv.style.width = "";
|
|
15230
|
+
widgetDiv.style.height = "";
|
|
15231
|
+
widgetDiv.style.opacity = "";
|
|
15232
|
+
widgetDiv.style.transition = "";
|
|
15233
|
+
Blockly.Events.enable();
|
|
15234
|
+
Blockly.Events.setGroup(true);
|
|
15235
|
+
this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
|
|
15236
|
+
this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
|
|
15237
|
+
this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
|
|
15238
|
+
this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
|
|
15239
|
+
this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
|
|
15240
|
+
this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
|
|
15241
|
+
this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
|
|
15242
|
+
this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
|
|
15243
|
+
Blockly.Events.setGroup(false);
|
|
15244
|
+
if (this.mostRecentValue)
|
|
15245
|
+
this.setBlockData(JSON.stringify(this.mostRecentValue));
|
|
15246
|
+
});
|
|
15247
|
+
const widgetDiv = Blockly.WidgetDiv.DIV;
|
|
15248
|
+
const opts = {
|
|
15249
|
+
onClose: () => {
|
|
15250
|
+
fv.hide();
|
|
15251
|
+
Blockly.WidgetDiv.hideIfOwner(widgetOwner);
|
|
15252
|
+
},
|
|
15253
|
+
onSoundChange: (newSound) => {
|
|
15254
|
+
this.mostRecentValue = newSound;
|
|
15255
|
+
this.updateSiblingBlocks(newSound);
|
|
15256
|
+
this.redrawPreview();
|
|
15257
|
+
},
|
|
15258
|
+
initialSound: initialSound
|
|
15259
|
+
};
|
|
15260
|
+
const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
|
|
15261
|
+
const block = this.sourceBlock_;
|
|
15262
|
+
const bounds = block.getBoundingRectangle();
|
|
15263
|
+
const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
|
|
15264
|
+
const animationDistance = 20;
|
|
15265
|
+
const left = coord.x + 20;
|
|
15266
|
+
const top = coord.y - animationDistance;
|
|
15267
|
+
widgetDiv.style.opacity = "0";
|
|
15268
|
+
widgetDiv.classList.add("sound-effect-editor-widget");
|
|
15269
|
+
widgetDiv.style.position = "absolute";
|
|
15270
|
+
widgetDiv.style.left = left + "px";
|
|
15271
|
+
widgetDiv.style.top = top + "px";
|
|
15272
|
+
widgetDiv.style.width = "30rem";
|
|
15273
|
+
widgetDiv.style.height = "40rem";
|
|
15274
|
+
widgetDiv.style.display = "block";
|
|
15275
|
+
widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
|
|
15276
|
+
fv.onHide(() => {
|
|
15277
|
+
// do nothing
|
|
15278
|
+
});
|
|
15279
|
+
fv.show();
|
|
15280
|
+
const divBounds = widgetDiv.getBoundingClientRect();
|
|
15281
|
+
const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
|
|
15282
|
+
if (divBounds.height > injectDivBounds.height) {
|
|
15283
|
+
widgetDiv.style.height = "";
|
|
15284
|
+
widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
|
|
15285
|
+
widgetDiv.style.bottom = `calc(1rem + ${animationDistance}px)`;
|
|
15286
|
+
}
|
|
15287
|
+
else {
|
|
15288
|
+
if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
|
|
15289
|
+
// This editor is pretty tall, so just center vertically on the inject div
|
|
15290
|
+
widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
|
|
15291
|
+
}
|
|
15292
|
+
}
|
|
15293
|
+
const toolboxWidth = block.workspace.getToolbox().getWidth();
|
|
15294
|
+
if (divBounds.width > injectDivBounds.width - toolboxWidth) {
|
|
15295
|
+
widgetDiv.style.width = "";
|
|
15296
|
+
widgetDiv.style.left = "1rem";
|
|
15297
|
+
widgetDiv.style.right = "1rem";
|
|
15298
|
+
}
|
|
15299
|
+
else {
|
|
15300
|
+
// Check to see if we are bleeding off the right side of the canvas
|
|
15301
|
+
if (divBounds.left + divBounds.width >= injectDivBounds.right) {
|
|
15302
|
+
// If so, try and place to the left of the block instead of the right
|
|
15303
|
+
const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
|
|
15304
|
+
const workspaceLeft = injectDivBounds.left + toolboxWidth;
|
|
15305
|
+
if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
|
|
15306
|
+
widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
|
|
15307
|
+
}
|
|
15308
|
+
else {
|
|
15309
|
+
// As a last resort, just center on the inject div
|
|
15310
|
+
widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
|
|
15311
|
+
}
|
|
15312
|
+
}
|
|
15313
|
+
}
|
|
15314
|
+
const finalDimensions = widgetDiv.getBoundingClientRect();
|
|
15315
|
+
bbox = new Blockly.utils.Rect(finalDimensions.top, finalDimensions.bottom, finalDimensions.left, finalDimensions.right);
|
|
15316
|
+
requestAnimationFrame(() => {
|
|
15317
|
+
widgetDiv.style.opacity = "1";
|
|
15318
|
+
widgetDiv.style.transform = `translateY(${animationDistance}px)`;
|
|
15319
|
+
});
|
|
15320
|
+
}
|
|
15321
|
+
render_() {
|
|
15322
|
+
super.render_();
|
|
15323
|
+
this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
|
|
15324
|
+
this.size_.width = TOTAL_WIDTH;
|
|
15325
|
+
}
|
|
15326
|
+
updateSiblingBlocks(sound) {
|
|
15327
|
+
this.setNumberInputValue(this.options.durationInputName, sound.duration);
|
|
15328
|
+
this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
|
|
15329
|
+
this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
|
|
15330
|
+
this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
|
|
15331
|
+
this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
|
|
15332
|
+
this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
|
|
15333
|
+
this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
|
|
15334
|
+
this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
|
|
15335
|
+
}
|
|
15336
|
+
setNumberInputValue(name, value) {
|
|
15337
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15338
|
+
if (!block)
|
|
15339
|
+
return;
|
|
15340
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15341
|
+
block.setFieldValue(Math.round(value), "NUM");
|
|
15342
|
+
}
|
|
15343
|
+
else if (block.type === "math_number_minmax") {
|
|
15344
|
+
block.setFieldValue(Math.round(value), "SLIDER");
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15347
|
+
getNumberInputValue(name, defaultValue) {
|
|
15348
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15349
|
+
if (!block)
|
|
15350
|
+
return defaultValue;
|
|
15351
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15352
|
+
return parseInt(block.getFieldValue("NUM") + "");
|
|
15353
|
+
}
|
|
15354
|
+
else if (block.type === "math_number_minmax") {
|
|
15355
|
+
return parseInt(block.getFieldValue("SLIDER") + "");
|
|
15356
|
+
}
|
|
15357
|
+
return defaultValue;
|
|
15358
|
+
}
|
|
15359
|
+
fireNumberInputUpdate(name, oldValue) {
|
|
15360
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15361
|
+
if (!block)
|
|
15362
|
+
return;
|
|
15363
|
+
let fieldName;
|
|
15364
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15365
|
+
fieldName = "NUM";
|
|
15366
|
+
}
|
|
15367
|
+
else if (block.type === "math_number_minmax") {
|
|
15368
|
+
fieldName = "SLIDER";
|
|
15369
|
+
}
|
|
15370
|
+
if (!fieldName)
|
|
15371
|
+
return;
|
|
15372
|
+
Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
|
|
15373
|
+
}
|
|
15374
|
+
setFieldDropdownValue(name, value) {
|
|
15375
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15376
|
+
if (!field)
|
|
15377
|
+
return;
|
|
15378
|
+
field.setValue(value);
|
|
15379
|
+
}
|
|
15380
|
+
getFieldDropdownValue(name) {
|
|
15381
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15382
|
+
if (!field)
|
|
15383
|
+
return undefined;
|
|
15384
|
+
return field.getValue();
|
|
15385
|
+
}
|
|
15386
|
+
fireFieldDropdownUpdate(name, oldValue) {
|
|
15387
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15388
|
+
if (!field)
|
|
15389
|
+
return;
|
|
15390
|
+
Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
|
|
15391
|
+
}
|
|
15392
|
+
readCurrentSound() {
|
|
15393
|
+
const savedSound = this.readBlockDataSound();
|
|
15394
|
+
return {
|
|
15395
|
+
duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
|
|
15396
|
+
startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
|
|
15397
|
+
endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
|
|
15398
|
+
startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
|
|
15399
|
+
endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
|
|
15400
|
+
wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
|
|
15401
|
+
interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
|
|
15402
|
+
effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
|
|
15403
|
+
};
|
|
15404
|
+
}
|
|
15405
|
+
// This stores the values of the fields in case a block (e.g. a variable) is placed in one
|
|
15406
|
+
// of the inputs.
|
|
15407
|
+
readBlockDataSound() {
|
|
15408
|
+
const data = this.getBlockData();
|
|
15409
|
+
let sound;
|
|
15410
|
+
try {
|
|
15411
|
+
sound = JSON.parse(data);
|
|
15412
|
+
}
|
|
15413
|
+
catch (e) {
|
|
15414
|
+
sound = {
|
|
15415
|
+
duration: 1000,
|
|
15416
|
+
startFrequency: 100,
|
|
15417
|
+
endFrequency: 1800,
|
|
15418
|
+
startVolume: 1023,
|
|
15419
|
+
endVolume: 0,
|
|
15420
|
+
wave: "sine",
|
|
15421
|
+
interpolation: "linear",
|
|
15422
|
+
effect: "none"
|
|
15423
|
+
};
|
|
15424
|
+
}
|
|
15425
|
+
return sound;
|
|
15426
|
+
}
|
|
15427
|
+
}
|
|
15428
|
+
pxtblockly.FieldSoundEffect = FieldSoundEffect;
|
|
15429
|
+
const waveformMapping = {
|
|
15430
|
+
"sine": "WaveShape.Sine",
|
|
15431
|
+
"square": "WaveShape.Square",
|
|
15432
|
+
"sawtooth": "WaveShape.Sawtooth",
|
|
15433
|
+
"triangle": "WaveShape.Triangle",
|
|
15434
|
+
"noise": "WaveShape.Noise",
|
|
15435
|
+
};
|
|
15436
|
+
const effectMapping = {
|
|
15437
|
+
"none": "SoundExpressionEffect.None",
|
|
15438
|
+
"vibrato": "SoundExpressionEffect.Vibrato",
|
|
15439
|
+
"tremolo": "SoundExpressionEffect.Tremolo",
|
|
15440
|
+
"warble": "SoundExpressionEffect.Warble",
|
|
15441
|
+
};
|
|
15442
|
+
const interpolationMapping = {
|
|
15443
|
+
"linear": "InterpolationCurve.Linear",
|
|
15444
|
+
"curve": "InterpolationCurve.Curve",
|
|
15445
|
+
"logarithmic": "InterpolationCurve.Logarithmic",
|
|
15446
|
+
};
|
|
15447
|
+
function reverseLookup(map, value) {
|
|
15448
|
+
return Object.keys(map).find(k => map[k] === value);
|
|
15449
|
+
}
|
|
15450
|
+
})(pxtblockly || (pxtblockly = {}));
|
|
15078
15451
|
/// <reference path="../../localtypings/blockly.d.ts"/>
|
|
15079
15452
|
/// <reference path="../../built/pxtsim.d.ts"/>
|
|
15080
15453
|
var pxtblockly;
|
|
@@ -16540,4 +16913,21 @@ var pxtblockly;
|
|
|
16540
16913
|
}
|
|
16541
16914
|
}
|
|
16542
16915
|
pxtblockly.getTemporaryAssets = getTemporaryAssets;
|
|
16916
|
+
function workspaceToScreenCoordinates(ws, wsCoordinates) {
|
|
16917
|
+
// The position in pixels relative to the origin of the
|
|
16918
|
+
// main workspace.
|
|
16919
|
+
const scaledWS = wsCoordinates.scale(ws.scale);
|
|
16920
|
+
// The offset in pixels between the main workspace's origin and the upper
|
|
16921
|
+
// left corner of the injection div.
|
|
16922
|
+
const mainOffsetPixels = ws.getOriginOffsetInPixels();
|
|
16923
|
+
// The client coordinates offset by the injection div's upper left corner.
|
|
16924
|
+
const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
|
|
16925
|
+
const injectionDiv = ws.getInjectionDiv();
|
|
16926
|
+
// Bounding rect coordinates are in client coordinates, meaning that they
|
|
16927
|
+
// are in pixels relative to the upper left corner of the visible browser
|
|
16928
|
+
// window. These coordinates change when you scroll the browser window.
|
|
16929
|
+
const boundingRect = injectionDiv.getBoundingClientRect();
|
|
16930
|
+
return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
|
|
16931
|
+
}
|
|
16932
|
+
pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
|
|
16543
16933
|
})(pxtblockly || (pxtblockly = {}));
|