diff --git a/invokeai/frontend/web/.eslintrc.js b/invokeai/frontend/web/.eslintrc.js index c2b1433a9a..976aea9739 100644 --- a/invokeai/frontend/web/.eslintrc.js +++ b/invokeai/frontend/web/.eslintrc.js @@ -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' }, diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 00cbdcccd1..49315b80dd 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -19,7 +19,7 @@ import sdxlReducer from 'features/sdxl/store/sdxlSlice'; import configReducer from 'features/system/store/configSlice'; import systemReducer from 'features/system/store/systemSlice'; import queueReducer from 'features/queue/store/queueSlice'; -import modelmanagerReducer from 'features/ui/components/tabs/ModelManager/store/modelManagerSlice'; +import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice'; import hotkeysReducer from 'features/ui/store/hotkeysSlice'; import uiReducer from 'features/ui/store/uiSlice'; import dynamicMiddlewares from 'redux-dynamic-middlewares'; diff --git a/invokeai/frontend/web/src/common/components/IAIAlertDialog.tsx b/invokeai/frontend/web/src/common/components/IAIAlertDialog.tsx index bd919c162a..a82095daa3 100644 --- a/invokeai/frontend/web/src/common/components/IAIAlertDialog.tsx +++ b/invokeai/frontend/web/src/common/components/IAIAlertDialog.tsx @@ -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(null); - const handleAccept = () => { + const handleAccept = useCallback(() => { acceptCallback(); onClose(); - }; + }, [acceptCallback, onClose]); - const handleCancel = () => { + const handleCancel = useCallback(() => { cancelCallback && cancelCallback(); onClose(); - }; + }, [cancelCallback, onClose]); return ( <> diff --git a/invokeai/frontend/web/src/common/components/IAIErrorLoadingImageFallback.tsx b/invokeai/frontend/web/src/common/components/IAIErrorLoadingImageFallback.tsx deleted file mode 100644 index 0a5d4fb12f..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIErrorLoadingImageFallback.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box, Flex, Icon } from '@chakra-ui/react'; -import { memo } from 'react'; -import { FaExclamation } from 'react-icons/fa'; - -const IAIErrorLoadingImageFallback = () => { - return ( - - - - - - ); -}; - -export default memo(IAIErrorLoadingImageFallback); diff --git a/invokeai/frontend/web/src/common/components/IAIForm.tsx b/invokeai/frontend/web/src/common/components/IAIForm.tsx deleted file mode 100644 index c94d177a21..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIForm.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { chakra } from '@chakra-ui/react'; - -/** - * Chakra-enabled
- */ -const IAIForm = chakra.form; - -export default IAIForm; diff --git a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormErrorMessage.tsx b/invokeai/frontend/web/src/common/components/IAIForms/IAIFormErrorMessage.tsx deleted file mode 100644 index 22aa10cb06..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormErrorMessage.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FormErrorMessage, FormErrorMessageProps } from '@chakra-ui/react'; -import { ReactNode } from 'react'; - -type IAIFormErrorMessageProps = FormErrorMessageProps & { - children: ReactNode | string; -}; - -export default function IAIFormErrorMessage(props: IAIFormErrorMessageProps) { - const { children, ...rest } = props; - return ( - - {children} - - ); -} diff --git a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormHelperText.tsx b/invokeai/frontend/web/src/common/components/IAIForms/IAIFormHelperText.tsx deleted file mode 100644 index 4d48ac991c..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormHelperText.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FormHelperText, FormHelperTextProps } from '@chakra-ui/react'; -import { ReactNode } from 'react'; - -type IAIFormHelperTextProps = FormHelperTextProps & { - children: ReactNode | string; -}; - -export default function IAIFormHelperText(props: IAIFormHelperTextProps) { - const { children, ...rest } = props; - return ( - - {children} - - ); -} diff --git a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx b/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx deleted file mode 100644 index 83e91366c2..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIForms/IAIFormItemWrapper.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Flex, useColorMode } from '@chakra-ui/react'; -import { ReactElement } from 'react'; -import { mode } from 'theme/util/mode'; - -export function IAIFormItemWrapper({ - children, -}: { - children: ReactElement | ReactElement[]; -}) { - const { colorMode } = useColorMode(); - return ( - - {children} - - ); -} diff --git a/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx b/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx deleted file mode 100644 index 97ff24689c..0000000000 --- a/invokeai/frontend/web/src/common/components/IAIFullCheckbox.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { - Checkbox, - CheckboxProps, - FormControl, - FormControlProps, - FormLabel, -} from '@chakra-ui/react'; -import { memo, ReactNode } from 'react'; - -type IAIFullCheckboxProps = CheckboxProps & { - label: string | ReactNode; - formControlProps?: FormControlProps; -}; - -const IAIFullCheckbox = (props: IAIFullCheckboxProps) => { - const { label, formControlProps, ...rest } = props; - return ( - - {label} - - - ); -}; - -export default memo(IAIFullCheckbox); diff --git a/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx index afe8b9442b..a324f80770 100644 --- a/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx +++ b/invokeai/frontend/web/src/common/components/IAIMantineInput.tsx @@ -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 ( - ({ - 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 ; } diff --git a/invokeai/frontend/web/src/common/components/IAINumberInput.tsx b/invokeai/frontend/web/src/common/components/IAINumberInput.tsx index 243dac9d63..36098ec097 100644 --- a/invokeai/frontend/web/src/common/components/IAINumberInput.tsx +++ b/invokeai/frontend/web/src/common/components/IAINumberInput.tsx @@ -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) => { - 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) => { + 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) => { diff --git a/invokeai/frontend/web/src/common/components/IAISelect.tsx b/invokeai/frontend/web/src/common/components/IAISelect.tsx index 7bc8952d94..faa5732017 100644 --- a/invokeai/frontend/web/src/common/components/IAISelect.tsx +++ b/invokeai/frontend/web/src/common/components/IAISelect.tsx @@ -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) => { + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + e.nativeEvent.stopPropagation(); + e.nativeEvent.cancelBubble = true; + }, []); return ( ) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - e.nativeEvent.stopPropagation(); - e.nativeEvent.cancelBubble = true; - }} + onClick={handleClick} sx={ horizontal ? { diff --git a/invokeai/frontend/web/src/common/components/IAISlider.tsx b/invokeai/frontend/web/src/common/components/IAISlider.tsx index e879c00977..3ed3ee1920 100644 --- a/invokeai/frontend/web/src/common/components/IAISlider.tsx +++ b/invokeai/frontend/web/src/common/components/IAISlider.tsx @@ -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 ( { 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} /> - onChange(Number(localInputValue))} - /> - onChange(Number(localInputValue))} - /> + + )} diff --git a/invokeai/frontend/web/src/common/components/ImageUploader.tsx b/invokeai/frontend/web/src/common/components/ImageUploader.tsx index 59349e615d..913d3b4414 100644 --- a/invokeai/frontend/web/src/common/components/ImageUploader.tsx +++ b/invokeai/frontend/web/src/common/components/ImageUploader.tsx @@ -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 ( - { - // Bail out if user hits spacebar - do not open the uploader - if (e.key === ' ') { - return; - } - }} - > + {children} diff --git a/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx b/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx deleted file mode 100644 index 2db202ddc0..0000000000 --- a/invokeai/frontend/web/src/common/components/SelectImagePlaceholder.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Flex, Icon } from '@chakra-ui/react'; -import { memo } from 'react'; -import { FaImage } from 'react-icons/fa'; - -const SelectImagePlaceholder = () => { - return ( - - - - ); -}; - -export default memo(SelectImagePlaceholder); diff --git a/invokeai/frontend/web/src/features/ui/hooks/useCopyImageToClipboard.ts b/invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts similarity index 100% rename from invokeai/frontend/web/src/features/ui/hooks/useCopyImageToClipboard.ts rename to invokeai/frontend/web/src/common/hooks/useCopyImageToClipboard.ts diff --git a/invokeai/frontend/web/src/common/hooks/useResolution.ts b/invokeai/frontend/web/src/common/hooks/useResolution.ts deleted file mode 100644 index fb52555be8..0000000000 --- a/invokeai/frontend/web/src/common/hooks/useResolution.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useBreakpoint } from '@chakra-ui/react'; - -export default function useResolution(): - | 'mobile' - | 'tablet' - | 'desktop' - | 'unknown' { - const breakpointValue = useBreakpoint(); - - const mobileResolutions = ['base', 'sm']; - const tabletResolutions = ['md', 'lg']; - const desktopResolutions = ['xl', '2xl']; - - if (mobileResolutions.includes(breakpointValue)) { - return 'mobile'; - } - if (tabletResolutions.includes(breakpointValue)) { - return 'tablet'; - } - if (desktopResolutions.includes(breakpointValue)) { - return 'desktop'; - } - return 'unknown'; -} diff --git a/invokeai/frontend/web/src/common/util/getTimestamp.ts b/invokeai/frontend/web/src/common/util/getTimestamp.ts deleted file mode 100644 index daa9f8dc33..0000000000 --- a/invokeai/frontend/web/src/common/util/getTimestamp.ts +++ /dev/null @@ -1,7 +0,0 @@ -import dateFormat from 'dateformat'; - -/** - * Get a `now` timestamp with 1s precision, formatted as ISO datetime. - */ -export const getTimestamp = () => - dateFormat(new Date(), `yyyy-mm-dd'T'HH:MM:ss:lo`); diff --git a/invokeai/frontend/web/src/common/util/seedWeightPairs.ts b/invokeai/frontend/web/src/common/util/seedWeightPairs.ts deleted file mode 100644 index 564f66c1e3..0000000000 --- a/invokeai/frontend/web/src/common/util/seedWeightPairs.ts +++ /dev/null @@ -1,71 +0,0 @@ -// TODO: Restore variations -// Support code from v2.3 in here. - -// export const stringToSeedWeights = ( -// string: string -// ): InvokeAI.SeedWeights | boolean => { -// const stringPairs = string.split(','); -// const arrPairs = stringPairs.map((p) => p.split(':')); -// const pairs = arrPairs.map((p: Array): InvokeAI.SeedWeightPair => { -// return { seed: Number(p[0]), weight: Number(p[1]) }; -// }); - -// if (!validateSeedWeights(pairs)) { -// return false; -// } - -// return pairs; -// }; - -// export const validateSeedWeights = ( -// seedWeights: InvokeAI.SeedWeights | string -// ): boolean => { -// return typeof seedWeights === 'string' -// ? Boolean(stringToSeedWeights(seedWeights)) -// : Boolean( -// seedWeights.length && -// !seedWeights.some((pair: InvokeAI.SeedWeightPair) => { -// const { seed, weight } = pair; -// const isSeedValid = !isNaN(parseInt(seed.toString(), 10)); -// const isWeightValid = -// !isNaN(parseInt(weight.toString(), 10)) && -// weight >= 0 && -// weight <= 1; -// return !(isSeedValid && isWeightValid); -// }) -// ); -// }; - -// export const seedWeightsToString = ( -// seedWeights: InvokeAI.SeedWeights -// ): string => { -// return seedWeights.reduce((acc, pair, i, arr) => { -// const { seed, weight } = pair; -// acc += `${seed}:${weight}`; -// if (i !== arr.length - 1) { -// acc += ','; -// } -// return acc; -// }, ''); -// }; - -// export const seedWeightsToArray = ( -// seedWeights: InvokeAI.SeedWeights -// ): Array> => { -// return seedWeights.map((pair: InvokeAI.SeedWeightPair) => [ -// pair.seed, -// pair.weight, -// ]); -// }; - -// export const stringToSeedWeightsArray = ( -// string: string -// ): Array> => { -// const stringPairs = string.split(','); -// const arrPairs = stringPairs.map((p) => p.split(':')); -// return arrPairs.map( -// (p: Array): Array => [parseInt(p[0], 10), parseFloat(p[1])] -// ); -// }; - -export default {}; diff --git a/invokeai/frontend/web/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx b/invokeai/frontend/web/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx index a86497aade..3f5264fe92 100644 --- a/invokeai/frontend/web/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/ClearCanvasHistoryButtonModal.tsx @@ -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 ( dispatch(clearCanvasHistory())} + acceptCallback={acceptCallback} acceptButtonText={t('unifiedCanvas.clearHistory')} triggerComponent={ } isDisabled={isStaging}> diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx index 43e8febd66..e1b2105bbc 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasMaskOptions.tsx @@ -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) => { + dispatch(setShouldPreserveMaskedArea(e.target.checked)); + }, + [dispatch] + ); + + const handleChangeMaskColor = useCallback( + (newColor: RgbaColor) => { + dispatch(setMaskColor(newColor)); + }, + [dispatch] + ); return ( { - dispatch(setShouldPreserveMaskedArea(e.target.checked)) - } + onChange={handleChangePreserveMaskedArea} /> - dispatch(setMaskColor(newColor))} - /> + } onClick={handleSaveMask}> Save Mask diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton.tsx index 18ded1c9c7..e20980f318 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton.tsx @@ -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'], diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx index aae2da5632..c653c4ccd2 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasSettingsButtonPopover.tsx @@ -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) => - dispatch(setShouldSnapToGrid(e.target.checked)); + const handleChangeShouldSnapToGrid = useCallback( + (e: ChangeEvent) => + dispatch(setShouldSnapToGrid(e.target.checked)), + [dispatch] + ); + + const handleChangeShouldShowIntermediates = useCallback( + (e: ChangeEvent) => + dispatch(setShouldShowIntermediates(e.target.checked)), + [dispatch] + ); + const handleChangeShouldShowGrid = useCallback( + (e: ChangeEvent) => + dispatch(setShouldShowGrid(e.target.checked)), + [dispatch] + ); + const handleChangeShouldDarkenOutsideBoundingBox = useCallback( + (e: ChangeEvent) => + dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)), + [dispatch] + ); + const handleChangeShouldAutoSave = useCallback( + (e: ChangeEvent) => + dispatch(setShouldAutoSave(e.target.checked)), + [dispatch] + ); + const handleChangeShouldCropToBoundingBoxOnSave = useCallback( + (e: ChangeEvent) => + dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)), + [dispatch] + ); + const handleChangeShouldRestrictStrokesToBox = useCallback( + (e: ChangeEvent) => + dispatch(setShouldRestrictStrokesToBox(e.target.checked)), + [dispatch] + ); + const handleChangeShouldShowCanvasDebugInfo = useCallback( + (e: ChangeEvent) => + dispatch(setShouldShowCanvasDebugInfo(e.target.checked)), + [dispatch] + ); + const handleChangeShouldAntialias = useCallback( + (e: ChangeEvent) => + dispatch(setShouldAntialias(e.target.checked)), + [dispatch] + ); return ( { - dispatch(setShouldShowIntermediates(e.target.checked)) - } + onChange={handleChangeShouldShowIntermediates} /> dispatch(setShouldShowGrid(e.target.checked))} + onChange={handleChangeShouldShowGrid} /> { - dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)) - } + onChange={handleChangeShouldDarkenOutsideBoundingBox} /> dispatch(setShouldAutoSave(e.target.checked))} + onChange={handleChangeShouldAutoSave} /> - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) - } + onChange={handleChangeShouldCropToBoundingBoxOnSave} /> - dispatch(setShouldRestrictStrokesToBox(e.target.checked)) - } + onChange={handleChangeShouldRestrictStrokesToBox} /> - dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) - } + onChange={handleChangeShouldShowCanvasDebugInfo} /> dispatch(setShouldAntialias(e.target.checked))} + onChange={handleChangeShouldAntialias} /> diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx index 0030f1f06e..40b3186fb1 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolChooserOptions.tsx @@ -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 ( @@ -233,7 +256,7 @@ const IAICanvasToolChooserOptions = () => { label={t('unifiedCanvas.brushSize')} value={brushSize} withInput - onChange={(newSize) => dispatch(setBrushSize(newSize))} + onChange={handleChangeBrushSize} sliderNumberInputProps={{ max: 500 }} /> @@ -247,7 +270,7 @@ const IAICanvasToolChooserOptions = () => { dispatch(setBrushColor(newColor))} + onChange={handleChangeBrushColor} /> diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx index 617fedf0f0..89a710c14d 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasToolbar.tsx @@ -25,9 +25,9 @@ import { LAYER_NAMES_DICT, } from 'features/canvas/store/canvasTypes'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; -import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard'; +import { useCopyImageToClipboard } from 'common/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 ( { + const handleUndo = useCallback(() => { dispatch(undo()); - }; + }, [dispatch]); useHotkeys( ['meta+z', 'ctrl+z'], diff --git a/invokeai/frontend/web/src/features/canvas/util/konvaNodeToDataURL.ts b/invokeai/frontend/web/src/features/canvas/util/konvaNodeToDataURL.ts deleted file mode 100644 index 5d0aaf5443..0000000000 --- a/invokeai/frontend/web/src/features/canvas/util/konvaNodeToDataURL.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Konva from 'konva'; -import { IRect } from 'konva/lib/types'; - -/** - * Converts a Konva node to a dataURL - * @param node - The Konva node to convert to a dataURL - * @param boundingBox - The bounding box to crop to - * @returns A dataURL of the node cropped to the bounding box - */ -export const konvaNodeToDataURL = ( - node: Konva.Node, - boundingBox: IRect -): string => { - // get a dataURL of the bbox'd region - return node.toDataURL(boundingBox); -}; diff --git a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx index 6bdf434d52..122b2b7d38 100644 --- a/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx +++ b/invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx @@ -87,6 +87,11 @@ const ChangeBoardModal = () => { selectedBoard, ]); + const handleSetSelectedBoard = useCallback( + (v: string | null) => setSelectedBoard(v), + [] + ); + const cancelRef = useRef(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} /> diff --git a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterPreprocessButton.tsx b/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterPreprocessButton.tsx deleted file mode 100644 index 44a34dcd46..0000000000 --- a/invokeai/frontend/web/src/features/controlAdapters/components/ControlAdapterPreprocessButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useAppDispatch } from 'app/store/storeHooks'; -import IAIButton from 'common/components/IAIButton'; -import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue'; -import { memo, useCallback } from 'react'; -import { useControlAdapterControlImage } from '../hooks/useControlAdapterControlImage'; -import { controlAdapterImageProcessed } from '../store/actions'; - -type Props = { - id: string; -}; - -const ControlAdapterPreprocessButton = ({ id }: Props) => { - const controlImage = useControlAdapterControlImage(id); - const dispatch = useAppDispatch(); - const isReady = useIsReadyToEnqueue(); - - const handleProcess = useCallback(() => { - dispatch( - controlAdapterImageProcessed({ - id, - }) - ); - }, [id, dispatch]); - - return ( - - Preprocess - - ); -}; - -export default memo(ControlAdapterPreprocessButton); diff --git a/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts b/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts deleted file mode 100644 index 8337712ea5..0000000000 --- a/invokeai/frontend/web/src/features/dynamicPrompts/store/selectors.ts +++ /dev/null @@ -1 +0,0 @@ -// diff --git a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx index 164fd01a1f..32334a9f88 100644 --- a/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx +++ b/invokeai/frontend/web/src/features/embedding/components/ParamEmbeddingPopover.tsx @@ -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 ( { 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} /> )} diff --git a/invokeai/frontend/web/src/features/embedding/store/embeddingSlice.ts b/invokeai/frontend/web/src/features/embedding/store/embeddingSlice.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx index 29211a7558..9c77500ef3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardAutoAddSelect.tsx @@ -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 ( { 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} /> ); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx index 5dd6b94753..97e49b493c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx @@ -90,6 +90,50 @@ const BoardContextMenu = ({ e.preventDefault(); }, []); + const renderMenuFunc = useCallback( + () => ( + + + } + isDisabled={isAutoAdd || autoAssignBoardOnClick} + onClick={handleSetAutoAdd} + > + {t('boards.menuItemAutoAdd')} + + {isBulkDownloadEnabled && ( + } onClickCapture={handleBulkDownload}> + {t('boards.downloadBoard')} + + )} + {!board && } + {board && ( + + )} + + + ), + [ + autoAssignBoardOnClick, + board, + boardName, + handleBulkDownload, + handleSetAutoAdd, + isAutoAdd, + isBulkDownloadEnabled, + setBoardToDelete, + skipEvent, + t, + ] + ); + return ( menuProps={{ size: 'sm', isLazy: true }} @@ -97,38 +141,7 @@ const BoardContextMenu = ({ bg: 'transparent', _hover: { bg: 'transparent' }, }} - renderMenu={() => ( - - - } - isDisabled={isAutoAdd || autoAssignBoardOnClick} - onClick={handleSetAutoAdd} - > - {t('boards.menuItemAutoAdd')} - - {isBulkDownloadEnabled && ( - } - onClickCapture={handleBulkDownload} - > - {t('boards.downloadBoard')} - - )} - {!board && } - {board && ( - - )} - - - )} + renderMenu={renderMenuFunc} > {children} diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx deleted file mode 100644 index 7a95e7fcd9..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/GenericBoard.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { As, Badge, Flex } from '@chakra-ui/react'; -import IAIDroppable from 'common/components/IAIDroppable'; -import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { TypesafeDroppableData } from 'features/dnd/types'; -import { BoardId } from 'features/gallery/store/types'; -import { ReactNode, memo } from 'react'; -import BoardContextMenu from '../BoardContextMenu'; - -type GenericBoardProps = { - board_id: BoardId; - droppableData?: TypesafeDroppableData; - onClick: () => void; - isSelected: boolean; - icon: As; - label: string; - dropLabel?: ReactNode; - badgeCount?: number; -}; - -export const formatBadgeCount = (count: number) => - Intl.NumberFormat('en-US', { - notation: 'compact', - maximumFractionDigits: 1, - }).format(count); - -const GenericBoard = (props: GenericBoardProps) => { - const { - board_id, - droppableData, - onClick, - isSelected, - icon, - label, - badgeCount, - dropLabel, - } = props; - - return ( - - {(ref) => ( - - - - - {badgeCount !== undefined && ( - {formatBadgeCount(badgeCount)} - )} - - - - - {label} - - - )} - - ); -}; - -export default memo(GenericBoard); diff --git a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx b/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx deleted file mode 100644 index 462aa4b5e6..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/Boards/BoardsList/SystemBoardButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAIButton from 'common/components/IAIButton'; -import { boardIdSelected } from 'features/gallery/store/gallerySlice'; -import { memo, useCallback, useMemo } from 'react'; -import { useBoardName } from 'services/api/hooks/useBoardName'; - -type Props = { - board_id: 'images' | 'assets' | 'no_board'; -}; - -const SystemBoardButton = ({ board_id }: Props) => { - const dispatch = useAppDispatch(); - - const selector = useMemo( - () => - createSelector( - [stateSelector], - ({ gallery }) => { - const { selectedBoardId } = gallery; - return { isSelected: selectedBoardId === board_id }; - }, - defaultSelectorOptions - ), - [board_id] - ); - - const { isSelected } = useAppSelector(selector); - - const boardName = useBoardName(board_id); - - const handleClick = useCallback(() => { - dispatch(boardIdSelected({ boardId: board_id })); - }, [board_id, dispatch]); - - return ( - - {boardName} - - ); -}; - -export default memo(SystemBoardButton); diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageHidden.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageHidden.tsx deleted file mode 100644 index af2a7c5f98..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImage/CurrentImageHidden.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { memo } from 'react'; -import { FaEyeSlash } from 'react-icons/fa'; - -const CurrentImageHidden = () => { - return ( - - - - ); -}; - -export default memo(CurrentImageHidden); diff --git a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx index 2eab78d118..015a206fa2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GallerySettingsPopover.tsx @@ -61,6 +61,12 @@ const GallerySettingsPopover = () => { [dispatch] ); + const handleChangeAutoAssignBoardOnClick = useCallback( + (e: ChangeEvent) => + dispatch(autoAssignBoardOnClickChanged(e.target.checked)), + [dispatch] + ); + return ( { ) => - dispatch(autoAssignBoardOnClickChanged(e.target.checked)) - } + onChange={handleChangeAutoAssignBoardOnClick} /> diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageContextMenu.tsx index 0c13b37f0c..6fc5765785 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageContextMenu.tsx @@ -35,6 +35,34 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => { e.preventDefault(); }, []); + const renderMenuFunc = useCallback(() => { + if (!imageDTO) { + return null; + } + + if (selectionCount > 1) { + return ( + + + + ); + } + + return ( + + + + ); + }, [imageDTO, selectionCount, skipEvent]); + return ( 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 ( - - - - ); - } - - return ( - - - - ); - }} + renderMenu={renderMenuFunc} > {children} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx index ed12eb5ff4..5a88ef320f 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx @@ -13,7 +13,7 @@ import { workflowLoadRequested } from 'features/nodes/store/actions'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { initialImageSelected } from 'features/parameters/store/actions'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard'; +import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard'; import { setActiveTab } from 'features/ui/store/uiSlice'; import { memo, useCallback } from 'react'; import { flushSync } from 'react-dom'; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx deleted file mode 100644 index 95577efc13..0000000000 --- a/invokeai/frontend/web/src/features/gallery/components/ImageFallbackSpinner.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react'; -import { memo } from 'react'; - -type ImageFallbackSpinnerProps = SpinnerProps; - -const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => { - const { size = 'xl', ...rest } = props; - - return ( - - - - ); -}; - -export default memo(ImageFallbackSpinner); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx index dc41a2ef2a..4b7997f2ef 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageGrid.tsx @@ -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) => ( + + ), + [] + ); + useEffect(() => { // Initialize the gallery's custom scrollbar const { current: root } = rootRef; @@ -131,9 +139,7 @@ const GalleryImageGrid = () => { List: ImageGridListContainer, }} scrollerRef={setScroller} - itemContent={(index, imageName) => ( - - )} + itemContent={itemContentFunc} /> { 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)} /> ))} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx index c03fd26ba1..135159562b 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataItem.tsx @@ -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} /> )} diff --git a/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx b/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx index 6048e4a159..e2fc841f15 100644 --- a/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx +++ b/invokeai/frontend/web/src/features/lora/components/ParamLoraSelect.tsx @@ -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 ( @@ -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" /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts b/invokeai/frontend/web/src/features/modelManager/store/modelManagerSlice.ts similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelManagerSlice.ts rename to invokeai/frontend/web/src/features/modelManager/store/modelManagerSlice.ts diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx similarity index 78% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx index 0fa46ec57f..98ab96dea9 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AddModels.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AddModels.tsx @@ -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 ( setAddModelMode('simple')} + onClick={handleAddModelSimple} > Simple setAddModelMode('advanced')} + onClick={handleAddModelAdvanced} > Advanced diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx similarity index 86% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx index 94a65a9122..f2d5efb565 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddCheckpoint.tsx @@ -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 = + 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 ( @@ -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} /> setUseCustomConfig(!useCustomConfig)} + onChange={handleChangeUseCustomConfig} label={t('modelManager.useCustomConfig')} /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx similarity index 86% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx index 7c22b4b2ac..2a087681c2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/AdvancedAddDiffusers.tsx @@ -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 = + 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 ( @@ -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} /> ('diffusers'); const { t } = useTranslation(); + const handleChange = useCallback((v: string | null) => { + if (!v) { + return; + } + setAdvancedAddMode(v as ManualAddMode); + }, []); return ( @@ -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} /> dispatch(setAdvancedAddScanModel(model)), + [dispatch] + ); + const renderModels = ({ models, showActions = true, @@ -140,7 +145,7 @@ export default function FoundModelsList() { {t('modelManager.quickAdd')} dispatch(setAdvancedAddScanModel(model))} + onClick={handleClickSetAdvanced.bind(null, model)} isLoading={isLoading} > {t('modelManager.advanced')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx similarity index 84% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx index 615c76b71e..17f4467af5 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/ScanAdvancedAddModels.tsx @@ -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() { } aria-label={t('modelManager.closeAdvanced')} - onClick={() => dispatch(setAdvancedAddScanModel(null))} + onClick={handleClickSetAdvanced} size="sm" /> @@ -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 ? ( { + const scanAgainHandler = useCallback(() => { refetchFoundModels(); - }; + }, [refetchFoundModels]); + + const handleClickClearCheckpointFolder = useCallback(() => { + dispatch(setSearchFolder(null)); + dispatch(setAdvancedAddScanModel(null)); + }, [dispatch]); return ( } size="sm" - onClick={() => { - dispatch(setSearchFolder(null)); - dispatch(setAdvancedAddScanModel(null)); - }} + onClick={handleClickClearCheckpointFolder} isDisabled={!searchFolder} colorScheme="red" /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/SimpleAddModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/util.ts b/invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/util.ts similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/AddModelsPanel/util.ts rename to invokeai/frontend/web/src/features/modelManager/subpanels/AddModelsPanel/util.ts diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ImportModelsPanel.tsx similarity index 78% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ImportModelsPanel.tsx index b0a8c124eb..acf6b705db 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ImportModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ImportModelsPanel.tsx @@ -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('add'); const { t } = useTranslation(); + const handleClickAddTab = useCallback(() => setAddModelTab('add'), []); + const handleClickScanTab = useCallback(() => setAddModelTab('scan'), []); + return ( setAddModelTab('add')} + onClick={handleClickAddTab} isChecked={addModelTab == 'add'} size="sm" width="100%" @@ -23,7 +26,7 @@ export default function ImportModelsPanel() { {t('modelManager.addModel')} setAddModelTab('scan')} + onClick={handleClickScanTab} isChecked={addModelTab == 'scan'} size="sm" width="100%" diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/MergeModelsPanel.tsx similarity index 80% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/MergeModelsPanel.tsx index e5c68ba6cf..f9132517c2 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/MergeModelsPanel.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/MergeModelsPanel.tsx @@ -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) => 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) => + setModelMergeCustomSaveLoc(e.target.value), + [] + ); + const handleChangeModelMergeForce = useCallback( + (e: ChangeEvent) => 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 ( @@ -180,7 +239,7 @@ export default function MergeModelsPanel() { value={modelOne} placeholder={t('modelManager.selectModel')} data={modelOneList} - onChange={(v) => setModelOne(v)} + onChange={handleChangeModelOne} /> setModelTwo(v)} + onChange={handleChangeModelTwo} /> { - if (!v) { - setModelThree(null); - setModelMergeInterp('add_difference'); - } else { - setModelThree(v); - setModelMergeInterp('weighted_sum'); - } - }} + onChange={handleChangeModelThree} /> setMergedModelName(e.target.value)} + onChange={handleChangeMergedModelName} /> setModelMergeAlpha(v)} + onChange={handleChangeModelMergeAlpha} withInput withReset - handleReset={() => setModelMergeAlpha(0.5)} + handleReset={handleResetModelMergeAlpha} withSliderMarks /> @@ -257,10 +308,7 @@ export default function MergeModelsPanel() { {t('modelManager.interpolationType')} - setModelMergeInterp(v)} - > + {modelThree === null ? ( <> @@ -305,7 +353,7 @@ export default function MergeModelsPanel() { setModelMergeSaveLocType(v)} + onChange={handleChangeMergeSaveLocType} > @@ -323,7 +371,7 @@ export default function MergeModelsPanel() { setModelMergeCustomSaveLoc(e.target.value)} + onChange={handleChangeMergeCustomSaveLoc} /> )} @@ -331,7 +379,7 @@ export default function MergeModelsPanel() { setModelMergeForce(e.target.checked)} + onChange={handleChangeModelMergeForce} fontWeight="500" /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx similarity index 97% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx index f4943d3ce1..e6a8abbaaf 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/CheckpointModelEdit.tsx @@ -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) { )} setUseCustomConfig(!useCustomConfig)} + onChange={handleChangeUseCustomConfig} label="Use Custom Config" /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/DiffusersModelEdit.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/LoRAModelEdit.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelConvert.tsx similarity index 86% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelConvert.tsx index 56dcdfd52f..1b92981bda 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelConvert.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelConvert.tsx @@ -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) => { + 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 ( {t('modelManager.convertToDiffusersSaveLocation')} - setSaveLocation(v as SaveLocation)} - > + @@ -162,9 +177,7 @@ export default function ModelConvert(props: ModelConvertProps) { { - setCustomSaveLocation(e.target.value); - }} + onChange={handleChangeCustomSaveLocation} width="full" /> diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx similarity index 96% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelList.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx index a77d58d07f..879aea0f6c 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelList.tsx @@ -100,7 +100,7 @@ const ModelList = (props: ModelListProps) => { setModelFormatFilter('all')} + onClick={setModelFormatFilter.bind(null, 'all')} isChecked={modelFormatFilter === 'all'} size="sm" > @@ -108,35 +108,35 @@ const ModelList = (props: ModelListProps) => { setModelFormatFilter('diffusers')} + onClick={setModelFormatFilter.bind(null, 'diffusers')} isChecked={modelFormatFilter === 'diffusers'} > {t('modelManager.diffusersModels')} setModelFormatFilter('checkpoint')} + onClick={setModelFormatFilter.bind(null, 'checkpoint')} isChecked={modelFormatFilter === 'checkpoint'} > {t('modelManager.checkpointModels')} setModelFormatFilter('onnx')} + onClick={setModelFormatFilter.bind(null, 'onnx')} isChecked={modelFormatFilter === 'onnx'} > {t('modelManager.onnxModels')} setModelFormatFilter('olive')} + onClick={setModelFormatFilter.bind(null, 'olive')} isChecked={modelFormatFilter === 'olive'} > {t('modelManager.oliveModels')} setModelFormatFilter('lora')} + onClick={setModelFormatFilter.bind(null, 'lora')} isChecked={modelFormatFilter === 'lora'} > {t('modelManager.loraModels')} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerPanel/ModelListItem.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerPanel/ModelListItem.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModels.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModels.tsx similarity index 100% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModels.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModels.tsx diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx similarity index 93% rename from invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx rename to invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx index 33ee345ef7..c58abaaae5 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx +++ b/invokeai/frontend/web/src/features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton.tsx @@ -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 ? ( ) => { const [isHovering, setIsHovering] = useState(false); - const handleMouseEnter = () => { + const handleMouseEnter = useCallback(() => { setIsHovering(true); - }; + }, []); - const handleMouseLeave = () => { + const handleMouseLeave = useCallback(() => { setIsHovering(false); - }; + }, []); return ( { nodeId, ]); + const renderMenuFunc = useCallback( + () => + !menuItems.length ? null : ( + + + {menuItems} + + + ), + [fieldTemplateTitle, label, menuItems, skipEvent, t] + ); + return ( menuProps={{ @@ -114,21 +132,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => { bg: 'transparent', _hover: { bg: 'transparent' }, }} - renderMenu={() => - !menuItems.length ? null : ( - - - {menuItems} - - - ) - } + renderMenu={renderMenuFunc} > {children} diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ClipInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ClipInputField.tsx deleted file mode 100644 index cf5d7fae95..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ClipInputField.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { - ClipInputFieldTemplate, - ClipInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const ClipInputFieldComponent = ( - _props: FieldComponentProps -) => { - return null; -}; - -export default memo(ClipInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionInputField.tsx deleted file mode 100644 index 7cbc46f28c..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionInputField.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - CollectionInputFieldTemplate, - CollectionInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const CollectionInputFieldComponent = ( - _props: FieldComponentProps< - CollectionInputFieldValue, - CollectionInputFieldTemplate - > -) => { - return null; -}; - -export default memo(CollectionInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionItemInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionItemInputField.tsx deleted file mode 100644 index e67a20bdfb..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/CollectionItemInputField.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - CollectionItemInputFieldTemplate, - CollectionItemInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const CollectionItemInputFieldComponent = ( - _props: FieldComponentProps< - CollectionItemInputFieldValue, - CollectionItemInputFieldTemplate - > -) => { - return null; -}; - -export default memo(CollectionItemInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ConditioningInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ConditioningInputField.tsx deleted file mode 100644 index 9d174f40c5..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ConditioningInputField.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - ConditioningInputFieldTemplate, - ConditioningInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const ConditioningInputFieldComponent = ( - _props: FieldComponentProps< - ConditioningInputFieldValue, - ConditioningInputFieldTemplate - > -) => { - return null; -}; - -export default memo(ConditioningInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ControlInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ControlInputField.tsx deleted file mode 100644 index 2aaac52615..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ControlInputField.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - ControlInputFieldTemplate, - ControlInputFieldValue, - ControlPolymorphicInputFieldTemplate, - ControlPolymorphicInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const ControlInputFieldComponent = ( - _props: FieldComponentProps< - ControlInputFieldValue | ControlPolymorphicInputFieldValue, - ControlInputFieldTemplate | ControlPolymorphicInputFieldTemplate - > -) => { - return null; -}; - -export default memo(ControlInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/DenoiseMaskInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/DenoiseMaskInputField.tsx deleted file mode 100644 index 79b2668887..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/DenoiseMaskInputField.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { - DenoiseMaskInputFieldTemplate, - DenoiseMaskInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const DenoiseMaskInputFieldComponent = ( - _props: FieldComponentProps< - DenoiseMaskInputFieldValue, - DenoiseMaskInputFieldTemplate - > -) => { - return null; -}; - -export default memo(DenoiseMaskInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IPAdapterInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IPAdapterInputField.tsx deleted file mode 100644 index efeffe5723..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/IPAdapterInputField.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - IPAdapterInputFieldTemplate, - IPAdapterInputFieldValue, - FieldComponentProps, - IPAdapterPolymorphicInputFieldValue, - IPAdapterPolymorphicInputFieldTemplate, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const IPAdapterInputFieldComponent = ( - _props: FieldComponentProps< - IPAdapterInputFieldValue | IPAdapterPolymorphicInputFieldValue, - IPAdapterInputFieldTemplate | IPAdapterPolymorphicInputFieldTemplate - > -) => { - return null; -}; - -export default memo(IPAdapterInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageCollectionInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageCollectionInputField.tsx deleted file mode 100644 index 4a19f08614..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageCollectionInputField.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { - ImageCollectionInputFieldTemplate, - ImageCollectionInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -import { Flex } from '@chakra-ui/react'; -import IAIDndImage from 'common/components/IAIDndImage'; -import IAIDropOverlay from 'common/components/IAIDropOverlay'; -import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks'; -import { NodesMultiImageDropData } from 'features/dnd/types'; -import { isValidDrop } from 'features/dnd/util/isValidDrop'; -import { useGetImageDTOQuery } from 'services/api/endpoints/images'; - -const ImageCollectionInputFieldComponent = ( - props: FieldComponentProps< - ImageCollectionInputFieldValue, - ImageCollectionInputFieldTemplate - > -) => { - const { nodeId, field } = props; - - // const dispatch = useAppDispatch(); - - // const handleDrop = useCallback( - // ({ image_name }: ImageDTO) => { - // dispatch( - // fieldValueChanged({ - // nodeId, - // fieldName: field.name, - // value: uniqBy([...(field.value ?? []), { image_name }], 'image_name'), - // }) - // ); - // }, - // [dispatch, field.name, field.value, nodeId] - // ); - - const droppableData: NodesMultiImageDropData = { - id: `node-${nodeId}-${field.name}`, - actionType: 'SET_MULTI_NODES_IMAGE', - context: { nodeId: nodeId, fieldName: field.name }, - }; - - const { - isOver, - setNodeRef: setDroppableRef, - active, - } = useDroppableTypesafe({ - id: `node_${nodeId}`, - data: droppableData, - }); - - // const handleReset = useCallback(() => { - // dispatch( - // fieldValueChanged({ - // nodeId, - // fieldName: field.name, - // value: undefined, - // }) - // ); - // }, [dispatch, field.name, nodeId]); - - return ( - - {field.value?.map(({ image_name }) => ( - - ))} - {isValidDrop(droppableData, active) && } - - ); -}; - -export default memo(ImageCollectionInputFieldComponent); - -type ImageSubFieldProps = { imageName: string }; - -const ImageSubField = (props: ImageSubFieldProps) => { - const { currentData: image } = useGetImageDTOQuery(props.imageName); - - return ( - - ); -}; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LatentsInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LatentsInputField.tsx deleted file mode 100644 index a5065be0ee..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LatentsInputField.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - LatentsInputFieldTemplate, - LatentsInputFieldValue, - FieldComponentProps, - LatentsPolymorphicInputFieldValue, - LatentsPolymorphicInputFieldTemplate, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const LatentsInputFieldComponent = ( - _props: FieldComponentProps< - LatentsInputFieldValue | LatentsPolymorphicInputFieldValue, - LatentsInputFieldTemplate | LatentsPolymorphicInputFieldTemplate - > -) => { - return null; -}; - -export default memo(LatentsInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx index 3ca87b3e85..712b1ff5a0 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/LoRAModelInputField.tsx @@ -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 ( @@ -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={{ diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx index 08483986e3..af68b4291c 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/MainModelInputField.tsx @@ -11,7 +11,7 @@ import { import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; +import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; import { forEach } from 'lodash-es'; import { memo, useCallback, useMemo } from 'react'; import { NON_SDXL_MAIN_MODELS } from 'services/api/constants'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberInputField.tsx index 2afbec1df8..2b2763ca3e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberInputField.tsx @@ -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 ( diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx index 19f2c5ac8e..e6db6031b8 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/RefinerModelInputField.tsx @@ -11,7 +11,7 @@ import { import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; +import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; import { forEach } from 'lodash-es'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx index 89cb8d5150..c6ef5c6bb4 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/SDXLMainModelInputField.tsx @@ -11,7 +11,7 @@ import { import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; +import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; import { forEach } from 'lodash-es'; import { memo, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/T2IAdapterInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/T2IAdapterInputField.tsx deleted file mode 100644 index 210adb6eff..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/T2IAdapterInputField.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { - T2IAdapterInputFieldTemplate, - T2IAdapterInputFieldValue, - T2IAdapterPolymorphicInputFieldTemplate, - T2IAdapterPolymorphicInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const T2IAdapterInputFieldComponent = ( - _props: FieldComponentProps< - T2IAdapterInputFieldValue | T2IAdapterPolymorphicInputFieldValue, - T2IAdapterInputFieldTemplate | T2IAdapterPolymorphicInputFieldTemplate - > -) => { - return null; -}; - -export default memo(T2IAdapterInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/UnetInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/UnetInputField.tsx deleted file mode 100644 index 2beefc7034..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/UnetInputField.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { - UNetInputFieldTemplate, - UNetInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const UNetInputFieldComponent = ( - _props: FieldComponentProps -) => { - return null; -}; - -export default memo(UNetInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/VaeInputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/VaeInputField.tsx deleted file mode 100644 index 738267faab..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/VaeInputField.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { - VaeInputFieldTemplate, - VaeInputFieldValue, - FieldComponentProps, -} from 'features/nodes/types/types'; -import { memo } from 'react'; - -const VaeInputFieldComponent = ( - _props: FieldComponentProps -) => { - return null; -}; - -export default memo(VaeInputFieldComponent); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeResizer.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeResizer.tsx deleted file mode 100644 index 6391e86471..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeResizer.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { NODE_MIN_WIDTH } from 'features/nodes/types/constants'; -import { memo } from 'react'; -import { NodeResizeControl, NodeResizerProps } from 'reactflow'; - -// this causes https://github.com/invoke-ai/InvokeAI/issues/4140 -// not using it for now - -const NodeResizer = (props: NodeResizerProps) => { - const { ...rest } = props; - return ( - - ); -}; - -export default memo(NodeResizer); diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx deleted file mode 100644 index ffc260b95a..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { InvocationTemplate, NodeData } from 'features/nodes/types/types'; -import { memo } from 'react'; -import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea'; -import NodeTitle from '../../flow/nodes/common/NodeTitle'; -import ScrollableContent from '../ScrollableContent'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - stateSelector, - ({ nodes }) => { - const lastSelectedNodeId = - nodes.selectedNodes[nodes.selectedNodes.length - 1]; - - const lastSelectedNode = nodes.nodes.find( - (node) => node.id === lastSelectedNodeId - ); - - const lastSelectedNodeTemplate = lastSelectedNode - ? nodes.nodeTemplates[lastSelectedNode.data.type] - : undefined; - - return { - data: lastSelectedNode?.data, - template: lastSelectedNodeTemplate, - }; - }, - defaultSelectorOptions -); - -const InspectorDetailsTab = () => { - const { data, template } = useAppSelector(selector); - const { t } = useTranslation(); - - if (!template || !data) { - return ( - - ); - } - - return ; -}; - -export default memo(InspectorDetailsTab); - -const Content = (props: { data: NodeData; template: InvocationTemplate }) => { - const { data } = props; - - return ( - - - - - - - - - ); -}; diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowNotesTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowNotesTab.tsx deleted file mode 100644 index d1ea0bcea1..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowNotesTab.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Box, Text } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAITextarea from 'common/components/IAITextarea'; -import { workflowNotesChanged } from 'features/nodes/store/nodesSlice'; -import { ChangeEvent, memo, useCallback } from 'react'; - -const selector = createSelector(stateSelector, ({ nodes }) => { - const { notes } = nodes.workflow; - - return { - notes, - }; -}); - -const WorkflowNotesTab = () => { - const { notes } = useAppSelector(selector); - const dispatch = useAppDispatch(); - - const handleChangeNotes = useCallback( - (e: ChangeEvent) => { - dispatch(workflowNotesChanged(e.target.value)); - }, - [dispatch] - ); - - return ( - - - - - {notes.length} - - - - ); -}; - -export default memo(WorkflowNotesTab); diff --git a/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts b/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts deleted file mode 100644 index 2c4ec37f0b..0000000000 --- a/invokeai/frontend/web/src/features/nodes/store/util/makeTemplateSelector.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import { AnyInvocationType } from 'services/events/types'; - -export const makeTemplateSelector = (type: AnyInvocationType) => - createSelector( - stateSelector, - ({ nodes }) => nodes.nodeTemplates[type], - defaultSelectorOptions - ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx deleted file mode 100644 index b9cc8511aa..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxCollapse.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import IAICollapse from 'common/components/IAICollapse'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import ParamBoundingBoxHeight from './ParamBoundingBoxHeight'; -import ParamBoundingBoxWidth from './ParamBoundingBoxWidth'; - -const ParamBoundingBoxCollapse = () => { - const { t } = useTranslation(); - - return ( - - - - - - - ); -}; - -export default memo(ParamBoundingBoxCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx index 1711c9344a..18a5b52e99 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx @@ -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 ( { 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 ( { ); 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 ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceSteps.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceSteps.tsx index 463d596a5c..3c3bc59c20 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceSteps.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceSteps.tsx @@ -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 ( { step={1} sliderNumberInputProps={{ max: 999 }} value={canvasCoherenceSteps} - onChange={(v) => { - dispatch(setCanvasCoherenceSteps(v)); - }} + onChange={handleChange} withInput withSliderMarks withReset - handleReset={() => { - dispatch(setCanvasCoherenceSteps(20)); - }} + handleReset={handleReset} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceStrength.tsx index e5311db3a6..7fc3096062 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceStrength.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/CoherencePass/ParamCanvasCoherenceStrength.tsx @@ -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 ( { 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} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx index bbadbbabf0..013d4ad4a3 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlur.tsx @@ -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 ( { - dispatch(setMaskBlur(v)); - }} + onChange={handleChange} withInput withSliderMarks withReset - handleReset={() => { - dispatch(setMaskBlur(16)); - }} + handleReset={handleReset} /> ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlurMethod.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlurMethod.tsx index df0b2ebc90..b7490461b5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlurMethod.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/Compositing/MaskAdjustment/ParamMaskBlurMethod.tsx @@ -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 ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx index 1a0e497739..06594b7b1b 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaleBeforeProcessing.tsx @@ -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 ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx index 6d0ac52b2f..62b7c899f2 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx @@ -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 ( { 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 ( { + dispatch(setAspectRatio(ratio.value)); + dispatch(setShouldLockAspectRatio(false)); + }, + [dispatch] + ); + return ( {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} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx index 03f502846c..0305424ef0 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageFit.tsx @@ -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) => - dispatch(setShouldFitToWidthHeight(e.target.checked)); + const handleChangeFit = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldFitToWidthHeight(e.target.checked)); + }, + [dispatch] + ); const { t } = useTranslation(); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/MainModel/ParamMainModelSelect.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/MainModel/ParamMainModelSelect.tsx index f4d7421eed..74b6e7e0f3 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/MainModel/ParamMainModelSelect.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/MainModel/ParamMainModelSelect.tsx @@ -12,7 +12,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { modelSelected } from 'features/parameters/store/actions'; import { MODEL_TYPE_MAP } from 'features/parameters/types/constants'; import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam'; -import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; +import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { forEach } from 'lodash-es'; import { NON_REFINER_BASE_MODELS } from 'services/api/constants'; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx deleted file mode 100644 index 7244800c41..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamNoiseThreshold.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAISlider from 'common/components/IAISlider'; -import { setThreshold } from 'features/parameters/store/generationSlice'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - stateSelector, - (state) => { - const { threshold } = state.generation; - return { - threshold, - }; - }, - defaultSelectorOptions -); - -export default function ParamNoiseThreshold() { - const dispatch = useAppDispatch(); - const { threshold } = useAppSelector(selector); - const { t } = useTranslation(); - - return ( - dispatch(setThreshold(v))} - handleReset={() => dispatch(setThreshold(0))} - value={threshold} - withInput - withReset - withSliderMarks - /> - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx deleted file mode 100644 index b5429dc292..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Noise/ParamPerlinNoise.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -import IAISlider from 'common/components/IAISlider'; -import { setPerlin } from 'features/parameters/store/generationSlice'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - stateSelector, - (state) => { - const { perlin } = state.generation; - return { - perlin, - }; - }, - defaultSelectorOptions -); - -export default function ParamPerlinNoise() { - const dispatch = useAppDispatch(); - const { perlin } = useAppSelector(selector); - const { t } = useTranslation(); - - return ( - dispatch(setPerlin(v))} - handleReset={() => dispatch(setPerlin(0))} - value={perlin} - withInput - withReset - withSliderMarks - /> - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx index 481fe27964..4dc86fb8ee 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Seed/ParamSeed.tsx @@ -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 ( { (state: RootState) => state.generation.shouldRandomizeSeed ); - const handleChangeShouldRandomizeSeed = (e: ChangeEvent) => - dispatch(setShouldRandomizeSeed(e.target.checked)); + const handleChangeShouldRandomizeSeed = useCallback( + (e: ChangeEvent) => + dispatch(setShouldRandomizeSeed(e.target.checked)), + [dispatch] + ); return ( - dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))); + const handleClickRandomizeSeed = useCallback( + () => dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))), + [dispatch] + ); return ( { + dispatch(setHorizontalSymmetrySteps(v)); + }, + [dispatch] + ); + const handleReset = useCallback(() => { + dispatch(setHorizontalSymmetrySteps(0)); + }, [dispatch]); + return ( dispatch(setHorizontalSymmetrySteps(v))} + onChange={handleChange} min={0} max={steps} step={1} withInput withSliderMarks withReset - handleReset={() => dispatch(setHorizontalSymmetrySteps(0))} + handleReset={handleReset} /> ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx index 59386ff526..5de62fd9b2 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryToggle.tsx @@ -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) => { + dispatch(setShouldUseSymmetry(e.target.checked)); + }, + [dispatch] + ); return ( dispatch(setShouldUseSymmetry(e.target.checked))} + onChange={handleChange} /> ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx index c8ddb46a3a..90656272ec 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Symmetry/ParamSymmetryVertical.tsx @@ -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 ( dispatch(setVerticalSymmetrySteps(v))} + onChange={handleChange} min={0} max={steps} step={1} withInput withSliderMarks withReset - handleReset={() => dispatch(setVerticalSymmetrySteps(0))} + handleReset={handleReset} /> ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/ParamRealESRGANModel.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/ParamRealESRGANModel.tsx index 5c1bc09717..8a5ac94fac 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/ParamRealESRGANModel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Upscale/ParamRealESRGANModel.tsx @@ -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 ( state.generation.variationAmount - ); - - const shouldGenerateVariations = useAppSelector( - (state: RootState) => state.generation.shouldGenerateVariations - ); - - const { t } = useTranslation(); - - const dispatch = useAppDispatch(); - - return ( - dispatch(setVariationAmount(v))} - handleReset={() => dispatch(setVariationAmount(0.1))} - withInput - withReset - withSliderMarks - /> - ); -} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx deleted file mode 100644 index 7ea248498d..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationCollapse.tsx +++ /dev/null @@ -1,51 +0,0 @@ -// TODO: variations - -// import { Flex } from '@chakra-ui/react'; -// import { createSelector } from '@reduxjs/toolkit'; -// import { stateSelector } from 'app/store/store'; -// import { useAppSelector } from 'app/store/storeHooks'; -// import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; -// import IAICollapse from 'common/components/IAICollapse'; -// import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -// import { memo } from 'react'; -// import { useTranslation } from 'react-i18next'; -// import ParamVariationAmount from './ParamVariationAmount'; -// import { ParamVariationToggle } from './ParamVariationToggle'; -// import ParamVariationWeights from './ParamVariationWeights'; - -// const selector = createSelector( -// stateSelector, -// (state) => { -// const activeLabel = state.generation.shouldGenerateVariations -// ? 'Enabled' -// : undefined; - -// return { activeLabel }; -// }, -// defaultSelectorOptions -// ); - -// const ParamVariationCollapse = () => { -// const { t } = useTranslation(); -// const { activeLabel } = useAppSelector(selector); - -// const isVariationEnabled = useFeatureStatus('variation').isFeatureEnabled; - -// if (!isVariationEnabled) { -// return null; -// } - -// return ( -// -// -// -// -// -// -// -// ); -// }; - -// export default memo(ParamVariationCollapse); - -export default {}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx deleted file mode 100644 index 96929ea00a..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationToggle.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISwitch from 'common/components/IAISwitch'; -import { setShouldGenerateVariations } from 'features/parameters/store/generationSlice'; -import { ChangeEvent } from 'react'; - -export const ParamVariationToggle = () => { - const dispatch = useAppDispatch(); - - const shouldGenerateVariations = useAppSelector( - (state: RootState) => state.generation.shouldGenerateVariations - ); - - const handleChange = (e: ChangeEvent) => - dispatch(setShouldGenerateVariations(e.target.checked)); - - return ( - - ); -}; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx deleted file mode 100644 index 45029e8536..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Variations/ParamVariationWeights.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// TODO: variations - -// import { RootState } from 'app/store/store'; -// import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -// import IAIInput from 'common/components/IAIInput'; -// import { validateSeedWeights } from 'common/util/seedWeightPairs'; -// import { setSeedWeights } from 'features/parameters/store/generationSlice'; -// import { ChangeEvent } from 'react'; -// import { useTranslation } from 'react-i18next'; - -// export default function ParamVariationWeights() { -// const seedWeights = useAppSelector( -// (state: RootState) => state.generation.seedWeights -// ); - -// const shouldGenerateVariations = useAppSelector( -// (state: RootState) => state.generation.shouldGenerateVariations -// ); - -// const { t } = useTranslation(); - -// const dispatch = useAppDispatch(); - -// const handleChangeSeedWeights = (e: ChangeEvent) => -// dispatch(setSeedWeights(e.target.value)); - -// return ( -// -// ); -// } - -export default {}; diff --git a/invokeai/frontend/web/src/features/parameters/store/postprocessingSelectors.ts b/invokeai/frontend/web/src/features/parameters/store/postprocessingSelectors.ts deleted file mode 100644 index 2908d16c54..0000000000 --- a/invokeai/frontend/web/src/features/parameters/store/postprocessingSelectors.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { RootState } from 'app/store/store'; - -export const postprocessingSelector = (state: RootState) => - state.postprocessing; diff --git a/invokeai/frontend/web/src/features/queue/components/CurrentQueueItemCard.tsx b/invokeai/frontend/web/src/features/queue/components/CurrentQueueItemCard.tsx deleted file mode 100644 index 2f7fc6fd8b..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/CurrentQueueItemCard.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { memo } from 'react'; -import QueueItemCard from './common/QueueItemCard'; -import { useGetCurrentQueueItemQuery } from 'services/api/endpoints/queue'; -import { useTranslation } from 'react-i18next'; - -const CurrentQueueItemCard = () => { - const { t } = useTranslation(); - const { data: currentQueueItemData } = useGetCurrentQueueItemQuery(); - - return ( - - ); -}; - -export default memo(CurrentQueueItemCard); diff --git a/invokeai/frontend/web/src/features/queue/components/NextQueueItemCard.tsx b/invokeai/frontend/web/src/features/queue/components/NextQueueItemCard.tsx deleted file mode 100644 index f9b9e874ab..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/NextQueueItemCard.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useGetNextQueueItemQuery } from 'services/api/endpoints/queue'; -import QueueItemCard from './common/QueueItemCard'; - -const NextQueueItemCard = () => { - const { t } = useTranslation(); - const { data: nextQueueItemData } = useGetNextQueueItemQuery(); - - return ( - - ); -}; - -export default memo(NextQueueItemCard); diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemSkeleton.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemSkeleton.tsx deleted file mode 100644 index 529c46af74..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemSkeleton.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Flex, Skeleton } from '@chakra-ui/react'; -import { memo } from 'react'; -import { COLUMN_WIDTHS } from './constants'; - -const QueueItemSkeleton = () => { - return ( - - - -   - - - - -   - - - - -   - - - - -   - - - - -   - - - - ); -}; - -export default memo(QueueItemSkeleton); diff --git a/invokeai/frontend/web/src/features/queue/components/QueueStatusCard.tsx b/invokeai/frontend/web/src/features/queue/components/QueueStatusCard.tsx deleted file mode 100644 index fb57500461..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/QueueStatusCard.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Flex, Heading, Text } from '@chakra-ui/react'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useGetQueueStatusQuery } from 'services/api/endpoints/queue'; - -const QueueStatusCard = () => { - const { t } = useTranslation(); - const { data: queueStatus } = useGetQueueStatusQuery(); - - return ( - - {t('queue.status')} - - - {t('queue.pending')}:{' '} - - {queueStatus?.queue.pending} - - - - {t('queue.in_progress')}:{' '} - - {queueStatus?.queue.in_progress} - - - - {t('queue.completed')}:{' '} - - {queueStatus?.queue.completed} - - - - {t('queue.failed')}:{' '} - - {queueStatus?.queue.failed} - - - - {t('queue.canceled')}:{' '} - - {queueStatus?.queue.canceled} - - - ); -}; - -export default memo(QueueStatusCard); diff --git a/invokeai/frontend/web/src/features/queue/components/common/QueueItemCard.tsx b/invokeai/frontend/web/src/features/queue/components/common/QueueItemCard.tsx deleted file mode 100644 index d441be4ecb..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/common/QueueItemCard.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Flex, Heading, Text, Tooltip } from '@chakra-ui/react'; -import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent'; -import { memo } from 'react'; -import { components } from 'services/api/schema'; - -const QueueItemCard = ({ - session_queue_item, - label, -}: { - session_queue_item?: components['schemas']['SessionQueueItem'] | null; - label: string; -}) => { - return ( - - - {label} - {session_queue_item && ( - - {session_queue_item.batch_id} - - )} - - {session_queue_item && ( - - Batch Values: - {session_queue_item.field_values && - session_queue_item.field_values - .filter((v) => v.node_path !== 'metadata_accumulator') - .map(({ node_path, field_name, value }) => ( - - - {node_path}.{field_name} - - : {value} - - ))} - - )} - - ); -}; - -export default memo(QueueItemCard); diff --git a/invokeai/frontend/web/src/features/queue/components/common/QueueStatusDot.tsx b/invokeai/frontend/web/src/features/queue/components/common/QueueStatusDot.tsx deleted file mode 100644 index 6a500cada2..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/common/QueueStatusDot.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import { memo, useMemo } from 'react'; -import { SessionQueueItemStatus } from 'services/api/endpoints/queue'; - -const STATUSES = { - pending: { colorScheme: 'cyan', translationKey: 'queue.pending' }, - in_progress: { colorScheme: 'yellow', translationKey: 'queue.in_progress' }, - completed: { colorScheme: 'green', translationKey: 'queue.completed' }, - failed: { colorScheme: 'red', translationKey: 'queue.failed' }, - canceled: { colorScheme: 'orange', translationKey: 'queue.canceled' }, -}; - -const QueueStatusDot = ({ status }: { status: SessionQueueItemStatus }) => { - const sx = useMemo( - () => ({ - w: 2, - h: 2, - bg: `${STATUSES[status].colorScheme}.${500}`, - _dark: { - bg: `${STATUSES[status].colorScheme}.${400}`, - }, - borderRadius: '100%', - }), - [status] - ); - return ; -}; -export default memo(QueueStatusDot); diff --git a/invokeai/frontend/web/src/features/queue/util/formatNumberShort.ts b/invokeai/frontend/web/src/features/queue/util/formatNumberShort.ts deleted file mode 100644 index 9cc300c960..0000000000 --- a/invokeai/frontend/web/src/features/queue/util/formatNumberShort.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const formatNumberShort = (num: number) => - Intl.NumberFormat('en-US', { - notation: 'standard', - }).format(num); diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx index 94c4a3aeb0..5d563e7817 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLConcatButton.tsx @@ -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 ( ) => { - dispatch(setShouldUseSDXLRefiner(e.target.checked)); - }; + const handleUseSDXLRefinerChange = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldUseSDXLRefiner(e.target.checked)); + }, + [dispatch] + ); return ( void; -}; - -const ResetWebUIButton = ({ onSettingsModalClose }: Props) => { - const { t } = useTranslation(); - const [countdown, setCountdown] = useState(5); - - const { - isOpen: isRefreshModalOpen, - onOpen: onRefreshModalOpen, - onClose: onRefreshModalClose, - } = useDisclosure(); - - const handleClickResetWebUI = useCallback(() => { - // Only remove our keys - Object.keys(window.localStorage).forEach((key) => { - if ( - LOCALSTORAGE_KEYS.includes(key) || - key.startsWith(LOCALSTORAGE_PREFIX) - ) { - localStorage.removeItem(key); - } - }); - onSettingsModalClose(); - onRefreshModalOpen(); - setInterval(() => setCountdown((prev) => prev - 1), 1000); - }, [onSettingsModalClose, onRefreshModalOpen]); - - useEffect(() => { - if (countdown <= 0) { - window.location.reload(); - } - }, [countdown]); - - return ( - <> - - {t('settings.resetWebUI')} - - - - - - - - - {t('settings.resetComplete')} - Reloading in {countdown}... - - - - - - - - ); -}; - -export default memo(ResetWebUIButton); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 383e3558b0..fdfe3fb0ce 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -212,6 +212,61 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { useFeatureStatus('localization').isFeatureEnabled; const language = useAppSelector(languageSelector); + const handleChangeShouldConfirmOnDelete = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldConfirmOnDelete(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldUseNSFWChecker = useCallback( + (e: ChangeEvent) => { + dispatch(shouldUseNSFWCheckerChanged(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldUseWatermarker = useCallback( + (e: ChangeEvent) => { + dispatch(shouldUseWatermarkerChanged(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldUseSliders = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldUseSliders(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldShowProgressInViewer = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldShowProgressInViewer(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldAntialiasProgressImage = useCallback( + (e: ChangeEvent) => { + dispatch(shouldAntialiasProgressImageChanged(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldAutoChangeDimensions = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldAutoChangeDimensions(e.target.checked)); + }, + [dispatch] + ); + const handleChangeShouldEnableInformationalPopovers = useCallback( + (e: ChangeEvent) => { + dispatch(setShouldEnableInformationalPopovers(e.target.checked)); + }, + [dispatch] + ); + const handleChangeEnableImageDebugging = useCallback( + (e: ChangeEvent) => { + dispatch(setEnableImageDebugging(e.target.checked)); + }, + [dispatch] + ); + return ( <> {cloneElement(children, { @@ -235,9 +290,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { ) => - dispatch(setShouldConfirmOnDelete(e.target.checked)) - } + onChange={handleChangeShouldConfirmOnDelete} /> @@ -248,17 +301,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { label="Enable NSFW Checker" isDisabled={!isNSFWCheckerAvailable} isChecked={shouldUseNSFWChecker} - onChange={(e: ChangeEvent) => - dispatch(shouldUseNSFWCheckerChanged(e.target.checked)) - } + onChange={handleChangeShouldUseNSFWChecker} /> ) => - dispatch(shouldUseWatermarkerChanged(e.target.checked)) - } + onChange={handleChangeShouldUseWatermarker} /> @@ -272,32 +321,22 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { ) => - dispatch(setShouldUseSliders(e.target.checked)) - } + onChange={handleChangeShouldUseSliders} /> ) => - dispatch(setShouldShowProgressInViewer(e.target.checked)) - } + onChange={handleChangeShouldShowProgressInViewer} /> ) => - dispatch( - shouldAntialiasProgressImageChanged(e.target.checked) - ) - } + onChange={handleChangeShouldAntialiasProgressImage} /> ) => - dispatch(setShouldAutoChangeDimensions(e.target.checked)) - } + onChange={handleChangeShouldAutoChangeDimensions} /> {shouldShowLocalizationToggle && ( { ) => - dispatch( - setShouldEnableInformationalPopovers(e.target.checked) - ) - } + onChange={handleChangeShouldEnableInformationalPopovers} /> @@ -340,9 +375,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { ) => - dispatch(setEnableImageDebugging(e.target.checked)) - } + onChange={handleChangeEnableImageDebugging} /> )} diff --git a/invokeai/frontend/web/src/features/system/store/actions.ts b/invokeai/frontend/web/src/features/system/store/actions.ts deleted file mode 100644 index 66181bc803..0000000000 --- a/invokeai/frontend/web/src/features/system/store/actions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createAction } from '@reduxjs/toolkit'; - -export const sessionReadyToInvoke = createAction('system/sessionReadyToInvoke'); diff --git a/invokeai/frontend/web/src/features/system/store/sessionSlice.ts b/invokeai/frontend/web/src/features/system/store/sessionSlice.ts deleted file mode 100644 index 40d59c7baa..0000000000 --- a/invokeai/frontend/web/src/features/system/store/sessionSlice.ts +++ /dev/null @@ -1,62 +0,0 @@ -// TODO: split system slice inot this - -// import type { PayloadAction } from '@reduxjs/toolkit'; -// import { createSlice } from '@reduxjs/toolkit'; -// import { socketSubscribed, socketUnsubscribed } from 'services/events/actions'; - -// export type SessionState = { -// /** -// * The current socket session id -// */ -// sessionId: string; -// /** -// * Whether the current session is a canvas session. Needed to manage the staging area. -// */ -// isCanvasSession: boolean; -// /** -// * When a session is canceled, its ID is stored here until a new session is created. -// */ -// canceledSessionId: string; -// }; - -// export const initialSessionState: SessionState = { -// sessionId: '', -// isCanvasSession: false, -// canceledSessionId: '', -// }; - -// export const sessionSlice = createSlice({ -// name: 'session', -// initialState: initialSessionState, -// reducers: { -// sessionIdChanged: (state, action: PayloadAction) => { -// state.sessionId = action.payload; -// }, -// isCanvasSessionChanged: (state, action: PayloadAction) => { -// state.isCanvasSession = action.payload; -// }, -// }, -// extraReducers: (builder) => { -// /** -// * Socket Subscribed -// */ -// builder.addCase(socketSubscribed, (state, action) => { -// state.sessionId = action.payload.sessionId; -// state.canceledSessionId = ''; -// }); - -// /** -// * Socket Unsubscribed -// */ -// builder.addCase(socketUnsubscribed, (state) => { -// state.sessionId = ''; -// }); -// }, -// }); - -// export const { sessionIdChanged, isCanvasSessionChanged } = -// sessionSlice.actions; - -// export default sessionSlice.reducer; - -export default {}; diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index b18a069cfa..c999214841 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -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; diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index da4d409943..49a6e120ab 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -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; diff --git a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx deleted file mode 100644 index 722ee46fd5..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; -import { PropsWithChildren, memo } from 'react'; -const OverlayScrollable = (props: PropsWithChildren) => { - return ( - - {props.children} - - ); -}; -export default memo(OverlayScrollable); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx index 1c8ea3a735..50a9b3b581 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/ModelManagerTab.tsx @@ -1,10 +1,10 @@ import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'; import i18n from 'i18n'; import { ReactNode, memo } from 'react'; -import ImportModelsPanel from './subpanels/ImportModelsPanel'; -import MergeModelsPanel from './subpanels/MergeModelsPanel'; -import ModelManagerPanel from './subpanels/ModelManagerPanel'; -import ModelManagerSettingsPanel from './subpanels/ModelManagerSettingsPanel'; +import ImportModelsPanel from '../../../../modelManager/subpanels/ImportModelsPanel'; +import MergeModelsPanel from '../../../../modelManager/subpanels/MergeModelsPanel'; +import ModelManagerPanel from '../../../../modelManager/subpanels/ModelManagerPanel'; +import ModelManagerSettingsPanel from '../../../../modelManager/subpanels/ModelManagerSettingsPanel'; type ModelManagerTabName = | 'modelManager' diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx deleted file mode 100644 index 593282760a..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ModelManager/store/modelmanagerSelectors.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { RootState } from 'app/store/store'; - -export const modelmanagerSelector = (state: RootState) => state.modelmanager; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx deleted file mode 100644 index 9b9310b197..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings'; -import UnifiedCanvasLimitStrokesToBox from './UnifiedCanvasLimitStrokesToBox'; - -export default function UnifiedCanvasBaseBrushSettings() { - return ( - - - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx deleted file mode 100644 index 1a5e42461b..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import UnifiedCanvasBrushSize from './UnifiedCanvasBrushSize'; -import UnifiedCanvasColorPicker from './UnifiedCanvasColorPicker'; - -export default function UnifiedCanvasBrushSettings() { - return ( - - - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx deleted file mode 100644 index 0792b787cd..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISlider from 'common/components/IAISlider'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { setBrushSize } from 'features/canvas/store/canvasSlice'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasBrushSize() { - const dispatch = useAppDispatch(); - - const brushSize = useAppSelector( - (state: RootState) => state.canvas.brushSize - ); - - const { t } = useTranslation(); - - const isStaging = useAppSelector(isStagingSelector); - - useHotkeys( - ['BracketLeft'], - () => { - dispatch(setBrushSize(Math.max(brushSize - 5, 5))); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [brushSize] - ); - - useHotkeys( - ['BracketRight'], - () => { - dispatch(setBrushSize(Math.min(brushSize + 5, 500))); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [brushSize] - ); - - return ( - dispatch(setBrushSize(newSize))} - sliderNumberInputProps={{ max: 500 }} - isCompact - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx deleted file mode 100644 index 325afa7327..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useAppDispatch } from 'app/store/storeHooks'; -import IAIButton from 'common/components/IAIButton'; - -import { clearMask } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -import { FaTrash } from 'react-icons/fa'; - -export default function UnifiedCanvasClearMask() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const handleClearMask = () => dispatch(clearMask()); - - return ( - } - onClick={handleClearMask} - tooltip={`${t('unifiedCanvas.clearMask')} (Shift+C)`} - > - {t('unifiedCanvas.betaClear')} - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx deleted file mode 100644 index e57d87915f..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Box, Flex } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIColorPicker from 'common/components/IAIColorPicker'; -import IAIPopover from 'common/components/IAIPopover'; -import { - canvasSelector, - isStagingSelector, -} from 'features/canvas/store/canvasSelectors'; -import { setBrushColor, setMaskColor } from 'features/canvas/store/canvasSlice'; -import { clamp, isEqual } from 'lodash-es'; - -import { useHotkeys } from 'react-hotkeys-hook'; - -const selector = createSelector( - [canvasSelector, isStagingSelector], - (canvas, isStaging) => { - const { brushColor, maskColor, layer } = canvas; - return { - brushColor, - maskColor, - layer, - isStaging, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -export default function UnifiedCanvasColorPicker() { - const dispatch = useAppDispatch(); - const { brushColor, maskColor, layer, isStaging } = useAppSelector(selector); - - const currentColorDisplay = () => { - if (layer === 'base') { - return `rgba(${brushColor.r},${brushColor.g},${brushColor.b},${brushColor.a})`; - } - if (layer === 'mask') { - return `rgba(${maskColor.r},${maskColor.g},${maskColor.b},${maskColor.a})`; - } - }; - - useHotkeys( - ['shift+BracketLeft'], - () => { - dispatch( - setBrushColor({ - ...brushColor, - a: clamp(brushColor.a - 0.05, 0.05, 1), - }) - ); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [brushColor] - ); - - useHotkeys( - ['shift+BracketRight'], - () => { - dispatch( - setBrushColor({ - ...brushColor, - a: clamp(brushColor.a + 0.05, 0.05, 1), - }) - ); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [brushColor] - ); - - return ( - - } - > - - {layer === 'base' && ( - - dispatch(setBrushColor(newColor))} - /> - - )} - {layer === 'mask' && ( - - dispatch(setMaskColor(newColor))} - /> - - )} - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx deleted file mode 100644 index 53e36f62b6..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasDarkenOutsideSelection() { - const shouldDarkenOutsideBoundingBox = useAppSelector( - (state: RootState) => state.canvas.shouldDarkenOutsideBoundingBox - ); - - const dispatch = useAppDispatch(); - - const { t } = useTranslation(); - - return ( - - dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)) - } - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx deleted file mode 100644 index ceb58cb5ca..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasEnableMask() { - const isMaskEnabled = useAppSelector( - (state: RootState) => state.canvas.isMaskEnabled - ); - - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const handleToggleEnableMask = () => - dispatch(setIsMaskEnabled(!isMaskEnabled)); - - return ( - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx deleted file mode 100644 index 65f77c582f..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasLimitStrokesToBox() { - const dispatch = useAppDispatch(); - - const shouldRestrictStrokesToBox = useAppSelector( - (state: RootState) => state.canvas.shouldRestrictStrokesToBox - ); - - const { t } = useTranslation(); - - return ( - - dispatch(setShouldRestrictStrokesToBox(e.target.checked)) - } - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx deleted file mode 100644 index 71f41496ed..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import UnifiedCanvasBrushSettings from './UnifiedCanvasBrushSettings'; -import UnifiedCanvasClearMask from './UnifiedCanvasClearMask'; -import UnifiedCanvasEnableMask from './UnifiedCanvasEnableMask'; -import UnifiedCanvasPreserveMask from './UnifiedCanvasPreserveMask'; - -export default function UnifiedCanvasMaskBrushSettings() { - return ( - - - - - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx deleted file mode 100644 index 8881c93bb1..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Flex } from '@chakra-ui/layout'; -import UnifiedCanvasDarkenOutsideSelection from './UnifiedCanvasDarkenOutsideSelection'; -import UnifiedCanvasShowGrid from './UnifiedCanvasShowGrid'; -import UnifiedCanvasSnapToGrid from './UnifiedCanvasSnapToGrid'; - -export default function UnifiedCanvasMoveSettings() { - return ( - - - - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx deleted file mode 100644 index fd3396533c..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasPreserveMask() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const shouldPreserveMaskedArea = useAppSelector( - (state: RootState) => state.canvas.shouldPreserveMaskedArea - ); - - return ( - dispatch(setShouldPreserveMaskedArea(e.target.checked))} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx deleted file mode 100644 index 22737ce0bb..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import IAIIconButton from 'common/components/IAIIconButton'; -import IAIPopover from 'common/components/IAIPopover'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; -import { - setShouldAntialias, - setShouldAutoSave, - setShouldCropToBoundingBoxOnSave, - setShouldShowCanvasDebugInfo, - setShouldShowIntermediates, -} from 'features/canvas/store/canvasSlice'; - -import { FaWrench } from 'react-icons/fa'; - -import ClearCanvasHistoryButtonModal from 'features/canvas/components/ClearCanvasHistoryButtonModal'; -import { isEqual } from 'lodash-es'; -import { useTranslation } from 'react-i18next'; -import { memo } from 'react'; - -export const canvasControlsSelector = createSelector( - [canvasSelector], - (canvas) => { - const { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldShowCanvasDebugInfo, - shouldShowIntermediates, - shouldAntialias, - } = canvas; - - return { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldShowCanvasDebugInfo, - shouldShowIntermediates, - shouldAntialias, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const UnifiedCanvasSettings = () => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const { - shouldAutoSave, - shouldCropToBoundingBoxOnSave, - shouldShowCanvasDebugInfo, - shouldShowIntermediates, - shouldAntialias, - } = useAppSelector(canvasControlsSelector); - - return ( - } - /> - } - > - - - dispatch(setShouldShowIntermediates(e.target.checked)) - } - /> - dispatch(setShouldAutoSave(e.target.checked))} - /> - - dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)) - } - /> - - dispatch(setShouldShowCanvasDebugInfo(e.target.checked)) - } - /> - dispatch(setShouldAntialias(e.target.checked))} - /> - - - - ); -}; - -export default memo(UnifiedCanvasSettings); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx deleted file mode 100644 index e17f74ce41..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setShouldShowGrid } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasShowGrid() { - const shouldShowGrid = useAppSelector( - (state: RootState) => state.canvas.shouldShowGrid - ); - - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - return ( - dispatch(setShouldShowGrid(e.target.checked))} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx deleted file mode 100644 index 69e9a4e78b..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox'; -import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice'; -import { ChangeEvent } from 'react'; -import { useTranslation } from 'react-i18next'; - -export default function UnifiedCanvasSnapToGrid() { - const shouldSnapToGrid = useAppSelector( - (state: RootState) => state.canvas.shouldSnapToGrid - ); - - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const handleChangeShouldSnapToGrid = (e: ChangeEvent) => - dispatch(setShouldSnapToGrid(e.target.checked)); - - return ( - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx deleted file mode 100644 index e5503ac203..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; -import { canvasSelector } from 'features/canvas/store/canvasSelectors'; - -import { Flex } from '@chakra-ui/react'; -import { isEqual } from 'lodash-es'; -import UnifiedCanvasBaseBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings'; -import UnifiedCanvasMaskBrushSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings'; -import UnifiedCanvasMoveSettings from './UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings'; - -const selector = createSelector( - [canvasSelector], - (canvas) => { - const { tool, layer } = canvas; - return { - tool, - layer, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -export default function UnifiedCanvasToolSettingsBeta() { - const { tool, layer } = useAppSelector(selector); - - return ( - - {layer == 'base' && ['brush', 'eraser', 'colorPicker'].includes(tool) && ( - - )} - {layer == 'mask' && ['brush', 'eraser', 'colorPicker'].includes(tool) && ( - - )} - {tool == 'move' && } - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx deleted file mode 100644 index a865bcd0de..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { canvasCopiedToClipboard } from 'features/canvas/store/actions'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard'; -import { useCallback } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaCopy } from 'react-icons/fa'; - -export default function UnifiedCanvasCopyToClipboard() { - const isStaging = useAppSelector(isStagingSelector); - const { isClipboardAPIAvailable } = useCopyImageToClipboard(); - - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - useHotkeys( - ['meta+c', 'ctrl+c'], - () => { - handleCopyImageToClipboard(); - }, - { - enabled: () => !isStaging && isClipboardAPIAvailable, - preventDefault: true, - }, - [isClipboardAPIAvailable] - ); - - const handleCopyImageToClipboard = useCallback(() => { - if (!isClipboardAPIAvailable) { - return; - } - dispatch(canvasCopiedToClipboard()); - }, [dispatch, isClipboardAPIAvailable]); - - if (!isClipboardAPIAvailable) { - return null; - } - - return ( - } - onClick={handleCopyImageToClipboard} - isDisabled={isStaging} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx deleted file mode 100644 index 1039c5f364..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { canvasDownloadedAsImage } from 'features/canvas/store/actions'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaDownload } from 'react-icons/fa'; - -export default function UnifiedCanvasDownloadImage() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const canvasBaseLayer = getCanvasBaseLayer(); - - const isStaging = useAppSelector(isStagingSelector); - - useHotkeys( - ['shift+d'], - () => { - handleDownloadAsImage(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [canvasBaseLayer] - ); - - const handleDownloadAsImage = () => { - dispatch(canvasDownloadedAsImage()); - }; - - return ( - } - onClick={handleDownloadAsImage} - isDisabled={isStaging} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx deleted file mode 100644 index ac83e7f20b..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { useTranslation } from 'react-i18next'; -import { FaUpload } from 'react-icons/fa'; - -export default function UnifiedCanvasFileUploader() { - const isStaging = useAppSelector(isStagingSelector); - - const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({ - postUploadAction: { type: 'SET_CANVAS_INITIAL_IMAGE' }, - }); - const { t } = useTranslation(); - - return ( - <> - } - isDisabled={isStaging} - {...getUploadButtonProps()} - /> - - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx deleted file mode 100644 index d7fb9d8201..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIMantineSelect from 'common/components/IAIMantineSelect'; -import { - canvasSelector, - isStagingSelector, -} from 'features/canvas/store/canvasSelectors'; -import { setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice'; -import { - CanvasLayer, - LAYER_NAMES_DICT, -} from 'features/canvas/store/canvasTypes'; -import { isEqual } from 'lodash-es'; - -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; - -const selector = createSelector( - [canvasSelector, isStagingSelector], - (canvas, isStaging) => { - const { layer, isMaskEnabled } = canvas; - return { layer, isMaskEnabled, isStaging }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -export default function UnifiedCanvasLayerSelect() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - const { layer, isMaskEnabled, isStaging } = useAppSelector(selector); - - const handleToggleMaskLayer = () => { - dispatch(setLayer(layer === 'mask' ? 'base' : 'mask')); - }; - - useHotkeys( - ['q'], - () => { - handleToggleMaskLayer(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [layer] - ); - - const handleChangeLayer = (v: string) => { - const newLayer = v as CanvasLayer; - dispatch(setLayer(newLayer)); - if (newLayer === 'mask' && !isMaskEnabled) { - dispatch(setIsMaskEnabled(true)); - } - }; - return ( - - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx deleted file mode 100644 index 66c378e068..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { canvasMerged } from 'features/canvas/store/actions'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaLayerGroup } from 'react-icons/fa'; -export default function UnifiedCanvasMergeVisible() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const isStaging = useAppSelector(isStagingSelector); - - useHotkeys( - ['shift+m'], - () => { - handleMergeVisible(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [] - ); - - const handleMergeVisible = () => { - dispatch(canvasMerged()); - }; - return ( - } - onClick={handleMergeVisible} - isDisabled={isStaging} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx deleted file mode 100644 index 5362657def..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { RootState } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { setTool } from 'features/canvas/store/canvasSlice'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaArrowsAlt } from 'react-icons/fa'; - -export default function UnifiedCanvasMoveTool() { - const tool = useAppSelector((state: RootState) => state.canvas.tool); - const isStaging = useAppSelector(isStagingSelector); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - useHotkeys( - ['v'], - () => { - handleSelectMoveTool(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [] - ); - - const handleSelectMoveTool = () => dispatch(setTool('move')); - - return ( - } - isChecked={tool === 'move' || isStaging} - onClick={handleSelectMoveTool} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx deleted file mode 100644 index 85396c87be..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { resetCanvas } from 'features/canvas/store/canvasSlice'; -import { useTranslation } from 'react-i18next'; -import { FaTrash } from 'react-icons/fa'; - -export default function UnifiedCanvasResetCanvas() { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const isStaging = useAppSelector(isStagingSelector); - - const handleResetCanvas = () => { - dispatch(resetCanvas()); - }; - return ( - } - onClick={handleResetCanvas} - isDisabled={isStaging} - colorScheme="error" - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx deleted file mode 100644 index 7d0478476e..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useAppDispatch } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick'; -import { resetCanvasView } from 'features/canvas/store/canvasSlice'; -import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaCrosshairs } from 'react-icons/fa'; - -export default function UnifiedCanvasResetView() { - const canvasBaseLayer = getCanvasBaseLayer(); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - useHotkeys( - ['r'], - () => { - handleResetCanvasView(); - }, - { - enabled: () => true, - preventDefault: true, - }, - [canvasBaseLayer] - ); - - const handleClickResetCanvasView = useSingleAndDoubleClick( - () => handleResetCanvasView(false), - () => handleResetCanvasView(true) - ); - - const handleResetCanvasView = (shouldScaleTo1 = false) => { - const canvasBaseLayer = getCanvasBaseLayer(); - if (!canvasBaseLayer) { - return; - } - const clientRect = canvasBaseLayer.getClientRect({ - skipTransform: true, - }); - dispatch( - resetCanvasView({ - contentRect: clientRect, - shouldScaleTo1, - }) - ); - }; - return ( - } - onClick={handleClickResetCanvasView} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx deleted file mode 100644 index 31617a4dbe..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { canvasSavedToGallery } from 'features/canvas/store/actions'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaSave } from 'react-icons/fa'; - -export default function UnifiedCanvasSaveToGallery() { - const isStaging = useAppSelector(isStagingSelector); - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - - useHotkeys( - ['shift+s'], - () => { - handleSaveToGallery(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [] - ); - - const handleSaveToGallery = () => { - dispatch(canvasSavedToGallery()); - }; - - return ( - } - onClick={handleSaveToGallery} - isDisabled={isStaging} - /> - ); -} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx deleted file mode 100644 index 59a0890960..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { ButtonGroup, Flex } from '@chakra-ui/react'; -import { createSelector } from '@reduxjs/toolkit'; -import { stateSelector } from 'app/store/store'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { isStagingSelector } from 'features/canvas/store/canvasSelectors'; -import { - addEraseRect, - addFillRect, - setTool, -} from 'features/canvas/store/canvasSlice'; -import { isEqual } from 'lodash-es'; -import { memo, useCallback } from 'react'; - -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { - FaEraser, - FaEyeDropper, - FaFillDrip, - FaPaintBrush, - FaPlus, -} from 'react-icons/fa'; - -export const selector = createSelector( - [stateSelector, isStagingSelector], - ({ canvas }, isStaging) => { - const { tool } = canvas; - - return { - tool, - isStaging, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: isEqual, - }, - } -); - -const UnifiedCanvasToolSelect = () => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const { tool, isStaging } = useAppSelector(selector); - - useHotkeys( - ['b'], - () => { - handleSelectBrushTool(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [] - ); - - useHotkeys( - ['e'], - () => { - handleSelectEraserTool(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [tool] - ); - - useHotkeys( - ['c'], - () => { - handleSelectColorPickerTool(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - }, - [tool] - ); - - useHotkeys( - ['shift+f'], - () => { - handleFillRect(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - } - ); - - useHotkeys( - ['delete', 'backspace'], - () => { - handleEraseBoundingBox(); - }, - { - enabled: () => !isStaging, - preventDefault: true, - } - ); - - 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] - ); - - return ( - - - } - isChecked={tool === 'brush' && !isStaging} - onClick={handleSelectBrushTool} - isDisabled={isStaging} - /> - } - isChecked={tool === 'eraser' && !isStaging} - isDisabled={isStaging} - onClick={handleSelectEraserTool} - /> - - - } - isDisabled={isStaging} - onClick={handleFillRect} - /> - } - isDisabled={isStaging} - onClick={handleEraseBoundingBox} - /> - - } - isChecked={tool === 'colorPicker' && !isStaging} - isDisabled={isStaging} - onClick={handleSelectColorPickerTool} - width="max-content" - /> - - ); -}; - -export default memo(UnifiedCanvasToolSelect); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx deleted file mode 100644 index 7ef5af6949..0000000000 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Flex } from '@chakra-ui/react'; - -import IAICanvasRedoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasRedoButton'; -import IAICanvasUndoButton from 'features/canvas/components/IAICanvasToolbar/IAICanvasUndoButton'; -import { memo } from 'react'; -import UnifiedCanvasSettings from './UnifiedCanvasToolSettings/UnifiedCanvasSettings'; -import UnifiedCanvasCopyToClipboard from './UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard'; -import UnifiedCanvasDownloadImage from './UnifiedCanvasToolbar/UnifiedCanvasDownloadImage'; -import UnifiedCanvasFileUploader from './UnifiedCanvasToolbar/UnifiedCanvasFileUploader'; -import UnifiedCanvasLayerSelect from './UnifiedCanvasToolbar/UnifiedCanvasLayerSelect'; -import UnifiedCanvasMergeVisible from './UnifiedCanvasToolbar/UnifiedCanvasMergeVisible'; -import UnifiedCanvasMoveTool from './UnifiedCanvasToolbar/UnifiedCanvasMoveTool'; -import UnifiedCanvasResetCanvas from './UnifiedCanvasToolbar/UnifiedCanvasResetCanvas'; -import UnifiedCanvasResetView from './UnifiedCanvasToolbar/UnifiedCanvasResetView'; -import UnifiedCanvasSaveToGallery from './UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery'; -import UnifiedCanvasToolSelect from './UnifiedCanvasToolbar/UnifiedCanvasToolSelect'; - -const UnifiedCanvasToolbarBeta = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export default memo(UnifiedCanvasToolbarBeta); diff --git a/invokeai/frontend/web/src/theme/components/table.ts b/invokeai/frontend/web/src/theme/components/table.ts deleted file mode 100644 index e69de29bb2..0000000000