diff --git a/config/2026/config.json b/config/2026/config.json index 9b0ef6306..914b74ec7 100644 --- a/config/2026/config.json +++ b/config/2026/config.json @@ -74,6 +74,14 @@ "formResetBehavior": "preserve", "defaultValue": "" }, + { + "title": "Assigned Station", + "description": "The station assigned to the scout (e.g., R1, R2, R3, B1, B2, B3). Use this to automatically assign the team and robot position based on the match number and station.", + "type": "TBA-assigned-station", + "required": false, + "code": "assignedStation", + "formResetBehavior": "preserve" + }, { "title": "Match Number", "description": "Select the match number.", @@ -90,7 +98,8 @@ "required": true, "code": "robot", "formResetBehavior": "preserve", - "defaultValue": null + "defaultValue": null, + "autoAssignFromFieldCode": "assignedStation" }, { "title": "Starting Position", diff --git a/package.json b/package.json index 20fb5a02b..8f4e31b2f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dev": "vite", "build": "tsc && vite build ", "preview": "vite preview", - "schema": "ts-node src/scripts/generateJsonSchema.ts src/assets/schema.json && cp src/assets/schema.json public/schema.json" + "schema": "npx tsx src/scripts/generateJsonSchema.ts src/assets/schema.json && cp src/assets/schema.json public/schema.json" }, "dependencies": { "@headlessui/react": "^1.7.19", diff --git a/public/schema.json b/public/schema.json index fa58f2281..84cc3413a 100644 --- a/public/schema.json +++ b/public/schema.json @@ -11,7 +11,7 @@ }, "year": { "type": "number", - "description": "The year this scouting config is relevant for." + "description": "The year this scouting config is relevant for. Defaults to the current year if not provided." }, "delimiter": { "type": "string", @@ -294,6 +294,45 @@ ], "additionalProperties": false }, + { + "type": "object", + "properties": { + "title": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title" + }, + "description": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description" + }, + "type": { + "type": "string", + "const": "multi-counter" + }, + "required": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required" + }, + "code": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code" + }, + "disabled": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled" + }, + "formResetBehavior": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior" + }, + "defaultValue": { + "type": "number", + "default": 0, + "description": "The default value" + } + }, + "required": [ + "title", + "type", + "required", + "code" + ], + "additionalProperties": false + }, { "type": "object", "properties": { @@ -741,6 +780,10 @@ "timerDuration": { "type": "number", "description": "Expected duration in seconds (for UI reference, e.g., 15 for auto, 135 for teleop)" + }, + "autoStopSeconds": { + "type": "number", + "description": "Automatically stop the timer after this many seconds. Useful to prevent the timer from running past the match phase duration." } }, "required": [ @@ -786,7 +829,16 @@ "type": "number" }, "robotPosition": { - "type": "string" + "type": "string", + "enum": [ + "R1", + "R2", + "R3", + "B1", + "B2", + "B3" + ], + "description": "The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance." } }, "required": [ @@ -801,6 +853,10 @@ ], "default": null, "description": "The default team and robot position" + }, + "autoAssignFromFieldCode": { + "type": "string", + "description": "Optional code of another field to auto-assign" } }, "required": [ @@ -857,6 +913,59 @@ "code" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "title": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title" + }, + "description": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description" + }, + "type": { + "type": "string", + "const": "TBA-assigned-station" + }, + "required": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required" + }, + "code": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code" + }, + "disabled": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled" + }, + "formResetBehavior": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior" + }, + "defaultValue": { + "type": "string", + "default": "", + "description": "If assigning by driver station, the driver station to use for selecting team and robot position across matches" + }, + "choices": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "default": { + "R1": "Red 1", + "R2": "Red 2", + "R3": "Red 3", + "B1": "Blue 1", + "B2": "Blue 2", + "B3": "Blue 3" + } + } + }, + "required": [ + "title", + "type", + "required", + "code" + ], + "additionalProperties": false } ] } @@ -876,7 +985,6 @@ "required": [ "title", "page_title", - "year", "delimiter", "teamNumber", "sections" diff --git a/src/assets/schema.json b/src/assets/schema.json index fa58f2281..84cc3413a 100644 --- a/src/assets/schema.json +++ b/src/assets/schema.json @@ -11,7 +11,7 @@ }, "year": { "type": "number", - "description": "The year this scouting config is relevant for." + "description": "The year this scouting config is relevant for. Defaults to the current year if not provided." }, "delimiter": { "type": "string", @@ -294,6 +294,45 @@ ], "additionalProperties": false }, + { + "type": "object", + "properties": { + "title": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title" + }, + "description": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description" + }, + "type": { + "type": "string", + "const": "multi-counter" + }, + "required": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required" + }, + "code": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code" + }, + "disabled": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled" + }, + "formResetBehavior": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior" + }, + "defaultValue": { + "type": "number", + "default": 0, + "description": "The default value" + } + }, + "required": [ + "title", + "type", + "required", + "code" + ], + "additionalProperties": false + }, { "type": "object", "properties": { @@ -741,6 +780,10 @@ "timerDuration": { "type": "number", "description": "Expected duration in seconds (for UI reference, e.g., 15 for auto, 135 for teleop)" + }, + "autoStopSeconds": { + "type": "number", + "description": "Automatically stop the timer after this many seconds. Useful to prevent the timer from running past the match phase duration." } }, "required": [ @@ -786,7 +829,16 @@ "type": "number" }, "robotPosition": { - "type": "string" + "type": "string", + "enum": [ + "R1", + "R2", + "R3", + "B1", + "B2", + "B3" + ], + "description": "The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance." } }, "required": [ @@ -801,6 +853,10 @@ ], "default": null, "description": "The default team and robot position" + }, + "autoAssignFromFieldCode": { + "type": "string", + "description": "Optional code of another field to auto-assign" } }, "required": [ @@ -857,6 +913,59 @@ "code" ], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "title": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title" + }, + "description": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description" + }, + "type": { + "type": "string", + "const": "TBA-assigned-station" + }, + "required": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required" + }, + "code": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code" + }, + "disabled": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled" + }, + "formResetBehavior": { + "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior" + }, + "defaultValue": { + "type": "string", + "default": "", + "description": "If assigning by driver station, the driver station to use for selecting team and robot position across matches" + }, + "choices": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "default": { + "R1": "Red 1", + "R2": "Red 2", + "R3": "Red 3", + "B1": "Blue 1", + "B2": "Blue 2", + "B3": "Blue 3" + } + } + }, + "required": [ + "title", + "type", + "required", + "code" + ], + "additionalProperties": false } ] } @@ -876,7 +985,6 @@ "required": [ "title", "page_title", - "year", "delimiter", "teamNumber", "sections" diff --git a/src/components/inputs/BaseInputProps.ts b/src/components/inputs/BaseInputProps.ts index f5a33d5fe..32f8dfa99 100644 --- a/src/components/inputs/BaseInputProps.ts +++ b/src/components/inputs/BaseInputProps.ts @@ -15,6 +15,7 @@ export const inputTypeSchema = z 'action-tracker', 'TBA-team-and-robot', 'TBA-match-number', + 'TBA-assigned-station', ]) .describe('The type of input'); @@ -150,16 +151,35 @@ export const actionTrackerInputSchema = inputBaseSchema.extend({ ), }); +export const robotPosition = z + .enum(['R1', 'R2', 'R3', 'B1', 'B2', 'B3']) + .describe( + 'The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance.', + ); + +export const robotPositionLabels = { + R1: 'Red 1', + R2: 'Red 2', + R3: 'Red 3', + B1: 'Blue 1', + B2: 'Blue 2', + B3: 'Blue 3', +}; + export const tbaTeamAndRobotInputSchema = inputBaseSchema.extend({ type: z.literal('TBA-team-and-robot'), defaultValue: z .object({ teamNumber: z.number(), - robotPosition: z.string(), + robotPosition, }) .nullable() .default(null) .describe('The default team and robot position'), + autoAssignFromFieldCode: z + .string() + .optional() + .describe('Optional code of another field to auto-assign'), }); export const tbaMatchNumberInputSchema = inputBaseSchema.extend({ @@ -169,6 +189,17 @@ export const tbaMatchNumberInputSchema = inputBaseSchema.extend({ defaultValue: z.number().default(0).describe('The default value'), }); +export const tbaAssignedStationInputSchema = selectInputSchema.extend({ + type: z.literal('TBA-assigned-station'), + choices: z.record(z.string()).default(robotPositionLabels), + defaultValue: z + .string() + .default('') + .describe( + 'If assigning by driver station, the driver station to use for selecting team and robot position across matches', + ), +}); + export const sectionSchema = z.object({ name: z.string(), fields: z.array( @@ -186,6 +217,7 @@ export const sectionSchema = z.object({ actionTrackerInputSchema, tbaTeamAndRobotInputSchema, tbaMatchNumberInputSchema, + tbaAssignedStationInputSchema, ]), ), }); diff --git a/src/components/inputs/ConfigurableInput.tsx b/src/components/inputs/ConfigurableInput.tsx index 9b4d4a07d..f5dad2656 100644 --- a/src/components/inputs/ConfigurableInput.tsx +++ b/src/components/inputs/ConfigurableInput.tsx @@ -46,5 +46,7 @@ export default function ConfigurableInput(props: ConfigurableInputProps) { return ; case 'TBA-match-number': return ; + case 'TBA-assigned-station': + return ; } } diff --git a/src/components/inputs/TBATeamAndRobotInput.tsx b/src/components/inputs/TBATeamAndRobotInput.tsx index 0d66643d2..85e2b8a73 100644 --- a/src/components/inputs/TBATeamAndRobotInput.tsx +++ b/src/components/inputs/TBATeamAndRobotInput.tsx @@ -35,17 +35,11 @@ export default function TBATeamAndRobotInput(props: ConfigurableInputProps) { // (e.g. Red 1, Red 2, Red 3, Blue 1, Blue 2, Blue 3) across matches. To save time // and reduce errors, we can automatically select the team and robot position based // on the selected match number and driver station. - // - // By temporary convention, we assume the field for driver station selection is - // called "driverStation" and contains values like "R1", "R2", "R3", "B1", "B2", - // "B3". If the driver station field is present and has a valid value, we will - // automatically select the corresponding team and robot position for the scout. - // - // This is an optional feature that can be used by teams who want it, and it will - // eventually graduate into explicit config. const driverStation = useQRScoutState(() => { - return getFieldValue("driverStation"); - }) + return data?.autoAssignFromFieldCode + ? getFieldValue(data.autoAssignFromFieldCode) + : undefined; + }); if (!data) { return
Invalid input
; @@ -108,15 +102,17 @@ export default function TBATeamAndRobotInput(props: ConfigurableInputProps) { // Automatically select team and robot based on selected driver station and match number useEffect(() => { if (driverStation !== '') { - const teamNumber = teamOptions.find((team) => team.robotPosition == driverStation)?.teamNumber; + const teamNumber = teamOptions.find( + team => team.robotPosition == driverStation, + )?.teamNumber; if (teamNumber !== undefined) { setValue({ teamNumber, robotPosition: driverStation, - }) + }); } } - }, [teamOptions, selectedMatchNumber, driverStation]) + }, [teamOptions, selectedMatchNumber, driverStation]); const resetState = useCallback( ({ force }: { force: boolean }) => {