mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Gallery list improvement (#622)
* Add grid view to galleries * Show scene in gallery card * Add is missing scene gallery filter * Don't store galleries with no images
This commit is contained in:
@@ -8,4 +8,9 @@ fragment GalleryData on Gallery {
|
||||
name
|
||||
path
|
||||
}
|
||||
scene {
|
||||
id
|
||||
title
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
query FindGalleries($filter: FindFilterType) {
|
||||
findGalleries(filter: $filter) {
|
||||
query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType) {
|
||||
findGalleries(gallery_filter: $gallery_filter, filter: $filter) {
|
||||
count
|
||||
galleries {
|
||||
...GalleryData
|
||||
|
||||
@@ -28,7 +28,7 @@ type Query {
|
||||
findMovies(movie_filter: MovieFilterType, filter: FindFilterType): FindMoviesResultType!
|
||||
|
||||
findGallery(id: ID!): Gallery
|
||||
findGalleries(filter: FindFilterType): FindGalleriesResultType!
|
||||
findGalleries(gallery_filter: GalleryFilterType, filter: FindFilterType): FindGalleriesResultType!
|
||||
|
||||
findTag(id: ID!): Tag
|
||||
|
||||
|
||||
@@ -96,6 +96,11 @@ input StudioFilterType {
|
||||
parents: MultiCriterionInput
|
||||
}
|
||||
|
||||
input GalleryFilterType {
|
||||
"""Filter to only include galleries missing this property"""
|
||||
is_missing: String
|
||||
}
|
||||
|
||||
enum CriterionModifier {
|
||||
"""="""
|
||||
EQUALS,
|
||||
|
||||
@@ -4,6 +4,7 @@ type Gallery {
|
||||
checksum: String!
|
||||
path: String!
|
||||
title: String
|
||||
scene: Scene
|
||||
|
||||
"""The files in the gallery"""
|
||||
files: [GalleryFilesType!]! # Resolver
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
@@ -13,3 +14,12 @@ func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]*mo
|
||||
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
|
||||
return obj.GetFiles(baseURL), nil
|
||||
}
|
||||
|
||||
func (r *galleryResolver) Scene(ctx context.Context, obj *models.Gallery) (*models.Scene, error) {
|
||||
if !obj.SceneID.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
return qb.Find(int(obj.SceneID.Int64))
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *queryResolver) FindGallery(ctx context.Context, id string) (*models.Gallery, error) {
|
||||
@@ -12,9 +13,9 @@ func (r *queryResolver) FindGallery(ctx context.Context, id string) (*models.Gal
|
||||
return qb.Find(idInt)
|
||||
}
|
||||
|
||||
func (r *queryResolver) FindGalleries(ctx context.Context, filter *models.FindFilterType) (*models.FindGalleriesResultType, error) {
|
||||
func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType) (*models.FindGalleriesResultType, error) {
|
||||
qb := models.NewGalleryQueryBuilder()
|
||||
galleries, total := qb.Query(filter)
|
||||
galleries, total := qb.Query(galleryFilter, filter)
|
||||
return &models.FindGalleriesResultType{
|
||||
Count: total,
|
||||
Galleries: galleries,
|
||||
|
||||
@@ -26,7 +26,7 @@ func (t *CleanTask) Start(wg *sync.WaitGroup) {
|
||||
t.deleteScene(t.Scene.ID)
|
||||
}
|
||||
|
||||
if t.Gallery != nil && t.shouldClean(t.Gallery.Path) {
|
||||
if t.Gallery != nil && t.shouldCleanGallery(t.Gallery) {
|
||||
t.deleteGallery(t.Gallery.ID)
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,19 @@ func (t *CleanTask) shouldClean(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *CleanTask) shouldCleanGallery(g *models.Gallery) bool {
|
||||
if t.shouldClean(g.Path) {
|
||||
return true
|
||||
}
|
||||
|
||||
if t.Gallery.CountFiles() == 0 {
|
||||
logger.Infof("Gallery has 0 images. Cleaning: \"%s\"", g.Path)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *CleanTask) deleteScene(sceneID int) {
|
||||
ctx := context.TODO()
|
||||
qb := models.NewSceneQueryBuilder()
|
||||
|
||||
@@ -64,7 +64,6 @@ func (t *ScanTask) scanGallery() {
|
||||
_, err = qb.Update(*gallery, tx)
|
||||
}
|
||||
} else {
|
||||
logger.Infof("%s doesn't exist. Creating new item...", t.FilePath)
|
||||
currentTime := time.Now()
|
||||
|
||||
newGallery := models.Gallery{
|
||||
@@ -73,7 +72,12 @@ func (t *ScanTask) scanGallery() {
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
}
|
||||
_, err = qb.Create(newGallery, tx)
|
||||
|
||||
// don't create gallery if it has no images
|
||||
if newGallery.CountFiles() > 0 {
|
||||
logger.Infof("%s doesn't exist. Creating new item...", t.FilePath)
|
||||
_, err = qb.Create(newGallery, tx)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -4,17 +4,18 @@ import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
_ "golang.org/x/image/webp"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
type Gallery struct {
|
||||
@@ -28,6 +29,16 @@ type Gallery struct {
|
||||
|
||||
const DefaultGthumbWidth int = 200
|
||||
|
||||
func (g *Gallery) CountFiles() int {
|
||||
filteredFiles, readCloser, err := g.listZipContents()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
defer readCloser.Close()
|
||||
|
||||
return len(filteredFiles)
|
||||
}
|
||||
|
||||
func (g *Gallery) GetFiles(baseURL string) []*GalleryFilesType {
|
||||
var galleryFiles []*GalleryFilesType
|
||||
filteredFiles, readCloser, err := g.listZipContents()
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
)
|
||||
|
||||
const galleryTable = "galleries"
|
||||
|
||||
type GalleryQueryBuilder struct{}
|
||||
|
||||
func NewGalleryQueryBuilder() GalleryQueryBuilder {
|
||||
@@ -112,25 +114,36 @@ func (qb *GalleryQueryBuilder) All() ([]*Gallery, error) {
|
||||
return qb.queryGalleries(selectAll("galleries")+qb.getGallerySort(nil), nil, nil)
|
||||
}
|
||||
|
||||
func (qb *GalleryQueryBuilder) Query(findFilter *FindFilterType) ([]*Gallery, int) {
|
||||
func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int) {
|
||||
if galleryFilter == nil {
|
||||
galleryFilter = &GalleryFilterType{}
|
||||
}
|
||||
if findFilter == nil {
|
||||
findFilter = &FindFilterType{}
|
||||
}
|
||||
|
||||
var whereClauses []string
|
||||
var havingClauses []string
|
||||
var args []interface{}
|
||||
body := selectDistinctIDs("galleries")
|
||||
query := queryBuilder{
|
||||
tableName: galleryTable,
|
||||
}
|
||||
|
||||
query.body = selectDistinctIDs("galleries")
|
||||
|
||||
if q := findFilter.Q; q != nil && *q != "" {
|
||||
searchColumns := []string{"galleries.path", "galleries.checksum"}
|
||||
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
||||
whereClauses = append(whereClauses, clause)
|
||||
args = append(args, thisArgs...)
|
||||
query.addWhere(clause)
|
||||
query.addArg(thisArgs...)
|
||||
}
|
||||
|
||||
sortAndPagination := qb.getGallerySort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult := executeFindQuery("galleries", body, args, sortAndPagination, whereClauses, havingClauses)
|
||||
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||
switch *isMissingFilter {
|
||||
case "scene":
|
||||
query.addWhere("galleries.scene_id IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
query.sortAndPagination = qb.getGallerySort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult := query.executeFind()
|
||||
|
||||
var galleries []*Gallery
|
||||
for _, id := range idsResult {
|
||||
|
||||
@@ -98,6 +98,58 @@ func TestGalleryFindBySceneID(t *testing.T) {
|
||||
assert.Nil(t, gallery)
|
||||
}
|
||||
|
||||
func TestGalleryQueryQ(t *testing.T) {
|
||||
const galleryIdx = 0
|
||||
|
||||
q := getGalleryStringValue(galleryIdx, pathField)
|
||||
|
||||
sqb := models.NewGalleryQueryBuilder()
|
||||
|
||||
galleryQueryQ(t, sqb, q, galleryIdx)
|
||||
}
|
||||
|
||||
func galleryQueryQ(t *testing.T, qb models.GalleryQueryBuilder, q string, expectedGalleryIdx int) {
|
||||
filter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
galleries, _ := qb.Query(nil, &filter)
|
||||
|
||||
assert.Len(t, galleries, 1)
|
||||
gallery := galleries[0]
|
||||
assert.Equal(t, galleryIDs[expectedGalleryIdx], gallery.ID)
|
||||
|
||||
// no Q should return all results
|
||||
filter.Q = nil
|
||||
galleries, _ = qb.Query(nil, &filter)
|
||||
|
||||
assert.Len(t, galleries, totalGalleries)
|
||||
}
|
||||
|
||||
func TestGalleryQueryIsMissingScene(t *testing.T) {
|
||||
qb := models.NewGalleryQueryBuilder()
|
||||
isMissing := "scene"
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
IsMissing: &isMissing,
|
||||
}
|
||||
|
||||
q := getGalleryStringValue(galleryIdxWithScene, titleField)
|
||||
findFilter := models.FindFilterType{
|
||||
Q: &q,
|
||||
}
|
||||
|
||||
galleries, _ := qb.Query(&galleryFilter, &findFilter)
|
||||
|
||||
assert.Len(t, galleries, 0)
|
||||
|
||||
findFilter.Q = nil
|
||||
galleries, _ = qb.Query(&galleryFilter, &findFilter)
|
||||
|
||||
// ensure non of the ids equal the one with gallery
|
||||
for _, gallery := range galleries {
|
||||
assert.NotEqual(t, galleryIDs[galleryIdxWithScene], gallery.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO ValidGalleriesForScenePath
|
||||
// TODO Count
|
||||
// TODO All
|
||||
|
||||
@@ -24,7 +24,7 @@ const performersNameCase = 3
|
||||
const performersNameNoCase = 2
|
||||
const moviesNameCase = 2
|
||||
const moviesNameNoCase = 1
|
||||
const totalGalleries = 1
|
||||
const totalGalleries = 2
|
||||
const tagsNameNoCase = 2
|
||||
const tagsNameCase = 5
|
||||
const studiosNameCase = 4
|
||||
|
||||
@@ -6,6 +6,9 @@ const markup = `
|
||||
* Add support for parent/child studios.
|
||||
|
||||
### 🎨 Improvements
|
||||
* Add gallery grid view.
|
||||
* Add is-missing scene filter for gallery query.
|
||||
* Don't import galleries with no images, and delete galleries with no images during clean.
|
||||
* Show pagination at top as well as bottom of the page.
|
||||
* Add split xpath post-processing action.
|
||||
* Improved the layout of the scene page.
|
||||
|
||||
71
ui/v2.5/src/components/Galleries/GalleryCard.tsx
Normal file
71
ui/v2.5/src/components/Galleries/GalleryCard.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Card, Button, ButtonGroup } from "react-bootstrap";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { FormattedPlural } from "react-intl";
|
||||
import { HoverPopover, Icon, TagLink } from "../Shared";
|
||||
|
||||
interface IProps {
|
||||
gallery: GQL.GalleryDataFragment;
|
||||
zoomIndex: number;
|
||||
}
|
||||
|
||||
export const GalleryCard: React.FC<IProps> = ({ gallery, zoomIndex }) => {
|
||||
function maybeRenderScenePopoverButton() {
|
||||
if (!gallery.scene) return;
|
||||
|
||||
const popoverContent = (
|
||||
<TagLink key={gallery.scene.id} scene={gallery.scene} />
|
||||
);
|
||||
|
||||
return (
|
||||
<HoverPopover placement="bottom" content={popoverContent}>
|
||||
<Link to={`/scenes/${gallery.scene.id}`}>
|
||||
<Button className="minimal">
|
||||
<Icon icon="play-circle" />
|
||||
</Button>
|
||||
</Link>
|
||||
</HoverPopover>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderPopoverButtonGroup() {
|
||||
if (gallery.scene) {
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<ButtonGroup className="card-popovers">
|
||||
{maybeRenderScenePopoverButton()}
|
||||
</ButtonGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={`gallery-card zoom-${zoomIndex}`}>
|
||||
<Link to={`/galleries/${gallery.id}`} className="gallery-card-header">
|
||||
{gallery.files.length > 0 ? (
|
||||
<img
|
||||
className="gallery-card-image"
|
||||
alt={gallery.path}
|
||||
src={`${gallery.files[0].path}?thumb=true`}
|
||||
/>
|
||||
) : undefined}
|
||||
</Link>
|
||||
<div className="card-section">
|
||||
<h5 className="card-section-title">{gallery.path}</h5>
|
||||
<span>
|
||||
{gallery.files.length}
|
||||
<FormattedPlural
|
||||
value={gallery.files.length ?? 0}
|
||||
one="image"
|
||||
other="images"
|
||||
/>
|
||||
.
|
||||
</span>
|
||||
</div>
|
||||
{maybeRenderPopoverButtonGroup()}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -5,21 +5,35 @@ import { FindGalleriesQueryResult } from "src/core/generated-graphql";
|
||||
import { useGalleriesList } from "src/hooks";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { DisplayMode } from "src/models/list-filter/types";
|
||||
import { GalleryCard } from "./GalleryCard";
|
||||
|
||||
export const GalleryList: React.FC = () => {
|
||||
const listData = useGalleriesList({
|
||||
zoomable: true,
|
||||
renderContent,
|
||||
});
|
||||
|
||||
function renderContent(
|
||||
result: FindGalleriesQueryResult,
|
||||
filter: ListFilterModel
|
||||
filter: ListFilterModel,
|
||||
selectedIds: Set<string>,
|
||||
zoomIndex: number
|
||||
) {
|
||||
if (!result.data || !result.data.findGalleries) {
|
||||
return;
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.Grid) {
|
||||
return <h1>TODO</h1>;
|
||||
return (
|
||||
<div className="row justify-content-center">
|
||||
{result.data.findGalleries.galleries.map((gallery) => (
|
||||
<GalleryCard
|
||||
key={gallery.id}
|
||||
gallery={gallery}
|
||||
zoomIndex={zoomIndex}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (filter.displayMode === DisplayMode.List) {
|
||||
return (
|
||||
|
||||
@@ -5,3 +5,12 @@
|
||||
}
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
|
||||
.gallery-card {
|
||||
padding: 0.5rem;
|
||||
|
||||
&-image {
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<ButtonGroup className="scene-popovers">
|
||||
<ButtonGroup className="card-popovers">
|
||||
{maybeRenderTagPopoverButton()}
|
||||
{maybeRenderPerformerPopoverButton()}
|
||||
{maybeRenderMoviePopoverButton()}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.scene-popovers {
|
||||
.card-popovers {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
SceneMarkerDataFragment,
|
||||
TagDataFragment,
|
||||
MovieDataFragment,
|
||||
SceneDataFragment,
|
||||
} from "src/core/generated-graphql";
|
||||
import { NavUtils, TextUtils } from "src/utils";
|
||||
|
||||
@@ -14,6 +15,7 @@ interface IProps {
|
||||
performer?: Partial<PerformerDataFragment>;
|
||||
marker?: Partial<SceneMarkerDataFragment>;
|
||||
movie?: Partial<MovieDataFragment>;
|
||||
scene?: Partial<SceneDataFragment>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -34,6 +36,11 @@ export const TagLink: React.FC<IProps> = (props: IProps) => {
|
||||
title = `${props.marker.title} - ${TextUtils.secondsToTimestamp(
|
||||
props.marker.seconds || 0
|
||||
)}`;
|
||||
} else if (props.scene) {
|
||||
link = `/scenes/${props.scene.id}`;
|
||||
title = props.scene.title
|
||||
? props.scene.title
|
||||
: TextUtils.fileNameFromPath(props.scene.path ?? "");
|
||||
}
|
||||
return (
|
||||
<Badge className={`tag-item ${props.className}`} variant="secondary">
|
||||
|
||||
@@ -33,6 +33,7 @@ export const useFindGalleries = (filter: ListFilterModel) =>
|
||||
GQL.useFindGalleriesQuery({
|
||||
variables: {
|
||||
filter: filter.makeFindFilter(),
|
||||
gallery_filter: filter.makeGalleryFilter(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,8 @@ textarea.text-input {
|
||||
.zoom-0 {
|
||||
width: 240px;
|
||||
|
||||
.scene-card-video {
|
||||
.scene-card-video,
|
||||
.gallery-card-image {
|
||||
max-height: 180px;
|
||||
}
|
||||
|
||||
@@ -110,7 +111,8 @@ textarea.text-input {
|
||||
.zoom-1 {
|
||||
width: 320px;
|
||||
|
||||
.scene-card-video {
|
||||
.scene-card-video,
|
||||
.gallery-card-image {
|
||||
max-height: 240px;
|
||||
}
|
||||
|
||||
@@ -122,7 +124,8 @@ textarea.text-input {
|
||||
.zoom-2 {
|
||||
width: 480px;
|
||||
|
||||
.scene-card-video {
|
||||
.scene-card-video,
|
||||
.gallery-card-image {
|
||||
max-height: 360px;
|
||||
}
|
||||
|
||||
@@ -134,7 +137,8 @@ textarea.text-input {
|
||||
.zoom-3 {
|
||||
width: 640px;
|
||||
|
||||
.scene-card-video {
|
||||
.scene-card-video,
|
||||
.gallery-card-image {
|
||||
max-height: 480px;
|
||||
}
|
||||
|
||||
@@ -144,7 +148,8 @@ textarea.text-input {
|
||||
}
|
||||
}
|
||||
|
||||
.scene-card-video {
|
||||
.scene-card-video,
|
||||
.gallery-card-image {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ export type CriterionType =
|
||||
| "hasMarkers"
|
||||
| "sceneIsMissing"
|
||||
| "performerIsMissing"
|
||||
| "galleryIsMissing"
|
||||
| "tags"
|
||||
| "sceneTags"
|
||||
| "performers"
|
||||
@@ -58,6 +59,8 @@ export abstract class Criterion {
|
||||
return "Is Missing";
|
||||
case "performerIsMissing":
|
||||
return "Is Missing";
|
||||
case "galleryIsMissing":
|
||||
return "Is Missing";
|
||||
case "tags":
|
||||
return "Tags";
|
||||
case "sceneTags":
|
||||
|
||||
@@ -53,3 +53,13 @@ export class PerformerIsMissingCriterionOption implements ICriterionOption {
|
||||
public label: string = Criterion.getLabel("performerIsMissing");
|
||||
public value: CriterionType = "performerIsMissing";
|
||||
}
|
||||
|
||||
export class GalleryIsMissingCriterion extends IsMissingCriterion {
|
||||
public type: CriterionType = "galleryIsMissing";
|
||||
public options: string[] = ["scene"];
|
||||
}
|
||||
|
||||
export class GalleryIsMissingCriterionOption implements ICriterionOption {
|
||||
public label: string = Criterion.getLabel("galleryIsMissing");
|
||||
public value: CriterionType = "galleryIsMissing";
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { HasMarkersCriterion } from "./has-markers";
|
||||
import {
|
||||
PerformerIsMissingCriterion,
|
||||
SceneIsMissingCriterion,
|
||||
GalleryIsMissingCriterion,
|
||||
} from "./is-missing";
|
||||
import { NoneCriterion } from "./none";
|
||||
import { PerformersCriterion } from "./performers";
|
||||
@@ -42,6 +43,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
return new SceneIsMissingCriterion();
|
||||
case "performerIsMissing":
|
||||
return new PerformerIsMissingCriterion();
|
||||
case "galleryIsMissing":
|
||||
return new GalleryIsMissingCriterion();
|
||||
case "tags":
|
||||
return new TagsCriterion("tags");
|
||||
case "sceneTags":
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
SortDirectionEnum,
|
||||
MovieFilterType,
|
||||
StudioFilterType,
|
||||
GalleryFilterType,
|
||||
} from "src/core/generated-graphql";
|
||||
import { stringToGender } from "src/core/StashService";
|
||||
import {
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
IsMissingCriterion,
|
||||
PerformerIsMissingCriterionOption,
|
||||
SceneIsMissingCriterionOption,
|
||||
GalleryIsMissingCriterionOption,
|
||||
} from "./criteria/is-missing";
|
||||
import { NoneCriterionOption } from "./criteria/none";
|
||||
import {
|
||||
@@ -182,8 +184,11 @@ export class ListFilterModel {
|
||||
case FilterMode.Galleries:
|
||||
this.sortBy = "path";
|
||||
this.sortByOptions = ["path"];
|
||||
this.displayModeOptions = [DisplayMode.List];
|
||||
this.criterionOptions = [new NoneCriterionOption()];
|
||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||
this.criterionOptions = [
|
||||
new NoneCriterionOption(),
|
||||
new GalleryIsMissingCriterionOption(),
|
||||
];
|
||||
break;
|
||||
case FilterMode.SceneMarkers:
|
||||
this.sortBy = "title";
|
||||
@@ -601,6 +606,21 @@ export class ListFilterModel {
|
||||
// no default
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public makeGalleryFilter(): GalleryFilterType {
|
||||
const result: GalleryFilterType = {};
|
||||
this.criteria.forEach((criterion) => {
|
||||
switch (criterion.type) {
|
||||
case "galleryIsMissing":
|
||||
result.is_missing = (criterion as IsMissingCriterion).value;
|
||||
break;
|
||||
// no default
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user