add video_count and asset_count to boards UI

This commit is contained in:
Mary Hipp
2025-08-25 11:57:15 -04:00
committed by Mary Hipp Rogers
parent d4378d9f2a
commit e36490c2ec
8 changed files with 84 additions and 20 deletions

View File

@@ -61,6 +61,8 @@
"imagesWithCount_other": "{{count}} images",
"assetsWithCount_one": "{{count}} asset",
"assetsWithCount_other": "{{count}} assets",
"videosWithCount_one": "{{count}} video",
"videosWithCount_other": "{{count}} videos",
"updateBoardError": "Error updating board"
},
"accordions": {

View File

@@ -1,26 +1,21 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useTranslation } from 'react-i18next';
import { useGetBoardAssetsTotalQuery, useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import type { BoardDTO } from 'services/api/types';
type Props = {
board: BoardDTO | null;
boardCounts: {
image_count: number;
asset_count: number;
video_count: number;
};
};
export const BoardTooltip = ({ board }: Props) => {
export const BoardTooltip = ({ board, boardCounts }: Props) => {
const { t } = useTranslation();
const { imagesTotal } = useGetBoardImagesTotalQuery(board?.board_id || 'none', {
selectFromResult: ({ data }) => {
return { imagesTotal: data?.total ?? 0 };
},
});
const { assetsTotal } = useGetBoardAssetsTotalQuery(board?.board_id || 'none', {
selectFromResult: ({ data }) => {
return { assetsTotal: data?.total ?? 0 };
},
});
const { currentData: coverImage } = useGetImageDTOQuery(board?.cover_image_name ?? skipToken);
return (
@@ -39,8 +34,10 @@ export const BoardTooltip = ({ board }: Props) => {
<Flex flexDir="column" alignItems="center">
{board && <Text fontWeight="semibold">{board.board_name}</Text>}
<Text noOfLines={1}>
{t('boards.imagesWithCount', { count: imagesTotal })}, {t('boards.assetsWithCount', { count: assetsTotal })}
{t('boards.imagesWithCount', { count: boardCounts.image_count })},{' '}
{t('boards.assetsWithCount', { count: boardCounts.asset_count })}
</Text>
<Text noOfLines={1}>{t('boards.videosWithCount', { count: boardCounts.video_count })}</Text>
{board?.archived && <Text>({t('boards.archived')})</Text>}
</Flex>
</Flex>

View File

@@ -50,11 +50,26 @@ const GalleryBoard = ({ board, isSelected }: GalleryBoardProps) => {
[board.board_id]
);
const boardCounts = useMemo(
() => ({
image_count: board.image_count,
asset_count: board.asset_count,
video_count: board.video_count,
}),
[board]
);
return (
<Box position="relative" w="full" h={12}>
<BoardContextMenu board={board}>
{(ref) => (
<Tooltip label={<BoardTooltip board={board} />} openDelay={1000} placement="left" closeOnScroll p={2}>
<Tooltip
label={<BoardTooltip board={board} boardCounts={boardCounts} />}
openDelay={1000}
placement="right"
closeOnScroll
p={2}
>
<Flex
ref={ref}
onClick={onClick}
@@ -71,12 +86,16 @@ const GalleryBoard = ({ board, isSelected }: GalleryBoardProps) => {
h="full"
>
<CoverImage board={board} />
<Flex w="full">
<Flex flex={1}>
<BoardEditableTitle board={board} isSelected={isSelected} />
</Flex>
{autoAddBoardId === board.board_id && <AutoAddBadge />}
{board.archived && <Icon as={PiArchiveBold} fill="base.300" />}
<Text variant="subtext">{board.image_count}</Text>
<Flex justifyContent="flex-end">
<Text variant="subtext">
{board.image_count} | {board.asset_count} | {board.video_count}
</Text>
</Flex>
</Flex>
</Tooltip>
)}

View File

@@ -15,7 +15,11 @@ import {
import { autoAddBoardIdChanged, boardIdSelected } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetBoardImagesTotalQuery } from 'services/api/endpoints/boards';
import {
useGetBoardAssetsTotalQuery,
useGetBoardImagesTotalQuery,
useGetBoardVideosTotalQuery,
} from 'services/api/endpoints/boards';
import { useBoardName } from 'services/api/hooks/useBoardName';
interface Props {
@@ -33,6 +37,16 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
return { imagesTotal: data?.total ?? 0 };
},
});
const { assetsTotal } = useGetBoardAssetsTotalQuery('none', {
selectFromResult: ({ data }) => {
return { assetsTotal: data?.total ?? 0 };
},
});
const { videoTotal } = useGetBoardVideosTotalQuery('none', {
selectFromResult: ({ data }) => {
return { videoTotal: data?.total ?? 0 };
},
});
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const autoAssignBoardOnClick = useAppSelector(selectAutoAssignBoardOnClick);
const boardSearchText = useAppSelector(selectBoardSearchText);
@@ -56,7 +70,17 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
<Box position="relative" w="full" h={12}>
<NoBoardBoardContextMenu>
{(ref) => (
<Tooltip label={<BoardTooltip board={null} />} openDelay={1000} placement="left" closeOnScroll>
<Tooltip
label={
<BoardTooltip
board={null}
boardCounts={{ image_count: imagesTotal, asset_count: assetsTotal, video_count: videoTotal }}
/>
}
openDelay={1000}
placement="right"
closeOnScroll
>
<Flex
ref={ref}
onClick={handleSelectBoard}
@@ -92,7 +116,9 @@ const NoBoardBoard = memo(({ isSelected }: Props) => {
{boardName}
</Text>
{autoAddBoardId === 'none' && <AutoAddBadge />}
<Text variant="subtext">{imagesTotal}</Text>
<Text variant="subtext">
{imagesTotal} | {assetsTotal} | {videoTotal}
</Text>
</Flex>
</Tooltip>
)}

View File

@@ -3,6 +3,7 @@ import queryString from 'query-string';
import type {
BoardDTO,
CreateBoardArg,
GetVideoIdsResult,
ImageCategory,
ListBoardsArgs,
OffsetPaginatedResults_ImageDTO_,
@@ -12,6 +13,7 @@ import { getListImagesUrl } from 'services/api/util';
import type { ApiTagDescription } from '..';
import { api, buildV1Url, LIST_TAG } from '..';
import { buildVideosUrl } from './videos';
/**
* Builds an endpoint URL for the boards router
@@ -95,6 +97,17 @@ export const boardsApi = api.injectEndpoints({
},
}),
getBoardVideosTotal: build.query<{ total: number }, string | undefined>({
query: (board_id) => ({
url: buildVideosUrl('ids', { board_id: board_id ?? 'none' }),
method: 'GET',
}),
providesTags: (result, error, arg) => [{ type: 'BoardVideosTotal', id: arg ?? 'none' }, 'FetchOnReconnect'],
transformResponse: (response: GetVideoIdsResult) => {
return { total: response.total_count };
},
}),
/**
* Boards Mutations
*/
@@ -132,6 +145,7 @@ export const {
useListAllBoardsQuery,
useGetBoardImagesTotalQuery,
useGetBoardAssetsTotalQuery,
useGetBoardVideosTotalQuery,
useCreateBoardMutation,
useUpdateBoardMutation,
useListAllImageNamesForBoardQuery,

View File

@@ -18,7 +18,7 @@ import { api, buildV1Url, LIST_TAG } from '..';
* buildVideosUrl('some-path')
* // '/api/v1/videos/some-path'
*/
const buildVideosUrl = (path: string = '', query?: Parameters<typeof buildV1Url>[1]) =>
export const buildVideosUrl = (path: string = '', query?: Parameters<typeof buildV1Url>[1]) =>
buildV1Url(`videos/${path}`, query);
const buildBoardVideosUrl = (path: string = '') => buildV1Url(`board_videos/${path}`);

View File

@@ -19,6 +19,7 @@ const tagTypes = [
'Board',
'BoardImagesTotal',
'BoardAssetsTotal',
'BoardVideosTotal',
'HFTokenStatus',
'Image',
'ImageNameList',

View File

@@ -2781,6 +2781,11 @@ export type components = {
* @description The number of images in the board.
*/
image_count: number;
/**
* Asset Count
* @description The number of assets in the board.
*/
asset_count: number;
/**
* Video Count
* @description The number of videos in the board.