From 98480d134bb67b1d3d39b56dac23e4a592381657 Mon Sep 17 00:00:00 2001 From: Bruno Alves de Faria Date: Wed, 17 Jan 2018 13:59:07 -0200 Subject: [PATCH] Implementing image download dialog behaviors --- .../client/components/bootstrap/index.js | 1 + .../components/bootstrap/input/number.html | 14 + .../ohif-core/client/ui/dialog/display.js | 2 +- Packages/ohif-design/styles/common/form.styl | 4 +- .../ohif-design/styles/common/global.styl | 3 + .../client/compatibility/imageDownload.js | 15 ++ .../client/compatibility/index.js | 1 + .../viewer/downloadDialog/downloadDialog.html | 75 ++---- .../viewer/downloadDialog/downloadDialog.js | 242 ++++++++---------- .../viewer/downloadDialog/downloadDialog.styl | 45 +--- .../client/lib/viewportUtils.js | 7 +- 11 files changed, 190 insertions(+), 219 deletions(-) create mode 100644 Packages/ohif-core/client/components/bootstrap/input/number.html create mode 100644 Packages/ohif-lesiontracker/client/compatibility/imageDownload.js diff --git a/Packages/ohif-core/client/components/bootstrap/index.js b/Packages/ohif-core/client/components/bootstrap/index.js index cf5e5ed2f..00f5655ae 100644 --- a/Packages/ohif-core/client/components/bootstrap/index.js +++ b/Packages/ohif-core/client/components/bootstrap/index.js @@ -26,6 +26,7 @@ import './form/link.html'; import './input/checkbox.html'; import './input/hidden.html'; import './input/groupRadio.html'; +import './input/number.html'; import './input/password.html'; import './input/radio.html'; import './input/range.html'; diff --git a/Packages/ohif-core/client/components/bootstrap/input/number.html b/Packages/ohif-core/client/components/bootstrap/input/number.html new file mode 100644 index 000000000..2d31b0a2c --- /dev/null +++ b/Packages/ohif-core/client/components/bootstrap/input/number.html @@ -0,0 +1,14 @@ + diff --git a/Packages/ohif-core/client/ui/dialog/display.js b/Packages/ohif-core/client/ui/dialog/display.js index 21930f4d1..9f30104b6 100644 --- a/Packages/ohif-core/client/ui/dialog/display.js +++ b/Packages/ohif-core/client/ui/dialog/display.js @@ -65,7 +65,7 @@ OHIF.ui.showDialog = (templateName, dialogData={}) => { $body.off('ohif.navigated', navigationHandler); }; - promise.then(dismissModal).catch(dismissModal); + promise.then(() => dismissModal(false)).catch(() => dismissModal(false)); // Dismiss the modal if navigation occurs and it should not be kept opened if (!dialogData.keepOpenOnNavigation) { diff --git a/Packages/ohif-design/styles/common/form.styl b/Packages/ohif-design/styles/common/form.styl index d5eedfa16..83c12d01c 100644 --- a/Packages/ohif-design/styles/common/form.styl +++ b/Packages/ohif-design/styles/common/form.styl @@ -15,7 +15,7 @@ label.wrapperLabel:not(.checkboxLabel) .form-themed - .btn, input[type=text], input[type=password] + .btn, input[type=text], input[type=password], input[type=number] &[disabled], &.disabled @@ -28,7 +28,7 @@ label.wrapperLabel:not(.checkboxLabel) theme('color', '$textPrimaryColor') cursor: auto - input[type=text], input[type=password] + input[type=text], input[type=password], input[type=number] theme('background-color', '$uiGray') theme('border-color', '$uiBorderColor') theme('color', '$textPrimaryColor') diff --git a/Packages/ohif-design/styles/common/global.styl b/Packages/ohif-design/styles/common/global.styl index ae8a24f77..55cd0346c 100644 --- a/Packages/ohif-design/styles/common/global.styl +++ b/Packages/ohif-design/styles/common/global.styl @@ -149,3 +149,6 @@ body .select2-container--default .select2-results>.select2-results__options .flex-grow flex-grow: 1 + +.nowrap + white-space: nowrap diff --git a/Packages/ohif-lesiontracker/client/compatibility/imageDownload.js b/Packages/ohif-lesiontracker/client/compatibility/imageDownload.js new file mode 100644 index 000000000..a8529671f --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/imageDownload.js @@ -0,0 +1,15 @@ +import { OHIF } from 'meteor/ohif:core'; + +OHIF.viewerbase.getImageDownloadDialogAnnotationTools = () => { + const { measurementTools } = OHIF.measurements.MeasurementApi.getConfiguration(); + + const resultSet = new Set(); + Object.values(measurementTools).forEach(toolGroup => { + toolGroup.childTools.forEach(tool => { + if (tool.childTools) return; + resultSet.add(tool.cornerstoneToolType); + }); + }); + + return Array.from(resultSet); +}; diff --git a/Packages/ohif-lesiontracker/client/compatibility/index.js b/Packages/ohif-lesiontracker/client/compatibility/index.js index f1fc36855..5babe4392 100644 --- a/Packages/ohif-lesiontracker/client/compatibility/index.js +++ b/Packages/ohif-lesiontracker/client/compatibility/index.js @@ -1,4 +1,5 @@ import './bidirectionalTool'; +import './imageDownload.js'; import './nonTargetTool.js'; import './scaleOverlayTool.js'; import './deleteLesionKeyboardTool.js'; diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html index 2b627ec92..0f5cac96c 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.html @@ -4,7 +4,8 @@ class='themed' dialogClass='modal-lg' formClass='form-themed' - confirmLabel='Download' + confirmLabel='Download Image' + confirmCallback=instance.getConfirmCallback cancelLabel='Cancel' id='imageDownloadDialog' schema=instance.schema @@ -16,8 +17,8 @@ - - + + - - + +
Width{{>inputText key='width'}}Image width (px){{>inputNumber key='width' min="1" max="16384"}}
Height{{>inputText key='height'}}Image height (px){{>inputNumber key='height' min="1" max="16384"}}
@@ -40,56 +41,32 @@ File type - {{>inputSelect key='type'}} + {{>inputSelect key='type' hideSearch=true}} -
{{>inputCheckbox key='showAnnotations'}}
+
+ + + + + + {{#if showQuality}} + + + + + {{/if}} + +
{{>inputCheckbox key='showAnnotations'}}
Image quality (%){{>inputNumber key='quality' min="1" max="100"}}
+
- +
+

Image Preview

+ +
{{/dialogForm}} - diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js index 0a32928b7..0804ff759 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.js @@ -1,10 +1,15 @@ import { Template } from 'meteor/templating'; +import { Tracker } from 'meteor/tracker'; import { ReactiveVar } from 'meteor/reactive-var'; import { Session } from 'meteor/session'; import { $ } from 'meteor/jquery'; import { SimpleSchema } from 'meteor/aldeed:simple-schema'; import { OHIF } from 'meteor/ohif:core'; -import { cornerstone } from 'meteor/ohif:cornerstone'; +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; + +OHIF.viewerbase.getImageDownloadDialogAnnotationTools = () => { + return ['length', 'probe', 'simpleAngle', 'arrowAnnotate', 'ellipticalRoi', 'rectangleRoi']; +}; Template.imageDownloadDialog.onCreated(() => { const instance = Template.instance(); @@ -25,19 +30,30 @@ Template.imageDownloadDialog.onCreated(() => { showAnnotations: { type: Boolean, label: 'Show Annotations', - defaultValue: false + defaultValue: true + }, + quality: { + type: Number, + defaultValue: 100 } }); + instance.changeObserver = new Tracker.Dependency(); + instance.keepAspect = new ReactiveVar(true); instance.showAnnotations = new ReactiveVar(false); + + instance.lastImage = {}; + + instance.getConfirmCallback = () => () => { + instance.downloadImage(); + }; }); Template.imageDownloadDialog.onRendered(() => { const instance = Template.instance(); const { viewportUtils } = OHIF.viewerbase; - instance.quality = 1; instance.$viewportElement = instance.$('.viewport-element'); instance.viewportElement = instance.$viewportElement[0]; instance.$viewportPreview = instance.$('.viewport-preview'); @@ -53,14 +69,22 @@ Template.imageDownloadDialog.onRendered(() => { canvas[size] = value; canvas.style[size] = `${value}px`; - instance.form.item(size).value(value); + instance.form.item(size).$element.val(value); + }; + + instance.toggleAnnotations = toggle => { + const action = toggle ? 'enable' : 'disable'; + const annotationTools = OHIF.viewerbase.getImageDownloadDialogAnnotationTools(); + annotationTools.forEach(tool => cornerstoneTools[tool][action](instance.viewportElement)); }; instance.updateViewportPreview = () => { instance.$viewportElement.one('CornerstoneImageRendered', (event, enabledElement) => { + const formData = instance.form.value(); const image = instance.viewportPreview; - const type = 'image/' + instance.form.item('type').value(); - const dataUrl = instance.downloadCanvas.toDataURL(type, instance.quality); + const type = 'image/' + formData.type; + const quality = formData.type === 'png' ? 1 : formData.quality / 100; + const dataUrl = instance.downloadCanvas.toDataURL(type, quality); image.src = dataUrl; const $element = $(enabledElement.element); @@ -77,13 +101,30 @@ Template.imageDownloadDialog.onRendered(() => { }); }; + // TODO: Add quality parameter to cornerstoneTools' saveAs method + instance.downloadImage = () => { + const formData = instance.form.value(); + const link = document.createElement('a'); + link.download = `${formData.name}.${formData.type}`; + link.href = instance.viewportPreview.src; + + // Create a 'fake' click event to trigger the download + if (document.createEvent) { + const event = document.createEvent('MouseEvents'); + event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + } else if (link.fireEvent) { + link.fireEvent('onclick'); + } + }; + instance.autorun(() => { + instance.changeObserver.depend(); Session.get('UpdateDownloadViewport'); const activeViewport = viewportUtils.getActiveViewportElement(); if (activeViewport) { const enabledElement = cornerstone.getEnabledElement(activeViewport); - const { width, height } = enabledElement.image; const viewport = Object.assign({}, enabledElement.viewport); delete viewport.scale; @@ -93,140 +134,83 @@ Template.imageDownloadDialog.onRendered(() => { }; cornerstone.loadImage(enabledElement.image.imageId).then(image => { - cornerstone.displayImage(instance.viewportElement, image); - cornerstone.setViewport(instance.viewportElement, viewport); - cornerstone.resize(instance.viewportElement, true); + instance.lastImage = image; + const { viewportElement, downloadCanvas } = instance; + const formData = instance.form.value(); - instance.setElementSize(instance.viewportElement, instance.downloadCanvas, 'width', width); - instance.setElementSize(instance.viewportElement, instance.downloadCanvas, 'height', height); + cornerstone.displayImage(viewportElement, image); + cornerstone.setViewport(viewportElement, viewport); + cornerstone.resize(viewportElement, true); - cornerstone.fitToWindow(instance.viewportElement); + instance.toggleAnnotations(formData.showAnnotations); + + const width = Math.min(formData.width || image.width, 16384); + const height = Math.min(formData.height || image.height, 16384); + instance.setElementSize(viewportElement, downloadCanvas, 'width', width); + instance.setElementSize(viewportElement, downloadCanvas, 'height', height); + + cornerstone.fitToWindow(viewportElement); instance.updateViewportPreview(); }); } }); }); -Template.imageDownloadDialog.helpers({ - keepAspect() { - return Template.instance().keepAspect.get(); - } -}); - Template.imageDownloadDialog.events({ 'click .js-keep-aspect'(event, instance) { const currentState = instance.keepAspect.get(); instance.keepAspect.set(!currentState); + instance.$('[data-key=width]').trigger('input'); + }, + + 'change [data-key=showAnnotations], change [data-key=type]'(event, instance) { + instance.changeObserver.changed(); + }, + + 'input [data-key=quality]'(event, instance) { + instance.changeObserver.changed(); + }, + + 'input [data-key=width]'(event, instance) { + const { viewportElement, downloadCanvas } = instance; + const formData = instance.form.value(); + const { width, height } = instance.lastImage; + const newWidth = formData.width; + instance.setElementSize(viewportElement, downloadCanvas, 'width', newWidth); + if (instance.keepAspect.get()) { + const multiplier = newWidth / width; + const newHeight = Math.round(height * multiplier); + instance.setElementSize(viewportElement, downloadCanvas, 'height', newHeight); + } + + instance.changeObserver.changed(); + }, + + 'input [data-key=height]'(event, instance) { + const { viewportElement, downloadCanvas } = instance; + const formData = instance.form.value(); + const { width, height } = instance.lastImage; + const newHeight = formData.height; + instance.setElementSize(viewportElement, downloadCanvas, 'height', newHeight); + if (instance.keepAspect.get()) { + const multiplier = newHeight / height; + const newWidth = Math.round(width * multiplier); + instance.setElementSize(viewportElement, downloadCanvas, 'width', newWidth); + } + + instance.changeObserver.changed(); } }); -// import { Template } from 'meteor/templating'; -// import { Session } from 'meteor/session'; -// import { OHIF } from 'meteor/ohif:core'; -// import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; -// -// function setElementSize(element, canvas, size, value) { -// $(element)[size](value); -// canvas[size] = value; -// canvas.style[size] = `${value}px`; -// } -// -// Template.imageDownloadDialog.onCreated(() => { -// const instance = Template.instance(); -// const { viewportUtils } = OHIF.viewerbase; -// -// instance.autorun(() => { -// Session.get('UpdateDownloadViewport'); -// const activeViewport = viewportUtils.getActiveViewportElement(); -// -// if (activeViewport) { -// const enabledElement = cornerstone.getEnabledElement(activeViewport); -// -// cornerstone.loadImage(enabledElement.image.imageId).then(function(image) { -// cornerstone.displayImage(instance.$previewElement, image); -// cornerstone.displayImage(instance.$downloadElement, image); -// cornerstone.resize(instance.$previewElement, true); -// -// setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'width', 300); -// setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'height', 200); -// -// cornerstone.fitToWindow(instance.$downloadElement); -// }); -// } -// }); -// }); -// -// Template.imageDownloadDialog.onRendered(() => { -// const instance = Template.instance(); -// const $dialog = instance.$('#imageDownloadDialog'); -// -// instance.$previewElement = $('#previewElement')[0]; -// instance.$downloadElement = document.createElement('div'); -// instance.availableTools = ['length', 'probe', 'simpleAngle', 'arrowAnnotate', 'ellipticalRoi', 'rectangleRoi']; -// instance.showAnnotations = false; -// -// cornerstone.enable(instance.$previewElement); -// cornerstone.enable(instance.$downloadElement); -// instance.$downloadCanvas = $(instance.$downloadElement).find('canvas')[0]; -// -// // Make the dialog bounded and draggable -// $dialog.draggable({ defaultElementCursor: 'move' }); -// -// // Polyfill for older browsers -// dialogPolyfill.registerDialog($dialog.get(0)); -// -// // // Prevent dialog from being dragged when user clicks any button -// const $controls = $dialog.find('.form-group, .instructions'); -// $controls.on('mousedown touchstart', event => event.stopPropagation()); -// }); -// -// Template.imageDownloadDialog.events({ -// 'change .form-group input[name=width]'(event, instance){ -// const width = $(event.currentTarget).val(); -// -// setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'width', width); -// cornerstone.fitToWindow(instance.$downloadElement); -// }, -// -// 'change .form-group input[name=height]'(event, instance){ -// const height = $(event.currentTarget).val(); -// -// setElementSize(instance.$downloadElement, instance.$downloadCanvas, 'height', height); -// cornerstone.fitToWindow(instance.$downloadElement); -// }, -// -// 'change #imageDownloadDialog .form-group .form-check input[type=checkbox]'(event, instance) { -// const $previewElement = instance.$previewElement; -// const $downloadElement = instance.$downloadElement; -// -// instance.showAnnotations = !instance.showAnnotations; -// -// const action = (instance.showAnnotations) ? 'enable' : 'disable'; -// -// instance.availableTools.forEach(tool => { -// cornerstoneTools[tool][action]($previewElement); -// cornerstoneTools[tool][action]($downloadElement); -// }); -// }, -// -// 'click .dropdown-menu .dropdown-item'(event, instance) { -// const extension = $(event.currentTarget).text(); -// const $extensionButton = $('.btn.extension'); -// -// $extensionButton.text(extension); -// }, -// -// 'click button.download'(event, instance) { -// const fileName = $('.fileName').val(); -// const extension = $('.btn.extension').text().trim(); -// -// if (!fileName || !extension) return; -// -// cornerstoneTools.saveAs(instance.$downloadElement, `${fileName}.${extension}`, `image/${extension}`); -// }, -// -// 'click button.cancel'(event, instance) { -// const { viewportUtils } = OHIF.viewerbase; -// viewportUtils.toggleDownloadDialog(); -// } -// }); +Template.imageDownloadDialog.helpers({ + keepAspect() { + return Template.instance().keepAspect.get(); + }, + + showQuality() { + const instance = Template.instance(); + instance.changeObserver.depend(); + if (!instance.form) return true; + return instance.form.item('type').value() === 'jpeg'; + } +}); diff --git a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl index 6b9c235ec..d884f04bd 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl +++ b/Packages/ohif-viewerbase/client/components/viewer/downloadDialog/downloadDialog.styl @@ -1,43 +1,14 @@ -@import "{ohif:design}/app" +@require '{ohif:design}/app' #imageDownloadDialog + .image-preview + display: table + margin: 0 auto + + h3 + white-space: nowrap + .viewport-preview max-width: 512px max-height: 512px - - - - .instructions - margin-bottom: 20px - text-align: center - - .extension - color: #000000 - background-color: #888 - - .col-form-label - margin-top: 5px - - .dropdown-menu .dropdown-item - display: block; - width: 100%; - padding: 3px 1.5rem; - clear: both; - font-weight: 400; - color: #292b2c; - text-align: inherit; - white-space: nowrap; - background: 0 0; - border: 0; - - #previewElement, #downloadElement - width: 300px - height: 200px - margin: 0 auto - - #downloadElement - display: none - - .button-holder - margin-top: 15px diff --git a/Packages/ohif-viewerbase/client/lib/viewportUtils.js b/Packages/ohif-viewerbase/client/lib/viewportUtils.js index 97a66113c..2d21ece3c 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportUtils.js +++ b/Packages/ohif-viewerbase/client/lib/viewportUtils.js @@ -229,7 +229,12 @@ const toggleCineDialog = () => { const toggleDownloadDialog = () => { stopActiveClip(); - OHIF.ui.showDialog('imageDownloadDialog'); + const $dialog = $('#imageDownloadDialog'); + if ($dialog.length) { + $dialog.find('.close:first').click(); + } else { + OHIF.ui.showDialog('imageDownloadDialog'); + } }; const isDownloadEnabled = () => {