11import { Box , Stack , Tab , Tabs , TextField } from "@mui/material"
22import type React from "react"
3- import { useCallback , useEffect , useReducer , useState } from "react"
3+ import { useCallback , useEffect , useState } from "react"
44import { globalAddToast , globalOpenModal } from "@/components/GlobalUIControls.ts"
55import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
6- import type { UserPreference , UserPreferences } from "@/systems/preferences/PreferenceTypes"
6+ import type { UserPreferences } from "@/systems/preferences/PreferenceTypes"
77import { SoundPlayer } from "@/systems/sound/SoundPlayer"
88import World from "@/systems/World"
9- import Checkbox from "@/ui/components/Checkbox"
9+ import Checkbox , { type CheckboxProps } from "@/ui/components/Checkbox"
1010import Label from "@/ui/components/Label"
1111import type { ModalImplProps } from "@/ui/components/Modal"
12- import StatefulSlider from "@/ui/components/StatefulSlider"
12+ import StatefulSlider , { type StatefulSliderProps } from "@/ui/components/StatefulSlider"
1313import { Button , Spacer , SynthesisIcons } from "@/ui/components/StyledComponents"
1414import { useThemeContext } from "@/ui/helpers/ThemeProviderHelpers"
1515import { useUIContext } from "@/ui/helpers/UIProviderHelpers"
@@ -52,10 +52,6 @@ type ThemeEditorTabActions = {
5252 reset : ( ) => void
5353}
5454
55- type GeneralTabProps = {
56- writePreference : < K extends UserPreference > ( pref : K , value : UserPreferences [ K ] ) => void
57- }
58-
5955type GraphicsTabProps = {
6056 onActionsChange ?: ( actions : GraphicsTabActions ) => void
6157}
@@ -71,7 +67,7 @@ interface TabConfigBase {
7167
7268interface GeneralTabConfig extends TabConfigBase {
7369 key : "general"
74- component : React . ComponentType < GeneralTabProps >
70+ component : React . ComponentType
7571}
7672
7773interface GraphicsTabConfig extends TabConfigBase {
@@ -113,112 +109,123 @@ const ColorEditor: React.FC<{
113109 )
114110}
115111
116- const GeneralTab : React . FC < GeneralTabProps > = ( { writePreference } ) => (
112+ type TypedPreferenceKey < T > = {
113+ [ K in keyof UserPreferences ] : UserPreferences [ K ] extends T ? K : never
114+ } [ keyof UserPreferences ]
115+
116+ const GeneralTabSlider = ( {
117+ preference,
118+ label,
119+ ...props
120+ } : Omit < StatefulSliderProps , "defaultValue" | "onChange" > & { preference : TypedPreferenceKey < number > } ) => {
121+ const [ pref , setPref ] = useState ( PreferencesSystem . getUserPreference ( preference ) )
122+ useEffect ( ( ) => {
123+ PreferencesSystem . setUserPreference ( preference , pref )
124+ } , [ pref ] )
125+
126+ return < StatefulSlider { ...props } label = { label } defaultValue = { pref } onChange = { setPref } />
127+ }
128+
129+ const GeneralTabCheckbox = ( {
130+ preference,
131+ label,
132+ ...props
133+ } : Omit < CheckboxProps , "checked" | "onClick" > & { preference : TypedPreferenceKey < boolean > } ) => {
134+ const [ pref , setPref ] = useState ( PreferencesSystem . getUserPreference ( preference ) )
135+ useEffect ( ( ) => {
136+ PreferencesSystem . setUserPreference ( preference , pref )
137+ } , [ pref ] )
138+
139+ return < Checkbox { ...props } label = { label } checked = { pref } onClick = { setPref } />
140+ }
141+
142+ const GeneralTab : React . FC = ( ) => (
117143 < Stack direction = "column" gap = { 2 } >
118144 < Spacer height = { 5 } />
119145 < Label size = "sm" > Camera Settings</ Label >
120- < StatefulSlider
146+ < GeneralTabSlider
147+ preference = "SceneRotationSensitivity"
148+ label = "Scene Rotation Sensitivity"
149+ tooltip = "Controls how fast the scene rotates when dragging with the mouse."
150+ showValue = { false }
121151 min = { 0.1 }
122152 max = { 2.0 }
123- defaultValue = { PreferencesSystem . getUserPreference ( "SceneRotationSensitivity" ) }
124- label = { "Scene Rotation Sensitivity" }
125- onChange = { value => writePreference ( "SceneRotationSensitivity" , value ) }
126153 step = { 0.1 }
127- tooltip = "Controls how fast the scene rotates when dragging with the mouse."
128- showValue = { false }
129154 />
130155 < Spacer height = { 5 } />
131- < StatefulSlider
156+ < GeneralTabSlider
157+ preference = "ScenePanSensitivity"
158+ label = "Scene Pan Sensitivity"
159+ tooltip = "Controls how fast the scene pans when dragging with the right mouse button."
160+ showValue = { false }
132161 min = { 0.1 }
133162 max = { 3.0 }
134- defaultValue = { PreferencesSystem . getUserPreference ( "ScenePanSensitivity" ) }
135- label = { "Scene Pan Sensitivity" }
136- onChange = { value => writePreference ( "ScenePanSensitivity" , value ) }
137163 step = { 0.1 }
138- tooltip = "Controls how fast the scene pans when dragging with the right mouse button."
139- showValue = { false }
140164 />
141165 < Spacer height = { 5 } />
142- < StatefulSlider
166+ < GeneralTabSlider
167+ preference = "ViewCubeRotationSensitivity"
168+ label = "ViewCube Rotation Sensitivity"
169+ tooltip = "Controls how fast the view changes when dragging on the view cube."
170+ showValue = { false }
143171 min = { 0.06 }
144172 max = { 6.0 }
145- defaultValue = { PreferencesSystem . getUserPreference ( "ViewCubeRotationSensitivity" ) }
146- label = { "ViewCube Rotation Sensitivity" }
147- onChange = { value => writePreference ( "ViewCubeRotationSensitivity" , value ) }
148173 step = { 0.06 }
149- tooltip = "Controls how fast the view changes when dragging on the view cube."
150- showValue = { false }
151174 />
152- < Checkbox
175+ < GeneralTabCheckbox
176+ preference = "ShowViewCube"
153177 label = "Show View Cube"
154- checked = { PreferencesSystem . getUserPreference ( "ShowViewCube" ) }
155- onClick = { checked => writePreference ( "ShowViewCube" , checked ) }
156178 tooltip = "Show the view cube in the top-right corner for quick camera orientation changes."
157179 />
158180 < Spacer height = { 10 } />
159181 < Label size = "md" sx = { { fontWeight : 600 } } >
160182 Preferences
161183 </ Label >
162184 < Stack direction = "column" >
163- < Checkbox
185+ < GeneralTabCheckbox
186+ preference = "ReportAnalytics"
164187 label = "Report Analytics"
165- checked = { PreferencesSystem . getUserPreference ( "ReportAnalytics" ) }
166- onClick = { checked => writePreference ( "ReportAnalytics" , checked ) }
167188 tooltip = "Record user data such as what robots are spawned and how they are configured. No personal data will be collected."
168189 />
169- < Checkbox
190+ < GeneralTabCheckbox
191+ preference = "SubsystemGravity"
170192 label = "Realistic Subsystem Gravity"
171- checked = { PreferencesSystem . getUserPreference ( "SubsystemGravity" ) }
172- onClick = { checked => writePreference ( "SubsystemGravity" , checked ) }
173193 tooltip = "Allows you to set a target torque or force for subsystems and joints. If not properly configured, joints may not be able to resist gravity or may not behave as intended."
174194 />
175- < Checkbox
195+ < GeneralTabCheckbox
196+ preference = "RenderScoringZones"
176197 label = "Show Score Zones"
177- checked = { PreferencesSystem . getUserPreference ( "RenderScoringZones" ) }
178- onClick = { checked => writePreference ( "RenderScoringZones" , checked ) }
179198 tooltip = "If disabled, scoring zones will not be visible but will continue to function the same."
180199 />
181- < Checkbox
200+ < GeneralTabCheckbox
201+ preference = "RenderProtectedZones"
182202 label = "Show Protected Zones"
183- checked = { PreferencesSystem . getUserPreference ( "RenderProtectedZones" ) }
184- onClick = { checked => writePreference ( "RenderProtectedZones" , checked ) }
185203 tooltip = "If disabled, protected zones will not be visible but will continue to function the same."
186204 />
187- < Checkbox
205+ < GeneralTabCheckbox
206+ preference = "RenderSceneTags"
188207 label = "Show Scene Tags"
189- checked = { PreferencesSystem . getUserPreference ( "RenderSceneTags" ) }
190- onClick = { checked => writePreference ( "RenderSceneTags" , checked ) }
191208 tooltip = "Name tags above robot."
192209 />
193- < Checkbox
210+ < GeneralTabCheckbox
211+ preference = "RenderScoreboard"
194212 label = "Show Scoreboard"
195- checked = { PreferencesSystem . getUserPreference ( "RenderScoreboard" ) }
196- onClick = { checked => {
197- writePreference ( "RenderScoreboard" , checked )
198- if ( checked ) {
199- // TODO: figure out scoreboard - I think it should be its own component and not a panel
200- // openPanel("scoreboard");
201- }
202- } }
203213 />
204- < Checkbox
214+ < GeneralTabCheckbox
215+ preference = "ShowCenterOfMassIndicators"
205216 label = "Show Centers of Mass"
206- checked = { PreferencesSystem . getUserPreference ( "ShowCenterOfMassIndicators" ) }
207- onClick = { checked => writePreference ( "ShowCenterOfMassIndicators" , checked ) }
208217 tooltip = "Show a purple dot to indicate the center of mass of each robot in frame"
209218 />
210- < Checkbox
219+ < GeneralTabCheckbox
220+ preference = "MuteAllSound"
211221 label = "Mute All Sound"
212- checked = { PreferencesSystem . getUserPreference ( "MuteAllSound" ) }
213- onClick = { checked => writePreference ( "MuteAllSound" , checked ) }
214222 />
215- < StatefulSlider
223+ < GeneralTabSlider
224+ preference = "SFXVolume"
225+ label = "SFX Volume"
226+ tooltip = "Volume of sound effects (%)."
216227 min = { 0 }
217228 max = { 100 }
218- defaultValue = { PreferencesSystem . getUserPreference ( "SFXVolume" ) }
219- label = { "SFX Volume" }
220- onChange = { value => writePreference ( "SFXVolume" , value ) }
221- tooltip = "Volume of sound effects (%)."
222229 />
223230 </ Stack >
224231 </ Stack >
@@ -539,7 +546,6 @@ interface SettingsModalCustomProps {
539546
540547const SettingsModal : React . FC < ModalImplProps < void , SettingsModalCustomProps | undefined > > = ( { modal } ) => {
541548 const { configureScreen } = useUIContext ( )
542- const [ _ , refresh ] = useReducer ( x => ! x , false )
543549 const [ activeTab , setActiveTab ] = useState < string > ( modal ?. props . custom ?. initialTab || "general" )
544550
545551 const [ graphicsActions , setGraphicsActions ] = useState < GraphicsTabActions | null > ( null )
@@ -552,11 +558,6 @@ const SettingsModal: React.FC<ModalImplProps<void, SettingsModalCustomProps | un
552558 { key : "theme" , label : "Theme Editor" , component : ThemeEditorTab } ,
553559 ]
554560
555- const writePreference = < K extends UserPreference > ( pref : K , value : UserPreferences [ K ] ) => {
556- PreferencesSystem . setUserPreference ( pref , value )
557- refresh ( )
558- }
559-
560561 const save = useCallback ( ( ) => {
561562 if ( graphicsActions ) {
562563 graphicsActions . save ( )
@@ -595,7 +596,7 @@ const SettingsModal: React.FC<ModalImplProps<void, SettingsModalCustomProps | un
595596 switch ( currentTab . key ) {
596597 case "general" : {
597598 const GeneralComponent = currentTab . component
598- return < GeneralComponent writePreference = { writePreference } />
599+ return < GeneralComponent />
599600 }
600601 case "graphics" : {
601602 const GraphicsComponent = currentTab . component
0 commit comments