webapp/src/components/_shared/Lightbox/Lightbox.tsx

182 lines
4.5 KiB
TypeScript
Raw Normal View History

2023-11-13 08:05:05 +00:00
import { clsx } from 'clsx'
import { Show, createEffect, on, createMemo, createSignal, onCleanup } from 'solid-js'
import { getImageUrl } from '../../../utils/getImageUrl'
2023-11-13 16:55:32 +00:00
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { Icon } from '../Icon'
import styles from './Lightbox.module.scss'
2023-11-13 08:05:05 +00:00
type Props = {
class?: string
imageAlt?: string
2023-11-13 08:05:05 +00:00
image: string
onClose: () => void
}
const ZOOM_STEP = 1.08
const TRANSITION_SPEED = 300
2023-11-13 08:05:05 +00:00
export const Lightbox = (props: Props) => {
const [zoomLevel, setZoomLevel] = createSignal(1)
const [pictureScalePercentage, setPictureScalePercentage] = createSignal<number | null>(null)
const [translateX, setTranslateX] = createSignal(0)
const [translateY, setTranslateY] = createSignal(0)
const [transitionEnabled, setTransitionEnabled] = createSignal(false)
const lightboxRef: {
current: HTMLElement
} = {
current: null,
}
2023-11-13 08:05:05 +00:00
const handleSmoothAction = (action: () => void) => {
setTransitionEnabled(true)
action()
setTimeout(() => setTransitionEnabled(false), TRANSITION_SPEED)
}
2023-11-13 08:05:05 +00:00
const closeLightbox = () => {
lightboxRef.current?.classList.add(styles.fadeOut)
setTimeout(() => {
props.onClose()
}, 200)
2023-11-13 08:05:05 +00:00
}
const zoomIn = (event) => {
event.stopPropagation()
handleSmoothAction(() => {
setZoomLevel(zoomLevel() * ZOOM_STEP)
})
2023-11-13 08:05:05 +00:00
}
2023-11-13 08:05:05 +00:00
const zoomOut = (event) => {
event.stopPropagation()
handleSmoothAction(() => {
setZoomLevel(zoomLevel() / ZOOM_STEP)
})
}
const positionReset = () => {
setTranslateX(0)
setTranslateY(0)
2023-11-13 08:05:05 +00:00
}
2023-11-22 21:31:51 +00:00
const zoomReset = (event) => {
event.stopPropagation()
handleSmoothAction(() => {
setZoomLevel(1)
positionReset()
})
2023-11-22 21:31:51 +00:00
}
2023-11-13 08:05:05 +00:00
const handleMouseWheelZoom = (event) => {
event.preventDefault()
event.stopPropagation()
let scale = zoomLevel()
scale += event.deltaY * -0.01
scale = Math.min(Math.max(0.125, scale), 4)
handleSmoothAction(() => {
setZoomLevel(scale * ZOOM_STEP)
})
}
2023-11-13 08:05:05 +00:00
2023-11-13 16:55:32 +00:00
useEscKeyDownHandler(closeLightbox)
let startX: number = 0
let startY: number = 0
let isDragging: boolean = false
const onMouseDown: (event: MouseEvent) => void = (event) => {
startX = event.clientX - translateX()
startY = event.clientY - translateY()
isDragging = true
event.preventDefault()
}
const onMouseMove: (event: MouseEvent) => void = (event) => {
if (isDragging) {
setTranslateX(event.clientX - startX)
setTranslateY(event.clientY - startY)
}
}
const onMouseUp: () => void = () => {
isDragging = false
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
onCleanup(() => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
})
const lightboxStyle = createMemo(() => ({
transform: `translate(${translateX()}px, ${translateY()}px) scale(${zoomLevel()})`,
transition: transitionEnabled() ? `transform ${TRANSITION_SPEED}ms ease-in-out` : '',
cursor: 'grab',
}))
let fadeTimer
createEffect(
on(
() => zoomLevel(),
() => {
clearTimeout(fadeTimer)
fadeTimer = setTimeout(() => {
setPictureScalePercentage(null)
}, 2200)
setPictureScalePercentage(Math.round(zoomLevel() * 100))
},
{ defer: true },
),
)
2023-11-13 08:05:05 +00:00
return (
<div
class={clsx(styles.Lightbox, props.class)}
onClick={closeLightbox}
onWheel={(e) => e.preventDefault()}
ref={(el) => (lightboxRef.current = el)}
>
<Show when={pictureScalePercentage()}>
<div class={styles.scalePercentage}>{`${pictureScalePercentage()}%`}</div>
</Show>
2024-01-22 22:14:04 +00:00
<div class={styles.close} onClick={closeLightbox}>
2023-11-22 21:31:51 +00:00
<Icon name="close-white" class={styles.icon} />
2024-01-22 22:14:04 +00:00
</div>
2023-11-13 08:05:05 +00:00
<div class={styles.zoomControls}>
<button class={styles.control} onClick={(event) => zoomOut(event)}>
2023-11-22 21:31:51 +00:00
&minus;
2023-11-13 08:05:05 +00:00
</button>
2023-11-22 21:31:51 +00:00
<button class={clsx(styles.control, styles.controlDefault)} onClick={(event) => zoomReset(event)}>
1:1
2023-11-13 16:55:32 +00:00
</button>
2023-11-13 08:05:05 +00:00
<button class={styles.control} onClick={(event) => zoomIn(event)}>
2023-11-22 21:31:51 +00:00
+
2023-11-13 08:05:05 +00:00
</button>
</div>
<img
class={styles.image}
src={getImageUrl(props.image, { noSizeUrlPart: true })}
alt={props.imageAlt || ''}
2023-11-13 08:05:05 +00:00
onClick={(event) => event.stopPropagation()}
onWheel={handleMouseWheelZoom}
style={lightboxStyle()}
onMouseDown={onMouseDown}
2023-11-13 08:05:05 +00:00
/>
</div>
)
}