feat(ui): remove extraneous selectedEdges and selectedNodes state

This commit is contained in:
psychedelicious 2024-05-16 21:10:43 +10:00
parent b0c7c7cb47
commit 6cf5b402c6
8 changed files with 92 additions and 86 deletions

View File

@ -15,8 +15,6 @@ import {
nodesDeleted,
redo,
selectedAll,
selectedEdgesChanged,
selectedNodesChanged,
undo,
viewportChanged,
} from 'features/nodes/store/nodesSlice';
@ -32,7 +30,6 @@ import type {
OnMoveEnd,
OnNodesChange,
OnNodesDelete,
OnSelectionChangeFunc,
ProOptions,
ReactFlowProps,
} from 'reactflow';
@ -111,14 +108,6 @@ export const Flow = memo(() => {
[dispatch]
);
const handleSelectionChange: OnSelectionChangeFunc = useCallback(
({ nodes, edges }) => {
dispatch(selectedNodesChanged(nodes ? nodes.map((n) => n.id) : []));
dispatch(selectedEdgesChanged(edges ? edges.map((e) => e.id) : []));
},
[dispatch]
);
const handleMoveEnd: OnMoveEnd = useCallback(
(e, viewport) => {
dispatch(viewportChanged(viewport));
@ -258,7 +247,6 @@ export const Flow = memo(() => {
onConnectEnd={onConnectEnd}
onMoveEnd={handleMoveEnd}
connectionLineComponent={CustomConnectionLine}
onSelectionChange={handleSelectionChange}
isValidConnection={isValidConnection}
minZoom={0.1}
snapToGrid={shouldSnapToGrid}

View File

@ -3,27 +3,21 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector(selectNodesSlice, (nodes) => {
const lastSelectedNodeId = nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find((node) => node.id === lastSelectedNodeId);
return {
data: lastSelectedNode?.data,
};
});
const selector = createMemoizedSelector(selectNodesSlice, (nodes) => selectLastSelectedNode(nodes));
const InspectorDataTab = () => {
const { t } = useTranslation();
const { data } = useAppSelector(selector);
const lastSelectedNode = useAppSelector(selector);
if (!data) {
if (!lastSelectedNode) {
return <IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />;
}
return <DataViewer data={data} label="Node Data" />;
return <DataViewer data={lastSelectedNode.data} label="Node Data" />;
};
export default memo(InspectorDataTab);

View File

@ -7,6 +7,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -18,8 +19,7 @@ const InspectorDetailsTab = () => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const lastSelectedNodeId = nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find((node) => node.id === lastSelectedNodeId);
const lastSelectedNode = selectLastSelectedNode(nodes);
const lastSelectedNodeTemplate = lastSelectedNode ? templates[lastSelectedNode.data.type] : undefined;
if (!isInvocationNode(lastSelectedNode) || !lastSelectedNodeTemplate) {

View File

@ -6,6 +6,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -19,11 +20,10 @@ const InspectorOutputsTab = () => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const lastSelectedNodeId = nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find((node) => node.id === lastSelectedNodeId);
const lastSelectedNode = selectLastSelectedNode(nodes);
const lastSelectedNodeTemplate = lastSelectedNode ? templates[lastSelectedNode.data.type] : undefined;
const nes = nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
const nes = nodes.nodeExecutionStates[lastSelectedNode?.id ?? '__UNKNOWN_NODE__'];
if (!isInvocationNode(lastSelectedNode) || !nes || !lastSelectedNodeTemplate) {
return;

View File

@ -4,6 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode } from 'features/nodes/store/selectors';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -12,8 +13,7 @@ const NodeTemplateInspector = () => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
const lastSelectedNodeId = nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find((node) => node.id === lastSelectedNodeId);
const lastSelectedNode = selectLastSelectedNode(nodes);
const lastSelectedNodeTemplate = lastSelectedNode ? templates[lastSelectedNode.data.type] : undefined;
return lastSelectedNodeTemplate;

View File

@ -74,8 +74,6 @@ const initialNodesState: NodesState = {
_version: 1,
nodes: [],
edges: [],
selectedNodes: [],
selectedEdges: [],
nodeExecutionStates: {},
viewport: { x: 0, y: 0, zoom: 1 },
};
@ -351,12 +349,6 @@ export const nodesSlice = createSlice({
state.nodes
);
},
selectedNodesChanged: (state, action: PayloadAction<string[]>) => {
state.selectedNodes = action.payload;
},
selectedEdgesChanged: (state, action: PayloadAction<string[]>) => {
state.selectedEdges = action.payload;
},
fieldValueReset: (state, action: FieldValueAction<StatefulFieldValue>) => {
fieldValueReducer(state, action, zStatefulFieldValue);
},
@ -593,8 +585,6 @@ export const {
nodeUseCacheChanged,
notesNodeValueChanged,
selectedAll,
selectedEdgesChanged,
selectedNodesChanged,
selectionPasted,
viewportChanged,
edgeAdded,
@ -602,6 +592,78 @@ export const {
redo,
} = nodesSlice.actions;
export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({});
export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null);
export const $isModifyingEdge = atom(false);
export const $isAddNodePopoverOpen = atom(false);
export const closeAddNodePopover = () => {
$isAddNodePopoverOpen.set(false);
$pendingConnection.set(null);
};
export const openAddNodePopover = () => {
$isAddNodePopoverOpen.set(true);
};
export const selectNodesSlice = (state: RootState) => state.nodes.present;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateNodesState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};
export const nodesPersistConfig: PersistConfig<NodesState> = {
name: nodesSlice.name,
initialState: initialNodesState,
migrate: migrateNodesState,
persistDenylist: [],
};
const selectionMatcher = isAnyOf(selectedAll, selectionPasted, nodeExclusivelySelected);
const isSelectionAction = (action: UnknownAction) => {
if (selectionMatcher(action)) {
return true;
}
if (nodesChanged.match(action)) {
if (action.payload.every((change) => change.type === 'select')) {
return true;
}
}
return false;
};
const individualGroupByMatcher = isAnyOf(nodesChanged, viewportChanged);
export const nodesUndoableConfig: UndoableOptions<NodesState, UnknownAction> = {
limit: 64,
undoType: nodesSlice.actions.undo.type,
redoType: nodesSlice.actions.redo.type,
groupBy: (action, state, history) => {
if (isSelectionAction(action)) {
// Changes to selection should never be recorded on their own
return history.group;
}
if (individualGroupByMatcher(action)) {
return action.type;
}
return null;
},
filter: (action, _state, _history) => {
if (nodesChanged.match(action)) {
if (action.payload.every((change) => change.type === 'dimensions')) {
return false;
}
}
return true;
},
};
// This is used for tracking `state.workflow.isTouched`
export const isAnyNodeOrEdgeMutation = isAnyOf(
connectionMade,
@ -636,47 +698,3 @@ export const isAnyNodeOrEdgeMutation = isAnyOf(
selectionPasted,
edgeAdded
);
export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({});
export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null);
export const $isModifyingEdge = atom(false);
export const $isAddNodePopoverOpen = atom(false);
export const closeAddNodePopover = () => {
$isAddNodePopoverOpen.set(false);
$pendingConnection.set(null);
};
export const openAddNodePopover = () => {
$isAddNodePopoverOpen.set(true);
};
export const selectNodesSlice = (state: RootState) => state.nodes.present;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const migrateNodesState = (state: any): any => {
if (!('_version' in state)) {
state._version = 1;
}
return state;
};
export const nodesPersistConfig: PersistConfig<NodesState> = {
name: nodesSlice.name,
initialState: initialNodesState,
migrate: migrateNodesState,
persistDenylist: ['selectedNodes', 'selectedEdges'],
};
export const nodesUndoableConfig: UndoableOptions<NodesState, UnknownAction> = {
limit: 64,
undoType: nodesSlice.actions.undo.type,
redoType: nodesSlice.actions.redo.type,
groupBy: (action, state, history) => {
return null;
},
filter: (action, _state, _history) => {
return true;
},
};

View File

@ -28,3 +28,11 @@ export const selectFieldInputInstance = (
const data = selectNodeData(nodesSlice, nodeId);
return data?.inputs[fieldName] ?? null;
};
export const selectLastSelectedNode = (nodesSlice: NodesState) => {
const selectedNodes = nodesSlice.nodes.filter((n) => n.selected);
if (selectedNodes.length === 1) {
return selectedNodes[0];
}
return null;
};

View File

@ -26,8 +26,6 @@ export type NodesState = {
_version: 1;
nodes: AnyNode[];
edges: InvocationNodeEdge[];
selectedNodes: string[];
selectedEdges: string[];
nodeExecutionStates: Record<string, NodeExecutionState>;
viewport: Viewport;
};