From 5937787c40d33a729f4b07cd5f4dc783a721f621 Mon Sep 17 00:00:00 2001 From: Thomas Sparks <69657545+thsparks@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:06:26 -0800 Subject: [PATCH] Teacher Tool: Import and Export Rubric (#9845) This change enables exporting rubrics to a file and importing them again. I've tucked the options behind a new "Action Menu", which is just a meatball menu on the right side of the toolbar. I didn't spend a ton of time styling that, just wanted to get something fairly quick working. --- pxtlib/util.ts | 5 + teachertool/src/App.tsx | 2 + teachertool/src/components/ActionsMenu.tsx | 49 +++++++++ .../src/components/ActiveRubricDisplay.tsx | 4 +- teachertool/src/components/CatalogModal.tsx | 2 +- teachertool/src/components/DebugInput.tsx | 4 - .../src/components/ImportRubricModal.tsx | 99 +++++++++++++++++++ teachertool/src/components/NoticeLabel.tsx | 37 +++++++ teachertool/src/components/RubricPreview.tsx | 24 +++++ .../src/components/RubricWorkspace.tsx | 5 +- .../styling/ActionsMenu.module.scss | 26 +++++ .../styling/ImportRubricModal.module.scss | 13 +++ .../styling/NoticeLabel.module.scss | 43 ++++++++ .../styling/RubricPreview.module.scss | 24 +++++ teachertool/src/services/fileSystemService.ts | 33 +++++++ teachertool/src/state/helpers.ts | 38 ++++++- .../src/transforms/getRubricFromFileAsync.ts | 20 ++++ teachertool/src/transforms/hideModal.ts | 7 +- teachertool/src/transforms/setRubric.ts | 8 ++ .../{showCatalogModal.ts => showModal.ts} | 5 +- .../tryLoadLastActiveRubricAsync.ts | 42 +------- teachertool/src/types/errorCode.ts | 5 +- teachertool/src/types/index.ts | 2 +- webapp/src/package.ts | 3 +- 24 files changed, 443 insertions(+), 57 deletions(-) create mode 100644 teachertool/src/components/ActionsMenu.tsx create mode 100644 teachertool/src/components/ImportRubricModal.tsx create mode 100644 teachertool/src/components/NoticeLabel.tsx create mode 100644 teachertool/src/components/RubricPreview.tsx create mode 100644 teachertool/src/components/styling/ActionsMenu.module.scss create mode 100644 teachertool/src/components/styling/ImportRubricModal.module.scss create mode 100644 teachertool/src/components/styling/NoticeLabel.module.scss create mode 100644 teachertool/src/components/styling/RubricPreview.module.scss create mode 100644 teachertool/src/services/fileSystemService.ts create mode 100644 teachertool/src/transforms/getRubricFromFileAsync.ts create mode 100644 teachertool/src/transforms/setRubric.ts rename teachertool/src/transforms/{showCatalogModal.ts => showModal.ts} (52%) diff --git a/pxtlib/util.ts b/pxtlib/util.ts index bb98dbcc3b7e..f7e6d84a151b 100644 --- a/pxtlib/util.ts +++ b/pxtlib/util.ts @@ -185,6 +185,11 @@ namespace ts.pxtc.Util { } } + export function sanitizeFileName(name: string): string { + /* eslint-disable no-control-regex */ + return name.replace(/[()\\\/.,?*^:<>!;'#$%^&|"@+=«»°{}\[\]¾½¼³²¦¬¤¢£~­¯¸`±\x00-\x1F]/g, '').trim().replace(/\s+/g, '-'); + } + export function repeatMap(n: number, fn: (index: number) => T): T[] { n = n || 0; let r: T[] = []; diff --git a/teachertool/src/App.tsx b/teachertool/src/App.tsx index fde995629a32..893c2fbfe5c9 100644 --- a/teachertool/src/App.tsx +++ b/teachertool/src/App.tsx @@ -16,6 +16,7 @@ import { postNotification } from "./transforms/postNotification"; import { loadCatalogAsync } from "./transforms/loadCatalogAsync"; import { loadValidatorPlansAsync } from "./transforms/loadValidatorPlansAsync"; import { tryLoadLastActiveRubricAsync } from "./transforms/tryLoadLastActiveRubricAsync"; +import { ImportRubricModal } from "./components/ImportRubricModal"; export const App = () => { const { state, dispatch } = useContext(AppStateContext); @@ -58,6 +59,7 @@ export const App = () => { + ); diff --git a/teachertool/src/components/ActionsMenu.tsx b/teachertool/src/components/ActionsMenu.tsx new file mode 100644 index 000000000000..784e5da17378 --- /dev/null +++ b/teachertool/src/components/ActionsMenu.tsx @@ -0,0 +1,49 @@ +import { useContext } from "react"; +import { MenuDropdown, MenuItem } from "react-common/components/controls/MenuDropdown"; +import { writeRubricToFile } from "../services/fileSystemService"; +import { AppStateContext } from "../state/appStateContext"; +// eslint-disable-next-line import/no-internal-modules +import css from "./styling/ActionsMenu.module.scss"; +import { showModal } from "../transforms/showModal"; + +export interface IProps {} + +export const ActionsMenu: React.FC = () => { + const { state: teacherTool } = useContext(AppStateContext); + + function handleImportRubricClicked() { + showModal("import-rubric"); + } + + function handleExportRubricClicked() { + writeRubricToFile(teacherTool.rubric); + } + + const menuItems: MenuItem[] = [ + { + id: "import-rubric", + title: lf("Import Rubric"), + label: lf("Import Rubric"), + ariaLabel: lf("Import Rubric"), + onClick: handleImportRubricClicked, + }, + { + id: "export-rubric", + title: lf("Export Rubric"), + label: lf("Export Rubric"), + ariaLabel: lf("Export Rubric"), + onClick: handleExportRubricClicked, + }, + ]; + + const dropdownLabel = ; + return ( + + ); +}; diff --git a/teachertool/src/components/ActiveRubricDisplay.tsx b/teachertool/src/components/ActiveRubricDisplay.tsx index 5c6b996999ca..528d81e6a542 100644 --- a/teachertool/src/components/ActiveRubricDisplay.tsx +++ b/teachertool/src/components/ActiveRubricDisplay.tsx @@ -5,7 +5,7 @@ import { AppStateContext } from "../state/appStateContext"; import { getCatalogCriteriaWithId } from "../state/helpers"; import { Button } from "react-common/components/controls/Button"; import { removeCriteriaFromRubric } from "../transforms/removeCriteriaFromRubric"; -import { showCatalogModal } from "../transforms/showCatalogModal"; +import { showModal } from "../transforms/showModal"; import { setRubricName } from "../transforms/setRubricName"; import { DebouncedInput } from "./DebouncedInput"; @@ -45,7 +45,7 @@ export const ActiveRubricDisplay: React.FC = ({}) => {