pxt-core 7.5.8 → 7.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/built/pxt.js +1002 -2
- package/built/pxtblockly.js +428 -50
- package/built/pxtblocks.d.ts +34 -0
- package/built/pxtblocks.js +428 -50
- package/built/pxtlib.d.ts +19 -1
- package/built/pxtlib.js +125 -1
- package/built/pxtsim.d.ts +222 -0
- package/built/pxtsim.js +877 -1
- package/built/target.js +1 -1
- package/built/web/icons.css +49 -10
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtblockly.js +1 -1
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtsim.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-authcode.css +104 -1
- package/built/web/react-common-skillmap.css +1 -1
- package/built/web/rtlreact-common-skillmap.css +1 -1
- package/built/web/rtlsemantic.css +2 -2
- package/built/web/semantic.css +2 -2
- package/built/web/skillmap/js/main.b5f3628d.chunk.js +1 -0
- package/docfiles/footer.html +1 -1
- package/docfiles/script.html +1 -1
- package/docfiles/thin-footer.html +1 -1
- package/localtypings/pxtarget.d.ts +1 -0
- package/package.json +1 -1
- package/react-common/components/controls/Button.tsx +7 -3
- package/react-common/components/controls/DraggableGraph.tsx +242 -0
- package/react-common/components/controls/Dropdown.tsx +121 -0
- package/react-common/components/controls/FocusList.tsx +17 -8
- package/react-common/components/controls/Input.tsx +13 -3
- package/react-common/components/controls/RadioButtonGroup.tsx +66 -0
- package/react-common/components/util.tsx +23 -0
- package/react-common/styles/controls/DraggableGraph.less +13 -0
- package/react-common/styles/controls/Dropdown.less +64 -0
- package/react-common/styles/controls/RadioButtonGroup.less +36 -0
- package/react-common/styles/react-common-variables.less +24 -0
- package/react-common/styles/react-common.less +3 -0
- package/theme/pxt.less +1 -0
- package/theme/soundeffecteditor.less +132 -0
- package/webapp/public/skillmap.html +1 -1
- package/built/web/skillmap/js/main.2485091f.chunk.js +0 -1
package/built/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,21 @@ 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;
|
|
10881
10892
|
appendMutation(b, {
|
|
10882
10893
|
mutationToDom: (el) => {
|
|
10883
10894
|
// The reason we store the inputsInitialized variable separately from visibleOptions
|
|
@@ -10892,8 +10903,8 @@ var pxt;
|
|
|
10892
10903
|
state.setEventsEnabled(false);
|
|
10893
10904
|
if (saved.hasAttribute(inputInitAttr) && saved.getAttribute(inputInitAttr) == "true" && !state.getBoolean(inputInitAttr)) {
|
|
10894
10905
|
state.setValue(inputInitAttr, true);
|
|
10895
|
-
initOptionalInputs();
|
|
10896
10906
|
}
|
|
10907
|
+
initOptionalInputs();
|
|
10897
10908
|
if (saved.hasAttribute(numVisibleAttr)) {
|
|
10898
10909
|
const val = parseInt(saved.getAttribute(numVisibleAttr));
|
|
10899
10910
|
if (!isNaN(val)) {
|
|
@@ -10904,6 +10915,7 @@ var pxt;
|
|
|
10904
10915
|
}
|
|
10905
10916
|
else {
|
|
10906
10917
|
state.setValue(numVisibleAttr, addDelta(delta));
|
|
10918
|
+
updateButtons();
|
|
10907
10919
|
}
|
|
10908
10920
|
}
|
|
10909
10921
|
else {
|
|
@@ -10914,14 +10926,29 @@ var pxt;
|
|
|
10914
10926
|
state.setEventsEnabled(true);
|
|
10915
10927
|
}
|
|
10916
10928
|
});
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10929
|
+
initOptionalInputs();
|
|
10930
|
+
if (compileHiddenArguments) {
|
|
10931
|
+
// Make sure all inputs have shadow blocks attached
|
|
10932
|
+
let optIndex = 0;
|
|
10933
|
+
for (let i = 0; i < b.inputList.length; i++) {
|
|
10934
|
+
const input = b.inputList[i];
|
|
10935
|
+
if (pxt.Util.startsWith(input.name, blocks.optionalInputWithFieldPrefix) || optionNames.indexOf(input.name) !== -1) {
|
|
10936
|
+
if (input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
10937
|
+
const param = comp.definitionNameToParam[def.parameters[optIndex].name];
|
|
10938
|
+
attachShadowBlock(input, param);
|
|
10939
|
+
}
|
|
10940
|
+
++optIndex;
|
|
10941
|
+
}
|
|
10923
10942
|
}
|
|
10924
|
-
}
|
|
10943
|
+
}
|
|
10944
|
+
b.render = (opt_bubble) => {
|
|
10945
|
+
if (updatingInputs)
|
|
10946
|
+
return;
|
|
10947
|
+
updatingInputs = true;
|
|
10948
|
+
updateShape(0, undefined, true);
|
|
10949
|
+
updatingInputs = false;
|
|
10950
|
+
Blockly.BlockSvg.prototype.render.call(b, opt_bubble);
|
|
10951
|
+
};
|
|
10925
10952
|
// Set skipRender to true if the block is still initializing. Otherwise
|
|
10926
10953
|
// the inputs will render before their shadow blocks are created and
|
|
10927
10954
|
// leave behind annoying artifacts
|
|
@@ -10951,25 +10978,14 @@ var pxt;
|
|
|
10951
10978
|
setInputVisible(input, visible);
|
|
10952
10979
|
if (visible && input.connection && !input.connection.isConnected() && !b.isInsertionMarker()) {
|
|
10953
10980
|
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();
|
|
10981
|
+
attachShadowBlock(input, param);
|
|
10968
10982
|
}
|
|
10969
10983
|
++optIndex;
|
|
10970
10984
|
}
|
|
10971
10985
|
}
|
|
10972
10986
|
updateButtons();
|
|
10987
|
+
if (variableInlineInputs)
|
|
10988
|
+
b.setInputsInline(visibleOptions < 4);
|
|
10973
10989
|
if (!skipRender)
|
|
10974
10990
|
b.render();
|
|
10975
10991
|
}
|
|
@@ -10978,31 +10994,32 @@ var pxt;
|
|
|
10978
10994
|
.appendField(new Blockly.FieldImage(uri, 24, 24, alt, () => updateShape(delta), false));
|
|
10979
10995
|
}
|
|
10980
10996
|
function updateButtons() {
|
|
10997
|
+
if (updatingInputs)
|
|
10998
|
+
return;
|
|
10981
10999
|
const visibleOptions = state.getNumber(numVisibleAttr);
|
|
10982
11000
|
const showPlus = visibleOptions !== totalOptions;
|
|
10983
11001
|
const showMinus = visibleOptions !== 0;
|
|
10984
|
-
|
|
10985
|
-
const hasPlus = !!b.getInput(buttonAddName);
|
|
10986
|
-
if (!showPlus) {
|
|
11002
|
+
if (b.inputList.some(i => i.name === buttonAddName))
|
|
10987
11003
|
b.removeInput(buttonAddName, true);
|
|
10988
|
-
|
|
10989
|
-
if (!showMinus) {
|
|
11004
|
+
if (b.inputList.some(i => i.name === buttonRemName))
|
|
10990
11005
|
b.removeInput(buttonRemName, true);
|
|
11006
|
+
if (b.inputList.some(i => i.name === buttonAddRemName))
|
|
11007
|
+
b.removeInput(buttonAddRemName, true);
|
|
11008
|
+
if (showPlus && showMinus) {
|
|
11009
|
+
addPlusAndMinusButtons();
|
|
10991
11010
|
}
|
|
10992
|
-
if (
|
|
10993
|
-
|
|
11011
|
+
else if (showPlus) {
|
|
11012
|
+
addPlusButton();
|
|
10994
11013
|
}
|
|
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
|
-
}
|
|
11014
|
+
else if (showMinus) {
|
|
11015
|
+
addMinusButton();
|
|
11004
11016
|
}
|
|
11005
11017
|
}
|
|
11018
|
+
function addPlusAndMinusButtons() {
|
|
11019
|
+
b.appendDummyInput(buttonAddRemName)
|
|
11020
|
+
.appendField(new Blockly.FieldImage(b.REMOVE_IMAGE_DATAURI, 24, 24, lf("Hide optional arguments"), () => updateShape(-1 * buttonDelta), false))
|
|
11021
|
+
.appendField(new Blockly.FieldImage(b.ADD_IMAGE_DATAURI, 24, 24, lf("Reveal optional arguments"), () => updateShape(buttonDelta), false));
|
|
11022
|
+
}
|
|
11006
11023
|
function addPlusButton() {
|
|
11007
11024
|
addButton(buttonAddName, b.ADD_IMAGE_DATAURI, lf("Reveal optional arguments"), buttonDelta);
|
|
11008
11025
|
}
|
|
@@ -11019,12 +11036,23 @@ var pxt;
|
|
|
11019
11036
|
}
|
|
11020
11037
|
function setInputVisible(input, visible) {
|
|
11021
11038
|
// If the block isn't rendered, Blockly will crash
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11039
|
+
input.setVisible(visible);
|
|
11040
|
+
}
|
|
11041
|
+
function attachShadowBlock(input, param) {
|
|
11042
|
+
let shadow = blocks.createShadowValue(info, param);
|
|
11043
|
+
if (shadow.tagName.toLowerCase() === "value") {
|
|
11044
|
+
// Unwrap the block
|
|
11045
|
+
shadow = shadow.firstElementChild;
|
|
11046
|
+
}
|
|
11047
|
+
Blockly.Events.disable();
|
|
11048
|
+
try {
|
|
11049
|
+
const nb = Blockly.Xml.domToBlock(shadow, b.workspace);
|
|
11050
|
+
if (nb) {
|
|
11051
|
+
input.connection.connect(nb.outputConnection);
|
|
11052
|
+
}
|
|
11027
11053
|
}
|
|
11054
|
+
catch (e) { }
|
|
11055
|
+
Blockly.Events.enable();
|
|
11028
11056
|
}
|
|
11029
11057
|
}
|
|
11030
11058
|
blocks.initExpandableBlock = initExpandableBlock;
|
|
@@ -11364,6 +11392,17 @@ var pxtblockly;
|
|
|
11364
11392
|
this.loaded = true;
|
|
11365
11393
|
this.valueText = this.onValueChanged(this.valueText);
|
|
11366
11394
|
}
|
|
11395
|
+
getAnchorDimensions() {
|
|
11396
|
+
const boundingBox = this.getScaledBBox();
|
|
11397
|
+
if (this.sourceBlock_.RTL) {
|
|
11398
|
+
boundingBox.right += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
11399
|
+
}
|
|
11400
|
+
else {
|
|
11401
|
+
boundingBox.left -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
|
|
11402
|
+
}
|
|
11403
|
+
return boundingBox;
|
|
11404
|
+
}
|
|
11405
|
+
;
|
|
11367
11406
|
isInitialized() {
|
|
11368
11407
|
return !!this.fieldGroup_;
|
|
11369
11408
|
}
|
|
@@ -11373,6 +11412,23 @@ var pxtblockly;
|
|
|
11373
11412
|
setBlockData(value) {
|
|
11374
11413
|
pxt.blocks.setBlockDataForField(this.sourceBlock_, this.name, value);
|
|
11375
11414
|
}
|
|
11415
|
+
getSiblingBlock(inputName, useGrandparent = false) {
|
|
11416
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
11417
|
+
if (!block || !block.inputList)
|
|
11418
|
+
return undefined;
|
|
11419
|
+
for (const input of block.inputList) {
|
|
11420
|
+
if (input.name === inputName) {
|
|
11421
|
+
return input.connection.targetBlock();
|
|
11422
|
+
}
|
|
11423
|
+
}
|
|
11424
|
+
return undefined;
|
|
11425
|
+
}
|
|
11426
|
+
getSiblingField(fieldName, useGrandparent = false) {
|
|
11427
|
+
const block = useGrandparent ? this.sourceBlock_.parentBlock_ : this.sourceBlock_;
|
|
11428
|
+
if (!block)
|
|
11429
|
+
return undefined;
|
|
11430
|
+
return block.getField(fieldName);
|
|
11431
|
+
}
|
|
11376
11432
|
}
|
|
11377
11433
|
pxtblockly.FieldBase = FieldBase;
|
|
11378
11434
|
})(pxtblockly || (pxtblockly = {}));
|
|
@@ -15075,6 +15131,311 @@ var pxtblockly;
|
|
|
15075
15131
|
}
|
|
15076
15132
|
pxtblockly.FieldProtractor = FieldProtractor;
|
|
15077
15133
|
})(pxtblockly || (pxtblockly = {}));
|
|
15134
|
+
/// <reference path="../../built/pxtlib.d.ts" />
|
|
15135
|
+
/// <reference path="./field_base.ts" />
|
|
15136
|
+
var pxtblockly;
|
|
15137
|
+
(function (pxtblockly) {
|
|
15138
|
+
var svg = pxt.svgUtil;
|
|
15139
|
+
const MUSIC_ICON_WIDTH = 20;
|
|
15140
|
+
const TOTAL_WIDTH = 160;
|
|
15141
|
+
const TOTAL_HEIGHT = 40;
|
|
15142
|
+
const X_PADDING = 5;
|
|
15143
|
+
const Y_PADDING = 4;
|
|
15144
|
+
const PREVIEW_WIDTH = TOTAL_WIDTH - X_PADDING * 5 - MUSIC_ICON_WIDTH;
|
|
15145
|
+
class FieldSoundEffect extends pxtblockly.FieldBase {
|
|
15146
|
+
onInit() {
|
|
15147
|
+
if (!this.options)
|
|
15148
|
+
this.options = {};
|
|
15149
|
+
if (!this.options.durationInputName)
|
|
15150
|
+
this.options.durationInputName = "duration";
|
|
15151
|
+
if (!this.options.startFrequencyInputName)
|
|
15152
|
+
this.options.startFrequencyInputName = "startFrequency";
|
|
15153
|
+
if (!this.options.endFrequencyInputName)
|
|
15154
|
+
this.options.endFrequencyInputName = "endFrequency";
|
|
15155
|
+
if (!this.options.startVolumeInputName)
|
|
15156
|
+
this.options.startVolumeInputName = "startVolume";
|
|
15157
|
+
if (!this.options.endVolumeInputName)
|
|
15158
|
+
this.options.endVolumeInputName = "endVolume";
|
|
15159
|
+
if (!this.options.waveFieldName)
|
|
15160
|
+
this.options.waveFieldName = "waveShape";
|
|
15161
|
+
if (!this.options.interpolationFieldName)
|
|
15162
|
+
this.options.interpolationFieldName = "interpolation";
|
|
15163
|
+
if (!this.options.effectFieldName)
|
|
15164
|
+
this.options.effectFieldName = "effect";
|
|
15165
|
+
this.redrawPreview();
|
|
15166
|
+
}
|
|
15167
|
+
onDispose() {
|
|
15168
|
+
}
|
|
15169
|
+
onValueChanged(newValue) {
|
|
15170
|
+
return newValue;
|
|
15171
|
+
}
|
|
15172
|
+
redrawPreview() {
|
|
15173
|
+
if (!this.fieldGroup_)
|
|
15174
|
+
return;
|
|
15175
|
+
pxsim.U.clear(this.fieldGroup_);
|
|
15176
|
+
const bg = new svg.Rect()
|
|
15177
|
+
.at(X_PADDING, Y_PADDING)
|
|
15178
|
+
.size(TOTAL_WIDTH, TOTAL_HEIGHT)
|
|
15179
|
+
.setClass("blocklySpriteField")
|
|
15180
|
+
.stroke("#fff", 1)
|
|
15181
|
+
.corner(TOTAL_HEIGHT / 2);
|
|
15182
|
+
const clipPathId = "preview-clip-" + pxt.U.guidGen();
|
|
15183
|
+
const clip = new svg.ClipPath()
|
|
15184
|
+
.id(clipPathId)
|
|
15185
|
+
.clipPathUnits(false);
|
|
15186
|
+
const clipRect = new svg.Rect()
|
|
15187
|
+
.size(PREVIEW_WIDTH, TOTAL_HEIGHT)
|
|
15188
|
+
.fill("#FFF")
|
|
15189
|
+
.at(0, 0);
|
|
15190
|
+
clip.appendChild(clipRect);
|
|
15191
|
+
const path = new svg.Path()
|
|
15192
|
+
.stroke("grey", 2)
|
|
15193
|
+
.fill("none")
|
|
15194
|
+
.setD(pxt.assets.renderSoundPath(this.readCurrentSound(), TOTAL_WIDTH - X_PADDING * 4 - MUSIC_ICON_WIDTH, TOTAL_HEIGHT - Y_PADDING * 2))
|
|
15195
|
+
.clipPath("url('#" + clipPathId + "')");
|
|
15196
|
+
const g = new svg.Group()
|
|
15197
|
+
.translate(MUSIC_ICON_WIDTH + X_PADDING * 3, Y_PADDING + 3);
|
|
15198
|
+
g.appendChild(clip);
|
|
15199
|
+
g.appendChild(path);
|
|
15200
|
+
const musicIcon = new svg.Text("\uf001")
|
|
15201
|
+
.appendClass("melody-editor-field-icon")
|
|
15202
|
+
.setAttribute("alignment-baseline", "middle")
|
|
15203
|
+
.anchor("middle")
|
|
15204
|
+
.at(X_PADDING * 2 + MUSIC_ICON_WIDTH / 2, TOTAL_HEIGHT / 2 + 4);
|
|
15205
|
+
this.fieldGroup_.appendChild(bg.el);
|
|
15206
|
+
this.fieldGroup_.appendChild(musicIcon.el);
|
|
15207
|
+
this.fieldGroup_.appendChild(g.el);
|
|
15208
|
+
}
|
|
15209
|
+
showEditor_() {
|
|
15210
|
+
const initialSound = this.readCurrentSound();
|
|
15211
|
+
Blockly.Events.disable();
|
|
15212
|
+
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, () => {
|
|
15213
|
+
fv.hide();
|
|
15214
|
+
widgetDiv.classList.remove("sound-effect-editor-widget");
|
|
15215
|
+
widgetDiv.style.transform = "";
|
|
15216
|
+
widgetDiv.style.position = "";
|
|
15217
|
+
widgetDiv.style.left = "";
|
|
15218
|
+
widgetDiv.style.top = "";
|
|
15219
|
+
widgetDiv.style.width = "";
|
|
15220
|
+
widgetDiv.style.height = "";
|
|
15221
|
+
widgetDiv.style.opacity = "";
|
|
15222
|
+
widgetDiv.style.transition = "";
|
|
15223
|
+
Blockly.Events.enable();
|
|
15224
|
+
Blockly.Events.setGroup(true);
|
|
15225
|
+
this.fireNumberInputUpdate(this.options.durationInputName, initialSound.duration);
|
|
15226
|
+
this.fireNumberInputUpdate(this.options.startFrequencyInputName, initialSound.startFrequency);
|
|
15227
|
+
this.fireNumberInputUpdate(this.options.endFrequencyInputName, initialSound.endFrequency);
|
|
15228
|
+
this.fireNumberInputUpdate(this.options.startVolumeInputName, initialSound.startVolume);
|
|
15229
|
+
this.fireNumberInputUpdate(this.options.endVolumeInputName, initialSound.endVolume);
|
|
15230
|
+
this.fireFieldDropdownUpdate(this.options.waveFieldName, waveformMapping[initialSound.wave]);
|
|
15231
|
+
this.fireFieldDropdownUpdate(this.options.interpolationFieldName, interpolationMapping[initialSound.interpolation]);
|
|
15232
|
+
this.fireFieldDropdownUpdate(this.options.effectFieldName, effectMapping[initialSound.effect]);
|
|
15233
|
+
Blockly.Events.setGroup(false);
|
|
15234
|
+
if (this.mostRecentValue)
|
|
15235
|
+
this.setBlockData(JSON.stringify(this.mostRecentValue));
|
|
15236
|
+
});
|
|
15237
|
+
const widgetDiv = Blockly.WidgetDiv.DIV;
|
|
15238
|
+
const opts = {
|
|
15239
|
+
onClose: () => {
|
|
15240
|
+
fv.hide();
|
|
15241
|
+
Blockly.WidgetDiv.hideIfOwner(this);
|
|
15242
|
+
},
|
|
15243
|
+
onSoundChange: (newSound) => {
|
|
15244
|
+
this.mostRecentValue = newSound;
|
|
15245
|
+
this.updateSiblingBlocks(newSound);
|
|
15246
|
+
this.redrawPreview();
|
|
15247
|
+
},
|
|
15248
|
+
initialSound: initialSound
|
|
15249
|
+
};
|
|
15250
|
+
const fv = pxt.react.getFieldEditorView("soundeffect-editor", initialSound, opts, widgetDiv);
|
|
15251
|
+
const block = this.sourceBlock_;
|
|
15252
|
+
const bounds = block.getBoundingRectangle();
|
|
15253
|
+
const coord = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.right, bounds.top));
|
|
15254
|
+
const animationDistance = 20;
|
|
15255
|
+
const left = coord.x + 20;
|
|
15256
|
+
const top = coord.y - animationDistance;
|
|
15257
|
+
widgetDiv.style.opacity = "0";
|
|
15258
|
+
widgetDiv.classList.add("sound-effect-editor-widget");
|
|
15259
|
+
widgetDiv.style.position = "absolute";
|
|
15260
|
+
widgetDiv.style.left = left + "px";
|
|
15261
|
+
widgetDiv.style.top = top + "px";
|
|
15262
|
+
widgetDiv.style.width = "30rem";
|
|
15263
|
+
widgetDiv.style.height = "40rem";
|
|
15264
|
+
widgetDiv.style.display = "block";
|
|
15265
|
+
widgetDiv.style.transition = "transform 0.25s ease 0s, opacity 0.25s ease 0s";
|
|
15266
|
+
fv.onHide(() => {
|
|
15267
|
+
// do nothing
|
|
15268
|
+
});
|
|
15269
|
+
fv.show();
|
|
15270
|
+
const divBounds = widgetDiv.getBoundingClientRect();
|
|
15271
|
+
const injectDivBounds = block.workspace.getInjectionDiv().getBoundingClientRect();
|
|
15272
|
+
if (divBounds.height > injectDivBounds.height) {
|
|
15273
|
+
widgetDiv.style.height = "";
|
|
15274
|
+
widgetDiv.style.top = `calc(1rem - ${animationDistance}px)`;
|
|
15275
|
+
widgetDiv.style.bottom = "1rem";
|
|
15276
|
+
}
|
|
15277
|
+
else {
|
|
15278
|
+
if (divBounds.bottom > injectDivBounds.bottom || divBounds.top < injectDivBounds.top) {
|
|
15279
|
+
// This editor is pretty tall, so just center vertically on the inject div
|
|
15280
|
+
widgetDiv.style.top = (injectDivBounds.top + (injectDivBounds.height / 2) - (divBounds.height / 2)) - animationDistance + "px";
|
|
15281
|
+
}
|
|
15282
|
+
}
|
|
15283
|
+
if (divBounds.width > injectDivBounds.width) {
|
|
15284
|
+
widgetDiv.style.width = "";
|
|
15285
|
+
widgetDiv.style.left = "1rem";
|
|
15286
|
+
widgetDiv.style.right = "1rem";
|
|
15287
|
+
}
|
|
15288
|
+
else {
|
|
15289
|
+
// Check to see if we are bleeding off the right side of the canvas
|
|
15290
|
+
if (divBounds.left + divBounds.width >= injectDivBounds.right) {
|
|
15291
|
+
// If so, try and place to the left of the block instead of the right
|
|
15292
|
+
const blockLeft = pxtblockly.workspaceToScreenCoordinates(block.workspace, new Blockly.utils.Coordinate(bounds.left, bounds.top));
|
|
15293
|
+
const toolboxWidth = block.workspace.getToolbox().getWidth();
|
|
15294
|
+
const workspaceLeft = injectDivBounds.left + toolboxWidth;
|
|
15295
|
+
if (blockLeft.x - divBounds.width - 20 > workspaceLeft) {
|
|
15296
|
+
widgetDiv.style.left = (blockLeft.x - divBounds.width - 20) + "px";
|
|
15297
|
+
}
|
|
15298
|
+
else {
|
|
15299
|
+
// As a last resort, just center on the inject div
|
|
15300
|
+
widgetDiv.style.left = (workspaceLeft + ((injectDivBounds.width - toolboxWidth) / 2) - divBounds.width / 2) + "px";
|
|
15301
|
+
}
|
|
15302
|
+
}
|
|
15303
|
+
}
|
|
15304
|
+
requestAnimationFrame(() => {
|
|
15305
|
+
widgetDiv.style.opacity = "1";
|
|
15306
|
+
widgetDiv.style.transform = `translateY(${animationDistance}px)`;
|
|
15307
|
+
});
|
|
15308
|
+
}
|
|
15309
|
+
render_() {
|
|
15310
|
+
super.render_();
|
|
15311
|
+
this.size_.height = TOTAL_HEIGHT + Y_PADDING * 2;
|
|
15312
|
+
this.size_.width = TOTAL_WIDTH;
|
|
15313
|
+
}
|
|
15314
|
+
updateSiblingBlocks(sound) {
|
|
15315
|
+
this.setNumberInputValue(this.options.durationInputName, sound.duration);
|
|
15316
|
+
this.setNumberInputValue(this.options.startFrequencyInputName, sound.startFrequency);
|
|
15317
|
+
this.setNumberInputValue(this.options.endFrequencyInputName, sound.endFrequency);
|
|
15318
|
+
this.setNumberInputValue(this.options.startVolumeInputName, sound.startVolume);
|
|
15319
|
+
this.setNumberInputValue(this.options.endVolumeInputName, sound.endVolume);
|
|
15320
|
+
this.setFieldDropdownValue(this.options.waveFieldName, waveformMapping[sound.wave]);
|
|
15321
|
+
this.setFieldDropdownValue(this.options.interpolationFieldName, interpolationMapping[sound.interpolation]);
|
|
15322
|
+
this.setFieldDropdownValue(this.options.effectFieldName, effectMapping[sound.effect]);
|
|
15323
|
+
}
|
|
15324
|
+
setNumberInputValue(name, value) {
|
|
15325
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15326
|
+
if (!block)
|
|
15327
|
+
return;
|
|
15328
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15329
|
+
block.setFieldValue(Math.round(value), "NUM");
|
|
15330
|
+
}
|
|
15331
|
+
else if (block.type === "math_number_minmax") {
|
|
15332
|
+
block.setFieldValue(Math.round(value), "SLIDER");
|
|
15333
|
+
}
|
|
15334
|
+
}
|
|
15335
|
+
getNumberInputValue(name, defaultValue) {
|
|
15336
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15337
|
+
if (!block)
|
|
15338
|
+
return defaultValue;
|
|
15339
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15340
|
+
return parseInt(block.getFieldValue("NUM") + "");
|
|
15341
|
+
}
|
|
15342
|
+
else if (block.type === "math_number_minmax") {
|
|
15343
|
+
return parseInt(block.getFieldValue("SLIDER") + "");
|
|
15344
|
+
}
|
|
15345
|
+
return defaultValue;
|
|
15346
|
+
}
|
|
15347
|
+
fireNumberInputUpdate(name, oldValue) {
|
|
15348
|
+
const block = this.getSiblingBlock(name) || this.getSiblingBlock(name, true);
|
|
15349
|
+
if (!block)
|
|
15350
|
+
return;
|
|
15351
|
+
let fieldName;
|
|
15352
|
+
if (block.type === "math_number" || block.type === "math_integer" || block.type === "math_whole_number") {
|
|
15353
|
+
fieldName = "NUM";
|
|
15354
|
+
}
|
|
15355
|
+
else if (block.type === "math_number_minmax") {
|
|
15356
|
+
fieldName = "SLIDER";
|
|
15357
|
+
}
|
|
15358
|
+
if (!fieldName)
|
|
15359
|
+
return;
|
|
15360
|
+
Blockly.Events.fire(new Blockly.Events.Change(block, "field", fieldName, oldValue, this.getNumberInputValue(name, oldValue)));
|
|
15361
|
+
}
|
|
15362
|
+
setFieldDropdownValue(name, value) {
|
|
15363
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15364
|
+
if (!field)
|
|
15365
|
+
return;
|
|
15366
|
+
field.setValue(value);
|
|
15367
|
+
}
|
|
15368
|
+
getFieldDropdownValue(name) {
|
|
15369
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15370
|
+
if (!field)
|
|
15371
|
+
return undefined;
|
|
15372
|
+
return field.getValue();
|
|
15373
|
+
}
|
|
15374
|
+
fireFieldDropdownUpdate(name, oldValue) {
|
|
15375
|
+
const field = this.getSiblingField(name) || this.getSiblingField(name, true);
|
|
15376
|
+
if (!field)
|
|
15377
|
+
return;
|
|
15378
|
+
Blockly.Events.fire(new Blockly.Events.Change(field.sourceBlock_, "field", field.name, oldValue, this.getFieldDropdownValue(name)));
|
|
15379
|
+
}
|
|
15380
|
+
readCurrentSound() {
|
|
15381
|
+
const savedSound = this.readBlockDataSound();
|
|
15382
|
+
return {
|
|
15383
|
+
duration: this.getNumberInputValue(this.options.durationInputName, savedSound.duration),
|
|
15384
|
+
startFrequency: this.getNumberInputValue(this.options.startFrequencyInputName, savedSound.startFrequency),
|
|
15385
|
+
endFrequency: this.getNumberInputValue(this.options.endFrequencyInputName, savedSound.endFrequency),
|
|
15386
|
+
startVolume: this.getNumberInputValue(this.options.startVolumeInputName, savedSound.startVolume),
|
|
15387
|
+
endVolume: this.getNumberInputValue(this.options.endVolumeInputName, savedSound.endVolume),
|
|
15388
|
+
wave: reverseLookup(waveformMapping, this.getFieldDropdownValue(this.options.waveFieldName)) || savedSound.wave,
|
|
15389
|
+
interpolation: reverseLookup(interpolationMapping, this.getFieldDropdownValue(this.options.interpolationFieldName)) || savedSound.interpolation,
|
|
15390
|
+
effect: reverseLookup(effectMapping, this.getFieldDropdownValue(this.options.effectFieldName)) || savedSound.effect,
|
|
15391
|
+
};
|
|
15392
|
+
}
|
|
15393
|
+
// This stores the values of the fields in case a block (e.g. a variable) is placed in one
|
|
15394
|
+
// of the inputs.
|
|
15395
|
+
readBlockDataSound() {
|
|
15396
|
+
const data = this.getBlockData();
|
|
15397
|
+
let sound;
|
|
15398
|
+
try {
|
|
15399
|
+
sound = JSON.parse(data);
|
|
15400
|
+
}
|
|
15401
|
+
catch (e) {
|
|
15402
|
+
sound = {
|
|
15403
|
+
duration: 1000,
|
|
15404
|
+
startFrequency: 100,
|
|
15405
|
+
endFrequency: 1800,
|
|
15406
|
+
startVolume: 1023,
|
|
15407
|
+
endVolume: 0,
|
|
15408
|
+
wave: "sine",
|
|
15409
|
+
interpolation: "linear",
|
|
15410
|
+
effect: "none"
|
|
15411
|
+
};
|
|
15412
|
+
}
|
|
15413
|
+
return sound;
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
pxtblockly.FieldSoundEffect = FieldSoundEffect;
|
|
15417
|
+
const waveformMapping = {
|
|
15418
|
+
"sine": "WaveShape.Sine",
|
|
15419
|
+
"square": "WaveShape.Square",
|
|
15420
|
+
"sawtooth": "WaveShape.Sawtooth",
|
|
15421
|
+
"triangle": "WaveShape.Triangle",
|
|
15422
|
+
"noise": "WaveShape.Noise",
|
|
15423
|
+
};
|
|
15424
|
+
const effectMapping = {
|
|
15425
|
+
"none": "SoundExpressionEffect.None",
|
|
15426
|
+
"vibrato": "SoundExpressionEffect.Vibrato",
|
|
15427
|
+
"tremolo": "SoundExpressionEffect.Tremolo",
|
|
15428
|
+
"warble": "SoundExpressionEffect.Warble",
|
|
15429
|
+
};
|
|
15430
|
+
const interpolationMapping = {
|
|
15431
|
+
"linear": "InterpolationCurve.Linear",
|
|
15432
|
+
"curve": "InterpolationCurve.Curve",
|
|
15433
|
+
"logarithmic": "InterpolationCurve.Logarithmic",
|
|
15434
|
+
};
|
|
15435
|
+
function reverseLookup(map, value) {
|
|
15436
|
+
return Object.keys(map).find(k => map[k] === value);
|
|
15437
|
+
}
|
|
15438
|
+
})(pxtblockly || (pxtblockly = {}));
|
|
15078
15439
|
/// <reference path="../../localtypings/blockly.d.ts"/>
|
|
15079
15440
|
/// <reference path="../../built/pxtsim.d.ts"/>
|
|
15080
15441
|
var pxtblockly;
|
|
@@ -16540,4 +16901,21 @@ var pxtblockly;
|
|
|
16540
16901
|
}
|
|
16541
16902
|
}
|
|
16542
16903
|
pxtblockly.getTemporaryAssets = getTemporaryAssets;
|
|
16904
|
+
function workspaceToScreenCoordinates(ws, wsCoordinates) {
|
|
16905
|
+
// The position in pixels relative to the origin of the
|
|
16906
|
+
// main workspace.
|
|
16907
|
+
const scaledWS = wsCoordinates.scale(ws.scale);
|
|
16908
|
+
// The offset in pixels between the main workspace's origin and the upper
|
|
16909
|
+
// left corner of the injection div.
|
|
16910
|
+
const mainOffsetPixels = ws.getOriginOffsetInPixels();
|
|
16911
|
+
// The client coordinates offset by the injection div's upper left corner.
|
|
16912
|
+
const clientOffsetPixels = Blockly.utils.Coordinate.sum(scaledWS, mainOffsetPixels);
|
|
16913
|
+
const injectionDiv = ws.getInjectionDiv();
|
|
16914
|
+
// Bounding rect coordinates are in client coordinates, meaning that they
|
|
16915
|
+
// are in pixels relative to the upper left corner of the visible browser
|
|
16916
|
+
// window. These coordinates change when you scroll the browser window.
|
|
16917
|
+
const boundingRect = injectionDiv.getBoundingClientRect();
|
|
16918
|
+
return new Blockly.utils.Coordinate(clientOffsetPixels.x + boundingRect.left, clientOffsetPixels.y + boundingRect.top);
|
|
16919
|
+
}
|
|
16920
|
+
pxtblockly.workspaceToScreenCoordinates = workspaceToScreenCoordinates;
|
|
16543
16921
|
})(pxtblockly || (pxtblockly = {}));
|