diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx index b213d927b2..dfd38c493f 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueItemComponent.tsx @@ -9,7 +9,7 @@ import { getSecondsFromTimestamps } from 'features/queue/util/getSecondsFromTime import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { selectShouldShowCredits } from 'features/system/store/configSlice'; import type { MouseEvent } from 'react'; -import { memo, useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { PiArrowCounterClockwiseBold, PiXBold } from 'react-icons/pi'; import { useSelector } from 'react-redux'; @@ -17,14 +17,12 @@ import type { S } from 'services/api/types'; import { COLUMN_WIDTHS } from './constants'; import QueueItemDetail from './QueueItemDetail'; -import type { ListContext } from './types'; const selectedStyles = { bg: 'base.700' }; type InnerItemProps = { index: number; item: S['SessionQueueItem']; - context: ListContext; }; const sx: ChakraProps['sx'] = { @@ -32,12 +30,18 @@ const sx: ChakraProps['sx'] = { "&[aria-selected='true']": selectedStyles, }; -const QueueItemComponent = ({ index, item, context }: InnerItemProps) => { +const QueueItemComponent = ({ index, item }: InnerItemProps) => { const { t } = useTranslation(); const isRetryEnabled = useFeatureStatus('retryQueueItem'); + const [openQueueItems, setOpenQueueItems] = useState([]); const handleToggle = useCallback(() => { - context.toggleQueueItem(item.item_id); - }, [context, item.item_id]); + setOpenQueueItems((prev) => { + if (prev.includes(item.item_id)) { + return prev.filter((id) => id !== item.item_id); + } + return [...prev, item.item_id]; + }); + }, [item]); const cancelQueueItem = useCancelQueueItem(); const onClickCancelQueueItem = useCallback( (e: MouseEvent) => { @@ -54,7 +58,7 @@ const QueueItemComponent = ({ index, item, context }: InnerItemProps) => { }, [item.item_id, retryQueueItem] ); - const isOpen = useMemo(() => context.openQueueItems.includes(item.item_id), [context.openQueueItems, item.item_id]); + const isOpen = useMemo(() => openQueueItems.includes(item.item_id), [openQueueItems, item.item_id]); const executionTime = useMemo(() => { if (!item.completed_at || !item.started_at) { diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueList.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueList.tsx index 134bca77ee..bba9b205a6 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueList.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueList.tsx @@ -1,7 +1,7 @@ import { Flex, Heading } from '@invoke-ai/ui-library'; import { IAINoContentFallbackWithSpinner } from 'common/components/IAIImageFallback'; import { useRangeBasedQueueItemFetching } from 'features/queue/hooks/useRangeBasedQueueItemFetching'; -import { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import type { Components, @@ -17,42 +17,39 @@ import { queueApi } from 'services/api/endpoints/queue'; import QueueItemComponent, { QueueItemPlaceholder } from './QueueItemComponent'; import QueueListComponent from './QueueListComponent'; import QueueListHeader from './QueueListHeader'; -import type { ListContext } from './types'; import { useQueueItemIds } from './useQueueItemIds'; import { useScrollableQueueList } from './useScrollableQueueList'; -const QueueItemAtPosition = memo( - ({ index, itemId, context }: { index: number; itemId: number; context: ListContext }) => { - /* - * We rely on the useRangeBasedQueueItemFetching to fetch all queue items, caching them with RTK Query. - * - * In this component, we just want to consume that cache. Unforutnately, RTK Query does not provide a way to - * subscribe to a query without triggering a new fetch. - * - * There is a hack, though: - * - https://github.com/reduxjs/redux-toolkit/discussions/4213 - * - * This essentially means "subscribe to the query once it has some data". - */ +const QueueItemAtPosition = memo(({ index, itemId }: { index: number; itemId: number }) => { + /* + * We rely on the useRangeBasedQueueItemFetching to fetch all queue items, caching them with RTK Query. + * + * In this component, we just want to consume that cache. Unforutnately, RTK Query does not provide a way to + * subscribe to a query without triggering a new fetch. + * + * There is a hack, though: + * - https://github.com/reduxjs/redux-toolkit/discussions/4213 + * + * This essentially means "subscribe to the query once it has some data". + */ - // Use `currentData` instead of `data` to prevent a flash of previous queue item rendered at this index - const { currentData: queueItem, isUninitialized } = queueApi.endpoints.getQueueItem.useQueryState(itemId); - queueApi.endpoints.getQueueItem.useQuerySubscription(itemId, { skip: isUninitialized }); + // Use `currentData` instead of `data` to prevent a flash of previous queue item rendered at this index + const { currentData: queueItem, isUninitialized } = queueApi.endpoints.getQueueItem.useQueryState(itemId); + queueApi.endpoints.getQueueItem.useQuerySubscription(itemId, { skip: isUninitialized }); - if (!queueItem) { - return ; - } - - return ; + if (!queueItem) { + return ; } -); + + return ; +}); QueueItemAtPosition.displayName = 'QueueItemAtPosition'; -const itemContent: ItemContent = (index, itemId, context) => ( - +const itemContent: ItemContent = (index, itemId) => ( + ); -const ScrollSeekPlaceholderComponent: Components['ScrollSeekPlaceholder'] = (props) => ( +const ScrollSeekPlaceholderComponent: Components['ScrollSeekPlaceholder'] = (props) => ( @@ -60,7 +57,7 @@ const ScrollSeekPlaceholderComponent: Components['ScrollSee ScrollSeekPlaceholderComponent.displayName = 'ScrollSeekPlaceholderComponent'; -const components: Components = { +const components: Components = { List: QueueListComponent, ScrollSeekPlaceholder: ScrollSeekPlaceholderComponent, }; @@ -83,8 +80,8 @@ export const QueueList = () => { // Get the ordered list of queue item ids - this is our primary data source for virtualization const { queryArgs, itemIds, isLoading } = useQueueItemIds(); - const computeItemKey: ComputeItemKey = useCallback( - (index, itemId) => { + const computeItemKey: ComputeItemKey = useCallback( + (index: number, itemId: number) => { return `${JSON.stringify(queryArgs)}-${itemId ?? index}`; }, [queryArgs] @@ -110,19 +107,6 @@ export const QueueList = () => { [onRangeChanged] ); - const [openQueueItems, setOpenQueueItems] = useState([]); - - const toggleQueueItem = useCallback((item_id: number) => { - setOpenQueueItems((prev) => { - if (prev.includes(item_id)) { - return prev.filter((id) => id !== item_id); - } - return [...prev, item_id]; - }); - }, []); - - const context = useMemo(() => ({ openQueueItems, toggleQueueItem }), [openQueueItems, toggleQueueItem]); - if (isLoading) { return ; } @@ -139,9 +123,8 @@ export const QueueList = () => { - + ref={virtuosoRef} - context={context} data={itemIds} increaseViewportBy={4096} itemContent={itemContent} diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListComponent.tsx b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListComponent.tsx index 932ef0d6d5..7eba0b01bf 100644 --- a/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListComponent.tsx +++ b/invokeai/frontend/web/src/features/queue/components/QueueList/QueueListComponent.tsx @@ -2,9 +2,7 @@ import { Flex, forwardRef, typedMemo } from '@invoke-ai/ui-library'; import type { Components } from 'react-virtuoso'; import type { S } from 'services/api/types'; -import type { ListContext } from './types'; - -const QueueListComponent: Components['List'] = typedMemo( +const QueueListComponent: Components['List'] = typedMemo( forwardRef((props, ref) => { return ( diff --git a/invokeai/frontend/web/src/features/queue/components/QueueList/types.ts b/invokeai/frontend/web/src/features/queue/components/QueueList/types.ts deleted file mode 100644 index c9b317ac57..0000000000 --- a/invokeai/frontend/web/src/features/queue/components/QueueList/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ListContext = { - openQueueItems: number[]; - toggleQueueItem: (item_id: number) => void; -};