Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tabular Input Support #29

Open
wants to merge 13 commits into
base: staging
Choose a base branch
from
55 changes: 55 additions & 0 deletions src/ProblemLayout/ProblemInput/ProblemInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import TextField from "@material-ui/core/TextField";
import MultipleChoice from "./MultipleChoice";
import GridInput from "./GridInput";
import MatrixInput from "./MatrixInput";
import TableInput from "./TableInput";
import { renderText } from "../../ProblemLogic/renderText";
import clsx from "clsx";
import './ProblemInput.css'
import { shuffleArray } from "../../util/shuffleArray";
import { EQUATION_EDITOR_AUTO_COMMANDS, EQUATION_EDITOR_AUTO_OPERATORS, ThemeContext } from "../../config/config";
import { stagingProp } from "../../util/addStagingProperty";
import { parseMatrixTex } from "../../util/parseMatrixTex";
import { parseTableTex } from "../../util/parseTableTex";

class ProblemInput extends React.Component {
static contextType = ThemeContext;
Expand All @@ -22,6 +24,8 @@ class ProblemInput extends React.Component {
this.equationRef = createRef()

this.onEquationChange = this.onEquationChange.bind(this)

this.questionAnswer = this.props.stepAnswer
}

componentDidMount() {
Expand All @@ -30,6 +34,11 @@ class ProblemInput extends React.Component {
console.log('automatically determined matrix input to be the correct problem type')
}

console.debug('problem', this.props.step, 'seed', this.props.seed)
if (this.isTableInput()) {
console.log('automatically determined table input to be the correct problem type')
}

const mqDisplayArea = this.equationRef?.current?.querySelector(".mq-editable-field > .mq-root-block")
if (mqDisplayArea != null) {
mqDisplayArea.ariaHidden = true
Expand All @@ -52,6 +61,17 @@ class ProblemInput extends React.Component {
}
}

isTableInput() {
if (this.props.step?.stepAnswer) {
return this.props.step?.problemType !== "MultipleChoice" &&
/\\begin{[a-zA-Z]?tabular}/.test(this.props.step.stepAnswer[0])
}
if (this.props.step?.hintAnswer) {
return this.props.step?.problemType !== "MultipleChoice" &&
/\\begin{[a-zA-Z]?tabular}/.test(this.props.step.hintAnswer[0])
}
}

onEquationChange(eq) {
const containerEl = this.equationRef?.current
const eqContentEl = this.equationRef?.current?.querySelector(".mq-editable-field")
Expand All @@ -75,6 +95,17 @@ class ProblemInput extends React.Component {
}
this.props.setInputValState(eq)
}


getDimAndHeaders(correctAnswer) {
const temp = correctAnswer;
const TotalRows = correctAnswer.split("\\\\").length;
const numCols = correctAnswer.split("\\\\").join("&").split("&").length / TotalRows;
const headers = temp.match(/([a-zA-z]+)/g).slice(2,2+numCols)
const numRows = TotalRows - 1;
return {numRows, numCols, headers}
}


render() {
const { classes, state, index } = this.props;
Expand All @@ -87,8 +118,15 @@ class ProblemInput extends React.Component {
problemType = "MatrixInput"
}

if (this.isTableInput()) {
problemType = "TableInput"
}


return (
<Grid container spacing={0} justifyContent="center" alignItems="center">
{/* {console.log("tbl",parseTableTex(correctAnswer))} */}
{/* {console.log("mx", parseMatrixTex(correctAnswer))} */}
<Grid item xs={1} md={problemType === "TextBox" ? 4 : false}/>
<Grid item xs={9} md={problemType === "TextBox" ? 3 : 12}>
{(problemType === "TextBox" && this.props.step.answerType !== "string") && (
Expand Down Expand Up @@ -171,13 +209,30 @@ class ProblemInput extends React.Component {
} : {}}
/>
)}
{problemType === "TableInput" && (
<TableInput
onChange={(newVal) => this.props.setInputValState(newVal)}
numRows={this.getDimAndHeaders(correctAnswer).numRows}
numCols={this.getDimAndHeaders(correctAnswer).numCols}
headers= {this.getDimAndHeaders(correctAnswer).headers}
context={this.props.context}
classes={this.props.classes}
index={index}
{...(use_expanded_view && debug) ? {
defaultValue: parseTableTex(correctAnswer)[0]
} : {}}
/>


)}
</Grid>
<Grid item xs={2} md={1}>
<div style={{ marginLeft: "20%" }}>
{this.props.step.units && renderText(this.props.step.units)}
</div>
</Grid>
<Grid item xs={false} md={problemType === "TextBox" ? 3 : false}/>

</Grid>
)
}
Expand Down
145 changes: 145 additions & 0 deletions src/ProblemLayout/ProblemInput/TableInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, { createRef } from "react";
import './GridInput.css'
import Button from "@material-ui/core/Button";
import clsx from 'clsx';
import EquationEditor from "equation-editor-react";
import { Box,TextField } from "@material-ui/core";
import { EQUATION_EDITOR_AUTO_COMMANDS, EQUATION_EDITOR_AUTO_OPERATORS } from "../../config/config";
import { stagingProp } from "../../util/addStagingProperty";

class TableInput extends React.Component {

constructor(props) {

super(props);
this.state = {
gridState: props.defaultValue || this.genEmptyGrid(props.numRows, props.numCols),
fer: Math.random()
};
this.gridRef = createRef()
this.clearCells = this.clearCells.bind(this)
this.numRows = props.numRows;
this.numCols = props.numCols;
this.headers = props.headers;
this.renderTabular = props.renderTabular;
}


genEmptyGrid = (numRows, numCols) => new Array(numRows)
.fill(0)
.map(_ => new Array(numCols).fill(""))

cellFieldChange(str, idx, jdx) {
const gridState = this.state.gridState
gridState[idx][jdx] = str;

this.setState({
gridState
}, () => {
this.props.onChange(JSON.stringify(this.state.gridState))
})
}

clearCells(evt) {
if (evt != null && evt.type === 'submit') {
evt.preventDefault()
}

this.setState({
gridState: this.genEmptyGrid(this.numRows, this.numCols),
fer: Math.random()
}, () => {
this.props.onChange(JSON.stringify(this.state.gridState))
})

}


render() {
const { classes, index } = this.props;

const { gridState, fer } = this.state;

const revealClearButton = gridState.reduce((acc, cur, _) =>
acc + cur.reduce((_acc, _cur, __) =>
_acc + _cur.length, 0
), 0
) > 0; // only reveal the clear button if there is at least something in a cell

return (
<div>
<Box
display={'grid'}
gridTemplateColumns={`repeat(${gridState[0].length}, 0fr)`}
overflow={'auto'}
pt={0}
pb={1}
gridColumnGap={1}
gridGap={1}
justifyContent={'center'}
sx={{
'& .MuiTextField-root': {borderRadius: 0,
outline: "1px solid #c4c4c4", borderTop: '1px solid #c4c4c4', height:'40px',
background: '#ececec'}

}}
>
{console.log(gridState[0])}
{
gridState[0].map((row, idx) => {
return (
<TextField
disabled
id="filled-disabled"
defaultValue={this.props.headers[idx]}
key ={idx}
className={clsx(classes.textInput)}
InputProps={{ disableUnderline: true,
style: { color: "#808080"}
}}
inputProps={{min: 0, style: { textAlign: 'center' }}}
/>
)
})
}
{
gridState.map((row, idx) =>
row.map((val, jdx) => {
return (
<center
className={clsx(classes.textTblLatex, 'grid-cell')}
key={`cell-${idx}-${jdx}-${fer}`}
aria-label={`Cell (${idx}, ${jdx})`}
{...stagingProp({
"data-selenium-target": `grid-answer-cell-${jdx + idx * this.state.numCols}-${index}`
})}
>
<EquationEditor
value={val}
onChange={(str) => this.cellFieldChange(str, idx, jdx)}
style={{ width: "100%" }}
autoCommands={EQUATION_EDITOR_AUTO_COMMANDS}
autoOperatorNames={EQUATION_EDITOR_AUTO_OPERATORS}
/>

</center>

)
})
)
}

</Box>
<Box mt={1} display={'grid'} width={'100%'} alignItems={'center'} justifyContent={'center'}>
<Button variant="contained" color="secondary" onClick={this.clearCells}
className={clsx("revealable", revealClearButton && "revealed")}>clear all
cells</Button>
</Box>
</div>
)
}

}


export default TableInput;
21 changes: 21 additions & 0 deletions src/ProblemLayout/commonStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,27 @@ const styles = theme => ({
paddingBottom: 5
}
},
textTblLatex: {
borderRadius: "0px",
outline: "1px solid #c4c4c4",
'&:hover': {
border: "1px solid #000000",
},
'&:focus-within': {
border: "2px solid #3f51b5",
},
height: 50,
width: '100%',
'& > .mq-editable-field': {
display: 'table',
tableLayout: 'fixed'
},
'& > * > *[mathquill-block-id]': {
height: 50,
display: 'table-cell',
paddingBottom: 5
}
},
textBoxLatexIncorrect: {
boxShadow: "0 0 0.75pt 0.75pt red",
'&:focus-within': {
Expand Down
23 changes: 23 additions & 0 deletions src/ProblemLogic/checkAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { variabilize } from './variabilize.js';
import insert from "../util/strInsert";
import { parseMatrixTex } from "../util/parseMatrixTex";
import { IS_DEVELOPMENT, IS_STAGING_OR_DEVELOPMENT } from "../util/getBuildType";
import { parseTableTex } from '../util/parseTableTex.js';

const KAS = require('../kas.js');

Expand Down Expand Up @@ -63,6 +64,8 @@ function parse(_string) {

function checkAnswer(attempt, actual, answerType, precision, variabilization) {
let parsed = attempt.replace(/\s+/g, '');
console.debug(`attempt: ${attempt} vs. actual:`, actual)
console.log(parsed)
if (variabilization) {
actual = actual.map((actualAns) => variabilize(actualAns, variabilization));
}
Expand Down Expand Up @@ -93,6 +96,26 @@ function checkAnswer(attempt, actual, answerType, precision, variabilization) {
})

return [attempt, correctAnswer]
} else if (/\\begin{[a-zA-Z]?tabular}/.test(actual)){
console.debug(`attempt: ${attempt} vs. actual:`, actual)
const studentMatrix = JSON.parse(attempt)
const solutionMatrices = parseTableTex(actual);

console.debug('solutions: ', solutionMatrices)
correctAnswer = solutionMatrices.some(matrix => {
return matrix.reduce((acc, row, idx) => acc && row.reduce((_acc, cell, jdx) => {
const _studentRow = studentMatrix[idx] || []
const _studentCell = _studentRow[jdx] || ""
const _studentExpr = parse(_studentCell).expr

const _solExpr = parse(cell).expr

return _acc && KAS.compare(_studentExpr, _solExpr).equal
}, true), true)
})

return [attempt, correctAnswer]

} else {
if (IS_STAGING_OR_DEVELOPMENT) {
console.debug("Using KAS to compare answer with solution", attempt, actual)
Expand Down
Loading