add sync state for dropdown

This commit is contained in:
Andrew Pareles 2024-12-11 01:24:04 -08:00
parent cb2de5505b
commit a28194c601
3 changed files with 59 additions and 33 deletions

View file

@ -3,10 +3,12 @@
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import { useEffect, useRef } from 'react'
import { FeatureName, featureNames, ProviderName, providerNames } from '../../../../../../../platform/void/common/voidConfigTypes.js'
import { useConfigState, useService } from '../util/services.js'
import ErrorBoundary from './ErrorBoundary.js'
import { VoidSelectBox } from './inputs.js'
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js'
@ -30,6 +32,28 @@ export const SidebarModelSettingsForFeature = ({ featureName }: { featureName: F
models.push(['Provider', 'Model'])
}
const selectBoxRef = useRef<SelectBox | null>(null)
useEffect(() => {
// this is really just to sync the state on initial mount, when init value hasn't been set yet
let synced = false
const syncStateOnMount = () => {
if (!selectBoxRef.current) return
if (synced) return
synced = true
const settingsAtProvider = voidConfigService.state.modelSelectionOfFeature[featureName]
const index = models.findIndex(v => v[0] === settingsAtProvider?.providerName && v[1] === settingsAtProvider?.modelName)
if (index !== -1)
selectBoxRef.current.select(index)
}
syncStateOnMount()
synced = false // sync the next time state changes (but not after that - the "current.value = ..." triggers a state change, causing an infinite loop!)
const disposable = voidConfigService.onDidChangeState(syncStateOnMount)
return () => disposable.dispose()
}, [selectBoxRef, voidConfigService, models, featureName])
return <>
<h2>{featureName}</h2>
{
@ -37,7 +61,7 @@ export const SidebarModelSettingsForFeature = ({ featureName }: { featureName: F
initVal={models[0]}
options={wasEmpty ? [{ text: 'Please add a Provider!', value: models[0] }] : models.map(s => ({ text: s.join(' - '), value: s }))}
onChangeSelection={(newVal) => { voidConfigService.setModelSelectionOfFeature(featureName, { providerName: newVal[0] as ProviderName, modelName: newVal[1] }) }}
selectBoxRef={{ current: null }}
selectBoxRef={selectBoxRef}
/>}
{/* <h1>Settings - {featureName}</h1> */}

View file

@ -18,6 +18,7 @@ const Setting = ({ providerName, settingName }: { providerName: ProviderName, se
const instanceRef = useRef<InputBox | null>(null)
// set init val to the current state
useEffect(() => {
// this is really just to sync the state on initial mount, when init value hasn't been set yet
let synced = false
@ -32,14 +33,14 @@ const Setting = ({ providerName, settingName }: { providerName: ProviderName, se
const stateVal = settingsAtProvider[settingName]
if (instanceRef.current.value !== stateVal) {
instanceRef.current.value = stateVal // triggers onDidChangeState
instanceRef.current.value = stateVal // triggers onChangeText
}
}
syncStateOnMount()
synced = false // sync the next time state changes (but not after that - the "current.value = ..." triggers a state change, causing an infinite loop!)
const disposable = voidConfigService.onDidChangeState(syncStateOnMount)
return () => disposable.dispose()
}, [instanceRef, voidConfigService])
}, [instanceRef, voidConfigService, providerName, settingName])
return <><ErrorBoundary>
<h2>{title}</h2>

View file

@ -50,7 +50,6 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, placeholder, mult
const contextViewProvider = useService('contextViewService');
return <WidgetComponent
ctor={InputBox}
propsFn={useCallback((container) => [
@ -91,52 +90,54 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, placeholder, mult
export const VoidSelectBox = <T,>({ onChangeSelection, initVal, selectBoxRef, options }: {
initVal: T;
selectBoxRef: React.MutableRefObject<SelectBox | null>;
options: readonly { text: string, value: T }[];
onChangeSelection: (value: T) => void;
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const contextViewProvider = useService('contextViewService');
useEffect(() => {
if (!containerRef.current) return;
let containerRef = useRef<HTMLDivElement | null>(null);
const defaultIndex = options.findIndex(opt => opt.value === initVal);
return <WidgetComponent
ctor={SelectBox}
propsFn={useCallback((container) => {
containerRef.current = container
const defaultIndex = options.findIndex(opt => opt.value === initVal);
return [
options.map(opt => ({ text: opt.text })),
defaultIndex,
contextViewProvider,
unthemedSelectBoxStyles
] as const;
}, [containerRef, options, initVal, contextViewProvider])}
selectBoxRef.current = new SelectBox(
options.map(opt => ({ text: opt.text })),
defaultIndex,
contextViewProvider,
unthemedSelectBoxStyles
);
dispose={useCallback((instance: SelectBox) => {
instance.dispose();
for (let child of containerRef.current?.childNodes ?? [])
containerRef.current?.removeChild(child)
}, [containerRef])}
selectBoxRef.current.render(containerRef.current);
onCreateInstance={useCallback((instance: SelectBox) => {
selectBoxRef.current = instance;
if (containerRef.current) instance.render(containerRef.current)
const disposables = [
instance.onDidSelect(e => {
console.log('e.selected', JSON.stringify(e));
onChangeSelection(options[e.index].value);
})
];
return disposables;
}, [containerRef, selectBoxRef, options, onChangeSelection])}
selectBoxRef.current.onDidSelect(e => { console.log('e.selected', JSON.stringify(e)); onChangeSelection(options[e.index].value); });
// cleanup
return () => {
if (selectBoxRef.current) {
selectBoxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
}
};
}, [options, initVal, onChangeSelection, contextViewProvider, selectBoxRef]);
return <div ref={containerRef} className="w-full" />;
/>;
};
// export const VoidCheckBox = ({ onChangeChecked, initVal, label, checkboxRef, }: {
// onChangeChecked: (checked: boolean) => void;
// initVal: boolean;