feat(ui): use spinner for queue loading state

Skeletons are for when we know the number of specific content items that are loading. When the queue is loading, we don't know how many items there are, or how many will load, so the whole list should be replaced with loading state.

The previous behaviour rendered a static number of skeletons. That number would rarely be the right number - the app shouldn't say "I'm loading 7 queue items", then load none, or load 50.

A future enhancement could use the queue item skeleton component and go by the total number of queue items, as reported by the queue status. I tried this but had some layout jankiness, not worth the effort right now.

The queue item skeleton component's styling was updated to support this future enhancement, making it exactly the same size as a queue item (it was a bit smaller before).
This commit is contained in:
psychedelicious 2023-09-26 10:21:11 +10:00
parent 1e3590111d
commit 358116bc22
4 changed files with 85 additions and 73 deletions

View File

@ -79,7 +79,7 @@
"lightMode": "Light Mode",
"linear": "Linear",
"load": "Load",
"loading": "Loading",
"loading": "Loading $t({{noun}})...",
"loadingInvokeAI": "Loading Invoke AI",
"learnMore": "Learn More",
"modelManager": "Model Manager",

View File

@ -81,3 +81,38 @@ export const IAINoContentFallback = (props: IAINoImageFallbackProps) => {
</Flex>
);
};
type IAINoImageFallbackWithSpinnerProps = FlexProps & {
label?: string;
};
export const IAINoContentFallbackWithSpinner = (
props: IAINoImageFallbackWithSpinnerProps
) => {
const { sx, ...rest } = props;
return (
<Flex
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'base',
flexDir: 'column',
gap: 2,
userSelect: 'none',
opacity: 0.7,
color: 'base.700',
_dark: {
color: 'base.500',
},
...sx,
}}
{...rest}
>
<Spinner size="xl" />
{props.label && <Text textAlign="center">{props.label}</Text>}
</Flex>
);
};

View File

@ -1,46 +1,37 @@
import { Flex, Skeleton, Text } from '@chakra-ui/react';
import { Flex, Skeleton } from '@chakra-ui/react';
import { memo } from 'react';
import { COLUMN_WIDTHS } from './constants';
const QueueItemSkeleton = () => {
return (
<Flex
alignItems="center"
gap={4}
p={1}
pb={2}
textTransform="uppercase"
fontWeight={700}
fontSize="xs"
letterSpacing={1}
>
<Flex alignItems="center" p={1.5} gap={4} minH={9} h="full" w="full">
<Flex
w={COLUMN_WIDTHS.number}
justifyContent="flex-end"
alignItems="center"
>
<Skeleton width="20px">
<Text variant="subtext">&nbsp;</Text>
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.statusBadge} alignItems="center">
<Skeleton width="100%">
<Text variant="subtext">&nbsp;</Text>
<Flex w={COLUMN_WIDTHS.statusBadge} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.time} alignItems="center">
<Skeleton width="100%">
<Text variant="subtext">&nbsp;</Text>
<Flex w={COLUMN_WIDTHS.time} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.batchId} alignItems="center">
<Skeleton width="100%">
<Text variant="subtext">&nbsp;</Text>
<Flex w={COLUMN_WIDTHS.batchId} alignItems="center">
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.fieldValues} alignItems="center" flex="1">
<Skeleton width="100%">
<Text variant="subtext">&nbsp;</Text>
<Flex w={COLUMN_WIDTHS.fieldValues} alignItems="center" flexGrow={1}>
<Skeleton w="full" h="full">
&nbsp;
</Skeleton>
</Flex>
</Flex>

View File

@ -3,6 +3,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback';
import {
listCursorChanged,
listPriorityChanged,
@ -23,7 +24,6 @@ import QueueItemComponent from './QueueItemComponent';
import QueueListComponent from './QueueListComponent';
import QueueListHeader from './QueueListHeader';
import { ListContext } from './types';
import QueueItemSkeleton from './QueueItemSkeleton';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TableVirtuosoScrollerRef = (ref: HTMLElement | Window | null) => any;
@ -126,54 +126,40 @@ const QueueList = () => {
[openQueueItems, toggleQueueItem]
);
if (isLoading) {
return <IAINoContentFallbackWithSpinner />;
}
if (!queueItems.length) {
return (
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<Heading color="base.400" _dark={{ color: 'base.500' }}>
{t('queue.queueEmpty')}
</Heading>
</Flex>
);
}
return (
<Flex w="full" h="full" flexDir="column">
{isLoading ? (
<>
<QueueListHeader />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
<QueueItemSkeleton />
</>
) : (
<>
{queueItems.length ? (
<>
<QueueListHeader />
<Flex
ref={rootRef}
w="full"
h="full"
alignItems="center"
justifyContent="center"
>
<Virtuoso<SessionQueueItemDTO, ListContext>
data={queueItems}
endReached={handleLoadMore}
scrollerRef={setScroller as TableVirtuosoScrollerRef}
itemContent={itemContent}
computeItemKey={computeItemKey}
components={components}
context={context}
/>
</Flex>
</>
) : (
<Flex w="full" h="full" alignItems="center" justifyContent="center">
<Heading color="base.400" _dark={{ color: 'base.500' }}>
{t('queue.queueEmpty')}
</Heading>
</Flex>
)}
</>
)}
<QueueListHeader />
<Flex
ref={rootRef}
w="full"
h="full"
alignItems="center"
justifyContent="center"
>
<Virtuoso<SessionQueueItemDTO, ListContext>
data={queueItems}
endReached={handleLoadMore}
scrollerRef={setScroller as TableVirtuosoScrollerRef}
itemContent={itemContent}
computeItemKey={computeItemKey}
components={components}
context={context}
/>
</Flex>
</Flex>
);
};