feat(ui): makeConnectionErrorSelector now creates a parameterized selector

This commit is contained in:
psychedelicious 2024-05-18 17:28:56 +10:00
parent 6658897210
commit 9d127fee6b
2 changed files with 93 additions and 97 deletions

View File

@ -34,16 +34,8 @@ export const useConnectionState = ({ nodeId, fieldName, kind }: UseConnectionSta
); );
const selectConnectionError = useMemo( const selectConnectionError = useMemo(
() => () => makeConnectionErrorSelector(templates, nodeId, fieldName, kind === 'inputs' ? 'target' : 'source', fieldType),
makeConnectionErrorSelector( [templates, nodeId, fieldName, kind, fieldType]
templates,
pendingConnection,
nodeId,
fieldName,
kind === 'inputs' ? 'target' : 'source',
fieldType
),
[templates, pendingConnection, nodeId, fieldName, kind, fieldType]
); );
const isConnected = useAppSelector(selectIsConnected); const isConnected = useAppSelector(selectIsConnected);
@ -58,7 +50,7 @@ export const useConnectionState = ({ nodeId, fieldName, kind }: UseConnectionSta
pendingConnection.fieldTemplate.fieldKind === { inputs: 'input', outputs: 'output' }[kind] pendingConnection.fieldTemplate.fieldKind === { inputs: 'input', outputs: 'output' }[kind]
); );
}, [fieldName, kind, nodeId, pendingConnection]); }, [fieldName, kind, nodeId, pendingConnection]);
const connectionError = useAppSelector(selectConnectionError); const connectionError = useAppSelector((s) => selectConnectionError(s, pendingConnection));
const shouldDim = useMemo( const shouldDim = useMemo(
() => Boolean(isConnectionInProgress && connectionError && !isConnectionStartField), () => Boolean(isConnectionInProgress && connectionError && !isConnectionStartField),

View File

@ -1,7 +1,8 @@
import graphlib from '@dagrejs/graphlib'; import graphlib from '@dagrejs/graphlib';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice'; import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import type { PendingConnection, Templates } from 'features/nodes/store/types'; import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types';
import { type FieldType, isStatefulFieldType } from 'features/nodes/types/field'; import { type FieldType, isStatefulFieldType } from 'features/nodes/types/field';
import type { AnyNode, InvocationNode, InvocationNodeEdge, InvocationTemplate } from 'features/nodes/types/invocation'; import type { AnyNode, InvocationNode, InvocationNodeEdge, InvocationTemplate } from 'features/nodes/types/invocation';
import i18n from 'i18next'; import i18n from 'i18next';
@ -190,105 +191,108 @@ export const getCollectItemType = (
*/ */
export const makeConnectionErrorSelector = ( export const makeConnectionErrorSelector = (
templates: Templates, templates: Templates,
pendingConnection: PendingConnection | null,
nodeId: string, nodeId: string,
fieldName: string, fieldName: string,
handleType: HandleType, handleType: HandleType,
fieldType: FieldType fieldType: FieldType
) => { ) => {
return createMemoizedSelector(selectNodesSlice, (nodesSlice) => { return createMemoizedSelector(
const { nodes, edges } = nodesSlice; selectNodesSlice,
(state: RootState, pendingConnection: PendingConnection | null) => pendingConnection,
(nodesSlice: NodesState, pendingConnection: PendingConnection | null) => {
const { nodes, edges } = nodesSlice;
if (!pendingConnection) { if (!pendingConnection) {
return i18n.t('nodes.noConnectionInProgress'); return i18n.t('nodes.noConnectionInProgress');
}
const connectionNodeId = pendingConnection.node.id;
const connectionFieldName = pendingConnection.fieldTemplate.name;
const connectionHandleType = pendingConnection.fieldTemplate.fieldKind === 'input' ? 'target' : 'source';
const connectionStartFieldType = pendingConnection.fieldTemplate.type;
if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
return i18n.t('nodes.noConnectionData');
}
const targetType = handleType === 'target' ? fieldType : connectionStartFieldType;
const sourceType = handleType === 'source' ? fieldType : connectionStartFieldType;
if (nodeId === connectionNodeId) {
return i18n.t('nodes.cannotConnectToSelf');
}
if (handleType === connectionHandleType) {
if (handleType === 'source') {
return i18n.t('nodes.cannotConnectOutputToOutput');
} }
return i18n.t('nodes.cannotConnectInputToInput');
}
// we have to figure out which is the target and which is the source const connectionNodeId = pendingConnection.node.id;
const targetNodeId = handleType === 'target' ? nodeId : connectionNodeId; const connectionFieldName = pendingConnection.fieldTemplate.name;
const targetFieldName = handleType === 'target' ? fieldName : connectionFieldName; const connectionHandleType = pendingConnection.fieldTemplate.fieldKind === 'input' ? 'target' : 'source';
const sourceNodeId = handleType === 'source' ? nodeId : connectionNodeId; const connectionStartFieldType = pendingConnection.fieldTemplate.type;
const sourceFieldName = handleType === 'source' ? fieldName : connectionFieldName;
if ( if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
edges.find((edge) => { return i18n.t('nodes.noConnectionData');
edge.target === targetNodeId && }
edge.targetHandle === targetFieldName &&
edge.source === sourceNodeId &&
edge.sourceHandle === sourceFieldName;
})
) {
// We already have a connection from this source to this target
return i18n.t('nodes.cannotDuplicateConnection');
}
const targetNode = nodes.find((node) => node.id === targetNodeId); const targetType = handleType === 'target' ? fieldType : connectionStartFieldType;
assert(targetNode, `Target node not found: ${targetNodeId}`); const sourceType = handleType === 'source' ? fieldType : connectionStartFieldType;
const targetTemplate = templates[targetNode.data.type];
assert(targetTemplate, `Target template not found: ${targetNode.data.type}`);
if (targetTemplate.inputs[targetFieldName]?.input === 'direct') { if (nodeId === connectionNodeId) {
return i18n.t('nodes.cannotConnectToDirectInput'); return i18n.t('nodes.cannotConnectToSelf');
} }
if (targetNode.data.type === 'collect' && targetFieldName === 'item') {
// Collect nodes shouldn't mix and match field types if (handleType === connectionHandleType) {
const collectItemType = getCollectItemType(templates, nodes, edges, targetNode.id); if (handleType === 'source') {
if (collectItemType) { return i18n.t('nodes.cannotConnectOutputToOutput');
if (!areTypesEqual(sourceType, collectItemType)) { }
return i18n.t('nodes.cannotMixAndMatchCollectionItemTypes'); return i18n.t('nodes.cannotConnectInputToInput');
}
// we have to figure out which is the target and which is the source
const targetNodeId = handleType === 'target' ? nodeId : connectionNodeId;
const targetFieldName = handleType === 'target' ? fieldName : connectionFieldName;
const sourceNodeId = handleType === 'source' ? nodeId : connectionNodeId;
const sourceFieldName = handleType === 'source' ? fieldName : connectionFieldName;
if (
edges.find((edge) => {
edge.target === targetNodeId &&
edge.targetHandle === targetFieldName &&
edge.source === sourceNodeId &&
edge.sourceHandle === sourceFieldName;
})
) {
// We already have a connection from this source to this target
return i18n.t('nodes.cannotDuplicateConnection');
}
const targetNode = nodes.find((node) => node.id === targetNodeId);
assert(targetNode, `Target node not found: ${targetNodeId}`);
const targetTemplate = templates[targetNode.data.type];
assert(targetTemplate, `Target template not found: ${targetNode.data.type}`);
if (targetTemplate.inputs[targetFieldName]?.input === 'direct') {
return i18n.t('nodes.cannotConnectToDirectInput');
}
if (targetNode.data.type === 'collect' && targetFieldName === 'item') {
// Collect nodes shouldn't mix and match field types
const collectItemType = getCollectItemType(templates, nodes, edges, targetNode.id);
if (collectItemType) {
if (!areTypesEqual(sourceType, collectItemType)) {
return i18n.t('nodes.cannotMixAndMatchCollectionItemTypes');
}
} }
} }
if (
edges.find((edge) => {
return edge.target === targetNodeId && edge.targetHandle === targetFieldName;
}) &&
// except CollectionItem inputs can have multiples
targetType.name !== 'CollectionItemField'
) {
return i18n.t('nodes.inputMayOnlyHaveOneConnection');
}
if (!validateSourceAndTargetTypes(sourceType, targetType)) {
return i18n.t('nodes.fieldTypesMustMatch');
}
const hasCycles = getHasCycles(
connectionHandleType === 'source' ? connectionNodeId : nodeId,
connectionHandleType === 'source' ? nodeId : connectionNodeId,
nodes,
edges
);
if (hasCycles) {
return i18n.t('nodes.connectionWouldCreateCycle');
}
return;
} }
);
if (
edges.find((edge) => {
return edge.target === targetNodeId && edge.targetHandle === targetFieldName;
}) &&
// except CollectionItem inputs can have multiples
targetType.name !== 'CollectionItemField'
) {
return i18n.t('nodes.inputMayOnlyHaveOneConnection');
}
if (!validateSourceAndTargetTypes(sourceType, targetType)) {
return i18n.t('nodes.fieldTypesMustMatch');
}
const hasCycles = getHasCycles(
connectionHandleType === 'source' ? connectionNodeId : nodeId,
connectionHandleType === 'source' ? nodeId : connectionNodeId,
nodes,
edges
);
if (hasCycles) {
return i18n.t('nodes.connectionWouldCreateCycle');
}
return;
});
}; };
/** /**