Skip to content
Permalink
Browse files
feat(still-image): new still-image viewer (DEV-1150) (#792)
* open image in new tab

* comment

* move replace file function

* download still image

* doc

* adapt test file

* test(still-image): fix tests

* test(still-image): use HttpClientTestingModule instead of HttpClientModule

* style(still-image): round top corners of osd container

Co-authored-by: mdelez <60604010+mdelez@users.noreply.github.com>
  • Loading branch information
Vijeinath and mdelez committed Aug 11, 2022
1 parent bddb52c commit 2eccd8a6afc5bb78e6a9fd8cf4bd3e718cb4c78a
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 20 deletions.
@@ -35,13 +35,19 @@
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #more="matMenu">
<a mat-menu-item [href]="iiifUrl" target="_blank">
Open file in new tab
</a>
<button mat-menu-item (click)="openImageInNewTab(iiifUrl)">
Open IIIF file in new tab
</button>
<button mat-menu-item [cdkCopyToClipboard]="iiifUrl"
(click)="openSnackBar('IIIF URL copied to clipboard!')">
Copy IIIF URL to clipboard
</button>
<button mat-menu-item (click)="downloadStillImage(images[0].fileValue.fileUrl)">
Download file
</button>
<button mat-menu-item (click)="openReplaceFileDialog()">
Replace file
</button>
</mat-menu>
<!-- empty placeholder to simulate two buttons; this helps to have the zoom buttons centered with fill-remaining-space setup -->
<span class="empty-space"></span>
@@ -88,16 +94,12 @@

<span class="fill-remaining-space"></span>

<!-- action buttons: create annotation/region, replace file, fullscreen -->
<!-- action buttons: create annotation/region, fullscreen -->
<span>
<button mat-icon-button id="DSP_OSD_DRAW_REGION" matTooltip="Draw Region" [disabled]="failedToLoad"
(click)="drawButtonClicked()" [class.active]="regionDrawMode">
<mat-icon svgIcon="draw_region_icon"></mat-icon>
</button>
<button mat-icon-button id="DSP_OSD_REPLACE_IMAGE" class="replace-image" matTooltip="Replace image"
(click)="openReplaceFileDialog()">
<mat-icon>cloud_upload</mat-icon>
</button>
<button mat-icon-button id="DSP_OSD_FULL_PAGE" matTooltip="Open in fullscreen" [disabled]="failedToLoad">
<mat-icon>fullscreen</mat-icon>
</button>
@@ -21,6 +21,7 @@
color: $bright;
background-color: $dark;
height: calc(100% - 64px);
border-radius: 8px 8px 0px 0px;

&.drawing {
cursor: crosshair;
@@ -110,6 +111,14 @@
z-index: 1000;
}

/*
Openseadragon styling
*/

::ng-deep .openseadragon-container {
border-radius: 8px 8px 0px 0px;
}

/*
Overlay styling
*/
@@ -2,9 +2,9 @@ import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
import { OverlayContainer } from '@angular/cdk/overlay';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
@@ -124,7 +124,7 @@ describe('StillImageComponent', () => {
let rootLoader: HarnessLoader;
let overlayContainer: OverlayContainer;

beforeEach(waitForAsync(() => {
beforeEach(() => {

const adminSpyObj = {
v2: {
@@ -140,7 +140,7 @@ describe('StillImageComponent', () => {
],
imports: [
BrowserAnimationsModule,
HttpClientModule,
HttpClientTestingModule,
MatDialogModule,
MatIconModule,
MatMenuModule,
@@ -164,7 +164,7 @@ describe('StillImageComponent', () => {
]
})
.compileComponents();
}));
});

beforeEach(() => {
testHostFixture = TestBed.createComponent(TestHostComponent);
@@ -173,9 +173,6 @@ describe('StillImageComponent', () => {

overlayContainer = TestBed.inject(OverlayContainer);
rootLoader = TestbedHarnessEnvironment.documentRootLoader(testHostFixture);
});

it('should create', () => {
expect(testHostComponent).toBeTruthy();
expect(testHostComponent.osdViewerComp).toBeTruthy();
});
@@ -14,6 +14,7 @@ import {
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {
ApiResponseError,
Constants,
@@ -136,6 +137,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit

loading = true;
failedToLoad = false;
originalFilename: string;

regionDrawMode = false; // stores whether viewer is currently drawing a region
private _regionDragInfo; // stores the information of the first click for drawing a region
@@ -144,6 +146,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit

constructor(
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private readonly _http: HttpClient,
private _dialog: MatDialog,
private _domSanitizer: DomSanitizer,
private _elementRef: ElementRef,
@@ -190,6 +193,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
this._setupViewer();
}
if (changes['images']) {
this._getOriginalFilename();

this._openImages();
this._unhighlightAllRegions();
// --> TODO: check if this is necessary or could be handled below
@@ -220,7 +225,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit

/**
* renders all ReadStillImageFileValues to be found in [[this.images]].
* (Although this.images is a Angular Input property, the built-in change detection of Angular does not detect changes in complex objects or arrays, only reassignment of objects/arrays.
* (Although this.images is an Angular Input property, the built-in change detection of Angular does not detect changes in complex objects or arrays, only reassignment of objects/arrays.
* Use this method if additional ReadStillImageFileValues were added to this.images after creation/assignment of the this.images array.)
*/
updateImages() {
@@ -232,7 +237,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit

/**
* renders all regions to be found in [[this.images]].
* (Although this.images is a Angular Input property, the built-in change detection of Angular does not detect changes in complex objects or arrays, only reassignment of objects/arrays.
* (Although this.images is an Angular Input property, the built-in change detection of Angular does not detect changes in complex objects or arrays, only reassignment of objects/arrays.
* Use this method if additional regions were added to the resources.images)
*/
updateRegions() {
@@ -360,6 +365,32 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
this._notification.openSnackBar(message);
}

async downloadStillImage(url: string) {
try {
const res = await this._http.get(url, { responseType: 'blob' }).toPromise();
this.downloadFile(res);
} catch (e) {
this._errorHandler.showMessage(e);
}
}

downloadFile(data) {
const url = window.URL.createObjectURL(data);
const e = document.createElement('a');
e.href = url;

// set filename
if (this.originalFilename === undefined) {
e.download = url.substr(url.lastIndexOf('/') + 1);
} else {
e.download = this.originalFilename;
}

document.body.appendChild(e);
e.click();
document.body.removeChild(e);
}

openReplaceFileDialog() {
const propId = this.parentResource.properties[Constants.HasStillImageFileValue][0].id;

@@ -384,11 +415,31 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
});
}

openImageInNewTab(url: string) {
window.open(url, '_blank');
}

openPage(p: number) {
this.regionDrawMode = false;
this.goToPage.emit(p);
}

private _getOriginalFilename() {
const requestOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
withCredentials: true
};

const index = this.images[0].fileValue.fileUrl.indexOf(this.images[0].fileValue.filename);
const pathToJson = this.images[0].fileValue.fileUrl.substring(0, index + this.images[0].fileValue.filename.length) + '/knora.json';

this._http.get(pathToJson, requestOptions).subscribe(
res => {
this.originalFilename = res['originalFilename'];
}
);
}

private _replaceFile(file: UpdateFileValue) {
const updateRes = new UpdateResource();
updateRes.id = this.parentResource.id;
@@ -403,6 +454,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
this._valueOperationEventService.emit(
new EmitEvent(Events.FileValueUpdated, new UpdatedFileEventValue(
res2.properties[Constants.HasStillImageFileValue][0])));

this._getOriginalFilename();
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
@@ -415,6 +468,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
* @param startPoint the start point of the drawing
* @param endPoint the end point of the drawing
* @param imageSize the image size for calculations
* @param overlay the overlay element that represents the region
*/
private _openRegionDialog(startPoint: Point2D, endPoint: Point2D, imageSize: Point2D, overlay: Element): void {
const dialogConfig: MatDialogConfig = {
@@ -708,7 +762,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit
* @param geometry - the geometry describing the ROI
* @param aspectRatio - the aspectRatio (h/w) of the image on which the geometry should be placed
* @param xOffset - the x-offset in Openseadragon viewport coordinates of the image on which the geometry should be placed
* @param toolTip - the tooltip which should be displayed on mousehover of the svg element
* @param regionLabel - the label of the region
* @param regionComment - the comment of the region
*/
private _createSVGOverlay(regionIri: string, geometry: RegionGeometry, aspectRatio: number, xOffset: number, regionLabel: string, regionComment: string): void {
const lineColor = geometry.lineColor;
@@ -13,7 +13,7 @@
[resourceIri]="incomingResource ? incomingResource.res.id : resource.res.id"
[project]="resource.res.attachedToProject"
[currentTab]="selectedTabLabel"
[parentResource]="resource.res"
[parentResource]="incomingResource ? incomingResource.res : resource.res"
[activateRegion]="selectedRegion"
(loaded)="representationLoaded($event)"
(goToPage)="compoundNavigation($event)"

0 comments on commit 2eccd8a

Please sign in to comment.