mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +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 {
|
||||
id
|
||||
name
|
||||
gender
|
||||
image_path
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ fragment PerformerData on Performer {
|
|||
checksum
|
||||
name
|
||||
url
|
||||
gender
|
||||
twitter
|
||||
instagram
|
||||
birthdate
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
mutation PerformerCreate(
|
||||
$name: String,
|
||||
$url: String,
|
||||
$gender: GenderEnum,
|
||||
$birthdate: String,
|
||||
$ethnicity: String,
|
||||
$country: String,
|
||||
|
|
@ -20,6 +21,7 @@ mutation PerformerCreate(
|
|||
performerCreate(input: {
|
||||
name: $name,
|
||||
url: $url,
|
||||
gender: $gender,
|
||||
birthdate: $birthdate,
|
||||
ethnicity: $ethnicity,
|
||||
country: $country,
|
||||
|
|
@ -44,6 +46,7 @@ mutation PerformerUpdate(
|
|||
$id: ID!,
|
||||
$name: String,
|
||||
$url: String,
|
||||
$gender: GenderEnum,
|
||||
$birthdate: String,
|
||||
$ethnicity: String,
|
||||
$country: String,
|
||||
|
|
@ -64,6 +67,7 @@ mutation PerformerUpdate(
|
|||
id: $id,
|
||||
name: $name,
|
||||
url: $url,
|
||||
gender: $gender,
|
||||
birthdate: $birthdate,
|
||||
ethnicity: $ethnicity,
|
||||
country: $country,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ input PerformerFilterType {
|
|||
piercings: StringCriterionInput
|
||||
"""Filter by aliases"""
|
||||
aliases: StringCriterionInput
|
||||
"""Filter by gender"""
|
||||
gender: GenderCriterionInput
|
||||
}
|
||||
|
||||
input SceneMarkerFilterType {
|
||||
|
|
@ -114,4 +116,9 @@ input IntCriterionInput {
|
|||
input MultiCriterionInput {
|
||||
value: [ID!]
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
||||
input GenderCriterionInput {
|
||||
value: GenderEnum
|
||||
modifier: CriterionModifier!
|
||||
}
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
enum GenderEnum {
|
||||
MALE
|
||||
FEMALE
|
||||
TRANSGENDER_MALE
|
||||
TRANSGENDER_FEMALE
|
||||
INTERSEX
|
||||
}
|
||||
|
||||
type Performer {
|
||||
id: ID!
|
||||
checksum: String!
|
||||
name: String
|
||||
url: String
|
||||
gender: GenderEnum
|
||||
twitter: String
|
||||
instagram: String
|
||||
birthdate: String
|
||||
|
|
@ -26,6 +35,7 @@ type Performer {
|
|||
input PerformerCreateInput {
|
||||
name: String
|
||||
url: String
|
||||
gender: GenderEnum
|
||||
birthdate: String
|
||||
ethnicity: String
|
||||
country: String
|
||||
|
|
@ -48,6 +58,7 @@ input PerformerUpdateInput {
|
|||
id: ID!
|
||||
name: String
|
||||
url: String
|
||||
gender: GenderEnum
|
||||
birthdate: String
|
||||
ethnicity: String
|
||||
country: String
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stashapp/stash/pkg/api/urlbuilders"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
|
@ -20,6 +21,19 @@ func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*st
|
|||
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) {
|
||||
if obj.Twitter.Valid {
|
||||
return &obj.Twitter.String, nil
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
|||
if input.URL != nil {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
updatedPerformer.Birthdate = models.SQLiteDate{String: *input.Birthdate, Valid: true}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
var DB *sqlx.DB
|
||||
var dbPath string
|
||||
var appSchemaVersion uint = 4
|
||||
var appSchemaVersion uint = 5
|
||||
var databaseSchemaVersion uint
|
||||
|
||||
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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"os"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type Performer struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Gender string `json:"gender,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Twitter string `json:"twitter,omitempty"`
|
||||
Instagram string `json:"instagram,omitempty"`
|
||||
|
|
|
|||
|
|
@ -238,6 +238,9 @@ func (t *ExportTask) ExportPerformers(ctx context.Context) {
|
|||
if performer.Name.Valid {
|
||||
newPerformerJSON.Name = performer.Name.String
|
||||
}
|
||||
if performer.Gender.Valid {
|
||||
newPerformerJSON.Gender = performer.Gender.String
|
||||
}
|
||||
if performer.URL.Valid {
|
||||
newPerformerJSON.URL = performer.URL.String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,9 @@ func (t *ImportTask) ImportPerformers(ctx context.Context) {
|
|||
if performerJSON.Name != "" {
|
||||
newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true}
|
||||
}
|
||||
if performerJSON.Gender != "" {
|
||||
newPerformer.Gender = sql.NullString{String: performerJSON.Gender, Valid: true}
|
||||
}
|
||||
if performerJSON.URL != "" {
|
||||
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
|
||||
newMovie := models.Movie{
|
||||
FrontImage: frontimageData,
|
||||
BackImage: backimageData,
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: movieJSON.Name, Valid: true},
|
||||
Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true},
|
||||
Date: models.SQLiteDate{String: movieJSON.Date, Valid: true},
|
||||
Duration: sql.NullString{String: movieJSON.Duration, Valid: true},
|
||||
Rating: sql.NullString{String: movieJSON.Rating, Valid: true},
|
||||
Director: sql.NullString{String: movieJSON.Director, Valid: true},
|
||||
Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true},
|
||||
URL: sql.NullString{String: movieJSON.URL, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.CreatedAt)},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.UpdatedAt)},
|
||||
FrontImage: frontimageData,
|
||||
BackImage: backimageData,
|
||||
Checksum: checksum,
|
||||
Name: sql.NullString{String: movieJSON.Name, Valid: true},
|
||||
Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true},
|
||||
Date: models.SQLiteDate{String: movieJSON.Date, Valid: true},
|
||||
Duration: sql.NullString{String: movieJSON.Duration, Valid: true},
|
||||
Rating: sql.NullString{String: movieJSON.Rating, Valid: true},
|
||||
Director: sql.NullString{String: movieJSON.Director, Valid: true},
|
||||
Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true},
|
||||
URL: sql.NullString{String: movieJSON.URL, Valid: true},
|
||||
CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.CreatedAt)},
|
||||
UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.UpdatedAt)},
|
||||
}
|
||||
|
||||
_, err = qb.Create(newMovie, tx)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ type Performer struct {
|
|||
Image []byte `db:"image" json:"image"`
|
||||
Checksum string `db:"checksum" json:"checksum"`
|
||||
Name sql.NullString `db:"name" json:"name"`
|
||||
Gender sql.NullString `db:"gender" json:"gender"`
|
||||
URL sql.NullString `db:"url" json:"url"`
|
||||
Twitter sql.NullString `db:"twitter" json:"twitter"`
|
||||
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) {
|
||||
ensureTx(tx)
|
||||
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,
|
||||
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,
|
||||
:aliases, :favorite, :created_at, :updated_at)
|
||||
`,
|
||||
|
|
@ -153,6 +153,11 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin
|
|||
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+".country", performerFilter.Country, &query)
|
||||
handleStringCriterion(tableName+".eye_color", performerFilter.EyeColor, &query)
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
const [url, setUrl] = useState<string>();
|
||||
const [twitter, setTwitter] = useState<string>();
|
||||
const [instagram, setInstagram] = useState<string>();
|
||||
const [gender, setGender] = useState<string | undefined>(undefined);
|
||||
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
@ -92,6 +93,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
setUrl(state.url ?? undefined);
|
||||
setTwitter(state.twitter ?? undefined);
|
||||
setInstagram(state.instagram ?? undefined);
|
||||
setGender(StashService.genderToString((state as GQL.PerformerDataFragment).gender ?? undefined));
|
||||
}
|
||||
|
||||
function updatePerformerEditStateFromScraper(
|
||||
|
|
@ -153,7 +155,8 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
url,
|
||||
twitter,
|
||||
instagram,
|
||||
image
|
||||
image,
|
||||
gender: StashService.stringToGender(gender)
|
||||
};
|
||||
|
||||
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 (
|
||||
<>
|
||||
{renderDeleteAlert()}
|
||||
|
|
@ -406,6 +419,7 @@ export const PerformerDetailsPanel: React.FC<IPerformerDetails> = ({
|
|||
<tbody>
|
||||
{maybeRenderName()}
|
||||
{maybeRenderAliases()}
|
||||
{renderGender()}
|
||||
{TableUtils.renderInputGroup({
|
||||
title: "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
|
||||
private constructor() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ export type CriterionType =
|
|||
| "career_length"
|
||||
| "tattoos"
|
||||
| "piercings"
|
||||
| "aliases";
|
||||
| "aliases"
|
||||
| "gender";
|
||||
|
||||
type Option = string | number | IOptionType;
|
||||
export type CriterionValue = string | number | ILabeledId[];
|
||||
|
|
@ -87,6 +88,8 @@ export abstract class Criterion {
|
|||
return "Piercings";
|
||||
case "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 { StudiosCriterion } from "./studios";
|
||||
import { TagsCriterion } from "./tags";
|
||||
import { GenderCriterion } from "./gender";
|
||||
import { MoviesCriterion } from "./movies";
|
||||
|
||||
export function makeCriteria(type: CriterionType = "none") {
|
||||
|
|
@ -60,6 +61,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||
];
|
||||
return ret;
|
||||
}
|
||||
case "gender":
|
||||
return new GenderCriterion();
|
||||
case "ethnicity":
|
||||
case "country":
|
||||
case "eye_color":
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ import {
|
|||
} from "./criteria/tags";
|
||||
import { makeCriteria } from "./criteria/utils";
|
||||
import { DisplayMode, FilterMode } from "./types";
|
||||
import { GenderCriterionOption, GenderCriterion } from "./criteria/gender";
|
||||
import { StashService } from "src/core/StashService";
|
||||
import { MoviesCriterionOption, MoviesCriterion } from "./criteria/movies";
|
||||
|
||||
interface IQueryParameters {
|
||||
|
|
@ -141,7 +143,8 @@ export class ListFilterModel {
|
|||
|
||||
this.criterionOptions = [
|
||||
new NoneCriterionOption(),
|
||||
new FavoriteCriterionOption()
|
||||
new FavoriteCriterionOption(),
|
||||
new GenderCriterionOption(),
|
||||
];
|
||||
|
||||
this.criterionOptions = this.criterionOptions.concat(
|
||||
|
|
@ -502,6 +505,11 @@ export class ListFilterModel {
|
|||
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
||||
break;
|
||||
}
|
||||
case "gender": {
|
||||
const gCrit = criterion as GenderCriterion;
|
||||
result.gender = { value: StashService.stringToGender(gCrit.value), modifier: gCrit.modifier };
|
||||
break;
|
||||
}
|
||||
// no default
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
|||
const [url, setUrl] = useState<string | undefined>(undefined);
|
||||
const [twitter, setTwitter] = useState<string | undefined>(undefined);
|
||||
const [instagram, setInstagram] = useState<string | undefined>(undefined);
|
||||
const [gender, setGender] = useState<string | undefined>(undefined);
|
||||
|
||||
// Network state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
|
@ -80,6 +81,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
|||
setUrl(state.url);
|
||||
setTwitter(state.twitter);
|
||||
setInstagram(state.instagram);
|
||||
setGender(StashService.genderToString((state as GQL.PerformerDataFragment).gender));
|
||||
}
|
||||
|
||||
function updatePerformerEditStateFromScraper(state: Partial<GQL.ScrapedPerformerDataFragment | GQL.ScrapeFreeonesScrapeFreeones>) {
|
||||
|
|
@ -147,6 +149,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
|||
twitter,
|
||||
instagram,
|
||||
image,
|
||||
gender: StashService.stringToGender(gender)
|
||||
};
|
||||
|
||||
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 instagramPrefix = "https://www.instagram.com/";
|
||||
|
||||
|
|
@ -385,6 +398,7 @@ export const PerformerDetailsPanel: FunctionComponent<IPerformerDetailsProps> =
|
|||
<tbody>
|
||||
{maybeRenderName()}
|
||||
{maybeRenderAliases()}
|
||||
{renderGender()}
|
||||
{TableUtils.renderInputGroup(
|
||||
{title: "Birthdate (YYYY-MM-DD)", value: birthdate, isEditing: !!props.isEditing, onChange: setBirthdate})}
|
||||
{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 {
|
||||
if (_.isPlainObject(value)) {
|
||||
return _.mapValues(value, StashService.nullToUndefined);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export type CriterionType =
|
|||
"career_length" |
|
||||
"tattoos" |
|
||||
"piercings" |
|
||||
"aliases";
|
||||
"aliases" |
|
||||
"gender";
|
||||
|
||||
export abstract class Criterion<Option = any, Value = any> {
|
||||
public static getLabel(type: CriterionType = "none"): string {
|
||||
|
|
@ -58,6 +59,7 @@ export abstract class Criterion<Option = any, Value = any> {
|
|||
case "tattoos": return "Tattoos";
|
||||
case "piercings": return "Piercings";
|
||||
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 { MoviesCriterion } from "./movies";
|
||||
import { TagsCriterion } from "./tags";
|
||||
import { GenderCriterion } from "./gender";
|
||||
|
||||
export function makeCriteria(type: CriterionType = "none") {
|
||||
switch (type) {
|
||||
|
|
@ -40,6 +41,7 @@ export function makeCriteria(type: CriterionType = "none") {
|
|||
Criterion.getModifierOption(CriterionModifier.LessThan)
|
||||
];
|
||||
return ret;
|
||||
case "gender": return new GenderCriterion();
|
||||
case "ethnicity":
|
||||
case "country":
|
||||
case "eye_color":
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import {
|
|||
DisplayMode,
|
||||
FilterMode,
|
||||
} from "./types";
|
||||
import { GenderCriterionOption, GenderCriterion } from "./criteria/gender";
|
||||
import { StashService } from "../../core/StashService";
|
||||
|
||||
interface IQueryParameters {
|
||||
sortby?: string;
|
||||
|
|
@ -100,7 +102,8 @@ export class ListFilterModel {
|
|||
|
||||
this.criterionOptions = [
|
||||
new NoneCriterionOption(),
|
||||
new FavoriteCriterionOption()
|
||||
new FavoriteCriterionOption(),
|
||||
new GenderCriterionOption(),
|
||||
];
|
||||
|
||||
this.criterionOptions = this.criterionOptions.concat(numberCriteria.concat(stringCriteria).map((c) => {
|
||||
|
|
@ -419,6 +422,10 @@ export class ListFilterModel {
|
|||
const aCrit = criterion as StringCriterion;
|
||||
result.aliases = { value: aCrit.value, modifier: aCrit.modifier };
|
||||
break;
|
||||
case "gender":
|
||||
const gCrit = criterion as GenderCriterion;
|
||||
result.gender = { value: StashService.stringToGender(gCrit.value), modifier: gCrit.modifier };
|
||||
break;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
|
|
|||
Loading…
Reference in a new issue