add native input selection in settings

This commit is contained in:
Andrew Pareles 2024-12-05 01:17:05 -08:00
parent 18e3f19512
commit a647530d77
5 changed files with 167 additions and 82 deletions

View file

@ -1,59 +0,0 @@
import React, { useEffect, useRef } from 'react';
import { useService } from '../util/services.js';
import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultInputBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
export const InputBox = ({ onChangeText, placeholder, historyInputBoxRef, }: {
onChangeText: (value: string) => void;
placeholder: string;
historyInputBoxRef: React.MutableRefObject<HistoryInputBox | null>; // update this whenever historyInputBoxRef.current changes
}) => {
const contextViewProvider = useService('contextViewService');
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
// create and mount the HistoryInputBox
historyInputBoxRef.current = new HistoryInputBox(
containerRef.current,
contextViewProvider,
{
inputBoxStyles: {
...defaultInputBoxStyles,
inputBackground: 'transparent',
},
placeholder,
history: [],
flexibleHeight: true,
flexibleMaxHeight: 500,
flexibleWidth: false,
}
);
historyInputBoxRef.current.onDidChange((newStr) => {
onChangeText(newStr)
})
// historyInputBoxRef.current.onDidHeightChange((newHeight) => {
// console.log('CHANGE height', newHeight);
// })
// cleanup
return () => {
if (historyInputBoxRef.current) {
historyInputBoxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
historyInputBoxRef.current = null;
}
};
}, [onChangeText, placeholder, contextViewProvider]); // Empty dependency array since we only want to mount/unmount once
return <div ref={containerRef} className="w-full" />;
};

View file

@ -19,7 +19,7 @@ import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { ErrorDisplay } from './ErrorDisplay.js';
import { LLMMessageServiceParams } from '../../../../../../../platform/void/common/llmMessageTypes.js';
import { getCmdKey } from '../../../getCmdKey.js'
import { InputBox } from './InputBox.js';
import { InputBox } from './inputs.js';
import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
// read files from VSCode
@ -298,7 +298,9 @@ export const SidebarChat = () => {
<InputBox
placeholder={`${getCmdKey()}+L to select`}
onChangeText={onChangeText}
historyInputBoxRef={inputBoxRef}
inputBoxRef={inputBoxRef}
multiline={true}
initVal=''
/>
{/* <textarea

View file

@ -2,9 +2,12 @@
* Copyright (c) Glass Devtools, Inc. All rights reserved.
* Void Editor additions licensed under the AGPLv3 License.
*--------------------------------------------------------------------------------------------*/
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useConfigState, useService } from '../util/services.js';
import { IVoidConfigStateService, nonDefaultConfigFields, PartialVoidConfig, VoidConfig, VoidConfigField, VoidConfigInfo, SetFieldFnType, ConfigState } from '../../../registerConfig.js';
import { EnumInputBox, InputBox } from './inputs.js';
import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { SelectBox } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
const SettingOfFieldAndParam = ({ field, param, configState, configStateService }:
@ -16,13 +19,30 @@ const SettingOfFieldAndParam = ({ field, param, configState, configStateService
const { enumArr, defaultVal, description } = configStateService.voidConfigInfo[field][param]
const val = partialVoidConfig[field]?.[param] ?? defaultVal // current value of this item
const updateState = (newValue: string) => { configStateService.setField(field, param, newValue) }
const updateState = useCallback((newValue: string) => {
configStateService.setField(field, param, newValue)
}, [configStateService, field, param])
const inputBoxRef = useRef<HistoryInputBox | null>(null);
const selectBoxRef = useRef<SelectBox | null>(null);
const forceState = useCallback((newValue: string) => {
if (inputBoxRef.current) {
// inputBoxRef.current.addToHistory();
inputBoxRef.current.value = newValue;
}
if (selectBoxRef.current) {
selectBoxRef.current.select(enumArr?.indexOf(newValue) ?? 0);
}
updateState(newValue);
}, [enumArr, updateState])
const resetButton = <button
disabled={val === defaultVal}
title={val === defaultVal ? 'This is the default value.' : `Revert value to '${defaultVal}'?`}
className='group btn btn-sm disabled:opacity-75 disabled:cursor-default'
onClick={() => updateState(defaultVal)}
onClick={() => forceState(defaultVal)}
>
<svg
className='size-5 group-disabled:stroke-current group-disabled:fill-current group-hover:stroke-red-600 group-hover:fill-red-600 duration-200'
@ -30,27 +50,42 @@ const SettingOfFieldAndParam = ({ field, param, configState, configStateService
</svg>
</button>
const inputElement = enumArr === undefined ?
// string
(<input
className='input p-1 w-full'
type='text'
value={val}
onChange={(e) => updateState(e.target.value)}
(<InputBox
onChangeText={updateState}
initVal={val}
multiline={false}
placeholder=''
inputBoxRef={inputBoxRef}
/>)
// <input
// className='input p-1 w-full'
// type='text'
// value={val}
// onChange={(e) => updateState(e.target.value)}
// />
:
// enum
(<select
className='dropdown p-1 w-full'
value={val}
onChange={(e) => updateState(e.target.value)}
>
{enumArr.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>)
(<EnumInputBox
onChangeSelection={updateState}
initVal={val}
options={enumArr}
selectBoxRef={selectBoxRef}
/>)
// (<select
// className='dropdown p-1 w-full'
// value={val}
// onChange={(e) => updateState(e.target.value)}
// >
// {enumArr.map((option) => (
// <option key={option} value={option}>
// {option}
// </option>
// ))}
// </select>)
return <div>
<label className='hidden'>{param}</label>

View file

@ -0,0 +1,104 @@
import React, { useEffect, useRef } from 'react';
import { useService } from '../util/services.js';
import { HistoryInputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
import { defaultInputBoxStyles } from '../../../../../../../platform/theme/browser/defaultStyles.js';
import { SelectBox, unthemedSelectBoxStyles } from '../../../../../../../base/browser/ui/selectBox/selectBox.js';
export const InputBox = ({ onChangeText, initVal, placeholder, inputBoxRef, multiline }: {
onChangeText: (value: string) => void;
placeholder: string;
inputBoxRef: React.MutableRefObject<HistoryInputBox | null>;
multiline: boolean;
initVal: string;
}) => {
const contextViewProvider = useService('contextViewService');
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current) return;
// create and mount the HistoryInputBox
inputBoxRef.current = new HistoryInputBox(
containerRef.current,
contextViewProvider,
{
inputBoxStyles: {
...defaultInputBoxStyles,
inputBackground: 'transparent',
},
placeholder,
history: [initVal],
flexibleHeight: multiline,
flexibleMaxHeight: 500,
flexibleWidth: false,
}
);
inputBoxRef.current.onDidChange((newStr) => {
console.log('CHANGE TEXT on inputbox', newStr)
onChangeText(newStr)
})
// cleanup
return () => {
if (inputBoxRef.current) {
inputBoxRef.current.dispose();
if (containerRef.current) {
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
}
inputBoxRef.current = null;
}
};
}, [inputBoxRef, onChangeText, placeholder, contextViewProvider, initVal, multiline]); // Empty dependency array since we only want to mount/unmount once
return <div ref={containerRef} className="w-full" />;
};
export const EnumInputBox = ({ onChangeSelection, initVal, selectBoxRef, options }: {
onChangeSelection: (value: string) => void;
initVal: string;
selectBoxRef: React.MutableRefObject<SelectBox | null>;
options: readonly string[];
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const contextViewProvider = useService('contextViewService');
useEffect(() => {
if (!containerRef.current) return;
const defaultIndex = options.indexOf(initVal);
selectBoxRef.current = new SelectBox(
options.map(opt => ({ text: opt })),
defaultIndex,
contextViewProvider,
unthemedSelectBoxStyles
);
selectBoxRef.current.render(containerRef.current);
selectBoxRef.current.onDidSelect(e => { onChangeSelection(e.selected); });
// 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]);
return <div ref={containerRef} className="w-full" />;
};

View file

@ -69,8 +69,10 @@ export type ReactServicesType = {
modelService: IModelService;
inlineDiffService: IInlineDiffsService;
sendLLMMessageService: ISendLLMMessageService;
contextViewService: IContextViewService;
clipboardService: IClipboardService;
contextViewService: IContextViewService;
contextMenuService: IContextMenuService;
}
// ---------- Define viewpane ----------
@ -115,8 +117,9 @@ class VoidSidebarViewPane extends ViewPane {
modelService: accessor.get(IModelService),
inlineDiffService: accessor.get(IInlineDiffsService),
sendLLMMessageService: accessor.get(ISendLLMMessageService),
contextViewService: accessor.get(IContextViewService),
clipboardService: accessor.get(IClipboardService),
contextViewService: accessor.get(IContextViewService),
contextMenuService: accessor.get(IContextMenuService),
}
mountFn(parent, services);
});