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";
import { PopoverCountButton } from "../Shared/PopoverCountButton";
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 { usePerformerUpdate } from "src/core/StashService";
import { ILabeledId } from "src/models/list-filter/types";
import { FavoriteIcon } from "../Shared/FavoriteIcon";
import { PatchComponent } from "src/patch";
import { ExternalLinksButton } from "../Shared/ExternalLinksButton";
import { ConfigurationContext } from "src/hooks/Config";
export interface IPerformerCardExtraCriteria {
scenes?: ModifierCriterion<CriterionValue>[];
@@ -176,6 +179,8 @@ const PerformerCardPopovers: React.FC<IPerformerCardProps> = PatchComponent(
const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
"PerformerCard.Overlays",
({ performer }) => {
const { configuration } = React.useContext(ConfigurationContext);
const uiConfig = configuration?.ui;
const [updatePerformer] = usePerformerUpdate();
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 (
<>
<FavoriteIcon
@@ -224,6 +286,7 @@ const PerformerCardOverlays: React.FC<IPerformerCardProps> = PatchComponent(
className="hide-not-favorite"
/>
{maybeRenderRatingBanner()}
{maybeRenderLinks()}
{maybeRenderFlag()}
</>
);

View File

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

View File

@@ -470,6 +470,7 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
onChange={(v) => saveUI({ showChildTagContent: v })}
/>
</SettingSection>
<SettingSection headingID="config.ui.studio_panel.heading">
<BooleanSetting
id="show-child-studio-content"
@@ -480,6 +481,15 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
/>
</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">
<NumberSetting
headingID="config.ui.image_wall.margin"

View File

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