pxt-core 9.1.1 → 9.1.3

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.
Files changed (37) hide show
  1. package/built/pxt.js +85 -29
  2. package/built/pxtblockly.js +69 -8
  3. package/built/pxtblocks.js +69 -8
  4. package/built/pxteditor.d.ts +7 -2
  5. package/built/pxteditor.js +6 -0
  6. package/built/pxtlib.d.ts +0 -1
  7. package/built/pxtlib.js +57 -22
  8. package/built/pxtsim.d.ts +8 -0
  9. package/built/pxtsim.js +28 -7
  10. package/built/target.js +1 -1
  11. package/built/web/main.js +1 -1
  12. package/built/web/pxtapp.js +1 -1
  13. package/built/web/pxtblockly.js +1 -1
  14. package/built/web/pxtblocks.js +1 -1
  15. package/built/web/pxteditor.js +1 -1
  16. package/built/web/pxtembed.js +2 -2
  17. package/built/web/pxtlib.js +1 -1
  18. package/built/web/pxtsim.js +1 -1
  19. package/built/web/pxtworker.js +1 -1
  20. package/built/web/react-common-authcode.css +1 -1
  21. package/built/web/react-common-multiplayer.css +1 -1
  22. package/built/web/react-common-skillmap.css +1 -1
  23. package/built/web/rtlreact-common-authcode.css +1 -1
  24. package/built/web/rtlreact-common-multiplayer.css +1 -1
  25. package/built/web/rtlreact-common-skillmap.css +1 -1
  26. package/built/web/rtlsemantic.css +2 -2
  27. package/built/web/semantic.css +2 -2
  28. package/built/web/skillmap/js/{main.b6e4b712.js → main.a9bdebe3.js} +2 -2
  29. package/common-docs/identity/sign-in.md +7 -15
  30. package/localtypings/pxtarget.d.ts +2 -0
  31. package/package.json +1 -1
  32. package/react-common/components/profile/Profile.tsx +15 -0
  33. package/react-common/styles/profile/profile.less +12 -0
  34. package/theme/common.less +9 -6
  35. package/theme/themes/pxt/globals/site.variables +1 -1
  36. package/theme/tutorial-sidebar.less +2 -2
  37. package/webapp/public/skillmap.html +1 -1
package/built/pxt.js CHANGED
@@ -97876,6 +97876,7 @@ var pxt;
97876
97876
  const AUTH_LOGIN_STATE_KEY = "login-state"; // stored in local storage.
97877
97877
  const AUTH_USER_STATE_KEY = "user-state"; // stored in local storage.
97878
97878
  const X_PXT_TARGET = "x-pxt-target"; // header passed in auth rest calls.
97879
+ const INTERACTIVE_LOGIN_UNTIL = "interactive-login-until"; // hint whether to prompt user or try SSO first.
97879
97880
  let authDisabled = false;
97880
97881
  auth.DEFAULT_USER_PREFERENCES = () => ({
97881
97882
  highContrast: false,
@@ -97901,19 +97902,29 @@ var pxt;
97901
97902
  // Last known auth token state. This is provided as a convenience for legacy methods that cannot be made async.
97902
97903
  // Preference hasAuthTokenAsync() over taking a dependency on this cached value.
97903
97904
  auth.cachedHasAuthToken = false;
97904
- async function getAuthTokenAsync() {
97905
- let token;
97905
+ async function setLocalStorageValueAsync(key, value) {
97906
+ if (!!value)
97907
+ return await pxt.storage.shared.setAsync(AUTH_CONTAINER, key, value);
97908
+ else
97909
+ return await pxt.storage.shared.delAsync(AUTH_CONTAINER, key);
97910
+ }
97911
+ async function getLocalStorageValueAsync(key) {
97906
97912
  try {
97907
- token = await pxt.storage.shared.getAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY);
97913
+ return await pxt.storage.shared.getAsync(AUTH_CONTAINER, key);
97908
97914
  }
97909
- catch (_a) { }
97915
+ catch (_a) {
97916
+ return undefined;
97917
+ }
97918
+ }
97919
+ async function getAuthTokenAsync() {
97920
+ const token = await getLocalStorageValueAsync(CSRF_TOKEN_KEY);
97910
97921
  auth.cachedHasAuthToken = !!token;
97911
97922
  return token;
97912
97923
  }
97913
97924
  auth.getAuthTokenAsync = getAuthTokenAsync;
97914
97925
  async function setAuthTokenAsync(token) {
97915
97926
  auth.cachedHasAuthToken = !!token;
97916
- return await pxt.storage.shared.setAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY, token);
97927
+ return await setLocalStorageValueAsync(CSRF_TOKEN_KEY, token);
97917
97928
  }
97918
97929
  async function hasAuthTokenAsync() {
97919
97930
  return !!(await getAuthTokenAsync());
@@ -97921,7 +97932,7 @@ var pxt;
97921
97932
  auth.hasAuthTokenAsync = hasAuthTokenAsync;
97922
97933
  async function delAuthTokenAsync() {
97923
97934
  auth.cachedHasAuthToken = false;
97924
- return await pxt.storage.shared.delAsync(AUTH_CONTAINER, CSRF_TOKEN_KEY);
97935
+ return await setLocalStorageValueAsync(CSRF_TOKEN_KEY, undefined);
97925
97936
  }
97926
97937
  async function getUserStateAsync() {
97927
97938
  let userState;
@@ -97984,16 +97995,20 @@ var pxt;
97984
97995
  idp,
97985
97996
  persistent
97986
97997
  };
97998
+ // Should the user be prompted to interactively login, or can we try to silently login?
97999
+ const interactiveUntil = parseInt(await getLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL));
98000
+ const interactiveLogin = (interactiveUntil || 0) > Date.now();
97987
98001
  // Redirect to the login endpoint.
97988
98002
  const loginUrl = pxt.Util.stringifyQueryString('/api/auth/login', {
97989
98003
  response_type: "token",
97990
98004
  provider: idp,
97991
98005
  persistent,
97992
- redirect_uri: `${window.location.origin}${window.location.pathname}?authcallback=1&state=${loginState.key}`
98006
+ redirect_uri: `${window.location.origin}${window.location.pathname}?authcallback=1&state=${loginState.key}`,
98007
+ prompt: interactiveLogin ? "select_account" : "silent"
97993
98008
  });
97994
98009
  const apiResult = await this.apiAsync(loginUrl);
97995
98010
  if (apiResult.success) {
97996
- loginState.authCodeVerifier = apiResult.resp.authCodeVerifier;
98011
+ loginState.authCodeVerifier = apiResult.resp.authCodeVerifier; // will be undefined unless configured for the target
97997
98012
  await pxt.storage.shared.setAsync(AUTH_CONTAINER, AUTH_LOGIN_STATE_KEY, loginState);
97998
98013
  pxt.tickEvent('auth.login.start', { 'provider': idp });
97999
98014
  window.location.href = apiResult.resp.loginUrl;
@@ -98012,13 +98027,15 @@ var pxt;
98012
98027
  if (!hasIdentity()) {
98013
98028
  return;
98014
98029
  }
98015
- // Clear local auth state so we can no longer make authenticated requests.
98016
- await this.clearAuthStateAsync();
98017
98030
  await AuthClient.staticLogoutAsync(continuationHash);
98018
98031
  try {
98019
- await this.onSignedOut();
98032
+ await this.onStateCleared();
98020
98033
  }
98021
98034
  catch (_a) { }
98035
+ try {
98036
+ await this.onSignedOut();
98037
+ }
98038
+ catch (_b) { }
98022
98039
  }
98023
98040
  /**
98024
98041
  * Sign out the user and clear the auth token cookie.
@@ -98028,19 +98045,39 @@ var pxt;
98028
98045
  return;
98029
98046
  }
98030
98047
  pxt.tickEvent('auth.logout');
98031
- // Tell backend to clear the http-only auth cookie. Ignore errors here, we want to clear local state even if the backend rejects this request.
98048
+ // Indicate that for the next minute, signin should be interactive.
98049
+ // Use case: SSO signed in with the wrong account. User wants to sign in with a different account.
98050
+ await setLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL, (Date.now() + 60000).toString());
98051
+ continuationHash = continuationHash ? continuationHash.startsWith('#') ? continuationHash : `#${continuationHash}` : "";
98052
+ const clientRedirect = `${window.location.origin}${window.location.pathname}${window.location.search}${continuationHash}`;
98053
+ // Tell backend to clear the http-only auth cookie.
98054
+ let logoutUri = "";
98032
98055
  try {
98033
- await AuthClient.staticApiAsync('/api/auth/logout');
98056
+ const uri = pxt.Util.stringifyQueryString('/api/auth/logout', {
98057
+ redirect_uri: clientRedirect,
98058
+ authcallback: '1'
98059
+ });
98060
+ const apiResult = await AuthClient.staticApiAsync(uri);
98061
+ if (apiResult.success) {
98062
+ logoutUri = apiResult.resp.logoutUrl;
98063
+ }
98064
+ }
98065
+ catch (_a) {
98066
+ // Ignore errors.
98034
98067
  }
98035
- catch (_a) { }
98036
98068
  // Clear local auth state so we can no longer make authenticated requests.
98037
98069
  await delAuthTokenAsync();
98038
98070
  await delUserStateAsync();
98039
- // Redirect to home screen
98040
98071
  if (pxt.BrowserUtils.hasWindow()) {
98041
- const hash = continuationHash ? continuationHash.startsWith('#') ? continuationHash : `#${continuationHash}` : "";
98042
- window.location.href = `${window.location.origin}${window.location.pathname}${window.location.search}${hash}`;
98043
- location.reload();
98072
+ if (logoutUri) {
98073
+ // Redirect to logout endpoint
98074
+ window.location.href = logoutUri;
98075
+ }
98076
+ else {
98077
+ // Redirect to home screen
98078
+ window.location.href = clientRedirect;
98079
+ location.reload();
98080
+ }
98044
98081
  }
98045
98082
  }
98046
98083
  async deleteProfileAsync() {
@@ -98436,6 +98473,8 @@ var pxt;
98436
98473
  // without its cookie-based counterpart. When "Remember me" is false,
98437
98474
  // the cookie is not persisted.
98438
98475
  await setAuthTokenAsync(authToken);
98476
+ // Clear interactive login flag. Next auth request will try silent SSO first.
98477
+ await setLocalStorageValueAsync(INTERACTIVE_LOGIN_UNTIL, undefined);
98439
98478
  pxt.tickEvent('auth.login.success', { 'provider': loginState.idp });
98440
98479
  } while (false);
98441
98480
  // Clear url parameters and redirect to the callback location.
@@ -102460,10 +102499,6 @@ var pxt;
102460
102499
  return typeof window != "undefined" && !!window.PointerEvent;
102461
102500
  }
102462
102501
  BrowserUtils.hasPointerEvents = hasPointerEvents;
102463
- function hasSaveAs() {
102464
- return isEdge() || isIE() || isFirefox();
102465
- }
102466
- BrowserUtils.hasSaveAs = hasSaveAs;
102467
102502
  function os() {
102468
102503
  if (isWindows())
102469
102504
  return "windows";
@@ -155238,6 +155273,13 @@ var pxsim;
155238
155273
  }
155239
155274
  }
155240
155275
  pxsim.Runtime = Runtime;
155276
+ function setParentMuteState(state) {
155277
+ Runtime.postMessage({
155278
+ type: "setmutebuttonstate",
155279
+ state
155280
+ });
155281
+ }
155282
+ pxsim.setParentMuteState = setParentMuteState;
155241
155283
  class PerfCounter {
155242
155284
  constructor(name) {
155243
155285
  this.name = name;
@@ -155898,8 +155940,19 @@ var pxsim;
155898
155940
  this.setFrameState(frame);
155899
155941
  return true;
155900
155942
  }
155943
+ handleDeferredMessages(frame) {
155944
+ var _a, _b, _c;
155945
+ if (frame.dataset["loading"]) {
155946
+ delete frame.dataset["loading"];
155947
+ (_b = (_a = this.deferredMessages) === null || _a === void 0 ? void 0 : _a.filter(defMsg => defMsg[0] === frame)) === null || _b === void 0 ? void 0 : _b.forEach(defMsg => {
155948
+ const [_, msg] = defMsg;
155949
+ this.postMessageCore(frame, msg);
155950
+ });
155951
+ this.deferredMessages = (_c = this.deferredMessages) === null || _c === void 0 ? void 0 : _c.filter(defMsg => defMsg[0] !== frame);
155952
+ }
155953
+ }
155901
155954
  handleMessage(msg, source) {
155902
- var _a, _b, _c, _d;
155955
+ var _a, _b, _c;
155903
155956
  switch (msg.type || '') {
155904
155957
  case 'ready': {
155905
155958
  const frameid = msg.frameid;
@@ -155910,12 +155963,7 @@ var pxsim;
155910
155963
  this.startFrame(frame);
155911
155964
  if (this.options.revealElement)
155912
155965
  this.options.revealElement(frame);
155913
- delete frame.dataset["loading"];
155914
- (_c = (_b = this.deferredMessages) === null || _b === void 0 ? void 0 : _b.filter(defMsg => defMsg[0] === frame)) === null || _c === void 0 ? void 0 : _c.forEach(defMsg => {
155915
- const [_, msg] = defMsg;
155916
- this.postMessageCore(frame, msg);
155917
- });
155918
- this.deferredMessages = (_d = this.deferredMessages) === null || _d === void 0 ? void 0 : _d.filter(defMsg => defMsg[0] !== frame);
155966
+ this.handleDeferredMessages(frame);
155919
155967
  }
155920
155968
  if (this.options.onSimulatorReady)
155921
155969
  this.options.onSimulatorReady();
@@ -155930,6 +155978,7 @@ var pxsim;
155930
155978
  switch (stmsg.state) {
155931
155979
  case "running":
155932
155980
  this.setState(SimulatorState.Running);
155981
+ this.handleDeferredMessages(frame);
155933
155982
  break;
155934
155983
  case "killed":
155935
155984
  this.setState(SimulatorState.Stopped);
@@ -155966,6 +156015,9 @@ var pxsim;
155966
156015
  if (this.options.onTopLevelCodeEnd)
155967
156016
  this.options.onTopLevelCodeEnd();
155968
156017
  break;
156018
+ case 'setmutebuttonstate':
156019
+ (_c = (_b = this.options).onMuteButtonStateChange) === null || _c === void 0 ? void 0 : _c.call(_b, msg.state);
156020
+ break;
155969
156021
  default:
155970
156022
  this.postMessage(msg, source);
155971
156023
  break;
@@ -156370,6 +156422,10 @@ var pxsim;
156370
156422
  ctx.resume();
156371
156423
  }
156372
156424
  AudioContextManager.mute = mute;
156425
+ function isMuted() {
156426
+ return _mute;
156427
+ }
156428
+ AudioContextManager.isMuted = isMuted;
156373
156429
  function stopTone() {
156374
156430
  setCurrentToneGain(0);
156375
156431
  _frequency = 0;
@@ -13656,6 +13656,51 @@ var pxtblockly;
13656
13656
  if (value === "CREATE") {
13657
13657
  promptAndCreateKind(this.sourceBlock_.workspace, this.opts, lf("New {0}:", this.opts.memberName), newName => newName && this.setValue(newName));
13658
13658
  }
13659
+ else if (value === "RENAME") {
13660
+ const ws = this.sourceBlock_.workspace;
13661
+ const toRename = ws.getVariable(this.value_, kindType(this.opts.name));
13662
+ const oldName = toRename.name;
13663
+ if (this.opts.initialMembers.indexOf(oldName) !== -1) {
13664
+ Blockly.alert(lf("The built-in {0} '{1}' cannot be renamed. Try creating a new kind instead!", this.opts.memberName, oldName));
13665
+ return;
13666
+ }
13667
+ promptAndRenameKind(ws, Object.assign(Object.assign({}, this.opts), { toRename }), lf("Rename '{0}':", oldName), newName => {
13668
+ // Update the values of all existing field instances
13669
+ const allFields = pxtblockly.getAllFields(ws, field => field instanceof FieldKind);
13670
+ for (const field of allFields) {
13671
+ if (field.ref.getValue() === oldName) {
13672
+ field.ref.setValue(newName);
13673
+ }
13674
+ }
13675
+ });
13676
+ }
13677
+ else if (value === "DELETE") {
13678
+ const ws = this.sourceBlock_.workspace;
13679
+ const toDelete = ws.getVariable(this.value_, kindType(this.opts.name));
13680
+ const varName = toDelete.name;
13681
+ if (this.opts.initialMembers.indexOf(varName) !== -1) {
13682
+ Blockly.alert(lf("The built-in {0} '{1}' cannot be deleted.", this.opts.memberName, varName));
13683
+ return;
13684
+ }
13685
+ const uses = pxtblockly.getAllFields(ws, field => field instanceof FieldKind && field.getValue() === varName);
13686
+ if (uses.length > 1) {
13687
+ Blockly.confirm(lf("Delete {0} uses of the \"{1}\" {2}?", uses.length, varName, this.opts.memberName), response => {
13688
+ if (!response)
13689
+ return;
13690
+ Blockly.Events.setGroup(true);
13691
+ for (const use of uses) {
13692
+ use.block.dispose(true);
13693
+ }
13694
+ ws.deleteVariableById(toDelete.getId());
13695
+ this.setValue(this.opts.initialMembers[0]);
13696
+ Blockly.Events.setGroup(false);
13697
+ });
13698
+ }
13699
+ else {
13700
+ ws.deleteVariableById(toDelete.getId());
13701
+ this.setValue(this.opts.initialMembers[0]);
13702
+ }
13703
+ }
13659
13704
  else {
13660
13705
  super.onItemSelected_(menu, menuItem);
13661
13706
  }
@@ -13680,7 +13725,7 @@ var pxtblockly;
13680
13725
  createVariableForKind(ws, this.opts, memberName);
13681
13726
  }
13682
13727
  });
13683
- if (this.getValue() === "CREATE") {
13728
+ if (this.getValue() === "CREATE" || this.getValue() === "RENAME" || this.getValue() === "DELETE") {
13684
13729
  if (this.opts.initialMembers.length) {
13685
13730
  this.setValue(this.opts.initialMembers[0]);
13686
13731
  }
@@ -13704,10 +13749,13 @@ var pxtblockly;
13704
13749
  opts.initialMembers.forEach((e) => res.push([e, e]));
13705
13750
  }
13706
13751
  res.push([lf("Add a new {0}...", opts.memberName), "CREATE"]);
13752
+ res.push([undefined, "SEPARATOR"]);
13753
+ res.push([lf("Rename {0}...", opts.memberName), "RENAME"]);
13754
+ res.push([lf("Delete {0}...", opts.memberName), "DELETE"]);
13707
13755
  return res;
13708
13756
  };
13709
13757
  }
13710
- function promptAndCreateKind(ws, opts, message, cb) {
13758
+ function promptForName(ws, opts, message, cb, prompt) {
13711
13759
  Blockly.prompt(message, null, response => {
13712
13760
  if (response) {
13713
13761
  let nameIsValid = false;
@@ -13720,28 +13768,41 @@ var pxtblockly;
13720
13768
  }
13721
13769
  }
13722
13770
  if (!nameIsValid) {
13723
- Blockly.alert(lf("Names must start with a letter and can only contain letters, numbers, '$', and '_'."), () => promptAndCreateKind(ws, opts, message, cb));
13771
+ Blockly.alert(lf("Names must start with a letter and can only contain letters, numbers, '$', and '_'."), () => promptForName(ws, opts, message, cb, prompt));
13724
13772
  return;
13725
13773
  }
13726
- if (pxt.blocks.isReservedWord(response)) {
13727
- Blockly.alert(lf("'{0}' is a reserved word and cannot be used.", response), () => promptAndCreateKind(ws, opts, message, cb));
13774
+ if (pxt.blocks.isReservedWord(response) || response === "CREATE" || response === "RENAME" || response === "DELETE") {
13775
+ Blockly.alert(lf("'{0}' is a reserved word and cannot be used.", response), () => promptForName(ws, opts, message, cb, prompt));
13728
13776
  return;
13729
13777
  }
13730
13778
  const existing = getExistingKindMembers(ws, opts.name);
13731
13779
  for (let i = 0; i < existing.length; i++) {
13732
13780
  const name = existing[i];
13733
13781
  if (name === response) {
13734
- Blockly.alert(lf("A {0} named '{1}' already exists.", opts.memberName, response), () => promptAndCreateKind(ws, opts, message, cb));
13782
+ Blockly.alert(lf("A {0} named '{1}' already exists.", opts.memberName, response), () => promptForName(ws, opts, message, cb, prompt));
13735
13783
  return;
13736
13784
  }
13737
13785
  }
13738
13786
  if (response === opts.createFunctionName) {
13739
- Blockly.alert(lf("'{0}' is a reserved name.", opts.createFunctionName), () => promptAndCreateKind(ws, opts, message, cb));
13787
+ Blockly.alert(lf("'{0}' is a reserved name.", opts.createFunctionName), () => promptForName(ws, opts, message, cb, prompt));
13740
13788
  }
13741
- cb(createVariableForKind(ws, opts, response));
13789
+ cb(response);
13742
13790
  }
13743
13791
  }, { placeholder: opts.promptHint });
13744
13792
  }
13793
+ function promptAndCreateKind(ws, opts, message, cb) {
13794
+ const responseHandler = (response) => {
13795
+ cb(createVariableForKind(ws, opts, response));
13796
+ };
13797
+ promptForName(ws, opts, message, responseHandler, promptAndCreateKind);
13798
+ }
13799
+ function promptAndRenameKind(ws, opts, message, cb) {
13800
+ const responseHandler = (response) => {
13801
+ ws.getVariableMap().renameVariable(opts.toRename, response);
13802
+ cb(response);
13803
+ };
13804
+ promptForName(ws, opts, message, responseHandler, promptAndRenameKind);
13805
+ }
13745
13806
  function getExistingKindMembers(ws, kindName) {
13746
13807
  const existing = ws.getVariablesOfType(kindType(kindName));
13747
13808
  if (existing && existing.length) {
@@ -10094,6 +10094,51 @@ var pxtblockly;
10094
10094
  if (value === "CREATE") {
10095
10095
  promptAndCreateKind(this.sourceBlock_.workspace, this.opts, lf("New {0}:", this.opts.memberName), newName => newName && this.setValue(newName));
10096
10096
  }
10097
+ else if (value === "RENAME") {
10098
+ const ws = this.sourceBlock_.workspace;
10099
+ const toRename = ws.getVariable(this.value_, kindType(this.opts.name));
10100
+ const oldName = toRename.name;
10101
+ if (this.opts.initialMembers.indexOf(oldName) !== -1) {
10102
+ Blockly.alert(lf("The built-in {0} '{1}' cannot be renamed. Try creating a new kind instead!", this.opts.memberName, oldName));
10103
+ return;
10104
+ }
10105
+ promptAndRenameKind(ws, Object.assign(Object.assign({}, this.opts), { toRename }), lf("Rename '{0}':", oldName), newName => {
10106
+ // Update the values of all existing field instances
10107
+ const allFields = pxtblockly.getAllFields(ws, field => field instanceof FieldKind);
10108
+ for (const field of allFields) {
10109
+ if (field.ref.getValue() === oldName) {
10110
+ field.ref.setValue(newName);
10111
+ }
10112
+ }
10113
+ });
10114
+ }
10115
+ else if (value === "DELETE") {
10116
+ const ws = this.sourceBlock_.workspace;
10117
+ const toDelete = ws.getVariable(this.value_, kindType(this.opts.name));
10118
+ const varName = toDelete.name;
10119
+ if (this.opts.initialMembers.indexOf(varName) !== -1) {
10120
+ Blockly.alert(lf("The built-in {0} '{1}' cannot be deleted.", this.opts.memberName, varName));
10121
+ return;
10122
+ }
10123
+ const uses = pxtblockly.getAllFields(ws, field => field instanceof FieldKind && field.getValue() === varName);
10124
+ if (uses.length > 1) {
10125
+ Blockly.confirm(lf("Delete {0} uses of the \"{1}\" {2}?", uses.length, varName, this.opts.memberName), response => {
10126
+ if (!response)
10127
+ return;
10128
+ Blockly.Events.setGroup(true);
10129
+ for (const use of uses) {
10130
+ use.block.dispose(true);
10131
+ }
10132
+ ws.deleteVariableById(toDelete.getId());
10133
+ this.setValue(this.opts.initialMembers[0]);
10134
+ Blockly.Events.setGroup(false);
10135
+ });
10136
+ }
10137
+ else {
10138
+ ws.deleteVariableById(toDelete.getId());
10139
+ this.setValue(this.opts.initialMembers[0]);
10140
+ }
10141
+ }
10097
10142
  else {
10098
10143
  super.onItemSelected_(menu, menuItem);
10099
10144
  }
@@ -10118,7 +10163,7 @@ var pxtblockly;
10118
10163
  createVariableForKind(ws, this.opts, memberName);
10119
10164
  }
10120
10165
  });
10121
- if (this.getValue() === "CREATE") {
10166
+ if (this.getValue() === "CREATE" || this.getValue() === "RENAME" || this.getValue() === "DELETE") {
10122
10167
  if (this.opts.initialMembers.length) {
10123
10168
  this.setValue(this.opts.initialMembers[0]);
10124
10169
  }
@@ -10142,10 +10187,13 @@ var pxtblockly;
10142
10187
  opts.initialMembers.forEach((e) => res.push([e, e]));
10143
10188
  }
10144
10189
  res.push([lf("Add a new {0}...", opts.memberName), "CREATE"]);
10190
+ res.push([undefined, "SEPARATOR"]);
10191
+ res.push([lf("Rename {0}...", opts.memberName), "RENAME"]);
10192
+ res.push([lf("Delete {0}...", opts.memberName), "DELETE"]);
10145
10193
  return res;
10146
10194
  };
10147
10195
  }
10148
- function promptAndCreateKind(ws, opts, message, cb) {
10196
+ function promptForName(ws, opts, message, cb, prompt) {
10149
10197
  Blockly.prompt(message, null, response => {
10150
10198
  if (response) {
10151
10199
  let nameIsValid = false;
@@ -10158,28 +10206,41 @@ var pxtblockly;
10158
10206
  }
10159
10207
  }
10160
10208
  if (!nameIsValid) {
10161
- Blockly.alert(lf("Names must start with a letter and can only contain letters, numbers, '$', and '_'."), () => promptAndCreateKind(ws, opts, message, cb));
10209
+ Blockly.alert(lf("Names must start with a letter and can only contain letters, numbers, '$', and '_'."), () => promptForName(ws, opts, message, cb, prompt));
10162
10210
  return;
10163
10211
  }
10164
- if (pxt.blocks.isReservedWord(response)) {
10165
- Blockly.alert(lf("'{0}' is a reserved word and cannot be used.", response), () => promptAndCreateKind(ws, opts, message, cb));
10212
+ if (pxt.blocks.isReservedWord(response) || response === "CREATE" || response === "RENAME" || response === "DELETE") {
10213
+ Blockly.alert(lf("'{0}' is a reserved word and cannot be used.", response), () => promptForName(ws, opts, message, cb, prompt));
10166
10214
  return;
10167
10215
  }
10168
10216
  const existing = getExistingKindMembers(ws, opts.name);
10169
10217
  for (let i = 0; i < existing.length; i++) {
10170
10218
  const name = existing[i];
10171
10219
  if (name === response) {
10172
- Blockly.alert(lf("A {0} named '{1}' already exists.", opts.memberName, response), () => promptAndCreateKind(ws, opts, message, cb));
10220
+ Blockly.alert(lf("A {0} named '{1}' already exists.", opts.memberName, response), () => promptForName(ws, opts, message, cb, prompt));
10173
10221
  return;
10174
10222
  }
10175
10223
  }
10176
10224
  if (response === opts.createFunctionName) {
10177
- Blockly.alert(lf("'{0}' is a reserved name.", opts.createFunctionName), () => promptAndCreateKind(ws, opts, message, cb));
10225
+ Blockly.alert(lf("'{0}' is a reserved name.", opts.createFunctionName), () => promptForName(ws, opts, message, cb, prompt));
10178
10226
  }
10179
- cb(createVariableForKind(ws, opts, response));
10227
+ cb(response);
10180
10228
  }
10181
10229
  }, { placeholder: opts.promptHint });
10182
10230
  }
10231
+ function promptAndCreateKind(ws, opts, message, cb) {
10232
+ const responseHandler = (response) => {
10233
+ cb(createVariableForKind(ws, opts, response));
10234
+ };
10235
+ promptForName(ws, opts, message, responseHandler, promptAndCreateKind);
10236
+ }
10237
+ function promptAndRenameKind(ws, opts, message, cb) {
10238
+ const responseHandler = (response) => {
10239
+ ws.getVariableMap().renameVariable(opts.toRename, response);
10240
+ cb(response);
10241
+ };
10242
+ promptForName(ws, opts, message, responseHandler, promptAndRenameKind);
10243
+ }
10183
10244
  function getExistingKindMembers(ws, kindName) {
10184
10245
  const existing = ws.getVariablesOfType(kindType(kindName));
10185
10246
  if (existing && existing.length) {
@@ -36,6 +36,11 @@ declare namespace pxt.editor {
36
36
  HeaderOnly = "errorListHeader",
37
37
  Expanded = "errorListExpanded"
38
38
  }
39
+ enum MuteState {
40
+ Muted = "muted",
41
+ Unmuted = "unmuted",
42
+ Disabled = "disabled"
43
+ }
39
44
  interface IAppProps {
40
45
  }
41
46
  interface IAppState {
@@ -64,7 +69,7 @@ declare namespace pxt.editor {
64
69
  showParts?: boolean;
65
70
  fullscreen?: boolean;
66
71
  showMiniSim?: boolean;
67
- mute?: boolean;
72
+ mute?: MuteState;
68
73
  embedSimView?: boolean;
69
74
  editorPosition?: {
70
75
  lineNumber: number;
@@ -275,7 +280,7 @@ declare namespace pxt.editor {
275
280
  toggleTrace(intervalSpeed?: number): void;
276
281
  setTrace(enabled: boolean, intervalSpeed?: number): void;
277
282
  toggleMute(): void;
278
- setMute(on: boolean): void;
283
+ setMute(state: MuteState): void;
279
284
  openInstructions(): void;
280
285
  closeFlyout(): void;
281
286
  printCode(): void;
@@ -19,6 +19,12 @@ var pxt;
19
19
  ErrorListState["HeaderOnly"] = "errorListHeader";
20
20
  ErrorListState["Expanded"] = "errorListExpanded";
21
21
  })(ErrorListState = editor.ErrorListState || (editor.ErrorListState = {}));
22
+ let MuteState;
23
+ (function (MuteState) {
24
+ MuteState["Muted"] = "muted";
25
+ MuteState["Unmuted"] = "unmuted";
26
+ MuteState["Disabled"] = "disabled";
27
+ })(MuteState = editor.MuteState || (editor.MuteState = {}));
22
28
  let FilterState;
23
29
  (function (FilterState) {
24
30
  FilterState[FilterState["Hidden"] = 0] = "Hidden";
package/built/pxtlib.d.ts CHANGED
@@ -727,7 +727,6 @@ declare namespace pxt.BrowserUtils {
727
727
  export function noSharedLocalStorage(): boolean;
728
728
  export function useOldTutorialLayout(): boolean;
729
729
  export function hasPointerEvents(): boolean;
730
- export function hasSaveAs(): boolean;
731
730
  export function os(): string;
732
731
  export function browser(): string;
733
732
  export function browserVersion(): string;