mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add ignore autotag flag (#2439)
* Add autoTagIgnored to database schema * Graphql changes * UI changes * Add field to edit performers dialog * Apply flag to autotag behaviour
This commit is contained in:
parent
2aee6cc18e
commit
61d9f57ce9
52 changed files with 477 additions and 206 deletions
|
|
@ -7,6 +7,7 @@ fragment SlimPerformerData on Performer {
|
|||
instagram
|
||||
image_path
|
||||
favorite
|
||||
ignore_auto_tag
|
||||
country
|
||||
birthdate
|
||||
ethnicity
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ fragment PerformerData on Performer {
|
|||
piercings
|
||||
aliases
|
||||
favorite
|
||||
ignore_auto_tag
|
||||
image_path
|
||||
scene_count
|
||||
image_count
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ fragment StudioData on Studio {
|
|||
name
|
||||
image_path
|
||||
}
|
||||
ignore_auto_tag
|
||||
image_path
|
||||
scene_count
|
||||
image_count
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ fragment TagData on Tag {
|
|||
id
|
||||
name
|
||||
aliases
|
||||
ignore_auto_tag
|
||||
image_path
|
||||
scene_count
|
||||
scene_marker_count
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ input PerformerFilterType {
|
|||
death_year: IntCriterionInput
|
||||
"""Filter by studios where performer appears in scene/image/gallery"""
|
||||
studios: HierarchicalMultiCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input SceneMarkerFilterType {
|
||||
|
|
@ -219,6 +221,8 @@ input StudioFilterType {
|
|||
url: StringCriterionInput
|
||||
"""Filter by studio aliases"""
|
||||
aliases: StringCriterionInput
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input GalleryFilterType {
|
||||
|
|
@ -305,6 +309,9 @@ input TagFilterType {
|
|||
|
||||
"""Filter by number f child tags the tag has"""
|
||||
child_count: IntCriterionInput
|
||||
|
||||
"""Filter by autotag ignore value"""
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input ImageFilterType {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ type Performer {
|
|||
aliases: String
|
||||
favorite: Boolean!
|
||||
tags: [Tag!]!
|
||||
ignore_auto_tag: Boolean!
|
||||
|
||||
image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
|
|
@ -73,6 +74,7 @@ input PerformerCreateInput {
|
|||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input PerformerUpdateInput {
|
||||
|
|
@ -103,6 +105,7 @@ input PerformerUpdateInput {
|
|||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input BulkPerformerUpdateInput {
|
||||
|
|
@ -130,6 +133,7 @@ input BulkPerformerUpdateInput {
|
|||
death_date: String
|
||||
hair_color: String
|
||||
weight: Int
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input PerformerDestroyInput {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ type Studio {
|
|||
parent_studio: Studio
|
||||
child_studios: [Studio!]!
|
||||
aliases: [String!]!
|
||||
ignore_auto_tag: Boolean!
|
||||
|
||||
image_path: String # Resolver
|
||||
scene_count: Int # Resolver
|
||||
|
|
@ -30,6 +31,7 @@ input StudioCreateInput {
|
|||
rating: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input StudioUpdateInput {
|
||||
|
|
@ -43,6 +45,7 @@ input StudioUpdateInput {
|
|||
rating: Int
|
||||
details: String
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
}
|
||||
|
||||
input StudioDestroyInput {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ type Tag {
|
|||
id: ID!
|
||||
name: String!
|
||||
aliases: [String!]!
|
||||
ignore_auto_tag: Boolean!
|
||||
created_at: Time!
|
||||
updated_at: Time!
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ type Tag {
|
|||
input TagCreateInput {
|
||||
name: String!
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
|
|
@ -31,6 +33,7 @@ input TagUpdateInput {
|
|||
id: ID!
|
||||
name: String
|
||||
aliases: [String!]
|
||||
ignore_auto_tag: Boolean
|
||||
|
||||
"""This should be a URL or a base64 encoded data URL"""
|
||||
image: String
|
||||
|
|
|
|||
|
|
@ -117,6 +117,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
|||
weight := int64(*input.Weight)
|
||||
newPerformer.Weight = sql.NullInt64{Int64: weight, Valid: true}
|
||||
}
|
||||
if input.IgnoreAutoTag != nil {
|
||||
newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag
|
||||
}
|
||||
|
||||
if err := performer.ValidateDeathDate(nil, input.Birthdate, input.DeathDate); err != nil {
|
||||
if err != nil {
|
||||
|
|
@ -223,6 +226,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
|||
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
|
||||
// Start the transaction and save the p
|
||||
var p *models.Performer
|
||||
|
|
@ -331,6 +335,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models
|
|||
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
|
||||
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
|
||||
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
|
||||
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
|
||||
if translator.hasField("gender") {
|
||||
if input.Gender != nil {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
|
|||
if input.Details != nil {
|
||||
newStudio.Details = sql.NullString{String: *input.Details, Valid: true}
|
||||
}
|
||||
if input.IgnoreAutoTag != nil {
|
||||
newStudio.IgnoreAutoTag = *input.IgnoreAutoTag
|
||||
}
|
||||
|
||||
// Start the transaction and save the studio
|
||||
var s *models.Studio
|
||||
|
|
@ -148,6 +151,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
|
|||
updatedStudio.Details = translator.nullString(input.Details, "details")
|
||||
updatedStudio.ParentID = translator.nullInt64FromString(input.ParentID, "parent_id")
|
||||
updatedStudio.Rating = translator.nullInt64(input.Rating, "rating")
|
||||
updatedStudio.IgnoreAutoTag = input.IgnoreAutoTag
|
||||
|
||||
// Start the transaction and save the studio
|
||||
var s *models.Studio
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreate
|
|||
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
|
||||
}
|
||||
|
||||
if input.IgnoreAutoTag != nil {
|
||||
newTag.IgnoreAutoTag = *input.IgnoreAutoTag
|
||||
}
|
||||
|
||||
var imageData []byte
|
||||
var err error
|
||||
|
||||
|
|
@ -179,6 +183,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
|
|||
|
||||
updatedTag := models.TagPartial{
|
||||
ID: tagID,
|
||||
IgnoreAutoTag: input.IgnoreAutoTag,
|
||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ func runTests(m *testing.M) int {
|
|||
|
||||
f.Close()
|
||||
databaseFile := f.Name()
|
||||
database.Initialize(databaseFile)
|
||||
if err := database.Initialize(databaseFile); err != nil {
|
||||
panic(fmt.Sprintf("Could not initialize database: %s", err.Error()))
|
||||
}
|
||||
|
||||
// defer close and delete the database
|
||||
defer testTeardown(databaseFile)
|
||||
|
|
|
|||
|
|
@ -126,10 +126,16 @@ func (j *autoTagJob) autoTagPerformers(ctx context.Context, progress *job.Progre
|
|||
|
||||
if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
performerQuery := r.Performer()
|
||||
ignoreAutoTag := false
|
||||
perPage := -1
|
||||
|
||||
if performerId == "*" {
|
||||
var err error
|
||||
performers, err = performerQuery.All()
|
||||
performers, _, err = performerQuery.Query(&models.PerformerFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}, &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying performers: %v", err)
|
||||
}
|
||||
|
|
@ -193,9 +199,15 @@ func (j *autoTagJob) autoTagStudios(ctx context.Context, progress *job.Progress,
|
|||
|
||||
if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
studioQuery := r.Studio()
|
||||
ignoreAutoTag := false
|
||||
perPage := -1
|
||||
if studioId == "*" {
|
||||
var err error
|
||||
studios, err = studioQuery.All()
|
||||
studios, _, err = studioQuery.Query(&models.StudioFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}, &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying studios: %v", err)
|
||||
}
|
||||
|
|
@ -264,9 +276,15 @@ func (j *autoTagJob) autoTagTags(ctx context.Context, progress *job.Progress, pa
|
|||
var tags []*models.Tag
|
||||
if err := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
tagQuery := r.Tag()
|
||||
ignoreAutoTag := false
|
||||
perPage := -1
|
||||
if tagId == "*" {
|
||||
var err error
|
||||
tags, err = tagQuery.All()
|
||||
tags, _, err = tagQuery.Query(&models.TagFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}, &models.FindFilterType{
|
||||
PerPage: &perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying tags: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
var DB *sqlx.DB
|
||||
var WriteMu sync.Mutex
|
||||
var dbPath string
|
||||
var appSchemaVersion uint = 29
|
||||
var appSchemaVersion uint = 30
|
||||
var databaseSchemaVersion uint
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
|
|
|
|||
3
pkg/database/migrations/30_ignore_autotag.up..sql
Normal file
3
pkg/database/migrations/30_ignore_autotag.up..sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE `performers` ADD COLUMN `ignore_auto_tag` boolean not null default '0';
|
||||
ALTER TABLE `studios` ADD COLUMN `ignore_auto_tag` boolean not null default '0';
|
||||
ALTER TABLE `tags` ADD COLUMN `ignore_auto_tag` boolean not null default '0';
|
||||
|
|
@ -36,6 +36,7 @@ type Performer struct {
|
|||
HairColor string `json:"hair_color,omitempty"`
|
||||
Weight int `json:"weight,omitempty"`
|
||||
StashIDs []models.StashID `json:"stash_ids,omitempty"`
|
||||
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
|
||||
}
|
||||
|
||||
func LoadPerformerFile(filePath string) (*Performer, error) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type Studio struct {
|
|||
Details string `json:"details,omitempty"`
|
||||
Aliases []string `json:"aliases,omitempty"`
|
||||
StashIDs []models.StashID `json:"stash_ids,omitempty"`
|
||||
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
|
||||
}
|
||||
|
||||
func LoadStudioFile(filePath string) (*Studio, error) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ type Tag struct {
|
|||
Aliases []string `json:"aliases,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Parents []string `json:"parents,omitempty"`
|
||||
IgnoreAutoTag bool `json:"ignore_auto_tag,omitempty"`
|
||||
CreatedAt models.JSONTime `json:"created_at,omitempty"`
|
||||
UpdatedAt models.JSONTime `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type Performer struct {
|
|||
DeathDate SQLiteDate `db:"death_date" json:"death_date"`
|
||||
HairColor sql.NullString `db:"hair_color" json:"hair_color"`
|
||||
Weight sql.NullInt64 `db:"weight" json:"weight"`
|
||||
IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
type PerformerPartial struct {
|
||||
|
|
@ -63,6 +64,7 @@ type PerformerPartial struct {
|
|||
DeathDate *SQLiteDate `db:"death_date" json:"death_date"`
|
||||
HairColor *sql.NullString `db:"hair_color" json:"hair_color"`
|
||||
Weight *sql.NullInt64 `db:"weight" json:"weight"`
|
||||
IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
func NewPerformer(name string) *Performer {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type Studio struct {
|
|||
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
Rating sql.NullInt64 `db:"rating" json:"rating"`
|
||||
Details sql.NullString `db:"details" json:"details"`
|
||||
IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
type StudioPartial struct {
|
||||
|
|
@ -29,6 +30,7 @@ type StudioPartial struct {
|
|||
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
Rating *sql.NullInt64 `db:"rating" json:"rating"`
|
||||
Details *sql.NullString `db:"details" json:"details"`
|
||||
IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
var DefaultStudioImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import "time"
|
|||
type Tag struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"` // TODO make schema not null
|
||||
IgnoreAutoTag bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
|
@ -12,6 +13,7 @@ type Tag struct {
|
|||
type TagPartial struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Name *string `db:"name" json:"name"` // TODO make schema not null
|
||||
IgnoreAutoTag *bool `db:"ignore_auto_tag" json:"ignore_auto_tag"`
|
||||
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
// ToJSON converts a Performer object into its JSON equivalent.
|
||||
func ToJSON(reader models.PerformerReader, performer *models.Performer) (*jsonschema.Performer, error) {
|
||||
newPerformerJSON := jsonschema.Performer{
|
||||
IgnoreAutoTag: performer.IgnoreAutoTag,
|
||||
CreatedAt: models.JSONTime{Time: performer.CreatedAt.Timestamp},
|
||||
UpdatedAt: models.JSONTime{Time: performer.UpdatedAt.Timestamp},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const (
|
|||
details = "details"
|
||||
hairColor = "hairColor"
|
||||
weight = 60
|
||||
autoTagIgnored = true
|
||||
)
|
||||
|
||||
var imageBytes = []byte("imageBytes")
|
||||
|
|
@ -106,6 +107,7 @@ func createFullPerformer(id int, name string) *models.Performer {
|
|||
Int64: weight,
|
||||
Valid: true,
|
||||
},
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +157,7 @@ func createFullJSONPerformer(name string, image string) *jsonschema.Performer {
|
|||
StashIDs: []models.StashID{
|
||||
stashID,
|
||||
},
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform
|
|||
newPerformer := models.Performer{
|
||||
Checksum: checksum,
|
||||
Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true},
|
||||
IgnoreAutoTag: performerJSON.IgnoreAutoTag,
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.CreatedAt.GetTime()},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.UpdatedAt.GetTime()},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,7 +191,11 @@ func (qb *performerQueryBuilder) QueryForAutoTag(words []string) ([]*models.Perf
|
|||
// args = append(args, w+"%")
|
||||
}
|
||||
|
||||
where := strings.Join(whereClauses, " OR ")
|
||||
whereOr := "(" + strings.Join(whereClauses, " OR ") + ")"
|
||||
where := strings.Join([]string{
|
||||
"ignore_auto_tag = 0",
|
||||
whereOr,
|
||||
}, " AND ")
|
||||
return qb.queryPerformers(query+" WHERE "+where, args)
|
||||
}
|
||||
|
||||
|
|
@ -244,6 +248,7 @@ func (qb *performerQueryBuilder) makeFilter(filter *models.PerformerFilterType)
|
|||
query.handleCriterion(stringCriterionHandler(filter.Details, tableName+".details"))
|
||||
|
||||
query.handleCriterion(boolCriterionHandler(filter.FilterFavorites, tableName+".favorite"))
|
||||
query.handleCriterion(boolCriterionHandler(filter.IgnoreAutoTag, tableName+".ignore_auto_tag"))
|
||||
|
||||
query.handleCriterion(yearFilterCriterionHandler(filter.BirthYear, tableName+".birthdate"))
|
||||
query.handleCriterion(yearFilterCriterionHandler(filter.DeathYear, tableName+".death_date"))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package sqlite_test
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -238,11 +239,31 @@ func TestPerformerIllegalQuery(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryIgnoreAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
ignoreAutoTag := true
|
||||
performerFilter := models.PerformerFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}
|
||||
|
||||
sqb := r.Performer()
|
||||
|
||||
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||
|
||||
assert.Len(t, performers, int(math.Ceil(float64(totalPerformers)/5)))
|
||||
for _, p := range performers {
|
||||
assert.True(t, p.IgnoreAutoTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryForAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
tqb := r.Performer()
|
||||
|
||||
name := performerNames[performerIdxWithScene] // find a performer by name
|
||||
name := performerNames[performerIdx1WithScene] // find a performer by name
|
||||
|
||||
performers, err := tqb.QueryForAutoTag([]string{name})
|
||||
|
||||
|
|
@ -251,8 +272,8 @@ func TestPerformerQueryForAutoTag(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Len(t, performers, 2)
|
||||
assert.Equal(t, strings.ToLower(performerNames[performerIdxWithScene]), strings.ToLower(performers[0].Name.String))
|
||||
assert.Equal(t, strings.ToLower(performerNames[performerIdxWithScene]), strings.ToLower(performers[1].Name.String))
|
||||
assert.Equal(t, strings.ToLower(performerNames[performerIdx1WithScene]), strings.ToLower(performers[0].Name.String))
|
||||
assert.Equal(t, strings.ToLower(performerNames[performerIdx1WithScene]), strings.ToLower(performers[1].Name.String))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ const (
|
|||
|
||||
performersNameCase = performerIdx1WithDupName
|
||||
performersNameNoCase = 2
|
||||
|
||||
totalPerformers = performersNameCase + performersNameNoCase
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -166,6 +168,8 @@ const (
|
|||
|
||||
tagsNameNoCase = 2
|
||||
tagsNameCase = tagIdx1WithDupName
|
||||
|
||||
totalTags = tagsNameCase + tagsNameNoCase
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -190,6 +194,8 @@ const (
|
|||
|
||||
studiosNameCase = studioIdxWithDupName
|
||||
studiosNameNoCase = 1
|
||||
|
||||
totalStudios = studiosNameCase + studiosNameNoCase
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -422,7 +428,9 @@ func runTests(m *testing.M) int {
|
|||
|
||||
f.Close()
|
||||
databaseFile := f.Name()
|
||||
database.Initialize(databaseFile)
|
||||
if err := database.Initialize(databaseFile); err != nil {
|
||||
panic(fmt.Sprintf("Could not initialize database: %s", err.Error()))
|
||||
}
|
||||
|
||||
// defer close and delete the database
|
||||
defer testTeardown(databaseFile)
|
||||
|
|
@ -815,6 +823,10 @@ func getPerformerCareerLength(index int) *string {
|
|||
return &ret
|
||||
}
|
||||
|
||||
func getIgnoreAutoTag(index int) bool {
|
||||
return index%5 == 0
|
||||
}
|
||||
|
||||
// createPerformers creates n performers with plain Name and o performers with camel cased NaMe included
|
||||
func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error {
|
||||
const namePlain = "Name"
|
||||
|
|
@ -844,6 +856,7 @@ func createPerformers(pqb models.PerformerReaderWriter, n int, o int) error {
|
|||
Details: sql.NullString{String: getPerformerStringValue(i, "Details"), Valid: true},
|
||||
Ethnicity: sql.NullString{String: getPerformerStringValue(i, "Ethnicity"), Valid: true},
|
||||
Rating: getRating(i),
|
||||
IgnoreAutoTag: getIgnoreAutoTag(i),
|
||||
}
|
||||
|
||||
careerLength := getPerformerCareerLength(i)
|
||||
|
|
@ -946,6 +959,7 @@ func createTags(tqb models.TagReaderWriter, n int, o int) error {
|
|||
|
||||
tag := models.Tag{
|
||||
Name: getTagStringValue(index, name),
|
||||
IgnoreAutoTag: getIgnoreAutoTag(i),
|
||||
}
|
||||
|
||||
created, err := tqb.Create(tag)
|
||||
|
|
@ -1018,6 +1032,7 @@ func createStudios(sqb models.StudioReaderWriter, n int, o int) error {
|
|||
Name: sql.NullString{String: name, Valid: true},
|
||||
Checksum: md5.FromString(name),
|
||||
URL: getStudioNullStringValue(index, urlField),
|
||||
IgnoreAutoTag: getIgnoreAutoTag(i),
|
||||
}
|
||||
created, err := createStudioFromModel(sqb, studio)
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,11 @@ func (qb *studioQueryBuilder) QueryForAutoTag(words []string) ([]*models.Studio,
|
|||
args = append(args, ww)
|
||||
}
|
||||
|
||||
where := strings.Join(whereClauses, " OR ")
|
||||
whereOr := "(" + strings.Join(whereClauses, " OR ") + ")"
|
||||
where := strings.Join([]string{
|
||||
"studios.ignore_auto_tag = 0",
|
||||
whereOr,
|
||||
}, " AND ")
|
||||
return qb.queryStudios(query+" WHERE "+where, args)
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +210,7 @@ func (qb *studioQueryBuilder) makeFilter(studioFilter *models.StudioFilterType)
|
|||
query.handleCriterion(stringCriterionHandler(studioFilter.Details, studioTable+".details"))
|
||||
query.handleCriterion(stringCriterionHandler(studioFilter.URL, studioTable+".url"))
|
||||
query.handleCriterion(intCriterionHandler(studioFilter.Rating, studioTable+".rating"))
|
||||
query.handleCriterion(boolCriterionHandler(studioFilter.IgnoreAutoTag, studioTable+".ignore_auto_tag"))
|
||||
|
||||
query.handleCriterion(criterionHandlerFunc(func(f *filterBuilder) {
|
||||
if studioFilter.StashID != nil {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -183,11 +184,31 @@ func TestStudioIllegalQuery(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestStudioQueryIgnoreAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
ignoreAutoTag := true
|
||||
studioFilter := models.StudioFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}
|
||||
|
||||
sqb := r.Studio()
|
||||
|
||||
studios := queryStudio(t, sqb, &studioFilter, nil)
|
||||
|
||||
assert.Len(t, studios, int(math.Ceil(float64(totalStudios)/5)))
|
||||
for _, s := range studios {
|
||||
assert.True(t, s.IgnoreAutoTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestStudioQueryForAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
tqb := r.Studio()
|
||||
|
||||
name := studioNames[studioIdxWithScene] // find a studio by name
|
||||
name := studioNames[studioIdxWithMovie] // find a studio by name
|
||||
|
||||
studios, err := tqb.QueryForAutoTag([]string{name})
|
||||
|
||||
|
|
@ -195,12 +216,11 @@ func TestStudioQueryForAutoTag(t *testing.T) {
|
|||
t.Errorf("Error finding studios: %s", err.Error())
|
||||
}
|
||||
|
||||
assert.Len(t, studios, 2)
|
||||
assert.Equal(t, strings.ToLower(studioNames[studioIdxWithScene]), strings.ToLower(studios[0].Name.String))
|
||||
assert.Equal(t, strings.ToLower(studioNames[studioIdxWithScene]), strings.ToLower(studios[1].Name.String))
|
||||
assert.Len(t, studios, 1)
|
||||
assert.Equal(t, strings.ToLower(studioNames[studioIdxWithMovie]), strings.ToLower(studios[0].Name.String))
|
||||
|
||||
// find by alias
|
||||
name = getStudioStringValue(studioIdxWithScene, "Alias")
|
||||
name = getStudioStringValue(studioIdxWithMovie, "Alias")
|
||||
studios, err = tqb.QueryForAutoTag([]string{name})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -208,7 +228,7 @@ func TestStudioQueryForAutoTag(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Len(t, studios, 1)
|
||||
assert.Equal(t, studioIDs[studioIdxWithScene], studios[0].ID)
|
||||
assert.Equal(t, studioIDs[studioIdxWithMovie], studios[0].ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -245,7 +245,11 @@ func (qb *tagQueryBuilder) QueryForAutoTag(words []string) ([]*models.Tag, error
|
|||
args = append(args, ww)
|
||||
}
|
||||
|
||||
where := strings.Join(whereClauses, " OR ")
|
||||
whereOr := "(" + strings.Join(whereClauses, " OR ") + ")"
|
||||
where := strings.Join([]string{
|
||||
"tags.ignore_auto_tag = 0",
|
||||
whereOr,
|
||||
}, " AND ")
|
||||
return qb.queryTags(query+" WHERE "+where, args)
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +299,7 @@ func (qb *tagQueryBuilder) makeFilter(tagFilter *models.TagFilterType) *filterBu
|
|||
|
||||
query.handleCriterion(stringCriterionHandler(tagFilter.Name, tagTable+".name"))
|
||||
query.handleCriterion(tagAliasCriterionHandler(qb, tagFilter.Aliases))
|
||||
query.handleCriterion(boolCriterionHandler(tagFilter.IgnoreAutoTag, tagTable+".ignore_auto_tag"))
|
||||
|
||||
query.handleCriterion(tagIsMissingCriterionHandler(qb, tagFilter.IsMissing))
|
||||
query.handleCriterion(tagSceneCountCriterionHandler(qb, tagFilter.SceneCount))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ package sqlite_test
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -72,11 +73,31 @@ func TestTagFindByName(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestTagQueryIgnoreAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
ignoreAutoTag := true
|
||||
tagFilter := models.TagFilterType{
|
||||
IgnoreAutoTag: &ignoreAutoTag,
|
||||
}
|
||||
|
||||
sqb := r.Tag()
|
||||
|
||||
tags := queryTags(t, sqb, &tagFilter, nil)
|
||||
|
||||
assert.Len(t, tags, int(math.Ceil(float64(totalTags)/5)))
|
||||
for _, s := range tags {
|
||||
assert.True(t, s.IgnoreAutoTag)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestTagQueryForAutoTag(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
tqb := r.Tag()
|
||||
|
||||
name := tagNames[tagIdxWithScene] // find a tag by name
|
||||
name := tagNames[tagIdx1WithScene] // find a tag by name
|
||||
|
||||
tags, err := tqb.QueryForAutoTag([]string{name})
|
||||
|
||||
|
|
@ -85,12 +106,12 @@ func TestTagQueryForAutoTag(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Len(t, tags, 2)
|
||||
lcName := tagNames[tagIdxWithScene]
|
||||
lcName := tagNames[tagIdx1WithScene]
|
||||
assert.Equal(t, strings.ToLower(lcName), strings.ToLower(tags[0].Name))
|
||||
assert.Equal(t, strings.ToLower(lcName), strings.ToLower(tags[1].Name))
|
||||
|
||||
// find by alias
|
||||
name = getTagStringValue(tagIdxWithScene, "Alias")
|
||||
name = getTagStringValue(tagIdx1WithScene, "Alias")
|
||||
tags, err = tqb.QueryForAutoTag([]string{name})
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -98,7 +119,7 @@ func TestTagQueryForAutoTag(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Len(t, tags, 1)
|
||||
assert.Equal(t, tagIDs[tagIdxWithScene], tags[0].ID)
|
||||
assert.Equal(t, tagIDs[tagIdx1WithScene], tags[0].ID)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
// ToJSON converts a Studio object into its JSON equivalent.
|
||||
func ToJSON(reader models.StudioReader, studio *models.Studio) (*jsonschema.Studio, error) {
|
||||
newStudioJSON := jsonschema.Studio{
|
||||
IgnoreAutoTag: studio.IgnoreAutoTag,
|
||||
CreatedAt: models.JSONTime{Time: studio.CreatedAt.Timestamp},
|
||||
UpdatedAt: models.JSONTime{Time: studio.UpdatedAt.Timestamp},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const (
|
|||
details = "details"
|
||||
rating = 5
|
||||
parentStudioName = "parentStudio"
|
||||
autoTagIgnored = true
|
||||
)
|
||||
|
||||
var parentStudio models.Studio = models.Studio{
|
||||
|
|
@ -67,6 +68,7 @@ func createFullStudio(id int, parentID int) models.Studio {
|
|||
Timestamp: updateTime,
|
||||
},
|
||||
Rating: models.NullInt64(rating),
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
}
|
||||
|
||||
if parentID != 0 {
|
||||
|
|
@ -106,6 +108,7 @@ func createFullJSONStudio(parentStudio, image string, aliases []string) *jsonsch
|
|||
StashIDs: []models.StashID{
|
||||
stashID,
|
||||
},
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ func (i *Importer) PreImport() error {
|
|||
Name: sql.NullString{String: i.Input.Name, Valid: true},
|
||||
URL: sql.NullString{String: i.Input.URL, Valid: true},
|
||||
Details: sql.NullString{String: i.Input.Details, Valid: true},
|
||||
IgnoreAutoTag: i.Input.IgnoreAutoTag,
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()},
|
||||
Rating: sql.NullInt64{Int64: int64(i.Input.Rating), Valid: true},
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ func TestImporterPreImport(t *testing.T) {
|
|||
Input: jsonschema.Studio{
|
||||
Name: studioName,
|
||||
Image: invalidImage,
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
func ToJSON(reader models.TagReader, tag *models.Tag) (*jsonschema.Tag, error) {
|
||||
newTagJSON := jsonschema.Tag{
|
||||
Name: tag.Name,
|
||||
IgnoreAutoTag: tag.IgnoreAutoTag,
|
||||
CreatedAt: models.JSONTime{Time: tag.CreatedAt.Timestamp},
|
||||
UpdatedAt: models.JSONTime{Time: tag.UpdatedAt.Timestamp},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const (
|
|||
const tagName = "testTag"
|
||||
|
||||
var (
|
||||
autoTagIgnored = true
|
||||
createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
updateTime = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
|
@ -32,6 +33,7 @@ func createTag(id int) models.Tag {
|
|||
return models.Tag{
|
||||
ID: id,
|
||||
Name: tagName,
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
CreatedAt: models.SQLiteTimestamp{
|
||||
Timestamp: createTime,
|
||||
},
|
||||
|
|
@ -45,6 +47,7 @@ func createJSONTag(aliases []string, image string, parents []string) *jsonschema
|
|||
return &jsonschema.Tag{
|
||||
Name: tagName,
|
||||
Aliases: aliases,
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
CreatedAt: models.JSONTime{
|
||||
Time: createTime,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ type Importer struct {
|
|||
func (i *Importer) PreImport() error {
|
||||
i.tag = models.Tag{
|
||||
Name: i.Input.Name,
|
||||
IgnoreAutoTag: i.Input.IgnoreAutoTag,
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func TestImporterPreImport(t *testing.T) {
|
|||
Input: jsonschema.Tag{
|
||||
Name: tagName,
|
||||
Image: invalidImage,
|
||||
IgnoreAutoTag: autoTagIgnored,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
##### 💥 Note: Image Slideshow Delay (in Interface Settings) is now in seconds rather than milliseconds and has not been converted. Please adjust your settings as needed.
|
||||
|
||||
### ✨ New Features
|
||||
* Add Ignore Auto Tag flag to Performers, Studios and Tags. ([#2439](https://github.com/stashapp/stash/pull/2439))
|
||||
* Add python location in System Settings for script scrapers and plugins. ([#2409](https://github.com/stashapp/stash/pull/2409))
|
||||
|
||||
### 🎨 Improvements
|
||||
* Revamped scene details page. ([#2466](https://github.com/stashapp/stash/pull/2466))
|
||||
* Restyled scene details page on mobile devices. ([#2466](https://github.com/stashapp/stash/pull/2466))
|
||||
* Added support for Handy APIv2. ([#2193](https://github.com/stashapp/stash/pull/2193))
|
||||
* Hide tabs with no content in Performer, Studio and Tag pages. ([#2468](https://github.com/stashapp/stash/pull/2468))
|
||||
* Added support for bulk editing most performer fields. ([#2467](https://github.com/stashapp/stash/pull/2467))
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ const performerFields = [
|
|||
"hair_color",
|
||||
"tattoos",
|
||||
"piercings",
|
||||
"ignore_auto_tag",
|
||||
];
|
||||
|
||||
export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
||||
|
|
@ -302,6 +303,16 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||
mode={tagIds.mode}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group controlId="ignore-auto-tags">
|
||||
<IndeterminateCheckbox
|
||||
label={intl.formatMessage({ id: "ignore_auto_tag" })}
|
||||
setChecked={(checked) =>
|
||||
setUpdateField({ ignore_auto_tag: checked })
|
||||
}
|
||||
checked={updateInput.ignore_auto_tag ?? undefined}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
death_date: yup.string().optional(),
|
||||
hair_color: yup.string().optional(),
|
||||
weight: yup.number().optional(),
|
||||
ignore_auto_tag: yup.boolean().optional(),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
|
|
@ -143,6 +144,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
death_date: performer.death_date ?? "",
|
||||
hair_color: performer.hair_color ?? "",
|
||||
weight: performer.weight ?? undefined,
|
||||
ignore_auto_tag: performer.ignore_auto_tag ?? false,
|
||||
};
|
||||
|
||||
type InputValues = typeof initialValues;
|
||||
|
|
@ -944,6 +946,22 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
|
||||
{renderStashIDs()}
|
||||
|
||||
<hr />
|
||||
|
||||
<Form.Group controlId="ignore-auto-tag" as={Row}>
|
||||
<Form.Label column sm={labelXS} xl={labelXL}>
|
||||
<FormattedMessage id="ignore_auto_tag" />
|
||||
</Form.Label>
|
||||
<Col sm={fieldXS} xl={fieldXL}>
|
||||
<Form.Check
|
||||
{...formik.getFieldProps({
|
||||
name: "ignore_auto_tag",
|
||||
type: "checkbox",
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
|
||||
{renderButtons("mt-3")}
|
||||
</Form>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
},
|
||||
message: "aliases must be unique",
|
||||
}),
|
||||
ignore_auto_tag: yup.boolean().optional(),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
|
|
@ -66,6 +67,7 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
parent_id: studio.parent_studio?.id,
|
||||
stash_ids: studio.stash_ids ?? undefined,
|
||||
aliases: studio.aliases,
|
||||
ignore_auto_tag: studio.ignore_auto_tag ?? false,
|
||||
};
|
||||
|
||||
type InputValues = typeof initialValues;
|
||||
|
|
@ -317,6 +319,22 @@ export const StudioEditPanel: React.FC<IStudioEditPanel> = ({
|
|||
</Form.Group>
|
||||
</Form>
|
||||
|
||||
<hr />
|
||||
|
||||
<Form.Group controlId="ignore-auto-tag" as={Row}>
|
||||
<Form.Label column xs={3}>
|
||||
<FormattedMessage id="ignore_auto_tag" />
|
||||
</Form.Label>
|
||||
<Col xs={9}>
|
||||
<Form.Check
|
||||
{...formik.getFieldProps({
|
||||
name: "ignore_auto_tag",
|
||||
type: "checkbox",
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
|
||||
<DetailsEditNavbar
|
||||
objectName={studio?.name ?? intl.formatMessage({ id: "studio" })}
|
||||
isNew={isNew}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export const TagEditPanel: React.FC<ITagEditPanel> = ({
|
|||
}),
|
||||
parent_ids: yup.array(yup.string().required()).optional().nullable(),
|
||||
child_ids: yup.array(yup.string().required()).optional().nullable(),
|
||||
ignore_auto_tag: yup.boolean().optional(),
|
||||
});
|
||||
|
||||
const initialValues = {
|
||||
|
|
@ -60,6 +61,7 @@ export const TagEditPanel: React.FC<ITagEditPanel> = ({
|
|||
aliases: tag?.aliases,
|
||||
parent_ids: (tag?.parents ?? []).map((t) => t.id),
|
||||
child_ids: (tag?.children ?? []).map((t) => t.id),
|
||||
ignore_auto_tag: tag?.ignore_auto_tag ?? false,
|
||||
};
|
||||
|
||||
type InputValues = typeof initialValues;
|
||||
|
|
@ -211,6 +213,22 @@ export const TagEditPanel: React.FC<ITagEditPanel> = ({
|
|||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
|
||||
<hr />
|
||||
|
||||
<Form.Group controlId="ignore-auto-tag" as={Row}>
|
||||
<Form.Label column xs={labelXS} xl={labelXL}>
|
||||
<FormattedMessage id="ignore_auto_tag" />
|
||||
</Form.Label>
|
||||
<Col xs={fieldXS} xl={fieldXL}>
|
||||
<Form.Check
|
||||
{...formik.getFieldProps({
|
||||
name: "ignore_auto_tag",
|
||||
type: "checkbox",
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
|
||||
<DetailsEditNavbar
|
||||
|
|
|
|||
|
|
@ -870,6 +870,11 @@ dl.details-list {
|
|||
grid-template-columns: minmax(16.67%, auto) 1fr;
|
||||
}
|
||||
|
||||
// middle align checkboxes
|
||||
.form-group .form-check .form-check-input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
// Fix Safari styling on dropdowns
|
||||
select {
|
||||
-webkit-appearance: none;
|
||||
|
|
|
|||
|
|
@ -733,6 +733,7 @@
|
|||
"hasMarkers": "Has Markers",
|
||||
"height": "Height",
|
||||
"help": "Help",
|
||||
"ignore_auto_tag": "Ignore Auto Tag",
|
||||
"image": "Image",
|
||||
"image_count": "Image Count",
|
||||
"images": "Images",
|
||||
|
|
|
|||
|
|
@ -291,6 +291,18 @@ export class BooleanCriterion extends StringCriterion {
|
|||
}
|
||||
}
|
||||
|
||||
export function createBooleanCriterionOption(
|
||||
value: CriterionType,
|
||||
messageID?: string,
|
||||
parameterName?: string
|
||||
) {
|
||||
return new BooleanCriterionOption(
|
||||
messageID ?? value,
|
||||
value,
|
||||
parameterName ?? messageID ?? value
|
||||
);
|
||||
}
|
||||
|
||||
export class NumberCriterionOption extends CriterionOption {
|
||||
constructor(
|
||||
messageID: string,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
MandatoryNumberCriterionOption,
|
||||
StringCriterionOption,
|
||||
ILabeledIdCriterion,
|
||||
BooleanCriterion,
|
||||
BooleanCriterionOption,
|
||||
} from "./criterion";
|
||||
import { OrganizedCriterion } from "./organized";
|
||||
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
|
||||
|
|
@ -173,5 +175,7 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||
"child_count"
|
||||
)
|
||||
);
|
||||
case "ignore_auto_tag":
|
||||
return new BooleanCriterion(new BooleanCriterionOption(type, type));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
createNumberCriterionOption,
|
||||
createMandatoryNumberCriterionOption,
|
||||
createStringCriterionOption,
|
||||
createBooleanCriterionOption,
|
||||
} from "./criteria/criterion";
|
||||
import { FavoriteCriterionOption } from "./criteria/favorite";
|
||||
import { GenderCriterionOption } from "./criteria/gender";
|
||||
|
|
@ -79,6 +80,7 @@ const criterionOptions = [
|
|||
createMandatoryNumberCriterionOption("scene_count"),
|
||||
createMandatoryNumberCriterionOption("image_count"),
|
||||
createMandatoryNumberCriterionOption("gallery_count"),
|
||||
createBooleanCriterionOption("ignore_auto_tag"),
|
||||
...numberCriteria.map((c) => createNumberCriterionOption(c)),
|
||||
...stringCriteria.map((c) => createStringCriterionOption(c)),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
createBooleanCriterionOption,
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
|
|
@ -34,6 +35,7 @@ const criterionOptions = [
|
|||
ParentStudiosCriterionOption,
|
||||
StudioIsMissingCriterionOption,
|
||||
RatingCriterionOption,
|
||||
createBooleanCriterionOption("ignore_auto_tag"),
|
||||
createMandatoryNumberCriterionOption("scene_count"),
|
||||
createMandatoryNumberCriterionOption("image_count"),
|
||||
createMandatoryNumberCriterionOption("gallery_count"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
createBooleanCriterionOption,
|
||||
createMandatoryNumberCriterionOption,
|
||||
createMandatoryStringCriterionOption,
|
||||
createStringCriterionOption,
|
||||
|
|
@ -43,6 +44,7 @@ const criterionOptions = [
|
|||
createMandatoryStringCriterionOption("name"),
|
||||
TagIsMissingCriterionOption,
|
||||
createStringCriterionOption("aliases"),
|
||||
createBooleanCriterionOption("ignore_auto_tag"),
|
||||
createMandatoryNumberCriterionOption("scene_count"),
|
||||
createMandatoryNumberCriterionOption("image_count"),
|
||||
createMandatoryNumberCriterionOption("gallery_count"),
|
||||
|
|
|
|||
|
|
@ -127,4 +127,5 @@ export type CriterionType =
|
|||
| "child_tag_count"
|
||||
| "performer_favorite"
|
||||
| "performer_age"
|
||||
| "duplicated";
|
||||
| "duplicated"
|
||||
| "ignore_auto_tag";
|
||||
|
|
|
|||
Loading…
Reference in a new issue