From afe3b140dbd3c824807f0d613e7d3d843b5edb38 Mon Sep 17 00:00:00 2001 From: Ty Tremblay Date: Sun, 19 Jan 2025 15:28:21 -0500 Subject: [PATCH 1/4] fix theme selector --- .../Sections/ConfigSection/ThemeSelector.tsx | 10 ++++++++-- src/components/ThemeProvider.tsx | 12 ++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) 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/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 = { From 4bd679525e0798007bc37cc7e17b743c2f8d2283 Mon Sep 17 00:00:00 2001 From: Ty Tremblay Date: Sun, 19 Jan 2025 17:09:33 -0500 Subject: [PATCH 2/4] preserve prematch fields --- config/2025/config.json | 831 ++++++++++++------------ src/components/Sections/FormSection.tsx | 2 +- src/components/inputs/BaseInputProps.ts | 29 +- src/components/inputs/CheckboxInput.tsx | 20 +- src/components/inputs/CounterInput.tsx | 32 +- src/components/inputs/NumberInput.tsx | 35 +- src/components/inputs/RangeInput.tsx | 34 +- src/components/inputs/SelectInput.tsx | 24 +- src/components/inputs/StringInput.tsx | 24 +- src/components/inputs/TimerInput.tsx | 15 +- 10 files changed, 554 insertions(+), 492 deletions(-) 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/FormSection.tsx b/src/components/Sections/FormSection.tsx index a7762b9..50f93ca 100644 --- a/src/components/Sections/FormSection.tsx +++ b/src/components/Sections/FormSection.tsx @@ -35,7 +35,7 @@ export default function FormSection(props: SectionProps) { section={props.name} code={input.code} type={input.type} - preserveSection={section?.preserveDataOnReset} + preserveSection={section?.formResetBehavior === 'preserve'} /> ))} 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..f12a478 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' || props.preserveSection) { + return; + } + setChecked(data.defaultValue); + }, + [data.defaultValue], + ); useEvent('resetFields', resetState); - useEffect(() => { updateValue(props.code, checked); }, [checked]); diff --git a/src/components/inputs/CounterInput.tsx b/src/components/inputs/CounterInput.tsx index 3439e56..49051ac 100644 --- a/src/components/inputs/CounterInput.tsx +++ b/src/components/inputs/CounterInput.tsx @@ -17,16 +17,30 @@ 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]); + if (props.preserveSection) { + return; + } + 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..5e48f2e 100644 --- a/src/components/inputs/RangeInput.tsx +++ b/src/components/inputs/RangeInput.tsx @@ -16,16 +16,32 @@ 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]); + + if (props.preserveSection) { + return; + } + + 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..a43b9eb 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' || props.preserveSection) { + 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..8ff70be 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' || props.preserveSection) { + 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..7797814 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' || props.preserveSection) { 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) { - From 5fd887a0e86187972cba31e4eb618fd16819cf42 Mon Sep 17 00:00:00 2001 From: Ty Tremblay Date: Sun, 19 Jan 2025 17:17:51 -0500 Subject: [PATCH 3/4] can't preserve sections anymore --- src/components/Sections/FormSection.tsx | 1 - src/components/inputs/CheckboxInput.tsx | 2 +- src/components/inputs/ConfigurableInput.tsx | 1 - src/components/inputs/CounterInput.tsx | 3 --- src/components/inputs/RangeInput.tsx | 5 ----- src/components/inputs/SelectInput.tsx | 2 +- src/components/inputs/StringInput.tsx | 2 +- src/components/inputs/TimerInput.tsx | 2 +- 8 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/components/Sections/FormSection.tsx b/src/components/Sections/FormSection.tsx index 50f93ca..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?.formResetBehavior === 'preserve'} /> ))} diff --git a/src/components/inputs/CheckboxInput.tsx b/src/components/inputs/CheckboxInput.tsx index f12a478..5d4559d 100644 --- a/src/components/inputs/CheckboxInput.tsx +++ b/src/components/inputs/CheckboxInput.tsx @@ -22,7 +22,7 @@ export default function CheckboxInput(props: ConfigurableInputProps) { setChecked(data.defaultValue); return; } - if (data.formResetBehavior === 'preserve' || props.preserveSection) { + if (data.formResetBehavior === 'preserve') { return; } setChecked(data.defaultValue); 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 49051ac..737c603 100644 --- a/src/components/inputs/CounterInput.tsx +++ b/src/components/inputs/CounterInput.tsx @@ -23,9 +23,6 @@ export default function CounterInput(props: ConfigurableInputProps) { setValue(data.defaultValue); return; } - if (props.preserveSection) { - return; - } switch (data.formResetBehavior) { case 'reset': setValue(data.defaultValue); diff --git a/src/components/inputs/RangeInput.tsx b/src/components/inputs/RangeInput.tsx index 5e48f2e..60e9734 100644 --- a/src/components/inputs/RangeInput.tsx +++ b/src/components/inputs/RangeInput.tsx @@ -22,11 +22,6 @@ export default function RangeInput(props: ConfigurableInputProps) { setValue(data.defaultValue); return; } - - if (props.preserveSection) { - return; - } - switch (data.formResetBehavior) { case 'reset': setValue(data.defaultValue); diff --git a/src/components/inputs/SelectInput.tsx b/src/components/inputs/SelectInput.tsx index a43b9eb..fb0f329 100644 --- a/src/components/inputs/SelectInput.tsx +++ b/src/components/inputs/SelectInput.tsx @@ -37,7 +37,7 @@ export default function SelectInput(props: ConfigurableInputProps) { setValue(data.defaultValue); return; } - if (data.formResetBehavior === 'preserve' || props.preserveSection) { + if (data.formResetBehavior === 'preserve') { return; } setValue(data.defaultValue); diff --git a/src/components/inputs/StringInput.tsx b/src/components/inputs/StringInput.tsx index 8ff70be..34e7727 100644 --- a/src/components/inputs/StringInput.tsx +++ b/src/components/inputs/StringInput.tsx @@ -27,7 +27,7 @@ export default function StringInput(props: ConfigurableInputProps) { setValue(data.defaultValue); return; } - if (data.formResetBehavior === 'preserve' || props.preserveSection) { + if (data.formResetBehavior === 'preserve') { return; } setValue(data.defaultValue); diff --git a/src/components/inputs/TimerInput.tsx b/src/components/inputs/TimerInput.tsx index 7797814..3839254 100644 --- a/src/components/inputs/TimerInput.tsx +++ b/src/components/inputs/TimerInput.tsx @@ -39,7 +39,7 @@ export default function TimerInput(props: ConfigurableInputProps) { updateValue(props.code, data.defaultValue); return; } - if (data.formResetBehavior === 'preserve' || props.preserveSection) { + if (data.formResetBehavior === 'preserve') { return; } From 2766073a6dffd0d91c91ccf08573def82859a1dc Mon Sep 17 00:00:00 2001 From: Ty Tremblay Date: Sun, 19 Jan 2025 17:23:56 -0500 Subject: [PATCH 4/4] update readme --- README.md | 59 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 21 deletions(-) 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.