Skip to content

Commit

Permalink
Use popover in add parameter experience (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
wintonzheng authored Dec 12, 2024
1 parent a574bda commit 3b8b963
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 345 deletions.
35 changes: 29 additions & 6 deletions skyvern-frontend/src/components/WorkflowBlockInput.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
import { PlusIcon } from "@radix-ui/react-icons";
import { cn } from "@/util/utils";
import { Input } from "./ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";

type Props = React.ComponentProps<typeof Input> & {
onIconClick: () => void;
type Props = Omit<React.ComponentProps<typeof Input>, "onChange"> & {
onChange: (value: string) => void;
nodeId: string;
};

function WorkflowBlockInput(props: Props) {
const { nodeId, onChange, ...inputProps } = props;

return (
<div className="relative">
<Input {...props} className={cn("pr-9", props.className)} />
<Input
{...inputProps}
className={cn("pr-9", props.className)}
onChange={(event) => {
onChange(event.target.value);
}}
/>
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
<div className="rounded p-1 hover:bg-muted" onClick={props.onIconClick}>
<PlusIcon className="size-4" />
</div>
<Popover>
<PopoverTrigger asChild>
<div className="rounded p-1 hover:bg-muted">
<PlusIcon className="size-4" />
</div>
</PopoverTrigger>
<PopoverContent>
<WorkflowBlockParameterSelect
nodeId={nodeId}
onAdd={(parameterKey) => {
onChange(`${props.value ?? ""}{{${parameterKey}}}`);
}}
/>
</PopoverContent>
</Popover>
</div>
</div>
);
Expand Down
35 changes: 29 additions & 6 deletions skyvern-frontend/src/components/WorkflowBlockInputTextarea.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
import { PlusIcon } from "@radix-ui/react-icons";
import { cn } from "@/util/utils";
import { AutoResizingTextarea } from "./AutoResizingTextarea/AutoResizingTextarea";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";

type Props = React.ComponentProps<typeof AutoResizingTextarea> & {
onIconClick: () => void;
type Props = Omit<
React.ComponentProps<typeof AutoResizingTextarea>,
"onChange"
> & {
onChange: (value: string) => void;
nodeId: string;
};

function WorkflowBlockInputTextarea(props: Props) {
const { nodeId, onChange, ...textAreaProps } = props;

return (
<div className="relative">
<AutoResizingTextarea
{...props}
{...textAreaProps}
onChange={(event) => {
onChange(event.target.value);
}}
className={cn("pr-9", props.className)}
/>
<div className="absolute right-0 top-0 flex size-9 cursor-pointer items-center justify-center">
<div className="rounded p-1 hover:bg-muted" onClick={props.onIconClick}>
<PlusIcon className="size-4" />
</div>
<Popover>
<PopoverTrigger asChild>
<div className="rounded p-1 hover:bg-muted">
<PlusIcon className="size-4" />
</div>
</PopoverTrigger>
<PopoverContent>
<WorkflowBlockParameterSelect
nodeId={nodeId}
onAdd={(parameterKey) => {
onChange(`${props.value ?? ""}{{${parameterKey}}}`);
}}
/>
</PopoverContent>
</Popover>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Switch } from "@/components/ui/switch";
import { ClickIcon } from "@/components/icons/ClickIcon";
import { placeholders, helpTooltips } from "../../helpContent";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
import { WorkflowBlockInput } from "@/components/WorkflowBlockInput";

const urlTooltip =
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
Expand All @@ -32,9 +32,6 @@ const navigationGoalTooltip =
const navigationGoalPlaceholder = 'Input {{ name }} into "Name" field.';

function ActionNode({ id, data }: NodeProps<ActionNode>) {
const [parametersPanelField, setParametersPanelField] = useState<
string | null
>(null);
const { updateNodeData } = useReactFlow();
const { editable } = data;
const [label, setLabel] = useNodeLabelChangeHandler({
Expand Down Expand Up @@ -107,14 +104,9 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
<HelpTooltip content={urlTooltip} />
</div>
<WorkflowBlockInputTextarea
onIconClick={() => {
setParametersPanelField("url");
}}
onChange={(event) => {
if (!editable) {
return;
}
handleChange("url", event.target.value);
nodeId={id}
onChange={(value) => {
handleChange("url", value);
}}
value={inputs.url}
placeholder={placeholders["action"]["url"]}
Expand All @@ -129,14 +121,9 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
<HelpTooltip content={navigationGoalTooltip} />
</div>
<WorkflowBlockInputTextarea
onIconClick={() => {
setParametersPanelField("navigationGoal");
}}
onChange={(event) => {
if (!editable) {
return;
}
handleChange("navigationGoal", event.target.value);
nodeId={id}
onChange={(value) => {
handleChange("navigationGoal", value);
}}
value={inputs.navigationGoal}
placeholder={navigationGoalPlaceholder}
Expand Down Expand Up @@ -302,16 +289,14 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
content={helpTooltips["action"]["fileSuffix"]}
/>
</div>
<Input
<WorkflowBlockInput
nodeId={id}
type="text"
placeholder={placeholders["action"]["downloadSuffix"]}
className="nopan w-52 text-xs"
value={inputs.downloadSuffix ?? ""}
onChange={(event) => {
if (!editable) {
return;
}
handleChange("downloadSuffix", event.target.value);
onChange={(value) => {
handleChange("downloadSuffix", value);
}}
/>
</div>
Expand All @@ -326,11 +311,9 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
/>
</div>
<WorkflowBlockInputTextarea
onIconClick={() => {
setParametersPanelField("totpVerificationUrl");
}}
onChange={(event) => {
handleChange("totpVerificationUrl", event.target.value);
nodeId={id}
onChange={(value) => {
handleChange("totpVerificationUrl", value);
}}
value={inputs.totpVerificationUrl ?? ""}
placeholder={placeholders["action"]["totpVerificationUrl"]}
Expand All @@ -347,14 +330,9 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
/>
</div>
<WorkflowBlockInputTextarea
onIconClick={() => {
setParametersPanelField("totpIdentifier");
}}
onChange={(event) => {
if (!editable) {
return;
}
handleChange("totpIdentifier", event.target.value);
nodeId={id}
onChange={(value) => {
handleChange("totpIdentifier", value);
}}
value={inputs.totpIdentifier ?? ""}
placeholder={placeholders["action"]["totpIdentifier"]}
Expand All @@ -366,25 +344,6 @@ function ActionNode({ id, data }: NodeProps<ActionNode>) {
</AccordionItem>
</Accordion>
</div>
{typeof parametersPanelField === "string" && (
<WorkflowBlockParameterSelect
nodeId={id}
onClose={() => setParametersPanelField(null)}
onAdd={(parameterKey) => {
if (parametersPanelField === null || !editable) {
return;
}
if (parametersPanelField in inputs) {
const currentValue =
inputs[parametersPanelField as keyof typeof inputs];
handleChange(
parametersPanelField,
`${currentValue ?? ""}{{ ${parameterKey} }}`,
);
}
}}
/>
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
import { HelpTooltip } from "@/components/HelpTooltip";
import { ExtractIcon } from "@/components/icons/ExtractIcon";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useDeleteNodeCallback } from "@/routes/workflows/hooks/useDeleteNodeCallback";
import { useNodeLabelChangeHandler } from "@/routes/workflows/hooks/useLabelChangeHandler";
import { Handle, NodeProps, Position, useReactFlow } from "@xyflow/react";
import { useState } from "react";
import { EditableNodeTitle } from "../components/EditableNodeTitle";
import { NodeActionMenu } from "../NodeActionMenu";
import { HelpTooltip } from "@/components/HelpTooltip";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { dataSchemaExampleValue } from "../types";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { Switch } from "@/components/ui/switch";
import type { ExtractionNode } from "./types";
import { ExtractIcon } from "@/components/icons/ExtractIcon";

import { helpTooltips, placeholders } from "../../helpContent";
import { WorkflowBlockParameterSelect } from "../WorkflowBlockParameterSelect";
import { WorkflowBlockInputTextarea } from "@/components/WorkflowBlockInputTextarea";
import { helpTooltips, placeholders } from "../../helpContent";

function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
const [parametersPanelField, setParametersPanelField] = useState<
string | null
>(null);
const { updateNodeData } = useReactFlow();
const { editable } = data;
const [label, setLabel] = useNodeLabelChangeHandler({
Expand Down Expand Up @@ -101,14 +97,12 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
/>
</div>
<WorkflowBlockInputTextarea
onIconClick={() => {
setParametersPanelField("dataExtractionGoal");
}}
onChange={(event) => {
nodeId={id}
onChange={(value) => {
if (!editable) {
return;
}
handleChange("dataExtractionGoal", event.target.value);
handleChange("dataExtractionGoal", value);
}}
value={inputs.dataExtractionGoal}
placeholder={placeholders["extraction"]["dataExtractionGoal"]}
Expand Down Expand Up @@ -263,25 +257,6 @@ function ExtractionNode({ id, data }: NodeProps<ExtractionNode>) {
</AccordionItem>
</Accordion>
</div>
{typeof parametersPanelField === "string" && (
<WorkflowBlockParameterSelect
nodeId={id}
onClose={() => setParametersPanelField(null)}
onAdd={(parameterKey) => {
if (parametersPanelField === null || !editable) {
return;
}
if (parametersPanelField in inputs) {
const currentValue =
inputs[parametersPanelField as keyof typeof inputs];
handleChange(
parametersPanelField,
`${currentValue ?? ""}{{ ${parameterKey} }}`,
);
}
}}
/>
)}
</div>
);
}
Expand Down
Loading

0 comments on commit 3b8b963

Please sign in to comment.