mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Merge 5eae9c223e into 061d21dede
This commit is contained in:
commit
2d55c11ea5
6 changed files with 109 additions and 28 deletions
|
|
@ -9,14 +9,27 @@ query FindImages(
|
||||||
image_ids: $image_ids
|
image_ids: $image_ids
|
||||||
) {
|
) {
|
||||||
count
|
count
|
||||||
megapixels
|
|
||||||
filesize
|
|
||||||
images {
|
images {
|
||||||
...SlimImageData
|
...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) {
|
query FindImage($id: ID!, $checksum: String) {
|
||||||
findImage(id: $id, checksum: $checksum) {
|
findImage(id: $id, checksum: $checksum) {
|
||||||
...ImageData
|
...ImageData
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import cloneDeep from "lodash-es/cloneDeep";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
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 { ItemList, ItemListContext, showWhenSelected } from "../List/ItemList";
|
||||||
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
import { useLightbox } from "src/hooks/Lightbox/hooks";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||||
|
|
@ -269,9 +273,17 @@ function getCount(result: GQL.FindImagesQueryResult) {
|
||||||
return result?.data?.findImages?.count ?? 0;
|
return result?.data?.findImages?.count ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMetadataByline(result: GQL.FindImagesQueryResult) {
|
function renderMetadataByline(
|
||||||
const megapixels = result?.data?.findImages?.megapixels;
|
result: GQL.FindImagesQueryResult,
|
||||||
const size = result?.data?.findImages?.filesize;
|
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"> (...)</span>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!megapixels && !size) {
|
if (!megapixels && !size) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -450,6 +462,7 @@ export const ImageList: React.FC<IImageList> = ({
|
||||||
<ItemListContext
|
<ItemListContext
|
||||||
filterMode={filterMode}
|
filterMode={filterMode}
|
||||||
useResult={useFindImages}
|
useResult={useFindImages}
|
||||||
|
useMetadataInfo={useFindImagesMetadata}
|
||||||
getItems={getItems}
|
getItems={getItems}
|
||||||
getCount={getCount}
|
getCount={getCount}
|
||||||
alterQuery={alterQuery}
|
alterQuery={alterQuery}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
view?: View;
|
||||||
otherOperations?: IItemListOperation<T>[];
|
otherOperations?: IItemListOperation<T>[];
|
||||||
renderContent: (
|
renderContent: (
|
||||||
|
|
@ -123,7 +123,7 @@ interface IItemListProps<T extends QueryResult, E extends IHasID> {
|
||||||
onChangePage: (page: number) => void,
|
onChangePage: (page: number) => void,
|
||||||
pageCount: number
|
pageCount: number
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
renderMetadataByline?: (data: T) => React.ReactNode;
|
renderMetadataByline?: (data: T, metadataInfo?: M) => React.ReactNode;
|
||||||
renderEditDialog?: (
|
renderEditDialog?: (
|
||||||
selected: E[],
|
selected: E[],
|
||||||
onClose: (applied: boolean) => void
|
onClose: (applied: boolean) => void
|
||||||
|
|
@ -140,8 +140,8 @@ interface IItemListProps<T extends QueryResult, E extends IHasID> {
|
||||||
renderToolbar?: (props: IFilteredListToolbar) => React.ReactNode;
|
renderToolbar?: (props: IFilteredListToolbar) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ItemList = <T extends QueryResult, E extends IHasID>(
|
export const ItemList = <T extends QueryResult, E extends IHasID, M = unknown>(
|
||||||
props: IItemListProps<T, E>
|
props: IItemListProps<T, E, M>
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
view,
|
view,
|
||||||
|
|
@ -155,8 +155,8 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const { filter, setFilter: updateFilter } = useFilter();
|
const { filter, setFilter: updateFilter } = useFilter();
|
||||||
const { effectiveFilter, result, cachedResult, totalCount } =
|
const { effectiveFilter, result, metadataInfo, cachedResult, totalCount } =
|
||||||
useQueryResultContext<T, E>();
|
useQueryResultContext<T, E, M>();
|
||||||
const listSelect = useListContext<E>();
|
const listSelect = useListContext<E>();
|
||||||
const {
|
const {
|
||||||
selectedIds,
|
selectedIds,
|
||||||
|
|
@ -174,8 +174,8 @@ export const ItemList = <T extends QueryResult, E extends IHasID>(
|
||||||
const metadataByline = useMemo(() => {
|
const metadataByline = useMemo(() => {
|
||||||
if (cachedResult.loading) return "";
|
if (cachedResult.loading) return "";
|
||||||
|
|
||||||
return renderMetadataByline?.(cachedResult) ?? "";
|
return renderMetadataByline?.(cachedResult, metadataInfo) ?? "";
|
||||||
}, [renderMetadataByline, cachedResult]);
|
}, [renderMetadataByline, cachedResult, metadataInfo]);
|
||||||
|
|
||||||
const pages = Math.ceil(totalCount / filter.itemsPerPage);
|
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;
|
filterMode: GQL.FilterMode;
|
||||||
defaultSort?: string;
|
defaultSort?: string;
|
||||||
defaultFilter?: ListFilterModel;
|
defaultFilter?: ListFilterModel;
|
||||||
useResult: (filter: ListFilterModel) => T;
|
useResult: (filter: ListFilterModel) => T;
|
||||||
|
useMetadataInfo?: (filter: ListFilterModel) => M;
|
||||||
getCount: (data: T) => number;
|
getCount: (data: T) => number;
|
||||||
getItems: (data: T) => E[];
|
getItems: (data: T) => E[];
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
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
|
// Provides the contexts for the ItemList component. Includes functionality to scroll
|
||||||
// to top on page change.
|
// to top on page change.
|
||||||
export const ItemListContext = <T extends QueryResult, E extends IHasID>(
|
export const ItemListContext = <
|
||||||
props: PropsWithChildren<IItemListContextProps<T, E>>
|
T extends QueryResult,
|
||||||
|
E extends IHasID,
|
||||||
|
M = unknown
|
||||||
|
>(
|
||||||
|
props: PropsWithChildren<IItemListContextProps<T, E, M>>
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
filterMode,
|
filterMode,
|
||||||
defaultSort,
|
defaultSort,
|
||||||
defaultFilter: providedDefaultFilter,
|
defaultFilter: providedDefaultFilter,
|
||||||
useResult,
|
useResult,
|
||||||
|
useMetadataInfo,
|
||||||
getCount,
|
getCount,
|
||||||
getItems,
|
getItems,
|
||||||
view,
|
view,
|
||||||
|
|
@ -425,6 +435,7 @@ export const ItemListContext = <T extends QueryResult, E extends IHasID>(
|
||||||
<QueryResultContext
|
<QueryResultContext
|
||||||
filterHook={filterHook}
|
filterHook={filterHook}
|
||||||
useResult={useResult}
|
useResult={useResult}
|
||||||
|
useMetadataInfo={useMetadataInfo}
|
||||||
getCount={getCount}
|
getCount={getCount}
|
||||||
getItems={getItems}
|
getItems={getItems}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -80,21 +80,25 @@ export function useListContextOptional<T extends IHasID = IHasID>() {
|
||||||
|
|
||||||
interface IQueryResultContextOptions<
|
interface IQueryResultContextOptions<
|
||||||
T extends QueryResult,
|
T extends QueryResult,
|
||||||
E extends IHasID = IHasID
|
E extends IHasID = IHasID,
|
||||||
|
M = unknown
|
||||||
> {
|
> {
|
||||||
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
filterHook?: (filter: ListFilterModel) => ListFilterModel;
|
||||||
useResult: (filter: ListFilterModel) => T;
|
useResult: (filter: ListFilterModel) => T;
|
||||||
|
useMetadataInfo?: (filter: ListFilterModel) => M;
|
||||||
getCount: (data: T) => number;
|
getCount: (data: T) => number;
|
||||||
getItems: (data: T) => E[];
|
getItems: (data: T) => E[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueryResultContextState<
|
export interface IQueryResultContextState<
|
||||||
T extends QueryResult = QueryResult,
|
T extends QueryResult = QueryResult,
|
||||||
E extends IHasID = IHasID
|
E extends IHasID = IHasID,
|
||||||
|
M = unknown
|
||||||
> {
|
> {
|
||||||
effectiveFilter: ListFilterModel;
|
effectiveFilter: ListFilterModel;
|
||||||
result: T;
|
result: T;
|
||||||
cachedResult: T;
|
cachedResult: T;
|
||||||
|
metadataInfo?: M;
|
||||||
items: E[];
|
items: E[];
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
}
|
}
|
||||||
|
|
@ -104,15 +108,23 @@ export const QueryResultStateContext =
|
||||||
|
|
||||||
export const QueryResultContext = <
|
export const QueryResultContext = <
|
||||||
T extends QueryResult,
|
T extends QueryResult,
|
||||||
E extends IHasID = IHasID
|
E extends IHasID = IHasID,
|
||||||
|
M = unknown
|
||||||
>(
|
>(
|
||||||
props: IQueryResultContextOptions<T, E> & {
|
props: IQueryResultContextOptions<T, E, M> & {
|
||||||
children?:
|
children?:
|
||||||
| ((props: IQueryResultContextState<T, E>) => React.ReactNode)
|
| ((props: IQueryResultContextState<T, E, M>) => React.ReactNode)
|
||||||
| React.ReactNode;
|
| React.ReactNode;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const { filterHook, useResult, getItems, getCount, children } = props;
|
const {
|
||||||
|
filterHook,
|
||||||
|
useResult,
|
||||||
|
useMetadataInfo,
|
||||||
|
getItems,
|
||||||
|
getCount,
|
||||||
|
children,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const { filter } = useFilter();
|
const { filter } = useFilter();
|
||||||
const effectiveFilter = useMemo(() => {
|
const effectiveFilter = useMemo(() => {
|
||||||
|
|
@ -122,9 +134,16 @@ export const QueryResultContext = <
|
||||||
return filter;
|
return filter;
|
||||||
}, [filter, filterHook]);
|
}, [filter, filterHook]);
|
||||||
|
|
||||||
const result = useResult(effectiveFilter);
|
// metadata filter is the effective filter with the sort, page size and page number removed
|
||||||
|
const metadataFilter = useMemo(
|
||||||
|
() => effectiveFilter.metadataInfo(),
|
||||||
|
[effectiveFilter]
|
||||||
|
);
|
||||||
|
|
||||||
// use cached query result for pagination and metadata rendering
|
const result = useResult(effectiveFilter);
|
||||||
|
const metadataInfo = useMetadataInfo?.(metadataFilter);
|
||||||
|
|
||||||
|
// use cached query result for pagination
|
||||||
const cachedResult = useCachedQueryResult(effectiveFilter, result);
|
const cachedResult = useCachedQueryResult(effectiveFilter, result);
|
||||||
|
|
||||||
const items = useMemo(() => getItems(result), [getItems, result]);
|
const items = useMemo(() => getItems(result), [getItems, result]);
|
||||||
|
|
@ -133,12 +152,13 @@ export const QueryResultContext = <
|
||||||
[getCount, cachedResult]
|
[getCount, cachedResult]
|
||||||
);
|
);
|
||||||
|
|
||||||
const state: IQueryResultContextState<T, E> = {
|
const state: IQueryResultContextState<T, E, M> = {
|
||||||
effectiveFilter,
|
effectiveFilter,
|
||||||
result,
|
result,
|
||||||
cachedResult,
|
cachedResult,
|
||||||
items,
|
items,
|
||||||
totalCount,
|
totalCount,
|
||||||
|
metadataInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -154,7 +174,8 @@ export const QueryResultContext = <
|
||||||
|
|
||||||
export function useQueryResultContext<
|
export function useQueryResultContext<
|
||||||
T extends QueryResult,
|
T extends QueryResult,
|
||||||
E extends IHasID = IHasID
|
E extends IHasID = IHasID,
|
||||||
|
M = unknown
|
||||||
>() {
|
>() {
|
||||||
const context = React.useContext(QueryResultStateContext);
|
const context = React.useContext(QueryResultStateContext);
|
||||||
|
|
||||||
|
|
@ -164,5 +185,5 @@ export function useQueryResultContext<
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return context as IQueryResultContextState<T, E>;
|
return context as IQueryResultContextState<T, E, M>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) =>
|
export const queryFindImages = (filter: ListFilterModel) =>
|
||||||
client.query<GQL.FindImagesQuery>({
|
client.query<GQL.FindImagesQuery>({
|
||||||
query: GQL.FindImagesDocument,
|
query: GQL.FindImagesDocument,
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,20 @@ 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.sortDirection = DEFAULT_PARAMS.sortDirection;
|
||||||
|
clone.itemsPerPage = 0;
|
||||||
|
clone.zoomIndex = 1;
|
||||||
|
clone.displayMode = DEFAULT_PARAMS.displayMode;
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
// returns the number of filters applied
|
// returns the number of filters applied
|
||||||
public count() {
|
public count() {
|
||||||
// don't include search term
|
// don't include search term
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue