[Glitch] Change zoom icon in web UI
Port e7fd0985c9 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									03829d8e1d
								
							
						
					
					
						commit
						a969c6a6a6
					
				|  | @ -153,7 +153,7 @@ class ModalRoot extends PureComponent { | |||
|     return ( | ||||
|       <div className='modal-root' ref={this.setRef}> | ||||
|         <div style={{ pointerEvents: visible ? 'auto' : 'none' }}> | ||||
|           <div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.7)` : null }} /> | ||||
|           <div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.9)` : null }} /> | ||||
|           <div role='dialog' className='modal-root__container'>{children}</div> | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ export default class ImageLoader extends PureComponent { | |||
|     width: PropTypes.number, | ||||
|     height: PropTypes.number, | ||||
|     onClick: PropTypes.func, | ||||
|     zoomButtonHidden: PropTypes.bool, | ||||
|     zoomedIn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|  | @ -134,7 +134,7 @@ export default class ImageLoader extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { alt, lang, src, width, height, onClick } = this.props; | ||||
|     const { alt, lang, src, width, height, onClick, zoomedIn } = this.props; | ||||
|     const { loading } = this.state; | ||||
| 
 | ||||
|     const className = classNames('image-loader', { | ||||
|  | @ -149,6 +149,7 @@ export default class ImageLoader extends PureComponent { | |||
|             <div className='loading-bar__container' style={{ width: this.state.width || width }}> | ||||
|               <LoadingBar className='loading-bar' loading={1} /> | ||||
|             </div> | ||||
| 
 | ||||
|             <canvas | ||||
|               className='image-loader__preview-canvas' | ||||
|               ref={this.setCanvasRef} | ||||
|  | @ -164,7 +165,7 @@ export default class ImageLoader extends PureComponent { | |||
|             onClick={onClick} | ||||
|             width={width} | ||||
|             height={height} | ||||
|             zoomButtonHidden={this.props.zoomButtonHidden} | ||||
|             zoomedIn={zoomedIn} | ||||
|           /> | ||||
|         )} | ||||
|       </div> | ||||
|  |  | |||
|  | @ -12,6 +12,8 @@ import ReactSwipeableViews from 'react-swipeable-views'; | |||
| import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; | ||||
| import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; | ||||
| import CloseIcon from '@/material-icons/400-24px/close.svg?react'; | ||||
| import FitScreenIcon from '@/material-icons/400-24px/fit_screen.svg?react'; | ||||
| import ActualSizeIcon from '@/svg-icons/actual_size.svg?react'; | ||||
| import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; | ||||
| import { GIFV } from 'flavours/glitch/components/gifv'; | ||||
| import { Icon }  from 'flavours/glitch/components/icon'; | ||||
|  | @ -26,6 +28,8 @@ const messages = defineMessages({ | |||
|   close: { id: 'lightbox.close', defaultMessage: 'Close' }, | ||||
|   previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, | ||||
|   next: { id: 'lightbox.next', defaultMessage: 'Next' }, | ||||
|   zoomIn: { id: 'lightbox.zoom_in', defaultMessage: 'Zoom to actual size' }, | ||||
|   zoomOut: { id: 'lightbox.zoom_out', defaultMessage: 'Zoom to fit' }, | ||||
| }); | ||||
| 
 | ||||
| class MediaModal extends ImmutablePureComponent { | ||||
|  | @ -46,30 +50,39 @@ class MediaModal extends ImmutablePureComponent { | |||
|   state = { | ||||
|     index: null, | ||||
|     navigationHidden: false, | ||||
|     zoomButtonHidden: false, | ||||
|     zoomedIn: false, | ||||
|   }; | ||||
| 
 | ||||
|   handleZoomClick = () => { | ||||
|     this.setState(prevState => ({ | ||||
|       zoomedIn: !prevState.zoomedIn, | ||||
|     })); | ||||
|   }; | ||||
| 
 | ||||
|   handleSwipe = (index) => { | ||||
|     this.setState({ index: index % this.props.media.size }); | ||||
|     this.setState({ | ||||
|       index: index % this.props.media.size, | ||||
|       zoomedIn: false, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleTransitionEnd = () => { | ||||
|     this.setState({ | ||||
|       zoomButtonHidden: false, | ||||
|       zoomedIn: false, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleNextClick = () => { | ||||
|     this.setState({ | ||||
|       index: (this.getIndex() + 1) % this.props.media.size, | ||||
|       zoomButtonHidden: true, | ||||
|       zoomedIn: false, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handlePrevClick = () => { | ||||
|     this.setState({ | ||||
|       index: (this.props.media.size + this.getIndex() - 1) % this.props.media.size, | ||||
|       zoomButtonHidden: true, | ||||
|       zoomedIn: false, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -78,7 +91,7 @@ class MediaModal extends ImmutablePureComponent { | |||
| 
 | ||||
|     this.setState({ | ||||
|       index: index % this.props.media.size, | ||||
|       zoomButtonHidden: true, | ||||
|       zoomedIn: false, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|  | @ -130,15 +143,22 @@ class MediaModal extends ImmutablePureComponent { | |||
|     return this.state.index !== null ? this.state.index : this.props.index; | ||||
|   } | ||||
| 
 | ||||
|   toggleNavigation = () => { | ||||
|   handleToggleNavigation = () => { | ||||
|     this.setState(prevState => ({ | ||||
|       navigationHidden: !prevState.navigationHidden, | ||||
|     })); | ||||
|   }; | ||||
| 
 | ||||
|   setRef = c => { | ||||
|     this.setState({ | ||||
|       viewportWidth: c?.clientWidth, | ||||
|       viewportHeight: c?.clientHeight, | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { media, statusId, lang, intl, onClose } = this.props; | ||||
|     const { navigationHidden } = this.state; | ||||
|     const { navigationHidden, zoomedIn, viewportWidth, viewportHeight } = this.state; | ||||
| 
 | ||||
|     const index = this.getIndex(); | ||||
| 
 | ||||
|  | @ -160,8 +180,8 @@ class MediaModal extends ImmutablePureComponent { | |||
|             alt={description} | ||||
|             lang={lang} | ||||
|             key={image.get('url')} | ||||
|             onClick={this.toggleNavigation} | ||||
|             zoomButtonHidden={this.state.zoomButtonHidden} | ||||
|             onClick={this.handleToggleNavigation} | ||||
|             zoomedIn={zoomedIn} | ||||
|           /> | ||||
|         ); | ||||
|       } else if (image.get('type') === 'video') { | ||||
|  | @ -229,9 +249,12 @@ class MediaModal extends ImmutablePureComponent { | |||
|       )); | ||||
|     } | ||||
| 
 | ||||
|     const currentMedia = media.get(index); | ||||
|     const zoomable = currentMedia.get('type') === 'image' && (currentMedia.getIn(['meta', 'original', 'width']) > viewportWidth || currentMedia.getIn(['meta', 'original', 'height']) > viewportHeight); | ||||
| 
 | ||||
|     return ( | ||||
|       <div className='modal-root__modal media-modal'> | ||||
|         <div className='media-modal__closer' role='presentation' onClick={onClose} > | ||||
|       <div className='modal-root__modal media-modal' ref={this.setRef}> | ||||
|         <div className='media-modal__closer' role='presentation' onClick={onClose}> | ||||
|           <ReactSwipeableViews | ||||
|             style={swipeableViewsStyle} | ||||
|             containerStyle={containerStyle} | ||||
|  | @ -245,7 +268,10 @@ class MediaModal extends ImmutablePureComponent { | |||
|         </div> | ||||
| 
 | ||||
|         <div className={navigationClassName}> | ||||
|           <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={40} /> | ||||
|           <div className='media-modal__buttons'> | ||||
|             {zoomable && <IconButton title={intl.formatMessage(zoomedIn ? messages.zoomOut : messages.zoomIn)} iconComponent={zoomedIn ? FitScreenIcon : ActualSizeIcon} onClick={this.handleZoomClick} />} | ||||
|             <IconButton title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} /> | ||||
|           </div> | ||||
| 
 | ||||
|           {leftNav} | ||||
|           {rightNav} | ||||
|  |  | |||
|  | @ -1,17 +1,6 @@ | |||
| import PropTypes from 'prop-types'; | ||||
| import { PureComponent } from 'react'; | ||||
| 
 | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| import FullscreenExitIcon from '@/material-icons/400-24px/fullscreen_exit.svg?react'; | ||||
| import RectangleIcon from '@/material-icons/400-24px/rectangle.svg?react'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   compress: { id: 'lightbox.compress', defaultMessage: 'Compress image view box' }, | ||||
|   expand: { id: 'lightbox.expand', defaultMessage: 'Expand image view box' }, | ||||
| }); | ||||
| 
 | ||||
| const MIN_SCALE = 1; | ||||
| const MAX_SCALE = 4; | ||||
| const NAV_BAR_HEIGHT = 66; | ||||
|  | @ -104,8 +93,7 @@ class ZoomableImage extends PureComponent { | |||
|     width: PropTypes.number, | ||||
|     height: PropTypes.number, | ||||
|     onClick: PropTypes.func, | ||||
|     zoomButtonHidden: PropTypes.bool, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     zoomedIn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
|  | @ -131,8 +119,6 @@ class ZoomableImage extends PureComponent { | |||
|       translateX: null, | ||||
|       translateY: null, | ||||
|     }, | ||||
|     zoomState: 'expand', // 'expand' 'compress' | ||||
|     navigationHidden: false, | ||||
|     dragPosition: { top: 0, left: 0, x: 0, y: 0 }, | ||||
|     dragged: false, | ||||
|     lockScroll: { x: 0, y: 0 }, | ||||
|  | @ -169,35 +155,20 @@ class ZoomableImage extends PureComponent { | |||
|     this.container.addEventListener('DOMMouseScroll', handler); | ||||
|     this.removers.push(() => this.container.removeEventListener('DOMMouseScroll', handler)); | ||||
| 
 | ||||
|     this.initZoomMatrix(); | ||||
|     this._initZoomMatrix(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.removeEventListeners(); | ||||
|     this._removeEventListeners(); | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate () { | ||||
|     this.setState({ zoomState: this.state.scale >= this.state.zoomMatrix.rate ? 'compress' : 'expand' }); | ||||
| 
 | ||||
|     if (this.state.scale === MIN_SCALE) { | ||||
|       this.container.style.removeProperty('cursor'); | ||||
|   componentDidUpdate (prevProps) { | ||||
|     if (prevProps.zoomedIn !== this.props.zoomedIn) { | ||||
|       this._toggleZoom(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   UNSAFE_componentWillReceiveProps () { | ||||
|     // reset when slide to next image | ||||
|     if (this.props.zoomButtonHidden) { | ||||
|       this.setState({ | ||||
|         scale: MIN_SCALE, | ||||
|         lockTranslate: { x: 0, y: 0 }, | ||||
|       }, () => { | ||||
|         this.container.scrollLeft = 0; | ||||
|         this.container.scrollTop = 0; | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   removeEventListeners () { | ||||
|   _removeEventListeners () { | ||||
|     this.removers.forEach(listeners => listeners()); | ||||
|     this.removers = []; | ||||
|   } | ||||
|  | @ -220,9 +191,6 @@ class ZoomableImage extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   mouseDownHandler = e => { | ||||
|     this.container.style.cursor = 'grabbing'; | ||||
|     this.container.style.userSelect = 'none'; | ||||
| 
 | ||||
|     this.setState({ dragPosition: { | ||||
|       left: this.container.scrollLeft, | ||||
|       top: this.container.scrollTop, | ||||
|  | @ -246,9 +214,6 @@ class ZoomableImage extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   mouseUpHandler = () => { | ||||
|     this.container.style.cursor = 'grab'; | ||||
|     this.container.style.removeProperty('user-select'); | ||||
| 
 | ||||
|     this.image.removeEventListener('mousemove', this.mouseMoveHandler); | ||||
|     this.image.removeEventListener('mouseup', this.mouseUpHandler); | ||||
|   }; | ||||
|  | @ -276,13 +241,13 @@ class ZoomableImage extends PureComponent { | |||
|     const _MAX_SCALE = Math.max(MAX_SCALE, this.state.zoomMatrix.rate); | ||||
|     const scale = clamp(MIN_SCALE, _MAX_SCALE, this.state.scale * distance / this.lastDistance); | ||||
| 
 | ||||
|     this.zoom(scale, midpoint); | ||||
|     this._zoom(scale, midpoint); | ||||
| 
 | ||||
|     this.lastMidpoint = midpoint; | ||||
|     this.lastDistance = distance; | ||||
|   }; | ||||
| 
 | ||||
|   zoom(nextScale, midpoint) { | ||||
|   _zoom(nextScale, midpoint) { | ||||
|     const { scale, zoomMatrix } = this.state; | ||||
|     const { scrollLeft, scrollTop } = this.container; | ||||
| 
 | ||||
|  | @ -318,14 +283,13 @@ class ZoomableImage extends PureComponent { | |||
|     if (dragged) return; | ||||
|     const handler = this.props.onClick; | ||||
|     if (handler) handler(); | ||||
|     this.setState({ navigationHidden: !this.state.navigationHidden }); | ||||
|   }; | ||||
| 
 | ||||
|   handleMouseDown = e => { | ||||
|     e.preventDefault(); | ||||
|   }; | ||||
| 
 | ||||
|   initZoomMatrix = () => { | ||||
|   _initZoomMatrix = () => { | ||||
|     const { width, height } = this.props; | ||||
|     const { clientWidth, clientHeight } = this.container; | ||||
|     const { offsetWidth, offsetHeight } = this.image; | ||||
|  | @ -357,10 +321,7 @@ class ZoomableImage extends PureComponent { | |||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleZoomClick = e => { | ||||
|     e.preventDefault(); | ||||
|     e.stopPropagation(); | ||||
| 
 | ||||
|   _toggleZoom () { | ||||
|     const { scale, zoomMatrix } = this.state; | ||||
| 
 | ||||
|     if ( scale >= zoomMatrix.rate ) { | ||||
|  | @ -394,10 +355,7 @@ class ZoomableImage extends PureComponent { | |||
|         this.container.scrollTop = zoomMatrix.scrollTop; | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     this.container.style.cursor = 'grab'; | ||||
|     this.container.style.removeProperty('user-select'); | ||||
|   }; | ||||
|   } | ||||
| 
 | ||||
|   setContainerRef = c => { | ||||
|     this.container = c; | ||||
|  | @ -408,29 +366,16 @@ class ZoomableImage extends PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { alt, lang, src, width, height, intl } = this.props; | ||||
|     const { scale, lockTranslate } = this.state; | ||||
|     const { alt, lang, src, width, height } = this.props; | ||||
|     const { scale, lockTranslate, dragged } = this.state; | ||||
|     const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll'; | ||||
|     const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : ''; | ||||
|     const zoomButtonTitle = this.state.zoomState === 'compress' ? intl.formatMessage(messages.compress) : intl.formatMessage(messages.expand); | ||||
|     const cursor = scale === MIN_SCALE ? null : (dragged ? 'grabbing' : 'grab'); | ||||
| 
 | ||||
|     return ( | ||||
|       <> | ||||
|         <IconButton | ||||
|           className={`media-modal__zoom-button ${zoomButtonShouldHide}`} | ||||
|           title={zoomButtonTitle} | ||||
|           icon={this.state.zoomState} | ||||
|           iconComponent={this.state.zoomState === 'compress' ? FullscreenExitIcon : RectangleIcon} | ||||
|           onClick={this.handleZoomClick} | ||||
|           size={40} | ||||
|           style={{ | ||||
|             fontSize: '30px', /* Fontawesome's fa-compress fa-expand is larger than fa-close */ | ||||
|           }} | ||||
|         /> | ||||
|       <div | ||||
|         className='zoomable-image' | ||||
|         ref={this.setContainerRef} | ||||
|           style={{ overflow }} | ||||
|         style={{ overflow, cursor, userSelect: 'none' }} | ||||
|       > | ||||
|         <img | ||||
|           role='presentation' | ||||
|  | @ -450,10 +395,8 @@ class ZoomableImage extends PureComponent { | |||
|           onMouseDown={this.handleMouseDown} | ||||
|         /> | ||||
|       </div> | ||||
|       </> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export default injectIntl(ZoomableImage); | ||||
| export default ZoomableImage; | ||||
|  |  | |||
|  | @ -6212,9 +6212,23 @@ a.status-card { | |||
|   height: 100%; | ||||
|   position: relative; | ||||
| 
 | ||||
|   &__close, | ||||
|   &__zoom-button { | ||||
|   &__buttons { | ||||
|     position: absolute; | ||||
|     inset-inline-end: 8px; | ||||
|     top: 8px; | ||||
|     z-index: 100; | ||||
|     display: flex; | ||||
|     gap: 8px; | ||||
|     align-items: center; | ||||
| 
 | ||||
|     .icon-button { | ||||
|       color: rgba($white, 0.7); | ||||
|       padding: 8px; | ||||
| 
 | ||||
|       .icon { | ||||
|         width: 24px; | ||||
|         height: 24px; | ||||
|       } | ||||
| 
 | ||||
|       &:hover, | ||||
|       &:focus, | ||||
|  | @ -6227,6 +6241,7 @@ a.status-card { | |||
|         background-color: rgba($white, 0.3); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .media-modal__closer { | ||||
|  | @ -6385,28 +6400,6 @@ a.status-card { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .media-modal__close { | ||||
|   position: absolute; | ||||
|   inset-inline-end: 8px; | ||||
|   top: 8px; | ||||
|   z-index: 100; | ||||
| } | ||||
| 
 | ||||
| .media-modal__zoom-button { | ||||
|   position: absolute; | ||||
|   inset-inline-end: 64px; | ||||
|   top: 8px; | ||||
|   z-index: 100; | ||||
|   pointer-events: auto; | ||||
|   transition: opacity 0.3s linear; | ||||
|   will-change: opacity; | ||||
| } | ||||
| 
 | ||||
| .media-modal__zoom-button--hidden { | ||||
|   pointer-events: none; | ||||
|   opacity: 0; | ||||
| } | ||||
| 
 | ||||
| .onboarding-modal, | ||||
| .error-modal, | ||||
| .embed-modal { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue