Fix Slider not being able to take typed input

This commit is contained in:
blessedcoolant 2023-01-24 16:43:29 +13:00
parent 884768c39d
commit 5344481809
11 changed files with 187 additions and 138 deletions

View File

@ -23,7 +23,7 @@ import {
Tooltip, Tooltip,
TooltipProps, TooltipProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React, { FocusEvent, useEffect, useMemo, useState } from 'react'; import React, { FocusEvent, useMemo, useState, useEffect } from 'react';
import { BiReset } from 'react-icons/bi'; import { BiReset } from 'react-icons/bi';
import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton'; import IAIIconButton, { IAIIconButtonProps } from './IAIIconButton';
import _ from 'lodash'; import _ from 'lodash';
@ -81,7 +81,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
withInput = false, withInput = false,
isInteger = false, isInteger = false,
inputWidth = '5.5rem', inputWidth = '5.5rem',
inputReadOnly = true, inputReadOnly = false,
withReset = false, withReset = false,
hideTooltip = false, hideTooltip = false,
isCompact = false, isCompact = false,
@ -103,32 +103,35 @@ export default function IAISlider(props: IAIFullSliderProps) {
...rest ...rest
} = props; } = props;
const [localInputValue, setLocalInputValue] = useState<string>(String(value)); const [localInputValue, setLocalInputValue] = useState<
string | number | undefined
>(String(value));
useEffect(() => {
setLocalInputValue(value);
}, [value]);
const numberInputMax = useMemo( const numberInputMax = useMemo(
() => (sliderNumberInputProps?.max ? sliderNumberInputProps.max : max), () => (sliderNumberInputProps?.max ? sliderNumberInputProps.max : max),
[max, sliderNumberInputProps?.max] [max, sliderNumberInputProps?.max]
); );
useEffect(() => { const handleSliderChange = (v: number) => {
if (String(value) !== localInputValue && localInputValue !== '') { onChange(v);
setLocalInputValue(String(value)); };
}
}, [value, localInputValue, setLocalInputValue]);
const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => { const handleInputBlur = (e: FocusEvent<HTMLInputElement>) => {
if (e.target.value === '') e.target.value = String(min);
const clamped = _.clamp( const clamped = _.clamp(
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value), isInteger ? Math.floor(Number(e.target.value)) : Number(localInputValue),
min, min,
numberInputMax numberInputMax
); );
setLocalInputValue(String(clamped));
onChange(clamped); onChange(clamped);
}; };
const handleInputChange = (v: number | string) => { const handleInputChange = (v: number | string) => {
setLocalInputValue(String(v)); setLocalInputValue(v);
onChange(Number(v));
}; };
const handleResetDisable = () => { const handleResetDisable = () => {
@ -172,7 +175,7 @@ export default function IAISlider(props: IAIFullSliderProps) {
min={min} min={min}
max={max} max={max}
step={step} step={step}
onChange={handleInputChange} onChange={handleSliderChange}
onMouseEnter={() => setShowTooltip(true)} onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)} onMouseLeave={() => setShowTooltip(false)}
focusThumbOnChange={false} focusThumbOnChange={false}
@ -236,13 +239,19 @@ export default function IAISlider(props: IAIFullSliderProps) {
<NumberInputField <NumberInputField
className="invokeai__slider-number-input" className="invokeai__slider-number-input"
width={inputWidth} width={inputWidth}
minWidth={inputWidth}
readOnly={inputReadOnly} readOnly={inputReadOnly}
minWidth={inputWidth}
{...sliderNumberInputFieldProps} {...sliderNumberInputFieldProps}
/> />
<NumberInputStepper {...sliderNumberInputStepperProps}> <NumberInputStepper {...sliderNumberInputStepperProps}>
<NumberIncrementStepper className="invokeai__slider-number-stepper" /> <NumberIncrementStepper
<NumberDecrementStepper className="invokeai__slider-number-stepper" /> onClick={() => onChange(Number(localInputValue))}
className="invokeai__slider-number-stepper"
/>
<NumberDecrementStepper
onClick={() => onChange(Number(localInputValue))}
className="invokeai__slider-number-stepper"
/>
</NumberInputStepper> </NumberInputStepper>
</NumberInput> </NumberInput>
)} )}

View File

@ -75,11 +75,12 @@ const BoundingBoxSettings = () => {
step={64} step={64}
value={boundingBoxDimensions.width} value={boundingBoxDimensions.width}
onChange={handleChangeWidth} onChange={handleChangeWidth}
handleReset={handleResetWidth}
sliderNumberInputProps={{ max: 4096 }} sliderNumberInputProps={{ max: 4096 }}
withSliderMarks withSliderMarks
withInput withInput
inputReadOnly
withReset withReset
handleReset={handleResetWidth}
/> />
<IAISlider <IAISlider
label={t('options:height')} label={t('options:height')}
@ -88,11 +89,12 @@ const BoundingBoxSettings = () => {
step={64} step={64}
value={boundingBoxDimensions.height} value={boundingBoxDimensions.height}
onChange={handleChangeHeight} onChange={handleChangeHeight}
handleReset={handleResetHeight}
sliderNumberInputProps={{ max: 4096 }} sliderNumberInputProps={{ max: 4096 }}
withSliderMarks withSliderMarks
withInput withInput
inputReadOnly
withReset withReset
handleReset={handleResetHeight}
/> />
</Flex> </Flex>
); );

View File

@ -124,11 +124,12 @@ const InfillAndScalingOptions = () => {
step={64} step={64}
value={scaledBoundingBoxDimensions.width} value={scaledBoundingBoxDimensions.width}
onChange={handleChangeScaledWidth} onChange={handleChangeScaledWidth}
handleReset={handleResetScaledWidth}
sliderNumberInputProps={{ max: 4096 }} sliderNumberInputProps={{ max: 4096 }}
withSliderMarks withSliderMarks
withInput withInput
inputReadOnly
withReset withReset
handleReset={handleResetScaledWidth}
/> />
<IAISlider <IAISlider
isInputDisabled={!isManual} isInputDisabled={!isManual}
@ -140,11 +141,12 @@ const InfillAndScalingOptions = () => {
step={64} step={64}
value={scaledBoundingBoxDimensions.height} value={scaledBoundingBoxDimensions.height}
onChange={handleChangeScaledHeight} onChange={handleChangeScaledHeight}
handleReset={handleResetScaledHeight}
sliderNumberInputProps={{ max: 4096 }} sliderNumberInputProps={{ max: 4096 }}
withSliderMarks withSliderMarks
withInput withInput
inputReadOnly
withReset withReset
handleReset={handleResetScaledHeight}
/> />
<InpaintReplace /> <InpaintReplace />
<IAISelect <IAISelect
@ -166,12 +168,12 @@ const InfillAndScalingOptions = () => {
onChange={(v) => { onChange={(v) => {
dispatch(setTileSize(v)); dispatch(setTileSize(v));
}} }}
handleReset={() => {
dispatch(setTileSize(32));
}}
withInput withInput
withSliderMarks withSliderMarks
withReset withReset
handleReset={() => {
dispatch(setTileSize(32));
}}
/> />
</Flex> </Flex>
); );

View File

@ -1,113 +0,0 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { optionsSelector } from 'features/options/store/optionsSelectors';
import {
setSeamBlur,
setSeamSize,
setSeamSteps,
setSeamStrength,
} from 'features/options/store/optionsSlice';
import _ from 'lodash';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
[optionsSelector],
(options) => {
const { seamSize, seamBlur, seamStrength, seamSteps } = options;
return {
seamSize,
seamBlur,
seamStrength,
seamSteps,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const SeamCorrectionOptions = () => {
const dispatch = useAppDispatch();
const { seamSize, seamBlur, seamStrength, seamSteps } =
useAppSelector(selector);
const { t } = useTranslation();
return (
<Flex direction="column" gap="1rem">
<IAISlider
sliderMarkRightOffset={-6}
label={t('options:seamSize')}
min={1}
max={256}
sliderNumberInputProps={{ max: 512 }}
value={seamSize}
onChange={(v) => {
dispatch(setSeamSize(v));
}}
handleReset={() => dispatch(setSeamSize(96))}
withInput
withSliderMarks
withReset
/>
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamBlur')}
min={0}
max={64}
sliderNumberInputProps={{ max: 512 }}
value={seamBlur}
onChange={(v) => {
dispatch(setSeamBlur(v));
}}
handleReset={() => {
dispatch(setSeamBlur(16));
}}
withInput
withSliderMarks
withReset
/>
<IAISlider
sliderMarkRightOffset={-7}
label={t('options:seamStrength')}
min={0.01}
max={0.99}
step={0.01}
value={seamStrength}
onChange={(v) => {
dispatch(setSeamStrength(v));
}}
handleReset={() => {
dispatch(setSeamStrength(0.7));
}}
withInput
withSliderMarks
withReset
/>
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamSteps')}
min={1}
max={32}
sliderNumberInputProps={{ max: 100 }}
value={seamSteps}
onChange={(v) => {
dispatch(setSeamSteps(v));
}}
handleReset={() => {
dispatch(setSeamSteps(10));
}}
withInput
withSliderMarks
withReset
/>
</Flex>
);
};
export default SeamCorrectionOptions;

View File

@ -0,0 +1,32 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamBlur } from 'features/options/store/optionsSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function SeamBlur() {
const dispatch = useAppDispatch();
const seamBlur = useAppSelector((state: RootState) => state.options.seamBlur);
const { t } = useTranslation();
return (
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamBlur')}
min={0}
max={64}
sliderNumberInputProps={{ max: 512 }}
value={seamBlur}
onChange={(v) => {
dispatch(setSeamBlur(v));
}}
withInput
withSliderMarks
withReset
handleReset={() => {
dispatch(setSeamBlur(16));
}}
/>
);
}

View File

@ -0,0 +1,18 @@
import { Flex } from '@chakra-ui/react';
import SeamBlur from './SeamBlur';
import SeamSize from './SeamSize';
import SeamSteps from './SeamSteps';
import SeamStrength from './SeamStrength';
const SeamCorrectionOptions = () => {
return (
<Flex direction="column" gap="1rem">
<SeamSize />
<SeamBlur />
<SeamStrength />
<SeamSteps />
</Flex>
);
};
export default SeamCorrectionOptions;

View File

@ -0,0 +1,31 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSize } from 'features/options/store/optionsSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function SeamSize() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seamSize = useAppSelector((state: RootState) => state.options.seamSize);
return (
<IAISlider
sliderMarkRightOffset={-6}
label={t('options:seamSize')}
min={1}
max={256}
sliderNumberInputProps={{ max: 512 }}
value={seamSize}
onChange={(v) => {
dispatch(setSeamSize(v));
}}
withInput
withSliderMarks
withReset
handleReset={() => dispatch(setSeamSize(96))}
/>
);
}

View File

@ -0,0 +1,34 @@
import type { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamSteps } from 'features/options/store/optionsSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function SeamSteps() {
const { t } = useTranslation();
const seamSteps = useAppSelector(
(state: RootState) => state.options.seamSteps
);
const dispatch = useAppDispatch();
return (
<IAISlider
sliderMarkRightOffset={-4}
label={t('options:seamSteps')}
min={1}
max={32}
sliderNumberInputProps={{ max: 999 }}
value={seamSteps}
onChange={(v) => {
dispatch(setSeamSteps(v));
}}
withInput
withSliderMarks
withReset
handleReset={() => {
dispatch(setSeamSteps(10));
}}
/>
);
}

View File

@ -0,0 +1,34 @@
import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISlider from 'common/components/IAISlider';
import { setSeamStrength } from 'features/options/store/optionsSlice';
import React from 'react';
import { useTranslation } from 'react-i18next';
export default function SeamStrength() {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const seamStrength = useAppSelector(
(state: RootState) => state.options.seamStrength
);
return (
<IAISlider
sliderMarkRightOffset={-7}
label={t('options:seamStrength')}
min={0.01}
max={0.99}
step={0.01}
value={seamStrength}
onChange={(v) => {
dispatch(setSeamStrength(v));
}}
withInput
withSliderMarks
withReset
handleReset={() => {
dispatch(setSeamStrength(0.7));
}}
/>
);
}

View File

@ -36,9 +36,9 @@ export default function ImageToImageStrength(props: ImageToImageStrengthProps) {
isInteger={false} isInteger={false}
styleClass={styleClass} styleClass={styleClass}
withInput withInput
withReset
withSliderMarks withSliderMarks
inputWidth={'5.5rem'} inputWidth={'5.5rem'}
withReset
handleReset={handleImg2ImgStrengthReset} handleReset={handleImg2ImgStrengthReset}
/> />
); );

View File

@ -1,7 +1,7 @@
// import { Feature } from 'app/features'; // import { Feature } from 'app/features';
import { Feature } from 'app/features'; import { Feature } from 'app/features';
import ImageToImageStrength from 'features/options/components/AdvancedOptions/ImageToImage/ImageToImageStrength'; import ImageToImageStrength from 'features/options/components/AdvancedOptions/ImageToImage/ImageToImageStrength';
import SeamCorrectionOptions from 'features/options/components/AdvancedOptions/Canvas/SeamCorrectionOptions'; import SeamCorrectionOptions from 'features/options/components/AdvancedOptions/Canvas/SeamCorrectionOptions/SeamCorrectionOptions';
import SeedOptions from 'features/options/components/AdvancedOptions/Seed/SeedOptions'; import SeedOptions from 'features/options/components/AdvancedOptions/Seed/SeedOptions';
import GenerateVariationsToggle from 'features/options/components/AdvancedOptions/Variations/GenerateVariations'; import GenerateVariationsToggle from 'features/options/components/AdvancedOptions/Variations/GenerateVariations';
import VariationsOptions from 'features/options/components/AdvancedOptions/Variations/VariationsOptions'; import VariationsOptions from 'features/options/components/AdvancedOptions/Variations/VariationsOptions';