Fix lightbox issues (#2426)

* Don't handle non-left-click events
* Improve lightbox initial positioning
* Fix crash when navigating left from first image
This commit is contained in:
WithoutPants
2022-03-28 09:33:13 +11:00
committed by GitHub
parent 2099d10734
commit a2c611f90d
2 changed files with 130 additions and 95 deletions

View File

@@ -566,7 +566,7 @@ export const LightboxComponent: React.FC<IProps> = ({
return <LoadingIndicator />; return <LoadingIndicator />;
} }
const currentImage = images[currentIndex]; const currentImage: ILightboxImage | undefined = images[currentIndex];
function setRating(v: number | null) { function setRating(v: number | null) {
if (currentImage?.id) { if (currentImage?.id) {
@@ -582,7 +582,7 @@ export const LightboxComponent: React.FC<IProps> = ({
} }
async function onIncrementClick() { async function onIncrementClick() {
if (currentImage.id === undefined) return; if (currentImage?.id === undefined) return;
try { try {
await mutateImageIncrementO(currentImage.id); await mutateImageIncrementO(currentImage.id);
} catch (e) { } catch (e) {
@@ -591,7 +591,7 @@ export const LightboxComponent: React.FC<IProps> = ({
} }
async function onDecrementClick() { async function onDecrementClick() {
if (currentImage.id === undefined) return; if (currentImage?.id === undefined) return;
try { try {
await mutateImageDecrementO(currentImage.id); await mutateImageDecrementO(currentImage.id);
} catch (e) { } catch (e) {
@@ -600,9 +600,9 @@ export const LightboxComponent: React.FC<IProps> = ({
} }
async function onResetClick() { async function onResetClick() {
if (currentImage.id === undefined) return; if (currentImage?.id === undefined) return;
try { try {
await mutateImageResetO(currentImage.id); await mutateImageResetO(currentImage?.id);
} catch (e) { } catch (e) {
Toast.error(e); Toast.error(e);
} }
@@ -766,18 +766,18 @@ export const LightboxComponent: React.FC<IProps> = ({
)} )}
<div className={CLASSNAME_FOOTER}> <div className={CLASSNAME_FOOTER}>
<div className={CLASSNAME_FOOTER_LEFT}> <div className={CLASSNAME_FOOTER_LEFT}>
{currentImage.id !== undefined && ( {currentImage?.id !== undefined && (
<> <>
<div> <div>
<OCounterButton <OCounterButton
onDecrement={onDecrementClick} onDecrement={onDecrementClick}
onIncrement={onIncrementClick} onIncrement={onIncrementClick}
onReset={onResetClick} onReset={onResetClick}
value={currentImage.o_counter ?? 0} value={currentImage?.o_counter ?? 0}
/> />
</div> </div>
<RatingStars <RatingStars
value={currentImage.rating ?? undefined} value={currentImage?.rating ?? undefined}
onSetRating={(v) => { onSetRating={(v) => {
setRating(v ?? null); setRating(v ?? null);
}} }}
@@ -786,7 +786,7 @@ export const LightboxComponent: React.FC<IProps> = ({
)} )}
</div> </div>
<div> <div>
{currentImage.title && ( {currentImage?.title && (
<Link to={`/images/${currentImage.id}`} onClick={() => hide()}> <Link to={`/images/${currentImage.id}`} onClick={() => hide()}>
{currentImage.title ?? ""} {currentImage.title ?? ""}
</Link> </Link>

View File

@@ -7,6 +7,44 @@ const CLASSNAME = "Lightbox";
const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`; const CLASSNAME_CAROUSEL = `${CLASSNAME}-carousel`;
const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`; const CLASSNAME_IMAGE = `${CLASSNAME_CAROUSEL}-image`;
function calculateDefaultZoom(
width: number,
height: number,
boundWidth: number,
boundHeight: number,
displayMode: GQL.ImageLightboxDisplayMode,
scaleUp: boolean
) {
// set initial zoom level based on options
let xZoom: number;
let yZoom: number;
let newZoom = 1;
switch (displayMode) {
case GQL.ImageLightboxDisplayMode.FitXy:
xZoom = boundWidth / width;
yZoom = boundHeight / height;
if (!scaleUp) {
xZoom = Math.min(xZoom, 1);
yZoom = Math.min(yZoom, 1);
}
newZoom = Math.min(xZoom, yZoom);
break;
case GQL.ImageLightboxDisplayMode.FitX:
newZoom = boundWidth / width;
if (!scaleUp) {
newZoom = Math.min(newZoom, 1);
}
break;
case GQL.ImageLightboxDisplayMode.Original:
newZoom = 1;
break;
}
return newZoom;
}
interface IProps { interface IProps {
src: string; src: string;
displayMode: GQL.ImageLightboxDisplayMode; displayMode: GQL.ImageLightboxDisplayMode;
@@ -76,8 +114,58 @@ export const LightboxImage: React.FC<IProps> = ({
}; };
}, [src]); }, [src]);
const minMaxY = useCallback(
(appliedZoom: number) => {
let minY, maxY: number;
const inBounds = appliedZoom * height <= boxHeight;
// NOTE: I don't even know how these work, but they do
if (!inBounds) {
if (height > boxHeight) {
minY =
(appliedZoom * height - height) / 2 -
appliedZoom * height +
boxHeight;
maxY = (appliedZoom * height - height) / 2;
} else {
minY = (boxHeight - appliedZoom * height) / 2;
maxY = (appliedZoom * height - boxHeight) / 2;
}
} else {
minY = Math.min((boxHeight - height) / 2, 0);
maxY = minY;
}
return [minY, maxY];
},
[height, boxHeight]
);
const calculateInitialPosition = useCallback(
(appliedZoom: number) => {
// Center image from container's center
const newPositionX = Math.min((boxWidth - width) / 2, 0);
let newPositionY: number;
if (displayMode === GQL.ImageLightboxDisplayMode.FitXy) {
newPositionY = Math.min((boxHeight - height) / 2, 0);
} else {
// otherwise, align image with container
const [minY, maxY] = minMaxY(appliedZoom);
if (!alignBottom) {
newPositionY = maxY;
} else {
newPositionY = minY;
}
}
return [newPositionX, newPositionY];
},
[displayMode, boxWidth, width, boxHeight, height, alignBottom, minMaxY]
);
useEffect(() => { useEffect(() => {
// don't set anything until we have the heights // don't set anything until we have the dimensions
if (!width || !height || !boxWidth || !boxHeight) { if (!width || !height || !boxWidth || !boxHeight) {
return; return;
} }
@@ -90,80 +178,47 @@ export const LightboxImage: React.FC<IProps> = ({
} }
// set initial zoom level based on options // set initial zoom level based on options
let xZoom: number; const newZoom = calculateDefaultZoom(
let yZoom: number; width,
let newZoom = 1; height,
let newPositionY = 0; boxWidth,
switch (displayMode) { boxHeight,
case GQL.ImageLightboxDisplayMode.FitXy: displayMode,
xZoom = boxWidth / width; scaleUp
yZoom = boxHeight / height; );
if (!scaleUp) {
xZoom = Math.min(xZoom, 1);
yZoom = Math.min(yZoom, 1);
}
newZoom = Math.min(xZoom, yZoom);
break;
case GQL.ImageLightboxDisplayMode.FitX:
newZoom = boxWidth / width;
if (!scaleUp) {
newZoom = Math.min(newZoom, 1);
}
break;
case GQL.ImageLightboxDisplayMode.Original:
newZoom = 1;
break;
}
// Center image from container's center
const newPositionX = Math.min((boxWidth - width) / 2, 0);
// if fitting to screen, then centre, other
if (displayMode === GQL.ImageLightboxDisplayMode.FitXy) {
newPositionY = Math.min((boxHeight - height) / 2, 0);
} else {
// otherwise, align top of image with container
if (!alignBottom) {
newPositionY = Math.min((height * newZoom - height) / 2, 0);
} else {
newPositionY = boxHeight - height * newZoom;
}
}
setDefaultZoom(newZoom); setDefaultZoom(newZoom);
const [newPositionX, newPositionY] = calculateInitialPosition(newZoom * 1);
setPositionX(newPositionX); setPositionX(newPositionX);
setPositionY(newPositionY); setPositionY(newPositionY);
}, [width, height, boxWidth, boxHeight, displayMode, scaleUp, alignBottom]); }, [
width,
const calculateInitialPosition = useCallback(() => { height,
// Center image from container's center boxWidth,
const newPositionX = Math.min((boxWidth - width) / 2, 0); boxHeight,
let newPositionY: number; displayMode,
scaleUp,
if (zoom * defaultZoom * height > boxHeight) { alignBottom,
if (!alignBottom) { calculateInitialPosition,
newPositionY = (height * zoom * defaultZoom - height) / 2; ]);
} else {
newPositionY = boxHeight - height * zoom * defaultZoom;
}
} else {
newPositionY = Math.min((boxHeight - height) / 2, 0);
}
return [newPositionX, newPositionY];
}, [boxWidth, width, boxHeight, height, zoom, defaultZoom, alignBottom]);
useEffect(() => { useEffect(() => {
if (resetPosition !== resetPositionRef.current) { if (resetPosition !== resetPositionRef.current) {
resetPositionRef.current = resetPosition; resetPositionRef.current = resetPosition;
const [x, y] = calculateInitialPosition(); const [x, y] = calculateInitialPosition(zoom * defaultZoom);
setPositionX(x); setPositionX(x);
setPositionY(y); setPositionY(y);
} }
}, [resetPosition, resetPositionRef, calculateInitialPosition]); }, [
zoom,
defaultZoom,
resetPosition,
resetPositionRef,
calculateInitialPosition,
]);
function getScrollMode(ev: React.WheelEvent<HTMLDivElement>) { function getScrollMode(ev: React.WheelEvent<HTMLDivElement>) {
if (ev.shiftKey) { if (ev.shiftKey) {
@@ -186,27 +241,7 @@ export const LightboxImage: React.FC<IProps> = ({
} }
function onImageScrollPanY(ev: React.WheelEvent<HTMLDivElement>) { function onImageScrollPanY(ev: React.WheelEvent<HTMLDivElement>) {
const appliedZoom = zoom * defaultZoom; const [minY, maxY] = minMaxY(zoom * defaultZoom);
let minY, maxY: number;
const inBounds = zoom * defaultZoom * height <= boxHeight;
// NOTE: I don't even know how these work, but they do
if (!inBounds) {
if (height > boxHeight) {
minY =
(appliedZoom * height - height) / 2 -
appliedZoom * height +
boxHeight;
maxY = (appliedZoom * height - height) / 2;
} else {
minY = (boxHeight - appliedZoom * height) / 2;
maxY = (appliedZoom * height - boxHeight) / 2;
}
} else {
minY = Math.min((boxHeight - height) / 2, 0);
maxY = minY;
}
let newPositionY = let newPositionY =
positionY + (ev.deltaY < 0 ? SCROLL_PAN_STEP : -SCROLL_PAN_STEP); positionY + (ev.deltaY < 0 ? SCROLL_PAN_STEP : -SCROLL_PAN_STEP);
@@ -219,10 +254,8 @@ export const LightboxImage: React.FC<IProps> = ({
onRight(); onRight();
} else { } else {
// ensure image doesn't go offscreen // ensure image doesn't go offscreen
console.log("unconstrained y: " + newPositionY);
newPositionY = Math.max(newPositionY, minY); newPositionY = Math.max(newPositionY, minY);
newPositionY = Math.min(newPositionY, maxY); newPositionY = Math.min(newPositionY, maxY);
console.log("positionY: " + positionY + " newPositionY: " + newPositionY);
setPositionY(newPositionY); setPositionY(newPositionY);
} }
@@ -267,6 +300,8 @@ export const LightboxImage: React.FC<IProps> = ({
} }
function onImageMouseUp(ev: React.MouseEvent<HTMLDivElement, MouseEvent>) { function onImageMouseUp(ev: React.MouseEvent<HTMLDivElement, MouseEvent>) {
if (ev.button !== 0) return;
if ( if (
!mouseDownEvent.current || !mouseDownEvent.current ||
ev.timeStamp - mouseDownEvent.current.timeStamp > 200 ev.timeStamp - mouseDownEvent.current.timeStamp > 200