import * as React from "react"; import { ComponentProps } from "./BaseComponent"; import styled from "styled-components"; import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch"; import { Colors } from "constants/Colors"; export interface StyledImageProps { defaultImageUrl: string; enableRotation?: boolean; imageUrl?: string; backgroundColor?: string; showHoverPointer?: boolean; objectFit: string; onClick?: (event: React.MouseEvent) => void; } export const StyledImage = styled.div< StyledImageProps & { imageError: boolean; } >` position: relative; display: flex; flex-direction: "row"; background-size: ${(props) => props.objectFit ?? "contain"}; cursor: ${(props) => props.showHoverPointer && props.onClick ? "pointer" : "inherit"}; background: ${(props) => props.backgroundColor}; background-image: ${(props) => `url(${props.imageError ? props.defaultImageUrl : props.imageUrl})`}; background-position: center; background-repeat: no-repeat; height: 100%; width: 100%; `; const Wrapper = styled.div` height: 100%; width: 100%; .react-transform-element, .react-transform-component { height: 100%; width: 100%; } `; const ControlBtnWrapper = styled.div` position: absolute; top: 2px; right: 2px; padding: 5px 0px; z-index: 1; display: flex; justify-content: center; align-items: center; background: white; `; const ControlBtn = styled.div` height: 25px; width: 45px; color: white; padding: 0px 10px; display: inline-block; &.separator { border-right: 1px solid ${Colors.ALTO2}; } & > div { cursor: pointer; height: 100%; width: 100%; padding: 4px; transition: background 0.2s linear; & > svg { height: 16px; width: 17px; } &: hover { background: #ebebeb; } } `; enum ZoomingState { MAX_ZOOMED_OUT = "MAX_ZOOMED_OUT", MAX_ZOOMED_IN = "MAX_ZOOMED_IN", } class ImageComponent extends React.Component< ImageComponentProps, { imageError: boolean; showImageControl: boolean; imageRotation: number; zoomingState: ZoomingState; } > { isPanning: boolean; constructor(props: ImageComponentProps) { super(props); this.isPanning = false; this.state = { imageError: false, showImageControl: false, imageRotation: 0, zoomingState: ZoomingState.MAX_ZOOMED_OUT, }; } render() { const { maxZoomLevel } = this.props; const { imageRotation } = this.state; const zoomActive = maxZoomLevel !== undefined && maxZoomLevel > 1 && !this.isPanning; const isZoomingIn = this.state.zoomingState === ZoomingState.MAX_ZOOMED_OUT; let cursor = "inherit"; if (zoomActive) { cursor = isZoomingIn ? "zoom-in" : "zoom-out"; } return ( { this.isPanning = true; }} onPanningStart={() => { this.props.disableDrag(true); }} onPanningStop={() => { this.props.disableDrag(false); }} onZoomChange={(zoom: any) => { if (zoomActive) { //Check max zoom if ( maxZoomLevel === zoom.scale && // Added for preventing infinite loops this.state.zoomingState !== ZoomingState.MAX_ZOOMED_IN ) { this.setState({ zoomingState: ZoomingState.MAX_ZOOMED_IN, }); // Check min zoom } else if ( zoom.scale === 1 && this.state.zoomingState !== ZoomingState.MAX_ZOOMED_OUT ) { this.setState({ zoomingState: ZoomingState.MAX_ZOOMED_OUT, }); } } }} options={{ maxScale: maxZoomLevel, disabled: !zoomActive, transformEnabled: zoomActive, }} pan={{ disabled: !zoomActive, }} wheel={{ disabled: !zoomActive, }} > {({ zoomIn, zoomOut }: any) => ( <> {this.renderImageControl()} ) => { if (!this.isPanning) { if (isZoomingIn) { zoomIn(event); } else { zoomOut(event); } this.props.onClick && this.props.onClick(event); } this.isPanning = false; }} style={{ cursor, transform: `rotate(${imageRotation}deg)`, }} > {/* Used for running onImageError and onImageLoad Functions since Background Image doesn't have the functionality */} {this.props.widgetName} )} ); } renderImageControl = () => { const { enableDownload, enableRotation } = this.props; const { showImageControl } = this.state; if (showImageControl && (enableRotation || enableDownload)) { return ( {enableRotation && ( <>
)} {enableDownload && (
)}
); } }; handleImageRotate = (rotateRight: boolean) => (e: any) => { const { imageRotation } = this.state; const nextRotation = rotateRight ? imageRotation + 90 : imageRotation - 90; this.setState({ imageRotation: nextRotation % 360 }); if (!!e) { e.preventDefault(); e.stopPropagation(); } }; handleImageDownload = (e: any) => { const { imageUrl, widgetId } = this.props; const fileName = `${widgetId}-download`; const xhr = new XMLHttpRequest(); xhr.open("GET", imageUrl, true); xhr.responseType = "blob"; xhr.onload = function() { const urlCreator = window.URL || window.webkitURL; const imageUrl = urlCreator.createObjectURL(this.response); const tag = document.createElement("a"); tag.href = imageUrl; tag.download = fileName; document.body.appendChild(tag); tag.click(); document.body.removeChild(tag); window.URL.revokeObjectURL(imageUrl); }; xhr.send(); if (!!e) { e.preventDefault(); e.stopPropagation(); } }; onMouseEnter = () => this.setState({ showImageControl: true }); onMouseLeave = () => this.setState({ showImageControl: false }); onImageError = () => { this.setState({ imageError: true, }); }; onImageLoad = () => { this.setState({ imageError: false, }); }; } export interface ImageComponentProps extends ComponentProps { imageUrl: string; defaultImageUrl: string; isLoading: boolean; showHoverPointer?: boolean; maxZoomLevel: number; enableRotation?: boolean; enableDownload?: boolean; objectFit: string; disableDrag: (disabled: boolean) => void; onClick?: (event: React.MouseEvent) => void; } export default ImageComponent;