replace workflow parameter React Context with a zustand store; use everywhere (#3187)

This commit is contained in:
Jonathan Dobson
2025-08-14 08:04:48 -04:00
committed by GitHub
parent 2556d04e70
commit a2f6b9e539
16 changed files with 269 additions and 263 deletions

View File

@@ -1,11 +1,10 @@
import { PlusIcon } from "@radix-ui/react-icons"; import { PlusIcon } from "@radix-ui/react-icons";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect"; import { WorkflowBlockParameterSelect } from "@/routes/workflows/editor/nodes/WorkflowBlockParameterSelect";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Cross2Icon } from "@radix-ui/react-icons"; import { Cross2Icon } from "@radix-ui/react-icons";
import "./workflow-block-input-set.css"; import "./workflow-block-input-set.css";
import { useWorkflowParametersState } from "@/routes/workflows/editor/useWorkflowParametersState";
type Props = { type Props = {
onChange: (parameterKeys: Set<string>) => void; onChange: (parameterKeys: Set<string>) => void;
nodeId: string; nodeId: string;
@@ -16,7 +15,7 @@ function WorkflowBlockInputSet(props: Props) {
const { nodeId, onChange, values } = props; const { nodeId, onChange, values } = props;
const [parameterKeys, setParameterKeys] = useState<Set<string>>(values); const [parameterKeys, setParameterKeys] = useState<Set<string>>(values);
const hasKeys = parameterKeys.size > 0; const hasKeys = parameterKeys.size > 0;
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const availableParameterKeys = new Set( const availableParameterKeys = new Set(
workflowParameters.map((parameter) => parameter.key), workflowParameters.map((parameter) => parameter.key),
); );

View File

@@ -7,7 +7,7 @@ import {
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { useCredentialsQuery } from "../hooks/useCredentialsQuery"; import { useCredentialsQuery } from "../hooks/useCredentialsQuery";
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { WorkflowParameterValueType } from "../types/workflowTypes"; import { WorkflowParameterValueType } from "../types/workflowTypes";
import { PlusIcon } from "@radix-ui/react-icons"; import { PlusIcon } from "@radix-ui/react-icons";
import { import {
@@ -24,7 +24,7 @@ type Props = {
function CredentialParameterSourceSelector({ value, onChange }: Props) { function CredentialParameterSourceSelector({ value, onChange }: Props) {
const { data: credentials, isFetching } = useCredentialsQuery(); const { data: credentials, isFetching } = useCredentialsQuery();
const { setIsOpen, setType } = useCredentialModalState(); const { setIsOpen, setType } = useCredentialModalState();
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const workflowParametersOfTypeCredentialId = workflowParameters.filter( const workflowParametersOfTypeCredentialId = workflowParameters.filter(
(parameter) => (parameter) =>
parameter.parameterType === "workflow" && parameter.parameterType === "workflow" &&

View File

@@ -1,5 +1,5 @@
import { useNodes } from "@xyflow/react"; import { useNodes } from "@xyflow/react";
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { AppNode, isWorkflowBlockNode } from "../editor/nodes"; import { AppNode, isWorkflowBlockNode } from "../editor/nodes";
import { getOutputParameterKey } from "../editor/workflowEditorUtils"; import { getOutputParameterKey } from "../editor/workflowEditorUtils";
import { import {
@@ -16,7 +16,7 @@ type Props = {
}; };
function SourceParameterKeySelector({ value, onChange }: Props) { function SourceParameterKeySelector({ value, onChange }: Props) {
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const contextParameterKeys = workflowParameters const contextParameterKeys = workflowParameters
.filter((parameter) => parameter.parameterType !== "credential") .filter((parameter) => parameter.parameterType !== "credential")

View File

@@ -16,6 +16,7 @@ import {
useWorkflowSave, useWorkflowSave,
type WorkflowSaveData, type WorkflowSaveData,
} from "@/store/WorkflowHasChangesStore"; } from "@/store/WorkflowHasChangesStore";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore"; import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore";
import { ReloadIcon } from "@radix-ui/react-icons"; import { ReloadIcon } from "@radix-ui/react-icons";
import { import {
@@ -223,7 +224,7 @@ type Props = {
onNodesChange: (changes: Array<NodeChange<AppNode>>) => void; onNodesChange: (changes: Array<NodeChange<AppNode>>) => void;
onEdgesChange: (changes: Array<EdgeChange>) => void; onEdgesChange: (changes: Array<EdgeChange>) => void;
initialTitle: string; initialTitle: string;
initialParameters: ParametersState; // initialParameters: ParametersState;
workflow: WorkflowApiResponse; workflow: WorkflowApiResponse;
onDebuggableBlockCountChange: (count: number) => void; onDebuggableBlockCountChange: (count: number) => void;
onMouseDownCapture?: () => void; onMouseDownCapture?: () => void;
@@ -238,7 +239,7 @@ function FlowRenderer({
onNodesChange, onNodesChange,
onEdgesChange, onEdgesChange,
initialTitle, initialTitle,
initialParameters, // initialParameters,
workflow, workflow,
onDebuggableBlockCountChange, onDebuggableBlockCountChange,
onMouseDownCapture, onMouseDownCapture,
@@ -247,7 +248,8 @@ function FlowRenderer({
const reactFlowInstance = useReactFlow(); const reactFlowInstance = useReactFlow();
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const { title, initializeTitle } = useWorkflowTitleStore(); const { title, initializeTitle } = useWorkflowTitleStore();
const [parameters] = useState<ParametersState>(initialParameters); // const [parameters] = useState<ParametersState>(initialParameters);
const parameters = useWorkflowParametersStore((state) => state.parameters);
const nodesInitialized = useNodesInitialized(); const nodesInitialized = useNodesInitialized();
const [shouldConstrainPan, setShouldConstrainPan] = useState(false); const [shouldConstrainPan, setShouldConstrainPan] = useState(false);
const onNodesChangeTimeoutRef = useRef<NodeJS.Timeout | null>(null); const onNodesChangeTimeoutRef = useRef<NodeJS.Timeout | null>(null);

View File

@@ -1,3 +1,4 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { ReactFlowProvider } from "@xyflow/react"; import { ReactFlowProvider } from "@xyflow/react";
@@ -6,6 +7,7 @@ import { WorkflowSettings } from "../types/workflowTypes";
import { getElements } from "./workflowEditorUtils"; import { getElements } from "./workflowEditorUtils";
import { getInitialParameters } from "./utils"; import { getInitialParameters } from "./utils";
import { Workspace } from "./Workspace"; import { Workspace } from "./Workspace";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
function WorkflowDebugger() { function WorkflowDebugger() {
const { workflowPermanentId } = useParams(); const { workflowPermanentId } = useParams();
@@ -13,6 +15,17 @@ function WorkflowDebugger() {
workflowPermanentId, workflowPermanentId,
}); });
const setParameters = useWorkflowParametersStore(
(state) => state.setParameters,
);
useEffect(() => {
if (workflow) {
const initialParameters = getInitialParameters(workflow);
setParameters(initialParameters);
}
}, [workflow, setParameters]);
if (!workflow) { if (!workflow) {
return null; return null;
} }
@@ -42,7 +55,6 @@ function WorkflowDebugger() {
<Workspace <Workspace
initialEdges={elements.edges} initialEdges={elements.edges}
initialNodes={elements.nodes} initialNodes={elements.nodes}
initialParameters={getInitialParameters(workflow)}
initialTitle={workflow.title} initialTitle={workflow.title}
showBrowser={true} showBrowser={true}
workflow={workflow} workflow={workflow}

View File

@@ -1,10 +1,12 @@
import { ReactFlowProvider } from "@xyflow/react"; import { ReactFlowProvider } from "@xyflow/react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useEffect } from "react";
import { useWorkflowQuery } from "../hooks/useWorkflowQuery"; import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
import { getElements } from "./workflowEditorUtils"; import { getElements } from "./workflowEditorUtils";
import { LogoMinimized } from "@/components/LogoMinimized"; import { LogoMinimized } from "@/components/LogoMinimized";
import { WorkflowSettings } from "../types/workflowTypes"; import { WorkflowSettings } from "../types/workflowTypes";
import { useGlobalWorkflowsQuery } from "../hooks/useGlobalWorkflowsQuery"; import { useGlobalWorkflowsQuery } from "../hooks/useGlobalWorkflowsQuery";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { getInitialParameters } from "./utils"; import { getInitialParameters } from "./utils";
import { Workspace } from "./Workspace"; import { Workspace } from "./Workspace";
@@ -18,6 +20,17 @@ function WorkflowEditor() {
const { data: globalWorkflows, isLoading: isGlobalWorkflowsLoading } = const { data: globalWorkflows, isLoading: isGlobalWorkflowsLoading } =
useGlobalWorkflowsQuery(); useGlobalWorkflowsQuery();
const setParameters = useWorkflowParametersStore(
(state) => state.setParameters,
);
useEffect(() => {
if (workflow) {
const initialParameters = getInitialParameters(workflow);
setParameters(initialParameters);
}
}, [workflow, setParameters]);
if (isLoading || isGlobalWorkflowsLoading) { if (isLoading || isGlobalWorkflowsLoading) {
return ( return (
<div className="flex h-screen w-full items-center justify-center"> <div className="flex h-screen w-full items-center justify-center">
@@ -60,7 +73,6 @@ function WorkflowEditor() {
<Workspace <Workspace
initialEdges={elements.edges} initialEdges={elements.edges}
initialNodes={elements.nodes} initialNodes={elements.nodes}
initialParameters={getInitialParameters(workflow)}
initialTitle={workflow.title} initialTitle={workflow.title}
showBrowser={false} showBrowser={false}
workflow={workflow} workflow={workflow}

View File

@@ -1,13 +0,0 @@
import { createContext } from "react";
import { ParametersState } from "./types";
type WorkflowParametersState = [
ParametersState,
React.Dispatch<React.SetStateAction<ParametersState>>,
];
const WorkflowParametersStateContext = createContext<
WorkflowParametersState | undefined
>(undefined);
export { WorkflowParametersStateContext };

View File

@@ -44,7 +44,6 @@ import { FlowRenderer, type FlowRendererProps } from "./FlowRenderer";
import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes"; import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes";
import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel"; import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel";
import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel"; import { WorkflowParametersPanel } from "./panels/WorkflowParametersPanel";
import { ParametersState } from "./types";
import { getWorkflowErrors } from "./workflowEditorUtils"; import { getWorkflowErrors } from "./workflowEditorUtils";
import { WorkflowHeader } from "./WorkflowHeader"; import { WorkflowHeader } from "./WorkflowHeader";
import { import {
@@ -55,16 +54,12 @@ import {
layout, layout,
startNode, startNode,
} from "./workflowEditorUtils"; } from "./workflowEditorUtils";
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
const Constants = { const Constants = {
NewBrowserCooldown: 30000, NewBrowserCooldown: 30000,
} as const; } as const;
type Props = Pick< type Props = Pick<FlowRendererProps, "initialTitle" | "workflow"> & {
FlowRendererProps,
"initialTitle" | "initialParameters" | "workflow"
> & {
initialNodes: Array<AppNode>; initialNodes: Array<AppNode>;
initialEdges: Array<Edge>; initialEdges: Array<Edge>;
showBrowser?: boolean; showBrowser?: boolean;
@@ -82,15 +77,12 @@ function Workspace({
initialNodes, initialNodes,
initialEdges, initialEdges,
initialTitle, initialTitle,
initialParameters,
showBrowser = false, showBrowser = false,
workflow, workflow,
}: Props) { }: Props) {
const { blockLabel, workflowPermanentId } = useParams(); const { blockLabel, workflowPermanentId } = useParams();
const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } = const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } =
useWorkflowPanelStore(); useWorkflowPanelStore();
const [parameters, setParameters] =
useState<ParametersState>(initialParameters);
const debugStore = useDebugStore(); const debugStore = useDebugStore();
const [debuggableBlockCount, setDebuggableBlockCount] = useState(0); const [debuggableBlockCount, setDebuggableBlockCount] = useState(0);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
@@ -335,208 +327,204 @@ function Workspace({
} }
return ( return (
<WorkflowParametersStateContext.Provider <div className="relative h-full w-full">
value={[parameters, setParameters]} <Dialog
> open={openDialogue}
<div className="relative h-full w-full"> onOpenChange={(open) => {
<Dialog if (!open && cycleBrowser.isPending) {
open={openDialogue} return;
onOpenChange={(open) => { }
if (!open && cycleBrowser.isPending) { setOpenDialogue(open);
return; }}
} >
setOpenDialogue(open); <DialogContent>
}} <DialogHeader>
> <DialogTitle>Cycle (Get a new browser)</DialogTitle>
<DialogContent> <DialogDescription>
<DialogHeader> <div className="pb-2 pt-4 text-sm text-slate-400">
<DialogTitle>Cycle (Get a new browser)</DialogTitle> {cycleBrowser.isPending ? (
<DialogDescription> <>
<div className="pb-2 pt-4 text-sm text-slate-400"> Cooking you up a fresh browser...
{cycleBrowser.isPending ? ( <AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
<> </>
Cooking you up a fresh browser... ) : (
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" /> "Abandon this browser for a new one. Are you sure?"
</>
) : (
"Abandon this browser for a new one. Are you sure?"
)}
</div>
</DialogDescription>
</DialogHeader>
<DialogFooter>
{!cycleBrowser.isPending && (
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
)}
<Button
variant="default"
onClick={() => {
cycleBrowser.mutate(workflowPermanentId!);
}}
disabled={cycleBrowser.isPending}
>
Yes, Continue{" "}
{cycleBrowser.isPending && (
<ReloadIcon className="ml-2 size-4 animate-spin" />
)} )}
</Button> </div>
</DialogFooter> </DialogDescription>
</DialogContent> </DialogHeader>
</Dialog> <DialogFooter>
{!cycleBrowser.isPending && (
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
)}
<Button
variant="default"
onClick={() => {
cycleBrowser.mutate(workflowPermanentId!);
}}
disabled={cycleBrowser.isPending}
>
Yes, Continue{" "}
{cycleBrowser.isPending && (
<ReloadIcon className="ml-2 size-4 animate-spin" />
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* header panel */} {/* header panel */}
<div <div
className="absolute left-6 right-6 top-8 h-20" className="absolute left-6 right-6 top-8 h-20"
style={{ zIndex: rankedItems.header ?? 3 }} style={{ zIndex: rankedItems.header ?? 3 }}
onMouseDownCapture={() => { onMouseDownCapture={() => {
promote("header"); promote("header");
}} }}
> >
<WorkflowHeader <WorkflowHeader
debuggableBlockCount={debuggableBlockCount} debuggableBlockCount={debuggableBlockCount}
saving={workflowChangesStore.saveIsPending} saving={workflowChangesStore.saveIsPending}
parametersPanelOpen={ parametersPanelOpen={
workflowPanelState.active &&
workflowPanelState.content === "parameters"
}
onParametersClick={() => {
if (
workflowPanelState.active && workflowPanelState.active &&
workflowPanelState.content === "parameters" workflowPanelState.content === "parameters"
} ) {
onParametersClick={() => {
if (
workflowPanelState.active &&
workflowPanelState.content === "parameters"
) {
closeWorkflowPanel();
promote("header");
} else {
setWorkflowPanelState({
active: true,
content: "parameters",
});
promote("dropdown");
}
}}
onSave={async () => {
const errors = getWorkflowErrors(nodes);
if (errors.length > 0) {
toast({
title: "Can not save workflow because of errors:",
description: (
<div className="space-y-2">
{errors.map((error) => (
<p key={error}>{error}</p>
))}
</div>
),
variant: "destructive",
});
return;
}
await saveWorkflow.mutateAsync();
}}
onRun={() => {
closeWorkflowPanel(); closeWorkflowPanel();
promote("header"); promote("header");
}} } else {
/> setWorkflowPanelState({
</div> active: true,
content: "parameters",
{/* sub panels */} });
{workflowPanelState.active && (
<div
className="absolute right-6 top-[7.75rem]"
style={{ zIndex: rankedItems.dropdown ?? 2 }}
onMouseDownCapture={() => {
promote("dropdown"); promote("dropdown");
}} }
> }}
{workflowPanelState.content === "parameters" && ( onSave={async () => {
<WorkflowParametersPanel const errors = getWorkflowErrors(nodes);
onMouseDownCapture={() => { if (errors.length > 0) {
promote("dropdown"); toast({
}} title: "Can not save workflow because of errors:",
/> description: (
)} <div className="space-y-2">
{workflowPanelState.content === "nodeLibrary" && ( {errors.map((error) => (
<WorkflowNodeLibraryPanel <p key={error}>{error}</p>
onMouseDownCapture={() => { ))}
promote("dropdown"); </div>
}} ),
onNodeClick={(props) => { variant: "destructive",
addNode(props); });
}} return;
/> }
)} await saveWorkflow.mutateAsync();
</div> }}
)} onRun={() => {
closeWorkflowPanel();
promote("header");
}}
/>
</div>
{debugStore.isDebugMode && ( {/* sub panels */}
<div {workflowPanelState.active && (
className="absolute right-6 top-[8.5rem] h-[calc(100vh-9.5rem)]" <div
style={{ zIndex: rankedItems.history ?? 1 }} className="absolute right-6 top-[7.75rem]"
onMouseDownCapture={() => { style={{ zIndex: rankedItems.dropdown ?? 2 }}
closeWorkflowPanel(); onMouseDownCapture={() => {
promote("history"); promote("dropdown");
}} }}
> >
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end"> {workflowPanelState.content === "parameters" && (
<div className="pointer-events-auto relative h-full w-full overflow-hidden rounded-xl border-2 border-slate-500"> <WorkflowParametersPanel
<WorkflowDebuggerRun /> onMouseDownCapture={() => {
</div> promote("dropdown");
}}
/>
)}
{workflowPanelState.content === "nodeLibrary" && (
<WorkflowNodeLibraryPanel
onMouseDownCapture={() => {
promote("dropdown");
}}
onNodeClick={(props) => {
addNode(props);
}}
/>
)}
</div>
)}
{debugStore.isDebugMode && (
<div
className="absolute right-6 top-[8.5rem] h-[calc(100vh-9.5rem)]"
style={{ zIndex: rankedItems.history ?? 1 }}
onMouseDownCapture={() => {
closeWorkflowPanel();
promote("history");
}}
>
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end">
<div className="pointer-events-auto relative h-full w-full overflow-hidden rounded-xl border-2 border-slate-500">
<WorkflowDebuggerRun />
</div> </div>
</div> </div>
)} </div>
)}
{/* infinite canvas */} {/* infinite canvas */}
<FlowRenderer <FlowRenderer
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
setNodes={setNodes} setNodes={setNodes}
setEdges={setEdges} setEdges={setEdges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
initialTitle={initialTitle} initialTitle={initialTitle}
initialParameters={initialParameters} // initialParameters={initialParameters}
workflow={workflow} workflow={workflow}
onDebuggableBlockCountChange={(c) => setDebuggableBlockCount(c)} onDebuggableBlockCountChange={(c) => setDebuggableBlockCount(c)}
onMouseDownCapture={() => promote("infiniteCanvas")} onMouseDownCapture={() => promote("infiniteCanvas")}
zIndex={rankedItems.infiniteCanvas} zIndex={rankedItems.infiniteCanvas}
/> />
{/* browser */} {/* browser */}
{showBrowser && ( {showBrowser && (
<FloatingWindow <FloatingWindow
title={browserTitle} title={browserTitle}
bounded={false} bounded={false}
initialPosition={initialBrowserPosition} initialPosition={initialBrowserPosition}
initialWidth={initialWidth} initialWidth={initialWidth}
initialHeight={initialHeight} initialHeight={initialHeight}
showMaximizeButton={true} showMaximizeButton={true}
showMinimizeButton={true} showMinimizeButton={true}
showPowerButton={blockLabel === undefined && showPowerButton} showPowerButton={blockLabel === undefined && showPowerButton}
showReloadButton={true} showReloadButton={true}
zIndex={rankedItems.browserWindow ?? 4} zIndex={rankedItems.browserWindow ?? 4}
// -- // --
onCycle={handleOnCycle} onCycle={handleOnCycle}
onFocus={() => promote("browserWindow")} onFocus={() => promote("browserWindow")}
> >
{activeDebugSession && {activeDebugSession &&
activeDebugSession.browser_session_id && activeDebugSession.browser_session_id &&
!cycleBrowser.isPending ? ( !cycleBrowser.isPending ? (
<BrowserStream <BrowserStream
interactive={interactor === "human"} interactive={interactor === "human"}
browserSessionId={activeDebugSession.browser_session_id} browserSessionId={activeDebugSession.browser_session_id}
/> />
) : ( ) : (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400"> <div className="flex h-full w-full flex-col items-center justify-center gap-2 pb-2 pt-4 text-sm text-slate-400">
Connecting to your browser... Connecting to your browser...
<AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" /> <AnimatedWave text=".‧₊˚ ⋅ ✨★ ‧₊˚ ⋅" />
</div> </div>
)} )}
</FloatingWindow> </FloatingWindow>
)} )}
</div> </div>
</WorkflowParametersStateContext.Provider>
); );
} }

View File

@@ -9,7 +9,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery"; import { useCredentialsQuery } from "@/routes/workflows/hooks/useCredentialsQuery";
import CloudContext from "@/store/CloudContext"; import CloudContext from "@/store/CloudContext";
import { useContext } from "react"; import { useContext } from "react";
import { useWorkflowParametersState } from "../../useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { CredentialsModal } from "@/routes/credentials/CredentialsModal"; import { CredentialsModal } from "@/routes/credentials/CredentialsModal";
import { PlusIcon } from "@radix-ui/react-icons"; import { PlusIcon } from "@radix-ui/react-icons";
import { import {
@@ -30,8 +30,10 @@ type Props = {
function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) { function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) {
const { setIsOpen, setType } = useCredentialModalState(); const { setIsOpen, setType } = useCredentialModalState();
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const [workflowParameters, setWorkflowParameters] = const {
useWorkflowParametersState(); parameters: workflowParameters,
setParameters: setWorkflowParameters,
} = useWorkflowParametersStore();
const credentialParameters = workflowParameters.filter( const credentialParameters = workflowParameters.filter(
(parameter) => (parameter) =>
parameter.parameterType === "credential" || parameter.parameterType === "credential" ||
@@ -170,16 +172,14 @@ function LoginBlockCredentialSelector({ nodeId, value, onChange }: Props) {
<CredentialsModal <CredentialsModal
onCredentialCreated={(id) => { onCredentialCreated={(id) => {
onChange?.(id); onChange?.(id);
setWorkflowParameters((prev) => { setWorkflowParameters([
return [ ...workflowParameters,
...prev, {
{ parameterType: "credential",
parameterType: "credential", credentialId: id,
credentialId: id, key: id,
key: id, },
}, ]);
];
});
}} }}
/> />
</> </>

View File

@@ -1,5 +1,5 @@
import { MultiSelect } from "@/components/ui/multi-select"; import { MultiSelect } from "@/components/ui/multi-select";
import { useWorkflowParametersState } from "../../useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { helpTooltips } from "../../helpContent"; import { helpTooltips } from "../../helpContent";
@@ -14,7 +14,7 @@ function ParametersMultiSelect({
parameters, parameters,
onParametersChange, onParametersChange,
}: Props) { }: Props) {
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const keys = workflowParameters const keys = workflowParameters
.map((parameter) => parameter.key) .map((parameter) => parameter.key)
.concat(availableOutputParameters); .concat(availableOutputParameters);

View File

@@ -1,5 +1,5 @@
import { MultiSelect } from "@/components/ui/multi-select"; import { MultiSelect } from "@/components/ui/multi-select";
import { useWorkflowParametersState } from "../../useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { HelpTooltip } from "@/components/HelpTooltip"; import { HelpTooltip } from "@/components/HelpTooltip";
import { helpTooltips } from "../../helpContent"; import { helpTooltips } from "../../helpContent";
@@ -14,7 +14,7 @@ function TaskNodeParametersPanel({
parameters, parameters,
onParametersChange, onParametersChange,
}: Props) { }: Props) {
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const keys = workflowParameters const keys = workflowParameters
.map((parameter) => parameter.key) .map((parameter) => parameter.key)
.concat(availableOutputParameters); .concat(availableOutputParameters);

View File

@@ -1,5 +1,5 @@
import { useEdges, useNodes } from "@xyflow/react"; import { useEdges, useNodes } from "@xyflow/react";
import { useWorkflowParametersState } from "../useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { AppNode } from "."; import { AppNode } from ".";
import { getAvailableOutputParameterKeys } from "../workflowEditorUtils"; import { getAvailableOutputParameterKeys } from "../workflowEditorUtils";
import { PlusIcon } from "@radix-ui/react-icons"; import { PlusIcon } from "@radix-ui/react-icons";
@@ -15,7 +15,7 @@ type Props = {
function WorkflowBlockParameterSelect({ nodeId, onAdd }: Props) { function WorkflowBlockParameterSelect({ nodeId, onAdd }: Props) {
const [content, setContent] = useState("parameters"); const [content, setContent] = useState("parameters");
const [workflowParameters] = useWorkflowParametersState(); const { parameters: workflowParameters } = useWorkflowParametersStore();
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const edges = useEdges(); const edges = useEdges();
const outputParameterKeys = getAvailableOutputParameterKeys( const outputParameterKeys = getAvailableOutputParameterKeys(

View File

@@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import { useWorkflowParametersState } from "../useWorkflowParametersState";
import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel"; import { WorkflowParameterAddPanel } from "./WorkflowParameterAddPanel";
import { ParametersState } from "../types"; import { ParametersState } from "../types";
import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel"; import { WorkflowParameterEditPanel } from "./WorkflowParameterEditPanel";
@@ -26,6 +25,7 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useReactFlow } from "@xyflow/react"; import { useReactFlow } from "@xyflow/react";
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore"; import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area";
import { import {
WorkflowEditorParameterType, WorkflowEditorParameterType,
@@ -44,8 +44,10 @@ function WorkflowParametersPanel({ onMouseDownCapture }: Props) {
const setHasChanges = useWorkflowHasChangesStore( const setHasChanges = useWorkflowHasChangesStore(
(state) => state.setHasChanges, (state) => state.setHasChanges,
); );
const [workflowParameters, setWorkflowParameters] = const {
useWorkflowParametersState(); parameters: workflowParameters,
setParameters: setWorkflowParameters,
} = useWorkflowParametersStore();
const [operationPanelState, setOperationPanelState] = useState<{ const [operationPanelState, setOperationPanelState] = useState<{
active: boolean; active: boolean;
operation: "add" | "edit"; operation: "add" | "edit";

View File

@@ -1,14 +0,0 @@
import { useContext } from "react";
import { WorkflowParametersStateContext } from "./WorkflowParametersStateContext";
function useWorkflowParametersState() {
const value = useContext(WorkflowParametersStateContext);
if (value === undefined) {
throw new Error(
"useWorkflowParametersState must be used within a WorkflowParametersStateProvider",
);
}
return value;
}
export { useWorkflowParametersState };

View File

@@ -6,7 +6,7 @@ import {
getUpdatedParametersAfterLabelUpdateForSourceParameterKey, getUpdatedParametersAfterLabelUpdateForSourceParameterKey,
} from "../editor/workflowEditorUtils"; } from "../editor/workflowEditorUtils";
import { useState } from "react"; import { useState } from "react";
import { useWorkflowParametersState } from "../editor/useWorkflowParametersState"; import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
type Props = { type Props = {
id: string; id: string;
@@ -17,8 +17,10 @@ function useNodeLabelChangeHandler({ id, initialValue }: Props) {
const [label, setLabel] = useState(initialValue); const [label, setLabel] = useState(initialValue);
const nodes = useNodes<AppNode>(); const nodes = useNodes<AppNode>();
const { setNodes } = useReactFlow(); const { setNodes } = useReactFlow();
const [workflowParameters, setWorkflowParameters] = const {
useWorkflowParametersState(); parameters: workflowParameters,
setParameters: setWorkflowParameters,
} = useWorkflowParametersStore();
function handleLabelChange(value: string) { function handleLabelChange(value: string) {
const existingLabels = nodes const existingLabels = nodes

View File

@@ -0,0 +1,16 @@
import { create } from "zustand";
import { ParametersState } from "@/routes/workflows/editor/types";
interface WorkflowParametersStore {
parameters: ParametersState;
setParameters: (parameters: ParametersState) => void;
}
const useWorkflowParametersStore = create<WorkflowParametersStore>((set) => {
return {
parameters: [],
setParameters: (parameters: ParametersState) => set({ parameters }),
};
});
export { useWorkflowParametersStore };