mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 12:54:38 +03:00
Common studio overlay component (#4540)
* Move GridCard to own directory * Make common studio overlay component
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { HoverPopover } from "../Shared/HoverPopover";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
import { SceneLink, TagLink } from "../Shared/TagLink";
|
||||
@@ -10,11 +9,11 @@ import { TruncatedText } from "../Shared/TruncatedText";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { faBox, faPlayCircle, faTag } from "@fortawesome/free-solid-svg-icons";
|
||||
import { galleryTitle } from "src/core/galleries";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||
|
||||
interface IProps {
|
||||
gallery: GQL.SlimGalleryDataFragment;
|
||||
@@ -26,8 +25,6 @@ interface IProps {
|
||||
}
|
||||
|
||||
export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
const showStudioAsText = configuration?.interface.showStudioAsText ?? false;
|
||||
const [cardWidth, setCardWidth] = useState<number>();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -121,27 +118,6 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderSceneStudioOverlay() {
|
||||
if (!props.gallery.studio) return;
|
||||
|
||||
return (
|
||||
<div className="scene-studio-overlay">
|
||||
<Link to={`/studios/${props.gallery.studio.id}`}>
|
||||
{showStudioAsText ? (
|
||||
props.gallery.studio.name
|
||||
) : (
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
loading="lazy"
|
||||
alt={props.gallery.studio.name}
|
||||
src={props.gallery.studio.image_path ?? ""}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderOrganized() {
|
||||
if (props.gallery.organized) {
|
||||
return (
|
||||
@@ -202,7 +178,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
||||
<RatingBanner rating={props.gallery.rating100} />
|
||||
</>
|
||||
}
|
||||
overlays={maybeRenderSceneStudioOverlay()}
|
||||
overlays={<StudioOverlay studio={props.gallery.studio} />}
|
||||
details={
|
||||
<div className="gallery-card__details">
|
||||
<span className="gallery-card__date">{props.gallery.date}</span>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { EditGalleriesDialog } from "./EditGalleriesDialog";
|
||||
import { DeleteGalleriesDialog } from "./DeleteGalleriesDialog";
|
||||
import { ExportDialog } from "../Shared/ExportDialog";
|
||||
import { GalleryListTable } from "./GalleryListTable";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
const GalleryItemList = makeItemList({
|
||||
filterMode: GQL.FilterMode.Galleries,
|
||||
|
||||
@@ -7,7 +7,10 @@ import { GalleryLink, TagLink } from "src/components/Shared/TagLink";
|
||||
import { HoverPopover } from "src/components/Shared/HoverPopover";
|
||||
import { SweatDrops } from "src/components/Shared/SweatDrops";
|
||||
import { PerformerPopoverButton } from "src/components/Shared/PerformerPopoverButton";
|
||||
import { GridCard, calculateCardWidth } from "src/components/Shared/GridCard";
|
||||
import {
|
||||
GridCard,
|
||||
calculateCardWidth,
|
||||
} from "src/components/Shared/GridCard/GridCard";
|
||||
import { RatingBanner } from "src/components/Shared/RatingBanner";
|
||||
import {
|
||||
faBox,
|
||||
@@ -18,6 +21,7 @@ import {
|
||||
import { objectTitle } from "src/core/files";
|
||||
import { TruncatedText } from "../Shared/TruncatedText";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||
|
||||
interface IImageCardProps {
|
||||
image: GQL.SlimImageDataFragment;
|
||||
@@ -221,6 +225,7 @@ export const ImageCard: React.FC<IImageCardProps> = (
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
overlays={<StudioOverlay studio={props.image.studio} />}
|
||||
popovers={maybeRenderPopoverButtonGroup()}
|
||||
selected={props.selected}
|
||||
selecting={props.selecting}
|
||||
|
||||
@@ -32,7 +32,7 @@ import { ExportDialog } from "../Shared/ExportDialog";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
interface IImageWallProps {
|
||||
images: GQL.SlimImageDataFragment[];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, ButtonGroup } from "react-bootstrap";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { HoverPopover } from "../Shared/HoverPopover";
|
||||
import { Icon } from "../Shared/Icon";
|
||||
import { SceneLink } from "../Shared/TagLink";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { MovieCard } from "./MovieCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
interface IMovieCardGrid {
|
||||
movies: GQL.MovieDataFragment[];
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useIntl } from "react-intl";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import TextUtils from "src/utils/text";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { CountryFlag } from "../Shared/CountryFlag";
|
||||
import { SweatDrops } from "../Shared/SweatDrops";
|
||||
import { HoverPopover } from "../Shared/HoverPopover";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { IPerformerCardExtraCriteria, PerformerCard } from "./PerformerCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
interface IPerformerCardGrid {
|
||||
performers: GQL.PerformerDataFragment[];
|
||||
|
||||
@@ -18,7 +18,7 @@ import TextUtils from "src/utils/text";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { RatingBanner } from "../Shared/RatingBanner";
|
||||
import { FormattedNumber } from "react-intl";
|
||||
import {
|
||||
@@ -33,6 +33,7 @@ import { objectPath, objectTitle } from "src/core/files";
|
||||
import { PreviewScrubber } from "./PreviewScrubber";
|
||||
import { PatchComponent } from "src/pluginApi";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
import { StudioOverlay } from "../Shared/GridCard/StudioOverlay";
|
||||
|
||||
interface IScenePreviewProps {
|
||||
isPortrait: boolean;
|
||||
@@ -328,44 +329,7 @@ const SceneCardDetails = PatchComponent(
|
||||
const SceneCardOverlays = PatchComponent(
|
||||
"SceneCard.Overlays",
|
||||
(props: ISceneCardProps) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
|
||||
function renderStudioThumbnail() {
|
||||
const studioImage = props.scene.studio?.image_path;
|
||||
const studioName = props.scene.studio?.name;
|
||||
|
||||
if (configuration?.interface.showStudioAsText || !studioImage) {
|
||||
return studioName;
|
||||
}
|
||||
|
||||
const studioImageURL = new URL(studioImage);
|
||||
if (studioImageURL.searchParams.get("default") === "true") {
|
||||
return studioName;
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
loading="lazy"
|
||||
alt={studioName}
|
||||
src={studioImage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function maybeRenderSceneStudioOverlay() {
|
||||
if (!props.scene.studio) return;
|
||||
|
||||
return (
|
||||
<div className="scene-studio-overlay">
|
||||
<Link to={`/studios/${props.scene.studio.id}`}>
|
||||
{renderStudioThumbnail()}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{maybeRenderSceneStudioOverlay()}</>;
|
||||
return <StudioOverlay studio={props.scene.studio} />;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useRef } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { SceneQueue } from "src/models/sceneQueue";
|
||||
import { SceneCard } from "./SceneCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
|
||||
interface ISceneCardsGrid {
|
||||
scenes: GQL.SlimSceneDataFragment[];
|
||||
|
||||
@@ -132,33 +132,6 @@ textarea.scene-description {
|
||||
left: 0.7rem;
|
||||
}
|
||||
|
||||
.scene-studio-overlay {
|
||||
display: block;
|
||||
font-weight: 900;
|
||||
height: 10%;
|
||||
max-width: 40%;
|
||||
opacity: 0.75;
|
||||
position: absolute;
|
||||
right: 0.7rem;
|
||||
top: 0.7rem;
|
||||
z-index: 8;
|
||||
|
||||
.image-thumbnail {
|
||||
height: 50px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
letter-spacing: -0.03rem;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 3px #000;
|
||||
}
|
||||
}
|
||||
|
||||
.extra-scene-info {
|
||||
display: none;
|
||||
}
|
||||
@@ -177,14 +150,7 @@ textarea.scene-description {
|
||||
|
||||
.scene-card,
|
||||
.gallery-card {
|
||||
a {
|
||||
color: $text-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.scene-specs-overlay,
|
||||
.rating-banner,
|
||||
.scene-studio-overlay {
|
||||
.scene-specs-overlay {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
@@ -223,19 +189,11 @@ textarea.scene-description {
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
.scene-specs-overlay,
|
||||
.rating-banner,
|
||||
.scene-studio-overlay {
|
||||
.scene-specs-overlay {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.scene-studio-overlay:hover,
|
||||
.scene-studio-overlay:active {
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.scene-card-check {
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.5s;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { Card, Form } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import cx from "classnames";
|
||||
import { TruncatedText } from "./TruncatedText";
|
||||
import { TruncatedText } from "../TruncatedText";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
|
||||
interface ICardProps {
|
||||
51
ui/v2.5/src/components/Shared/GridCard/StudioOverlay.tsx
Normal file
51
ui/v2.5/src/components/Shared/GridCard/StudioOverlay.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
interface IStudio {
|
||||
id: string;
|
||||
name: string;
|
||||
image_path?: string | null;
|
||||
}
|
||||
|
||||
export const StudioOverlay: React.FC<{
|
||||
studio: IStudio | null | undefined;
|
||||
}> = ({ studio }) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
|
||||
const configValue = configuration?.interface.showStudioAsText;
|
||||
|
||||
const showStudioAsText = useMemo(() => {
|
||||
if (configValue || !studio?.image_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the studio has a default image, show the studio name as text
|
||||
const studioImageURL = new URL(studio.image_path);
|
||||
if (studioImageURL.searchParams.get("default") === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [configValue, studio?.image_path]);
|
||||
|
||||
if (!studio) return <></>;
|
||||
|
||||
return (
|
||||
// this class name is incorrect
|
||||
<div className="studio-overlay">
|
||||
<Link to={`/studios/${studio.id}`}>
|
||||
{showStudioAsText ? (
|
||||
studio.name
|
||||
) : (
|
||||
<img
|
||||
className="image-thumbnail"
|
||||
loading="lazy"
|
||||
alt={studio.name}
|
||||
src={studio.image_path ?? ""}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
59
ui/v2.5/src/components/Shared/GridCard/styles.scss
Normal file
59
ui/v2.5/src/components/Shared/GridCard/styles.scss
Normal file
@@ -0,0 +1,59 @@
|
||||
.grid-card {
|
||||
a {
|
||||
color: $text-color;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.rating-banner {
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
.rating-banner,
|
||||
.studio-overlay {
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.studio-overlay:hover,
|
||||
.studio-overlay:active {
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.studio-overlay {
|
||||
display: block;
|
||||
font-weight: 900;
|
||||
height: 10%;
|
||||
max-width: 40%;
|
||||
opacity: 0.75;
|
||||
position: absolute;
|
||||
right: 0.7rem;
|
||||
top: 0.7rem;
|
||||
transition: opacity 0.5s;
|
||||
z-index: 8;
|
||||
|
||||
.image-thumbnail {
|
||||
height: 50px;
|
||||
object-fit: contain;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
letter-spacing: -0.03rem;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 3px #000;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
opacity: 0.75;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import { GridCard, calculateCardWidth } from "src/components/Shared/GridCard";
|
||||
import {
|
||||
GridCard,
|
||||
calculateCardWidth,
|
||||
} from "src/components/Shared/GridCard/GridCard";
|
||||
import { ButtonGroup } from "react-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
import { StudioCard } from "./StudioCard";
|
||||
|
||||
interface IStudioCardGrid {
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as GQL from "src/core/generated-graphql";
|
||||
import NavUtils from "src/utils/navigation";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { TruncatedText } from "../Shared/TruncatedText";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard";
|
||||
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
|
||||
import { PopoverCountButton } from "../Shared/PopoverCountButton";
|
||||
import ScreenUtils from "src/utils/screen";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useRef } from "react";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { useContainerDimensions } from "../Shared/GridCard";
|
||||
import { useContainerDimensions } from "../Shared/GridCard/GridCard";
|
||||
import { TagCard } from "./TagCard";
|
||||
|
||||
interface ITagCardGrid {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
@import "src/components/Setup/styles.scss";
|
||||
@import "src/components/Studios/styles.scss";
|
||||
@import "src/components/Shared/styles.scss";
|
||||
@import "src/components/Shared/GridCard/styles.scss";
|
||||
@import "src/components/Shared/Rating/styles.scss";
|
||||
@import "src/components/Shared/PackageManager/styles.scss";
|
||||
@import "src/components/Tags/styles.scss";
|
||||
|
||||
Reference in New Issue
Block a user