pxt-core 7.4.7 → 7.4.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 +90 -53
- package/built/pxtblockly.js +1361 -1450
- package/built/pxtblocks.d.ts +4 -4
- package/built/pxtblocks.js +35 -80
- package/built/pxtlib.d.ts +11 -2
- package/built/pxtlib.js +90 -53
- package/built/target.js +1 -1
- package/built/web/blockly.css +1 -1
- 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 +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common.css +10 -5
- package/built/web/rtlblockly.css +1 -1
- package/built/web/rtlsemantic.css +2 -2
- package/built/web/semantic.css +2 -2
- package/built/web/skillmap/css/main.96b1b3f1.chunk.css +1 -0
- package/built/web/skillmap/js/2.7dd06a3a.chunk.js +2 -0
- package/built/web/skillmap/js/main.55881627.chunk.js +1 -0
- package/localtypings/blockly.d.ts +12268 -7535
- package/localtypings/pxtarget.d.ts +1 -0
- package/package.json +2 -2
- package/theme/blockly-core.less +22 -13
- package/theme/melodyeditor.less +2 -2
- package/theme/print.less +1 -1
- package/theme/pxt.less +0 -1
- package/theme/toolbox.less +1 -0
- package/webapp/public/blockly/blockly_compressed.js +1271 -1288
- package/webapp/public/blockly/blocks_compressed.js +47 -65
- package/webapp/public/blockly/msg/js/en.js +8 -17
- package/webapp/public/blockly/msg/json/en.json +6 -15
- package/webapp/public/skillmap.html +2 -2
- package/built/web/skillmap/css/main.70954d9b.chunk.css +0 -1
- package/built/web/skillmap/js/2.c64f6be2.chunk.js +0 -2
- package/built/web/skillmap/js/main.86ef11e3.chunk.js +0 -1
package/built/pxtblocks.d.ts
CHANGED
|
@@ -471,7 +471,7 @@ declare namespace pxtblockly {
|
|
|
471
471
|
protected animateRef: any;
|
|
472
472
|
protected asset: pxt.Animation;
|
|
473
473
|
protected initInterval: number;
|
|
474
|
-
|
|
474
|
+
initView(): void;
|
|
475
475
|
showEditor_(): void;
|
|
476
476
|
render_(): void;
|
|
477
477
|
protected getAssetType(): pxt.AssetType;
|
|
@@ -508,7 +508,7 @@ declare namespace pxtblockly {
|
|
|
508
508
|
CURSOR: string;
|
|
509
509
|
private type_;
|
|
510
510
|
constructor(state: string, params: Blockly.FieldCustomOptions, opt_validator?: Function);
|
|
511
|
-
|
|
511
|
+
initView(): void;
|
|
512
512
|
updateSize_(): void;
|
|
513
513
|
/**
|
|
514
514
|
* Return 'TRUE' if the toggle is ON, 'FALSE' otherwise.
|
|
@@ -1243,7 +1243,7 @@ declare namespace pxtblockly {
|
|
|
1243
1243
|
protected blocksInfo: pxtc.BlocksInfo;
|
|
1244
1244
|
protected transparent: TilesetDropdownOption;
|
|
1245
1245
|
constructor(text: string, options: FieldImageDropdownOptions, validator?: Function);
|
|
1246
|
-
|
|
1246
|
+
initView(): void;
|
|
1247
1247
|
getValue(): any;
|
|
1248
1248
|
getText(): string;
|
|
1249
1249
|
render_(): void;
|
|
@@ -1265,7 +1265,7 @@ declare namespace pxtblockly {
|
|
|
1265
1265
|
CURSOR: string;
|
|
1266
1266
|
private type_;
|
|
1267
1267
|
constructor(state: string, params: Blockly.FieldCustomOptions, opt_validator?: Function);
|
|
1268
|
-
|
|
1268
|
+
initView(): void;
|
|
1269
1269
|
getDisplayText_(): string;
|
|
1270
1270
|
getTrueText(): string;
|
|
1271
1271
|
getFalseText(): string;
|
package/built/pxtblocks.js
CHANGED
|
@@ -1497,7 +1497,7 @@ var pxt;
|
|
|
1497
1497
|
function compileBlockAsync(b, blockInfo) {
|
|
1498
1498
|
const w = b.workspace;
|
|
1499
1499
|
const e = mkEnv(w, blockInfo);
|
|
1500
|
-
infer(w && w.getAllBlocks(), e, w);
|
|
1500
|
+
infer(w && w.getAllBlocks(false), e, w);
|
|
1501
1501
|
const compiled = compileStatementBlock(e, b);
|
|
1502
1502
|
removeAllPlaceholders();
|
|
1503
1503
|
return tdASTtoTS(e, compiled);
|
|
@@ -1518,7 +1518,7 @@ var pxt;
|
|
|
1518
1518
|
function compileWorkspace(e, w, blockInfo) {
|
|
1519
1519
|
try {
|
|
1520
1520
|
// all compiled top level blocks are events
|
|
1521
|
-
let allBlocks = w.getAllBlocks();
|
|
1521
|
+
let allBlocks = w.getAllBlocks(false);
|
|
1522
1522
|
if (pxt.react.getTilemapProject) {
|
|
1523
1523
|
pxt.react.getTilemapProject().removeInactiveBlockAssets(allBlocks.map(b => b.id));
|
|
1524
1524
|
}
|
|
@@ -1788,13 +1788,11 @@ var pxt;
|
|
|
1788
1788
|
});
|
|
1789
1789
|
}
|
|
1790
1790
|
function maybeAddComment(b, comments) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
comments.push(b.comment.getText());
|
|
1797
|
-
}
|
|
1791
|
+
var _a;
|
|
1792
|
+
// Check if getCommentText exists, block may be placeholder
|
|
1793
|
+
const text = (_a = b.getCommentText) === null || _a === void 0 ? void 0 : _a.call(b);
|
|
1794
|
+
if (text) {
|
|
1795
|
+
comments.push(text);
|
|
1798
1796
|
}
|
|
1799
1797
|
}
|
|
1800
1798
|
function addCommentNodes(comments, r) {
|
|
@@ -2457,9 +2455,9 @@ var pxt;
|
|
|
2457
2455
|
}
|
|
2458
2456
|
});
|
|
2459
2457
|
// we'll ignore disabled blocks in the final output
|
|
2460
|
-
const oldBlocks = oldWs.getAllBlocks().filter(b => b.isEnabled());
|
|
2458
|
+
const oldBlocks = oldWs.getAllBlocks(false).filter(b => b.isEnabled());
|
|
2461
2459
|
const oldTopBlocks = oldWs.getTopBlocks(false).filter(b => b.isEnabled());
|
|
2462
|
-
const newBlocks = newWs.getAllBlocks().filter(b => b.isEnabled());
|
|
2460
|
+
const newBlocks = newWs.getAllBlocks(false).filter(b => b.isEnabled());
|
|
2463
2461
|
log(`blocks`, newBlocks.map(b => b.toDevString()));
|
|
2464
2462
|
log(newBlocks);
|
|
2465
2463
|
if (oldBlocks.length == 0 && newBlocks.length == 0) {
|
|
@@ -2481,11 +2479,11 @@ var pxt;
|
|
|
2481
2479
|
const newXml = pxt.blocks.saveWorkspaceXml(newWs, true);
|
|
2482
2480
|
pxt.blocks.domToWorkspaceNoEvents(Blockly.Xml.textToDom(newXml), ws);
|
|
2483
2481
|
// delete disabled blocks from final workspace
|
|
2484
|
-
ws.getAllBlocks().filter(b => !b.isEnabled()).forEach(b => {
|
|
2482
|
+
ws.getAllBlocks(false).filter(b => !b.isEnabled()).forEach(b => {
|
|
2485
2483
|
log('disabled ', b.toDevString());
|
|
2486
2484
|
b.dispose(false);
|
|
2487
2485
|
});
|
|
2488
|
-
const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(), b => b.id);
|
|
2486
|
+
const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(false), b => b.id);
|
|
2489
2487
|
log(`todo blocks`, todoBlocks);
|
|
2490
2488
|
logTodo('start');
|
|
2491
2489
|
// 1. deleted top blocks
|
|
@@ -2495,7 +2493,7 @@ var pxt;
|
|
|
2495
2493
|
done(b);
|
|
2496
2494
|
const b2 = cloneIntoDiff(b);
|
|
2497
2495
|
done(b2);
|
|
2498
|
-
b2.
|
|
2496
|
+
b2.setEnabled(false);
|
|
2499
2497
|
});
|
|
2500
2498
|
logTodo('deleted top');
|
|
2501
2499
|
}
|
|
@@ -2560,7 +2558,7 @@ var pxt;
|
|
|
2560
2558
|
});
|
|
2561
2559
|
logTodo('unmodified');
|
|
2562
2560
|
// if nothing is left in the workspace, we "missed" change
|
|
2563
|
-
if (!ws.getAllBlocks().length) {
|
|
2561
|
+
if (!ws.getAllBlocks(false).length) {
|
|
2564
2562
|
pxt.tickEvent("blocks.diff", { missed: 1 });
|
|
2565
2563
|
return {
|
|
2566
2564
|
ws,
|
|
@@ -2593,7 +2591,7 @@ var pxt;
|
|
|
2593
2591
|
function stitch(b) {
|
|
2594
2592
|
log(`stitching ${b.toDevString()}->${dids[b.id]}`);
|
|
2595
2593
|
const wb = ws.getBlockById(dids[b.id]);
|
|
2596
|
-
wb.
|
|
2594
|
+
wb.setEnabled(false);
|
|
2597
2595
|
markUsed(wb);
|
|
2598
2596
|
done(wb);
|
|
2599
2597
|
// connect previous connection to delted or existing block
|
|
@@ -2855,10 +2853,10 @@ var pxt;
|
|
|
2855
2853
|
function applyMetaComments(workspace) {
|
|
2856
2854
|
// process meta comments
|
|
2857
2855
|
// @highlight -> highlight block
|
|
2858
|
-
workspace.getAllBlocks()
|
|
2859
|
-
.filter(b => !!b.
|
|
2856
|
+
workspace.getAllBlocks(false)
|
|
2857
|
+
.filter(b => !!b.getCommentText())
|
|
2860
2858
|
.forEach(b => {
|
|
2861
|
-
const c = b.
|
|
2859
|
+
const c = b.getCommentText();
|
|
2862
2860
|
if (/@highlight/.test(c)) {
|
|
2863
2861
|
const cc = c.replace(/@highlight/g, '').trim();
|
|
2864
2862
|
b.setCommentText(cc || null);
|
|
@@ -4343,7 +4341,7 @@ var pxt;
|
|
|
4343
4341
|
}
|
|
4344
4342
|
else if (pr.type == "number" && pr.shadowBlockId && pr.shadowBlockId == "value") {
|
|
4345
4343
|
inputName = undefined;
|
|
4346
|
-
fields.push(namedField(new Blockly.
|
|
4344
|
+
fields.push(namedField(new Blockly.FieldNumber("0"), defName));
|
|
4347
4345
|
}
|
|
4348
4346
|
else if (pr.type == "string" && pr.shadowOptions && pr.shadowOptions.toString) {
|
|
4349
4347
|
inputCheck = null;
|
|
@@ -5445,11 +5443,6 @@ var pxt;
|
|
|
5445
5443
|
let blockText = '<xml>' +
|
|
5446
5444
|
'<block type="variables_change" gap="' + gap + '">' +
|
|
5447
5445
|
Blockly.Variables.generateVariableFieldXmlString(mostRecentVariable) +
|
|
5448
|
-
'<value name="DELTA">' +
|
|
5449
|
-
'<shadow type="math_number">' +
|
|
5450
|
-
'<field name="NUM">1</field>' +
|
|
5451
|
-
'</shadow>' +
|
|
5452
|
-
'</value>' +
|
|
5453
5446
|
'</block>' +
|
|
5454
5447
|
'</xml>';
|
|
5455
5448
|
let block = Blockly.Xml.textToDom(blockText).firstChild;
|
|
@@ -7622,19 +7615,8 @@ var pxtblockly;
|
|
|
7622
7615
|
this.valueText = text;
|
|
7623
7616
|
}
|
|
7624
7617
|
init() {
|
|
7625
|
-
|
|
7626
|
-
return;
|
|
7627
|
-
// Build the DOM.
|
|
7628
|
-
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
|
7629
|
-
if (!this.visible_) {
|
|
7630
|
-
this.fieldGroup_.style.display = 'none';
|
|
7631
|
-
}
|
|
7618
|
+
super.init();
|
|
7632
7619
|
this.onInit();
|
|
7633
|
-
this.updateEditable();
|
|
7634
|
-
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
|
7635
|
-
// Force a render.
|
|
7636
|
-
this.render_();
|
|
7637
|
-
this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(this.getClickTarget_(), "mousedown", this, this.onMouseDown_);
|
|
7638
7620
|
}
|
|
7639
7621
|
dispose() {
|
|
7640
7622
|
this.onDispose();
|
|
@@ -7787,9 +7769,11 @@ var pxtblockly;
|
|
|
7787
7769
|
}
|
|
7788
7770
|
getDisplayText_() {
|
|
7789
7771
|
// This is only used when isGreyBlock is true
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7772
|
+
if (this.isGreyBlock) {
|
|
7773
|
+
const text = pxt.Util.htmlUnescape(this.valueText);
|
|
7774
|
+
return text.substr(0, text.indexOf("(")) + "(...)";
|
|
7775
|
+
}
|
|
7776
|
+
return "";
|
|
7793
7777
|
}
|
|
7794
7778
|
updateEditable() {
|
|
7795
7779
|
if (this.isGreyBlock && this.fieldGroup_) {
|
|
@@ -8046,12 +8030,7 @@ var pxtblockly;
|
|
|
8046
8030
|
}
|
|
8047
8031
|
};
|
|
8048
8032
|
}
|
|
8049
|
-
|
|
8050
|
-
if (this.fieldGroup_) {
|
|
8051
|
-
// Field has already been initialized once.
|
|
8052
|
-
return;
|
|
8053
|
-
}
|
|
8054
|
-
super.init();
|
|
8033
|
+
initView() {
|
|
8055
8034
|
// Register mouseover events for animating preview
|
|
8056
8035
|
this.sourceBlock_.getSvgRoot().addEventListener("mouseenter", this.onMouseEnter);
|
|
8057
8036
|
this.sourceBlock_.getSvgRoot().addEventListener("mouseleave", this.onMouseLeave);
|
|
@@ -8257,16 +8236,10 @@ var pxtblockly;
|
|
|
8257
8236
|
this.addArgType('toggle');
|
|
8258
8237
|
this.type_ = params.type;
|
|
8259
8238
|
}
|
|
8260
|
-
|
|
8261
|
-
if (this.fieldGroup_) {
|
|
8262
|
-
// Field has already been initialized once.
|
|
8239
|
+
initView() {
|
|
8240
|
+
if (!this.fieldGroup_) {
|
|
8263
8241
|
return;
|
|
8264
8242
|
}
|
|
8265
|
-
// Build the DOM.
|
|
8266
|
-
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
|
8267
|
-
if (!this.visible_) {
|
|
8268
|
-
this.fieldGroup_.style.display = 'none';
|
|
8269
|
-
}
|
|
8270
8243
|
// Add an attribute to cassify the type of field.
|
|
8271
8244
|
if (this.getArgTypes() !== null) {
|
|
8272
8245
|
if (this.sourceBlock_.isShadow()) {
|
|
@@ -8295,15 +8268,10 @@ var pxtblockly;
|
|
|
8295
8268
|
'dy': '0.6ex',
|
|
8296
8269
|
'y': size.height / 2
|
|
8297
8270
|
}, this.fieldGroup_);
|
|
8298
|
-
this.updateEditable();
|
|
8299
|
-
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
|
8300
8271
|
this.switchToggle(this.state_);
|
|
8301
8272
|
this.setValue(this.getValue());
|
|
8302
8273
|
// Force a render.
|
|
8303
|
-
this.
|
|
8304
|
-
this.size_.width = 0;
|
|
8305
|
-
this.mouseDownWrapper_ =
|
|
8306
|
-
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
|
8274
|
+
this.markDirty();
|
|
8307
8275
|
}
|
|
8308
8276
|
updateSize_() {
|
|
8309
8277
|
this.size_.width = 30;
|
|
@@ -9773,7 +9741,7 @@ var pxtblockly;
|
|
|
9773
9741
|
}
|
|
9774
9742
|
render_() {
|
|
9775
9743
|
if (!this.visible_) {
|
|
9776
|
-
this.
|
|
9744
|
+
this.markDirty();
|
|
9777
9745
|
return;
|
|
9778
9746
|
}
|
|
9779
9747
|
if (!this.elt) {
|
|
@@ -11277,7 +11245,7 @@ var pxtblockly;
|
|
|
11277
11245
|
dropdownCreate() {
|
|
11278
11246
|
let functionList = [];
|
|
11279
11247
|
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
|
11280
|
-
let blocks = this.sourceBlock_.workspace.getAllBlocks();
|
|
11248
|
+
let blocks = this.sourceBlock_.workspace.getAllBlocks(false);
|
|
11281
11249
|
// Iterate through every block and check the name.
|
|
11282
11250
|
for (let i = 0; i < blocks.length; i++) {
|
|
11283
11251
|
if (blocks[i].getProcedureDef) {
|
|
@@ -11306,10 +11274,6 @@ var pxtblockly;
|
|
|
11306
11274
|
}
|
|
11307
11275
|
onItemSelected(menu, menuItem) {
|
|
11308
11276
|
let itemText = menuItem.getValue();
|
|
11309
|
-
if (this.sourceBlock_) {
|
|
11310
|
-
// Call any validation function, and allow it to override.
|
|
11311
|
-
itemText = this.callValidator(itemText);
|
|
11312
|
-
}
|
|
11313
11277
|
if (itemText !== null) {
|
|
11314
11278
|
this.setValue(itemText);
|
|
11315
11279
|
}
|
|
@@ -11804,8 +11768,8 @@ var pxtblockly;
|
|
|
11804
11768
|
}
|
|
11805
11769
|
return FieldTileset.referencedTiles;
|
|
11806
11770
|
}
|
|
11807
|
-
|
|
11808
|
-
super.
|
|
11771
|
+
initView() {
|
|
11772
|
+
super.initView();
|
|
11809
11773
|
if (this.sourceBlock_ && this.sourceBlock_.isInFlyout) {
|
|
11810
11774
|
this.setValue(this.getOptions()[0][1]);
|
|
11811
11775
|
}
|
|
@@ -11954,16 +11918,10 @@ var pxtblockly;
|
|
|
11954
11918
|
this.addArgType('toggle');
|
|
11955
11919
|
this.type_ = params.type;
|
|
11956
11920
|
}
|
|
11957
|
-
|
|
11958
|
-
if (this.fieldGroup_) {
|
|
11959
|
-
// Field has already been initialized once.
|
|
11921
|
+
initView() {
|
|
11922
|
+
if (!this.fieldGroup_) {
|
|
11960
11923
|
return;
|
|
11961
11924
|
}
|
|
11962
|
-
// Build the DOM.
|
|
11963
|
-
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
|
11964
|
-
if (!this.visible_) {
|
|
11965
|
-
this.fieldGroup_.style.display = 'none';
|
|
11966
|
-
}
|
|
11967
11925
|
// Add an attribute to cassify the type of field.
|
|
11968
11926
|
if (this.getArgTypes() !== null) {
|
|
11969
11927
|
if (this.sourceBlock_.isShadow()) {
|
|
@@ -12035,10 +11993,7 @@ var pxtblockly;
|
|
|
12035
11993
|
this.switchToggle(this.state_);
|
|
12036
11994
|
this.setValue(this.getValue());
|
|
12037
11995
|
// Force a render.
|
|
12038
|
-
this.
|
|
12039
|
-
this.size_.width = 0;
|
|
12040
|
-
this.mouseDownWrapper_ =
|
|
12041
|
-
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
|
11996
|
+
this.markDirty();
|
|
12042
11997
|
}
|
|
12043
11998
|
getDisplayText_() {
|
|
12044
11999
|
return this.state_ ? this.getTrueText() : this.getFalseText();
|
package/built/pxtlib.d.ts
CHANGED
|
@@ -57,6 +57,10 @@ declare namespace pxt.auth {
|
|
|
57
57
|
type UserBadgeState = {
|
|
58
58
|
badges: Badge[];
|
|
59
59
|
};
|
|
60
|
+
type SetPrefResult = {
|
|
61
|
+
success: boolean;
|
|
62
|
+
res: UserPreferences;
|
|
63
|
+
};
|
|
60
64
|
/**
|
|
61
65
|
* User preference state that should be synced with the cloud.
|
|
62
66
|
*/
|
|
@@ -66,6 +70,7 @@ declare namespace pxt.auth {
|
|
|
66
70
|
reader?: string;
|
|
67
71
|
skillmap?: UserSkillmapState;
|
|
68
72
|
badges?: UserBadgeState;
|
|
73
|
+
email?: boolean;
|
|
69
74
|
};
|
|
70
75
|
const DEFAULT_USER_PREFERENCES: () => UserPreferences;
|
|
71
76
|
/**
|
|
@@ -124,8 +129,11 @@ declare namespace pxt.auth {
|
|
|
124
129
|
username?: string;
|
|
125
130
|
avatarUrl?: string;
|
|
126
131
|
}): Promise<boolean>;
|
|
127
|
-
private
|
|
128
|
-
patchUserPreferencesAsync(
|
|
132
|
+
private patchQueue;
|
|
133
|
+
patchUserPreferencesAsync(patchOps: ts.pxtc.jsonPatch.PatchOperation | ts.pxtc.jsonPatch.PatchOperation[], opts?: {
|
|
134
|
+
immediate?: boolean;
|
|
135
|
+
filter?: (op: ts.pxtc.jsonPatch.PatchOperation) => boolean;
|
|
136
|
+
}): Promise<SetPrefResult>;
|
|
129
137
|
hasUserId(): boolean;
|
|
130
138
|
private fetchUserAsync;
|
|
131
139
|
private setUserProfileAsync;
|
|
@@ -297,6 +305,7 @@ declare namespace ts.pxtc.Util {
|
|
|
297
305
|
export function groupBy<T>(arr: T[], f: (t: T) => string): pxt.Map<T[]>;
|
|
298
306
|
export function toDictionary<T>(arr: T[], f: (t: T) => string): pxt.Map<T>;
|
|
299
307
|
export function toSet<T>(arr: T[], f: (t: T) => string): pxt.Map<boolean>;
|
|
308
|
+
export function deepCopy(src: any): any;
|
|
300
309
|
export interface ArrayLike<T> {
|
|
301
310
|
[index: number]: T;
|
|
302
311
|
length: number;
|
package/built/pxtlib.js
CHANGED
|
@@ -185,7 +185,8 @@ var pxt;
|
|
|
185
185
|
highContrast: false,
|
|
186
186
|
language: pxt.appTarget.appTheme.defaultLocale,
|
|
187
187
|
reader: "",
|
|
188
|
-
skillmap: { mapProgress: {}, completedTags: {} }
|
|
188
|
+
skillmap: { mapProgress: {}, completedTags: {} },
|
|
189
|
+
email: false
|
|
189
190
|
});
|
|
190
191
|
let _client;
|
|
191
192
|
function client() { return _client; }
|
|
@@ -198,7 +199,7 @@ var pxt;
|
|
|
198
199
|
constructor() {
|
|
199
200
|
this.initialUserPreferences_ = undefined;
|
|
200
201
|
this.initialAuthCheck_ = undefined;
|
|
201
|
-
this.
|
|
202
|
+
this.patchQueue = [];
|
|
202
203
|
pxt.Util.assert(!_client);
|
|
203
204
|
// Set global instance.
|
|
204
205
|
_client = this;
|
|
@@ -388,62 +389,86 @@ var pxt;
|
|
|
388
389
|
}
|
|
389
390
|
return result.success;
|
|
390
391
|
}
|
|
391
|
-
async patchUserPreferencesAsync(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
392
|
+
async patchUserPreferencesAsync(patchOps, opts = {}) {
|
|
393
|
+
const defaultSuccessAsync = async () => ({ success: true, res: await this.userPreferencesAsync() });
|
|
394
|
+
patchOps = Array.isArray(patchOps) ? patchOps : [patchOps];
|
|
395
|
+
patchOps = patchOps.filter(op => !!op);
|
|
396
|
+
if (!patchOps.length) {
|
|
397
|
+
return await defaultSuccessAsync();
|
|
398
|
+
}
|
|
399
|
+
const patchDiff = (pSrc, ops, filter) => {
|
|
400
|
+
// Apply patches to pDst and return the diff as a set of new patch ops.
|
|
401
|
+
const pDst = pxt.U.deepCopy(pSrc);
|
|
402
|
+
ts.pxtc.jsonPatch.patchInPlace(pDst, ops);
|
|
403
|
+
let diff = ts.pxtc.jsonPatch.diff(pSrc, pDst);
|
|
404
|
+
// Run caller-provided filter
|
|
405
|
+
if (diff.length && filter) {
|
|
406
|
+
diff = diff.filter(filter);
|
|
407
|
+
}
|
|
408
|
+
return diff;
|
|
409
|
+
};
|
|
410
|
+
// Process incoming patch operations to produce a more fine-grained set of diffs. Incoming patches may be overly destructive
|
|
411
|
+
// Apply the patch in isolation and get the diff from original
|
|
397
412
|
const curPref = await this.userPreferencesAsync();
|
|
398
|
-
|
|
413
|
+
const diff = patchDiff(curPref, patchOps, opts.filter);
|
|
414
|
+
if (!diff.length) {
|
|
415
|
+
return await defaultSuccessAsync();
|
|
416
|
+
}
|
|
417
|
+
// Apply the new diff to the current state
|
|
418
|
+
ts.pxtc.jsonPatch.patchInPlace(curPref, diff);
|
|
399
419
|
await this.setUserPreferencesAsync(curPref);
|
|
400
|
-
// If
|
|
420
|
+
// If the user is not logged in, non-persistent local state is all we'll use (no sync to cloud)
|
|
401
421
|
if (!await this.loggedInAsync()) {
|
|
402
|
-
return;
|
|
422
|
+
return await defaultSuccessAsync();
|
|
403
423
|
}
|
|
404
|
-
// If the user is logged in,
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
this.prefPatchOps.some((existing, iExisting) => {
|
|
408
|
-
if (!ts.pxtc.jsonPatch.opsAreEqual(existing, incoming))
|
|
409
|
-
return false;
|
|
410
|
-
// Patches are equivalent, replace in queue
|
|
411
|
-
this.prefPatchOps[iExisting] = incoming;
|
|
412
|
-
// Clear from incoming so we don't add it below
|
|
413
|
-
ops[iIncoming] = null;
|
|
414
|
-
return true;
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
// Add remaining ops to the queue
|
|
418
|
-
ops.filter(op => !!op).forEach(op => this.prefPatchOps.push(op));
|
|
424
|
+
// If the user is logged in, sync to cloud, but debounce the api call as this can be called frequently from skillmaps
|
|
425
|
+
// Queue the patch for sync with backend
|
|
426
|
+
this.patchQueue.push({ ops: patchOps, filter: opts.filter });
|
|
419
427
|
clearTimeout(debouncePreferencesChangedTimeout);
|
|
420
|
-
const
|
|
428
|
+
const syncPrefs = async () => {
|
|
421
429
|
debouncePreferencesChangedStarted = 0;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
+
if (!this.patchQueue.length) {
|
|
431
|
+
return await defaultSuccessAsync();
|
|
432
|
+
}
|
|
433
|
+
// Fetch latest prefs from remote
|
|
434
|
+
const getResult = await this.apiAsync('/api/user/preferences');
|
|
435
|
+
if (!getResult.success) {
|
|
436
|
+
pxt.reportError("identity", "failed to fetch preferences for patch", getResult);
|
|
437
|
+
return { success: false, res: undefined };
|
|
438
|
+
}
|
|
439
|
+
// Apply queued patches to the remote state in isolation and develop a final diff to send to the backend
|
|
440
|
+
const remotePrefs = pxt.U.deepCopy(getResult.resp);
|
|
441
|
+
const patchQueue = this.patchQueue;
|
|
442
|
+
this.patchQueue = []; // Reset the queue
|
|
443
|
+
patchQueue.forEach(patch => {
|
|
444
|
+
const diff = patchDiff(remotePrefs, patch.ops, patch.filter);
|
|
445
|
+
ts.pxtc.jsonPatch.patchInPlace(remotePrefs, diff);
|
|
446
|
+
});
|
|
447
|
+
// Diff the original and patched remote states to get a final set of patch operations
|
|
448
|
+
const finalOps = pxtc.jsonPatch.diff(getResult.resp, remotePrefs);
|
|
449
|
+
const patchResult = await this.apiAsync('/api/user/preferences', finalOps, 'PATCH');
|
|
450
|
+
if (patchResult.success) {
|
|
451
|
+
// Set user profile from returned value so we stay in sync
|
|
452
|
+
this.setUserPreferencesAsync(patchResult.resp);
|
|
430
453
|
}
|
|
431
454
|
else {
|
|
432
|
-
pxt.reportError("identity", "
|
|
455
|
+
pxt.reportError("identity", "failed to patch preferences", patchResult);
|
|
433
456
|
}
|
|
457
|
+
return { success: patchResult.success, res: patchResult.resp };
|
|
434
458
|
};
|
|
435
|
-
if (immediate) {
|
|
436
|
-
await
|
|
459
|
+
if (opts.immediate) {
|
|
460
|
+
return await syncPrefs();
|
|
437
461
|
}
|
|
438
462
|
else {
|
|
439
463
|
if (!debouncePreferencesChangedStarted) {
|
|
440
464
|
debouncePreferencesChangedStarted = pxt.U.now();
|
|
441
465
|
}
|
|
442
466
|
if (PREFERENCES_DEBOUNCE_MAX_MS < pxt.U.now() - debouncePreferencesChangedStarted) {
|
|
443
|
-
await
|
|
467
|
+
return await syncPrefs();
|
|
444
468
|
}
|
|
445
469
|
else {
|
|
446
|
-
debouncePreferencesChangedTimeout = setTimeout(
|
|
470
|
+
debouncePreferencesChangedTimeout = setTimeout(syncPrefs, PREFERENCES_DEBOUNCE_MS);
|
|
471
|
+
return { success: false, res: undefined }; // This needs to be implemented correctly to return a promise with the debouncer
|
|
447
472
|
}
|
|
448
473
|
}
|
|
449
474
|
}
|
|
@@ -1525,6 +1550,18 @@ var ts;
|
|
|
1525
1550
|
return r;
|
|
1526
1551
|
}
|
|
1527
1552
|
Util.toSet = toSet;
|
|
1553
|
+
function deepCopy(src) {
|
|
1554
|
+
if (typeof src !== "object" || src === null) {
|
|
1555
|
+
return src;
|
|
1556
|
+
}
|
|
1557
|
+
const dst = Array.isArray(src) ? [] : {};
|
|
1558
|
+
for (const key in src) {
|
|
1559
|
+
const value = src[key];
|
|
1560
|
+
dst[key] = deepCopy(value);
|
|
1561
|
+
}
|
|
1562
|
+
return dst;
|
|
1563
|
+
}
|
|
1564
|
+
Util.deepCopy = deepCopy;
|
|
1528
1565
|
function toArray(a) {
|
|
1529
1566
|
if (Array.isArray(a)) {
|
|
1530
1567
|
return a;
|
|
@@ -3944,7 +3981,7 @@ var pxt;
|
|
|
3944
3981
|
'pxt_controls_for': {
|
|
3945
3982
|
name: pxt.Util.lf("a loop that repeats the number of times you say"),
|
|
3946
3983
|
tooltip: pxt.Util.lf("Have the variable '{0}' take on the values from 0 to the end number, counting by 1, and do the specified blocks."),
|
|
3947
|
-
url: 'blocks/loops/for',
|
|
3984
|
+
url: '/blocks/loops/for',
|
|
3948
3985
|
category: 'loops',
|
|
3949
3986
|
block: {
|
|
3950
3987
|
message0: pxt.Util.lf("for %1 from 0 to %2"),
|
|
@@ -3955,7 +3992,7 @@ var pxt;
|
|
|
3955
3992
|
'controls_simple_for': {
|
|
3956
3993
|
name: pxt.Util.lf("a loop that repeats the number of times you say"),
|
|
3957
3994
|
tooltip: pxt.Util.lf("Have the variable '{0}' take on the values from 0 to the end number, counting by 1, and do the specified blocks."),
|
|
3958
|
-
url: 'blocks/loops/for',
|
|
3995
|
+
url: '/blocks/loops/for',
|
|
3959
3996
|
category: 'loops',
|
|
3960
3997
|
block: {
|
|
3961
3998
|
message0: pxt.Util.lf("for %1 from 0 to %2"),
|
|
@@ -3966,7 +4003,7 @@ var pxt;
|
|
|
3966
4003
|
'pxt_controls_for_of': {
|
|
3967
4004
|
name: pxt.Util.lf("a loop that repeats for each value in an array"),
|
|
3968
4005
|
tooltip: pxt.Util.lf("Have the variable '{0}' take the value of each item in the array one by one, and do the specified blocks."),
|
|
3969
|
-
url: 'blocks/loops/for-of',
|
|
4006
|
+
url: '/blocks/loops/for-of',
|
|
3970
4007
|
category: 'loops',
|
|
3971
4008
|
block: {
|
|
3972
4009
|
message0: pxt.Util.lf("for element %1 of %2"),
|
|
@@ -3977,7 +4014,7 @@ var pxt;
|
|
|
3977
4014
|
'controls_for_of': {
|
|
3978
4015
|
name: pxt.Util.lf("a loop that repeats for each value in an array"),
|
|
3979
4016
|
tooltip: pxt.Util.lf("Have the variable '{0}' take the value of each item in the array one by one, and do the specified blocks."),
|
|
3980
|
-
url: 'blocks/loops/for-of',
|
|
4017
|
+
url: '/blocks/loops/for-of',
|
|
3981
4018
|
category: 'loops',
|
|
3982
4019
|
block: {
|
|
3983
4020
|
message0: pxt.Util.lf("for element %1 of %2"),
|
|
@@ -4262,7 +4299,7 @@ var pxt;
|
|
|
4262
4299
|
'text': {
|
|
4263
4300
|
name: pxt.Util.lf("a piece of text"),
|
|
4264
4301
|
tooltip: pxt.Util.lf("A letter, word, or line of text."),
|
|
4265
|
-
url: 'types/string',
|
|
4302
|
+
url: '/types/string',
|
|
4266
4303
|
category: 'text',
|
|
4267
4304
|
block: {
|
|
4268
4305
|
search: pxt.Util.lf("a piece of text") // Only used for search; this string is not surfaced in the block's text
|
|
@@ -4271,7 +4308,7 @@ var pxt;
|
|
|
4271
4308
|
'text_length': {
|
|
4272
4309
|
name: pxt.Util.lf("number of characters in the string"),
|
|
4273
4310
|
tooltip: pxt.Util.lf("Returns the number of letters (including spaces) in the provided text."),
|
|
4274
|
-
url: 'reference/text/length',
|
|
4311
|
+
url: '/reference/text/length',
|
|
4275
4312
|
category: 'text',
|
|
4276
4313
|
block: {
|
|
4277
4314
|
TEXT_LENGTH_TITLE: pxt.Util.lf("length of %1")
|
|
@@ -4280,7 +4317,7 @@ var pxt;
|
|
|
4280
4317
|
'text_join': {
|
|
4281
4318
|
name: pxt.Util.lf("join items to create text"),
|
|
4282
4319
|
tooltip: pxt.Util.lf("Create a piece of text by joining together any number of items."),
|
|
4283
|
-
url: 'reference/text/join',
|
|
4320
|
+
url: '/reference/text/join',
|
|
4284
4321
|
category: 'text',
|
|
4285
4322
|
block: {
|
|
4286
4323
|
TEXT_JOIN_TITLE_CREATEWITH: pxt.Util.lf("join")
|
|
@@ -4289,7 +4326,7 @@ var pxt;
|
|
|
4289
4326
|
'procedures_defnoreturn': {
|
|
4290
4327
|
name: pxt.Util.lf("define the function"),
|
|
4291
4328
|
tooltip: pxt.Util.lf("Create a function."),
|
|
4292
|
-
url: 'types/function/define',
|
|
4329
|
+
url: '/types/function/define',
|
|
4293
4330
|
category: 'functions',
|
|
4294
4331
|
block: {
|
|
4295
4332
|
PROCEDURES_DEFNORETURN_TITLE: pxt.Util.lf("function"),
|
|
@@ -4299,7 +4336,7 @@ var pxt;
|
|
|
4299
4336
|
'procedures_callnoreturn': {
|
|
4300
4337
|
name: pxt.Util.lf("call the function"),
|
|
4301
4338
|
tooltip: pxt.Util.lf("Call the user-defined function."),
|
|
4302
|
-
url: 'types/function/call',
|
|
4339
|
+
url: '/types/function/call',
|
|
4303
4340
|
category: 'functions',
|
|
4304
4341
|
block: {
|
|
4305
4342
|
PROCEDURES_CALLNORETURN_TITLE: pxt.Util.lf("call function")
|
|
@@ -4308,7 +4345,7 @@ var pxt;
|
|
|
4308
4345
|
'function_return': {
|
|
4309
4346
|
name: pxt.Util.lf("return a value from within a function"),
|
|
4310
4347
|
tooltip: pxt.Util.lf("Return a value from within a user-defined function."),
|
|
4311
|
-
url: 'types/function/return',
|
|
4348
|
+
url: '/types/function/return',
|
|
4312
4349
|
category: 'functions',
|
|
4313
4350
|
block: {
|
|
4314
4351
|
message_with_value: pxt.Util.lf("return %1"),
|
|
@@ -4318,7 +4355,7 @@ var pxt;
|
|
|
4318
4355
|
'function_definition': {
|
|
4319
4356
|
name: pxt.Util.lf("define the function"),
|
|
4320
4357
|
tooltip: pxt.Util.lf("Create a function."),
|
|
4321
|
-
url: 'types/function/define',
|
|
4358
|
+
url: '/types/function/define',
|
|
4322
4359
|
category: 'functions',
|
|
4323
4360
|
block: {
|
|
4324
4361
|
FUNCTIONS_EDIT_OPTION: pxt.Util.lf("Edit Function")
|
|
@@ -4327,7 +4364,7 @@ var pxt;
|
|
|
4327
4364
|
'function_call': {
|
|
4328
4365
|
name: pxt.Util.lf("call the function"),
|
|
4329
4366
|
tooltip: pxt.Util.lf("Call the user-defined function."),
|
|
4330
|
-
url: 'types/function/call',
|
|
4367
|
+
url: '/types/function/call',
|
|
4331
4368
|
category: 'functions',
|
|
4332
4369
|
block: {
|
|
4333
4370
|
FUNCTIONS_CALL_TITLE: pxt.Util.lf("call"),
|
|
@@ -4337,7 +4374,7 @@ var pxt;
|
|
|
4337
4374
|
'function_call_output': {
|
|
4338
4375
|
name: pxt.Util.lf("call the function with a return value"),
|
|
4339
4376
|
tooltip: pxt.Util.lf("Call the user-defined function with a return value."),
|
|
4340
|
-
url: 'types/function/call',
|
|
4377
|
+
url: '/types/function/call',
|
|
4341
4378
|
category: 'functions',
|
|
4342
4379
|
block: {}
|
|
4343
4380
|
}
|