pxt-core 9.3.13 → 9.3.15

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 (42) hide show
  1. package/README.md +11 -0
  2. package/built/cli.js +7 -2
  3. package/built/pxt.js +57 -3
  4. package/built/pxtblockly.js +97 -57
  5. package/built/pxtblocks.d.ts +59 -21
  6. package/built/pxtblocks.js +97 -57
  7. package/built/pxtlib.d.ts +22 -0
  8. package/built/pxtlib.js +50 -1
  9. package/built/target.js +1 -1
  10. package/built/tests/blocksrunner.js +1 -1
  11. package/built/web/main.js +1 -1
  12. package/built/web/multiplayer/js/{main.78cecdcb.js → main.75ca8c58.js} +2 -2
  13. package/built/web/pxtapp.js +1 -1
  14. package/built/web/pxtasseteditor.js +1 -1
  15. package/built/web/pxtblockly.js +2 -2
  16. package/built/web/pxtblocks.js +1 -1
  17. package/built/web/pxtembed.js +2 -2
  18. package/built/web/pxtlib.js +1 -1
  19. package/built/web/pxtworker.js +1 -1
  20. package/built/web/rtlsemantic.css +1 -1
  21. package/built/web/runnerembed.js +1 -0
  22. package/built/web/semantic.css +1 -1
  23. package/built/web/skillmap/js/{main.8222bb34.js → main.236bd49e.js} +2 -2
  24. package/built/web/teachertool/css/main.e9386f28.css +1 -0
  25. package/built/web/teachertool/js/{main.3a94a341.js → main.8aa6604c.js} +2 -2
  26. package/localtypings/projectheader.d.ts +21 -0
  27. package/{built → localtypings}/pxteditor.d.ts +1095 -1090
  28. package/package.json +1 -1
  29. package/react-common/components/controls/MenuDropdown.tsx +5 -2
  30. package/react-common/components/util.tsx +1 -1
  31. package/theme/pxt.less +1 -0
  32. package/theme/themepacks.less +41 -0
  33. package/webapp/public/embed.js +1 -1
  34. package/webapp/public/multiplayer.html +1 -1
  35. package/webapp/public/skillmap.html +1 -1
  36. package/webapp/public/teachertool.html +1 -3
  37. package/built/pxteditor.js +0 -1834
  38. package/built/pxtrunner.d.ts +0 -151
  39. package/built/pxtrunner.js +0 -2626
  40. package/built/web/pxteditor.js +0 -1
  41. package/built/web/pxtrunner.js +0 -1
  42. package/built/web/teachertool/css/main.59776cd1.css +0 -1
package/README.md CHANGED
@@ -114,6 +114,17 @@ npm install -g svgo
114
114
  svgo svgicons/myicon.svg
115
115
  ```
116
116
 
117
+ ### Shared Styling
118
+
119
+ When adding a CSS color or other style element that will be shared across editor targets (e.g. micro:bit, Arcade) and sub-applications (a.k.a. "CRAs", like skillmap, teachertool, etc.). Declare a CSS variable for it in `theme/themepacks.less`:
120
+
121
+ 1. Add the new variable to the `:root` pseudo-class. Choose a reasonable default value according to the guidlines in the file.
122
+ 2. Add the new variable to all theme classes defined in that file. At the time of this writing, only `theme-highcontrast` is defined. Choose a value that works well for the given theme.
123
+ 3. Add the new variable to the theme overrides for each target. This will be done in the target repo's `theme/themepacks.less` file (e.g. pxt-microbit, pxt-arcade).
124
+
125
+ Variables declared this way will be available to CRAs at runtime, and they will be initialized with the override values defined by the target in which they're running.
126
+
127
+
117
128
  ### Documentation Highlighting
118
129
 
119
130
  In the documentation, highlighting of code snippets uses highlight.js (hljs).
package/built/cli.js CHANGED
@@ -2403,8 +2403,13 @@ async function buildAndWatchTargetAsync(includeSourceMaps, rebundle) {
2403
2403
  }
2404
2404
  else {
2405
2405
  console.log("rebuilding css");
2406
- await buildSemanticUIAsync();
2407
- console.log("css build complete");
2406
+ try {
2407
+ await buildSemanticUIAsync();
2408
+ console.log("css build complete");
2409
+ }
2410
+ catch (e) {
2411
+ console.error("css build failed", e);
2412
+ }
2408
2413
  }
2409
2414
  return lessFiles;
2410
2415
  };
package/built/pxt.js CHANGED
@@ -99066,6 +99066,11 @@ var ts;
99066
99066
  }
99067
99067
  }
99068
99068
  Util.fileReadAsTextAsync = fileReadAsTextAsync;
99069
+ function sanitizeFileName(name) {
99070
+ /* eslint-disable no-control-regex */
99071
+ return name.replace(/[()\\\/.,?*^:<>!;'#$%^&|"@+=«»°{}\[\]¾½¼³²¦¬¤¢£~­¯¸`±\x00-\x1F]/g, '').trim().replace(/\s+/g, '-');
99072
+ }
99073
+ Util.sanitizeFileName = sanitizeFileName;
99069
99074
  function repeatMap(n, fn) {
99070
99075
  n = n || 0;
99071
99076
  let r = [];
@@ -100104,7 +100109,7 @@ var ts;
100104
100109
  "sk": { englishName: "Slovak", localizedName: "Slovenčina" },
100105
100110
  "sl": { englishName: "Slovenian", localizedName: "Slovenski" },
100106
100111
  "sq": { englishName: "Albanian", localizedName: "shqip" },
100107
- "sr": { englishName: "Serbian (Latin)", localizedName: "Srpski" },
100112
+ "sr": { englishName: "Serbian (Cyrillic)", localizedName: "Srpski" },
100108
100113
  "su": { englishName: "Sundanese", localizedName: "ᮘᮞ ᮞᮥᮔ᮪ᮓ" },
100109
100114
  "sv-SE": { englishName: "Swedish", localizedName: "Svenska" },
100110
100115
  "sw": { englishName: "Swahili", localizedName: "Kiswahili" },
@@ -101617,6 +101622,7 @@ var pxt;
101617
101622
  var blocks;
101618
101623
  (function (blocks) {
101619
101624
  const THIS_NAME = "this";
101625
+ blocks.showBlockIdInTooltip = false;
101620
101626
  // The JS Math functions supported in the blocks. The order of this array
101621
101627
  // determines the order of the dropdown in the math_js_op block
101622
101628
  blocks.MATH_FUNCTIONS = {
@@ -102318,6 +102324,21 @@ var pxt;
102318
102324
  }));
102319
102325
  });
102320
102326
  }
102327
+ if (pxt.blocks.showBlockIdInTooltip) {
102328
+ for (const id of Object.keys(_blockDefinitions)) {
102329
+ const tooltip = _blockDefinitions[id].tooltip;
102330
+ if (typeof tooltip === "object" && tooltip !== null) {
102331
+ for (const innerKey in tooltip) {
102332
+ if (tooltip.hasOwnProperty(innerKey)) {
102333
+ _blockDefinitions[id].tooltip[innerKey] = `${tooltip[innerKey]} (id: ${id})`;
102334
+ }
102335
+ }
102336
+ }
102337
+ else {
102338
+ _blockDefinitions[id].tooltip = `${_blockDefinitions[id].tooltip} (id: ${id})`;
102339
+ }
102340
+ }
102341
+ }
102321
102342
  }
102322
102343
  })(blocks = pxt.blocks || (pxt.blocks = {}));
102323
102344
  })(pxt || (pxt = {}));
@@ -103680,6 +103701,26 @@ var pxt;
103680
103701
  }
103681
103702
  }
103682
103703
  BrowserUtils.legacyCopyText = legacyCopyText;
103704
+ /**
103705
+ * Sets the theme of the application by adding a class to the body. Themes
103706
+ * are defined in CSS variable packs. The default theme is defined in
103707
+ * `themes/themepacks.less`, in the `:root` pseudoclass. `highcontrast` is
103708
+ * also defined there. Target-specific themes are defined in the target
103709
+ * repo's `theme/themepack.less`.
103710
+ */
103711
+ function setApplicationTheme(theme) {
103712
+ const body = document.body;
103713
+ const classes = body.classList;
103714
+ for (let i = 0; i < classes.length; i++) {
103715
+ if (/^theme-/.test(classes[i])) {
103716
+ body.classList.remove(classes[i]);
103717
+ }
103718
+ }
103719
+ if (theme) {
103720
+ body.classList.add(`theme-${theme}`);
103721
+ }
103722
+ }
103723
+ BrowserUtils.setApplicationTheme = setApplicationTheme;
103683
103724
  })(BrowserUtils = pxt.BrowserUtils || (pxt.BrowserUtils = {}));
103684
103725
  })(pxt || (pxt = {}));
103685
103726
  var pxt;
@@ -117164,6 +117205,14 @@ var pxt;
117164
117205
  })(svgUtil = pxt.svgUtil || (pxt.svgUtil = {}));
117165
117206
  })(pxt || (pxt = {}));
117166
117207
  var pxt;
117208
+ (function (pxt) {
117209
+ var editor;
117210
+ (function (editor) {
117211
+ editor.initExtensionsAsync = opts => Promise.resolve({});
117212
+ editor.initFieldExtensionsAsync = opts => Promise.resolve({});
117213
+ })(editor = pxt.editor || (pxt.editor = {}));
117214
+ })(pxt || (pxt = {}));
117215
+ var pxt;
117167
117216
  (function (pxt) {
117168
117217
  pxt.IMAGE_MIME_TYPE = "image/x-mkcd-f4";
117169
117218
  pxt.TILEMAP_MIME_TYPE = "application/mkcd-tilemap";
@@ -162971,8 +163020,13 @@ async function buildAndWatchTargetAsync(includeSourceMaps, rebundle) {
162971
163020
  }
162972
163021
  else {
162973
163022
  console.log("rebuilding css");
162974
- await buildSemanticUIAsync();
162975
- console.log("css build complete");
163023
+ try {
163024
+ await buildSemanticUIAsync();
163025
+ console.log("css build complete");
163026
+ }
163027
+ catch (e) {
163028
+ console.error("css build failed", e);
163029
+ }
162976
163030
  }
162977
163031
  return lessFiles;
162978
163032
  };
@@ -7919,7 +7919,7 @@ var pxt;
7919
7919
  const hasHandlers = hasArrowFunction(fn);
7920
7920
  block.setPreviousStatement(!(hasHandlers && !fn.attributes.handlerStatement) && fn.retType == "void");
7921
7921
  block.setNextStatement(!(hasHandlers && !fn.attributes.handlerStatement) && fn.retType == "void");
7922
- block.setTooltip(/^__/.test(fn.namespace) ? "" : fn.attributes.jsDoc);
7922
+ block.setTooltip((/^__/.test(fn.namespace) ? "" : fn.attributes.jsDoc) + (pxt.blocks.showBlockIdInTooltip ? " (id: '" + fn.attributes.blockId + "')" : ""));
7923
7923
  function buildBlockFromDef(def, expanded = false) {
7924
7924
  let anonIndex = 0;
7925
7925
  let firstParam = !expanded && !!comp.thisParameter;
@@ -11525,66 +11525,56 @@ var pxt;
11525
11525
  (function (pxt) {
11526
11526
  var blocks;
11527
11527
  (function (blocks) {
11528
- class Rubric {
11529
- constructor(criteria) {
11530
- this.criteria = criteria;
11531
- }
11532
- }
11533
- blocks.Rubric = Rubric;
11534
- function parseRubric(rubric) {
11535
- let rubricObj;
11536
- try {
11537
- rubricObj = JSON.parse(rubric);
11538
- }
11539
- catch (e) {
11540
- console.error(`Error parsing rubric! ${e}`);
11541
- return null;
11542
- }
11543
- if (!rubricObj.criteria) {
11544
- console.error(`No criteria found in rubric`);
11545
- return null;
11546
- }
11547
- const criteria = rubricObj.criteria.map((c) => {
11548
- return blocks.getCriteria(c);
11549
- }).filter((r) => !!r);
11550
- return new Rubric(criteria);
11528
+ const maxConcurrentChecks = 4;
11529
+ async function runValidatorPlanAsync(usedBlocks, plan) {
11530
+ // Each plan can have multiple checks it needs to run.
11531
+ // Run all of them in parallel, and then check if the number of successes is greater than the specified threshold.
11532
+ // TBD if it's faster to run in parallel without short-circuiting once the threshold is reached, or if it's faster to run sequentially and short-circuit.
11533
+ const startTime = Date.now();
11534
+ const checkRuns = pxt.Util.promisePoolAsync(maxConcurrentChecks, plan.checks, async (check) => {
11535
+ switch (check.validator) {
11536
+ case "blocksExist":
11537
+ return runBlocksExistValidation(usedBlocks, check);
11538
+ case "blockCommentsExist":
11539
+ return runValidateBlockCommentsExist(usedBlocks, check);
11540
+ case "specificBlockCommentsExist":
11541
+ return runValidateSpecificBlockCommentsExist(usedBlocks, check);
11542
+ case "blocksInSetExist":
11543
+ return runBlocksInSetExistValidation(usedBlocks, check);
11544
+ default:
11545
+ pxt.debug(`Unrecognized validator: ${check.validator}`);
11546
+ return false;
11547
+ }
11548
+ });
11549
+ const results = await checkRuns;
11550
+ const successCount = results.filter((r) => r).length;
11551
+ const passed = successCount >= plan.threshold;
11552
+ pxt.tickEvent("validation.evaluation_complete", {
11553
+ plan: plan.name,
11554
+ durationMs: Date.now() - startTime,
11555
+ passed: `${passed}`,
11556
+ });
11557
+ return passed;
11551
11558
  }
11552
- blocks.parseRubric = parseRubric;
11553
- })(blocks = pxt.blocks || (pxt.blocks = {}));
11554
- })(pxt || (pxt = {}));
11555
- var pxt;
11556
- (function (pxt) {
11557
- var blocks;
11558
- (function (blocks) {
11559
- function getCriteria(data) {
11560
- switch (data.criteriaId) {
11561
- case "blockCheck":
11562
- return new BlockCheckCriteria(data.displayText, data.blockRequirements);
11563
- case "comment":
11564
- return new CommentCriteria(data.displayText, data.count);
11565
- default:
11566
- console.error(`Unrecognized criteriaId: ${data.criteriaId}`);
11567
- return null;
11568
- }
11559
+ blocks.runValidatorPlanAsync = runValidatorPlanAsync;
11560
+ function runBlocksExistValidation(usedBlocks, inputs) {
11561
+ const blockResults = blocks.validateBlocksExist({ usedBlocks, requiredBlockCounts: inputs.blockCounts });
11562
+ const success = blockResults.disabledBlocks.length === 0 &&
11563
+ blockResults.missingBlocks.length === 0 &&
11564
+ blockResults.insufficientBlocks.length === 0;
11565
+ return success;
11569
11566
  }
11570
- blocks.getCriteria = getCriteria;
11571
- class RubricCriteria {
11572
- constructor(displayText) {
11573
- this.displayText = displayText;
11574
- }
11567
+ function runValidateBlockCommentsExist(usedBlocks, inputs) {
11568
+ const blockResults = blocks.validateBlockCommentsExist({ usedBlocks, numRequired: inputs.count });
11569
+ return blockResults.passed;
11575
11570
  }
11576
- blocks.RubricCriteria = RubricCriteria;
11577
- class BlockCheckCriteria extends RubricCriteria {
11578
- constructor(displayText, blockRequirements) {
11579
- super(displayText);
11580
- this.blockRequirements = blockRequirements;
11581
- }
11571
+ function runValidateSpecificBlockCommentsExist(usedBlocks, inputs) {
11572
+ const blockResults = blocks.validateSpecificBlockCommentsExist({ usedBlocks, blockType: inputs.blockType });
11573
+ return blockResults.passed;
11582
11574
  }
11583
- class CommentCriteria extends RubricCriteria {
11584
- constructor(displayText, count) {
11585
- super(displayText);
11586
- this.count = count;
11587
- }
11575
+ function runBlocksInSetExistValidation(usedBlocks, inputs) {
11576
+ const blockResults = blocks.validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
11577
+ return blockResults.passed;
11588
11578
  }
11589
11579
  })(blocks = pxt.blocks || (pxt.blocks = {}));
11590
11580
  })(pxt || (pxt = {}));
@@ -11624,6 +11614,56 @@ var pxt;
11624
11614
  blocks.validateBlocksExist = validateBlocksExist;
11625
11615
  })(blocks = pxt.blocks || (pxt.blocks = {}));
11626
11616
  })(pxt || (pxt = {}));
11617
+ var pxt;
11618
+ (function (pxt) {
11619
+ var blocks;
11620
+ (function (blocks) {
11621
+ // validates that a combination of blocks in the set satisfies the required count
11622
+ // returns the blocks that make the validator pass
11623
+ function validateBlocksInSetExist({ usedBlocks, blockIdsToCheck, count, requireUnique }) {
11624
+ const successfulBlocks = [];
11625
+ const enabledBlocks = usedBlocks.filter((block) => block.isEnabled());
11626
+ for (const block of blockIdsToCheck) {
11627
+ const blockInstances = enabledBlocks.filter((b) => b.type === block);
11628
+ if (requireUnique && blockInstances.length >= 1) {
11629
+ successfulBlocks.push(blockInstances[0]);
11630
+ }
11631
+ else {
11632
+ successfulBlocks.push(...blockInstances);
11633
+ }
11634
+ }
11635
+ return { successfulBlocks, passed: successfulBlocks.length >= count };
11636
+ }
11637
+ blocks.validateBlocksInSetExist = validateBlocksInSetExist;
11638
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
11639
+ })(pxt || (pxt = {}));
11640
+ var pxt;
11641
+ (function (pxt) {
11642
+ var blocks;
11643
+ (function (blocks) {
11644
+ // validates that one or more blocks comments are in the project
11645
+ // returns the blocks that have comments for teacher tool scenario
11646
+ function validateBlockCommentsExist({ usedBlocks, numRequired }) {
11647
+ const commentedBlocks = usedBlocks.filter((block) => !!block.getCommentText());
11648
+ return { commentedBlocks, passed: commentedBlocks.length >= numRequired };
11649
+ }
11650
+ blocks.validateBlockCommentsExist = validateBlockCommentsExist;
11651
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
11652
+ })(pxt || (pxt = {}));
11653
+ var pxt;
11654
+ (function (pxt) {
11655
+ var blocks;
11656
+ (function (blocks) {
11657
+ // validates that all of a specific block type have comments
11658
+ // returns the blocks that do not have comments for a tutorial validation scenario
11659
+ function validateSpecificBlockCommentsExist({ usedBlocks, blockType }) {
11660
+ const allSpecifcBlocks = usedBlocks.filter((block) => block.type === blockType);
11661
+ const uncommentedBlocks = allSpecifcBlocks.filter((block) => !block.getCommentText());
11662
+ return { uncommentedBlocks, passed: uncommentedBlocks.length === 0 };
11663
+ }
11664
+ blocks.validateSpecificBlockCommentsExist = validateSpecificBlockCommentsExist;
11665
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
11666
+ })(pxt || (pxt = {}));
11627
11667
  var pxtblockly;
11628
11668
  (function (pxtblockly) {
11629
11669
  class FieldBase extends Blockly.Field {
@@ -393,30 +393,12 @@ declare namespace pxt.blocks {
393
393
  function initMathRoundBlock(): void;
394
394
  }
395
395
  declare namespace pxt.blocks {
396
- class Rubric {
397
- criteria: RubricCriteria[];
398
- constructor(criteria: RubricCriteria[]);
396
+ interface EvaluationResult {
397
+ result: boolean;
399
398
  }
400
- function parseRubric(rubric: string): Rubric;
401
399
  }
402
400
  declare namespace pxt.blocks {
403
- interface BlockSet {
404
- blocks: string[];
405
- count: number;
406
- }
407
- export interface CriteriaData {
408
- criteriaId: string;
409
- displayText: string;
410
- blockRequirements?: BlockSet[];
411
- count?: number;
412
- }
413
- export function getCriteria(data: CriteriaData): RubricCriteria;
414
- export abstract class RubricCriteria {
415
- displayText: string;
416
- abstract criteriaId: string;
417
- constructor(displayText: string);
418
- }
419
- export {};
401
+ function runValidatorPlanAsync(usedBlocks: Blockly.Block[], plan: ValidatorPlan): Promise<boolean>;
420
402
  }
421
403
  declare namespace pxt.blocks {
422
404
  function validateBlocksExist({ usedBlocks, requiredBlockCounts }: {
@@ -428,6 +410,62 @@ declare namespace pxt.blocks {
428
410
  insufficientBlocks: string[];
429
411
  };
430
412
  }
413
+ declare namespace pxt.blocks {
414
+ function validateBlocksInSetExist({ usedBlocks, blockIdsToCheck, count, requireUnique }: {
415
+ usedBlocks: Blockly.Block[];
416
+ blockIdsToCheck: string[];
417
+ count: number;
418
+ requireUnique?: boolean;
419
+ }): {
420
+ successfulBlocks: Blockly.Block[];
421
+ passed: boolean;
422
+ };
423
+ }
424
+ declare namespace pxt.blocks {
425
+ function validateBlockCommentsExist({ usedBlocks, numRequired }: {
426
+ usedBlocks: Blockly.Block[];
427
+ numRequired: number;
428
+ }): {
429
+ commentedBlocks: Blockly.Block[];
430
+ passed: boolean;
431
+ };
432
+ }
433
+ declare namespace pxt.blocks {
434
+ function validateSpecificBlockCommentsExist({ usedBlocks, blockType }: {
435
+ usedBlocks: Blockly.Block[];
436
+ blockType: string;
437
+ }): {
438
+ uncommentedBlocks: Blockly.Block[];
439
+ passed: boolean;
440
+ };
441
+ }
442
+ declare namespace pxt.blocks {
443
+ interface ValidatorPlan {
444
+ name: string;
445
+ threshold: number;
446
+ checks: ValidatorCheckBase[];
447
+ }
448
+ interface ValidatorCheckBase {
449
+ validator: string;
450
+ }
451
+ interface BlocksExistValidatorCheck extends ValidatorCheckBase {
452
+ validator: "blocksExist";
453
+ blockCounts: pxt.Map<number>;
454
+ }
455
+ interface BlockCommentsExistValidatorCheck extends ValidatorCheckBase {
456
+ validator: "blockCommentsExist";
457
+ count: number;
458
+ }
459
+ interface SpecificBlockCommentsExistValidatorCheck extends ValidatorCheckBase {
460
+ validator: "specificBlockCommentsExist";
461
+ blockType: string;
462
+ }
463
+ interface BlocksInSetExistValidatorCheck extends ValidatorCheckBase {
464
+ validator: "blocksInSetExist";
465
+ blocks: string[];
466
+ count: number;
467
+ }
468
+ }
431
469
  declare namespace pxtblockly {
432
470
  abstract class FieldBase<U> extends Blockly.Field implements Blockly.FieldCustom {
433
471
  isFieldCustom_: true;
@@ -4357,7 +4357,7 @@ var pxt;
4357
4357
  const hasHandlers = hasArrowFunction(fn);
4358
4358
  block.setPreviousStatement(!(hasHandlers && !fn.attributes.handlerStatement) && fn.retType == "void");
4359
4359
  block.setNextStatement(!(hasHandlers && !fn.attributes.handlerStatement) && fn.retType == "void");
4360
- block.setTooltip(/^__/.test(fn.namespace) ? "" : fn.attributes.jsDoc);
4360
+ block.setTooltip((/^__/.test(fn.namespace) ? "" : fn.attributes.jsDoc) + (pxt.blocks.showBlockIdInTooltip ? " (id: '" + fn.attributes.blockId + "')" : ""));
4361
4361
  function buildBlockFromDef(def, expanded = false) {
4362
4362
  let anonIndex = 0;
4363
4363
  let firstParam = !expanded && !!comp.thisParameter;
@@ -7963,66 +7963,56 @@ var pxt;
7963
7963
  (function (pxt) {
7964
7964
  var blocks;
7965
7965
  (function (blocks) {
7966
- class Rubric {
7967
- constructor(criteria) {
7968
- this.criteria = criteria;
7969
- }
7970
- }
7971
- blocks.Rubric = Rubric;
7972
- function parseRubric(rubric) {
7973
- let rubricObj;
7974
- try {
7975
- rubricObj = JSON.parse(rubric);
7976
- }
7977
- catch (e) {
7978
- console.error(`Error parsing rubric! ${e}`);
7979
- return null;
7980
- }
7981
- if (!rubricObj.criteria) {
7982
- console.error(`No criteria found in rubric`);
7983
- return null;
7984
- }
7985
- const criteria = rubricObj.criteria.map((c) => {
7986
- return blocks.getCriteria(c);
7987
- }).filter((r) => !!r);
7988
- return new Rubric(criteria);
7966
+ const maxConcurrentChecks = 4;
7967
+ async function runValidatorPlanAsync(usedBlocks, plan) {
7968
+ // Each plan can have multiple checks it needs to run.
7969
+ // Run all of them in parallel, and then check if the number of successes is greater than the specified threshold.
7970
+ // TBD if it's faster to run in parallel without short-circuiting once the threshold is reached, or if it's faster to run sequentially and short-circuit.
7971
+ const startTime = Date.now();
7972
+ const checkRuns = pxt.Util.promisePoolAsync(maxConcurrentChecks, plan.checks, async (check) => {
7973
+ switch (check.validator) {
7974
+ case "blocksExist":
7975
+ return runBlocksExistValidation(usedBlocks, check);
7976
+ case "blockCommentsExist":
7977
+ return runValidateBlockCommentsExist(usedBlocks, check);
7978
+ case "specificBlockCommentsExist":
7979
+ return runValidateSpecificBlockCommentsExist(usedBlocks, check);
7980
+ case "blocksInSetExist":
7981
+ return runBlocksInSetExistValidation(usedBlocks, check);
7982
+ default:
7983
+ pxt.debug(`Unrecognized validator: ${check.validator}`);
7984
+ return false;
7985
+ }
7986
+ });
7987
+ const results = await checkRuns;
7988
+ const successCount = results.filter((r) => r).length;
7989
+ const passed = successCount >= plan.threshold;
7990
+ pxt.tickEvent("validation.evaluation_complete", {
7991
+ plan: plan.name,
7992
+ durationMs: Date.now() - startTime,
7993
+ passed: `${passed}`,
7994
+ });
7995
+ return passed;
7989
7996
  }
7990
- blocks.parseRubric = parseRubric;
7991
- })(blocks = pxt.blocks || (pxt.blocks = {}));
7992
- })(pxt || (pxt = {}));
7993
- var pxt;
7994
- (function (pxt) {
7995
- var blocks;
7996
- (function (blocks) {
7997
- function getCriteria(data) {
7998
- switch (data.criteriaId) {
7999
- case "blockCheck":
8000
- return new BlockCheckCriteria(data.displayText, data.blockRequirements);
8001
- case "comment":
8002
- return new CommentCriteria(data.displayText, data.count);
8003
- default:
8004
- console.error(`Unrecognized criteriaId: ${data.criteriaId}`);
8005
- return null;
8006
- }
7997
+ blocks.runValidatorPlanAsync = runValidatorPlanAsync;
7998
+ function runBlocksExistValidation(usedBlocks, inputs) {
7999
+ const blockResults = blocks.validateBlocksExist({ usedBlocks, requiredBlockCounts: inputs.blockCounts });
8000
+ const success = blockResults.disabledBlocks.length === 0 &&
8001
+ blockResults.missingBlocks.length === 0 &&
8002
+ blockResults.insufficientBlocks.length === 0;
8003
+ return success;
8007
8004
  }
8008
- blocks.getCriteria = getCriteria;
8009
- class RubricCriteria {
8010
- constructor(displayText) {
8011
- this.displayText = displayText;
8012
- }
8005
+ function runValidateBlockCommentsExist(usedBlocks, inputs) {
8006
+ const blockResults = blocks.validateBlockCommentsExist({ usedBlocks, numRequired: inputs.count });
8007
+ return blockResults.passed;
8013
8008
  }
8014
- blocks.RubricCriteria = RubricCriteria;
8015
- class BlockCheckCriteria extends RubricCriteria {
8016
- constructor(displayText, blockRequirements) {
8017
- super(displayText);
8018
- this.blockRequirements = blockRequirements;
8019
- }
8009
+ function runValidateSpecificBlockCommentsExist(usedBlocks, inputs) {
8010
+ const blockResults = blocks.validateSpecificBlockCommentsExist({ usedBlocks, blockType: inputs.blockType });
8011
+ return blockResults.passed;
8020
8012
  }
8021
- class CommentCriteria extends RubricCriteria {
8022
- constructor(displayText, count) {
8023
- super(displayText);
8024
- this.count = count;
8025
- }
8013
+ function runBlocksInSetExistValidation(usedBlocks, inputs) {
8014
+ const blockResults = blocks.validateBlocksInSetExist({ usedBlocks, blockIdsToCheck: inputs.blocks, count: inputs.count });
8015
+ return blockResults.passed;
8026
8016
  }
8027
8017
  })(blocks = pxt.blocks || (pxt.blocks = {}));
8028
8018
  })(pxt || (pxt = {}));
@@ -8062,6 +8052,56 @@ var pxt;
8062
8052
  blocks.validateBlocksExist = validateBlocksExist;
8063
8053
  })(blocks = pxt.blocks || (pxt.blocks = {}));
8064
8054
  })(pxt || (pxt = {}));
8055
+ var pxt;
8056
+ (function (pxt) {
8057
+ var blocks;
8058
+ (function (blocks) {
8059
+ // validates that a combination of blocks in the set satisfies the required count
8060
+ // returns the blocks that make the validator pass
8061
+ function validateBlocksInSetExist({ usedBlocks, blockIdsToCheck, count, requireUnique }) {
8062
+ const successfulBlocks = [];
8063
+ const enabledBlocks = usedBlocks.filter((block) => block.isEnabled());
8064
+ for (const block of blockIdsToCheck) {
8065
+ const blockInstances = enabledBlocks.filter((b) => b.type === block);
8066
+ if (requireUnique && blockInstances.length >= 1) {
8067
+ successfulBlocks.push(blockInstances[0]);
8068
+ }
8069
+ else {
8070
+ successfulBlocks.push(...blockInstances);
8071
+ }
8072
+ }
8073
+ return { successfulBlocks, passed: successfulBlocks.length >= count };
8074
+ }
8075
+ blocks.validateBlocksInSetExist = validateBlocksInSetExist;
8076
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
8077
+ })(pxt || (pxt = {}));
8078
+ var pxt;
8079
+ (function (pxt) {
8080
+ var blocks;
8081
+ (function (blocks) {
8082
+ // validates that one or more blocks comments are in the project
8083
+ // returns the blocks that have comments for teacher tool scenario
8084
+ function validateBlockCommentsExist({ usedBlocks, numRequired }) {
8085
+ const commentedBlocks = usedBlocks.filter((block) => !!block.getCommentText());
8086
+ return { commentedBlocks, passed: commentedBlocks.length >= numRequired };
8087
+ }
8088
+ blocks.validateBlockCommentsExist = validateBlockCommentsExist;
8089
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
8090
+ })(pxt || (pxt = {}));
8091
+ var pxt;
8092
+ (function (pxt) {
8093
+ var blocks;
8094
+ (function (blocks) {
8095
+ // validates that all of a specific block type have comments
8096
+ // returns the blocks that do not have comments for a tutorial validation scenario
8097
+ function validateSpecificBlockCommentsExist({ usedBlocks, blockType }) {
8098
+ const allSpecifcBlocks = usedBlocks.filter((block) => block.type === blockType);
8099
+ const uncommentedBlocks = allSpecifcBlocks.filter((block) => !block.getCommentText());
8100
+ return { uncommentedBlocks, passed: uncommentedBlocks.length === 0 };
8101
+ }
8102
+ blocks.validateSpecificBlockCommentsExist = validateSpecificBlockCommentsExist;
8103
+ })(blocks = pxt.blocks || (pxt.blocks = {}));
8104
+ })(pxt || (pxt = {}));
8065
8105
  var pxtblockly;
8066
8106
  (function (pxtblockly) {
8067
8107
  class FieldBase extends Blockly.Field {
package/built/pxtlib.d.ts CHANGED
@@ -276,6 +276,7 @@ declare namespace ts.pxtc.Util {
276
276
  export function blobReadAsDataURL(blob: Blob): Promise<string>;
277
277
  export function fileReadAsBufferAsync(f: File): Promise<Uint8Array>;
278
278
  export function fileReadAsTextAsync(f: File): Promise<string>;
279
+ export function sanitizeFileName(name: string): string;
279
280
  export function repeatMap<T>(n: number, fn: (index: number) => T): T[];
280
281
  export function listsEqual<T>(a: T[], b: T[]): boolean;
281
282
  export function oops(msg?: string): Error;
@@ -632,6 +633,7 @@ declare namespace pxt {
632
633
  function isOutputText(trg?: pxtc.CompileTarget): boolean;
633
634
  }
634
635
  declare namespace pxt.blocks {
636
+ let showBlockIdInTooltip: boolean;
635
637
  const MATH_FUNCTIONS: {
636
638
  unary: string[];
637
639
  binary: string[];
@@ -888,6 +890,14 @@ declare namespace pxt.BrowserUtils {
888
890
  export function setCookieLang(langId: string, docs?: boolean): void;
889
891
  export function cacheBustingUrl(url: string): string;
890
892
  export function legacyCopyText(element: HTMLInputElement | HTMLTextAreaElement): boolean;
893
+ /**
894
+ * Sets the theme of the application by adding a class to the body. Themes
895
+ * are defined in CSS variable packs. The default theme is defined in
896
+ * `themes/themepacks.less`, in the `:root` pseudoclass. `highcontrast` is
897
+ * also defined there. Target-specific themes are defined in the target
898
+ * repo's `theme/themepack.less`.
899
+ */
900
+ export function setApplicationTheme(theme: string | undefined): void;
891
901
  export {};
892
902
  }
893
903
  declare namespace pxt.cloud {
@@ -2710,6 +2720,18 @@ declare namespace pxt.svgUtil.helpers {
2710
2720
  protected rePosition(): void;
2711
2721
  }
2712
2722
  }
2723
+ declare namespace pxt.editor {
2724
+ let initExtensionsAsync: (opts: pxt.editor.ExtensionOptions) => Promise<pxt.editor.ExtensionResult>;
2725
+ let initFieldExtensionsAsync: (opts: pxt.editor.FieldExtensionOptions) => Promise<pxt.editor.FieldExtensionResult>;
2726
+ interface ExtensionOptions {
2727
+ }
2728
+ interface FieldExtensionResult {
2729
+ }
2730
+ interface ExtensionResult {
2731
+ }
2732
+ interface FieldExtensionOptions {
2733
+ }
2734
+ }
2713
2735
  declare namespace pxt {
2714
2736
  export const IMAGE_MIME_TYPE = "image/x-mkcd-f4";
2715
2737
  export const TILEMAP_MIME_TYPE = "application/mkcd-tilemap";