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 = {
|
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);
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user