Skip to content
Permalink
Browse files
fix(region): highlight info of clicked region (DEV-724) (#703)
* fix(region): highlight info of clicked region (DEV-724)

* refactor(resource): format code

* feat(region): highlight region and display comment with label

* refactor(still-image): remove console.log

* feat(region): edit label, comment and color updates tooltip

* test(region): fix tests and refactor code

* chore(text-value): add comment for DEV-797

* refactor(still-image): organize imports

* refactor(resource): remove console.log

* docs(still-image): add comment
  • Loading branch information
kilchenmann committed Apr 13, 2022
1 parent 374b5a8 commit 2d983696a754871f298e724a45a8ef412f7bd025
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 59 deletions.
@@ -114,7 +114,7 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy {
*/
@Output() referredResourceHovered: EventEmitter<ReadLinkValue> = new EventEmitter<ReadLinkValue>();

@Output() regionColorChanged: EventEmitter<ReadColorValue> = new EventEmitter<ReadColorValue>();
@Output() regionChanged: EventEmitter<ReadValue> = new EventEmitter<ReadValue>();

lastModificationDate: string;

@@ -352,6 +352,10 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy {
(response: UpdateResourceMetadataResponse) => {
this.resource.res.label = payload.label;
this.lastModificationDate = response.lastModificationDate;
// if annotations tab is active; a label of a region has been changed --> update regions
if (this.isAnnotation) {
this.regionChanged.emit();
}
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
@@ -456,8 +460,9 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy {
if (updatedValue instanceof ReadTextValueAsXml) {
this._updateStandoffLinkValue();
}
if (updatedValue instanceof ReadColorValue) {
this.regionColorChanged.emit();
// if annotations tab is active;
if (this.isAnnotation) {
this.regionChanged.emit();
}
} else {
console.warn('No properties exist for this resource');
@@ -1,13 +1,10 @@
@import "../../../../../assets/style/config";
// sizes
$max-width: 800px;
$panelSize: 40px;

$osd-height: 460px;

// colors
$dark: #000;
$bright: #ccc;

:host {
// display: inline-flex;
width: 100%;
@@ -57,6 +54,26 @@ $bright: #ccc;
}
}

.annotation-tooltip {
display: none;
position: fixed;
background-color: $black-60-opacity;
color: $bright;
padding: 8px;
border-radius: $border-radius;
min-height: 24px;
max-height: 258px;
max-width: 256px;
box-sizing: border-box;
transition: 0.1s;
transform: translate(16px, 16px);
font-size: small;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 1000;
}

/*
Overlay styling
*/
@@ -5,9 +5,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { HttpClientModule } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MatButtonHarness } from '@angular/material/button/testing';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { MatIconModule } from '@angular/material/icon';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatToolbarModule } from '@angular/material/toolbar';
@@ -107,13 +105,12 @@ class TestHostComponent implements OnInit {
this.readResource = res;
});

this.stillImageFileRepresentations
= [
new FileRepresentation(stillImageFileValue,
[
new Region(makeRegion([rectangleGeom], 'first'))
])
];
this.stillImageFileRepresentations = [
new FileRepresentation(stillImageFileValue,
[new Region(makeRegion([rectangleGeom], 'first'))]
)
];

}

regHovered(regIri: string) {
@@ -1,13 +1,11 @@
import {
Component,
ElementRef,
EventEmitter,
Inject,
EventEmitter, Inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
OnDestroy, Output,
Renderer2,
SimpleChanges
} from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
@@ -17,7 +15,6 @@ import {
ApiResponseError,
Constants,
CreateColorValue,
CreateFileValue,
CreateGeomValue,
CreateLinkValue,
CreateResource,
@@ -104,7 +101,7 @@ export class GeometryForRegion {
*/
interface PolygonsForRegion {

[key: string]: HTMLDivElement[];
[key: string]: HTMLElement[];

}

@@ -143,6 +140,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
private _errorHandler: ErrorHandlerService,
private _matIconRegistry: MatIconRegistry,
private _notification: NotificationService,
private _renderer: Renderer2,
private _valueOperationEventService: ValueOperationEventService
) {
OpenSeadragon.setString('Tooltips.Home', '');
@@ -185,17 +183,15 @@ export class StillImageComponent implements OnChanges, OnDestroy {
this._unhighlightAllRegions();
// tODO: check if this is necessary or could be handled below
// (remove the 'else' before the 'if', so changes['activateRegion'] is always checked for)
if (this.activateRegion !== undefined) {
this._highlightRegion(this.activateRegion);
}
if (this.currentTab === 'annotations') {
this.renderRegions();
}
} else if (changes['activateRegion']) {
}
if (this.activateRegion !== undefined) {
this._highlightRegion(this.activateRegion);
}
if (this.currentTab === 'annotations') {
this.renderRegions();
}
if (changes['activateRegion']) {
this._unhighlightAllRegions();
if (this.activateRegion !== undefined) {
this._highlightRegion(this.activateRegion);
}
}
}

@@ -310,11 +306,13 @@ export class StillImageComponent implements OnChanges, OnDestroy {
geometry.lineColor = colorValues[0].color;
}

this._createSVGOverlay(geom.region.id, geometry, aspectRatio, imageXOffset, geom.region.label);
const commentValue = (geom.region.properties[Constants.HasComment] ? geom.region.properties[Constants.HasComment][0].strval : '');

this._createSVGOverlay(geom.region.id, geometry, aspectRatio, imageXOffset, geom.region.label, commentValue);

imageXOffset++;
}

imageXOffset++;
}

}
@@ -326,7 +324,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
for (const reg in this._regions) {
if (this._regions.hasOwnProperty(reg)) {
for (const pol of this._regions[reg]) {
if (pol instanceof HTMLDivElement) {
if (pol instanceof HTMLElement) {
pol.remove();
}
}
@@ -345,7 +343,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
this._notification.openSnackBar(message);
}

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

const dialogConfig: MatDialogConfig = {
@@ -354,7 +352,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
position: {
top: '112px'
},
data: { mode: 'replaceFile', title: '2D Image (Still Image)', subtitle: 'Update image of the resource' , representation: 'stillImage', id: propId },
data: { mode: 'replaceFile', title: '2D Image (Still Image)', subtitle: 'Update image of the resource', representation: 'stillImage', id: propId },
disableClose: true
};
const dialogRef = this._dialog.open(
@@ -476,7 +474,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
if (!this.regionDrawMode) {
return;
}
const overlayElement = document.createElement('div');
const overlayElement = this._renderer.createElement('div');
overlayElement.style.background = 'rgba(255,0,0,0.3)';
const viewportPos = this._viewer.viewport.pointFromPixel((event as OpenSeadragon.ViewerEvent).position);
this._viewer.addOverlay(overlayElement, new OpenSeadragon.Rect(viewportPos.x, viewportPos.y, 0, 0));
@@ -523,7 +521,7 @@ export class StillImageComponent implements OnChanges, OnDestroy {
*/
private _highlightRegion(regionIri) {

const activeRegion: HTMLDivElement[] = this._regions[regionIri];
const activeRegion: HTMLElement[] = this._regions[regionIri];

if (activeRegion !== undefined) {
for (const pol of activeRegion) {
@@ -658,19 +656,14 @@ export class StillImageComponent implements OnChanges, OnDestroy {
* @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
*/
private _createSVGOverlay(regionIri: string, geometry: RegionGeometry, aspectRatio: number, xOffset: number, toolTip: string): void {
private _createSVGOverlay(regionIri: string, geometry: RegionGeometry, aspectRatio: number, xOffset: number, regionLabel: string, regionComment: string): void {
const lineColor = geometry.lineColor;
const lineWidth = geometry.lineWidth;

const elt = document.createElement('div');
elt.id = 'region-overlay-' + Math.random() * 10000;
elt.className = 'region';
elt.title = toolTip;
elt.setAttribute('style', 'outline: solid ' + lineColor + ' ' + lineWidth + 'px;');

elt.addEventListener('click', (event: MouseEvent) => {
this.regionClicked.emit(regionIri);
}, false);
const regEle: HTMLElement = this._renderer.createElement('div');
regEle.id = 'region-overlay-' + Math.random() * 10000;
regEle.className = 'region';
regEle.setAttribute('style', 'outline: solid ' + lineColor + ' ' + lineWidth + 'px;');

const diffX = geometry.points[1].x - geometry.points[0].x;
const diffY = geometry.points[1].y - geometry.points[0].y;
@@ -684,11 +677,36 @@ export class StillImageComponent implements OnChanges, OnDestroy {
loc.y = loc.y * aspectRatio;

this._viewer.addOverlay({
element: elt,
location: loc
element: regEle,
location: loc,
});

// mouse tracker has to be activated on the open seadragon viewer
// solution from: https://github.com/openseadragon/openseadragon/issues/1419#issuecomment-371564878
const tracker = new OpenSeadragon.MouseTracker({
element: regEle,
clickHandler: function (event) { }
});

this._regions[regionIri].push(regEle);

const comEle: HTMLElement = this._renderer.createElement('div');
comEle.className = 'annotation-tooltip';
comEle.innerHTML = `<strong>${regionLabel}</strong><br>${regionComment}`;
regEle.append(comEle);

regEle.addEventListener('mousemove', (event: MouseEvent) => {
comEle.setAttribute('style', 'display: block; left: ' + event.clientX + 'px; top: ' + event.clientY + 'px');
});
regEle.addEventListener('mouseleave', (event: MouseEvent) => {
comEle.setAttribute('style', 'display: none');
});
regEle.addEventListener('click', (event: MouseEvent) => {
this.regionClicked.emit(regionIri);
});

this._regions[regionIri].push(elt);
}



}
@@ -63,8 +63,14 @@
</ng-template>
<div class="region-property" *ngFor="let annotation of annotationResources" [id]="annotation.res.id"
[class.active]="annotation.res.id === selectedRegion">
<app-properties [resource]="annotation" [displayProjectInfo]="false" [isAnnotation]="true"
[adminPermissions]="adminPermissions" [editPermissions]="editPermissions" [valueUuidToHighlight]="valueUuid" (regionColorChanged)="updateRegionColor()">
<app-properties
[resource]="annotation"
[displayProjectInfo]="false"
[isAnnotation]="true"
[adminPermissions]="adminPermissions"
[editPermissions]="editPermissions"
[valueUuidToHighlight]="valueUuid"
(regionChanged)="updateRegion()">
</app-properties>
</div>

@@ -238,7 +238,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy {

} else {
// if there is no incomingResource and the resource has a still image property, assign the iiiUrl to be passed as an input to the still-image component
if (!this.incomingResource && this.resource.res.properties[Constants.HasStillImageFileValue]){
if (!this.incomingResource && this.resource.res.properties[Constants.HasStillImageFileValue]) {
this.iiifUrl = (this.resource.res.properties[Constants.HasStillImageFileValue][0] as ReadStillImageFileValue).fileUrl;
}

@@ -304,7 +304,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy {
this.incomingResource = res;

// if the resource is a still image, assign the iiiUrl to be passed as an input to the still-image component
if (this.incomingResource.res.properties[Constants.HasStillImageFileValue]){
if (this.incomingResource.res.properties[Constants.HasStillImageFileValue]) {
this.iiifUrl = (this.incomingResource.res.properties[Constants.HasStillImageFileValue][0] as ReadStillImageFileValue).fileUrl;
}

@@ -430,6 +430,13 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy {
representations.push(stillImage);

this.annotationResources = annotations;

// developer feature: this keeps the annotations tab open, if you add "/annotations" to the end of the URL
// e.g. http://0.0.0.0:4200/resource/[project-shortcode]/[resource-iri]/annotations
if (this.valueUuid === 'annotations') {
this.selectedTab = (this.incomingResource ? 2 : 1);
this.selectedTabLabel = 'annotations';
}
}

} else if (resource.res.properties[Constants.HasDocumentFileValue]) {
@@ -584,7 +591,10 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy {
// and scroll to region with this id
const region = document.getElementById(iri);
if (region) {
region.scrollIntoView();
region.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}

}
@@ -599,7 +609,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy {
this.openRegion(iri);
}

updateRegionColor(){
updateRegion() {
if (this.stillImageComponent !== undefined) {
this.stillImageComponent.updateRegions();
}
@@ -1,4 +1,5 @@
<span *ngIf="mode === 'read'; else showForm" class="read-mode-view">
<!-- TODO in DEV-797: appLinkify pipe should only be used if property's gui-element is not richt-text; I had the same issue in regions -->
<span class="rm-value text-value" [innerHtml]="valueFormControl.value | appLinkify" style="white-space: pre-wrap;"></span>
<span class="rm-comment" *ngIf="shouldShowComment">{{commentFormControl.value}}</span>
</span>
@@ -761,6 +761,14 @@ $gc-small: $form-width - $gc-large - 4;
font-size: 11px;
}

.annotation-tooltip {

p {
white-space: normal;
text-overflow: ellipsis;
}
}

// --------------------------------------

//

0 comments on commit 2d98369

Please sign in to comment.