mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): clearer viewer/editor context switching
This commit is contained in:
parent
fdfc379a84
commit
d9b92d19f9
@ -88,6 +88,7 @@
|
|||||||
"negativePrompt": "Negative Prompt",
|
"negativePrompt": "Negative Prompt",
|
||||||
"discordLabel": "Discord",
|
"discordLabel": "Discord",
|
||||||
"dontAskMeAgain": "Don't ask me again",
|
"dontAskMeAgain": "Don't ask me again",
|
||||||
|
"editor": "Editor",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"file": "File",
|
"file": "File",
|
||||||
"folder": "Folder",
|
"folder": "Folder",
|
||||||
@ -364,7 +365,7 @@
|
|||||||
"bulkDownloadFailed": "Download Failed",
|
"bulkDownloadFailed": "Download Failed",
|
||||||
"problemDeletingImages": "Problem Deleting Images",
|
"problemDeletingImages": "Problem Deleting Images",
|
||||||
"problemDeletingImagesDesc": "One or more images could not be deleted",
|
"problemDeletingImagesDesc": "One or more images could not be deleted",
|
||||||
"backToEditor": "Back to {{tab}} (Esc)"
|
"switchTo": "Switch to {{ tab }} (Z)"
|
||||||
},
|
},
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
"searchHotkeys": "Search Hotkeys",
|
"searchHotkeys": "Search Hotkeys",
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||||
|
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -219,7 +220,11 @@ const IAICanvasToolbar = () => {
|
|||||||
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
<Flex w="full" gap={2} alignItems="center">
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineEnd="auto" />
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} gap={2} justifyContent="center">
|
||||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||||
<FormControl isDisabled={isStaging} w="5rem">
|
<FormControl isDisabled={isStaging} w="5rem">
|
||||||
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||||
@ -311,6 +316,12 @@ const IAICanvasToolbar = () => {
|
|||||||
<IAICanvasSettingsButtonPopover />
|
<IAICanvasSettingsButtonPopover />
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<ViewerButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,16 +4,27 @@ import { BrushSize } from 'features/controlLayers/components/BrushSize';
|
|||||||
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
|
||||||
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
|
||||||
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
|
||||||
|
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const ControlLayersToolbar = memo(() => {
|
export const ControlLayersToolbar = memo(() => {
|
||||||
return (
|
return (
|
||||||
<Flex gap={4}>
|
<Flex w="full" gap={2}>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineEnd="auto" />
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={1} gap={2} justifyContent="center">
|
||||||
<BrushSize />
|
<BrushSize />
|
||||||
<ToolChooser />
|
<ToolChooser />
|
||||||
<UndoRedoButtonGroup />
|
<UndoRedoButtonGroup />
|
||||||
<ControlLayersSettingsPopover />
|
<ControlLayersSettingsPopover />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex flex={1} justifyContent="center">
|
||||||
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
|
<ViewerButton />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { Button } from '@invoke-ai/ui-library';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { PiArrowLeftBold } from 'react-icons/pi';
|
|
||||||
|
|
||||||
import { TAB_NAME_TO_TKEY, useImageViewer } from './useImageViewer';
|
|
||||||
|
|
||||||
export const BackToEditorButton = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { onClose } = useImageViewer();
|
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
|
||||||
const tooltip = useMemo(
|
|
||||||
() => t('gallery.backToEditor', { tab: t(TAB_NAME_TO_TKEY[activeTabName]) }),
|
|
||||||
[t, activeTabName]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button aria-label={tooltip} tooltip={tooltip} onClick={onClose} leftIcon={<PiArrowLeftBold />} variant="ghost">
|
|
||||||
{t('common.back')}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useImageViewer } from './useImageViewer';
|
||||||
|
|
||||||
|
export const TAB_NAME_TO_TKEY: Record<InvokeTabName, string> = {
|
||||||
|
generation: 'ui.tabs.generationTab',
|
||||||
|
canvas: 'ui.tabs.canvasTab',
|
||||||
|
workflows: 'ui.tabs.workflowsTab',
|
||||||
|
models: 'ui.tabs.modelsTab',
|
||||||
|
queue: 'ui.tabs.queueTab',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TAB_NAME_TO_TKEY_SHORT: Record<InvokeTabName, string> = {
|
||||||
|
generation: 'ui.tabs.generation',
|
||||||
|
canvas: 'ui.tabs.canvas',
|
||||||
|
workflows: 'ui.tabs.workflows',
|
||||||
|
models: 'ui.tabs.models',
|
||||||
|
queue: 'ui.tabs.queue',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditorButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { onClose } = useImageViewer();
|
||||||
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
|
const tooltip = useMemo(() => t('gallery.switchTo', { tab: t(TAB_NAME_TO_TKEY[activeTabName]) }), [t, activeTabName]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button aria-label={tooltip} tooltip={tooltip} onClick={onClose} variant="ghost">
|
||||||
|
{t(TAB_NAME_TO_TKEY_SHORT[activeTabName])}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
@ -10,9 +10,9 @@ import { AnimatePresence, motion } from 'framer-motion';
|
|||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import { BackToEditorButton } from './BackToEditorButton';
|
|
||||||
import CurrentImageButtons from './CurrentImageButtons';
|
import CurrentImageButtons from './CurrentImageButtons';
|
||||||
import CurrentImagePreview from './CurrentImagePreview';
|
import CurrentImagePreview from './CurrentImagePreview';
|
||||||
|
import { EditorButton } from './EditorButton';
|
||||||
|
|
||||||
const initial: AnimationProps['initial'] = {
|
const initial: AnimationProps['initial'] = {
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
@ -39,13 +39,14 @@ export const ImageViewer = memo(() => {
|
|||||||
return isOpen;
|
return isOpen;
|
||||||
}, [isOpen, isViewerEnabled]);
|
}, [isOpen, isViewerEnabled]);
|
||||||
|
|
||||||
useHotkeys('shift+s', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]);
|
useHotkeys('z', onToggle, { enabled: isViewerEnabled }, [isViewerEnabled, onToggle]);
|
||||||
useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]);
|
useHotkeys('esc', onClose, { enabled: isViewerEnabled }, [isViewerEnabled, onClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{shouldShowViewer && (
|
{shouldShowViewer && (
|
||||||
<Flex
|
<Flex
|
||||||
|
key="imageViewer"
|
||||||
as={motion.div}
|
as={motion.div}
|
||||||
initial={initial}
|
initial={initial}
|
||||||
animate={animate}
|
animate={animate}
|
||||||
@ -76,7 +77,7 @@ export const ImageViewer = memo(() => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Flex flex={1} justifyContent="center">
|
<Flex flex={1} justifyContent="center">
|
||||||
<Flex gap={2} marginInlineStart="auto">
|
<Flex gap={2} marginInlineStart="auto">
|
||||||
<BackToEditorButton />
|
<EditorButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Button } from '@invoke-ai/ui-library';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useImageViewer } from './useImageViewer';
|
||||||
|
|
||||||
|
export const ViewerButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { onOpen } = useImageViewer();
|
||||||
|
const tooltip = useMemo(() => t('gallery.switchTo', { tab: t('common.viewer') }), [t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button aria-label={tooltip} tooltip={tooltip} onClick={onOpen} variant="ghost" pointerEvents="auto">
|
||||||
|
{t('common.viewer')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
@ -1,16 +1,7 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export const TAB_NAME_TO_TKEY: Record<InvokeTabName, string> = {
|
|
||||||
generation: 'ui.tabs.generation',
|
|
||||||
canvas: 'ui.tabs.canvas',
|
|
||||||
workflows: 'ui.tabs.workflows',
|
|
||||||
models: 'ui.tabs.models',
|
|
||||||
queue: 'ui.tabs.queue',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useImageViewer = () => {
|
export const useImageViewer = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isOpen = useAppSelector((s) => s.gallery.isImageViewerOpen);
|
const isOpen = useAppSelector((s) => s.gallery.isImageViewerOpen);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
|
||||||
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
|
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
|
||||||
import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton';
|
import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton';
|
||||||
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
|
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
|
||||||
@ -22,6 +23,7 @@ const TopCenterPanel = () => {
|
|||||||
<ClearFlowButton />
|
<ClearFlowButton />
|
||||||
<SaveWorkflowButton />
|
<SaveWorkflowButton />
|
||||||
<WorkflowLibraryMenu />
|
<WorkflowLibraryMenu />
|
||||||
|
<ViewerButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user