Skip to content

Commit

Permalink
feat: add multiple image support #1542
Browse files Browse the repository at this point in the history
  • Loading branch information
marek-mihok committed Sep 26, 2022
1 parent 6c20276 commit 44df791
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 45 deletions.
2 changes: 1 addition & 1 deletion ui/src/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const
: ''
return <>
<img className={css.img} alt={title} src={src} width={formItemWidth(width)} onClick={() => setLightboxVisible(true)} />
<Lightbox title={title} type={type} image={image} path={path} visible={lightboxVisible} onDismiss={() => setLightboxVisible(false)} />
<Lightbox images={[{ title, type, image, path }]} visible={lightboxVisible} onDismiss={() => setLightboxVisible(false)} />
</>
},
View = bond(({ name, state, changed }: Model<State>) => {
Expand Down
173 changes: 129 additions & 44 deletions ui/src/parts/lightbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,75 +12,160 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { B, S } from 'h2o-wave'
import { B, S, U } from 'h2o-wave'
import React from 'react'
import { stylesheet } from 'typestyle'
import * as Fluent from '@fluentui/react'
import { clas } from '../theme'

const css = stylesheet({
body: {
position: 'fixed',
inset: '0px',
width: '100%',
height: '100%',
zIndex: 1,
backgroundColor: '#000000', // TODO:
touchAction: 'pinch-zoom' // TODO:
const
iconStyles: Fluent.IButtonStyles = {
icon: { color: '#ffffff', lineHeight: 22, height: 'unset', padding: 4 },
iconHovered: { color: '#ffffff' }, // TODO:
iconPressed: { color: 'rgba(255, 255, 255, 0.7)' },
flexContainer: { justifyContent: 'center' },
root: { margin: '4px 4px', width: 38, height: 38, backgroundColor: 'rgba(0, 0, 0, 0.3)' },
rootHovered: { backgroundColor: 'rgba(255, 255, 255, 0.3)' }
},
img: {
flexGrow: 1,
objectFit: 'contain',
cursor: 'pointer'
},
header: {
width: '100%',
height: '40px',
textAlign: 'right'
},
content: {
display: 'flex',
maxHeight: 'calc(100% - 100px)',
maxWidth: '100%',
},
footer: {
height: '60px',
textAlign: 'center',
color: '#ffffff', // TODO:
width: '100%'
}
})
css = stylesheet({
body: {
position: 'fixed',
inset: '0px',
width: '100%',
height: '100%',
zIndex: 1,
backgroundColor: '#000000', // TODO:
touchAction: 'pinch-zoom' // TODO:
},
img: {
flexGrow: 1,
objectFit: 'contain',
cursor: 'pointer'
},
header: {
width: '100%',
height: '40px',
textAlign: 'right'
},
content: {
display: 'flex',
maxWidth: '100%',
},
footer: {
textAlign: 'center',
color: '#ffffff', // TODO:
width: '100%'
},
imageNav: {
height: '180px',
padding: '20px 0px',
overflow: 'auto',
whiteSpace: 'nowrap'
},
navImg: {
height: '160px',
padding: '0px 1px',
opacity: 0.6,
$nest: {
'&:hover': {
opacity: 1
}
}
},
arrow: {
cursor: "pointer",
position: "absolute",
top: "50%",
width: "auto",
padding: "16px",
marginTop: "-50px",
color: "white",
fontWeight: "bold",
fontSize: "20px",
transition: "0.6s ease",
borderRadius: "0 3px 3px 0",
userSelect: "none",
}
})

interface Props {
visible: B,
onDismiss: () => void,
title: S,
description?: S,
type?: S,
image?: S,
path?: S,
images: {
title: S,
description?: S,
type?: S,
image?: S,
path?: S,
}[],
defaultImageIdx?: U
}

export const Lightbox = ({ visible, onDismiss, type, image, path, title, description }: Props) => {
export const Lightbox = ({ visible, onDismiss, images, defaultImageIdx }: Props) => {
const
[activeImageIdx, setActiveImageIdx] = React.useState(defaultImageIdx || 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])

if (images.length === 0 || (activeImageIdx >= images.length)) return <>{'Error'}</> // TODO:
const
{ type, image, path, title, description } = images[activeImageIdx],
src = path
? path
: (image && type)
? `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={{
icon: { color: '#ffffff' }, // TODO:
root: { padding: '10px 10px' }
}}
styles={iconStyles}
onClick={onDismiss}
iconProps={{ iconName: 'Cancel', style: { fontSize: '22px' } }}
/>
</div>
<div className={css.content}><img className={css.img} alt={title} src={src} /></div>
<div className={css.footer}>{title}{description ? ` - ${description}` : ''}</div>
<div className={css.content} style={{ maxHeight: `calc(100% - ${images.length > 1 ? '300px' : '100px'})` }}>
<img className={css.img} alt={title} src={src} />
{images.length > 1 &&
<>
<div className={css.arrow} style={{ left: 0 }}>
<Fluent.ActionButton
styles={iconStyles}
onClick={() => setActiveImageIdx((activeImageIdx === 0) ? (images.length - 1) : activeImageIdx - 1)}
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)}
iconProps={{ iconName: 'ChevronRight', style: { fontSize: '22px' } }}
/>
</div>
</>
}
</div>
<div className={css.footer} style={{ height: images.length > 1 ? '260px' : '60px' }}>
{title}{description ? ` - ${description}` : ''}
{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) }} />
})}
</div>}
</div>
</div>
)
}

0 comments on commit 44df791

Please sign in to comment.