Animate lightbox and better touch support
This commit is contained in:
parent
7488fa5abc
commit
7dca544295
3 changed files with 231 additions and 209 deletions
|
@ -5,18 +5,21 @@
|
||||||
&__container {
|
&__container {
|
||||||
background-color: $color-black-alpha-90;
|
background-color: $color-black-alpha-90;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 0 16px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
&--zoom {
|
&__animated {
|
||||||
padding: 0;
|
bottom: 0;
|
||||||
}
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__main-container {
|
&__main-container {
|
||||||
|
@ -76,15 +79,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__shadow-container {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__object {
|
&__object {
|
||||||
&--container {
|
&--container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
@ -93,34 +87,21 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
|
&--zoom {
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
height: auto;
|
height: auto;
|
||||||
left: 50%;
|
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0 40px;
|
padding: 0 40px;
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__shadow-container &__object {
|
|
||||||
max-height: 200%;
|
|
||||||
max-width: 200%;
|
|
||||||
padding: 10%;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__object--container--zoom &__object {
|
|
||||||
max-height: 200%;
|
|
||||||
max-width: 200%;
|
|
||||||
padding: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__object--container--fill &__object {
|
&__object--container--fill &__object {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -206,6 +187,9 @@
|
||||||
height: 56px;
|
height: 56px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
opacity: 1;
|
||||||
|
padding: 0 16px;
|
||||||
|
transition: opacity 150ms cubic-bezier(0.17, 0.17, 0, 1);
|
||||||
|
|
||||||
&--container {
|
&--container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -226,6 +210,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
opacity: 1;
|
||||||
|
padding: 0 16px;
|
||||||
|
transition: opacity 150ms cubic-bezier(0.17, 0.17, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__container--zoom {
|
||||||
|
.Lightbox__header,
|
||||||
|
.Lightbox__footer {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__button {
|
&__button {
|
||||||
@include button-reset;
|
@include button-reset;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
CSSProperties,
|
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -13,6 +12,7 @@ import classNames from 'classnames';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
import { useSpring, animated, to } from '@react-spring/web';
|
||||||
|
|
||||||
import * as GoogleChrome from '../util/GoogleChrome';
|
import * as GoogleChrome from '../util/GoogleChrome';
|
||||||
import { AttachmentType, isGIF } from '../types/Attachment';
|
import { AttachmentType, isGIF } from '../types/Attachment';
|
||||||
|
@ -41,11 +41,13 @@ export type PropsType = {
|
||||||
selectedIndex?: number;
|
selectedIndex?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ZoomType {
|
const ZOOM_SCALE = 3;
|
||||||
None,
|
|
||||||
FillScreen,
|
const INITIAL_IMAGE_TRANSFORM = {
|
||||||
ZoomAndPan,
|
scale: 1,
|
||||||
}
|
translateX: 0,
|
||||||
|
translateY: 0,
|
||||||
|
};
|
||||||
|
|
||||||
export function Lightbox({
|
export function Lightbox({
|
||||||
children,
|
children,
|
||||||
|
@ -67,24 +69,29 @@ export function Lightbox({
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [videoTime, setVideoTime] = useState<number | undefined>();
|
const [videoTime, setVideoTime] = useState<number | undefined>();
|
||||||
const [zoomType, setZoomType] = useState<ZoomType>(ZoomType.None);
|
const [isZoomed, setIsZoomed] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [focusRef] = useRestoreFocus();
|
const [focusRef] = useRestoreFocus();
|
||||||
const imageRef = useRef<HTMLImageElement | null>(null);
|
const animateRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [imagePanStyle, setImagePanStyle] = useState<CSSProperties>({});
|
const dragCacheRef = useRef<
|
||||||
const zoomCoordsRef = useRef<
|
|
||||||
| {
|
| {
|
||||||
initX: number;
|
startX: number;
|
||||||
initY: number;
|
startY: number;
|
||||||
screenWidth: number;
|
translateX: number;
|
||||||
screenHeight: number;
|
translateY: number;
|
||||||
x: number;
|
}
|
||||||
y: number;
|
| undefined
|
||||||
|
>();
|
||||||
|
const imageRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
const zoomCacheRef = useRef<
|
||||||
|
| {
|
||||||
|
maxX: number;
|
||||||
|
maxY: number;
|
||||||
|
screenWidth: number;
|
||||||
|
screenHeight: number;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const isZoomed = zoomType !== ZoomType.None;
|
|
||||||
|
|
||||||
const onPrevious = useCallback(
|
const onPrevious = useCallback(
|
||||||
(
|
(
|
||||||
|
@ -238,122 +245,162 @@ export function Lightbox({
|
||||||
};
|
};
|
||||||
}, [isViewOnce, isAttachmentGIF, onTimeUpdate, playVideo, videoElement]);
|
}, [isViewOnce, isAttachmentGIF, onTimeUpdate, playVideo, videoElement]);
|
||||||
|
|
||||||
|
const [{ scale, translateX, translateY }, springApi] = useSpring(
|
||||||
|
() => INITIAL_IMAGE_TRANSFORM
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxBoundsLimiter = useCallback((x: number, y: number): [
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
] => {
|
||||||
|
const zoomCache = zoomCacheRef.current;
|
||||||
|
|
||||||
|
if (!zoomCache) {
|
||||||
|
return [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { maxX, maxY } = zoomCache;
|
||||||
|
|
||||||
|
const posX = Math.min(maxX, Math.max(-maxX, x));
|
||||||
|
const posY = Math.min(maxY, Math.max(-maxY, y));
|
||||||
|
|
||||||
|
return [posX, posY];
|
||||||
|
}, []);
|
||||||
|
|
||||||
const positionImage = useCallback(
|
const positionImage = useCallback(
|
||||||
(ev?: { clientX: number; clientY: number }) => {
|
(ev: MouseEvent) => {
|
||||||
const imageNode = imageRef.current;
|
const zoomCache = zoomCacheRef.current;
|
||||||
const zoomCoords = zoomCoordsRef.current;
|
|
||||||
if (!imageNode || !zoomCoords) {
|
if (!zoomCache) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev) {
|
const { screenWidth, screenHeight } = zoomCache;
|
||||||
zoomCoords.x = ev.clientX;
|
|
||||||
zoomCoords.y = ev.clientY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldTransformX = imageNode.naturalWidth > zoomCoords.screenWidth;
|
const offsetX = screenWidth / 2 - ev.clientX;
|
||||||
const shouldTransformY =
|
const offsetY = screenHeight / 2 - ev.clientY;
|
||||||
imageNode.naturalHeight > zoomCoords.screenHeight;
|
const posX = offsetX * ZOOM_SCALE;
|
||||||
|
const posY = offsetY * ZOOM_SCALE;
|
||||||
|
const [x, y] = maxBoundsLimiter(posX, posY);
|
||||||
|
|
||||||
const nextImagePanStyle: CSSProperties = {
|
springApi.start({
|
||||||
left: '50%',
|
scale: ZOOM_SCALE,
|
||||||
top: '50%',
|
translateX: x,
|
||||||
};
|
translateY: y,
|
||||||
|
|
||||||
let translateX = '-50%';
|
|
||||||
let translateY = '-50%';
|
|
||||||
|
|
||||||
if (shouldTransformX) {
|
|
||||||
const offset = imageNode.offsetWidth - zoomCoords.screenWidth;
|
|
||||||
|
|
||||||
const scaleX = (-1 / zoomCoords.screenWidth) * offset;
|
|
||||||
|
|
||||||
const posX = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(zoomCoords.screenWidth, zoomCoords.x)
|
|
||||||
);
|
|
||||||
|
|
||||||
translateX = `${posX * scaleX}px`;
|
|
||||||
nextImagePanStyle.left = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldTransformY) {
|
|
||||||
const offset = imageNode.offsetHeight - zoomCoords.screenHeight;
|
|
||||||
|
|
||||||
const scaleY = (-1 / zoomCoords.screenHeight) * offset;
|
|
||||||
|
|
||||||
const posY = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(zoomCoords.screenHeight, zoomCoords.y)
|
|
||||||
);
|
|
||||||
|
|
||||||
translateY = `${posY * scaleY}px`;
|
|
||||||
nextImagePanStyle.top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
setImagePanStyle({
|
|
||||||
...nextImagePanStyle,
|
|
||||||
transform: `translate(${translateX}, ${translateY})`,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[]
|
[maxBoundsLimiter, springApi]
|
||||||
);
|
);
|
||||||
|
|
||||||
function canPanImage(): boolean {
|
const handleTouchStart = useCallback(
|
||||||
const imageNode = imageRef.current;
|
(ev: TouchEvent) => {
|
||||||
|
const [touch] = ev.touches;
|
||||||
|
|
||||||
return Boolean(
|
dragCacheRef.current = {
|
||||||
imageNode &&
|
startX: touch.clientX,
|
||||||
(imageNode.naturalWidth > document.documentElement.clientWidth ||
|
startY: touch.clientY,
|
||||||
imageNode.naturalHeight > document.documentElement.clientHeight)
|
translateX: translateX.get(),
|
||||||
);
|
translateY: translateY.get(),
|
||||||
}
|
};
|
||||||
|
},
|
||||||
|
[translateY, translateX]
|
||||||
|
);
|
||||||
|
|
||||||
const handleTouchMove = useCallback(
|
const handleTouchMove = useCallback(
|
||||||
(ev: TouchEvent) => {
|
(ev: TouchEvent) => {
|
||||||
const imageNode = imageRef.current;
|
const dragCache = dragCacheRef.current;
|
||||||
const zoomCoords = zoomCoordsRef.current;
|
|
||||||
|
|
||||||
ev.preventDefault();
|
if (!dragCache) {
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
if (!imageNode || !zoomCoords) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [touch] = ev.touches;
|
const [touch] = ev.touches;
|
||||||
const { initX, initY } = zoomCoords;
|
|
||||||
|
|
||||||
positionImage({
|
const deltaX = touch.clientX - dragCache.startX;
|
||||||
clientX: initX + (initX - touch.clientX),
|
const deltaY = touch.clientY - dragCache.startY;
|
||||||
clientY: initY + (initY - touch.clientY),
|
|
||||||
|
const x = dragCache.translateX + deltaX;
|
||||||
|
const y = dragCache.translateY + deltaY;
|
||||||
|
|
||||||
|
springApi.start({
|
||||||
|
scale: ZOOM_SCALE,
|
||||||
|
translateX: x,
|
||||||
|
translateY: y,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[positionImage]
|
[springApi]
|
||||||
|
);
|
||||||
|
|
||||||
|
const zoomButtonHandler = useCallback(
|
||||||
|
(ev: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const imageNode = imageRef.current;
|
||||||
|
const animateNode = animateRef.current;
|
||||||
|
if (!imageNode || !animateNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isZoomed) {
|
||||||
|
zoomCacheRef.current = {
|
||||||
|
maxX: imageNode.offsetWidth,
|
||||||
|
maxY: imageNode.offsetHeight,
|
||||||
|
screenHeight: window.innerHeight,
|
||||||
|
screenWidth: window.innerWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
height,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width,
|
||||||
|
} = animateNode.getBoundingClientRect();
|
||||||
|
|
||||||
|
const offsetX = ev.clientX - left - width / 2;
|
||||||
|
const offsetY = ev.clientY - top - height / 2;
|
||||||
|
const posX = -offsetX * ZOOM_SCALE + translateX.get();
|
||||||
|
const posY = -offsetY * ZOOM_SCALE + translateY.get();
|
||||||
|
const [x, y] = maxBoundsLimiter(posX, posY);
|
||||||
|
|
||||||
|
springApi.start({
|
||||||
|
scale: ZOOM_SCALE,
|
||||||
|
translateX: x,
|
||||||
|
translateY: y,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsZoomed(true);
|
||||||
|
} else {
|
||||||
|
springApi.start(INITIAL_IMAGE_TRANSFORM);
|
||||||
|
setIsZoomed(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isZoomed, maxBoundsLimiter, translateX, translateY, springApi]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const imageNode = imageRef.current;
|
const animateNode = animateRef.current;
|
||||||
let hasListener = false;
|
let hasListener = false;
|
||||||
|
|
||||||
if (imageNode && zoomType !== ZoomType.None && canPanImage()) {
|
if (animateNode && isZoomed) {
|
||||||
hasListener = true;
|
hasListener = true;
|
||||||
document.addEventListener('mousemove', positionImage);
|
document.addEventListener('mousemove', positionImage);
|
||||||
document.addEventListener('touchmove', handleTouchMove);
|
document.addEventListener('touchmove', handleTouchMove);
|
||||||
|
document.addEventListener('touchstart', handleTouchStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (hasListener) {
|
if (hasListener) {
|
||||||
document.removeEventListener('mousemove', positionImage);
|
document.removeEventListener('mousemove', positionImage);
|
||||||
document.removeEventListener('touchmove', handleTouchMove);
|
document.removeEventListener('touchmove', handleTouchMove);
|
||||||
|
document.removeEventListener('touchstart', handleTouchStart);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [handleTouchMove, positionImage, zoomType]);
|
}, [handleTouchMove, handleTouchStart, isZoomed, positionImage]);
|
||||||
|
|
||||||
const caption = attachment?.caption;
|
const caption = attachment?.caption;
|
||||||
|
|
||||||
let content: JSX.Element;
|
let content: JSX.Element;
|
||||||
let shadowImage: JSX.Element | undefined;
|
|
||||||
if (!contentType) {
|
if (!contentType) {
|
||||||
content = <>{children}</>;
|
content = <>{children}</>;
|
||||||
} else {
|
} else {
|
||||||
|
@ -366,64 +413,27 @@ export function Lightbox({
|
||||||
|
|
||||||
if (isImageTypeSupported) {
|
if (isImageTypeSupported) {
|
||||||
if (objectURL) {
|
if (objectURL) {
|
||||||
shadowImage = (
|
|
||||||
<div className="Lightbox__shadow-container">
|
|
||||||
<div className="Lightbox__object--container">
|
|
||||||
<img
|
|
||||||
alt={i18n('lightboxImageAlt')}
|
|
||||||
className="Lightbox__object"
|
|
||||||
ref={imageRef}
|
|
||||||
src={objectURL}
|
|
||||||
tabIndex={-1}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
content = (
|
content = (
|
||||||
<button
|
<button
|
||||||
className="Lightbox__zoom-button"
|
className="Lightbox__zoom-button"
|
||||||
onClick={(
|
onClick={zoomButtonHandler}
|
||||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
|
||||||
) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
if (zoomType === ZoomType.None) {
|
|
||||||
if (canPanImage()) {
|
|
||||||
setZoomType(ZoomType.ZoomAndPan);
|
|
||||||
zoomCoordsRef.current = {
|
|
||||||
initX: event.clientX,
|
|
||||||
initY: event.clientY,
|
|
||||||
screenHeight: document.documentElement.clientHeight,
|
|
||||||
screenWidth: document.documentElement.clientWidth,
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY,
|
|
||||||
};
|
|
||||||
positionImage();
|
|
||||||
} else {
|
|
||||||
setZoomType(ZoomType.FillScreen);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setZoomType(ZoomType.None);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={i18n('lightboxImageAlt')}
|
alt={i18n('lightboxImageAlt')}
|
||||||
className="Lightbox__object"
|
className="Lightbox__object"
|
||||||
onContextMenu={(event: React.MouseEvent<HTMLImageElement>) => {
|
onContextMenu={(ev: React.MouseEvent<HTMLImageElement>) => {
|
||||||
// These are the only image types supported by Electron's NativeImage
|
// These are the only image types supported by Electron's NativeImage
|
||||||
if (
|
if (
|
||||||
event &&
|
ev &&
|
||||||
contentType !== IMAGE_PNG &&
|
contentType !== IMAGE_PNG &&
|
||||||
!/image\/jpe?g/g.test(contentType)
|
!/image\/jpe?g/g.test(contentType)
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
src={objectURL}
|
src={objectURL}
|
||||||
style={zoomType === ZoomType.ZoomAndPan ? imagePanStyle : {}}
|
ref={imageRef}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -490,7 +500,7 @@ export function Lightbox({
|
||||||
? createPortal(
|
? createPortal(
|
||||||
<div
|
<div
|
||||||
className={classNames('Lightbox Lightbox__container', {
|
className={classNames('Lightbox Lightbox__container', {
|
||||||
'Lightbox__container--zoom': zoomType === ZoomType.ZoomAndPan,
|
'Lightbox__container--zoom': isZoomed,
|
||||||
})}
|
})}
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -511,12 +521,12 @@ export function Lightbox({
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<div
|
<div className="Lightbox__animated">
|
||||||
className="Lightbox__main-container"
|
<div
|
||||||
tabIndex={-1}
|
className="Lightbox__main-container"
|
||||||
ref={focusRef}
|
tabIndex={-1}
|
||||||
>
|
ref={focusRef}
|
||||||
{!isZoomed && (
|
>
|
||||||
<div className="Lightbox__header">
|
<div className="Lightbox__header">
|
||||||
{getConversation ? (
|
{getConversation ? (
|
||||||
<LightboxHeader
|
<LightboxHeader
|
||||||
|
@ -552,40 +562,41 @@ export function Lightbox({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<animated.div
|
||||||
<div
|
className={classNames('Lightbox__object--container', {
|
||||||
className={classNames('Lightbox__object--container', {
|
'Lightbox__object--container--zoom': isZoomed,
|
||||||
'Lightbox__object--container--fill':
|
})}
|
||||||
zoomType === ZoomType.FillScreen,
|
ref={animateRef}
|
||||||
'Lightbox__object--container--zoom':
|
style={{
|
||||||
zoomType === ZoomType.ZoomAndPan,
|
transform: to(
|
||||||
})}
|
[scale, translateX, translateY],
|
||||||
>
|
(s, x, y) => `translate(${x}px, ${y}px) scale(${s})`
|
||||||
{content}
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</animated.div>
|
||||||
|
{hasPrevious && (
|
||||||
|
<div className="Lightbox__nav-prev">
|
||||||
|
<button
|
||||||
|
aria-label={i18n('previous')}
|
||||||
|
className="Lightbox__button Lightbox__button--previous"
|
||||||
|
onClick={onPrevious}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{hasNext && (
|
||||||
|
<div className="Lightbox__nav-next">
|
||||||
|
<button
|
||||||
|
aria-label={i18n('next')}
|
||||||
|
className="Lightbox__button Lightbox__button--next"
|
||||||
|
onClick={onNext}
|
||||||
|
type="button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{shadowImage}
|
|
||||||
{hasPrevious && (
|
|
||||||
<div className="Lightbox__nav-prev">
|
|
||||||
<button
|
|
||||||
aria-label={i18n('previous')}
|
|
||||||
className="Lightbox__button Lightbox__button--previous"
|
|
||||||
onClick={onPrevious}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hasNext && (
|
|
||||||
<div className="Lightbox__nav-next">
|
|
||||||
<button
|
|
||||||
aria-label={i18n('next')}
|
|
||||||
className="Lightbox__button Lightbox__button--next"
|
|
||||||
onClick={onNext}
|
|
||||||
type="button"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!isZoomed && (
|
|
||||||
<div className="Lightbox__footer">
|
<div className="Lightbox__footer">
|
||||||
{isViewOnce && videoTime ? (
|
{isViewOnce && videoTime ? (
|
||||||
<div className="Lightbox__timestamp">
|
<div className="Lightbox__timestamp">
|
||||||
|
@ -636,7 +647,7 @@ export function Lightbox({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
root
|
root
|
||||||
)
|
)
|
||||||
|
|
|
@ -12677,9 +12677,23 @@
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/Lightbox.tsx",
|
"path": "ts/components/Lightbox.tsx",
|
||||||
"line": " const zoomCoordsRef = useRef<",
|
"line": " const animateRef = useRef<HTMLDivElement | null>(null);",
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-09-24T00:03:36.061Z"
|
"updated": "2021-10-11T21:21:08.188Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "ts/components/Lightbox.tsx",
|
||||||
|
"line": " const dragCacheRef = useRef<",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2021-10-11T21:21:08.188Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "ts/components/Lightbox.tsx",
|
||||||
|
"line": " const zoomCacheRef = useRef<",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2021-10-11T21:21:08.188Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue