pxt-core 7.3.20 → 7.3.24

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 CHANGED
@@ -97885,7 +97885,7 @@ var pxt;
97885
97885
  constructor() {
97886
97886
  this.initialUserPreferences_ = undefined;
97887
97887
  this.initialAuthCheck_ = undefined;
97888
- this.prefPatchOps = [];
97888
+ this.patchQueue = [];
97889
97889
  pxt.Util.assert(!_client);
97890
97890
  // Set global instance.
97891
97891
  _client = this;
@@ -98075,63 +98075,85 @@ var pxt;
98075
98075
  }
98076
98076
  return result.success;
98077
98077
  }
98078
- async patchUserPreferencesAsync(ops, immediate = false) {
98079
- ops = Array.isArray(ops) ? ops : [ops];
98080
- ops = ops.filter(op => !!op);
98078
+ async patchUserPreferencesAsync(patchOps, opts = {}) {
98079
+ const defaultSuccessAsync = async () => ({ success: true, res: await this.userPreferencesAsync() });
98080
+ patchOps = Array.isArray(patchOps) ? patchOps : [patchOps];
98081
+ patchOps = patchOps.filter(op => !!op);
98082
+ if (!patchOps.length) {
98083
+ return await defaultSuccessAsync();
98084
+ }
98085
+ const patchDiff = (pSrc, ops, filter) => {
98086
+ // Apply patches to pDst and return the diff as a set of new patch ops.
98087
+ const pDst = pxt.U.deepCopy(pSrc);
98088
+ ts.pxtc.jsonPatch.patchInPlace(pDst, ops);
98089
+ let diff = ts.pxtc.jsonPatch.diff(pSrc, pDst);
98090
+ // Run caller-provided filter
98091
+ if (diff.length && filter) {
98092
+ diff = diff.filter(filter);
98093
+ }
98094
+ return diff;
98095
+ };
98096
+ // Process incoming patch operations to produce a more fine-grained set of diffs. Incoming patches may be overly destructive
98097
+ // Apply the patch in isolation and get the diff from original
98081
98098
  const curPref = await this.userPreferencesAsync();
98082
- if (!ops.length) {
98083
- return { success: true, res: curPref };
98099
+ const diff = patchDiff(curPref, patchOps, opts.filter);
98100
+ if (!diff.length) {
98101
+ return await defaultSuccessAsync();
98084
98102
  }
98085
- ts.pxtc.jsonPatch.patchInPlace(curPref, ops);
98103
+ // Apply the new diff to the current state
98104
+ ts.pxtc.jsonPatch.patchInPlace(curPref, diff);
98086
98105
  await this.setUserPreferencesAsync(curPref);
98087
- // If we're not logged in, non-persistent local state is all we'll use
98106
+ // If the user is not logged in, non-persistent local state is all we'll use (no sync to cloud)
98088
98107
  if (!await this.loggedInAsync()) {
98089
- return { success: true, res: curPref };
98108
+ return await defaultSuccessAsync();
98090
98109
  }
98091
- // If the user is logged in, save to cloud, but debounce the api call as this can be called frequently from skillmaps
98092
- // Replace matching patches in the queue
98093
- ops.forEach((incoming, iIncoming) => {
98094
- this.prefPatchOps.some((existing, iExisting) => {
98095
- if (!ts.pxtc.jsonPatch.opsAreEqual(existing, incoming))
98096
- return false;
98097
- // Patches are equivalent, replace in queue
98098
- this.prefPatchOps[iExisting] = incoming;
98099
- // Clear from incoming so we don't add it below
98100
- ops[iIncoming] = null;
98101
- return true;
98102
- });
98103
- });
98104
- // Add remaining ops to the queue
98105
- ops.filter(op => !!op).forEach(op => this.prefPatchOps.push(op));
98110
+ // If the user is logged in, sync to cloud, but debounce the api call as this can be called frequently from skillmaps
98111
+ // Queue the patch for sync with backend
98112
+ this.patchQueue.push({ ops: patchOps, filter: opts.filter });
98106
98113
  clearTimeout(debouncePreferencesChangedTimeout);
98107
- const savePrefs = async () => {
98114
+ const syncPrefs = async () => {
98108
98115
  debouncePreferencesChangedStarted = 0;
98109
- // Clear queued patch ops before send.
98110
- const prefPatchOps = this.prefPatchOps;
98111
- this.prefPatchOps = [];
98112
- const result = await this.apiAsync('/api/user/preferences', prefPatchOps, 'PATCH');
98113
- if (result.success) {
98114
- pxt.debug("Updating local user preferences w/ cloud data after result of POST");
98115
- // Set user profile from returned value so we stay in-sync
98116
- this.setUserPreferencesAsync(result.resp);
98116
+ if (!this.patchQueue.length) {
98117
+ return await defaultSuccessAsync();
98118
+ }
98119
+ // Fetch latest prefs from remote
98120
+ const getResult = await this.apiAsync('/api/user/preferences');
98121
+ if (!getResult.success) {
98122
+ pxt.reportError("identity", "failed to fetch preferences for patch", getResult);
98123
+ return { success: false, res: undefined };
98124
+ }
98125
+ // Apply queued patches to the remote state in isolation and develop a final diff to send to the backend
98126
+ const remotePrefs = pxt.U.deepCopy(getResult.resp);
98127
+ const patchQueue = this.patchQueue;
98128
+ this.patchQueue = []; // Reset the queue
98129
+ patchQueue.forEach(patch => {
98130
+ const diff = patchDiff(remotePrefs, patch.ops, patch.filter);
98131
+ ts.pxtc.jsonPatch.patchInPlace(remotePrefs, diff);
98132
+ });
98133
+ // Diff the original and patched remote states to get a final set of patch operations
98134
+ const finalOps = pxtc.jsonPatch.diff(getResult.resp, remotePrefs);
98135
+ const patchResult = await this.apiAsync('/api/user/preferences', finalOps, 'PATCH');
98136
+ if (patchResult.success) {
98137
+ // Set user profile from returned value so we stay in sync
98138
+ this.setUserPreferencesAsync(patchResult.resp);
98117
98139
  }
98118
98140
  else {
98119
- pxt.reportError("identity", "update preferences failed", result);
98141
+ pxt.reportError("identity", "failed to patch preferences", patchResult);
98120
98142
  }
98121
- return { success: result.success, res: result.resp };
98143
+ return { success: patchResult.success, res: patchResult.resp };
98122
98144
  };
98123
- if (immediate) {
98124
- return await savePrefs();
98145
+ if (opts.immediate) {
98146
+ return await syncPrefs();
98125
98147
  }
98126
98148
  else {
98127
98149
  if (!debouncePreferencesChangedStarted) {
98128
98150
  debouncePreferencesChangedStarted = pxt.U.now();
98129
98151
  }
98130
98152
  if (PREFERENCES_DEBOUNCE_MAX_MS < pxt.U.now() - debouncePreferencesChangedStarted) {
98131
- return await savePrefs();
98153
+ return await syncPrefs();
98132
98154
  }
98133
98155
  else {
98134
- debouncePreferencesChangedTimeout = setTimeout(savePrefs, PREFERENCES_DEBOUNCE_MS);
98156
+ debouncePreferencesChangedTimeout = setTimeout(syncPrefs, PREFERENCES_DEBOUNCE_MS);
98135
98157
  return { success: false, res: undefined }; // This needs to be implemented correctly to return a promise with the debouncer
98136
98158
  }
98137
98159
  }
@@ -99214,6 +99236,18 @@ var ts;
99214
99236
  return r;
99215
99237
  }
99216
99238
  Util.toSet = toSet;
99239
+ function deepCopy(src) {
99240
+ if (typeof src !== "object" || src === null) {
99241
+ return src;
99242
+ }
99243
+ const dst = Array.isArray(src) ? [] : {};
99244
+ for (const key in src) {
99245
+ const value = src[key];
99246
+ dst[key] = deepCopy(value);
99247
+ }
99248
+ return dst;
99249
+ }
99250
+ Util.deepCopy = deepCopy;
99217
99251
  function toArray(a) {
99218
99252
  if (Array.isArray(a)) {
99219
99253
  return a;