@@ -4,22 +4,12 @@ import type React from "react"
44import { type ChangeEvent , useEffect , useMemo , useRef , useState } from "react"
55import DefaultMatchModeConfigs from "@/systems/match_mode/DefaultMatchModeConfigs"
66import MatchMode from "@/systems/match_mode/MatchMode"
7- import {
8- DEFAULT_AUTONOMOUS_TIME ,
9- DEFAULT_ENDGAME_TIME ,
10- DEFAULT_HEIGHT_LIMIT_PENALTY ,
11- DEFAULT_IGNORE_ROTATION ,
12- DEFAULT_MAX_HEIGHT ,
13- DEFAULT_SIDE_EXTENSION_PENALTY ,
14- DEFAULT_SIDE_MAX_EXTENSION ,
15- DEFAULT_TELEOP_TIME ,
16- } from "@/systems/match_mode/MatchModeTypes"
7+
178import { globalAddToast } from "@/ui/components/GlobalUIControls"
189import Label from "@/ui/components/Label"
1910import type { PanelImplProps } from "@/ui/components/Panel"
2011import { NegativeButton , PositiveButton , SynthesisIcons } from "@/ui/components/StyledComponents"
2112import { CloseType , useUIContext } from "@/ui/helpers/UIProviderHelpers"
22- import { convertFeetToMeters } from "@/util/UnitConversions"
2313
2414/**
2515 * Configuration for match mode rules and timing.
@@ -55,7 +45,6 @@ export interface MatchModeConfig {
5545
5646 /**
5747 * Maximum allowed robot height in meters (stored internally).
58- * User input is in feet but converted to meters during config processing.
5948 * Set to Infinity for no height limit. (default: Infinity)
6049 */
6150 maxHeight : number
@@ -80,6 +69,19 @@ export interface MatchModeConfig {
8069 sideExtensionPenalty : number
8170}
8271
72+ const props : Readonly < { id : keyof MatchModeConfig ; expectedType : string ; required : boolean } > [ ] = [
73+ { id : "id" , expectedType : "string" , required : true } ,
74+ { id : "name" , expectedType : "string" , required : true } ,
75+ { id : "autonomousTime" , expectedType : "number" , required : false } ,
76+ { id : "teleopTime" , expectedType : "number" , required : false } ,
77+ { id : "endgameTime" , expectedType : "number" , required : false } ,
78+ { id : "ignoreRotation" , expectedType : "boolean" , required : false } ,
79+ { id : "maxHeight" , expectedType : "number" , required : false } ,
80+ { id : "heightLimitPenalty" , expectedType : "number" , required : false } ,
81+ { id : "sideMaxExtension" , expectedType : "number" , required : false } ,
82+ { id : "sideExtensionPenalty" , expectedType : "number" , required : false } ,
83+ ]
84+
8385function matchConfigSelected ( config : MatchModeConfig ) {
8486 if ( MatchMode . getInstance ( ) . isMatchEnabled ( ) ) {
8587 globalAddToast (
@@ -196,87 +198,53 @@ const MatchModeConfigPanel: React.FC<PanelImplProps<void, void>> = ({ panel }) =
196198 }
197199
198200 const validateAndNormalizeMatchModeConfig = ( config : unknown ) : MatchModeConfig | null => {
199- let valid = true
200-
201201 // Type guard to check if config is an object
202202 if ( typeof config !== "object" || config === null ) {
203203 console . error ( "Match mode config validation failed: config must be an object" )
204204 globalAddToast ( "error" , "Invalid Match Mode Config" , "Configuration must be an object" )
205205 return null
206206 }
207-
208207 const configObj = config as Record < string , unknown >
209208
210- const props : { id : string ; expectedType : string ; required : boolean } [ ] = [
211- { id : "id" , expectedType : "string" , required : true } ,
212- { id : "name" , expectedType : "string" , required : true } ,
213- { id : "autonomousTime" , expectedType : "number" , required : false } ,
214- { id : "teleopTime" , expectedType : "number" , required : false } ,
215- { id : "endgameTime" , expectedType : "number" , required : false } ,
216- { id : "ignoreRotation" , expectedType : "boolean" , required : false } ,
217- { id : "maxHeight" , expectedType : "number" , required : false } ,
218- { id : "heightLimitPenalty" , expectedType : "number" , required : false } ,
219- { id : "sideMaxExtension" , expectedType : "number" , required : false } ,
220- { id : "sideExtensionPenalty" , expectedType : "number" , required : false } ,
221- ]
222-
223209 const typeError = ( id : string , expectedType ?: string ) => {
224210 const errorMessage = expectedType ? `must be a ${ expectedType } ` : "is required"
225211 console . error ( `Match mode config validation failed: the '${ id } ' field ${ errorMessage } ` )
226212 globalAddToast ( "error" , "Invalid Match Mode Config" , `The '${ id } ' field ${ errorMessage } ` )
227213 }
228214
229- for ( const prop of props ) {
230- if ( configObj [ prop . id ] == undefined ) {
231- if ( prop . required ) {
232- typeError ( prop . id )
233- valid = false
234- }
235- } else if ( typeof configObj [ prop . id ] != prop . expectedType ) {
236- if ( prop . required ) {
237- typeError ( prop . id , prop . expectedType )
238- valid = false
239- } else {
240- globalAddToast (
241- "warning" ,
242- "Invalid Match Mode Config" ,
243- `The '${ prop . id } ' field must be a ${ prop . expectedType } , ignoring ${ prop . id } field`
244- )
215+ function checkValidity ( configObj : Record < string , unknown > ) : configObj is Partial < MatchModeConfig > {
216+ for ( const prop of props ) {
217+ if ( configObj [ prop . id ] == undefined ) {
218+ if ( prop . required ) {
219+ typeError ( prop . id )
220+ return false
221+ }
222+ } else if ( typeof configObj [ prop . id ] != prop . expectedType ) {
223+ if ( prop . required ) {
224+ typeError ( prop . id , prop . expectedType )
225+ return false
226+ } else {
227+ globalAddToast (
228+ "warning" ,
229+ "Invalid Match Mode Config" ,
230+ `The '${ prop . id } ' field must be a ${ prop . expectedType } , ignoring ${ prop . id } field`
231+ )
232+ }
245233 }
246234 }
235+ return true
247236 }
248237
249- if ( ! valid ) {
238+ if ( ! checkValidity ( configObj ) ) {
250239 return null
251240 }
252241
253- // If validation passes, normalize the config with defaults for missing fields
254- const normalizedConfig : MatchModeConfig = {
255- id : configObj . id as string ,
256- name : configObj . name as string ,
257- isDefault : false , // User-uploaded configs are not default configs
258- autonomousTime :
259- typeof configObj . autonomousTime === "number" ? configObj . autonomousTime : DEFAULT_AUTONOMOUS_TIME ,
260- teleopTime : typeof configObj . teleopTime === "number" ? configObj . teleopTime : DEFAULT_TELEOP_TIME ,
261- endgameTime : typeof configObj . endgameTime === "number" ? configObj . endgameTime : DEFAULT_ENDGAME_TIME ,
262- ignoreRotation :
263- typeof configObj . ignoreRotation === "boolean" ? configObj . ignoreRotation : DEFAULT_IGNORE_ROTATION ,
264- maxHeight :
265- typeof configObj . maxHeight === "number" ? convertFeetToMeters ( configObj . maxHeight ) : DEFAULT_MAX_HEIGHT ,
266- heightLimitPenalty :
267- typeof configObj . heightLimitPenalty === "number"
268- ? configObj . heightLimitPenalty
269- : DEFAULT_HEIGHT_LIMIT_PENALTY ,
270- sideMaxExtension :
271- typeof configObj . sideMaxExtension === "number"
272- ? convertFeetToMeters ( configObj . sideMaxExtension )
273- : DEFAULT_SIDE_MAX_EXTENSION ,
274- sideExtensionPenalty :
275- typeof configObj . sideExtensionPenalty === "number"
276- ? configObj . sideExtensionPenalty
277- : DEFAULT_SIDE_EXTENSION_PENALTY ,
242+ // If validation passes, use the default values in any missing fields
243+ const normalizedConfig = {
244+ ...DefaultMatchModeConfigs . fallbackValues ( ) ,
245+ ...configObj ,
278246 }
279-
247+ normalizedConfig . isDefault = false
280248 return normalizedConfig
281249 }
282250
0 commit comments