Skip to content

Commit f37253c

Browse files
committed
Merge branch 'redesign-2024' into feature/set-assignments
2 parents 0e4d45d + 6713845 commit f37253c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1866
-547
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
REACT_APP_API_VERSION=v3.16.21rc2-SNAPSHOT
1+
REACT_APP_API_VERSION=v3.16.21rc2

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "isaac-react-app",
3-
"version": "4.0.0-rc2-SNAPSHOT",
3+
"version": "4.0.0-rc3-SNAPSHOT",
44
"private": true,
55
"engines": {
66
"node": ">=18",
@@ -123,8 +123,8 @@
123123
"@types/redux-mock-store": "^1.0.6",
124124
"@types/remarkable": "^2.0.8",
125125
"@types/uuid": "^10.0.0",
126-
"@typescript-eslint/eslint-plugin": "^8.3.0",
127-
"@typescript-eslint/parser": "^8.3.0",
126+
"@typescript-eslint/eslint-plugin": "^8.21.0",
127+
"@typescript-eslint/parser": "^8.21.0",
128128
"axios-mock-adapter": "^1.22.0",
129129
"babel-eslint": "^10.1.0",
130130
"babel-loader": "^9.1.3",
@@ -137,8 +137,8 @@
137137
"dotenv": "^10.0.0",
138138
"eslint": "^9.18.0",
139139
"eslint-plugin-jsx-a11y": "^6.9.0",
140-
"eslint-plugin-react": "^7.35.0",
141-
"eslint-plugin-react-hooks": "^4.6.2",
140+
"eslint-plugin-react": "^7.37.4",
141+
"eslint-plugin-react-hooks": "^5.1.0",
142142
"file-loader": "^6.2.0",
143143
"globals": "^15.9.0",
144144
"graphql": "^16.9.0",
@@ -161,7 +161,7 @@
161161
"ts-jest": "^29.2.5",
162162
"ts-loader": "^9.5.2",
163163
"typescript": "^5.5.4",
164-
"typescript-eslint": "^8.3.0",
164+
"typescript-eslint": "^8.21.0",
165165
"undici": "^6.19.8",
166166
"webpack": "^5.94.0",
167167
"webpack-bundle-analyzer": "^4.10.2",
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
76 KB
Loading

src/IsaacApiTypes.tsx

Lines changed: 6 additions & 6 deletions
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[];
@@ -219,8 +219,8 @@ export interface IsaacSymbolicQuestionDTO extends QuestionDTO {
219219

220220
export interface IsaacCoordinateQuestionDTO extends QuestionDTO {
221221
numberOfCoordinates?: number;
222-
placeholderXValue?: string;
223-
placeholderYValue?: string;
222+
numberOfDimensions?: number;
223+
placeholderValues?: string[];
224224
}
225225

226226
export interface IsaacTopicSummaryPageDTO extends SeguePageDTO {
@@ -513,8 +513,9 @@ export interface ParsonsItemDTO extends ItemDTO {
513513
}
514514

515515
export interface CoordinateItemDTO extends ItemDTO {
516-
x?: string;
517-
y?: string;
516+
coordinates?: string[];
517+
x?: string; // deprecated
518+
y?: string; // deprecated
518519
}
519520

520521
export interface QuantityDTO extends ChoiceDTO {
@@ -669,7 +670,6 @@ export interface GameboardItem {
669670
title?: string;
670671
subtitle?: string;
671672
description?: string;
672-
uri?: string;
673673
tags?: string[];
674674
audience?: AudienceContext[];
675675
creationContext?: AudienceContext;

src/IsaacAppTypes.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export type Action =
121121
| {type: ACTION_TYPE.QUESTION_SET_CURRENT_ATTEMPT; questionId: string; attempt: Immutable<ApiTypes.ChoiceDTO | ValidatedChoice<ApiTypes.ChoiceDTO>>}
122122

123123
| {type: ACTION_TYPE.QUESTION_SEARCH_REQUEST}
124-
| {type: ACTION_TYPE.QUESTION_SEARCH_RESPONSE_SUCCESS; questionResults: ApiTypes.ResultsWrapper<ApiTypes.ContentSummaryDTO>}
124+
| {type: ACTION_TYPE.QUESTION_SEARCH_RESPONSE_SUCCESS; questionResults: ApiTypes.SearchResultsWrapper<ApiTypes.ContentSummaryDTO>}
125125
| {type: ACTION_TYPE.QUESTION_SEARCH_RESPONSE_FAILURE}
126126

127127
| {type: ACTION_TYPE.MY_QUESTION_ANSWERS_BY_DATE_REQUEST}
@@ -747,7 +747,7 @@ export interface SearchShortcut {
747747
hash?: string;
748748
}
749749

750-
export type QuestionCorrectness = "CORRECT" | "INCORRECT" | "NOT_ANSWERED" | "NOT_SUBMITTED";
750+
export type QuestionCorrectness = "CORRECT" | "INCORRECT" | "NOT_ANSWERED" | "NOT_SUBMITTED";
751751

752752
export type Subject = "physics" | "maths" | "chemistry" | "biology";
753753

src/app/components/content/IsaacCoordinateQuestion.tsx

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ import {IsaacQuestionProps} from "../../../IsaacAppTypes";
77
import {Immutable} from "immer";
88
import QuestionInputValidation from "../elements/inputs/QuestionInputValidation";
99

10-
// Custom input component for coordinates - a pair of inputs, one for x and one for y, formatted with brackets
11-
// and a comma in between.
10+
// Custom input component for coordinates
1211
interface CoordinateInputProps {
1312
value: Immutable<CoordinateItemDTO>;
14-
placeholderXValue?: string;
15-
placeholderYValue?: string;
13+
placeholderValues: string[];
14+
numberOfDimensions: number;
1615
onChange: (value: Immutable<CoordinateItemDTO>) => void;
1716
readonly?: boolean;
1817
remove?: () => void;
1918
}
2019

21-
export const coordinateInputValidator = (input: string[][]) => {
20+
export const coordinateInputValidator = (input: (readonly string[])[]) => {
2221
const errors: string[] = [];
2322
const allBadChars: string[] = [];
2423
let containsComma = false;
@@ -31,7 +30,7 @@ export const coordinateInputValidator = (input: string[][]) => {
3130
if (/[0-9]\s*[+/÷\-x×]\s*[0-9]/.test(value)) {
3231
containsOperator = true;
3332
}
34-
const foundBadChars = [...value.matchAll(/[^ 0-9+-.eE]/g)];
33+
const foundBadChars = [...value.matchAll(/[^ 0-9+-.eE]/g)];
3534
if (foundBadChars.length > 0) {
3635
allBadChars.push(foundBadChars.toString());
3736
}
@@ -50,33 +49,60 @@ export const coordinateInputValidator = (input: string[][]) => {
5049
return errors;
5150
};
5251

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+
5387
const CoordinateInput = (props: CoordinateInputProps) => {
54-
const {value, placeholderXValue, placeholderYValue, onChange, readonly, remove} = props;
55-
return <span className="coordinate-input">
56-
(
57-
<Input
58-
type="text"
59-
className="force-print"
60-
placeholder={placeholderXValue ?? "x"}
61-
value={value.x ?? ""}
62-
onChange={event => onChange({...value, x: event.target.value === "" ? undefined : event.target.value})}
63-
readOnly={readonly}
64-
/>
65-
<span className="coordinate-input-separator">,&nbsp;</span>
66-
<Input
67-
type="text"
68-
className="force-print"
69-
placeholder={placeholderYValue ?? "y"}
70-
value={value.y ?? ""}
71-
onChange={event => onChange({...value, y: event.target.value === "" ? undefined : event.target.value})}
72-
readOnly={readonly}
73-
/>
74-
)
75-
{remove && <Button className="ms-3" size="sm" onClick={remove}>Delete</Button>}
88+
const {value, placeholderValues, numberOfDimensions, onChange, readonly, remove} = props;
89+
return <span className="coordinate-input">({[...Array(numberOfDimensions)].map((_, i) =>
90+
<span key={i}>
91+
<Input
92+
type="text"
93+
className="force-print"
94+
placeholder={placeholderValues[i] ?? ""}
95+
value={coordItemToValue(value, i)}
96+
onChange={event => onChange(updateCoordItem(value, event.target.value, i, numberOfDimensions))}
97+
readOnly={readonly}
98+
/>
99+
{(i < numberOfDimensions - 1) && <span className="coordinate-input-separator">,&nbsp;</span>}
100+
</span>)})
101+
{remove && <Button className="ms-3" size="sm" onClick={remove}>Delete</Button>}
76102
</span>;
77103
};
78104

79-
const DEFAULT_COORDINATE_ITEM = {type: "coordinateItem", x: undefined, y: undefined};
105+
const DEFAULT_COORDINATE_ITEM = {type: "coordinateItem", coordinates: []};
80106

81107
const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps<IsaacCoordinateQuestionDTO>) => {
82108

@@ -89,13 +115,13 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
89115
}, [dispatchSetCurrentAttempt, currentAttempt]);
90116

91117
const updateItem = useCallback((index: number, value: Immutable<CoordinateItemDTO>) => {
92-
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? item : {...DEFAULT_COORDINATE_ITEM});
93-
items[index] = value;
118+
const items = [...(currentAttempt?.items ?? [])].map(item => isDefined(item) ? cleanItem(item) : {...DEFAULT_COORDINATE_ITEM});
119+
items[index] = cleanItem(value);
94120
dispatchSetCurrentAttempt({type: "coordinateChoice", items});
95121
}, [currentAttempt, dispatchSetCurrentAttempt]);
96122

97123
const removeItem = useCallback((index: number) => {
98-
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});
99125
items.splice(index, 1);
100126
dispatchSetCurrentAttempt({type: "coordinateChoice", items});
101127
}, [currentAttempt, dispatchSetCurrentAttempt]);
@@ -110,8 +136,8 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
110136
? Array.from({length: doc.numberOfCoordinates}).map((_, index) =>
111137
<CoordinateInput
112138
key={index}
113-
placeholderXValue={doc.placeholderXValue}
114-
placeholderYValue={doc.placeholderYValue}
139+
placeholderValues={doc.placeholderValues ?? []}
140+
numberOfDimensions={doc.numberOfDimensions ?? 1}
115141
value={currentAttempt?.items?.[index] ?? {...DEFAULT_COORDINATE_ITEM}}
116142
readonly={readonly}
117143
onChange={value => updateItem(index, value)}
@@ -121,8 +147,8 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
121147
{currentAttempt?.items?.map((item, index) =>
122148
<CoordinateInput
123149
key={index}
124-
placeholderXValue={doc.placeholderXValue}
125-
placeholderYValue={doc.placeholderYValue}
150+
placeholderValues={doc.placeholderValues ?? []}
151+
numberOfDimensions={doc.numberOfDimensions ?? 1}
126152
value={item}
127153
readonly={readonly}
128154
onChange={value => updateItem(index, value)}
@@ -132,14 +158,14 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
132158
</>
133159
: <CoordinateInput
134160
key={0}
135-
placeholderXValue={doc.placeholderXValue}
136-
placeholderYValue={doc.placeholderYValue}
161+
placeholderValues={doc.placeholderValues ?? []}
162+
numberOfDimensions={doc.numberOfDimensions ?? 1}
137163
value={{...DEFAULT_COORDINATE_ITEM}}
138164
readonly={readonly}
139165
onChange={value => updateItem(0, value)}
140166
/>
141167
}
142-
<QuestionInputValidation userInput={currentAttempt?.items?.map(answer => [answer.x ?? "", answer.y ?? ""]) ?? []} validator={coordinateInputValidator}/>
168+
<QuestionInputValidation userInput={currentAttempt?.items?.map(answer => answer.coordinates ?? []) ?? []} validator={coordinateInputValidator}/>
143169
{!doc.numberOfCoordinates && <Button color="secondary" size="sm" className="mt-3" onClick={() => updateItem(currentAttempt?.items?.length ?? 1, {...DEFAULT_COORDINATE_ITEM})}>Add coordinate</Button>}
144170
</div>;
145171
};

src/app/components/elements/PageTitle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface TitleIconProps {
6767
type: "img" | "hex";
6868
subject?: Subject;
6969
icon: string;
70+
size?: string;
7071
}
7172

7273
export interface PageTitleProps {

src/app/components/elements/SearchInputs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const PhySearchInput = withSearch(({inputProps, setSearchText, searchText
4040
return <>
4141
<Label for='header-search' className='visually-hidden'>Search</Label>
4242
<Input
43-
id="header-search" className="navbar-search" {...inputProps}
43+
id="header-search" {...inputProps}
4444
value={searchText} onChange={setSearchTextAsValue}
4545
/>
4646
<SearchButton/>

src/app/components/elements/StageAndDifficultySummaryIcons.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ interface StageAndDifficultySummaryIconsProps {
1111
className?: string,
1212
iconClassName?: string,
1313
stack?: boolean,
14+
spacerWidth?: number,
1415
}
1516

1617
export const StageAndDifficultySummaryIcons = (props: StageAndDifficultySummaryIconsProps) => {
17-
const {audienceViews, className, iconClassName, stack} = props;
18+
const {audienceViews, className, iconClassName, stack, spacerWidth} = props;
1819
const difficulties: Difficulty[] = audienceViews.map(v => v.difficulty).filter(v => v !== undefined);
1920
return siteSpecific(
2021
<div className={classNames(className, "d-flex flex-column")}>
@@ -23,7 +24,7 @@ export const StageAndDifficultySummaryIcons = (props: StageAndDifficultySummaryI
2324
{view.stage && view.stage !== STAGE.ALL && stageLabelMap[view.stage] + " "}
2425
{view.difficulty && <>
2526
{simpleDifficultyLabelMap[view.difficulty]}
26-
<Spacer/>
27+
<Spacer width={spacerWidth}/>
2728
<DifficultyIcons className={classNames("d-inline-block ps-1", iconClassName)} difficulty={view.difficulty} />
2829
</>}
2930
</span>

src/app/components/elements/cards/EventCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const PhysicsEventCard = ({event}: {event: AugmentedEvent}) => {
3434
</div>}
3535
</a>}
3636
<CardBody className="d-flex flex-column ps-0">
37-
{title && <CardTitle tag="h4" className="mb-0">{title}</CardTitle>}
37+
{title && <CardTitle className="mb-0 pod-title">{title}</CardTitle>}
3838
<CardText className="mb-0">
3939
{subtitle && <p className="m-0">{subtitle}</p>}
4040
</CardText>

src/app/components/elements/cards/NewsCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {Card, CardBody, CardImg, CardProps, CardText, CardTitle} from "reactstra
44
import {IsaacPodDTO} from "../../../../IsaacApiTypes";
55
import {apiHelper, siteSpecific} from "../../../services";
66
import {AdaCard} from "./AdaCard";
7-
import { Spacer } from "../Spacer";
87
import classNames from "classnames";
8+
import { Spacer } from "../Spacer";
99

1010
interface NewsCardProps extends CardProps {
1111
newsItem: IsaacPodDTO;
@@ -24,7 +24,7 @@ const PhysicsNewsCard = ({newsItem, ...props}: NewsCardProps) => {
2424
/>
2525
</a>}
2626
<CardBody className="d-flex flex-column ps-0">
27-
<CardTitle tag="h4" className="mb-0">{title}</CardTitle>
27+
<CardTitle className="mb-0 pod-title">{title}</CardTitle>
2828
<CardText>
2929
{value && <p>{value}</p>}
3030
</CardText>

src/app/components/elements/inputs/SchoolInput.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {useCallback, useEffect, useState} from "react";
22
import AsyncCreatableSelect from "react-select/async-creatable";
33
import {School, ValidationUser} from "../../../../IsaacAppTypes";
4-
import {schoolNameWithPostcode, siteSpecific, validateUserSchool} from "../../../services";
4+
import {isPhy, schoolNameWithPostcode, siteSpecific, validateUserSchool} from "../../../services";
55
import throttle from "lodash/throttle";
66
import classNames from "classnames";
77
import {Immutable} from "immer";
@@ -120,7 +120,7 @@ export const SchoolInput = ({userToUpdate, setUserToUpdate, submissionAttempted,
120120
placeholder={"Type your school name"}
121121
value={schoolValue}
122122
components={customComponents}
123-
className={(isInvalid ? "react-select-error " : "") + "basic-multi-select"}
123+
className={classNames("basic-multi-select", {"react-select-error": isInvalid, "school-input": isPhy})}
124124
classNamePrefix="select"
125125
onChange={handleSetSchool}
126126
loadOptions={searchSchoolsFn}
@@ -133,6 +133,7 @@ export const SchoolInput = ({userToUpdate, setUserToUpdate, submissionAttempted,
133133
<StyledCheckbox
134134
type="checkbox" id={`${idPrefix}-not-associated-with-school`}
135135
checked={userToUpdate.schoolOther === NOT_APPLICABLE}
136+
color={siteSpecific("primary", "")}
136137
invalid={isInvalid}
137138
disabled={disableInput || !setUserToUpdate}
138139
onChange={(e => {

0 commit comments

Comments
 (0)