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:
gitgiggety
2022-11-15 01:52:05 +01:00
committed by GitHub
parent ce17230c13
commit f66333bac9
36 changed files with 757 additions and 1 deletions

View File

@@ -105,6 +105,14 @@ input PerformerFilterType {
studios: HierarchicalMultiCriterionInput studios: HierarchicalMultiCriterionInput
"""Filter by autotag ignore value""" """Filter by autotag ignore value"""
ignore_auto_tag: Boolean 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 { input SceneMarkerFilterType {
@@ -116,6 +124,16 @@ input SceneMarkerFilterType {
scene_tags: HierarchicalMultiCriterionInput scene_tags: HierarchicalMultiCriterionInput
"""Filter to only include scene markers with these performers""" """Filter to only include scene markers with these performers"""
performers: MultiCriterionInput 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 { input SceneFilterType {
@@ -183,6 +201,12 @@ input SceneFilterType {
interactive_speed: IntCriterionInput interactive_speed: IntCriterionInput
"""Filter by captions""" """Filter by captions"""
captions: StringCriterionInput captions: StringCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
input MovieFilterType { input MovieFilterType {
@@ -203,6 +227,12 @@ input MovieFilterType {
url: StringCriterionInput url: StringCriterionInput
"""Filter to only include movies where performer appears in a scene""" """Filter to only include movies where performer appears in a scene"""
performers: MultiCriterionInput performers: MultiCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
input StudioFilterType { input StudioFilterType {
@@ -232,6 +262,10 @@ input StudioFilterType {
aliases: StringCriterionInput aliases: StringCriterionInput
"""Filter by autotag ignore value""" """Filter by autotag ignore value"""
ignore_auto_tag: Boolean ignore_auto_tag: Boolean
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
input GalleryFilterType { input GalleryFilterType {
@@ -279,6 +313,12 @@ input GalleryFilterType {
image_count: IntCriterionInput image_count: IntCriterionInput
"""Filter by url""" """Filter by url"""
url: StringCriterionInput url: StringCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
input TagFilterType { input TagFilterType {
@@ -327,6 +367,12 @@ input TagFilterType {
"""Filter by autotag ignore value""" """Filter by autotag ignore value"""
ignore_auto_tag: Boolean ignore_auto_tag: Boolean
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
input ImageFilterType { input ImageFilterType {
@@ -370,6 +416,10 @@ input ImageFilterType {
performer_favorite: Boolean performer_favorite: Boolean
"""Filter to only include images with these galleries""" """Filter to only include images with these galleries"""
galleries: MultiCriterionInput galleries: MultiCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
} }
enum CriterionModifier { enum CriterionModifier {
@@ -426,6 +476,18 @@ input HierarchicalMultiCriterionInput {
depth: Int depth: Int
} }
input DateCriterionInput {
value: String!
value2: String
modifier: CriterionModifier!
}
input TimestampCriterionInput {
value: String!
value2: String
modifier: CriterionModifier!
}
enum FilterMode { enum FilterMode {
SCENES, SCENES,
PERFORMERS, PERFORMERS,

View File

@@ -124,3 +124,15 @@ type MultiCriterionInput struct {
Value []string `json:"value"` Value []string `json:"value"`
Modifier CriterionModifier `json:"modifier"` 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"`
}

View File

@@ -49,6 +49,12 @@ type GalleryFilterType struct {
ImageCount *IntCriterionInput `json:"image_count"` ImageCount *IntCriterionInput `json:"image_count"`
// Filter by url // Filter by url
URL *StringCriterionInput `json:"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 { type GalleryUpdateInput struct {

View File

@@ -40,6 +40,10 @@ type ImageFilterType struct {
PerformerFavorite *bool `json:"performer_favorite"` PerformerFavorite *bool `json:"performer_favorite"`
// Filter to only include images with these galleries // Filter to only include images with these galleries
Galleries *MultiCriterionInput `json:"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 { type ImageDestroyInput struct {

View File

@@ -18,6 +18,12 @@ type MovieFilterType struct {
URL *StringCriterionInput `json:"url"` URL *StringCriterionInput `json:"url"`
// Filter to only include movies where performer appears in a scene // Filter to only include movies where performer appears in a scene
Performers *MultiCriterionInput `json:"performers"` 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 { type MovieReader interface {

View File

@@ -125,6 +125,14 @@ type PerformerFilterType struct {
Studios *HierarchicalMultiCriterionInput `json:"studios"` Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter by autotag ignore value // Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"` 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 { type PerformerFinder interface {

View File

@@ -75,6 +75,12 @@ type SceneFilterType struct {
InteractiveSpeed *IntCriterionInput `json:"interactive_speed"` InteractiveSpeed *IntCriterionInput `json:"interactive_speed"`
Captions *StringCriterionInput `json:"captions"` 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 { type SceneQueryOptions struct {

View File

@@ -11,6 +11,16 @@ type SceneMarkerFilterType struct {
SceneTags *HierarchicalMultiCriterionInput `json:"scene_tags"` SceneTags *HierarchicalMultiCriterionInput `json:"scene_tags"`
// Filter to only include scene markers with these performers // Filter to only include scene markers with these performers
Performers *MultiCriterionInput `json:"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 { type MarkerStringsResultType struct {

View File

@@ -28,6 +28,10 @@ type StudioFilterType struct {
Aliases *StringCriterionInput `json:"aliases"` Aliases *StringCriterionInput `json:"aliases"`
// Filter by autotag ignore value // Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"` 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 { type StudioFinder interface {

View File

@@ -34,6 +34,10 @@ type TagFilterType struct {
ChildCount *IntCriterionInput `json:"child_count"` ChildCount *IntCriterionInput `json:"child_count"`
// Filter by autotag ignore value // Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"` 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 { type TagFinder interface {

View File

@@ -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 // handle for MultiCriterion where there is a join table between the new
// objects // objects
type joinedMultiCriterionHandlerBuilder struct { type joinedMultiCriterionHandlerBuilder struct {

View File

@@ -665,6 +665,9 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
query.handleCriterion(ctx, galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount)) query.handleCriterion(ctx, galleryImageCountCriterionHandler(qb, galleryFilter.ImageCount))
query.handleCriterion(ctx, galleryPerformerFavoriteCriterionHandler(galleryFilter.PerformerFavorite)) query.handleCriterion(ctx, galleryPerformerFavoriteCriterionHandler(galleryFilter.PerformerFavorite))
query.handleCriterion(ctx, galleryPerformerAgeCriterionHandler(galleryFilter.PerformerAge)) 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 return query
} }

View File

@@ -647,6 +647,8 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
query.handleCriterion(ctx, imageStudioCriterionHandler(qb, imageFilter.Studios)) query.handleCriterion(ctx, imageStudioCriterionHandler(qb, imageFilter.Studios))
query.handleCriterion(ctx, imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags)) query.handleCriterion(ctx, imagePerformerTagsCriterionHandler(qb, imageFilter.PerformerTags))
query.handleCriterion(ctx, imagePerformerFavoriteCriterionHandler(imageFilter.PerformerFavorite)) 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 return query
} }

View File

@@ -153,6 +153,9 @@ func (qb *movieQueryBuilder) makeFilter(ctx context.Context, movieFilter *models
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.URL, "movies.url")) query.handleCriterion(ctx, stringCriterionHandler(movieFilter.URL, "movies.url"))
query.handleCriterion(ctx, movieStudioCriterionHandler(qb, movieFilter.Studios)) query.handleCriterion(ctx, movieStudioCriterionHandler(qb, movieFilter.Studios))
query.handleCriterion(ctx, moviePerformersCriterionHandler(qb, movieFilter.Performers)) 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 return query
} }

View File

@@ -541,6 +541,10 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount)) query.handleCriterion(ctx, performerSceneCountCriterionHandler(qb, filter.SceneCount))
query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount)) query.handleCriterion(ctx, performerImageCountCriterionHandler(qb, filter.ImageCount))
query.handleCriterion(ctx, performerGalleryCountCriterionHandler(qb, filter.GalleryCount)) 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 return query
} }

View File

@@ -877,6 +877,9 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
query.handleCriterion(ctx, scenePerformerFavoriteCriterionHandler(sceneFilter.PerformerFavorite)) query.handleCriterion(ctx, scenePerformerFavoriteCriterionHandler(sceneFilter.PerformerFavorite))
query.handleCriterion(ctx, scenePerformerAgeCriterionHandler(sceneFilter.PerformerAge)) query.handleCriterion(ctx, scenePerformerAgeCriterionHandler(sceneFilter.PerformerAge))
query.handleCriterion(ctx, scenePhashDuplicatedCriterionHandler(sceneFilter.Duplicated, qb.addSceneFilesTable)) 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 return query
} }

View File

@@ -131,6 +131,11 @@ func (qb *sceneMarkerQueryBuilder) makeFilter(ctx context.Context, sceneMarkerFi
query.handleCriterion(ctx, sceneMarkerTagsCriterionHandler(qb, sceneMarkerFilter.Tags)) query.handleCriterion(ctx, sceneMarkerTagsCriterionHandler(qb, sceneMarkerFilter.Tags))
query.handleCriterion(ctx, sceneMarkerSceneTagsCriterionHandler(qb, sceneMarkerFilter.SceneTags)) query.handleCriterion(ctx, sceneMarkerSceneTagsCriterionHandler(qb, sceneMarkerFilter.SceneTags))
query.handleCriterion(ctx, sceneMarkerPerformersCriterionHandler(qb, sceneMarkerFilter.Performers)) 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 return query
} }

View File

@@ -9,6 +9,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/stashapp/stash/pkg/models" "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) 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 // returns where clause and having clause
func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, foreignFK string, criterion *models.MultiCriterionInput) (string, string) { func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, foreignFK string, criterion *models.MultiCriterionInput) (string, string) {
whereClause := "" whereClause := ""

View File

@@ -250,6 +250,8 @@ func (qb *studioQueryBuilder) makeFilter(ctx context.Context, studioFilter *mode
query.handleCriterion(ctx, studioGalleryCountCriterionHandler(qb, studioFilter.GalleryCount)) query.handleCriterion(ctx, studioGalleryCountCriterionHandler(qb, studioFilter.GalleryCount))
query.handleCriterion(ctx, studioParentCriterionHandler(qb, studioFilter.Parents)) query.handleCriterion(ctx, studioParentCriterionHandler(qb, studioFilter.Parents))
query.handleCriterion(ctx, studioAliasCriterionHandler(qb, studioFilter.Aliases)) 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 return query
} }

View File

@@ -338,6 +338,8 @@ func (qb *tagQueryBuilder) makeFilter(ctx context.Context, tagFilter *models.Tag
query.handleCriterion(ctx, tagChildrenCriterionHandler(qb, tagFilter.Children)) query.handleCriterion(ctx, tagChildrenCriterionHandler(qb, tagFilter.Children))
query.handleCriterion(ctx, tagParentCountCriterionHandler(qb, tagFilter.ParentCount)) query.handleCriterion(ctx, tagParentCountCriterionHandler(qb, tagFilter.ParentCount))
query.handleCriterion(ctx, tagChildCountCriterionHandler(qb, tagFilter.ChildCount)) 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 return query
} }

View File

@@ -9,6 +9,8 @@ import {
IHierarchicalLabeledIdCriterion, IHierarchicalLabeledIdCriterion,
NumberCriterion, NumberCriterion,
ILabeledIdCriterion, ILabeledIdCriterion,
DateCriterion,
TimestampCriterion,
} from "src/models/list-filter/criteria/criterion"; } from "src/models/list-filter/criteria/criterion";
import { import {
NoneCriterion, NoneCriterion,
@@ -20,6 +22,8 @@ import { FormattedMessage, useIntl } from "react-intl";
import { import {
criterionIsHierarchicalLabelValue, criterionIsHierarchicalLabelValue,
criterionIsNumberValue, criterionIsNumberValue,
criterionIsDateValue,
criterionIsTimestampValue,
CriterionType, CriterionType,
} from "src/models/list-filter/types"; } from "src/models/list-filter/types";
import { DurationFilter } from "./Filters/DurationFilter"; import { DurationFilter } from "./Filters/DurationFilter";
@@ -28,6 +32,8 @@ import { LabeledIdFilter } from "./Filters/LabeledIdFilter";
import { HierarchicalLabelValueFilter } from "./Filters/HierarchicalLabelValueFilter"; import { HierarchicalLabelValueFilter } from "./Filters/HierarchicalLabelValueFilter";
import { OptionsFilter } from "./Filters/OptionsFilter"; import { OptionsFilter } from "./Filters/OptionsFilter";
import { InputFilter } from "./Filters/InputFilter"; 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 { CountryCriterion } from "src/models/list-filter/criteria/country";
import { CountrySelect } from "../Shared"; import { CountrySelect } from "../Shared";
@@ -152,6 +158,8 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
options && options &&
!criterionIsHierarchicalLabelValue(criterion.value) && !criterionIsHierarchicalLabelValue(criterion.value) &&
!criterionIsNumberValue(criterion.value) && !criterionIsNumberValue(criterion.value) &&
!criterionIsDateValue(criterion.value) &&
!criterionIsTimestampValue(criterion.value) &&
!Array.isArray(criterion.value) !Array.isArray(criterion.value)
) { ) {
defaultValue.current = 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) { if (criterion instanceof NumberCriterion) {
return ( return (
<NumberFilter criterion={criterion} onValueChanged={onValueChanged} /> <NumberFilter criterion={criterion} onValueChanged={onValueChanged} />

View File

@@ -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}
</>
);
};

View File

@@ -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}
</>
);
};

View File

@@ -1,4 +1,5 @@
### ✨ New Features ### ✨ 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)) * 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 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)) * Added ability to split and merge scenes. ([#3006](https://github.com/stashapp/stash/pull/3006))

View File

@@ -930,6 +930,9 @@
"release_notes": "Release Notes", "release_notes": "Release Notes",
"resolution": "Resolution", "resolution": "Resolution",
"scene": "Scene", "scene": "Scene",
"scene_date": "Date of Scene",
"scene_created_at": "Scene Created At",
"scene_updated_at": "Scene Updated At",
"sceneTagger": "Scene Tagger", "sceneTagger": "Scene Tagger",
"sceneTags": "Scene Tags", "sceneTags": "Scene Tags",
"scene_code": "Studio Code", "scene_code": "Studio Code",

View File

@@ -8,6 +8,8 @@ import {
IntCriterionInput, IntCriterionInput,
MultiCriterionInput, MultiCriterionInput,
PHashDuplicationCriterionInput, PHashDuplicationCriterionInput,
DateCriterionInput,
TimestampCriterionInput,
} from "src/core/generated-graphql"; } from "src/core/generated-graphql";
import DurationUtils from "src/utils/duration"; import DurationUtils from "src/utils/duration";
import { import {
@@ -17,6 +19,8 @@ import {
ILabeledValue, ILabeledValue,
INumberValue, INumberValue,
IOptionType, IOptionType,
IDateValue,
ITimestampValue,
} from "../types"; } from "../types";
export type Option = string | number | IOptionType; export type Option = string | number | IOptionType;
@@ -24,7 +28,9 @@ export type CriterionValue =
| string | string
| ILabeledId[] | ILabeledId[]
| IHierarchicalLabelValue | IHierarchicalLabelValue
| INumberValue; | INumberValue
| IDateValue
| ITimestampValue;
const modifierMessageIDs = { const modifierMessageIDs = {
[CriterionModifier.Equals]: "criterion_modifier.equals", [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);
}

View File

@@ -10,6 +10,10 @@ import {
ILabeledIdCriterion, ILabeledIdCriterion,
BooleanCriterion, BooleanCriterion,
BooleanCriterionOption, BooleanCriterionOption,
DateCriterion,
DateCriterionOption,
TimestampCriterion,
MandatoryTimestampCriterionOption,
} from "./criterion"; } from "./criterion";
import { OrganizedCriterion } from "./organized"; import { OrganizedCriterion } from "./organized";
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite"; import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
@@ -193,5 +197,17 @@ export function makeCriteria(type: CriterionType = "none") {
); );
case "ignore_auto_tag": case "ignore_auto_tag":
return new BooleanCriterion(new BooleanCriterionOption(type, type)); 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)
);
} }
} }

View File

@@ -1,6 +1,8 @@
import { import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { GalleryIsMissingCriterionOption } from "./criteria/is-missing"; import { GalleryIsMissingCriterionOption } from "./criteria/is-missing";
@@ -61,6 +63,9 @@ const criterionOptions = [
StudiosCriterionOption, StudiosCriterionOption,
createStringCriterionOption("url"), createStringCriterionOption("url"),
createMandatoryNumberCriterionOption("file_count", "zip_file_count"), createMandatoryNumberCriterionOption("file_count", "zip_file_count"),
createDateCriterionOption("date"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const GalleryListFilterOptions = new ListFilterOptions( export const GalleryListFilterOptions = new ListFilterOptions(

View File

@@ -2,6 +2,7 @@ import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createMandatoryStringCriterionOption, createMandatoryStringCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { ImageIsMissingCriterionOption } from "./criteria/is-missing"; import { ImageIsMissingCriterionOption } from "./criteria/is-missing";
@@ -45,6 +46,8 @@ const criterionOptions = [
PerformerFavoriteCriterionOption, PerformerFavoriteCriterionOption,
StudiosCriterionOption, StudiosCriterionOption,
createMandatoryNumberCriterionOption("file_count"), createMandatoryNumberCriterionOption("file_count"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const ImageListFilterOptions = new ListFilterOptions( export const ImageListFilterOptions = new ListFilterOptions(
defaultSortBy, defaultSortBy,

View File

@@ -1,6 +1,8 @@
import { import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { MovieIsMissingCriterionOption } from "./criteria/is-missing"; import { MovieIsMissingCriterionOption } from "./criteria/is-missing";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
@@ -30,6 +32,9 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("duration"), createMandatoryNumberCriterionOption("duration"),
RatingCriterionOption, RatingCriterionOption,
PerformersCriterionOption, PerformersCriterionOption,
createDateCriterionOption("date"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const MovieListFilterOptions = new ListFilterOptions( export const MovieListFilterOptions = new ListFilterOptions(

View File

@@ -3,6 +3,8 @@ import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createBooleanCriterionOption, createBooleanCriterionOption,
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
NumberCriterionOption, NumberCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { FavoriteCriterionOption } from "./criteria/favorite"; import { FavoriteCriterionOption } from "./criteria/favorite";
@@ -84,6 +86,10 @@ const criterionOptions = [
new NumberCriterionOption("height", "height_cm", "height_cm"), new NumberCriterionOption("height", "height_cm", "height_cm"),
...numberCriteria.map((c) => createNumberCriterionOption(c)), ...numberCriteria.map((c) => createNumberCriterionOption(c)),
...stringCriteria.map((c) => createStringCriterionOption(c)), ...stringCriteria.map((c) => createStringCriterionOption(c)),
createDateCriterionOption("birthdate"),
createDateCriterionOption("death_date"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const PerformerListFilterOptions = new ListFilterOptions( export const PerformerListFilterOptions = new ListFilterOptions(
defaultSortBy, defaultSortBy,

View File

@@ -2,6 +2,10 @@ import { PerformersCriterionOption } from "./criteria/performers";
import { SceneTagsCriterionOption, TagsCriterionOption } from "./criteria/tags"; import { SceneTagsCriterionOption, TagsCriterionOption } from "./criteria/tags";
import { ListFilterOptions } from "./filter-options"; import { ListFilterOptions } from "./filter-options";
import { DisplayMode } from "./types"; import { DisplayMode } from "./types";
import {
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion";
const defaultSortBy = "title"; const defaultSortBy = "title";
const sortByOptions = [ const sortByOptions = [
@@ -16,6 +20,11 @@ const criterionOptions = [
TagsCriterionOption, TagsCriterionOption,
SceneTagsCriterionOption, SceneTagsCriterionOption,
PerformersCriterionOption, PerformersCriterionOption,
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
createDateCriterionOption("scene_date"),
createMandatoryTimestampCriterionOption("scene_created_at"),
createMandatoryTimestampCriterionOption("scene_updated_at"),
]; ];
export const SceneMarkerListFilterOptions = new ListFilterOptions( export const SceneMarkerListFilterOptions = new ListFilterOptions(

View File

@@ -2,6 +2,8 @@ import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createMandatoryStringCriterionOption, createMandatoryStringCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createDateCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { HasMarkersCriterionOption } from "./criteria/has-markers"; import { HasMarkersCriterionOption } from "./criteria/has-markers";
import { SceneIsMissingCriterionOption } from "./criteria/is-missing"; import { SceneIsMissingCriterionOption } from "./criteria/is-missing";
@@ -85,6 +87,9 @@ const criterionOptions = [
CaptionsCriterionOption, CaptionsCriterionOption,
createMandatoryNumberCriterionOption("interactive_speed"), createMandatoryNumberCriterionOption("interactive_speed"),
createMandatoryNumberCriterionOption("file_count"), createMandatoryNumberCriterionOption("file_count"),
createDateCriterionOption("date"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const SceneListFilterOptions = new ListFilterOptions( export const SceneListFilterOptions = new ListFilterOptions(

View File

@@ -3,6 +3,7 @@ import {
createMandatoryNumberCriterionOption, createMandatoryNumberCriterionOption,
createMandatoryStringCriterionOption, createMandatoryStringCriterionOption,
createStringCriterionOption, createStringCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { StudioIsMissingCriterionOption } from "./criteria/is-missing"; import { StudioIsMissingCriterionOption } from "./criteria/is-missing";
import { RatingCriterionOption } from "./criteria/rating"; import { RatingCriterionOption } from "./criteria/rating";
@@ -42,6 +43,8 @@ const criterionOptions = [
createStringCriterionOption("url"), createStringCriterionOption("url"),
createStringCriterionOption("stash_id"), createStringCriterionOption("stash_id"),
createStringCriterionOption("aliases"), createStringCriterionOption("aliases"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const StudioListFilterOptions = new ListFilterOptions( export const StudioListFilterOptions = new ListFilterOptions(

View File

@@ -4,6 +4,7 @@ import {
createMandatoryStringCriterionOption, createMandatoryStringCriterionOption,
createStringCriterionOption, createStringCriterionOption,
MandatoryNumberCriterionOption, MandatoryNumberCriterionOption,
createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { TagIsMissingCriterionOption } from "./criteria/is-missing"; import { TagIsMissingCriterionOption } from "./criteria/is-missing";
import { ListFilterOptions } from "./filter-options"; import { ListFilterOptions } from "./filter-options";
@@ -63,6 +64,8 @@ const criterionOptions = [
"child_tag_count", "child_tag_count",
"child_count" "child_count"
), ),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
]; ];
export const TagListFilterOptions = new ListFilterOptions( export const TagListFilterOptions = new ListFilterOptions(

View File

@@ -33,6 +33,16 @@ export interface IPHashDuplicationValue {
distance?: number; // currently not implemented distance?: number; // currently not implemented
} }
export interface IDateValue {
value: string;
value2: string | undefined;
}
export interface ITimestampValue {
value: string;
value2: string | undefined;
}
export function criterionIsHierarchicalLabelValue( export function criterionIsHierarchicalLabelValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any value: any
@@ -47,6 +57,20 @@ export function criterionIsNumberValue(
return typeof value === "object" && "value" in value && "value2" in value; 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 { export interface IOptionType {
id: string; id: string;
name?: string; name?: string;
@@ -126,5 +150,13 @@ export type CriterionType =
| "duplicated" | "duplicated"
| "ignore_auto_tag" | "ignore_auto_tag"
| "file_count" | "file_count"
| "date"
| "created_at"
| "updated_at"
| "birthdate"
| "death_date"
| "scene_date"
| "scene_created_at"
| "scene_updated_at"
| "description" | "description"
| "scene_code"; | "scene_code";