mirror of
https://github.com/stashapp/stash.git
synced 2025-12-07 08:54:10 +01:00
Add gender support for performer (#371)
Co-authored-by: HiddenPants255 <> Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
parent
d886012d74
commit
494b794228
27 changed files with 333 additions and 22 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
fragment SlimPerformerData on Performer {
|
fragment SlimPerformerData on Performer {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
gender
|
||||||
image_path
|
image_path
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ fragment PerformerData on Performer {
|
||||||
checksum
|
checksum
|
||||||
name
|
name
|
||||||
url
|
url
|
||||||
|
gender
|
||||||
twitter
|
twitter
|
||||||
instagram
|
instagram
|
||||||
birthdate
|
birthdate
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mutation PerformerCreate(
|
mutation PerformerCreate(
|
||||||
$name: String,
|
$name: String,
|
||||||
$url: String,
|
$url: String,
|
||||||
|
$gender: GenderEnum,
|
||||||
$birthdate: String,
|
$birthdate: String,
|
||||||
$ethnicity: String,
|
$ethnicity: String,
|
||||||
$country: String,
|
$country: String,
|
||||||
|
|
@ -20,6 +21,7 @@ mutation PerformerCreate(
|
||||||
performerCreate(input: {
|
performerCreate(input: {
|
||||||
name: $name,
|
name: $name,
|
||||||
url: $url,
|
url: $url,
|
||||||
|
gender: $gender,
|
||||||
birthdate: $birthdate,
|
birthdate: $birthdate,
|
||||||
ethnicity: $ethnicity,
|
ethnicity: $ethnicity,
|
||||||
country: $country,
|
country: $country,
|
||||||
|
|
@ -44,6 +46,7 @@ mutation PerformerUpdate(
|
||||||
$id: ID!,
|
$id: ID!,
|
||||||
$name: String,
|
$name: String,
|
||||||
$url: String,
|
$url: String,
|
||||||
|
$gender: GenderEnum,
|
||||||
$birthdate: String,
|
$birthdate: String,
|
||||||
$ethnicity: String,
|
$ethnicity: String,
|
||||||
$country: String,
|
$country: String,
|
||||||
|
|
@ -64,6 +67,7 @@ mutation PerformerUpdate(
|
||||||
id: $id,
|
id: $id,
|
||||||
name: $name,
|
name: $name,
|
||||||
url: $url,
|
url: $url,
|
||||||
|
gender: $gender,
|
||||||
birthdate: $birthdate,
|
birthdate: $birthdate,
|
||||||
ethnicity: $ethnicity,
|
ethnicity: $ethnicity,
|
||||||
country: $country,
|
country: $country,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ input PerformerFilterType {
|
||||||
piercings: StringCriterionInput
|
piercings: StringCriterionInput
|
||||||
"""Filter by aliases"""
|
"""Filter by aliases"""
|
||||||
aliases: StringCriterionInput
|
aliases: StringCriterionInput
|
||||||
|
"""Filter by gender"""
|
||||||
|
gender: GenderCriterionInput
|
||||||
}
|
}
|
||||||
|
|
||||||
input SceneMarkerFilterType {
|
input SceneMarkerFilterType {
|
||||||
|
|
@ -115,3 +117,8 @@ input MultiCriterionInput {
|
||||||
value: [ID!]
|
value: [ID!]
|
||||||
modifier: CriterionModifier!
|
modifier: CriterionModifier!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input GenderCriterionInput {
|
||||||
|
value: GenderEnum
|
||||||
|
modifier: CriterionModifier!
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
|
enum GenderEnum {
|
||||||
|
MALE
|
||||||
|
FEMALE
|
||||||
|
TRANSGENDER_MALE
|
||||||
|
TRANSGENDER_FEMALE
|
||||||
|
INTERSEX
|
||||||
|
}
|
||||||
|
|
||||||
type Performer {
|
type Performer {
|
||||||
id: ID!
|
id: ID!
|
||||||
checksum: String!
|
checksum: String!
|
||||||
name: String
|
name: String
|
||||||
url: String
|
url: String
|
||||||
|
gender: GenderEnum
|
||||||
twitter: String
|
twitter: String
|
||||||
instagram: String
|
instagram: String
|
||||||
birthdate: String
|
birthdate: String
|
||||||
|
|
@ -26,6 +35,7 @@ type Performer {
|
||||||
input PerformerCreateInput {
|
input PerformerCreateInput {
|
||||||
name: String
|
name: String
|
||||||
url: String
|
url: String
|
||||||
|
gender: GenderEnum
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
country: String
|
country: String
|
||||||
|
|
@ -48,6 +58,7 @@ input PerformerUpdateInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
url: String
|
url: String
|
||||||
|
gender: GenderEnum
|
||||||
birthdate: String
|
birthdate: String
|
||||||
ethnicity: String
|
ethnicity: String
|
||||||
country: String
|
country: String
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
@ -20,6 +21,19 @@ func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*st
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *performerResolver) Gender(ctx context.Context, obj *models.Performer) (*models.GenderEnum, error) {
|
||||||
|
var ret models.GenderEnum
|
||||||
|
|
||||||
|
if obj.Gender.Valid {
|
||||||
|
ret = models.GenderEnum(obj.Gender.String)
|
||||||
|
if ret.IsValid() {
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
|
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
|
||||||
if obj.Twitter.Valid {
|
if obj.Twitter.Valid {
|
||||||
return &obj.Twitter.String, nil
|
return &obj.Twitter.String, nil
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
||||||
if input.URL != nil {
|
if input.URL != nil {
|
||||||
newPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
|
newPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
|
||||||
}
|
}
|
||||||
|
if input.Gender != nil {
|
||||||
|
newPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true}
|
||||||
|
}
|
||||||
if input.Birthdate != nil {
|
if input.Birthdate != nil {
|
||||||
newPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
newPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +131,9 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
|
||||||
if input.URL != nil {
|
if input.URL != nil {
|
||||||
updatedPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
|
updatedPerformer.URL = sql.NullString{String: *input.URL, Valid: true}
|
||||||
}
|
}
|
||||||
|
if input.Gender != nil {
|
||||||
|
updatedPerformer.Gender = sql.NullString{String: input.Gender.String(), Valid: true}
|
||||||
|
}
|
||||||
if input.Birthdate != nil {
|
if input.Birthdate != nil {
|
||||||
updatedPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
updatedPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
|
|
||||||
var DB *sqlx.DB
|
var DB *sqlx.DB
|
||||||
var dbPath string
|
var dbPath string
|
||||||
var appSchemaVersion uint = 4
|
var appSchemaVersion uint = 5
|
||||||
var databaseSchemaVersion uint
|
var databaseSchemaVersion uint
|
||||||
|
|
||||||
const sqlite3Driver = "sqlite3_regexp"
|
const sqlite3Driver = "sqlite3_regexp"
|
||||||
|
|
|
||||||
89
pkg/database/migrations/5_performer_gender.down.sql
Normal file
89
pkg/database/migrations/5_performer_gender.down.sql
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
PRAGMA foreign_keys=off;
|
||||||
|
|
||||||
|
-- need to re-create the performers table without the added column.
|
||||||
|
-- also need re-create the performers_scenes table due to the foreign key
|
||||||
|
|
||||||
|
-- rename existing performers table
|
||||||
|
ALTER TABLE `performers` RENAME TO `performers_old`;
|
||||||
|
ALTER TABLE `performers_scenes` RENAME TO `performers_scenes_old`;
|
||||||
|
|
||||||
|
-- drop the indexes
|
||||||
|
DROP INDEX IF EXISTS `index_performers_on_name`;
|
||||||
|
DROP INDEX IF EXISTS `index_performers_on_checksum`;
|
||||||
|
DROP INDEX IF EXISTS `index_performers_scenes_on_scene_id`;
|
||||||
|
DROP INDEX IF EXISTS `index_performers_scenes_on_performer_id`;
|
||||||
|
|
||||||
|
-- recreate the tables
|
||||||
|
CREATE TABLE `performers` (
|
||||||
|
`id` integer not null primary key autoincrement,
|
||||||
|
`image` blob not null,
|
||||||
|
`checksum` varchar(255) not null,
|
||||||
|
`name` varchar(255),
|
||||||
|
`url` varchar(255),
|
||||||
|
`twitter` varchar(255),
|
||||||
|
`instagram` varchar(255),
|
||||||
|
`birthdate` date,
|
||||||
|
`ethnicity` varchar(255),
|
||||||
|
`country` varchar(255),
|
||||||
|
`eye_color` varchar(255),
|
||||||
|
`height` varchar(255),
|
||||||
|
`measurements` varchar(255),
|
||||||
|
`fake_tits` varchar(255),
|
||||||
|
`career_length` varchar(255),
|
||||||
|
`tattoos` varchar(255),
|
||||||
|
`piercings` varchar(255),
|
||||||
|
`aliases` varchar(255),
|
||||||
|
`favorite` boolean not null default '0',
|
||||||
|
`created_at` datetime not null,
|
||||||
|
`updated_at` datetime not null
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE `performers_scenes` (
|
||||||
|
`performer_id` integer,
|
||||||
|
`scene_id` integer,
|
||||||
|
foreign key(`performer_id`) references `performers`(`id`),
|
||||||
|
foreign key(`scene_id`) references `scenes`(`id`)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO `performers`
|
||||||
|
SELECT
|
||||||
|
`id`,
|
||||||
|
`image`,
|
||||||
|
`checksum`,
|
||||||
|
`name`,
|
||||||
|
`url`,
|
||||||
|
`twitter`,
|
||||||
|
`instagram`,
|
||||||
|
`birthdate`,
|
||||||
|
`ethnicity`,
|
||||||
|
`country`,
|
||||||
|
`eye_color`,
|
||||||
|
`height`,
|
||||||
|
`measurements`,
|
||||||
|
`fake_tits`,
|
||||||
|
`career_length`,
|
||||||
|
`tattoos`,
|
||||||
|
`piercings`,
|
||||||
|
`aliases`,
|
||||||
|
`favorite`,
|
||||||
|
`created_at`,
|
||||||
|
`updated_at`
|
||||||
|
FROM `performers_old`;
|
||||||
|
|
||||||
|
INSERT INTO `performers_scenes`
|
||||||
|
SELECT
|
||||||
|
`performer_id`,
|
||||||
|
`scene_id`
|
||||||
|
FROM `performers_scenes_old`;
|
||||||
|
|
||||||
|
DROP TABLE `performers_scenes_old`;
|
||||||
|
DROP TABLE `performers_old`;
|
||||||
|
|
||||||
|
-- re-create the indexes after removing the old tables
|
||||||
|
CREATE INDEX `index_performers_on_name` on `performers` (`name`);
|
||||||
|
CREATE INDEX `index_performers_on_checksum` on `performers` (`checksum`);
|
||||||
|
CREATE INDEX `index_performers_scenes_on_scene_id` on `performers_scenes` (`scene_id`);
|
||||||
|
CREATE INDEX `index_performers_scenes_on_performer_id` on `performers_scenes` (`performer_id`);
|
||||||
|
|
||||||
|
PRAGMA foreign_keys=on;
|
||||||
1
pkg/database/migrations/5_performer_gender.up.sql
Normal file
1
pkg/database/migrations/5_performer_gender.up.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE `performers` ADD COLUMN `gender` varchar(20);
|
||||||
|
|
@ -3,12 +3,14 @@ package jsonschema
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/stashapp/stash/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Performer struct {
|
type Performer struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Twitter string `json:"twitter,omitempty"`
|
Twitter string `json:"twitter,omitempty"`
|
||||||
Instagram string `json:"instagram,omitempty"`
|
Instagram string `json:"instagram,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,9 @@ func (t *ExportTask) ExportPerformers(ctx context.Context) {
|
||||||
if performer.Name.Valid {
|
if performer.Name.Valid {
|
||||||
newPerformerJSON.Name = performer.Name.String
|
newPerformerJSON.Name = performer.Name.String
|
||||||
}
|
}
|
||||||
|
if performer.Gender.Valid {
|
||||||
|
newPerformerJSON.Gender = performer.Gender.String
|
||||||
|
}
|
||||||
if performer.URL.Valid {
|
if performer.URL.Valid {
|
||||||
newPerformerJSON.URL = performer.URL.String
|
newPerformerJSON.URL = performer.URL.String
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,9 @@ func (t *ImportTask) ImportPerformers(ctx context.Context) {
|
||||||
if performerJSON.Name != "" {
|
if performerJSON.Name != "" {
|
||||||
newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true}
|
newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true}
|
||||||
}
|
}
|
||||||
|
if performerJSON.Gender != "" {
|
||||||
|
newPerformer.Gender = sql.NullString{String: performerJSON.Gender, Valid: true}
|
||||||
|
}
|
||||||
if performerJSON.URL != "" {
|
if performerJSON.URL != "" {
|
||||||
newPerformer.URL = sql.NullString{String: performerJSON.URL, Valid: true}
|
newPerformer.URL = sql.NullString{String: performerJSON.URL, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
@ -241,19 +244,19 @@ func (t *ImportTask) ImportMovies(ctx context.Context) {
|
||||||
|
|
||||||
// Populate a new movie from the input
|
// Populate a new movie from the input
|
||||||
newMovie := models.Movie{
|
newMovie := models.Movie{
|
||||||
FrontImage: frontimageData,
|
FrontImage: frontimageData,
|
||||||
BackImage: backimageData,
|
BackImage: backimageData,
|
||||||
Checksum: checksum,
|
Checksum: checksum,
|
||||||
Name: sql.NullString{String: movieJSON.Name, Valid: true},
|
Name: sql.NullString{String: movieJSON.Name, Valid: true},
|
||||||
Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true},
|
Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true},
|
||||||
Date: models.SQLiteDate{String: movieJSON.Date, Valid: true},
|
Date: models.SQLiteDate{String: movieJSON.Date, Valid: true},
|
||||||
Duration: sql.NullString{String: movieJSON.Duration, Valid: true},
|
Duration: sql.NullString{String: movieJSON.Duration, Valid: true},
|
||||||
Rating: sql.NullString{String: movieJSON.Rating, Valid: true},
|
Rating: sql.NullString{String: movieJSON.Rating, Valid: true},
|
||||||
Director: sql.NullString{String: movieJSON.Director, Valid: true},
|
Director: sql.NullString{String: movieJSON.Director, Valid: true},
|
||||||
Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true},
|
Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true},
|
||||||
URL: sql.NullString{String: movieJSON.URL, Valid: true},
|
URL: sql.NullString{String: movieJSON.URL, Valid: true},
|
||||||
CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.CreatedAt)},
|
CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.CreatedAt)},
|
||||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.UpdatedAt)},
|
UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.UpdatedAt)},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = qb.Create(newMovie, tx)
|
_, err = qb.Create(newMovie, tx)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ type Performer struct {
|
||||||
Image []byte `db:"image" json:"image"`
|
Image []byte `db:"image" json:"image"`
|
||||||
Checksum string `db:"checksum" json:"checksum"`
|
Checksum string `db:"checksum" json:"checksum"`
|
||||||
Name sql.NullString `db:"name" json:"name"`
|
Name sql.NullString `db:"name" json:"name"`
|
||||||
|
Gender sql.NullString `db:"gender" json:"gender"`
|
||||||
URL sql.NullString `db:"url" json:"url"`
|
URL sql.NullString `db:"url" json:"url"`
|
||||||
Twitter sql.NullString `db:"twitter" json:"twitter"`
|
Twitter sql.NullString `db:"twitter" json:"twitter"`
|
||||||
Instagram sql.NullString `db:"instagram" json:"instagram"`
|
Instagram sql.NullString `db:"instagram" json:"instagram"`
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ func NewPerformerQueryBuilder() PerformerQueryBuilder {
|
||||||
func (qb *PerformerQueryBuilder) Create(newPerformer Performer, tx *sqlx.Tx) (*Performer, error) {
|
func (qb *PerformerQueryBuilder) Create(newPerformer Performer, tx *sqlx.Tx) (*Performer, error) {
|
||||||
ensureTx(tx)
|
ensureTx(tx)
|
||||||
result, err := tx.NamedExec(
|
result, err := tx.NamedExec(
|
||||||
`INSERT INTO performers (image, checksum, name, url, twitter, instagram, birthdate, ethnicity, country,
|
`INSERT INTO performers (image, checksum, name, url, gender, twitter, instagram, birthdate, ethnicity, country,
|
||||||
eye_color, height, measurements, fake_tits, career_length, tattoos, piercings,
|
eye_color, height, measurements, fake_tits, career_length, tattoos, piercings,
|
||||||
aliases, favorite, created_at, updated_at)
|
aliases, favorite, created_at, updated_at)
|
||||||
VALUES (:image, :checksum, :name, :url, :twitter, :instagram, :birthdate, :ethnicity, :country,
|
VALUES (:image, :checksum, :name, :url, :gender, :twitter, :instagram, :birthdate, :ethnicity, :country,
|
||||||
:eye_color, :height, :measurements, :fake_tits, :career_length, :tattoos, :piercings,
|
:eye_color, :height, :measurements, :fake_tits, :career_length, :tattoos, :piercings,
|
||||||
:aliases, :favorite, :created_at, :updated_at)
|
:aliases, :favorite, :created_at, :updated_at)
|
||||||
`,
|
`,
|
||||||
|
|
@ -153,6 +153,11 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin
|
||||||
query.addArg(thisArgs...)
|
query.addArg(thisArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if gender := performerFilter.Gender; gender != nil {
|
||||||
|
query.addWhere("performers.gender = ?")
|
||||||
|
query.addArg(gender.Value.String())
|
||||||
|
}
|
||||||
|
|
||||||
handleStringCriterion(tableName+".ethnicity", performerFilter.Ethnicity, &query)
|
handleStringCriterion(tableName+".ethnicity", performerFilter.Ethnicity, &query)
|
||||||
handleStringCriterion(tableName+".country", performerFilter.Country, &query)
|
handleStringCriterion(tableName+".country", performerFilter.Country, &query)
|
||||||
handleStringCriterion(tableName+".eye_color", performerFilter.EyeColor, &query)
|
handleStringCriterion(tableName+".eye_color", performerFilter.EyeColor, &query)
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||||
const [url, setUrl] = useState<string>();
|
const [url, setUrl] = useState<string>();
|
||||||
const [twitter, setTwitter] = useState<string>();
|
const [twitter, setTwitter] = useState<string>();
|
||||||
const [instagram, setInstagram] = useState<string>();
|
const [instagram, setInstagram] = useState<string>();
|
||||||
|
const [gender, setGender] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
// Network state
|
// Network state
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
@ -92,6 +93,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||||
setUrl(state.url ?? undefined);
|
setUrl(state.url ?? undefined);
|
||||||
setTwitter(state.twitter ?? undefined);
|
setTwitter(state.twitter ?? undefined);
|
||||||
setInstagram(state.instagram ?? undefined);
|
setInstagram(state.instagram ?? undefined);
|
||||||
|
setGender(StashService.genderToString((state as GQL.PerformerDataFragment).gender ?? undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePerformerEditStateFromScraper(
|
function updatePerformerEditStateFromScraper(
|
||||||
|
|
@ -153,7 +155,8 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||||
url,
|
url,
|
||||||
twitter,
|
twitter,
|
||||||
instagram,
|
instagram,
|
||||||
image
|
image,
|
||||||
|
gender: StashService.stringToGender(gender)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
|
|
@ -397,6 +400,16 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGender() {
|
||||||
|
return TableUtils.renderHtmlSelect({
|
||||||
|
title: "Gender",
|
||||||
|
value: gender,
|
||||||
|
isEditing: !!isEditing,
|
||||||
|
onChange: (value: string) => setGender(value),
|
||||||
|
selectOptions: [""].concat(StashService.getGenderStrings()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderDeleteAlert()}
|
{renderDeleteAlert()}
|
||||||
|
|
@ -406,6 +419,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
||||||
<tbody>
|
<tbody>
|
||||||
{maybeRenderName()}
|
{maybeRenderName()}
|
||||||
{maybeRenderAliases()}
|
{maybeRenderAliases()}
|
||||||
|
{renderGender()}
|
||||||
{TableUtils.renderInputGroup({
|
{TableUtils.renderInputGroup({
|
||||||
title: "Birthdate",
|
title: "Birthdate",
|
||||||
value: birthdate,
|
value: birthdate,
|
||||||
|
|
|
||||||
|
|
@ -681,6 +681,40 @@ export class StashService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static stringGenderMap = new Map<string, GQL.GenderEnum>(
|
||||||
|
[["Male", GQL.GenderEnum.Male],
|
||||||
|
["Female", GQL.GenderEnum.Female],
|
||||||
|
["Transgender Male", GQL.GenderEnum.TransgenderMale],
|
||||||
|
["Transgender Female", GQL.GenderEnum.TransgenderFemale],
|
||||||
|
["Intersex", GQL.GenderEnum.Intersex]]
|
||||||
|
);
|
||||||
|
|
||||||
|
public static genderToString(value?: GQL.GenderEnum) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundEntry = Array.from(StashService.stringGenderMap.entries()).find((e) => {
|
||||||
|
return e[1] === value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundEntry) {
|
||||||
|
return foundEntry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static stringToGender(value?: string) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StashService.stringGenderMap.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getGenderStrings() {
|
||||||
|
return Array.from(StashService.stringGenderMap.keys());
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ export type CriterionType =
|
||||||
| "career_length"
|
| "career_length"
|
||||||
| "tattoos"
|
| "tattoos"
|
||||||
| "piercings"
|
| "piercings"
|
||||||
| "aliases";
|
| "aliases"
|
||||||
|
| "gender";
|
||||||
|
|
||||||
type Option = string | number | IOptionType;
|
type Option = string | number | IOptionType;
|
||||||
export type CriterionValue = string | number | ILabeledId[];
|
export type CriterionValue = string | number | ILabeledId[];
|
||||||
|
|
@ -87,6 +88,8 @@ export abstract class Criterion {
|
||||||
return "Piercings";
|
return "Piercings";
|
||||||
case "aliases":
|
case "aliases":
|
||||||
return "Aliases";
|
return "Aliases";
|
||||||
|
case "gender":
|
||||||
|
return "Gender";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
ui/v2.5/src/models/list-filter/criteria/gender.ts
Normal file
21
ui/v2.5/src/models/list-filter/criteria/gender.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { CriterionModifier } from "src/core/generated-graphql";
|
||||||
|
import { StashService } from "src/core/StashService";
|
||||||
|
import {
|
||||||
|
Criterion,
|
||||||
|
CriterionType,
|
||||||
|
ICriterionOption,
|
||||||
|
} from "./criterion";
|
||||||
|
|
||||||
|
export class GenderCriterion extends Criterion {
|
||||||
|
public type: CriterionType = "gender";
|
||||||
|
public parameterName: string = "gender";
|
||||||
|
public modifier = CriterionModifier.Equals;
|
||||||
|
public modifierOptions = [];
|
||||||
|
public options: string[] = StashService.getGenderStrings();
|
||||||
|
public value: string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenderCriterionOption implements ICriterionOption {
|
||||||
|
public label: string = Criterion.getLabel("gender");
|
||||||
|
public value: CriterionType = "gender";
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import { RatingCriterion } from "./rating";
|
||||||
import { ResolutionCriterion } from "./resolution";
|
import { ResolutionCriterion } from "./resolution";
|
||||||
import { StudiosCriterion } from "./studios";
|
import { StudiosCriterion } from "./studios";
|
||||||
import { TagsCriterion } from "./tags";
|
import { TagsCriterion } from "./tags";
|
||||||
|
import { GenderCriterion } from "./gender";
|
||||||
import { MoviesCriterion } from "./movies";
|
import { MoviesCriterion } from "./movies";
|
||||||
|
|
||||||
export function makeCriteria(type: CriterionType = "none") {
|
export function makeCriteria(type: CriterionType = "none") {
|
||||||
|
|
@ -60,6 +61,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||||
];
|
];
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
case "gender":
|
||||||
|
return new GenderCriterion();
|
||||||
case "ethnicity":
|
case "ethnicity":
|
||||||
case "country":
|
case "country":
|
||||||
case "eye_color":
|
case "eye_color":
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ import {
|
||||||
} from "./criteria/tags";
|
} from "./criteria/tags";
|
||||||
import { makeCriteria } from "./criteria/utils";
|
import { makeCriteria } from "./criteria/utils";
|
||||||
import { DisplayMode, FilterMode } from "./types";
|
import { DisplayMode, FilterMode } from "./types";
|
||||||
|
import { GenderCriterionOption, GenderCriterion } from "./criteria/gender";
|
||||||
|
import { StashService } from "src/core/StashService";
|
||||||
import { MoviesCriterionOption, MoviesCriterion } from "./criteria/movies";
|
import { MoviesCriterionOption, MoviesCriterion } from "./criteria/movies";
|
||||||
|
|
||||||
interface IQueryParameters {
|
interface IQueryParameters {
|
||||||
|
|
@ -141,7 +143,8 @@ export class ListFilterModel {
|
||||||
|
|
||||||
this.criterionOptions = [
|
this.criterionOptions = [
|
||||||
new NoneCriterionOption(),
|
new NoneCriterionOption(),
|
||||||
new FavoriteCriterionOption()
|
new FavoriteCriterionOption(),
|
||||||
|
new GenderCriterionOption(),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.criterionOptions = this.criterionOptions.concat(
|
this.criterionOptions = this.criterionOptions.concat(
|
||||||
|
|
@ -502,6 +505,11 @@ export class ListFilterModel {
|
||||||
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "gender": {
|
||||||
|
const gCrit = criterion as GenderCriterion;
|
||||||
|
result.gender = { value: StashService.stringToGender(gCrit.value), modifier: gCrit.modifier };
|
||||||
|
break;
|
||||||
|
}
|
||||||
// no default
|
// no default
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
||||||
const [url, setUrl] = useState<string | undefined>(undefined);
|
const [url, setUrl] = useState<string | undefined>(undefined);
|
||||||
const [twitter, setTwitter] = useState<string | undefined>(undefined);
|
const [twitter, setTwitter] = useState<string | undefined>(undefined);
|
||||||
const [instagram, setInstagram] = useState<string | undefined>(undefined);
|
const [instagram, setInstagram] = useState<string | undefined>(undefined);
|
||||||
|
const [gender, setGender] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
// Network state
|
// Network state
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
@ -80,6 +81,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
||||||
setUrl(state.url);
|
setUrl(state.url);
|
||||||
setTwitter(state.twitter);
|
setTwitter(state.twitter);
|
||||||
setInstagram(state.instagram);
|
setInstagram(state.instagram);
|
||||||
|
setGender(StashService.genderToString((state as GQL.PerformerDataFragment).gender));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePerformerEditStateFromScraper(state: Partial<GQL.ScrapedPerformerDataFragment | GQL.ScrapeFreeonesScrapeFreeones>) {
|
function updatePerformerEditStateFromScraper(state: Partial<GQL.ScrapedPerformerDataFragment | GQL.ScrapeFreeonesScrapeFreeones>) {
|
||||||
|
|
@ -147,6 +149,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
||||||
twitter,
|
twitter,
|
||||||
instagram,
|
instagram,
|
||||||
image,
|
image,
|
||||||
|
gender: StashService.stringToGender(gender)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!props.isNew) {
|
if (!props.isNew) {
|
||||||
|
|
@ -373,6 +376,16 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderGender() {
|
||||||
|
return TableUtils.renderHtmlSelect({
|
||||||
|
title: "Gender",
|
||||||
|
value: gender,
|
||||||
|
isEditing: !!props.isEditing,
|
||||||
|
onChange: (value: string) => setGender(value),
|
||||||
|
selectOptions: [""].concat(StashService.getGenderStrings()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const twitterPrefix = "https://twitter.com/";
|
const twitterPrefix = "https://twitter.com/";
|
||||||
const instagramPrefix = "https://www.instagram.com/";
|
const instagramPrefix = "https://www.instagram.com/";
|
||||||
|
|
||||||
|
|
@ -385,6 +398,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
||||||
<tbody>
|
<tbody>
|
||||||
{maybeRenderName()}
|
{maybeRenderName()}
|
||||||
{maybeRenderAliases()}
|
{maybeRenderAliases()}
|
||||||
|
{renderGender()}
|
||||||
{TableUtils.renderInputGroup(
|
{TableUtils.renderInputGroup(
|
||||||
{title: "Birthdate (YYYY-MM-DD)", value: birthdate, isEditing: !!props.isEditing, onChange: setBirthdate})}
|
{title: "Birthdate (YYYY-MM-DD)", value: birthdate, isEditing: !!props.isEditing, onChange: setBirthdate})}
|
||||||
{renderEthnicity()}
|
{renderEthnicity()}
|
||||||
|
|
|
||||||
|
|
@ -580,6 +580,40 @@ export class StashService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static stringGenderMap = new Map<string, GQL.GenderEnum>(
|
||||||
|
[["Male", GQL.GenderEnum.Male],
|
||||||
|
["Female", GQL.GenderEnum.Female],
|
||||||
|
["Transgender Male", GQL.GenderEnum.TransgenderMale],
|
||||||
|
["Transgender Female", GQL.GenderEnum.TransgenderFemale],
|
||||||
|
["Intersex", GQL.GenderEnum.Intersex]]
|
||||||
|
);
|
||||||
|
|
||||||
|
public static genderToString(value?: GQL.GenderEnum) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundEntry = Array.from(StashService.stringGenderMap.entries()).find((e) => {
|
||||||
|
return e[1] === value;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundEntry) {
|
||||||
|
return foundEntry[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static stringToGender(value?: string) {
|
||||||
|
if (!value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StashService.stringGenderMap.get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getGenderStrings() {
|
||||||
|
return Array.from(StashService.stringGenderMap.keys());
|
||||||
|
}
|
||||||
|
|
||||||
public static nullToUndefined(value: any): any {
|
public static nullToUndefined(value: any): any {
|
||||||
if (_.isPlainObject(value)) {
|
if (_.isPlainObject(value)) {
|
||||||
return _.mapValues(value, StashService.nullToUndefined);
|
return _.mapValues(value, StashService.nullToUndefined);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ export type CriterionType =
|
||||||
"career_length" |
|
"career_length" |
|
||||||
"tattoos" |
|
"tattoos" |
|
||||||
"piercings" |
|
"piercings" |
|
||||||
"aliases";
|
"aliases" |
|
||||||
|
"gender";
|
||||||
|
|
||||||
export abstract class Criterion<Option = any, Value = any> {
|
export abstract class Criterion<Option = any, Value = any> {
|
||||||
public static getLabel(type: CriterionType = "none"): string {
|
public static getLabel(type: CriterionType = "none"): string {
|
||||||
|
|
@ -58,6 +59,7 @@ export abstract class Criterion<Option = any, Value = any> {
|
||||||
case "tattoos": return "Tattoos";
|
case "tattoos": return "Tattoos";
|
||||||
case "piercings": return "Piercings";
|
case "piercings": return "Piercings";
|
||||||
case "aliases": return "Aliases";
|
case "aliases": return "Aliases";
|
||||||
|
case "gender": return "Gender";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
ui/v2/src/models/list-filter/criteria/gender.ts
Normal file
21
ui/v2/src/models/list-filter/criteria/gender.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { CriterionModifier } from "../../../core/generated-graphql";
|
||||||
|
import { StashService } from "../../../core/StashService";
|
||||||
|
import {
|
||||||
|
Criterion,
|
||||||
|
CriterionType,
|
||||||
|
ICriterionOption,
|
||||||
|
} from "./criterion";
|
||||||
|
|
||||||
|
export class GenderCriterion extends Criterion<string, string> {
|
||||||
|
public type: CriterionType = "gender";
|
||||||
|
public parameterName: string = "gender";
|
||||||
|
public modifier = CriterionModifier.Equals;
|
||||||
|
public modifierOptions = [];
|
||||||
|
public options: string[] = StashService.getGenderStrings();
|
||||||
|
public value: string = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenderCriterionOption implements ICriterionOption {
|
||||||
|
public label: string = Criterion.getLabel("gender");
|
||||||
|
public value: CriterionType = "gender";
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import { ResolutionCriterion } from "./resolution";
|
||||||
import { StudiosCriterion } from "./studios";
|
import { StudiosCriterion } from "./studios";
|
||||||
import { MoviesCriterion } from "./movies";
|
import { MoviesCriterion } from "./movies";
|
||||||
import { TagsCriterion } from "./tags";
|
import { TagsCriterion } from "./tags";
|
||||||
|
import { GenderCriterion } from "./gender";
|
||||||
|
|
||||||
export function makeCriteria(type: CriterionType = "none") {
|
export function makeCriteria(type: CriterionType = "none") {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -40,6 +41,7 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||||
Criterion.getModifierOption(CriterionModifier.LessThan)
|
Criterion.getModifierOption(CriterionModifier.LessThan)
|
||||||
];
|
];
|
||||||
return ret;
|
return ret;
|
||||||
|
case "gender": return new GenderCriterion();
|
||||||
case "ethnicity":
|
case "ethnicity":
|
||||||
case "country":
|
case "country":
|
||||||
case "eye_color":
|
case "eye_color":
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import {
|
||||||
DisplayMode,
|
DisplayMode,
|
||||||
FilterMode,
|
FilterMode,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
import { GenderCriterionOption, GenderCriterion } from "./criteria/gender";
|
||||||
|
import { StashService } from "../../core/StashService";
|
||||||
|
|
||||||
interface IQueryParameters {
|
interface IQueryParameters {
|
||||||
sortby?: string;
|
sortby?: string;
|
||||||
|
|
@ -100,7 +102,8 @@ export class ListFilterModel {
|
||||||
|
|
||||||
this.criterionOptions = [
|
this.criterionOptions = [
|
||||||
new NoneCriterionOption(),
|
new NoneCriterionOption(),
|
||||||
new FavoriteCriterionOption()
|
new FavoriteCriterionOption(),
|
||||||
|
new GenderCriterionOption(),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.criterionOptions = this.criterionOptions.concat(numberCriteria.concat(stringCriteria).map((c) => {
|
this.criterionOptions = this.criterionOptions.concat(numberCriteria.concat(stringCriteria).map((c) => {
|
||||||
|
|
@ -419,6 +422,10 @@ export class ListFilterModel {
|
||||||
const aCrit = criterion as StringCriterion;
|
const aCrit = criterion as StringCriterion;
|
||||||
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
||||||
break;
|
break;
|
||||||
|
case "gender":
|
||||||
|
const gCrit = criterion as GenderCriterion;
|
||||||
|
result.gender = { value: StashService.stringToGender(gCrit.value), modifier: gCrit.modifier };
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue