Canvas: Let user define name/value env var
Change-Id: I9beffcc6f0dcbb674ef82b37b93b5f5ef7d189bc
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index 55dced4..355e41b 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -558,6 +558,56 @@
function EnvVars({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
const { id, data } = node;
const store = useStateStore();
+ const [name, setName] = useState("");
+ const [value, setValue] = useState("");
+
+ const addEnvVar = useCallback(() => {
+ if (!name.trim() || !value.trim()) return;
+ store.updateNodeData<"app">(id, {
+ envVars: (data.envVars || []).concat({
+ id: uuidv4(),
+ source: null,
+ name: name.toUpperCase(),
+ value: value,
+ }),
+ });
+ setName("");
+ setValue("");
+ }, [id, data, store, name, value]);
+
+ const removeEnvVar = useCallback(
+ (varId: string) => {
+ store.updateNodeData<"app">(id, {
+ envVars: (data.envVars || []).filter((v) => v.id !== varId),
+ });
+ },
+ [id, data, store],
+ );
+
+ const editValueEnvVar = useCallback(
+ (varId: string) => {
+ if (disabled) return;
+ store.updateNodeData<"app">(id, {
+ envVars: (data.envVars || []).map((v) => (v.id === varId ? { ...v, isEditting: true } : v)),
+ });
+ },
+ [id, data, store, disabled],
+ );
+
+ const saveValueEnvVar = useCallback(
+ (varId: string, newName: string, newValue: string) => {
+ store.updateNodeData<"app">(id, {
+ envVars: (data.envVars || []).map((v) => {
+ if (v.id === varId) {
+ return { ...v, name: newName.toUpperCase(), value: newValue, isEditting: false };
+ }
+ return v;
+ }),
+ });
+ },
+ [id, data, store],
+ );
+
const editAlias = useCallback(
(e: BoundEnvVar) => {
return () => {
@@ -580,6 +630,7 @@
},
[id, data, store, disabled],
);
+
const saveAlias = useCallback(
(e: BoundEnvVar, value: string, store: AppState) => {
store.updateNodeData(id, {
@@ -589,11 +640,19 @@
return o;
}
if (value) {
- return {
- ...o,
- isEditting: false,
- alias: value.toUpperCase(),
- };
+ if ("name" in o && value.toUpperCase() === o.name.toUpperCase()) {
+ return {
+ ...o,
+ isEditting: false,
+ alias: undefined,
+ };
+ } else {
+ return {
+ ...o,
+ isEditting: false,
+ alias: value.toUpperCase(),
+ };
+ }
}
if ("alias" in o) {
const { alias: _, ...rest } = o;
@@ -611,17 +670,23 @@
},
[id, data],
);
+
const saveAliasOnEnter = useCallback(
(e: BoundEnvVar) => {
return (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
- event.preventDefault();
saveAlias(e, event.currentTarget.value, store);
+ } else if (event.key === "Escape") {
+ store.updateNodeData(id, {
+ ...data,
+ envVars: data.envVars!.map((o) => (o.id === e.id ? { ...o, isEditting: false } : o)),
+ });
}
};
},
- [store, saveAlias],
+ [store, saveAlias, id, data],
);
+
const saveAliasOnBlur = useCallback(
(e: BoundEnvVar) => {
return (event: FocusEvent<HTMLInputElement>) => {
@@ -630,48 +695,153 @@
},
[store, saveAlias],
);
+
return (
- <ul>
- {data &&
- data.envVars &&
- data.envVars.map((v) => {
+ <div className="flex flex-col gap-1">
+ <div className="grid grid-cols-[auto_1fr_1fr_auto] gap-1">
+ {data?.envVars?.map((v) => {
+ if ("value" in v) {
+ if (v.isEditting) {
+ return (
+ <div key={v.id} className="contents">
+ <Input
+ className="uppercase col-start-2"
+ defaultValue={v.name}
+ onKeyUp={(e) => {
+ if (e.key === "Enter") {
+ const nameInput = e.currentTarget;
+ const valueInput = nameInput.parentElement?.querySelector(
+ 'input[placeholder="Value"]',
+ ) as HTMLInputElement;
+ if (valueInput) {
+ saveValueEnvVar(v.id, nameInput.value, valueInput.value);
+ }
+ } else if (e.key === "Escape") {
+ store.updateNodeData(id, {
+ ...data,
+ envVars: data.envVars!.map((o) =>
+ o.id === v.id ? { ...o, isEditting: false } : o,
+ ),
+ });
+ }
+ }}
+ autoFocus
+ disabled={disabled}
+ />
+ <Input
+ placeholder="Value"
+ defaultValue={v.value}
+ onKeyUp={(e) => {
+ if (e.key === "Enter") {
+ const valueInput = e.currentTarget;
+ const nameInput = valueInput.parentElement?.querySelector(
+ 'input:not([placeholder="Value"])',
+ ) as HTMLInputElement;
+ if (nameInput) {
+ saveValueEnvVar(v.id, nameInput.value, valueInput.value);
+ }
+ } else if (e.key === "Escape") {
+ store.updateNodeData(id, {
+ ...data,
+ envVars: data.envVars!.map((o) =>
+ o.id === v.id ? { ...o, isEditting: false } : o,
+ ),
+ });
+ }
+ }}
+ disabled={disabled}
+ />
+ <Button
+ variant="destructive"
+ size="sm"
+ onClick={() => removeEnvVar(v.id)}
+ disabled={disabled}
+ >
+ Remove
+ </Button>
+ </div>
+ );
+ }
+ return (
+ <div
+ key={v.id}
+ className={`contents ${disabled ? "" : "cursor-text"}`}
+ onClick={() => editValueEnvVar(v.id)}
+ >
+ <div>{!disabled && <Pencil className="w-4 h-4" />}</div>
+ <div className={`${disabled ? "col-span-2" : ""} col-start-2`}>{v.name}</div>
+ <div>{v.value}</div>
+ <Button
+ variant="destructive"
+ size="sm"
+ onClick={(e) => {
+ e.stopPropagation();
+ removeEnvVar(v.id);
+ }}
+ disabled={disabled}
+ >
+ Remove
+ </Button>
+ </div>
+ );
+ }
if ("name" in v) {
const value = "alias" in v ? v.alias : v.name;
if (v.isEditting) {
return (
- <li key={v.id}>
- <Input
- type="text"
- className="uppercase"
- defaultValue={value}
- onKeyUp={saveAliasOnEnter(v)}
- onBlur={saveAliasOnBlur(v)}
- autoFocus={true}
- disabled={disabled}
- />
- </li>
+ <Input
+ type="text"
+ className="uppercase col-start-2 col-span-3"
+ defaultValue={value}
+ onKeyUp={saveAliasOnEnter(v)}
+ onBlur={saveAliasOnBlur(v)}
+ autoFocus={true}
+ disabled={disabled}
+ />
);
}
return (
- <li key={v.id} onClick={editAlias(v)}>
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger className="w-full">
- <div
- className={`w-full flex flex-row items-center gap-1 ${disabled ? "" : "cursor-text"}`}
- >
- {!disabled && <Pencil className="w-4 h-4" />}
- <div className="uppercase">{value}</div>
- </div>
- </TooltipTrigger>
- <TooltipContent>{v.name}</TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </li>
+ <div
+ key={v.id}
+ onClick={editAlias(v)}
+ className={`contents ${disabled ? "" : "cursor-text"}`}
+ >
+ {!disabled && <Pencil className="w-4 h-4" />}
+ <div className="col-start-2 col-span-3">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger className="uppercase">{value}</TooltipTrigger>
+ <TooltipContent>{v.name}</TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ </div>
+ </div>
);
}
+ return null;
})}
- </ul>
+ {!disabled && (
+ <div className="contents">
+ <Input
+ placeholder="Name"
+ className="uppercase col-start-2"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ disabled={disabled}
+ />
+ <Input
+ placeholder="Value"
+ value={value}
+ onChange={(e) => setValue(e.target.value)}
+ disabled={disabled}
+ />
+ <Button onClick={addEnvVar} disabled={disabled || !name.trim() || !value.trim()}>
+ Add
+ </Button>
+ </div>
+ )}
+ </div>
+ </div>
);
}