import React, { useCallback } from 'react'; import type { UserConfigurablePreferences } from 'compass-preferences-model'; import { getSettingDescription, featureFlags } from 'compass-preferences-model'; import { settingStateLabels } from './state-labels'; import { Checkbox, Label, Description, css, spacing, TextInput, FormFieldContainer, Badge, } from '@mongodb-js/compass-components'; import { changeFieldValue } from '../../stores/settings'; import type { RootState } from '../../stores'; import { connect } from 'react-redux'; type KeysMatching = keyof { [P in keyof T as T[P] extends V ? P : never]: P; }; // Currently, boolean, numeric, and string options are supported in the UI. type BooleanPreferences = KeysMatching< UserConfigurablePreferences, boolean | undefined >; type NumericPreferences = KeysMatching< UserConfigurablePreferences, number | undefined >; type StringPreferences = KeysMatching< UserConfigurablePreferences, string | undefined >; type SupportedPreferences = | BooleanPreferences | NumericPreferences | StringPreferences; const inputStyles = css({ marginTop: spacing[3], marginBottom: spacing[3], }); const devBadgeStyles = css({ marginLeft: spacing[2], }); const fieldContainerStyles = css({ margin: `${spacing[3]}px 0`, fieldset: { paddingLeft: `${spacing[4]}px`, }, }); type HandleChange = < N extends PreferenceName >( field: N, value: UserConfigurablePreferences[N] ) => void; export type SettingsListProps = { fields: readonly PreferenceName[]; }; function SettingLabel({ name }: { name: SupportedPreferences }) { const { short, long } = getSettingDescription(name).description; return ( <> {long && {long}} ); } function BooleanSetting({ name, onChange, value, disabled, }: { name: PreferenceName; onChange: HandleChange; value: boolean; disabled: boolean; }) { const handleCheckboxChange = useCallback( (event: React.ChangeEvent) => { onChange(name, event.target.checked); }, [name, onChange] ); return ( } checked={value} disabled={disabled} /> ); } function NumericSetting({ name, onChange, value, disabled, required, }: { name: PreferenceName; onChange: HandleChange; value: number | undefined; disabled: boolean; required: boolean; }) { const onChangeEvent = useCallback( (event: React.ChangeEvent) => { const { value } = event.target; onChange(name, value === '' ? (required ? 0 : undefined) : +value); }, [name, onChange, required] ); return ( <> ); } function StringSetting({ name, onChange, value, disabled, required, }: { name: PreferenceName; onChange: HandleChange; value: string | undefined; disabled: boolean; required: boolean; }) { const onChangeEvent = useCallback( (event: React.ChangeEvent) => { const { value } = event.target; onChange( name, (value === '' ? required ? '' : undefined : value) as UserConfigurablePreferences[PreferenceName] ); }, [name, onChange, required] ); return ( <> ); } type AnySetting = { name: string; type: unknown; value?: unknown; onChange(field: string, value: unknown): void; }; type SettingsInputProps = AnySetting & { stateLabel?: React.ReactNode; disabled?: boolean; required?: boolean; }; function isSupported(props: AnySetting): props is | { name: StringPreferences; type: 'string'; value?: string; onChange: HandleChange; } | { name: NumericPreferences; type: 'number'; value?: number; onChange: HandleChange; } | { name: BooleanPreferences; type: 'boolean'; value?: boolean; onChange: HandleChange; } { return ['number', 'string', 'boolean'].includes(props.type as string); } function SettingsInput({ stateLabel = '', disabled = false, required = false, ...props }: SettingsInputProps): React.ReactElement { if (!isSupported(props)) { throw new Error( `Do not know how to render type ${props.type} for preference ${props.name}` ); } let input = null; const { name, type, onChange, value } = props; if (type === 'boolean') { input = ( ); } if (type === 'number') { input = ( ); } if (type === 'string') { input = ( ); } return (
{input} {stateLabel ?? ''}
); } const ConnectedSettingsInput = connect( (state: RootState, ownProps: { name: SupportedPreferences }) => { const { settings: { settings, preferenceStates }, } = state; const { name } = ownProps; const { type } = getSettingDescription(name); return { value: settings[name], type: type, disabled: !!preferenceStates[name], stateLabel: settingStateLabels[preferenceStates[name] ?? ''], }; }, { onChange: changeFieldValue } )(SettingsInput); export function SettingsList({ fields, }: SettingsListProps) { return ( <> {fields.map((name) => { return ( ); })} ); } export default React.memo(SettingsList);