fix(ui): popover ref & wrapping of children (wip)

This commit is contained in:
psychedelicious 2023-09-21 20:01:00 +10:00 committed by Kent Keirsey
parent b6e9cd4fe2
commit 5aefa49d7d
13 changed files with 140 additions and 106 deletions

View File

@ -1213,14 +1213,14 @@
}, },
"dynamicPromptsCombinatorial": { "dynamicPromptsCombinatorial": {
"heading": "Combinatorial Generation", "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": { "infillMethod": {
"heading": "Infill Method", "heading": "Infill Method",
"paragraph": "Method to infill the selected area." "paragraph": "Method to infill the selected area."
}, },
"lora": { "lora": {
"heading": "LoRA", "heading": "LoRA Weight",
"paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image." "paragraph": "Weight of the LoRA. Higher weight will lead to larger impacts on the final image."
}, },
"noiseEnable": { "noiseEnable": {
@ -1239,21 +1239,21 @@
"heading": "Denoising Strength", "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." "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": { "paramIterations": {
"heading": "Images", "heading": "Iterations",
"paragraph": "Number of images that will be generated." "paragraph": "The number of images to generate. If Dynamic Prompts is enabled, each of the prompts will be generated this many times."
}, },
"paramModel": { "paramModel": {
"heading": "Model", "heading": "Model",
"paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content." "paragraph": "Model used for the denoising steps. Different models are trained to specialize in producing different aesthetic results and content."
}, },
"paramNegativeConditioning": { "paramNegativeConditioning": {
"heading": "Negative Prompts", "heading": "Negative Prompt",
"paragraph": "This is where you enter your negative prompts." "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": { "paramPositiveConditioning": {
"heading": "Positive Prompts", "heading": "Positive Prompt",
"paragraph": "This is where you enter your positive prompts." "paragraph": "Guides the generation process. You may use any words or phrases. Supports Compel and Dynamic Prompts syntaxes and embeddings."
}, },
"paramRatio": { "paramRatio": {
"heading": "Ratio", "heading": "Ratio",

View File

@ -1,37 +1,43 @@
import { import {
Box,
Button, Button,
Popover, Divider,
PopoverTrigger,
PopoverContent,
PopoverArrow,
PopoverCloseButton,
PopoverHeader,
PopoverBody,
PopoverProps,
Flex, Flex,
Text, Heading,
Image, Image,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverProps,
PopoverTrigger,
Portal,
Text,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppSelector } from '../../app/store/storeHooks'; import { ReactNode, memo } from 'react';
import { useTranslation } from 'react-i18next'; 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; details: string;
children: JSX.Element; children: ReactNode;
image?: string; image?: string;
buttonLabel?: string; buttonLabel?: string;
buttonHref?: string; buttonHref?: string;
placement?: PopoverProps['placement']; placement?: PopoverProps['placement'];
} };
function IAIInformationalPopover({ const IAIInformationalPopover = ({
details, details,
image, image,
buttonLabel, buttonLabel,
buttonHref, buttonHref,
children, children,
placement, placement,
}: Props): JSX.Element { }: Props) => {
const shouldEnableInformationalPopovers = useAppSelector( const shouldEnableInformationalPopovers = useAppSelector(
(state) => state.system.shouldEnableInformationalPopovers (state) => state.system.shouldEnableInformationalPopovers
); );
@ -41,18 +47,21 @@ function IAIInformationalPopover({
const paragraph = t(`popovers.${details}.paragraph`); const paragraph = t(`popovers.${details}.paragraph`);
if (!shouldEnableInformationalPopovers) { if (!shouldEnableInformationalPopovers) {
return children; return <>{children}</>;
} else { }
return ( return (
<Popover <Popover
placement={placement || 'top'} placement={placement || 'top'}
closeOnBlur={false} closeOnBlur={false}
trigger="hover" trigger="hover"
variant="informational" variant="informational"
openDelay={OPEN_DELAY}
> >
<PopoverTrigger> <PopoverTrigger>
<div>{children}</div> <Box w="full">{children}</Box>
</PopoverTrigger> </PopoverTrigger>
<Portal>
<PopoverContent> <PopoverContent>
<PopoverArrow /> <PopoverArrow />
<PopoverCloseButton /> <PopoverCloseButton />
@ -83,14 +92,17 @@ function IAIInformationalPopover({
gap: 3, gap: 3,
flexDirection: 'column', flexDirection: 'column',
width: '100%', width: '100%',
p: 3,
pt: heading ? 0 : 3,
}} }}
> >
{heading && <PopoverHeader>{heading}</PopoverHeader>} {heading && (
<Text sx={{ px: 3 }}>{paragraph}</Text> <>
<Heading size="sm">{heading}</Heading>
<Divider />
</>
)}
<Text>{paragraph}</Text>
{buttonLabel && ( {buttonLabel && (
<Flex sx={{ px: 3 }} justifyContent="flex-end"> <Flex justifyContent="flex-end">
<Button <Button
onClick={() => window.open(buttonHref)} onClick={() => window.open(buttonHref)}
size="sm" size="sm"
@ -104,9 +116,9 @@ function IAIInformationalPopover({
</Flex> </Flex>
</PopoverBody> </PopoverBody>
</PopoverContent> </PopoverContent>
</Portal>
</Popover> </Popover>
); );
} };
}
export default IAIInformationalPopover; export default memo(IAIInformationalPopover);

View File

@ -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 { MultiSelect, MultiSelectProps } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
@ -11,7 +11,7 @@ type IAIMultiSelectProps = Omit<MultiSelectProps, 'label'> & {
label?: string; label?: string;
}; };
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => { const IAIMantineMultiSelect = forwardRef((props: IAIMultiSelectProps, ref) => {
const { const {
searchable = true, searchable = true,
tooltip, tooltip,
@ -47,7 +47,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
<MultiSelect <MultiSelect
label={ label={
label ? ( label ? (
<FormControl isDisabled={disabled}> <FormControl ref={ref} isDisabled={disabled}>
<FormLabel>{label}</FormLabel> <FormLabel>{label}</FormLabel>
</FormControl> </FormControl>
) : undefined ) : undefined
@ -63,6 +63,8 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
/> />
</Tooltip> </Tooltip>
); );
}; });
IAIMantineMultiSelect.displayName = 'IAIMantineMultiSelect';
export default memo(IAIMantineMultiSelect); export default memo(IAIMantineMultiSelect);

View File

@ -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 { Select, SelectProps } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice'; import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
@ -17,7 +17,7 @@ type IAISelectProps = Omit<SelectProps, 'label'> & {
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement>;
}; };
const IAIMantineSearchableSelect = (props: IAISelectProps) => { const IAIMantineSearchableSelect = forwardRef((props: IAISelectProps, ref) => {
const { const {
searchable = true, searchable = true,
tooltip, tooltip,
@ -74,7 +74,7 @@ const IAIMantineSearchableSelect = (props: IAISelectProps) => {
ref={inputRef} ref={inputRef}
label={ label={
label ? ( label ? (
<FormControl isDisabled={disabled}> <FormControl ref={ref} isDisabled={disabled}>
<FormLabel>{label}</FormLabel> <FormLabel>{label}</FormLabel>
</FormControl> </FormControl>
) : undefined ) : undefined
@ -92,6 +92,8 @@ const IAIMantineSearchableSelect = (props: IAISelectProps) => {
/> />
</Tooltip> </Tooltip>
); );
}; });
IAIMantineSearchableSelect.displayName = 'IAIMantineSearchableSelect';
export default memo(IAIMantineSearchableSelect); export default memo(IAIMantineSearchableSelect);

View File

@ -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 { Select, SelectProps } from '@mantine/core';
import { useMantineSelectStyles } from 'mantine-theme/hooks/useMantineSelectStyles'; import { useMantineSelectStyles } from 'mantine-theme/hooks/useMantineSelectStyles';
import { RefObject, memo } from 'react'; import { RefObject, memo } from 'react';
@ -15,7 +15,7 @@ export type IAISelectProps = Omit<SelectProps, 'label'> & {
label?: string; label?: string;
}; };
const IAIMantineSelect = (props: IAISelectProps) => { const IAIMantineSelect = forwardRef((props: IAISelectProps, ref) => {
const { tooltip, inputRef, label, disabled, required, ...rest } = props; const { tooltip, inputRef, label, disabled, required, ...rest } = props;
const styles = useMantineSelectStyles(); const styles = useMantineSelectStyles();
@ -25,7 +25,7 @@ const IAIMantineSelect = (props: IAISelectProps) => {
<Select <Select
label={ label={
label ? ( label ? (
<FormControl isRequired={required} isDisabled={disabled}> <FormControl ref={ref} isRequired={required} isDisabled={disabled}>
<FormLabel>{label}</FormLabel> <FormLabel>{label}</FormLabel>
</FormControl> </FormControl>
) : undefined ) : undefined
@ -37,6 +37,8 @@ const IAIMantineSelect = (props: IAISelectProps) => {
/> />
</Tooltip> </Tooltip>
); );
}; });
IAIMantineSelect.displayName = 'IAIMantineSelect';
export default memo(IAIMantineSelect); export default memo(IAIMantineSelect);

View File

@ -13,6 +13,7 @@ import {
NumberInputStepperProps, NumberInputStepperProps,
Tooltip, Tooltip,
TooltipProps, TooltipProps,
forwardRef,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { stopPastePropagation } from 'common/util/stopPastePropagation'; import { stopPastePropagation } from 'common/util/stopPastePropagation';
@ -50,7 +51,7 @@ interface Props extends Omit<NumberInputProps, 'onChange'> {
/** /**
* Customized Chakra FormControl + NumberInput multi-part component. * Customized Chakra FormControl + NumberInput multi-part component.
*/ */
const IAINumberInput = (props: Props) => { const IAINumberInput = forwardRef((props: Props, ref) => {
const { const {
label, label,
isDisabled = false, isDisabled = false,
@ -141,6 +142,7 @@ const IAINumberInput = (props: Props) => {
return ( return (
<Tooltip {...tooltipProps}> <Tooltip {...tooltipProps}>
<FormControl <FormControl
ref={ref}
isDisabled={isDisabled} isDisabled={isDisabled}
isInvalid={isInvalid} isInvalid={isInvalid}
{...formControlProps} {...formControlProps}
@ -172,6 +174,8 @@ const IAINumberInput = (props: Props) => {
</FormControl> </FormControl>
</Tooltip> </Tooltip>
); );
}; });
IAINumberInput.displayName = 'IAINumberInput';
export default memo(IAINumberInput); export default memo(IAINumberInput);

View File

@ -22,6 +22,7 @@ import {
SliderTrackProps, SliderTrackProps,
Tooltip, Tooltip,
TooltipProps, TooltipProps,
forwardRef,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
@ -71,7 +72,7 @@ export type IAIFullSliderProps = {
sliderIAIIconButtonProps?: IAIIconButtonProps; sliderIAIIconButtonProps?: IAIIconButtonProps;
}; };
const IAISlider = (props: IAIFullSliderProps) => { const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
const { const {
label, label,
@ -187,6 +188,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
return ( return (
<FormControl <FormControl
ref={ref}
onClick={forceInputBlur} onClick={forceInputBlur}
sx={ sx={
isCompact isCompact
@ -354,6 +356,8 @@ const IAISlider = (props: IAIFullSliderProps) => {
</HStack> </HStack>
</FormControl> </FormControl>
); );
}; });
IAISlider.displayName = 'IAISlider';
export default memo(IAISlider); export default memo(IAISlider);

View File

@ -72,4 +72,6 @@ const IAISwitch = (props: IAISwitchProps) => {
); );
}; };
IAISwitch.displayName = 'IAISwitch';
export default memo(IAISwitch); export default memo(IAISwitch);

View File

@ -61,7 +61,7 @@ const ParamIterations = ({ asSlider }: Props) => {
}, [dispatch, initial]); }, [dispatch, initial]);
return asSlider || shouldUseSliders ? ( return asSlider || shouldUseSliders ? (
<IAIInformationalPopover details="paramImages"> <IAIInformationalPopover details="paramIterations">
<IAISlider <IAISlider
label={t('parameters.iterations')} label={t('parameters.iterations')}
step={step} step={step}
@ -77,7 +77,7 @@ const ParamIterations = ({ asSlider }: Props) => {
/> />
</IAIInformationalPopover> </IAIInformationalPopover>
) : ( ) : (
<IAIInformationalPopover details="paramImages"> <IAIInformationalPopover details="paramIterations">
<IAINumberInput <IAINumberInput
label={t('parameters.iterations')} label={t('parameters.iterations')}
step={step} step={step}

View File

@ -1,6 +1,7 @@
import { Box, FormControl, useDisclosure } from '@chakra-ui/react'; import { Box, FormControl, useDisclosure } from '@chakra-ui/react';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
import IAITextarea from 'common/components/IAITextarea'; import IAITextarea from 'common/components/IAITextarea';
import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton'; import AddEmbeddingButton from 'features/embedding/components/AddEmbeddingButton';
import ParamEmbeddingPopover from 'features/embedding/components/ParamEmbeddingPopover'; 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 { flushSync } from 'react-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus'; import { useFeatureStatus } from '../../../../system/hooks/useFeatureStatus';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover';
const ParamNegativeConditioning = () => { const ParamNegativeConditioning = () => {
const negativePrompt = useAppSelector( const negativePrompt = useAppSelector(
@ -76,13 +76,16 @@ const ParamNegativeConditioning = () => {
const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled; const isEmbeddingEnabled = useFeatureStatus('embedding').isFeatureEnabled;
return ( return (
<IAIInformationalPopover
placement="right"
details="paramNegativeConditioning"
>
<FormControl> <FormControl>
<ParamEmbeddingPopover <ParamEmbeddingPopover
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
onSelect={handleSelectEmbedding} onSelect={handleSelectEmbedding}
> >
<IAIInformationalPopover details="paramNegativeConditioning">
<IAITextarea <IAITextarea
id="negativePrompt" id="negativePrompt"
name="negativePrompt" name="negativePrompt"
@ -95,7 +98,6 @@ const ParamNegativeConditioning = () => {
minH={16} minH={16}
{...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })} {...(isEmbeddingEnabled && { onKeyDown: handleKeyDown })}
/> />
</IAIInformationalPopover>
</ParamEmbeddingPopover> </ParamEmbeddingPopover>
{!isOpen && isEmbeddingEnabled && ( {!isOpen && isEmbeddingEnabled && (
<Box <Box
@ -109,6 +111,7 @@ const ParamNegativeConditioning = () => {
</Box> </Box>
)} )}
</FormControl> </FormControl>
</IAIInformationalPopover>
); );
}; };

View File

@ -104,13 +104,16 @@ const ParamPositiveConditioning = () => {
return ( return (
<Box position="relative"> <Box position="relative">
<IAIInformationalPopover
placement="right"
details="paramPositiveConditioning"
>
<FormControl> <FormControl>
<ParamEmbeddingPopover <ParamEmbeddingPopover
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
onSelect={handleSelectEmbedding} onSelect={handleSelectEmbedding}
> >
<IAIInformationalPopover details="paramPositiveConditioning">
<IAITextarea <IAITextarea
id="prompt" id="prompt"
name="prompt" name="prompt"
@ -122,9 +125,9 @@ const ParamPositiveConditioning = () => {
resize="vertical" resize="vertical"
minH={32} minH={32}
/> />
</IAIInformationalPopover>
</ParamEmbeddingPopover> </ParamEmbeddingPopover>
</FormControl> </FormControl>
</IAIInformationalPopover>
{!isOpen && isEmbeddingEnabled && ( {!isOpen && isEmbeddingEnabled && (
<Box <Box
sx={{ sx={{

View File

@ -119,8 +119,8 @@ const ParamMainModelSelect = () => {
data={[]} 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 <IAIMantineSearchableSelect
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
label={t('modelManager.model')} label={t('modelManager.model')}
@ -134,13 +134,13 @@ const ParamMainModelSelect = () => {
onChange={handleChangeModel} onChange={handleChangeModel}
w="100%" w="100%"
/> />
</IAIInformationalPopover>
{isSyncModelEnabled && ( {isSyncModelEnabled && (
<Box mt={7}> <Box mt={7}>
<SyncModelsButton iconMode /> <SyncModelsButton iconMode />
</Box> </Box>
)} )}
</Flex> </Flex>
</IAIInformationalPopover>
); );
}; };

View File

@ -37,7 +37,7 @@ const informationalContent = defineStyle((props) => {
'colors.base.400', 'colors.base.400',
'colors.base.400' 'colors.base.400'
)(props), )(props),
p: 0, p: 4,
bg: mode('base.100', 'base.600')(props), bg: mode('base.100', 'base.600')(props),
border: 'none', border: 'none',
shadow: 'dark-lg', shadow: 'dark-lg',