@@ -7,18 +7,17 @@ import {IsaacQuestionProps} from "../../../IsaacAppTypes";
7
7
import { Immutable } from "immer" ;
8
8
import QuestionInputValidation from "../elements/inputs/QuestionInputValidation" ;
9
9
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
12
11
interface CoordinateInputProps {
13
12
value : Immutable < CoordinateItemDTO > ;
14
- placeholderXValue ? : string ;
15
- placeholderYValue ?: string ;
13
+ placeholderValues : string [ ] ;
14
+ numberOfDimensions : number ;
16
15
onChange : ( value : Immutable < CoordinateItemDTO > ) => void ;
17
16
readonly ?: boolean ;
18
17
remove ?: ( ) => void ;
19
18
}
20
19
21
- export const coordinateInputValidator = ( input : string [ ] [ ] ) => {
20
+ export const coordinateInputValidator = ( input : ( readonly string [ ] ) [ ] ) => {
22
21
const errors : string [ ] = [ ] ;
23
22
const allBadChars : string [ ] = [ ] ;
24
23
let containsComma = false ;
@@ -31,7 +30,7 @@ export const coordinateInputValidator = (input: string[][]) => {
31
30
if ( / [ 0 - 9 ] \s * [ + / ÷ \- x × ] \s * [ 0 - 9 ] / . test ( value ) ) {
32
31
containsOperator = true ;
33
32
}
34
- const foundBadChars = [ ...value . matchAll ( / [ ^ 0 - 9 + - . e E ] / g) ] ;
33
+ const foundBadChars = [ ...value . matchAll ( / [ ^ 0 - 9 + - . e E ] / g) ] ;
35
34
if ( foundBadChars . length > 0 ) {
36
35
allBadChars . push ( foundBadChars . toString ( ) ) ;
37
36
}
@@ -50,33 +49,60 @@ export const coordinateInputValidator = (input: string[][]) => {
50
49
return errors ;
51
50
} ;
52
51
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
+
53
87
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" > , </ 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" > , </ span > }
100
+ </ span > ) } )
101
+ { remove && < Button className = "ms-3" size = "sm" onClick = { remove } > Delete</ Button > }
76
102
</ span > ;
77
103
} ;
78
104
79
- const DEFAULT_COORDINATE_ITEM = { type : "coordinateItem" , x : undefined , y : undefined } ;
105
+ const DEFAULT_COORDINATE_ITEM = { type : "coordinateItem" , coordinates : [ ] } ;
80
106
81
107
const IsaacCoordinateQuestion = ( { doc, questionId, readonly} : IsaacQuestionProps < IsaacCoordinateQuestionDTO > ) => {
82
108
@@ -89,13 +115,13 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
89
115
} , [ dispatchSetCurrentAttempt , currentAttempt ] ) ;
90
116
91
117
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 ) ;
94
120
dispatchSetCurrentAttempt ( { type : "coordinateChoice" , items} ) ;
95
121
} , [ currentAttempt , dispatchSetCurrentAttempt ] ) ;
96
122
97
123
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 } ) ;
99
125
items . splice ( index , 1 ) ;
100
126
dispatchSetCurrentAttempt ( { type : "coordinateChoice" , items} ) ;
101
127
} , [ currentAttempt , dispatchSetCurrentAttempt ] ) ;
@@ -110,8 +136,8 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
110
136
? Array . from ( { length : doc . numberOfCoordinates } ) . map ( ( _ , index ) =>
111
137
< CoordinateInput
112
138
key = { index }
113
- placeholderXValue = { doc . placeholderXValue }
114
- placeholderYValue = { doc . placeholderYValue }
139
+ placeholderValues = { doc . placeholderValues ?? [ ] }
140
+ numberOfDimensions = { doc . numberOfDimensions ?? 1 }
115
141
value = { currentAttempt ?. items ?. [ index ] ?? { ...DEFAULT_COORDINATE_ITEM } }
116
142
readonly = { readonly }
117
143
onChange = { value => updateItem ( index , value ) }
@@ -121,8 +147,8 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
121
147
{ currentAttempt ?. items ?. map ( ( item , index ) =>
122
148
< CoordinateInput
123
149
key = { index }
124
- placeholderXValue = { doc . placeholderXValue }
125
- placeholderYValue = { doc . placeholderYValue }
150
+ placeholderValues = { doc . placeholderValues ?? [ ] }
151
+ numberOfDimensions = { doc . numberOfDimensions ?? 1 }
126
152
value = { item }
127
153
readonly = { readonly }
128
154
onChange = { value => updateItem ( index , value ) }
@@ -132,14 +158,14 @@ const IsaacCoordinateQuestion = ({doc, questionId, readonly}: IsaacQuestionProps
132
158
</ >
133
159
: < CoordinateInput
134
160
key = { 0 }
135
- placeholderXValue = { doc . placeholderXValue }
136
- placeholderYValue = { doc . placeholderYValue }
161
+ placeholderValues = { doc . placeholderValues ?? [ ] }
162
+ numberOfDimensions = { doc . numberOfDimensions ?? 1 }
137
163
value = { { ...DEFAULT_COORDINATE_ITEM } }
138
164
readonly = { readonly }
139
165
onChange = { value => updateItem ( 0 , value ) }
140
166
/>
141
167
}
142
- < QuestionInputValidation userInput = { currentAttempt ?. items ?. map ( answer => [ answer . x ?? "" , answer . y ?? "" ] ) ?? [ ] } validator = { coordinateInputValidator } />
168
+ < QuestionInputValidation userInput = { currentAttempt ?. items ?. map ( answer => answer . coordinates ?? [ ] ) ?? [ ] } validator = { coordinateInputValidator } />
143
169
{ ! doc . numberOfCoordinates && < Button color = "secondary" size = "sm" className = "mt-3" onClick = { ( ) => updateItem ( currentAttempt ?. items ?. length ?? 1 , { ...DEFAULT_COORDINATE_ITEM } ) } > Add coordinate</ Button > }
144
170
</ div > ;
145
171
} ;
0 commit comments