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:
hiddenpants255 2020-04-01 05:36:38 +07:00 committed by GitHub
parent d886012d74
commit 494b794228
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 333 additions and 22 deletions

View file

@ -1,5 +1,6 @@
fragment SlimPerformerData on Performer { fragment SlimPerformerData on Performer {
id id
name name
gender
image_path image_path
} }

View file

@ -3,6 +3,7 @@ fragment PerformerData on Performer {
checksum checksum
name name
url url
gender
twitter twitter
instagram instagram
birthdate birthdate

View file

@ -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,

View file

@ -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 {
@ -114,4 +116,9 @@ input IntCriterionInput {
input MultiCriterionInput { input MultiCriterionInput {
value: [ID!] value: [ID!]
modifier: CriterionModifier! modifier: CriterionModifier!
}
input GenderCriterionInput {
value: GenderEnum
modifier: CriterionModifier!
} }

View file

@ -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

View file

@ -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

View file

@ -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}
} }

View file

@ -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"

View 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;

View file

@ -0,0 +1 @@
ALTER TABLE `performers` ADD COLUMN `gender` varchar(20);

View file

@ -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"`

View file

@ -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
} }

View file

@ -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)

View file

@ -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"`

View file

@ -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)

View file

@ -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,

View file

@ -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() {}
} }

View file

@ -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";
} }
} }

View 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";
}

View file

@ -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":

View file

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

View file

@ -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()}

View file

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

View file

@ -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";
} }
} }

View 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";
}

View file

@ -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":

View file

@ -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;