Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/microsoft/pxt into thspar…
Browse files Browse the repository at this point in the history
…ks/teachertool/update_catalog
  • Loading branch information
thsparks committed May 3, 2024
2 parents fe185fc + ffec97e commit c2c2bde
Show file tree
Hide file tree
Showing 60 changed files with 538 additions and 487 deletions.
32 changes: 16 additions & 16 deletions common-docs/teachertool/catalog-shared.json
Expand Up @@ -20,22 +20,6 @@
}
]
},
{
"id": "D21D76A2-D9FD-4F9B-B0AC-973CB870EA78",
"use": "variable_set",
"template": "At least one custom variable is set",
"docPath": "/teachertool",
"description": "At least one user-defined variable is set to a value.",
"maxCount": 1
},
{
"id": "0173898D-8A48-4266-AAB9-CE934471A734",
"use": "variable_accessed",
"template": "At least one variable is accessed",
"docPath": "/teachertool",
"description": "At least one variable's value is read.",
"maxCount": 1
},
{
"id": "7AE7EA2A-3AC8-42DC-89DB-65E3AE157156",
"use": "block_comment_used",
Expand Down Expand Up @@ -83,6 +67,22 @@
"default": 1
}
]
},
{
"id": "0DFA44C8-3CA5-4C77-946E-AF09F6C03879",
"use": "variable_usage",
"template": "Uses at least ${count} variables",
"docPath": "/teachertool",
"description": "The program creates and uses at least this many user-defined variables.",
"maxCount": 1,
"params": [
{
"name": "count",
"type": "number",
"paths": ["checks[0].count"],
"default": 1
}
]
}
]
}
44 changes: 12 additions & 32 deletions common-docs/teachertool/validator-plans-shared.json
Expand Up @@ -86,38 +86,6 @@
}
]
},
{
".desc": "A variable's value is set",
"name": "variable_set",
"threshold": 1,
"checks": [
{
"validator": "blocksExist",
"blockCounts": [
{
"blockId": "variables_set",
"count": 1
}
]
}
]
},
{
".desc": "A variable's value is used",
"name": "variable_accessed",
"threshold": 1,
"checks": [
{
"validator": "blocksExist",
"blockCounts": [
{
"blockId": "variables_get",
"count": 1
}
]
}
]
},
{
".desc": "A parameter's value is used",
"name": "parameter_variable_accessed",
Expand Down Expand Up @@ -302,6 +270,18 @@
}
]
},
{
".desc": "Variable creation & usage validation, with optional name filtering",
"name": "variable_usage",
"threshold": 1,
"checks": [
{
"validator": "variableUsage",
"count": 0,
"name": ""
}
]
},
{
".desc": "The zero number block is present.",
"name": "number_zero",
Expand Down
6 changes: 6 additions & 0 deletions localtypings/validatorPlan.d.ts
Expand Up @@ -40,6 +40,12 @@ declare namespace pxt.blocks {
count: number;
}

export interface VariableUsageValidatorCheck extends ValidatorCheckBase {
validator: "variableUsage";
count: number;
name?: string;
}

export interface EvaluationResult {
result?: boolean;
notes?: string;
Expand Down
20 changes: 20 additions & 0 deletions pxteditor/code-validation/runValidatorPlan.ts
Expand Up @@ -8,6 +8,7 @@ import { validateBlocksInSetExist } from "./validateBlocksInSetExist";
import { validateBlockCommentsExist } from "./validateCommentsExist";
import { validateSpecificBlockCommentsExist } from "./validateSpecificBlockCommentsExist";
import { getNestedChildBlocks } from "./getNestedChildBlocks";
import { validateVariableUsage } from "./validateVariableUsage";

export function runValidatorPlan(usedBlocks: Blockly.Block[], plan: pxt.blocks.ValidatorPlan, planLib: pxt.blocks.ValidatorPlan[]): boolean {
const startTime = Date.now();
Expand All @@ -32,6 +33,9 @@ export function runValidatorPlan(usedBlocks: Blockly.Block[], plan: pxt.blocks.V
case "blockFieldValueExists":
[successfulBlocks, checkPassed] = [...runBlockFieldValueExistsValidation(usedBlocks, check as pxt.blocks.BlockFieldValueExistsCheck)];
break;
case "variableUsage":
[successfulBlocks, checkPassed] = [...runVariableUsageValidation(usedBlocks, check as pxt.blocks.VariableUsageValidatorCheck)];
break;
default:
pxt.debug(`Unrecognized validator: ${check.validator}`);
checkPassed = false;
Expand Down Expand Up @@ -104,3 +108,19 @@ function runBlockFieldValueExistsValidation(usedBlocks: Blockly.Block[], inputs:
});
return [blockResults.successfulBlocks, blockResults.passed];
}

function runVariableUsageValidation(usedBlocks: Blockly.Block[], inputs: pxt.blocks.VariableUsageValidatorCheck): [Blockly.Block[], boolean] {
const blockResults = validateVariableUsage({
usedBlocks,
count: inputs.count,
name: inputs.name
});

// Flatten the map of passing variable definition blocks
const passingVarDefinitions: Blockly.Block[] = [];
for (const blocks of blockResults.passingVarDefinitions.values()) {
passingVarDefinitions.push(...blocks);
}

return [passingVarDefinitions, blockResults.passed];
}
55 changes: 55 additions & 0 deletions pxteditor/code-validation/validateVariableUsage.ts
@@ -0,0 +1,55 @@
/// <reference path="../../localtypings/pxtpackage.d.ts" />
import * as Blockly from "blockly";

// Validates that variables are created and used within the workspace.
// Name is optional. If undefined or empty, all variable names are permitted.
// Returns the definition blocks for variables that passed the check.
export function validateVariableUsage({
usedBlocks,
count,
name,
}: {
usedBlocks: Blockly.Block[];
count: number;
name?: String;
}): {
passingVarDefinitions: Map<string, Blockly.Block[]>;
passed: boolean;
} {
const varDefinitionBlocks: Map<string, Blockly.Block[]> = new Map();
const usedVars: Set<string> = new Set();

for (const block of usedBlocks) {
if (!block.isEnabled()) {
continue;
}

const varsUsed = block.getVarModels();
for (const varModel of varsUsed ?? []) {
const varName = varModel.name;
if (!name || varName === name) {
if (block.type === "variables_set" || block.type === "variables_change") {
// Variable created
if (!varDefinitionBlocks.has(varName)) {
varDefinitionBlocks.set(varName, []);
}
varDefinitionBlocks.get(varName).push(block);
} else {
// Variable used
usedVars.add(varName);
}
}
}
}

// Var passes check if it is both used and defined.
// We return the definition blocks to allow for recursively checking how the var was set.
const passingVarDefinitions = new Map<string, Blockly.Block[]>();
for (const [varName, definitionBlocks] of varDefinitionBlocks) {
if (usedVars.has(varName)) {
passingVarDefinitions.set(varName, definitionBlocks);
}
}

return { passingVarDefinitions, passed: passingVarDefinitions.size >= count };
}
8 changes: 4 additions & 4 deletions teachertool/src/App.tsx
Expand Up @@ -11,8 +11,8 @@ import { Toasts } from "./components/Toasts";
import { showToast } from "./transforms/showToast";
import { loadCatalogAsync } from "./transforms/loadCatalogAsync";
import { loadValidatorPlansAsync } from "./transforms/loadValidatorPlansAsync";
import { tryLoadLastActiveRubricAsync } from "./transforms/tryLoadLastActiveRubricAsync";
import { ImportRubricModal } from "./components/ImportRubricModal";
import { tryLoadLastActiveChecklistAsync } from "./transforms/tryLoadLastActiveChecklistAsync";
import { ImportChecklistModal } from "./components/ImportChecklistModal";
import { ConfirmationModal } from "./components/ConfirmationModal";
import { BlockPickerModal } from "./components/BlockPickerModal";
import { ScreenReaderAnnouncer } from "./components/ScreenReaderAnnouncer";
Expand All @@ -33,7 +33,7 @@ export const App = () => {
// Load catalog and validator plans into state.
await loadCatalogAsync();
await loadValidatorPlansAsync();
await tryLoadLastActiveRubricAsync();
await tryLoadLastActiveChecklistAsync();

// Test notification
showToast({
Expand All @@ -57,7 +57,7 @@ export const App = () => {
<>
<HeaderBar />
<MainPanel />
<ImportRubricModal />
<ImportChecklistModal />
<ConfirmationModal />
<BlockPickerModal />
<Toasts />
Expand Down
@@ -1,28 +1,28 @@
import { useContext } from "react";
import { Strings } from "../constants";
import { AppStateContext } from "../state/appStateContext";
import { setRubricName } from "../transforms/setRubricName";
import { setChecklistName } from "../transforms/setChecklistName";
import { DebouncedInput } from "./DebouncedInput";
import { AddCriteriaButton } from "./AddCriteriaButton";
import css from "./styling/ActiveRubricDisplay.module.scss";
import css from "./styling/ActiveChecklistDisplay.module.scss";
import React from "react";
import { CriteriaTable } from "./CriteriaTable";

interface ActiveRubricDisplayProps {}
export const ActiveRubricDisplay: React.FC<ActiveRubricDisplayProps> = ({}) => {
interface ActiveChecklistDisplayProps {}
export const ActiveChecklistDisplay: React.FC<ActiveChecklistDisplayProps> = ({}) => {
const { state: teacherTool } = useContext(AppStateContext);

return (
<div className={css["rubric-display"]}>
<div className={css["rubric-name-input-container"]}>
<div className={css["checklist-display"]}>
<div className={css["checklist-name-input-container"]}>
<DebouncedInput
label={Strings.Name}
ariaLabel={Strings.Name}
onChange={setRubricName}
placeholder={Strings.RubricName}
initialValue={teacherTool.rubric.name}
onChange={setChecklistName}
placeholder={Strings.ChecklistName}
initialValue={teacherTool.checklist.name}
preserveValueOnBlur={true}
className={css["rubric-name-input"]}
className={css["checklist-name-input"]}
/>
</div>
<CriteriaTable />
Expand Down
8 changes: 4 additions & 4 deletions teachertool/src/components/CatalogOverlay.tsx
@@ -1,6 +1,6 @@
import { useContext, useMemo, useState } from "react";
import { AppStateContext } from "../state/appStateContext";
import { addCriteriaToRubric } from "../transforms/addCriteriaToRubric";
import { addCriteriaToChecklist } from "../transforms/addCriteriaToChecklist";
import { CatalogCriteria } from "../types/criteria";
import { getCatalogCriteria } from "../state/helpers";
import { ReadOnlyCriteriaDisplay } from "./ReadonlyCriteriaDisplay";
Expand Down Expand Up @@ -75,7 +75,7 @@ const CatalogList: React.FC = () => {

const criteria = useMemo<CatalogCriteria[]>(
() => getCatalogCriteria(teacherTool),
[teacherTool.catalog, teacherTool.rubric]
[teacherTool.catalog, teacherTool.checklist]
);

function updateRecentlyAddedValue(id: string, value: NodeJS.Timeout | undefined) {
Expand All @@ -91,7 +91,7 @@ const CatalogList: React.FC = () => {
}

function onItemClicked(c: CatalogCriteria) {
addCriteriaToRubric([c.id]);
addCriteriaToChecklist([c.id]);

// Set a timeout to remove the recently added indicator
// and save it in the state.
Expand All @@ -109,7 +109,7 @@ const CatalogList: React.FC = () => {
return (
<div className={css["catalog-list"]}>
{criteria.map(c => {
const existingInstanceCount = teacherTool.rubric.criteria.filter(
const existingInstanceCount = teacherTool.checklist.criteria.filter(
i => i.catalogCriteriaId === c.id
).length;
const isMaxed = c.maxCount !== undefined && existingInstanceCount >= c.maxCount;
Expand Down
23 changes: 23 additions & 0 deletions teachertool/src/components/ChecklistPreview.tsx
@@ -0,0 +1,23 @@
import { getCatalogCriteriaWithId } from "../state/helpers";
import { Checklist } from "../types/checklist";
import css from "./styling/ChecklistPreview.module.scss";

export interface IChecklistPreviewProps {
checklist: Checklist;
}

export const ChecklistPreview: React.FC<IChecklistPreviewProps> = ({ checklist }) => {
return (
<div className={css["container"]}>
<div className={css["checklist-header"]}>{checklist.name}</div>
{checklist.criteria.map((c, i) => {
const template = getCatalogCriteriaWithId(c.catalogCriteriaId)?.template;
return template ? (
<div key={i} className={css["checklist-criteria"]}>
{template}
</div>
) : null;
})}
</div>
);
};

0 comments on commit c2c2bde

Please sign in to comment.