Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use popover in add parameter experience #1377

Merged
merged 1 commit into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading