Add external links display option for performer thumbnails (#6153)

* Add external links display option for performer thumbnails

- Introduced a new setting to show links on performer thumbnails.
- Updated PerformerCard to conditionally render social media links (Twitter, Instagram) and other external links.
- Enhanced ExternalLinksButton to open single links directly if specified.
- Updated configuration and localization files to support the new feature.
This commit is contained in:
theqwertyqwert
2025-11-10 02:54:44 +02:00
committed by GitHub
parent d5b1046267
commit 34becdf436
6 changed files with 109 additions and 9 deletions

View File

@@ -17,12 +17,15 @@ import {
} from "src/models/list-filter/criteria/criterion"; } from "src/models/list-filter/criteria/criterion";
import { PopoverCountButton } from "../Shared/PopoverCountButton"; import { PopoverCountButton } from "../Shared/PopoverCountButton";
import GenderIcon from "./GenderIcon"; import GenderIcon from "./GenderIcon";
import { faTag } from "@fortawesome/free-solid-svg-icons"; import { faLink, faTag } from "@fortawesome/free-solid-svg-icons";
import { faInstagram, faTwitter } from "@fortawesome/free-brands-svg-icons";
import { RatingBanner } from "../Shared/RatingBanner"; import { RatingBanner } from "../Shared/RatingBanner";
import { usePerformerUpdate } from "src/core/StashService"; import { usePerformerUpdate } from "src/core/StashService";
import { ILabeledId } from "src/models/list-filter/types"; import { ILabeledId } from "src/models/list-filter/types";
import { FavoriteIcon } from "../Shared/FavoriteIcon"; import { FavoriteIcon } from "../Shared/FavoriteIcon";
import { PatchComponent } from "src/patch"; import { PatchComponent } from "src/patch";
import { ExternalLinksButton } from "../Shared/ExternalLinksButton";
import { ConfigurationContext } from "src/hooks/Config";
export interface IPerformerCardExtraCriteria { export interface IPerformerCardExtraCriteria {
scenes?: ModifierCriterion<CriterionValue>[]; scenes?: ModifierCriterion<CriterionValue>[];
@@ -176,6 +179,8 @@ const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent( const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
"PerformerCard.Overlays", "PerformerCard.Overlays",
({ performer }) => { ({ performer }) => {
const { configuration } = React.useContext(ConfigurationContext);
const uiConfig = configuration?.ui;
const [updatePerformer] = usePerformerUpdate(); const [updatePerformer] = usePerformerUpdate();
function onToggleFavorite(v: boolean) { function onToggleFavorite(v: boolean) {
@@ -215,6 +220,63 @@ const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
} }
} }
function maybeRenderLinks() {
if (!uiConfig?.showLinksOnPerformerCard) {
return;
}
if (performer.urls && performer.urls.length > 0) {
const twitter = performer.urls.filter((u) =>
u.match(/https?:\/\/(?:www\.)?(?:twitter|x).com\//)
);
const instagram = performer.urls.filter((u) =>
u.match(/https?:\/\/(?:www\.)?instagram.com\//)
);
const others = performer.urls.filter(
(u) => !twitter.includes(u) && !instagram.includes(u)
);
return (
<div
className="performer-card__links"
style={{
position: "absolute",
left: "0",
bottom: "0",
display: "flex",
gap: "0.5rem",
flexDirection: "column-reverse",
}}
>
{twitter.length > 0 && (
<ExternalLinksButton
className="performer-card__link twitter"
urls={twitter}
icon={faTwitter}
openIfSingle={true}
></ExternalLinksButton>
)}
{instagram.length > 0 && (
<ExternalLinksButton
className="performer-card__link instagram"
urls={instagram}
icon={faInstagram}
openIfSingle={true}
></ExternalLinksButton>
)}
{others.length > 0 && (
<ExternalLinksButton
className="performer-card__link"
icon={faLink}
urls={others}
openIfSingle={true}
/>
)}
</div>
);
}
}
return ( return (
<> <>
<FavoriteIcon <FavoriteIcon
@@ -224,6 +286,7 @@ const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
className="hide-not-favorite" className="hide-not-favorite"
/> />
{maybeRenderRatingBanner()} {maybeRenderRatingBanner()}
{maybeRenderLinks()}
{maybeRenderFlag()} {maybeRenderFlag()}
</> </>
); );

View File

@@ -107,6 +107,10 @@
.thumbnail-section { .thumbnail-section {
position: relative; position: relative;
.instagram {
color: pink;
}
} }
&-image { &-image {

View File

@@ -470,6 +470,7 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
onChange={(v) => saveUI({ showChildTagContent: v })} onChange={(v) => saveUI({ showChildTagContent: v })}
/> />
</SettingSection> </SettingSection>
<SettingSection headingID="config.ui.studio_panel.heading"> <SettingSection headingID="config.ui.studio_panel.heading">
<BooleanSetting <BooleanSetting
id="show-child-studio-content" id="show-child-studio-content"
@@ -480,6 +481,15 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
/> />
</SettingSection> </SettingSection>
<SettingSection headingID="config.ui.performer_list.heading">
<BooleanSetting
id="show-links-on-grid-card"
headingID="config.ui.performer_list.options.show_links_on_grid_card.heading"
checked={ui.showLinksOnPerformerCard ?? undefined}
onChange={(v) => saveUI({ showLinksOnPerformerCard: v })}
/>
</SettingSection>
<SettingSection headingID="config.ui.image_wall.heading"> <SettingSection headingID="config.ui.image_wall.heading">
<NumberSetting <NumberSetting
headingID="config.ui.image_wall.margin" headingID="config.ui.image_wall.margin"

View File

@@ -12,9 +12,10 @@ export const ExternalLinksButton: React.FC<{
icon?: IconDefinition; icon?: IconDefinition;
urls: string[]; urls: string[];
className?: string; className?: string;
openIfSingle?: boolean;
}> = PatchComponent( }> = PatchComponent(
"ExternalLinksButton", "ExternalLinksButton",
({ urls, icon = faLink, className = "" }) => { ({ urls, icon = faLink, className = "", openIfSingle = false }) => {
if (!urls.length) { if (!urls.length) {
return null; return null;
} }
@@ -36,14 +37,27 @@ export const ExternalLinksButton: React.FC<{
document.body document.body
); );
return ( if (openIfSingle && urls.length === 1) {
<Dropdown className="external-links-button"> return (
<Dropdown.Toggle as={Button} className={`minimal link ${className}`}> <ExternalLink
className={`external-links-button-link minimal btn link ${className}`}
href={TextUtils.sanitiseURL(urls[0])}
target="_blank"
rel="noopener noreferrer"
>
<Icon icon={icon} /> <Icon icon={icon} />
</Dropdown.Toggle> </ExternalLink>
<Menu /> );
</Dropdown> } else {
); return (
<Dropdown className="external-links-button">
<Dropdown.Toggle as={Button} className={`minimal link ${className}`}>
<Icon icon={icon} />
</Dropdown.Toggle>
<Menu />
</Dropdown>
);
}
} }
); );

View File

@@ -45,6 +45,7 @@ export interface IUIConfig {
showChildTagContent?: boolean; showChildTagContent?: boolean;
showChildStudioContent?: boolean; showChildStudioContent?: boolean;
showLinksOnPerformerCard?: boolean;
showTagCardOnHover?: boolean; showTagCardOnHover?: boolean;
abbreviateCounters?: boolean; abbreviateCounters?: boolean;

View File

@@ -801,6 +801,14 @@
} }
} }
}, },
"performer_list": {
"heading": "Performer list",
"options": {
"show_links_on_grid_card": {
"heading": "Display links on performer grid cards"
}
}
},
"tag_panel": { "tag_panel": {
"heading": "Tag view", "heading": "Tag view",
"options": { "options": {