feat(ui): restore floating options and gallery buttons

This commit is contained in:
psychedelicious 2024-01-04 19:32:33 +11:00 committed by Kent Keirsey
parent 8db14911d7
commit 2d922a0a65
8 changed files with 187 additions and 143 deletions

View File

@ -1,27 +1,23 @@
import type { ChakraProps } from '@chakra-ui/react';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaTimes } from 'react-icons/fa';
import QueueButton from './common/QueueButton';
type Props = {
asIconButton?: boolean;
sx?: ChakraProps['sx'];
};
const CancelCurrentQueueItemButton = ({ asIconButton, sx }: Props) => {
const CancelCurrentQueueItemIconButton = ({ sx }: Props) => {
const { t } = useTranslation();
const { cancelQueueItem, isLoading, isDisabled } =
useCancelCurrentQueueItem();
return (
<QueueButton
<InvIconButton
isDisabled={isDisabled}
isLoading={isLoading}
asIconButton={asIconButton}
label={t('queue.cancel')}
aria-label={t('queue.cancel')}
tooltip={t('queue.cancelTooltip')}
icon={<FaTimes />}
onClick={cancelQueueItem}
@ -31,4 +27,4 @@ const CancelCurrentQueueItemButton = ({ asIconButton, sx }: Props) => {
);
};
export default memo(CancelCurrentQueueItemButton);
export default memo(CancelCurrentQueueItemIconButton);

View File

@ -1,47 +1,34 @@
import { type ChakraProps, useDisclosure } from '@chakra-ui/react';
import { InvConfirmationAlertDialog } from 'common/components/InvConfirmationAlertDialog/InvConfirmationAlertDialog';
import { InvText } from 'common/components/InvText/wrapper';
import { useDisclosure } from '@chakra-ui/react';
import { InvButton } from 'common/components/InvButton/InvButton';
import type { InvButtonProps } from 'common/components/InvButton/types';
import ClearQueueConfirmationAlertDialog from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
import QueueButton from './common/QueueButton';
type Props = InvButtonProps;
type Props = {
asIconButton?: boolean;
sx?: ChakraProps['sx'];
};
const ClearQueueButton = ({ asIconButton, sx }: Props) => {
const ClearQueueButton = (props: Props) => {
const { t } = useTranslation();
const { isOpen, onClose, onOpen } = useDisclosure();
const { clearQueue, isLoading, isDisabled } = useClearQueue();
const disclosure = useDisclosure();
const { isLoading, isDisabled } = useClearQueue();
return (
<>
<QueueButton
<InvButton
isDisabled={isDisabled}
isLoading={isLoading}
asIconButton={asIconButton}
label={t('queue.clear')}
tooltip={t('queue.clearTooltip')}
icon={<FaTrash />}
leftIcon={<FaTrash />}
colorScheme="error"
sx={sx}
onClick={onOpen}
/>
<InvConfirmationAlertDialog
isOpen={isOpen}
onClose={onClose}
title={t('queue.clearTooltip')}
acceptCallback={clearQueue}
acceptButtonText={t('queue.clear')}
onClick={disclosure.onOpen}
data-testid={t('queue.clear')}
{...props}
>
<InvText>{t('queue.clearQueueAlertDialog')}</InvText>
<br />
<InvText>{t('queue.clearQueueAlertDialog2')}</InvText>
</InvConfirmationAlertDialog>
{t('queue.clear')}
</InvButton>
<ClearQueueConfirmationAlertDialog disclosure={disclosure} />
</>
);
};

View File

@ -0,0 +1,32 @@
import type {
UseDisclosureReturn} from '@chakra-ui/react';
import { InvConfirmationAlertDialog } from 'common/components/InvConfirmationAlertDialog/InvConfirmationAlertDialog';
import { InvText } from 'common/components/InvText/wrapper';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
disclosure: UseDisclosureReturn;
};
const ClearQueueButton = ({ disclosure }: Props) => {
const { t } = useTranslation();
const { clearQueue } = useClearQueue();
return (
<InvConfirmationAlertDialog
isOpen={disclosure.isOpen}
onClose={disclosure.onClose}
title={t('queue.clearTooltip')}
acceptCallback={clearQueue}
acceptButtonText={t('queue.clear')}
>
<InvText>{t('queue.clearQueueAlertDialog')}</InvText>
<br />
<InvText>{t('queue.clearQueueAlertDialog2')}</InvText>
</InvConfirmationAlertDialog>
);
};
export default memo(ClearQueueButton);

View File

@ -0,0 +1,35 @@
import { useDisclosure } from '@chakra-ui/react';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import type { InvIconButtonProps } from 'common/components/InvIconButton/types';
import ClearQueueConfirmationAlertDialog from 'features/queue/components/ClearQueueConfirmationAlertDialog';
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
type Props = Omit<InvIconButtonProps, 'aria-label'>;
const ClearQueueIconButton = (props: Props) => {
const { t } = useTranslation();
const disclosure = useDisclosure();
const { isLoading, isDisabled } = useClearQueue();
return (
<>
<InvIconButton
isDisabled={isDisabled}
isLoading={isLoading}
aria-label={t('queue.clear')}
tooltip={t('queue.clearTooltip')}
icon={<FaTrash />}
colorScheme="error"
onClick={disclosure.onOpen}
data-testid={t('queue.clear')}
{...props}
/>
<ClearQueueConfirmationAlertDialog disclosure={disclosure} />
</>
);
};
export default memo(ClearQueueIconButton);

View File

@ -1,6 +1,6 @@
import { Flex, Spacer } from '@chakra-ui/react';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import ClearQueueButton from 'features/queue/components/ClearQueueButton';
import ClearQueueIconButton from 'features/queue/components/ClearQueueIconButton';
import QueueFrontButton from 'features/queue/components/QueueFrontButton';
import ProgressBar from 'features/system/components/ProgressBar';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
@ -28,7 +28,7 @@ const QueueControls = () => {
{/* <CancelCurrentQueueItemButton asIconButton />
{isResumeEnabled && <ResumeProcessorButton asIconButton />}
{isPauseEnabled && <PauseProcessorButton asIconButton />} */}
<ClearQueueButton asIconButton />
<ClearQueueIconButton />
</InvButtonGroup>
<ProgressBar />
</Flex>

View File

@ -1,22 +1,20 @@
import { Flex } from '@chakra-ui/layout';
import { Portal } from '@chakra-ui/portal';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MdPhotoLibrary } from 'react-icons/md';
type Props = {
isGalleryCollapsed: boolean;
expandGallery: () => void;
panelApi: UsePanelReturn;
};
const FloatingGalleryButton = ({
isGalleryCollapsed,
expandGallery,
}: Props) => {
const FloatingGalleryButton = (props: Props) => {
const { t } = useTranslation();
if (!isGalleryCollapsed) {
if (!props.panelApi.isCollapsed) {
return null;
}
@ -27,18 +25,21 @@ const FloatingGalleryButton = ({
transform="translate(0, -50%)"
minW={8}
top="50%"
insetInlineEnd="1.63rem"
insetInlineEnd={0}
>
<InvIconButton
tooltip="Show Gallery (G)"
aria-label={t('accessibility.showGalleryPanel')}
onClick={expandGallery}
icon={<MdPhotoLibrary />}
p={0}
px={3}
h={48}
borderEndRadius={0}
/>
<InvTooltip
label={t('accessibility.showGalleryPanel')}
placement="start"
>
<InvIconButton
aria-label={t('accessibility.showGalleryPanel')}
onClick={props.panelApi.expand}
icon={<MdPhotoLibrary />}
p={0}
h={48}
borderEndRadius={0}
/>
</InvTooltip>
</Flex>
</Portal>
);

View File

@ -1,40 +1,45 @@
import type { ChakraProps } from '@chakra-ui/react';
import { SpinnerIcon } from '@chakra-ui/icons';
import type { SystemStyleObject } from '@chakra-ui/react';
import { Flex, Portal } from '@chakra-ui/react';
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import CancelCurrentQueueItemButton from 'features/queue/components/CancelCurrentQueueItemButton';
import ClearQueueButton from 'features/queue/components/ClearQueueButton';
import CancelCurrentQueueItemIconButton from 'features/queue/components/CancelCurrentQueueItemIconButton';
import ClearQueueIconButton from 'features/queue/components/ClearQueueIconButton';
import { QueueButtonTooltip } from 'features/queue/components/QueueButtonTooltip';
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import type { RefObject } from 'react';
import { memo, useCallback } from 'react';
import type { UsePanelReturn } from 'features/ui/hooks/usePanel';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaSlidersH } from 'react-icons/fa';
import { IoSparkles } from 'react-icons/io5';
import type { ImperativePanelHandle } from 'react-resizable-panels';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
import { spinAnimationSlow } from 'theme/animations';
const floatingButtonStyles: ChakraProps['sx'] = {
const floatingButtonStyles: SystemStyleObject = {
borderStartRadius: 0,
flexGrow: 1,
};
type Props = {
isSidePanelCollapsed: boolean;
sidePanelRef: RefObject<ImperativePanelHandle>;
panelApi: UsePanelReturn;
};
const FloatingSidePanelButtons = ({
isSidePanelCollapsed,
sidePanelRef,
}: Props) => {
const FloatingSidePanelButtons = (props: Props) => {
const { t } = useTranslation();
const { queueBack, isLoading, isDisabled } = useQueueBack();
const { data: queueStatus } = useGetQueueStatusQuery();
const handleShowSidePanel = useCallback(() => {
sidePanelRef.current?.expand();
}, [sidePanelRef]);
const queueButtonIcon = useMemo(
() =>
queueStatus?.processor.is_processing ? (
<SpinnerIcon animation={spinAnimationSlow} />
) : (
<IoSparkles />
),
[queueStatus?.processor.is_processing]
);
if (!isSidePanelCollapsed) {
if (!props.panelApi.isCollapsed) {
return null;
}
@ -45,7 +50,7 @@ const FloatingSidePanelButtons = ({
transform="translate(0, -50%)"
minW={8}
top="50%"
insetInlineStart="5.13rem"
insetInlineStart="54px"
direction="column"
gap={2}
h={48}
@ -54,29 +59,23 @@ const FloatingSidePanelButtons = ({
<InvIconButton
tooltip={t('parameters.showOptionsPanel')}
aria-label={t('parameters.showOptionsPanel')}
onClick={handleShowSidePanel}
onClick={props.panelApi.expand}
sx={floatingButtonStyles}
icon={<FaSlidersH />}
/>
<InvIconButton
aria-label={t('queue.queueBack')}
pos="absolute"
insetInlineStart={0}
onClick={queueBack}
isLoading={isLoading}
isDisabled={isDisabled}
icon={<IoSparkles />}
variant="solid"
colorScheme="yellow"
icon={queueButtonIcon}
colorScheme="invokeYellow"
tooltip={<QueueButtonTooltip />}
sx={floatingButtonStyles}
/>
<CancelCurrentQueueItemButton
asIconButton
sx={floatingButtonStyles}
/>
<CancelCurrentQueueItemIconButton sx={floatingButtonStyles} />
</InvButtonGroup>
<ClearQueueButton asIconButton sx={floatingButtonStyles} />
<ClearQueueIconButton sx={floatingButtonStyles} />
</Flex>
</Portal>
);

View File

@ -16,6 +16,8 @@ import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditor
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu';
import StatusIndicator from 'features/system/components/StatusIndicator';
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
import { usePanel } from 'features/ui/hooks/usePanel';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
@ -99,8 +101,8 @@ const enabledTabsSelector = createMemoizedSelector(
}
);
export const NO_GALLERY_TABS: InvokeTabName[] = ['modelManager', 'queue'];
export const NO_SIDE_PANEL_TABS: InvokeTabName[] = ['modelManager', 'queue'];
export const NO_GALLERY_PANEL_TABS: InvokeTabName[] = ['modelManager', 'queue'];
export const NO_OPTIONS_PANEL_TABS: InvokeTabName[] = ['modelManager', 'queue'];
const panelStyles: CSSProperties = { height: '100%', width: '100%' };
const GALLERY_MIN_SIZE_PX = 310;
const GALLERY_MIN_SIZE_PCT = 20;
@ -121,6 +123,14 @@ const InvokeTabs = () => {
e.target.blur();
}
}, []);
const shouldShowOptionsPanel = useMemo(
() => !NO_OPTIONS_PANEL_TABS.includes(activeTabName),
[activeTabName]
);
const shouldShowGalleryPanel = useMemo(
() => !NO_GALLERY_PANEL_TABS.includes(activeTabName),
[activeTabName]
);
const tabs = useMemo(
() =>
@ -185,60 +195,38 @@ const InvokeTabs = () => {
const panelStorage = usePanelStorage();
const {
ref: optionsPanelRef,
minSize: optionsPanelMinSize,
isCollapsed: isOptionsPanelCollapsed,
onCollapse: onCollapseOptionsPanel,
onExpand: onExpandOptionsPanel,
reset: resetOptionsPanel,
expand: expandOptionsPanel,
collapse: collapseOptionsPanel,
toggle: toggleOptionsPanel,
onDoubleClickHandle: onDoubleClickOptionsPanelHandle,
} = usePanel(optionsPanelUsePanelOptions);
const optionsPanel = usePanel(optionsPanelUsePanelOptions);
const {
ref: galleryPanelRef,
minSize: galleryPanelMinSize,
isCollapsed: isGalleryPanelCollapsed,
onCollapse: onCollapseGalleryPanel,
onExpand: onExpandGalleryPanel,
reset: resetGalleryPanel,
expand: expandGalleryPanel,
collapse: collapseGalleryPanel,
toggle: toggleGalleryPanel,
onDoubleClickHandle: onDoubleClickGalleryPanelHandle,
} = usePanel(galleryPanelUsePanelOptions);
const galleryPanel = usePanel(galleryPanelUsePanelOptions);
useHotkeys('g', toggleGalleryPanel, [toggleGalleryPanel]);
useHotkeys(['t', 'o'], toggleOptionsPanel, [toggleOptionsPanel]);
useHotkeys('g', galleryPanel.toggle, [galleryPanel.toggle]);
useHotkeys(['t', 'o'], optionsPanel.toggle, [optionsPanel.toggle]);
useHotkeys(
'shift+r',
() => {
resetOptionsPanel();
resetGalleryPanel();
optionsPanel.reset();
galleryPanel.reset();
},
[resetOptionsPanel, resetGalleryPanel]
[optionsPanel.reset, galleryPanel.reset]
);
useHotkeys(
'f',
() => {
if (isOptionsPanelCollapsed || isGalleryPanelCollapsed) {
expandOptionsPanel();
expandGalleryPanel();
if (optionsPanel.isCollapsed || galleryPanel.isCollapsed) {
optionsPanel.expand();
galleryPanel.expand();
} else {
collapseOptionsPanel();
collapseGalleryPanel();
optionsPanel.collapse();
galleryPanel.collapse();
}
},
[
isOptionsPanelCollapsed,
isGalleryPanelCollapsed,
expandOptionsPanel,
expandGalleryPanel,
collapseOptionsPanel,
collapseGalleryPanel,
optionsPanel.isCollapsed,
galleryPanel.isCollapsed,
optionsPanel.expand,
galleryPanel.expand,
optionsPanel.collapse,
galleryPanel.collapse,
]
);
@ -272,16 +260,16 @@ const InvokeTabs = () => {
style={panelStyles}
storage={panelStorage}
>
{!NO_SIDE_PANEL_TABS.includes(activeTabName) && (
{shouldShowOptionsPanel && (
<>
<Panel
id="options-panel"
ref={optionsPanelRef}
ref={optionsPanel.ref}
order={0}
defaultSize={optionsPanelMinSize}
minSize={optionsPanelMinSize}
onCollapse={onCollapseOptionsPanel}
onExpand={onExpandOptionsPanel}
defaultSize={optionsPanel.minSize}
minSize={optionsPanel.minSize}
onCollapse={optionsPanel.onCollapse}
onExpand={optionsPanel.onExpand}
collapsible
>
{activeTabName === 'nodes' ? (
@ -292,7 +280,7 @@ const InvokeTabs = () => {
</Panel>
<ResizeHandle
id="options-main-handle"
onDoubleClick={onDoubleClickOptionsPanelHandle}
onDoubleClick={optionsPanel.onDoubleClickHandle}
orientation="vertical"
/>
</>
@ -302,21 +290,21 @@ const InvokeTabs = () => {
{tabPanels}
</InvTabPanels>
</Panel>
{!NO_GALLERY_TABS.includes(activeTabName) && (
{shouldShowGalleryPanel && (
<>
<ResizeHandle
id="main-gallery-handle"
orientation="vertical"
onDoubleClick={onDoubleClickGalleryPanelHandle}
onDoubleClick={galleryPanel.onDoubleClickHandle}
/>
<Panel
id="gallery-panel"
ref={galleryPanelRef}
ref={galleryPanel.ref}
order={2}
defaultSize={galleryPanelMinSize}
minSize={galleryPanelMinSize}
onCollapse={onCollapseGalleryPanel}
onExpand={onExpandGalleryPanel}
defaultSize={galleryPanel.minSize}
minSize={galleryPanel.minSize}
onCollapse={galleryPanel.onCollapse}
onExpand={galleryPanel.onExpand}
collapsible
>
<ImageGalleryContent />
@ -324,6 +312,12 @@ const InvokeTabs = () => {
</>
)}
</PanelGroup>
{shouldShowOptionsPanel && (
<FloatingParametersPanelButtons panelApi={optionsPanel} />
)}
{shouldShowGalleryPanel && (
<FloatingGalleryButton panelApi={galleryPanel} />
)}
</InvTabs>
);
};