Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 146 additions & 7 deletions client/src/client-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -76,6 +77,38 @@ const configDescription: Record<keyof ClientConfig, string> = {
colors: ''
}

const configCategories: Record<keyof ClientConfig, string> = {
// 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 Tools',
validateMaps: 'Developer Tools',

// Mischellanous
resolutionScale: '',
colors: ''
}

export function getDefaultConfig(): ClientConfig {
const config: ClientConfig = { ...DEFAULT_CONFIG }
for (const key in config) {
Expand All @@ -100,8 +133,14 @@ export const ConfigPage: React.FC<Props> = (props) => {
const context = useAppContext()
const keyboard = useKeyboard()

const [input, setInput] = useState('')
const [isSearchFocused, setIsSearchFocused] = useState(false)
const [shouldForceOpen, setShouldForceOpen] = useState(false)

const sidebarColor = context.state.config.colors[Colors.SIDEBAR_BACKGROUND]

useEffect(() => {
if (context.state.disableHotkeys) return
if (context.state.disableHotkeys || isSearchFocused) return

if (keyboard.keyCode === 'KeyF')
context.updateConfigValue('focusRobotTurn', !context.state.config.focusRobotTurn)
Expand All @@ -111,15 +150,77 @@ export const ConfigPage: React.FC<Props> = (props) => {

if (!props.open) return null

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()
const description = configDescription[key]?.toLowerCase() || ''
return key.toLowerCase().includes(s) || description.includes(s)
})

const groupedCategories = filteredEntries.reduce(
(acc, { category, key }) => {
if (!acc[category]) acc[category] = []
acc[category].push(key)
return acc
},
{} as Record<string, Array<keyof ClientConfig>>
)

return (
<div className={'flex flex-col'}>
<div className="mb-2">Edit Client Config:</div>
{Object.entries(DEFAULT_CONFIG).map(([k, v]) => {
const key = k as keyof ClientConfig
if (typeof v === 'string') return <ConfigStringElement configKey={key} key={key} />
if (typeof v === 'boolean') return <ConfigBooleanElement configKey={key} key={key} />
if (typeof v === 'number') return <ConfigNumberElement configKey={key} key={key} />
})}
<input
type="text"
placeholder="Search Configs..."
className="w-full mb-3 px-3 py-2 border border-white shadow-lg rounded-xl"
value={input}
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"
style={{
backgroundColor: sidebarColor
}}
/>
{Object.entries(groupedCategories)
.filter(([category]) => category !== '')
.map(([category, keys]) => (
<ConfigCategoryDropdown
key={category}
title={category}
keys={keys}
forceOpen={shouldForceOpen && !!input.trim()}
hasInput={!!input.trim()}
/>
))}

{groupedCategories[''] && (
<div className="mb-3">
{groupedCategories[''].map((key) => {
const value = DEFAULT_CONFIG[key]
if (key === 'colors') return null
if (typeof value === 'number') return <ConfigNumberElement configKey={key} key={key} />
return null
})}
</div>
)}

<ColorConfig />
</div>
Expand Down Expand Up @@ -251,6 +352,44 @@ const SingleColorPicker = (props: { displayName: string; colorName: Colors }) =>
)
}

const ConfigCategoryDropdown: React.FC<{
title: string
keys: Array<keyof ClientConfig>
forceOpen?: boolean
hasInput?: boolean
}> = ({ title, keys, forceOpen, hasInput }) => {
const [open, setOpen] = useState<boolean>(false)

useEffect(() => {
if (forceOpen) {
setOpen(true)
} else if (!hasInput) {
setOpen(false)
}
}, [forceOpen, hasInput])

const onClick = () => {
setOpen(!open)
}
return (
<div className="mb-3">
<SectionHeader title={title} open={open} onClick={onClick} children={<div></div>}></SectionHeader>

{open && (
<div className=" px-3 py-2">
{keys.map((key) => {
const value = DEFAULT_CONFIG[key]
if (typeof value === 'boolean') return <ConfigBooleanElement configKey={key} key={key} />
if (typeof value === 'number') return <ConfigNumberElement configKey={key} key={key} />
if (typeof value === 'string') return <ConfigStringElement configKey={key} key={key} />
return null
})}
</div>
)}
</div>
)
}

const ConfigBooleanElement: React.FC<{ configKey: keyof ClientConfig }> = ({ configKey }) => {
const context = useAppContext()
const value = context.state.config[configKey] as boolean
Expand Down