mirror of
https://github.com/stashapp/stash.git
synced 2026-04-20 14:04:51 +02:00
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:
parent
b4c7ad4b81
commit
87eabf0871
12 changed files with 80 additions and 47 deletions
|
|
@ -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 })}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
order: 1;
|
||||
}
|
||||
|
||||
.gallery-studio-image {
|
||||
.studio-logo {
|
||||
flex: 0 0 25%;
|
||||
order: 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
order: 1;
|
||||
}
|
||||
|
||||
.image-studio-image {
|
||||
.studio-logo {
|
||||
flex: 0 0 25%;
|
||||
order: 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
order: 1;
|
||||
}
|
||||
|
||||
.scene-studio-image {
|
||||
.studio-logo {
|
||||
flex: 0 0 25%;
|
||||
order: 2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
35
ui/v2.5/src/components/Shared/StudioLogo.tsx
Normal file
35
ui/v2.5/src/components/Shared/StudioLogo.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ export interface IUIConfig {
|
|||
showLinksOnPerformerCard?: boolean;
|
||||
showTagCardOnHover?: boolean;
|
||||
|
||||
showStudioText?: boolean;
|
||||
|
||||
previewVolume?: number;
|
||||
|
||||
abbreviateCounters?: boolean;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue