Show studio name if studio image not set on detail pages (#6716)

* Add StudioLogo component

If no studio image is set, shows the studio icon with the studio name.

* Add option to always show studio text
* Implement studio as text option
* Add studio logo to image
* Clarify existing show studio as text option
This commit is contained in:
WithoutPants 2026-03-23 17:13:34 +11:00 committed by GitHub
parent b4c7ad4b81
commit 87eabf0871
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 80 additions and 47 deletions

View file

@ -1,11 +1,6 @@
import { Button, Tab, Nav, Dropdown } from "react-bootstrap";
import React, { useEffect, useMemo, useState } from "react";
import {
useHistory,
Link,
RouteComponentProps,
Redirect,
} from "react-router-dom";
import { useHistory, RouteComponentProps, Redirect } from "react-router-dom";
import { FormattedMessage, useIntl } from "react-intl";
import { Helmet } from "react-helmet";
import * as GQL from "src/core/generated-graphql";
@ -50,6 +45,7 @@ import { useConfigurationContext } from "src/hooks/Config";
import { TruncatedText } from "src/components/Shared/TruncatedText";
import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
import { StudioLogo } from "src/components/Shared/StudioLogo";
interface IProps {
gallery: GQL.GalleryDataFragment;
@ -66,6 +62,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
const Toast = useToast();
const intl = useIntl();
const { configuration } = useConfigurationContext();
const { showStudioText } = configuration?.ui ?? {};
const showLightbox = useGalleryLightbox(gallery.id, gallery.chapters);
const [collapsed, setCollapsed] = useState(false);
@ -415,17 +412,7 @@ export const GalleryPage: React.FC<IProps> = ({ gallery, add }) => {
<div className={`gallery-tabs ${collapsed ? "collapsed" : ""}`}>
<div>
<div className="gallery-header-container">
{gallery.studio && (
<h1 className="text-center gallery-studio-image">
<Link to={`/studios/${gallery.studio.id}`}>
<img
src={gallery.studio.image_path ?? ""}
alt={`${gallery.studio.name} logo`}
className="studio-logo"
/>
</Link>
</h1>
)}
<StudioLogo studio={gallery.studio} showText={showStudioText} />
<h3
className={cx("gallery-header", { "no-studio": !gallery.studio })}
>

View file

@ -17,7 +17,7 @@
order: 1;
}
.gallery-studio-image {
.studio-logo {
flex: 0 0 25%;
order: 2;
}

View file

@ -1,7 +1,7 @@
import { Tab, Nav, Dropdown } from "react-bootstrap";
import React, { useEffect, useMemo, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
import { useHistory, RouteComponentProps } from "react-router-dom";
import { Helmet } from "react-helmet";
import {
useFindImage,
@ -37,6 +37,7 @@ import { TruncatedText } from "src/components/Shared/TruncatedText";
import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
import { GenerateDialog } from "src/components/Dialogs/GenerateDialog";
import { StudioLogo } from "src/components/Shared/StudioLogo";
interface IProps {
image: GQL.ImageDataFragment;
@ -51,6 +52,7 @@ const ImagePage: React.FC<IProps> = ({ image }) => {
const Toast = useToast();
const intl = useIntl();
const { configuration } = useConfigurationContext();
const { showStudioText } = configuration?.ui ?? {};
const [incrementO] = useImageIncrementO(image.id);
const [decrementO] = useImageDecrementO(image.id);
@ -326,17 +328,7 @@ const ImagePage: React.FC<IProps> = ({ image }) => {
<div className="image-tabs order-xl-first order-last">
<div>
<div className="image-header-container">
{image.studio && (
<h1 className="text-center image-studio-image">
<Link to={`/studios/${image.studio.id}`}>
<img
src={image.studio.image_path ?? ""}
alt={`${image.studio.name} logo`}
className="studio-logo"
/>
</Link>
</h1>
)}
<StudioLogo studio={image.studio} showText={showStudioText} />
<h3 className={cx("image-header", { "no-studio": !image.studio })}>
<TruncatedText lineCount={2} text={title} />
</h3>

View file

@ -9,7 +9,7 @@
order: 1;
}
.image-studio-image {
.studio-logo {
flex: 0 0 25%;
order: 2;
}

View file

@ -7,7 +7,7 @@ import React, {
useLayoutEffect,
} from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useHistory, Link, RouteComponentProps } from "react-router-dom";
import { useHistory, RouteComponentProps } from "react-router-dom";
import { Helmet } from "react-helmet";
import * as GQL from "src/core/generated-graphql";
import {
@ -56,6 +56,7 @@ import { PatchComponent, PatchContainerComponent } from "src/patch";
import { SceneMergeModal } from "../SceneMergeDialog";
import { goBackOrReplace } from "src/utils/history";
import { FormattedDate } from "src/components/Shared/Date";
import { StudioLogo } from "src/components/Shared/StudioLogo";
const SubmitStashBoxDraft = lazyComponent(
() => import("src/components/Dialogs/SubmitDraft")
@ -190,6 +191,7 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
const [updateScene] = useSceneUpdate();
const [generateScreenshot] = useSceneGenerateScreenshot();
const { configuration } = useConfigurationContext();
const { showStudioText } = configuration?.ui ?? {};
const [showDraftModal, setShowDraftModal] = useState(false);
const boxes = configuration?.general?.stashBoxes ?? [];
@ -674,17 +676,7 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
>
<div>
<div className="scene-header-container">
{scene.studio && (
<h1 className="text-center scene-studio-image">
<Link to={`/studios/${scene.studio.id}`}>
<img
src={scene.studio.image_path ?? ""}
alt={`${scene.studio.name} logo`}
className="studio-logo"
/>
</Link>
</h1>
)}
<StudioLogo studio={scene.studio} showText={showStudioText} />
<h3 className={cx("scene-header", { "no-studio": !scene.studio })}>
<TruncatedText lineCount={2} text={title} />
</h3>

View file

@ -74,7 +74,7 @@
order: 1;
}
.scene-studio-image {
.studio-logo {
flex: 0 0 25%;
order: 2;
}

View file

@ -372,6 +372,7 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
<BooleanSetting
id="show-text-studios"
headingID="config.ui.scene_list.options.show_studio_as_text"
subHeadingID="config.ui.scene_list.options.show_studio_as_text_desc"
checked={iface.showStudioAsText ?? undefined}
onChange={(v) => saveInterface({ showStudioAsText: v })}
/>
@ -692,6 +693,13 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
checked={ui.compactExpandedDetails ?? undefined}
onChange={(v) => saveUI({ compactExpandedDetails: v })}
/>
<BooleanSetting
id="show_studio_text"
headingID="config.ui.detail.show_studio_text.heading"
subHeadingID="config.ui.detail.show_studio_text.description"
checked={ui.showStudioText ?? false}
onChange={(v) => saveUI({ showStudioText: v })}
/>
</SettingSection>
<SettingSection headingID="config.ui.editing.heading">

View file

@ -0,0 +1,35 @@
import { Link } from "react-router-dom";
import { Studio } from "src/core/generated-graphql";
import { Icon } from "./Icon";
import { faVideo } from "@fortawesome/free-solid-svg-icons";
export const StudioLogo: React.FC<{
studio: Pick<Studio, "id" | "image_path" | "name"> | undefined | null;
showText?: boolean;
}> = ({ studio, showText = false }) => {
if (!studio) return null;
const hasLogo =
!showText &&
studio.image_path &&
!studio.image_path.endsWith("default=true");
return (
<h1 className="text-center studio-logo">
<Link to={`/studios/${studio.id}`}>
{hasLogo ? (
<img
src={studio.image_path ?? ""}
alt={`${studio.name} logo`}
className="studio-logo"
/>
) : (
<span className="studio-name">
<Icon icon={faVideo} />
{studio.name}
</span>
)}
</Link>
</h1>
);
};

View file

@ -1256,3 +1256,15 @@ input[type="range"].double-range-slider-max {
.text-input + .input-group-append .btn.minimal {
background-color: $textfield-bg;
}
.studio-logo a:hover {
color: inherit;
}
.studio-logo .studio-name {
color: $text-color;
font-size: 1.5rem;
font-weight: 500;
margin-top: 0.5rem;
text-align: center;
}

View file

@ -49,6 +49,8 @@ export interface IUIConfig {
showLinksOnPerformerCard?: boolean;
showTagCardOnHover?: boolean;
showStudioText?: boolean;
previewVolume?: number;
abbreviateCounters?: boolean;

View file

@ -19,9 +19,9 @@ The Scene Wall and Marker pages display scene preview videos (mp4) by default. T
> **⚠️ Note:** scene/marker preview videos must be generated to see them in the applicable wall page if Video preview type is selected. Likewise, if Animated image is selected, then Image Previews must be generated.
## Show Studios as text
## Show studio overlay as text
By default, a scene's studio will be shown as an image overlay. Checking this option changes this to display studios as a text name instead.
By default, in the grid card view the studio will be shown as an image overlay of the studio logo. Checking this option changes this to display studios as a text name instead.
## Scene Player options

View file

@ -712,6 +712,10 @@
"show_all_details": {
"description": "When enabled, all content details will be shown by default and each detail item will fit under a single column.",
"heading": "Show all details"
},
"show_studio_text": {
"description": "Always display studio name as text on details views instead of an icon.",
"heading": "Show studio as text"
}
},
"editing": {
@ -817,7 +821,8 @@
"scene_list": {
"heading": "Grid View",
"options": {
"show_studio_as_text": "Display studio overlay as text"
"show_studio_as_text": "Display studio overlay as text",
"show_studio_as_text_desc": "By default, the studio logo is shown on cards in the grid. Enable this option to always show the studio name as text instead."
}
},
"scene_player": {