From ca1cc0e2c29fa253bdb7e14e2a3e5fef59870c50 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:30:21 +1000 Subject: [PATCH] feat(ui): rerender mitigation sweep --- .../frontend/web/src/app/components/App.tsx | 4 +- .../src/common/components/Loading/Loading.tsx | 3 +- .../frontend/web/src/common/util/getUrl.ts | 12 +++- .../components/CurrentImageButtons.tsx | 58 ++++++++++--------- .../components/CurrentImagePreview.tsx | 7 ++- .../gallery/components/ImageGalleryPanel.tsx | 7 ++- .../ProcessButtons/CancelButton.tsx | 36 +----------- .../features/parameters/hooks/usePrompt.ts | 22 ++++--- .../features/system/components/Console.tsx | 4 +- .../system/components/ProgressBar.tsx | 3 +- .../features/system/components/SiteHeader.tsx | 5 +- .../system/store/systemPersistsDenylist.ts | 2 +- .../src/features/system/store/systemSlice.ts | 37 +----------- .../ui/components/FloatingGalleryButton.tsx | 3 +- .../FloatingParametersPanelButtons.tsx | 3 +- .../src/features/ui/components/InvokeTabs.tsx | 30 +++++----- 16 files changed, 98 insertions(+), 138 deletions(-) diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 7cdbe830bb..b33a7cac3f 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -28,11 +28,13 @@ import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +const DEFAULT_CONFIG = {}; + interface Props extends PropsWithChildren { config?: PartialAppConfig; } -const App = ({ config = {}, children }: Props) => { +const App = ({ config = DEFAULT_CONFIG, children }: Props) => { useToastWatcher(); useGlobalHotkeys(); diff --git a/invokeai/frontend/web/src/common/components/Loading/Loading.tsx b/invokeai/frontend/web/src/common/components/Loading/Loading.tsx index 45a304747b..8625e5b49b 100644 --- a/invokeai/frontend/web/src/common/components/Loading/Loading.tsx +++ b/invokeai/frontend/web/src/common/components/Loading/Loading.tsx @@ -1,5 +1,6 @@ import { Flex, Image, Spinner } from '@chakra-ui/react'; import InvokeAILogoImage from 'assets/images/logo.png'; +import { memo } from 'react'; // This component loads before the theme so we cannot use theme tokens here @@ -29,4 +30,4 @@ const Loading = () => { ); }; -export default Loading; +export default memo(Loading); diff --git a/invokeai/frontend/web/src/common/util/getUrl.ts b/invokeai/frontend/web/src/common/util/getUrl.ts index 956e3428dc..72607057e4 100644 --- a/invokeai/frontend/web/src/common/util/getUrl.ts +++ b/invokeai/frontend/web/src/common/util/getUrl.ts @@ -1,5 +1,6 @@ import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; +import { useCallback } from 'react'; import { OpenAPI } from 'services/api'; export const getUrlAlt = (url: string, shouldTransformUrls: boolean) => { @@ -15,14 +16,19 @@ export const useGetUrl = () => { (state: RootState) => state.config.shouldTransformUrls ); - return { - shouldTransformUrls, - getUrl: (url?: string) => { + const getUrl = useCallback( + (url?: string) => { if (OpenAPI.BASE && shouldTransformUrls) { return [OpenAPI.BASE, url].join('/'); } return url; }, + [shouldTransformUrls] + ); + + return { + shouldTransformUrls, + getUrl, }; }; diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx index 6d247c7ed4..3cfe932b75 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx @@ -163,16 +163,16 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { const { t } = useTranslation(); const setBothPrompts = useSetBothPrompts(); - const handleClickUseAsInitialImage = () => { + const handleClickUseAsInitialImage = useCallback(() => { if (!image) return; if (isLightboxOpen) dispatch(setIsLightboxOpen(false)); dispatch(initialImageSelected(image.name)); // dispatch(setInitialImage(currentImage)); // dispatch(setActiveTab('img2img')); - }; + }, [dispatch, image, isLightboxOpen]); - const handleCopyImage = async () => { + const handleCopyImage = useCallback(async () => { if (!image?.url) { return; } @@ -194,9 +194,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { duration: 2500, isClosable: true, }); - }; + }, [getUrl, t, image?.url, toast]); - const handleCopyImageLink = () => { + const handleCopyImageLink = useCallback(() => { const url = image ? shouldTransformUrls ? getUrl(image.url) @@ -215,7 +215,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { isClosable: true, }); }); - }; + }, [toast, shouldTransformUrls, getUrl, t, image]); useHotkeys( 'shift+i', @@ -241,11 +241,11 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { [image] ); - const handlePreviewVisibility = () => { + const handlePreviewVisibility = useCallback(() => { dispatch(setShouldHidePreview(!shouldHidePreview)); - }; + }, [dispatch, shouldHidePreview]); - const handleClickUseAllParameters = () => { + const handleClickUseAllParameters = useCallback(() => { if (!image) return; // selectedImage.metadata && // dispatch(setAllParameters(selectedImage.metadata)); @@ -254,7 +254,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { // } else if (selectedImage.metadata?.image.type === 'txt2img') { // dispatch(setActiveTab('txt2img')); // } - }; + }, [image]); useHotkeys( 'a', @@ -338,9 +338,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { [image] ); - const handleClickUpscale = () => { + const handleClickUpscale = useCallback(() => { // selectedImage && dispatch(runESRGAN(selectedImage)); - }; + }, []); useHotkeys( 'Shift+U', @@ -369,9 +369,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { ] ); - const handleClickFixFaces = () => { + const handleClickFixFaces = useCallback(() => { // selectedImage && dispatch(runFacetool(selectedImage)); - }; + }, []); useHotkeys( 'Shift+R', @@ -401,10 +401,12 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { ] ); - const handleClickShowImageDetails = () => - dispatch(setShouldShowImageDetails(!shouldShowImageDetails)); + const handleClickShowImageDetails = useCallback( + () => dispatch(setShouldShowImageDetails(!shouldShowImageDetails)), + [dispatch, shouldShowImageDetails] + ); - const handleSendToCanvas = () => { + const handleSendToCanvas = useCallback(() => { if (!image) return; if (isLightboxOpen) dispatch(setIsLightboxOpen(false)); @@ -421,7 +423,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { duration: 2500, isClosable: true, }); - }; + }, [image, isLightboxOpen, dispatch, activeTabName, toast, t]); useHotkeys( 'i', @@ -440,19 +442,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { [image, shouldShowImageDetails] ); - const handleInitiateDelete = () => { + const handleDelete = useCallback(() => { + if (canDeleteImage && image) { + dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); + } + }, [image, canDeleteImage, dispatch]); + + const handleInitiateDelete = useCallback(() => { if (shouldConfirmOnDelete) { onDeleteDialogOpen(); } else { handleDelete(); } - }; - - const handleDelete = () => { - if (canDeleteImage && image) { - dispatch(imageDeleted({ imageType: image.type, imageName: image.name })); - } - }; + }, [shouldConfirmOnDelete, onDeleteDialogOpen, handleDelete]); useHotkeys('delete', handleInitiateDelete, [ image, @@ -461,9 +463,9 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => { isProcessing, ]); - const handleLightBox = () => { + const handleLightBox = useCallback(() => { dispatch(setIsLightboxOpen(!isLightboxOpen)); - }; + }, [dispatch, isLightboxOpen]); return ( <> diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx index 54d69b9e5b..8855bf11d3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImagePreview.tsx @@ -11,6 +11,7 @@ import CurrentImageFallback from './CurrentImageFallback'; import ImageMetadataViewer from './ImageMetaDataViewer/ImageMetadataViewer'; import NextPrevImageButtons from './NextPrevImageButtons'; import CurrentImageHidden from './CurrentImageHidden'; +import { memo } from 'react'; export const imagesSelector = createSelector( [uiSelector, selectedImageSelector, systemSelector], @@ -50,7 +51,7 @@ export const imagesSelector = createSelector( } ); -export default function CurrentImagePreview() { +const CurrentImagePreview = () => { const { shouldShowImageDetails, imageToDisplay, shouldHidePreview } = useAppSelector(imagesSelector); const { getUrl } = useGetUrl(); @@ -115,4 +116,6 @@ export default function CurrentImagePreview() { )} ); -} +}; + +export default memo(CurrentImagePreview); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx index 37ed69d4da..996603d3e6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx @@ -28,6 +28,7 @@ import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvas import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors'; import useResolution from 'common/hooks/useResolution'; import { Flex } from '@chakra-ui/react'; +import { memo } from 'react'; const GALLERY_TAB_WIDTHS: Record< InvokeTabName, @@ -72,7 +73,7 @@ const galleryPanelSelector = createSelector( } ); -export default function ImageGalleryPanel() { +export const ImageGalleryPanel = () => { const dispatch = useAppDispatch(); const { shouldPinGallery, @@ -232,4 +233,6 @@ export default function ImageGalleryPanel() { }; return renderImageGallery(); -} +}; + +export default memo(ImageGalleryPanel); diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index 2500081925..a71e8a3638 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -6,14 +6,12 @@ import IAIIconButton, { import { systemSelector } from 'features/system/store/systemSelectors'; import { SystemState, - setCancelAfter, - setCancelType, cancelScheduled, cancelTypeChanged, CancelType, } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; -import { useEffect, useCallback, memo } from 'react'; +import { useCallback, memo } from 'react'; import { ButtonSpinner, ButtonGroup, @@ -27,16 +25,9 @@ import { import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; -import { - MdArrowDropDown, - MdArrowDropUp, - MdCancel, - MdCancelScheduleSend, -} from 'react-icons/md'; +import { MdCancel, MdCancelScheduleSend } from 'react-icons/md'; -import IAISimpleMenu from 'common/components/IAISimpleMenu'; import { sessionCanceled } from 'services/thunks/session'; -import { FaChevronDown } from 'react-icons/fa'; import { BiChevronDown } from 'react-icons/bi'; const cancelButtonSelector = createSelector( @@ -48,8 +39,6 @@ const cancelButtonSelector = createSelector( isCancelable: system.isCancelable, currentIteration: system.currentIteration, totalIterations: system.totalIterations, - // cancelType: system.cancelOptions.cancelType, - // cancelAfter: system.cancelOptions.cancelAfter, sessionId: system.sessionId, cancelType: system.cancelType, isCancelScheduled: system.isCancelScheduled, @@ -75,11 +64,8 @@ const CancelButton = ( isProcessing, isConnected, isCancelable, - currentIteration, - totalIterations, cancelType, isCancelScheduled, - // cancelAfter, sessionId, } = useAppSelector(cancelButtonSelector); @@ -105,7 +91,6 @@ const CancelButton = ( }, [dispatch] ); - // const isCancelScheduled = cancelAfter === null ? false : true; useHotkeys( 'shift+x', @@ -117,23 +102,6 @@ const CancelButton = ( [isConnected, isProcessing, isCancelable] ); - // useEffect(() => { - // if (cancelAfter !== null && cancelAfter < currentIteration) { - // handleClickCancel(); - // } - // }, [cancelAfter, currentIteration, handleClickCancel]); - - // const cancelMenuItems = [ - // { - // item: t('parameters.cancel.immediate'), - // onClick: () => dispatch(cancelTypeChanged('immediate')), - // }, - // { - // item: t('parameters.cancel.schedule'), - // onClick: () => dispatch(cancelTypeChanged('scheduled')), - // }, - // ]; - return ( {cancelType === 'immediate' ? ( diff --git a/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts b/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts index 02293c6ace..40080b77c7 100644 --- a/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts +++ b/invokeai/frontend/web/src/features/parameters/hooks/usePrompt.ts @@ -4,23 +4,27 @@ import * as InvokeAI from 'app/types/invokeai'; import promptToString from 'common/util/promptToString'; import { useAppDispatch } from 'app/store/storeHooks'; import { setNegativePrompt, setPrompt } from '../store/generationSlice'; +import { useCallback } from 'react'; // TECHDEBT: We have two metadata prompt formats and need to handle recalling either of them. // This hook provides a function to do that. const useSetBothPrompts = () => { const dispatch = useAppDispatch(); - return (inputPrompt: InvokeAI.Prompt) => { - const promptString = - typeof inputPrompt === 'string' - ? inputPrompt - : promptToString(inputPrompt); + return useCallback( + (inputPrompt: InvokeAI.Prompt) => { + const promptString = + typeof inputPrompt === 'string' + ? inputPrompt + : promptToString(inputPrompt); - const [prompt, negativePrompt] = getPromptAndNegative(promptString); + const [prompt, negativePrompt] = getPromptAndNegative(promptString); - dispatch(setPrompt(prompt)); - dispatch(setNegativePrompt(negativePrompt)); - }; + dispatch(setPrompt(prompt)); + dispatch(setNegativePrompt(negativePrompt)); + }, + [dispatch] + ); }; export default useSetBothPrompts; diff --git a/invokeai/frontend/web/src/features/system/components/Console.tsx b/invokeai/frontend/web/src/features/system/components/Console.tsx index 338e5b1382..4f7946ee78 100644 --- a/invokeai/frontend/web/src/features/system/components/Console.tsx +++ b/invokeai/frontend/web/src/features/system/components/Console.tsx @@ -9,7 +9,7 @@ import { } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; import { Resizable } from 're-resizable'; -import { useLayoutEffect, useRef, useState } from 'react'; +import { memo, useLayoutEffect, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaAngleDoubleDown, FaCode, FaMinus } from 'react-icons/fa'; @@ -194,4 +194,4 @@ const Console = () => { ); }; -export default Console; +export default memo(Console); diff --git a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx index 35699c632c..03e78965a3 100644 --- a/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx +++ b/invokeai/frontend/web/src/features/system/components/ProgressBar.tsx @@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppSelector } from 'app/store/storeHooks'; import { SystemState } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { PROGRESS_BAR_THICKNESS } from 'theme/util/constants'; import { systemSelector } from '../store/systemSelectors'; @@ -40,4 +41,4 @@ const ProgressBar = () => { ); }; -export default ProgressBar; +export default memo(ProgressBar); diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx index 004e3690fd..350e1291aa 100644 --- a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx +++ b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx @@ -1,5 +1,5 @@ import { Flex, Grid } from '@chakra-ui/react'; -import { useState } from 'react'; +import { memo, useState } from 'react'; import ModelSelect from './ModelSelect'; import StatusIndicator from './StatusIndicator'; @@ -65,5 +65,4 @@ const SiteHeader = () => { ); }; -SiteHeader.displayName = 'SiteHeader'; -export default SiteHeader; +export default memo(SiteHeader); diff --git a/invokeai/frontend/web/src/features/system/store/systemPersistsDenylist.ts b/invokeai/frontend/web/src/features/system/store/systemPersistsDenylist.ts index 84dbd0ecc7..bac2a652b5 100644 --- a/invokeai/frontend/web/src/features/system/store/systemPersistsDenylist.ts +++ b/invokeai/frontend/web/src/features/system/store/systemPersistsDenylist.ts @@ -17,7 +17,7 @@ const itemsToDenylist: (keyof SystemState)[] = [ 'totalSteps', 'openModel', 'isCancelScheduled', - 'sessionId', + // 'sessionId', 'progressImage', 'wereModelsReceived', 'wasSchemaParsed', diff --git a/invokeai/frontend/web/src/features/system/store/systemSlice.ts b/invokeai/frontend/web/src/features/system/store/systemSlice.ts index 8f1410490c..3f572a253d 100644 --- a/invokeai/frontend/web/src/features/system/store/systemSlice.ts +++ b/invokeai/frontend/web/src/features/system/store/systemSlice.ts @@ -89,18 +89,6 @@ export interface SystemState * Array of node IDs that we want to handle when events received */ subscribedNodeIds: string[]; - // /** - // * Whether or not URLs should be transformed to use a different host - // */ - // shouldTransformUrls: boolean; - // /** - // * Array of disabled tabs - // */ - // disabledTabs: InvokeTabName[]; - // /** - // * Array of disabled features - // */ - // disabledFeatures: InvokeAI.AppFeature[]; /** * Whether or not the available models were received */ @@ -358,27 +346,6 @@ export const systemSlice = createSlice({ subscribedNodeIdsSet: (state, action: PayloadAction) => { state.subscribedNodeIds = action.payload; }, - // /** - // * `shouldTransformUrls` was changed - // */ - // shouldTransformUrlsChanged: (state, action: PayloadAction) => { - // state.shouldTransformUrls = action.payload; - // }, - // /** - // * `disabledTabs` was changed - // */ - // disabledTabsChanged: (state, action: PayloadAction) => { - // state.disabledTabs = action.payload; - // }, - // /** - // * `disabledFeatures` was changed - // */ - // disabledFeaturesChanged: ( - // state, - // action: PayloadAction - // ) => { - // state.disabledFeatures = action.payload; - // }, }, extraReducers(builder) { /** @@ -386,6 +353,7 @@ export const systemSlice = createSlice({ */ builder.addCase(socketSubscribed, (state, action) => { state.sessionId = action.payload.sessionId; + console.log(`Subscribed to session ${action.payload.sessionId}`); }); /** @@ -594,9 +562,6 @@ export const { scheduledCancelAborted, cancelTypeChanged, subscribedNodeIdsSet, - // shouldTransformUrlsChanged, - // disabledTabsChanged, - // disabledFeaturesChanged, } = systemSlice.actions; export default systemSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index 38cada229f..83a699aca0 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -7,6 +7,7 @@ import { setShouldShowGallery } from 'features/ui/store/uiSlice'; import { isEqual } from 'lodash-es'; import { MdPhotoLibrary } from 'react-icons/md'; import { activeTabNameSelector, uiSelector } from '../store/uiSelectors'; +import { memo } from 'react'; const floatingGalleryButtonSelector = createSelector( [activeTabNameSelector, uiSelector], @@ -58,4 +59,4 @@ const FloatingGalleryButton = () => { ) : null; }; -export default FloatingGalleryButton; +export default memo(FloatingGalleryButton); diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index c671c0dd02..3055216b66 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -11,6 +11,7 @@ import { } from 'features/ui/store/uiSelectors'; import { setShouldShowParametersPanel } from 'features/ui/store/uiSlice'; import { isEqual } from 'lodash-es'; +import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { FaSlidersH } from 'react-icons/fa'; @@ -94,4 +95,4 @@ const FloatingParametersPanelButtons = () => { ) : null; }; -export default FloatingParametersPanelButtons; +export default memo(FloatingParametersPanelButtons); diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index e053986897..0c65a99293 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -14,7 +14,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice'; -import { ReactNode, useMemo } from 'react'; +import { memo, ReactNode, useMemo } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; import { activeTabIndexSelector } from '../store/uiSelectors'; @@ -24,10 +24,10 @@ import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import NodeEditor from 'features/nodes/components/NodeEditor'; import GenerateWorkspace from './tabs/Generate/GenerateWorkspace'; -import { FaImage } from 'react-icons/fa'; import { createSelector } from '@reduxjs/toolkit'; -import { BsLightningChargeFill, BsLightningFill } from 'react-icons/bs'; +import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; +import { isEqual } from 'lodash'; export interface InvokeTabInfo { id: InvokeTabName; @@ -35,10 +35,6 @@ export interface InvokeTabInfo { workarea: ReactNode; } -const tabIconStyles: ChakraProps['sx'] = { - boxSize: 6, -}; - const tabs: InvokeTabInfo[] = [ { id: 'generate', @@ -57,13 +53,19 @@ const tabs: InvokeTabInfo[] = [ }, ]; -const enabledTabsSelector = createSelector(configSelector, (config) => { - const { disabledTabs } = config; +const enabledTabsSelector = createSelector( + configSelector, + (config) => { + const { disabledTabs } = config; - return tabs.filter((tab) => !disabledTabs.includes(tab.id)); -}); + return tabs.filter((tab) => !disabledTabs.includes(tab.id)); + }, + { + memoizeOptions: { resultEqualityCheck: isEqual }, + } +); -export default function InvokeTabs() { +const InvokeTabs = () => { const activeTab = useAppSelector(activeTabIndexSelector); const enabledTabs = useAppSelector(enabledTabsSelector); const isLightBoxOpen = useAppSelector( @@ -160,4 +162,6 @@ export default function InvokeTabs() { {tabPanels} ); -} +}; + +export default memo(InvokeTabs);