mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add selection mode toggle
This commit is contained in:
parent
5cf9b75d77
commit
6d10e40c9b
@ -1,5 +1,8 @@
|
|||||||
import { useToken } from '@chakra-ui/react';
|
import { useToken } from '@chakra-ui/react';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { contextMenusClosed } from 'features/ui/store/uiSlice';
|
import { contextMenusClosed } from 'features/ui/store/uiSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
@ -61,14 +64,24 @@ const nodeTypes = {
|
|||||||
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
|
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
|
||||||
const proOptions: ProOptions = { hideAttribution: true };
|
const proOptions: ProOptions = { hideAttribution: true };
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const { shouldSnapToGrid, selectionMode } = nodes;
|
||||||
|
return {
|
||||||
|
shouldSnapToGrid,
|
||||||
|
selectionMode,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
export const Flow = () => {
|
export const Flow = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const nodes = useAppSelector((state) => state.nodes.nodes);
|
const nodes = useAppSelector((state) => state.nodes.nodes);
|
||||||
const edges = useAppSelector((state) => state.nodes.edges);
|
const edges = useAppSelector((state) => state.nodes.edges);
|
||||||
const viewport = useAppSelector((state) => state.nodes.viewport);
|
const viewport = useAppSelector((state) => state.nodes.viewport);
|
||||||
const shouldSnapToGrid = useAppSelector(
|
const { shouldSnapToGrid, selectionMode } = useAppSelector(selector);
|
||||||
(state) => state.nodes.shouldSnapToGrid
|
|
||||||
);
|
|
||||||
|
|
||||||
const isValidConnection = useIsValidConnection();
|
const isValidConnection = useIsValidConnection();
|
||||||
|
|
||||||
@ -181,6 +194,7 @@ export const Flow = () => {
|
|||||||
style={{ borderRadius }}
|
style={{ borderRadius }}
|
||||||
onPaneClick={handlePaneClick}
|
onPaneClick={handlePaneClick}
|
||||||
deleteKeyCode={DELETE_KEYS}
|
deleteKeyCode={DELETE_KEYS}
|
||||||
|
selectionMode={selectionMode}
|
||||||
>
|
>
|
||||||
<TopLeftPanel />
|
<TopLeftPanel />
|
||||||
<TopCenterPanel />
|
<TopCenterPanel />
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Divider,
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
|
FormLabelProps,
|
||||||
Heading,
|
Heading,
|
||||||
Modal,
|
Modal,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
@ -13,17 +14,19 @@ import {
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import IAISwitch from 'common/components/IAISwitch';
|
import IAISwitch from 'common/components/IAISwitch';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
|
||||||
import { FaCog } from 'react-icons/fa';
|
|
||||||
import {
|
import {
|
||||||
|
selectionModeChanged,
|
||||||
shouldAnimateEdgesChanged,
|
shouldAnimateEdgesChanged,
|
||||||
shouldColorEdgesChanged,
|
shouldColorEdgesChanged,
|
||||||
shouldSnapToGridChanged,
|
shouldSnapToGridChanged,
|
||||||
shouldValidateGraphChanged,
|
shouldValidateGraphChanged,
|
||||||
} from 'features/nodes/store/nodesSlice';
|
} from 'features/nodes/store/nodesSlice';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
|
import { FaCog } from 'react-icons/fa';
|
||||||
|
import { SelectionMode } from 'reactflow';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -33,12 +36,14 @@ const selector = createSelector(
|
|||||||
shouldValidateGraph,
|
shouldValidateGraph,
|
||||||
shouldSnapToGrid,
|
shouldSnapToGrid,
|
||||||
shouldColorEdges,
|
shouldColorEdges,
|
||||||
|
selectionMode,
|
||||||
} = nodes;
|
} = nodes;
|
||||||
return {
|
return {
|
||||||
shouldAnimateEdges,
|
shouldAnimateEdges,
|
||||||
shouldValidateGraph,
|
shouldValidateGraph,
|
||||||
shouldSnapToGrid,
|
shouldSnapToGrid,
|
||||||
shouldColorEdges,
|
shouldColorEdges,
|
||||||
|
selectionModeIsChecked: selectionMode === SelectionMode.Full,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -52,6 +57,7 @@ const NodeEditorSettings = () => {
|
|||||||
shouldValidateGraph,
|
shouldValidateGraph,
|
||||||
shouldSnapToGrid,
|
shouldSnapToGrid,
|
||||||
shouldColorEdges,
|
shouldColorEdges,
|
||||||
|
selectionModeIsChecked,
|
||||||
} = useAppSelector(selector);
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
const handleChangeShouldValidate = useCallback(
|
const handleChangeShouldValidate = useCallback(
|
||||||
@ -82,6 +88,13 @@ const NodeEditorSettings = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChangeSelectionMode = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
dispatch(selectionModeChanged(e.target.checked));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
@ -105,6 +118,7 @@ const NodeEditorSettings = () => {
|
|||||||
>
|
>
|
||||||
<Heading size="sm">General</Heading>
|
<Heading size="sm">General</Heading>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
|
formLabelProps={formLabelProps}
|
||||||
onChange={handleChangeShouldAnimate}
|
onChange={handleChangeShouldAnimate}
|
||||||
isChecked={shouldAnimateEdges}
|
isChecked={shouldAnimateEdges}
|
||||||
label="Animated Edges"
|
label="Animated Edges"
|
||||||
@ -112,6 +126,7 @@ const NodeEditorSettings = () => {
|
|||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldSnapToGrid}
|
isChecked={shouldSnapToGrid}
|
||||||
onChange={handleChangeShouldSnap}
|
onChange={handleChangeShouldSnap}
|
||||||
label="Snap to Grid"
|
label="Snap to Grid"
|
||||||
@ -119,15 +134,24 @@ const NodeEditorSettings = () => {
|
|||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldColorEdges}
|
isChecked={shouldColorEdges}
|
||||||
onChange={handleChangeShouldColor}
|
onChange={handleChangeShouldColor}
|
||||||
label="Color-Code Edges"
|
label="Color-Code Edges"
|
||||||
helperText="Color-code edges according to their connected fields"
|
helperText="Color-code edges according to their connected fields"
|
||||||
/>
|
/>
|
||||||
|
<IAISwitch
|
||||||
|
formLabelProps={formLabelProps}
|
||||||
|
isChecked={selectionModeIsChecked}
|
||||||
|
onChange={handleChangeSelectionMode}
|
||||||
|
label="Fully Contain Nodes to Select"
|
||||||
|
helperText="Nodes must be fully inside the selection box to be selected"
|
||||||
|
/>
|
||||||
<Heading size="sm" pt={4}>
|
<Heading size="sm" pt={4}>
|
||||||
Advanced
|
Advanced
|
||||||
</Heading>
|
</Heading>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
|
formLabelProps={formLabelProps}
|
||||||
isChecked={shouldValidateGraph}
|
isChecked={shouldValidateGraph}
|
||||||
onChange={handleChangeShouldValidate}
|
onChange={handleChangeShouldValidate}
|
||||||
label="Validate Connections and Graph"
|
label="Validate Connections and Graph"
|
||||||
@ -142,3 +166,7 @@ const NodeEditorSettings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(NodeEditorSettings);
|
export default memo(NodeEditorSettings);
|
||||||
|
|
||||||
|
const formLabelProps: FormLabelProps = {
|
||||||
|
fontWeight: 600,
|
||||||
|
};
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
Node,
|
Node,
|
||||||
NodeChange,
|
NodeChange,
|
||||||
OnConnectStartParams,
|
OnConnectStartParams,
|
||||||
|
SelectionMode,
|
||||||
Viewport,
|
Viewport,
|
||||||
} from 'reactflow';
|
} from 'reactflow';
|
||||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||||
@ -103,6 +104,7 @@ export const initialNodesState: NodesState = {
|
|||||||
mouseOverField: null,
|
mouseOverField: null,
|
||||||
nodesToCopy: [],
|
nodesToCopy: [],
|
||||||
edgesToCopy: [],
|
edgesToCopy: [],
|
||||||
|
selectionMode: SelectionMode.Partial,
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldValueAction<T extends InputFieldValue> = PayloadAction<{
|
type FieldValueAction<T extends InputFieldValue> = PayloadAction<{
|
||||||
@ -721,6 +723,11 @@ const nodesSlice = createSlice({
|
|||||||
addNodePopoverToggled: (state) => {
|
addNodePopoverToggled: (state) => {
|
||||||
state.isAddNodePopoverOpen = !state.isAddNodePopoverOpen;
|
state.isAddNodePopoverOpen = !state.isAddNodePopoverOpen;
|
||||||
},
|
},
|
||||||
|
selectionModeChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.selectionMode = action.payload
|
||||||
|
? SelectionMode.Full
|
||||||
|
: SelectionMode.Partial;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(receivedOpenAPISchema.pending, (state) => {
|
builder.addCase(receivedOpenAPISchema.pending, (state) => {
|
||||||
@ -832,6 +839,7 @@ export const {
|
|||||||
addNodePopoverOpened,
|
addNodePopoverOpened,
|
||||||
addNodePopoverClosed,
|
addNodePopoverClosed,
|
||||||
addNodePopoverToggled,
|
addNodePopoverToggled,
|
||||||
|
selectionModeChanged,
|
||||||
} = nodesSlice.actions;
|
} = nodesSlice.actions;
|
||||||
|
|
||||||
export default nodesSlice.reducer;
|
export default nodesSlice.reducer;
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { Edge, Node, OnConnectStartParams, Viewport } from 'reactflow';
|
import {
|
||||||
|
Edge,
|
||||||
|
Node,
|
||||||
|
OnConnectStartParams,
|
||||||
|
SelectionMode,
|
||||||
|
Viewport,
|
||||||
|
} from 'reactflow';
|
||||||
import {
|
import {
|
||||||
FieldIdentifier,
|
FieldIdentifier,
|
||||||
FieldType,
|
FieldType,
|
||||||
@ -32,4 +38,5 @@ export type NodesState = {
|
|||||||
nodesToCopy: Node<NodeData>[];
|
nodesToCopy: Node<NodeData>[];
|
||||||
edgesToCopy: Edge<InvocationEdgeExtra>[];
|
edgesToCopy: Edge<InvocationEdgeExtra>[];
|
||||||
isAddNodePopoverOpen: boolean;
|
isAddNodePopoverOpen: boolean;
|
||||||
|
selectionMode: SelectionMode;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user