refactor(ui): continued reorg of components & hooks

This commit is contained in:
psychedelicious
2025-01-20 16:25:16 +11:00
parent 1ba9b5407c
commit e9677940d0
7 changed files with 70 additions and 90 deletions

View File

@@ -5,7 +5,7 @@ import InvocationNodeClassificationIcon from 'features/nodes/components/flow/nod
import { memo } from 'react';
import InvocationNodeCollapsedHandles from './InvocationNodeCollapsedHandles';
import InvocationNodeInfoIcon from './InvocationNodeInfoIcon';
import { InvocationNodeInfoIcon } from './InvocationNodeInfoIcon';
import InvocationNodeStatusIndicator from './InvocationNodeStatusIndicator';
type Props = {

View File

@@ -1,9 +1,10 @@
import { Flex, Icon, Text, Tooltip } from '@invoke-ai/ui-library';
import { compare } from 'compare-versions';
import { useNode } from 'features/nodes/hooks/useNode';
import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { useInvocationNodeNotes } from 'features/nodes/hooks/useNodeNotes';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiInfoBold } from 'react-icons/pi';
@@ -12,7 +13,7 @@ interface Props {
nodeId: string;
}
const InvocationNodeInfoIcon = ({ nodeId }: Props) => {
export const InvocationNodeInfoIcon = memo(({ nodeId }: Props) => {
const needsUpdate = useNodeNeedsUpdate(nodeId);
return (
@@ -20,96 +21,66 @@ const InvocationNodeInfoIcon = ({ nodeId }: Props) => {
<Icon as={PiInfoBold} display="block" boxSize={4} w={8} color={needsUpdate ? 'error.400' : 'base.400'} />
</Tooltip>
);
};
});
export default memo(InvocationNodeInfoIcon);
InvocationNodeInfoIcon.displayName = 'InvocationNodeInfoIcon';
const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
const node = useNode(nodeId);
const notes = useInvocationNodeNotes(nodeId);
const label = useNodeLabel(nodeId);
const version = useNodeVersion(nodeId);
const nodeTemplate = useNodeTemplate(nodeId);
const { t } = useTranslation();
const title = useMemo(() => {
if (node.data?.label && nodeTemplate?.title) {
return `${node.data.label} (${nodeTemplate.title})`;
if (label) {
return `${label} (${nodeTemplate.title})`;
}
if (node.data?.label && !nodeTemplate) {
return node.data.label;
}
if (!node.data?.label && nodeTemplate) {
return nodeTemplate.title;
}
return t('nodes.unknownNode');
}, [node.data.label, nodeTemplate, t]);
const versionComponent = useMemo(() => {
if (!isInvocationNode(node) || !nodeTemplate) {
return null;
}
if (!node.data.version) {
return (
<Text as="span" color="error.500">
{t('nodes.versionUnknown')}
</Text>
);
}
if (!nodeTemplate.version) {
return (
<Text as="span" color="error.500">
{t('nodes.version')} {node.data.version} ({t('nodes.unknownTemplate')})
</Text>
);
}
if (compare(node.data.version, nodeTemplate.version, '<')) {
return (
<Text as="span" color="error.500">
{t('nodes.version')} {node.data.version} ({t('nodes.updateNode')})
</Text>
);
}
if (compare(node.data.version, nodeTemplate.version, '>')) {
return (
<Text as="span" color="error.500">
{t('nodes.version')} {node.data.version} ({t('nodes.updateApp')})
</Text>
);
}
return (
<Text as="span">
{t('nodes.version')} {node.data.version}
</Text>
);
}, [node, nodeTemplate, t]);
if (!isInvocationNode(node)) {
return <Text fontWeight="semibold">{t('nodes.unknownNode')}</Text>;
}
return nodeTemplate.title;
}, [label, nodeTemplate.title]);
return (
<Flex flexDir="column">
<Text as="span" fontWeight="semibold">
{title}
</Text>
{nodeTemplate?.nodePack && (
<Text opacity={0.7}>
{t('nodes.nodePack')}: {nodeTemplate.nodePack}
</Text>
)}
<Text opacity={0.7} fontStyle="oblique 5deg">
{nodeTemplate?.description}
<Text opacity={0.7}>
{t('nodes.nodePack')}: {nodeTemplate.nodePack}
</Text>
{versionComponent}
{node.data?.notes && <Text>{node.data.notes}</Text>}
<Text opacity={0.7} fontStyle="oblique 5deg">
{nodeTemplate.description}
</Text>
<Version nodeVersion={version} templateVersion={nodeTemplate.version} />
{notes && <Text>{notes}</Text>}
</Flex>
);
});
TooltipContent.displayName = 'TooltipContent';
const Version = ({ nodeVersion, templateVersion }: { nodeVersion: string; templateVersion: string }) => {
const { t } = useTranslation();
if (compare(nodeVersion, templateVersion, '<')) {
return (
<Text as="span" color="error.500">
{t('nodes.version')} {nodeVersion} ({t('nodes.updateNode')})
</Text>
);
}
if (compare(nodeVersion, templateVersion, '>')) {
return (
<Text as="span" color="error.500">
{t('nodes.version')} {nodeVersion} ({t('nodes.updateApp')})
</Text>
);
}
return (
<Text as="span">
{t('nodes.version')} {nodeVersion}
</Text>
);
};

View File

@@ -1,7 +1,7 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate';
import { useNodeHasImageOutput } from 'features/nodes/hooks/useNodeHasImageOutput';
import { useNodeIsIntermediate } from 'features/nodes/hooks/useNodeIsIntermediate';
import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -11,7 +11,7 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const hasImageOutput = useNodeHasImageOutput(nodeId);
const isIntermediate = useIsIntermediate(nodeId);
const isIntermediate = useNodeIsIntermediate(nodeId);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(

View File

@@ -1,4 +1,3 @@
// TODO: enable this at some point
import { useStore } from '@nanostores/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice';
@@ -8,11 +7,6 @@ import { selectShouldShouldValidateGraph } from 'features/nodes/store/workflowSe
import { useCallback } from 'react';
import type { Connection } from 'reactflow';
/**
* NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts`
* TODO: Figure out how to do this without duplicating all the logic
*/
export const useIsValidConnection = () => {
const store = useAppStore();
const templates = useStore($templates);

View File

@@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { selectNodeData, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
export const useIsIntermediate = (nodeId: string): boolean => {
export const useNodeIsIntermediate = (nodeId: string): boolean => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {

View File

@@ -1,16 +1,13 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
import { assert } from 'tsafe';
export const useInvocationNodeNotes = (nodeId: string): string => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
const node = selectNode(nodes, nodeId);
assert(isInvocationNode(node), `Node with id ${nodeId} is not an invocation node`);
const node = selectInvocationNode(nodes, nodeId);
return node.data.notes;
}),
[nodeId]

View File

@@ -0,0 +1,18 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
export const useNodeVersion = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodesSlice) => {
const node = selectInvocationNode(nodesSlice, nodeId);
return node.data.version;
}),
[nodeId]
);
const version = useAppSelector(selector);
return version;
};