mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
311 lines
11 KiB
TypeScript
311 lines
11 KiB
TypeScript
import _ from "lodash";
|
|
import queryString from "query-string";
|
|
import React, { useEffect, useState } from "react";
|
|
import { Spinner } from 'react-bootstrap';
|
|
import { QueryHookResult } from "react-apollo-hooks";
|
|
import { ListFilter } from "../components/list/ListFilter";
|
|
import { Pagination } from "../components/list/Pagination";
|
|
import { StashService } from "../core/StashService";
|
|
import { IBaseProps } from "../models";
|
|
import { Criterion } from "../models/list-filter/criteria/criterion";
|
|
import { ListFilterModel } from "../models/list-filter/filter";
|
|
import { DisplayMode, FilterMode } from "../models/list-filter/types";
|
|
|
|
export interface IListHookData {
|
|
filter: ListFilterModel;
|
|
template: JSX.Element;
|
|
options: IListHookOptions;
|
|
onSelectChange: (id: string, selected : boolean, shiftKey: boolean) => void;
|
|
}
|
|
|
|
interface IListHookOperation {
|
|
text: string;
|
|
onClick: (result: QueryHookResult<any, any>, filter: ListFilterModel, selectedIds: Set<string>) => void;
|
|
}
|
|
|
|
export interface IListHookOptions {
|
|
filterMode: FilterMode;
|
|
props: IBaseProps;
|
|
zoomable?: boolean;
|
|
otherOperations?: IListHookOperation[];
|
|
renderContent: (result: QueryHookResult<any, any>, filter: ListFilterModel, selectedIds: Set<string>, zoomIndex: number) => JSX.Element | undefined;
|
|
renderSelectedOptions?: (result: QueryHookResult<any, any>, selectedIds: Set<string>) => JSX.Element | undefined;
|
|
}
|
|
|
|
export class ListHook {
|
|
public static useList(options: IListHookOptions): IListHookData {
|
|
const [filter, setFilter] = useState<ListFilterModel>(new ListFilterModel(options.filterMode));
|
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
const [lastClickedId, setLastClickedId] = useState<string | undefined>(undefined);
|
|
const [totalCount, setTotalCount] = useState<number>(0);
|
|
const [zoomIndex, setZoomIndex] = useState<number>(1);
|
|
|
|
// Update the filter when the query parameters change
|
|
useEffect(() => {
|
|
const queryParams = queryString.parse(options.props.location.search);
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.configureFromQueryParameters(queryParams);
|
|
setFilter(newFilter);
|
|
|
|
// TODO: Need this side effect to update the query params properly
|
|
filter.configureFromQueryParameters(queryParams);
|
|
}, [options.props.location.search]);
|
|
|
|
let result: QueryHookResult<any, any>;
|
|
|
|
let getData: (filter : ListFilterModel) => QueryHookResult<any, any>;
|
|
let getItems: () => any[];
|
|
let getCount: () => number;
|
|
|
|
switch (options.filterMode) {
|
|
case FilterMode.Scenes: {
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindScenes(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findScenes ? result.data.findScenes.scenes : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findScenes ? result.data.findScenes.count : 0; }
|
|
break;
|
|
}
|
|
case FilterMode.SceneMarkers: {
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindSceneMarkers(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findSceneMarkers ? result.data.findSceneMarkers.scene_markers : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findSceneMarkers ? result.data.findSceneMarkers.count : 0; }
|
|
break;
|
|
}
|
|
case FilterMode.Galleries: {
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindGalleries(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findGalleries ? result.data.findGalleries.galleries : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findGalleries ? result.data.findGalleries.count : 0; }
|
|
break;
|
|
}
|
|
case FilterMode.Studios: {
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindStudios(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findStudios ? result.data.findStudios.studios : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findStudios ? result.data.findStudios.count : 0; }
|
|
break;
|
|
}
|
|
case FilterMode.Performers: {
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindPerformers(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findPerformers ? result.data.findPerformers.performers : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findPerformers ? result.data.findPerformers.count : 0; }
|
|
break;
|
|
}
|
|
default: {
|
|
console.error("REMOVE DEFAULT IN LIST HOOK");
|
|
getData = (filter : ListFilterModel) => { return StashService.useFindScenes(filter); }
|
|
getItems = () => { return !!result.data && !!result.data.findScenes ? result.data.findScenes.scenes : []; }
|
|
getCount = () => { return !!result.data && !!result.data.findScenes ? result.data.findScenes.count : 0; }
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = getData(filter);
|
|
|
|
useEffect(() => {
|
|
setTotalCount(getCount());
|
|
|
|
// select none when data changes
|
|
onSelectNone();
|
|
setLastClickedId(undefined);
|
|
}, [result.data])
|
|
|
|
// Update the query parameters when the data changes
|
|
useEffect(() => {
|
|
const location = Object.assign({}, options.props.history.location);
|
|
location.search = filter.makeQueryParameters();
|
|
options.props.history.replace(location);
|
|
}, [result.data, filter.displayMode]);
|
|
|
|
// Update the total count
|
|
useEffect(() => {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.totalCount = totalCount;
|
|
setFilter(newFilter);
|
|
}, [totalCount]);
|
|
|
|
function onChangePageSize(pageSize: number) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.itemsPerPage = pageSize;
|
|
newFilter.currentPage = 1;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onChangeQuery(query: string) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.searchTerm = query;
|
|
newFilter.currentPage = 1;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onChangeSortDirection(sortDirection: "asc" | "desc") {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.sortDirection = sortDirection;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onChangeSortBy(sortBy: string) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.sortBy = sortBy;
|
|
newFilter.currentPage = 1;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onChangeDisplayMode(displayMode: DisplayMode) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.displayMode = displayMode;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onAddCriterion(criterion: Criterion, oldId?: string) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
|
|
// Find if we are editing an existing criteria, then modify that. Or create a new one.
|
|
const existingIndex = newFilter.criteria.findIndex((c) => {
|
|
// If we modified an existing criterion, then look for the old id.
|
|
const id = !!oldId ? oldId : criterion.getId();
|
|
return c.getId() === id;
|
|
});
|
|
if (existingIndex === -1) {
|
|
newFilter.criteria.push(criterion);
|
|
} else {
|
|
newFilter.criteria[existingIndex] = criterion;
|
|
}
|
|
|
|
// Remove duplicate modifiers
|
|
newFilter.criteria = newFilter.criteria.filter((obj, pos, arr) => {
|
|
return arr.map((mapObj: any) => mapObj.getId()).indexOf(obj.getId()) === pos;
|
|
});
|
|
|
|
newFilter.currentPage = 1;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onRemoveCriterion(removedCriterion: Criterion) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.criteria = newFilter.criteria.filter((criterion) => criterion.getId() !== removedCriterion.getId());
|
|
newFilter.currentPage = 1;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onChangePage(page: number) {
|
|
const newFilter = _.cloneDeep(filter);
|
|
newFilter.currentPage = page;
|
|
setFilter(newFilter);
|
|
}
|
|
|
|
function onSelectChange(id: string, selected : boolean, shiftKey: boolean) {
|
|
if (shiftKey) {
|
|
multiSelect(id, selected);
|
|
} else {
|
|
singleSelect(id, selected);
|
|
}
|
|
}
|
|
|
|
function singleSelect(id: string, selected: boolean) {
|
|
setLastClickedId(id);
|
|
|
|
const newSelectedIds = _.clone(selectedIds);
|
|
if (selected) {
|
|
newSelectedIds.add(id);
|
|
} else {
|
|
newSelectedIds.delete(id);
|
|
}
|
|
|
|
setSelectedIds(newSelectedIds);
|
|
}
|
|
|
|
function multiSelect(id: string, selected : boolean) {
|
|
let startIndex = 0;
|
|
let thisIndex = -1;
|
|
|
|
if (!!lastClickedId) {
|
|
startIndex = getItems().findIndex((item) => {
|
|
return item.id === lastClickedId;
|
|
});
|
|
}
|
|
|
|
thisIndex = getItems().findIndex((item) => {
|
|
return item.id === id;
|
|
});
|
|
|
|
selectRange(startIndex, thisIndex);
|
|
}
|
|
|
|
function selectRange(startIndex : number, endIndex : number) {
|
|
if (startIndex > endIndex) {
|
|
let tmp = startIndex;
|
|
startIndex = endIndex;
|
|
endIndex = tmp;
|
|
}
|
|
|
|
const subset = getItems().slice(startIndex, endIndex + 1);
|
|
const newSelectedIds : Set<string> = new Set();
|
|
|
|
subset.forEach((item) => {
|
|
newSelectedIds.add(item.id);
|
|
});
|
|
|
|
setSelectedIds(newSelectedIds);
|
|
}
|
|
|
|
function onSelectAll() {
|
|
const newSelectedIds : Set<string> = new Set();
|
|
getItems().forEach((item) => {
|
|
newSelectedIds.add(item.id);
|
|
});
|
|
|
|
setSelectedIds(newSelectedIds);
|
|
setLastClickedId(undefined);
|
|
}
|
|
|
|
function onSelectNone() {
|
|
const newSelectedIds : Set<string> = new Set();
|
|
setSelectedIds(newSelectedIds);
|
|
setLastClickedId(undefined);
|
|
}
|
|
|
|
function onChangeZoom(newZoomIndex : number) {
|
|
setZoomIndex(newZoomIndex);
|
|
}
|
|
|
|
const otherOperations = options.otherOperations ? options.otherOperations.map((o) => {
|
|
return {
|
|
text: o.text,
|
|
onClick: () => {
|
|
o.onClick(result, filter, selectedIds);
|
|
}
|
|
}
|
|
}) : undefined;
|
|
|
|
const template = (
|
|
<div>
|
|
<ListFilter
|
|
onChangePageSize={onChangePageSize}
|
|
onChangeQuery={onChangeQuery}
|
|
onChangeSortDirection={onChangeSortDirection}
|
|
onChangeSortBy={onChangeSortBy}
|
|
onChangeDisplayMode={onChangeDisplayMode}
|
|
onAddCriterion={onAddCriterion}
|
|
onRemoveCriterion={onRemoveCriterion}
|
|
onSelectAll={onSelectAll}
|
|
onSelectNone={onSelectNone}
|
|
zoomIndex={options.zoomable ? zoomIndex : undefined}
|
|
onChangeZoom={options.zoomable ? onChangeZoom : undefined}
|
|
otherOperations={otherOperations}
|
|
filter={filter}
|
|
/>
|
|
{options.renderSelectedOptions && selectedIds.size > 0 ? options.renderSelectedOptions(result, selectedIds) : undefined}
|
|
{result.loading ? <Spinner animation="border" variant="light" /> : undefined}
|
|
{result.error ? <h1>{result.error.message}</h1> : undefined}
|
|
{options.renderContent(result, filter, selectedIds, zoomIndex)}
|
|
<Pagination
|
|
itemsPerPage={filter.itemsPerPage}
|
|
currentPage={filter.currentPage}
|
|
totalItems={totalCount}
|
|
onChangePage={onChangePage}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
return { filter, template, options, onSelectChange };
|
|
}
|
|
}
|