Change show male performers option into list of gender checkboxes (#6321)

This commit is contained in:
WithoutPants 2025-11-28 13:51:20 +11:00 committed by GitHub
parent e052a431d1
commit d1ee64d36f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 53 additions and 25 deletions

View file

@ -1,4 +1,4 @@
import { ScraperSourceInput } from "src/core/generated-graphql"; import { GenderEnum, ScraperSourceInput } from "src/core/generated-graphql";
export const STASH_BOX_PREFIX = "stashbox:"; export const STASH_BOX_PREFIX = "stashbox:";
export const SCRAPER_PREFIX = "scraper:"; export const SCRAPER_PREFIX = "scraper:";
@ -27,7 +27,6 @@ export const DEFAULT_EXCLUDED_STUDIO_FIELDS = ["name"];
export const initialConfig: ITaggerConfig = { export const initialConfig: ITaggerConfig = {
blacklist: DEFAULT_BLACKLIST, blacklist: DEFAULT_BLACKLIST,
showMales: true,
mode: "auto", mode: "auto",
setCoverImage: true, setCoverImage: true,
setTags: true, setTags: true,
@ -43,7 +42,7 @@ export type ParseMode = "auto" | "filename" | "dir" | "path" | "metadata";
export type TagOperation = "merge" | "overwrite"; export type TagOperation = "merge" | "overwrite";
export interface ITaggerConfig { export interface ITaggerConfig {
blacklist: string[]; blacklist: string[];
showMales: boolean; performerGenders?: GenderEnum[];
mode: ParseMode; mode: ParseMode;
setCoverImage: boolean; setCoverImage: boolean;
setTags: boolean; setTags: boolean;

View file

@ -13,6 +13,8 @@ import { FormattedMessage, useIntl } from "react-intl";
import { Icon } from "src/components/Shared/Icon"; import { Icon } from "src/components/Shared/Icon";
import { ParseMode, TagOperation } from "../constants"; import { ParseMode, TagOperation } from "../constants";
import { TaggerStateContext } from "../context"; import { TaggerStateContext } from "../context";
import { GenderEnum } from "src/core/generated-graphql";
import { genderList } from "src/utils/gender";
const Blacklist: React.FC<{ const Blacklist: React.FC<{
list: string[]; list: string[];
@ -118,6 +120,26 @@ const Config: React.FC<IConfigProps> = ({ show }) => {
const { config, setConfig } = useContext(TaggerStateContext); const { config, setConfig } = useContext(TaggerStateContext);
const intl = useIntl(); const intl = useIntl();
function renderGenderCheckbox(gender: GenderEnum) {
const performerGenders = config.performerGenders || genderList.slice();
return (
<Form.Check
key={gender}
label={<FormattedMessage id={`gender_types.${gender}`} />}
checked={performerGenders.includes(gender)}
onChange={(e) => {
const isChecked = e.currentTarget.checked;
setConfig({
...config,
performerGenders: isChecked
? [...performerGenders, gender]
: performerGenders.filter((g) => g !== gender),
});
}}
/>
);
}
return ( return (
<Collapse in={show}> <Collapse in={show}>
<Card> <Card>
@ -127,18 +149,16 @@ const Config: React.FC<IConfigProps> = ({ show }) => {
</h4> </h4>
<hr className="w-100" /> <hr className="w-100" />
<Form className="col-md-6"> <Form className="col-md-6">
<Form.Group controlId="tag-males" className="align-items-center"> <Form.Group
<Form.Check controlId="performer-genders"
label={ className="align-items-center"
<FormattedMessage id="component_tagger.config.show_male_label" /> >
} <Form.Label>
checked={config.showMales} <FormattedMessage id="component_tagger.config.performer_genders.heading" />
onChange={(e) => </Form.Label>
setConfig({ ...config, showMales: e.currentTarget.checked }) {genderList.map(renderGenderCheckbox)}
}
/>
<Form.Text> <Form.Text>
<FormattedMessage id="component_tagger.config.show_male_desc" /> <FormattedMessage id="component_tagger.config.performer_genders.description" />
</Form.Text> </Form.Text>
</Form.Group> </Form.Group>
<Form.Group controlId="set-cover" className="align-items-center"> <Form.Group controlId="set-cover" className="align-items-center">

View file

@ -21,7 +21,7 @@ import { TagSelect } from "src/components/Shared/Select";
import { TruncatedText } from "src/components/Shared/TruncatedText"; import { TruncatedText } from "src/components/Shared/TruncatedText";
import { OperationButton } from "src/components/Shared/OperationButton"; import { OperationButton } from "src/components/Shared/OperationButton";
import * as FormUtils from "src/utils/form"; import * as FormUtils from "src/utils/form";
import { stringToGender } from "src/utils/gender"; import { genderList, stringToGender } from "src/utils/gender";
import { IScrapedScene, TaggerStateContext } from "../context"; import { IScrapedScene, TaggerStateContext } from "../context";
import { OptionalField } from "../IncludeButton"; import { OptionalField } from "../IncludeButton";
import { SceneTaggerModalsState } from "./sceneTaggerModals"; import { SceneTaggerModalsState } from "./sceneTaggerModals";
@ -237,17 +237,15 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
saveScene, saveScene,
} = React.useContext(TaggerStateContext); } = React.useContext(TaggerStateContext);
const performerGenders = config.performerGenders || genderList;
const performers = useMemo( const performers = useMemo(
() => () =>
scene.performers?.filter((p) => { scene.performers?.filter((p) => {
if (!config.showMales) { const gender = p.gender ? stringToGender(p.gender, true) : undefined;
return ( return !gender || performerGenders.includes(gender);
!p.gender || stringToGender(p.gender, true) !== GQL.GenderEnum.Male
);
}
return true;
}) ?? [], }) ?? [],
[config, scene] [scene, performerGenders]
); );
const { createPerformerModal, createStudioModal } = React.useContext( const { createPerformerModal, createStudioModal } = React.useContext(

View file

@ -190,6 +190,10 @@
}, },
"mark_organized_desc": "Immediately mark the scene as Organized after the Save button is clicked.", "mark_organized_desc": "Immediately mark the scene as Organized after the Save button is clicked.",
"mark_organized_label": "Mark as Organized on save", "mark_organized_label": "Mark as Organized on save",
"performer_genders": {
"heading": "Performer genders",
"description": "Performers with these genders will be shown when tagging scenes."
},
"query_mode_auto": "Auto", "query_mode_auto": "Auto",
"query_mode_auto_desc": "Uses metadata if present, or filename", "query_mode_auto_desc": "Uses metadata if present, or filename",
"query_mode_dir": "Dir", "query_mode_dir": "Dir",
@ -205,8 +209,6 @@
"set_cover_label": "Set scene cover image", "set_cover_label": "Set scene cover image",
"set_tag_desc": "Attach tags to scene, either by overwriting or merging with existing tags on scene.", "set_tag_desc": "Attach tags to scene, either by overwriting or merging with existing tags on scene.",
"set_tag_label": "Set tags", "set_tag_label": "Set tags",
"show_male_desc": "Toggle whether male performers will be available to tag.",
"show_male_label": "Show male performers",
"source": "Source" "source": "Source"
}, },
"noun_query": "Query", "noun_query": "Query",

View file

@ -1,14 +1,23 @@
import * as GQL from "../core/generated-graphql"; import * as GQL from "../core/generated-graphql";
export const stringGenderMap = new Map<string, GQL.GenderEnum>([ export const stringGenderMap = new Map<string, GQL.GenderEnum>([
["Male", GQL.GenderEnum.Male],
["Female", GQL.GenderEnum.Female], ["Female", GQL.GenderEnum.Female],
["Male", GQL.GenderEnum.Male],
["Transgender Male", GQL.GenderEnum.TransgenderMale], ["Transgender Male", GQL.GenderEnum.TransgenderMale],
["Transgender Female", GQL.GenderEnum.TransgenderFemale], ["Transgender Female", GQL.GenderEnum.TransgenderFemale],
["Intersex", GQL.GenderEnum.Intersex], ["Intersex", GQL.GenderEnum.Intersex],
["Non-Binary", GQL.GenderEnum.NonBinary], ["Non-Binary", GQL.GenderEnum.NonBinary],
]); ]);
export const genderList = [
GQL.GenderEnum.Female,
GQL.GenderEnum.Male,
GQL.GenderEnum.TransgenderFemale,
GQL.GenderEnum.TransgenderMale,
GQL.GenderEnum.Intersex,
GQL.GenderEnum.NonBinary,
];
export const genderToString = (value?: GQL.GenderEnum | string | null) => { export const genderToString = (value?: GQL.GenderEnum | string | null) => {
if (!value) { if (!value) {
return undefined; return undefined;