mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
addressed feedback
This commit is contained in:
parent
2ce70b4457
commit
5de2288cfa
@ -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);
|
@ -13,10 +13,9 @@ import { PiMinusBold, PiPlusBold } from 'react-icons/pi';
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
fieldName: string;
|
||||
isHovered: boolean;
|
||||
};
|
||||
|
||||
const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => {
|
||||
const FieldLinearViewToggle = ({ nodeId, fieldName }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -38,37 +37,31 @@ const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => {
|
||||
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
|
||||
}, [dispatch, fieldName, nodeId]);
|
||||
|
||||
const ToggleButton = useMemo(() => {
|
||||
if (!isHovered) {
|
||||
return null;
|
||||
} else if (!isExposed) {
|
||||
return (
|
||||
<IconButton
|
||||
mx="2"
|
||||
tooltip={t('nodes.addLinearView')}
|
||||
aria-label={t('nodes.addLinearView')}
|
||||
icon={<PiPlusBold />}
|
||||
onClick={handleExposeField}
|
||||
pointerEvents="auto"
|
||||
size="xs"
|
||||
/>
|
||||
);
|
||||
} else if (isExposed) {
|
||||
return (
|
||||
<IconButton
|
||||
mx="2"
|
||||
tooltip={t('nodes.removeLinearView')}
|
||||
aria-label={t('nodes.removeLinearView')}
|
||||
icon={<PiMinusBold />}
|
||||
onClick={handleUnexposeField}
|
||||
pointerEvents="auto"
|
||||
size="xs"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [isHovered, handleExposeField, handleUnexposeField, isExposed, t]);
|
||||
|
||||
return ToggleButton;
|
||||
if (!isExposed) {
|
||||
return (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
tooltip={t('nodes.addLinearView')}
|
||||
aria-label={t('nodes.addLinearView')}
|
||||
icon={<PiPlusBold />}
|
||||
onClick={handleExposeField}
|
||||
pointerEvents="auto"
|
||||
size="xs"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
tooltip={t('nodes.removeLinearView')}
|
||||
aria-label={t('nodes.removeLinearView')}
|
||||
icon={<PiMinusBold />}
|
||||
onClick={handleUnexposeField}
|
||||
pointerEvents="auto"
|
||||
size="xs"
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(FieldLinearViewToggle);
|
||||
|
@ -8,7 +8,6 @@ import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldContextMenu from './FieldContextMenu';
|
||||
import FieldHandle from './FieldHandle';
|
||||
import FieldLinearViewToggle from './FieldLinearViewToggle';
|
||||
import InputFieldRenderer from './InputFieldRenderer';
|
||||
@ -48,11 +47,11 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
return false;
|
||||
}, [fieldTemplate, isConnected, doesFieldHaveValue]);
|
||||
|
||||
const handleMouseOver = useCallback(() => {
|
||||
const onMouseEnter = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
}, []);
|
||||
|
||||
const handleMouseOut = useCallback(() => {
|
||||
const onMouseLeave = useCallback(() => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
@ -97,22 +96,17 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
return (
|
||||
<InputFieldWrapper shouldDim={shouldDim}>
|
||||
<FormControl isInvalid={isMissingInput} isDisabled={isConnected} orientation="vertical" px={2}>
|
||||
<Flex flexDir="column" w="full" gap={1} onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOut}>
|
||||
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input">
|
||||
{(ref) => (
|
||||
<Flex>
|
||||
<EditableFieldTitle
|
||||
ref={ref}
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
isMissingInput={isMissingInput}
|
||||
withTooltip
|
||||
/>
|
||||
<FieldLinearViewToggle nodeId={nodeId} fieldName={fieldName} isHovered={isHovered} />
|
||||
</Flex>
|
||||
)}
|
||||
</FieldContextMenu>
|
||||
<Flex flexDir="column" w="full" gap={1} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
<Flex>
|
||||
<EditableFieldTitle
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
isMissingInput={isMissingInput}
|
||||
withTooltip
|
||||
/>
|
||||
{isHovered && <FieldLinearViewToggle nodeId={nodeId} fieldName={fieldName} />}
|
||||
</Flex>
|
||||
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
|
||||
</Flex>
|
||||
</FormControl>
|
||||
|
Loading…
Reference in New Issue
Block a user