Skip to content

Commit

Permalink
Restructure id input to avoid getting stuck with previous node's ID v…
Browse files Browse the repository at this point in the history
…alue and causing confusion for values in settings pane
  • Loading branch information
nstrayer committed Nov 6, 2023
1 parent d2b28a7 commit 310b145
Showing 1 changed file with 33 additions and 26 deletions.
59 changes: 33 additions & 26 deletions inst/editor/src/SettingsPanel/SettingsInput/IdInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ export function IdInput({
// since this is a leaf node
const appInfo = useCurrentAppInfo();
const updateServerCode = useUpdateServerCode();

const serverNodes = useCurrentServerNodes();

const [syncStatus, setSyncStatus] = React.useState<
"synced" | "unsynced" | null
>(null);
const [currValue, setCurrValue] = React.useState(value);
const [invalidMsg, setInvalidMsg] = React.useState<null | string>(null);

// TempValue is used to store the value of the input when it is invalid. This
// is used to prevent the input from getting stuck in an invalid state if the
// user deletes all the way to an empty string etc...
const [tempValue, setTempValue] = React.useState<string | null>(null);

// Check if the current value exists in the server locations
const locationsOfId = getLocationsInServerOfId(value, serverNodes);

Expand All @@ -61,27 +64,30 @@ export function IdInput({
const bindingIds = getAllInputOutputIdsInApp(ui_tree);

const updateValue = (newValue: string) => {
// Check if the requested new value is already in use and set invalid if it is
const takenId = bindingIds.includes(newValue) && newValue !== value;

// Replace spaces with underscores
newValue = newValue.replace(/ /g, "_");

setCurrValue(newValue);
// Check if the requested new value is already in use and set invalid if it is
const isTakenId = bindingIds.includes(newValue) && newValue !== value;
const isEmptyId = newValue === "";

if (takenId) {
if (isTakenId) {
setInvalidMsg(`The id ${newValue} is already taken`);
return;
}

// If the id is empty, don't send that as that's not a valid ID
if (newValue === "") {
if (isEmptyId) {
setInvalidMsg("ID cannot be empty");
}

if (isTakenId || isEmptyId) {
setTempValue(newValue);
return;
}

onChange(newValue);
setInvalidMsg(null);
setTempValue(null);

if (syncStatus === "synced" && locationsOfId !== null) {
updateServerCode((oldScript) => {
Expand All @@ -98,27 +104,28 @@ export function IdInput({
}
};

const isInvalid = invalidMsg !== null;

const common_props = {
className: "SUE-Input",
"aria-label": label,
"aria-labelledby": makeLabelId(id),
"aria-invalid": isInvalid,
autoComplete: "off",
id,
value: currValue,
onChange: (
e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => {
updateValue(e.target.value);
},
};
const isInvalid = typeof tempValue === "string";

return (
<>
<div className="flex items-center gap-1">
<input {...common_props} type="text" />
<input
className="SUE-Input"
aria-label={label}
aria-labelledby={makeLabelId(id)}
aria-invalid={isInvalid}
id={id}
// If the tempValue is not null we must be in an invalid state and
// should show that value instead of the real real one so users can
// fix it. This prevents the input from never letting you delete all
// the way to an empty field getting stuck writing out an id that
// contains another id as a prefix
value={isInvalid ? tempValue : value}
onChange={(e) => {
updateValue(e.target.value);
}}
type="text"
/>
{boundToServer && (
<Tooltip placement="right-start">
<TooltipTrigger asChild>
Expand Down

0 comments on commit 310b145

Please sign in to comment.