Skip to content

Commit d979d6d

Browse files
committed
Allow loading of old-style coordinate answers
Moving to using an array means we don't load the x and y values from existing answers. This supports doing that, whilst cleaning the updated choice so that if it is submitted again there is no sign of the old-style properties.
1 parent fd8b29a commit d979d6d

File tree

2 files changed

+44
-8
lines changed

2 files changed

+44
-8
lines changed

src/IsaacApiTypes.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface AssignmentStatusDTO {
4444
errorMessage?: string;
4545
}
4646

47-
export interface AssignmentProgressDTO {
47+
export interface AssignmentProgressDTO {
4848
user?: UserSummaryDTO;
4949
correctPartResults?: number[];
5050
incorrectPartResults?: number[];
@@ -514,6 +514,8 @@ export interface ParsonsItemDTO extends ItemDTO {
514514

515515
export interface CoordinateItemDTO extends ItemDTO {
516516
coordinates?: string[];
517+
x?: string; // deprecated
518+
y?: string; // deprecated
517519
}
518520

519521
export interface QuantityDTO extends ChoiceDTO {

src/app/components/content/IsaacCoordinateQuestion.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const coordinateInputValidator = (input: (readonly string[])[]) => {
3030
if (/[0-9]\s*[+/÷\-x×]\s*[0-9]/.test(value)) {
3131
containsOperator = true;
3232
}
33-
const foundBadChars = [...value.matchAll(/[^ 0-9+-.eE]/g)];
33+
const foundBadChars = [...value.matchAll(/[^ 0-9+-.eE]/g)];
3434
if (foundBadChars.length > 0) {
3535
allBadChars.push(foundBadChars.toString());
3636
}
@@ -49,6 +49,41 @@ export const coordinateInputValidator = (input: (readonly string[])[]) => {
4949
return errors;
5050
};
5151

52+
const coordItemToValue = function (item: Immutable<CoordinateItemDTO>, index: number) {
53+
if (isDefined(item.x) && isDefined(item.y) ) {
54+
// This is an old-style choice, we need to display the x and y properties for indexes 0 and 1.
55+
return index === 0 ? item.x : (index === 1 ? item.y : "");
56+
}
57+
return isDefined(item.coordinates?.[index]) ? item.coordinates[index] : "";
58+
};
59+
60+
const updateCoordItem = function (item: Immutable<CoordinateItemDTO>, newValue: string, index: number, numberOfDimensions: number) {
61+
let coords;
62+
if (!item?.coordinates?.length) {
63+
// Create an array, and backfill with old-style x and y if necessary:
64+
coords = Array<string>(numberOfDimensions).fill("");
65+
if (isDefined(item.x)) {
66+
coords[0] = item.x;
67+
}
68+
if (isDefined(item.y)) {
69+
coords[1] = item.y;
70+
}
71+
} else {
72+
coords = item.coordinates;
73+
}
74+
coords = coords.with(index, newValue);
75+
return {...item, coordinates: coords};
76+
};
77+
78+
const cleanItem = function (item: Immutable<CoordinateItemDTO>) {
79+
const { x, y, ...cleaned } = item;
80+
// Remove x and y from the top-level object, but only discard if coordinates already set, otherwise use to init:
81+
if (isDefined(x) && isDefined(y) && !isDefined(cleaned.coordinates)) {
82+
return {...cleaned, coordinates: [x, y]};
83+
}
84+
return cleaned;
85+
};
86+
5287
const CoordinateInput = (props: CoordinateInputProps) => {
5388
const {value, placeholderValues, numberOfDimensions, onChange, readonly, remove} = props;
5489
return <span className="coordinate-input">({[...Array(numberOfDimensions)].map((_, i) =>
@@ -57,9 +92,8 @@ const CoordinateInput = (props: CoordinateInputProps) => {
5792
type="text"
5893
className="force-print"
5994
placeholder={placeholderValues[i] ?? ""}
60-
value={isDefined(value.coordinates?.[i]) ? value.coordinates[i] : ""}
61-
onChange={event => onChange({...value, coordinates: value.coordinates && value.coordinates.length ? value.coordinates.with(i, event.target.value) :
62-
(event.target.value === "" ? undefined : Array<string>(numberOfDimensions).fill("").with(i, event.target.value))})}
95+
value={coordItemToValue(value, i)}
96+
onChange={event => onChange(updateCoordItem(value, event.target.value, i, numberOfDimensions))}
6397
readOnly={readonly}
6498
/>
6599
{(i < numberOfDimensions - 1) && <span className="coordinate-input-separator">,&nbsp;</span>}
@@ -81,13 +115,13 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
81115
}, [dispatchSetCurrentAttempt, currentAttempt]);
82116

83117
const updateItem = useCallback((index: number, value: Immutable<CoordinateItemDTO>) => {
84-
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? item : {...DEFAULT_COORDINATE_ITEM});
85-
items[index] = value;
118+
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? cleanItem(item) : {...DEFAULT_COORDINATE_ITEM});
119+
items[index] = cleanItem(value);
86120
dispatchSetCurrentAttempt({type: "coordinateChoice", items});
87121
}, [currentAttempt, dispatchSetCurrentAttempt]);
88122

89123
const removeItem = useCallback((index: number) => {
90-
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? item : {...DEFAULT_COORDINATE_ITEM});
124+
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? cleanItem(item) : {...DEFAULT_COORDINATE_ITEM});
91125
items.splice(index, 1);
92126
dispatchSetCurrentAttempt({type: "coordinateChoice", items});
93127
}, [currentAttempt, dispatchSetCurrentAttempt]);

0 commit comments

Comments
 (0)