mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
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:
@@ -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()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -107,6 +107,10 @@
|
|||||||
|
|
||||||
.thumbnail-section {
|
.thumbnail-section {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.instagram {
|
||||||
|
color: pink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-image {
|
&-image {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,6 +37,18 @@ export const ExternalLinksButton: React.FC<{
|
|||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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} />
|
||||||
|
</ExternalLink>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Dropdown className="external-links-button">
|
<Dropdown className="external-links-button">
|
||||||
<Dropdown.Toggle as={Button} className={`minimal link ${className}`}>
|
<Dropdown.Toggle as={Button} className={`minimal link ${className}`}>
|
||||||
@@ -45,6 +58,7 @@ export const ExternalLinksButton: React.FC<{
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ExternalLinkButtons: React.FC<{ urls: string[] | undefined }> =
|
export const ExternalLinkButtons: React.FC<{ urls: string[] | undefined }> =
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user