Extract AccessPathSuggestBox component

This extracts all common functionality from the various model editor
suggest box components into a single component that can be shared by
the model editor suggest box components to make improvements easier.
This commit is contained in:
Koen Vlaswinkel
2024-02-07 14:00:08 +01:00
parent 57faebd5b9
commit 688aa304f5
4 changed files with 102 additions and 123 deletions

View File

@@ -0,0 +1,70 @@
import { useEffect, useState } from "react";
import type { AccessPathOption } from "../../model-editor/suggestions";
import { SuggestBox } from "../common/SuggestBox";
import { useDebounceCallback } from "../common/useDebounceCallback";
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
import {
parseAccessPathTokens,
validateAccessPath,
} from "../../model-editor/shared/access-paths";
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
type Props = {
value: string | undefined;
onChange: (value: string) => void;
suggestions: AccessPathOption[];
disabled?: boolean;
"aria-label": string;
};
const parseValueToTokens = (value: string) =>
parseAccessPathTokens(value).map((t) => t.text);
const getIcon = (option: AccessPathOption) => (
<ModelSuggestionIcon name={option.icon} />
);
const getDetails = (option: AccessPathOption) => option.details;
export const AccessPathSuggestBox = ({
value: givenValue,
suggestions,
onChange,
disabled,
"aria-label": ariaLabel,
}: Props) => {
const [value, setValue] = useState<string | undefined>(givenValue);
useEffect(() => {
setValue(givenValue);
}, [givenValue]);
// 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) => {
if (newValue === undefined) {
return;
}
onChange(newValue);
},
500,
);
return (
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
value={value}
onChange={setValue}
options={suggestions}
parseValueToTokens={parseValueToTokens}
validateValue={validateAccessPath}
getIcon={getIcon}
getDetails={getDetails}
disabled={disabled}
aria-label={ariaLabel}
/>
);
};

View File

@@ -1,19 +1,12 @@
import { useEffect, useMemo, useState } from "react";
import { useCallback, useMemo } from "react";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import {
calculateNewProvenance,
modeledMethodSupportsInput,
} from "../../model-editor/modeled-method";
import type { AccessPathOption } from "../../model-editor/suggestions";
import { SuggestBox } from "../common/SuggestBox";
import { useDebounceCallback } from "../common/useDebounceCallback";
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
import {
parseAccessPathTokens,
validateAccessPath,
} from "../../model-editor/shared/access-paths";
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
import { ModelTypePathSuggestBox } from "./ModelTypePathSuggestBox";
import { AccessPathSuggestBox } from "./AccessPathSuggestBox";
type Props = {
modeledMethod: ModeledMethod | undefined;
@@ -22,37 +15,13 @@ type Props = {
onChange: (modeledMethod: ModeledMethod) => void;
};
const parseValueToTokens = (value: string) =>
parseAccessPathTokens(value).map((t) => t.text);
const getIcon = (option: AccessPathOption) => (
<ModelSuggestionIcon name={option.icon} />
);
const getDetails = (option: AccessPathOption) => option.details;
export const ModelInputSuggestBox = ({
modeledMethod,
suggestions,
typePathSuggestions,
onChange,
}: Props) => {
const [value, setValue] = useState<string | undefined>(
modeledMethod && modeledMethodSupportsInput(modeledMethod)
? modeledMethod.input
: undefined,
);
useEffect(() => {
if (modeledMethod && modeledMethodSupportsInput(modeledMethod)) {
setValue(modeledMethod.input);
}
}, [modeledMethod]);
// Debounce the callback to avoid updating the model too often.
// Not doing this results in a lot of lag when typing.
useDebounceCallback(
value,
const handleChange = useCallback(
(input: string | undefined) => {
if (
!modeledMethod ||
@@ -68,7 +37,7 @@ export const ModelInputSuggestBox = ({
input,
});
},
500,
[onChange, modeledMethod],
);
const enabled = useMemo(
@@ -87,14 +56,14 @@ export const ModelInputSuggestBox = ({
}
return (
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
value={value}
onChange={setValue}
options={suggestions}
parseValueToTokens={parseValueToTokens}
validateValue={validateAccessPath}
getIcon={getIcon}
getDetails={getDetails}
<AccessPathSuggestBox
value={
modeledMethod && modeledMethodSupportsInput(modeledMethod)
? modeledMethod.input
: undefined
}
onChange={handleChange}
suggestions={suggestions}
disabled={!enabled}
aria-label="Input"
/>

View File

@@ -1,19 +1,12 @@
import { useEffect, useMemo, useState } from "react";
import { useCallback, useMemo } from "react";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import {
calculateNewProvenance,
modeledMethodSupportsOutput,
} from "../../model-editor/modeled-method";
import type { AccessPathOption } from "../../model-editor/suggestions";
import { SuggestBox } from "../common/SuggestBox";
import { useDebounceCallback } from "../common/useDebounceCallback";
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
import {
parseAccessPathTokens,
validateAccessPath,
} from "../../model-editor/shared/access-paths";
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
import { ModelTypeTextbox } from "./ModelTypeTextbox";
import { AccessPathSuggestBox } from "./AccessPathSuggestBox";
type Props = {
modeledMethod: ModeledMethod | undefined;
@@ -21,36 +14,12 @@ type Props = {
onChange: (modeledMethod: ModeledMethod) => void;
};
const parseValueToTokens = (value: string) =>
parseAccessPathTokens(value).map((t) => t.text);
const getIcon = (option: AccessPathOption) => (
<ModelSuggestionIcon name={option.icon} />
);
const getDetails = (option: AccessPathOption) => option.details;
export const ModelOutputSuggestBox = ({
modeledMethod,
suggestions,
onChange,
}: Props) => {
const [value, setValue] = useState<string | undefined>(
modeledMethod && modeledMethodSupportsOutput(modeledMethod)
? modeledMethod.output
: undefined,
);
useEffect(() => {
if (modeledMethod && modeledMethodSupportsOutput(modeledMethod)) {
setValue(modeledMethod.output);
}
}, [modeledMethod]);
// Debounce the callback to avoid updating the model too often.
// Not doing this results in a lot of lag when typing.
useDebounceCallback(
value,
const handleChange = useCallback(
(output: string | undefined) => {
if (
!modeledMethod ||
@@ -66,7 +35,7 @@ export const ModelOutputSuggestBox = ({
output,
});
},
500,
[modeledMethod, onChange],
);
const enabled = useMemo(
@@ -86,15 +55,15 @@ export const ModelOutputSuggestBox = ({
}
return (
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
value={value}
options={suggestions}
<AccessPathSuggestBox
value={
modeledMethod && modeledMethodSupportsOutput(modeledMethod)
? modeledMethod.output
: undefined
}
suggestions={suggestions}
disabled={!enabled}
onChange={setValue}
parseValueToTokens={parseValueToTokens}
validateValue={validateAccessPath}
getIcon={getIcon}
getDetails={getDetails}
onChange={handleChange}
aria-label="Output"
/>
);

View File

@@ -1,14 +1,7 @@
import { useEffect, useState } from "react";
import { useCallback } from "react";
import type { TypeModeledMethod } from "../../model-editor/modeled-method";
import type { AccessPathOption } from "../../model-editor/suggestions";
import { SuggestBox } from "../common/SuggestBox";
import { useDebounceCallback } from "../common/useDebounceCallback";
import type { AccessPathDiagnostic } from "../../model-editor/shared/access-paths";
import {
parseAccessPathTokens,
validateAccessPath,
} from "../../model-editor/shared/access-paths";
import { ModelSuggestionIcon } from "./ModelSuggestionIcon";
import { AccessPathSuggestBox } from "./AccessPathSuggestBox";
type Props = {
modeledMethod: TypeModeledMethod;
@@ -16,30 +9,12 @@ type Props = {
onChange: (modeledMethod: TypeModeledMethod) => void;
};
const parseValueToTokens = (value: string) =>
parseAccessPathTokens(value).map((t) => t.text);
const getIcon = (option: AccessPathOption) => (
<ModelSuggestionIcon name={option.icon} />
);
const getDetails = (option: AccessPathOption) => option.details;
export const ModelTypePathSuggestBox = ({
modeledMethod,
suggestions,
onChange,
}: Props) => {
const [value, setValue] = useState<string | undefined>(modeledMethod.path);
useEffect(() => {
setValue(modeledMethod.path);
}, [modeledMethod]);
// Debounce the callback to avoid updating the model too often.
// Not doing this results in a lot of lag when typing.
useDebounceCallback(
value,
const handleChange = useCallback(
(path: string | undefined) => {
if (path === undefined) {
return;
@@ -50,18 +25,14 @@ export const ModelTypePathSuggestBox = ({
path,
});
},
500,
[modeledMethod, onChange],
);
return (
<SuggestBox<AccessPathOption, AccessPathDiagnostic>
value={value}
options={suggestions}
onChange={setValue}
parseValueToTokens={parseValueToTokens}
validateValue={validateAccessPath}
getIcon={getIcon}
getDetails={getDetails}
<AccessPathSuggestBox
value={modeledMethod.path}
suggestions={suggestions}
onChange={handleChange}
aria-label="Path"
/>
);