diff --git a/data/content-versions.json b/data/content-versions.json index 6e29933b..fd629553 100644 --- a/data/content-versions.json +++ b/data/content-versions.json @@ -1,5 +1,5 @@ { - "defaultVersion": "d194749e", + "defaultVersion": "2d6223ee", "overrides": { "raiselearning.org": { "39": "latest", diff --git a/docs/static/textheavyadjustedtable.png b/docs/static/textheavyadjustedtable.png new file mode 100644 index 00000000..1ca7e8c6 Binary files /dev/null and b/docs/static/textheavyadjustedtable.png differ diff --git a/docs/styles.md b/docs/styles.md index b758571f..4490e2ef 100644 --- a/docs/styles.md +++ b/docs/styles.md @@ -4,6 +4,7 @@ - [Styling Content for RAISE](#styling-content-for-raise) - Tables - [Text Heavy Tables](#text-heavy-table) + - [Text Heavy Adjusted Tables](#text-heavy-adjusted-table) - [Horizontal Tables](#horizontal-table) - [Mid-Sized Tables](#mid-size-table) - Skinny Tables @@ -83,6 +84,64 @@ Add as a class attribute to a table html tag. --- +## Text Heavy Adjusted Table + +Adds a solid border, styled table header, and padding to a table with column widths determined by the widest content in a column. + +**Example** + +
+ +
+ +**Availability** + +Add as a class attribute to a table html tag. + +**Usage** + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Lesson NumberLesson TitleAssociated Texas Essential Knowledge and Skills [TEKS]
1.1Exploring Expressions and Equations

A1(A) apply mathematics to problems arising in everyday life, society, and the workplace

+

A2(C) write linear equations in two variables given a table of values, a graph, and a verbal description

1.2Writing Equations to Model Relationships (Part 1)

A1(A) apply mathematics to problems arising in everyday life, society, and the workplace

+

A2(C) write linear equations in two variables given a table of values, a graph, and a verbal description

1.3Writing Equations to Model Relationships (Part 2)

A1(A) apply mathematics to problems arising in everyday life, society, and the workplace

+

A1(C) select tools, including real objects, manipulatives, paper and pencil, and technology as appropriate, and techniques, including mental math, estimation, and number sense as appropriate, to solve problems

+

A2(C) write linear equations in two variables given a table of values, a graph, and a verbal description.

1.4Equations and Their Solutions

A1(A) apply mathematics to problems arising in everyday life, society, and the workplace

+

A1(C) select tools, including real objects, manipulatives, paper and pencil, and technology as appropriate, and techniques, including mental math, estimation, and number sense as appropriate, to solve problems

+

A2(C) write linear equations in two variables given a table of values, a graph, and a verbal description

+``` + +--- + ## Horizontal Table Adds a solid border, styled header cell, and padding to a horizontal table. diff --git a/src/components/DropdownProblem.tsx b/src/components/DropdownProblem.tsx index 8ad1fb19..525543db 100644 --- a/src/components/DropdownProblem.tsx +++ b/src/components/DropdownProblem.tsx @@ -2,7 +2,7 @@ import type { BaseProblemProps } from './ProblemSetBlock' import { determineFeedback } from '../lib/problems' import { Formik, Form, Field, ErrorMessage } from 'formik' import { mathifyElement } from '../lib/math' -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import * as Yup from 'yup' import { AttemptsCounter } from './AttemptsCounter' import { CorrectAnswerIcon, WrongAnswerIcon } from './Icons' @@ -15,11 +15,21 @@ interface DropdownFormValues { response: string } +interface PersistorData { + userResponse: string + formDisabled: boolean + retriesAllowed: number + feedback: string +} + +enum PersistorGetStatus { + Uninitialized, + Success, + Failure +} + export function buildClassName(response: string, solution: string, formDisabled: boolean): string { let className = 'os-form-select' - if (response !== '') { - className += ' os-selected-answer-choice' - } if (solution === response && formDisabled) { className += ' os-correct-answer-choice os-disabled' } @@ -32,11 +42,13 @@ export function buildClassName(response: string, solution: string, formDisabled: export const DropdownProblem = ({ solvedCallback, exhaustedCallback, allowedRetryCallback, content, contentId, buttonText, solutionOptions, encourageResponse, correctResponse, solution, retryLimit, answerResponses, attemptsExhaustedResponse, - onProblemAttempt + onProblemAttempt, persistor }: DropdownProblemProps): JSX.Element => { const [feedback, setFeedback] = useState('') const [formDisabled, setFormDisabled] = useState(false) const [retriesAllowed, setRetriesAllowed] = useState(0) + const [response, setResponse] = useState('') + const [persistorGetStatus, setPersistorGetStatus] = useState(PersistorGetStatus.Uninitialized) const schema = Yup.object({ response: Yup.string().trim().required('Please select an answer') @@ -66,11 +78,54 @@ export const DropdownProblem = ({ return input === answer } + const setPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + return + } + + if (response !== '' || formDisabled || retriesAllowed !== 0) { + const newPersistedData: PersistorData = { userResponse: response, formDisabled, retriesAllowed, feedback } + await persistor.put(contentId, JSON.stringify(newPersistedData)) + } + } catch (err) { + console.error(err) + } + } + + const getPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + setPersistorGetStatus(PersistorGetStatus.Success) + return + } + + const persistedState = await persistor.get(contentId) + if (persistedState !== null) { + const parsedPersistedState = JSON.parse(persistedState) + setResponse(parsedPersistedState.userResponse) + setFormDisabled(parsedPersistedState.formDisabled) + setRetriesAllowed(parsedPersistedState.retriesAllowed) + setFeedback(parsedPersistedState.feedback) + } + setPersistorGetStatus(PersistorGetStatus.Success) + } catch (err) { + setPersistorGetStatus(PersistorGetStatus.Failure) + } + } + + useEffect(() => { + setPersistedState().catch(() => { }) + getPersistedState().catch(() => { }) + }, [response, formDisabled, retriesAllowed]) + const handleSubmit = async (values: DropdownFormValues): Promise => { let correct = false let finalAttempt = false const attempt = retriesAllowed + 1 + setResponse(values.response) + if (evaluateInput(values.response, solution)) { correct = true setFeedback(correctResponse) @@ -99,23 +154,46 @@ export const DropdownProblem = ({ } } + if (persistorGetStatus === PersistorGetStatus.Failure) { + return ( +
+
+ There was an error loading content. Please try refreshing the page. +
+
+ ) + } + + if (persistorGetStatus === PersistorGetStatus.Uninitialized) { + return ( +
+
+
+ Loading... +
+
+
+ ) + } + return (
{({ isSubmitting, values, setFieldValue }) => (
- {solution === values.response && formDisabled && + {solution === response && formDisabled &&
} - {solution !== values.response && formDisabled && + {solution !== response && formDisabled &&
@@ -123,9 +201,9 @@ export const DropdownProblem = ({ ) => { clearFeedback(); void setFieldValue('response', e.target.value) }} > {generateOptions()} diff --git a/src/components/InputProblem.tsx b/src/components/InputProblem.tsx index e69b9fef..dc7b9795 100644 --- a/src/components/InputProblem.tsx +++ b/src/components/InputProblem.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react' +import React, { useState, useCallback, useEffect } from 'react' import type { BaseProblemProps } from './ProblemSetBlock' import { determineFeedback } from '../lib/problems' import { Formik, Form, Field, ErrorMessage, type FormikHelpers } from 'formik' @@ -24,6 +24,19 @@ interface InputFormValues { response: string } +interface PersistorData { + userResponse: string + inputDisabled: boolean + retriesAllowed: number + feedback: string +} + +enum PersistorGetStatus { + Uninitialized, + Success, + Failure +} + export function buildClassName(correct: boolean, formDisabled: boolean, errorResponse: string | undefined): string { let className = 'os-form-control' if (correct && formDisabled) { @@ -40,11 +53,14 @@ export function buildClassName(correct: boolean, formDisabled: boolean, errorRes export const InputProblem = ({ solvedCallback, exhaustedCallback, allowedRetryCallback, attemptsExhaustedResponse, - solution, retryLimit, content, contentId, comparator, encourageResponse, buttonText, correctResponse, answerResponses, onProblemAttempt + solution, retryLimit, content, contentId, comparator, encourageResponse, buttonText, correctResponse, answerResponses, onProblemAttempt, + persistor }: InputProblemProps): JSX.Element => { const [retriesAllowed, setRetriesAllowed] = useState(0) const [inputDisabled, setInputDisabled] = useState(false) const [feedback, setFeedback] = useState('') + const [response, setResponse] = useState('') + const [persistorGetStatus, setPersistorGetStatus] = useState(PersistorGetStatus.Uninitialized) const NUMERIC_INPUT_ERROR = 'Enter numeric values only' const EXCEEDED_MAX_INPUT_ERROR = 'Input is too long' const NON_EMPTY_VALUE_ERROR = 'Please provide valid input' @@ -105,11 +121,54 @@ export const InputProblem = ({ return trimmedInput.toLowerCase() === trimmedAnswer.toLowerCase() } + const setPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + return + } + + if (response !== '' || inputDisabled || retriesAllowed !== 0) { + const newPersistedData: PersistorData = { userResponse: response, inputDisabled, retriesAllowed, feedback } + await persistor.put(contentId, JSON.stringify(newPersistedData)) + } + } catch (err) { + console.error(err) + } + } + + const getPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + setPersistorGetStatus(PersistorGetStatus.Success) + return + } + + const persistedState = await persistor.get(contentId) + if (persistedState !== null) { + const parsedPersistedState = JSON.parse(persistedState) + setResponse(parsedPersistedState.userResponse) + setInputDisabled(parsedPersistedState.inputDisabled) + setRetriesAllowed(parsedPersistedState.retriesAllowed) + setFeedback(parsedPersistedState.feedback) + } + setPersistorGetStatus(PersistorGetStatus.Success) + } catch (err) { + setPersistorGetStatus(PersistorGetStatus.Failure) + } + } + + useEffect(() => { + setPersistedState().catch(() => { }) + getPersistedState().catch(() => { }) + }, [response, inputDisabled, retriesAllowed]) + const handleSubmit = async (values: InputFormValues, { setFieldError }: FormikHelpers): Promise => { let correct = false let finalAttempt = false const attempt = retriesAllowed + 1 + setResponse(values.response) + if (values.response.trim() === '') { if ((comparator.toLowerCase() === 'integer') || (comparator.toLowerCase() === 'float')) { setFieldError('response', NUMERIC_INPUT_ERROR) @@ -149,26 +208,50 @@ export const InputProblem = ({ const clearFeedback = (): void => { setFeedback('') } + + if (persistorGetStatus === PersistorGetStatus.Failure) { + return ( +
+
+ There was an error loading content. Please try refreshing the page. +
+
+ ) + } + + if (persistorGetStatus === PersistorGetStatus.Uninitialized) { + return ( +
+
+
+ Loading... +
+
+
+ ) + } + return (
{({ isSubmitting, setFieldValue, values, errors }) => (
- {evaluateInput(values.response, solution) && inputDisabled && + {evaluateInput(response, solution) && inputDisabled &&
} - {!evaluateInput(values.response, solution) && inputDisabled && + {!evaluateInput(response, solution) && inputDisabled &&
diff --git a/src/components/MultipleChoiceProblem.tsx b/src/components/MultipleChoiceProblem.tsx index e05a64ef..48c6f57e 100644 --- a/src/components/MultipleChoiceProblem.tsx +++ b/src/components/MultipleChoiceProblem.tsx @@ -1,6 +1,6 @@ import type { BaseProblemProps } from './ProblemSetBlock' import { determineFeedback } from '../lib/problems' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Formik, Form, ErrorMessage, type FormikErrors } from 'formik' import { mathifyElement } from '../lib/math' import * as Yup from 'yup' @@ -15,6 +15,20 @@ interface MultipleChoiceFormValues { response: string } +interface PersistorData { + userResponse: string + formDisabled: boolean + retriesAllowed: number + feedback: string + showAnswers: boolean +} + +enum PersistorGetStatus { + Uninitialized, + Success, + Failure +} + export function buildClassName(val: string, solution: string, response: string, showAnswers: boolean): string { let className = 'os-form-check os-default-answer-choice' @@ -51,13 +65,16 @@ export const MultipleChoiceProblem = ({ solution, retryLimit, attemptsExhaustedResponse, - onProblemAttempt + onProblemAttempt, + persistor }: MultipleChoiceProps): JSX.Element => { const [feedback, setFeedback] = useState('') const [formDisabled, setFormDisabled] = useState(false) const [retriesAllowed, setRetriesAllowed] = useState(0) const [showAnswers, setShowAnswers] = useState(false) const parsedOptionValues: string[] = JSON.parse(solutionOptions) + const [response, setResponse] = useState('') + const [persistorGetStatus, setPersistorGetStatus] = useState(PersistorGetStatus.Uninitialized) const schema = Yup.object({ response: Yup.string().trim().required('Please select an answer') @@ -102,7 +119,7 @@ export const MultipleChoiceProblem = ({ disabled={isSubmitting || formDisabled} onChange={onChange} showAnswer={showAnswers} - selected={values.response === val} + selected={response === val} />
) @@ -115,6 +132,48 @@ export const MultipleChoiceProblem = ({ return input === answer } + const setPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + return + } + + if (response !== '' || formDisabled || retriesAllowed !== 0) { + const newPersistedData: PersistorData = { userResponse: response, formDisabled, retriesAllowed, feedback, showAnswers } + await persistor.put(contentId, JSON.stringify(newPersistedData)) + } + } catch (err) { + console.error(err) + } + } + + const getPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + setPersistorGetStatus(PersistorGetStatus.Success) + return + } + + const persistedState = await persistor.get(contentId) + if (persistedState !== null) { + const parsedPersistedState = JSON.parse(persistedState) + setResponse(parsedPersistedState.userResponse) + setFormDisabled(parsedPersistedState.formDisabled) + setRetriesAllowed(parsedPersistedState.retriesAllowed) + setFeedback(parsedPersistedState.feedback) + setShowAnswers(parsedPersistedState.showAnswers) + } + setPersistorGetStatus(PersistorGetStatus.Success) + } catch (err) { + setPersistorGetStatus(PersistorGetStatus.Failure) + } + } + + useEffect(() => { + setPersistedState().catch(() => { }) + getPersistedState().catch(() => { }) + }, [response, formDisabled, retriesAllowed]) + const handleSubmit = async ( values: MultipleChoiceFormValues ): Promise => { @@ -122,6 +181,8 @@ export const MultipleChoiceProblem = ({ let finalAttempt = false const attempt = retriesAllowed + 1 + setResponse(values.response) + if (evaluateInput(values.response, solution)) { correct = true setFeedback(correctResponse) @@ -159,13 +220,36 @@ export const MultipleChoiceProblem = ({ } } + if (persistorGetStatus === PersistorGetStatus.Failure) { + return ( +
+
+ There was an error loading content. Please try refreshing the page. +
+
+ ) + } + + if (persistorGetStatus === PersistorGetStatus.Uninitialized) { + return ( +
+
+
+ Loading... +
+
+
+ ) + } + return (
{({ isSubmitting, setFieldValue, values }) => ( diff --git a/src/components/MultiselectProblem.tsx b/src/components/MultiselectProblem.tsx index 80b903db..faef7890 100644 --- a/src/components/MultiselectProblem.tsx +++ b/src/components/MultiselectProblem.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { mathifyElement } from '../lib/math' import { determineFeedback } from '../lib/problems' import type { BaseProblemProps } from './ProblemSetBlock' @@ -15,12 +15,26 @@ interface MultiselectFormValues { response: string[] } -export function buildClassName(solutionArray: string[], showAnswers: boolean, val: string, values: { response: string[] }): string { +interface PersistorData { + userResponse: string[] + formDisabled: boolean + retriesAllowed: number + feedback: string + showAnswers: boolean +} + +enum PersistorGetStatus { + Uninitialized, + Success, + Failure +} + +export function buildClassName(solutionArray: string[], showAnswers: boolean, val: string, response: string[]): string { let className = 'os-default-answer-choice' if (solutionArray.includes(val) && showAnswers) { className += ' os-correct-answer-choice' - } else if (!solutionArray.includes(val) && values.response.includes(val) && showAnswers) { + } else if (!solutionArray.includes(val) && response.includes(val) && showAnswers) { className += ' os-wrong-answer-choice' } @@ -28,11 +42,11 @@ export function buildClassName(solutionArray: string[], showAnswers: boolean, va className += ' os-disabled' } - if (values.response.includes(val)) { + if (response.includes(val)) { className += ' os-selected-answer-choice' } - if (values.response.includes(val) && showAnswers) { + if (response.includes(val) && showAnswers) { className += ' os-form-check' } @@ -53,7 +67,8 @@ export const MultiselectProblem = ({ retryLimit, answerResponses, attemptsExhaustedResponse, - onProblemAttempt + onProblemAttempt, + persistor }: MultiselectProps): JSX.Element => { const [feedback, setFeedback] = useState('') const [formDisabled, setFormDisabled] = useState(false) @@ -61,6 +76,8 @@ export const MultiselectProblem = ({ const solutionArray: string[] = JSON.parse(solution) const parsedOptionValues: string[] = JSON.parse(solutionOptions) const [showAnswers, setShowAnswers] = useState(false) + const [response, setResponse] = useState([]) + const [persistorGetStatus, setPersistorGetStatus] = useState(PersistorGetStatus.Uninitialized) const schema = Yup.object({ response: Yup.array().min(1, 'Please select an answer') @@ -102,14 +119,14 @@ export const MultiselectProblem = ({ const onChange = (e: React.ChangeEvent): void => { clearFeedback(); void setFieldValue('response', modifyModel(values, e)) } parsedOptionValues.forEach(val => options.push( -
+
)) @@ -133,10 +150,55 @@ export const MultiselectProblem = ({ return true } + const setPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + return + } + + if (response.length > 0 || formDisabled || retriesAllowed !== 0) { + const newPersistedData: PersistorData = { userResponse: response, formDisabled, retriesAllowed, feedback, showAnswers } + await persistor.put(contentId, JSON.stringify(newPersistedData)) + } + } catch (err) { + console.error(err) + } + } + + const getPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + setPersistorGetStatus(PersistorGetStatus.Success) + return + } + + const persistedState = await persistor.get(contentId) + if (persistedState !== null) { + const parsedPersistedState = JSON.parse(persistedState) + setResponse(parsedPersistedState.userResponse) + setFormDisabled(parsedPersistedState.formDisabled) + setRetriesAllowed(parsedPersistedState.retriesAllowed) + setFeedback(parsedPersistedState.feedback) + setShowAnswers(parsedPersistedState.showAnswers) + } + setPersistorGetStatus(PersistorGetStatus.Success) + } catch (err) { + setPersistorGetStatus(PersistorGetStatus.Failure) + } + } + + useEffect(() => { + setPersistedState().catch(() => { }) + getPersistedState().catch(() => { }) + }, [JSON.stringify(response), formDisabled, retriesAllowed]) + const handleSubmit = async (values: MultiselectFormValues): Promise => { let correct = false let finalAttempt = false const attempt = retriesAllowed + 1 + + setResponse(values.response) + if (compareForm(values.response, solutionArray)) { correct = true setFeedback(correctResponse) @@ -174,13 +236,36 @@ export const MultiselectProblem = ({ } } + if (persistorGetStatus === PersistorGetStatus.Failure) { + return ( +
+
+ There was an error loading content. Please try refreshing the page. +
+
+ ) + } + + if (persistorGetStatus === PersistorGetStatus.Uninitialized) { + return ( +
+
+
+ Loading... +
+
+
+ ) + } + return (
{({ isSubmitting, setFieldValue, values }) => ( diff --git a/src/components/ProblemSetBlock.tsx b/src/components/ProblemSetBlock.tsx index fcd55f35..7d3f127b 100644 --- a/src/components/ProblemSetBlock.tsx +++ b/src/components/ProblemSetBlock.tsx @@ -5,6 +5,7 @@ import { InputProblem } from './InputProblem' import { MultipleChoiceProblem } from './MultipleChoiceProblem' import { MultiselectProblem } from './MultiselectProblem' import { ContentLoadedContext } from '../lib/contexts' +import { type Persistor, browserPersistor } from '../lib/persistor' export interface ProblemData { type: string @@ -46,6 +47,7 @@ export interface BaseProblemProps { finalAttempt: boolean, psetProblemContentId: string | undefined ) => void + persistor?: Persistor } export const NO_MORE_ATTEMPTS_MESSAGE = 'No more attempts allowed' @@ -177,7 +179,8 @@ export const ProblemSetBlock = ({ waitForEvent, fireSuccessEvent, fireLearningOp buttonText: prob.buttonText, attemptsExhaustedResponse: prob.attemptsExhaustedResponse, answerResponses: prob.answerResponses, - onProblemAttempt: problemAttemptedCallbackFactory(prob.type) + onProblemAttempt: problemAttemptedCallbackFactory(prob.type), + persistor: browserPersistor } if (prob.type === PROBLEM_TYPE_INPUT) { children.push( void + persistor?: Persistor } interface InputFormValues { response: string } +interface PersistorData { + userResponse: string + responseSubmitted: boolean +} + +enum PersistorGetStatus { + Uninitialized, + Success, + Failure +} + export function buildClassName(formDisabled: boolean, errorResponse: string | undefined): string { let className = 'os-form-control' if (formDisabled) { @@ -40,8 +53,10 @@ export function buildClassName(formDisabled: boolean, errorResponse: string | un return className } -export const UserInputBlock = ({ content, prompt, ack, waitForEvent, fireEvent, buttonText, contentId, onInputSubmitted }: UserInputBlockProps): JSX.Element => { +export const UserInputBlock = ({ content, prompt, ack, waitForEvent, fireEvent, buttonText, contentId, onInputSubmitted, persistor }: UserInputBlockProps): JSX.Element => { const [responseSubmitted, setResponseSubmitted] = useState(false) + const [response, setResponse] = useState('') + const [persistorGetStatus, setPersistorGetStatus] = useState(PersistorGetStatus.Uninitialized) const contentLoadedContext = useContext(ContentLoadedContext) const NON_EMPTY_VALUE_ERROR = 'Please provide valid input' const EXCEEDED_MAX_INPUT_ERROR = 'Input is too long' @@ -51,12 +66,45 @@ export const UserInputBlock = ({ content, prompt, ack, waitForEvent, fireEvent, .max(MAX_CHARACTER_INPUT_BLOCK_LENGTH, EXCEEDED_MAX_INPUT_ERROR) }) + const getPersistedState = async (): Promise => { + try { + if (contentId === undefined || persistor === undefined) { + setPersistorGetStatus(PersistorGetStatus.Success) + return + } + + const persistedState = await persistor.get(contentId) + if (persistedState !== null) { + const parsedPersistedState = JSON.parse(persistedState) + setResponse(parsedPersistedState.userResponse) + setResponseSubmitted(parsedPersistedState.responseSubmitted) + } + setPersistorGetStatus(PersistorGetStatus.Success) + } catch (err) { + setPersistorGetStatus(PersistorGetStatus.Failure) + } + } + + useEffect(() => { + getPersistedState().catch(() => { }) + }, []) + const handleSubmit = async (values: InputFormValues, { setFieldError }: FormikHelpers): Promise => { if (values.response.trim() === '') { setFieldError('response', NON_EMPTY_VALUE_ERROR) return } + try { + if (contentId !== undefined && persistor !== undefined) { + const newPersistedData: PersistorData = { userResponse: values.response, responseSubmitted: true } + await persistor.put(contentId, JSON.stringify(newPersistedData)) + } + } catch (error) { + setFieldError('response', 'Error saving response. Please try again.') + return + } + setResponseSubmitted(true) if (fireEvent !== undefined) { @@ -86,13 +134,35 @@ export const UserInputBlock = ({ content, prompt, ack, waitForEvent, fireEvent, < div className='my-3 os-feedback-message ' ref={contentRefCallback} dangerouslySetInnerHTML={{ __html: ack }} /> ) + if (persistorGetStatus === PersistorGetStatus.Failure) { + return ( +
+
+ There was an error loading content. Please try refreshing the page. +
+
+ ) + } + + if (persistorGetStatus === PersistorGetStatus.Uninitialized) { + return ( +
+
+
+ Loading... +
+
+
+ ) + } + return (
fireEvent={fireEvent} contentId={contentId} onInputSubmitted={onInputSubmitted} + persistor={browserPersistor} /> } diff --git a/src/lib/persistor.ts b/src/lib/persistor.ts new file mode 100644 index 00000000..a2331037 --- /dev/null +++ b/src/lib/persistor.ts @@ -0,0 +1,13 @@ +export interface Persistor { + get: (key: string) => Promise + put: (key: string, value: string) => Promise +} + +export const browserPersistor: Persistor = { + get: async (key: string): Promise => { + return localStorage.getItem(key) + }, + put: async (key: string, value: string) => { + localStorage.setItem(key, value) + } +} diff --git a/src/styles/content.scss b/src/styles/content.scss index a1edfa8d..32414377 100644 --- a/src/styles/content.scss +++ b/src/styles/content.scss @@ -102,6 +102,29 @@ table.os-raise-standardtable { } } +%os-raise-textheavytable-base { + @extend %os-raise-basictable; + + width: 100%; + + th, + td { + text-align: left; + } + + th { + vertical-align: bottom; + } + + td { + vertical-align: top; + } + + td:first-child { + border-left: none; + } +} + table.os-raise-doubleheadertable { table-layout: fixed; width: 100%; @@ -140,29 +163,16 @@ table.os-raise-doubleheadertable { } table.os-raise-textheavytable { - @extend %os-raise-basictable; + @extend %os-raise-textheavytable-base; table-layout: fixed; - width: 100%; +} - /* stylelint-disable-next-line no-descending-specificity */ - th, - td { - text-align: left; - } - - /* stylelint-disable-next-line no-descending-specificity */ - th { - vertical-align: bottom; - } +table.os-raise-textheavyadjustedtable { + @extend %os-raise-textheavytable-base; - td { - vertical-align: top; - } + table-layout: auto; - td:first-child { - border-left: none; - } } table.os-raise-midsizetable { diff --git a/src/tests/DropdownProblem.test.tsx b/src/tests/DropdownProblem.test.tsx index 595dfae1..dce9dd4f 100644 --- a/src/tests/DropdownProblem.test.tsx +++ b/src/tests/DropdownProblem.test.tsx @@ -348,11 +348,11 @@ test('returns the correct className string', () => { let className = buildClassName(response, solution, formDisabled) - expect(className).toEqual('os-form-select os-selected-answer-choice os-correct-answer-choice os-disabled') + expect(className).toEqual('os-form-select os-correct-answer-choice os-disabled') solution = 'B' className = buildClassName(response, solution, formDisabled) - expect(className).toEqual('os-form-select os-selected-answer-choice os-wrong-answer-choice os-disabled') + expect(className).toEqual('os-form-select os-wrong-answer-choice os-disabled') }) diff --git a/src/tests/MultiselectProblem.test.tsx b/src/tests/MultiselectProblem.test.tsx index 1cfae087..48eab14c 100644 --- a/src/tests/MultiselectProblem.test.tsx +++ b/src/tests/MultiselectProblem.test.tsx @@ -410,7 +410,7 @@ test('returns the correct className string', () => { const solutionArray = ['A', 'B'] const showAnswers = true let val = 'B' - let values = { response: ['A', 'B'] } + let values = ['A', 'B'] const expectedCorrectClassName = 'os-default-answer-choice os-correct-answer-choice os-disabled os-selected-answer-choice os-form-check' const expectedIncorrectClassName = @@ -420,7 +420,7 @@ test('returns the correct className string', () => { expect(correctAnswerClassName).toBe(expectedCorrectClassName) val = 'C' - values = { response: ['A', 'B', 'C'] } + values = ['A', 'B', 'C'] const incorrectAnswerClassName = buildClassName(solutionArray, showAnswers, val, values) expect(incorrectAnswerClassName).toBe(expectedIncorrectClassName) }) diff --git a/src/tests/UserInputBlock.test.tsx b/src/tests/UserInputBlock.test.tsx index e8e907d7..cdcce992 100644 --- a/src/tests/UserInputBlock.test.tsx +++ b/src/tests/UserInputBlock.test.tsx @@ -215,7 +215,7 @@ test('UserInputBlock calls onInputSumbitted callback', async () => { {generatedContentBlock as JSX.Element} ) - + await screen.findByRole('textbox') fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Input text' } }) await act(async () => { screen.getByRole('button').click() @@ -250,7 +250,7 @@ test('UserInputBlock does not call onInputSumbitted on default context', async ( render( generatedContentBlock as JSX.Element ) - + await screen.findByRole('textbox') fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Input text' } }) await act(async () => { screen.getByRole('button').click()