mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Add penis length and circumcision stats to performers. (#3627)
* Add penis length stat to performers. * Modified the UI to display and edit the stat. * Added the ability to filter floats to allow filtering by penis length. * Add circumcision stat to performer. * Refactor enum filtering * Change boolean filter to radio buttons * Return null for empty enum values --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,8 @@ fragment SlimPerformerData on Performer {
|
|||||||
eye_color
|
eye_color
|
||||||
height_cm
|
height_cm
|
||||||
fake_tits
|
fake_tits
|
||||||
|
penis_length
|
||||||
|
circumcised
|
||||||
career_length
|
career_length
|
||||||
tattoos
|
tattoos
|
||||||
piercings
|
piercings
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ fragment PerformerData on Performer {
|
|||||||
height_cm
|
height_cm
|
||||||
measurements
|
measurements
|
||||||
fake_tits
|
fake_tits
|
||||||
|
penis_length
|
||||||
|
circumcised
|
||||||
career_length
|
career_length
|
||||||
tattoos
|
tattoos
|
||||||
piercings
|
piercings
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ fragment ScrapedPerformerData on ScrapedPerformer {
|
|||||||
height
|
height
|
||||||
measurements
|
measurements
|
||||||
fake_tits
|
fake_tits
|
||||||
|
penis_length
|
||||||
|
circumcised
|
||||||
career_length
|
career_length
|
||||||
tattoos
|
tattoos
|
||||||
piercings
|
piercings
|
||||||
@@ -43,6 +45,8 @@ fragment ScrapedScenePerformerData on ScrapedPerformer {
|
|||||||
height
|
height
|
||||||
measurements
|
measurements
|
||||||
fake_tits
|
fake_tits
|
||||||
|
penis_length
|
||||||
|
circumcised
|
||||||
career_length
|
career_length
|
||||||
tattoos
|
tattoos
|
||||||
piercings
|
piercings
|
||||||
|
|||||||
@@ -76,6 +76,10 @@ input PerformerFilterType {
|
|||||||
measurements: StringCriterionInput
|
measurements: StringCriterionInput
|
||||||
"""Filter by fake tits value"""
|
"""Filter by fake tits value"""
|
||||||
fake_tits: StringCriterionInput
|
fake_tits: StringCriterionInput
|
||||||
|
"""Filter by penis length value"""
|
||||||
|
penis_length: FloatCriterionInput
|
||||||
|
"""Filter by ciricumcision"""
|
||||||
|
circumcised: CircumcisionCriterionInput
|
||||||
"""Filter by career length"""
|
"""Filter by career length"""
|
||||||
career_length: StringCriterionInput
|
career_length: StringCriterionInput
|
||||||
"""Filter by tattoos"""
|
"""Filter by tattoos"""
|
||||||
@@ -505,6 +509,12 @@ input IntCriterionInput {
|
|||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input FloatCriterionInput {
|
||||||
|
value: Float!
|
||||||
|
value2: Float
|
||||||
|
modifier: CriterionModifier!
|
||||||
|
}
|
||||||
|
|
||||||
input MultiCriterionInput {
|
input MultiCriterionInput {
|
||||||
value: [ID!]
|
value: [ID!]
|
||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
@@ -515,6 +525,11 @@ input GenderCriterionInput {
|
|||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CircumcisionCriterionInput {
|
||||||
|
value: [CircumisedEnum!]
|
||||||
|
modifier: CriterionModifier!
|
||||||
|
}
|
||||||
|
|
||||||
input HierarchicalMultiCriterionInput {
|
input HierarchicalMultiCriterionInput {
|
||||||
value: [ID!]
|
value: [ID!]
|
||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ enum GenderEnum {
|
|||||||
NON_BINARY
|
NON_BINARY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum CircumisedEnum {
|
||||||
|
CUT
|
||||||
|
UNCUT
|
||||||
|
}
|
||||||
|
|
||||||
type Performer {
|
type Performer {
|
||||||
id: ID!
|
id: ID!
|
||||||
checksum: String @deprecated(reason: "Not used")
|
checksum: String @deprecated(reason: "Not used")
|
||||||
@@ -24,6 +29,8 @@ type Performer {
|
|||||||
height_cm: Int
|
height_cm: Int
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: Float
|
||||||
|
circumcised: CircumisedEnum
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
@@ -69,6 +76,8 @@ input PerformerCreateInput {
|
|||||||
height_cm: Int
|
height_cm: Int
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: Float
|
||||||
|
circumcised: CircumisedEnum
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
@@ -107,6 +116,8 @@ input PerformerUpdateInput {
|
|||||||
height_cm: Int
|
height_cm: Int
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: Float
|
||||||
|
circumcised: CircumisedEnum
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
@@ -150,6 +161,8 @@ input BulkPerformerUpdateInput {
|
|||||||
height_cm: Int
|
height_cm: Int
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: Float
|
||||||
|
circumcised: CircumisedEnum
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ type ScrapedPerformer {
|
|||||||
height: String
|
height: String
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: String
|
||||||
|
circumcised: String
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
@@ -48,6 +50,8 @@ input ScrapedPerformerInput {
|
|||||||
height: String
|
height: String
|
||||||
measurements: String
|
measurements: String
|
||||||
fake_tits: String
|
fake_tits: String
|
||||||
|
penis_length: String
|
||||||
|
circumcised: String
|
||||||
career_length: String
|
career_length: String
|
||||||
tattoos: String
|
tattoos: String
|
||||||
piercings: String
|
piercings: String
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func initialiseCustomImages() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRandomPerformerImageUsingName(name string, gender models.GenderEnum, customPath string) ([]byte, error) {
|
func getRandomPerformerImageUsingName(name string, gender *models.GenderEnum, customPath string) ([]byte, error) {
|
||||||
var box *imageBox
|
var box *imageBox
|
||||||
|
|
||||||
// If we have a custom path, we should return a new box in the given path.
|
// If we have a custom path, we should return a new box in the given path.
|
||||||
@@ -95,8 +95,13 @@ func getRandomPerformerImageUsingName(name string, gender models.GenderEnum, cus
|
|||||||
box = performerBoxCustom
|
box = performerBoxCustom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var g models.GenderEnum
|
||||||
|
if gender != nil {
|
||||||
|
g = *gender
|
||||||
|
}
|
||||||
|
|
||||||
if box == nil {
|
if box == nil {
|
||||||
switch gender {
|
switch g {
|
||||||
case models.GenderEnumFemale:
|
case models.GenderEnumFemale:
|
||||||
box = performerBox
|
box = performerBox
|
||||||
case models.GenderEnumMale:
|
case models.GenderEnumMale:
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||||||
newPerformer.URL = *input.URL
|
newPerformer.URL = *input.URL
|
||||||
}
|
}
|
||||||
if input.Gender != nil {
|
if input.Gender != nil {
|
||||||
newPerformer.Gender = *input.Gender
|
newPerformer.Gender = input.Gender
|
||||||
}
|
}
|
||||||
if input.Birthdate != nil {
|
if input.Birthdate != nil {
|
||||||
d := models.NewDate(*input.Birthdate)
|
d := models.NewDate(*input.Birthdate)
|
||||||
@@ -98,6 +98,12 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerC
|
|||||||
if input.FakeTits != nil {
|
if input.FakeTits != nil {
|
||||||
newPerformer.FakeTits = *input.FakeTits
|
newPerformer.FakeTits = *input.FakeTits
|
||||||
}
|
}
|
||||||
|
if input.PenisLength != nil {
|
||||||
|
newPerformer.PenisLength = input.PenisLength
|
||||||
|
}
|
||||||
|
if input.Circumcised != nil {
|
||||||
|
newPerformer.Circumcised = input.Circumcised
|
||||||
|
}
|
||||||
if input.CareerLength != nil {
|
if input.CareerLength != nil {
|
||||||
newPerformer.CareerLength = *input.CareerLength
|
newPerformer.CareerLength = *input.CareerLength
|
||||||
}
|
}
|
||||||
@@ -222,6 +228,16 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerU
|
|||||||
|
|
||||||
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
updatedPerformer.Ethnicity = translator.optionalString(input.Ethnicity, "ethnicity")
|
||||||
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
||||||
|
updatedPerformer.PenisLength = translator.optionalFloat64(input.PenisLength, "penis_length")
|
||||||
|
|
||||||
|
if translator.hasField("circumcised") {
|
||||||
|
if input.Circumcised != nil {
|
||||||
|
updatedPerformer.Circumcised = models.NewOptionalString(input.Circumcised.String())
|
||||||
|
} else {
|
||||||
|
updatedPerformer.Circumcised = models.NewOptionalStringPtr(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||||
@@ -339,6 +355,16 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe
|
|||||||
|
|
||||||
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
|
updatedPerformer.Measurements = translator.optionalString(input.Measurements, "measurements")
|
||||||
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
updatedPerformer.FakeTits = translator.optionalString(input.FakeTits, "fake_tits")
|
||||||
|
updatedPerformer.PenisLength = translator.optionalFloat64(input.PenisLength, "penis_length")
|
||||||
|
|
||||||
|
if translator.hasField("circumcised") {
|
||||||
|
if input.Circumcised != nil {
|
||||||
|
updatedPerformer.Circumcised = models.NewOptionalString(input.Circumcised.String())
|
||||||
|
} else {
|
||||||
|
updatedPerformer.Circumcised = models.NewOptionalStringPtr(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
updatedPerformer.CareerLength = translator.optionalString(input.CareerLength, "career_length")
|
||||||
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
updatedPerformer.Tattoos = translator.optionalString(input.Tattoos, "tattoos")
|
||||||
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
updatedPerformer.Piercings = translator.optionalString(input.Piercings, "piercings")
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ func scrapedToPerformerInput(performer *models.ScrapedPerformer) models.Performe
|
|||||||
ret.DeathDate = &d
|
ret.DeathDate = &d
|
||||||
}
|
}
|
||||||
if performer.Gender != nil {
|
if performer.Gender != nil {
|
||||||
ret.Gender = models.GenderEnum(*performer.Gender)
|
v := models.GenderEnum(*performer.Gender)
|
||||||
|
ret.Gender = &v
|
||||||
}
|
}
|
||||||
if performer.Ethnicity != nil {
|
if performer.Ethnicity != nil {
|
||||||
ret.Ethnicity = *performer.Ethnicity
|
ret.Ethnicity = *performer.Ethnicity
|
||||||
@@ -97,6 +98,16 @@ func scrapedToPerformerInput(performer *models.ScrapedPerformer) models.Performe
|
|||||||
if performer.FakeTits != nil {
|
if performer.FakeTits != nil {
|
||||||
ret.FakeTits = *performer.FakeTits
|
ret.FakeTits = *performer.FakeTits
|
||||||
}
|
}
|
||||||
|
if performer.PenisLength != nil {
|
||||||
|
h, err := strconv.ParseFloat(*performer.PenisLength, 64)
|
||||||
|
if err == nil {
|
||||||
|
ret.PenisLength = &h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if performer.Circumcised != nil {
|
||||||
|
v := models.CircumisedEnum(*performer.Circumcised)
|
||||||
|
ret.Circumcised = &v
|
||||||
|
}
|
||||||
if performer.CareerLength != nil {
|
if performer.CareerLength != nil {
|
||||||
ret.CareerLength = *performer.CareerLength
|
ret.CareerLength = *performer.CareerLength
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,10 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||||||
return &d
|
return &d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genderPtr := func(g models.GenderEnum) *models.GenderEnum {
|
||||||
|
return &g
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
performer *models.ScrapedPerformer
|
performer *models.ScrapedPerformer
|
||||||
@@ -259,7 +263,7 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
|||||||
Name: name,
|
Name: name,
|
||||||
Birthdate: dateToDatePtr(models.NewDate(*nextVal())),
|
Birthdate: dateToDatePtr(models.NewDate(*nextVal())),
|
||||||
DeathDate: dateToDatePtr(models.NewDate(*nextVal())),
|
DeathDate: dateToDatePtr(models.NewDate(*nextVal())),
|
||||||
Gender: models.GenderEnum(*nextVal()),
|
Gender: genderPtr(models.GenderEnum(*nextVal())),
|
||||||
Ethnicity: *nextVal(),
|
Ethnicity: *nextVal(),
|
||||||
Country: *nextVal(),
|
Country: *nextVal(),
|
||||||
EyeColor: *nextVal(),
|
EyeColor: *nextVal(),
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
|||||||
EyeColor: getString(performer.EyeColor),
|
EyeColor: getString(performer.EyeColor),
|
||||||
HairColor: getString(performer.HairColor),
|
HairColor: getString(performer.HairColor),
|
||||||
FakeTits: getString(performer.FakeTits),
|
FakeTits: getString(performer.FakeTits),
|
||||||
Gender: models.GenderEnum(getString(performer.Gender)),
|
|
||||||
Height: getIntPtr(performer.Height),
|
Height: getIntPtr(performer.Height),
|
||||||
Weight: getIntPtr(performer.Weight),
|
Weight: getIntPtr(performer.Weight),
|
||||||
Instagram: getString(performer.Instagram),
|
Instagram: getString(performer.Instagram),
|
||||||
@@ -150,6 +149,11 @@ func (t *StashBoxPerformerTagTask) stashBoxPerformerTag(ctx context.Context) {
|
|||||||
UpdatedAt: currentTime,
|
UpdatedAt: currentTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performer.Gender != nil {
|
||||||
|
v := models.GenderEnum(getString(performer.Gender))
|
||||||
|
newPerformer.Gender = &v
|
||||||
|
}
|
||||||
|
|
||||||
err := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
|
err := txn.WithTxn(ctx, instance.Repository, func(ctx context.Context) error {
|
||||||
r := instance.Repository
|
r := instance.Repository
|
||||||
err := r.Performer.Create(ctx, &newPerformer)
|
err := r.Performer.Create(ctx, &newPerformer)
|
||||||
|
|||||||
@@ -109,6 +109,20 @@ func (i IntCriterionInput) ValidModifier() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FloatCriterionInput struct {
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
Value2 *float64 `json:"value2"`
|
||||||
|
Modifier CriterionModifier `json:"modifier"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i FloatCriterionInput) ValidModifier() bool {
|
||||||
|
switch i.Modifier {
|
||||||
|
case CriterionModifierEquals, CriterionModifierNotEquals, CriterionModifierGreaterThan, CriterionModifierLessThan, CriterionModifierIsNull, CriterionModifierNotNull, CriterionModifierBetween, CriterionModifierNotBetween:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type ResolutionCriterionInput struct {
|
type ResolutionCriterionInput struct {
|
||||||
Value ResolutionEnum `json:"value"`
|
Value ResolutionEnum `json:"value"`
|
||||||
Modifier CriterionModifier `json:"modifier"`
|
Modifier CriterionModifier `json:"modifier"`
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ type Performer struct {
|
|||||||
Height string `json:"height,omitempty"`
|
Height string `json:"height,omitempty"`
|
||||||
Measurements string `json:"measurements,omitempty"`
|
Measurements string `json:"measurements,omitempty"`
|
||||||
FakeTits string `json:"fake_tits,omitempty"`
|
FakeTits string `json:"fake_tits,omitempty"`
|
||||||
|
PenisLength float64 `json:"penis_length,omitempty"`
|
||||||
|
Circumcised string `json:"circumcised,omitempty"`
|
||||||
CareerLength string `json:"career_length,omitempty"`
|
CareerLength string `json:"career_length,omitempty"`
|
||||||
Tattoos string `json:"tattoos,omitempty"`
|
Tattoos string `json:"tattoos,omitempty"`
|
||||||
Piercings string `json:"piercings,omitempty"`
|
Piercings string `json:"piercings,omitempty"`
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type Performer struct {
|
|||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Disambiguation string `json:"disambiguation"`
|
Disambiguation string `json:"disambiguation"`
|
||||||
Gender GenderEnum `json:"gender"`
|
Gender *GenderEnum `json:"gender"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Twitter string `json:"twitter"`
|
Twitter string `json:"twitter"`
|
||||||
Instagram string `json:"instagram"`
|
Instagram string `json:"instagram"`
|
||||||
@@ -20,6 +20,8 @@ type Performer struct {
|
|||||||
Height *int `json:"height"`
|
Height *int `json:"height"`
|
||||||
Measurements string `json:"measurements"`
|
Measurements string `json:"measurements"`
|
||||||
FakeTits string `json:"fake_tits"`
|
FakeTits string `json:"fake_tits"`
|
||||||
|
PenisLength *float64 `json:"penis_length"`
|
||||||
|
Circumcised *CircumisedEnum `json:"circumcised"`
|
||||||
CareerLength string `json:"career_length"`
|
CareerLength string `json:"career_length"`
|
||||||
Tattoos string `json:"tattoos"`
|
Tattoos string `json:"tattoos"`
|
||||||
Piercings string `json:"piercings"`
|
Piercings string `json:"piercings"`
|
||||||
@@ -90,6 +92,8 @@ type PerformerPartial struct {
|
|||||||
Height OptionalInt
|
Height OptionalInt
|
||||||
Measurements OptionalString
|
Measurements OptionalString
|
||||||
FakeTits OptionalString
|
FakeTits OptionalString
|
||||||
|
PenisLength OptionalFloat64
|
||||||
|
Circumcised OptionalString
|
||||||
CareerLength OptionalString
|
CareerLength OptionalString
|
||||||
Tattoos OptionalString
|
Tattoos OptionalString
|
||||||
Piercings OptionalString
|
Piercings OptionalString
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ type ScrapedPerformer struct {
|
|||||||
Height *string `json:"height"`
|
Height *string `json:"height"`
|
||||||
Measurements *string `json:"measurements"`
|
Measurements *string `json:"measurements"`
|
||||||
FakeTits *string `json:"fake_tits"`
|
FakeTits *string `json:"fake_tits"`
|
||||||
|
PenisLength *string `json:"penis_length"`
|
||||||
|
Circumcised *string `json:"circumcised"`
|
||||||
CareerLength *string `json:"career_length"`
|
CareerLength *string `json:"career_length"`
|
||||||
Tattoos *string `json:"tattoos"`
|
Tattoos *string `json:"tattoos"`
|
||||||
Piercings *string `json:"piercings"`
|
Piercings *string `json:"piercings"`
|
||||||
|
|||||||
@@ -61,6 +61,52 @@ type GenderCriterionInput struct {
|
|||||||
Modifier CriterionModifier `json:"modifier"`
|
Modifier CriterionModifier `json:"modifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CircumisedEnum string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CircumisedEnumCut CircumisedEnum = "CUT"
|
||||||
|
CircumisedEnumUncut CircumisedEnum = "UNCUT"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllCircumcisionEnum = []CircumisedEnum{
|
||||||
|
CircumisedEnumCut,
|
||||||
|
CircumisedEnumUncut,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e CircumisedEnum) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case CircumisedEnumCut, CircumisedEnumUncut:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e CircumisedEnum) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CircumisedEnum) UnmarshalGQL(v interface{}) error {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("enums must be strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = CircumisedEnum(str)
|
||||||
|
if !e.IsValid() {
|
||||||
|
return fmt.Errorf("%s is not a valid CircumisedEnum", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e CircumisedEnum) MarshalGQL(w io.Writer) {
|
||||||
|
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type CircumcisionCriterionInput struct {
|
||||||
|
Value []CircumisedEnum `json:"value"`
|
||||||
|
Modifier CriterionModifier `json:"modifier"`
|
||||||
|
}
|
||||||
|
|
||||||
type PerformerFilterType struct {
|
type PerformerFilterType struct {
|
||||||
And *PerformerFilterType `json:"AND"`
|
And *PerformerFilterType `json:"AND"`
|
||||||
Or *PerformerFilterType `json:"OR"`
|
Or *PerformerFilterType `json:"OR"`
|
||||||
@@ -88,6 +134,10 @@ type PerformerFilterType struct {
|
|||||||
Measurements *StringCriterionInput `json:"measurements"`
|
Measurements *StringCriterionInput `json:"measurements"`
|
||||||
// Filter by fake tits value
|
// Filter by fake tits value
|
||||||
FakeTits *StringCriterionInput `json:"fake_tits"`
|
FakeTits *StringCriterionInput `json:"fake_tits"`
|
||||||
|
// Filter by penis length value
|
||||||
|
PenisLength *FloatCriterionInput `json:"penis_length"`
|
||||||
|
// Filter by circumcision
|
||||||
|
Circumcised *CircumcisionCriterionInput `json:"circumcised"`
|
||||||
// Filter by career length
|
// Filter by career length
|
||||||
CareerLength *StringCriterionInput `json:"career_length"`
|
CareerLength *StringCriterionInput `json:"career_length"`
|
||||||
// Filter by tattoos
|
// Filter by tattoos
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ func ToJSON(ctx context.Context, reader ImageAliasStashIDGetter, performer *mode
|
|||||||
newPerformerJSON := jsonschema.Performer{
|
newPerformerJSON := jsonschema.Performer{
|
||||||
Name: performer.Name,
|
Name: performer.Name,
|
||||||
Disambiguation: performer.Disambiguation,
|
Disambiguation: performer.Disambiguation,
|
||||||
Gender: performer.Gender.String(),
|
|
||||||
URL: performer.URL,
|
URL: performer.URL,
|
||||||
Ethnicity: performer.Ethnicity,
|
Ethnicity: performer.Ethnicity,
|
||||||
Country: performer.Country,
|
Country: performer.Country,
|
||||||
@@ -43,6 +42,14 @@ func ToJSON(ctx context.Context, reader ImageAliasStashIDGetter, performer *mode
|
|||||||
UpdatedAt: json.JSONTime{Time: performer.UpdatedAt},
|
UpdatedAt: json.JSONTime{Time: performer.UpdatedAt},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performer.Gender != nil {
|
||||||
|
newPerformerJSON.Gender = performer.Gender.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if performer.Circumcised != nil {
|
||||||
|
newPerformerJSON.Circumcised = performer.Circumcised.String()
|
||||||
|
}
|
||||||
|
|
||||||
if performer.Birthdate != nil {
|
if performer.Birthdate != nil {
|
||||||
newPerformerJSON.Birthdate = performer.Birthdate.String()
|
newPerformerJSON.Birthdate = performer.Birthdate.String()
|
||||||
}
|
}
|
||||||
@@ -61,6 +68,10 @@ func ToJSON(ctx context.Context, reader ImageAliasStashIDGetter, performer *mode
|
|||||||
newPerformerJSON.Weight = *performer.Weight
|
newPerformerJSON.Weight = *performer.Weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performer.PenisLength != nil {
|
||||||
|
newPerformerJSON.PenisLength = *performer.PenisLength
|
||||||
|
}
|
||||||
|
|
||||||
if err := performer.LoadAliases(ctx, reader); err != nil {
|
if err := performer.LoadAliases(ctx, reader); err != nil {
|
||||||
return nil, fmt.Errorf("loading performer aliases: %w", err)
|
return nil, fmt.Errorf("loading performer aliases: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const (
|
|||||||
ethnicity = "ethnicity"
|
ethnicity = "ethnicity"
|
||||||
eyeColor = "eyeColor"
|
eyeColor = "eyeColor"
|
||||||
fakeTits = "fakeTits"
|
fakeTits = "fakeTits"
|
||||||
gender = "gender"
|
|
||||||
instagram = "instagram"
|
instagram = "instagram"
|
||||||
measurements = "measurements"
|
measurements = "measurements"
|
||||||
piercings = "piercings"
|
piercings = "piercings"
|
||||||
@@ -42,10 +41,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
genderEnum = models.GenderEnumFemale
|
||||||
|
gender = genderEnum.String()
|
||||||
aliases = []string{"alias1", "alias2"}
|
aliases = []string{"alias1", "alias2"}
|
||||||
rating = 5
|
rating = 5
|
||||||
height = 123
|
height = 123
|
||||||
weight = 60
|
weight = 60
|
||||||
|
penisLength = 1.23
|
||||||
|
circumcisedEnum = models.CircumisedEnumCut
|
||||||
|
circumcised = circumcisedEnum.String()
|
||||||
)
|
)
|
||||||
|
|
||||||
var imageBytes = []byte("imageBytes")
|
var imageBytes = []byte("imageBytes")
|
||||||
@@ -81,8 +85,10 @@ func createFullPerformer(id int, name string) *models.Performer {
|
|||||||
Ethnicity: ethnicity,
|
Ethnicity: ethnicity,
|
||||||
EyeColor: eyeColor,
|
EyeColor: eyeColor,
|
||||||
FakeTits: fakeTits,
|
FakeTits: fakeTits,
|
||||||
|
PenisLength: &penisLength,
|
||||||
|
Circumcised: &circumcisedEnum,
|
||||||
Favorite: true,
|
Favorite: true,
|
||||||
Gender: gender,
|
Gender: &genderEnum,
|
||||||
Height: &height,
|
Height: &height,
|
||||||
Instagram: instagram,
|
Instagram: instagram,
|
||||||
Measurements: measurements,
|
Measurements: measurements,
|
||||||
@@ -125,6 +131,8 @@ func createFullJSONPerformer(name string, image string) *jsonschema.Performer {
|
|||||||
Ethnicity: ethnicity,
|
Ethnicity: ethnicity,
|
||||||
EyeColor: eyeColor,
|
EyeColor: eyeColor,
|
||||||
FakeTits: fakeTits,
|
FakeTits: fakeTits,
|
||||||
|
PenisLength: penisLength,
|
||||||
|
Circumcised: circumcised,
|
||||||
Favorite: true,
|
Favorite: true,
|
||||||
Gender: gender,
|
Gender: gender,
|
||||||
Height: strconv.Itoa(height),
|
Height: strconv.Itoa(height),
|
||||||
|
|||||||
@@ -189,7 +189,6 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform
|
|||||||
newPerformer := models.Performer{
|
newPerformer := models.Performer{
|
||||||
Name: performerJSON.Name,
|
Name: performerJSON.Name,
|
||||||
Disambiguation: performerJSON.Disambiguation,
|
Disambiguation: performerJSON.Disambiguation,
|
||||||
Gender: models.GenderEnum(performerJSON.Gender),
|
|
||||||
URL: performerJSON.URL,
|
URL: performerJSON.URL,
|
||||||
Ethnicity: performerJSON.Ethnicity,
|
Ethnicity: performerJSON.Ethnicity,
|
||||||
Country: performerJSON.Country,
|
Country: performerJSON.Country,
|
||||||
@@ -213,6 +212,16 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform
|
|||||||
StashIDs: models.NewRelatedStashIDs(performerJSON.StashIDs),
|
StashIDs: models.NewRelatedStashIDs(performerJSON.StashIDs),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performerJSON.Gender != "" {
|
||||||
|
v := models.GenderEnum(performerJSON.Gender)
|
||||||
|
newPerformer.Gender = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if performerJSON.Circumcised != "" {
|
||||||
|
v := models.CircumisedEnum(performerJSON.Circumcised)
|
||||||
|
newPerformer.Circumcised = &v
|
||||||
|
}
|
||||||
|
|
||||||
if performerJSON.Birthdate != "" {
|
if performerJSON.Birthdate != "" {
|
||||||
d, err := utils.ParseDateStringAsTime(performerJSON.Birthdate)
|
d, err := utils.ParseDateStringAsTime(performerJSON.Birthdate)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -237,6 +246,10 @@ func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Perform
|
|||||||
newPerformer.Weight = &performerJSON.Weight
|
newPerformer.Weight = &performerJSON.Weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if performerJSON.PenisLength != 0 {
|
||||||
|
newPerformer.PenisLength = &performerJSON.PenisLength
|
||||||
|
}
|
||||||
|
|
||||||
if performerJSON.Height != "" {
|
if performerJSON.Height != "" {
|
||||||
h, err := strconv.Atoi(performerJSON.Height)
|
h, err := strconv.Atoi(performerJSON.Height)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func autotagMatchPerformers(ctx context.Context, path string, performerReader ma
|
|||||||
Name: &pp.Name,
|
Name: &pp.Name,
|
||||||
StoredID: &id,
|
StoredID: &id,
|
||||||
}
|
}
|
||||||
if pp.Gender.IsValid() {
|
if pp.Gender != nil && pp.Gender.IsValid() {
|
||||||
v := pp.Gender.String()
|
v := pp.Gender.String()
|
||||||
sp.Gender = &v
|
sp.Gender = &v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ type ScrapedPerformerInput struct {
|
|||||||
Height *string `json:"height"`
|
Height *string `json:"height"`
|
||||||
Measurements *string `json:"measurements"`
|
Measurements *string `json:"measurements"`
|
||||||
FakeTits *string `json:"fake_tits"`
|
FakeTits *string `json:"fake_tits"`
|
||||||
|
PenisLength *string `json:"penis_length"`
|
||||||
|
Circumcised *string `json:"circumcised"`
|
||||||
CareerLength *string `json:"career_length"`
|
CareerLength *string `json:"career_length"`
|
||||||
Tattoos *string `json:"tattoos"`
|
Tattoos *string `json:"tattoos"`
|
||||||
Piercings *string `json:"piercings"`
|
Piercings *string `json:"piercings"`
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ type scrapedPerformerStash struct {
|
|||||||
Height *string `graphql:"height" json:"height"`
|
Height *string `graphql:"height" json:"height"`
|
||||||
Measurements *string `graphql:"measurements" json:"measurements"`
|
Measurements *string `graphql:"measurements" json:"measurements"`
|
||||||
FakeTits *string `graphql:"fake_tits" json:"fake_tits"`
|
FakeTits *string `graphql:"fake_tits" json:"fake_tits"`
|
||||||
|
PenisLength *string `graphql:"penis_length" json:"penis_length"`
|
||||||
|
Circumcised *string `graphql:"circumcised" json:"circumcised"`
|
||||||
CareerLength *string `graphql:"career_length" json:"career_length"`
|
CareerLength *string `graphql:"career_length" json:"career_length"`
|
||||||
Tattoos *string `graphql:"tattoos" json:"tattoos"`
|
Tattoos *string `graphql:"tattoos" json:"tattoos"`
|
||||||
Piercings *string `graphql:"piercings" json:"piercings"`
|
Piercings *string `graphql:"piercings" json:"piercings"`
|
||||||
|
|||||||
@@ -1009,7 +1009,7 @@ func (c Client) SubmitPerformerDraft(ctx context.Context, performer *models.Perf
|
|||||||
if performer.FakeTits != "" {
|
if performer.FakeTits != "" {
|
||||||
draft.BreastType = &performer.FakeTits
|
draft.BreastType = &performer.FakeTits
|
||||||
}
|
}
|
||||||
if performer.Gender.IsValid() {
|
if performer.Gender != nil && performer.Gender.IsValid() {
|
||||||
v := performer.Gender.String()
|
v := performer.Gender.String()
|
||||||
draft.Gender = &v
|
draft.Gender = &v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const (
|
|||||||
dbConnTimeout = 30
|
dbConnTimeout = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
var appSchemaVersion uint = 45
|
var appSchemaVersion uint = 46
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations/*.sql
|
||||||
var migrationsBox embed.FS
|
var migrationsBox embed.FS
|
||||||
|
|||||||
@@ -426,6 +426,29 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enumCriterionHandler(modifier models.CriterionModifier, values []string, column string) criterionHandlerFunc {
|
||||||
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
|
if modifier.IsValid() {
|
||||||
|
switch modifier {
|
||||||
|
case models.CriterionModifierIncludes, models.CriterionModifierEquals:
|
||||||
|
if len(values) > 0 {
|
||||||
|
f.whereClauses = append(f.whereClauses, getEnumSearchClause(column, values, false))
|
||||||
|
}
|
||||||
|
case models.CriterionModifierExcludes, models.CriterionModifierNotEquals:
|
||||||
|
if len(values) > 0 {
|
||||||
|
f.whereClauses = append(f.whereClauses, getEnumSearchClause(column, values, true))
|
||||||
|
}
|
||||||
|
case models.CriterionModifierIsNull:
|
||||||
|
f.addWhere("(" + column + " IS NULL OR TRIM(" + column + ") = '')")
|
||||||
|
case models.CriterionModifierNotNull:
|
||||||
|
f.addWhere("(" + column + " IS NOT NULL AND TRIM(" + column + ") != '')")
|
||||||
|
default:
|
||||||
|
panic("unsupported string filter modifier")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||||
return func(ctx context.Context, f *filterBuilder) {
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
@@ -525,6 +548,15 @@ func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||||
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
|
if c != nil {
|
||||||
|
clause, args := getFloatCriterionWhereClause(column, *c)
|
||||||
|
f.addWhere(clause, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||||
return func(ctx context.Context, f *filterBuilder) {
|
return func(ctx context.Context, f *filterBuilder) {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
|
|||||||
2
pkg/sqlite/migrations/46_penis_stats.up.sql
Normal file
2
pkg/sqlite/migrations/46_penis_stats.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `performers` ADD COLUMN `penis_length` float;
|
||||||
|
ALTER TABLE `performers` ADD COLUMN `circumcised` varchar[10];
|
||||||
@@ -42,6 +42,8 @@ type performerRow struct {
|
|||||||
Height null.Int `db:"height"`
|
Height null.Int `db:"height"`
|
||||||
Measurements zero.String `db:"measurements"`
|
Measurements zero.String `db:"measurements"`
|
||||||
FakeTits zero.String `db:"fake_tits"`
|
FakeTits zero.String `db:"fake_tits"`
|
||||||
|
PenisLength null.Float `db:"penis_length"`
|
||||||
|
Circumcised zero.String `db:"circumcised"`
|
||||||
CareerLength zero.String `db:"career_length"`
|
CareerLength zero.String `db:"career_length"`
|
||||||
Tattoos zero.String `db:"tattoos"`
|
Tattoos zero.String `db:"tattoos"`
|
||||||
Piercings zero.String `db:"piercings"`
|
Piercings zero.String `db:"piercings"`
|
||||||
@@ -64,7 +66,7 @@ func (r *performerRow) fromPerformer(o models.Performer) {
|
|||||||
r.ID = o.ID
|
r.ID = o.ID
|
||||||
r.Name = o.Name
|
r.Name = o.Name
|
||||||
r.Disambigation = zero.StringFrom(o.Disambiguation)
|
r.Disambigation = zero.StringFrom(o.Disambiguation)
|
||||||
if o.Gender.IsValid() {
|
if o.Gender != nil && o.Gender.IsValid() {
|
||||||
r.Gender = zero.StringFrom(o.Gender.String())
|
r.Gender = zero.StringFrom(o.Gender.String())
|
||||||
}
|
}
|
||||||
r.URL = zero.StringFrom(o.URL)
|
r.URL = zero.StringFrom(o.URL)
|
||||||
@@ -79,6 +81,10 @@ func (r *performerRow) fromPerformer(o models.Performer) {
|
|||||||
r.Height = intFromPtr(o.Height)
|
r.Height = intFromPtr(o.Height)
|
||||||
r.Measurements = zero.StringFrom(o.Measurements)
|
r.Measurements = zero.StringFrom(o.Measurements)
|
||||||
r.FakeTits = zero.StringFrom(o.FakeTits)
|
r.FakeTits = zero.StringFrom(o.FakeTits)
|
||||||
|
r.PenisLength = null.FloatFromPtr(o.PenisLength)
|
||||||
|
if o.Circumcised != nil && o.Circumcised.IsValid() {
|
||||||
|
r.Circumcised = zero.StringFrom(o.Circumcised.String())
|
||||||
|
}
|
||||||
r.CareerLength = zero.StringFrom(o.CareerLength)
|
r.CareerLength = zero.StringFrom(o.CareerLength)
|
||||||
r.Tattoos = zero.StringFrom(o.Tattoos)
|
r.Tattoos = zero.StringFrom(o.Tattoos)
|
||||||
r.Piercings = zero.StringFrom(o.Piercings)
|
r.Piercings = zero.StringFrom(o.Piercings)
|
||||||
@@ -100,7 +106,6 @@ func (r *performerRow) resolve() *models.Performer {
|
|||||||
ID: r.ID,
|
ID: r.ID,
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Disambiguation: r.Disambigation.String,
|
Disambiguation: r.Disambigation.String,
|
||||||
Gender: models.GenderEnum(r.Gender.String),
|
|
||||||
URL: r.URL.String,
|
URL: r.URL.String,
|
||||||
Twitter: r.Twitter.String,
|
Twitter: r.Twitter.String,
|
||||||
Instagram: r.Instagram.String,
|
Instagram: r.Instagram.String,
|
||||||
@@ -111,6 +116,7 @@ func (r *performerRow) resolve() *models.Performer {
|
|||||||
Height: nullIntPtr(r.Height),
|
Height: nullIntPtr(r.Height),
|
||||||
Measurements: r.Measurements.String,
|
Measurements: r.Measurements.String,
|
||||||
FakeTits: r.FakeTits.String,
|
FakeTits: r.FakeTits.String,
|
||||||
|
PenisLength: nullFloatPtr(r.PenisLength),
|
||||||
CareerLength: r.CareerLength.String,
|
CareerLength: r.CareerLength.String,
|
||||||
Tattoos: r.Tattoos.String,
|
Tattoos: r.Tattoos.String,
|
||||||
Piercings: r.Piercings.String,
|
Piercings: r.Piercings.String,
|
||||||
@@ -126,6 +132,16 @@ func (r *performerRow) resolve() *models.Performer {
|
|||||||
IgnoreAutoTag: r.IgnoreAutoTag,
|
IgnoreAutoTag: r.IgnoreAutoTag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.Gender.ValueOrZero() != "" {
|
||||||
|
v := models.GenderEnum(r.Gender.String)
|
||||||
|
ret.Gender = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Circumcised.ValueOrZero() != "" {
|
||||||
|
v := models.CircumisedEnum(r.Circumcised.String)
|
||||||
|
ret.Circumcised = &v
|
||||||
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +163,8 @@ func (r *performerRowRecord) fromPartial(o models.PerformerPartial) {
|
|||||||
r.setNullInt("height", o.Height)
|
r.setNullInt("height", o.Height)
|
||||||
r.setNullString("measurements", o.Measurements)
|
r.setNullString("measurements", o.Measurements)
|
||||||
r.setNullString("fake_tits", o.FakeTits)
|
r.setNullString("fake_tits", o.FakeTits)
|
||||||
|
r.setNullFloat64("penis_length", o.PenisLength)
|
||||||
|
r.setNullString("circumcised", o.Circumcised)
|
||||||
r.setNullString("career_length", o.CareerLength)
|
r.setNullString("career_length", o.CareerLength)
|
||||||
r.setNullString("tattoos", o.Tattoos)
|
r.setNullString("tattoos", o.Tattoos)
|
||||||
r.setNullString("piercings", o.Piercings)
|
r.setNullString("piercings", o.Piercings)
|
||||||
@@ -597,6 +615,15 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
|||||||
|
|
||||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Measurements, tableName+".measurements"))
|
query.handleCriterion(ctx, stringCriterionHandler(filter.Measurements, tableName+".measurements"))
|
||||||
query.handleCriterion(ctx, stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
|
query.handleCriterion(ctx, stringCriterionHandler(filter.FakeTits, tableName+".fake_tits"))
|
||||||
|
query.handleCriterion(ctx, floatCriterionHandler(filter.PenisLength, tableName+".penis_length", nil))
|
||||||
|
|
||||||
|
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||||
|
if circumcised := filter.Circumcised; circumcised != nil {
|
||||||
|
v := utils.StringerSliceToStringSlice(circumcised.Value)
|
||||||
|
enumCriterionHandler(circumcised.Modifier, v, tableName+".circumcised")(ctx, f)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
query.handleCriterion(ctx, stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
|
query.handleCriterion(ctx, stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
|
||||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
|
query.handleCriterion(ctx, stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
|
||||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Piercings, tableName+".piercings"))
|
query.handleCriterion(ctx, stringCriterionHandler(filter.Piercings, tableName+".piercings"))
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ func Test_PerformerStore_Create(t *testing.T) {
|
|||||||
height = 134
|
height = 134
|
||||||
measurements = "measurements"
|
measurements = "measurements"
|
||||||
fakeTits = "fakeTits"
|
fakeTits = "fakeTits"
|
||||||
|
penisLength = 1.23
|
||||||
|
circumcised = models.CircumisedEnumCut
|
||||||
careerLength = "careerLength"
|
careerLength = "careerLength"
|
||||||
tattoos = "tattoos"
|
tattoos = "tattoos"
|
||||||
piercings = "piercings"
|
piercings = "piercings"
|
||||||
@@ -81,7 +83,7 @@ func Test_PerformerStore_Create(t *testing.T) {
|
|||||||
models.Performer{
|
models.Performer{
|
||||||
Name: name,
|
Name: name,
|
||||||
Disambiguation: disambiguation,
|
Disambiguation: disambiguation,
|
||||||
Gender: gender,
|
Gender: &gender,
|
||||||
URL: url,
|
URL: url,
|
||||||
Twitter: twitter,
|
Twitter: twitter,
|
||||||
Instagram: instagram,
|
Instagram: instagram,
|
||||||
@@ -92,6 +94,8 @@ func Test_PerformerStore_Create(t *testing.T) {
|
|||||||
Height: &height,
|
Height: &height,
|
||||||
Measurements: measurements,
|
Measurements: measurements,
|
||||||
FakeTits: fakeTits,
|
FakeTits: fakeTits,
|
||||||
|
PenisLength: &penisLength,
|
||||||
|
Circumcised: &circumcised,
|
||||||
CareerLength: careerLength,
|
CareerLength: careerLength,
|
||||||
Tattoos: tattoos,
|
Tattoos: tattoos,
|
||||||
Piercings: piercings,
|
Piercings: piercings,
|
||||||
@@ -196,6 +200,8 @@ func Test_PerformerStore_Update(t *testing.T) {
|
|||||||
height = 134
|
height = 134
|
||||||
measurements = "measurements"
|
measurements = "measurements"
|
||||||
fakeTits = "fakeTits"
|
fakeTits = "fakeTits"
|
||||||
|
penisLength = 1.23
|
||||||
|
circumcised = models.CircumisedEnumCut
|
||||||
careerLength = "careerLength"
|
careerLength = "careerLength"
|
||||||
tattoos = "tattoos"
|
tattoos = "tattoos"
|
||||||
piercings = "piercings"
|
piercings = "piercings"
|
||||||
@@ -226,7 +232,7 @@ func Test_PerformerStore_Update(t *testing.T) {
|
|||||||
ID: performerIDs[performerIdxWithGallery],
|
ID: performerIDs[performerIdxWithGallery],
|
||||||
Name: name,
|
Name: name,
|
||||||
Disambiguation: disambiguation,
|
Disambiguation: disambiguation,
|
||||||
Gender: gender,
|
Gender: &gender,
|
||||||
URL: url,
|
URL: url,
|
||||||
Twitter: twitter,
|
Twitter: twitter,
|
||||||
Instagram: instagram,
|
Instagram: instagram,
|
||||||
@@ -237,6 +243,8 @@ func Test_PerformerStore_Update(t *testing.T) {
|
|||||||
Height: &height,
|
Height: &height,
|
||||||
Measurements: measurements,
|
Measurements: measurements,
|
||||||
FakeTits: fakeTits,
|
FakeTits: fakeTits,
|
||||||
|
PenisLength: &penisLength,
|
||||||
|
Circumcised: &circumcised,
|
||||||
CareerLength: careerLength,
|
CareerLength: careerLength,
|
||||||
Tattoos: tattoos,
|
Tattoos: tattoos,
|
||||||
Piercings: piercings,
|
Piercings: piercings,
|
||||||
@@ -327,6 +335,7 @@ func clearPerformerPartial() models.PerformerPartial {
|
|||||||
nullString := models.OptionalString{Set: true, Null: true}
|
nullString := models.OptionalString{Set: true, Null: true}
|
||||||
nullDate := models.OptionalDate{Set: true, Null: true}
|
nullDate := models.OptionalDate{Set: true, Null: true}
|
||||||
nullInt := models.OptionalInt{Set: true, Null: true}
|
nullInt := models.OptionalInt{Set: true, Null: true}
|
||||||
|
nullFloat := models.OptionalFloat64{Set: true, Null: true}
|
||||||
|
|
||||||
// leave mandatory fields
|
// leave mandatory fields
|
||||||
return models.PerformerPartial{
|
return models.PerformerPartial{
|
||||||
@@ -342,6 +351,8 @@ func clearPerformerPartial() models.PerformerPartial {
|
|||||||
Height: nullInt,
|
Height: nullInt,
|
||||||
Measurements: nullString,
|
Measurements: nullString,
|
||||||
FakeTits: nullString,
|
FakeTits: nullString,
|
||||||
|
PenisLength: nullFloat,
|
||||||
|
Circumcised: nullString,
|
||||||
CareerLength: nullString,
|
CareerLength: nullString,
|
||||||
Tattoos: nullString,
|
Tattoos: nullString,
|
||||||
Piercings: nullString,
|
Piercings: nullString,
|
||||||
@@ -372,6 +383,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
|||||||
height = 143
|
height = 143
|
||||||
measurements = "measurements"
|
measurements = "measurements"
|
||||||
fakeTits = "fakeTits"
|
fakeTits = "fakeTits"
|
||||||
|
penisLength = 1.23
|
||||||
|
circumcised = models.CircumisedEnumCut
|
||||||
careerLength = "careerLength"
|
careerLength = "careerLength"
|
||||||
tattoos = "tattoos"
|
tattoos = "tattoos"
|
||||||
piercings = "piercings"
|
piercings = "piercings"
|
||||||
@@ -415,6 +428,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
|||||||
Height: models.NewOptionalInt(height),
|
Height: models.NewOptionalInt(height),
|
||||||
Measurements: models.NewOptionalString(measurements),
|
Measurements: models.NewOptionalString(measurements),
|
||||||
FakeTits: models.NewOptionalString(fakeTits),
|
FakeTits: models.NewOptionalString(fakeTits),
|
||||||
|
PenisLength: models.NewOptionalFloat64(penisLength),
|
||||||
|
Circumcised: models.NewOptionalString(circumcised.String()),
|
||||||
CareerLength: models.NewOptionalString(careerLength),
|
CareerLength: models.NewOptionalString(careerLength),
|
||||||
Tattoos: models.NewOptionalString(tattoos),
|
Tattoos: models.NewOptionalString(tattoos),
|
||||||
Piercings: models.NewOptionalString(piercings),
|
Piercings: models.NewOptionalString(piercings),
|
||||||
@@ -453,7 +468,7 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
|||||||
ID: performerIDs[performerIdxWithDupName],
|
ID: performerIDs[performerIdxWithDupName],
|
||||||
Name: name,
|
Name: name,
|
||||||
Disambiguation: disambiguation,
|
Disambiguation: disambiguation,
|
||||||
Gender: gender,
|
Gender: &gender,
|
||||||
URL: url,
|
URL: url,
|
||||||
Twitter: twitter,
|
Twitter: twitter,
|
||||||
Instagram: instagram,
|
Instagram: instagram,
|
||||||
@@ -464,6 +479,8 @@ func Test_PerformerStore_UpdatePartial(t *testing.T) {
|
|||||||
Height: &height,
|
Height: &height,
|
||||||
Measurements: measurements,
|
Measurements: measurements,
|
||||||
FakeTits: fakeTits,
|
FakeTits: fakeTits,
|
||||||
|
PenisLength: &penisLength,
|
||||||
|
Circumcised: &circumcised,
|
||||||
CareerLength: careerLength,
|
CareerLength: careerLength,
|
||||||
Tattoos: tattoos,
|
Tattoos: tattoos,
|
||||||
Piercings: piercings,
|
Piercings: piercings,
|
||||||
@@ -957,16 +974,30 @@ func TestPerformerQuery(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias",
|
"circumcised (cut)",
|
||||||
nil,
|
nil,
|
||||||
&models.PerformerFilterType{
|
&models.PerformerFilterType{
|
||||||
Aliases: &models.StringCriterionInput{
|
Circumcised: &models.CircumcisionCriterionInput{
|
||||||
Value: getPerformerStringValue(performerIdxWithGallery, "alias"),
|
Value: []models.CircumisedEnum{models.CircumisedEnumCut},
|
||||||
Modifier: models.CriterionModifierEquals,
|
Modifier: models.CriterionModifierIncludes,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]int{performerIdxWithGallery},
|
[]int{performerIdx1WithScene},
|
||||||
[]int{performerIdxWithScene},
|
[]int{performerIdxWithScene, performerIdx2WithScene},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"circumcised (excludes cut)",
|
||||||
|
nil,
|
||||||
|
&models.PerformerFilterType{
|
||||||
|
Circumcised: &models.CircumcisionCriterionInput{
|
||||||
|
Value: []models.CircumisedEnum{models.CircumisedEnumCut},
|
||||||
|
Modifier: models.CriterionModifierExcludes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]int{performerIdx2WithScene},
|
||||||
|
// performerIdxWithScene has null value
|
||||||
|
[]int{performerIdx1WithScene, performerIdxWithScene},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -995,6 +1026,107 @@ func TestPerformerQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPerformerQueryPenisLength(t *testing.T) {
|
||||||
|
var upper = 4.0
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
modifier models.CriterionModifier
|
||||||
|
value float64
|
||||||
|
value2 *float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"equals",
|
||||||
|
models.CriterionModifierEquals,
|
||||||
|
1,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not equals",
|
||||||
|
models.CriterionModifierNotEquals,
|
||||||
|
1,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"greater than",
|
||||||
|
models.CriterionModifierGreaterThan,
|
||||||
|
1,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"between",
|
||||||
|
models.CriterionModifierBetween,
|
||||||
|
2,
|
||||||
|
&upper,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"greater than",
|
||||||
|
models.CriterionModifierNotBetween,
|
||||||
|
2,
|
||||||
|
&upper,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"null",
|
||||||
|
models.CriterionModifierIsNull,
|
||||||
|
0,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not null",
|
||||||
|
models.CriterionModifierNotNull,
|
||||||
|
0,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
||||||
|
filter := &models.PerformerFilterType{
|
||||||
|
PenisLength: &models.FloatCriterionInput{
|
||||||
|
Modifier: tt.modifier,
|
||||||
|
Value: tt.value,
|
||||||
|
Value2: tt.value2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
performers, _, err := db.Performer.Query(ctx, filter, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("PerformerStore.Query() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range performers {
|
||||||
|
verifyFloat(t, p.PenisLength, *filter.PenisLength)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyFloat(t *testing.T, value *float64, criterion models.FloatCriterionInput) bool {
|
||||||
|
t.Helper()
|
||||||
|
assert := assert.New(t)
|
||||||
|
switch criterion.Modifier {
|
||||||
|
case models.CriterionModifierEquals:
|
||||||
|
return assert.NotNil(value) && assert.Equal(criterion.Value, *value)
|
||||||
|
case models.CriterionModifierNotEquals:
|
||||||
|
return assert.NotNil(value) && assert.NotEqual(criterion.Value, *value)
|
||||||
|
case models.CriterionModifierGreaterThan:
|
||||||
|
return assert.NotNil(value) && assert.Greater(*value, criterion.Value)
|
||||||
|
case models.CriterionModifierLessThan:
|
||||||
|
return assert.NotNil(value) && assert.Less(*value, criterion.Value)
|
||||||
|
case models.CriterionModifierBetween:
|
||||||
|
return assert.NotNil(value) && assert.GreaterOrEqual(*value, criterion.Value) && assert.LessOrEqual(*value, *criterion.Value2)
|
||||||
|
case models.CriterionModifierNotBetween:
|
||||||
|
return assert.NotNil(value) && assert.True(*value < criterion.Value || *value > *criterion.Value2)
|
||||||
|
case models.CriterionModifierIsNull:
|
||||||
|
return assert.Nil(value)
|
||||||
|
case models.CriterionModifierNotNull:
|
||||||
|
return assert.NotNil(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func TestPerformerQueryForAutoTag(t *testing.T) {
|
func TestPerformerQueryForAutoTag(t *testing.T) {
|
||||||
withTxn(func(ctx context.Context) error {
|
withTxn(func(ctx context.Context) error {
|
||||||
tqb := db.Performer
|
tqb := db.Performer
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sqlite
|
|||||||
import (
|
import (
|
||||||
"github.com/doug-martin/goqu/v9/exp"
|
"github.com/doug-martin/goqu/v9/exp"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
|
"gopkg.in/guregu/null.v4"
|
||||||
"gopkg.in/guregu/null.v4/zero"
|
"gopkg.in/guregu/null.v4/zero"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,11 +78,11 @@ func (r *updateRecord) setFloat64(destField string, v models.OptionalFloat64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (r *updateRecord) setNullFloat64(destField string, v models.OptionalFloat64) {
|
func (r *updateRecord) setNullFloat64(destField string, v models.OptionalFloat64) {
|
||||||
// if v.Set {
|
if v.Set {
|
||||||
// r.set(destField, null.FloatFromPtr(v.Ptr()))
|
r.set(destField, null.FloatFromPtr(v.Ptr()))
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
func (r *updateRecord) setSQLiteTimestamp(destField string, v models.OptionalTime) {
|
func (r *updateRecord) setSQLiteTimestamp(destField string, v models.OptionalTime) {
|
||||||
if v.Set {
|
if v.Set {
|
||||||
|
|||||||
@@ -1331,6 +1331,29 @@ func getPerformerCareerLength(index int) *string {
|
|||||||
return &ret
|
return &ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPerformerPenisLength(index int) *float64 {
|
||||||
|
if index%5 == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := float64(index)
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPerformerCircumcised(index int) *models.CircumisedEnum {
|
||||||
|
var ret models.CircumisedEnum
|
||||||
|
switch {
|
||||||
|
case index%3 == 0:
|
||||||
|
return nil
|
||||||
|
case index%3 == 1:
|
||||||
|
ret = models.CircumisedEnumCut
|
||||||
|
default:
|
||||||
|
ret = models.CircumisedEnumUncut
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret
|
||||||
|
}
|
||||||
|
|
||||||
func getIgnoreAutoTag(index int) bool {
|
func getIgnoreAutoTag(index int) bool {
|
||||||
return index%5 == 0
|
return index%5 == 0
|
||||||
}
|
}
|
||||||
@@ -1372,6 +1395,8 @@ func createPerformers(ctx context.Context, n int, o int) error {
|
|||||||
DeathDate: getPerformerDeathDate(i),
|
DeathDate: getPerformerDeathDate(i),
|
||||||
Details: getPerformerStringValue(i, "Details"),
|
Details: getPerformerStringValue(i, "Details"),
|
||||||
Ethnicity: getPerformerStringValue(i, "Ethnicity"),
|
Ethnicity: getPerformerStringValue(i, "Ethnicity"),
|
||||||
|
PenisLength: getPerformerPenisLength(i),
|
||||||
|
Circumcised: getPerformerCircumcised(i),
|
||||||
Rating: getIntPtr(getRating(i)),
|
Rating: getIntPtr(getRating(i)),
|
||||||
IgnoreAutoTag: getIgnoreAutoTag(i),
|
IgnoreAutoTag: getIgnoreAutoTag(i),
|
||||||
TagIDs: models.NewRelatedIDs(tids),
|
TagIDs: models.NewRelatedIDs(tids),
|
||||||
|
|||||||
@@ -159,6 +159,22 @@ func getStringSearchClause(columns []string, q string, not bool) sqlClause {
|
|||||||
return makeClause("("+likes+")", args...)
|
return makeClause("("+likes+")", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEnumSearchClause(column string, enumVals []string, not bool) sqlClause {
|
||||||
|
var args []interface{}
|
||||||
|
|
||||||
|
notStr := ""
|
||||||
|
if not {
|
||||||
|
notStr = " NOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
clause := fmt.Sprintf("(%s%s IN %s)", column, notStr, getInBinding(len(enumVals)))
|
||||||
|
for _, enumVal := range enumVals {
|
||||||
|
args = append(args, enumVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeClause(clause, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func getInBinding(length int) string {
|
func getInBinding(length int) string {
|
||||||
bindings := strings.Repeat("?, ", length)
|
bindings := strings.Repeat("?, ", length)
|
||||||
bindings = strings.TrimRight(bindings, ", ")
|
bindings = strings.TrimRight(bindings, ", ")
|
||||||
@@ -175,8 +191,26 @@ func getIntWhereClause(column string, modifier models.CriterionModifier, value i
|
|||||||
upper = &u
|
upper = &u
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []interface{}{value}
|
args := []interface{}{value, *upper}
|
||||||
betweenArgs := []interface{}{value, *upper}
|
return getNumericWhereClause(column, modifier, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFloatCriterionWhereClause(column string, input models.FloatCriterionInput) (string, []interface{}) {
|
||||||
|
return getFloatWhereClause(column, input.Modifier, input.Value, input.Value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFloatWhereClause(column string, modifier models.CriterionModifier, value float64, upper *float64) (string, []interface{}) {
|
||||||
|
if upper == nil {
|
||||||
|
u := 0.0
|
||||||
|
upper = &u
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []interface{}{value, *upper}
|
||||||
|
return getNumericWhereClause(column, modifier, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNumericWhereClause(column string, modifier models.CriterionModifier, args []interface{}) (string, []interface{}) {
|
||||||
|
singleArgs := args[0:1]
|
||||||
|
|
||||||
switch modifier {
|
switch modifier {
|
||||||
case models.CriterionModifierIsNull:
|
case models.CriterionModifierIsNull:
|
||||||
@@ -184,20 +218,20 @@ func getIntWhereClause(column string, modifier models.CriterionModifier, value i
|
|||||||
case models.CriterionModifierNotNull:
|
case models.CriterionModifierNotNull:
|
||||||
return fmt.Sprintf("%s IS NOT NULL", column), nil
|
return fmt.Sprintf("%s IS NOT NULL", column), nil
|
||||||
case models.CriterionModifierEquals:
|
case models.CriterionModifierEquals:
|
||||||
return fmt.Sprintf("%s = ?", column), args
|
return fmt.Sprintf("%s = ?", column), singleArgs
|
||||||
case models.CriterionModifierNotEquals:
|
case models.CriterionModifierNotEquals:
|
||||||
return fmt.Sprintf("%s != ?", column), args
|
return fmt.Sprintf("%s != ?", column), singleArgs
|
||||||
case models.CriterionModifierBetween:
|
case models.CriterionModifierBetween:
|
||||||
return fmt.Sprintf("%s BETWEEN ? AND ?", column), betweenArgs
|
return fmt.Sprintf("%s BETWEEN ? AND ?", column), args
|
||||||
case models.CriterionModifierNotBetween:
|
case models.CriterionModifierNotBetween:
|
||||||
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), betweenArgs
|
return fmt.Sprintf("%s NOT BETWEEN ? AND ?", column), args
|
||||||
case models.CriterionModifierLessThan:
|
case models.CriterionModifierLessThan:
|
||||||
return fmt.Sprintf("%s < ?", column), args
|
return fmt.Sprintf("%s < ?", column), singleArgs
|
||||||
case models.CriterionModifierGreaterThan:
|
case models.CriterionModifierGreaterThan:
|
||||||
return fmt.Sprintf("%s > ?", column), args
|
return fmt.Sprintf("%s > ?", column), singleArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unsupported int modifier type " + modifier)
|
panic("unsupported numeric modifier type " + modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDateCriterionWhereClause(column string, input models.DateCriterionInput) (string, []interface{}) {
|
func getDateCriterionWhereClause(column string, input models.DateCriterionInput) (string, []interface{}) {
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ func nullIntPtr(i null.Int) *int {
|
|||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nullFloatPtr(i null.Float) *float64 {
|
||||||
|
if !i.Valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := float64(i.Float64)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
func nullIntFolderIDPtr(i null.Int) *file.FolderID {
|
func nullIntFolderIDPtr(i null.Int) *file.FolderID {
|
||||||
if !i.Valid {
|
if !i.Valid {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -31,3 +31,13 @@ func StrFormat(format string, m StrFormatMap) string {
|
|||||||
|
|
||||||
return strings.NewReplacer(args...).Replace(format)
|
return strings.NewReplacer(args...).Replace(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringerSliceToStringSlice converts a slice of fmt.Stringers to a slice of strings.
|
||||||
|
func StringerSliceToStringSlice[V fmt.Stringer](v []V) []string {
|
||||||
|
ret := make([]string, len(v))
|
||||||
|
for i, vv := range v {
|
||||||
|
ret[i] = vv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { StashIDFilter } from "./Filters/StashIDFilter";
|
|||||||
import { RatingCriterion } from "../../models/list-filter/criteria/rating";
|
import { RatingCriterion } from "../../models/list-filter/criteria/rating";
|
||||||
import { RatingFilter } from "./Filters/RatingFilter";
|
import { RatingFilter } from "./Filters/RatingFilter";
|
||||||
import { BooleanFilter } from "./Filters/BooleanFilter";
|
import { BooleanFilter } from "./Filters/BooleanFilter";
|
||||||
import { OptionsListFilter } from "./Filters/OptionsListFilter";
|
import { OptionFilter, OptionListFilter } from "./Filters/OptionFilter";
|
||||||
import { PathFilter } from "./Filters/PathFilter";
|
import { PathFilter } from "./Filters/PathFilter";
|
||||||
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
import { PhashCriterion } from "src/models/list-filter/criteria/phash";
|
||||||
import { PhashFilter } from "./Filters/PhashFilter";
|
import { PhashFilter } from "./Filters/PhashFilter";
|
||||||
@@ -132,18 +132,17 @@ const GenericCriterionEditor: React.FC<IGenericCriterionEditor> = ({
|
|||||||
!criterionIsNumberValue(criterion.value) &&
|
!criterionIsNumberValue(criterion.value) &&
|
||||||
!criterionIsStashIDValue(criterion.value) &&
|
!criterionIsStashIDValue(criterion.value) &&
|
||||||
!criterionIsDateValue(criterion.value) &&
|
!criterionIsDateValue(criterion.value) &&
|
||||||
!criterionIsTimestampValue(criterion.value) &&
|
!criterionIsTimestampValue(criterion.value)
|
||||||
!Array.isArray(criterion.value)
|
|
||||||
) {
|
) {
|
||||||
// if (!modifierOptions || modifierOptions.length === 0) {
|
if (!Array.isArray(criterion.value)) {
|
||||||
return (
|
return (
|
||||||
<OptionsListFilter criterion={criterion} setCriterion={setCriterion} />
|
<OptionFilter criterion={criterion} setCriterion={setCriterion} />
|
||||||
);
|
);
|
||||||
// }
|
} else {
|
||||||
|
return (
|
||||||
// return (
|
<OptionListFilter criterion={criterion} setCriterion={setCriterion} />
|
||||||
// <OptionsFilter criterion={criterion} onValueChanged={onValueChanged} />
|
);
|
||||||
// );
|
}
|
||||||
}
|
}
|
||||||
if (criterion.criterionOption instanceof PathCriterionOption) {
|
if (criterion.criterionOption instanceof PathCriterionOption) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -30,14 +30,14 @@ export const BooleanFilter: React.FC<IBooleanFilter> = ({
|
|||||||
id={`${criterion.getId()}-true`}
|
id={`${criterion.getId()}-true`}
|
||||||
onChange={() => onSelect(true)}
|
onChange={() => onSelect(true)}
|
||||||
checked={criterion.value === "true"}
|
checked={criterion.value === "true"}
|
||||||
type="checkbox"
|
type="radio"
|
||||||
label={<FormattedMessage id="true" />}
|
label={<FormattedMessage id="true" />}
|
||||||
/>
|
/>
|
||||||
<Form.Check
|
<Form.Check
|
||||||
id={`${criterion.getId()}-false`}
|
id={`${criterion.getId()}-false`}
|
||||||
onChange={() => onSelect(false)}
|
onChange={() => onSelect(false)}
|
||||||
checked={criterion.value === "false"}
|
checked={criterion.value === "false"}
|
||||||
type="checkbox"
|
type="radio"
|
||||||
label={<FormattedMessage id="false" />}
|
label={<FormattedMessage id="false" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
85
ui/v2.5/src/components/List/Filters/OptionFilter.tsx
Normal file
85
ui/v2.5/src/components/List/Filters/OptionFilter.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import cloneDeep from "lodash-es/cloneDeep";
|
||||||
|
import React from "react";
|
||||||
|
import { Form } from "react-bootstrap";
|
||||||
|
import {
|
||||||
|
CriterionValue,
|
||||||
|
Criterion,
|
||||||
|
} from "src/models/list-filter/criteria/criterion";
|
||||||
|
|
||||||
|
interface IOptionsFilter {
|
||||||
|
criterion: Criterion<CriterionValue>;
|
||||||
|
setCriterion: (c: Criterion<CriterionValue>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionFilter: React.FC<IOptionsFilter> = ({
|
||||||
|
criterion,
|
||||||
|
setCriterion,
|
||||||
|
}) => {
|
||||||
|
function onSelect(v: string) {
|
||||||
|
const c = cloneDeep(criterion);
|
||||||
|
if (c.value === v) {
|
||||||
|
c.value = "";
|
||||||
|
} else {
|
||||||
|
c.value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCriterion(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { options } = criterion.criterionOption;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="option-list-filter">
|
||||||
|
{options?.map((o) => (
|
||||||
|
<Form.Check
|
||||||
|
id={`${criterion.getId()}-${o.toString()}`}
|
||||||
|
key={o.toString()}
|
||||||
|
onChange={() => onSelect(o.toString())}
|
||||||
|
checked={criterion.value === o.toString()}
|
||||||
|
type="radio"
|
||||||
|
label={o.toString()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IOptionsListFilter {
|
||||||
|
criterion: Criterion<CriterionValue>;
|
||||||
|
setCriterion: (c: Criterion<CriterionValue>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionListFilter: React.FC<IOptionsListFilter> = ({
|
||||||
|
criterion,
|
||||||
|
setCriterion,
|
||||||
|
}) => {
|
||||||
|
function onSelect(v: string) {
|
||||||
|
const c = cloneDeep(criterion);
|
||||||
|
const cv = c.value as string[];
|
||||||
|
if (cv.includes(v)) {
|
||||||
|
c.value = cv.filter((x) => x !== v);
|
||||||
|
} else {
|
||||||
|
c.value = [...cv, v];
|
||||||
|
}
|
||||||
|
|
||||||
|
setCriterion(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { options } = criterion.criterionOption;
|
||||||
|
const value = criterion.value as string[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="option-list-filter">
|
||||||
|
{options?.map((o) => (
|
||||||
|
<Form.Check
|
||||||
|
id={`${criterion.getId()}-${o.toString()}`}
|
||||||
|
key={o.toString()}
|
||||||
|
onChange={() => onSelect(o.toString())}
|
||||||
|
checked={value.includes(o.toString())}
|
||||||
|
type="checkbox"
|
||||||
|
label={o.toString()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import React, { useMemo } from "react";
|
|
||||||
import { Form } from "react-bootstrap";
|
|
||||||
import {
|
|
||||||
Criterion,
|
|
||||||
CriterionValue,
|
|
||||||
} from "../../../models/list-filter/criteria/criterion";
|
|
||||||
|
|
||||||
interface IOptionsFilterProps {
|
|
||||||
criterion: Criterion<CriterionValue>;
|
|
||||||
onValueChanged: (value: CriterionValue) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OptionsFilter: React.FC<IOptionsFilterProps> = ({
|
|
||||||
criterion,
|
|
||||||
onValueChanged,
|
|
||||||
}) => {
|
|
||||||
function onChanged(event: React.ChangeEvent<HTMLSelectElement>) {
|
|
||||||
onValueChanged(event.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = useMemo(() => {
|
|
||||||
const ret = criterion.criterionOption.options?.slice() ?? [];
|
|
||||||
|
|
||||||
ret.unshift("");
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}, [criterion.criterionOption.options]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
as="select"
|
|
||||||
onChange={onChanged}
|
|
||||||
value={criterion.value.toString()}
|
|
||||||
className="btn-secondary"
|
|
||||||
>
|
|
||||||
{options.map((c) => (
|
|
||||||
<option key={c.toString()} value={c.toString()}>
|
|
||||||
{c ? c : "---"}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Group>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import cloneDeep from "lodash-es/cloneDeep";
|
|
||||||
import React from "react";
|
|
||||||
import { Form } from "react-bootstrap";
|
|
||||||
import {
|
|
||||||
CriterionValue,
|
|
||||||
Criterion,
|
|
||||||
} from "src/models/list-filter/criteria/criterion";
|
|
||||||
|
|
||||||
interface IOptionsListFilter {
|
|
||||||
criterion: Criterion<CriterionValue>;
|
|
||||||
setCriterion: (c: Criterion<CriterionValue>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OptionsListFilter: React.FC<IOptionsListFilter> = ({
|
|
||||||
criterion,
|
|
||||||
setCriterion,
|
|
||||||
}) => {
|
|
||||||
function onSelect(v: string) {
|
|
||||||
const c = cloneDeep(criterion);
|
|
||||||
if (c.value === v) {
|
|
||||||
c.value = "";
|
|
||||||
} else {
|
|
||||||
c.value = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCriterion(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { options } = criterion.criterionOption;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="option-list-filter">
|
|
||||||
{options?.map((o) => (
|
|
||||||
<Form.Check
|
|
||||||
id={`${criterion.getId()}-${o.toString()}`}
|
|
||||||
key={o.toString()}
|
|
||||||
onChange={() => onSelect(o.toString())}
|
|
||||||
checked={criterion.value === o.toString()}
|
|
||||||
type="checkbox"
|
|
||||||
label={o.toString()}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -17,6 +17,11 @@ import {
|
|||||||
genderToString,
|
genderToString,
|
||||||
stringToGender,
|
stringToGender,
|
||||||
} from "src/utils/gender";
|
} from "src/utils/gender";
|
||||||
|
import {
|
||||||
|
circumcisedStrings,
|
||||||
|
circumcisedToString,
|
||||||
|
stringToCircumcised,
|
||||||
|
} from "src/utils/circumcised";
|
||||||
import { IndeterminateCheckbox } from "../Shared/IndeterminateCheckbox";
|
import { IndeterminateCheckbox } from "../Shared/IndeterminateCheckbox";
|
||||||
import { BulkUpdateTextInput } from "../Shared/BulkUpdateTextInput";
|
import { BulkUpdateTextInput } from "../Shared/BulkUpdateTextInput";
|
||||||
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faPencilAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
@@ -45,6 +50,8 @@ const performerFields = [
|
|||||||
// "weight",
|
// "weight",
|
||||||
"measurements",
|
"measurements",
|
||||||
"fake_tits",
|
"fake_tits",
|
||||||
|
"penis_length",
|
||||||
|
"circumcised",
|
||||||
"hair_color",
|
"hair_color",
|
||||||
"tattoos",
|
"tattoos",
|
||||||
"piercings",
|
"piercings",
|
||||||
@@ -64,10 +71,12 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||||||
useState<GQL.BulkPerformerUpdateInput>({});
|
useState<GQL.BulkPerformerUpdateInput>({});
|
||||||
// weight needs conversion to/from number
|
// weight needs conversion to/from number
|
||||||
const [weight, setWeight] = useState<string | undefined>();
|
const [weight, setWeight] = useState<string | undefined>();
|
||||||
|
const [penis_length, setPenisLength] = useState<string | undefined>();
|
||||||
const [updateInput, setUpdateInput] = useState<GQL.BulkPerformerUpdateInput>(
|
const [updateInput, setUpdateInput] = useState<GQL.BulkPerformerUpdateInput>(
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const genderOptions = [""].concat(genderStrings);
|
const genderOptions = [""].concat(genderStrings);
|
||||||
|
const circumcisedOptions = [""].concat(circumcisedStrings);
|
||||||
|
|
||||||
const [updatePerformers] = useBulkPerformerUpdate(getPerformerInput());
|
const [updatePerformers] = useBulkPerformerUpdate(getPerformerInput());
|
||||||
|
|
||||||
@@ -100,11 +109,19 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||||||
updateInput.gender,
|
updateInput.gender,
|
||||||
aggregateState.gender
|
aggregateState.gender
|
||||||
);
|
);
|
||||||
|
performerInput.circumcised = getAggregateInputValue(
|
||||||
|
updateInput.circumcised,
|
||||||
|
aggregateState.circumcised
|
||||||
|
);
|
||||||
|
|
||||||
if (weight !== undefined) {
|
if (weight !== undefined) {
|
||||||
performerInput.weight = parseFloat(weight);
|
performerInput.weight = parseFloat(weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (penis_length !== undefined) {
|
||||||
|
performerInput.penis_length = parseFloat(penis_length);
|
||||||
|
}
|
||||||
|
|
||||||
return performerInput;
|
return performerInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +152,7 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||||||
const state = props.selected;
|
const state = props.selected;
|
||||||
let updateTagIds: string[] = [];
|
let updateTagIds: string[] = [];
|
||||||
let updateWeight: string | undefined | null = undefined;
|
let updateWeight: string | undefined | null = undefined;
|
||||||
|
let updatePenisLength: string | undefined | null = undefined;
|
||||||
let first = true;
|
let first = true;
|
||||||
|
|
||||||
state.forEach((performer: GQL.SlimPerformerDataFragment) => {
|
state.forEach((performer: GQL.SlimPerformerDataFragment) => {
|
||||||
@@ -151,6 +169,16 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||||||
: performer.weight;
|
: performer.weight;
|
||||||
updateWeight = getAggregateState(updateWeight, thisWeight, first);
|
updateWeight = getAggregateState(updateWeight, thisWeight, first);
|
||||||
|
|
||||||
|
const thisPenisLength =
|
||||||
|
performer.penis_length !== undefined && performer.penis_length !== null
|
||||||
|
? performer.penis_length.toString()
|
||||||
|
: performer.penis_length;
|
||||||
|
updatePenisLength = getAggregateState(
|
||||||
|
updatePenisLength,
|
||||||
|
thisPenisLength,
|
||||||
|
first
|
||||||
|
);
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,6 +298,32 @@ export const EditPerformersDialog: React.FC<IListOperationProps> = (
|
|||||||
{renderTextField("measurements", updateInput.measurements, (v) =>
|
{renderTextField("measurements", updateInput.measurements, (v) =>
|
||||||
setUpdateField({ measurements: v })
|
setUpdateField({ measurements: v })
|
||||||
)}
|
)}
|
||||||
|
{renderTextField("penis_length", penis_length, (v) =>
|
||||||
|
setPenisLength(v)
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>
|
||||||
|
<FormattedMessage id="circumcised" />
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
className="input-control"
|
||||||
|
value={circumcisedToString(updateInput.circumcised)}
|
||||||
|
onChange={(event) =>
|
||||||
|
setUpdateField({
|
||||||
|
circumcised: stringToCircumcised(event.currentTarget.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{circumcisedOptions.map((opt) => (
|
||||||
|
<option value={opt} key={opt}>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
{renderTextField("fake_tits", updateInput.fake_tits, (v) =>
|
{renderTextField("fake_tits", updateInput.fake_tits, (v) =>
|
||||||
setUpdateField({ fake_tits: v })
|
setUpdateField({ fake_tits: v })
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import TextUtils from "src/utils/text";
|
|||||||
import { getStashboxBase } from "src/utils/stashbox";
|
import { getStashboxBase } from "src/utils/stashbox";
|
||||||
import { getCountryByISO } from "src/utils/country";
|
import { getCountryByISO } from "src/utils/country";
|
||||||
import { TextField, URLField } from "src/utils/field";
|
import { TextField, URLField } from "src/utils/field";
|
||||||
import { cmToImperial, kgToLbs } from "src/utils/units";
|
import { cmToImperial, cmToInches, kgToLbs } from "src/utils/units";
|
||||||
|
|
||||||
interface IPerformerDetails {
|
interface IPerformerDetails {
|
||||||
performer: GQL.PerformerDataFragment;
|
performer: GQL.PerformerDataFragment;
|
||||||
@@ -133,6 +133,49 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatPenisLength = (penis_length?: number | null) => {
|
||||||
|
if (!penis_length) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const inches = cmToInches(penis_length);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="performer-penis-length">
|
||||||
|
<span className="penis-length-metric">
|
||||||
|
{intl.formatNumber(penis_length, {
|
||||||
|
style: "unit",
|
||||||
|
unit: "centimeter",
|
||||||
|
unitDisplay: "short",
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span className="penis-length-imperial">
|
||||||
|
{intl.formatNumber(inches, {
|
||||||
|
style: "unit",
|
||||||
|
unit: "inch",
|
||||||
|
unitDisplay: "narrow",
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCircumcised = (circumcised?: GQL.CircumisedEnum | null) => {
|
||||||
|
if (!circumcised) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="penis-circumcised">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "circumcised_types." + performer.circumcised,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dl className="details-list">
|
<dl className="details-list">
|
||||||
<TextField
|
<TextField
|
||||||
@@ -179,6 +222,17 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{(performer.penis_length || performer.circumcised) && (
|
||||||
|
<>
|
||||||
|
<dt>
|
||||||
|
<FormattedMessage id="penis" />:
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{formatPenisLength(performer.penis_length)}
|
||||||
|
{formatCircumcised(performer.circumcised)}
|
||||||
|
</dd>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<TextField id="measurements" value={performer.measurements} />
|
<TextField id="measurements" value={performer.measurements} />
|
||||||
<TextField id="fake_tits" value={performer.fake_tits} />
|
<TextField id="fake_tits" value={performer.fake_tits} />
|
||||||
<TextField id="career_length" value={performer.career_length} />
|
<TextField id="career_length" value={performer.career_length} />
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ import {
|
|||||||
stringGenderMap,
|
stringGenderMap,
|
||||||
stringToGender,
|
stringToGender,
|
||||||
} from "src/utils/gender";
|
} from "src/utils/gender";
|
||||||
|
import {
|
||||||
|
circumcisedToString,
|
||||||
|
stringCircumMap,
|
||||||
|
stringToCircumcised,
|
||||||
|
} from "src/utils/circumcised";
|
||||||
import { ConfigurationContext } from "src/hooks/Config";
|
import { ConfigurationContext } from "src/hooks/Config";
|
||||||
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
import { PerformerScrapeDialog } from "./PerformerScrapeDialog";
|
||||||
import PerformerScrapeModal from "./PerformerScrapeModal";
|
import PerformerScrapeModal from "./PerformerScrapeModal";
|
||||||
@@ -153,6 +158,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
weight: yup.number().nullable().defined().default(null),
|
weight: yup.number().nullable().defined().default(null),
|
||||||
measurements: yup.string().ensure(),
|
measurements: yup.string().ensure(),
|
||||||
fake_tits: yup.string().ensure(),
|
fake_tits: yup.string().ensure(),
|
||||||
|
penis_length: yup.number().nullable().defined().default(null),
|
||||||
|
circumcised: yup.string<GQL.CircumisedEnum | "">().ensure(),
|
||||||
tattoos: yup.string().ensure(),
|
tattoos: yup.string().ensure(),
|
||||||
piercings: yup.string().ensure(),
|
piercings: yup.string().ensure(),
|
||||||
career_length: yup.string().ensure(),
|
career_length: yup.string().ensure(),
|
||||||
@@ -181,6 +188,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
weight: performer.weight ?? null,
|
weight: performer.weight ?? null,
|
||||||
measurements: performer.measurements ?? "",
|
measurements: performer.measurements ?? "",
|
||||||
fake_tits: performer.fake_tits ?? "",
|
fake_tits: performer.fake_tits ?? "",
|
||||||
|
penis_length: performer.penis_length ?? null,
|
||||||
|
circumcised: (performer.circumcised as GQL.CircumisedEnum) ?? "",
|
||||||
tattoos: performer.tattoos ?? "",
|
tattoos: performer.tattoos ?? "",
|
||||||
piercings: performer.piercings ?? "",
|
piercings: performer.piercings ?? "",
|
||||||
career_length: performer.career_length ?? "",
|
career_length: performer.career_length ?? "",
|
||||||
@@ -219,6 +228,21 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateScrapedCircumcised(scrapedCircumcised?: string) {
|
||||||
|
if (!scrapedCircumcised) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const upperCircumcised = scrapedCircumcised.toUpperCase();
|
||||||
|
const asEnum = circumcisedToString(upperCircumcised);
|
||||||
|
if (asEnum) {
|
||||||
|
return stringToCircumcised(asEnum);
|
||||||
|
} else {
|
||||||
|
const caseInsensitive = true;
|
||||||
|
return stringToCircumcised(scrapedCircumcised, caseInsensitive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderNewTags() {
|
function renderNewTags() {
|
||||||
if (!newTags || newTags.length === 0) {
|
if (!newTags || newTags.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -355,6 +379,13 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
formik.setFieldValue("gender", newGender);
|
formik.setFieldValue("gender", newGender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state.circumcised) {
|
||||||
|
// circumcised is a string in the scraper data
|
||||||
|
const newCircumcised = translateScrapedCircumcised(state.circumcised);
|
||||||
|
if (newCircumcised) {
|
||||||
|
formik.setFieldValue("circumcised", newCircumcised);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state.tags) {
|
if (state.tags) {
|
||||||
// map tags to their ids and filter out those not found
|
// map tags to their ids and filter out those not found
|
||||||
const newTagIds = state.tags.map((t) => t.stored_id).filter((t) => t);
|
const newTagIds = state.tags.map((t) => t.stored_id).filter((t) => t);
|
||||||
@@ -387,6 +418,9 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
if (state.weight) {
|
if (state.weight) {
|
||||||
formik.setFieldValue("weight", state.weight);
|
formik.setFieldValue("weight", state.weight);
|
||||||
}
|
}
|
||||||
|
if (state.penis_length) {
|
||||||
|
formik.setFieldValue("penis_length", state.penis_length);
|
||||||
|
}
|
||||||
|
|
||||||
const remoteSiteID = state.remote_site_id;
|
const remoteSiteID = state.remote_site_id;
|
||||||
if (remoteSiteID && (scraper as IStashBox).endpoint) {
|
if (remoteSiteID && (scraper as IStashBox).endpoint) {
|
||||||
@@ -431,6 +465,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
gender: input.gender || null,
|
gender: input.gender || null,
|
||||||
height_cm: input.height_cm || null,
|
height_cm: input.height_cm || null,
|
||||||
weight: input.weight || null,
|
weight: input.weight || null,
|
||||||
|
penis_length: input.penis_length || null,
|
||||||
|
circumcised: input.circumcised || null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -446,6 +482,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
gender: input.gender || null,
|
gender: input.gender || null,
|
||||||
height_cm: input.height_cm || null,
|
height_cm: input.height_cm || null,
|
||||||
weight: input.weight || null,
|
weight: input.weight || null,
|
||||||
|
penis_length: input.penis_length || null,
|
||||||
|
circumcised: input.circumcised || null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -663,6 +701,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
const currentPerformer = {
|
const currentPerformer = {
|
||||||
...formik.values,
|
...formik.values,
|
||||||
gender: formik.values.gender || null,
|
gender: formik.values.gender || null,
|
||||||
|
circumcised: formik.values.circumcised || null,
|
||||||
image: formik.values.image ?? performer.image_path,
|
image: formik.values.image ?? performer.image_path,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -990,6 +1029,31 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||||||
type: "number",
|
type: "number",
|
||||||
messageID: "weight_kg",
|
messageID: "weight_kg",
|
||||||
})}
|
})}
|
||||||
|
{renderField("penis_length", {
|
||||||
|
type: "number",
|
||||||
|
messageID: "penis_length_cm",
|
||||||
|
})}
|
||||||
|
|
||||||
|
<Form.Group as={Row}>
|
||||||
|
<Form.Label column xs={labelXS} xl={labelXL}>
|
||||||
|
<FormattedMessage id="circumcised" />
|
||||||
|
</Form.Label>
|
||||||
|
<Col xs="auto">
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
className="input-control"
|
||||||
|
{...formik.getFieldProps("circumcised")}
|
||||||
|
>
|
||||||
|
<option value="" key=""></option>
|
||||||
|
{Array.from(stringCircumMap.entries()).map(([name, value]) => (
|
||||||
|
<option value={value} key={value}>
|
||||||
|
{name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
</Col>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
{renderField("measurements")}
|
{renderField("measurements")}
|
||||||
{renderField("fake_tits")}
|
{renderField("fake_tits")}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import {
|
|||||||
genderToString,
|
genderToString,
|
||||||
stringToGender,
|
stringToGender,
|
||||||
} from "src/utils/gender";
|
} from "src/utils/gender";
|
||||||
|
import {
|
||||||
|
circumcisedStrings,
|
||||||
|
circumcisedToString,
|
||||||
|
stringToCircumcised,
|
||||||
|
} from "src/utils/circumcised";
|
||||||
import { IStashBox } from "./PerformerStashBoxModal";
|
import { IStashBox } from "./PerformerStashBoxModal";
|
||||||
|
|
||||||
function renderScrapedGender(
|
function renderScrapedGender(
|
||||||
@@ -120,6 +125,55 @@ function renderScrapedTagsRow(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderScrapedCircumcised(
|
||||||
|
result: ScrapeResult<string>,
|
||||||
|
isNew?: boolean,
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
) {
|
||||||
|
const selectOptions = [""].concat(circumcisedStrings);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Control
|
||||||
|
as="select"
|
||||||
|
className="input-control"
|
||||||
|
disabled={!isNew}
|
||||||
|
plaintext={!isNew}
|
||||||
|
value={isNew ? result.newValue : result.originalValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (isNew && onChange) {
|
||||||
|
onChange(e.currentTarget.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectOptions.map((opt) => (
|
||||||
|
<option value={opt} key={opt}>
|
||||||
|
{opt}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Form.Control>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderScrapedCircumcisedRow(
|
||||||
|
title: string,
|
||||||
|
result: ScrapeResult<string>,
|
||||||
|
onChange: (value: ScrapeResult<string>) => void
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<ScrapeDialogRow
|
||||||
|
title={title}
|
||||||
|
result={result}
|
||||||
|
renderOriginalField={() => renderScrapedCircumcised(result)}
|
||||||
|
renderNewField={() =>
|
||||||
|
renderScrapedCircumcised(result, true, (value) =>
|
||||||
|
onChange(result.cloneWithValue(value))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface IPerformerScrapeDialogProps {
|
interface IPerformerScrapeDialogProps {
|
||||||
performer: Partial<GQL.PerformerUpdateInput>;
|
performer: Partial<GQL.PerformerUpdateInput>;
|
||||||
scraped: GQL.ScrapedPerformer;
|
scraped: GQL.ScrapedPerformer;
|
||||||
@@ -165,6 +219,27 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
return genderToString(retEnum);
|
return genderToString(retEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function translateScrapedCircumcised(scrapedCircumcised?: string | null) {
|
||||||
|
if (!scrapedCircumcised) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let retEnum: GQL.CircumisedEnum | undefined;
|
||||||
|
|
||||||
|
// try to translate from enum values first
|
||||||
|
const upperCircumcised = scrapedCircumcised.toUpperCase();
|
||||||
|
const asEnum = circumcisedToString(upperCircumcised);
|
||||||
|
if (asEnum) {
|
||||||
|
retEnum = stringToCircumcised(asEnum);
|
||||||
|
} else {
|
||||||
|
// try to match against circumcised strings
|
||||||
|
const caseInsensitive = true;
|
||||||
|
retEnum = stringToCircumcised(scrapedCircumcised, caseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return circumcisedToString(retEnum);
|
||||||
|
}
|
||||||
|
|
||||||
const [name, setName] = useState<ScrapeResult<string>>(
|
const [name, setName] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.performer.name, props.scraped.name)
|
new ScrapeResult<string>(props.performer.name, props.scraped.name)
|
||||||
);
|
);
|
||||||
@@ -216,6 +291,12 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
props.scraped.weight
|
props.scraped.weight
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const [penisLength, setPenisLength] = useState<ScrapeResult<string>>(
|
||||||
|
new ScrapeResult<string>(
|
||||||
|
props.performer.penis_length?.toString(),
|
||||||
|
props.scraped.penis_length
|
||||||
|
)
|
||||||
|
);
|
||||||
const [measurements, setMeasurements] = useState<ScrapeResult<string>>(
|
const [measurements, setMeasurements] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(
|
new ScrapeResult<string>(
|
||||||
props.performer.measurements,
|
props.performer.measurements,
|
||||||
@@ -252,6 +333,12 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
translateScrapedGender(props.scraped.gender)
|
translateScrapedGender(props.scraped.gender)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const [circumcised, setCircumcised] = useState<ScrapeResult<string>>(
|
||||||
|
new ScrapeResult<string>(
|
||||||
|
circumcisedToString(props.performer.circumcised),
|
||||||
|
translateScrapedCircumcised(props.scraped.circumcised)
|
||||||
|
)
|
||||||
|
);
|
||||||
const [details, setDetails] = useState<ScrapeResult<string>>(
|
const [details, setDetails] = useState<ScrapeResult<string>>(
|
||||||
new ScrapeResult<string>(props.performer.details, props.scraped.details)
|
new ScrapeResult<string>(props.performer.details, props.scraped.details)
|
||||||
);
|
);
|
||||||
@@ -338,6 +425,8 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
height,
|
height,
|
||||||
measurements,
|
measurements,
|
||||||
fakeTits,
|
fakeTits,
|
||||||
|
penisLength,
|
||||||
|
circumcised,
|
||||||
careerLength,
|
careerLength,
|
||||||
tattoos,
|
tattoos,
|
||||||
piercings,
|
piercings,
|
||||||
@@ -426,6 +515,8 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
death_date: deathDate.getNewValue(),
|
death_date: deathDate.getNewValue(),
|
||||||
hair_color: hairColor.getNewValue(),
|
hair_color: hairColor.getNewValue(),
|
||||||
weight: weight.getNewValue(),
|
weight: weight.getNewValue(),
|
||||||
|
penis_length: penisLength.getNewValue(),
|
||||||
|
circumcised: circumcised.getNewValue(),
|
||||||
remote_site_id: remoteSiteID.getNewValue(),
|
remote_site_id: remoteSiteID.getNewValue(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -493,6 +584,16 @@ export const PerformerScrapeDialog: React.FC<IPerformerScrapeDialogProps> = (
|
|||||||
result={height}
|
result={height}
|
||||||
onChange={(value) => setHeight(value)}
|
onChange={(value) => setHeight(value)}
|
||||||
/>
|
/>
|
||||||
|
<ScrapedInputGroupRow
|
||||||
|
title={intl.formatMessage({ id: "penis_length" })}
|
||||||
|
result={penisLength}
|
||||||
|
onChange={(value) => setPenisLength(value)}
|
||||||
|
/>
|
||||||
|
{renderScrapedCircumcisedRow(
|
||||||
|
intl.formatMessage({ id: "circumcised" }),
|
||||||
|
circumcised,
|
||||||
|
(value) => setCircumcised(value)
|
||||||
|
)}
|
||||||
<ScrapedInputGroupRow
|
<ScrapedInputGroupRow
|
||||||
title={intl.formatMessage({ id: "measurements" })}
|
title={intl.formatMessage({ id: "measurements" })}
|
||||||
result={measurements}
|
result={measurements}
|
||||||
|
|||||||
@@ -190,8 +190,9 @@
|
|||||||
color: #c8a2c8;
|
color: #c8a2c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.performer-height {
|
.performer-height .height-imperial,
|
||||||
.height-imperial {
|
.performer-weight .weight-imperial,
|
||||||
|
.performer-penis-length .penis-length-imperial {
|
||||||
&::before {
|
&::before {
|
||||||
content: " (";
|
content: " (";
|
||||||
}
|
}
|
||||||
@@ -200,17 +201,10 @@
|
|||||||
content: ")";
|
content: ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.performer-weight {
|
.penis-circumcised {
|
||||||
.weight-imperial {
|
|
||||||
&::before {
|
&::before {
|
||||||
content: " (";
|
content: " ";
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: ")";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
getOperationName,
|
getOperationName,
|
||||||
} from "@apollo/client/utilities";
|
} from "@apollo/client/utilities";
|
||||||
import { stringToGender } from "src/utils/gender";
|
import { stringToGender } from "src/utils/gender";
|
||||||
|
import { stringToCircumcised } from "src/utils/circumcised";
|
||||||
import { filterData } from "../utils/data";
|
import { filterData } from "../utils/data";
|
||||||
import { ListFilterModel } from "../models/list-filter/filter";
|
import { ListFilterModel } from "../models/list-filter/filter";
|
||||||
import * as GQL from "./generated-graphql";
|
import * as GQL from "./generated-graphql";
|
||||||
@@ -1314,6 +1315,10 @@ export const makePerformerCreateInput = (toCreate: GQL.ScrapedPerformer) => {
|
|||||||
death_date: toCreate.death_date,
|
death_date: toCreate.death_date,
|
||||||
hair_color: toCreate.hair_color,
|
hair_color: toCreate.hair_color,
|
||||||
weight: toCreate.weight ? Number(toCreate.weight) : undefined,
|
weight: toCreate.weight ? Number(toCreate.weight) : undefined,
|
||||||
|
penis_length: toCreate.penis_length
|
||||||
|
? Number(toCreate.penis_length)
|
||||||
|
: undefined,
|
||||||
|
circumcised: stringToCircumcised(toCreate.circumcised),
|
||||||
};
|
};
|
||||||
return input;
|
return input;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -141,6 +141,11 @@
|
|||||||
"captions": "Captions",
|
"captions": "Captions",
|
||||||
"career_length": "Career Length",
|
"career_length": "Career Length",
|
||||||
"chapters": "Chapters",
|
"chapters": "Chapters",
|
||||||
|
"circumcised": "Circumcised",
|
||||||
|
"circumcised_types": {
|
||||||
|
"UNCUT": "Uncut",
|
||||||
|
"CUT": "Cut"
|
||||||
|
},
|
||||||
"component_tagger": {
|
"component_tagger": {
|
||||||
"config": {
|
"config": {
|
||||||
"active_instance": "Active stash-box instance:",
|
"active_instance": "Active stash-box instance:",
|
||||||
@@ -1016,6 +1021,9 @@
|
|||||||
"parent_tags": "Parent Tags",
|
"parent_tags": "Parent Tags",
|
||||||
"part_of": "Part of {parent}",
|
"part_of": "Part of {parent}",
|
||||||
"path": "Path",
|
"path": "Path",
|
||||||
|
"penis": "Penis",
|
||||||
|
"penis_length": "Penis Length",
|
||||||
|
"penis_length_cm": "Penis Length (cm)",
|
||||||
"perceptual_similarity": "Perceptual Similarity (phash)",
|
"perceptual_similarity": "Perceptual Similarity (phash)",
|
||||||
"performer": "Performer",
|
"performer": "Performer",
|
||||||
"performerTags": "Performer Tags",
|
"performerTags": "Performer Tags",
|
||||||
|
|||||||
36
ui/v2.5/src/models/list-filter/criteria/circumcised.ts
Normal file
36
ui/v2.5/src/models/list-filter/criteria/circumcised.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
CircumcisionCriterionInput,
|
||||||
|
CircumisedEnum,
|
||||||
|
CriterionModifier,
|
||||||
|
} from "src/core/generated-graphql";
|
||||||
|
import { circumcisedStrings, stringToCircumcised } from "src/utils/circumcised";
|
||||||
|
import { CriterionOption, MultiStringCriterion } from "./criterion";
|
||||||
|
|
||||||
|
export const CircumcisedCriterionOption = new CriterionOption({
|
||||||
|
messageID: "circumcised",
|
||||||
|
type: "circumcised",
|
||||||
|
options: circumcisedStrings,
|
||||||
|
modifierOptions: [
|
||||||
|
CriterionModifier.Includes,
|
||||||
|
CriterionModifier.Excludes,
|
||||||
|
CriterionModifier.IsNull,
|
||||||
|
CriterionModifier.NotNull,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export class CircumcisedCriterion extends MultiStringCriterion {
|
||||||
|
constructor() {
|
||||||
|
super(CircumcisedCriterionOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toCriterionInput(): CircumcisionCriterionInput {
|
||||||
|
const value = this.value.map((v) =>
|
||||||
|
stringToCircumcised(v)
|
||||||
|
) as CircumisedEnum[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
modifier: this.modifier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
export type Option = string | number | IOptionType;
|
export type Option = string | number | IOptionType;
|
||||||
export type CriterionValue =
|
export type CriterionValue =
|
||||||
| string
|
| string
|
||||||
|
| string[]
|
||||||
| ILabeledId[]
|
| ILabeledId[]
|
||||||
| IHierarchicalLabelValue
|
| IHierarchicalLabelValue
|
||||||
| INumberValue
|
| INumberValue
|
||||||
@@ -243,6 +244,24 @@ export class StringCriterion extends Criterion<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class MultiStringCriterion extends Criterion<string[]> {
|
||||||
|
constructor(type: CriterionOption) {
|
||||||
|
super(type, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLabelValue(_intl: IntlShape) {
|
||||||
|
return this.value.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public isValid(): boolean {
|
||||||
|
return (
|
||||||
|
this.modifier === CriterionModifier.IsNull ||
|
||||||
|
this.modifier === CriterionModifier.NotNull ||
|
||||||
|
this.value.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class MandatoryStringCriterionOption extends CriterionOption {
|
export class MandatoryStringCriterionOption extends CriterionOption {
|
||||||
constructor(
|
constructor(
|
||||||
messageID: string,
|
messageID: string,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
TagsCriterionOption,
|
TagsCriterionOption,
|
||||||
} from "./tags";
|
} from "./tags";
|
||||||
import { GenderCriterion } from "./gender";
|
import { GenderCriterion } from "./gender";
|
||||||
|
import { CircumcisedCriterion } from "./circumcised";
|
||||||
import { MoviesCriterionOption } from "./movies";
|
import { MoviesCriterionOption } from "./movies";
|
||||||
import { GalleriesCriterion } from "./galleries";
|
import { GalleriesCriterion } from "./galleries";
|
||||||
import { CriterionType } from "../types";
|
import { CriterionType } from "../types";
|
||||||
@@ -155,12 +156,16 @@ export function makeCriteria(
|
|||||||
case "death_year":
|
case "death_year":
|
||||||
case "weight":
|
case "weight":
|
||||||
return new NumberCriterion(new NumberCriterionOption(type, type));
|
return new NumberCriterion(new NumberCriterionOption(type, type));
|
||||||
|
case "penis_length":
|
||||||
|
return new NumberCriterion(new NumberCriterionOption(type, type));
|
||||||
case "age":
|
case "age":
|
||||||
return new NumberCriterion(
|
return new NumberCriterion(
|
||||||
new MandatoryNumberCriterionOption(type, type)
|
new MandatoryNumberCriterionOption(type, type)
|
||||||
);
|
);
|
||||||
case "gender":
|
case "gender":
|
||||||
return new GenderCriterion();
|
return new GenderCriterion();
|
||||||
|
case "circumcised":
|
||||||
|
return new CircumcisedCriterion();
|
||||||
case "sceneChecksum":
|
case "sceneChecksum":
|
||||||
case "galleryChecksum":
|
case "galleryChecksum":
|
||||||
return new StringCriterion(
|
return new StringCriterion(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from "./criteria/criterion";
|
} from "./criteria/criterion";
|
||||||
import { FavoriteCriterionOption } from "./criteria/favorite";
|
import { FavoriteCriterionOption } from "./criteria/favorite";
|
||||||
import { GenderCriterionOption } from "./criteria/gender";
|
import { GenderCriterionOption } from "./criteria/gender";
|
||||||
|
import { CircumcisedCriterionOption } from "./criteria/circumcised";
|
||||||
import { PerformerIsMissingCriterionOption } from "./criteria/is-missing";
|
import { PerformerIsMissingCriterionOption } from "./criteria/is-missing";
|
||||||
import { StashIDCriterionOption } from "./criteria/stash-ids";
|
import { StashIDCriterionOption } from "./criteria/stash-ids";
|
||||||
import { StudiosCriterionOption } from "./criteria/studios";
|
import { StudiosCriterionOption } from "./criteria/studios";
|
||||||
@@ -25,6 +26,7 @@ const sortByOptions = [
|
|||||||
"tag_count",
|
"tag_count",
|
||||||
"random",
|
"random",
|
||||||
"rating",
|
"rating",
|
||||||
|
"penis_length",
|
||||||
]
|
]
|
||||||
.map(ListFilterOptions.createSortBy)
|
.map(ListFilterOptions.createSortBy)
|
||||||
.concat([
|
.concat([
|
||||||
@@ -57,6 +59,7 @@ const numberCriteria: CriterionType[] = [
|
|||||||
"death_year",
|
"death_year",
|
||||||
"age",
|
"age",
|
||||||
"weight",
|
"weight",
|
||||||
|
"penis_length",
|
||||||
];
|
];
|
||||||
|
|
||||||
const stringCriteria: CriterionType[] = [
|
const stringCriteria: CriterionType[] = [
|
||||||
@@ -78,6 +81,7 @@ const stringCriteria: CriterionType[] = [
|
|||||||
const criterionOptions = [
|
const criterionOptions = [
|
||||||
FavoriteCriterionOption,
|
FavoriteCriterionOption,
|
||||||
GenderCriterionOption,
|
GenderCriterionOption,
|
||||||
|
CircumcisedCriterionOption,
|
||||||
PerformerIsMissingCriterionOption,
|
PerformerIsMissingCriterionOption,
|
||||||
TagsCriterionOption,
|
TagsCriterionOption,
|
||||||
StudiosCriterionOption,
|
StudiosCriterionOption,
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ export type CriterionType =
|
|||||||
| "weight"
|
| "weight"
|
||||||
| "measurements"
|
| "measurements"
|
||||||
| "fake_tits"
|
| "fake_tits"
|
||||||
|
| "penis_length"
|
||||||
|
| "circumcised"
|
||||||
| "career_length"
|
| "career_length"
|
||||||
| "tattoos"
|
| "tattoos"
|
||||||
| "piercings"
|
| "piercings"
|
||||||
|
|||||||
51
ui/v2.5/src/utils/circumcised.ts
Normal file
51
ui/v2.5/src/utils/circumcised.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import * as GQL from "../core/generated-graphql";
|
||||||
|
|
||||||
|
export const stringCircumMap = new Map<string, GQL.CircumisedEnum>([
|
||||||
|
["Uncut", GQL.CircumisedEnum.Uncut],
|
||||||
|
["Cut", GQL.CircumisedEnum.Cut],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const circumcisedToString = (
|
||||||
|
value?: GQL.CircumisedEnum | String | null
|
||||||
|
) => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundEntry = Array.from(stringCircumMap.entries()).find((e) => {
|
||||||
|
return e[1] === value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundEntry) {
|
||||||
|
return foundEntry[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringToCircumcised = (
|
||||||
|
value?: string | null,
|
||||||
|
caseInsensitive?: boolean
|
||||||
|
): GQL.CircumisedEnum | undefined => {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = Object.entries(GQL.CircumisedEnum).find(
|
||||||
|
(e) => e[1] === value
|
||||||
|
);
|
||||||
|
if (existing) return existing[1];
|
||||||
|
|
||||||
|
const ret = stringCircumMap.get(value);
|
||||||
|
if (ret || !caseInsensitive) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
const asUpper = value.toUpperCase();
|
||||||
|
const foundEntry = Array.from(stringCircumMap.entries()).find((e) => {
|
||||||
|
return e[0].toUpperCase() === asUpper;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundEntry) {
|
||||||
|
return foundEntry[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const circumcisedStrings = Array.from(stringCircumMap.keys());
|
||||||
@@ -9,3 +9,9 @@ export function cmToImperial(cm: number) {
|
|||||||
export function kgToLbs(kg: number) {
|
export function kgToLbs(kg: number) {
|
||||||
return Math.round(kg * 2.20462262185);
|
return Math.round(kg * 2.20462262185);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cmToInches(cm: number) {
|
||||||
|
const cmInInches = 0.393700787;
|
||||||
|
const inches = cm * cmInInches;
|
||||||
|
return inches;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user