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

View File

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