diff --git a/README.md b/README.md index c5458b4..94691e7 100644 --- a/README.md +++ b/README.md @@ -47,26 +47,43 @@ The config.json can be edited to change most parts of QRScout, and change the li The basic structure of the config.json file is as follows: -Root: -$schema: A refrence to the schema used by the config.json file. This shouldn't be changed from the default "../schema.json". -title: The title of the page. This is what appears in the tab bar. -page_title: The title that appears at the top of the QRScout page. -delimiter: The line delimiter used by the QR code -sections: An array of sections/columns that hold and organize form inputs - -Induvidual sections: -name: The name of the section/column -preserveDataOnReset: If the data in this section is preserved when the Reset Form button is pressed. -fields: An array of fields, which describe form inputs. - -Induvidual fields: -title: The name of this field -type: One of "text", "number", "boolean", "range", "select", "counter", "image" or "timer". Describes the type of input this is. -required: a boolean indicating if this must be filled out before the QRCode is generated. If any field with this set to true is not filled out, QRScout will not generate a QRCode when the commit button is pressed. -code: camelCase string with a unique name indicaing what this field is. -disabled: Boolean indicating if this field is disabled. If it is, things cannot be inputted into it. This and the requied value are mutually exclusive if you want people to be able to submit this form. -preserveDataOnReset: If the data in this field is preserved when the Reset Form button is pressed. -choices: An object containng numbered keys mapping to values that this can hold. For example: +### Root: + +`$schema`: A refrence to the schema used by the config.json file. This shouldn't be changed from the default "../schema.json". + +`title`: The title of the page. This is what appears in the tab bar. + +`page_title`: The title that appears at the top of the QRScout page. + +`delimiter`: The line delimiter used by the QR code + +`sections`: An array of sections/columns that hold and organize form inputs + +### Individual sections: + +`name`: The name of the section/column + +`fields`: An array of fields, which describe form inputs. + +### Individual fields: + +`title`: The name of this field + +`type`: One of "text", "number", "boolean", "range", "select", "counter", "image" or "timer". Describes the type of input this is. + +`required`: a boolean indicating if this must be filled out before the QRCode is generated. If any field with this set to true is not filled out, QRScout will not generate a QRCode when the commit button is pressed. + +`code`: camelCase string with a unique name indicaing what this field is. + +`disabled`: Boolean indicating if this field is disabled. If it is, things cannot be inputted into it. This and the requied value are mutually exclusive if you want people to be able to submit this form. + +`formResetBehavior`: One of "reset", "preserve", or "increment". + +- `reset` will reset the field whenver the form resets +- `preserve` will retain the current value +- `increment` will increment the value based on the field's settings + +`choices`: An object containng numbered keys mapping to values that this can hold. For example: ```json "choices": { @@ -75,4 +92,4 @@ choices: An object containng numbered keys mapping to values that this can hold. } ``` -defaultValue: The default value of this field. +`defaultValue`: The default value of this field. diff --git a/config/2025/config.json b/config/2025/config.json index 65a07a2..49a80d4 100644 --- a/config/2025/config.json +++ b/config/2025/config.json @@ -1,428 +1,429 @@ { - "title": "QRScout", - "page_title": "Reefscape", - "delimiter": "\t", - "teamNumber": 2713, - "theme": { - "light": { - "background": "0 0% 100%", - "foreground": "0 0% 3.9%", - "card": "0 0% 100%", - "card_foreground": "0 0% 3.9%", - "popover": "0 0% 100%", - "popover_foreground": "0 0% 3.9%", - "primary": "354.44 71.3% 47.9%", - "primary_foreground": "0 85.7% 97.3%", - "secondary": "0 0% 96.1%", - "secondary_foreground": "0 0% 9%", - "muted": "0 0% 96.1%", - "muted_foreground": "0 0% 45.1%", - "accent": "0 0% 96.1%", - "accent_foreground": "0 0% 9%", - "destructive": "0 84.2% 60.2%", - "destructive_foreground": "0 0% 98%", - "border": "0 0% 89.8%", - "input": "0 0% 89.8%", - "ring": "354.44 71.3% 47.9%", - "chart_1": "12 76% 61%", - "chart_2": "173 58% 39%", - "chart_3": "197 37% 24%", - "chart_4": "43 74% 66%", - "chart_5": "27 87% 67%" + "title": "QRScout", + "page_title": "Reefscape", + "delimiter": "\t", + "teamNumber": 2713, + "theme": { + "light": { + "background": "0 0% 100%", + "foreground": "0 0% 3.9%", + "card": "0 0% 100%", + "card_foreground": "0 0% 3.9%", + "popover": "0 0% 100%", + "popover_foreground": "0 0% 3.9%", + "primary": "354.44 71.3% 47.9%", + "primary_foreground": "0 85.7% 97.3%", + "secondary": "0 0% 96.1%", + "secondary_foreground": "0 0% 9%", + "muted": "0 0% 96.1%", + "muted_foreground": "0 0% 45.1%", + "accent": "0 0% 96.1%", + "accent_foreground": "0 0% 9%", + "destructive": "0 84.2% 60.2%", + "destructive_foreground": "0 0% 98%", + "border": "0 0% 89.8%", + "input": "0 0% 89.8%", + "ring": "354.44 71.3% 47.9%", + "chart_1": "12 76% 61%", + "chart_2": "173 58% 39%", + "chart_3": "197 37% 24%", + "chart_4": "43 74% 66%", + "chart_5": "27 87% 67%" + }, + "dark": { + "background": "0 0% 3.9%", + "foreground": "0 0% 98%", + "card": "0 0% 3.9%", + "card_foreground": "0 0% 98%", + "popover": "0 0% 3.9%", + "popover_foreground": "0 0% 98%", + "primary": "354.44 71.3% 47.9%", + "primary_foreground": "0 85.7% 97.3%", + "secondary": "0 0% 14.9%", + "secondary_foreground": "0 0% 98%", + "muted": "0 0% 14.9%", + "muted_foreground": "0 0% 63.9%", + "accent": "0 0% 14.9%", + "accent_foreground": "0 0% 98%", + "destructive": "0 62.8% 30.6%", + "destructive_foreground": "0 0% 98%", + "border": "0 0% 14.9%", + "input": "0 0% 14.9%", + "ring": "354.44 71.3% 47.9%", + "chart_1": "220 70% 50%", + "chart_2": "160 60% 45%", + "chart_3": "30 80% 55%", + "chart_4": "280 65% 60%", + "chart_5": "340 75% 55%" + } + }, + "sections": [ + { + "name": "Prematch", + "fields": [ + { + "title": "Scouter Initials", + "description": "Enter the initials of the scouter.", + "type": "text", + "required": true, + "code": "scouter", + "defaultValue": "", + "formResetBehavior": "preserve" + }, + { + "title": "Match Number", + "description": "Enter the match number.", + "type": "number", + "required": true, + "code": "matchNumber", + "defaultValue": 1, + "formResetBehavior": "increment" + }, + { + "title": "Robot", + "description": "The robot you are scouting this match, based on driver station position.", + "type": "select", + "required": true, + "code": "robot", + "defaultValue": "R1", + "choices": { + "R1": "Red 1", + "R2": "Red 2", + "R3": "Red 3", + "B1": "Blue 1", + "B2": "Blue 2", + "B3": "Blue 3" + }, + "formResetBehavior": "preserve" + }, + { + "title": "Team Number", + "description": "The team number of the robot you're scouting.", + "type": "number", + "required": true, + "code": "teamNumber", + "defaultValue": 0, + "min": 0, + "max": 19999 }, - "dark": { - "background": "0 0% 3.9%", - "foreground": "0 0% 98%", - "card": "0 0% 3.9%", - "card_foreground": "0 0% 98%", - "popover": "0 0% 3.9%", - "popover_foreground": "0 0% 98%", - "primary": "354.44 71.3% 47.9%", - "primary_foreground": "0 85.7% 97.3%", - "secondary": "0 0% 14.9%", - "secondary_foreground": "0 0% 98%", - "muted": "0 0% 14.9%", - "muted_foreground": "0 0% 63.9%", - "accent": "0 0% 14.9%", - "accent_foreground": "0 0% 98%", - "destructive": "0 62.8% 30.6%", - "destructive_foreground": "0 0% 98%", - "border": "0 0% 14.9%", - "input": "0 0% 14.9%", - "ring": "354.44 71.3% 47.9%", - "chart_1": "220 70% 50%", - "chart_2": "160 60% 45%", - "chart_3": "30 80% 55%", - "chart_4": "280 65% 60%", - "chart_5": "340 75% 55%" + { + "title": "Starting Position", + "description": "The starting position of the robot.", + "type": "select", + "required": true, + "code": "Prsp", + "defaultValue": "", + "choices": { + "R1": "Left", + "R2": "Middle", + "R3": "Right" + } + }, + { + "title": "No Show", + "description": "Check if the robot did not show up for the match.", + "type": "boolean", + "required": false, + "code": "noShow", + "defaultValue": false + }, + { + "title": "Cage Position", + "description": "The starting level of the cage", + "type": "select", + "required": true, + "code": "CPos", + "defaultValue": "", + "choices": { + "SP1": "Deep", + "SP2": "Shallow" + } } + ] }, - "sections": [ - { - "name": "Prematch", - "preserveDataOnReset": true, - "fields": [ - { - "title": "Scouter Initials", - "description": "Enter the initials of the scouter.", - "type": "text", - "required": true, - "code": "scouter", - "defaultValue": "" - }, - { - "title": "Match Number", - "description": "Enter the match number.", - "type": "number", - "required": true, - "code": "matchNumber", - "defaultValue": 1, - "autoIncrementOnReset": true - }, - { - "title": "Robot", - "description": "The robot you are scouting this match, based on driver station position.", - "type": "select", - "required": true, - "code": "robot", - "defaultValue": "R1", - "choices": { - "R1": "Red 1", - "R2": "Red 2", - "R3": "Red 3", - "B1": "Blue 1", - "B2": "Blue 2", - "B3": "Blue 3" - } - }, - { - "title": "Team Number", - "description": "The team number of the robot you're scouting.", - "type": "number", - "required": true, - "code": "teamNumber", - "defaultValue": 0, - "min": 0, - "max": 19999 - }, - { - "title": "Starting Position", - "description": "The starting position of the robot.", - "type": "select", - "required": true, - "code": "Prsp", - "defaultValue": "", - "choices": { - "R1": "Left", - "R2": "Middle", - "R3": "Right" - } - }, - { - "title": "No Show", - "description": "Check if the robot did not show up for the match.", - "type": "boolean", - "required": false, - "code": "noShow", - "defaultValue": false - }, - { - "title": "Cage Position", - "description": "The starting level of the cage", - "type": "select", - "required": true, - "code": "CPos", - "defaultValue": "", - "choices": { - "SP1": "Deep", - "SP2": "Shallow" - } - } - ] + { + "name": "Autonomous", + "fields": [ + { + "title": "Moved?", + "description": "Check if the robot moved during autonomous.", + "type": "boolean", + "required": false, + "code": "Mved", + "defaultValue": false + }, + { + "title": "Timer", + "description": "The time it took for the robot to finish autonomous.", + "type": "timer", + "required": false, + "code": "timer", + "defaultValue": 0 }, { - "name": "Autonomous", - "fields": [ - { - "title": "Moved?", - "description": "Check if the robot moved during autonomous.", - "type": "boolean", - "required": false, - "code": "Mved", - "defaultValue": false - }, - { - "title": "Timer", - "description": "The time it took for the robot to finish autonomous.", - "type": "timer", - "required": false, - "code": "timer", - "defaultValue": 0 - }, - { - "title": "Coral L1 Scored", - "description": "The number of level 1 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLOA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L2 Scored", - "description": "The number of level 2 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLTA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L3 Scored", - "description": "The number of level 3 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLThA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L4 Scored", - "description": "The number of level 4 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLFA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Barge Algae Scored", - "description": "The number of Algae scored in the barge during autonomous.", - "type": "counter", - "required": false, - "code": "BASA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Processor Algae Scored", - "description": "The number of Algae scored in the processor during autonomous.", - "type": "counter", - "required": false, - "code": "PASA", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Dislodged Algae?", - "type": "boolean", - "required": false, - "code": "dto", - "defaultValue": false - }, - { - "title": "Auto Foul", - "type": "counter", - "required": false, - "code": "auf", - "defaultValue": 0, - "min": 0, - "step": 1 - } - ] + "title": "Coral L1 Scored", + "description": "The number of level 1 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLOA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L2 Scored", + "description": "The number of level 2 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLTA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L3 Scored", + "description": "The number of level 3 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLThA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L4 Scored", + "description": "The number of level 4 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLFA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Barge Algae Scored", + "description": "The number of Algae scored in the barge during autonomous.", + "type": "counter", + "required": false, + "code": "BASA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Processor Algae Scored", + "description": "The number of Algae scored in the processor during autonomous.", + "type": "counter", + "required": false, + "code": "PASA", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Dislodged Algae?", + "type": "boolean", + "required": false, + "code": "dto", + "defaultValue": false + }, + { + "title": "Auto Foul", + "type": "counter", + "required": false, + "code": "auf", + "defaultValue": 0, + "min": 0, + "step": 1 + } + ] + }, + { + "name": "Teleop", + "fields": [ + { + "title": "Dislodged Algae?", + "type": "boolean", + "required": false, + "code": "daT", + "defaultValue": false + }, + { + "title": "Pickup Location", + "type": "select", + "required": false, + "code": "TGPL", + "defaultValue": "", + "choices": { + "1": "None", + "2": "Ground", + "3": "Human Player", + "4": "Both" + }, + "multiSelect": true + }, + { + "title": "Coral L1 Scored", + "description": "The number of level 1 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLOT", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L2 Scored", + "description": "The number of level 2 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLTT", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L3 Scored", + "description": "The number of level 3 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLThT", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Coral L4 Scored", + "description": "The number of level 4 coral scored during autonomous.", + "type": "counter", + "required": false, + "code": "CLFT", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Barge Algae Scored", + "description": "The number of Algae scored in the barge during autonomous.", + "type": "counter", + "required": false, + "code": "BAST", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Processor Algae Scored", + "description": "The number of Algae scored in the processor during autonomous.", + "type": "counter", + "required": false, + "code": "PAST", + "defaultValue": 0, + "min": 0, + "step": 1 + }, + { + "title": "Crossed Feild/Played Defense?", + "type": "boolean", + "required": false, + "code": "CFPDT", + "defaultValue": false + }, + { + "title": "Tipped/Fell Over?", + "type": "boolean", + "required": false, + "code": "TFOT", + "defaultValue": false + }, + { + "title": "Touched Opposing Cage?", + "type": "counter", + "required": false, + "code": "Fou/Tech", + "defaultValue": 0, + "step": 1 + }, + { + "title": "Died?", + "type": "boolean", + "required": false, + "code": "DEg", + "defaultValue": false + } + ] + }, + { + "name": "Endgame", + "fields": [ + { + "title": "End Position", + "type": "select", + "required": true, + "code": "epo", + "defaultValue": "No", + "choices": { + "No": "Not Parked", + "P": "Parked", + "Sc": "Shallow Climb", + "Dc": "Deep Climb", + "Fc": "Failed Climb" + } + }, + { + "title": "Defended?", + "type": "boolean", + "required": false, + "code": "DEFEg", + "defaultValue": false + } + ] + }, + { + "name": "Postmatch", + "fields": [ + { + "title": "Offense Skill", + "type": "range", + "required": false, + "code": "or", + "defaultValue": 3, + "min": 1, + "max": 5, + "step": 1 }, { - "name": "Teleop", - "fields": [ - { - "title": "Dislodged Algae?", - "type": "boolean", - "required": false, - "code": "daT", - "defaultValue": false - }, - { - "title": "Pickup Location", - "type": "select", - "required": false, - "code": "TGPL", - "defaultValue": "", - "choices": { - "1": "None", - "2": "Ground", - "3": "Human Player", - "4": "Both" - }, - "multiSelect": true - }, - { - "title": "Coral L1 Scored", - "description": "The number of level 1 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLOT", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L2 Scored", - "description": "The number of level 2 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLTT", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L3 Scored", - "description": "The number of level 3 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLThT", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Coral L4 Scored", - "description": "The number of level 4 coral scored during autonomous.", - "type": "counter", - "required": false, - "code": "CLFT", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Barge Algae Scored", - "description": "The number of Algae scored in the barge during autonomous.", - "type": "counter", - "required": false, - "code": "BAST", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Processor Algae Scored", - "description": "The number of Algae scored in the processor during autonomous.", - "type": "counter", - "required": false, - "code": "PAST", - "defaultValue": 0, - "min": 0, - "step": 1 - }, - { - "title": "Crossed Feild/Played Defense?", - "type": "boolean", - "required": false, - "code": "CFPDT", - "defaultValue": false - }, - { - "title": "Tipped/Fell Over?", - "type": "boolean", - "required": false, - "code": "TFOT", - "defaultValue": false - }, - { - "title": "Touched Opposing Cage?", - "type": "counter", - "required": false, - "code": "Fou/Tech", - "defaultValue": 0, - "step": 1 - }, - { - "title": "Died?", - "type": "boolean", - "required": false, - "code": "DEg", - "defaultValue": false - } - ] + "title": "Defensive Skill", + "type": "range", + "required": false, + "code": "dr", + "defaultValue": 3, + "min": 1, + "max": 5, + "step": 1 }, { - "name": "Endgame", - "fields": [ - { - "title": "End Position", - "type": "select", - "required": true, - "code": "epo", - "defaultValue": "No", - "choices": { - "No": "Not Parked", - "P": "Parked", - "Sc": "Shallow Climb", - "Dc": "Deep Climb", - "Fc": "Failed Climb" - } - }, - { - "title": "Defended?", - "type": "boolean", - "required": false, - "code": "DEFEg", - "defaultValue": false - } - ] + "title": "Yellow/Red Card", + "type": "select", + "required": true, + "code": "yc", + "defaultValue": "No Card", + "choices": { + "No Card": "No Card", + "Yellow": "Yellow Card", + "Red": "Red Card" + } }, { - "name": "Postmatch", - "fields": [ - { - "title": "Offense Skill", - "type": "range", - "required": false, - "code": "or", - "defaultValue": 3, - "min": 1, - "max": 5, - "step": 1 - }, - { - "title": "Defensive Skill", - "type": "range", - "required": false, - "code": "dr", - "defaultValue": 3, - "min": 1, - "max": 5, - "step": 1 - }, - { - "title": "Yellow/Red Card", - "type": "select", - "required": true, - "code": "yc", - "defaultValue": "No Card", - "choices": { - "No Card": "No Card", - "Yellow": "Yellow Card", - "Red": "Red Card" - } - }, - { - "title": "Comments", - "type": "text", - "required": false, - "code": "co", - "defaultValue": "", - "min": 0, - "max": 50 - } - ] + "title": "Comments", + "type": "text", + "required": false, + "code": "co", + "defaultValue": "", + "min": 0, + "max": 50 } - ] -} \ No newline at end of file + ] + } + ] +} diff --git a/src/components/Sections/ConfigSection/ThemeSelector.tsx b/src/components/Sections/ConfigSection/ThemeSelector.tsx index 216d1ed..8e7cc1c 100644 --- a/src/components/Sections/ConfigSection/ThemeSelector.tsx +++ b/src/components/Sections/ConfigSection/ThemeSelector.tsx @@ -1,12 +1,18 @@ -import { useTheme } from '@/components/ThemeProvider'; +import { Theme, useTheme } from '@/components/ThemeProvider'; import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'; import { Computer, Moon, Sun } from 'lucide-react'; export function ThemeSelector() { const { theme, setTheme } = useTheme(); + const handleValueChange = (value: Theme) => { + if (value === 'light' || value === 'dark' || value === 'system') { + setTheme(value); + } + }; + return ( - + diff --git a/src/components/Sections/FormSection.tsx b/src/components/Sections/FormSection.tsx index a7762b9..4612913 100644 --- a/src/components/Sections/FormSection.tsx +++ b/src/components/Sections/FormSection.tsx @@ -35,7 +35,6 @@ export default function FormSection(props: SectionProps) { section={props.name} code={input.code} type={input.type} - preserveSection={section?.preserveDataOnReset} /> ))} diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx index 7e21efa..d9f2f42 100644 --- a/src/components/ThemeProvider.tsx +++ b/src/components/ThemeProvider.tsx @@ -2,7 +2,7 @@ import { useQRScoutState } from '@/store/store'; import { setColorScheme } from '@/util/theme'; import { createContext, useContext, useEffect, useState } from 'react'; -type Theme = 'dark' | 'light' | 'system' | ''; +export type Theme = 'dark' | 'light' | 'system'; type ThemeProviderProps = { children: React.ReactNode; @@ -57,13 +57,9 @@ export function ThemeProvider({ root.classList.add(systemTheme); return; } - - if(theme !== '') { - setResolvedTheme(theme); - root.classList.add(theme); - } - - + + setResolvedTheme(theme); + root.classList.add(theme); }, [theme]); const value = { diff --git a/src/components/inputs/BaseInputProps.ts b/src/components/inputs/BaseInputProps.ts index 3776de4..96b06a9 100644 --- a/src/components/inputs/BaseInputProps.ts +++ b/src/components/inputs/BaseInputProps.ts @@ -11,12 +11,10 @@ export const inputBaseSchema = z.object({ required: z.boolean().describe('Whether this input is required'), code: z.string().describe('A unique code for this input'), disabled: z.boolean().optional().describe('Whether this input is disabled'), - preserveDataOnReset: z - .boolean() - .optional() - .describe( - 'Whether this input should be preserved when the scouting form is reset', - ), + formResetBehavior: z + .enum(['reset', 'preserve', 'increment']) + .default('reset') + .describe('The behavior of this input when the form is reset'), defaultValue: z.unknown().describe('The default value'), }); @@ -32,12 +30,6 @@ export const numberInputSchema = inputBaseSchema.extend({ min: z.number().optional().describe('The minimum value'), max: z.number().optional().describe('The maximum value'), defaultValue: z.number().default(0).describe('The default value'), - autoIncrementOnReset: z - .boolean() - .optional() - .describe( - 'Whether this input should auto-increment on reset, instead of resetting to the default value', - ), }); export const selectInputSchema = inputBaseSchema.extend({ @@ -59,12 +51,6 @@ export const counterInputSchema = inputBaseSchema.extend({ max: z.number().optional().describe('The maximum value'), step: z.number().optional().describe('The step value').default(1), defaultValue: z.number().default(0).describe('The default value'), - autoIncrementOnReset: z - .boolean() - .optional() - .describe( - 'Whether this input should auto-increment on reset, instead of resetting to the default value', - ), }); export const rangeInputSchema = inputBaseSchema.extend({ @@ -73,12 +59,6 @@ export const rangeInputSchema = inputBaseSchema.extend({ max: z.number().optional().describe('The maximum value'), step: z.number().optional().describe('The step value').default(1), defaultValue: z.number().default(0).describe('The default value'), - autoIncrementOnReset: z - .boolean() - .optional() - .describe( - 'Whether this input should auto-increment on reset, instead of resetting to the default value', - ), }); export const booleanInputSchema = inputBaseSchema.extend({ @@ -93,7 +73,6 @@ export const timerInputSchema = inputBaseSchema.extend({ export const sectionSchema = z.object({ name: z.string(), - preserveDataOnReset: z.boolean().optional(), fields: z.array( z.discriminatedUnion('type', [ counterInputSchema, diff --git a/src/components/inputs/CheckboxInput.tsx b/src/components/inputs/CheckboxInput.tsx index c7e80c7..5d4559d 100644 --- a/src/components/inputs/CheckboxInput.tsx +++ b/src/components/inputs/CheckboxInput.tsx @@ -16,16 +16,22 @@ export default function CheckboxInput(props: ConfigurableInputProps) { const [checked, setChecked] = React.useState(data.defaultValue); - const resetState = React.useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { - return; - } - setChecked(data.defaultValue); - }, [data.defaultValue]); + const resetState = React.useCallback( + ({ force }: { force: boolean }) => { + if (force) { + setChecked(data.defaultValue); + return; + } + if (data.formResetBehavior === 'preserve') { + return; + } + setChecked(data.defaultValue); + }, + [data.defaultValue], + ); useEvent('resetFields', resetState); - useEffect(() => { updateValue(props.code, checked); }, [checked]); diff --git a/src/components/inputs/ConfigurableInput.tsx b/src/components/inputs/ConfigurableInput.tsx index 0f060aa..014d706 100644 --- a/src/components/inputs/ConfigurableInput.tsx +++ b/src/components/inputs/ConfigurableInput.tsx @@ -11,7 +11,6 @@ export interface ConfigurableInputProps { section: string; code: string; type: InputTypes; - preserveSection?: boolean; } export default function ConfigurableInput(props: ConfigurableInputProps) { diff --git a/src/components/inputs/CounterInput.tsx b/src/components/inputs/CounterInput.tsx index 3439e56..737c603 100644 --- a/src/components/inputs/CounterInput.tsx +++ b/src/components/inputs/CounterInput.tsx @@ -17,16 +17,27 @@ export default function CounterInput(props: ConfigurableInputProps) { const [value, setValue] = useState(data.defaultValue); - const resetState = useCallback(({force}: {force: boolean}) => { - if(!force && (data.preserveDataOnReset || props.preserveSection)) { - if (data.autoIncrementOnReset) { - const newVal = typeof value === 'number' ? value + data.step : 1; - setValue(newVal); + const resetState = useCallback( + ({ force }: { force: boolean }) => { + if (force) { + setValue(data.defaultValue); + return; } - return; - }; - setValue(data.defaultValue); - }, [value]); + switch (data.formResetBehavior) { + case 'reset': + setValue(data.defaultValue); + return; + case 'increment': + setValue(prev => (typeof prev === 'number' ? prev + data.step : 1)); + return; + case 'preserve': + return; + default: + return; + } + }, + [value], + ); useEvent('resetFields', resetState); diff --git a/src/components/inputs/NumberInput.tsx b/src/components/inputs/NumberInput.tsx index c366208..04cc492 100644 --- a/src/components/inputs/NumberInput.tsx +++ b/src/components/inputs/NumberInput.tsx @@ -16,17 +16,32 @@ export default function NumberInput(props: ConfigurableInputProps) { const [value, setValue] = React.useState(data.defaultValue); - const resetState = useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { - if (data.autoIncrementOnReset) { - const newVal = typeof value === 'number' ? value + 1 : 1; - setValue(newVal); + const resetState = useCallback( + ({ force }: { force: boolean }) => { + console.log( + `resetState ${data.code}`, + `force: ${force}`, + `behavior: ${data.formResetBehavior}`, + ); + if (force) { + setValue(data.defaultValue); + return; + } + switch (data.formResetBehavior) { + case 'reset': + setValue(data.defaultValue); + return; + case 'increment': + setValue(prev => (typeof prev === 'number' ? prev + 1 : 1)); + return; + case 'preserve': + return; + default: + return; } - return; - } else { - setValue(data.defaultValue); - } - }, [data.defaultValue, value]); + }, + [data.defaultValue, value], + ); useEvent('resetFields', resetState); diff --git a/src/components/inputs/RangeInput.tsx b/src/components/inputs/RangeInput.tsx index 156c064..60e9734 100644 --- a/src/components/inputs/RangeInput.tsx +++ b/src/components/inputs/RangeInput.tsx @@ -16,16 +16,27 @@ export default function RangeInput(props: ConfigurableInputProps) { const [value, setValue] = useState(data.defaultValue); - const resetState = useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { - if (data.autoIncrementOnReset) { - const newVal = typeof value === 'number' ? value + data.step : 1; - setValue(newVal); + const resetState = useCallback( + ({ force }: { force: boolean }) => { + if (force) { + setValue(data.defaultValue); + return; } - return; - } - setValue(data.defaultValue); - }, [data.defaultValue]); + switch (data.formResetBehavior) { + case 'reset': + setValue(data.defaultValue); + return; + case 'increment': + setValue(prev => (typeof prev === 'number' ? prev + data.step : 1)); + return; + case 'preserve': + return; + default: + return; + } + }, + [data.defaultValue], + ); useEvent('resetFields', resetState); diff --git a/src/components/inputs/SelectInput.tsx b/src/components/inputs/SelectInput.tsx index 833576f..fb0f329 100644 --- a/src/components/inputs/SelectInput.tsx +++ b/src/components/inputs/SelectInput.tsx @@ -26,12 +26,24 @@ export default function SelectInput(props: ConfigurableInputProps) { updateValue(props.code, value); }, [value]); - const resetState = useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { - return; - } - setValue(data.defaultValue); - }, [data.defaultValue]); + const resetState = useCallback( + ({ force }: { force: boolean }) => { + console.log( + `resetState ${data.code}`, + `force: ${force}`, + `behavior: ${data.formResetBehavior}`, + ); + if (force) { + setValue(data.defaultValue); + return; + } + if (data.formResetBehavior === 'preserve') { + return; + } + setValue(data.defaultValue); + }, + [data.defaultValue], + ); useEvent('resetFields', resetState); diff --git a/src/components/inputs/StringInput.tsx b/src/components/inputs/StringInput.tsx index 218580f..34e7727 100644 --- a/src/components/inputs/StringInput.tsx +++ b/src/components/inputs/StringInput.tsx @@ -16,12 +16,24 @@ export default function StringInput(props: ConfigurableInputProps) { const [value, setValue] = React.useState(data.defaultValue); - const resetState = useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { - return; - } - setValue(data.defaultValue); - }, [data.defaultValue]); + const resetState = useCallback( + ({ force }: { force: boolean }) => { + console.log( + `resetState ${data.code}`, + `force: ${force}`, + `behavior: ${data.formResetBehavior}`, + ); + if (force) { + setValue(data.defaultValue); + return; + } + if (data.formResetBehavior === 'preserve') { + return; + } + setValue(data.defaultValue); + }, + [data.defaultValue], + ); useEvent('resetFields', resetState); diff --git a/src/components/inputs/TimerInput.tsx b/src/components/inputs/TimerInput.tsx index 7e6dea7..3839254 100644 --- a/src/components/inputs/TimerInput.tsx +++ b/src/components/inputs/TimerInput.tsx @@ -31,8 +31,15 @@ export default function TimerInput(props: ConfigurableInputProps) { const average = useMemo(() => getAvg(times), [times]); - const resetState = useCallback(({force}: {force: boolean}) => { - if (!force && (data.preserveDataOnReset || props.preserveSection)) { + const resetState = useCallback(({ force }: { force: boolean }) => { + if (force) { + setTime(data.defaultValue); + toggleTimer(false); + setTimes([]); + updateValue(props.code, data.defaultValue); + return; + } + if (data.formResetBehavior === 'preserve') { return; } @@ -43,7 +50,7 @@ export default function TimerInput(props: ConfigurableInputProps) { }, []); useEvent('resetFields', resetState); - + function startStop() { toggleTimer(!isRunning); } @@ -84,7 +91,7 @@ export default function TimerInput(props: ConfigurableInputProps) { -