From d94aa4abf7dbc6b9e8920f3d9d0c5ae7838dc8b2 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:46:32 +1000 Subject: [PATCH] feat(ui): enforce loader when switching tabs --- invokeai/frontend/web/.eslintrc.js | 18 ++++++ .../src/app/components/GlobalHookIsolator.tsx | 2 + .../web/src/app/hooks/useStudioInitAction.ts | 23 +++---- .../web/src/common/hooks/useGlobalHotkeys.ts | 14 ++-- .../SimpleSession/GenerateLaunchpadPanel.tsx | 8 +-- ...ImageMenuItemNewCanvasFromImageSubMenu.tsx | 8 +-- .../ImageMenuItemNewLayerFromImageSubMenu.tsx | 20 +++--- .../ImageMenuItemSendToUpscale.tsx | 4 +- .../ImageViewer/NoContentForViewer.tsx | 14 ++-- .../hooks/useStarterModelsToast.tsx | 8 +-- .../WorkflowViewEditToggleButton.tsx | 3 - .../NavigateToModelManagerButton.tsx | 9 ++- .../parameters/components/ModelPicker.tsx | 9 ++- .../PostProcessing/PostProcessingPopover.tsx | 8 +-- .../components/QueueActionsMenuButton.tsx | 8 +-- .../UpscaleWarning.tsx | 6 +- .../src/features/ui/components/AppContent.tsx | 47 +++++++++----- .../src/features/ui/components/TabButton.tsx | 9 ++- .../ui/layouts/canvas-tab-auto-layout.tsx | 10 +-- .../ui/layouts/generate-tab-auto-layout.tsx | 10 +-- .../ui/layouts/models-tab-auto-layout.tsx | 10 +-- .../ui/layouts/navigation-api.test.ts | 12 ++-- .../src/features/ui/layouts/navigation-api.ts | 64 ++++++++++++++----- .../ui/layouts/queue-tab-auto-layout.tsx | 10 +-- .../web/src/features/ui/layouts/shared.ts | 2 + .../ui/layouts/upscaling-tab-auto-layout.tsx | 11 ++-- .../ui/layouts/workflows-tab-auto-layout.tsx | 10 +-- .../web/src/features/ui/store/uiSlice.ts | 20 ------ .../hooks/useValidateAndLoadWorkflow.ts | 3 + .../services/events/onModelInstallError.tsx | 8 +-- 30 files changed, 202 insertions(+), 186 deletions(-) diff --git a/invokeai/frontend/web/.eslintrc.js b/invokeai/frontend/web/.eslintrc.js index f30fc1ad2a..034730c64b 100644 --- a/invokeai/frontend/web/.eslintrc.js +++ b/invokeai/frontend/web/.eslintrc.js @@ -17,6 +17,15 @@ module.exports = { 'no-promise-executor-return': 'error', // https://eslint.org/docs/latest/rules/require-await 'require-await': 'error', + // Restrict setActiveTab calls to only use-navigation-api.tsx + 'no-restricted-syntax': [ + 'error', + { + selector: 'CallExpression[callee.name="setActiveTab"]', + message: + 'setActiveTab() can only be called from use-navigation-api.tsx. Use navigationApi.switchToTab() instead.', + }, + ], // TODO: ENABLE THIS RULE BEFORE v6.0.0 'react/display-name': 'off', 'no-restricted-properties': [ @@ -56,6 +65,15 @@ module.exports = { ], }, overrides: [ + /** + * Allow setActiveTab calls only in use-navigation-api.tsx + */ + { + files: ['**/use-navigation-api.tsx'], + rules: { + 'no-restricted-syntax': 'off', + }, + }, /** * Overrides for stories */ diff --git a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx index b017fc3893..46aed952d8 100644 --- a/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx +++ b/invokeai/frontend/web/src/app/components/GlobalHookIsolator.tsx @@ -11,6 +11,7 @@ import type { PartialAppConfig } from 'app/types/invokeai'; import { useFocusRegionWatcher } from 'common/hooks/focus'; import { useCloseChakraTooltipsOnDragFix } from 'common/hooks/useCloseChakraTooltipsOnDragFix'; import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys'; +import { useDndMonitor } from 'features/dnd/useDndMonitor'; import { useDynamicPromptsWatcher } from 'features/dynamicPrompts/hooks/useDynamicPromptsWatcher'; import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast'; import { useWorkflowBuilderWatcher } from 'features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher'; @@ -45,6 +46,7 @@ export const GlobalHookIsolator = memo( useSyncLoggingConfig(); useCloseChakraTooltipsOnDragFix(); useNavigationApi(); + useDndMonitor(); // Persistent subscription to the queue counts query - canvas relies on this to know if there are pending // and/or in progress canvas sessions. diff --git a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts index 7dfaae2759..4032f6cc8f 100644 --- a/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts +++ b/invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts @@ -19,7 +19,8 @@ import { } from 'features/nodes/store/workflowLibrarySlice'; import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice'; import { toast } from 'features/toast/toast'; -import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; +import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice'; import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog'; import { atom } from 'nanostores'; import { useCallback, useEffect } from 'react'; @@ -122,17 +123,17 @@ export const useStudioInitAction = (action?: StudioInitAction) => { ); const handleLoadWorkflow = useCallback( - async (workflowId: string) => { + (workflowId: string) => { // This shows a toast - await loadWorkflowWithDialog({ + loadWorkflowWithDialog({ type: 'library', data: workflowId, onSuccess: () => { - store.dispatch(setActiveTab('workflows')); + navigationApi.switchToTab('workflows'); }, }); }, - [loadWorkflowWithDialog, store] + [loadWorkflowWithDialog] ); const handleSelectStylePreset = useCallback( @@ -146,7 +147,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { return; } store.dispatch(activeStylePresetIdChanged(stylePresetId)); - store.dispatch(setActiveTab('canvas')); + navigationApi.switchToTab('canvas'); toast({ title: t('toast.stylePresetLoaded'), status: 'info', @@ -169,20 +170,20 @@ export const useStudioInitAction = (action?: StudioInitAction) => { break; case 'workflows': // Go to the workflows tab - store.dispatch(setActiveTab('workflows')); + navigationApi.switchToTab('workflows'); break; case 'upscaling': // Go to the upscaling tab - store.dispatch(setActiveTab('upscaling')); + navigationApi.switchToTab('upscaling'); break; case 'viewAllWorkflows': // Go to the workflows tab and open the workflow library modal - store.dispatch(setActiveTab('workflows')); + navigationApi.switchToTab('workflows'); $isWorkflowLibraryModalOpen.set(true); break; case 'viewAllWorkflowsRecommended': // Go to the workflows tab and open the workflow library modal with the recommended workflows view - store.dispatch(setActiveTab('workflows')); + navigationApi.switchToTab('workflows'); $isWorkflowLibraryModalOpen.set(true); store.dispatch(workflowLibraryViewChanged('defaults')); store.dispatch(workflowLibraryTagsReset()); @@ -194,7 +195,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => { break; case 'viewAllStylePresets': // Go to the canvas tab and open the style presets menu - store.dispatch(setActiveTab('canvas')); + navigationApi.switchToTab('canvas'); $isStylePresetsMenuOpen.set(true); break; } diff --git a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts index a4aa04fc07..ec8846d26b 100644 --- a/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts +++ b/invokeai/frontend/web/src/common/hooks/useGlobalHotkeys.ts @@ -6,7 +6,7 @@ import { useDeleteCurrentQueueItem } from 'features/queue/hooks/useDeleteCurrent import { useInvoke } from 'features/queue/hooks/useInvoke'; import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { getFocusedRegion } from './focus'; @@ -69,7 +69,7 @@ export const useGlobalHotkeys = () => { id: 'selectGenerateTab', category: 'app', callback: () => { - dispatch(setActiveTab('generate')); + navigationApi.switchToTab('generate'); }, dependencies: [dispatch], }); @@ -78,7 +78,7 @@ export const useGlobalHotkeys = () => { id: 'selectCanvasTab', category: 'app', callback: () => { - dispatch(setActiveTab('canvas')); + navigationApi.switchToTab('canvas'); }, dependencies: [dispatch], }); @@ -87,7 +87,7 @@ export const useGlobalHotkeys = () => { id: 'selectUpscalingTab', category: 'app', callback: () => { - dispatch(setActiveTab('upscaling')); + navigationApi.switchToTab('upscaling'); }, dependencies: [dispatch], }); @@ -96,7 +96,7 @@ export const useGlobalHotkeys = () => { id: 'selectWorkflowsTab', category: 'app', callback: () => { - dispatch(setActiveTab('workflows')); + navigationApi.switchToTab('workflows'); }, dependencies: [dispatch], }); @@ -105,7 +105,7 @@ export const useGlobalHotkeys = () => { id: 'selectModelsTab', category: 'app', callback: () => { - dispatch(setActiveTab('models')); + navigationApi.switchToTab('models'); }, options: { enabled: isModelManagerEnabled, @@ -117,7 +117,7 @@ export const useGlobalHotkeys = () => { id: 'selectQueueTab', category: 'app', callback: () => { - dispatch(setActiveTab('queue')); + navigationApi.switchToTab('queue'); }, dependencies: [dispatch, isModelManagerEnabled], }); diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel.tsx index f9dba37298..d9147814e8 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel.tsx @@ -1,17 +1,15 @@ import { Alert, Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import { InitialStateMainModelPicker } from 'features/controlLayers/components/SimpleSession/InitialStateMainModelPicker'; import { LaunchpadAddStyleReference } from 'features/controlLayers/components/SimpleSession/LaunchpadAddStyleReference'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { memo, useCallback } from 'react'; import { LaunchpadGenerateFromTextButton } from './LaunchpadGenerateFromTextButton'; export const GenerateLaunchpadPanel = memo(() => { - const dispatch = useAppDispatch(); const newCanvasSession = useCallback(() => { - dispatch(setActiveTab('canvas')); - }, [dispatch]); + navigationApi.switchToTab('canvas'); + }, []); return ( diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx index a1a9d64e7f..604284dadc 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu.tsx @@ -20,8 +20,8 @@ export const ImageMenuItemNewCanvasFromImageSubMenu = memo(() => { const onClickNewCanvasWithRasterLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); await newCanvasFromImage({ imageDTO, withResize: false, type: 'raster_layer', dispatch, getState }); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -31,8 +31,8 @@ export const ImageMenuItemNewCanvasFromImageSubMenu = memo(() => { const onClickNewCanvasWithControlLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); await newCanvasFromImage({ imageDTO, withResize: false, type: 'control_layer', dispatch, getState }); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -42,8 +42,8 @@ export const ImageMenuItemNewCanvasFromImageSubMenu = memo(() => { const onClickNewCanvasWithRasterLayerFromImageWithResize = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); await newCanvasFromImage({ imageDTO, withResize: true, type: 'raster_layer', dispatch, getState }); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -53,8 +53,8 @@ export const ImageMenuItemNewCanvasFromImageSubMenu = memo(() => { const onClickNewCanvasWithControlLayerFromImageWithResize = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); await newCanvasFromImage({ imageDTO, withResize: true, type: 'control_layer', dispatch, getState }); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx index 22966a8273..23ee544173 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu.tsx @@ -20,11 +20,11 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { const imageDTO = useImageDTOContext(); const isBusy = useCanvasIsBusySafe(); - const onClickNewRasterLayerFromImage = useCallback(() => { + const onClickNewRasterLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); createNewCanvasEntityFromImage({ imageDTO, type: 'raster_layer', dispatch, getState }); dispatch(sentImageToCanvas()); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -32,11 +32,11 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { }); }, [imageDTO, store, t]); - const onClickNewControlLayerFromImage = useCallback(() => { + const onClickNewControlLayerFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); createNewCanvasEntityFromImage({ imageDTO, type: 'control_layer', dispatch, getState }); dispatch(sentImageToCanvas()); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -44,11 +44,11 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { }); }, [imageDTO, store, t]); - const onClickNewInpaintMaskFromImage = useCallback(() => { + const onClickNewInpaintMaskFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); createNewCanvasEntityFromImage({ imageDTO, type: 'inpaint_mask', dispatch, getState }); dispatch(sentImageToCanvas()); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -56,11 +56,11 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { }); }, [imageDTO, store, t]); - const onClickNewRegionalGuidanceFromImage = useCallback(() => { + const onClickNewRegionalGuidanceFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance', dispatch, getState }); dispatch(sentImageToCanvas()); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), @@ -68,11 +68,11 @@ export const ImageMenuItemNewLayerFromImageSubMenu = memo(() => { }); }, [imageDTO, store, t]); - const onClickNewRegionalReferenceImageFromImage = useCallback(() => { + const onClickNewRegionalReferenceImageFromImage = useCallback(async () => { const { dispatch, getState } = store; + await navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); createNewCanvasEntityFromImage({ imageDTO, type: 'regional_guidance_with_reference_image', dispatch, getState }); dispatch(sentImageToCanvas()); - navigationApi.focusPanel('canvas', WORKSPACE_PANEL_ID); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToCanvas'), diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemSendToUpscale.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemSendToUpscale.tsx index 13c2287bf5..9674b6c9c4 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemSendToUpscale.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemSendToUpscale.tsx @@ -3,7 +3,7 @@ import { useAppDispatch } from 'app/store/storeHooks'; import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext'; import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice'; import { toast } from 'features/toast/toast'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiShareFatBold } from 'react-icons/pi'; @@ -15,7 +15,7 @@ export const ImageMenuItemSendToUpscale = memo(() => { const handleSendToCanvas = useCallback(() => { dispatch(upscaleInitialImageChanged(imageDTO)); - dispatch(setActiveTab('upscaling')); + navigationApi.switchToTab('upscaling'); toast({ id: 'SENT_TO_CANVAS', title: t('toast.sentToUpscale'), diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index 23b69dff73..62248a1883 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,14 +1,14 @@ import type { ButtonProps } from '@invoke-ai/ui-library'; import { Alert, AlertDescription, AlertIcon, Button, Divider, Flex, Link, Spinner, Text } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppSelector } from 'app/store/storeHooks'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { InvokeLogoIcon } from 'common/components/InvokeLogoIcon'; import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages'; import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectIsLocal } from 'features/system/store/configSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { selectActiveTab } from 'features/ui/store/uiSelectors'; -import { setActiveTab } from 'features/ui/store/uiSlice'; import type { PropsWithChildren } from 'react'; import { memo, useCallback, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; @@ -129,17 +129,15 @@ const GettingStartedVideosCallout = () => { }; const StarterBundlesCallout = () => { - const dispatch = useAppDispatch(); - const handleClickDownloadStarterModels = useCallback(() => { - dispatch(setActiveTab('models')); + navigationApi.switchToTab('models'); setInstallModelsTabByName('starterModels'); - }, [dispatch]); + }, []); const handleClickImportModels = useCallback(() => { - dispatch(setActiveTab('models')); + navigationApi.switchToTab('models'); setInstallModelsTabByName('urlOrLocal'); - }, [dispatch]); + }, []); return ( diff --git a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx index 2287453f08..c3de0dd3fb 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/hooks/useStarterModelsToast.tsx @@ -1,8 +1,7 @@ import { Button, Text, useToast } from '@invoke-ai/ui-library'; -import { useAppDispatch } from 'app/store/storeHooks'; import { setInstallModelsTabByName } from 'features/modelManagerV2/store/installModelsStore'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMainModels } from 'services/api/hooks/modelsByType'; @@ -40,14 +39,13 @@ export const useStarterModelsToast = () => { const ToastDescription = () => { const { t } = useTranslation(); - const dispatch = useAppDispatch(); const toast = useToast(); const onClick = useCallback(() => { - dispatch(setActiveTab('models')); + navigationApi.switchToTab('models'); setInstallModelsTabByName('launchpad'); toast.close(TOAST_ID); - }, [dispatch, toast]); + }, [toast]); return ( diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowViewEditToggleButton.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowViewEditToggleButton.tsx index c6221072e1..9bb0411d7f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowViewEditToggleButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowViewEditToggleButton.tsx @@ -3,7 +3,6 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { selectWorkflowMode, workflowModeChanged } from 'features/nodes/store/workflowLibrarySlice'; import { navigationApi } from 'features/ui/layouts/navigation-api'; import { VIEWER_PANEL_ID, WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared'; -import { setActiveTab } from 'features/ui/store/uiSlice'; import type { MouseEventHandler } from 'react'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; @@ -18,7 +17,6 @@ export const WorkflowViewEditToggleButton = memo(() => { (e) => { e.stopPropagation(); // Navigate to workflows tab and focus the Workflow Editor panel - dispatch(setActiveTab('workflows')); dispatch(workflowModeChanged('edit')); // Focus the Workflow Editor panel navigationApi.focusPanel('workflows', WORKSPACE_PANEL_ID); @@ -30,7 +28,6 @@ export const WorkflowViewEditToggleButton = memo(() => { (e) => { e.stopPropagation(); // Navigate to workflows tab and focus the Image Viewer panel - dispatch(setActiveTab('workflows')); dispatch(workflowModeChanged('view')); // Focus the Image Viewer panel navigationApi.focusPanel('workflows', VIEWER_PANEL_ID); diff --git a/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx b/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx index 1a21a5045d..6d97b6bdd7 100644 --- a/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/MainModel/NavigateToModelManagerButton.tsx @@ -2,9 +2,9 @@ import type { IconButtonProps } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { $onClickGoToModelManager } from 'app/store/nanostores/onClickGoToModelManager'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppSelector } from 'app/store/storeHooks'; import { selectIsModelsTabDisabled } from 'features/system/store/configSlice'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { PiCubeBold } from 'react-icons/pi'; @@ -14,11 +14,10 @@ export const NavigateToModelManagerButton = memo((props: Omit { - dispatch(setActiveTab('models')); - }, [dispatch]); + navigationApi.switchToTab('models'); + }, []); if (isModelsTabDisabled && !onClickGoToModelManager) { return null; diff --git a/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx b/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx index d384f0b707..b921f9a1c8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ModelPicker.tsx @@ -16,7 +16,7 @@ import { useStore } from '@nanostores/react'; import { EMPTY_ARRAY } from 'app/store/constants'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { $onClickGoToModelManager } from 'app/store/nanostores/onClickGoToModelManager'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useAppSelector } from 'app/store/storeHooks'; import type { Group, PickerContextState } from 'common/components/Picker/Picker'; import { buildGroup, getRegex, isOption, Picker, usePickerContext } from 'common/components/Picker/Picker'; import { useDisclosure } from 'common/hooks/useBoolean'; @@ -31,7 +31,7 @@ import { NavigateToModelManagerButton } from 'features/parameters/components/Mai import { API_BASE_MODELS, MODEL_TYPE_MAP, MODEL_TYPE_SHORT_MAP } from 'features/parameters/types/constants'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectIsModelsTabDisabled } from 'features/system/store/configSlice'; -import { setActiveTab } from 'features/ui/store/uiSlice'; +import { navigationApi } from 'features/ui/layouts/navigation-api'; import { filesize } from 'filesize'; import { memo, useCallback, useMemo, useRef } from 'react'; import { Trans, useTranslation } from 'react-i18next'; @@ -72,11 +72,10 @@ const getOptionId = (modelConfig: WithStarred) => m const ModelManagerLink = memo((props: ButtonProps) => { const onClickGoToModelManager = useStore($onClickGoToModelManager); - const dispatch = useAppDispatch(); const onClick = useCallback(() => { - dispatch(setActiveTab('models')); + navigationApi.switchToTab('models'); setInstallModelsTabByName('launchpad'); - }, [dispatch]); + }, []); return (