feat(ui): split workflow editor settings to separate slice

We need the undoable slice to be only undoable state - settings are not undoable.
This commit is contained in:
psychedelicious 2024-05-15 17:20:51 +10:00
parent 27826369f0
commit 9c0d44b412
15 changed files with 122 additions and 84 deletions

View File

@ -22,6 +22,7 @@ import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice';
import { loraPersistConfig, loraSlice } from 'features/lora/store/loraSlice'; import { loraPersistConfig, loraSlice } from 'features/lora/store/loraSlice';
import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice'; import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice';
import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice'; import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice'; import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice';
import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice'; import { postprocessingPersistConfig, postprocessingSlice } from 'features/parameters/store/postprocessingSlice';
@ -66,6 +67,7 @@ const allReducers = {
[workflowSlice.name]: workflowSlice.reducer, [workflowSlice.name]: workflowSlice.reducer,
[hrfSlice.name]: hrfSlice.reducer, [hrfSlice.name]: hrfSlice.reducer,
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig), [controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
[api.reducerPath]: api.reducer, [api.reducerPath]: api.reducer,
}; };
@ -111,6 +113,7 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig, [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
[hrfPersistConfig.name]: hrfPersistConfig, [hrfPersistConfig.name]: hrfPersistConfig,
[controlLayersPersistConfig.name]: controlLayersPersistConfig, [controlLayersPersistConfig.name]: controlLayersPersistConfig,
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
}; };
const unserialize: UnserializeFunction = (data, key) => { const unserialize: UnserializeFunction = (data, key) => {

View File

@ -10,6 +10,7 @@ import type { Layer } from 'features/controlLayers/store/types';
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt'; import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice'; import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { selectSystemSlice } from 'features/system/store/systemSlice'; import { selectSystemSlice } from 'features/system/store/systemSlice';
@ -31,11 +32,12 @@ const selector = createMemoizedSelector(
selectGenerationSlice, selectGenerationSlice,
selectSystemSlice, selectSystemSlice,
selectNodesSlice, selectNodesSlice,
selectWorkflowSettingsSlice,
selectDynamicPromptsSlice, selectDynamicPromptsSlice,
selectControlLayersSlice, selectControlLayersSlice,
activeTabNameSelector, activeTabNameSelector,
], ],
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => { (controlAdapters, generation, system, nodes, workflowSettings, dynamicPrompts, controlLayers, activeTabName) => {
const { model } = generation; const { model } = generation;
const { size } = controlLayers.present; const { size } = controlLayers.present;
const { positivePrompt } = controlLayers.present; const { positivePrompt } = controlLayers.present;
@ -50,7 +52,7 @@ const selector = createMemoizedSelector(
} }
if (activeTabName === 'workflows') { if (activeTabName === 'workflows') {
if (nodes.shouldValidateGraph) { if (workflowSettings.shouldValidateGraph) {
if (!nodes.nodes.length) { if (!nodes.nodes.length) {
reasons.push({ content: i18n.t('parameters.invoke.noNodesInGraph') }); reasons.push({ content: i18n.t('parameters.invoke.noNodesInGraph') });
} }

View File

@ -75,8 +75,8 @@ export const Flow = memo(() => {
const nodes = useAppSelector((s) => s.nodes.present.nodes); const nodes = useAppSelector((s) => s.nodes.present.nodes);
const edges = useAppSelector((s) => s.nodes.present.edges); const edges = useAppSelector((s) => s.nodes.present.edges);
const viewport = useAppSelector((s) => s.nodes.present.viewport); const viewport = useAppSelector((s) => s.nodes.present.viewport);
const shouldSnapToGrid = useAppSelector((s) => s.nodes.present.shouldSnapToGrid); const shouldSnapToGrid = useAppSelector((s) => s.workflowSettings.shouldSnapToGrid);
const selectionMode = useAppSelector((s) => s.nodes.present.selectionMode); const selectionMode = useAppSelector((s) => s.workflowSettings.selectionMode);
const flowWrapper = useRef<HTMLDivElement>(null); const flowWrapper = useRef<HTMLDivElement>(null);
const cursorPosition = useRef<XYPosition | null>(null); const cursorPosition = useRef<XYPosition | null>(null);
const isValidConnection = useIsValidConnection(); const isValidConnection = useIsValidConnection();

View File

@ -3,17 +3,20 @@ import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor'; import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { memo } from 'react'; import { memo } from 'react';
import type { ConnectionLineComponentProps } from 'reactflow'; import type { ConnectionLineComponentProps } from 'reactflow';
import { getBezierPath } from 'reactflow'; import { getBezierPath } from 'reactflow';
const selectStroke = createSelector(selectNodesSlice, (nodes) => const selectStroke = createSelector([selectNodesSlice, selectWorkflowSettingsSlice], (nodes, workflowSettings) =>
nodes.shouldColorEdges ? getFieldColor(nodes.connectionStartFieldType) : colorTokenToCssVar('base.500') workflowSettings.shouldColorEdges ? getFieldColor(nodes.connectionStartFieldType) : colorTokenToCssVar('base.500')
); );
const selectClassName = createSelector(selectNodesSlice, (nodes) => const selectClassName = createSelector(selectWorkflowSettingsSlice, (workflowSettings) =>
nodes.shouldAnimateEdges ? 'react-flow__custom_connection-path animated' : 'react-flow__custom_connection-path' workflowSettings.shouldAnimateEdges
? 'react-flow__custom_connection-path animated'
: 'react-flow__custom_connection-path'
); );
const pathStyles: CSSProperties = { opacity: 0.8 }; const pathStyles: CSSProperties = { opacity: 0.8 };

View File

@ -27,7 +27,7 @@ const InvocationDefaultEdge = ({
); );
const { isSelected, shouldAnimate, stroke, label } = useAppSelector(selector); const { isSelected, shouldAnimate, stroke, label } = useAppSelector(selector);
const shouldShowEdgeLabels = useAppSelector((s) => s.nodes.present.shouldShowEdgeLabels); const shouldShowEdgeLabels = useAppSelector((s) => s.workflowSettings.shouldShowEdgeLabels);
const [edgePath, labelX, labelY] = getBezierPath({ const [edgePath, labelX, labelY] = getBezierPath({
sourceX, sourceX,

View File

@ -2,6 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectFieldOutputTemplate, selectNodeTemplate } from 'features/nodes/store/selectors'; import { selectFieldOutputTemplate, selectNodeTemplate } from 'features/nodes/store/selectors';
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
import { isInvocationNode } from 'features/nodes/types/invocation'; import { isInvocationNode } from 'features/nodes/types/invocation';
import { getFieldColor } from './getEdgeColor'; import { getFieldColor } from './getEdgeColor';
@ -22,7 +23,8 @@ export const makeEdgeSelector = (
) => ) =>
createMemoizedSelector( createMemoizedSelector(
selectNodesSlice, selectNodesSlice,
(nodes): { isSelected: boolean; shouldAnimate: boolean; stroke: string; label: string } => { selectWorkflowSettingsSlice,
(nodes, workflowSettings): { isSelected: boolean; shouldAnimate: boolean; stroke: string; label: string } => {
const sourceNode = nodes.nodes.find((node) => node.id === source); const sourceNode = nodes.nodes.find((node) => node.id === source);
const targetNode = nodes.nodes.find((node) => node.id === target); const targetNode = nodes.nodes.find((node) => node.id === target);
@ -36,7 +38,7 @@ export const makeEdgeSelector = (
const outputFieldTemplate = selectFieldOutputTemplate(nodes, sourceNode.id, sourceHandleId); const outputFieldTemplate = selectFieldOutputTemplate(nodes, sourceNode.id, sourceHandleId);
const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined; const sourceType = isInvocationToInvocationEdge ? outputFieldTemplate?.type : undefined;
const stroke = sourceType && nodes.shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500'); const stroke = sourceType && workflowSettings.shouldColorEdges ? getFieldColor(sourceType) : colorTokenToCssVar('base.500');
const sourceNodeTemplate = selectNodeTemplate(nodes, sourceNode.id); const sourceNodeTemplate = selectNodeTemplate(nodes, sourceNode.id);
const targetNodeTemplate = selectNodeTemplate(nodes, targetNode.id); const targetNodeTemplate = selectNodeTemplate(nodes, targetNode.id);
@ -45,7 +47,7 @@ export const makeEdgeSelector = (
return { return {
isSelected, isSelected,
shouldAnimate: nodes.shouldAnimateEdges && isSelected, shouldAnimate: workflowSettings.shouldAnimateEdges && isSelected,
stroke, stroke,
label, label,
}; };

View File

@ -39,7 +39,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const opacity = useAppSelector((s) => s.nodes.present.nodeOpacity); const opacity = useAppSelector((s) => s.workflowSettings.nodeOpacity);
const { onCloseGlobal } = useGlobalMenuClose(); const { onCloseGlobal } = useGlobalMenuClose();
const handleClick = useCallback( const handleClick = useCallback(

View File

@ -1,12 +1,12 @@
import { CompositeSlider, Flex } from '@invoke-ai/ui-library'; import { CompositeSlider, Flex } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice'; import { nodeOpacityChanged } from 'features/nodes/store/workflowSettingsSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const NodeOpacitySlider = () => { const NodeOpacitySlider = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const nodeOpacity = useAppSelector((s) => s.nodes.present.nodeOpacity); const nodeOpacity = useAppSelector((s) => s.workflowSettings.nodeOpacity);
const { t } = useTranslation(); const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(

View File

@ -1,9 +1,6 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library'; import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { import { shouldShowMinimapPanelChanged } from 'features/nodes/store/workflowSettingsSlice';
// shouldShowFieldTypeLegendChanged,
shouldShowMinimapPanelChanged,
} from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -21,7 +18,7 @@ const ViewportControls = () => {
// const shouldShowFieldTypeLegend = useAppSelector( // const shouldShowFieldTypeLegend = useAppSelector(
// (s) => s.nodes.present.shouldShowFieldTypeLegend // (s) => s.nodes.present.shouldShowFieldTypeLegend
// ); // );
const shouldShowMinimapPanel = useAppSelector((s) => s.nodes.present.shouldShowMinimapPanel); const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel);
const handleClickedZoomIn = useCallback(() => { const handleClickedZoomIn = useCallback(() => {
zoomIn(); zoomIn();

View File

@ -16,7 +16,7 @@ const minimapStyles: SystemStyleObject = {
}; };
const MinimapPanel = () => { const MinimapPanel = () => {
const shouldShowMinimapPanel = useAppSelector((s) => s.nodes.present.shouldShowMinimapPanel); const shouldShowMinimapPanel = useAppSelector((s) => s.workflowSettings.shouldShowMinimapPanel);
return ( return (
<Flex gap={2} position="absolute" bottom={0} insetInlineEnd={0}> <Flex gap={2} position="absolute" bottom={0} insetInlineEnd={0}>

View File

@ -21,13 +21,13 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton'; import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
import { import {
selectionModeChanged, selectionModeChanged,
selectNodesSlice, selectWorkflowSettingsSlice,
shouldAnimateEdgesChanged, shouldAnimateEdgesChanged,
shouldColorEdgesChanged, shouldColorEdgesChanged,
shouldShowEdgeLabelsChanged, shouldShowEdgeLabelsChanged,
shouldSnapToGridChanged, shouldSnapToGridChanged,
shouldValidateGraphChanged, shouldValidateGraphChanged,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/workflowSettingsSlice';
import type { ChangeEvent, ReactNode } from 'react'; import type { ChangeEvent, ReactNode } from 'react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -35,7 +35,7 @@ import { SelectionMode } from 'reactflow';
const formLabelProps: FormLabelProps = { flexGrow: 1 }; const formLabelProps: FormLabelProps = { flexGrow: 1 };
const selector = createMemoizedSelector(selectNodesSlice, (nodes) => { const selector = createMemoizedSelector(selectWorkflowSettingsSlice, (workflowSettings) => {
const { const {
shouldAnimateEdges, shouldAnimateEdges,
shouldValidateGraph, shouldValidateGraph,
@ -43,7 +43,7 @@ const selector = createMemoizedSelector(selectNodesSlice, (nodes) => {
shouldColorEdges, shouldColorEdges,
shouldShowEdgeLabels, shouldShowEdgeLabels,
selectionMode, selectionMode,
} = nodes; } = workflowSettings;
return { return {
shouldAnimateEdges, shouldAnimateEdges,
shouldValidateGraph, shouldValidateGraph,

View File

@ -13,7 +13,7 @@ import type { Connection, Node } from 'reactflow';
export const useIsValidConnection = () => { export const useIsValidConnection = () => {
const store = useAppStore(); const store = useAppStore();
const shouldValidateGraph = useAppSelector((s) => s.nodes.present.shouldValidateGraph); const shouldValidateGraph = useAppSelector((s) => s.workflowSettings.shouldValidateGraph);
const isValidConnection = useCallback( const isValidConnection = useCallback(
({ source, sourceHandle, target, targetHandle }: Connection): boolean => { ({ source, sourceHandle, target, targetHandle }: Connection): boolean => {
// Connection must have valid targets // Connection must have valid targets

View File

@ -57,15 +57,7 @@ import type {
Viewport, Viewport,
XYPosition, XYPosition,
} from 'reactflow'; } from 'reactflow';
import { import { addEdge, applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from 'reactflow';
addEdge,
applyEdgeChanges,
applyNodeChanges,
getConnectedEdges,
getIncomers,
getOutgoers,
SelectionMode,
} from 'reactflow';
import type { UndoableOptions } from 'redux-undo'; import type { UndoableOptions } from 'redux-undo';
import { import {
socketGeneratorProgress, socketGeneratorProgress,
@ -99,21 +91,13 @@ const initialNodesState: NodesState = {
connectionMade: false, connectionMade: false,
modifyingEdge: false, modifyingEdge: false,
addNewNodePosition: null, addNewNodePosition: null,
shouldShowMinimapPanel: true,
shouldValidateGraph: true,
shouldAnimateEdges: true,
shouldSnapToGrid: false,
shouldColorEdges: true,
shouldShowEdgeLabels: false,
isAddNodePopoverOpen: false, isAddNodePopoverOpen: false,
nodeOpacity: 1,
selectedNodes: [], selectedNodes: [],
selectedEdges: [], selectedEdges: [],
nodeExecutionStates: {}, nodeExecutionStates: {},
viewport: { x: 0, y: 0, zoom: 1 }, viewport: { x: 0, y: 0, zoom: 1 },
nodesToCopy: [], nodesToCopy: [],
edgesToCopy: [], edgesToCopy: [],
selectionMode: SelectionMode.Partial,
}; };
type FieldValueAction<T extends FieldValue> = PayloadAction<{ type FieldValueAction<T extends FieldValue> = PayloadAction<{
@ -538,31 +522,10 @@ export const nodesSlice = createSlice({
} }
node.data.notes = value; node.data.notes = value;
}, },
shouldShowMinimapPanelChanged: (state, action: PayloadAction<boolean>) => {
state.shouldShowMinimapPanel = action.payload;
},
nodeEditorReset: (state) => { nodeEditorReset: (state) => {
state.nodes = []; state.nodes = [];
state.edges = []; state.edges = [];
}, },
shouldValidateGraphChanged: (state, action: PayloadAction<boolean>) => {
state.shouldValidateGraph = action.payload;
},
shouldAnimateEdgesChanged: (state, action: PayloadAction<boolean>) => {
state.shouldAnimateEdges = action.payload;
},
shouldShowEdgeLabelsChanged: (state, action: PayloadAction<boolean>) => {
state.shouldShowEdgeLabels = action.payload;
},
shouldSnapToGridChanged: (state, action: PayloadAction<boolean>) => {
state.shouldSnapToGrid = action.payload;
},
shouldColorEdgesChanged: (state, action: PayloadAction<boolean>) => {
state.shouldColorEdges = action.payload;
},
nodeOpacityChanged: (state, action: PayloadAction<number>) => {
state.nodeOpacity = action.payload;
},
viewportChanged: (state, action: PayloadAction<Viewport>) => { viewportChanged: (state, action: PayloadAction<Viewport>) => {
state.viewport = action.payload; state.viewport = action.payload;
}, },
@ -700,9 +663,6 @@ export const nodesSlice = createSlice({
state.connectionStartParams = null; state.connectionStartParams = null;
state.connectionStartFieldType = null; state.connectionStartFieldType = null;
}, },
selectionModeChanged: (state, action: PayloadAction<boolean>) => {
state.selectionMode = action.payload ? SelectionMode.Full : SelectionMode.Partial;
},
nodeTemplatesBuilt: (state, action: PayloadAction<Record<string, InvocationTemplate>>) => { nodeTemplatesBuilt: (state, action: PayloadAction<Record<string, InvocationTemplate>>) => {
state.templates = action.payload; state.templates = action.payload;
}, },
@ -819,7 +779,6 @@ export const {
nodeIsOpenChanged, nodeIsOpenChanged,
nodeLabelChanged, nodeLabelChanged,
nodeNotesChanged, nodeNotesChanged,
nodeOpacityChanged,
nodesChanged, nodesChanged,
nodesDeleted, nodesDeleted,
nodeUseCacheChanged, nodeUseCacheChanged,
@ -828,17 +787,10 @@ export const {
selectedEdgesChanged, selectedEdgesChanged,
selectedNodesChanged, selectedNodesChanged,
selectionCopied, selectionCopied,
selectionModeChanged,
selectionPasted, selectionPasted,
shouldAnimateEdgesChanged,
shouldColorEdgesChanged,
shouldShowMinimapPanelChanged,
shouldSnapToGridChanged,
shouldValidateGraphChanged,
viewportChanged, viewportChanged,
edgeAdded, edgeAdded,
nodeTemplatesBuilt, nodeTemplatesBuilt,
shouldShowEdgeLabelsChanged,
undo, undo,
redo, redo,
} = nodesSlice.actions; } = nodesSlice.actions;

View File

@ -6,7 +6,7 @@ import type {
NodeExecutionState, NodeExecutionState,
} from 'features/nodes/types/invocation'; } from 'features/nodes/types/invocation';
import type { WorkflowV3 } from 'features/nodes/types/workflow'; import type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { OnConnectStartParams, SelectionMode, Viewport, XYPosition } from 'reactflow'; import type { OnConnectStartParams, Viewport, XYPosition } from 'reactflow';
export type NodesState = { export type NodesState = {
_version: 1; _version: 1;
@ -17,13 +17,6 @@ export type NodesState = {
connectionStartFieldType: FieldType | null; connectionStartFieldType: FieldType | null;
connectionMade: boolean; connectionMade: boolean;
modifyingEdge: boolean; modifyingEdge: boolean;
shouldShowMinimapPanel: boolean;
shouldValidateGraph: boolean;
shouldAnimateEdges: boolean;
nodeOpacity: number;
shouldSnapToGrid: boolean;
shouldColorEdges: boolean;
shouldShowEdgeLabels: boolean;
selectedNodes: string[]; selectedNodes: string[];
selectedEdges: string[]; selectedEdges: string[];
nodeExecutionStates: Record<string, NodeExecutionState>; nodeExecutionStates: Record<string, NodeExecutionState>;
@ -32,7 +25,6 @@ export type NodesState = {
edgesToCopy: InvocationNodeEdge[]; edgesToCopy: InvocationNodeEdge[];
isAddNodePopoverOpen: boolean; isAddNodePopoverOpen: boolean;
addNewNodePosition: XYPosition | null; addNewNodePosition: XYPosition | null;
selectionMode: SelectionMode;
}; };
export type WorkflowMode = 'edit' | 'view'; export type WorkflowMode = 'edit' | 'view';

View File

@ -0,0 +1,87 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store';
import { SelectionMode } from 'reactflow';
export type WorkflowSettingsState = {
_version: 1;
shouldShowMinimapPanel: boolean;
shouldValidateGraph: boolean;
shouldAnimateEdges: boolean;
nodeOpacity: number;
shouldSnapToGrid: boolean;
shouldColorEdges: boolean;
shouldShowEdgeLabels: boolean;
selectionMode: SelectionMode;
};
const initialState: WorkflowSettingsState = {
_version: 1,
shouldShowMinimapPanel: true,
shouldValidateGraph: true,
shouldAnimateEdges: true,
shouldSnapToGrid: false,
shouldColorEdges: true,
shouldShowEdgeLabels: false,
nodeOpacity: 1,
selectionMode: SelectionMode.Partial,
};
export const workflowSettingsSlice = createSlice({
name: 'workflowSettings',
initialState,
reducers: {
shouldShowMinimapPanelChanged: (state, action: PayloadAction<boolean>) => {
state.shouldShowMinimapPanel = action.payload;
},
shouldValidateGraphChanged: (state, action: PayloadAction<boolean>) => {
state.shouldValidateGraph = action.payload;
},
shouldAnimateEdgesChanged: (state, action: PayloadAction<boolean>) => {
state.shouldAnimateEdges = action.payload;
},
shouldShowEdgeLabelsChanged: (state, action: PayloadAction<boolean>) => {
state.shouldShowEdgeLabels = action.payload;
},
shouldSnapToGridChanged: (state, action: PayloadAction<boolean>) => {
state.shouldSnapToGrid = action.payload;
},
shouldColorEdgesChanged: (state, action: PayloadAction<boolean>) => {
state.shouldColorEdges = action.payload;
},
nodeOpacityChanged: (state, action: PayloadAction<number>) => {
state.nodeOpacity = action.payload;
},
selectionModeChanged: (state, action: PayloadAction<boolean>) => {
state.selectionMode = action.payload ? SelectionMode.Full : SelectionMode.Partial;
},
},
});
export const {
shouldAnimateEdgesChanged,
shouldColorEdgesChanged,
shouldShowMinimapPanelChanged,
shouldShowEdgeLabelsChanged,
shouldSnapToGridChanged,
shouldValidateGraphChanged,
nodeOpacityChanged,
selectionModeChanged,
} = workflowSettingsSlice.actions;
export const selectWorkflowSettingsSlice = (state: RootState) => state.workflowSettings;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateWorkflowSettingsState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};
export const workflowSettingsPersistConfig: PersistConfig<WorkflowSettingsState> = {
name: workflowSettingsSlice.name,
initialState,
migrate: migrateWorkflowSettingsState,
persistDenylist: [],
};