import React, { useEffect, useMemo, useRef } from "react"; import { Button, ButtonGroup } from "react-bootstrap"; import { Link } from "react-router-dom"; import cx from "classnames"; import * as GQL from "src/core/generated-graphql"; import { Icon, TagLink, HoverPopover, SweatDrops, TruncatedText, } from "src/components/Shared"; import { NavUtils, TextUtils } from "src/utils"; import { SceneQueue } from "src/models/sceneQueue"; import { ConfigurationContext } from "src/hooks/Config"; import { PerformerPopoverButton } from "../Shared/PerformerPopoverButton"; import { GridCard } from "../Shared/GridCard"; import { RatingBanner } from "../Shared/RatingBanner"; import { FormattedNumber } from "react-intl"; import { faBox, faCopy, faFilm, faImages, faMapMarkerAlt, faTag, } from "@fortawesome/free-solid-svg-icons"; import { objectPath, objectTitle } from "src/core/files"; interface IScenePreviewProps { isPortrait: boolean; image?: string; video?: string; soundActive: boolean; } export const ScenePreview: React.FC = ({ image, video, isPortrait, soundActive, }) => { const videoEl = useRef(null); useEffect(() => { const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) // Catch is necessary due to DOMException if user hovers before clicking on page videoEl.current?.play()?.catch(() => {}); else videoEl.current?.pause(); }); }); if (videoEl.current) observer.observe(videoEl.current); }); useEffect(() => { if (videoEl?.current?.volume) videoEl.current.volume = soundActive ? 0.05 : 0; }, [soundActive]); return (
); }; interface ISceneCardProps { scene: GQL.SlimSceneDataFragment; index?: number; queue?: SceneQueue; compact?: boolean; selecting?: boolean; selected?: boolean | undefined; zoomIndex?: number; onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; } export const SceneCard: React.FC = ( props: ISceneCardProps ) => { const { configuration } = React.useContext(ConfigurationContext); const file = useMemo( () => (props.scene.files.length > 0 ? props.scene.files[0] : undefined), [props.scene] ); // studio image is missing if it uses the default const missingStudioImage = props.scene.studio?.image_path?.endsWith( "?default=true" ); const showStudioAsText = missingStudioImage || (configuration?.interface.showStudioAsText ?? false); function maybeRenderSceneSpecsOverlay() { let sizeObj = null; if (file?.size) { sizeObj = TextUtils.fileSize(file.size); } return (
{sizeObj != null ? ( {TextUtils.formatFileSizeUnit(sizeObj.unit)} ) : ( "" )} {file?.width && file?.height ? ( {" "} {TextUtils.resolution(file?.width, file?.height)} ) : ( "" )} {(file?.duration ?? 0) >= 1 ? TextUtils.secondsToTimestamp(file?.duration ?? 0) : ""}
); } function maybeRenderInteractiveSpeedOverlay() { return (
{props.scene.interactive_speed ?? ""}
); } function maybeRenderSceneStudioOverlay() { if (!props.scene.studio) return; return (
{showStudioAsText ? ( props.scene.studio.name ) : ( {props.scene.studio.name} )}
); } function maybeRenderTagPopoverButton() { if (props.scene.tags.length <= 0) return; const popoverContent = props.scene.tags.map((tag) => ( )); return ( ); } function maybeRenderPerformerPopoverButton() { if (props.scene.performers.length <= 0) return; return ; } function maybeRenderMoviePopoverButton() { if (props.scene.movies.length <= 0) return; const popoverContent = props.scene.movies.map((sceneMovie) => (
{sceneMovie.movie.name
)); return ( ); } function maybeRenderSceneMarkerPopoverButton() { if (props.scene.scene_markers.length <= 0) return; const popoverContent = props.scene.scene_markers.map((marker) => { const markerPopover = { ...marker, scene: { id: props.scene.id } }; return ; }); return ( ); } function maybeRenderOCounter() { if (props.scene.o_counter) { return (
); } } function maybeRenderGallery() { if (props.scene.galleries.length <= 0) return; const popoverContent = props.scene.galleries.map((gallery) => ( )); return ( ); } function maybeRenderOrganized() { if (props.scene.organized) { return (
); } } function maybeRenderDupeCopies() { const phash = file ? file.fingerprints.find((fp) => fp.type === "phash") : undefined; if (phash) { return (
); } } function maybeRenderPopoverButtonGroup() { if ( !props.compact && (props.scene.tags.length > 0 || props.scene.performers.length > 0 || props.scene.movies.length > 0 || props.scene.scene_markers.length > 0 || props.scene?.o_counter || props.scene.galleries.length > 0 || props.scene.organized) ) { return ( <>
{maybeRenderTagPopoverButton()} {maybeRenderPerformerPopoverButton()} {maybeRenderMoviePopoverButton()} {maybeRenderSceneMarkerPopoverButton()} {maybeRenderOCounter()} {maybeRenderGallery()} {maybeRenderOrganized()} {maybeRenderDupeCopies()} ); } } function isPortrait() { const width = file?.width ? file.width : 0; const height = file?.height ? file.height : 0; return height > width; } function zoomIndex() { if (!props.compact && props.zoomIndex !== undefined) { return `zoom-${props.zoomIndex}`; } return ""; } function filelessClass() { if (!props.scene.files.length) { return "fileless"; } return ""; } const cont = configuration?.interface.continuePlaylistDefault ?? false; const sceneLink = props.queue ? props.queue.makeLink(props.scene.id, { sceneIndex: props.index, continue: cont, }) : `/scenes/${props.scene.id}`; return ( {maybeRenderSceneSpecsOverlay()} {maybeRenderInteractiveSpeedOverlay()} } overlays={maybeRenderSceneStudioOverlay()} details={
{props.scene.date} {objectPath(props.scene)}
} popovers={maybeRenderPopoverButtonGroup()} selected={props.selected} selecting={props.selecting} onSelectedChanged={props.onSelectedChanged} /> ); };