CodeQL model editor: Add support for free text input in type models (#3217)
This commit is contained in:
27
extensions/ql-vscode/src/view/common/useDebounceCallback.ts
Normal file
27
extensions/ql-vscode/src/view/common/useDebounceCallback.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* Call the callback after the value has not changed for a certain amount of time.
|
||||
* @param value
|
||||
* @param callback
|
||||
* @param delay
|
||||
*/
|
||||
export function useDebounceCallback<T>(
|
||||
value: T,
|
||||
callback: (value: T) => void,
|
||||
delay?: number,
|
||||
) {
|
||||
const callbackRef = useRef<(value: T) => void>(callback);
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => callbackRef.current(value), delay || 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [value, delay]);
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
modeledMethodSupportsInput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import type { Method } from "../../model-editor/method";
|
||||
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
||||
import type { QueryLanguage } from "../../common/query-language";
|
||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
||||
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
|
||||
import { InputDropdown } from "./InputDropdown";
|
||||
import { ModelTypeTextbox } from "./ModelTypeTextbox";
|
||||
|
||||
type Props = {
|
||||
language: QueryLanguage;
|
||||
@@ -67,7 +67,14 @@ export const ModelInputDropdown = ({
|
||||
: undefined;
|
||||
|
||||
if (modeledMethod?.type === "type") {
|
||||
return <ReadonlyDropdown value={modeledMethod.path} aria-label="Path" />;
|
||||
return (
|
||||
<ModelTypeTextbox
|
||||
modeledMethod={modeledMethod}
|
||||
typeInfo="path"
|
||||
onChange={onChange}
|
||||
aria-label="Path"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const modelAccepted = isModelAccepted(modeledMethod, modelingStatus);
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
modeledMethodSupportsOutput,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import type { Method } from "../../model-editor/method";
|
||||
import { ReadonlyDropdown } from "../common/ReadonlyDropdown";
|
||||
import { getModelsAsDataLanguage } from "../../model-editor/languages";
|
||||
import type { QueryLanguage } from "../../common/query-language";
|
||||
import type { ModelingStatus } from "../../model-editor/shared/modeling-status";
|
||||
import { InputDropdown } from "./InputDropdown";
|
||||
import { ModelTypeTextbox } from "./ModelTypeTextbox";
|
||||
|
||||
type Props = {
|
||||
language: QueryLanguage;
|
||||
@@ -69,8 +69,10 @@ export const ModelOutputDropdown = ({
|
||||
|
||||
if (modeledMethod?.type === "type") {
|
||||
return (
|
||||
<ReadonlyDropdown
|
||||
value={modeledMethod.relatedTypeName}
|
||||
<ModelTypeTextbox
|
||||
modeledMethod={modeledMethod}
|
||||
typeInfo="relatedTypeName"
|
||||
onChange={onChange}
|
||||
aria-label="Related type name"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import type {
|
||||
ModeledMethod,
|
||||
TypeModeledMethod,
|
||||
} from "../../model-editor/modeled-method";
|
||||
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react";
|
||||
import { useDebounceCallback } from "../common/useDebounceCallback";
|
||||
|
||||
type Props = {
|
||||
modeledMethod: TypeModeledMethod;
|
||||
typeInfo: "path" | "relatedTypeName";
|
||||
onChange: (modeledMethod: ModeledMethod) => void;
|
||||
|
||||
"aria-label"?: string;
|
||||
};
|
||||
|
||||
export const ModelTypeTextbox = ({
|
||||
modeledMethod,
|
||||
typeInfo,
|
||||
onChange,
|
||||
...props
|
||||
}: Props): JSX.Element => {
|
||||
const [value, setValue] = useState<string | undefined>(
|
||||
modeledMethod[typeInfo],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(modeledMethod[typeInfo]);
|
||||
}, [modeledMethod, typeInfo]);
|
||||
|
||||
const handleChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
|
||||
setValue(target.value);
|
||||
}, []);
|
||||
|
||||
// Debounce the callback to avoid updating the model too often.
|
||||
// Not doing this results in a lot of lag when typing.
|
||||
useDebounceCallback(
|
||||
value,
|
||||
(newValue: string | undefined) => {
|
||||
onChange({
|
||||
...modeledMethod,
|
||||
[typeInfo]: newValue ?? "",
|
||||
});
|
||||
},
|
||||
500,
|
||||
);
|
||||
|
||||
return <VSCodeTextField value={value} onInput={handleChange} {...props} />;
|
||||
};
|
||||
Reference in New Issue
Block a user