addressed feedback

This commit is contained in:
Jennifer Player 2024-02-08 17:31:56 -05:00 committed by psychedelicious
parent 2ce70b4457
commit 5de2288cfa
3 changed files with 39 additions and 136 deletions

View File

@ -1,84 +0,0 @@
import type { ContextMenuProps } from '@invoke-ai/ui-library';
import { ContextMenu, MenuGroup, MenuItem, MenuList } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useFieldInputKind } from 'features/nodes/hooks/useFieldInputKind';
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
import {
selectWorkflowSlice,
workflowExposedFieldAdded,
workflowExposedFieldRemoved,
} from 'features/nodes/store/workflowSlice';
import type { ReactNode } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiMinusBold, PiPlusBold } from 'react-icons/pi';
type Props = {
nodeId: string;
fieldName: string;
kind: 'input' | 'output';
children: ContextMenuProps<HTMLDivElement>['children'];
};
const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
const dispatch = useAppDispatch();
const label = useFieldLabel(nodeId, fieldName);
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
const input = useFieldInputKind(nodeId, fieldName);
const { t } = useTranslation();
const selectIsExposed = useMemo(
() =>
createSelector(selectWorkflowSlice, (workflow) => {
return Boolean(workflow.exposedFields.find((f) => f.nodeId === nodeId && f.fieldName === fieldName));
}),
[fieldName, nodeId]
);
const mayExpose = useMemo(() => input && ['any', 'direct'].includes(input), [input]);
const isExposed = useAppSelector(selectIsExposed);
const handleExposeField = useCallback(() => {
dispatch(workflowExposedFieldAdded({ nodeId, fieldName }));
}, [dispatch, fieldName, nodeId]);
const handleUnexposeField = useCallback(() => {
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
}, [dispatch, fieldName, nodeId]);
const menuItems = useMemo(() => {
const menuItems: ReactNode[] = [];
if (mayExpose && !isExposed) {
menuItems.push(
<MenuItem key={`${nodeId}.${fieldName}.expose-field`} icon={<PiPlusBold />} onClick={handleExposeField}>
{t('nodes.addLinearView')}
</MenuItem>
);
}
if (mayExpose && isExposed) {
menuItems.push(
<MenuItem key={`${nodeId}.${fieldName}.unexpose-field`} icon={<PiMinusBold />} onClick={handleUnexposeField}>
{t('nodes.removeLinearView')}
</MenuItem>
);
}
return menuItems;
}, [fieldName, handleExposeField, handleUnexposeField, isExposed, mayExpose, nodeId, t]);
const renderMenuFunc = useCallback(
() =>
!menuItems.length ? null : (
<MenuList visibility="visible">
<MenuGroup title={label || fieldTemplateTitle || t('nodes.unknownField')}>{menuItems}</MenuGroup>
</MenuList>
),
[fieldTemplateTitle, label, menuItems, t]
);
return <ContextMenu renderMenu={renderMenuFunc}>{children}</ContextMenu>;
};
export default memo(FieldContextMenu);

View File

@ -13,10 +13,9 @@ import { PiMinusBold, PiPlusBold } from 'react-icons/pi';
type Props = { type Props = {
nodeId: string; nodeId: string;
fieldName: string; fieldName: string;
isHovered: boolean;
}; };
const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => { const FieldLinearViewToggle = ({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
@ -38,37 +37,31 @@ const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => {
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
}, [dispatch, fieldName, nodeId]); }, [dispatch, fieldName, nodeId]);
const ToggleButton = useMemo(() => { if (!isExposed) {
if (!isHovered) { return (
return null; <IconButton
} else if (!isExposed) { variant="ghost"
return ( tooltip={t('nodes.addLinearView')}
<IconButton aria-label={t('nodes.addLinearView')}
mx="2" icon={<PiPlusBold />}
tooltip={t('nodes.addLinearView')} onClick={handleExposeField}
aria-label={t('nodes.addLinearView')} pointerEvents="auto"
icon={<PiPlusBold />} size="xs"
onClick={handleExposeField} />
pointerEvents="auto" );
size="xs" } else {
/> return (
); <IconButton
} else if (isExposed) { variant="ghost"
return ( tooltip={t('nodes.removeLinearView')}
<IconButton aria-label={t('nodes.removeLinearView')}
mx="2" icon={<PiMinusBold />}
tooltip={t('nodes.removeLinearView')} onClick={handleUnexposeField}
aria-label={t('nodes.removeLinearView')} pointerEvents="auto"
icon={<PiMinusBold />} size="xs"
onClick={handleUnexposeField} />
pointerEvents="auto" );
size="xs" }
/>
);
}
}, [isHovered, handleExposeField, handleUnexposeField, isExposed, t]);
return ToggleButton;
}; };
export default memo(FieldLinearViewToggle); export default memo(FieldLinearViewToggle);

View File

@ -8,7 +8,6 @@ import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import EditableFieldTitle from './EditableFieldTitle'; import EditableFieldTitle from './EditableFieldTitle';
import FieldContextMenu from './FieldContextMenu';
import FieldHandle from './FieldHandle'; import FieldHandle from './FieldHandle';
import FieldLinearViewToggle from './FieldLinearViewToggle'; import FieldLinearViewToggle from './FieldLinearViewToggle';
import InputFieldRenderer from './InputFieldRenderer'; import InputFieldRenderer from './InputFieldRenderer';
@ -48,11 +47,11 @@ const InputField = ({ nodeId, fieldName }: Props) => {
return false; return false;
}, [fieldTemplate, isConnected, doesFieldHaveValue]); }, [fieldTemplate, isConnected, doesFieldHaveValue]);
const handleMouseOver = useCallback(() => { const onMouseEnter = useCallback(() => {
setIsHovered(true); setIsHovered(true);
}, []); }, []);
const handleMouseOut = useCallback(() => { const onMouseLeave = useCallback(() => {
setIsHovered(false); setIsHovered(false);
}, []); }, []);
@ -97,22 +96,17 @@ const InputField = ({ nodeId, fieldName }: Props) => {
return ( return (
<InputFieldWrapper shouldDim={shouldDim}> <InputFieldWrapper shouldDim={shouldDim}>
<FormControl isInvalid={isMissingInput} isDisabled={isConnected} orientation="vertical" px={2}> <FormControl isInvalid={isMissingInput} isDisabled={isConnected} orientation="vertical" px={2}>
<Flex flexDir="column" w="full" gap={1} onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOut}> <Flex flexDir="column" w="full" gap={1} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input"> <Flex>
{(ref) => ( <EditableFieldTitle
<Flex> nodeId={nodeId}
<EditableFieldTitle fieldName={fieldName}
ref={ref} kind="input"
nodeId={nodeId} isMissingInput={isMissingInput}
fieldName={fieldName} withTooltip
kind="input" />
isMissingInput={isMissingInput} {isHovered && <FieldLinearViewToggle nodeId={nodeId} fieldName={fieldName} />}
withTooltip </Flex>
/>
<FieldLinearViewToggle nodeId={nodeId} fieldName={fieldName} isHovered={isHovered} />
</Flex>
)}
</FieldContextMenu>
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} /> <InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
</Flex> </Flex>
</FormControl> </FormControl>