From 1ecdd9b250e46faf1ec62a45b23bc52076cb66eb Mon Sep 17 00:00:00 2001 From: Ivy Yip Date: Sun, 26 Oct 2025 12:35:19 -0400 Subject: [PATCH 1/5] config categories --- client/src/client-config.tsx | 82 +++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index 40a3889f5..b0c8c83ca 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -76,6 +76,38 @@ const configDescription: Record = { colors: '' } +const configCategories: Record = { + // Game Visualization + showAllIndicators: 'Game Visualization', + showAllRobotRadii: 'Game Visualization', + showSRPOutlines: 'Game Visualization', + showSRPText: 'Game Visualization', + showMapXY: 'Game Visualization', + enableFancyPaint: 'Game Visualization', + + // Robot Display & Status + showHealthBars: 'Robot Display & Status', + showPaintBars: 'Robot Display & Status', + showExceededBytecode: 'Robot Display & Status', + focusRobotTurn: 'Robot Display & Status', + + // Markers & Paint Debugging + showTimelineMarkers: 'Markers & Paint Debugging', + showPaintMarkers: 'Markers & Paint Debugging', + + // Game Playback + streamRunnerGames: 'Game Playback', + populateRunnerGames: 'Game Playback', + + // Developer & Validation Tools + profileGames: 'Developer & Validation Tools', + validateMaps: 'Developer & Validation Tools', + + // Mischellanous + resolutionScale: '', + colors: '' +} + export function getDefaultConfig(): ClientConfig { const config: ClientConfig = { ...DEFAULT_CONFIG } for (const key in config) { @@ -111,15 +143,31 @@ export const ConfigPage: React.FC = (props) => { if (!props.open) return null + const groupedCategories = Object.entries( + Object.keys(configCategories).reduce( + (acc, key) => { + const category = configCategories[key as keyof ClientConfig] + if (!acc[category]) acc[category] = [] + acc[category].push(key) + return acc + }, + {} as Record + ) + ) + return (
Edit Client Config:
- {Object.entries(DEFAULT_CONFIG).map(([k, v]) => { + {/* {Object.entries(DEFAULT_CONFIG).map(([k, v]) => { const key = k as keyof ClientConfig if (typeof v === 'string') return if (typeof v === 'boolean') return if (typeof v === 'number') return - })} + })} */} + + {groupedCategories.map(([category, keys]) => ( + } /> + ))}
@@ -251,6 +299,36 @@ const SingleColorPicker = (props: { displayName: string; colorName: Colors }) => ) } +const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array }> = ({ title, keys }) => { + const [open, setOpen] = useState(true) + + return ( +
+ {/* Header */} + + + {/* Collapsible content */} + {open && ( +
+ {keys.map((key) => { + const value = DEFAULT_CONFIG[key] + if (typeof value === 'boolean') return + if (typeof value === 'number') return + if (typeof value === 'string') return + return null + })} +
+ )} +
+ ) +} + const ConfigBooleanElement: React.FC<{ configKey: keyof ClientConfig }> = ({ configKey }) => { const context = useAppContext() const value = context.state.config[configKey] as boolean From 3c6729f4211fa7ba72676b5a6c2bf4818b532d9e Mon Sep 17 00:00:00 2001 From: Ivy Yip Date: Sun, 26 Oct 2025 13:38:36 -0400 Subject: [PATCH 2/5] add search --- client/src/client-config.tsx | 66 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index b0c8c83ca..3e91df966 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -15,6 +15,7 @@ import { } from './colors' import { BrightButton, Button } from './components/button' import { useKeyboard } from './util/keyboard' +import { SectionHeader } from './components/section-header' export type ClientConfig = typeof DEFAULT_CONFIG @@ -132,6 +133,8 @@ export const ConfigPage: React.FC = (props) => { const context = useAppContext() const keyboard = useKeyboard() + const [input, setInput] = useState('') + useEffect(() => { if (context.state.disableHotkeys) return @@ -143,30 +146,39 @@ export const ConfigPage: React.FC = (props) => { if (!props.open) return null - const groupedCategories = Object.entries( - Object.keys(configCategories).reduce( - (acc, key) => { - const category = configCategories[key as keyof ClientConfig] - if (!acc[category]) acc[category] = [] - acc[category].push(key) - return acc - }, - {} as Record - ) + const configEntries = Object.keys(DEFAULT_CONFIG).map((key) => ({ + key: key as keyof ClientConfig, + category: configCategories[key as keyof ClientConfig], + value: DEFAULT_CONFIG[key as keyof ClientConfig] + })) + + const filteredEntries = configEntries.filter(({ key, category }) => { + if (!input.trim()) return true + const s = input.toLowerCase() + return key.toLowerCase().includes(s) || category.toLowerCase().includes(s) + }) + + const groupedCategories = filteredEntries.reduce( + (acc, { category, key }) => { + if (!acc[category]) acc[category] = [] + acc[category].push(key) + return acc + }, + {} as Record> ) return (
Edit Client Config:
- {/* {Object.entries(DEFAULT_CONFIG).map(([k, v]) => { - const key = k as keyof ClientConfig - if (typeof v === 'string') return - if (typeof v === 'boolean') return - if (typeof v === 'number') return - })} */} - - {groupedCategories.map(([category, keys]) => ( - } /> + setInput(e.target.value)} + /> + {Object.entries(groupedCategories).map(([category, keys]) => ( + ))} @@ -302,18 +314,14 @@ const SingleColorPicker = (props: { displayName: string; colorName: Colors }) => const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array }> = ({ title, keys }) => { const [open, setOpen] = useState(true) + const onClick = () => { + setOpen(!open) + } + return (
- {/* Header */} - - - {/* Collapsible content */} +
}> + {open && (
{keys.map((key) => { From 68e3e494dec3329253538c3386bf2b5bada9086b Mon Sep 17 00:00:00 2001 From: Ivy Yip Date: Sat, 22 Nov 2025 19:33:46 -0500 Subject: [PATCH 3/5] final fixes for config --- client/src/client-config.tsx | 73 ++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index 3e91df966..9b2d7c2a8 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -101,8 +101,8 @@ const configCategories: Record = { populateRunnerGames: 'Game Playback', // Developer & Validation Tools - profileGames: 'Developer & Validation Tools', - validateMaps: 'Developer & Validation Tools', + profileGames: 'Developer Tools', + validateMaps: 'Developer Tools', // Mischellanous resolutionScale: '', @@ -134,9 +134,11 @@ export const ConfigPage: React.FC = (props) => { const keyboard = useKeyboard() const [input, setInput] = useState('') + const [isSearchFocused, setIsSearchFocused] = useState(false) + const [shouldForceOpen, setShouldForceOpen] = useState(false) useEffect(() => { - if (context.state.disableHotkeys) return + if (context.state.disableHotkeys || isSearchFocused) return if (keyboard.keyCode === 'KeyF') context.updateConfigValue('focusRobotTurn', !context.state.config.focusRobotTurn) @@ -173,13 +175,45 @@ export const ConfigPage: React.FC = (props) => { setInput(e.target.value)} + onChange={(e) => { + setInput(e.target.value) + setShouldForceOpen(true) + }} + onFocus={(e) => { + setIsSearchFocused(true) + setTimeout(() => e.target.select(), 0) + }} + onBlur={() => { + setIsSearchFocused(false) + setShouldForceOpen(false) + }} + autoCapitalize="off" + autoCorrect="off" + autoComplete="off" /> - {Object.entries(groupedCategories).map(([category, keys]) => ( - - ))} + {Object.entries(groupedCategories) + .filter(([category]) => category !== '') + .map(([category, keys]) => ( + + ))} + + {groupedCategories[''] && ( +
+ {groupedCategories[''].map((key) => { + const value = DEFAULT_CONFIG[key] + if (key === 'colors') return null + if (typeof value === 'number') return + return null + })} +
+ )}
@@ -311,18 +345,33 @@ const SingleColorPicker = (props: { displayName: string; colorName: Colors }) => ) } -const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array }> = ({ title, keys }) => { - const [open, setOpen] = useState(true) +const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array; forceOpen?: boolean }> = ({ + title, + keys, + forceOpen +}) => { + const [open, setOpen] = useState(false) + const [manuallyToggled, setManuallyToggled] = useState(false) + + useEffect(() => { + if (forceOpen && !manuallyToggled) { + setOpen(true) + } else if (!forceOpen && !manuallyToggled) { + setOpen(false) + } + }, [forceOpen, manuallyToggled]) const onClick = () => { setOpen(!open) + setManuallyToggled(true) } + const isOpen = open return (
-
}> +
}> - {open && ( + {(open || forceOpen) && (
{keys.map((key) => { const value = DEFAULT_CONFIG[key] From a7189e452c73703d73aa9215a4e54552198775c1 Mon Sep 17 00:00:00 2001 From: Ivy Yip Date: Sat, 22 Nov 2025 19:38:58 -0500 Subject: [PATCH 4/5] final fixes --- client/src/client-config.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index 9b2d7c2a8..e9f8b9986 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -366,12 +366,11 @@ const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array -
}> + }> - {(open || forceOpen) && ( + {open && (
{keys.map((key) => { const value = DEFAULT_CONFIG[key] From 6fe5967a996949e80a6371bf19d1132405c23f1f Mon Sep 17 00:00:00 2001 From: Ivy Yip Date: Sun, 23 Nov 2025 13:30:46 -0500 Subject: [PATCH 5/5] fixes for config categories --- client/src/client-config.tsx | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/client/src/client-config.tsx b/client/src/client-config.tsx index e9f8b9986..57c81ab02 100644 --- a/client/src/client-config.tsx +++ b/client/src/client-config.tsx @@ -137,6 +137,8 @@ export const ConfigPage: React.FC = (props) => { const [isSearchFocused, setIsSearchFocused] = useState(false) const [shouldForceOpen, setShouldForceOpen] = useState(false) + const sidebarColor = context.state.config.colors[Colors.SIDEBAR_BACKGROUND] + useEffect(() => { if (context.state.disableHotkeys || isSearchFocused) return @@ -157,7 +159,8 @@ export const ConfigPage: React.FC = (props) => { const filteredEntries = configEntries.filter(({ key, category }) => { if (!input.trim()) return true const s = input.toLowerCase() - return key.toLowerCase().includes(s) || category.toLowerCase().includes(s) + const description = configDescription[key]?.toLowerCase() || '' + return key.toLowerCase().includes(s) || description.includes(s) }) const groupedCategories = filteredEntries.reduce( @@ -175,7 +178,7 @@ export const ConfigPage: React.FC = (props) => { { setInput(e.target.value) @@ -192,6 +195,9 @@ export const ConfigPage: React.FC = (props) => { autoCapitalize="off" autoCorrect="off" autoComplete="off" + style={{ + backgroundColor: sidebarColor + }} /> {Object.entries(groupedCategories) .filter(([category]) => category !== '') @@ -201,6 +207,7 @@ export const ConfigPage: React.FC = (props) => { title={category} keys={keys} forceOpen={shouldForceOpen && !!input.trim()} + hasInput={!!input.trim()} /> ))} @@ -345,27 +352,25 @@ const SingleColorPicker = (props: { displayName: string; colorName: Colors }) => ) } -const ConfigCategoryDropdown: React.FC<{ title: string; keys: Array; forceOpen?: boolean }> = ({ - title, - keys, - forceOpen -}) => { +const ConfigCategoryDropdown: React.FC<{ + title: string + keys: Array + forceOpen?: boolean + hasInput?: boolean +}> = ({ title, keys, forceOpen, hasInput }) => { const [open, setOpen] = useState(false) - const [manuallyToggled, setManuallyToggled] = useState(false) useEffect(() => { - if (forceOpen && !manuallyToggled) { + if (forceOpen) { setOpen(true) - } else if (!forceOpen && !manuallyToggled) { + } else if (!hasInput) { setOpen(false) } - }, [forceOpen, manuallyToggled]) + }, [forceOpen, hasInput]) const onClick = () => { setOpen(!open) - setManuallyToggled(true) } - return (
}>