* feat(Download ScreenShot Dialog) - UI part of it * feat(Download ScreenShot Dialog) - UI part of it * feat(Download ScreenShot Dialog) - Missing Annotations showing controls * feat(Download ScreenShot Dialog) - Missing Annotations showing controls * feat(Download ScreenShot Dialog) - Partial Pull Request - Not ready to Prod yet. * feat(Download ScreenShot Dialog) - UI - v2 * feat(Download ScreenShot Dialog) - UI - v3 * feat(Download ScreenShot Dialog) - Resizing tool * feat(Download ScreenShot Dialog) - Resizing tool * feat(Download ScreenShot Dialog) - Removing hardcoded CSS left * feat(Download ScreenShot Dialog) - Bugfixes * feat(Download ScreenShot Dialog) - Bugfixes - Escape closes the modal * feat(Download ScreenShot Dialog) - Bugfixes - Responsive issues * WIP: Refactoring DownloadDialog * WIP: Refactoring DownloadDialog * Align label and hide background canvas * Add constantes and remove code not used * Reset state on close modal * Extract cornerstone to connect * Fix modal zindex and add custom canvas classname * CR Update: Add proptypes and fix useeffect warning * CR Update: Refactor hooks, styles and imports * CR Update: Remove important from css * CR Update: Update core utils test of index
336 lines
8.5 KiB
JavaScript
336 lines
8.5 KiB
JavaScript
import React, { useEffect, useState, createRef } from 'react';
|
|
import Modal from 'react-bootstrap-modal';
|
|
import PropTypes from 'prop-types';
|
|
|
|
import './DownloadDialog.styl';
|
|
import { TextInput, Select } from '@ohif/ui';
|
|
import { withTranslation } from '../../utils/LanguageProvider';
|
|
|
|
const FILE_TYPE_OPTIONS = [
|
|
{
|
|
key: 'jpg',
|
|
value: 'jpg',
|
|
},
|
|
{
|
|
key: 'png',
|
|
value: 'png',
|
|
},
|
|
];
|
|
|
|
const DEFAULT_FILENAME = 'image';
|
|
|
|
const DownloadDialog = ({
|
|
t,
|
|
isOpen,
|
|
activeViewport,
|
|
onClose,
|
|
updateViewportPreview,
|
|
enableViewport,
|
|
disableViewport,
|
|
toggleAnnotations,
|
|
loadImage,
|
|
downloadBlob,
|
|
defaultSize,
|
|
minimumSize,
|
|
canvasClass,
|
|
}) => {
|
|
const [filename, setFilename] = useState(DEFAULT_FILENAME);
|
|
const [fileType, setFileType] = useState('jpg');
|
|
|
|
const [height, setHeight] = useState(defaultSize);
|
|
const [width, setWidth] = useState(defaultSize);
|
|
|
|
const [showAnnotations, setShowAnnotations] = useState(true);
|
|
|
|
const [keepAspect, setKeepAspect] = useState(true);
|
|
const [lastImage, setLastImage] = useState();
|
|
|
|
const [viewportElement, setViewportElement] = useState();
|
|
const [viewportElementHeight, setViewportElementHeight] = useState(
|
|
minimumSize
|
|
);
|
|
const [viewportElementWidth, setViewportElementWidth] = useState(minimumSize);
|
|
|
|
const [downloadCanvas, setDownloadCanvas] = useState({
|
|
ref: createRef(),
|
|
width: minimumSize,
|
|
height: minimumSize,
|
|
});
|
|
|
|
const [viewportPreview, setViewportPreview] = useState({
|
|
src: null,
|
|
width: minimumSize,
|
|
height: minimumSize,
|
|
});
|
|
|
|
useEffect(() => {
|
|
enableViewport(viewportElement);
|
|
|
|
return () => {
|
|
disableViewport(viewportElement);
|
|
|
|
setHeight(defaultSize);
|
|
setWidth(defaultSize);
|
|
};
|
|
}, [defaultSize, disableViewport, enableViewport, viewportElement]);
|
|
|
|
useEffect(() => {
|
|
const loadAndUpdateViewports = async () => {
|
|
const {
|
|
image,
|
|
width: scaledWidth,
|
|
height: scaledHeight,
|
|
} = await loadImage(activeViewport, viewportElement, width, height);
|
|
|
|
setLastImage(image);
|
|
|
|
toggleAnnotations(showAnnotations, viewportElement);
|
|
|
|
setViewportElementHeight(scaledHeight);
|
|
setViewportElementWidth(scaledWidth);
|
|
|
|
setDownloadCanvas(state => ({
|
|
...state,
|
|
height: scaledHeight,
|
|
width: scaledWidth,
|
|
}));
|
|
|
|
const {
|
|
dataUrl,
|
|
width: viewportElementWidth,
|
|
height: viewportElementHeight,
|
|
} = await updateViewportPreview(
|
|
viewportElement,
|
|
downloadCanvas.ref.current,
|
|
fileType
|
|
);
|
|
|
|
setViewportPreview(state => ({
|
|
...state,
|
|
src: dataUrl,
|
|
width: viewportElementWidth,
|
|
height: viewportElementHeight,
|
|
}));
|
|
};
|
|
|
|
loadAndUpdateViewports();
|
|
}, [
|
|
activeViewport,
|
|
viewportElement,
|
|
showAnnotations,
|
|
height,
|
|
width,
|
|
loadImage,
|
|
toggleAnnotations,
|
|
updateViewportPreview,
|
|
fileType,
|
|
downloadCanvas.ref,
|
|
]);
|
|
|
|
const onHeightChange = () => {
|
|
const newHeight = event.target.value;
|
|
setHeight(newHeight);
|
|
|
|
setViewportElementHeight(newHeight);
|
|
|
|
setDownloadCanvas(state => ({
|
|
...state,
|
|
height: newHeight,
|
|
}));
|
|
|
|
if (keepAspect) {
|
|
const multiplier = newHeight / lastImage.height;
|
|
const newWidth = Math.round(lastImage.width * multiplier);
|
|
|
|
setWidth(newWidth);
|
|
setViewportElementWidth(newWidth);
|
|
|
|
setDownloadCanvas(state => ({
|
|
...state,
|
|
width: newWidth,
|
|
}));
|
|
}
|
|
};
|
|
|
|
const onWidthChange = event => {
|
|
const newWidth = event.target.value;
|
|
setWidth(newWidth);
|
|
|
|
setViewportElementWidth(newWidth);
|
|
|
|
setDownloadCanvas(state => ({
|
|
...state,
|
|
width: newWidth,
|
|
}));
|
|
|
|
if (keepAspect) {
|
|
const multiplier = newWidth / lastImage.width;
|
|
const newHeight = Math.round(lastImage.height * multiplier);
|
|
|
|
setHeight(newHeight);
|
|
setViewportElementHeight(newHeight);
|
|
|
|
setDownloadCanvas(state => ({
|
|
...state,
|
|
height: newHeight,
|
|
}));
|
|
}
|
|
};
|
|
|
|
const downloadImage = () => {
|
|
downloadBlob(
|
|
filename || DEFAULT_FILENAME,
|
|
fileType,
|
|
viewportElement,
|
|
downloadCanvas.ref.current
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
show={isOpen}
|
|
onHide={onClose}
|
|
aria-labelledby="ModalHeader"
|
|
className="DownloadDialog modal fade themed in"
|
|
backdrop={false}
|
|
large={true}
|
|
keyboard={true}
|
|
>
|
|
<Modal.Header closeButton>
|
|
<Modal.Title>{t('Download High Quality Image')}</Modal.Title>
|
|
</Modal.Header>
|
|
<Modal.Body>
|
|
<div className="title">
|
|
{t(
|
|
'Please specify the dimensions, filename, and desired type for the output image.'
|
|
)}
|
|
</div>
|
|
|
|
<div className="file-info-container">
|
|
<div className="col">
|
|
<div className="width">
|
|
<TextInput
|
|
type="number"
|
|
min={minimumSize}
|
|
value={width}
|
|
label={t('Image width (px)')}
|
|
onChange={onWidthChange}
|
|
/>
|
|
</div>
|
|
<div className="height">
|
|
<TextInput
|
|
type="number"
|
|
min={minimumSize}
|
|
value={height}
|
|
label={t('Image height (px)')}
|
|
onChange={onHeightChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col">
|
|
<div className="file-name">
|
|
<TextInput
|
|
type="text"
|
|
value={filename}
|
|
onChange={event => setFilename(event.target.value)}
|
|
label={t('File name')}
|
|
id="file-name"
|
|
/>
|
|
</div>
|
|
<div className="file-type">
|
|
<Select
|
|
value={fileType}
|
|
onChange={event => setFileType(event.target.value)}
|
|
options={FILE_TYPE_OPTIONS}
|
|
label={t('File type')}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col">
|
|
<div className="show-annotations">
|
|
<label htmlFor="show-annotations" className="form-check-label">
|
|
<input
|
|
id="show-annotations"
|
|
type="checkbox"
|
|
className="form-check-input"
|
|
checked={showAnnotations}
|
|
onChange={event => setShowAnnotations(event.target.checked)}
|
|
/>
|
|
{t('Show Annotations')}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
height: viewportElementHeight,
|
|
width: viewportElementWidth,
|
|
position: 'absolute',
|
|
left: '9999px',
|
|
}}
|
|
ref={ref => setViewportElement(ref)}
|
|
>
|
|
<canvas
|
|
className={canvasClass}
|
|
style={{
|
|
height: downloadCanvas.height,
|
|
width: downloadCanvas.width,
|
|
display: 'block',
|
|
}}
|
|
width={downloadCanvas.width}
|
|
height={downloadCanvas.height}
|
|
ref={downloadCanvas.ref}
|
|
></canvas>
|
|
</div>
|
|
|
|
<div className="preview">
|
|
<h4> {t('Image Preview')}</h4>
|
|
<img
|
|
className="viewport-preview"
|
|
src={viewportPreview.src}
|
|
alt="Viewport Preview"
|
|
style={{
|
|
height: viewportPreview.height,
|
|
width: viewportPreview.width,
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div className="actions">
|
|
<div className="action-cancel">
|
|
<button type="button" className="btn btn-danger" onClick={onClose}>
|
|
{t('Cancel')}
|
|
</button>
|
|
</div>
|
|
<div className="action-save">
|
|
<button onClick={downloadImage} className="btn btn-primary">
|
|
{t('Download')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal.Body>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
DownloadDialog.propTypes = {
|
|
t: PropTypes.func.isRequired,
|
|
isOpen: PropTypes.bool.isRequired,
|
|
activeViewport: PropTypes.object,
|
|
onClose: PropTypes.func.isRequired,
|
|
updateViewportPreview: PropTypes.func.isRequired,
|
|
enableViewport: PropTypes.func.isRequired,
|
|
disableViewport: PropTypes.func.isRequired,
|
|
toggleAnnotations: PropTypes.func.isRequired,
|
|
loadImage: PropTypes.func.isRequired,
|
|
downloadBlob: PropTypes.func.isRequired,
|
|
defaultSize: PropTypes.number.isRequired,
|
|
minimumSize: PropTypes.number.isRequired,
|
|
canvasClass: PropTypes.string.isRequired,
|
|
};
|
|
|
|
export default withTranslation('DownloadDialog')(DownloadDialog);
|