Fix misclicks resulting in navigating to new page during selection (#6599)

* Disable studio overlay link if selecting
* Prevent scene preview scrubber click navigating during selection
* Prevent gallery preview scrubber click navigating during selection
This commit is contained in:
WithoutPants 2026-02-25 10:54:20 +11:00 committed by GitHub
parent 86abe7b24c
commit 410dd27d93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 60 additions and 7 deletions

View file

@ -1,5 +1,5 @@
import { Button, ButtonGroup, OverlayTrigger, Tooltip } from "react-bootstrap";
import React, { useState } from "react";
import React, { useMemo, useState } from "react";
import * as GQL from "src/core/generated-graphql";
import { GridCard } from "../Shared/GridCard/GridCard";
import { HoverPopover } from "../Shared/HoverPopover";
@ -21,11 +21,13 @@ import { PatchComponent } from "src/patch";
interface IGalleryPreviewProps {
gallery: GQL.SlimGalleryDataFragment;
onScrubberClick?: (index: number) => void;
disabled?: boolean;
}
export const GalleryPreview: React.FC<IGalleryPreviewProps> = ({
gallery,
onScrubberClick,
disabled,
}) => {
const [imgSrc, setImgSrc] = useState<string | undefined>(
gallery.paths.cover ?? undefined
@ -48,6 +50,7 @@ export const GalleryPreview: React.FC<IGalleryPreviewProps> = ({
imageCount={gallery.image_count}
onClick={onScrubberClick}
onPathChanged={setImgSrc}
disabled={disabled}
/>
)}
</div>
@ -195,7 +198,16 @@ const GalleryCardDetails = PatchComponent(
const GalleryCardOverlays = PatchComponent(
"GalleryCard.Overlays",
(props: IGalleryCardProps) => {
return <StudioOverlay studio={props.gallery.studio} />;
const ret = useMemo(() => {
return (
<StudioOverlay
studio={props.gallery.studio}
disabled={props.selecting}
/>
);
}, [props.gallery.studio, props.selecting]);
return ret;
}
);
@ -211,6 +223,7 @@ const GalleryCardImage = PatchComponent(
onScrubberClick={(i) => {
history.push(`/galleries/${props.gallery.id}/images/${i}`);
}}
disabled={props.selecting}
/>
<RatingBanner rating={props.gallery.rating100} />
</>

View file

@ -10,6 +10,7 @@ export const GalleryPreviewScrubber: React.FC<{
imageCount: number;
onClick?: (imageIndex: number) => void;
onPathChanged: React.Dispatch<React.SetStateAction<string | undefined>>;
disabled?: boolean;
}> = ({
className,
previewPath,
@ -17,6 +18,7 @@ export const GalleryPreviewScrubber: React.FC<{
imageCount,
onClick,
onPathChanged,
disabled,
}) => {
const [activeIndex, setActiveIndex] = useState<number>();
const debounceSetActiveIndex = useThrottle(setActiveIndex, 50);
@ -48,6 +50,7 @@ export const GalleryPreviewScrubber: React.FC<{
activeIndex={activeIndex}
setActiveIndex={(i) => debounceSetActiveIndex(i)}
onClick={onScrubberClick}
disabled={disabled}
/>
</div>
);

View file

@ -148,7 +148,13 @@ const ImageCardDetails = PatchComponent(
const ImageCardOverlays = PatchComponent(
"ImageCard.Overlays",
(props: IImageCardProps) => {
return <StudioOverlay studio={props.image.studio} />;
const ret = useMemo(() => {
return (
<StudioOverlay studio={props.image.studio} disabled={props.selecting} />
);
}, [props.image.studio, props.selecting]);
return ret;
}
);

View file

@ -13,6 +13,7 @@ import { HoverScrubber } from "../Shared/HoverScrubber";
interface IScenePreviewProps {
vttPath: string | undefined;
onClick?: (timestamp: number) => void;
disabled?: boolean;
}
function scaleToFit(dimensions: { w: number; h: number }, bounds: DOMRect) {
@ -32,6 +33,7 @@ const defaultSprites = 81; // 9x9 grid by default
export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
vttPath,
onClick,
disabled,
}) => {
const imageParentRef = useRef<HTMLDivElement>(null);
const [style, setStyle] = useState({});
@ -113,6 +115,7 @@ export const PreviewScrubber: React.FC<IScenePreviewProps> = ({
activeIndex={activeIndex}
setActiveIndex={(i) => debounceSetActiveIndex(i)}
onClick={onScrubberClick}
disabled={disabled}
/>
</div>
);

View file

@ -38,6 +38,7 @@ interface IScenePreviewProps {
soundActive: boolean;
vttPath?: string;
onScrubberClick?: (timestamp: number) => void;
disabled?: boolean;
}
export const ScenePreview: React.FC<IScenePreviewProps> = ({
@ -47,6 +48,7 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
soundActive,
vttPath,
onScrubberClick,
disabled,
}) => {
const videoEl = useRef<HTMLVideoElement>(null);
@ -86,7 +88,11 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
ref={videoEl}
src={video}
/>
<PreviewScrubber vttPath={vttPath} onClick={onScrubberClick} />
<PreviewScrubber
vttPath={vttPath}
onClick={onScrubberClick}
disabled={disabled}
/>
</div>
);
};
@ -336,7 +342,13 @@ const SceneCardDetails = PatchComponent(
const SceneCardOverlays = PatchComponent(
"SceneCard.Overlays",
(props: ISceneCardProps) => {
return <StudioOverlay studio={props.scene.studio} />;
const ret = useMemo(() => {
return (
<StudioOverlay studio={props.scene.studio} disabled={props.selecting} />
);
}, [props.scene.studio, props.selecting]);
return ret;
}
);
@ -390,6 +402,7 @@ const SceneCardImage = PatchComponent(
}
function onScrubberClick(timestamp: number) {
if (props.selecting) return;
const link = props.queue
? props.queue.makeLink(props.scene.id, {
sceneIndex: props.index,
@ -416,6 +429,7 @@ const SceneCardImage = PatchComponent(
soundActive={configuration?.interface?.soundOnPreview ?? false}
vttPath={props.scene.paths.vtt ?? undefined}
onScrubberClick={onScrubberClick}
disabled={props.selecting}
/>
<RatingBanner rating={props.scene.rating100} />
{maybeRenderSceneSpecsOverlay()}

View file

@ -10,7 +10,8 @@ interface IStudio {
export const StudioOverlay: React.FC<{
studio: IStudio | null | undefined;
}> = ({ studio }) => {
disabled?: boolean;
}> = ({ studio, disabled }) => {
const { configuration } = useConfigurationContext();
const configValue = configuration?.interface.showStudioAsText;
@ -29,12 +30,18 @@ export const StudioOverlay: React.FC<{
return false;
}, [configValue, studio?.image_path]);
function onClick(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) {
if (disabled) {
e.preventDefault();
}
}
if (!studio) return <></>;
return (
// this class name is incorrect
<div className="studio-overlay">
<Link to={`/studios/${studio.id}`}>
<Link to={`/studios/${studio.id}`} onClick={onClick}>
{showStudioAsText ? (
studio.name
) : (

View file

@ -9,6 +9,7 @@ interface IHoverScrubber {
activeIndex: number | undefined;
setActiveIndex: (index: number | undefined) => void;
onClick?: (index: number) => void;
disabled?: boolean;
}
export const HoverScrubber: React.FC<IHoverScrubber> = ({
@ -16,6 +17,7 @@ export const HoverScrubber: React.FC<IHoverScrubber> = ({
activeIndex,
setActiveIndex,
onClick,
disabled,
}) => {
function getActiveIndex(
e:
@ -69,6 +71,11 @@ export const HoverScrubber: React.FC<IHoverScrubber> = ({
| React.TouchEvent<HTMLDivElement>
) {
if (!onClick) return;
if (disabled) {
// allow propagation up so that selection still works
e.preventDefault();
return;
}
const relatedTarget = e.currentTarget;