diff --git a/teachertool/src/components/CatalogModal.tsx b/teachertool/src/components/CatalogModal.tsx index a9b5ff03bf9e..10503b0f4249 100644 --- a/teachertool/src/components/CatalogModal.tsx +++ b/teachertool/src/components/CatalogModal.tsx @@ -6,32 +6,8 @@ import { hideModal } from "../transforms/hideModal"; import { addCriteriaToRubric } from "../transforms/addCriteriaToRubric"; import { CatalogCriteria } from "../types/criteria"; import { getSelectableCatalogCriteria } from "../state/helpers"; +import { ReadOnlyCriteriaDisplay } from "./ReadonlyCriteriaDisplay"; import css from "./styling/CatalogModal.module.scss"; -import { splitCriteriaTemplate } from "../utils"; - -interface CatalogCriteriaDisplayProps { - criteria: CatalogCriteria; -} -const CatalogCriteriaDisplay: React.FC = ({ criteria }) => { - const segments = useMemo(() => splitCriteriaTemplate(criteria.template), [criteria.template]); - - return ( -
- {criteria.template && ( -
- {segments.map((segment, index) => { - return ( - - {segment.content} - - ); - })} -
- )} - {criteria.description &&
{criteria.description}
} -
- ); -}; interface CatalogModalProps {} export const CatalogModal: React.FC = ({}) => { @@ -96,7 +72,7 @@ export const CatalogModal: React.FC = ({}) => { id={`checkbox_${criteria.id}`} key={criteria.id} className={css["catalog-item"]} - label={} + label={} onChange={newValue => handleCriteriaSelectedChange(criteria, newValue)} isChecked={isCriteriaSelected(criteria.id)} /> diff --git a/teachertool/src/components/CriteriaInstanceDisplay.tsx b/teachertool/src/components/CriteriaInstanceDisplay.tsx index 1b0fd7bb8541..ae0b7c7873ac 100644 --- a/teachertool/src/components/CriteriaInstanceDisplay.tsx +++ b/teachertool/src/components/CriteriaInstanceDisplay.tsx @@ -1,12 +1,13 @@ import { getCatalogCriteriaWithId } from "../state/helpers"; import { CriteriaInstance, CriteriaParameterValue } from "../types/criteria"; -import { DebouncedInput } from "./DebouncedInput"; import { logDebug } from "../services/loggingService"; import { setParameterValue } from "../transforms/setParameterValue"; import { classList } from "react-common/components/util"; import { splitCriteriaTemplate } from "../utils"; // eslint-disable-next-line import/no-internal-modules import css from "./styling/CriteriaInstanceDisplay.module.scss"; +import { useState } from "react"; +import { Input } from "react-common/components/controls/Input"; interface InlineInputSegmentProps { initialValue: string; @@ -22,32 +23,39 @@ const InlineInputSegment: React.FC = ({ shouldExpand, numeric, }) => { + const [isEmpty, setIsEmpty] = useState(!initialValue); + function onChange(newValue: string) { + setIsEmpty(!newValue); setParameterValue(instance.instanceId, param.name, newValue); } + const tooltip = isEmpty ? lf("{0}: value required", param.name) : param.name; return ( - +
+ +
); }; interface CriteriaInstanceDisplayProps { criteriaInstance: CriteriaInstance; } - export const CriteriaInstanceDisplay: React.FC = ({ criteriaInstance }) => { const catalogCriteria = getCatalogCriteriaWithId(criteriaInstance.catalogCriteriaId); if (!catalogCriteria) { diff --git a/teachertool/src/components/CriteriaResultEntry.tsx b/teachertool/src/components/CriteriaResultEntry.tsx index 1e9ad2721387..f92dc1b51ee1 100644 --- a/teachertool/src/components/CriteriaResultEntry.tsx +++ b/teachertool/src/components/CriteriaResultEntry.tsx @@ -9,6 +9,7 @@ import { setEvalResultNotes } from "../transforms/setEvalResultNotes"; import { CriteriaEvalResultDropdown } from "./CriteriaEvalResultDropdown"; import { DebouncedTextarea } from "./DebouncedTextarea"; import { getCatalogCriteriaWithId, getCriteriaInstanceWithId } from "../state/helpers"; +import { ReadOnlyCriteriaDisplay } from "./ReadonlyCriteriaDisplay"; interface AddNotesButtonProps { criteriaId: string; @@ -68,28 +69,20 @@ interface CriteriaResultEntryProps { export const CriteriaResultEntry: React.FC = ({ criteriaId }) => { const { state: teacherTool } = useContext(AppStateContext); const [showInput, setShowInput] = useState(!!teacherTool.evalResults[criteriaId]?.notes); - const criteriaDisplayString = useRef(getDisplayStringFromCriteriaInstanceId(criteriaId)); - function getDisplayStringFromCriteriaInstanceId(instanceId: string): string { - const instance = getCriteriaInstanceWithId(teacherTool, instanceId); - if (!instance) { - return ""; - } - - let displayText = getCatalogCriteriaWithId(instance.catalogCriteriaId)?.template ?? ""; - for (const param of instance.params ?? []) { - displayText = displayText.replace(new RegExp(`\\$\\{${param.name}}`, 'i'), param.value); - } - - return displayText; - } + const criteriaInstance = getCriteriaInstanceWithId(teacherTool, criteriaId); + const catalogCriteria = criteriaInstance ? getCatalogCriteriaWithId(criteriaInstance.catalogCriteriaId) : undefined; return ( <> - {criteriaDisplayString.current && ( + {catalogCriteria && (
-

{criteriaDisplayString.current}

+ = ({ + catalogCriteria, + criteriaInstance, + showDescription, +}) => { + const { state: teacherTool } = useContext(AppStateContext); + + function getSegmentDisplayText(segment: CriteriaTemplateSegment): string { + if (!criteriaInstance || segment.type === "plain-text") { + return segment.content; + } + + return getParameterValue(teacherTool, criteriaInstance.instanceId, segment.content) ?? ""; + } + + const segments = useMemo(() => splitCriteriaTemplate(catalogCriteria.template), [catalogCriteria.template]); + return ( +
+ {catalogCriteria.template && ( +
+ {segments.map((segment, index) => { + return ( + + {getSegmentDisplayText(segment)} + + ); + })} +
+ )} + {showDescription && catalogCriteria.description && ( +
{catalogCriteria.description}
+ )} +
+ ); +}; diff --git a/teachertool/src/components/styling/CatalogModal.module.scss b/teachertool/src/components/styling/CatalogModal.module.scss index 8e17f50ec695..929ac1138c79 100644 --- a/teachertool/src/components/styling/CatalogModal.module.scss +++ b/teachertool/src/components/styling/CatalogModal.module.scss @@ -11,27 +11,4 @@ .catalog-item { padding: 0.5rem; } - - .criteria-display { - display: flex; - flex-direction: column; - align-items: flex-start; - - .criteria-template { - .plain-text-segment { - margin-right: 0.3rem; - } - - .param-segment { - border-radius: 0.5rem; - border: 1px solid var(--pxt-page-foreground); - padding: 0.2rem 0.5rem; - margin-right: 0.3rem; - } - } - - .criteria-description { - color: var(--pxt-page-foreground-light); - } - } } diff --git a/teachertool/src/components/styling/CriteriaInstanceDisplay.module.scss b/teachertool/src/components/styling/CriteriaInstanceDisplay.module.scss index 07c1aa6596f6..b23f685f9604 100644 --- a/teachertool/src/components/styling/CriteriaInstanceDisplay.module.scss +++ b/teachertool/src/components/styling/CriteriaInstanceDisplay.module.scss @@ -8,7 +8,7 @@ border-radius: 0.5rem; border: solid 1px var(--pxt-page-foreground); padding: 0 0.4rem; - + &:focus::after { outline: none; border-radius: 0.5rem; @@ -42,38 +42,100 @@ flex-grow: 1; } - .inline-input { - margin: 0.2rem 0; + .inline-input-wrapper { + &:has(.long) { + flex-grow: 1; + } + + .inline-input { + margin: 0.2rem 0; + + &.string-input { + width: 10rem; - &.string-input { - width: 10rem; - - &.long { - flex-grow: 1; - input { - text-align: start; + &.long { + width: unset; + flex-grow: 1; + + input { + text-align: start; + } + } + + &.error:not(:focus):not(:focus-within) input { + margin-left: 1.8rem; } } - } - - &.number-input { - width: 2.3rem; - - // Hide arrows on right-hand side of number input. - // Firefox - input[type=number] { - -moz-appearance: textfield; + + &.number-input { + width: 2.7rem; // Just enough space for 3 digits + + &.error:not(:focus):not(:focus-within) { + width: 4rem; // Add space for icon + + input { + margin-left: 1.3rem; + } + + i { + bottom: 0.4rem; + } + } + + // Hide arrows on right-hand side of number input. + // Firefox + input[type=number] { + -moz-appearance: textfield; + } + + // Other browsers + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } } - // Other browsers - input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; + + input { + text-align: center; + padding: 0; } - } - input { - text-align: center; - padding: 0; + input { + transition: margin-left 0.05s ease-in-out; + } + + &.error { + &:not(:focus):not(:focus-within) div[class*="common-input-group"] { + border: solid 1px var(--pxt-error-accent); + background-color: var(--pxt-error-background); + transition: background-color 0.3s ease-in-out, border 0.3s ease-in-out; + + &:focus::after { + border: solid 2px var(--pxt-error); + } + + &:focus-within::after { + border: solid 2px var(--pxt-error); + } + + input::placeholder { + color: var(--pxt-page-foreground); + } + + i { + right: unset; + left: 0.5rem; + } + } + + &:focus, + &:focus-within { + i { + display: none; + } + } + } } } } diff --git a/teachertool/src/components/styling/ReadonlyCriteriaDisplay.module.scss b/teachertool/src/components/styling/ReadonlyCriteriaDisplay.module.scss new file mode 100644 index 000000000000..43f727415a11 --- /dev/null +++ b/teachertool/src/components/styling/ReadonlyCriteriaDisplay.module.scss @@ -0,0 +1,22 @@ +.criteria-display { + display: flex; + flex-direction: column; + align-items: flex-start; + + .criteria-template { + .plain-text-segment { + margin-right: 0.3rem; + } + + .param-segment { + border-radius: 0.5rem; + border: 1px solid var(--pxt-page-foreground); + padding: 0.2rem 0.5rem; + margin-right: 0.3rem; + } + } + + .criteria-description { + color: var(--pxt-page-foreground-light); + } +} diff --git a/teachertool/src/components/styling/ShareLinkInput.module.scss b/teachertool/src/components/styling/ShareLinkInput.module.scss index 0cbeaad79b4d..32d7e1090d19 100644 --- a/teachertool/src/components/styling/ShareLinkInput.module.scss +++ b/teachertool/src/components/styling/ShareLinkInput.module.scss @@ -16,6 +16,25 @@ input::placeholder { text-align: center; } + + i { + background-color: var(--pxt-content-background); + border-radius: 99px; + width: 1.5rem; + height: 1.5rem; + top: 0; + bottom: 0; + right: 0.25rem; + margin-top: auto; + margin-bottom: auto; + + &::before { + font-size: 1rem; + line-height: 1.5rem; + color: var(--pxt-content-foreground); + filter: saturate(0%); + } + } } } diff --git a/teachertool/src/state/helpers.ts b/teachertool/src/state/helpers.ts index 55d83df7afd4..8988cded86b5 100644 --- a/teachertool/src/state/helpers.ts +++ b/teachertool/src/state/helpers.ts @@ -15,6 +15,11 @@ export function getCriteriaInstanceWithId(state: AppState, id: string): Criteria return state.rubric.criteria.find(c => c.instanceId === id); } +export function getParameterValue(state: AppState, instanceId: string, paramName: string): string | undefined { + const instance = getCriteriaInstanceWithId(state, instanceId); + return instance?.params?.find(p => p.name === paramName)?.value; +} + export function verifyCriteriaInstanceIntegrity(instance: CriteriaInstance) { const catalogCriteria = getCatalogCriteriaWithId(instance.catalogCriteriaId); diff --git a/teachertool/src/style.overrides/Input.scss b/teachertool/src/style.overrides/Input.scss index 6c03df10be4a..0e0817a48082 100644 --- a/teachertool/src/style.overrides/Input.scss +++ b/teachertool/src/style.overrides/Input.scss @@ -27,24 +27,5 @@ font-style: italic; } } - - i { - background-color: var(--pxt-content-background); - border-radius: 99px; - width: 1.5rem; - height: 1.5rem; - top: 0; - bottom: 0; - right: 0.25rem; - margin-top: auto; - margin-bottom: auto; - - &::before { - font-size: 1rem; - line-height: 1.5rem; - color: var(--pxt-content-foreground); - filter: saturate(0%); - } - } } } diff --git a/teachertool/src/transforms/runEvaluateAsync.ts b/teachertool/src/transforms/runEvaluateAsync.ts index c17ca88a928b..14d17738c69a 100644 --- a/teachertool/src/transforms/runEvaluateAsync.ts +++ b/teachertool/src/transforms/runEvaluateAsync.ts @@ -54,13 +54,6 @@ function generateValidatorPlan( catalogId: criteriaInstance.catalogCriteriaId, paramName: param.name, }); - showToast( - makeToast( - "error", - // prettier-ignore - lf("Unable to evaluate criteria: missing '{0}' in '{1}'", param.name, getReadableCriteriaTemplate(catalogCriteria)) - ) - ); } return undefined; }