pxt-core 7.3.21 → 7.3.25
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 +73 -39
- package/built/pxtblockly.js +1448 -1359
- package/built/pxtblocks.d.ts +4 -4
- package/built/pxtblocks.js +80 -35
- package/built/pxtlib.d.ts +6 -2
- package/built/pxtlib.js +73 -39
- 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/rtlblockly.css +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/js/main.7ad07e3a.chunk.js +1 -0
- package/localtypings/blockly.d.ts +6555 -11288
- package/package.json +2 -2
- package/theme/blockly-core.less +13 -22
- package/theme/melodyeditor.less +2 -2
- package/theme/print.less +1 -1
- package/theme/toolbox.less +0 -1
- package/webapp/public/blockly/blockly_compressed.js +1286 -1269
- package/webapp/public/blockly/blocks_compressed.js +65 -47
- package/webapp/public/blockly/msg/js/en.js +17 -8
- package/webapp/public/blockly/msg/json/en.json +15 -6
- package/webapp/public/skillmap.html +1 -1
- package/built/web/skillmap/js/main.bdd63c80.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
|
+
init(): 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
|
+
init(): 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
|
+
init(): 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
|
+
init(): 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(
|
|
1500
|
+
infer(w && w.getAllBlocks(), 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();
|
|
1522
1522
|
if (pxt.react.getTilemapProject) {
|
|
1523
1523
|
pxt.react.getTilemapProject().removeInactiveBlockAssets(allBlocks.map(b => b.id));
|
|
1524
1524
|
}
|
|
@@ -1788,11 +1788,13 @@ var pxt;
|
|
|
1788
1788
|
});
|
|
1789
1789
|
}
|
|
1790
1790
|
function maybeAddComment(b, comments) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1791
|
+
if (b.comment) {
|
|
1792
|
+
if ((typeof b.comment) === "string") {
|
|
1793
|
+
comments.push(b.comment);
|
|
1794
|
+
}
|
|
1795
|
+
else {
|
|
1796
|
+
comments.push(b.comment.getText());
|
|
1797
|
+
}
|
|
1796
1798
|
}
|
|
1797
1799
|
}
|
|
1798
1800
|
function addCommentNodes(comments, r) {
|
|
@@ -2455,9 +2457,9 @@ var pxt;
|
|
|
2455
2457
|
}
|
|
2456
2458
|
});
|
|
2457
2459
|
// we'll ignore disabled blocks in the final output
|
|
2458
|
-
const oldBlocks = oldWs.getAllBlocks(
|
|
2460
|
+
const oldBlocks = oldWs.getAllBlocks().filter(b => b.isEnabled());
|
|
2459
2461
|
const oldTopBlocks = oldWs.getTopBlocks(false).filter(b => b.isEnabled());
|
|
2460
|
-
const newBlocks = newWs.getAllBlocks(
|
|
2462
|
+
const newBlocks = newWs.getAllBlocks().filter(b => b.isEnabled());
|
|
2461
2463
|
log(`blocks`, newBlocks.map(b => b.toDevString()));
|
|
2462
2464
|
log(newBlocks);
|
|
2463
2465
|
if (oldBlocks.length == 0 && newBlocks.length == 0) {
|
|
@@ -2479,11 +2481,11 @@ var pxt;
|
|
|
2479
2481
|
const newXml = pxt.blocks.saveWorkspaceXml(newWs, true);
|
|
2480
2482
|
pxt.blocks.domToWorkspaceNoEvents(Blockly.Xml.textToDom(newXml), ws);
|
|
2481
2483
|
// delete disabled blocks from final workspace
|
|
2482
|
-
ws.getAllBlocks(
|
|
2484
|
+
ws.getAllBlocks().filter(b => !b.isEnabled()).forEach(b => {
|
|
2483
2485
|
log('disabled ', b.toDevString());
|
|
2484
2486
|
b.dispose(false);
|
|
2485
2487
|
});
|
|
2486
|
-
const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(
|
|
2488
|
+
const todoBlocks = pxt.Util.toDictionary(ws.getAllBlocks(), b => b.id);
|
|
2487
2489
|
log(`todo blocks`, todoBlocks);
|
|
2488
2490
|
logTodo('start');
|
|
2489
2491
|
// 1. deleted top blocks
|
|
@@ -2493,7 +2495,7 @@ var pxt;
|
|
|
2493
2495
|
done(b);
|
|
2494
2496
|
const b2 = cloneIntoDiff(b);
|
|
2495
2497
|
done(b2);
|
|
2496
|
-
b2.
|
|
2498
|
+
b2.setDisabled(true);
|
|
2497
2499
|
});
|
|
2498
2500
|
logTodo('deleted top');
|
|
2499
2501
|
}
|
|
@@ -2558,7 +2560,7 @@ var pxt;
|
|
|
2558
2560
|
});
|
|
2559
2561
|
logTodo('unmodified');
|
|
2560
2562
|
// if nothing is left in the workspace, we "missed" change
|
|
2561
|
-
if (!ws.getAllBlocks(
|
|
2563
|
+
if (!ws.getAllBlocks().length) {
|
|
2562
2564
|
pxt.tickEvent("blocks.diff", { missed: 1 });
|
|
2563
2565
|
return {
|
|
2564
2566
|
ws,
|
|
@@ -2591,7 +2593,7 @@ var pxt;
|
|
|
2591
2593
|
function stitch(b) {
|
|
2592
2594
|
log(`stitching ${b.toDevString()}->${dids[b.id]}`);
|
|
2593
2595
|
const wb = ws.getBlockById(dids[b.id]);
|
|
2594
|
-
wb.
|
|
2596
|
+
wb.setDisabled(true);
|
|
2595
2597
|
markUsed(wb);
|
|
2596
2598
|
done(wb);
|
|
2597
2599
|
// connect previous connection to delted or existing block
|
|
@@ -2853,10 +2855,10 @@ var pxt;
|
|
|
2853
2855
|
function applyMetaComments(workspace) {
|
|
2854
2856
|
// process meta comments
|
|
2855
2857
|
// @highlight -> highlight block
|
|
2856
|
-
workspace.getAllBlocks(
|
|
2857
|
-
.filter(b => !!b.
|
|
2858
|
+
workspace.getAllBlocks()
|
|
2859
|
+
.filter(b => !!b.comment && b.comment instanceof Blockly.Comment)
|
|
2858
2860
|
.forEach(b => {
|
|
2859
|
-
const c = b.
|
|
2861
|
+
const c = b.comment.getText();
|
|
2860
2862
|
if (/@highlight/.test(c)) {
|
|
2861
2863
|
const cc = c.replace(/@highlight/g, '').trim();
|
|
2862
2864
|
b.setCommentText(cc || null);
|
|
@@ -4341,7 +4343,7 @@ var pxt;
|
|
|
4341
4343
|
}
|
|
4342
4344
|
else if (pr.type == "number" && pr.shadowBlockId && pr.shadowBlockId == "value") {
|
|
4343
4345
|
inputName = undefined;
|
|
4344
|
-
fields.push(namedField(new Blockly.
|
|
4346
|
+
fields.push(namedField(new Blockly.FieldTextInput("0", Blockly.FieldTextInput.numberValidator), defName));
|
|
4345
4347
|
}
|
|
4346
4348
|
else if (pr.type == "string" && pr.shadowOptions && pr.shadowOptions.toString) {
|
|
4347
4349
|
inputCheck = null;
|
|
@@ -5443,6 +5445,11 @@ var pxt;
|
|
|
5443
5445
|
let blockText = '<xml>' +
|
|
5444
5446
|
'<block type="variables_change" gap="' + gap + '">' +
|
|
5445
5447
|
Blockly.Variables.generateVariableFieldXmlString(mostRecentVariable) +
|
|
5448
|
+
'<value name="DELTA">' +
|
|
5449
|
+
'<shadow type="math_number">' +
|
|
5450
|
+
'<field name="NUM">1</field>' +
|
|
5451
|
+
'</shadow>' +
|
|
5452
|
+
'</value>' +
|
|
5446
5453
|
'</block>' +
|
|
5447
5454
|
'</xml>';
|
|
5448
5455
|
let block = Blockly.Xml.textToDom(blockText).firstChild;
|
|
@@ -7615,8 +7622,19 @@ var pxtblockly;
|
|
|
7615
7622
|
this.valueText = text;
|
|
7616
7623
|
}
|
|
7617
7624
|
init() {
|
|
7618
|
-
|
|
7625
|
+
if (this.isInitialized())
|
|
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
|
+
}
|
|
7619
7632
|
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_);
|
|
7620
7638
|
}
|
|
7621
7639
|
dispose() {
|
|
7622
7640
|
this.onDispose();
|
|
@@ -7769,11 +7787,9 @@ var pxtblockly;
|
|
|
7769
7787
|
}
|
|
7770
7788
|
getDisplayText_() {
|
|
7771
7789
|
// This is only used when isGreyBlock is true
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
}
|
|
7776
|
-
return "";
|
|
7790
|
+
const text = pxt.Util.htmlUnescape(this.valueText);
|
|
7791
|
+
return text.substr(0, text.indexOf("(")) + "(...)";
|
|
7792
|
+
;
|
|
7777
7793
|
}
|
|
7778
7794
|
updateEditable() {
|
|
7779
7795
|
if (this.isGreyBlock && this.fieldGroup_) {
|
|
@@ -8030,7 +8046,12 @@ var pxtblockly;
|
|
|
8030
8046
|
}
|
|
8031
8047
|
};
|
|
8032
8048
|
}
|
|
8033
|
-
|
|
8049
|
+
init() {
|
|
8050
|
+
if (this.fieldGroup_) {
|
|
8051
|
+
// Field has already been initialized once.
|
|
8052
|
+
return;
|
|
8053
|
+
}
|
|
8054
|
+
super.init();
|
|
8034
8055
|
// Register mouseover events for animating preview
|
|
8035
8056
|
this.sourceBlock_.getSvgRoot().addEventListener("mouseenter", this.onMouseEnter);
|
|
8036
8057
|
this.sourceBlock_.getSvgRoot().addEventListener("mouseleave", this.onMouseLeave);
|
|
@@ -8236,10 +8257,16 @@ var pxtblockly;
|
|
|
8236
8257
|
this.addArgType('toggle');
|
|
8237
8258
|
this.type_ = params.type;
|
|
8238
8259
|
}
|
|
8239
|
-
|
|
8240
|
-
if (
|
|
8260
|
+
init() {
|
|
8261
|
+
if (this.fieldGroup_) {
|
|
8262
|
+
// Field has already been initialized once.
|
|
8241
8263
|
return;
|
|
8242
8264
|
}
|
|
8265
|
+
// Build the DOM.
|
|
8266
|
+
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
|
8267
|
+
if (!this.visible_) {
|
|
8268
|
+
this.fieldGroup_.style.display = 'none';
|
|
8269
|
+
}
|
|
8243
8270
|
// Add an attribute to cassify the type of field.
|
|
8244
8271
|
if (this.getArgTypes() !== null) {
|
|
8245
8272
|
if (this.sourceBlock_.isShadow()) {
|
|
@@ -8268,10 +8295,15 @@ var pxtblockly;
|
|
|
8268
8295
|
'dy': '0.6ex',
|
|
8269
8296
|
'y': size.height / 2
|
|
8270
8297
|
}, this.fieldGroup_);
|
|
8298
|
+
this.updateEditable();
|
|
8299
|
+
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
|
8271
8300
|
this.switchToggle(this.state_);
|
|
8272
8301
|
this.setValue(this.getValue());
|
|
8273
8302
|
// Force a render.
|
|
8274
|
-
this.
|
|
8303
|
+
this.render_();
|
|
8304
|
+
this.size_.width = 0;
|
|
8305
|
+
this.mouseDownWrapper_ =
|
|
8306
|
+
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
|
8275
8307
|
}
|
|
8276
8308
|
updateSize_() {
|
|
8277
8309
|
this.size_.width = 30;
|
|
@@ -9741,7 +9773,7 @@ var pxtblockly;
|
|
|
9741
9773
|
}
|
|
9742
9774
|
render_() {
|
|
9743
9775
|
if (!this.visible_) {
|
|
9744
|
-
this.
|
|
9776
|
+
this.size_.width = 0;
|
|
9745
9777
|
return;
|
|
9746
9778
|
}
|
|
9747
9779
|
if (!this.elt) {
|
|
@@ -11245,7 +11277,7 @@ var pxtblockly;
|
|
|
11245
11277
|
dropdownCreate() {
|
|
11246
11278
|
let functionList = [];
|
|
11247
11279
|
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
|
11248
|
-
let blocks = this.sourceBlock_.workspace.getAllBlocks(
|
|
11280
|
+
let blocks = this.sourceBlock_.workspace.getAllBlocks();
|
|
11249
11281
|
// Iterate through every block and check the name.
|
|
11250
11282
|
for (let i = 0; i < blocks.length; i++) {
|
|
11251
11283
|
if (blocks[i].getProcedureDef) {
|
|
@@ -11274,6 +11306,10 @@ var pxtblockly;
|
|
|
11274
11306
|
}
|
|
11275
11307
|
onItemSelected(menu, menuItem) {
|
|
11276
11308
|
let itemText = menuItem.getValue();
|
|
11309
|
+
if (this.sourceBlock_) {
|
|
11310
|
+
// Call any validation function, and allow it to override.
|
|
11311
|
+
itemText = this.callValidator(itemText);
|
|
11312
|
+
}
|
|
11277
11313
|
if (itemText !== null) {
|
|
11278
11314
|
this.setValue(itemText);
|
|
11279
11315
|
}
|
|
@@ -11768,8 +11804,8 @@ var pxtblockly;
|
|
|
11768
11804
|
}
|
|
11769
11805
|
return FieldTileset.referencedTiles;
|
|
11770
11806
|
}
|
|
11771
|
-
|
|
11772
|
-
super.
|
|
11807
|
+
init() {
|
|
11808
|
+
super.init();
|
|
11773
11809
|
if (this.sourceBlock_ && this.sourceBlock_.isInFlyout) {
|
|
11774
11810
|
this.setValue(this.getOptions()[0][1]);
|
|
11775
11811
|
}
|
|
@@ -11918,10 +11954,16 @@ var pxtblockly;
|
|
|
11918
11954
|
this.addArgType('toggle');
|
|
11919
11955
|
this.type_ = params.type;
|
|
11920
11956
|
}
|
|
11921
|
-
|
|
11922
|
-
if (
|
|
11957
|
+
init() {
|
|
11958
|
+
if (this.fieldGroup_) {
|
|
11959
|
+
// Field has already been initialized once.
|
|
11923
11960
|
return;
|
|
11924
11961
|
}
|
|
11962
|
+
// Build the DOM.
|
|
11963
|
+
this.fieldGroup_ = Blockly.utils.dom.createSvgElement('g', {}, null);
|
|
11964
|
+
if (!this.visible_) {
|
|
11965
|
+
this.fieldGroup_.style.display = 'none';
|
|
11966
|
+
}
|
|
11925
11967
|
// Add an attribute to cassify the type of field.
|
|
11926
11968
|
if (this.getArgTypes() !== null) {
|
|
11927
11969
|
if (this.sourceBlock_.isShadow()) {
|
|
@@ -11993,7 +12035,10 @@ var pxtblockly;
|
|
|
11993
12035
|
this.switchToggle(this.state_);
|
|
11994
12036
|
this.setValue(this.getValue());
|
|
11995
12037
|
// Force a render.
|
|
11996
|
-
this.
|
|
12038
|
+
this.render_();
|
|
12039
|
+
this.size_.width = 0;
|
|
12040
|
+
this.mouseDownWrapper_ =
|
|
12041
|
+
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
|
11997
12042
|
}
|
|
11998
12043
|
getDisplayText_() {
|
|
11999
12044
|
return this.state_ ? this.getTrueText() : this.getFalseText();
|
package/built/pxtlib.d.ts
CHANGED
|
@@ -129,8 +129,11 @@ declare namespace pxt.auth {
|
|
|
129
129
|
username?: string;
|
|
130
130
|
avatarUrl?: string;
|
|
131
131
|
}): Promise<boolean>;
|
|
132
|
-
private
|
|
133
|
-
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>;
|
|
134
137
|
hasUserId(): boolean;
|
|
135
138
|
private fetchUserAsync;
|
|
136
139
|
private setUserProfileAsync;
|
|
@@ -302,6 +305,7 @@ declare namespace ts.pxtc.Util {
|
|
|
302
305
|
export function groupBy<T>(arr: T[], f: (t: T) => string): pxt.Map<T[]>;
|
|
303
306
|
export function toDictionary<T>(arr: T[], f: (t: T) => string): pxt.Map<T>;
|
|
304
307
|
export function toSet<T>(arr: T[], f: (t: T) => string): pxt.Map<boolean>;
|
|
308
|
+
export function deepCopy(src: any): any;
|
|
305
309
|
export interface ArrayLike<T> {
|
|
306
310
|
[index: number]: T;
|
|
307
311
|
length: number;
|
package/built/pxtlib.js
CHANGED
|
@@ -199,7 +199,7 @@ var pxt;
|
|
|
199
199
|
constructor() {
|
|
200
200
|
this.initialUserPreferences_ = undefined;
|
|
201
201
|
this.initialAuthCheck_ = undefined;
|
|
202
|
-
this.
|
|
202
|
+
this.patchQueue = [];
|
|
203
203
|
pxt.Util.assert(!_client);
|
|
204
204
|
// Set global instance.
|
|
205
205
|
_client = this;
|
|
@@ -389,63 +389,85 @@ var pxt;
|
|
|
389
389
|
}
|
|
390
390
|
return result.success;
|
|
391
391
|
}
|
|
392
|
-
async patchUserPreferencesAsync(
|
|
393
|
-
|
|
394
|
-
|
|
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
|
|
395
412
|
const curPref = await this.userPreferencesAsync();
|
|
396
|
-
|
|
397
|
-
|
|
413
|
+
const diff = patchDiff(curPref, patchOps, opts.filter);
|
|
414
|
+
if (!diff.length) {
|
|
415
|
+
return await defaultSuccessAsync();
|
|
398
416
|
}
|
|
399
|
-
|
|
417
|
+
// Apply the new diff to the current state
|
|
418
|
+
ts.pxtc.jsonPatch.patchInPlace(curPref, diff);
|
|
400
419
|
await this.setUserPreferencesAsync(curPref);
|
|
401
|
-
// If
|
|
420
|
+
// If the user is not logged in, non-persistent local state is all we'll use (no sync to cloud)
|
|
402
421
|
if (!await this.loggedInAsync()) {
|
|
403
|
-
return
|
|
422
|
+
return await defaultSuccessAsync();
|
|
404
423
|
}
|
|
405
|
-
// If the user is logged in,
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
this.prefPatchOps.some((existing, iExisting) => {
|
|
409
|
-
if (!ts.pxtc.jsonPatch.opsAreEqual(existing, incoming))
|
|
410
|
-
return false;
|
|
411
|
-
// Patches are equivalent, replace in queue
|
|
412
|
-
this.prefPatchOps[iExisting] = incoming;
|
|
413
|
-
// Clear from incoming so we don't add it below
|
|
414
|
-
ops[iIncoming] = null;
|
|
415
|
-
return true;
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
// Add remaining ops to the queue
|
|
419
|
-
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 });
|
|
420
427
|
clearTimeout(debouncePreferencesChangedTimeout);
|
|
421
|
-
const
|
|
428
|
+
const syncPrefs = async () => {
|
|
422
429
|
debouncePreferencesChangedStarted = 0;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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);
|
|
431
453
|
}
|
|
432
454
|
else {
|
|
433
|
-
pxt.reportError("identity", "
|
|
455
|
+
pxt.reportError("identity", "failed to patch preferences", patchResult);
|
|
434
456
|
}
|
|
435
|
-
return { success:
|
|
457
|
+
return { success: patchResult.success, res: patchResult.resp };
|
|
436
458
|
};
|
|
437
|
-
if (immediate) {
|
|
438
|
-
return await
|
|
459
|
+
if (opts.immediate) {
|
|
460
|
+
return await syncPrefs();
|
|
439
461
|
}
|
|
440
462
|
else {
|
|
441
463
|
if (!debouncePreferencesChangedStarted) {
|
|
442
464
|
debouncePreferencesChangedStarted = pxt.U.now();
|
|
443
465
|
}
|
|
444
466
|
if (PREFERENCES_DEBOUNCE_MAX_MS < pxt.U.now() - debouncePreferencesChangedStarted) {
|
|
445
|
-
return await
|
|
467
|
+
return await syncPrefs();
|
|
446
468
|
}
|
|
447
469
|
else {
|
|
448
|
-
debouncePreferencesChangedTimeout = setTimeout(
|
|
470
|
+
debouncePreferencesChangedTimeout = setTimeout(syncPrefs, PREFERENCES_DEBOUNCE_MS);
|
|
449
471
|
return { success: false, res: undefined }; // This needs to be implemented correctly to return a promise with the debouncer
|
|
450
472
|
}
|
|
451
473
|
}
|
|
@@ -1528,6 +1550,18 @@ var ts;
|
|
|
1528
1550
|
return r;
|
|
1529
1551
|
}
|
|
1530
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;
|
|
1531
1565
|
function toArray(a) {
|
|
1532
1566
|
if (Array.isArray(a)) {
|
|
1533
1567
|
return a;
|