feat(ui): upgrade redux and RTK

There are a few breaking changes, which I've addressed.

The vast majority of changes are related to new handling of `reselect`'s `createSelector` options.

For better or worse, we memoize just about all our selectors using lodash `isEqual` for `resultEqualityCheck`. The upgrade requires we explicitly set the `memoize` option to `lruMemoize` to continue using lodash here.

Doing that required changing our `defaultSelectorOptions`.

Instead of changing that and finding dozens of instances where we weren't using that and instead were defining selector options manually, I've created a pre-configured selector: `createMemoizedSelector`.

This is now used everywhere instead of `createSelector`.
This commit is contained in:
psychedelicious
2023-12-09 00:16:02 +11:00
parent 99f14b1dfe
commit 72cb8b83fe
215 changed files with 1651 additions and 2444 deletions

View File

@ -5,11 +5,10 @@ import {
PopoverBody,
PopoverContent,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppToaster } from 'app/components/Toaster';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
import {
@ -62,58 +61,54 @@ const AddNodePopover = () => {
(state) => state.nodes.connectionStartParams?.handleType
);
const selector = createSelector(
[stateSelector],
({ nodes }) => {
// If we have a connection in progress, we need to filter the node choices
const filteredNodeTemplates = fieldFilter
? filter(nodes.nodeTemplates, (template) => {
const handles =
handleFilter == 'source' ? template.inputs : template.outputs;
const selector = createMemoizedSelector([stateSelector], ({ nodes }) => {
// If we have a connection in progress, we need to filter the node choices
const filteredNodeTemplates = fieldFilter
? filter(nodes.nodeTemplates, (template) => {
const handles =
handleFilter == 'source' ? template.inputs : template.outputs;
return some(handles, (handle) => {
const sourceType =
handleFilter == 'source' ? fieldFilter : handle.type;
const targetType =
handleFilter == 'target' ? fieldFilter : handle.type;
return some(handles, (handle) => {
const sourceType =
handleFilter == 'source' ? fieldFilter : handle.type;
const targetType =
handleFilter == 'target' ? fieldFilter : handle.type;
return validateSourceAndTargetTypes(sourceType, targetType);
});
})
: map(nodes.nodeTemplates);
return validateSourceAndTargetTypes(sourceType, targetType);
});
})
: map(nodes.nodeTemplates);
const data: NodeTemplate[] = map(filteredNodeTemplates, (template) => {
return {
label: template.title,
value: template.type,
description: template.description,
tags: template.tags,
};
const data: NodeTemplate[] = map(filteredNodeTemplates, (template) => {
return {
label: template.title,
value: template.type,
description: template.description,
tags: template.tags,
};
});
//We only want these nodes if we're not filtered
if (fieldFilter === null) {
data.push({
label: t('nodes.currentImage'),
value: 'current_image',
description: t('nodes.currentImageDescription'),
tags: ['progress'],
});
//We only want these nodes if we're not filtered
if (fieldFilter === null) {
data.push({
label: t('nodes.currentImage'),
value: 'current_image',
description: t('nodes.currentImageDescription'),
tags: ['progress'],
});
data.push({
label: t('nodes.notes'),
value: 'notes',
description: t('nodes.notesDescription'),
tags: ['notes'],
});
}
data.push({
label: t('nodes.notes'),
value: 'notes',
description: t('nodes.notesDescription'),
tags: ['notes'],
});
}
data.sort((a, b) => a.label.localeCompare(b.label));
data.sort((a, b) => a.label.localeCompare(b.label));
return { data };
},
defaultSelectorOptions
);
return { data };
});
const { data } = useAppSelector(selector);
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);

View File

@ -1,8 +1,7 @@
import { useToken } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
import {
connectionEnded,
@ -67,17 +66,13 @@ const nodeTypes = {
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
const proOptions: ProOptions = { hideAttribution: true };
const selector = createSelector(
stateSelector,
({ nodes }) => {
const { shouldSnapToGrid, selectionMode } = nodes;
return {
shouldSnapToGrid,
selectionMode,
};
},
defaultSelectorOptions
);
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const { shouldSnapToGrid, selectionMode } = nodes;
return {
shouldSnapToGrid,
selectionMode,
};
});
export const Flow = () => {
const dispatch = useAppDispatch();

View File

@ -1,12 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { memo } from 'react';
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
const selector = createSelector(stateSelector, ({ nodes }) => {
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const { shouldAnimateEdges, connectionStartFieldType, shouldColorEdges } =
nodes;

View File

@ -1,6 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getFieldColor } from './getEdgeColor';
@ -12,31 +11,26 @@ export const makeEdgeSelector = (
targetHandleId: string | null | undefined,
selected?: boolean
) =>
createSelector(
stateSelector,
({ nodes }) => {
const sourceNode = nodes.nodes.find((node) => node.id === source);
const targetNode = nodes.nodes.find((node) => node.id === target);
createMemoizedSelector(stateSelector, ({ nodes }) => {
const sourceNode = nodes.nodes.find((node) => node.id === source);
const targetNode = nodes.nodes.find((node) => node.id === target);
const isInvocationToInvocationEdge =
isInvocationNode(sourceNode) && isInvocationNode(targetNode);
const isInvocationToInvocationEdge =
isInvocationNode(sourceNode) && isInvocationNode(targetNode);
const isSelected =
sourceNode?.selected || targetNode?.selected || selected;
const sourceType = isInvocationToInvocationEdge
? sourceNode?.data?.outputs[sourceHandleId || '']?.type
: undefined;
const isSelected = sourceNode?.selected || targetNode?.selected || selected;
const sourceType = isInvocationToInvocationEdge
? sourceNode?.data?.outputs[sourceHandleId || '']?.type
: undefined;
const stroke =
sourceType && nodes.shouldColorEdges
? getFieldColor(sourceType)
: colorTokenToCssVar('base.500');
const stroke =
sourceType && nodes.shouldColorEdges
? getFieldColor(sourceType)
: colorTokenToCssVar('base.500');
return {
isSelected,
shouldAnimate: nodes.shouldAnimateEdges && isSelected,
stroke,
};
},
defaultSelectorOptions
);
return {
isSelected,
shouldAnimate: nodes.shouldAnimateEdges && isSelected,
stroke,
};
});

View File

@ -1,25 +1,28 @@
import { useState, PropsWithChildren, memo, useCallback } from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from '@reduxjs/toolkit';
import { Flex, Image, Text } from '@chakra-ui/react';
import { motion } from 'framer-motion';
import { NodeProps } from 'reactflow';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import IAIDndImage from 'common/components/IAIDndImage';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { stateSelector } from 'app/store/store';
import { motion } from 'framer-motion';
import { PropsWithChildren, memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { NodeProps } from 'reactflow';
const selector = createSelector(stateSelector, ({ system, gallery }) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];
const selector = createMemoizedSelector(
stateSelector,
({ system, gallery }) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];
return {
imageDTO,
progressImage: system.denoiseProgress?.progress_image,
};
});
return {
imageDTO,
progressImage: system.denoiseProgress?.progress_image,
};
}
);
const CurrentImageNode = (props: NodeProps) => {
const { progressImage, imageDTO } = useSelector(selector);

View File

@ -7,7 +7,7 @@ import {
Text,
Tooltip,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
@ -16,8 +16,8 @@ import {
zNodeStatus,
} from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
type Props = {
nodeId: string;
@ -35,7 +35,7 @@ const circleStyles = {
const InvocationNodeStatusIndicator = ({ nodeId }: Props) => {
const selectNodeExecutionState = useMemo(
() =>
createSelector(
createMemoizedSelector(
stateSelector,
({ nodes }) => nodes.nodeExecutionStates[nodeId]
),

View File

@ -1,10 +1,10 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { InvocationNodeData } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import { NodeProps } from 'reactflow';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import InvocationNodeUnknownFallback from './InvocationNodeUnknownFallback';
const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
@ -13,7 +13,7 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const hasTemplateSelector = useMemo(
() =>
createSelector(stateSelector, ({ nodes }) =>
createMemoizedSelector(stateSelector, ({ nodes }) =>
Boolean(nodes.nodeTemplates[type])
),
[type]

View File

@ -1,8 +1,7 @@
import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import {
IAIContextMenu,
IAIContextMenuProps,
@ -15,9 +14,9 @@ import {
workflowExposedFieldRemoved,
} from 'features/nodes/store/workflowSlice';
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaMinus, FaPlus } from 'react-icons/fa';
import { menuListMotionProps } from 'theme/components/menu';
import { useTranslation } from 'react-i18next';
type Props = {
nodeId: string;
@ -39,19 +38,15 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ workflow }) => {
const isExposed = Boolean(
workflow.exposedFields.find(
(f) => f.nodeId === nodeId && f.fieldName === fieldName
)
);
createMemoizedSelector(stateSelector, ({ workflow }) => {
const isExposed = Boolean(
workflow.exposedFields.find(
(f) => f.nodeId === nodeId && f.fieldName === fieldName
)
);
return { isExposed };
},
defaultSelectorOptions
),
return { isExposed };
}),
[fieldName, nodeId]
);

View File

@ -1,5 +1,5 @@
import { Flex, Text } from '@chakra-ui/react';
import { skipToken } from '@reduxjs/toolkit/dist/query';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';

View File

@ -1,38 +1,33 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { fieldSchedulerValueChanged } from 'features/nodes/store/nodesSlice';
import {
SchedulerFieldInputTemplate,
SchedulerFieldInputInstance,
SchedulerFieldInputTemplate,
} from 'features/nodes/types/field';
import { FieldComponentProps } from './types';
import { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { SCHEDULER_LABEL_MAP } from 'features/parameters/types/constants';
import { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { map } from 'lodash-es';
import { memo, useCallback } from 'react';
import { FieldComponentProps } from './types';
const selector = createSelector(
[stateSelector],
({ ui }) => {
const { favoriteSchedulers: enabledSchedulers } = ui;
const selector = createMemoizedSelector([stateSelector], ({ ui }) => {
const { favoriteSchedulers: enabledSchedulers } = ui;
const data = map(SCHEDULER_LABEL_MAP, (label, name) => ({
value: name,
label: label,
group: enabledSchedulers.includes(name as ParameterScheduler)
? 'Favorites'
: undefined,
})).sort((a, b) => a.label.localeCompare(b.label));
const data = map(SCHEDULER_LABEL_MAP, (label, name) => ({
value: name,
label: label,
group: enabledSchedulers.includes(name as ParameterScheduler)
? 'Favorites'
: undefined,
})).sort((a, b) => a.label.localeCompare(b.label));
return {
data,
};
},
defaultSelectorOptions
);
return {
data,
};
});
const SchedulerFieldInputComponent = (
props: FieldComponentProps<

View File

@ -4,7 +4,7 @@ import {
useColorModeValue,
useToken,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
@ -37,7 +37,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const selectIsInProgress = useMemo(
() =>
createSelector(
createMemoizedSelector(
stateSelector,
({ nodes }) =>
nodes.nodeExecutionStates[nodeId]?.status ===

View File

@ -11,10 +11,9 @@ import {
ModalOverlay,
useDisclosure,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton';
import {
@ -32,26 +31,22 @@ const formLabelProps: FormLabelProps = {
fontWeight: 600,
};
const selector = createSelector(
stateSelector,
({ nodes }) => {
const {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
selectionMode,
} = nodes;
return {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
selectionModeIsChecked: selectionMode === SelectionMode.Full,
};
},
defaultSelectorOptions
);
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
selectionMode,
} = nodes;
return {
shouldAnimateEdges,
shouldValidateGraph,
shouldSnapToGrid,
shouldColorEdges,
selectionModeIsChecked: selectionMode === SelectionMode.Full,
};
});
type Props = {
children: (props: { onOpen: () => void }) => ReactNode;

View File

@ -1,27 +1,22 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { memo } from 'react';
const selector = createSelector(
stateSelector,
({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
return {
data: lastSelectedNode?.data,
};
},
defaultSelectorOptions
);
return {
data: lastSelectedNode?.data,
};
});
const InspectorDataTab = () => {
const { data } = useAppSelector(selector);

View File

@ -6,10 +6,9 @@ import {
HStack,
Text,
} from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
@ -23,27 +22,23 @@ import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import EditableNodeTitle from './details/EditableNodeTitle';
const selector = createSelector(
stateSelector,
({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
};
},
defaultSelectorOptions
);
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
};
});
const InspectorDetailsTab = () => {
const { node, template } = useAppSelector(selector);

View File

@ -1,43 +1,38 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { ImageOutput } from 'services/api/types';
import { AnyResult } from 'services/events/types';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import ImageOutputPreview from './outputs/ImageOutputPreview';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
const nes =
nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
const nes =
nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
nes,
};
},
defaultSelectorOptions
);
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
nes,
};
});
const InspectorOutputsTab = () => {
const { node, template, nes } = useAppSelector(selector);

View File

@ -1,32 +1,27 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const lastSelectedNodeId =
nodes.selectedNodes[nodes.selectedNodes.length - 1];
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNode = nodes.nodes.find(
(node) => node.id === lastSelectedNodeId
);
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
const lastSelectedNodeTemplate = lastSelectedNode
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
return {
template: lastSelectedNodeTemplate,
};
},
defaultSelectorOptions
);
return {
template: lastSelectedNodeTemplate,
};
});
const NodeTemplateInspector = () => {
const { template } = useAppSelector(selector);

View File

@ -1,8 +1,7 @@
import { Flex, FormControl, FormLabel } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAIInput from 'common/components/IAIInput';
import IAITextarea from 'common/components/IAITextarea';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
@ -18,24 +17,19 @@ import {
import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
({ workflow }) => {
const { author, name, description, tags, version, contact, notes } =
workflow;
const selector = createMemoizedSelector(stateSelector, ({ workflow }) => {
const { author, name, description, tags, version, contact, notes } = workflow;
return {
name,
author,
description,
tags,
version,
contact,
notes,
};
},
defaultSelectorOptions
);
return {
name,
author,
description,
tags,
version,
contact,
notes,
};
});
const WorkflowGeneralTab = () => {
const { author, name, description, tags, version, contact, notes } =

View File

@ -1,23 +1,18 @@
import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { memo } from 'react';
import LinearViewField from 'features/nodes/components/flow/nodes/Invocation/fields/LinearViewField';
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
({ workflow }) => {
return {
fields: workflow.exposedFields,
};
},
defaultSelectorOptions
);
const selector = createMemoizedSelector(stateSelector, ({ workflow }) => {
return {
fields: workflow.exposedFields,
};
});
const WorkflowLinearTab = () => {
const { fields } = useAppSelector(selector);

View File

@ -1,37 +1,32 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
export const useAnyOrDirectInputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(['any', 'direct'].includes(field.input) ||
field.type.isCollectionOrScalar) &&
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(['any', 'direct'].includes(field.input) ||
field.type.isCollectionOrScalar) &&
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
}),
[nodeId]
);

View File

@ -1,8 +1,6 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { useCallback } from 'react';
import { Node, useReactFlow } from 'reactflow';
import {
DRAG_HANDLE_CLASSNAME,
NODE_WIDTH,
@ -11,8 +9,10 @@ import { AnyNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildCurrentImageNode } from 'features/nodes/util/node/buildCurrentImageNode';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import { buildNotesNode } from 'features/nodes/util/node/buildNotesNode';
import { useCallback } from 'react';
import { Node, useReactFlow } from 'reactflow';
const templatesSelector = createSelector(
const templatesSelector = createMemoizedSelector(
[(state: RootState) => state.nodes],
(nodes) => nodes.nodeTemplates
);

View File

@ -1,40 +1,35 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
import { keys, map } from 'lodash-es';
import { useMemo } from 'react';
export const useConnectionInputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
// get the visible fields
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(field.input === 'connection' &&
!field.type.isCollectionOrScalar) ||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
// get the visible fields
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(field.input === 'connection' &&
!field.type.isCollectionOrScalar) ||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
);
return getSortedFilteredFieldNames(fields);
},
defaultSelectorOptions
),
return getSortedFilteredFieldNames(fields);
}),
[nodeId]
);

View File

@ -1,11 +1,11 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeIsConnectionValidSelector';
import { useMemo } from 'react';
import { useFieldType } from './useFieldType.ts';
const selectIsConnectionInProgress = createSelector(
const selectIsConnectionInProgress = createMemoizedSelector(
stateSelector,
({ nodes }) =>
nodes.connectionStartFieldType !== null &&
@ -27,7 +27,7 @@ export const useConnectionState = ({
const selectIsConnected = useMemo(
() =>
createSelector(stateSelector, ({ nodes }) =>
createMemoizedSelector(stateSelector, ({ nodes }) =>
Boolean(
nodes.edges.filter((edge) => {
return (
@ -54,7 +54,7 @@ export const useConnectionState = ({
const selectIsConnectionStartField = useMemo(
() =>
createSelector(stateSelector, ({ nodes }) =>
createMemoizedSelector(stateSelector, ({ nodes }) =>
Boolean(
nodes.connectionStartParams?.nodeId === nodeId &&
nodes.connectionStartParams?.handleId === fieldName &&

View File

@ -1,29 +1,24 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { compareVersions } from 'compare-versions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useDoNodeVersionsMatch = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
if (!nodeTemplate?.version || !node.data?.version) {
return false;
}
return compareVersions(nodeTemplate.version, node.data.version) === 0;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
if (!nodeTemplate?.version || !node.data?.version) {
return false;
}
return compareVersions(nodeTemplate.version, node.data.version) === 0;
}),
[nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useDoesInputHaveValue = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName]?.value !== undefined;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName]?.value !== undefined;
}),
[fieldName, nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldInstance = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName];
}),
[fieldName, nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldInputInstance = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node.data.inputs[fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node.data.inputs[fieldName];
}),
[fieldName, nodeId]
);

View File

@ -1,26 +1,21 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldInputKind = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
const fieldTemplate = nodeTemplate?.inputs[fieldName];
return fieldTemplate?.input;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
const fieldTemplate = nodeTemplate?.inputs[fieldName];
return fieldTemplate?.input;
}),
[fieldName, nodeId]
);

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldInputTemplate = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.inputs[fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.inputs[fieldName];
}),
[fieldName, nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldLabel = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName]?.label;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node?.data.inputs[fieldName]?.label;
}),
[fieldName, nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldOutputInstance = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node.data.outputs[fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
return node.data.outputs[fieldName];
}),
[fieldName, nodeId]
);

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldOutputTemplate = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.outputs[fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.outputs[fieldName];
}),
[fieldName, nodeId]
);

View File

@ -1,10 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { KIND_MAP } from 'features/nodes/types/constants';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldTemplate = (
nodeId: string,
@ -13,18 +12,14 @@ export const useFieldTemplate = (
) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.[KIND_MAP[kind]][fieldName];
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.[KIND_MAP[kind]][fieldName];
}),
[fieldName, kind, nodeId]
);

View File

@ -1,10 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { KIND_MAP } from 'features/nodes/types/constants';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldTemplateTitle = (
nodeId: string,
@ -13,18 +12,14 @@ export const useFieldTemplateTitle = (
) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.[KIND_MAP[kind]][fieldName]?.title;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate?.[KIND_MAP[kind]][fieldName]?.title;
}),
[fieldName, kind, nodeId]
);

View File

@ -1,10 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { KIND_MAP } from 'features/nodes/types/constants';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useFieldType = (
nodeId: string,
@ -13,18 +12,14 @@ export const useFieldType = (
) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const field = node.data[KIND_MAP[kind]][fieldName];
return field?.type;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return;
}
const field = node.data[KIND_MAP[kind]][fieldName];
return field?.type;
}),
[fieldName, kind, nodeId]
);

View File

@ -1,27 +1,22 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
const selector = createSelector(
stateSelector,
(state) => {
const nodes = state.nodes.nodes;
const templates = state.nodes.nodeTemplates;
const selector = createMemoizedSelector(stateSelector, (state) => {
const nodes = state.nodes.nodes;
const templates = state.nodes.nodeTemplates;
const needsUpdate = nodes.filter(isInvocationNode).some((node) => {
const template = templates[node.data.type];
if (!template) {
return false;
}
return getNeedsUpdate(node, template);
});
return needsUpdate;
},
defaultSelectorOptions
);
const needsUpdate = nodes.filter(isInvocationNode).some((node) => {
const template = templates[node.data.type];
if (!template) {
return false;
}
return getNeedsUpdate(node, template);
});
return needsUpdate;
});
export const useGetNodesNeedUpdate = () => {
const getNeedsUpdate = useAppSelector(selector);

View File

@ -1,31 +1,26 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { some } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
export const useHasImageOutput = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return some(
node.data.outputs,
(output) =>
output.type.name === 'ImageField' &&
// the image primitive node (node type "image") does not actually save the image, do not show the image-saving checkboxes
node.data.type !== 'image'
);
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return some(
node.data.outputs,
(output) =>
output.type.name === 'ImageField' &&
// the image primitive node (node type "image") does not actually save the image, do not show the image-saving checkboxes
node.data.type !== 'image'
);
}),
[nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useIsIntermediate = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return node.data.isIntermediate;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return node.data.isIntermediate;
}),
[nodeId]
);

View File

@ -1,20 +1,18 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useCallback, useMemo } from 'react';
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(
() =>
createSelector(
createMemoizedSelector(
stateSelector,
({ nodes }) =>
nodes.mouseOverField?.nodeId === nodeId &&
nodes.mouseOverField?.fieldName === fieldName,
defaultSelectorOptions
nodes.mouseOverField?.fieldName === fieldName
),
[fieldName, nodeId]
);

View File

@ -1,18 +1,16 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useCallback, useMemo } from 'react';
import { mouseOverNodeChanged } from 'features/nodes/store/nodesSlice';
import { useCallback, useMemo } from 'react';
export const useMouseOverNode = (nodeId: string) => {
const dispatch = useAppDispatch();
const selector = useMemo(
() =>
createSelector(
createMemoizedSelector(
stateSelector,
({ nodes }) => nodes.mouseOverNode === nodeId,
defaultSelectorOptions
({ nodes }) => nodes.mouseOverNode === nodeId
),
[nodeId]
);

View File

@ -1,20 +1,15 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
export const useNodeData = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
return node?.data;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
return node?.data;
}),
[nodeId]
);

View File

@ -1,25 +1,20 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodeLabel = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return node.data.label;
},
defaultSelectorOptions
),
return node.data.label;
}),
[nodeId]
);

View File

@ -1,23 +1,18 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { useMemo } from 'react';
export const useNodeNeedsUpdate = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
const template = nodes.nodeTemplates[node?.data.type ?? ''];
return { node, template };
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
const template = nodes.nodeTemplates[node?.data.type ?? ''];
return { node, template };
}),
[nodeId]
);

View File

@ -1,24 +1,19 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodePack = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return node.data.nodePack;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
return node.data.nodePack;
}),
[nodeId]
);

View File

@ -1,21 +1,16 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
export const useNodeTemplate = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
return nodeTemplate;
}),
[nodeId]
);

View File

@ -1,20 +1,18 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { InvocationTemplate } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodeTemplateByType = (type: string) => {
const selector = useMemo(
() =>
createSelector(
createMemoizedSelector(
stateSelector,
({ nodes }): InvocationTemplate | undefined => {
const nodeTemplate = nodes.nodeTemplates[type];
return nodeTemplate;
},
defaultSelectorOptions
}
),
[type]
);

View File

@ -1,28 +1,23 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useNodeTemplateTitle = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
const nodeTemplate = node
? nodes.nodeTemplates[node.data.type]
: undefined;
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
const nodeTemplate = node
? nodes.nodeTemplates[node.data.type]
: undefined;
return nodeTemplate?.title;
},
defaultSelectorOptions
),
return nodeTemplate?.title;
}),
[nodeId]
);

View File

@ -1,31 +1,26 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
import { map } from 'lodash-es';
import { useMemo } from 'react';
export const useOutputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
return getSortedFilteredFieldNames(map(nodeTemplate.outputs));
},
defaultSelectorOptions
),
return getSortedFilteredFieldNames(map(nodeTemplate.outputs));
}),
[nodeId]
);

View File

@ -1,26 +1,21 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useMemo } from 'react';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useUseCache = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
// cast to boolean to support older workflows that didn't have useCache
// TODO: handle this better somehow
return node.data.useCache;
},
defaultSelectorOptions
),
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return false;
}
// cast to boolean to support older workflows that didn't have useCache
// TODO: handle this better somehow
return node.data.useCache;
}),
[nodeId]
);

View File

@ -1,5 +1,4 @@
import { Connection, Edge, HandleType, Node } from 'reactflow';
import {
FieldInputInstance,
FieldOutputInstance,

View File

@ -1,4 +1,4 @@
import { createSelector } from '@reduxjs/toolkit';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { FieldType } from 'features/nodes/types/field';
import i18n from 'i18next';
@ -17,7 +17,7 @@ export const makeConnectionErrorSelector = (
handleType: HandleType,
fieldType?: FieldType
) => {
return createSelector(stateSelector, (state): string | undefined => {
return createMemoizedSelector(stateSelector, (state): string | undefined => {
if (!fieldType) {
return i18n.t('nodes.noFieldType');
}