mirror of https://github.com/stashapp/stash.git
Add date filters (#2834)
* graphql: support date and timestamp filter types * sql: add support for date & timestamp criterions * ui: add support for date and timestamp criterions * scenes: add support for filtering by date, created at and updated at * image: support filtering by created at and updated at * gallery: support filtering by date, created at and updated at * movie: support filtering by date, created at and updated at * studio: support filtering by date, created at and updated at * tag: support filtering by date, created at and updated at * performer: support filtering by bitrh & death date and created & updated at * marker: support filtering by created & updated at and scene date, created & updated at
This commit is contained in:
parent
ce17230c13
commit
f66333bac9
|
@ -105,6 +105,14 @@ input PerformerFilterType {
|
|||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
"""Filter by birthdate"""
|
||||
birthdate: DateCriterionInput
|
||||
"""Filter by death date"""
|
||||
death_date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input SceneMarkerFilterType {
|
||||
|
@ -116,6 +124,16 @@ input SceneMarkerFilterType {
|
|||
scene_tags: HierarchicalMultiCriterionInput
|
||||
"""Filter to only include scene markers with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
"""Filter by scene date"""
|
||||
scene_date: DateCriterionInput
|
||||
"""Filter by cscene reation time"""
|
||||
scene_created_at: TimestampCriterionInput
|
||||
"""Filter by lscene ast update time"""
|
||||
scene_updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input SceneFilterType {
|
||||
|
@ -183,6 +201,12 @@ input SceneFilterType {
|
|||
interactive_speed: IntCriterionInput
|
||||
"""Filter by captions"""
|
||||
captions: StringCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input MovieFilterType {
|
||||
|
@ -203,6 +227,12 @@ input MovieFilterType {
|
|||
url: StringCriterionInput
|
||||
"""Filter to only include movies where performer appears in a scene"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input StudioFilterType {
|
||||
|
@ -232,6 +262,10 @@ input StudioFilterType {
|
|||
aliases: StringCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input GalleryFilterType {
|
||||
|
@ -279,6 +313,12 @@ input GalleryFilterType {
|
|||
image_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
url: StringCriterionInput
|
||||
"""Filter by date"""
|
||||
date: DateCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input TagFilterType {
|
||||
|
@ -327,6 +367,12 @@ input TagFilterType {
|
|||
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
input ImageFilterType {
|
||||
|
@ -370,6 +416,10 @@ input ImageFilterType {
|
|||
performer_favorite: Boolean
|
||||
"""Filter to only include images with these galleries"""
|
||||
galleries: MultiCriterionInput
|
||||
"""Filter by creation time"""
|
||||
created_at: TimestampCriterionInput
|
||||
"""Filter by last update time"""
|
||||
updated_at: TimestampCriterionInput
|
||||
}
|
||||
|
||||
enum CriterionModifier {
|
||||
|
@ -426,6 +476,18 @@ input HierarchicalMultiCriterionInput {
|
|||
depth: Int
|
||||
}
|
||||
|
||||
input DateCriterionInput {
|
||||
value: String!
|
||||
value2: String
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input TimestampCriterionInput {
|
||||
value: String!
|
||||
value2: String
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
enum FilterMode {
|
||||
SCENES,
|
||||
PERFORMERS,
|
||||
|
|
|
@ -124,3 +124,15 @@ type MultiCriterionInput struct {
|
|||
Value []string `json:"value"`
|
||||
Modifier CriterionModifier `json:"modifier"`
|
||||
}
|
||||
|
||||
type DateCriterionInput struct {
|
||||
Value string `json:"value"`
|
||||
Value2 *string `json:"value2"`
|
||||
Modifier CriterionModifier `json:"modifier"`
|
||||
}
|
||||
|
||||
type TimestampCriterionInput struct {
|
||||
Value string `json:"value"`
|
||||
Value2 *string `json:"value2"`
|
||||
Modifier CriterionModifier `json:"modifier"`
|
||||
}
|
||||
|
|
|
@ -49,6 +49,12 @@ type GalleryFilterType struct {
|
|||
ImageCount *IntCriterionInput `json:"image_count"`
|
||||
// Filter by url
|
||||
URL *StringCriterionInput `json:"url"`
|
||||
// Filter by date
|
||||
Date *DateCriterionInput `json:"date"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type GalleryUpdateInput struct {
|
||||
|
|
|
@ -40,6 +40,10 @@ type ImageFilterType struct {
|
|||
PerformerFavorite *bool `json:"performer_favorite"`
|
||||
// Filter to only include images with these galleries
|
||||
Galleries *MultiCriterionInput `json:"galleries"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ImageDestroyInput struct {
|
||||
|
|
|
@ -18,6 +18,12 @@ type MovieFilterType struct {
|
|||
URL *StringCriterionInput `json:"url"`
|
||||
// Filter to only include movies where performer appears in a scene
|
||||
Performers *MultiCriterionInput `json:"performers"`
|
||||
// Filter by date
|
||||
Date *DateCriterionInput `json:"date"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type MovieReader interface {
|
||||
|
|
|
@ -125,6 +125,14 @@ type PerformerFilterType struct {
|
|||
Studios *HierarchicalMultiCriterionInput `json:"studios"`
|
||||
// Filter by autotag ignore value
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
// Filter by birthdate
|
||||
Birthdate *DateCriterionInput `json:"birth_date"`
|
||||
// Filter by death date
|
||||
DeathDate *DateCriterionInput `json:"death_date"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PerformerFinder interface {
|
||||
|
|
|
@ -75,6 +75,12 @@ type SceneFilterType struct {
|
|||
InteractiveSpeed *IntCriterionInput `json:"interactive_speed"`
|
||||
|
||||
Captions *StringCriterionInput `json:"captions"`
|
||||
// Filter by date
|
||||
Date *DateCriterionInput `json:"date"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SceneQueryOptions struct {
|
||||
|
|
|
@ -11,6 +11,16 @@ type SceneMarkerFilterType struct {
|
|||
SceneTags *HierarchicalMultiCriterionInput `json:"scene_tags"`
|
||||
// Filter to only include scene markers with these performers
|
||||
Performers *MultiCriterionInput `json:"performers"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
// Filter by scenes date
|
||||
SceneDate *DateCriterionInput `json:"scene_date"`
|
||||
// Filter by scenes created at
|
||||
SceneCreatedAt *TimestampCriterionInput `json:"scene_created_at"`
|
||||
// Filter by scenes updated at
|
||||
SceneUpdatedAt *TimestampCriterionInput `json:"scene_updated_at"`
|
||||
}
|
||||
|
||||
type MarkerStringsResultType struct {
|
||||
|
|
|
@ -28,6 +28,10 @@ type StudioFilterType struct {
|
|||
Aliases *StringCriterionInput `json:"aliases"`
|
||||
// Filter by autotag ignore value
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type StudioFinder interface {
|
||||
|
|
|
@ -34,6 +34,10 @@ type TagFilterType struct {
|
|||
ChildCount *IntCriterionInput `json:"child_count"`
|
||||
// Filter by autotag ignore value
|
||||
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
|
||||
// Filter by created at
|
||||
CreatedAt *TimestampCriterionInput `json:"created_at"`
|
||||
// Filter by updated at
|
||||
UpdatedAt *TimestampCriterionInput `json:"updated_at"`
|
||||
}
|
||||
|
||||
type TagFinder interface {
|
||||
|
|
|
@ -543,6 +543,24 @@ func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilde
|
|||
}
|
||||
}
|
||||
|
||||
func dateCriterionHandler(c *models.DateCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
clause, args := getDateCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func timestampCriterionHandler(c *models.TimestampCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
clause, args := getTimestampCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle for MultiCriterion where there is a join table between the new
|
||||
// objects
|
||||
type joinedMultiCriterionHandlerBuilder struct {
|
||||
|
|
|
@ -665,6 +665,9 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
|
|||
query.handleCriterion(ctx, galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
|
||||
query.handleCriterion(ctx, galleryPerformerFavoriteCriterionHandler(galleryFilter.PerformerFavorite))
|
||||
query.handleCriterion(ctx, galleryPerformerAgeCriterionHandler(galleryFilter.PerformerAge))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(galleryFilter.Date, "galleries.date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(galleryFilter.CreatedAt, "galleries.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(galleryFilter.UpdatedAt, "galleries.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -647,6 +647,8 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
|
|||
query.handleCriterion(ctx, imageStudioCriterionHandler(qb, imageFilter.Studios))
|
||||
query.handleCriterion(ctx, imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
|
||||
query.handleCriterion(ctx, imagePerformerFavoriteCriterionHandler(imageFilter.PerformerFavorite))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(imageFilter.CreatedAt, "images.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(imageFilter.UpdatedAt, "images.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -153,6 +153,9 @@ func (qb *movieQueryBuilder) makeFilter(ctx context.Context, movieFilter *models
|
|||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.URL, "movies.url"))
|
||||
query.handleCriterion(ctx, movieStudioCriterionHandler(qb, movieFilter.Studios))
|
||||
query.handleCriterion(ctx, moviePerformersCriterionHandler(qb, movieFilter.Performers))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(movieFilter.Date, "movies.date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(movieFilter.CreatedAt, "movies.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(movieFilter.UpdatedAt, "movies.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -541,6 +541,10 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
|||
query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount))
|
||||
query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount))
|
||||
query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(filter.Birthdate, tableName+".birthdate"))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(filter.DeathDate, tableName+".death_date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(filter.CreatedAt, tableName+".created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(filter.UpdatedAt, tableName+".updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -877,6 +877,9 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
|
|||
query.handleCriterion(ctx, scenePerformerFavoriteCriterionHandler(sceneFilter.PerformerFavorite))
|
||||
query.handleCriterion(ctx, scenePerformerAgeCriterionHandler(sceneFilter.PerformerAge))
|
||||
query.handleCriterion(ctx, scenePhashDuplicatedCriterionHandler(sceneFilter.Duplicated, qb.addSceneFilesTable))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(sceneFilter.Date, "scenes.date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneFilter.CreatedAt, "scenes.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneFilter.UpdatedAt, "scenes.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -131,6 +131,11 @@ func (qb *sceneMarkerQueryBuilder) makeFilter(ctx context.Context, sceneMarkerFi
|
|||
query.handleCriterion(ctx, sceneMarkerTagsCriterionHandler(qb, sceneMarkerFilter.Tags))
|
||||
query.handleCriterion(ctx, sceneMarkerSceneTagsCriterionHandler(qb, sceneMarkerFilter.SceneTags))
|
||||
query.handleCriterion(ctx, sceneMarkerPerformersCriterionHandler(qb, sceneMarkerFilter.Performers))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.CreatedAt, "scene_markers.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.UpdatedAt, "scene_markers.updated_at"))
|
||||
query.handleCriterion(ctx, dateCriterionHandler(sceneMarkerFilter.SceneDate, "scenes.date"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.SceneCreatedAt, "scenes.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(sceneMarkerFilter.SceneUpdatedAt, "scenes.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
@ -181,6 +182,76 @@ func getIntWhereClause(column string, modifier models.CriterionModifier, value i
|
|||
panic("unsupported int modifier type " + modifier)
|
||||
}
|
||||
|
||||
func getDateCriterionWhereClause(column string, input models.DateCriterionInput) (string, []interface{}) {
|
||||
return getDateWhereClause(column, input.Modifier, input.Value, input.Value2)
|
||||
}
|
||||
|
||||
func getDateWhereClause(column string, modifier models.CriterionModifier, value string, upper *string) (string, []interface{}) {
|
||||
if upper == nil {
|
||||
u := time.Now().AddDate(0, 0, 1).Format(time.RFC3339)
|
||||
upper = &u
|
||||
}
|
||||
|
||||
args := []interface{}{value}
|
||||
betweenArgs := []interface{}{value, *upper}
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
return fmt.Sprintf("(%s IS NULL OR %s = '')", column, column), nil
|
||||
case models.CriterionModifierNotNull:
|
||||
return fmt.Sprintf("(%s IS NOT NULL AND %s != '')", column, column), nil
|
||||
case models.CriterionModifierEquals:
|
||||
return fmt.Sprintf("%s = ?", column), args
|
||||
case models.CriterionModifierNotEquals:
|
||||
return fmt.Sprintf("%s != ?", column), args
|
||||
case models.CriterionModifierBetween:
|
||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierNotBetween:
|
||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierLessThan:
|
||||
return fmt.Sprintf("%s < ?", column), args
|
||||
case models.CriterionModifierGreaterThan:
|
||||
return fmt.Sprintf("%s > ?", column), args
|
||||
}
|
||||
|
||||
panic("unsupported date modifier type")
|
||||
}
|
||||
|
||||
func getTimestampCriterionWhereClause(column string, input models.TimestampCriterionInput) (string, []interface{}) {
|
||||
return getTimestampWhereClause(column, input.Modifier, input.Value, input.Value2)
|
||||
}
|
||||
|
||||
func getTimestampWhereClause(column string, modifier models.CriterionModifier, value string, upper *string) (string, []interface{}) {
|
||||
if upper == nil {
|
||||
u := time.Now().AddDate(0, 0, 1).Format(time.RFC3339)
|
||||
upper = &u
|
||||
}
|
||||
|
||||
args := []interface{}{value}
|
||||
betweenArgs := []interface{}{value, *upper}
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
return fmt.Sprintf("%s IS NULL", column), nil
|
||||
case models.CriterionModifierNotNull:
|
||||
return fmt.Sprintf("%s IS NOT NULL", column), nil
|
||||
case models.CriterionModifierEquals:
|
||||
return fmt.Sprintf("%s = ?", column), args
|
||||
case models.CriterionModifierNotEquals:
|
||||
return fmt.Sprintf("%s != ?", column), args
|
||||
case models.CriterionModifierBetween:
|
||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierNotBetween:
|
||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), betweenArgs
|
||||
case models.CriterionModifierLessThan:
|
||||
return fmt.Sprintf("%s < ?", column), args
|
||||
case models.CriterionModifierGreaterThan:
|
||||
return fmt.Sprintf("%s > ?", column), args
|
||||
}
|
||||
|
||||
panic("unsupported date modifier type")
|
||||
}
|
||||
|
||||
// returns where clause and having clause
|
||||
func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, foreignFK string, criterion *models.MultiCriterionInput) (string, string) {
|
||||
whereClause := ""
|
||||
|
|
|
@ -250,6 +250,8 @@ func (qb *studioQueryBuilder) makeFilter(ctx context.Context, studioFilter *mode
|
|||
query.handleCriterion(ctx, studioGalleryCountCriterionHandler(qb, studioFilter.GalleryCount))
|
||||
query.handleCriterion(ctx, studioParentCriterionHandler(qb, studioFilter.Parents))
|
||||
query.handleCriterion(ctx, studioAliasCriterionHandler(qb, studioFilter.Aliases))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(studioFilter.CreatedAt, "studios.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(studioFilter.UpdatedAt, "studios.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -338,6 +338,8 @@ func (qb *tagQueryBuilder) makeFilter(ctx context.Context, tagFilter *models.Tag
|
|||
query.handleCriterion(ctx, tagChildrenCriterionHandler(qb, tagFilter.Children))
|
||||
query.handleCriterion(ctx, tagParentCountCriterionHandler(qb, tagFilter.ParentCount))
|
||||
query.handleCriterion(ctx, tagChildCountCriterionHandler(qb, tagFilter.ChildCount))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(tagFilter.CreatedAt, "tags.created_at"))
|
||||
query.handleCriterion(ctx, timestampCriterionHandler(tagFilter.UpdatedAt, "tags.updated_at"))
|
||||
|
||||
return query
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
IHierarchicalLabeledIdCriterion,
|
||||
NumberCriterion,
|
||||
ILabeledIdCriterion,
|
||||
DateCriterion,
|
||||
TimestampCriterion,
|
||||
} from "src/models/list-filter/criteria/criterion";
|
||||
import {
|
||||
NoneCriterion,
|
||||
|
@ -20,6 +22,8 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
import {
|
||||
criterionIsHierarchicalLabelValue,
|
||||
criterionIsNumberValue,
|
||||
criterionIsDateValue,
|
||||
criterionIsTimestampValue,
|
||||
CriterionType,
|
||||
} from "src/models/list-filter/types";
|
||||
import { DurationFilter } from "./Filters/DurationFilter";
|
||||
|
@ -28,6 +32,8 @@ import { LabeledIdFilter } from "./Filters/LabeledIdFilter";
|
|||
import { HierarchicalLabelValueFilter } from "./Filters/HierarchicalLabelValueFilter";
|
||||
import { OptionsFilter } from "./Filters/OptionsFilter";
|
||||
import { InputFilter } from "./Filters/InputFilter";
|
||||
import { DateFilter } from "./Filters/DateFilter";
|
||||
import { TimestampFilter } from "./Filters/TimestampFilter";
|
||||
import { CountryCriterion } from "src/models/list-filter/criteria/country";
|
||||
import { CountrySelect } from "../Shared";
|
||||
|
||||
|
@ -152,6 +158,8 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
|||
options &&
|
||||
!criterionIsHierarchicalLabelValue(criterion.value) &&
|
||||
!criterionIsNumberValue(criterion.value) &&
|
||||
!criterionIsDateValue(criterion.value) &&
|
||||
!criterionIsTimestampValue(criterion.value) &&
|
||||
!Array.isArray(criterion.value)
|
||||
) {
|
||||
defaultValue.current = criterion.value;
|
||||
|
@ -170,6 +178,19 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (criterion instanceof DateCriterion) {
|
||||
return (
|
||||
<DateFilter criterion={criterion} onValueChanged={onValueChanged} />
|
||||
);
|
||||
}
|
||||
if (criterion instanceof TimestampCriterion) {
|
||||
return (
|
||||
<TimestampFilter
|
||||
criterion={criterion}
|
||||
onValueChanged={onValueChanged}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (criterion instanceof NumberCriterion) {
|
||||
return (
|
||||
<NumberFilter criterion={criterion} onValueChanged={onValueChanged} />
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import React, { useRef } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||
import { IDateValue } from "../../../models/list-filter/types";
|
||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
||||
|
||||
interface IDateFilterProps {
|
||||
criterion: Criterion<IDateValue>;
|
||||
onValueChanged: (value: IDateValue) => void;
|
||||
}
|
||||
|
||||
export const DateFilter: React.FC<IDateFilterProps> = ({
|
||||
criterion,
|
||||
onValueChanged,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const valueStage = useRef<IDateValue>(criterion.value);
|
||||
|
||||
function onChanged(
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
property: "value" | "value2"
|
||||
) {
|
||||
const { value } = event.target;
|
||||
valueStage.current[property] = value;
|
||||
}
|
||||
|
||||
function onBlurInput() {
|
||||
onValueChanged(valueStage.current);
|
||||
}
|
||||
|
||||
let equalsControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.Equals ||
|
||||
criterion.modifier === CriterionModifier.NotEquals
|
||||
) {
|
||||
equalsControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(e, "value")
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={criterion.value?.value ?? ""}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.value" }) + " (YYYY-MM-DD)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
let lowerControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.GreaterThan ||
|
||||
criterion.modifier === CriterionModifier.Between ||
|
||||
criterion.modifier === CriterionModifier.NotBetween
|
||||
) {
|
||||
lowerControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(e, "value")
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={criterion.value?.value ?? ""}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.greater_than" }) +
|
||||
" (YYYY-MM-DD)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
let upperControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.LessThan ||
|
||||
criterion.modifier === CriterionModifier.Between ||
|
||||
criterion.modifier === CriterionModifier.NotBetween
|
||||
) {
|
||||
upperControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(
|
||||
e,
|
||||
criterion.modifier === CriterionModifier.LessThan
|
||||
? "value"
|
||||
: "value2"
|
||||
)
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={
|
||||
(criterion.modifier === CriterionModifier.LessThan
|
||||
? criterion.value?.value
|
||||
: criterion.value?.value2) ?? ""
|
||||
}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.less_than" }) + " (YYYY-MM-DD)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{equalsControl}
|
||||
{lowerControl}
|
||||
{upperControl}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,123 @@
|
|||
import React, { useRef } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { useIntl } from "react-intl";
|
||||
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||
import { ITimestampValue } from "../../../models/list-filter/types";
|
||||
import { Criterion } from "../../../models/list-filter/criteria/criterion";
|
||||
|
||||
interface ITimestampFilterProps {
|
||||
criterion: Criterion<ITimestampValue>;
|
||||
onValueChanged: (value: ITimestampValue) => void;
|
||||
}
|
||||
|
||||
export const TimestampFilter: React.FC<ITimestampFilterProps> = ({
|
||||
criterion,
|
||||
onValueChanged,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const valueStage = useRef<ITimestampValue>(criterion.value);
|
||||
|
||||
function onChanged(
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
property: "value" | "value2"
|
||||
) {
|
||||
const { value } = event.target;
|
||||
valueStage.current[property] = value;
|
||||
}
|
||||
|
||||
function onBlurInput() {
|
||||
onValueChanged(valueStage.current);
|
||||
}
|
||||
|
||||
let equalsControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.Equals ||
|
||||
criterion.modifier === CriterionModifier.NotEquals
|
||||
) {
|
||||
equalsControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(e, "value")
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={criterion.value?.value ?? ""}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.value" }) +
|
||||
" (YYYY-MM-DD HH-MM)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
let lowerControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.GreaterThan ||
|
||||
criterion.modifier === CriterionModifier.Between ||
|
||||
criterion.modifier === CriterionModifier.NotBetween
|
||||
) {
|
||||
lowerControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(e, "value")
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={criterion.value?.value ?? ""}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.greater_than" }) +
|
||||
" (YYYY-MM-DD HH-MM)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
let upperControl: JSX.Element | null = null;
|
||||
if (
|
||||
criterion.modifier === CriterionModifier.LessThan ||
|
||||
criterion.modifier === CriterionModifier.Between ||
|
||||
criterion.modifier === CriterionModifier.NotBetween
|
||||
) {
|
||||
upperControl = (
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
className="btn-secondary"
|
||||
type="text"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChanged(
|
||||
e,
|
||||
criterion.modifier === CriterionModifier.LessThan
|
||||
? "value"
|
||||
: "value2"
|
||||
)
|
||||
}
|
||||
onBlur={onBlurInput}
|
||||
defaultValue={
|
||||
(criterion.modifier === CriterionModifier.LessThan
|
||||
? criterion.value?.value
|
||||
: criterion.value?.value2) ?? ""
|
||||
}
|
||||
placeholder={
|
||||
intl.formatMessage({ id: "criterion.less_than" }) +
|
||||
" (YYYY-MM-DD HH-MM)"
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{equalsControl}
|
||||
{lowerControl}
|
||||
{upperControl}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
### ✨ New Features
|
||||
* Added filter criteria for Birthdate, Death Date, Date, Created At and Updated At fields. ([#2834](https://github.com/stashapp/stash/pull/2834))
|
||||
* Support creation of scenes without files. ([#3006](https://github.com/stashapp/stash/pull/3006))
|
||||
* Added ability to reassign files to other scenes. ([#3006](https://github.com/stashapp/stash/pull/3006))
|
||||
* Added ability to split and merge scenes. ([#3006](https://github.com/stashapp/stash/pull/3006))
|
||||
|
|
|
@ -930,6 +930,9 @@
|
|||
"release_notes": "Release Notes",
|
||||
"resolution": "Resolution",
|
||||
"scene": "Scene",
|
||||
"scene_date": "Date of Scene",
|
||||
"scene_created_at": "Scene Created At",
|
||||
"scene_updated_at": "Scene Updated At",
|
||||
"sceneTagger": "Scene Tagger",
|
||||
"sceneTags": "Scene Tags",
|
||||
"scene_code": "Studio Code",
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
IntCriterionInput,
|
||||
MultiCriterionInput,
|
||||
PHashDuplicationCriterionInput,
|
||||
DateCriterionInput,
|
||||
TimestampCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import DurationUtils from "src/utils/duration";
|
||||
import {
|
||||
|
@ -17,6 +19,8 @@ import {
|
|||
ILabeledValue,
|
||||
INumberValue,
|
||||
IOptionType,
|
||||
IDateValue,
|
||||
ITimestampValue,
|
||||
} from "../types";
|
||||
|
||||
export type Option = string | number | IOptionType;
|
||||
|
@ -24,7 +28,9 @@ export type CriterionValue =
|
|||
| string
|
||||
| ILabeledId[]
|
||||
| IHierarchicalLabelValue
|
||||
| INumberValue;
|
||||
| INumberValue
|
||||
| IDateValue
|
||||
| ITimestampValue;
|
||||
|
||||
const modifierMessageIDs = {
|
||||
[CriterionModifier.Equals]: "criterion_modifier.equals",
|
||||
|
@ -501,3 +507,162 @@ export class PhashDuplicateCriterion extends StringCriterion {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DateCriterionOption extends CriterionOption {
|
||||
constructor(
|
||||
messageID: string,
|
||||
value: CriterionType,
|
||||
parameterName?: string,
|
||||
options?: Option[]
|
||||
) {
|
||||
super({
|
||||
messageID,
|
||||
type: value,
|
||||
parameterName,
|
||||
modifierOptions: [
|
||||
CriterionModifier.Equals,
|
||||
CriterionModifier.NotEquals,
|
||||
CriterionModifier.GreaterThan,
|
||||
CriterionModifier.LessThan,
|
||||
CriterionModifier.IsNull,
|
||||
CriterionModifier.NotNull,
|
||||
CriterionModifier.Between,
|
||||
CriterionModifier.NotBetween,
|
||||
],
|
||||
defaultModifier: CriterionModifier.Equals,
|
||||
options,
|
||||
inputType: "text",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function createDateCriterionOption(value: CriterionType) {
|
||||
return new DateCriterionOption(value, value, value);
|
||||
}
|
||||
|
||||
export class DateCriterion extends Criterion<IDateValue> {
|
||||
public encodeValue() {
|
||||
return {
|
||||
value: this.value.value,
|
||||
value2: this.value.value2,
|
||||
};
|
||||
}
|
||||
|
||||
protected toCriterionInput(): DateCriterionInput {
|
||||
return {
|
||||
modifier: this.modifier,
|
||||
value: this.value.value,
|
||||
value2: this.value.value2,
|
||||
};
|
||||
}
|
||||
|
||||
public getLabelValue() {
|
||||
const { value } = this.value;
|
||||
return this.modifier === CriterionModifier.Between ||
|
||||
this.modifier === CriterionModifier.NotBetween
|
||||
? `${value}, ${this.value.value2}`
|
||||
: `${value}`;
|
||||
}
|
||||
|
||||
constructor(type: CriterionOption) {
|
||||
super(type, { value: "", value2: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
export class TimestampCriterionOption extends CriterionOption {
|
||||
constructor(
|
||||
messageID: string,
|
||||
value: CriterionType,
|
||||
parameterName?: string,
|
||||
options?: Option[]
|
||||
) {
|
||||
super({
|
||||
messageID,
|
||||
type: value,
|
||||
parameterName,
|
||||
modifierOptions: [
|
||||
CriterionModifier.GreaterThan,
|
||||
CriterionModifier.LessThan,
|
||||
CriterionModifier.IsNull,
|
||||
CriterionModifier.NotNull,
|
||||
CriterionModifier.Between,
|
||||
CriterionModifier.NotBetween,
|
||||
],
|
||||
defaultModifier: CriterionModifier.GreaterThan,
|
||||
options,
|
||||
inputType: "text",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function createTimestampCriterionOption(value: CriterionType) {
|
||||
return new TimestampCriterionOption(value, value, value);
|
||||
}
|
||||
|
||||
export class TimestampCriterion extends Criterion<ITimestampValue> {
|
||||
public encodeValue() {
|
||||
return {
|
||||
value: this.value.value,
|
||||
value2: this.value.value2,
|
||||
};
|
||||
}
|
||||
|
||||
protected toCriterionInput(): TimestampCriterionInput {
|
||||
return {
|
||||
modifier: this.modifier,
|
||||
value: this.transformValueToInput(this.value.value),
|
||||
value2: this.value.value2
|
||||
? this.transformValueToInput(this.value.value2)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
public getLabelValue() {
|
||||
const { value } = this.value;
|
||||
return this.modifier === CriterionModifier.Between ||
|
||||
this.modifier === CriterionModifier.NotBetween
|
||||
? `${value}, ${this.value.value2}`
|
||||
: `${value}`;
|
||||
}
|
||||
|
||||
private transformValueToInput(value: string): string {
|
||||
value = value.trim();
|
||||
if (/^\d{4}-\d{2}-\d{2}(( |T)\d{2}:\d{2})?$/.test(value)) {
|
||||
return value.replace(" ", "T");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
constructor(type: CriterionOption) {
|
||||
super(type, { value: "", value2: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
export class MandatoryTimestampCriterionOption extends CriterionOption {
|
||||
constructor(
|
||||
messageID: string,
|
||||
value: CriterionType,
|
||||
parameterName?: string,
|
||||
options?: Option[]
|
||||
) {
|
||||
super({
|
||||
messageID,
|
||||
type: value,
|
||||
parameterName,
|
||||
modifierOptions: [
|
||||
CriterionModifier.GreaterThan,
|
||||
CriterionModifier.LessThan,
|
||||
CriterionModifier.Between,
|
||||
CriterionModifier.NotBetween,
|
||||
],
|
||||
defaultModifier: CriterionModifier.GreaterThan,
|
||||
options,
|
||||
inputType: "text",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function createMandatoryTimestampCriterionOption(value: CriterionType) {
|
||||
return new MandatoryTimestampCriterionOption(value, value, value);
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ import {
|
|||
ILabeledIdCriterion,
|
||||
BooleanCriterion,
|
||||
BooleanCriterionOption,
|
||||
DateCriterion,
|
||||
DateCriterionOption,
|
||||
TimestampCriterion,
|
||||
MandatoryTimestampCriterionOption,
|
||||
} from "./criterion";
|
||||
import { OrganizedCriterion } from "./organized";
|
||||
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
|
||||
|
@ -193,5 +197,17 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||
);
|
||||
case "ignore_auto_tag":
|
||||
return new BooleanCriterion(new BooleanCriterionOption(type, type));
|
||||
case "date":
|
||||
case "birthdate":
|
||||
case "death_date":
|
||||
case "scene_date":
|
||||
return new DateCriterion(new DateCriterionOption(type, type));
|
||||
case "created_at":
|
||||
case "updated_at":
|
||||
case "scene_created_at":
|
||||
case "scene_updated_at":
|
||||
return new TimestampCriterion(
|
||||
new MandatoryTimestampCriterionOption(type, type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createDateCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
|
||||
import { GalleryIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
|
@ -61,6 +63,9 @@ const criterionOptions = [
|
|||
StudiosCriterionOption,
|
||||
createStringCriterionOption("url"),
|
||||
createMandatoryNumberCriterionOption("file_count", "zip_file_count"),
|
||||
createDateCriterionOption("date"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
|
||||
export const GalleryListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
|
||||
import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
|
@ -45,6 +46,8 @@ const criterionOptions = [
|
|||
PerformerFavoriteCriterionOption,
|
||||
StudiosCriterionOption,
|
||||
createMandatoryNumberCriterionOption("file_count"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
export const ImageListFilterOptions = new ListFilterOptions(
|
||||
defaultSortBy,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createDateCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { MovieIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { RatingCriterionOption } from "./criteria/rating";
|
||||
|
@ -30,6 +32,9 @@ const criterionOptions = [
|
|||
createMandatoryNumberCriterionOption("duration"),
|
||||
RatingCriterionOption,
|
||||
PerformersCriterionOption,
|
||||
createDateCriterionOption("date"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
|
||||
export const MovieListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createBooleanCriterionOption,
|
||||
createDateCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
NumberCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { FavoriteCriterionOption } from "./criteria/favorite";
|
||||
|
@ -84,6 +86,10 @@ const criterionOptions = [
|
|||
new NumberCriterionOption("height", "height_cm", "height_cm"),
|
||||
...numberCriteria.map((c) => createNumberCriterionOption(c)),
|
||||
...stringCriteria.map((c) => createStringCriterionOption(c)),
|
||||
createDateCriterionOption("birthdate"),
|
||||
createDateCriterionOption("death_date"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
export const PerformerListFilterOptions = new ListFilterOptions(
|
||||
defaultSortBy,
|
||||
|
|
|
@ -2,6 +2,10 @@ import { PerformersCriterionOption } from "./criteria/performers";
|
|||
import { SceneTagsCriterionOption, TagsCriterionOption } from "./criteria/tags";
|
||||
import { ListFilterOptions } from "./filter-options";
|
||||
import { DisplayMode } from "./types";
|
||||
import {
|
||||
createDateCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
|
||||
const defaultSortBy = "title";
|
||||
const sortByOptions = [
|
||||
|
@ -16,6 +20,11 @@ const criterionOptions = [
|
|||
TagsCriterionOption,
|
||||
SceneTagsCriterionOption,
|
||||
PerformersCriterionOption,
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
createDateCriterionOption("scene_date"),
|
||||
createMandatoryTimestampCriterionOption("scene_created_at"),
|
||||
createMandatoryTimestampCriterionOption("scene_updated_at"),
|
||||
];
|
||||
|
||||
export const SceneMarkerListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -2,6 +2,8 @@ import {
|
|||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createDateCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { HasMarkersCriterionOption } from "./criteria/has-markers";
|
||||
import { SceneIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
|
@ -85,6 +87,9 @@ const criterionOptions = [
|
|||
CaptionsCriterionOption,
|
||||
createMandatoryNumberCriterionOption("interactive_speed"),
|
||||
createMandatoryNumberCriterionOption("file_count"),
|
||||
createDateCriterionOption("date"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
|
||||
export const SceneListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { StudioIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { RatingCriterionOption } from "./criteria/rating";
|
||||
|
@ -42,6 +43,8 @@ const criterionOptions = [
|
|||
createStringCriterionOption("url"),
|
||||
createStringCriterionOption("stash_id"),
|
||||
createStringCriterionOption("aliases"),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
|
||||
export const StudioListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
MandatoryNumberCriterionOption,
|
||||
createMandatoryTimestampCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { TagIsMissingCriterionOption } from "./criteria/is-missing";
|
||||
import { ListFilterOptions } from "./filter-options";
|
||||
|
@ -63,6 +64,8 @@ const criterionOptions = [
|
|||
"child_tag_count",
|
||||
"child_count"
|
||||
),
|
||||
createMandatoryTimestampCriterionOption("created_at"),
|
||||
createMandatoryTimestampCriterionOption("updated_at"),
|
||||
];
|
||||
|
||||
export const TagListFilterOptions = new ListFilterOptions(
|
||||
|
|
|
@ -33,6 +33,16 @@ export interface IPHashDuplicationValue {
|
|||
distance?: number; // currently not implemented
|
||||
}
|
||||
|
||||
export interface IDateValue {
|
||||
value: string;
|
||||
value2: string | undefined;
|
||||
}
|
||||
|
||||
export interface ITimestampValue {
|
||||
value: string;
|
||||
value2: string | undefined;
|
||||
}
|
||||
|
||||
export function criterionIsHierarchicalLabelValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any
|
||||
|
@ -47,6 +57,20 @@ export function criterionIsNumberValue(
|
|||
return typeof value === "object" && "value" in value && "value2" in value;
|
||||
}
|
||||
|
||||
export function criterionIsDateValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any
|
||||
): value is IDateValue {
|
||||
return typeof value === "object" && "value" in value && "value2" in value;
|
||||
}
|
||||
|
||||
export function criterionIsTimestampValue(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any
|
||||
): value is ITimestampValue {
|
||||
return typeof value === "object" && "value" in value && "value2" in value;
|
||||
}
|
||||
|
||||
export interface IOptionType {
|
||||
id: string;
|
||||
name?: string;
|
||||
|
@ -126,5 +150,13 @@ export type CriterionType =
|
|||
| "duplicated"
|
||||
| "ignore_auto_tag"
|
||||
| "file_count"
|
||||
| "date"
|
||||
| "created_at"
|
||||
| "updated_at"
|
||||
| "birthdate"
|
||||
| "death_date"
|
||||
| "scene_date"
|
||||
| "scene_created_at"
|
||||
| "scene_updated_at"
|
||||
| "description"
|
||||
| "scene_code";
|
||||
|
|
Loading…
Reference in New Issue