mirror of
https://github.com/stashapp/stash.git
synced 2026-05-09 05:05:29 +02:00
add default performer gender configuration option
- Introduced `defaultPerformerGender` input and output fields in GraphQL schema. - Updated configuration resolver to handle default performer gender. - Implemented logic to use default performer gender when creating new performers or scraping data. - Added UI component for setting default performer gender in settings panel. - Updated localization for the new default performer gender option.
This commit is contained in:
parent
103181a6d2
commit
487c461a75
9 changed files with 80 additions and 8 deletions
|
|
@ -420,6 +420,9 @@ input ConfigInterfaceInput {
|
|||
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
|
||||
disableCustomizations: Boolean
|
||||
|
||||
"Default gender to apply when creating a performer without explicit gender"
|
||||
defaultPerformerGender: GenderEnum
|
||||
|
||||
"Interface language"
|
||||
language: String
|
||||
|
||||
|
|
@ -497,6 +500,9 @@ type ConfigInterfaceResult {
|
|||
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
|
||||
disableCustomizations: Boolean
|
||||
|
||||
"Default gender to apply when creating a performer without explicit gender"
|
||||
defaultPerformerGender: GenderEnum
|
||||
|
||||
"Interface language"
|
||||
language: String
|
||||
|
||||
|
|
|
|||
|
|
@ -528,6 +528,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
|
|||
r.setConfigBool(config.CustomLocalesEnabled, input.CustomLocalesEnabled)
|
||||
|
||||
r.setConfigBool(config.DisableCustomizations, input.DisableCustomizations)
|
||||
r.setConfigString(config.DefaultPerformerGender, (*string)(input.DefaultPerformerGender))
|
||||
|
||||
if input.DisableDropdownCreate != nil {
|
||||
ddc := input.DisableDropdownCreate
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/stashapp/stash/internal/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/performer"
|
||||
"github.com/stashapp/stash/pkg/plugin/hook"
|
||||
|
|
@ -21,6 +22,14 @@ const (
|
|||
instagramURL = "https://instagram.com"
|
||||
)
|
||||
|
||||
func performerGenderOrDefault(gender *models.GenderEnum) *models.GenderEnum {
|
||||
if gender != nil {
|
||||
return gender
|
||||
}
|
||||
|
||||
return config.GetInstance().GetDefaultPerformerGender()
|
||||
}
|
||||
|
||||
// used to refetch performer after hooks run
|
||||
func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *models.Performer, err error) {
|
||||
if err := r.withTxn(ctx, func(ctx context.Context) error {
|
||||
|
|
@ -44,7 +53,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
|
|||
newPerformer.Name = strings.TrimSpace(input.Name)
|
||||
newPerformer.Disambiguation = translator.string(input.Disambiguation)
|
||||
newPerformer.Aliases = models.NewRelatedStrings(stringslice.UniqueExcludeFold(stringslice.TrimSpace(input.AliasList), newPerformer.Name))
|
||||
newPerformer.Gender = input.Gender
|
||||
newPerformer.Gender = performerGenderOrDefault(input.Gender)
|
||||
newPerformer.Ethnicity = translator.string(input.Ethnicity)
|
||||
newPerformer.Country = translator.string(input.Country)
|
||||
newPerformer.EyeColor = translator.string(input.EyeColor)
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
|||
customLocales := config.GetCustomLocales()
|
||||
customLocalesEnabled := config.GetCustomLocalesEnabled()
|
||||
disableCustomizations := config.GetDisableCustomizations()
|
||||
defaultPerformerGender := config.GetDefaultPerformerGender()
|
||||
language := config.GetLanguage()
|
||||
handyKey := config.GetHandyKey()
|
||||
scriptOffset := config.GetFunscriptOffset()
|
||||
|
|
@ -190,6 +191,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
|
|||
CustomLocales: &customLocales,
|
||||
CustomLocalesEnabled: &customLocalesEnabled,
|
||||
DisableCustomizations: &disableCustomizations,
|
||||
DefaultPerformerGender: defaultPerformerGender,
|
||||
Language: &language,
|
||||
|
||||
ImageLightbox: &imageLightboxOptions,
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ const (
|
|||
autostartVideoOnPlaySelectedDefault = true
|
||||
ContinuePlaylistDefault = "continue_playlist_default"
|
||||
ShowStudioAsText = "show_studio_as_text"
|
||||
DefaultPerformerGender = "default_performer_gender"
|
||||
CSSEnabled = "cssenabled"
|
||||
JavascriptEnabled = "javascriptenabled"
|
||||
CustomLocalesEnabled = "customlocalesenabled"
|
||||
|
|
@ -1311,6 +1312,15 @@ func (i *Config) GetShowStudioAsText() bool {
|
|||
return i.getBool(ShowStudioAsText)
|
||||
}
|
||||
|
||||
func (i *Config) GetDefaultPerformerGender() *models.GenderEnum {
|
||||
g := models.GenderEnum(i.getString(DefaultPerformerGender))
|
||||
if !g.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &g
|
||||
}
|
||||
|
||||
func (i *Config) getSlideshowDelay() int {
|
||||
// assume have lock
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
customLocales
|
||||
customLocalesEnabled
|
||||
disableCustomizations
|
||||
defaultPerformerGender
|
||||
language
|
||||
imageLightbox {
|
||||
slideshowDelay
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
const [scrapedPerformer, setScrapedPerformer] =
|
||||
useState<GQL.ScrapedPerformer>();
|
||||
const { configuration: stashConfig } = useConfigurationContext();
|
||||
const defaultPerformerGender =
|
||||
stashConfig?.interface.defaultPerformerGender ?? null;
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
|
|
@ -134,7 +136,7 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
name: performer.name ?? "",
|
||||
disambiguation: performer.disambiguation ?? "",
|
||||
alias_list: performer.alias_list ?? [],
|
||||
gender: performer.gender ?? null,
|
||||
gender: performer.gender ?? (isNew ? defaultPerformerGender : null),
|
||||
birthdate: performer.birthdate ?? "",
|
||||
death_date: performer.death_date ?? "",
|
||||
country: performer.country ?? "",
|
||||
|
|
@ -424,16 +426,23 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
|
||||
const result = await queryScrapePerformer(selectedScraper.id, ret);
|
||||
if (!result?.data?.scrapeSinglePerformer?.length) return;
|
||||
const withDefaultGender = (
|
||||
scrapedPerformerData: GQL.ScrapedPerformerDataFragment
|
||||
) =>
|
||||
!scrapedPerformerData.gender && defaultPerformerGender
|
||||
? { ...scrapedPerformerData, gender: defaultPerformerGender }
|
||||
: scrapedPerformerData;
|
||||
const scrapedResult = withDefaultGender(
|
||||
result.data.scrapeSinglePerformer[0]
|
||||
);
|
||||
|
||||
// assume one result
|
||||
// if this is a new performer, just dump the data
|
||||
if (isNew) {
|
||||
updatePerformerEditStateFromScraper(
|
||||
result.data.scrapeSinglePerformer[0]
|
||||
);
|
||||
updatePerformerEditStateFromScraper(scrapedResult);
|
||||
setScraper(undefined);
|
||||
} else {
|
||||
setScrapedPerformer(result.data.scrapeSinglePerformer[0]);
|
||||
setScrapedPerformer(scrapedResult);
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
|
|
@ -451,11 +460,19 @@ export const PerformerEditPanel: React.FC<IPerformerDetails> = ({
|
|||
return;
|
||||
}
|
||||
|
||||
const scrapedResult =
|
||||
!result.data.scrapePerformerURL.gender && defaultPerformerGender
|
||||
? {
|
||||
...result.data.scrapePerformerURL,
|
||||
gender: defaultPerformerGender,
|
||||
}
|
||||
: result.data.scrapePerformerURL;
|
||||
|
||||
// if this is a new performer, just dump the data
|
||||
if (isNew) {
|
||||
updatePerformerEditStateFromScraper(result.data.scrapePerformerURL);
|
||||
updatePerformerEditStateFromScraper(scrapedResult);
|
||||
} else {
|
||||
setScrapedPerformer(result.data.scrapePerformerURL);
|
||||
setScrapedPerformer(scrapedResult);
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import {
|
|||
defaultImageWallDirection,
|
||||
defaultImageWallMargin,
|
||||
} from "src/utils/imageWall";
|
||||
import { genderList } from "src/utils/gender";
|
||||
import { defaultMaxOptionsShown, defaultPreviewVolume } from "src/core/config";
|
||||
import { PatchComponent } from "src/patch";
|
||||
|
||||
|
|
@ -525,6 +526,27 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
|
|||
checked={ui.showLinksOnPerformerCard ?? undefined}
|
||||
onChange={(v) => saveUI({ showLinksOnPerformerCard: v })}
|
||||
/>
|
||||
<SelectSetting
|
||||
id="default-performer-gender"
|
||||
headingID="config.ui.performer_list.options.default_gender.heading"
|
||||
subHeadingID="config.ui.performer_list.options.default_gender.description"
|
||||
value={iface.defaultPerformerGender ?? ""}
|
||||
onChange={(v) =>
|
||||
saveInterface({
|
||||
defaultPerformerGender:
|
||||
v === "" ? null : (v as GQL.GenderEnum),
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="">{intl.formatMessage({ id: "none" })}</option>
|
||||
{genderList.map((gender) => (
|
||||
<option key={gender} value={gender}>
|
||||
{intl.formatMessage({
|
||||
id: `gender_types.${gender.toLowerCase()}`,
|
||||
})}
|
||||
</option>
|
||||
))}
|
||||
</SelectSetting>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection headingID="config.ui.image_wall.heading">
|
||||
|
|
|
|||
|
|
@ -894,6 +894,10 @@
|
|||
"performer_list": {
|
||||
"heading": "Performer List",
|
||||
"options": {
|
||||
"default_gender": {
|
||||
"description": "Sets the default gender for new performers when the scraper does not provide a gender.",
|
||||
"heading": "Default performer gender"
|
||||
},
|
||||
"show_links_on_grid_card": {
|
||||
"heading": "Display links on performer grid cards"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue