Make a separate query for loading image query metadata

This commit is contained in:
WithoutPants 2025-12-03 17:56:56 +11:00
parent 39fd8a6550
commit 3494bfb197
6 changed files with 108 additions and 27 deletions

View file

@ -9,14 +9,27 @@ query FindImages(
image_ids: $image_ids
) {
count
megapixels
filesize
images {
...SlimImageData
}
}
}
query FindImagesMetadata(
$filter: FindFilterType
$image_filter: ImageFilterType
$image_ids: [Int!]
) {
findImages(
filter: $filter
image_filter: $image_filter
image_ids: $image_ids
) {
megapixels
filesize
}
}
query FindImage($id: ID!, $checksum: String) {
findImage(id: $id, checksum: $checksum) {
...ImageData

View file

@ -4,7 +4,11 @@ import cloneDeep from "lodash-es/cloneDeep";
import { useHistory } from "react-router-dom";
import Mousetrap from "mousetrap";
import * as GQL from "src/core/generated-graphql";
import { queryFindImages, useFindImages } from "src/core/StashService";
import {
queryFindImages,
useFindImages,
useFindImagesMetadata,
} from "src/core/StashService";
import { ItemList, ItemListContext, showWhenSelected } from "../List/ItemList";
import { useLightbox } from "src/hooks/Lightbox/hooks";
import { ListFilterModel } from "src/models/list-filter/filter";
@ -269,9 +273,17 @@ function getCount(result: GQL.FindImagesQueryResult) {
return result?.data?.findImages?.count ?? 0;
}
function renderMetadataByline(result: GQL.FindImagesQueryResult) {
const megapixels = result?.data?.findImages?.megapixels;
const size = result?.data?.findImages?.filesize;
function renderMetadataByline(
result: GQL.FindImagesQueryResult,
metadataInfo?: GQL.FindImagesMetadataQueryResult
) {
const megapixels = metadataInfo?.data?.findImages?.megapixels;
const size = metadataInfo?.data?.findImages?.filesize;
if (metadataInfo?.loading) {
// return ellipsis
return <span className="images-stats">&nbsp;(...)</span>;
}
if (!megapixels && !size) {
return;
@ -450,6 +462,7 @@ export const ImageList: React.FC<IImageList> = ({
<ItemListContext
filterMode={filterMode}
useResult={useFindImages}
useMetadataInfo={useFindImagesMetadata}
getItems={getItems}
getCount={getCount}
alterQuery={alterQuery}

View file

@ -112,7 +112,7 @@ export function useFilteredItemList<
};
}
interface IItemListProps<T extends QueryResult, E extends IHasID> {
interface IItemListProps<T extends QueryResult, E extends IHasID, M = unknown> {
view?: View;
otherOperations?: IItemListOperation<T>[];
renderContent: (
@ -123,7 +123,7 @@ interface IItemListProps<T extends QueryResult, E extends IHasID> {
onChangePage: (page: number) => void,
pageCount: number
) => React.ReactNode;
renderMetadataByline?: (data: T) => React.ReactNode;
renderMetadataByline?: (data: T, metadataInfo?: M) => React.ReactNode;
renderEditDialog?: (
selected: E[],
onClose: (applied: boolean) => void
@ -140,8 +140,8 @@ interface IItemListProps<T extends QueryResult, E extends IHasID> {
renderToolbar?: (props: IFilteredListToolbar) => React.ReactNode;
}
export const ItemList = <T extends QueryResult, E extends IHasID>(
props: IItemListProps<T, E>
export const ItemList = <T extends QueryResult, E extends IHasID, M = unknown>(
props: IItemListProps<T, E, M>
) => {
const {
view,
@ -155,8 +155,8 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
} = props;
const { filter, setFilter: updateFilter } = useFilter();
const { effectiveFilter, result, cachedResult, totalCount } =
useQueryResultContext<T, E>();
const { effectiveFilter, result, metadataInfo, cachedResult, totalCount } =
useQueryResultContext<T, E, M>();
const listSelect = useListContext<E>();
const {
selectedIds,
@ -174,8 +174,8 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
const metadataByline = useMemo(() => {
if (cachedResult.loading) return "";
return renderMetadataByline?.(cachedResult) ?? "";
}, [renderMetadataByline, cachedResult]);
return renderMetadataByline?.(cachedResult, metadataInfo) ?? "";
}, [renderMetadataByline, cachedResult, metadataInfo]);
const pages = Math.ceil(totalCount / filter.itemsPerPage);
@ -369,11 +369,16 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
);
};
interface IItemListContextProps<T extends QueryResult, E extends IHasID> {
interface IItemListContextProps<
T extends QueryResult,
E extends IHasID,
M = unknown
> {
filterMode: GQL.FilterMode;
defaultSort?: string;
defaultFilter?: ListFilterModel;
useResult: (filter: ListFilterModel) => T;
useMetadataInfo?: (filter: ListFilterModel) => M;
getCount: (data: T) => number;
getItems: (data: T) => E[];
filterHook?: (filter: ListFilterModel) => ListFilterModel;
@ -384,14 +389,19 @@ interface IItemListContextProps<T extends QueryResult, E extends IHasID> {
// Provides the contexts for the ItemList component. Includes functionality to scroll
// to top on page change.
export const ItemListContext = <T extends QueryResult, E extends IHasID>(
props: PropsWithChildren<IItemListContextProps<T, E>>
export const ItemListContext = <
T extends QueryResult,
E extends IHasID,
M = unknown
>(
props: PropsWithChildren<IItemListContextProps<T, E, M>>
) => {
const {
filterMode,
defaultSort,
defaultFilter: providedDefaultFilter,
useResult,
useMetadataInfo,
getCount,
getItems,
view,
@ -425,6 +435,7 @@ export const ItemListContext = <T extends QueryResult, E extends IHasID>(
<QueryResultContext
filterHook={filterHook}
useResult={useResult}
useMetadataInfo={useMetadataInfo}
getCount={getCount}
getItems={getItems}
>

View file

@ -80,21 +80,25 @@ export function useListContextOptional<T extends IHasID = IHasID>() {
interface IQueryResultContextOptions<
T extends QueryResult,
E extends IHasID = IHasID
E extends IHasID = IHasID,
M = unknown
> {
filterHook?: (filter: ListFilterModel) => ListFilterModel;
useResult: (filter: ListFilterModel) => T;
useMetadataInfo?: (filter: ListFilterModel) => M;
getCount: (data: T) => number;
getItems: (data: T) => E[];
}
export interface IQueryResultContextState<
T extends QueryResult = QueryResult,
E extends IHasID = IHasID
E extends IHasID = IHasID,
M = unknown
> {
effectiveFilter: ListFilterModel;
result: T;
cachedResult: T;
metadataInfo?: M;
items: E[];
totalCount: number;
}
@ -104,15 +108,23 @@ export const QueryResultStateContext =
export const QueryResultContext = <
T extends QueryResult,
E extends IHasID = IHasID
E extends IHasID = IHasID,
M = unknown
>(
props: IQueryResultContextOptions<T, E> & {
props: IQueryResultContextOptions<T, E, M> & {
children?:
| ((props: IQueryResultContextState<T, E>) => React.ReactNode)
| ((props: IQueryResultContextState<T, E, M>) => React.ReactNode)
| React.ReactNode;
}
) => {
const { filterHook, useResult, getItems, getCount, children } = props;
const {
filterHook,
useResult,
useMetadataInfo,
getItems,
getCount,
children,
} = props;
const { filter } = useFilter();
const effectiveFilter = useMemo(() => {
@ -122,9 +134,17 @@ export const QueryResultContext = <
return filter;
}, [filter, filterHook]);
// metadata filter is the effective filter with the sort, page size and page number removed
const metadataFilter = useMemo(
() => effectiveFilter.metadataInfo(),
[effectiveFilter]
);
const result = useResult(effectiveFilter);
// use cached query result for pagination and metadata rendering
const metadataInfo = useMetadataInfo?.(metadataFilter);
// use cached query result for pagination
const cachedResult = useCachedQueryResult(effectiveFilter, result);
const items = useMemo(() => getItems(result), [getItems, result]);
@ -133,12 +153,13 @@ export const QueryResultContext = <
[getCount, cachedResult]
);
const state: IQueryResultContextState<T, E> = {
const state: IQueryResultContextState<T, E, M> = {
effectiveFilter,
result,
cachedResult,
items,
totalCount,
metadataInfo,
};
return (
@ -154,7 +175,8 @@ export const QueryResultContext = <
export function useQueryResultContext<
T extends QueryResult,
E extends IHasID = IHasID
E extends IHasID = IHasID,
M = unknown
>() {
const context = React.useContext(QueryResultStateContext);
@ -164,5 +186,5 @@ export function useQueryResultContext<
);
}
return context as IQueryResultContextState<T, E>;
return context as IQueryResultContextState<T, E, M>;
}

View file

@ -201,6 +201,15 @@ export const useFindImages = (filter?: ListFilterModel) =>
},
});
export const useFindImagesMetadata = (filter?: ListFilterModel) =>
GQL.useFindImagesMetadataQuery({
skip: filter === undefined,
variables: {
filter: filter?.makeFindFilter(),
image_filter: filter?.makeFilter(),
},
});
export const queryFindImages = (filter: ListFilterModel) =>
client.query<GQL.FindImagesQuery>({
query: GQL.FindImagesDocument,

View file

@ -103,6 +103,19 @@ export class ListFilterModel {
});
}
// returns a clone of the filter for metadata fetching
// this removes the sort, page size and page number and zoom index
public metadataInfo() {
const clone = this.clone();
clone.sortBy = undefined;
clone.randomSeed = -1;
clone.currentPage = 1;
clone.itemsPerPage = 0;
clone.zoomIndex = 1;
clone.displayMode = DEFAULT_PARAMS.displayMode;
return clone;
}
// returns the number of filters applied
public count() {
// don't include search term