Files
stash/ui/v2.5/src/components/Galleries/GalleryCard.tsx
2024-09-05 09:35:56 +10:00

237 lines
6.6 KiB
TypeScript

import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import * as GQL from "src/core/generated-graphql";
import { GridCard, calculateCardWidth } from "../Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
import { Icon } from "../Shared/Icon";
import { SceneLink, TagLink } from "../Shared/TagLink";
import { TruncatedText } from "../Shared/TruncatedText";
import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton";
import { PopoverCountButton } from "../Shared/PopoverCountButton";
import NavUtils from "src/utils/navigation";
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";
import { GalleryPreviewScrubber } from "./GalleryPreviewScrubber";
import cx from "classnames";
import { useHistory } from "react-router-dom";
interface IGalleryPreviewProps {
gallery: GQL.SlimGalleryDataFragment;
onScrubberClick?: (index: number) => void;
}
export const GalleryPreview: React.FC<IGalleryPreviewProps> = ({
gallery,
onScrubberClick,
}) => {
const [imgSrc, setImgSrc] = useState<string | undefined>(
gallery.paths.cover ?? undefined
);
return (
<div className={cx("gallery-card-cover")}>
{!!imgSrc && (
<img
loading="lazy"
className="gallery-card-image"
alt={gallery.title ?? ""}
src={imgSrc}
/>
)}
{gallery.image_count > 0 && (
<GalleryPreviewScrubber
previewPath={gallery.paths.preview}
defaultPath={gallery.paths.cover ?? ""}
imageCount={gallery.image_count}
onClick={onScrubberClick}
onPathChanged={setImgSrc}
/>
)}
</div>
);
};
interface IProps {
gallery: GQL.SlimGalleryDataFragment;
containerWidth?: number;
selecting?: boolean;
selected?: boolean | undefined;
zoomIndex?: number;
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
}
export const GalleryCard: React.FC<IProps> = (props) => {
const history = useHistory();
const [cardWidth, setCardWidth] = useState<number>();
useEffect(() => {
if (
!props.containerWidth ||
props.zoomIndex === undefined ||
ScreenUtils.isMobile()
)
return;
let zoomValue = props.zoomIndex;
let preferredCardWidth: number;
switch (zoomValue) {
case 0:
preferredCardWidth = 240;
break;
case 1:
preferredCardWidth = 340;
break;
case 2:
preferredCardWidth = 480;
break;
case 3:
preferredCardWidth = 640;
}
let fittedCardWidth = calculateCardWidth(
props.containerWidth,
preferredCardWidth!
);
setCardWidth(fittedCardWidth);
}, [props, props.containerWidth, props.zoomIndex]);
function maybeRenderScenePopoverButton() {
if (props.gallery.scenes.length === 0) return;
const popoverContent = props.gallery.scenes.map((scene) => (
<SceneLink key={scene.id} scene={scene} />
));
return (
<HoverPopover
className="scene-count"
placement="bottom"
content={popoverContent}
>
<Button className="minimal">
<Icon icon={faPlayCircle} />
<span>{props.gallery.scenes.length}</span>
</Button>
</HoverPopover>
);
}
function maybeRenderTagPopoverButton() {
if (props.gallery.tags.length <= 0) return;
const popoverContent = props.gallery.tags.map((tag) => (
<TagLink key={tag.id} tag={tag} linkType="gallery" />
));
return (
<HoverPopover
className="tag-count"
placement="bottom"
content={popoverContent}
>
<Button className="minimal">
<Icon icon={faTag} />
<span>{props.gallery.tags.length}</span>
</Button>
</HoverPopover>
);
}
function maybeRenderPerformerPopoverButton() {
if (props.gallery.performers.length <= 0) return;
return <PerformerPopoverButton performers={props.gallery.performers} />;
}
function maybeRenderImagesPopoverButton() {
if (!props.gallery.image_count) return;
return (
<PopoverCountButton
className="image-count"
type="image"
count={props.gallery.image_count}
url={NavUtils.makeGalleryImagesUrl(props.gallery)}
/>
);
}
function maybeRenderOrganized() {
if (props.gallery.organized) {
return (
<OverlayTrigger
overlay={<Tooltip id="organised-tooltip">{"Organized"}</Tooltip>}
placement="bottom"
>
<div className="organized">
<Button className="minimal">
<Icon icon={faBox} />
</Button>
</div>
</OverlayTrigger>
);
}
}
function maybeRenderPopoverButtonGroup() {
if (
props.gallery.scenes.length > 0 ||
props.gallery.performers.length > 0 ||
props.gallery.tags.length > 0 ||
props.gallery.organized ||
props.gallery.image_count > 0
) {
return (
<>
<hr />
<ButtonGroup className="card-popovers">
{maybeRenderImagesPopoverButton()}
{maybeRenderTagPopoverButton()}
{maybeRenderPerformerPopoverButton()}
{maybeRenderScenePopoverButton()}
{maybeRenderOrganized()}
</ButtonGroup>
</>
);
}
}
return (
<GridCard
className={`gallery-card zoom-${props.zoomIndex}`}
url={`/galleries/${props.gallery.id}`}
width={cardWidth}
title={galleryTitle(props.gallery)}
linkClassName="gallery-card-header"
image={
<>
<GalleryPreview
gallery={props.gallery}
onScrubberClick={(i) => {
history.push(`/galleries/${props.gallery.id}/images/${i}`);
}}
/>
<RatingBanner rating={props.gallery.rating100} />
</>
}
overlays={<StudioOverlay studio={props.gallery.studio} />}
details={
<div className="gallery-card__details">
<span className="gallery-card__date">{props.gallery.date}</span>
<TruncatedText
className="gallery-card__description"
text={props.gallery.details}
lineCount={3}
/>
</div>
}
popovers={maybeRenderPopoverButtonGroup()}
selected={props.selected}
selecting={props.selecting}
onSelectedChanged={props.onSelectedChanged}
/>
);
};