feat(ui): statusindicator changes

We are now using the lefthand vertical strip for the settings menu button. This is a good place for the status indicator.

Really, we only need to display something *if there is a problem*. If the app is processing, the progress bar indicates that.

For the case where the panels are collapsed, I'll add the floating buttons back in some form, and we'll indicate via those if the app is processing something.
This commit is contained in:
psychedelicious 2024-01-04 18:25:12 +11:00 committed by Kent Keirsey
parent d3b6d86e74
commit 7a57bc99cf
10 changed files with 97 additions and 132 deletions

View File

@ -204,7 +204,7 @@ export const buttonTheme = defineStyleConfig({
_hover: { _hover: {
bg: 'none', bg: 'none',
svg: { svg: {
fill: 'base.500', fill: 'base.400',
}, },
}, },
'&[data-selected="true"]': { '&[data-selected="true"]': {

View File

@ -1,35 +1,57 @@
import { progressAnatomy as parts } from '@chakra-ui/anatomy'; import { progressAnatomy as parts } from '@chakra-ui/anatomy';
import { import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system';
createMultiStyleConfigHelpers, import { generateStripe, getColorVar } from '@chakra-ui/theme-tools';
defineStyle,
} from '@chakra-ui/styled-system';
const { defineMultiStyleConfig, definePartsStyle } = const { defineMultiStyleConfig, definePartsStyle } =
createMultiStyleConfigHelpers(parts.keys); createMultiStyleConfigHelpers(parts.keys);
const invokeAIFilledTrack = defineStyle((_props) => ({
bg: 'blue.500',
}));
const invokeAITrack = defineStyle((_props) => {
return {
bg: 'base.800',
};
});
const invokeAI = definePartsStyle((props) => ({
filledTrack: invokeAIFilledTrack(props),
track: invokeAITrack(props),
}));
export const progressTheme = defineMultiStyleConfig({ export const progressTheme = defineMultiStyleConfig({
baseStyle: { baseStyle: definePartsStyle(
track: { borderRadius: '2px' }, ({ theme: t, colorScheme: c, hasStripe, isIndeterminate }) => {
}, const bgColor = `${c}.300`;
variants: { const addStripe = !isIndeterminate && hasStripe;
invokeAI, const gradient = `linear-gradient(
}, to right,
defaultProps: { transparent 0%,
variant: 'invokeAI', ${getColorVar(t, bgColor)} 50%,
}, transparent 100%
)`;
return {
track: {
borderRadius: '2px',
bg: 'base.800',
},
filledTrack: {
borderRadius: '2px',
...(addStripe && generateStripe()),
...(isIndeterminate ? { bgImage: gradient } : { bgColor }),
},
};
}
),
}); });
// export const progressTheme = defineMultiStyleConfig({
// baseStyle: definePartsStyle(
// ({ theme: t, colorScheme: c, hasStripe, isIndeterminate }) => {
// const bgColor = `${c}.500`;
// const addStripe = !isIndeterminate && hasStripe;
// const gradient = `linear-gradient(
// to right,
// transparent 0%,
// ${getColorVar(t, bgColor)} 50%,
// transparent 100%
// )`;
// return {
// track: {
// borderRadius: '2px',
// bg: 'base.800',
// },
// filledTrack: {
// borderRadius: '2px',
// ...(addStripe && generateStripe("xs")),
// ...(isIndeterminate ? { bgImage: gradient } : { bgColor }),
// },
// };
// }
// ),
// });

View File

@ -12,6 +12,7 @@ export const InvTooltip = memo(
ref={ref} ref={ref}
hasArrow={hasArrow} hasArrow={hasArrow}
placement={placement} placement={placement}
arrowSize={8}
{...rest} {...rest}
> >
{children} {children}

View File

@ -3,14 +3,15 @@ import { cssVar } from '@chakra-ui/theme-tools';
const $arrowBg = cssVar('popper-arrow-bg'); const $arrowBg = cssVar('popper-arrow-bg');
// define the base component styles
const baseStyle = defineStyle(() => ({ const baseStyle = defineStyle(() => ({
borderRadius: 'md', borderRadius: 'md',
shadow: 'dark-lg', shadow: 'dark-lg',
bg: 'base.200', bg: 'base.200',
color: 'base.800', color: 'base.800',
[$arrowBg.variable]: 'colors.base.200', [$arrowBg.variable]: 'colors.base.200',
pb: 1.5, pt: 1,
px: 2,
pb: 1,
})); }));
// export the component theme // export the component theme

View File

@ -28,6 +28,7 @@ const selector = createMemoizedSelector([stateSelector], (state) => {
const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = const { initial, min, sliderMax, inputMax, fineStep, coarseStep } =
state.config.sd.iterations; state.config.sd.iterations;
const { iterations } = state.generation; const { iterations } = state.generation;
const isLoadingDynamicPrompts = state.dynamicPrompts.isLoading;
return { return {
iterations, iterations,
@ -37,15 +38,14 @@ const selector = createMemoizedSelector([stateSelector], (state) => {
inputMax, inputMax,
step: coarseStep, step: coarseStep,
fineStep, fineStep,
isLoadingDynamicPrompts,
}; };
}); });
export const InvokeQueueBackButton = memo(() => { export const InvokeQueueBackButton = memo(() => {
const isLoadingDynamicPrompts = useAppSelector(
(state) => state.dynamicPrompts.isLoading
);
const { queueBack, isLoading, isDisabled } = useQueueBack(); const { queueBack, isLoading, isDisabled } = useQueueBack();
const { iterations, step, fineStep } = useAppSelector(selector); const { iterations, step, fineStep, isLoadingDynamicPrompts } =
useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => { (v: number) => {

View File

@ -3,7 +3,6 @@ import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup'
import ClearQueueButton from 'features/queue/components/ClearQueueButton'; import ClearQueueButton from 'features/queue/components/ClearQueueButton';
import QueueFrontButton from 'features/queue/components/QueueFrontButton'; import QueueFrontButton from 'features/queue/components/QueueFrontButton';
import ProgressBar from 'features/system/components/ProgressBar'; import ProgressBar from 'features/system/components/ProgressBar';
import StatusIndicator from 'features/system/components/StatusIndicator';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react'; import { memo } from 'react';
@ -31,10 +30,7 @@ const QueueControls = () => {
{isPauseEnabled && <PauseProcessorButton asIconButton />} */} {isPauseEnabled && <PauseProcessorButton asIconButton />} */}
<ClearQueueButton asIconButton /> <ClearQueueButton asIconButton />
</InvButtonGroup> </InvButtonGroup>
<Flex h={5} w="full" alignItems="center"> <ProgressBar />
<StatusIndicator />
<ProgressBar height={2} />
</Flex>
</Flex> </Flex>
); );
}; };

View File

@ -16,12 +16,7 @@ const progressBarSelector = createMemoizedSelector(
} }
); );
type ProgressBarProps = { const ProgressBar = () => {
height?: number | string;
};
const ProgressBar = (props: ProgressBarProps) => {
const { height = 'full' } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const { data: queueStatus } = useGetQueueStatusQuery(); const { data: queueStatus } = useGetQueueStatusQuery();
const { hasSteps, value, isConnected } = useAppSelector(progressBarSelector); const { hasSteps, value, isConnected } = useAppSelector(progressBarSelector);
@ -30,13 +25,12 @@ const ProgressBar = (props: ProgressBarProps) => {
<Progress <Progress
value={value} value={value}
aria-label={t('accessibility.invokeProgressBar')} aria-label={t('accessibility.invokeProgressBar')}
hasStripe
isIndeterminate={ isIndeterminate={
isConnected && Boolean(queueStatus?.queue.in_progress) && !hasSteps isConnected && Boolean(queueStatus?.queue.in_progress) && !hasSteps
} }
h={height} h={2}
w="full" w="full"
colorScheme="invokeYellow" colorScheme="blue"
/> />
); );
}; };

View File

@ -1,81 +1,28 @@
import { Flex, Icon } from '@chakra-ui/react'; import { Icon } from '@chakra-ui/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InvText } from 'common/components/InvText/wrapper';
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip'; import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
import { STATUS_TRANSLATION_KEYS } from 'features/system/store/types'; import { memo } from 'react';
import type { ResourceKey } from 'i18next';
import { memo, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaCircle } from 'react-icons/fa'; import { FaExclamationTriangle } from 'react-icons/fa';
import { useHoverDirty } from 'react-use';
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
const statusIndicatorSelector = createMemoizedSelector(
stateSelector,
({ system }) => {
const { isConnected, status } = system;
return {
isConnected,
statusTranslationKey: STATUS_TRANSLATION_KEYS[status],
};
}
);
const COLOR_MAP = {
ok: 'invokeYellow.500',
working: 'blue.500',
error: 'red.500',
};
const StatusIndicator = () => { const StatusIndicator = () => {
const { isConnected, statusTranslationKey } = useAppSelector( const isConnected = useAppSelector((state) => state.system.isConnected);
statusIndicatorSelector
);
const { t } = useTranslation(); const { t } = useTranslation();
const ref = useRef(null);
const { data: queueStatus } = useGetQueueStatusQuery();
const statusColor = useMemo(() => { if (!isConnected) {
if (!isConnected) { return (
return 'error';
}
if (queueStatus?.queue.in_progress) {
return 'working';
}
return 'ok';
}, [queueStatus?.queue.in_progress, isConnected]);
const isHovered = useHoverDirty(ref);
return (
<Flex ref={ref} alignItems="center" p={2} pl={0}>
<InvTooltip <InvTooltip
left={10} label={t('common.statusDisconnected')}
bottom={-24} placement="end"
background="base.800" shouldWrapChildren
label={ gutter={10}
isHovered && (
<InvText
fontSize="sm"
fontWeight="semibold"
pb="1px"
userSelect="none"
color={COLOR_MAP[statusColor]}
>
{t(statusTranslationKey as ResourceKey)}
</InvText>
)
}
> >
<Icon as={FaCircle} boxSize="0.6rem" color={COLOR_MAP[statusColor]} /> <Icon as={FaExclamationTriangle} color="error.300" />
</InvTooltip> </InvTooltip>
</Flex> );
); }
return null;
}; };
export default memo(StatusIndicator); export default memo(StatusIndicator);

View File

@ -15,6 +15,7 @@ import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup'; import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu'; import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu';
import StatusIndicator from 'features/system/components/StatusIndicator';
import type { UsePanelOptions } from 'features/ui/hooks/usePanel'; import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
import { usePanel } from 'features/ui/hooks/usePanel'; import { usePanel } from 'features/ui/hooks/usePanel';
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage'; import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
@ -125,17 +126,17 @@ const InvokeTabs = () => {
() => () =>
enabledTabs.map((tab) => ( enabledTabs.map((tab) => (
<InvTooltip key={tab.id} label={t(tab.translationKey)} placement="end"> <InvTooltip key={tab.id} label={t(tab.translationKey)} placement="end">
<InvTab <InvTab p={0}>
as={InvIconButton} <InvIconButton
p={0} onClick={handleClickTab}
onClick={handleClickTab} icon={tab.icon}
icon={tab.icon} size="md"
size="md" fontSize="24px"
fontSize="24px" variant="appTab"
variant="appTab" data-selected={activeTabName === tab.id}
data-selected={activeTabName === tab.id} aria-label={t(tab.translationKey)}
aria-label={t(tab.translationKey)} />
></InvTab> </InvTab>
</InvTooltip> </InvTooltip>
)), )),
[enabledTabs, t, handleClickTab, activeTabName] [enabledTabs, t, handleClickTab, activeTabName]
@ -254,13 +255,14 @@ const InvokeTabs = () => {
p={4} p={4}
isLazy isLazy
> >
<Flex flexDir="column" alignItems="center" pt={4}> <Flex flexDir="column" alignItems="center" pt={4} pb={2} gap={4}>
<InvokeAILogoComponent /> <InvokeAILogoComponent />
<InvTabList gap={4} pt={12} pb={4} h="full" flexDir="column"> <InvTabList gap={4} pt={6} h="full" flexDir="column">
{tabs} {tabs}
<Spacer />
<SettingsMenu />
</InvTabList> </InvTabList>
<Spacer />
<StatusIndicator />
<SettingsMenu />
</Flex> </Flex>
<PanelGroup <PanelGroup
ref={panelGroupRef} ref={panelGroupRef}

View File

@ -10,3 +10,5 @@ export const spinKeyframes = keyframes`
`; `;
export const spinAnimation = `${spinKeyframes} 0.45s linear infinite`; export const spinAnimation = `${spinKeyframes} 0.45s linear infinite`;
export const spinAnimationSlow = `${spinKeyframes} 1s linear infinite`;