feat(ui): misc perf/rerender improvements

More efficient selectors, memoized/stable references to objects, lazy popover/menu rendering.
This commit is contained in:
psychedelicious
2023-12-30 23:55:39 +11:00
committed by Kent Keirsey
parent 2ba505cce9
commit 539887b215
8 changed files with 345 additions and 332 deletions

View File

@ -7,13 +7,9 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvText } from 'common/components/InvText/wrapper';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
import type {
InvocationNode,
InvocationTemplate,
} from 'features/nodes/types/invocation';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import { memo, useMemo } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import EditableNodeTitle from './details/EditableNodeTitle';
@ -30,38 +26,47 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
? nodes.nodeTemplates[lastSelectedNode.data.type]
: undefined;
if (!isInvocationNode(lastSelectedNode) || !lastSelectedNodeTemplate) {
return;
}
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
nodeId: lastSelectedNode.data.id,
nodeVersion: lastSelectedNode.data.version,
templateTitle: lastSelectedNodeTemplate.title,
};
});
const InspectorDetailsTab = () => {
const { node, template } = useAppSelector(selector);
const data = useAppSelector(selector);
const { t } = useTranslation();
if (!template || !isInvocationNode(node)) {
if (!data) {
return (
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
);
}
return <Content node={node} template={template} />;
return (
<Content
nodeId={data.nodeId}
nodeVersion={data.nodeVersion}
templateTitle={data.templateTitle}
/>
);
};
export default memo(InspectorDetailsTab);
type ContentProps = {
node: InvocationNode;
template: InvocationTemplate;
nodeId: string;
nodeVersion: string;
templateTitle: string;
};
const Content = memo(({ node, template }: ContentProps) => {
const Content = memo((props: ContentProps) => {
const { t } = useTranslation();
const needsUpdate = useMemo(
() => getNeedsUpdate(node, template),
[node, template]
);
const needsUpdate = useNodeNeedsUpdate(props.nodeId);
return (
<Box position="relative" w="full" h="full">
<ScrollableContent>
@ -73,20 +78,20 @@ const Content = memo(({ node, template }: ContentProps) => {
p={1}
gap={2}
>
<EditableNodeTitle nodeId={node.data.id} />
<EditableNodeTitle nodeId={props.nodeId} />
<HStack>
<InvControl label={t('nodes.nodeType')}>
<InvText fontSize="sm" fontWeight="semibold">
{template.title}
{props.templateTitle}
</InvText>
</InvControl>
<InvControl label={t('nodes.nodeVersion')} isInvalid={needsUpdate}>
<InvText fontSize="sm" fontWeight="semibold">
{node.data.version}
{props.nodeVersion}
</InvText>
</InvControl>
</HStack>
<NotesTextarea nodeId={node.data.id} />
<NotesTextarea nodeId={props.nodeId} />
</Flex>
</ScrollableContent>
</Box>

View File

@ -28,24 +28,31 @@ const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
const nes =
nodes.nodeExecutionStates[lastSelectedNodeId ?? '__UNKNOWN_NODE__'];
if (
!isInvocationNode(lastSelectedNode) ||
!nes ||
!lastSelectedNodeTemplate
) {
return;
}
return {
node: lastSelectedNode,
template: lastSelectedNodeTemplate,
nes,
outputs: nes.outputs,
outputType: lastSelectedNodeTemplate.outputType,
};
});
const InspectorOutputsTab = () => {
const { node, template, nes } = useAppSelector(selector);
const data = useAppSelector(selector);
const { t } = useTranslation();
if (!node || !nes || !isInvocationNode(node)) {
if (!data) {
return (
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
);
}
if (nes.outputs.length === 0) {
if (data.outputs.length === 0) {
return (
<IAINoContentFallback label={t('nodes.noOutputRecorded')} icon={null} />
);
@ -63,15 +70,15 @@ const InspectorOutputsTab = () => {
h="full"
w="full"
>
{template?.outputType === 'image_output' ? (
nes.outputs.map((result, i) => (
{data.outputType === 'image_output' ? (
data.outputs.map((result, i) => (
<ImageOutputPreview
key={getKey(result, i)}
output={result as ImageOutput}
/>
))
) : (
<DataViewer data={nes.outputs} label={t('nodes.nodeOutputs')} />
<DataViewer data={data.outputs} label={t('nodes.nodeOutputs')} />
)}
</Flex>
</ScrollableContent>

View File

@ -11,20 +11,15 @@ export const useNodeNeedsUpdate = (nodeId: string) => {
createMemoizedSelector(stateSelector, ({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
const template = nodes.nodeTemplates[node?.data.type ?? ''];
return { node, template };
if (isInvocationNode(node) && template) {
return getNeedsUpdate(node, template);
}
return false;
}),
[nodeId]
);
const { node, template } = useAppSelector(selector);
const needsUpdate = useMemo(
() =>
isInvocationNode(node) && template
? getNeedsUpdate(node, template)
: false,
[node, template]
);
const needsUpdate = useAppSelector(selector);
return needsUpdate;
};