mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): use nanostores for useMouseOverNode
This greatly reduces the weight of the event handlers.
This commit is contained in:
parent
b490c8ae27
commit
5168415999
@ -1,9 +1,8 @@
|
|||||||
import { useToken } from '@chakra-ui/react';
|
import { useToken } from '@chakra-ui/react';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
|
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||||
import {
|
import {
|
||||||
connectionEnded,
|
connectionEnded,
|
||||||
connectionMade,
|
connectionMade,
|
||||||
@ -67,14 +66,6 @@ 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 = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
|
||||||
const { shouldSnapToGrid, selectionMode } = nodes;
|
|
||||||
return {
|
|
||||||
shouldSnapToGrid,
|
|
||||||
selectionMode,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const snapGrid: [number, number] = [25, 25];
|
const snapGrid: [number, number] = [25, 25];
|
||||||
|
|
||||||
export const Flow = memo(() => {
|
export const Flow = memo(() => {
|
||||||
@ -82,9 +73,12 @@ export const Flow = memo(() => {
|
|||||||
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, selectionMode } = useAppSelector(selector);
|
const shouldSnapToGrid = useAppSelector(
|
||||||
|
(state) => state.nodes.shouldSnapToGrid
|
||||||
|
);
|
||||||
|
const selectionMode = useAppSelector((state) => state.nodes.selectionMode);
|
||||||
const flowWrapper = useRef<HTMLDivElement>(null);
|
const flowWrapper = useRef<HTMLDivElement>(null);
|
||||||
const cursorPosition = useRef<XYPosition>();
|
const cursorPosition = useRef<XYPosition | null>(null);
|
||||||
const isValidConnection = useIsValidConnection();
|
const isValidConnection = useIsValidConnection();
|
||||||
|
|
||||||
const [borderRadius] = useToken('radii', ['base']);
|
const [borderRadius] = useToken('radii', ['base']);
|
||||||
@ -125,7 +119,15 @@ export const Flow = memo(() => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onConnectEnd: OnConnectEnd = useCallback(() => {
|
const onConnectEnd: OnConnectEnd = useCallback(() => {
|
||||||
dispatch(connectionEnded({ cursorPosition: cursorPosition.current }));
|
if (!cursorPosition.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
connectionEnded({
|
||||||
|
cursorPosition: cursorPosition.current,
|
||||||
|
mouseOverNodeId: $mouseOverNode.get(),
|
||||||
|
})
|
||||||
|
);
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const onEdgesDelete: OnEdgesDelete = useCallback(
|
const onEdgesDelete: OnEdgesDelete = useCallback(
|
||||||
@ -169,10 +171,11 @@ export const Flow = memo(() => {
|
|||||||
|
|
||||||
const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
||||||
if (flowWrapper.current?.getBoundingClientRect()) {
|
if (flowWrapper.current?.getBoundingClientRect()) {
|
||||||
cursorPosition.current = $flow.get()?.screenToFlowPosition({
|
cursorPosition.current =
|
||||||
x: event.clientX,
|
$flow.get()?.screenToFlowPosition({
|
||||||
y: event.clientY,
|
x: event.clientX,
|
||||||
});
|
y: event.clientY,
|
||||||
|
}) ?? null;
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -245,6 +248,9 @@ export const Flow = memo(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useHotkeys(['Ctrl+v', 'Meta+v'], (e) => {
|
useHotkeys(['Ctrl+v', 'Meta+v'], (e) => {
|
||||||
|
if (!cursorPosition.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dispatch(selectionPasted({ cursorPosition: cursorPosition.current }));
|
dispatch(selectionPasted({ cursorPosition: cursorPosition.current }));
|
||||||
});
|
});
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { mouseOverFieldChanged } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
|
|
||||||
export const useIsMouseOverField = (nodeId: string, fieldName: string) => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
|
||||||
createMemoizedSelector(
|
|
||||||
stateSelector,
|
|
||||||
({ nodes }) =>
|
|
||||||
nodes.mouseOverField?.nodeId === nodeId &&
|
|
||||||
nodes.mouseOverField?.fieldName === fieldName
|
|
||||||
),
|
|
||||||
[fieldName, nodeId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const isMouseOverField = useAppSelector(selector);
|
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
|
||||||
dispatch(mouseOverFieldChanged({ nodeId, fieldName }));
|
|
||||||
}, [dispatch, fieldName, nodeId]);
|
|
||||||
|
|
||||||
const handleMouseOut = useCallback(() => {
|
|
||||||
dispatch(mouseOverFieldChanged(null));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
return { isMouseOverField, handleMouseOver, handleMouseOut };
|
|
||||||
};
|
|
@ -1,29 +1,24 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { useStore } from '@nanostores/react';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { atom } from 'nanostores';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { mouseOverNodeChanged } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
|
export const $mouseOverNode = atom<string | null>(null);
|
||||||
|
|
||||||
export const useMouseOverNode = (nodeId: string) => {
|
export const useMouseOverNode = (nodeId: string) => {
|
||||||
const dispatch = useAppDispatch();
|
const mouseOverNode = useStore($mouseOverNode);
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
const isMouseOverNode = useMemo(
|
||||||
createMemoizedSelector(
|
() => mouseOverNode === nodeId,
|
||||||
stateSelector,
|
[mouseOverNode, nodeId]
|
||||||
({ nodes }) => nodes.mouseOverNode === nodeId
|
|
||||||
),
|
|
||||||
[nodeId]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isMouseOverNode = useAppSelector(selector);
|
|
||||||
|
|
||||||
const handleMouseOver = useCallback(() => {
|
const handleMouseOver = useCallback(() => {
|
||||||
!isMouseOverNode && dispatch(mouseOverNodeChanged(nodeId));
|
$mouseOverNode.set(nodeId);
|
||||||
}, [dispatch, nodeId, isMouseOverNode]);
|
}, [nodeId]);
|
||||||
|
|
||||||
const handleMouseOut = useCallback(() => {
|
const handleMouseOut = useCallback(() => {
|
||||||
isMouseOverNode && dispatch(mouseOverNodeChanged(null));
|
$mouseOverNode.set(null);
|
||||||
}, [dispatch, isMouseOverNode]);
|
}, []);
|
||||||
|
|
||||||
return { isMouseOverNode, handleMouseOver, handleMouseOut };
|
return { isMouseOverNode, handleMouseOver, handleMouseOut };
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,6 @@ import type {
|
|||||||
ColorFieldValue,
|
ColorFieldValue,
|
||||||
ControlNetModelFieldValue,
|
ControlNetModelFieldValue,
|
||||||
EnumFieldValue,
|
EnumFieldValue,
|
||||||
FieldIdentifier,
|
|
||||||
FieldValue,
|
FieldValue,
|
||||||
FloatFieldValue,
|
FloatFieldValue,
|
||||||
ImageFieldValue,
|
ImageFieldValue,
|
||||||
@ -116,8 +115,6 @@ export const initialNodesState: NodesState = {
|
|||||||
selectedEdges: [],
|
selectedEdges: [],
|
||||||
nodeExecutionStates: {},
|
nodeExecutionStates: {},
|
||||||
viewport: { x: 0, y: 0, zoom: 1 },
|
viewport: { x: 0, y: 0, zoom: 1 },
|
||||||
mouseOverField: null,
|
|
||||||
mouseOverNode: null,
|
|
||||||
nodesToCopy: [],
|
nodesToCopy: [],
|
||||||
edgesToCopy: [],
|
edgesToCopy: [],
|
||||||
selectionMode: SelectionMode.Partial,
|
selectionMode: SelectionMode.Partial,
|
||||||
@ -272,11 +269,18 @@ const nodesSlice = createSlice({
|
|||||||
|
|
||||||
state.connectionMade = true;
|
state.connectionMade = true;
|
||||||
},
|
},
|
||||||
connectionEnded: (state, action) => {
|
connectionEnded: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
cursorPosition: XYPosition;
|
||||||
|
mouseOverNodeId: string | null;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const { cursorPosition, mouseOverNodeId } = action.payload;
|
||||||
if (!state.connectionMade) {
|
if (!state.connectionMade) {
|
||||||
if (state.mouseOverNode) {
|
if (mouseOverNodeId) {
|
||||||
const nodeIndex = state.nodes.findIndex(
|
const nodeIndex = state.nodes.findIndex(
|
||||||
(n) => n.id === state.mouseOverNode
|
(n) => n.id === mouseOverNodeId
|
||||||
);
|
);
|
||||||
const mouseOverNode = state.nodes?.[nodeIndex];
|
const mouseOverNode = state.nodes?.[nodeIndex];
|
||||||
if (mouseOverNode && state.connectionStartParams) {
|
if (mouseOverNode && state.connectionStartParams) {
|
||||||
@ -308,7 +312,7 @@ const nodesSlice = createSlice({
|
|||||||
state.connectionStartParams = null;
|
state.connectionStartParams = null;
|
||||||
state.connectionStartFieldType = null;
|
state.connectionStartFieldType = null;
|
||||||
} else {
|
} else {
|
||||||
state.addNewNodePosition = action.payload.cursorPosition;
|
state.addNewNodePosition = cursorPosition;
|
||||||
state.isAddNodePopoverOpen = true;
|
state.isAddNodePopoverOpen = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -681,15 +685,6 @@ const nodesSlice = createSlice({
|
|||||||
viewportChanged: (state, action: PayloadAction<Viewport>) => {
|
viewportChanged: (state, action: PayloadAction<Viewport>) => {
|
||||||
state.viewport = action.payload;
|
state.viewport = action.payload;
|
||||||
},
|
},
|
||||||
mouseOverFieldChanged: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<FieldIdentifier | null>
|
|
||||||
) => {
|
|
||||||
state.mouseOverField = action.payload;
|
|
||||||
},
|
|
||||||
mouseOverNodeChanged: (state, action: PayloadAction<string | null>) => {
|
|
||||||
state.mouseOverNode = action.payload;
|
|
||||||
},
|
|
||||||
selectedAll: (state) => {
|
selectedAll: (state) => {
|
||||||
state.nodes = applyNodeChanges(
|
state.nodes = applyNodeChanges(
|
||||||
state.nodes.map((n) => ({ id: n.id, type: 'select', selected: true })),
|
state.nodes.map((n) => ({ id: n.id, type: 'select', selected: true })),
|
||||||
@ -929,8 +924,6 @@ export const {
|
|||||||
fieldSchedulerValueChanged,
|
fieldSchedulerValueChanged,
|
||||||
fieldStringValueChanged,
|
fieldStringValueChanged,
|
||||||
fieldVaeModelValueChanged,
|
fieldVaeModelValueChanged,
|
||||||
mouseOverFieldChanged,
|
|
||||||
mouseOverNodeChanged,
|
|
||||||
nodeAdded,
|
nodeAdded,
|
||||||
nodeReplaced,
|
nodeReplaced,
|
||||||
nodeEditorReset,
|
nodeEditorReset,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { FieldIdentifier, FieldType } from 'features/nodes/types/field';
|
import type { FieldType } from 'features/nodes/types/field';
|
||||||
import type {
|
import type {
|
||||||
AnyNode,
|
AnyNode,
|
||||||
InvocationNodeEdge,
|
InvocationNodeEdge,
|
||||||
@ -32,8 +32,6 @@ export type NodesState = {
|
|||||||
nodeExecutionStates: Record<string, NodeExecutionState>;
|
nodeExecutionStates: Record<string, NodeExecutionState>;
|
||||||
viewport: Viewport;
|
viewport: Viewport;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
mouseOverField: FieldIdentifier | null;
|
|
||||||
mouseOverNode: string | null;
|
|
||||||
nodesToCopy: AnyNode[];
|
nodesToCopy: AnyNode[];
|
||||||
edgesToCopy: InvocationNodeEdge[];
|
edgesToCopy: InvocationNodeEdge[];
|
||||||
isAddNodePopoverOpen: boolean;
|
isAddNodePopoverOpen: boolean;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user