mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): popover ref & wrapping of children (wip)
This commit is contained in:
parent
b6e9cd4fe2
commit
5aefa49d7d
@ -1213,14 +1213,14 @@
|
||||
},
|
||||
"dynamicPromptsCombinatorial": {
|
||||
"heading": "Combinatorial Generation",
|
||||
"paragraph": "Generate an image for every possible combination of Dynamic Prompt until the Max Prompts is reached."
|
||||
"paragraph": "Generate an image for every possible combination of Dynamic Prompts until the Max Prompts is reached."
|
||||
},
|
||||
"infillMethod": {
|
||||
"heading": "Infill Method",
|
||||
"paragraph": "Method to infill the selected area."
|
||||
},
|
||||
"lora": {
|
||||
"heading": "LoRA",
|
||||
"heading": "LoRA Weight",
|
||||
"paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image."
|
||||
},
|
||||
"noiseEnable": {
|
||||
@ -1239,21 +1239,21 @@
|
||||
"heading": "Denoising Strength",
|
||||
"paragraph": "How much noise is added to the input image. 0 will result in an identical image, while 1 will result in a completely new image."
|
||||
},
|
||||
"paramImages": {
|
||||
"heading": "Images",
|
||||
"paragraph": "Number of images that will be generated."
|
||||
"paramIterations": {
|
||||
"heading": "Iterations",
|
||||
"paragraph": "The number of images to generate. If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "Model",
|
||||
"paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content."
|
||||
},
|
||||
"paramNegativeConditioning": {
|
||||
"heading": "Negative Prompts",
|
||||
"paragraph": "This is where you enter your negative prompts."
|
||||
"heading": "Negative Prompt",
|
||||
"paragraph": "The generation process avoids the concepts in the negative prompt. Use this to exclude qualities or objects from the output. Supports Compel syntax and embeddings."
|
||||
},
|
||||
"paramPositiveConditioning": {
|
||||
"heading": "Positive Prompts",
|
||||
"paragraph": "This is where you enter your positive prompts."
|
||||
"heading": "Positive Prompt",
|
||||
"paragraph": "Guides the generation process. You may use any words or phrases. Supports Compel and Dynamic Prompts syntaxes and embeddings."
|
||||
},
|
||||
"paramRatio": {
|
||||
"heading": "Ratio",
|
||||
|
@ -1,37 +1,43 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
PopoverCloseButton,
|
||||
PopoverHeader,
|
||||
PopoverBody,
|
||||
PopoverProps,
|
||||
Divider,
|
||||
Flex,
|
||||
Text,
|
||||
Heading,
|
||||
Image,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverCloseButton,
|
||||
PopoverContent,
|
||||
PopoverProps,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppSelector } from '../../app/store/storeHooks';
|
||||
import { ReactNode, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAppSelector } from '../../app/store/storeHooks';
|
||||
|
||||
interface Props extends PopoverProps {
|
||||
const OPEN_DELAY = 1500;
|
||||
|
||||
type Props = Omit<PopoverProps, 'children'> & {
|
||||
details: string;
|
||||
children: JSX.Element;
|
||||
children: ReactNode;
|
||||
image?: string;
|
||||
buttonLabel?: string;
|
||||
buttonHref?: string;
|
||||
placement?: PopoverProps['placement'];
|
||||
}
|
||||
};
|
||||
|
||||
function IAIInformationalPopover({
|
||||
const IAIInformationalPopover = ({
|
||||
details,
|
||||
image,
|
||||
buttonLabel,
|
||||
buttonHref,
|
||||
children,
|
||||
placement,
|
||||
}: Props): JSX.Element {
|
||||
}: Props) => {
|
||||
const shouldEnableInformationalPopovers = useAppSelector(
|
||||
(state) => state.system.shouldEnableInformationalPopovers
|
||||
);
|
||||
@ -41,18 +47,21 @@ function IAIInformationalPopover({
|
||||
const paragraph = t(`popovers.${details}.paragraph`);
|
||||
|
||||
if (!shouldEnableInformationalPopovers) {
|
||||
return children;
|
||||
} else {
|
||||
return (
|
||||
<Popover
|
||||
placement={placement || 'top'}
|
||||
closeOnBlur={false}
|
||||
trigger="hover"
|
||||
variant="informational"
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<div>{children}</div>
|
||||
</PopoverTrigger>
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
placement={placement || 'top'}
|
||||
closeOnBlur={false}
|
||||
trigger="hover"
|
||||
variant="informational"
|
||||
openDelay={OPEN_DELAY}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Box w="full">{children}</Box>
|
||||
</PopoverTrigger>
|
||||
<Portal>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverCloseButton />
|
||||
@ -83,14 +92,17 @@ function IAIInformationalPopover({
|
||||
gap: 3,
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
p: 3,
|
||||
pt: heading ? 0 : 3,
|
||||
}}
|
||||
>
|
||||
{heading && <PopoverHeader>{heading}</PopoverHeader>}
|
||||
<Text sx={{ px: 3 }}>{paragraph}</Text>
|
||||
{heading && (
|
||||
<>
|
||||
<Heading size="sm">{heading}</Heading>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
<Text>{paragraph}</Text>
|
||||
{buttonLabel && (
|
||||
<Flex sx={{ px: 3 }} justifyContent="flex-end">
|
||||
<Flex justifyContent="flex-end">
|
||||
<Button
|
||||
onClick={() => window.open(buttonHref)}
|
||||
size="sm"
|
||||
@ -104,9 +116,9 @@ function IAIInformationalPopover({
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default IAIInformationalPopover;
|
||||
export default memo(IAIInformationalPopover);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormControl, FormLabel, Tooltip } from '@chakra-ui/react';
|
||||
import { FormControl, FormLabel, Tooltip, forwardRef } from '@chakra-ui/react';
|
||||
import { MultiSelect, MultiSelectProps } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||
@ -11,7 +11,7 @@ type IAIMultiSelectProps = Omit<MultiSelectProps, 'label'> & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
||||
const IAIMantineMultiSelect = forwardRef((props: IAIMultiSelectProps, ref) => {
|
||||
const {
|
||||
searchable = true,
|
||||
tooltip,
|
||||
@ -47,7 +47,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
||||
<MultiSelect
|
||||
label={
|
||||
label ? (
|
||||
<FormControl isDisabled={disabled}>
|
||||
<FormControl ref={ref} isDisabled={disabled}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
</FormControl>
|
||||
) : undefined
|
||||
@ -63,6 +63,8 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
IAIMantineMultiSelect.displayName = 'IAIMantineMultiSelect';
|
||||
|
||||
export default memo(IAIMantineMultiSelect);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormControl, FormLabel, Tooltip } from '@chakra-ui/react';
|
||||
import { FormControl, FormLabel, Tooltip, forwardRef } from '@chakra-ui/react';
|
||||
import { Select, SelectProps } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||
@ -17,7 +17,7 @@ type IAISelectProps = Omit<SelectProps, 'label'> & {
|
||||
inputRef?: RefObject<HTMLInputElement>;
|
||||
};
|
||||
|
||||
const IAIMantineSearchableSelect = (props: IAISelectProps) => {
|
||||
const IAIMantineSearchableSelect = forwardRef((props: IAISelectProps, ref) => {
|
||||
const {
|
||||
searchable = true,
|
||||
tooltip,
|
||||
@ -74,7 +74,7 @@ const IAIMantineSearchableSelect = (props: IAISelectProps) => {
|
||||
ref={inputRef}
|
||||
label={
|
||||
label ? (
|
||||
<FormControl isDisabled={disabled}>
|
||||
<FormControl ref={ref} isDisabled={disabled}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
</FormControl>
|
||||
) : undefined
|
||||
@ -92,6 +92,8 @@ const IAIMantineSearchableSelect = (props: IAISelectProps) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
IAIMantineSearchableSelect.displayName = 'IAIMantineSearchableSelect';
|
||||
|
||||
export default memo(IAIMantineSearchableSelect);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FormControl, FormLabel, Tooltip } from '@chakra-ui/react';
|
||||
import { FormControl, FormLabel, Tooltip, forwardRef } from '@chakra-ui/react';
|
||||
import { Select, SelectProps } from '@mantine/core';
|
||||
import { useMantineSelectStyles } from 'mantine-theme/hooks/useMantineSelectStyles';
|
||||
import { RefObject, memo } from 'react';
|
||||
@ -15,7 +15,7 @@ export type IAISelectProps = Omit<SelectProps, 'label'> & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const IAIMantineSelect = (props: IAISelectProps) => {
|
||||
const IAIMantineSelect = forwardRef((props: IAISelectProps, ref) => {
|
||||
const { tooltip, inputRef, label, disabled, required, ...rest } = props;
|
||||
|
||||
const styles = useMantineSelectStyles();
|
||||
@ -25,7 +25,7 @@ const IAIMantineSelect = (props: IAISelectProps) => {
|
||||
<Select
|
||||
label={
|
||||
label ? (
|
||||
<FormControl isRequired={required} isDisabled={disabled}>
|
||||
<FormControl ref={ref} isRequired={required} isDisabled={disabled}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
</FormControl>
|
||||
) : undefined
|
||||
@ -37,6 +37,8 @@ const IAIMantineSelect = (props: IAISelectProps) => {
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
IAIMantineSelect.displayName = 'IAIMantineSelect';
|
||||
|
||||
export default memo(IAIMantineSelect);
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
NumberInputStepperProps,
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
forwardRef,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||
@ -50,7 +51,7 @@ interface Props extends Omit<NumberInputProps, 'onChange'> {
|
||||
/**
|
||||
* Customized Chakra FormControl + NumberInput multi-part component.
|
||||
*/
|
||||
const IAINumberInput = (props: Props) => {
|
||||
const IAINumberInput = forwardRef((props: Props, ref) => {
|
||||
const {
|
||||
label,
|
||||
isDisabled = false,
|
||||
@ -141,6 +142,7 @@ const IAINumberInput = (props: Props) => {
|
||||
return (
|
||||
<Tooltip {...tooltipProps}>
|
||||
<FormControl
|
||||
ref={ref}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
{...formControlProps}
|
||||
@ -172,6 +174,8 @@ const IAINumberInput = (props: Props) => {
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
IAINumberInput.displayName = 'IAINumberInput';
|
||||
|
||||
export default memo(IAINumberInput);
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
SliderTrackProps,
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
forwardRef,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||
@ -71,7 +72,7 @@ export type IAIFullSliderProps = {
|
||||
sliderIAIIconButtonProps?: IAIIconButtonProps;
|
||||
};
|
||||
|
||||
const IAISlider = (props: IAIFullSliderProps) => {
|
||||
const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const {
|
||||
label,
|
||||
@ -187,6 +188,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
ref={ref}
|
||||
onClick={forceInputBlur}
|
||||
sx={
|
||||
isCompact
|
||||
@ -354,6 +356,8 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
||||
</HStack>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
IAISlider.displayName = 'IAISlider';
|
||||
|
||||
export default memo(IAISlider);
|
||||
|
@ -72,4 +72,6 @@ const IAISwitch = (props: IAISwitchProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
IAISwitch.displayName = 'IAISwitch';
|
||||
|
||||
export default memo(IAISwitch);
|
||||
|
@ -61,7 +61,7 @@ const ParamIterations = ({ asSlider }: Props) => {
|
||||
}, [dispatch, initial]);
|
||||
|
||||
return asSlider || shouldUseSliders ? (
|
||||
<IAIInformationalPopover details="paramImages">
|
||||
<IAIInformationalPopover details="paramIterations">
|
||||
<IAISlider
|
||||
label={t('parameters.iterations')}
|
||||
step={step}
|
||||
@ -77,7 +77,7 @@ const ParamIterations = ({ asSlider }: Props) => {
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
) : (
|
||||
<IAIInformationalPopover details="paramImages">
|
||||
<IAIInformationalPopover details="paramIterations">
|
||||
<IAINumberInput
|
||||
label={t('parameters.iterations')}
|
||||
step={step}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
|
||||
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover';
|
||||
@ -9,7 +10,6 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
|
||||
|
||||
const ParamNegativeConditioning = () => {
|
||||
const negativePrompt = useAppSelector(
|
||||
@ -76,13 +76,16 @@ const ParamNegativeConditioning = () => {
|
||||
const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled;
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<ParamEmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={handleSelectEmbedding}
|
||||
>
|
||||
<IAIInformationalPopover details="paramNegativeConditioning">
|
||||
<IAIInformationalPopover
|
||||
placement="right"
|
||||
details="paramNegativeConditioning"
|
||||
>
|
||||
<FormControl>
|
||||
<ParamEmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={handleSelectEmbedding}
|
||||
>
|
||||
<IAITextarea
|
||||
id="negativePrompt"
|
||||
name="negativePrompt"
|
||||
@ -95,20 +98,20 @@ const ParamNegativeConditioning = () => {
|
||||
minH={16}
|
||||
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
</ParamEmbeddingPopover>
|
||||
{!isOpen && isEmbeddingEnabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
}}
|
||||
>
|
||||
<AddEmbeddingButton onClick={onOpen} />
|
||||
</Box>
|
||||
)}
|
||||
</FormControl>
|
||||
</ParamEmbeddingPopover>
|
||||
{!isOpen && isEmbeddingEnabled && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
insetInlineEnd: 0,
|
||||
}}
|
||||
>
|
||||
<AddEmbeddingButton onClick={onOpen} />
|
||||
</Box>
|
||||
)}
|
||||
</FormControl>
|
||||
</IAIInformationalPopover>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -104,13 +104,16 @@ const ParamPositiveConditioning = () => {
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<FormControl>
|
||||
<ParamEmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={handleSelectEmbedding}
|
||||
>
|
||||
<IAIInformationalPopover details="paramPositiveConditioning">
|
||||
<IAIInformationalPopover
|
||||
placement="right"
|
||||
details="paramPositiveConditioning"
|
||||
>
|
||||
<FormControl>
|
||||
<ParamEmbeddingPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onSelect={handleSelectEmbedding}
|
||||
>
|
||||
<IAITextarea
|
||||
id="prompt"
|
||||
name="prompt"
|
||||
@ -122,9 +125,9 @@ const ParamPositiveConditioning = () => {
|
||||
resize="vertical"
|
||||
minH={32}
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
</ParamEmbeddingPopover>
|
||||
</FormControl>
|
||||
</ParamEmbeddingPopover>
|
||||
</FormControl>
|
||||
</IAIInformationalPopover>
|
||||
{!isOpen && isEmbeddingEnabled && (
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -119,8 +119,8 @@ const ParamMainModelSelect = () => {
|
||||
data={[]}
|
||||
/>
|
||||
) : (
|
||||
<IAIInformationalPopover details="paramModel" placement="bottom">
|
||||
<Flex w="100%" alignItems="center" gap={3}>
|
||||
<Flex w="100%" alignItems="center" gap={3}>
|
||||
<IAIInformationalPopover details="paramModel" placement="bottom">
|
||||
<IAIMantineSearchableSelect
|
||||
tooltip={selectedModel?.description}
|
||||
label={t('modelManager.model')}
|
||||
@ -134,13 +134,13 @@ const ParamMainModelSelect = () => {
|
||||
onChange={handleChangeModel}
|
||||
w="100%"
|
||||
/>
|
||||
{isSyncModelEnabled && (
|
||||
<Box mt={7}>
|
||||
<SyncModelsButton iconMode />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</IAIInformationalPopover>
|
||||
</IAIInformationalPopover>
|
||||
{isSyncModelEnabled && (
|
||||
<Box mt={7}>
|
||||
<SyncModelsButton iconMode />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -37,7 +37,7 @@ const informationalContent = defineStyle((props) => {
|
||||
'colors.base.400',
|
||||
'colors.base.400'
|
||||
)(props),
|
||||
p: 0,
|
||||
p: 4,
|
||||
bg: mode('base.100', 'base.600')(props),
|
||||
border: 'none',
|
||||
shadow: 'dark-lg',
|
||||
|
Loading…
Reference in New Issue
Block a user