Skip to content

Commit

Permalink
feat: add dynamic loading for image nav, add zoom image support, add …
Browse files Browse the repository at this point in the history
…image captions, improved styling #1542
  • Loading branch information
marek-mihok committed Sep 28, 2022
1 parent 44df791 commit f9d7ddd
Showing 1 changed file with 78 additions and 15 deletions.
93 changes: 78 additions & 15 deletions ui/src/parts/lightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ const
touchAction: 'pinch-zoom' // TODO:
},
img: {
flexGrow: 1,
objectFit: 'contain',
cursor: 'pointer'
// flexGrow: 1,
maxWidth: '100%',
alignSelf: 'center',
// objectFit: 'scale-down',
transformOrigin: 'top left',
transition: 'transform 0.25s ease',
cursor: 'pointer',
},
header: {
width: '100%',
Expand All @@ -49,7 +53,10 @@ const
},
content: {
display: 'flex',
position: 'relative',
maxWidth: '100%',
overflow: 'auto',
justifyContent: 'center',
},
footer: {
textAlign: 'center',
Expand All @@ -58,12 +65,14 @@ const
},
imageNav: {
height: '180px',
padding: '20px 0px',
paddingTop: '20px',
overflow: 'auto',
whiteSpace: 'nowrap'
},
navImg: {
height: '160px',
objectFit: 'cover',
width: '160px',
padding: '0px 1px',
opacity: 0.6,
$nest: {
Expand All @@ -85,7 +94,18 @@ const
transition: "0.6s ease",
borderRadius: "0 3px 3px 0",
userSelect: "none",
}
},
imgCaptions: {
whiteSpace: 'nowrap',
padding: '10px 40px',
height: '20px'
},
text: {
textOverflow: 'ellipsis',
overflow: 'hidden',
},
title: { fontWeight: 500 },
description: { opacity: 0.85 }
})

interface Props {
Expand All @@ -101,16 +121,36 @@ interface Props {
defaultImageIdx?: U
}

const
zoomLevels: { [key: U]: U } = { 0: 1, 1: 1.5, 2: 2, 3: 3, 4: 4 },
lazyImageObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const lazyImage = entry.target as HTMLImageElement
lazyImage.src = lazyImage.dataset.src!
lazyImage.classList.remove("lazy")
lazyImageObserver.unobserve(lazyImage)
}
})
})

export const Lightbox = ({ visible, onDismiss, images, defaultImageIdx }: Props) => {
const
[activeImageIdx, setActiveImageIdx] = React.useState(defaultImageIdx || 0),
[zoomLevel, setZoomLevel] = React.useState(0),
imageNavRef = React.useRef<HTMLDivElement | undefined>()

React.useEffect(() => {
if (imageNavRef.current) imageNavRef.current.scrollLeft = activeImageIdx * 162
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [imageNavRef.current, activeImageIdx])

React.useLayoutEffect(() => {
const lazyImages = [].slice.call(document.querySelectorAll(".lazy"))
lazyImages.forEach(lazyImage => lazyImageObserver.observe(lazyImage))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

if (images.length === 0 || (activeImageIdx >= images.length)) return <>{'Error'}</> // TODO:
const
{ type, image, path, title, description } = images[activeImageIdx],
Expand All @@ -120,49 +160,72 @@ export const Lightbox = ({ visible, onDismiss, images, defaultImageIdx }: Props)
? `data:image/${type};base64,${image}`
: ''




return (
<div aria-hidden={true} className={css.body} style={visible ? undefined : { display: 'none' }} >
<div className={css.header}>
<Fluent.ActionButton
styles={iconStyles}
onClick={onDismiss}
onClick={() => {
setZoomLevel(0)
onDismiss()
}}
iconProps={{ iconName: 'Cancel', style: { fontSize: '22px' } }}
/>
</div>
<div className={css.content} style={{ maxHeight: `calc(100% - ${images.length > 1 ? '300px' : '100px'})` }}>
<img className={css.img} alt={title} src={src} />
<div className={css.content} style={{ height: `calc(100% - ${images.length > 1 ? '300px' : '100px'})` }}>
<img
className={css.img}
alt={title}
src={src}
style={{
transform: `scale(${zoomLevels[zoomLevel]})`, // TODO:
cursor: zoomLevel < 4 ? 'zoom-in' : 'zoom-out',
maxHeight: 'inherit',
// objectFit: 'none'
}}
onClick={(_ev) => {
const zoom = zoomLevel === 4 ? 0 : zoomLevel + 1
setZoomLevel(zoom)
}}
/>
{images.length > 1 &&
<>
<div className={css.arrow} style={{ left: 0 }}>
<Fluent.ActionButton
styles={iconStyles}
onClick={() => setActiveImageIdx((activeImageIdx === 0) ? (images.length - 1) : activeImageIdx - 1)}
onClick={() => {
setActiveImageIdx((activeImageIdx === 0) ? (images.length - 1) : activeImageIdx - 1)
setZoomLevel(0)
}}
iconProps={{ iconName: 'ChevronLeft', style: { fontSize: '22px' } }}
/>
</div>
<div className={css.arrow} style={{ right: 0 }}>
<Fluent.ActionButton
styles={iconStyles}
onClick={() => setActiveImageIdx((activeImageIdx === images.length - 1) ? 0 : activeImageIdx + 1)}
onClick={() => {
setActiveImageIdx((activeImageIdx === images.length - 1) ? 0 : activeImageIdx + 1)
setZoomLevel(0)
}}
iconProps={{ iconName: 'ChevronRight', style: { fontSize: '22px' } }}
/>
</div>
</>
}
</div>
<div className={css.footer} style={{ height: images.length > 1 ? '260px' : '60px' }}>
{title}{description ? ` - ${description}` : ''}
<div className={css.imgCaptions}>
<div title={title} className={clas(css.text, css.title)}>{title}</div>
<div title={description} className={clas(css.text, css.description)}>{description ? description : ''}</div>
</div>
{images.length > 1 && <div className={css.imageNav} ref={imageNavRef}>
{images.map(({ type, image, path, title }, idx) => {
const src = path
? path
: (image && type)
? `data:image/${type};base64,${image}`
: ''
return <img key={idx} className={clas(css.img, css.navImg)} style={activeImageIdx === idx ? { opacity: 1 } : undefined} alt={title} src={src} onClick={() => { setActiveImageIdx(idx) }} />
return <img key={idx} className={clas(css.img, css.navImg, 'lazy')} style={activeImageIdx === idx ? { opacity: 1 } : undefined} alt={title} data-src={src} onClick={() => { setActiveImageIdx(idx) }} />
})}
</div>}
</div>
Expand Down

0 comments on commit f9d7ddd

Please sign in to comment.