2019-11-05 05:09:50 +00:00
|
|
|
import * as React from "react";
|
|
|
|
|
import { ComponentProps } from "./BaseComponent";
|
|
|
|
|
import styled from "styled-components";
|
2020-10-29 11:14:39 +00:00
|
|
|
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
|
2021-07-08 10:40:22 +00:00
|
|
|
import { Colors } from "constants/Colors";
|
2019-11-05 05:09:50 +00:00
|
|
|
|
2020-03-27 09:02:11 +00:00
|
|
|
export interface StyledImageProps {
|
2019-11-14 17:06:32 +00:00
|
|
|
defaultImageUrl: string;
|
2021-07-08 10:40:22 +00:00
|
|
|
enableRotation?: boolean;
|
2020-03-27 09:02:11 +00:00
|
|
|
imageUrl?: string;
|
|
|
|
|
backgroundColor?: string;
|
2020-09-18 10:12:57 +00:00
|
|
|
showHoverPointer?: boolean;
|
2021-06-29 13:30:14 +00:00
|
|
|
objectFit: string;
|
2020-09-18 10:12:57 +00:00
|
|
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
2019-11-14 17:06:32 +00:00
|
|
|
}
|
|
|
|
|
|
2020-04-03 04:48:40 +00:00
|
|
|
export const StyledImage = styled.div<
|
|
|
|
|
StyledImageProps & {
|
|
|
|
|
imageError: boolean;
|
|
|
|
|
}
|
|
|
|
|
>`
|
2020-03-27 09:02:11 +00:00
|
|
|
position: relative;
|
2021-06-29 13:30:14 +00:00
|
|
|
display: flex;
|
2020-03-27 09:02:11 +00:00
|
|
|
flex-direction: "row";
|
2021-07-09 08:05:14 +00:00
|
|
|
background-size: ${(props) => props.objectFit ?? "contain"};
|
2020-12-24 04:32:25 +00:00
|
|
|
cursor: ${(props) =>
|
2020-09-18 10:12:57 +00:00
|
|
|
props.showHoverPointer && props.onClick ? "pointer" : "inherit"};
|
2020-12-24 04:32:25 +00:00
|
|
|
background: ${(props) => props.backgroundColor};
|
2021-06-29 13:30:14 +00:00
|
|
|
background-image: ${(props) =>
|
|
|
|
|
`url(${props.imageError ? props.defaultImageUrl : props.imageUrl})`};
|
2020-03-27 09:02:11 +00:00
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: 100%;
|
2019-11-05 05:09:50 +00:00
|
|
|
`;
|
|
|
|
|
|
2020-10-29 11:14:39 +00:00
|
|
|
const Wrapper = styled.div`
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: 100%;
|
|
|
|
|
.react-transform-element,
|
|
|
|
|
.react-transform-component {
|
|
|
|
|
height: 100%;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
2021-07-08 10:40:22 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
2020-10-29 11:14:39 +00:00
|
|
|
enum ZoomingState {
|
|
|
|
|
MAX_ZOOMED_OUT = "MAX_ZOOMED_OUT",
|
|
|
|
|
MAX_ZOOMED_IN = "MAX_ZOOMED_IN",
|
|
|
|
|
}
|
2020-04-03 04:48:40 +00:00
|
|
|
class ImageComponent extends React.Component<
|
|
|
|
|
ImageComponentProps,
|
|
|
|
|
{
|
|
|
|
|
imageError: boolean;
|
2021-07-08 10:40:22 +00:00
|
|
|
showImageControl: boolean;
|
|
|
|
|
imageRotation: number;
|
2020-10-29 11:14:39 +00:00
|
|
|
zoomingState: ZoomingState;
|
2020-04-03 04:48:40 +00:00
|
|
|
}
|
|
|
|
|
> {
|
2020-11-02 09:51:14 +00:00
|
|
|
isPanning: boolean;
|
2020-04-03 04:48:40 +00:00
|
|
|
constructor(props: ImageComponentProps) {
|
|
|
|
|
super(props);
|
2020-11-02 09:51:14 +00:00
|
|
|
this.isPanning = false;
|
2020-04-03 04:48:40 +00:00
|
|
|
this.state = {
|
|
|
|
|
imageError: false,
|
2021-07-08 10:40:22 +00:00
|
|
|
showImageControl: false,
|
|
|
|
|
imageRotation: 0,
|
2020-10-29 11:14:39 +00:00
|
|
|
zoomingState: ZoomingState.MAX_ZOOMED_OUT,
|
2020-04-03 04:48:40 +00:00
|
|
|
};
|
|
|
|
|
}
|
2019-11-05 05:09:50 +00:00
|
|
|
render() {
|
2020-10-29 11:14:39 +00:00
|
|
|
const { maxZoomLevel } = this.props;
|
2021-07-08 10:40:22 +00:00
|
|
|
const { imageRotation } = this.state;
|
2020-11-02 09:51:14 +00:00
|
|
|
const zoomActive =
|
|
|
|
|
maxZoomLevel !== undefined && maxZoomLevel > 1 && !this.isPanning;
|
2020-10-29 11:14:39 +00:00
|
|
|
const isZoomingIn = this.state.zoomingState === ZoomingState.MAX_ZOOMED_OUT;
|
|
|
|
|
let cursor = "inherit";
|
|
|
|
|
if (zoomActive) {
|
|
|
|
|
cursor = isZoomingIn ? "zoom-in" : "zoom-out";
|
|
|
|
|
}
|
2019-12-03 04:41:10 +00:00
|
|
|
return (
|
2021-07-08 10:40:22 +00:00
|
|
|
<Wrapper
|
|
|
|
|
onMouseEnter={this.onMouseEnter}
|
|
|
|
|
onMouseLeave={this.onMouseLeave}
|
|
|
|
|
>
|
2020-10-29 11:14:39 +00:00
|
|
|
<TransformWrapper
|
|
|
|
|
defaultScale={1}
|
2021-04-28 10:28:39 +00:00
|
|
|
doubleClick={{
|
|
|
|
|
disabled: true,
|
2020-10-29 11:14:39 +00:00
|
|
|
}}
|
2020-11-02 09:51:14 +00:00
|
|
|
onPanning={() => {
|
|
|
|
|
this.isPanning = true;
|
|
|
|
|
}}
|
2021-04-28 10:28:39 +00:00
|
|
|
onPanningStart={() => {
|
|
|
|
|
this.props.disableDrag(true);
|
|
|
|
|
}}
|
2020-11-02 09:51:14 +00:00
|
|
|
onPanningStop={() => {
|
|
|
|
|
this.props.disableDrag(false);
|
|
|
|
|
}}
|
2020-10-29 11:14:39 +00:00
|
|
|
onZoomChange={(zoom: any) => {
|
|
|
|
|
if (zoomActive) {
|
2020-11-02 09:51:14 +00:00
|
|
|
//Check max zoom
|
2020-10-29 11:14:39 +00:00
|
|
|
if (
|
|
|
|
|
maxZoomLevel === zoom.scale &&
|
|
|
|
|
// Added for preventing infinite loops
|
|
|
|
|
this.state.zoomingState !== ZoomingState.MAX_ZOOMED_IN
|
|
|
|
|
) {
|
|
|
|
|
this.setState({
|
|
|
|
|
zoomingState: ZoomingState.MAX_ZOOMED_IN,
|
|
|
|
|
});
|
2020-11-02 09:51:14 +00:00
|
|
|
// Check min zoom
|
2020-10-29 11:14:39 +00:00
|
|
|
} else if (
|
|
|
|
|
zoom.scale === 1 &&
|
|
|
|
|
this.state.zoomingState !== ZoomingState.MAX_ZOOMED_OUT
|
|
|
|
|
) {
|
|
|
|
|
this.setState({
|
|
|
|
|
zoomingState: ZoomingState.MAX_ZOOMED_OUT,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-03 04:48:40 +00:00
|
|
|
}}
|
2021-04-28 10:28:39 +00:00
|
|
|
options={{
|
|
|
|
|
maxScale: maxZoomLevel,
|
|
|
|
|
disabled: !zoomActive,
|
|
|
|
|
transformEnabled: zoomActive,
|
|
|
|
|
}}
|
|
|
|
|
pan={{
|
|
|
|
|
disabled: !zoomActive,
|
|
|
|
|
}}
|
|
|
|
|
wheel={{
|
|
|
|
|
disabled: !zoomActive,
|
|
|
|
|
}}
|
2020-10-29 11:14:39 +00:00
|
|
|
>
|
2021-01-12 01:22:31 +00:00
|
|
|
{({ zoomIn, zoomOut }: any) => (
|
2021-07-08 10:40:22 +00:00
|
|
|
<>
|
|
|
|
|
{this.renderImageControl()}
|
|
|
|
|
<TransformComponent>
|
|
|
|
|
<StyledImage
|
|
|
|
|
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
|
|
|
|
imageError={this.state.imageError}
|
|
|
|
|
{...this.props}
|
|
|
|
|
data-testid="styledImage"
|
|
|
|
|
onClick={(event: React.MouseEvent<HTMLElement>) => {
|
|
|
|
|
if (!this.isPanning) {
|
|
|
|
|
if (isZoomingIn) {
|
|
|
|
|
zoomIn(event);
|
|
|
|
|
} else {
|
|
|
|
|
zoomOut(event);
|
|
|
|
|
}
|
|
|
|
|
this.props.onClick && this.props.onClick(event);
|
2020-10-29 11:14:39 +00:00
|
|
|
}
|
2021-07-08 10:40:22 +00:00
|
|
|
this.isPanning = false;
|
|
|
|
|
}}
|
2021-04-28 10:28:39 +00:00
|
|
|
style={{
|
2021-07-08 10:40:22 +00:00
|
|
|
cursor,
|
|
|
|
|
transform: `rotate(${imageRotation}deg)`,
|
2020-10-29 11:14:39 +00:00
|
|
|
}}
|
2021-07-08 10:40:22 +00:00
|
|
|
>
|
|
|
|
|
{/* Used for running onImageError and onImageLoad Functions since Background Image doesn't have the functionality */}
|
|
|
|
|
<img
|
|
|
|
|
alt={this.props.widgetName}
|
|
|
|
|
onError={this.onImageError}
|
|
|
|
|
onLoad={this.onImageLoad}
|
|
|
|
|
src={this.props.imageUrl}
|
|
|
|
|
style={{
|
|
|
|
|
display: "none",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</StyledImage>
|
|
|
|
|
</TransformComponent>
|
|
|
|
|
</>
|
2020-10-29 11:14:39 +00:00
|
|
|
)}
|
|
|
|
|
</TransformWrapper>
|
|
|
|
|
</Wrapper>
|
2019-12-03 04:41:10 +00:00
|
|
|
);
|
2019-11-05 05:09:50 +00:00
|
|
|
}
|
2020-04-03 04:48:40 +00:00
|
|
|
|
2021-07-08 10:40:22 +00:00
|
|
|
renderImageControl = () => {
|
|
|
|
|
const { enableDownload, enableRotation } = this.props;
|
|
|
|
|
const { showImageControl } = this.state;
|
|
|
|
|
|
|
|
|
|
if (showImageControl && (enableRotation || enableDownload)) {
|
|
|
|
|
return (
|
|
|
|
|
<ControlBtnWrapper>
|
|
|
|
|
{enableRotation && (
|
|
|
|
|
<>
|
|
|
|
|
<ControlBtn onClick={this.handleImageRotate(false)}>
|
|
|
|
|
<div>
|
|
|
|
|
<svg
|
|
|
|
|
fill="none"
|
|
|
|
|
height="12"
|
|
|
|
|
viewBox="0 0 12 12"
|
|
|
|
|
width="12"
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
>
|
|
|
|
|
<path
|
|
|
|
|
d="M2.28492 1.81862C3.27446 0.939565 4.57489 0.400391 6.00002 0.400391C9.08724 0.400391 11.6 2.91317 11.6 6.00039C11.6 9.08761 9.08724 11.6004 6.00002 11.6004C2.91281 11.6004 0.400024 9.08761 0.400024 6.00039H1.33336C1.33336 8.58317 3.41724 10.6671 6.00002 10.6671C8.58281 10.6671 10.6667 8.58317 10.6667 6.00039C10.6667 3.41761 8.58281 1.33372 6.00002 1.33372C4.82777 1.33372 3.76447 1.7682 2.94573 2.47943L4.13336 3.66706H1.33336V0.867057L2.28492 1.81862Z"
|
|
|
|
|
fill="#858282"
|
|
|
|
|
stroke="#858282"
|
|
|
|
|
strokeWidth="0.5"
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</ControlBtn>
|
|
|
|
|
<ControlBtn
|
|
|
|
|
className="separator"
|
|
|
|
|
onClick={this.handleImageRotate(true)}
|
|
|
|
|
>
|
|
|
|
|
<div>
|
|
|
|
|
<svg
|
|
|
|
|
fill="none"
|
|
|
|
|
height="12"
|
|
|
|
|
viewBox="0 0 12 12"
|
|
|
|
|
width="12"
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
>
|
|
|
|
|
<path
|
|
|
|
|
d="M0.400024 6.00039C0.400024 2.91317 2.91281 0.400391 6.00002 0.400391C7.42515 0.400391 8.72559 0.939565 9.71513 1.81862L10.6667 0.867057V3.66706H7.86669L9.05432 2.47943C8.23558 1.7682 7.17228 1.33372 6.00002 1.33372C3.41724 1.33372 1.33336 3.41761 1.33336 6.00039C1.33336 8.58317 3.41724 10.6671 6.00002 10.6671C8.58281 10.6671 10.6667 8.58317 10.6667 6.00039H11.6C11.6 9.08761 9.08724 11.6004 6.00002 11.6004C2.91281 11.6004 0.400024 9.08761 0.400024 6.00039Z"
|
|
|
|
|
fill="#858282"
|
|
|
|
|
stroke="#858282"
|
|
|
|
|
strokeWidth="0.5"
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</ControlBtn>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{enableDownload && (
|
|
|
|
|
<ControlBtn onClick={this.handleImageDownload}>
|
|
|
|
|
<div>
|
|
|
|
|
<svg fill="none" height="20" viewBox="0 0 20 20" width="20">
|
|
|
|
|
<path
|
|
|
|
|
clipRule="evenodd"
|
|
|
|
|
d="M15.4547 16.4284H13.117H6.88326H4.54559C2.8243 16.4284 1.42871 14.8933 1.42871 12.9999C1.42871 11.3987 2.43157 10.0641 3.7804 9.68786C3.93001 6.28329 6.47884 3.57129 9.61053 3.57129C12.7072 3.57129 15.2349 6.22243 15.4352 9.57386C17.183 9.56015 18.5716 11.1167 18.5716 12.9999C18.5716 14.8933 17.176 16.4284 15.4547 16.4284ZM12.7266 11.4286L9.99929 14.8572L7.27202 11.4286L8.83045 11.4286L8.83045 8.00004L11.1681 8.00003V11.4286L12.7266 11.4286Z"
|
|
|
|
|
fill="#939090"
|
|
|
|
|
fillRule="evenodd"
|
|
|
|
|
/>
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
</ControlBtn>
|
|
|
|
|
)}
|
|
|
|
|
</ControlBtnWrapper>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
2021-07-22 08:45:45 +00:00
|
|
|
const imageUrlObj = urlCreator.createObjectURL(this.response);
|
2021-07-08 10:40:22 +00:00
|
|
|
const tag = document.createElement("a");
|
2021-07-22 08:45:45 +00:00
|
|
|
tag.href = imageUrlObj;
|
2021-07-08 10:40:22 +00:00
|
|
|
tag.download = fileName;
|
|
|
|
|
document.body.appendChild(tag);
|
|
|
|
|
tag.click();
|
|
|
|
|
document.body.removeChild(tag);
|
2021-07-22 08:45:45 +00:00
|
|
|
window.URL.revokeObjectURL(imageUrlObj);
|
|
|
|
|
};
|
|
|
|
|
// if download fails open image in new tab
|
|
|
|
|
xhr.onerror = function() {
|
|
|
|
|
const tag = document.createElement("a");
|
|
|
|
|
tag.href = imageUrl;
|
|
|
|
|
tag.target = "_blank";
|
|
|
|
|
document.body.appendChild(tag);
|
|
|
|
|
tag.click();
|
|
|
|
|
document.body.removeChild(tag);
|
2021-07-08 10:40:22 +00:00
|
|
|
};
|
|
|
|
|
xhr.send();
|
|
|
|
|
|
|
|
|
|
if (!!e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMouseEnter = () => this.setState({ showImageControl: true });
|
|
|
|
|
|
|
|
|
|
onMouseLeave = () => this.setState({ showImageControl: false });
|
|
|
|
|
|
2020-04-03 04:48:40 +00:00
|
|
|
onImageError = () => {
|
|
|
|
|
this.setState({
|
|
|
|
|
imageError: true,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onImageLoad = () => {
|
|
|
|
|
this.setState({
|
|
|
|
|
imageError: false,
|
|
|
|
|
});
|
|
|
|
|
};
|
2019-11-05 05:09:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ImageComponentProps extends ComponentProps {
|
|
|
|
|
imageUrl: string;
|
|
|
|
|
defaultImageUrl: string;
|
2019-12-03 04:41:10 +00:00
|
|
|
isLoading: boolean;
|
2020-09-18 10:12:57 +00:00
|
|
|
showHoverPointer?: boolean;
|
2020-10-29 11:14:39 +00:00
|
|
|
maxZoomLevel: number;
|
2021-07-08 10:40:22 +00:00
|
|
|
enableRotation?: boolean;
|
|
|
|
|
enableDownload?: boolean;
|
2021-06-29 13:30:14 +00:00
|
|
|
objectFit: string;
|
2020-10-29 11:14:39 +00:00
|
|
|
disableDrag: (disabled: boolean) => void;
|
2020-09-18 10:12:57 +00:00
|
|
|
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
2019-11-05 05:09:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default ImageComponent;
|