-
Notifications
You must be signed in to change notification settings - Fork 588
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
24 changed files
with
443 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IProps> = () => { | ||
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 = <i className={"fas fa-ellipsis-v"} />; | ||
return ( | ||
<MenuDropdown | ||
id="actions-menu" | ||
className={css["actions-menu"]} | ||
items={menuItems} | ||
title={"Actions"} | ||
label={dropdownLabel} | ||
/> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { useContext, useEffect, useState } from "react"; | ||
import { AppStateContext } from "../state/appStateContext"; | ||
import { Modal } from "react-common/components/controls/Modal"; | ||
import { hideModal } from "../transforms/hideModal"; | ||
// eslint-disable-next-line import/no-internal-modules | ||
import css from "./styling/ImportRubricModal.module.scss"; | ||
import { getRubricFromFileAsync } from "../transforms/getRubricFromFileAsync"; | ||
import { NoticeLabel } from "./NoticeLabel"; | ||
import { Rubric } from "../types/rubric"; | ||
import { RubricPreview } from "./RubricPreview"; | ||
import { setRubric } from "../transforms/setRubric"; | ||
|
||
export interface IProps {} | ||
|
||
export const ImportRubricModal: React.FC<IProps> = () => { | ||
const { state: teacherTool } = useContext(AppStateContext); | ||
const [selectedFile, setSelectedFile] = useState<File | undefined>(undefined); | ||
const [selectedRubric, setSelectedRubric] = useState<Rubric | undefined>(undefined); | ||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined); | ||
|
||
useEffect(() => { | ||
async function updatePreview(file: File) { | ||
const parsedRubric = await getRubricFromFileAsync(file, false /* allow partial */); | ||
if (!parsedRubric) { | ||
setErrorMessage(lf("Invalid rubric file.")); | ||
} else { | ||
setErrorMessage(undefined); | ||
} | ||
setSelectedRubric(parsedRubric); | ||
} | ||
|
||
if (selectedFile) { | ||
updatePreview(selectedFile); | ||
} else { | ||
setSelectedRubric(undefined); | ||
setErrorMessage(undefined); | ||
} | ||
}, [selectedFile]); | ||
|
||
function closeModal() { | ||
setSelectedFile(undefined); | ||
setErrorMessage(undefined); | ||
setSelectedRubric(undefined); | ||
hideModal(); | ||
} | ||
|
||
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
if (event.target.files && event.target.files.length > 0) { | ||
setSelectedFile(event.target.files[0]); | ||
} else { | ||
setSelectedFile(undefined); | ||
} | ||
} | ||
|
||
function handleImportClicked() { | ||
if (selectedRubric) { | ||
setRubric(selectedRubric); | ||
} | ||
|
||
closeModal(); | ||
} | ||
|
||
const actions = [ | ||
{ | ||
label: lf("Cancel"), | ||
className: "secondary", | ||
onClick: closeModal, | ||
}, | ||
{ | ||
label: lf("Import"), | ||
className: "primary", | ||
onClick: handleImportClicked, | ||
disabled: !selectedRubric, | ||
}, | ||
]; | ||
|
||
return teacherTool.modal === "import-rubric" ? ( | ||
<Modal title={lf("Select rubric to import")} actions={actions} onClose={closeModal}> | ||
<div className={css["import-rubric"]}> | ||
<NoticeLabel severity="warning"> | ||
{lf("Warning! Your current rubric will be overwritten by the imported rubric.")} | ||
</NoticeLabel> | ||
{errorMessage && <NoticeLabel severity="error">{errorMessage}</NoticeLabel>} | ||
{selectedRubric && ( | ||
<div className={css["rubric-preview-container"]}> | ||
<RubricPreview rubric={selectedRubric} /> | ||
</div> | ||
)} | ||
<input | ||
type="file" | ||
tabIndex={0} | ||
autoFocus | ||
aria-label={lf("Select rubric file.")} | ||
onChange={handleFileChange} | ||
/> | ||
</div> | ||
</Modal> | ||
) : null; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { classList } from "react-common/components/util"; | ||
// eslint-disable-next-line import/no-internal-modules | ||
import css from "./styling/NoticeLabel.module.scss"; | ||
|
||
export type NoticeLabelSeverity = "info" | "warning" | "error" | "neutral"; | ||
|
||
export interface INoticeLabelProps extends React.PropsWithChildren<{}> { | ||
severity: NoticeLabelSeverity; | ||
} | ||
|
||
export const NoticeLabel: React.FC<INoticeLabelProps> = props => { | ||
let iconClass = undefined; | ||
switch (props.severity) { | ||
case "info": | ||
iconClass = "fas fa-exclamation-circle"; | ||
break; | ||
case "warning": | ||
iconClass = "fas fa-exclamation-triangle"; | ||
break; | ||
case "error": | ||
iconClass = "fas fa-times"; | ||
break; | ||
case "neutral": | ||
default: | ||
// no icon | ||
break; | ||
} | ||
|
||
return ( | ||
<div className={css["notice-label-background"]}> | ||
<div className={classList(css["notice-label-container"], css[`${props.severity}-notice-label-container`])}> | ||
{props.severity !== "neutral" && <i className={classList(iconClass, css["icon"])} />} | ||
<label className="notice-label">{props.children}</label> | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { getCatalogCriteriaWithId } from "../state/helpers"; | ||
import { Rubric } from "../types/rubric"; | ||
// eslint-disable-next-line import/no-internal-modules | ||
import css from "./styling/RubricPreview.module.scss"; | ||
|
||
export interface IRubricPreviewProps { | ||
rubric: Rubric; | ||
} | ||
|
||
export const RubricPreview: React.FC<IRubricPreviewProps> = ({ rubric }) => { | ||
return ( | ||
<div className={css["container"]}> | ||
<div className={css["rubric-header"]}>{rubric.name}</div> | ||
{rubric.criteria.map((c, i) => { | ||
const template = getCatalogCriteriaWithId(c.catalogCriteriaId)?.template; | ||
return template ? ( | ||
<div key={i} className={css["rubric-criteria"]}> | ||
{template} | ||
</div> | ||
) : null; | ||
})} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
teachertool/src/components/styling/ActionsMenu.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.actions-menu { | ||
button[class*="menu-button"] { | ||
padding: 0rem 0.2rem; | ||
color: var(--pxt-content-foreground); | ||
} | ||
|
||
button[class*="common-menu-dropdown-item"] { | ||
color: var(--pxt-content-foreground); | ||
} | ||
|
||
ul { | ||
background-color: var(--pxt-content-background); | ||
} | ||
|
||
li { | ||
border-bottom: 1px solid var(--pxt-content-accent); | ||
|
||
&:last-child { | ||
border-bottom: none; | ||
} | ||
|
||
&:hover { | ||
background-color: var(--pxt-content-accent); | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
teachertool/src/components/styling/ImportRubricModal.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.import-rubric { | ||
display: flex; | ||
flex-direction: column; | ||
gap: 0.5rem; | ||
|
||
.rubric-preview-container { | ||
max-height: 50vh; | ||
overflow-y: auto; | ||
border: 2px solid var(--pxt-content-foreground); | ||
border-radius: 0.3rem; | ||
background-color: var(--pxt-content-background-glass); | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
teachertool/src/components/styling/NoticeLabel.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
.notice-label-background { | ||
border-color: var(--pxt-content-foreground); | ||
background-color: var(--pxt-content-background); | ||
border-radius: 0.3rem; | ||
|
||
.notice-label-container { | ||
display: flex; | ||
flex-direction: row; | ||
align-items: center; | ||
padding: 0.3rem; | ||
border-radius: 0.3rem; | ||
border: 2px solid | ||
} | ||
|
||
.error-notice-label-container { | ||
border-color: rgba(red, 0.2); | ||
background-color: rgba(red, 0.2); | ||
justify-content: flex-start; | ||
} | ||
|
||
.warning-notice-label-container { | ||
border-color: rgba(orange, 0.4); | ||
background-color: rgba(orange, 0.4); | ||
justify-content: flex-start; | ||
} | ||
|
||
.info-notice-label-container { | ||
border-color: rgba(blue, 0.2); | ||
background-color: rgba(blue, 0.2); | ||
justify-content: flex-start; | ||
} | ||
|
||
.neutral-notice-label-container { | ||
border-color: rgba(black, 0.1); | ||
background-color: rgba(black, 0.1); | ||
align-content: center; | ||
justify-content: center; | ||
} | ||
|
||
.icon { | ||
margin-right: 0.5rem; | ||
} | ||
} |
Oops, something went wrong.