diff --git a/common-docs/teachertool/catalog-shared.json b/common-docs/teachertool/catalog-shared.json index 883162e77ca0..af52b385028d 100644 --- a/common-docs/teachertool/catalog-shared.json +++ b/common-docs/teachertool/catalog-shared.json @@ -6,6 +6,7 @@ "template": "${Block} used ${count} times", "description": "This block was used the specified number of times in your project.", "docPath": "/teachertool", + "tags": ["General"], "params": [ { "name": "block", @@ -27,6 +28,7 @@ "description": "The project contains at least the specified number of comments.", "docPath": "/teachertool", "maxCount": 1, + "tags": ["General"], "params": [ { "name": "count", @@ -43,6 +45,7 @@ "docPath": "/teachertool", "description": "The program uses at least this many loops of any kind (for, repeat, while, or for-of).", "maxCount": 1, + "tags": ["Code Elements"], "params": [ { "name": "count", @@ -59,6 +62,7 @@ "docPath": "/teachertool", "description": "At least this many user-defined functions are created and called.", "maxCount": 1, + "tags": ["Code Elements"], "params": [ { "name": "count", @@ -75,6 +79,7 @@ "docPath": "/teachertool", "description": "The program creates and uses at least this many user-defined variables.", "maxCount": 1, + "tags": ["Code Elements"], "params": [ { "name": "count", diff --git a/common-docs/teachertool/test/catalog-shared.json b/common-docs/teachertool/test/catalog-shared.json index 73e98c28ce44..bd4db6dc9042 100644 --- a/common-docs/teachertool/test/catalog-shared.json +++ b/common-docs/teachertool/test/catalog-shared.json @@ -7,6 +7,7 @@ "description": "Experimental: AI outputs may not be accurate. Use with caution and always review responses.", "docPath": "/teachertool", "maxCount": 10, + "tags": ["General"], "params": [ { "name": "question", diff --git a/teachertool/src/components/CatalogOverlay.tsx b/teachertool/src/components/CatalogOverlay.tsx index f01a244aaf56..7c414fa58e1e 100644 --- a/teachertool/src/components/CatalogOverlay.tsx +++ b/teachertool/src/components/CatalogOverlay.tsx @@ -6,12 +6,16 @@ import { getCatalogCriteria } from "../state/helpers"; import { ReadOnlyCriteriaDisplay } from "./ReadonlyCriteriaDisplay"; import { Strings } from "../constants"; import { Button } from "react-common/components/controls/Button"; +import { Accordion } from "react-common/components/controls/Accordion"; import { getReadableCriteriaTemplate, makeToast } from "../utils"; import { setCatalogOpen } from "../transforms/setCatalogOpen"; import { classList } from "react-common/components/util"; import { announceToScreenReader } from "../transforms/announceToScreenReader"; import { FocusTrap } from "react-common/components/controls/FocusTrap"; import css from "./styling/CatalogOverlay.module.scss"; +import { logError } from "../services/loggingService"; +import { ErrorCode } from "../types/errorCode"; +import { AccordionHeader, AccordionItem, AccordionPanel } from "react-common/components/controls/Accordion/Accordion"; interface CatalogHeaderProps { onClose: () => void; @@ -73,10 +77,22 @@ const CatalogList: React.FC = () => { const recentlyAddedWindowMs = 500; const [recentlyAddedIds, setRecentlyAddedIds] = useState>({}); - const criteria = useMemo( - () => getCatalogCriteria(teacherTool), - [teacherTool.catalog, teacherTool.checklist] - ); + // For now, we only look at the first tag of each criteria. + const criteriaGroupedByTag = useMemo>(() => { + const grouped: pxt.Map = {}; + getCatalogCriteria(teacherTool)?.forEach(c => { + const tag = c.tags?.[0]; + if (!tag) { + logError(ErrorCode.missingTag, { message: "Catalog criteria missing tag", criteria: c }); + return; + } + if (!grouped[tag]) { + grouped[tag] = []; + } + grouped[tag].push(c); + }); + return grouped; + }, [teacherTool.catalog]); function updateRecentlyAddedValue(id: string, value: NodeJS.Timeout | undefined) { setRecentlyAddedIds(prevState => { @@ -107,33 +123,42 @@ const CatalogList: React.FC = () => { } return ( -
- {criteria.map(c => { - const existingInstanceCount = teacherTool.checklist.criteria.filter( - i => i.catalogCriteriaId === c.id - ).length; - const isMaxed = c.maxCount !== undefined && existingInstanceCount >= c.maxCount; + + {Object.keys(criteriaGroupedByTag).map(tag => { return ( - c.template && ( -
+ ); }; diff --git a/teachertool/src/transforms/loadCatalogAsync.ts b/teachertool/src/transforms/loadCatalogAsync.ts index 876704bde6f1..8fb5bc0cffae 100644 --- a/teachertool/src/transforms/loadCatalogAsync.ts +++ b/teachertool/src/transforms/loadCatalogAsync.ts @@ -12,11 +12,16 @@ export async function loadCatalogAsync() { const { dispatch } = stateAndDispatch(); const fullCatalog = await loadTestableCollectionFromDocsAsync(prodFiles, "criteria"); - // Convert parameter names to lower-case for case-insensitive matching fullCatalog.forEach(c => { + // Convert parameter names to lower-case for case-insensitive matching c.params?.forEach(p => { p.name = p.name.toLocaleLowerCase(); }); + + // Add default tag if none are present + if (!c.tags || c.tags.length === 0) { + c.tags = ["Other"]; + } }); dispatch(Actions.setCatalog(fullCatalog)); diff --git a/teachertool/src/types/criteria.ts b/teachertool/src/types/criteria.ts index 97f9ac6e68cb..4b91bd3737d2 100644 --- a/teachertool/src/types/criteria.ts +++ b/teachertool/src/types/criteria.ts @@ -8,6 +8,7 @@ export interface CatalogCriteria { params: CriteriaParameter[] | undefined; // Any parameters that affect the criteria hideInCatalog?: boolean; // Whether the criteria should be hidden in the user-facing catalog maxCount?: number; // The maximum number of instances allowed for this criteria within a single checklist. Unlimited if undefined. + tags?: string[]; // Tags to help categorize the criteria } // An instance of a criteria in a checklist. diff --git a/teachertool/src/types/errorCode.ts b/teachertool/src/types/errorCode.ts index f122a6b22bcc..31df406efa2a 100644 --- a/teachertool/src/types/errorCode.ts +++ b/teachertool/src/types/errorCode.ts @@ -26,4 +26,5 @@ export enum ErrorCode { unrecognizedSystemParameter = "unrecognizedSystemParameter", invalidValidatorPlan = "invalidValidatorPlan", askCopilotQuestion = "askCopilotQuestion", + missingTag = "missingTag", }