mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add eslint rule react/jsx-no-bind
This rule enforces no arrow functions in component props. In practice, it means all functions passed as component props must be wrapped in `useCallback()`. This is a performance optimization to prevent unnecessary rerenders. The rule is added and all violations have been fixed, whew!
This commit is contained in:
parent
5eaea9dd64
commit
3a0ec635c9
@ -24,6 +24,7 @@ module.exports = {
|
||||
root: true,
|
||||
rules: {
|
||||
curly: 'error',
|
||||
'react/jsx-no-bind': ['error', { allowBind: true }],
|
||||
'react/jsx-curly-brace-presence': [
|
||||
'error',
|
||||
{ props: 'never', children: 'never' },
|
||||
|
@ -8,7 +8,14 @@ import {
|
||||
forwardRef,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { cloneElement, memo, ReactElement, ReactNode, useRef } from 'react';
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import IAIButton from './IAIButton';
|
||||
|
||||
@ -38,15 +45,15 @@ const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleAccept = () => {
|
||||
const handleAccept = useCallback(() => {
|
||||
acceptCallback();
|
||||
onClose();
|
||||
};
|
||||
}, [acceptCallback, onClose]);
|
||||
|
||||
const handleCancel = () => {
|
||||
const handleCancel = useCallback(() => {
|
||||
cancelCallback && cancelCallback();
|
||||
onClose();
|
||||
};
|
||||
}, [cancelCallback, onClose]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useColorMode } from '@chakra-ui/react';
|
||||
import { TextInput, TextInputProps } from '@mantine/core';
|
||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||
import { useCallback } from 'react';
|
||||
import { mode } from 'theme/util/mode';
|
||||
|
||||
type IAIMantineTextInputProps = TextInputProps;
|
||||
@ -20,26 +21,37 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) {
|
||||
} = useChakraThemeTokens();
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
styles={() => ({
|
||||
input: {
|
||||
color: mode(base900, base100)(colorMode),
|
||||
backgroundColor: mode(base50, base900)(colorMode),
|
||||
borderColor: mode(base200, base800)(colorMode),
|
||||
borderWidth: 2,
|
||||
outline: 'none',
|
||||
':focus': {
|
||||
borderColor: mode(accent300, accent500)(colorMode),
|
||||
},
|
||||
const stylesFunc = useCallback(
|
||||
() => ({
|
||||
input: {
|
||||
color: mode(base900, base100)(colorMode),
|
||||
backgroundColor: mode(base50, base900)(colorMode),
|
||||
borderColor: mode(base200, base800)(colorMode),
|
||||
borderWidth: 2,
|
||||
outline: 'none',
|
||||
':focus': {
|
||||
borderColor: mode(accent300, accent500)(colorMode),
|
||||
},
|
||||
label: {
|
||||
color: mode(base700, base300)(colorMode),
|
||||
fontWeight: 'normal',
|
||||
marginBottom: 4,
|
||||
},
|
||||
})}
|
||||
{...rest}
|
||||
/>
|
||||
},
|
||||
label: {
|
||||
color: mode(base700, base300)(colorMode),
|
||||
fontWeight: 'normal' as const,
|
||||
marginBottom: 4,
|
||||
},
|
||||
}),
|
||||
[
|
||||
accent300,
|
||||
accent500,
|
||||
base100,
|
||||
base200,
|
||||
base300,
|
||||
base50,
|
||||
base700,
|
||||
base800,
|
||||
base900,
|
||||
colorMode,
|
||||
]
|
||||
);
|
||||
|
||||
return <TextInput styles={stylesFunc} {...rest} />;
|
||||
}
|
||||
|
@ -98,28 +98,34 @@ const IAINumberInput = forwardRef((props: Props, ref) => {
|
||||
}
|
||||
}, [value, valueAsString]);
|
||||
|
||||
const handleOnChange = (v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
onChange(isInteger ? Math.floor(Number(v)) : Number(v));
|
||||
}
|
||||
};
|
||||
const handleOnChange = useCallback(
|
||||
(v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
onChange(isInteger ? Math.floor(Number(v)) : Number(v));
|
||||
}
|
||||
},
|
||||
[isInteger, onChange]
|
||||
);
|
||||
|
||||
/**
|
||||
* Clicking the steppers allows the value to go outside bounds; we need to
|
||||
* clamp it on blur and floor it if needed.
|
||||
*/
|
||||
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
const clamped = clamp(
|
||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||
min,
|
||||
max
|
||||
);
|
||||
setValueAsString(String(clamped));
|
||||
onChange(clamped);
|
||||
};
|
||||
const handleBlur = useCallback(
|
||||
(e: FocusEvent<HTMLInputElement>) => {
|
||||
const clamped = clamp(
|
||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||
min,
|
||||
max
|
||||
);
|
||||
setValueAsString(String(clamped));
|
||||
onChange(clamped);
|
||||
},
|
||||
[isInteger, max, min, onChange]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo, MouseEvent } from 'react';
|
||||
import { memo, MouseEvent, useCallback } from 'react';
|
||||
import IAIOption from './IAIOption';
|
||||
|
||||
type IAISelectProps = SelectProps & {
|
||||
@ -33,15 +33,16 @@ const IAISelect = (props: IAISelectProps) => {
|
||||
spaceEvenly,
|
||||
...rest
|
||||
} = props;
|
||||
const handleClick = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
e.nativeEvent.stopPropagation();
|
||||
e.nativeEvent.cancelBubble = true;
|
||||
}, []);
|
||||
return (
|
||||
<FormControl
|
||||
isDisabled={isDisabled}
|
||||
onClick={(e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
e.nativeEvent.stopPropagation();
|
||||
e.nativeEvent.cancelBubble = true;
|
||||
}}
|
||||
onClick={handleClick}
|
||||
sx={
|
||||
horizontal
|
||||
? {
|
||||
|
@ -186,6 +186,13 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => setShowTooltip(true), []);
|
||||
const handleMouseLeave = useCallback(() => setShowTooltip(false), []);
|
||||
const handleStepperClick = useCallback(
|
||||
() => onChange(Number(localInputValue)),
|
||||
[localInputValue, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
ref={ref}
|
||||
@ -219,8 +226,8 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
||||
max={max}
|
||||
step={step}
|
||||
onChange={handleSliderChange}
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
focusThumbOnChange={false}
|
||||
isDisabled={isDisabled}
|
||||
{...rest}
|
||||
@ -332,12 +339,8 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
||||
{...sliderNumberInputFieldProps}
|
||||
/>
|
||||
<NumberInputStepper {...sliderNumberInputStepperProps}>
|
||||
<NumberIncrementStepper
|
||||
onClick={() => onChange(Number(localInputValue))}
|
||||
/>
|
||||
<NumberDecrementStepper
|
||||
onClick={() => onChange(Number(localInputValue))}
|
||||
/>
|
||||
<NumberIncrementStepper onClick={handleStepperClick} />
|
||||
<NumberDecrementStepper onClick={handleStepperClick} />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
)}
|
||||
|
@ -146,16 +146,15 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
||||
};
|
||||
}, [inputRef]);
|
||||
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
// Bail out if user hits spacebar - do not open the uploader
|
||||
if (e.key === ' ') {
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...getRootProps({ style: {} })}
|
||||
onKeyDown={(e: KeyboardEvent) => {
|
||||
// Bail out if user hits spacebar - do not open the uploader
|
||||
if (e.key === ' ') {
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box {...getRootProps({ style: {} })} onKeyDown={handleKeyDown}>
|
||||
<input {...getInputProps()} />
|
||||
{children}
|
||||
<AnimatePresence>
|
||||
|
@ -5,17 +5,22 @@ import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
import { isStagingSelector } from '../store/canvasSelectors';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
const ClearCanvasHistoryButtonModal = () => {
|
||||
const isStaging = useAppSelector(isStagingSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const acceptCallback = useCallback(
|
||||
() => dispatch(clearCanvasHistory()),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIAlertDialog
|
||||
title={t('unifiedCanvas.clearCanvasHistory')}
|
||||
acceptCallback={() => dispatch(clearCanvasHistory())}
|
||||
acceptCallback={acceptCallback}
|
||||
acceptButtonText={t('unifiedCanvas.clearHistory')}
|
||||
triggerComponent={
|
||||
<IAIButton size="sm" leftIcon={<FaTrash />} isDisabled={isStaging}>
|
||||
|
@ -20,7 +20,8 @@ import {
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -95,18 +96,35 @@ const IAICanvasMaskOptions = () => {
|
||||
[isMaskEnabled]
|
||||
);
|
||||
|
||||
const handleToggleMaskLayer = () => {
|
||||
const handleToggleMaskLayer = useCallback(() => {
|
||||
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
|
||||
};
|
||||
}, [dispatch, layer]);
|
||||
|
||||
const handleClearMask = () => dispatch(clearMask());
|
||||
const handleClearMask = useCallback(() => {
|
||||
dispatch(clearMask());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleToggleEnableMask = () =>
|
||||
const handleToggleEnableMask = useCallback(() => {
|
||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||
}, [dispatch, isMaskEnabled]);
|
||||
|
||||
const handleSaveMask = async () => {
|
||||
const handleSaveMask = useCallback(async () => {
|
||||
dispatch(canvasMaskSavedToGallery());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleChangePreserveMaskedArea = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldPreserveMaskedArea(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeMaskColor = useCallback(
|
||||
(newColor: RgbaColor) => {
|
||||
dispatch(setMaskColor(newColor));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIPopover
|
||||
@ -131,15 +149,10 @@ const IAICanvasMaskOptions = () => {
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.preserveMaskedArea')}
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldPreserveMaskedArea(e.target.checked))
|
||||
}
|
||||
onChange={handleChangePreserveMaskedArea}
|
||||
/>
|
||||
<Box sx={{ paddingTop: 2, paddingBottom: 2 }}>
|
||||
<IAIColorPicker
|
||||
color={maskColor}
|
||||
onChange={(newColor) => dispatch(setMaskColor(newColor))}
|
||||
/>
|
||||
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||
</Box>
|
||||
<IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}>
|
||||
Save Mask
|
||||
|
@ -10,6 +10,7 @@ import { redo } from 'features/canvas/store/canvasSlice';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const canvasRedoSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
@ -34,9 +35,9 @@ export default function IAICanvasRedoButton() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleRedo = () => {
|
||||
const handleRedo = useCallback(() => {
|
||||
dispatch(redo());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useHotkeys(
|
||||
['meta+shift+z', 'ctrl+shift+z', 'control+y', 'meta+y'],
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import { ChangeEvent, memo } from 'react';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaWrench } from 'react-icons/fa';
|
||||
@ -86,8 +86,52 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
[shouldSnapToGrid]
|
||||
);
|
||||
|
||||
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldSnapToGrid(e.target.checked));
|
||||
const handleChangeShouldSnapToGrid = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldSnapToGrid(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeShouldShowIntermediates = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldShowGrid = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowGrid(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldDarkenOutsideBoundingBox = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAutoSave = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoSave(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldCropToBoundingBoxOnSave = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldRestrictStrokesToBox = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldShowCanvasDebugInfo = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAntialias = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAntialias(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIPopover
|
||||
@ -104,14 +148,12 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showIntermediates')}
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldShowIntermediates}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showGrid')}
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
||||
onChange={handleChangeShouldShowGrid}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.snapToGrid')}
|
||||
@ -121,41 +163,33 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.darkenOutsideSelection')}
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldDarkenOutsideBoundingBox}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
||||
onChange={handleChangeShouldAutoSave}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||
isChecked={shouldCropToBoundingBoxOnSave}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldCropToBoundingBoxOnSave}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.limitStrokesToBox')}
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldRestrictStrokesToBox}
|
||||
/>
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldShowCanvasDebugInfo}
|
||||
/>
|
||||
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.antialiasing')}
|
||||
isChecked={shouldAntialias}
|
||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
||||
onChange={handleChangeShouldAntialias}
|
||||
/>
|
||||
<ClearCanvasHistoryButtonModal />
|
||||
</Flex>
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
setTool,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { clamp, isEqual } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { RgbaColor } from 'react-colorful';
|
||||
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -172,11 +173,33 @@ const IAICanvasToolChooserOptions = () => {
|
||||
[brushColor]
|
||||
);
|
||||
|
||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
||||
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
|
||||
const handleFillRect = () => dispatch(addFillRect());
|
||||
const handleEraseBoundingBox = () => dispatch(addEraseRect());
|
||||
const handleSelectBrushTool = useCallback(() => {
|
||||
dispatch(setTool('brush'));
|
||||
}, [dispatch]);
|
||||
const handleSelectEraserTool = useCallback(() => {
|
||||
dispatch(setTool('eraser'));
|
||||
}, [dispatch]);
|
||||
const handleSelectColorPickerTool = useCallback(() => {
|
||||
dispatch(setTool('colorPicker'));
|
||||
}, [dispatch]);
|
||||
const handleFillRect = useCallback(() => {
|
||||
dispatch(addFillRect());
|
||||
}, [dispatch]);
|
||||
const handleEraseBoundingBox = useCallback(() => {
|
||||
dispatch(addEraseRect());
|
||||
}, [dispatch]);
|
||||
const handleChangeBrushSize = useCallback(
|
||||
(newSize: number) => {
|
||||
dispatch(setBrushSize(newSize));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeBrushColor = useCallback(
|
||||
(newColor: RgbaColor) => {
|
||||
dispatch(setBrushColor(newColor));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
@ -233,7 +256,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
label={t('unifiedCanvas.brushSize')}
|
||||
value={brushSize}
|
||||
withInput
|
||||
onChange={(newSize) => dispatch(setBrushSize(newSize))}
|
||||
onChange={handleChangeBrushSize}
|
||||
sliderNumberInputProps={{ max: 500 }}
|
||||
/>
|
||||
</Flex>
|
||||
@ -247,7 +270,7 @@ const IAICanvasToolChooserOptions = () => {
|
||||
<IAIColorPicker
|
||||
withNumberInput={true}
|
||||
color={brushColor}
|
||||
onChange={(newColor) => dispatch(setBrushColor(newColor))}
|
||||
onChange={handleChangeBrushColor}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
@ -151,7 +151,9 @@ const IAICanvasToolbar = () => {
|
||||
[canvasBaseLayer]
|
||||
);
|
||||
|
||||
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
||||
const handleSelectMoveTool = useCallback(() => {
|
||||
dispatch(setTool('move'));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickResetCanvasView = useSingleAndDoubleClick(
|
||||
() => handleResetCanvasView(false),
|
||||
@ -174,36 +176,39 @@ const IAICanvasToolbar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleResetCanvas = () => {
|
||||
const handleResetCanvas = useCallback(() => {
|
||||
dispatch(resetCanvas());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleMergeVisible = () => {
|
||||
const handleMergeVisible = useCallback(() => {
|
||||
dispatch(canvasMerged());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleSaveToGallery = () => {
|
||||
const handleSaveToGallery = useCallback(() => {
|
||||
dispatch(canvasSavedToGallery());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleCopyImageToClipboard = () => {
|
||||
const handleCopyImageToClipboard = useCallback(() => {
|
||||
if (!isClipboardAPIAvailable) {
|
||||
return;
|
||||
}
|
||||
dispatch(canvasCopiedToClipboard());
|
||||
};
|
||||
}, [dispatch, isClipboardAPIAvailable]);
|
||||
|
||||
const handleDownloadAsImage = () => {
|
||||
const handleDownloadAsImage = useCallback(() => {
|
||||
dispatch(canvasDownloadedAsImage());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleChangeLayer = (v: string) => {
|
||||
const newLayer = v as CanvasLayer;
|
||||
dispatch(setLayer(newLayer));
|
||||
if (newLayer === 'mask' && !isMaskEnabled) {
|
||||
dispatch(setIsMaskEnabled(true));
|
||||
}
|
||||
};
|
||||
const handleChangeLayer = useCallback(
|
||||
(v: string) => {
|
||||
const newLayer = v as CanvasLayer;
|
||||
dispatch(setLayer(newLayer));
|
||||
if (newLayer === 'mask' && !isMaskEnabled) {
|
||||
dispatch(setIsMaskEnabled(true));
|
||||
}
|
||||
},
|
||||
[dispatch, isMaskEnabled]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
@ -10,6 +10,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const canvasUndoSelector = createSelector(
|
||||
[stateSelector, activeTabNameSelector],
|
||||
@ -35,9 +36,9 @@ export default function IAICanvasUndoButton() {
|
||||
|
||||
const { canUndo, activeTabName } = useAppSelector(canvasUndoSelector);
|
||||
|
||||
const handleUndo = () => {
|
||||
const handleUndo = useCallback(() => {
|
||||
dispatch(undo());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useHotkeys(
|
||||
['meta+z', 'ctrl+z'],
|
||||
|
@ -87,6 +87,11 @@ const ChangeBoardModal = () => {
|
||||
selectedBoard,
|
||||
]);
|
||||
|
||||
const handleSetSelectedBoard = useCallback(
|
||||
(v: string | null) => setSelectedBoard(v),
|
||||
[]
|
||||
);
|
||||
|
||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
return (
|
||||
@ -113,7 +118,7 @@ const ChangeBoardModal = () => {
|
||||
isFetching ? t('boards.loading') : t('boards.selectBoard')
|
||||
}
|
||||
disabled={isFetching}
|
||||
onChange={(v) => setSelectedBoard(v)}
|
||||
onChange={handleSetSelectedBoard}
|
||||
value={selectedBoard}
|
||||
data={data}
|
||||
/>
|
||||
|
@ -14,9 +14,9 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = PropsWithChildren & {
|
||||
onSelect: (v: string) => void;
|
||||
@ -78,6 +78,13 @@ const ParamEmbeddingPopover = (props: Props) => {
|
||||
[onSelect]
|
||||
);
|
||||
|
||||
const filterFunc = useCallback(
|
||||
(value: string, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
initialFocusRef={inputRef}
|
||||
@ -127,12 +134,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={data.length === 0}
|
||||
onDropdownClose={onClose}
|
||||
filter={(value, item: SelectItem) =>
|
||||
item.label
|
||||
?.toLowerCase()
|
||||
.includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||
}
|
||||
filter={filterFunc}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)}
|
||||
|
@ -60,6 +60,13 @@ const BoardAutoAddSelect = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const filterFunc = useCallback(
|
||||
(value: string, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('boards.autoAddBoard')}
|
||||
@ -71,10 +78,7 @@ const BoardAutoAddSelect = () => {
|
||||
nothingFound={t('boards.noMatching')}
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={!hasBoards || autoAssignBoardOnClick}
|
||||
filter={(value, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||
}
|
||||
filter={filterFunc}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
|
@ -90,6 +90,50 @@ const BoardContextMenu = ({
|
||||
e.preventDefault();
|
||||
}, []);
|
||||
|
||||
const renderMenuFunc = useCallback(
|
||||
() => (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup title={boardName}>
|
||||
<MenuItem
|
||||
icon={<FaPlus />}
|
||||
isDisabled={isAutoAdd || autoAssignBoardOnClick}
|
||||
onClick={handleSetAutoAdd}
|
||||
>
|
||||
{t('boards.menuItemAutoAdd')}
|
||||
</MenuItem>
|
||||
{isBulkDownloadEnabled && (
|
||||
<MenuItem icon={<FaDownload />} onClickCapture={handleBulkDownload}>
|
||||
{t('boards.downloadBoard')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!board && <NoBoardContextMenuItems />}
|
||||
{board && (
|
||||
<GalleryBoardContextMenuItems
|
||||
board={board}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
/>
|
||||
)}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
),
|
||||
[
|
||||
autoAssignBoardOnClick,
|
||||
board,
|
||||
boardName,
|
||||
handleBulkDownload,
|
||||
handleSetAutoAdd,
|
||||
isAutoAdd,
|
||||
isBulkDownloadEnabled,
|
||||
setBoardToDelete,
|
||||
skipEvent,
|
||||
t,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
@ -97,38 +141,7 @@ const BoardContextMenu = ({
|
||||
bg: 'transparent',
|
||||
_hover: { bg: 'transparent' },
|
||||
}}
|
||||
renderMenu={() => (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup title={boardName}>
|
||||
<MenuItem
|
||||
icon={<FaPlus />}
|
||||
isDisabled={isAutoAdd || autoAssignBoardOnClick}
|
||||
onClick={handleSetAutoAdd}
|
||||
>
|
||||
{t('boards.menuItemAutoAdd')}
|
||||
</MenuItem>
|
||||
{isBulkDownloadEnabled && (
|
||||
<MenuItem
|
||||
icon={<FaDownload />}
|
||||
onClickCapture={handleBulkDownload}
|
||||
>
|
||||
{t('boards.downloadBoard')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!board && <NoBoardContextMenuItems />}
|
||||
{board && (
|
||||
<GalleryBoardContextMenuItems
|
||||
board={board}
|
||||
setBoardToDelete={setBoardToDelete}
|
||||
/>
|
||||
)}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
)}
|
||||
renderMenu={renderMenuFunc}
|
||||
>
|
||||
{children}
|
||||
</IAIContextMenu>
|
||||
|
@ -61,6 +61,12 @@ const GallerySettingsPopover = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeAutoAssignBoardOnClick = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
@ -91,9 +97,7 @@ const GallerySettingsPopover = () => {
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.autoAssignBoardOnClick')}
|
||||
isChecked={autoAssignBoardOnClick}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(autoAssignBoardOnClickChanged(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeAutoAssignBoardOnClick}
|
||||
/>
|
||||
<BoardAutoAddSelect />
|
||||
</Flex>
|
||||
|
@ -35,6 +35,34 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
e.preventDefault();
|
||||
}, []);
|
||||
|
||||
const renderMenuFunc = useCallback(() => {
|
||||
if (!imageDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selectionCount > 1) {
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MultipleSelectionMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<SingleSelectionMenuItems imageDTO={imageDTO} />
|
||||
</MenuList>
|
||||
);
|
||||
}, [imageDTO, selectionCount, skipEvent]);
|
||||
|
||||
return (
|
||||
<IAIContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
@ -42,33 +70,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||
bg: 'transparent',
|
||||
_hover: { bg: 'transparent' },
|
||||
}}
|
||||
renderMenu={() => {
|
||||
if (!imageDTO) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selectionCount > 1) {
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MultipleSelectionMenuItems />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<SingleSelectionMenuItems imageDTO={imageDTO} />
|
||||
</MenuList>
|
||||
);
|
||||
}}
|
||||
renderMenu={renderMenuFunc}
|
||||
>
|
||||
{children}
|
||||
</IAIContextMenu>
|
||||
|
@ -20,6 +20,7 @@ import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
|
||||
import GalleryImage from './GalleryImage';
|
||||
import ImageGridItemContainer from './ImageGridItemContainer';
|
||||
import ImageGridListContainer from './ImageGridListContainer';
|
||||
import { EntityId } from '@reduxjs/toolkit';
|
||||
|
||||
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
||||
defer: true,
|
||||
@ -71,6 +72,13 @@ const GalleryImageGrid = () => {
|
||||
});
|
||||
}, [areMoreAvailable, listImages, queryArgs, currentData?.ids.length]);
|
||||
|
||||
const itemContentFunc = useCallback(
|
||||
(index: number, imageName: EntityId) => (
|
||||
<GalleryImage key={imageName} imageName={imageName as string} />
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize the gallery's custom scrollbar
|
||||
const { current: root } = rootRef;
|
||||
@ -131,9 +139,7 @@ const GalleryImageGrid = () => {
|
||||
List: ImageGridListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, imageName) => (
|
||||
<GalleryImage key={imageName} imageName={imageName as string} />
|
||||
)}
|
||||
itemContent={itemContentFunc}
|
||||
/>
|
||||
</Box>
|
||||
<IAIButton
|
||||
|
@ -279,7 +279,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
key={index}
|
||||
label="LoRA"
|
||||
value={`${lora.lora.model_name} - ${lora.weight}`}
|
||||
onClick={() => handleRecallLoRA(lora)}
|
||||
onClick={handleRecallLoRA.bind(null, lora)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -289,7 +289,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
key={index}
|
||||
label="ControlNet"
|
||||
value={`${controlnet.control_model?.model_name} - ${controlnet.control_weight}`}
|
||||
onClick={() => handleRecallControlNet(controlnet)}
|
||||
onClick={handleRecallControlNet.bind(null, controlnet)}
|
||||
/>
|
||||
))}
|
||||
{validIPAdapters.map((ipAdapter, index) => (
|
||||
@ -297,7 +297,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
key={index}
|
||||
label="IP Adapter"
|
||||
value={`${ipAdapter.ip_adapter_model?.model_name} - ${ipAdapter.weight}`}
|
||||
onClick={() => handleRecallIPAdapter(ipAdapter)}
|
||||
onClick={handleRecallIPAdapter.bind(null, ipAdapter)}
|
||||
/>
|
||||
))}
|
||||
{validT2IAdapters.map((t2iAdapter, index) => (
|
||||
@ -305,7 +305,7 @@ const ImageMetadataActions = (props: Props) => {
|
||||
key={index}
|
||||
label="T2I Adapter"
|
||||
value={`${t2iAdapter.t2i_adapter_model?.model_name} - ${t2iAdapter.weight}`}
|
||||
onClick={() => handleRecallT2IAdapter(t2iAdapter)}
|
||||
onClick={handleRecallT2IAdapter.bind(null, t2iAdapter)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCopy } from 'react-icons/fa';
|
||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||
@ -27,6 +27,11 @@ const ImageMetadataItem = ({
|
||||
}: MetadataItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCopy = useCallback(
|
||||
() => navigator.clipboard.writeText(value.toString()),
|
||||
[value]
|
||||
);
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
@ -53,7 +58,7 @@ const ImageMetadataItem = ({
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
fontSize={14}
|
||||
onClick={() => navigator.clipboard.writeText(value.toString())}
|
||||
onClick={handleCopy}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
@ -76,6 +76,13 @@ const ParamLoRASelect = () => {
|
||||
[dispatch, loraModels?.entities]
|
||||
);
|
||||
|
||||
const filterFunc = useCallback(
|
||||
(value: string, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||
[]
|
||||
);
|
||||
|
||||
if (loraModels?.ids.length === 0) {
|
||||
return (
|
||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||
@ -94,10 +101,7 @@ const ParamLoRASelect = () => {
|
||||
nothingFound="No matching LoRAs"
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={data.length === 0}
|
||||
filter={(value, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||
}
|
||||
filter={filterFunc}
|
||||
onChange={handleChange}
|
||||
data-testid="add-lora"
|
||||
/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, PropsWithChildren, memo } from 'react';
|
||||
import { useState, PropsWithChildren, memo, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { Flex, Image, Text } from '@chakra-ui/react';
|
||||
@ -59,13 +59,13 @@ export default memo(CurrentImageNode);
|
||||
const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
setIsHovering(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
setIsHovering(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NodeWrapper
|
||||
|
@ -104,6 +104,24 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
nodeId,
|
||||
]);
|
||||
|
||||
const renderMenuFunc = useCallback(
|
||||
() =>
|
||||
!menuItems.length ? null : (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup
|
||||
title={label || fieldTemplateTitle || t('nodes.unknownField')}
|
||||
>
|
||||
{menuItems}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
),
|
||||
[fieldTemplateTitle, label, menuItems, skipEvent, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIContextMenu<HTMLDivElement>
|
||||
menuProps={{
|
||||
@ -114,21 +132,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
bg: 'transparent',
|
||||
_hover: { bg: 'transparent' },
|
||||
}}
|
||||
renderMenu={() =>
|
||||
!menuItems.length ? null : (
|
||||
<MenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup
|
||||
title={label || fieldTemplateTitle || t('nodes.unknownField')}
|
||||
>
|
||||
{menuItems}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
)
|
||||
}
|
||||
renderMenu={renderMenuFunc}
|
||||
>
|
||||
{children}
|
||||
</IAIContextMenu>
|
||||
|
@ -80,6 +80,13 @@ const LoRAModelInputFieldComponent = (
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const filterFunc = useCallback(
|
||||
(value: string, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||
[]
|
||||
);
|
||||
|
||||
if (loraModels?.ids.length === 0) {
|
||||
return (
|
||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||
@ -101,10 +108,7 @@ const LoRAModelInputFieldComponent = (
|
||||
nothingFound={t('models.noMatchingLoRAs')}
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={data.length === 0}
|
||||
filter={(value, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
||||
}
|
||||
filter={filterFunc}
|
||||
error={!selectedLoRAModel}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
IntegerPolymorphicInputFieldTemplate,
|
||||
IntegerPolymorphicInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { memo, useEffect, useMemo, useState } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
const NumberInputFieldComponent = (
|
||||
props: FieldComponentProps<
|
||||
@ -43,20 +43,23 @@ const NumberInputFieldComponent = (
|
||||
[fieldTemplate.type]
|
||||
);
|
||||
|
||||
const handleValueChanged = (v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
dispatch(
|
||||
fieldNumberValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: isIntegerField ? Math.floor(Number(v)) : Number(v),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
const handleValueChanged = useCallback(
|
||||
(v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
dispatch(
|
||||
fieldNumberValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: isIntegerField ? Math.floor(Number(v)) : Number(v),
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatch, field.name, isIntegerField, nodeId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
@ -6,7 +6,7 @@ import IAISlider from 'common/components/IAISlider';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -36,25 +36,28 @@ const ParamBoundingBoxWidth = () => {
|
||||
? 1024
|
||||
: 512;
|
||||
|
||||
const handleChangeHeight = (v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
height: Math.floor(v),
|
||||
})
|
||||
);
|
||||
if (aspectRatio) {
|
||||
const newWidth = roundToMultiple(v * aspectRatio, 64);
|
||||
const handleChangeHeight = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
...boundingBoxDimensions,
|
||||
height: Math.floor(v),
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
if (aspectRatio) {
|
||||
const newWidth = roundToMultiple(v * aspectRatio, 64);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
height: Math.floor(v),
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[aspectRatio, boundingBoxDimensions, dispatch]
|
||||
);
|
||||
|
||||
const handleResetHeight = () => {
|
||||
const handleResetHeight = useCallback(() => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
@ -70,7 +73,7 @@ const ParamBoundingBoxWidth = () => {
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [aspectRatio, boundingBoxDimensions, dispatch, initial]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
|
@ -6,7 +6,7 @@ import IAISlider from 'common/components/IAISlider';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -36,25 +36,28 @@ const ParamBoundingBoxWidth = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeWidth = (v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
width: Math.floor(v),
|
||||
})
|
||||
);
|
||||
if (aspectRatio) {
|
||||
const newHeight = roundToMultiple(v / aspectRatio, 64);
|
||||
const handleChangeWidth = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
width: Math.floor(v),
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
if (aspectRatio) {
|
||||
const newHeight = roundToMultiple(v / aspectRatio, 64);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: Math.floor(v),
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[aspectRatio, boundingBoxDimensions, dispatch]
|
||||
);
|
||||
|
||||
const handleResetWidth = () => {
|
||||
const handleResetWidth = useCallback(() => {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
...boundingBoxDimensions,
|
||||
@ -70,7 +73,7 @@ const ParamBoundingBoxWidth = () => {
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [aspectRatio, boundingBoxDimensions, dispatch, initial]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
|
@ -6,7 +6,7 @@ import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
|
||||
import { CanvasCoherenceModeParam } from 'features/parameters/types/parameterSchemas';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const coherenceModeSelectData: IAISelectDataType[] = [
|
||||
@ -22,13 +22,16 @@ const ParamCanvasCoherenceMode = () => {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCoherenceModeChange = (v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const handleCoherenceModeChange = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setCanvasCoherenceMode(v as CanvasCoherenceModeParam));
|
||||
};
|
||||
dispatch(setCanvasCoherenceMode(v as CanvasCoherenceModeParam));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="compositingCoherenceMode">
|
||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ParamCanvasCoherenceSteps = () => {
|
||||
@ -13,6 +13,17 @@ const ParamCanvasCoherenceSteps = () => {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setCanvasCoherenceSteps(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setCanvasCoherenceSteps(20));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="compositingCoherenceSteps">
|
||||
<IAISlider
|
||||
@ -22,15 +33,11 @@ const ParamCanvasCoherenceSteps = () => {
|
||||
step={1}
|
||||
sliderNumberInputProps={{ max: 999 }}
|
||||
value={canvasCoherenceSteps}
|
||||
onChange={(v) => {
|
||||
dispatch(setCanvasCoherenceSteps(v));
|
||||
}}
|
||||
onChange={handleChange}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setCanvasCoherenceSteps(20));
|
||||
}}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ParamCanvasCoherenceStrength = () => {
|
||||
@ -13,6 +13,16 @@ const ParamCanvasCoherenceStrength = () => {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setCanvasCoherenceStrength(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setCanvasCoherenceStrength(0.3));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="compositingStrength">
|
||||
<IAISlider
|
||||
@ -22,15 +32,11 @@ const ParamCanvasCoherenceStrength = () => {
|
||||
step={0.01}
|
||||
sliderNumberInputProps={{ max: 999 }}
|
||||
value={canvasCoherenceStrength}
|
||||
onChange={(v) => {
|
||||
dispatch(setCanvasCoherenceStrength(v));
|
||||
}}
|
||||
onChange={handleChange}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setCanvasCoherenceStrength(0.3));
|
||||
}}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setMaskBlur } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ParamMaskBlur() {
|
||||
@ -12,6 +13,16 @@ export default function ParamMaskBlur() {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setMaskBlur(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setMaskBlur(16));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="compositingBlur">
|
||||
<IAISlider
|
||||
@ -20,15 +31,11 @@ export default function ParamMaskBlur() {
|
||||
max={64}
|
||||
sliderNumberInputProps={{ max: 512 }}
|
||||
value={maskBlur}
|
||||
onChange={(v) => {
|
||||
dispatch(setMaskBlur(v));
|
||||
}}
|
||||
onChange={handleChange}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => {
|
||||
dispatch(setMaskBlur(16));
|
||||
}}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
</IAIInformationalPopover>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { setMaskBlurMethod } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type MaskBlurMethods = 'box' | 'gaussian';
|
||||
@ -21,12 +22,15 @@ export default function ParamMaskBlurMethod() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleMaskBlurMethodChange = (v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(setMaskBlurMethod(v as MaskBlurMethods));
|
||||
};
|
||||
const handleMaskBlurMethodChange = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(setMaskBlurMethod(v as MaskBlurMethods));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="compositingBlurMethod">
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
BoundingBoxScale,
|
||||
} from 'features/canvas/store/canvasTypes';
|
||||
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
@ -31,9 +31,12 @@ const ParamScaleBeforeProcessing = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeBoundingBoxScaleMethod = (v: string) => {
|
||||
dispatch(setBoundingBoxScaleMethod(v as BoundingBoxScale));
|
||||
};
|
||||
const handleChangeBoundingBoxScaleMethod = useCallback(
|
||||
(v: string) => {
|
||||
dispatch(setBoundingBoxScaleMethod(v as BoundingBoxScale));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIInformationalPopover feature="scaleBeforeProcessing">
|
||||
|
@ -6,7 +6,7 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
@ -36,23 +36,26 @@ const ParamScaledHeight = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeScaledHeight = (v: number) => {
|
||||
let newWidth = scaledBoundingBoxDimensions.width;
|
||||
const newHeight = Math.floor(v);
|
||||
const handleChangeScaledHeight = useCallback(
|
||||
(v: number) => {
|
||||
let newWidth = scaledBoundingBoxDimensions.width;
|
||||
const newHeight = Math.floor(v);
|
||||
|
||||
if (aspectRatio) {
|
||||
newWidth = roundToMultiple(newHeight * aspectRatio, 64);
|
||||
}
|
||||
if (aspectRatio) {
|
||||
newWidth = roundToMultiple(newHeight * aspectRatio, 64);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
};
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
},
|
||||
[aspectRatio, dispatch, scaledBoundingBoxDimensions.width]
|
||||
);
|
||||
|
||||
const handleResetScaledHeight = () => {
|
||||
const handleResetScaledHeight = useCallback(() => {
|
||||
let resetWidth = scaledBoundingBoxDimensions.width;
|
||||
const resetHeight = Math.floor(initial);
|
||||
|
||||
@ -66,7 +69,7 @@ const ParamScaledHeight = () => {
|
||||
height: resetHeight,
|
||||
})
|
||||
);
|
||||
};
|
||||
}, [aspectRatio, dispatch, initial, scaledBoundingBoxDimensions.width]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
|
@ -6,7 +6,7 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
@ -36,23 +36,26 @@ const ParamScaledWidth = () => {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeScaledWidth = (v: number) => {
|
||||
const newWidth = Math.floor(v);
|
||||
let newHeight = scaledBoundingBoxDimensions.height;
|
||||
const handleChangeScaledWidth = useCallback(
|
||||
(v: number) => {
|
||||
const newWidth = Math.floor(v);
|
||||
let newHeight = scaledBoundingBoxDimensions.height;
|
||||
|
||||
if (aspectRatio) {
|
||||
newHeight = roundToMultiple(newWidth / aspectRatio, 64);
|
||||
}
|
||||
if (aspectRatio) {
|
||||
newHeight = roundToMultiple(newWidth / aspectRatio, 64);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
};
|
||||
dispatch(
|
||||
setScaledBoundingBoxDimensions({
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
})
|
||||
);
|
||||
},
|
||||
[aspectRatio, dispatch, scaledBoundingBoxDimensions.height]
|
||||
);
|
||||
|
||||
const handleResetScaledWidth = () => {
|
||||
const handleResetScaledWidth = useCallback(() => {
|
||||
const resetWidth = Math.floor(initial);
|
||||
let resetHeight = scaledBoundingBoxDimensions.height;
|
||||
|
||||
@ -66,7 +69,7 @@ const ParamScaledWidth = () => {
|
||||
height: resetHeight,
|
||||
})
|
||||
);
|
||||
};
|
||||
}, [aspectRatio, dispatch, initial, scaledBoundingBoxDimensions.height]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import i18next from 'i18next';
|
||||
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const aspectRatios = [
|
||||
{ name: i18next.t('parameters.aspectRatioFree'), value: null },
|
||||
@ -29,6 +30,14 @@ export default function ParamAspectRatio() {
|
||||
);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(ratio: (typeof aspectRatios)[number]) => {
|
||||
dispatch(setAspectRatio(ratio.value));
|
||||
dispatch(setShouldLockAspectRatio(false));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached>
|
||||
{aspectRatios.map((ratio) => (
|
||||
@ -39,10 +48,7 @@ export default function ParamAspectRatio() {
|
||||
isDisabled={
|
||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||
}
|
||||
onClick={() => {
|
||||
dispatch(setAspectRatio(ratio.value));
|
||||
dispatch(setShouldLockAspectRatio(false));
|
||||
}}
|
||||
onClick={handleClick.bind(null, ratio)}
|
||||
>
|
||||
{ratio.name}
|
||||
</IAIButton>
|
||||
|
@ -2,7 +2,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { ChangeEvent, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ImageToImageFit() {
|
||||
@ -12,8 +12,12 @@ export default function ImageToImageFit() {
|
||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
||||
);
|
||||
|
||||
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldFitToWidthHeight(e.target.checked));
|
||||
const handleChangeFit = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldFitToWidthHeight(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ParamSeed() {
|
||||
@ -18,7 +19,10 @@ export default function ParamSeed() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChangeSeed = (v: number) => dispatch(setSeed(v));
|
||||
const handleChangeSeed = useCallback(
|
||||
(v: number) => dispatch(setSeed(v)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAINumberInput
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChangeEvent, memo } from 'react';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
|
||||
@ -13,8 +13,11 @@ const ParamSeedRandomize = () => {
|
||||
(state: RootState) => state.generation.shouldRandomizeSeed
|
||||
);
|
||||
|
||||
const handleChangeShouldRandomizeSeed = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
const handleChangeShouldRandomizeSeed = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
|
@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import randomInt from 'common/util/randomInt';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaRandom } from 'react-icons/fa';
|
||||
|
||||
@ -14,8 +15,10 @@ export default function ParamSeedShuffle() {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClickRandomizeSeed = () =>
|
||||
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
|
||||
const handleClickRandomizeSeed = useCallback(
|
||||
() => dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
|
@ -2,6 +2,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setHorizontalSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ParamSymmetryHorizontal() {
|
||||
@ -15,18 +16,28 @@ export default function ParamSymmetryHorizontal() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setHorizontalSymmetrySteps(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setHorizontalSymmetrySteps(0));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={t('parameters.hSymmetryStep')}
|
||||
value={horizontalSymmetrySteps}
|
||||
onChange={(v) => dispatch(setHorizontalSymmetrySteps(v))}
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
max={steps}
|
||||
step={1}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => dispatch(setHorizontalSymmetrySteps(0))}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldUseSymmetry } from 'features/parameters/store/generationSlice';
|
||||
import { ChangeEvent, useCallback } from 'react';
|
||||
|
||||
export default function ParamSymmetryToggle() {
|
||||
const shouldUseSymmetry = useAppSelector(
|
||||
@ -9,12 +10,18 @@ export default function ParamSymmetryToggle() {
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldUseSymmetry(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label="Enable Symmetry"
|
||||
isChecked={shouldUseSymmetry}
|
||||
onChange={(e) => dispatch(setShouldUseSymmetry(e.target.checked))}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { setVerticalSymmetrySteps } from 'features/parameters/store/generationSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ParamSymmetryVertical() {
|
||||
@ -15,18 +16,28 @@ export default function ParamSymmetryVertical() {
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
dispatch(setVerticalSymmetrySteps(v));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleReset = useCallback(() => {
|
||||
dispatch(setVerticalSymmetrySteps(0));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label={t('parameters.vSymmetryStep')}
|
||||
value={verticalSymmetrySteps}
|
||||
onChange={(v) => dispatch(setVerticalSymmetrySteps(v))}
|
||||
onChange={handleChange}
|
||||
min={0}
|
||||
max={steps}
|
||||
step={1}
|
||||
withInput
|
||||
withSliderMarks
|
||||
withReset
|
||||
handleReset={() => dispatch(setVerticalSymmetrySteps(0))}
|
||||
handleReset={handleReset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
ESRGANModelName,
|
||||
esrganModelNameChanged,
|
||||
} from 'features/parameters/store/postprocessingSlice';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const ESRGAN_MODEL_NAMES: SelectItem[] = [
|
||||
{
|
||||
@ -42,8 +43,10 @@ export default function ParamESRGANModel() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChange = (v: string) =>
|
||||
dispatch(esrganModelNameChanged(v as ESRGANModelName));
|
||||
const handleChange = useCallback(
|
||||
(v: string) => dispatch(esrganModelNameChanged(v as ESRGANModelName)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIMantineSelect
|
||||
|
@ -4,6 +4,7 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { FaLink } from 'react-icons/fa';
|
||||
import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export default function ParamSDXLConcatButton() {
|
||||
const shouldConcatSDXLStylePrompt = useAppSelector(
|
||||
@ -13,9 +14,9 @@ export default function ParamSDXLConcatButton() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleShouldConcatPromptChange = () => {
|
||||
const handleShouldConcatPromptChange = useCallback(() => {
|
||||
dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt));
|
||||
};
|
||||
}, [dispatch, shouldConcatSDXLStylePrompt]);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
|
@ -2,7 +2,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldUseSDXLRefiner } from 'features/sdxl/store/sdxlSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { ChangeEvent, useCallback } from 'react';
|
||||
import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -15,9 +15,12 @@ export default function ParamUseSDXLRefiner() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleUseSDXLRefinerChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldUseSDXLRefiner(e.target.checked));
|
||||
};
|
||||
const handleUseSDXLRefinerChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldUseSDXLRefiner(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
|
@ -212,6 +212,61 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
useFeatureStatus('localization').isFeatureEnabled;
|
||||
const language = useAppSelector(languageSelector);
|
||||
|
||||
const handleChangeShouldConfirmOnDelete = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldConfirmOnDelete(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldUseNSFWChecker = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(shouldUseNSFWCheckerChanged(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldUseWatermarker = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(shouldUseWatermarkerChanged(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldUseSliders = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldUseSliders(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldShowProgressInViewer = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldShowProgressInViewer(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAntialiasProgressImage = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(shouldAntialiasProgressImageChanged(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldAutoChangeDimensions = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldAutoChangeDimensions(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeShouldEnableInformationalPopovers = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setShouldEnableInformationalPopovers(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
const handleChangeEnableImageDebugging = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(setEnableImageDebugging(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{cloneElement(children, {
|
||||
@ -235,9 +290,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<SettingSwitch
|
||||
label={t('settings.confirmOnDelete')}
|
||||
isChecked={shouldConfirmOnDelete}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldConfirmOnDelete(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldConfirmOnDelete}
|
||||
/>
|
||||
</StyledFlex>
|
||||
|
||||
@ -248,17 +301,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
label="Enable NSFW Checker"
|
||||
isDisabled={!isNSFWCheckerAvailable}
|
||||
isChecked={shouldUseNSFWChecker}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(shouldUseNSFWCheckerChanged(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldUseNSFWChecker}
|
||||
/>
|
||||
<SettingSwitch
|
||||
label="Enable Invisible Watermark"
|
||||
isDisabled={!isWatermarkerAvailable}
|
||||
isChecked={shouldUseWatermarker}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(shouldUseWatermarkerChanged(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldUseWatermarker}
|
||||
/>
|
||||
</StyledFlex>
|
||||
|
||||
@ -272,32 +321,22 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<SettingSwitch
|
||||
label={t('settings.useSlidersForAll')}
|
||||
isChecked={shouldUseSliders}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldUseSliders(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldUseSliders}
|
||||
/>
|
||||
<SettingSwitch
|
||||
label={t('settings.showProgressInViewer')}
|
||||
isChecked={shouldShowProgressInViewer}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldShowProgressInViewer(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldShowProgressInViewer}
|
||||
/>
|
||||
<SettingSwitch
|
||||
label={t('settings.antialiasProgressImages')}
|
||||
isChecked={shouldAntialiasProgressImage}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(
|
||||
shouldAntialiasProgressImageChanged(e.target.checked)
|
||||
)
|
||||
}
|
||||
onChange={handleChangeShouldAntialiasProgressImage}
|
||||
/>
|
||||
<SettingSwitch
|
||||
label={t('settings.autoChangeDimensions')}
|
||||
isChecked={shouldAutoChangeDimensions}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoChangeDimensions(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeShouldAutoChangeDimensions}
|
||||
/>
|
||||
{shouldShowLocalizationToggle && (
|
||||
<IAIMantineSelect
|
||||
@ -314,11 +353,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<SettingSwitch
|
||||
label="Enable informational popovers"
|
||||
isChecked={shouldEnableInformationalPopovers}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(
|
||||
setShouldEnableInformationalPopovers(e.target.checked)
|
||||
)
|
||||
}
|
||||
onChange={handleChangeShouldEnableInformationalPopovers}
|
||||
/>
|
||||
</StyledFlex>
|
||||
|
||||
@ -340,9 +375,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
||||
<SettingSwitch
|
||||
label={t('settings.enableImageDebugging')}
|
||||
isChecked={enableImageDebugging}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setEnableImageDebugging(e.target.checked))
|
||||
}
|
||||
onChange={handleChangeEnableImageDebugging}
|
||||
/>
|
||||
</StyledFlex>
|
||||
)}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import { Portal } from '@chakra-ui/portal';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { RefObject, memo } from 'react';
|
||||
import { RefObject, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdPhotoLibrary } from 'react-icons/md';
|
||||
import { ImperativePanelHandle } from 'react-resizable-panels';
|
||||
@ -17,9 +17,9 @@ const FloatingGalleryButton = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleShowGallery = () => {
|
||||
const handleShowGallery = useCallback(() => {
|
||||
galleryPanelRef.current?.expand();
|
||||
};
|
||||
}, [galleryPanelRef]);
|
||||
|
||||
if (!isGalleryCollapsed) {
|
||||
return null;
|
||||
|
@ -3,7 +3,7 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import CancelCurrentQueueItemButton from 'features/queue/components/CancelCurrentQueueItemButton';
|
||||
import ClearQueueButton from 'features/queue/components/ClearQueueButton';
|
||||
import QueueBackButton from 'features/queue/components/QueueBackButton';
|
||||
import { RefObject, memo } from 'react';
|
||||
import { RefObject, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FaSlidersH } from 'react-icons/fa';
|
||||
@ -25,9 +25,9 @@ const FloatingSidePanelButtons = ({
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleShowSidePanel = () => {
|
||||
const handleShowSidePanel = useCallback(() => {
|
||||
sidePanelRef.current?.expand();
|
||||
};
|
||||
}, [sidePanelRef]);
|
||||
|
||||
if (!isSidePanelCollapsed) {
|
||||
return null;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import AdvancedAddModels from './AdvancedAddModels';
|
||||
import SimpleAddModels from './SimpleAddModels';
|
||||
|
||||
@ -8,6 +8,11 @@ export default function AddModels() {
|
||||
const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>(
|
||||
'simple'
|
||||
);
|
||||
const handleAddModelSimple = useCallback(() => setAddModelMode('simple'), []);
|
||||
const handleAddModelAdvanced = useCallback(
|
||||
() => setAddModelMode('advanced'),
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
@ -20,14 +25,14 @@ export default function AddModels() {
|
||||
<IAIButton
|
||||
size="sm"
|
||||
isChecked={addModelMode == 'simple'}
|
||||
onClick={() => setAddModelMode('simple')}
|
||||
onClick={handleAddModelSimple}
|
||||
>
|
||||
Simple
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
isChecked={addModelMode == 'advanced'}
|
||||
onClick={() => setAddModelMode('advanced')}
|
||||
onClick={handleAddModelAdvanced}
|
||||
>
|
||||
Advanced
|
||||
</IAIButton>
|
||||
|
@ -6,7 +6,7 @@ import IAIMantineTextInput from 'common/components/IAIMantineInput';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useState } from 'react';
|
||||
import { FocusEventHandler, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
||||
import { CheckpointModelConfig } from 'services/api/types';
|
||||
@ -83,6 +83,27 @@ export default function AdvancedAddCheckpoint(
|
||||
});
|
||||
};
|
||||
|
||||
const handleBlurModelLocation: FocusEventHandler<HTMLInputElement> =
|
||||
useCallback(
|
||||
(e) => {
|
||||
if (advancedAddCheckpointForm.values['model_name'] === '') {
|
||||
const modelName = getModelName(e.currentTarget.value);
|
||||
if (modelName) {
|
||||
advancedAddCheckpointForm.setFieldValue(
|
||||
'model_name',
|
||||
modelName as string
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[advancedAddCheckpointForm]
|
||||
);
|
||||
|
||||
const handleChangeUseCustomConfig = useCallback(
|
||||
() => setUseCustomConfig((prev) => !prev),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={advancedAddCheckpointForm.onSubmit((v) =>
|
||||
@ -104,17 +125,7 @@ export default function AdvancedAddCheckpoint(
|
||||
label={t('modelManager.modelLocation')}
|
||||
required
|
||||
{...advancedAddCheckpointForm.getInputProps('path')}
|
||||
onBlur={(e) => {
|
||||
if (advancedAddCheckpointForm.values['model_name'] === '') {
|
||||
const modelName = getModelName(e.currentTarget.value);
|
||||
if (modelName) {
|
||||
advancedAddCheckpointForm.setFieldValue(
|
||||
'model_name',
|
||||
modelName as string
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={handleBlurModelLocation}
|
||||
/>
|
||||
<IAIMantineTextInput
|
||||
label={t('modelManager.description')}
|
||||
@ -144,7 +155,7 @@ export default function AdvancedAddCheckpoint(
|
||||
)}
|
||||
<IAISimpleCheckbox
|
||||
isChecked={useCustomConfig}
|
||||
onChange={() => setUseCustomConfig(!useCustomConfig)}
|
||||
onChange={handleChangeUseCustomConfig}
|
||||
label={t('modelManager.useCustomConfig')}
|
||||
/>
|
||||
<IAIButton mt={2} type="submit">
|
||||
|
@ -12,6 +12,7 @@ import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
|
||||
import BaseModelSelect from '../shared/BaseModelSelect';
|
||||
import ModelVariantSelect from '../shared/ModelVariantSelect';
|
||||
import { getModelName } from './util';
|
||||
import { FocusEventHandler, useCallback } from 'react';
|
||||
|
||||
type AdvancedAddDiffusersProps = {
|
||||
model_path?: string;
|
||||
@ -74,6 +75,22 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleBlurModelLocation: FocusEventHandler<HTMLInputElement> =
|
||||
useCallback(
|
||||
(e) => {
|
||||
if (advancedAddDiffusersForm.values['model_name'] === '') {
|
||||
const modelName = getModelName(e.currentTarget.value, false);
|
||||
if (modelName) {
|
||||
advancedAddDiffusersForm.setFieldValue(
|
||||
'model_name',
|
||||
modelName as string
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[advancedAddDiffusersForm]
|
||||
);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={advancedAddDiffusersForm.onSubmit((v) =>
|
||||
@ -96,17 +113,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
||||
label={t('modelManager.modelLocation')}
|
||||
placeholder={t('modelManager.modelLocationValidationMsg')}
|
||||
{...advancedAddDiffusersForm.getInputProps('path')}
|
||||
onBlur={(e) => {
|
||||
if (advancedAddDiffusersForm.values['model_name'] === '') {
|
||||
const modelName = getModelName(e.currentTarget.value, false);
|
||||
if (modelName) {
|
||||
advancedAddDiffusersForm.setFieldValue(
|
||||
'model_name',
|
||||
modelName as string
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={handleBlurModelLocation}
|
||||
/>
|
||||
<IAIMantineTextInput
|
||||
label={t('modelManager.description')}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
||||
import AdvancedAddDiffusers from './AdvancedAddDiffusers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -18,6 +18,12 @@ export default function AdvancedAddModels() {
|
||||
useState<ManualAddMode>('diffusers');
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleChange = useCallback((v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
setAdvancedAddMode(v as ManualAddMode);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" gap={4} width="100%">
|
||||
@ -25,12 +31,7 @@ export default function AdvancedAddModels() {
|
||||
label={t('modelManager.modelType')}
|
||||
value={advancedAddMode}
|
||||
data={advancedAddModeData}
|
||||
onChange={(v) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
setAdvancedAddMode(v as ManualAddMode);
|
||||
}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
|
@ -92,6 +92,11 @@ export default function FoundModelsList() {
|
||||
setNameFilter(e.target.value);
|
||||
}, []);
|
||||
|
||||
const handleClickSetAdvanced = useCallback(
|
||||
(model: string) => dispatch(setAdvancedAddScanModel(model)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const renderModels = ({
|
||||
models,
|
||||
showActions = true,
|
||||
@ -140,7 +145,7 @@ export default function FoundModelsList() {
|
||||
{t('modelManager.quickAdd')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
onClick={() => dispatch(setAdvancedAddScanModel(model))}
|
||||
onClick={handleClickSetAdvanced.bind(null, model)}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{t('modelManager.advanced')}
|
||||
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
|
||||
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
||||
@ -35,6 +35,23 @@ export default function ScanAdvancedAddModels() {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleClickSetAdvanced = useCallback(
|
||||
() => dispatch(setAdvancedAddScanModel(null)),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const handleChangeAddMode = useCallback((v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
setAdvancedAddMode(v as ManualAddMode);
|
||||
if (v === 'checkpoint') {
|
||||
setIsCheckpoint(true);
|
||||
} else {
|
||||
setIsCheckpoint(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!advancedAddScanModel) {
|
||||
return null;
|
||||
}
|
||||
@ -68,7 +85,7 @@ export default function ScanAdvancedAddModels() {
|
||||
<IAIIconButton
|
||||
icon={<FaTimes />}
|
||||
aria-label={t('modelManager.closeAdvanced')}
|
||||
onClick={() => dispatch(setAdvancedAddScanModel(null))}
|
||||
onClick={handleClickSetAdvanced}
|
||||
size="sm"
|
||||
/>
|
||||
</Flex>
|
||||
@ -76,17 +93,7 @@ export default function ScanAdvancedAddModels() {
|
||||
label={t('modelManager.modelType')}
|
||||
value={advancedAddMode}
|
||||
data={advancedAddModeData}
|
||||
onChange={(v) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
setAdvancedAddMode(v as ManualAddMode);
|
||||
if (v === 'checkpoint') {
|
||||
setIsCheckpoint(true);
|
||||
} else {
|
||||
setIsCheckpoint(false);
|
||||
}
|
||||
}}
|
||||
onChange={handleChangeAddMode}
|
||||
/>
|
||||
{isCheckpoint ? (
|
||||
<AdvancedAddCheckpoint
|
||||
|
@ -42,9 +42,14 @@ function SearchFolderForm() {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const scanAgainHandler = () => {
|
||||
const scanAgainHandler = useCallback(() => {
|
||||
refetchFoundModels();
|
||||
};
|
||||
}, [refetchFoundModels]);
|
||||
|
||||
const handleClickClearCheckpointFolder = useCallback(() => {
|
||||
dispatch(setSearchFolder(null));
|
||||
dispatch(setAdvancedAddScanModel(null));
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<form
|
||||
@ -123,10 +128,7 @@ function SearchFolderForm() {
|
||||
tooltip={t('modelManager.clearCheckpointFolder')}
|
||||
icon={<FaTrash />}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
dispatch(setSearchFolder(null));
|
||||
dispatch(setAdvancedAddScanModel(null));
|
||||
}}
|
||||
onClick={handleClickClearCheckpointFolder}
|
||||
isDisabled={!searchFolder}
|
||||
colorScheme="red"
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import AddModels from './AddModelsPanel/AddModels';
|
||||
import ScanModels from './AddModelsPanel/ScanModels';
|
||||
@ -11,11 +11,14 @@ export default function ImportModelsPanel() {
|
||||
const [addModelTab, setAddModelTab] = useState<AddModelTabs>('add');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClickAddTab = useCallback(() => setAddModelTab('add'), []);
|
||||
const handleClickScanTab = useCallback(() => setAddModelTab('scan'), []);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" gap={4}>
|
||||
<ButtonGroup isAttached>
|
||||
<IAIButton
|
||||
onClick={() => setAddModelTab('add')}
|
||||
onClick={handleClickAddTab}
|
||||
isChecked={addModelTab == 'add'}
|
||||
size="sm"
|
||||
width="100%"
|
||||
@ -23,7 +26,7 @@ export default function ImportModelsPanel() {
|
||||
{t('modelManager.addModel')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
onClick={() => setAddModelTab('scan')}
|
||||
onClick={handleClickScanTab}
|
||||
isChecked={addModelTab == 'scan'}
|
||||
size="sm"
|
||||
width="100%"
|
||||
|
@ -9,7 +9,7 @@ import IAISlider from 'common/components/IAISlider';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { pickBy } from 'lodash-es';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ALL_BASE_MODELS } from 'services/api/constants';
|
||||
import {
|
||||
@ -94,13 +94,58 @@ export default function MergeModelsPanel() {
|
||||
modelsMap[baseModel as keyof typeof modelsMap]
|
||||
).filter((model) => model !== modelOne && model !== modelTwo);
|
||||
|
||||
const handleBaseModelChange = (v: string) => {
|
||||
const handleBaseModelChange = useCallback((v: string) => {
|
||||
setBaseModel(v as BaseModelType);
|
||||
setModelOne(null);
|
||||
setModelTwo(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const mergeModelsHandler = () => {
|
||||
const handleChangeModelOne = useCallback((v: string) => {
|
||||
setModelOne(v);
|
||||
}, []);
|
||||
const handleChangeModelTwo = useCallback((v: string) => {
|
||||
setModelTwo(v);
|
||||
}, []);
|
||||
const handleChangeModelThree = useCallback((v: string) => {
|
||||
if (!v) {
|
||||
setModelThree(null);
|
||||
setModelMergeInterp('add_difference');
|
||||
} else {
|
||||
setModelThree(v);
|
||||
setModelMergeInterp('weighted_sum');
|
||||
}
|
||||
}, []);
|
||||
const handleChangeMergedModelName = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => setMergedModelName(e.target.value),
|
||||
[]
|
||||
);
|
||||
const handleChangeModelMergeAlpha = useCallback(
|
||||
(v: number) => setModelMergeAlpha(v),
|
||||
[]
|
||||
);
|
||||
const handleResetModelMergeAlpha = useCallback(
|
||||
() => setModelMergeAlpha(0.5),
|
||||
[]
|
||||
);
|
||||
const handleChangeMergeInterp = useCallback(
|
||||
(v: MergeInterpolationMethods) => setModelMergeInterp(v),
|
||||
[]
|
||||
);
|
||||
const handleChangeMergeSaveLocType = useCallback(
|
||||
(v: 'root' | 'custom') => setModelMergeSaveLocType(v),
|
||||
[]
|
||||
);
|
||||
const handleChangeMergeCustomSaveLoc = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setModelMergeCustomSaveLoc(e.target.value),
|
||||
[]
|
||||
);
|
||||
const handleChangeModelMergeForce = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => setModelMergeForce(e.target.checked),
|
||||
[]
|
||||
);
|
||||
|
||||
const mergeModelsHandler = useCallback(() => {
|
||||
const models_names: string[] = [];
|
||||
|
||||
let modelsToMerge: (string | null)[] = [modelOne, modelTwo, modelThree];
|
||||
@ -150,7 +195,21 @@ export default function MergeModelsPanel() {
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [
|
||||
baseModel,
|
||||
dispatch,
|
||||
mergeModels,
|
||||
mergedModelName,
|
||||
modelMergeAlpha,
|
||||
modelMergeCustomSaveLoc,
|
||||
modelMergeForce,
|
||||
modelMergeInterp,
|
||||
modelMergeSaveLocType,
|
||||
modelOne,
|
||||
modelThree,
|
||||
modelTwo,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" rowGap={4}>
|
||||
@ -180,7 +239,7 @@ export default function MergeModelsPanel() {
|
||||
value={modelOne}
|
||||
placeholder={t('modelManager.selectModel')}
|
||||
data={modelOneList}
|
||||
onChange={(v) => setModelOne(v)}
|
||||
onChange={handleChangeModelOne}
|
||||
/>
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.modelTwo')}
|
||||
@ -188,7 +247,7 @@ export default function MergeModelsPanel() {
|
||||
placeholder={t('modelManager.selectModel')}
|
||||
value={modelTwo}
|
||||
data={modelTwoList}
|
||||
onChange={(v) => setModelTwo(v)}
|
||||
onChange={handleChangeModelTwo}
|
||||
/>
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.modelThree')}
|
||||
@ -196,22 +255,14 @@ export default function MergeModelsPanel() {
|
||||
w="100%"
|
||||
placeholder={t('modelManager.selectModel')}
|
||||
clearable
|
||||
onChange={(v) => {
|
||||
if (!v) {
|
||||
setModelThree(null);
|
||||
setModelMergeInterp('add_difference');
|
||||
} else {
|
||||
setModelThree(v);
|
||||
setModelMergeInterp('weighted_sum');
|
||||
}
|
||||
}}
|
||||
onChange={handleChangeModelThree}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<IAIInput
|
||||
label={t('modelManager.mergedModelName')}
|
||||
value={mergedModelName}
|
||||
onChange={(e) => setMergedModelName(e.target.value)}
|
||||
onChange={handleChangeMergedModelName}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
@ -232,10 +283,10 @@ export default function MergeModelsPanel() {
|
||||
max={0.99}
|
||||
step={0.01}
|
||||
value={modelMergeAlpha}
|
||||
onChange={(v) => setModelMergeAlpha(v)}
|
||||
onChange={handleChangeModelMergeAlpha}
|
||||
withInput
|
||||
withReset
|
||||
handleReset={() => setModelMergeAlpha(0.5)}
|
||||
handleReset={handleResetModelMergeAlpha}
|
||||
withSliderMarks
|
||||
/>
|
||||
<Text variant="subtext" fontSize="sm">
|
||||
@ -257,10 +308,7 @@ export default function MergeModelsPanel() {
|
||||
<Text fontWeight={500} fontSize="sm" variant="subtext">
|
||||
{t('modelManager.interpolationType')}
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={modelMergeInterp}
|
||||
onChange={(v: MergeInterpolationMethods) => setModelMergeInterp(v)}
|
||||
>
|
||||
<RadioGroup value={modelMergeInterp} onChange={handleChangeMergeInterp}>
|
||||
<Flex columnGap={4}>
|
||||
{modelThree === null ? (
|
||||
<>
|
||||
@ -305,7 +353,7 @@ export default function MergeModelsPanel() {
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={modelMergeSaveLocType}
|
||||
onChange={(v: 'root' | 'custom') => setModelMergeSaveLocType(v)}
|
||||
onChange={handleChangeMergeSaveLocType}
|
||||
>
|
||||
<Flex columnGap={4}>
|
||||
<Radio value="root">
|
||||
@ -323,7 +371,7 @@ export default function MergeModelsPanel() {
|
||||
<IAIInput
|
||||
label={t('modelManager.mergedModelCustomSaveLocation')}
|
||||
value={modelMergeCustomSaveLoc}
|
||||
onChange={(e) => setModelMergeCustomSaveLoc(e.target.value)}
|
||||
onChange={handleChangeMergeCustomSaveLoc}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
@ -331,7 +379,7 @@ export default function MergeModelsPanel() {
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.ignoreMismatch')}
|
||||
isChecked={modelMergeForce}
|
||||
onChange={(e) => setModelMergeForce(e.target.checked)}
|
||||
onChange={handleChangeModelMergeForce}
|
||||
fontWeight="500"
|
||||
/>
|
||||
|
||||
|
@ -59,6 +59,11 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
||||
},
|
||||
});
|
||||
|
||||
const handleChangeUseCustomConfig = useCallback(
|
||||
() => setUseCustomConfig((prev) => !prev),
|
||||
[]
|
||||
);
|
||||
|
||||
const editModelFormSubmitHandler = useCallback(
|
||||
(values: CheckpointModelConfig) => {
|
||||
const responseBody = {
|
||||
@ -181,7 +186,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
||||
)}
|
||||
<IAISimpleCheckbox
|
||||
isChecked={useCustomConfig}
|
||||
onChange={() => setUseCustomConfig(!useCustomConfig)}
|
||||
onChange={handleChangeUseCustomConfig}
|
||||
label="Use Custom Config"
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -14,7 +14,7 @@ import IAIAlertDialog from 'common/components/IAIAlertDialog';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useConvertMainModelsMutation } from 'services/api/endpoints/models';
|
||||
@ -42,11 +42,21 @@ export default function ModelConvert(props: ModelConvertProps) {
|
||||
setSaveLocation('InvokeAIRoot');
|
||||
}, [model]);
|
||||
|
||||
const modelConvertCancelHandler = () => {
|
||||
const modelConvertCancelHandler = useCallback(() => {
|
||||
setSaveLocation('InvokeAIRoot');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const modelConvertHandler = () => {
|
||||
const handleChangeSaveLocation = useCallback((v: string) => {
|
||||
setSaveLocation(v as SaveLocation);
|
||||
}, []);
|
||||
const handleChangeCustomSaveLocation = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
setCustomSaveLocation(e.target.value);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const modelConvertHandler = useCallback(() => {
|
||||
const queryArg = {
|
||||
base_model: model.base_model,
|
||||
model_name: model.model_name,
|
||||
@ -101,7 +111,15 @@ export default function ModelConvert(props: ModelConvertProps) {
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
}, [
|
||||
convertModel,
|
||||
customSaveLocation,
|
||||
dispatch,
|
||||
model.base_model,
|
||||
model.model_name,
|
||||
saveLocation,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<IAIAlertDialog
|
||||
@ -137,10 +155,7 @@ export default function ModelConvert(props: ModelConvertProps) {
|
||||
<Text fontWeight="600">
|
||||
{t('modelManager.convertToDiffusersSaveLocation')}
|
||||
</Text>
|
||||
<RadioGroup
|
||||
value={saveLocation}
|
||||
onChange={(v) => setSaveLocation(v as SaveLocation)}
|
||||
>
|
||||
<RadioGroup value={saveLocation} onChange={handleChangeSaveLocation}>
|
||||
<Flex gap={4}>
|
||||
<Radio value="InvokeAIRoot">
|
||||
<Tooltip label="Save converted model in the InvokeAI root folder">
|
||||
@ -162,9 +177,7 @@ export default function ModelConvert(props: ModelConvertProps) {
|
||||
</Text>
|
||||
<IAIInput
|
||||
value={customSaveLocation}
|
||||
onChange={(e) => {
|
||||
setCustomSaveLocation(e.target.value);
|
||||
}}
|
||||
onChange={handleChangeCustomSaveLocation}
|
||||
width="full"
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -100,7 +100,7 @@ const ModelList = (props: ModelListProps) => {
|
||||
<Flex flexDirection="column" gap={4} paddingInlineEnd={4}>
|
||||
<ButtonGroup isAttached>
|
||||
<IAIButton
|
||||
onClick={() => setModelFormatFilter('all')}
|
||||
onClick={setModelFormatFilter.bind(null, 'all')}
|
||||
isChecked={modelFormatFilter === 'all'}
|
||||
size="sm"
|
||||
>
|
||||
@ -108,35 +108,35 @@ const ModelList = (props: ModelListProps) => {
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={() => setModelFormatFilter('diffusers')}
|
||||
onClick={setModelFormatFilter.bind(null, 'diffusers')}
|
||||
isChecked={modelFormatFilter === 'diffusers'}
|
||||
>
|
||||
{t('modelManager.diffusersModels')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={() => setModelFormatFilter('checkpoint')}
|
||||
onClick={setModelFormatFilter.bind(null, 'checkpoint')}
|
||||
isChecked={modelFormatFilter === 'checkpoint'}
|
||||
>
|
||||
{t('modelManager.checkpointModels')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={() => setModelFormatFilter('onnx')}
|
||||
onClick={setModelFormatFilter.bind(null, 'onnx')}
|
||||
isChecked={modelFormatFilter === 'onnx'}
|
||||
>
|
||||
{t('modelManager.onnxModels')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={() => setModelFormatFilter('olive')}
|
||||
onClick={setModelFormatFilter.bind(null, 'olive')}
|
||||
isChecked={modelFormatFilter === 'olive'}
|
||||
>
|
||||
{t('modelManager.oliveModels')}
|
||||
</IAIButton>
|
||||
<IAIButton
|
||||
size="sm"
|
||||
onClick={() => setModelFormatFilter('lora')}
|
||||
onClick={setModelFormatFilter.bind(null, 'lora')}
|
||||
isChecked={modelFormatFilter === 'lora'}
|
||||
>
|
||||
{t('modelManager.loraModels')}
|
||||
|
@ -4,6 +4,7 @@ import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSync } from 'react-icons/fa';
|
||||
import { useSyncModelsMutation } from 'services/api/endpoints/models';
|
||||
@ -19,7 +20,7 @@ export default function SyncModelsButton(props: SyncModelsButtonProps) {
|
||||
|
||||
const [syncModels, { isLoading }] = useSyncModelsMutation();
|
||||
|
||||
const syncModelsHandler = () => {
|
||||
const syncModelsHandler = useCallback(() => {
|
||||
syncModels()
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
@ -44,7 +45,7 @@ export default function SyncModelsButton(props: SyncModelsButtonProps) {
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, [dispatch, syncModels, t]);
|
||||
|
||||
return !iconMode ? (
|
||||
<IAIButton
|
||||
|
Loading…
Reference in New Issue
Block a user