diff --git a/teachertool/src/App.tsx b/teachertool/src/App.tsx index e3e212b5ed60..fde995629a32 100644 --- a/teachertool/src/App.tsx +++ b/teachertool/src/App.tsx @@ -15,6 +15,7 @@ import { CatalogModal } from "./components/CatalogModal"; import { postNotification } from "./transforms/postNotification"; import { loadCatalogAsync } from "./transforms/loadCatalogAsync"; import { loadValidatorPlansAsync } from "./transforms/loadValidatorPlansAsync"; +import { tryLoadLastActiveRubricAsync } from "./transforms/tryLoadLastActiveRubricAsync"; export const App = () => { const { state, dispatch } = useContext(AppStateContext); @@ -34,6 +35,8 @@ export const App = () => { await loadCatalogAsync(); await loadValidatorPlansAsync(); + await tryLoadLastActiveRubricAsync(); + // TODO: Remove this. Delay app init to expose any startup race conditions. setTimeout(() => { // Test notification diff --git a/teachertool/src/components/ActiveRubricDisplay.tsx b/teachertool/src/components/ActiveRubricDisplay.tsx index e1a7f6b414c3..1d6572ab1479 100644 --- a/teachertool/src/components/ActiveRubricDisplay.tsx +++ b/teachertool/src/components/ActiveRubricDisplay.tsx @@ -7,7 +7,7 @@ import { Button } from "react-common/components/controls/Button"; import { removeCriteriaFromRubric } from "../transforms/removeCriteriaFromRubric"; import { showCatalogModal } from "../transforms/showCatalogModal"; import { Input } from "react-common/components/controls/Input"; -import { setRubricName } from "../state/actions"; +import { setRubricName } from "../transforms/setRubricName"; interface IProps {} diff --git a/teachertool/src/services/indexedDbService.ts b/teachertool/src/services/indexedDbService.ts index 6ab11978db01..6edc69f6f825 100644 --- a/teachertool/src/services/indexedDbService.ts +++ b/teachertool/src/services/indexedDbService.ts @@ -6,7 +6,7 @@ import { Rubric } from "../types/rubric"; const teacherToolDbName = "makecode-project-insights"; const dbVersion = 1; -const rubricsStoreName = "rubrics" +const rubricsStoreName = "rubrics"; const lastActiveRubricKey = "_lastActiveRubricName"; class TeacherToolDb { @@ -63,17 +63,21 @@ class TeacherToolDb { return this.getAsync(rubricsStoreName, lastActiveRubricKey); } - public getRubricWithNameAsync(name: string): Promise { - return this.getAsync(rubricsStoreName, name); - } - public saveLastActiveRubricNameAsync(name: string): Promise { return this.setAsync(rubricsStoreName, lastActiveRubricKey, name); } + public getRubric(name: string): Promise { + return this.getAsync(rubricsStoreName, name); + } + public saveRubric(rubric: Rubric): Promise { return this.setAsync(rubricsStoreName, rubric.name, rubric); } + + public deleteRubric(name: string): Promise { + return this.deleteAsync(rubricsStoreName, name); + } } const db = new TeacherToolDb(); @@ -90,13 +94,17 @@ export async function getLastActiveRubricAsync(): Promise { let rubric: Rubric | undefined = undefined; const name = await db.getLastActiveRubricNameAsync(); if (name) { - rubric = await db.getRubricWithNameAsync(name); + rubric = await db.getRubric(name); } return rubric; } -export async function saveLastActiveRubricAsync(rubric: Rubric) { +export async function saveRubric(rubric: Rubric) { await db.saveRubric(rubric); await db.saveLastActiveRubricNameAsync(rubric.name); } + +export async function deleteRubric(name: string) { + await db.deleteRubric(name); +} diff --git a/teachertool/src/state/actions.ts b/teachertool/src/state/actions.ts index bdcaddfc264d..eaff5ec9cfe1 100644 --- a/teachertool/src/state/actions.ts +++ b/teachertool/src/state/actions.ts @@ -1,5 +1,6 @@ import { ModalType, NotificationWithId } from "../types"; -import { CatalogCriteria, CriteriaEvaluationResult, CriteriaInstance } from "../types/criteria"; +import { CatalogCriteria, CriteriaEvaluationResult } from "../types/criteria"; +import { Rubric } from "../types/rubric"; // Changes to app state are performed by dispatching actions to the reducer type ActionBase = { @@ -49,9 +50,9 @@ type SetCatalog = ActionBase & { catalog: CatalogCriteria[] | undefined; }; -type SetSelectedCriteria = ActionBase & { - type: "SET_SELECTED_CRITERIA"; - criteria: CriteriaInstance[]; +type SetRubric = ActionBase & { + type: "SET_RUBRIC"; + rubric: Rubric; }; type RemoveCriteriaInstance = ActionBase & { @@ -59,11 +60,6 @@ type RemoveCriteriaInstance = ActionBase & { instanceId: string; }; -type SetRubricName = ActionBase & { - type: "SET_RUBRIC_NAME"; - name: string; -}; - type ShowModal = ActionBase & { type: "SHOW_MODAL"; modal: ModalType; @@ -91,9 +87,8 @@ export type Action = | ClearAllEvalResults | SetTargetConfig | SetCatalog - | SetSelectedCriteria + | SetRubric | RemoveCriteriaInstance - | SetRubricName | ShowModal | HideModal | SetValidatorPlans; @@ -141,9 +136,9 @@ const setCatalog = (catalog: CatalogCriteria[] | undefined): SetCatalog => ({ catalog, }); -const setSelectedCriteria = (criteria: CriteriaInstance[]): SetSelectedCriteria => ({ - type: "SET_SELECTED_CRITERIA", - criteria, +const setRubric = (rubric: Rubric): SetRubric => ({ + type: "SET_RUBRIC", + rubric, }); const removeCriteriaInstance = (instanceId: string): RemoveCriteriaInstance => ({ @@ -151,11 +146,6 @@ const removeCriteriaInstance = (instanceId: string): RemoveCriteriaInstance => ( instanceId, }); -const setRubricName = (name: string): SetRubricName => ({ - type: "SET_RUBRIC_NAME", - name, -}); - const showModal = (modal: ModalType): ShowModal => ({ type: "SHOW_MODAL", modal, @@ -179,9 +169,8 @@ export { clearAllEvalResults, setTargetConfig, setCatalog, - setSelectedCriteria, + setRubric, removeCriteriaInstance, - setRubricName, showModal, hideModal, setValidatorPlans, diff --git a/teachertool/src/state/reducer.ts b/teachertool/src/state/reducer.ts index f80767d9b198..8a9445f4a667 100644 --- a/teachertool/src/state/reducer.ts +++ b/teachertool/src/state/reducer.ts @@ -59,13 +59,10 @@ export default function reducer(state: AppState, action: Action): AppState { catalog: action.catalog, }; } - case "SET_SELECTED_CRITERIA": { + case "SET_RUBRIC": { return { ...state, - rubric: { - ...state.rubric, - criteria: action.criteria, - }, + rubric: action.rubric, }; } case "REMOVE_CRITERIA_INSTANCE": { @@ -77,15 +74,6 @@ export default function reducer(state: AppState, action: Action): AppState { }, }; } - case "SET_RUBRIC_NAME": { - return { - ...state, - rubric: { - ...state.rubric, - name: action.name, - }, - }; - } case "SHOW_MODAL": { return { ...state, diff --git a/teachertool/src/transforms/addCriteriaToRubric.ts b/teachertool/src/transforms/addCriteriaToRubric.ts index 21a6f0aa7740..e8aa7f3d14e1 100644 --- a/teachertool/src/transforms/addCriteriaToRubric.ts +++ b/teachertool/src/transforms/addCriteriaToRubric.ts @@ -5,12 +5,17 @@ import { logDebug, logError } from "../services/loggingService"; import { CriteriaInstance, CriteriaParameterValue } from "../types/criteria"; import { nanoid } from "nanoid"; import { ErrorCode } from "../types/errorCode"; +import { saveRubric } from "../services/indexedDbService"; export function addCriteriaToRubric(catalogCriteriaIds: string[]) { const { state: teacherTool, dispatch } = stateAndDispatch(); // Create instances for each of the catalog criteria. - const newSelectedCriteria = [...(teacherTool.rubric.criteria ?? [])]; + const newRubric = { + ...teacherTool.rubric, + criteria: [...(teacherTool.rubric.criteria ?? [])], + } + for (const catalogCriteriaId of catalogCriteriaIds) { const catalogCriteria = getCatalogCriteriaWithId(catalogCriteriaId); if (!catalogCriteria) { @@ -37,12 +42,14 @@ export function addCriteriaToRubric(catalogCriteriaIds: string[]) { params, } as CriteriaInstance; - newSelectedCriteria.push(criteriaInstance); + newRubric.criteria.push(criteriaInstance); } - dispatch(Actions.setSelectedCriteria(newSelectedCriteria)); + dispatch(Actions.setRubric(newRubric)); pxt.tickEvent("teachertool.addcriteria", { ids: JSON.stringify(catalogCriteriaIds), }); + + saveRubric(newRubric); // fire and forget, we don't really need to wait on this. } diff --git a/teachertool/src/transforms/importRubric.ts b/teachertool/src/transforms/importRubric.ts deleted file mode 100644 index 2bb5cbf3d97e..000000000000 --- a/teachertool/src/transforms/importRubric.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { stateAndDispatch } from "../state"; -import { CriteriaInstance } from "../types/criteria"; -import * as Actions from "../state/actions"; - -export function importRubric(serializedRubric: string) { - const { dispatch } = stateAndDispatch(); - - // todo : setRubric action. -} \ No newline at end of file diff --git a/teachertool/src/transforms/setRubricName.ts b/teachertool/src/transforms/setRubricName.ts index 4f3b2431472e..5dee9215e7ae 100644 --- a/teachertool/src/transforms/setRubricName.ts +++ b/teachertool/src/transforms/setRubricName.ts @@ -1,7 +1,29 @@ +import { deleteRubric, saveRubric } from "../services/indexedDbService"; import { stateAndDispatch } from "../state"; import * as Actions from "../state/actions"; export function setRubricName(name: string) { - const { dispatch } = stateAndDispatch(); - dispatch(Actions.setRubricName(name)); -} \ No newline at end of file + const { state: teacherTool, dispatch } = stateAndDispatch(); + + const oldName = teacherTool.rubric.name; + + if (oldName === name) { + return; + } + + const newRubric = { + ...teacherTool.rubric, + name, + } + dispatch(Actions.setRubric(newRubric)); + + // To save the renamed rubric, we can simply save a version with the new name, + // and then delete the entry with the previous name. + async function saveRenamedRubric() { + await saveRubric(newRubric); + await deleteRubric(oldName); + } + + // Fire and forget. We don't need to wait for the operation to finish. + saveRenamedRubric(); +} diff --git a/teachertool/src/transforms/tryLoadLastActiveRubricAsync.ts b/teachertool/src/transforms/tryLoadLastActiveRubricAsync.ts new file mode 100644 index 000000000000..a5e0514cb0c5 --- /dev/null +++ b/teachertool/src/transforms/tryLoadLastActiveRubricAsync.ts @@ -0,0 +1,17 @@ +import { stateAndDispatch } from "../state"; +import * as Actions from "../state/actions"; +import { getLastActiveRubricAsync } from "../services/indexedDbService"; +import { logDebug } from "../services/loggingService"; + +export async function tryLoadLastActiveRubricAsync() { + const { dispatch } = stateAndDispatch(); + + const lastActiveRubric = await getLastActiveRubricAsync(); + + if (lastActiveRubric) { + logDebug(`Loading last active rubric '${lastActiveRubric.name}'...`); + dispatch(Actions.setRubric(lastActiveRubric)); + } else { + logDebug(`No last active rubric to load.`); + } +} \ No newline at end of file