diff --git a/package.json b/package.json index 9d13f653c2e66d9d14b16a8855e815dbad992277..f8dbd7d7edb68224bb627c46b88a387f4c461459 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "file-loader": "^1.1.11", "hammerjs": "^2.0.8", "html-webpack-plugin": "^3.2.0", + "html2canvas": "^1.0.0-rc.1", "jasmine": "^3.1.0", "jasmine-core": "^3.4.0", "jasmine-spec-reporter": "^4.2.1", diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 3dc71fc6024f009fb76916bbe70e0bb5753962cd..1cc074671e5d15a70883a22ae78ba102f3a90e47 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -86,7 +86,9 @@ </div> <div class="d-flex flex-row justify-content-end z-index-10 position-absolute pe-none w-100 h-100"> - <signin-banner signinWrapper> + <signin-banner + signinWrapper + [parcellationIsSelected]="selectedParcellation? true : false"> </signin-banner> </div> diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 90174a621c3419f467dd174f7905107f345a626c..f0d6719323a10c5fcb47d67b23eeeb240d5f0dc9 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -769,6 +769,15 @@ export class NehubaContainer implements OnInit, OnChanges, OnDestroy{ } }) ) + + // To get WebGL content when taking screenshot + HTMLCanvasElement.prototype.getContext = function(origFn) { + return function(type, attribs) { + attribs = attribs || {} + attribs.preserveDrawingBuffer = true + return origFn.call(this, type, attribs) + } + }(HTMLCanvasElement.prototype.getContext) } // datasetViewerRegistry : Set<string> = new Set() @@ -1360,4 +1369,4 @@ export const userLmUnchanged = (oldlms, newlms) => { export const calculateSliceZoomFactor = (originalZoom) => originalZoom ? 700 * originalZoom / Math.min(window.innerHeight, window.innerWidth) - : 1e7 \ No newline at end of file + : 1e7 diff --git a/src/ui/signinBanner/signinBanner.components.ts b/src/ui/signinBanner/signinBanner.components.ts index 45743948ec5e763251ccf8246c5b3d8823fe9d4e..7d7c624e4fdccc188d20d46e188e0b300d6a69a4 100644 --- a/src/ui/signinBanner/signinBanner.components.ts +++ b/src/ui/signinBanner/signinBanner.components.ts @@ -20,6 +20,7 @@ import { Store, select } from "@ngrx/store"; export class SigninBanner{ @Input() darktheme: boolean + @Input() parcellationIsSelected: boolean public user$: Observable<User> public userBtnTooltip$: Observable<string> diff --git a/src/ui/signinBanner/signinBanner.template.html b/src/ui/signinBanner/signinBanner.template.html index 45b57df89f55b5a7f6c5dbf7a1a321b66f2833ce..5b066d36399f9484f4c288fd184897df859f4f7a 100644 --- a/src/ui/signinBanner/signinBanner.template.html +++ b/src/ui/signinBanner/signinBanner.template.html @@ -2,6 +2,19 @@ [iav-key-listener]="keyListenerConfig" (iav-key-event)="openTmplWithDialog(helpComponent)"> + <!-- Take screenshot --> + <div class="btnWrapper" *ngIf="parcellationIsSelected"> + <button mat-icon-button + (click)="takeScreenshotElement.takingScreenshot = true; + takeScreenshotElement.previewingScreenshot = false; + takeScreenshotElement.croppedCanvas = null; + takeScreenshotElement.loadingScreenshot = null;" + matTooltip="Take screenshot" + color="primary"> + <i class="fas fa-camera"></i> + </button> + </div> + <!-- pinned datasets --> <div class="btnWrapper"> @@ -145,3 +158,4 @@ </mat-list-item> </mat-list> </ng-template> +<take-screenshot #takeScreenshotElement style="z-index: 1509;" class="position-fixed fixed-top"></take-screenshot> diff --git a/src/ui/takeScreenshot/takeScreenshot.component.ts b/src/ui/takeScreenshot/takeScreenshot.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e070de583a2b513f8481f5b14d4f5e91c1c04b3e --- /dev/null +++ b/src/ui/takeScreenshot/takeScreenshot.component.ts @@ -0,0 +1,205 @@ +import {Component, ElementRef, HostListener, Inject, Input, OnInit, Renderer2, ViewChild} from "@angular/core"; +import html2canvas from "html2canvas"; +import {DOCUMENT} from "@angular/common"; +import {ESCAPE} from "@angular/cdk/keycodes"; + +@Component({ + selector: 'take-screenshot', + templateUrl: './takeScreenshot.template.html', + styleUrls: ['./takeScreenshot.style.css'] +}) +export class TakeScreenshotComponent implements OnInit { + + @ViewChild('downloadLink', {read: ElementRef}) downloadLink: ElementRef; + @ViewChild('helpBody', {read: ElementRef}) helpBody: ElementRef + @ViewChild('screenshotPreviewCard', {read: ElementRef}) screenshotPreviewCard: ElementRef + takingScreenshot = false + previewingScreenshot = false + loadingScreenshot = false + croppedCanvas = null + + mouseIsDown = false + isDragging = false + tookScreenShot = false // After the mouse is released + // Used to calculate where to start showing the dragging area + startX = 0 + startY = 0 + endX = 0 + endY = 0 + borderWidth = '' + // The box that contains the border and all required numbers. + boxTop = 0 + boxLeft = 0 + boxEndWidth = 0 + boxEndHeight = 0 + windowHeight = 0 + windowWidth = 0 + screenshotStartX = 0 + screenshotStartY = 0 + imageUrl + + constructor(private renderer: Renderer2, @Inject(DOCUMENT) private document: any) { + + } + + ngOnInit(): void { + this.windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth + this.windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight + } + + @HostListener('window:resize', ['$event']) + onResize(event) { + this.windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth + this.windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight + } + + @HostListener('window:keyup', ['$event']) + keyEvent(event: KeyboardEvent) { + if (this.takingScreenshot && event.key === 'Escape') { + this.cancelTakingScreenshot() + } + } + + + move = (e) => { + if (this.mouseIsDown) { + this.isDragging = true + + this.endY = e.clientY + this.endX = e.clientX + + if (this.endX >= this.startX && this.endY >= this.startY) { + // III quadrant + this.borderWidth = this.startY + 'px ' + + (this.windowWidth - this.endX) + 'px ' + + (this.windowHeight - this.endY) + 'px ' + + this.startX + 'px' + this.boxTop = this.startY + this.boxLeft = this.startX + this.boxEndWidth = this.endX - this.startX + this.boxEndHeight = this.endY - this.startY + + this.screenshotStartX = this.startX + this.screenshotStartY = this.startY + + } else if (this.endX <= this.startX && this.endY >= this.startY) { + // IV quadrant + + this.borderWidth = this.startY + 'px ' + + (this.windowWidth - this.startX) + 'px ' + + (this.windowHeight - this.endY) + 'px ' + + this.endX + 'px' + + this.boxLeft = this.endX + this.boxTop = this.startY + this.boxEndWidth = this.startX - this.endX + this.boxEndHeight = this.endY - this.startY + + this.screenshotStartX = this.endX + this.screenshotStartY = this.startY + + } else if (this.endX >= this.startX && this.endY <= this.startY) { + + // II quadrant + + this.borderWidth = this.endY + 'px ' + + (this.windowWidth - this.endX) + 'px ' + + (this.windowHeight - this.startY) + 'px ' + + this.startX + 'px' + + this.boxLeft = this.startX + this.boxTop = this.endY + this.boxEndWidth = this.endX - this.startX + this.boxEndHeight = this.startY - this.endY + + this.screenshotStartX = this.startX + this.screenshotStartY = this.endY + + } else if (this.endX <= this.startX && this.endY <= this.startY) { + // I quadrant + + this.boxLeft = this.endX + this.boxTop = this.endY + this.boxEndWidth = this.startX - this.endX + this.boxEndHeight = this.startY - this.endY + + this.borderWidth = this.endY + 'px ' + + (this.windowWidth - this.startX) + 'px ' + + (this.windowHeight - this.startY) + 'px ' + + this.endX + 'px' + + this.screenshotStartX = this.endX + this.screenshotStartY = this.endY + + } else { + this.isDragging = false + } + + } + } + + mouseDown = (e) => { + this.borderWidth = this.windowWidth + 'px ' + this.windowHeight + 'px' + + this.startX = e.clientX + this.startY = e.clientY + + + this.mouseIsDown = true + this.tookScreenShot = false + } + + mouseUp = (e) => { + this.borderWidth = '0' + + if (this.isDragging) { + // Don't take the screenshot unless the mouse moved somehow. + this.tookScreenShot = true + } + + this.isDragging = false + this.mouseIsDown = false + + this.loadingScreenshot = true + this.takingScreenshot = false + + this.takeScreenshot() + + } + + takeScreenshot() { + html2canvas(this.document.querySelector('#neuroglancer-container canvas')).then(canvas => { + this.croppedCanvas = null + this.croppedCanvas = this.renderer.createElement('canvas') + + this.croppedCanvas.width = this.boxEndWidth * window.devicePixelRatio + this.croppedCanvas.height = this.boxEndHeight * window.devicePixelRatio + + this.croppedCanvas.getContext('2d') + .drawImage(canvas, + this.screenshotStartX * window.devicePixelRatio, this.screenshotStartY * window.devicePixelRatio, + this.boxEndWidth * window.devicePixelRatio, this.boxEndHeight * window.devicePixelRatio, + 0, 0, + this.boxEndWidth * window.devicePixelRatio, this.boxEndHeight * window.devicePixelRatio) + }).then(() => { + this.screenshotPreviewCard.nativeElement.click() + this.loadingScreenshot = false + this.imageUrl = this.croppedCanvas.toDataURL() + this.previewingScreenshot = true + }) + } + + saveImage() { + this.downloadLink.nativeElement.href = this.croppedCanvas.toDataURL('image/png') + this.downloadLink.nativeElement.download = 'marble-diagram.png' + this.downloadLink.nativeElement.click() + } + + cancelTakingScreenshot() { + this.takingScreenshot = false; + this.previewingScreenshot = false; + this.loadingScreenshot = false; + this.croppedCanvas = null; + } + +} \ No newline at end of file diff --git a/src/ui/takeScreenshot/takeScreenshot.style.css b/src/ui/takeScreenshot/takeScreenshot.style.css new file mode 100644 index 0000000000000000000000000000000000000000..fa111504db764c8925a70e9b0c4824d38060f283 --- /dev/null +++ b/src/ui/takeScreenshot/takeScreenshot.style.css @@ -0,0 +1,47 @@ +.overlay, +.tooltip, +.borderedBox { + user-select: none; +} + +.overlay { + background-color: rgba(0, 0, 0, 0.5); +} + +.overlay.highlighting { + background: none; + border-color: rgba(0, 0, 0, 0.5); + border-style: solid; +} + +.screenshotContainer { + clear: both; + background-repeat: no-repeat; + background-size: cover; +} + +.smallSizeWindow { + width: 160px; + height: 40px; +} + +.cancelTimesPosition { + top: 5px; + right: 5px; +} + +.previewScreenshot { + max-width: 400px !important; + max-height: 400px !important; +} + +.previewImage { + max-width:350px !important; + max-height:350px !important; +} + +.screenshotPreviewCard { + bottom: 50px !important; + right: 150px !important; + z-index: 10520 !important; +} diff --git a/src/ui/takeScreenshot/takeScreenshot.template.html b/src/ui/takeScreenshot/takeScreenshot.template.html new file mode 100644 index 0000000000000000000000000000000000000000..d0788f75394b95edcafb031857c4cf7b033bfdfd --- /dev/null +++ b/src/ui/takeScreenshot/takeScreenshot.template.html @@ -0,0 +1,46 @@ +<div id="screenshot" + class="screenshotContainer overflow-hidden w-100 h-100" + (mousemove)="move($event)" + (mousedown)="mouseDown($event)" + (mouseup)="mouseUp($event)" + *ngIf="takingScreenshot" + [ngStyle]="{'cursor':takingScreenshot? 'crosshair' : 'auto'}"> + <div class="overlay position-fixed fixed-top w-100 h-100" [ngClass]="{ 'highlighting' : mouseIsDown }" [ngStyle]="{ borderWidth: borderWidth }"></div> + <div class="position-absolute border border-light" *ngIf="isDragging" [ngStyle]="{ left: boxLeft + 'px', top: boxTop + 'px', width: boxEndWidth + 'px', height: boxEndHeight + 'px' }"></div> + +</div> + +<mat-card #screenshotPreviewCard + class="position-fixed screenshotPreviewCard" + (click)="$event.stopPropagation()" + *ngIf="previewingScreenshot || takingScreenshot || loadingScreenshot"> + <i class="fas fa-times cursorPointer position-absolute cancelTimesPosition" + (click)="cancelTakingScreenshot()"> + </i> + + <div *ngIf="takingScreenshot || loadingScreenshot" class="smallSizeWindow"> + <div class="d-flex flex-column w-100 h-100" *ngIf="takingScreenshot"> + <span>Size: {{boxEndWidth}} x {{boxEndHeight}}px</span> + </div> + <div class="d-flex w-100 h-100 justify-content-center align-items-center" *ngIf="loadingScreenshot"> + <i class="fas fa-spinner fa-pulse"></i> + </div> + </div> + <div *ngIf="previewingScreenshot" class="d-flex flex-column align-items-center previewScreenshot"> + <img [src]="imageUrl" class="previewImage"> + <div class="d-flex w-100 justify-content-end mt-2"> + <button mat-stroked-button color="primary" class="mr-1 ml-1" (click)="saveImage(); previewingScreenshot = false; takingScreenshot = false; croppedCanvas = null;"> + <i class="fas fa-save"></i> Save + </button> + <button mat-stroked-button color="primary" class="mr-1 ml-1" (click)="previewingScreenshot = false; takingScreenshot = true; croppedCanvas = null;"> + <i class="fas fa-camera"></i> Try again + </button> + <button mat-stroked-button color="primary" class="mr-1 ml-1" (click)="cancelTakingScreenshot()"> + <i class="fas fa-times"></i> Cancel + </button> + </div> + </div> + <div id="download" class="d-none"> + <a #downloadLink></a> + </div> +</mat-card> \ No newline at end of file diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts index 8aab1e839546bd7203a3543f1947f4b8b03f05e6..9b8f237729d1bcafe310faab30160358639d16b2 100644 --- a/src/ui/ui.module.ts +++ b/src/ui/ui.module.ts @@ -71,6 +71,7 @@ import { RegionHierarchy } from './viewerStateController/regionHierachy/regionHi import { CurrentlySelectedRegions } from './viewerStateController/regionsListView/currentlySelectedRegions/currentlySelectedRegions.component' import { RegionTextSearchAutocomplete } from "./viewerStateController/regionSearch/regionSearch.component"; import { RegionsListView } from "./viewerStateController/regionsListView/simpleRegionsListView/regionListView.component"; +import {TakeScreenshotComponent} from "src/ui/takeScreenshot/takeScreenshot.component"; @NgModule({ imports : [ @@ -117,6 +118,7 @@ import { RegionsListView } from "./viewerStateController/regionsListView/simpleR SearchSideNav, RegionTextSearchAutocomplete, RegionsListView, + TakeScreenshotComponent, /* pipes */ GroupDatasetByRegion,