diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index b1151348d..64c0a011b 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -1,13 +1,62 @@ -import React, { useState } from "react"; +import React, { useEffect, useRef } from "react"; import { Button, ButtonGroup, Card, Form } from "react-bootstrap"; import { Link } from "react-router-dom"; import cx from "classnames"; import * as GQL from "src/core/generated-graphql"; import { useConfiguration } from "src/core/StashService"; -import { useVideoHover } from "src/hooks"; import { Icon, TagLink, HoverPopover, SweatDrops } from "src/components/Shared"; import { TextUtils } from "src/utils"; +interface IScenePreviewProps { + isPortrait: boolean; + image?: string; + video?: string; + soundActive: boolean; +} + +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(); + }); + }, + { root: document.documentElement } + ); + + 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; selecting?: boolean; @@ -19,11 +68,6 @@ interface ISceneCardProps { export const SceneCard: React.FC = ( props: ISceneCardProps ) => { - const [previewPath, setPreviewPath] = useState(); - const hoverHandler = useVideoHover({ - resetOnMouseLeave: false, - }); - const config = useConfiguration(); const showStudioAsText = config?.data?.configuration.interface.showStudioAsText ?? false; @@ -220,18 +264,6 @@ export const SceneCard: React.FC = ( } } - function onMouseEnter() { - if (!previewPath || previewPath === "") { - setPreviewPath(props.scene.paths.preview || ""); - } - hoverHandler.onMouseEnter(); - } - - function onMouseLeave() { - hoverHandler.onMouseLeave(); - setPreviewPath(""); - } - function handleSceneClick( event: React.MouseEvent ) { @@ -272,11 +304,7 @@ export const SceneCard: React.FC = ( let shiftKey = false; return ( - + = ( onDragOver={handleDragOver} draggable={props.selecting} > + {maybeRenderRatingBanner()} {maybeRenderSceneSpecsOverlay()} - {maybeRenderSceneStudioOverlay()} diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss index 5b16f4cfd..436d0a451 100644 --- a/ui/v2.5/src/components/Scenes/styles.scss +++ b/ui/v2.5/src/components/Scenes/styles.scss @@ -169,7 +169,7 @@ textarea.scene-description { padding: 0; } - .scene-card-check { + &-check { left: 0.5rem; margin-top: -12px; opacity: 0; @@ -190,6 +190,34 @@ textarea.scene-description { transition: opacity 0.5s; } + &-preview { + display: flex; + justify-content: center; + margin-bottom: 5px; + position: relative; + + &-image, + &-video { + height: 100%; + object-fit: cover; + width: 100%; + } + + &-video { + position: absolute; + top: -9999px; + transition: top 0s; + transition-delay: 0s; + } + + &.portrait { + .scene-card-preview-image, + .scene-card-preview-video { + object-fit: contain; + } + } + } + &:hover { .scene-specs-overlay, .rating-banner, @@ -207,6 +235,11 @@ textarea.scene-description { opacity: 0.75; transition: opacity 0.5s; } + + .scene-card-preview-video { + top: 0; + transition-delay: 0.2s; + } } } diff --git a/ui/v2.5/src/hooks/VideoHover.ts b/ui/v2.5/src/hooks/VideoHover.ts deleted file mode 100644 index ea2c10e66..000000000 --- a/ui/v2.5/src/hooks/VideoHover.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useConfiguration } from "../core/StashService"; - -export interface IVideoHoverHookData { - videoEl: React.RefObject; - isPlaying: React.MutableRefObject; - isHovering: React.MutableRefObject; - options: IVideoHoverHookOptions; -} - -export interface IVideoHoverHookOptions { - resetOnMouseLeave: boolean; -} - -export const useVideoHover = (options: IVideoHoverHookOptions) => { - const videoEl = useRef(null); - const isPlaying = useRef(false); - const isHovering = useRef(false); - const config = useConfiguration(); - - const onMouseEnter = () => { - isHovering.current = true; - - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - if (videoTag.paused && !isPlaying.current) { - videoTag.play().catch(() => {}); - } - }; - - const onMouseLeave = () => { - isHovering.current = false; - - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - if (!videoTag.paused && isPlaying) { - videoTag.pause(); - if (options.resetOnMouseLeave) { - videoTag.removeAttribute("src"); - videoTag.load(); - isPlaying.current = false; - } - } - }; - - const soundEnabled = - config?.data?.configuration?.interface?.soundOnPreview ?? true; - - useEffect(() => { - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - videoTag.onplaying = () => { - if (isHovering.current === true) { - isPlaying.current = true; - } else { - videoTag.pause(); - } - }; - videoTag.onpause = () => { - isPlaying.current = false; - }; - }, [videoEl]); - - useEffect(() => { - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - videoTag.volume = soundEnabled ? 0.05 : 0; - }, [soundEnabled]); - - return { - videoEl, - isPlaying, - isHovering, - options, - onMouseEnter, - onMouseLeave, - }; -}; diff --git a/ui/v2.5/src/hooks/index.ts b/ui/v2.5/src/hooks/index.ts index 1fc486dd2..7bd3d5284 100644 --- a/ui/v2.5/src/hooks/index.ts +++ b/ui/v2.5/src/hooks/index.ts @@ -1,6 +1,5 @@ export { default as useToast } from "./Toast"; export { useInterfaceLocalForage, useChangelogStorage } from "./LocalForage"; -export { useVideoHover } from "./VideoHover"; export { useScenesList, useSceneMarkersList, diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index e63933c91..e099b3573 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -99,7 +99,7 @@ textarea.text-input { .zoom-0 { width: 240px; - .scene-card-video { + .scene-card-preview { height: 135px; } @@ -116,7 +116,7 @@ textarea.text-input { .zoom-1 { width: 320px; - .scene-card-video { + .scene-card-preview { height: 180px; } @@ -133,7 +133,7 @@ textarea.text-input { .zoom-2 { width: 480px; - .scene-card-video { + .scene-card-preview { height: 270px; } @@ -150,7 +150,7 @@ textarea.text-input { .zoom-3 { width: 640px; - .scene-card-video { + .scene-card-preview { height: 360px; } @@ -165,15 +165,7 @@ textarea.text-input { } } -.scene-card-video { - object-fit: cover; - - &.portrait { - object-fit: contain; - } -} - -.scene-card-video, +.scene-card-preview, .gallery-card-image, .tag-card-image { height: auto;